diff --git a/AUTHORS b/AUTHORS
index e714607d..97f5e95c 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -709,6 +709,7 @@
 Marc des Garets <marc.desgarets@googlemail.com>
 Marcin Wiacek <marcin@mwiacek.com>
 Marco Rodrigues <gothicx@gmail.com>
+Marcos Caceres <marcos@marcosc.com>
 Mariam Ali <alimariam@noogler.google.com>
 Mario Pistrich <m.pistrich@gmail.com>
 Mario Sanchez Prada <mario.prada@samsung.com>
diff --git a/DEPS b/DEPS
index fb1d3bb..38186db7 100644
--- a/DEPS
+++ b/DEPS
@@ -235,7 +235,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': '87a0078b890928828c08790bb2c846724234edcb',
+  'skia_revision': '7e54e3083fba9b0f07536c155e91eab6dcc84f91',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
@@ -243,7 +243,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ANGLE
   # and whatever else without interference from each other.
-  'angle_revision': 'bcc8970347f1a51b650e4a79f5e4ddec11047e79',
+  'angle_revision': '43be4d9cd8e461e9c26fb28bb8f2f26b65aea75c',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -282,7 +282,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling freetype
   # and whatever else without interference from each other.
-  'freetype_revision': 'e294a95ca85f4d1aa2cd1a6e00e572acd7f03871',
+  'freetype_revision': 'ede96b239b90bf9c9d9a01f06005ae09fb4fa19b',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling freetype
   # and whatever else without interference from each other.
@@ -310,7 +310,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling devtools-frontend
   # and whatever else without interference from each other.
-  'devtools_frontend_revision': '0404c7edc03c2ded9787ba7d00f821414ce4285a',
+  'devtools_frontend_revision': '7008714d9a82c52dddf836fabf22dd17469c7328',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libprotobuf-mutator
   # and whatever else without interference from each other.
@@ -350,7 +350,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'dawn_revision': 'a4c8c8d5bffb66900d5bc76b143ce956f4fa6a13',
+  'dawn_revision': 'd174cef8fa6b677efd4d6ba71babebea41e5282b',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -1637,7 +1637,7 @@
     Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + 'e42f534de7afea5942ecae5993d9211e258918aa',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + '339965f93cdaaee297fe0764f1765de0ae1bd9f3',
+    Var('webrtc_git') + '/src.git' + '@' + '3cef26662aec5f992eb08491f19d1179ce685d5a',
 
   'src/third_party/libgifcodec':
      Var('skia_git') + '/libgifcodec' + '@'+  Var('libgifcodec_revision'),
@@ -1695,7 +1695,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@646a59e1f51fa8237c7104a6c7805d3d7581d021',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@26afe693b62d30de8c9836b9c2e90760b5589ca7',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/android_webview/BUILD.gn b/android_webview/BUILD.gn
index c49937a4..9789e3f6 100644
--- a/android_webview/BUILD.gn
+++ b/android_webview/BUILD.gn
@@ -39,7 +39,7 @@
   system_webview_apk_or_module_tmpl(target_name) {
     forward_variables_from(invoker, "*")
     deps = upstream_only_webview_deps
-    min_sdk_version = 21
+    min_sdk_version = default_min_sdk_version
     if (android_64bit_target_cpu && defined(include_32_bit_webview) &&
         !include_32_bit_webview) {
       android_manifest = system_webview_64_android_manifest
@@ -108,7 +108,7 @@
     system_webview_bundle("system_webview_bundle") {
       base_module_target = ":system_webview_base_bundle_module"
       bundle_name = "SystemWebView"
-      min_sdk_version = 21
+      min_sdk_version = default_min_sdk_version
       compress_shared_libraries = true
     }
 
@@ -145,7 +145,7 @@
     system_webview_bundle("system_webview_64_bundle") {
       base_module_target = ":system_webview_64_base_bundle_module"
       bundle_name = "SystemWebView64"
-      min_sdk_version = 21
+      min_sdk_version = default_min_sdk_version
       compress_shared_libraries = true
       include_32_bit_webview = false
     }
@@ -172,7 +172,7 @@
         base_module_target = ":system_webview_32_base_bundle_module"
         bundle_name = "SystemWebView32"
         include_64_bit_webview = false
-        min_sdk_version = 21
+        min_sdk_version = default_min_sdk_version
         compress_shared_libraries = true
       }
     }
diff --git a/android_webview/test/BUILD.gn b/android_webview/test/BUILD.gn
index 5849f86..6e209783 100644
--- a/android_webview/test/BUILD.gn
+++ b/android_webview/test/BUILD.gn
@@ -211,7 +211,7 @@
   apk_name = "WebViewInstrumentationTest"
   apk_under_test = ":webview_instrumentation_apk"
   android_manifest = "../javatests/AndroidManifest.xml"
-  min_sdk_version = 21
+  min_sdk_version = default_min_sdk_version
 
   deps = [
     ":webview_instrumentation_test_mock_services_java",
diff --git a/ash/app_list/model/search/search_result.h b/ash/app_list/model/search/search_result.h
index 8104b5b9..6504a99 100644
--- a/ash/app_list/model/search/search_result.h
+++ b/ash/app_list/model/search/search_result.h
@@ -110,6 +110,9 @@
   Category category() const { return metadata_->category; }
   void set_category(Category category) { metadata_->category = category; }
 
+  bool best_match() const { return metadata_->best_match; }
+  void set_best_match(bool best_match) { metadata_->best_match = best_match; }
+
   DisplayType display_type() const { return metadata_->display_type; }
   void set_display_type(DisplayType display_type) {
     metadata_->display_type = display_type;
diff --git a/ash/capture_mode/capture_mode_api.cc b/ash/capture_mode/capture_mode_api.cc
index 8a4f34f..b16ba24 100644
--- a/ash/capture_mode/capture_mode_api.cc
+++ b/ash/capture_mode/capture_mode_api.cc
@@ -12,8 +12,4 @@
   CaptureModeController::Get()->CaptureScreenshotsOfAllDisplays();
 }
 
-bool IsCaptureModeSessionActive() {
-  return CaptureModeController::Get()->IsActive();
-}
-
 }  // namespace ash
diff --git a/ash/capture_mode/capture_mode_session.h b/ash/capture_mode/capture_mode_session.h
index 684c237..0044d0b 100644
--- a/ash/capture_mode/capture_mode_session.h
+++ b/ash/capture_mode/capture_mode_session.h
@@ -172,6 +172,7 @@
   friend class CaptureModeAdvancedSettingsTestApi;
   friend class CaptureModeSessionFocusCycler;
   friend class CaptureModeSessionTestApi;
+  friend class CaptureModeTestApi;
   class CursorSetter;
   class ScopedA11yOverrideWindowSetter;
 
diff --git a/ash/capture_mode/capture_mode_test_api.cc b/ash/capture_mode/capture_mode_test_api.cc
index 0e36e77..cffe594 100644
--- a/ash/capture_mode/capture_mode_test_api.cc
+++ b/ash/capture_mode/capture_mode_test_api.cc
@@ -6,10 +6,12 @@
 
 #include "ash/capture_mode/capture_mode_controller.h"
 #include "ash/capture_mode/capture_mode_metrics.h"
+#include "ash/capture_mode/capture_mode_session.h"
 #include "ash/capture_mode/capture_mode_types.h"
 #include "ash/capture_mode/video_recording_watcher.h"
 #include "base/auto_reset.h"
 #include "base/check.h"
+#include "base/run_loop.h"
 
 namespace ash {
 
@@ -92,6 +94,32 @@
       .get();
 }
 
+void CaptureModeTestApi::SimulateOpeningFolderSelectionDialog() {
+  DCHECK(controller_->IsActive());
+  auto* session = controller_->capture_mode_session();
+  DCHECK(!session->capture_mode_settings_widget_);
+  session->SetSettingsMenuShown(true);
+  DCHECK(session->capture_mode_settings_widget_);
+  session->OpenFolderSelectionDialog();
+
+  // In browser tests, the dialog creation is asynchronous, so we'll need to
+  // wait for it.
+  if (GetFolderSelectionDialogWindow())
+    return;
+
+  base::RunLoop loop;
+  session->folder_selection_dialog_controller_
+      ->on_dialog_window_added_callback_for_test_ = loop.QuitClosure();
+  loop.Run();
+}
+
+aura::Window* CaptureModeTestApi::GetFolderSelectionDialogWindow() {
+  DCHECK(controller_->IsActive());
+  auto* session = controller_->capture_mode_session();
+  auto* dialog_controller = session->folder_selection_dialog_controller_.get();
+  return dialog_controller ? dialog_controller->dialog_window() : nullptr;
+}
+
 void CaptureModeTestApi::SetType(bool for_video) {
   controller_->SetType(for_video ? CaptureModeType::kVideo
                                  : CaptureModeType::kImage);
diff --git a/ash/capture_mode/folder_selection_dialog_controller.cc b/ash/capture_mode/folder_selection_dialog_controller.cc
index 2a0271641..8c34520 100644
--- a/ash/capture_mode/folder_selection_dialog_controller.cc
+++ b/ash/capture_mode/folder_selection_dialog_controller.cc
@@ -59,13 +59,14 @@
   DCHECK(root);
   DCHECK(root->IsRootWindow());
 
-  window_observation_.Observe(wm::TransientWindowManager::GetOrCreate(
-      dialog_background_dimmer_.window()));
+  auto* owner = dialog_background_dimmer_.window();
+  owner->SetId(kShellWindowId_CaptureModeFolderSelectionDialogOwner);
+  window_observation_.Observe(wm::TransientWindowManager::GetOrCreate(owner));
 
   dialog_background_dimmer_.SetDimColor(
       AshColorProvider::Get()->GetShieldLayerColor(
           AshColorProvider::ShieldLayerType::kShield40));
-  dialog_background_dimmer_.window()->Show();
+  owner->Show();
 
   select_folder_dialog_->SelectFile(
       ui::SelectFileDialog::SELECT_FOLDER,
@@ -74,7 +75,7 @@
       /*file_types=*/nullptr,
       /*file_type_index=*/0,
       /*default_extension=*/base::FilePath::StringType(),
-      /*owning_window=*/dialog_background_dimmer_.window(),
+      /*owning_window=*/owner,
       /*params=*/nullptr);
 }
 
@@ -104,7 +105,6 @@
   DCHECK(!dialog_window_);
 
   dialog_window_ = transient;
-  dialog_window_->SetId(kShellWindowId_CaptureModeFolderSelectionDialog);
 
   // The dialog should never resize, minimize or maximize.
   auto* widget = views::Widget::GetWidgetForNativeWindow(dialog_window_);
@@ -114,6 +114,9 @@
   widget_delegate->SetCanResize(false);
   widget_delegate->SetCanMinimize(false);
   widget_delegate->SetCanMaximize(false);
+
+  if (on_dialog_window_added_callback_for_test_)
+    std::move(on_dialog_window_added_callback_for_test_).Run();
 }
 
 void FolderSelectionDialogController::OnTransientChildRemoved(
diff --git a/ash/capture_mode/folder_selection_dialog_controller.h b/ash/capture_mode/folder_selection_dialog_controller.h
index 5b7bd83..9aef395 100644
--- a/ash/capture_mode/folder_selection_dialog_controller.h
+++ b/ash/capture_mode/folder_selection_dialog_controller.h
@@ -6,6 +6,7 @@
 #define ASH_CAPTURE_MODE_FOLDER_SELECTION_DIALOG_CONTROLLER_H_
 
 #include "ash/wm/window_dimmer.h"
+#include "base/callback_forward.h"
 #include "base/files/file_path.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/scoped_observation.h"
@@ -67,6 +68,8 @@
                                aura::Window* transient) override;
 
  private:
+  friend class CaptureModeTestApi;
+
   Delegate* const delegate_;
 
   // Dims everything behind the dialog (including the capture bar, the settings
@@ -81,6 +84,9 @@
   // |select_folder_dialog_| as a transient child of the dimming window.
   aura::Window* dialog_window_ = nullptr;
 
+  // An optional callback that will be invoked when |dialog_window_| gets added.
+  base::OnceClosure on_dialog_window_added_callback_for_test_;
+
   // We observe the transient window manager of the dimming window to know when
   // the dialog window is added or removed.
   base::ScopedObservation<wm::TransientWindowManager,
diff --git a/ash/public/cpp/app_list/app_list_types.h b/ash/public/cpp/app_list/app_list_types.h
index 8c9afd5..c9996e5 100644
--- a/ash/public/cpp/app_list/app_list_types.h
+++ b/ash/public/cpp/app_list/app_list_types.h
@@ -188,19 +188,21 @@
 // to be displayed in the search box should be associated with one category. It
 // is an error for results displayed in the search box to have a kUnknown
 // category, but results displayed in other views - eg. the Continue section -
-// may use kUnknown. These values are not yet stable, and should not yet be used
-// for metrics.
+// may use kUnknown.
+//
+// These values are persisted to logs. Entries should not be renumbered and
+// numeric values should never be reused.
 enum class AppListSearchResultCategory {
   kUnknown = 0,
-  kBestMatch = 1,
-  kApp = 2,
+  kApps = 1,
+  kAppShortcuts = 2,
   kWeb = 3,
   kFiles = 4,
-  kAssistant = 5,
-  kSettings = 6,
-  kHelp = 7,
-  kPlayStore = 8,
-  kMaxValue = kPlayStore,
+  kSettings = 5,
+  kHelp = 6,
+  kPlayStore = 7,
+  kSearchAndAssistant = 8,
+  kMaxValue = kSearchAndAssistant,
 };
 
 // Which UI container(s) the result should be displayed in.
@@ -369,6 +371,10 @@
   // section.
   AppListSearchResultCategory category = AppListSearchResultCategory::kUnknown;
 
+  // Whether this result is a top match and should be shown in the Top Matches
+  // section instead of its category.
+  bool best_match = false;
+
   // The type of this result.
   AppListSearchResultType result_type = AppListSearchResultType::kUnknown;
 
diff --git a/ash/public/cpp/capture_mode/capture_mode_api.h b/ash/public/cpp/capture_mode/capture_mode_api.h
index 0f41aef..2d399c8 100644
--- a/ash/public/cpp/capture_mode/capture_mode_api.h
+++ b/ash/public/cpp/capture_mode/capture_mode_api.h
@@ -14,9 +14,6 @@
 // Note: this won't start a capture mode session.
 void ASH_EXPORT CaptureScreenshotsOfAllDisplays();
 
-// Returns true if a capture mode session is currently active.
-bool ASH_EXPORT IsCaptureModeSessionActive();
-
 }  // namespace ash
 
 #endif  // ASH_PUBLIC_CPP_CAPTURE_MODE_CAPTURE_MODE_API_H_
diff --git a/ash/public/cpp/capture_mode/capture_mode_test_api.h b/ash/public/cpp/capture_mode/capture_mode_test_api.h
index 782dd967..8a4f603f 100644
--- a/ash/public/cpp/capture_mode/capture_mode_test_api.h
+++ b/ash/public/cpp/capture_mode/capture_mode_test_api.h
@@ -9,6 +9,10 @@
 #include "base/callback_forward.h"
 #include "base/files/file_path.h"
 
+namespace aura {
+class Window;
+}  // namespace aura
+
 namespace gfx {
 class Rect;
 }  // namespace gfx
@@ -72,6 +76,14 @@
   // Can only be called while recording is in progress for a Projector session.
   RecordingOverlayController* GetRecordingOverlayController();
 
+  // Simulates the flow taken by users to open the folder selection dialog from
+  // the settings menu, and waits until this dialog gets added.
+  void SimulateOpeningFolderSelectionDialog();
+
+  // Returns a pointer to the folder selection dialog window or nullptr if no
+  // such window exists.
+  aura::Window* GetFolderSelectionDialogWindow();
+
  private:
   // Sets the capture mode type to a video capture if |for_video| is true, or
   // image capture otherwise.
diff --git a/ash/public/cpp/shell_window_ids.h b/ash/public/cpp/shell_window_ids.h
index 99bbe640..e37a610 100644
--- a/ash/public/cpp/shell_window_ids.h
+++ b/ash/public/cpp/shell_window_ids.h
@@ -202,11 +202,12 @@
   // multiple displays connected.
   kShellWindowId_DisplayIdentificationHighlightWindow,
 
-  // The window hosting the folder selection menu for capture mode, which is not
-  // parented to a top-level container, yet it needs to be uniquely identified
-  // so that we can allow a WindowState to be created for it, since it can be
-  // dragged around.
-  kShellWindowId_CaptureModeFolderSelectionDialog,
+  // The window specified as the owner of the folder selection menu for capture
+  // mode, which will be a transient window parent of the about to be created
+  // dialog window. This is needed in order to prevent
+  // |SelectFileDialogExtension| from favoring to parent the dialog to a browser
+  // window (if one exists).
+  kShellWindowId_CaptureModeFolderSelectionDialogOwner,
 };
 
 // A list of system modal container IDs. The order of the list is important that
diff --git a/ash/system/network/OWNERS b/ash/system/network/OWNERS
index 7632e21..cb2f47e 100644
--- a/ash/system/network/OWNERS
+++ b/ash/system/network/OWNERS
@@ -1,4 +1,5 @@
 azeemarshad@chromium.org
+gordonseto@google.com
 jonmann@chromium.org
 khorimoto@chromium.org
 stevenjb@chromium.org
diff --git a/ash/system/status_area_widget_delegate.cc b/ash/system/status_area_widget_delegate.cc
index 3130cbf..459eaee 100644
--- a/ash/system/status_area_widget_delegate.cc
+++ b/ash/system/status_area_widget_delegate.cc
@@ -178,6 +178,7 @@
 }
 
 void StatusAreaWidgetDelegate::CalculateTargetBounds() {
+  // Prevents creating new layout manager when adding the tray buttons.
   if (is_adding_tray_buttons_)
     return;
   // Use a grid layout so that the trays can be centered in each cell, and
@@ -239,6 +240,10 @@
 }
 
 void StatusAreaWidgetDelegate::ChildPreferredSizeChanged(View* child) {
+  // Prevents resizing and layout row and column change when adding the tray
+  // buttons.
+  if (is_adding_tray_buttons_)
+    return;
   const gfx::Size current_size = size();
   const gfx::Size new_size = GetPreferredSize();
   if (new_size == current_size)
diff --git a/ash/system/time/calendar_month_view.h b/ash/system/time/calendar_month_view.h
index f07cfd0a..e21ca1a8 100644
--- a/ash/system/time/calendar_month_view.h
+++ b/ash/system/time/calendar_month_view.h
@@ -73,7 +73,7 @@
       int column_set_id,
       bool is_in_current_month);
 
-  // Owned by `UnifiedCalendarViewController`.
+  // Owned by `CalendarView`.
   CalendarViewController* const calendar_view_controller_;
 
   // If today's cell is in this view.
diff --git a/ash/system/time/calendar_view.cc b/ash/system/time/calendar_view.cc
index a70c1b6b..e2be34c 100644
--- a/ash/system/time/calendar_view.cc
+++ b/ash/system/time/calendar_view.cc
@@ -185,11 +185,10 @@
 };
 
 CalendarView::CalendarView(DetailedViewDelegate* delegate,
-                           UnifiedSystemTrayController* controller,
-                           CalendarViewController* calendar_view_controller)
+                           UnifiedSystemTrayController* controller)
     : TrayDetailedView(delegate),
       controller_(controller),
-      calendar_view_controller_(calendar_view_controller) {
+      calendar_view_controller_(std::make_unique<CalendarViewController>()) {
   CreateTitleRow(IDS_ASH_CALENDAR_TITLE);
 
   // Add the header.
@@ -259,7 +258,8 @@
   SetMonthViews();
 
   scoped_scroll_view_observer_.Observe(scroll_view_);
-  scoped_calendar_view_controller_observer_.Observe(calendar_view_controller_);
+  scoped_calendar_view_controller_observer_.Observe(
+      calendar_view_controller_.get());
   scoped_view_observer_.AddObservation(scroll_view_);
   scoped_view_observer_.AddObservation(content_view_);
 }
@@ -443,8 +443,8 @@
 }
 
 views::View* CalendarView::AddLabelWithId(LabelType type, bool add_at_front) {
-  auto label =
-      std::make_unique<MonthYearHeaderView>(type, calendar_view_controller_);
+  auto label = std::make_unique<MonthYearHeaderView>(
+      type, calendar_view_controller_.get());
   if (add_at_front)
     return content_view_->AddChildViewAt(std::move(label), 0);
   return content_view_->AddChildView(std::move(label));
@@ -452,8 +452,8 @@
 
 CalendarMonthView* CalendarView::AddMonth(base::Time month_first_date,
                                           bool add_at_front) {
-  auto month = std::make_unique<CalendarMonthView>(month_first_date,
-                                                   calendar_view_controller_);
+  auto month = std::make_unique<CalendarMonthView>(
+      month_first_date, calendar_view_controller_.get());
   month->SetBorder(views::CreateEmptyBorder(kMonthVerticalPadding, 0,
                                             kMonthVerticalPadding, 0));
   if (add_at_front) {
diff --git a/ash/system/time/calendar_view.h b/ash/system/time/calendar_view.h
index a4744c69..60bf891 100644
--- a/ash/system/time/calendar_view.h
+++ b/ash/system/time/calendar_view.h
@@ -33,8 +33,7 @@
   METADATA_HEADER(CalendarView);
 
   CalendarView(DetailedViewDelegate* delegate,
-               UnifiedSystemTrayController* controller,
-               CalendarViewController* calendar_view_controller);
+               UnifiedSystemTrayController* controller);
   CalendarView(const CalendarView& other) = delete;
   CalendarView& operator=(const CalendarView& other) = delete;
   ~CalendarView() override;
@@ -58,6 +57,10 @@
   views::Button* CreateInfoButton(views::Button::PressedCallback callback,
                                   int info_accessible_name_id) override;
 
+  CalendarViewController* calendar_view_controller() {
+    return calendar_view_controller_.get();
+  }
+
  private:
   // The header of each month view which shows the month's name. If the year of
   // this month is not the same as the current month, the year is also shown in
@@ -117,8 +120,7 @@
   // Unowned.
   UnifiedSystemTrayController* controller_;
 
-  // Owned by `UnifiedCalendarViewController`.
-  CalendarViewController* const calendar_view_controller_;
+  std::unique_ptr<CalendarViewController> calendar_view_controller_;
 
   // The content of the `scroll_view_`, which carries months and month labels.
   // Owned by `CalendarView`.
diff --git a/ash/system/time/calendar_view_unittest.cc b/ash/system/time/calendar_view_unittest.cc
index 6509a18..5889a83 100644
--- a/ash/system/time/calendar_view_unittest.cc
+++ b/ash/system/time/calendar_view_unittest.cc
@@ -25,7 +25,6 @@
   void SetUp() override {
     AshTestBase::SetUp();
 
-    controller_ = std::make_unique<CalendarViewController>();
     delegate_ =
         std::make_unique<DetailedViewDelegate>(/*tray_controller=*/nullptr);
     tray_model_ = std::make_unique<UnifiedSystemTrayModel>(/*shelf=*/nullptr);
@@ -35,7 +34,6 @@
 
   void TearDown() override {
     calendar_view_.reset();
-    controller_.reset();
     delegate_.reset();
     tray_controller_.reset();
     tray_model_.reset();
@@ -43,11 +41,10 @@
     AshTestBase::TearDown();
   }
 
-  void CreateCalendarView(base::Time date) {
+  void CreateCalendarView() {
     calendar_view_.reset();
-    controller_->UpdateMonth(date);
-    calendar_view_ = std::make_unique<CalendarView>(
-        delegate_.get(), tray_controller_.get(), controller_.get());
+    calendar_view_ =
+        std::make_unique<CalendarView>(delegate_.get(), tray_controller_.get());
     // TODO(https://crbug.com/1236276): remove calling `Layout()` once we can
     // pop up the view from the tray. (https://crbug.com/1254491) And add tests
     // for focusing behaviors.
@@ -121,7 +118,14 @@
 TEST_F(CalendarViewTest, Init) {
   base::Time date;
   ASSERT_TRUE(base::Time::FromString("24 Aug 2021 10:00 GMT", &date));
-  CreateCalendarView(date);
+
+  // Set time override.
+  SetFakeNow(date);
+  base::subtle::ScopedTimeClockOverrides time_override(
+      &CalendarViewTest::FakeTimeNow, /*time_ticks_override=*/nullptr,
+      /*thread_ticks_override=*/nullptr);
+
+  CreateCalendarView();
 
   EXPECT_EQ(u"July", GetPreviousLabelText());
   EXPECT_EQ(u"August", GetCurrentLabelText());
@@ -138,11 +142,20 @@
   EXPECT_EQ(
       u"29",
       static_cast<views::LabelButton*>(next_month()->children()[0])->GetText());
+}
 
-  // Test corner cases of the `CalendarView`.
+// Test the init view of the `CalendarView` starting with December.
+TEST_F(CalendarViewTest, InitDec) {
   base::Time dec_date;
   ASSERT_TRUE(base::Time::FromString("24 Dec 2021 10:00 GMT", &dec_date));
-  CreateCalendarView(dec_date);
+
+  // Set time override.
+  SetFakeNow(dec_date);
+  base::subtle::ScopedTimeClockOverrides time_override(
+      &CalendarViewTest::FakeTimeNow, /*time_ticks_override=*/nullptr,
+      /*thread_ticks_override=*/nullptr);
+
+  CreateCalendarView();
 
   EXPECT_EQ(u"November", GetPreviousLabelText());
   EXPECT_EQ(u"December", GetCurrentLabelText());
@@ -165,7 +178,13 @@
   base::Time date;
   ASSERT_TRUE(base::Time::FromString("24 Oct 2021 10:00 GMT", &date));
 
-  CreateCalendarView(date);
+  // Set time override.
+  SetFakeNow(date);
+  base::subtle::ScopedTimeClockOverrides time_override(
+      &CalendarViewTest::FakeTimeNow, /*time_ticks_override=*/nullptr,
+      /*thread_ticks_override=*/nullptr);
+
+  CreateCalendarView();
 
   EXPECT_EQ(u"September", GetPreviousLabelText());
   EXPECT_EQ(u"October", GetCurrentLabelText());
@@ -212,7 +231,7 @@
       &CalendarViewTest::FakeTimeNow, /*time_ticks_override=*/nullptr,
       /*thread_ticks_override=*/nullptr);
 
-  CreateCalendarView(date);
+  CreateCalendarView();
 
   EXPECT_EQ(u"September", GetPreviousLabelText());
   EXPECT_EQ(u"October", GetCurrentLabelText());
diff --git a/ash/system/time/unified_calendar_view_controller.cc b/ash/system/time/unified_calendar_view_controller.cc
index ae7360ff..aa464f5e 100644
--- a/ash/system/time/unified_calendar_view_controller.cc
+++ b/ash/system/time/unified_calendar_view_controller.cc
@@ -16,23 +16,24 @@
     UnifiedSystemTrayController* tray_controller)
     : detailed_view_delegate_(
           std::make_unique<DetailedViewDelegate>(tray_controller)),
-      calendar_view_controller_(std::make_unique<CalendarViewController>()),
       tray_controller_(tray_controller) {}
 
-UnifiedCalendarViewController::~UnifiedCalendarViewController() {}
+UnifiedCalendarViewController::~UnifiedCalendarViewController() = default;
 
 views::View* UnifiedCalendarViewController::CreateView() {
   DCHECK(!view_);
-  view_ = new CalendarView(detailed_view_delegate_.get(), tray_controller_,
-                           calendar_view_controller_.get());
+  view_ = new CalendarView(detailed_view_delegate_.get(), tray_controller_);
   return view_;
 }
 
 std::u16string UnifiedCalendarViewController::GetAccessibleName() const {
+  // Shows `Now()` as the initial time if calendar view is not created yet.
+  base::Time current_time =
+      view_ ? view_->calendar_view_controller()->current_date()
+            : base::Time::Now();
   return l10n_util::GetStringFUTF16(
       IDS_ASH_CALENDAR_BUBBLE_ACCESSIBLE_DESCRIPTION,
-      base::TimeFormatWithPattern(calendar_view_controller_->current_date(),
-                                  "MMMM yyyy"));
+      base::TimeFormatWithPattern(current_time, "MMMM yyyy"));
 }
 
 }  // namespace ash
diff --git a/ash/system/time/unified_calendar_view_controller.h b/ash/system/time/unified_calendar_view_controller.h
index d40d8ff..1355ac3 100644
--- a/ash/system/time/unified_calendar_view_controller.h
+++ b/ash/system/time/unified_calendar_view_controller.h
@@ -13,7 +13,6 @@
 class DetailedViewDelegate;
 class UnifiedSystemTrayController;
 class CalendarView;
-class CalendarViewController;
 
 // Controller of `CalendarView` in UnifiedSystemTray.
 class UnifiedCalendarViewController : public DetailedViewController {
@@ -32,7 +31,6 @@
 
  private:
   const std::unique_ptr<DetailedViewDelegate> detailed_view_delegate_;
-  std::unique_ptr<CalendarViewController> const calendar_view_controller_;
 
   // Unowned, the object that instantiated us.
   UnifiedSystemTrayController* const tray_controller_;
diff --git a/ash/webui/diagnostics_ui/backend/network_health_provider.cc b/ash/webui/diagnostics_ui/backend/network_health_provider.cc
index fddecaa..e4ade11 100644
--- a/ash/webui/diagnostics_ui/backend/network_health_provider.cc
+++ b/ash/webui/diagnostics_ui/backend/network_health_provider.cc
@@ -475,7 +475,6 @@
 
 void NetworkHealthProvider::OnDeviceStateListReceived(
     std::vector<network_mojom::DeviceStatePropertiesPtr> devices) {
-  bool list_changed = false;
   base::flat_set<std::string> networks_seen;
 
   // Iterate all devices. If the device is already known, then update it's
@@ -506,7 +505,6 @@
     if (!matched) {
       std::string observer_guid = AddNewNetwork(device);
       networks_seen.insert(std::move(observer_guid));
-      list_changed = true;
     }
   }
 
@@ -515,7 +513,6 @@
     const std::string& observer_guid = it->first;
     if (!base::Contains(networks_seen, observer_guid)) {
       it = networks_.erase(it);
-      list_changed = true;
       continue;
     }
 
diff --git a/ash/webui/shortcut_customization_ui/resources/BUILD.gn b/ash/webui/shortcut_customization_ui/resources/BUILD.gn
index a7108a9..57d6e677 100644
--- a/ash/webui/shortcut_customization_ui/resources/BUILD.gn
+++ b/ash/webui/shortcut_customization_ui/resources/BUILD.gn
@@ -76,8 +76,10 @@
 
 js_library("accelerator_edit_view") {
   deps = [
+    ":accelerator_lookup_manager",
     ":accelerator_view",
     ":icons",
+    ":mojo_interface_provider",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
     "//ui/webui/resources/cr_elements/cr_button:cr_button.m",
   ]
diff --git a/ash/webui/shortcut_customization_ui/resources/accelerator_edit_view.js b/ash/webui/shortcut_customization_ui/resources/accelerator_edit_view.js
index b01c5b4..9f8392ed2 100644
--- a/ash/webui/shortcut_customization_ui/resources/accelerator_edit_view.js
+++ b/ash/webui/shortcut_customization_ui/resources/accelerator_edit_view.js
@@ -11,8 +11,10 @@
 
 import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
+import {AcceleratorLookupManager} from './accelerator_lookup_manager.js';
 import {ViewState} from './accelerator_view.js'
-import {AcceleratorInfo, AcceleratorKeys, AcceleratorSource, AcceleratorState, AcceleratorType} from './shortcut_types.js';
+import {getShortcutProvider} from './mojo_interface_provider.js'
+import {AcceleratorConfigResult, AcceleratorInfo, AcceleratorKeys, AcceleratorSource, AcceleratorState, AcceleratorType, ShortcutProviderInterface} from './shortcut_types.js';
 
 /**
  * @fileoverview
@@ -91,6 +93,17 @@
     }
   }
 
+  /** @override */
+  constructor() {
+    super();
+
+    /** @private {!ShortcutProviderInterface} */
+    this.shortcutProvider_ = getShortcutProvider();
+
+    /** @private {!AcceleratorLookupManager} */
+    this.lookupManager_ = AcceleratorLookupManager.getInstance();
+  }
+
   /** @protected */
   onStatusMessageChanged_() {
     if (this.statusMessage === '') {
@@ -107,7 +120,21 @@
 
   /** @protected */
   onDeleteButtonClicked_() {
-    // TODO(jimmyxgong): Implement this function
+    this.shortcutProvider_
+        .removeAccelerator(
+            this.source, this.action, this.acceleratorInfo.accelerator)
+        .then((result) => {
+          if (result === AcceleratorConfigResult.kSuccess) {
+            this.lookupManager_.removeAccelerator(
+                this.source, this.action, this.acceleratorInfo.accelerator);
+
+            this.dispatchEvent(new CustomEvent('request-update-accelerator', {
+              bubbles: true,
+              composed: true,
+              detail: {source: this.source, action: this.action}
+            }));
+          }
+        });
   }
 
   /** @protected  */
diff --git a/ash/wm/window_state.cc b/ash/wm/window_state.cc
index 5bc2abf..df3cefd 100644
--- a/ash/wm/window_state.cc
+++ b/ash/wm/window_state.cc
@@ -971,12 +971,8 @@
   // WindowState is only for windows in top level container, unless they are
   // temporarily hidden when launched by window restore. The will be reparented
   // to a top level container soon, and need a WindowState.
-  // The window of capture mode folder selection dialog is also special. It's
-  // not parented to a top level container, but it needs a window state since it
-  // can be dragged around.
   if (!IsToplevelContainer(window->parent()) &&
-      !IsTemporarilyHiddenForFullrestore(window) &&
-      window->GetId() != kShellWindowId_CaptureModeFolderSelectionDialog) {
+      !IsTemporarilyHiddenForFullrestore(window)) {
     return nullptr;
   }
 
diff --git a/build/fuchsia/device_target.py b/build/fuchsia/device_target.py
index c9d5ab2f..92643bf7 100644
--- a/build/fuchsia/device_target.py
+++ b/build/fuchsia/device_target.py
@@ -254,3 +254,12 @@
     self.RunCommandPiped('dm reboot')
     time.sleep(_REBOOT_SLEEP_PERIOD)
     self.Start()
+
+  def Stop(self):
+    try:
+      super(DeviceTarget, self).Stop()
+    finally:
+      # End multiplexed ssh connection, ensure that ssh logging stops before
+      # tests/scripts return.
+      if self.IsStarted():
+        self.RunCommand(['-O', 'exit'])
diff --git a/cc/layers/picture_layer_unittest.cc b/cc/layers/picture_layer_unittest.cc
index d1f8dca..473269e 100644
--- a/cc/layers/picture_layer_unittest.cc
+++ b/cc/layers/picture_layer_unittest.cc
@@ -53,6 +53,7 @@
 
   EXPECT_EQ(0, host->SourceFrameNumber());
   host->WillCommit(nullptr);
+  EXPECT_EQ(1, host->SourceFrameNumber());
   host->CommitComplete();
   EXPECT_EQ(1, host->SourceFrameNumber());
 
diff --git a/cc/trees/layer_tree_host.cc b/cc/trees/layer_tree_host.cc
index f7e4f32..57ef1655 100644
--- a/cc/trees/layer_tree_host.cc
+++ b/cc/trees/layer_tree_host.cc
@@ -511,6 +511,7 @@
   commit_completion_event_ = std::move(completion);
   swap_promise_manager_.WillCommit();
   client_->WillCommit();
+  source_frame_number_++;
 }
 
 void LayerTreeHost::WaitForCommitCompletion() {
@@ -533,7 +534,6 @@
   // This DCHECK ensures that WaitForCommitCompletion() will not block.
   DCHECK(!in_commit());
   WaitForCommitCompletion();
-  source_frame_number_++;
   client_->DidCommit(impl_commit_start_time_, impl_commit_finish_time_);
   impl_commit_start_time_ = impl_commit_finish_time_ = base::TimeTicks();
   if (did_complete_scale_animation_) {
diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
index c096695..20e37bb 100644
--- a/chrome/android/BUILD.gn
+++ b/chrome/android/BUILD.gn
@@ -112,7 +112,7 @@
   output = chrome_public_android_manifest
   variables = chrome_public_jinja_variables
   variables += [
-    "min_sdk_version=21",
+    "min_sdk_version=$default_min_sdk_version",
     "target_sdk_version=$android_sdk_version",
   ]
 }
@@ -2806,7 +2806,7 @@
   variables = default_chrome_public_jinja_variables
   variables += [
     "manifest_package=$chrome_public_test_manifest_package",
-    "min_sdk_version=21",
+    "min_sdk_version=$default_min_sdk_version",
     "target_sdk_version=$android_sdk_version",
   ]
 }
@@ -2818,7 +2818,7 @@
   variables = default_chrome_public_jinja_variables
   variables += [
     "manifest_package=$chrome_public_test_manifest_package",
-    "min_sdk_version=21",
+    "min_sdk_version=$default_min_sdk_version",
     "target_sdk_version=$android_sdk_version",
   ]
 }
@@ -2829,7 +2829,7 @@
   output = chrome_public_test_vr_apk_manifest
   variables = chrome_public_jinja_variables
   variables += [
-    "min_sdk_version=21",
+    "min_sdk_version=$default_min_sdk_version",
     "target_sdk_version=$android_sdk_version",
   ]
 }
@@ -3367,7 +3367,7 @@
   }
   is_monochrome_or_trichrome = false
   manifest_package = chrome_public_manifest_package
-  min_sdk_version = 21
+  min_sdk_version = default_min_sdk_version
   module_descs = chrome_modern_module_descs
   version_code = chrome_modern_version_code
 
diff --git a/chrome/android/aapt2.config b/chrome/android/aapt2.config
index 6cfe45eb..ea85a3e 100644
--- a/chrome/android/aapt2.config
+++ b/chrome/android/aapt2.config
@@ -1,2 +1,3 @@
+drawable/ic_launcher#no_collapse
 drawable/ic_launcher_round#no_collapse
 drawable/shortcut_incognito#no_collapse
diff --git a/chrome/android/chrome_public_apk_tmpl.gni b/chrome/android/chrome_public_apk_tmpl.gni
index 8f483b15..f25c7aa5 100644
--- a/chrome/android/chrome_public_apk_tmpl.gni
+++ b/chrome/android/chrome_public_apk_tmpl.gni
@@ -139,7 +139,7 @@
     } else if (_is_monochrome) {
       min_sdk_version = 24
     } else {
-      min_sdk_version = 21
+      min_sdk_version = default_min_sdk_version
     }
 
     resource_exclusion_regex = common_resource_exclusion_regex
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkFeatures.java b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkFeatures.java
index 542dde8..bf7c448 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkFeatures.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkFeatures.java
@@ -18,9 +18,30 @@
  * {@code false}</li>
  * <li>{@code bookmark_visuals_enabled}: boolean; refresh the visual looks of the bookmarks
  * manager. Default: {@code false}</li>
+ * <li>{@code bookmarks_refresh_min_version}: boolean; see {@link #VERSION}.</li>
  * </ul>
  */
 public class BookmarkFeatures {
+    /**
+     * An indicator of the code version specific to bookmarks and the reading list. It allows
+     * feature flags to be individually enabled without changing the {@code min_version} for the
+     * whole study.
+     *
+     * <p>Feature flag params using the {@code VERSION} value:
+     * <ul>
+     * <li>{@code bookmarks_improved_save_flow_min_version} - {@link
+     * ChromeFeatureList#BOOKMARKS_IMPROVED_SAVE_FLOW}
+     * <li>{@code bookmarks_refresh_min_version} - {@link ChromeFeatureList#BOOKMARKS_REFRESH}
+     * <li>{@code read_later_min_version} - {@link ChromeFeatureList#READ_LATER}
+     * </ul>
+     *
+     * <p>These parameters allow to control for cases where a significant bug fix or change of param
+     * semantics was merged into release, and prior versions of the code are not to be enabled. In
+     * such case the patch increments the {@code VERSION}, while experiment sets {@code
+     * *_min_version} to set a lower bound on existing implementation with the fix.
+     */
+    static final int VERSION = 0;
+
     private static final boolean BOOKMARK_VISUALS_ENABLED_DEFAULT = false;
     private static final boolean IMPROVED_SAVE_FLOW_AUTODISMISS_ENABLED_DEFAULT = true;
     // This is the same as the default dismiss time for snackbars.
@@ -32,7 +53,11 @@
 
     public static boolean isImprovedSaveFlowEnabled() {
         return FeatureList.isInitialized()
-                && ChromeFeatureList.isEnabled(ChromeFeatureList.BOOKMARKS_IMPROVED_SAVE_FLOW);
+                && ChromeFeatureList.isEnabled(ChromeFeatureList.BOOKMARKS_IMPROVED_SAVE_FLOW)
+                && ChromeFeatureList.getFieldTrialParamByFeatureAsInt(
+                           ChromeFeatureList.BOOKMARKS_IMPROVED_SAVE_FLOW,
+                           "bookmarks_improved_save_flow_min_version", 0)
+                <= VERSION;
     }
 
     public static boolean isImprovedSaveFlowAutodismissEnabled() {
@@ -53,7 +78,10 @@
 
     public static boolean isBookmarksRefreshEnabled() {
         return FeatureList.isInitialized()
-                && ChromeFeatureList.isEnabled(ChromeFeatureList.BOOKMARKS_REFRESH);
+                && ChromeFeatureList.isEnabled(ChromeFeatureList.BOOKMARKS_REFRESH)
+                && ChromeFeatureList.getFieldTrialParamByFeatureAsInt(
+                           ChromeFeatureList.BOOKMARKS_REFRESH, "bookmarks_refresh_min_version", 0)
+                <= VERSION;
     }
 
     public static boolean isBookmarksVisualRefreshEnabled() {
@@ -74,4 +102,4 @@
                 && ChromeFeatureList.getFieldTrialParamByFeatureAsBoolean(
                         ChromeFeatureList.BOOKMARKS_REFRESH, "edit_bookmark_in_app_menu", false);
     }
-}
\ No newline at end of file
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/ReadingListFeatures.java b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/ReadingListFeatures.java
index d871f213..c72c0b9 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/ReadingListFeatures.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/ReadingListFeatures.java
@@ -24,16 +24,22 @@
  * discards the "last bookmark location". Default: {@link #DEFAULT_SESSION_LENGTH_SECONDS}</li>
  * <li>{@code use_root_bookmark_as_default}: boolean; use the root folder rather than "Mobile
  * bookmarks" as the default bookmark folder. Default: {@code false}</li>
+ * <li>{@code read_later_min_version}: boolean; see {@link BookmarkFeatures#VERSION}.</li>
  * </ul>
  */
 public class ReadingListFeatures {
+    /** @see BookmarkFeatures#VERSION */
+    static final int VERSION = BookmarkFeatures.VERSION;
     private static final int DEFAULT_SESSION_LENGTH_SECONDS = (int) TimeUnit.HOURS.toSeconds(1);
 
     private ReadingListFeatures() {}
 
     public static boolean isReadingListEnabled() {
         return FeatureList.isInitialized()
-                && ChromeFeatureList.isEnabled(ChromeFeatureList.READ_LATER);
+                && ChromeFeatureList.isEnabled(ChromeFeatureList.READ_LATER)
+                && ChromeFeatureList.getFieldTrialParamByFeatureAsInt(
+                           ChromeFeatureList.READ_LATER, "read_later_min_version", 0)
+                <= VERSION;
     }
 
     /** Returns whether the root folder should be used as the default location. */
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesCategoryCardView.java b/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesCategoryCardView.java
index 95915ff..365fe3b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesCategoryCardView.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesCategoryCardView.java
@@ -141,7 +141,7 @@
 
         @Override
         public boolean isItemSupported(@ContextMenuManager.ContextMenuItemId int menuItemId) {
-            return menuItemId != ContextMenuManager.ContextMenuItemId.LEARN_MORE;
+            return true;
         }
 
         @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/firstrun/FirstRunFlowSequencer.java b/chrome/android/java/src/org/chromium/chrome/browser/firstrun/FirstRunFlowSequencer.java
index d6c5b9a..4d8aa3a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/firstrun/FirstRunFlowSequencer.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/firstrun/FirstRunFlowSequencer.java
@@ -60,22 +60,22 @@
     public static class FirstRunFlowSequencerDelegate {
         /** @return true if the sync consent promo page should be shown. */
         boolean shouldShowSyncConsentPage(Activity activity, List<Account> accounts) {
-            // We show the sync consent page if sync is allowed, and not signed in, and
-            // - "skip the first use hints" is not set, or
-            // - "skip the first use hints" is set, but there is at least one account.
             final IdentityManager identityManager =
                     IdentityServicesProvider.get().getIdentityManager(
                             Profile.getLastUsedRegularProfile());
-            final boolean isSignedInWithSync = identityManager.hasPrimaryAccount(ConsentLevel.SYNC);
-            final boolean shouldShowSyncConsentPreMICe = isSyncAllowed() && !isSignedInWithSync
-                    && (!shouldSkipFirstUseHints(activity) || !accounts.isEmpty());
-
+            if (identityManager.hasPrimaryAccount(ConsentLevel.SYNC) || !isSyncAllowed()) {
+                // No need to show the sync consent page if users already consented to sync or
+                // if sync is not allowed.
+                return false;
+            }
             if (FREMobileIdentityConsistencyFieldTrial.isEnabled()) {
-                final boolean isSignedInWithoutSync =
-                        identityManager.hasPrimaryAccount(ConsentLevel.SIGNIN);
-                return shouldShowSyncConsentPreMICe && isSignedInWithoutSync;
+                // Show the sync consent page only to the signed-in users.
+                return identityManager.hasPrimaryAccount(ConsentLevel.SIGNIN);
             } else {
-                return shouldShowSyncConsentPreMICe;
+                // We show the sync consent page if sync is allowed, and not signed in, and
+                // - "skip the first use hints" is not set, or
+                // - "skip the first use hints" is set, but there is at least one account.
+                return !shouldSkipFirstUseHints(activity) || !accounts.isEmpty();
             }
         }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/native_page/ContextMenuManager.java b/chrome/android/java/src/org/chromium/chrome/browser/native_page/ContextMenuManager.java
index 0c05176a..ad1f07d 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/native_page/ContextMenuManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/native_page/ContextMenuManager.java
@@ -36,8 +36,7 @@
     @IntDef({ContextMenuItemId.SEARCH, ContextMenuItemId.OPEN_IN_NEW_TAB,
             ContextMenuItemId.OPEN_IN_NEW_TAB_IN_GROUP, ContextMenuItemId.OPEN_IN_INCOGNITO_TAB,
             ContextMenuItemId.OPEN_IN_NEW_WINDOW, ContextMenuItemId.SAVE_FOR_OFFLINE,
-            ContextMenuItemId.ADD_TO_MY_APPS, ContextMenuItemId.REMOVE,
-            ContextMenuItemId.LEARN_MORE})
+            ContextMenuItemId.ADD_TO_MY_APPS, ContextMenuItemId.REMOVE})
     @Retention(RetentionPolicy.SOURCE)
     public @interface ContextMenuItemId {
         // The order of the items will be based on the value of their ID. So if new items are added,
@@ -51,9 +50,8 @@
         int SAVE_FOR_OFFLINE = 5;
         int ADD_TO_MY_APPS = 6;
         int REMOVE = 7;
-        int LEARN_MORE = 8;
 
-        int NUM_ENTRIES = 9;
+        int NUM_ENTRIES = 8;
     }
 
     private final NativePageNavigationDelegate mNavigationDelegate;
@@ -273,8 +271,6 @@
             }
             case ContextMenuItemId.REMOVE:
                 return true;
-            case ContextMenuItemId.LEARN_MORE:
-                return true;
             case ContextMenuItemId.ADD_TO_MY_APPS:
                 return false;
             default:
@@ -305,8 +301,6 @@
                 return R.string.contextmenu_save_link;
             case ContextMenuItemId.REMOVE:
                 return R.string.remove;
-            case ContextMenuItemId.LEARN_MORE:
-                return R.string.learn_more;
         }
         assert false;
         return 0;
@@ -344,10 +338,6 @@
                 delegate.removeItem();
                 RecordUserAction.record(mUserActionPrefix + ".ContextMenu.RemoveItem");
                 return true;
-            case ContextMenuItemId.LEARN_MORE:
-                mNavigationDelegate.navigateToHelpPage();
-                RecordUserAction.record(mUserActionPrefix + ".ContextMenu.LearnMore");
-                return true;
             default:
                 return false;
         }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/SuggestionsConfig.java b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/SuggestionsConfig.java
index 9c778918..c106812 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/SuggestionsConfig.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/SuggestionsConfig.java
@@ -12,7 +12,6 @@
 import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
-import org.chromium.chrome.browser.util.ChromeAccessibilityUtil;
 import org.chromium.components.browser_ui.widget.displaystyle.UiConfig;
 
 import java.lang.annotation.Retention;
@@ -45,16 +44,6 @@
     private SuggestionsConfig() {}
 
     /**
-     * @return Whether scrolling to the bottom of suggestions triggers a load.
-     */
-    public static boolean scrollToLoad() {
-        // The scroll to load feature does not work well for users who require accessibility mode.
-        if (ChromeAccessibilityUtil.get().isAccessibilityEnabled()) return false;
-
-        return ChromeFeatureList.isEnabled(ChromeFeatureList.CONTENT_SUGGESTIONS_SCROLL_TO_LOAD);
-    }
-
-    /**
      * @param resources The resources to fetch the color from.
      * @return The background color for the suggestions sheet content.
      */
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/tile/TileGroup.java b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/tile/TileGroup.java
index 035d34f..88901d6 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/tile/TileGroup.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/tile/TileGroup.java
@@ -25,7 +25,6 @@
 import org.chromium.chrome.browser.offlinepages.OfflinePageItem;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.suggestions.SiteSuggestion;
-import org.chromium.chrome.browser.suggestions.SuggestionsConfig;
 import org.chromium.chrome.browser.suggestions.SuggestionsMetrics;
 import org.chromium.chrome.browser.suggestions.SuggestionsOfflineModelObserver;
 import org.chromium.chrome.browser.suggestions.SuggestionsUiDelegate;
@@ -598,8 +597,6 @@
                 case ContextMenuItemId.REMOVE:
                     return mSuggestion.sectionType == TileSectionType.PERSONALIZED
                             && mSuggestion.source != TileSource.EXPLORE;
-                case ContextMenuItemId.LEARN_MORE:
-                    return SuggestionsConfig.scrollToLoad();
                 case ContextMenuItemId.OPEN_IN_INCOGNITO_TAB:
                     return mSuggestion.source != TileSource.EXPLORE;
                 default:
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkIntentDataProviderFactory.java b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkIntentDataProviderFactory.java
index 8fdee79..ae77de13 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkIntentDataProviderFactory.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkIntentDataProviderFactory.java
@@ -47,6 +47,7 @@
 import org.chromium.components.webapps.ShortcutSource;
 import org.chromium.components.webapps.WebApkDistributor;
 import org.chromium.device.mojom.ScreenOrientationLockType;
+import org.chromium.ui.util.ColorUtils;
 import org.chromium.webapk.lib.common.WebApkCommonUtils;
 import org.chromium.webapk.lib.common.WebApkMetaDataUtils;
 import org.chromium.webapk.lib.common.splash.SplashLayout;
@@ -262,11 +263,10 @@
                 IntentUtils.safeGetString(bundle, WebApkMetaDataKeys.DISPLAY_MODE));
         int orientation = orientationFromString(
                 IntentUtils.safeGetString(bundle, WebApkMetaDataKeys.ORIENTATION));
-        long themeColor = WebApkMetaDataUtils.getLongFromMetaData(bundle,
-                WebApkMetaDataKeys.THEME_COLOR, WebappConstants.MANIFEST_COLOR_INVALID_OR_MISSING);
-        long backgroundColor =
-                WebApkMetaDataUtils.getLongFromMetaData(bundle, WebApkMetaDataKeys.BACKGROUND_COLOR,
-                        WebappConstants.MANIFEST_COLOR_INVALID_OR_MISSING);
+        long themeColor = WebApkMetaDataUtils.getLongFromMetaData(
+                bundle, WebApkMetaDataKeys.THEME_COLOR, ColorUtils.INVALID_COLOR);
+        long backgroundColor = WebApkMetaDataUtils.getLongFromMetaData(
+                bundle, WebApkMetaDataKeys.BACKGROUND_COLOR, ColorUtils.INVALID_COLOR);
 
         // Fetch the default background color from the WebAPK's resources. Fetching the default
         // background color from the WebAPK is important for consistency when:
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappDataStorage.java b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappDataStorage.java
index 0ad0e93..3ee3a7a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappDataStorage.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappDataStorage.java
@@ -28,6 +28,7 @@
 import org.chromium.components.webapk.lib.common.WebApkConstants;
 import org.chromium.components.webapps.ShortcutSource;
 import org.chromium.device.mojom.ScreenOrientationLockType;
+import org.chromium.ui.util.ColorUtils;
 
 import java.io.File;
 
@@ -226,10 +227,8 @@
                 mPreferences.getString(KEY_ICON, null), version,
                 mPreferences.getInt(KEY_DISPLAY_MODE, DisplayMode.STANDALONE),
                 mPreferences.getInt(KEY_ORIENTATION, ScreenOrientationLockType.DEFAULT),
-                mPreferences.getLong(
-                        KEY_THEME_COLOR, WebappConstants.MANIFEST_COLOR_INVALID_OR_MISSING),
-                mPreferences.getLong(
-                        KEY_BACKGROUND_COLOR, WebappConstants.MANIFEST_COLOR_INVALID_OR_MISSING),
+                mPreferences.getLong(KEY_THEME_COLOR, ColorUtils.INVALID_COLOR),
+                mPreferences.getLong(KEY_BACKGROUND_COLOR, ColorUtils.INVALID_COLOR),
                 mPreferences.getBoolean(KEY_IS_ICON_GENERATED, false),
                 mPreferences.getBoolean(KEY_IS_ICON_ADAPTIVE, false));
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappIntentDataProviderFactory.java b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappIntentDataProviderFactory.java
index d9ac55d5..bfcf55f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappIntentDataProviderFactory.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappIntentDataProviderFactory.java
@@ -19,6 +19,7 @@
 import org.chromium.chrome.browser.browserservices.intents.WebappIntentUtils;
 import org.chromium.components.webapps.ShortcutSource;
 import org.chromium.device.mojom.ScreenOrientationLockType;
+import org.chromium.ui.util.ColorUtils;
 import org.chromium.webapk.lib.common.splash.SplashLayout;
 
 /**
@@ -66,8 +67,8 @@
             return null;
         }
 
-        long themeColor = IntentUtils.safeGetLongExtra(intent, WebappConstants.EXTRA_THEME_COLOR,
-                WebappConstants.MANIFEST_COLOR_INVALID_OR_MISSING);
+        long themeColor = IntentUtils.safeGetLongExtra(
+                intent, WebappConstants.EXTRA_THEME_COLOR, ColorUtils.INVALID_COLOR);
         boolean hasValidToolbarColor = WebappIntentUtils.isLongColorValid(themeColor);
         int toolbarColor = hasValidToolbarColor ? (int) themeColor
                                                 : WebappIntentDataProvider.getDefaultToolbarColor();
@@ -85,9 +86,8 @@
         int orientation = IntentUtils.safeGetIntExtra(
                 intent, WebappConstants.EXTRA_ORIENTATION, ScreenOrientationLockType.DEFAULT);
         int source = sourceFromIntent(intent);
-        Integer backgroundColor = WebappIntentUtils.colorFromLongColor(
-                IntentUtils.safeGetLongExtra(intent, WebappConstants.EXTRA_BACKGROUND_COLOR,
-                        WebappConstants.MANIFEST_COLOR_INVALID_OR_MISSING));
+        Integer backgroundColor = WebappIntentUtils.colorFromLongColor(IntentUtils.safeGetLongExtra(
+                intent, WebappConstants.EXTRA_BACKGROUND_COLOR, ColorUtils.INVALID_COLOR));
         boolean isIconGenerated = IntentUtils.safeGetBooleanExtra(
                 intent, WebappConstants.EXTRA_IS_ICON_GENERATED, false);
         boolean isIconAdaptive = IntentUtils.safeGetBooleanExtra(
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkPersonalizedSigninPromoDismissTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkPersonalizedSigninPromoDismissTest.java
index 96fdb7ec..f9a9e40a 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkPersonalizedSigninPromoDismissTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkPersonalizedSigninPromoDismissTest.java
@@ -21,6 +21,7 @@
 import androidx.test.filters.MediumTest;
 
 import org.junit.After;
+import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -32,6 +33,7 @@
 import org.chromium.chrome.browser.ChromeTabbedActivity;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
+import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
 import org.chromium.chrome.browser.preferences.SharedPreferencesManager;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.signin.ui.SigninPromoController;
@@ -82,6 +84,8 @@
                 SigninPromoController.getPromoShowCountPreferenceName(
                         SigninAccessPoint.BOOKMARK_MANAGER));
         SigninPromoController.setPrefSigninPromoDeclinedBookmarksForTests(false);
+        SharedPreferencesManager.getInstance().removeKey(
+                ChromePreferenceKeys.SYNC_PROMO_TOTAL_SHOW_COUNT);
     }
 
     @Test
@@ -111,6 +115,9 @@
     @Test
     @MediumTest
     public void testPromoImpressionCountIncrementAfterDisplayingSigninPromo() {
+        Assert.assertEquals(0,
+                SharedPreferencesManager.getInstance().readInt(
+                        ChromePreferenceKeys.SYNC_PROMO_TOTAL_SHOW_COUNT));
         assertEquals(0,
                 SharedPreferencesManager.getInstance().readInt(
                         SigninPromoController.getPromoShowCountPreferenceName(
@@ -121,6 +128,9 @@
                 SharedPreferencesManager.getInstance().readInt(
                         SigninPromoController.getPromoShowCountPreferenceName(
                                 SigninAccessPoint.BOOKMARK_MANAGER)));
+        Assert.assertEquals(1,
+                SharedPreferencesManager.getInstance().readInt(
+                        ChromePreferenceKeys.SYNC_PROMO_TOTAL_SHOW_COUNT));
     }
 
     private void closeBookmarkManager() {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/ntp/RecentTabsPageTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/ntp/RecentTabsPageTest.java
index 6cd5b31..b4f4f1b2 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/ntp/RecentTabsPageTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/ntp/RecentTabsPageTest.java
@@ -23,6 +23,8 @@
 import org.chromium.base.test.util.CriteriaHelper;
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
+import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
+import org.chromium.chrome.browser.preferences.SharedPreferencesManager;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
@@ -74,6 +76,8 @@
     public void tearDown() {
         leaveRecentTabsPage();
         RecentTabsManager.setRecentlyClosedTabManagerForTests(null);
+        SharedPreferencesManager.getInstance().removeKey(
+                ChromePreferenceKeys.SYNC_PROMO_TOTAL_SHOW_COUNT);
     }
 
     @Test
@@ -98,19 +102,31 @@
     @LargeTest
     @Feature("RenderTest")
     public void testPersonalizedSigninPromoInRecentTabsPage() throws Exception {
+        Assert.assertEquals(0,
+                SharedPreferencesManager.getInstance().readInt(
+                        ChromePreferenceKeys.SYNC_PROMO_TOTAL_SHOW_COUNT));
         mAccountManagerTestRule.addAccountWithNameAndAvatar(
                 AccountManagerTestRule.TEST_ACCOUNT_EMAIL);
         mPage = loadRecentTabsPage();
         mRenderTestRule.render(mPage.getView(), "personalized_signin_promo_recent_tabs_page");
+        Assert.assertEquals(1,
+                SharedPreferencesManager.getInstance().readInt(
+                        ChromePreferenceKeys.SYNC_PROMO_TOTAL_SHOW_COUNT));
     }
 
     @Test
     @LargeTest
     @Feature("RenderTest")
     public void testPersonalizedSyncPromoInRecentTabsPage() throws Exception {
+        Assert.assertEquals(0,
+                SharedPreferencesManager.getInstance().readInt(
+                        ChromePreferenceKeys.SYNC_PROMO_TOTAL_SHOW_COUNT));
         mAccountManagerTestRule.addTestAccountThenSignin();
         mPage = loadRecentTabsPage();
         mRenderTestRule.render(mPage.getView(), "personalized_sync_promo_recent_tabs_page");
+        Assert.assertEquals(1,
+                SharedPreferencesManager.getInstance().readInt(
+                        ChromePreferenceKeys.SYNC_PROMO_TOTAL_SHOW_COUNT));
     }
 
     /**
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/settings/MainSettingsFragmentTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/settings/MainSettingsFragmentTest.java
index a5ea548..924cf0e 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/settings/MainSettingsFragmentTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/settings/MainSettingsFragmentTest.java
@@ -64,6 +64,7 @@
 import org.chromium.chrome.browser.password_check.PasswordCheck;
 import org.chromium.chrome.browser.password_check.PasswordCheckFactory;
 import org.chromium.chrome.browser.password_manager.settings.PasswordSettings;
+import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
 import org.chromium.chrome.browser.preferences.SharedPreferencesManager;
 import org.chromium.chrome.browser.privacy.settings.PrivacySettings;
 import org.chromium.chrome.browser.safety_check.SafetyCheckSettingsFragment;
@@ -157,6 +158,8 @@
         }
         SharedPreferencesManager.getInstance().removeKey(
                 SigninPromoController.getPromoShowCountPreferenceName(SigninAccessPoint.SETTINGS));
+        SharedPreferencesManager.getInstance().removeKey(
+                ChromePreferenceKeys.SYNC_PROMO_TOTAL_SHOW_COUNT);
     }
 
     @Test
@@ -401,12 +404,21 @@
     @Test
     @MediumTest
     public void testSyncPromoShownIsNotOverCounted() {
+        int promoShowCount = SharedPreferencesManager.getInstance().readInt(
+                SigninPromoController.getPromoShowCountPreferenceName(SigninAccessPoint.SETTINGS));
+        Assert.assertEquals(0, promoShowCount);
+        Assert.assertEquals(0,
+                SharedPreferencesManager.getInstance().readInt(
+                        ChromePreferenceKeys.SYNC_PROMO_TOTAL_SHOW_COUNT));
         launchSettingsActivity();
         onViewWaiting(allOf(withId(R.id.signin_promo_view_container), isDisplayed()));
 
-        int promoShowCount = SharedPreferencesManager.getInstance().readInt(
+        promoShowCount = SharedPreferencesManager.getInstance().readInt(
                 SigninPromoController.getPromoShowCountPreferenceName(SigninAccessPoint.SETTINGS));
-        Assert.assertEquals("Promo shown count should only be increased by 1", 1, promoShowCount);
+        Assert.assertEquals(1, promoShowCount);
+        Assert.assertEquals(1,
+                SharedPreferencesManager.getInstance().readInt(
+                        ChromePreferenceKeys.SYNC_PROMO_TOTAL_SHOW_COUNT));
     }
 
     private void launchSettingsActivity() {
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/webapps/WebApkUpdateManagerUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/webapps/WebApkUpdateManagerUnitTest.java
index 291efd9..99694a10c 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/webapps/WebApkUpdateManagerUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/webapps/WebApkUpdateManagerUnitTest.java
@@ -59,6 +59,7 @@
 import org.chromium.components.webapk.lib.common.WebApkMetaDataKeys;
 import org.chromium.components.webapps.WebApkDistributor;
 import org.chromium.device.mojom.ScreenOrientationLockType;
+import org.chromium.ui.util.ColorUtils;
 import org.chromium.webapk.lib.common.WebApkConstants;
 import org.chromium.webapk.lib.common.splash.SplashLayout;
 import org.chromium.webapk.test.WebApkTestHelper;
@@ -1136,11 +1137,11 @@
         assertNotEquals(oldDefaultBackgroundColor, splashLayoutDefaultBackgroundColor);
 
         ManifestData androidManifestData = defaultManifestData();
-        androidManifestData.backgroundColor = WebappConstants.MANIFEST_COLOR_INVALID_OR_MISSING;
+        androidManifestData.backgroundColor = ColorUtils.INVALID_COLOR;
         androidManifestData.defaultBackgroundColor = oldDefaultBackgroundColor;
 
         ManifestData fetchedManifestData = defaultManifestData();
-        fetchedManifestData.backgroundColor = WebappConstants.MANIFEST_COLOR_INVALID_OR_MISSING;
+        fetchedManifestData.backgroundColor = ColorUtils.INVALID_COLOR;
         fetchedManifestData.defaultBackgroundColor = splashLayoutDefaultBackgroundColor;
 
         assertFalse(checkUpdateNeededForFetchedManifest(androidManifestData, fetchedManifestData));
diff --git a/chrome/browser/android/browserservices/intents/BUILD.gn b/chrome/browser/android/browserservices/intents/BUILD.gn
index e5b2416..1aa6553 100644
--- a/chrome/browser/android/browserservices/intents/BUILD.gn
+++ b/chrome/browser/android/browserservices/intents/BUILD.gn
@@ -59,5 +59,6 @@
     "//third_party/blink/public/mojom:mojom_platform_java",
     "//third_party/hamcrest:hamcrest_library_java",
     "//third_party/junit",
+    "//ui/android:ui_no_recycler_view_java",
   ]
 }
diff --git a/chrome/browser/android/browserservices/intents/java/src/org/chromium/chrome/browser/browserservices/intents/WebApkInfoTest.java b/chrome/browser/android/browserservices/intents/java/src/org/chromium/chrome/browser/browserservices/intents/WebApkInfoTest.java
index 565ae424..b393d6d 100644
--- a/chrome/browser/android/browserservices/intents/java/src/org/chromium/chrome/browser/browserservices/intents/WebApkInfoTest.java
+++ b/chrome/browser/android/browserservices/intents/java/src/org/chromium/chrome/browser/browserservices/intents/WebApkInfoTest.java
@@ -34,6 +34,7 @@
 import org.chromium.components.webapps.ShortcutSource;
 import org.chromium.components.webapps.WebApkDistributor;
 import org.chromium.device.mojom.ScreenOrientationLockType;
+import org.chromium.ui.util.ColorUtils;
 import org.chromium.webapk.lib.common.WebApkConstants;
 import org.chromium.webapk.lib.common.splash.SplashLayout;
 import org.chromium.webapk.test.WebApkTestHelper;
@@ -696,8 +697,7 @@
     public void testBackgroundColorFallbackToDefaultNoCustomDefault() {
         Bundle bundle = new Bundle();
         bundle.putString(WebApkMetaDataKeys.START_URL, START_URL);
-        bundle.putString(WebApkMetaDataKeys.BACKGROUND_COLOR,
-                WebappConstants.MANIFEST_COLOR_INVALID_OR_MISSING + "L");
+        bundle.putString(WebApkMetaDataKeys.BACKGROUND_COLOR, ColorUtils.INVALID_COLOR + "L");
         WebApkTestHelper.registerWebApkWithMetaData(WEBAPK_PACKAGE_NAME, bundle, null);
 
         Intent intent = new Intent();
@@ -720,8 +720,7 @@
 
         Bundle bundle = new Bundle();
         bundle.putString(WebApkMetaDataKeys.START_URL, START_URL);
-        bundle.putString(WebApkMetaDataKeys.BACKGROUND_COLOR,
-                WebappConstants.MANIFEST_COLOR_INVALID_OR_MISSING + "L");
+        bundle.putString(WebApkMetaDataKeys.BACKGROUND_COLOR, ColorUtils.INVALID_COLOR + "L");
         bundle.putInt(
                 WebApkMetaDataKeys.DEFAULT_BACKGROUND_COLOR_ID, defaultBackgroundColorResourceId);
         WebApkTestHelper.registerWebApkWithMetaData(WEBAPK_PACKAGE_NAME, bundle, null);
diff --git a/chrome/browser/android/browserservices/intents/java/src/org/chromium/chrome/browser/browserservices/intents/WebappConstants.java b/chrome/browser/android/browserservices/intents/java/src/org/chromium/chrome/browser/browserservices/intents/WebappConstants.java
index c40243be..ec262e020 100644
--- a/chrome/browser/android/browserservices/intents/java/src/org/chromium/chrome/browser/browserservices/intents/WebappConstants.java
+++ b/chrome/browser/android/browserservices/intents/java/src/org/chromium/chrome/browser/browserservices/intents/WebappConstants.java
@@ -5,7 +5,6 @@
 package org.chromium.chrome.browser.browserservices.intents;
 
 import org.chromium.content_public.common.ScreenOrientationConstants;
-import org.chromium.ui.util.ColorUtils;
 
 /**
  * This class contains constants related to adding shortcuts to the Android Home
@@ -46,8 +45,5 @@
     // be correctly populated into the WebappRegistry/WebappDataStorage.
     public static final int WEBAPP_SHORTCUT_VERSION = 3;
 
-    // This value is equal to kInvalidOrMissingColor in the C++ blink::Manifest struct.
-    public static final long MANIFEST_COLOR_INVALID_OR_MISSING = ColorUtils.INVALID_COLOR;
-
     private WebappConstants() {}
 }
diff --git a/chrome/browser/android/browserservices/intents/java/src/org/chromium/chrome/browser/browserservices/intents/WebappInfo.java b/chrome/browser/android/browserservices/intents/java/src/org/chromium/chrome/browser/browserservices/intents/WebappInfo.java
index 31e837c..be926a80 100644
--- a/chrome/browser/android/browserservices/intents/java/src/org/chromium/chrome/browser/browserservices/intents/WebappInfo.java
+++ b/chrome/browser/android/browserservices/intents/java/src/org/chromium/chrome/browser/browserservices/intents/WebappInfo.java
@@ -15,6 +15,7 @@
 import org.chromium.components.webapps.ShortcutSource;
 import org.chromium.components.webapps.WebApkDistributor;
 import org.chromium.device.mojom.ScreenOrientationLockType;
+import org.chromium.ui.util.ColorUtils;
 
 import java.util.List;
 import java.util.Map;
@@ -95,11 +96,11 @@
 
     /**
      * Returns the toolbar color if it is valid, and
-     * WebappConstants.MANIFEST_COLOR_INVALID_OR_MISSING otherwise.
+     * ColorUtils.INVALID_COLOR otherwise.
      */
     public long toolbarColor() {
         return hasValidToolbarColor() ? mProvider.getColorProvider().getToolbarColor()
-                                      : WebappConstants.MANIFEST_COLOR_INVALID_OR_MISSING;
+                                      : ColorUtils.INVALID_COLOR;
     }
 
     /**
@@ -112,7 +113,7 @@
     /**
      * Background color is actually a 32 bit unsigned integer which encodes a color
      * in ARGB format. Return value is a long because we also need to encode the
-     * error state of WebappConstants.MANIFEST_COLOR_INVALID_OR_MISSING.
+     * error state of ColorUtils.INVALID_COLOR.
      */
     public long backgroundColor() {
         return WebappIntentUtils.colorFromIntegerColor(getWebappExtras().backgroundColor);
diff --git a/chrome/browser/android/browserservices/intents/java/src/org/chromium/chrome/browser/browserservices/intents/WebappInfoTest.java b/chrome/browser/android/browserservices/intents/java/src/org/chromium/chrome/browser/browserservices/intents/WebappInfoTest.java
index 171231e2..b98c5b2 100644
--- a/chrome/browser/android/browserservices/intents/java/src/org/chromium/chrome/browser/browserservices/intents/WebappInfoTest.java
+++ b/chrome/browser/android/browserservices/intents/java/src/org/chromium/chrome/browser/browserservices/intents/WebappInfoTest.java
@@ -19,6 +19,7 @@
 import org.chromium.chrome.test.util.browser.webapps.WebappTestHelper;
 import org.chromium.components.webapps.ShortcutSource;
 import org.chromium.device.mojom.ScreenOrientationLockType;
+import org.chromium.ui.util.ColorUtils;
 
 /**
  * Tests the WebappInfo class's ability to parse various URLs.
@@ -166,9 +167,8 @@
         intent.putExtra(WebappConstants.EXTRA_NAME, name);
         intent.putExtra(WebappConstants.EXTRA_SHORT_NAME, shortName);
         WebappInfo info = createWebappInfo(intent);
-        Assert.assertEquals(WebappConstants.MANIFEST_COLOR_INVALID_OR_MISSING, info.toolbarColor());
-        Assert.assertEquals(
-                WebappConstants.MANIFEST_COLOR_INVALID_OR_MISSING, info.backgroundColor());
+        Assert.assertEquals(ColorUtils.INVALID_COLOR, info.toolbarColor());
+        Assert.assertEquals(ColorUtils.INVALID_COLOR, info.backgroundColor());
     }
 
     @Test
diff --git a/chrome/browser/android/browserservices/intents/java/src/org/chromium/chrome/browser/browserservices/intents/WebappIntentUtils.java b/chrome/browser/android/browserservices/intents/java/src/org/chromium/chrome/browser/browserservices/intents/WebappIntentUtils.java
index 82b893a9..783cf26 100644
--- a/chrome/browser/android/browserservices/intents/java/src/org/chromium/chrome/browser/browserservices/intents/WebappIntentUtils.java
+++ b/chrome/browser/android/browserservices/intents/java/src/org/chromium/chrome/browser/browserservices/intents/WebappIntentUtils.java
@@ -9,6 +9,7 @@
 import android.provider.Browser;
 
 import org.chromium.base.IntentUtils;
+import org.chromium.ui.util.ColorUtils;
 import org.chromium.webapk.lib.common.WebApkConstants;
 
 import java.util.HashSet;
@@ -47,22 +48,22 @@
     /**
      * Converts color from signed Integer where an unspecified color is represented as null to
      * to unsigned long where an unspecified color is represented as
-     * {@link WebappConstants.MANIFEST_COLOR_INVALID_OR_MISSING}.
+     * {@link ColorUtils.INVALID_COLOR}.
      */
     public static long colorFromIntegerColor(Integer color) {
         if (color != null) {
             return color.intValue();
         }
-        return WebappConstants.MANIFEST_COLOR_INVALID_OR_MISSING;
+        return ColorUtils.INVALID_COLOR;
     }
 
     public static boolean isLongColorValid(long longColor) {
-        return (longColor != WebappConstants.MANIFEST_COLOR_INVALID_OR_MISSING);
+        return (longColor != ColorUtils.INVALID_COLOR);
     }
 
     /**
      * Converts color from unsigned long where an unspecified color is represented as
-     * {@link WebappConstants.MANIFEST_COLOR_INVALID_OR_MISSING} to a signed Integer where an
+     * {@link ColorUtils.INVALID_COLOR} to a signed Integer where an
      * unspecified color is represented as null.
      */
     public static Integer colorFromLongColor(long longColor) {
diff --git a/chrome/browser/android/shortcut_helper.cc b/chrome/browser/android/shortcut_helper.cc
index fe1d7a1b..afb14278 100644
--- a/chrome/browser/android/shortcut_helper.cc
+++ b/chrome/browser/android/shortcut_helper.cc
@@ -18,7 +18,7 @@
 #include "components/webapps/browser/android/shortcut_info.h"
 #include "content/public/browser/manifest_icon_downloader.h"
 #include "content/public/browser/web_contents.h"
-#include "ui/android/color_helpers.h"
+#include "ui/android/color_utils_android.h"
 #include "ui/gfx/android/java_bitmap.h"
 #include "url/gurl.h"
 
diff --git a/chrome/browser/android/webapk/webapk_handler_delegate.cc b/chrome/browser/android/webapk/webapk_handler_delegate.cc
index 26beb171..eefbce9 100644
--- a/chrome/browser/android/webapk/webapk_handler_delegate.cc
+++ b/chrome/browser/android/webapk/webapk_handler_delegate.cc
@@ -10,7 +10,7 @@
 #include "chrome/android/chrome_jni_headers/WebApkHandlerDelegate_jni.h"
 #include "third_party/blink/public/mojom/manifest/display_mode.mojom.h"
 #include "third_party/skia/include/core/SkColor.h"
-#include "ui/android/color_helpers.h"
+#include "ui/android/color_utils_android.h"
 
 using base::android::JavaParamRef;
 
diff --git a/chrome/browser/android/webapk/webapk_installer.cc b/chrome/browser/android/webapk/webapk_installer.cc
index 96372e36..bca7697 100644
--- a/chrome/browser/android/webapk/webapk_installer.cc
+++ b/chrome/browser/android/webapk/webapk_installer.cc
@@ -53,7 +53,7 @@
 #include "services/network/public/cpp/simple_url_loader.h"
 #include "services/network/public/mojom/url_response_head.mojom.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
-#include "ui/android/color_helpers.h"
+#include "ui/android/color_utils_android.h"
 #include "ui/gfx/android/java_bitmap.h"
 #include "ui/gfx/codec/png_codec.h"
 #include "url/origin.h"
diff --git a/chrome/browser/android/webapk/webapk_update_data_fetcher.cc b/chrome/browser/android/webapk/webapk_update_data_fetcher.cc
index d4dcd7ed..5135c17 100644
--- a/chrome/browser/android/webapk/webapk_update_data_fetcher.cc
+++ b/chrome/browser/android/webapk/webapk_update_data_fetcher.cc
@@ -26,7 +26,7 @@
 #include "third_party/blink/public/common/manifest/manifest_util.h"
 #include "third_party/blink/public/mojom/manifest/manifest.mojom.h"
 #include "third_party/smhasher/src/MurmurHash2.h"
-#include "ui/android/color_helpers.h"
+#include "ui/android/color_utils_android.h"
 #include "ui/gfx/android/java_bitmap.h"
 #include "ui/gfx/codec/png_codec.h"
 #include "url/gurl.h"
diff --git a/chrome/browser/android/webapk/webapk_update_manager.cc b/chrome/browser/android/webapk/webapk_update_manager.cc
index ede51fc..cef42d0 100644
--- a/chrome/browser/android/webapk/webapk_update_manager.cc
+++ b/chrome/browser/android/webapk/webapk_update_manager.cc
@@ -26,7 +26,7 @@
 #include "content/public/browser/browser_thread.h"
 #include "third_party/blink/public/mojom/manifest/manifest.mojom.h"
 #include "third_party/skia/include/core/SkBitmap.h"
-#include "ui/android/color_helpers.h"
+#include "ui/android/color_utils_android.h"
 #include "ui/gfx/android/java_bitmap.h"
 #include "url/gurl.h"
 
diff --git a/chrome/browser/apps/app_service/DEPS b/chrome/browser/apps/app_service/DEPS
index e7cb331..9de64387 100644
--- a/chrome/browser/apps/app_service/DEPS
+++ b/chrome/browser/apps/app_service/DEPS
@@ -1,5 +1,5 @@
 include_rules = [
-  "+components/services/app_service/app_service_impl.h",
+  "+components/services/app_service/app_service_mojom_impl.h",
   "+components/services/app_service/public",
   "+components/app_restore",
   "+components/webapk",
diff --git a/chrome/browser/apps/app_service/app_service_proxy_base.cc b/chrome/browser/apps/app_service/app_service_proxy_base.cc
index 09c6a4a9..38549c0 100644
--- a/chrome/browser/apps/app_service/app_service_proxy_base.cc
+++ b/chrome/browser/apps/app_service/app_service_proxy_base.cc
@@ -21,7 +21,7 @@
 #include "chrome/browser/ash/file_manager/app_id.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/web_applications/web_app_id_constants.h"
-#include "components/services/app_service/app_service_impl.h"
+#include "components/services/app_service/app_service_mojom_impl.h"
 #include "components/services/app_service/public/cpp/intent_constants.h"
 #include "components/services/app_service/public/cpp/intent_filter_util.h"
 #include "components/services/app_service/public/cpp/intent_util.h"
@@ -130,9 +130,10 @@
 
   browser_app_launcher_ = std::make_unique<apps::BrowserAppLauncher>(profile_);
 
-  app_service_impl_ =
-      std::make_unique<apps::AppServiceImpl>(profile_->GetPath());
-  app_service_impl_->BindReceiver(app_service_.BindNewPipeAndPassReceiver());
+  app_service_mojom_impl_ =
+      std::make_unique<apps::AppServiceMojomImpl>(profile_->GetPath());
+  app_service_mojom_impl_->BindReceiver(
+      app_service_.BindNewPipeAndPassReceiver());
 
   if (app_service_.is_connected()) {
     // The AppServiceProxy is a subscriber: something that wants to be able to
diff --git a/chrome/browser/apps/app_service/app_service_proxy_base.h b/chrome/browser/apps/app_service/app_service_proxy_base.h
index 6d19bac..74b4afa 100644
--- a/chrome/browser/apps/app_service/app_service_proxy_base.h
+++ b/chrome/browser/apps/app_service/app_service_proxy_base.h
@@ -31,7 +31,7 @@
 
 namespace apps {
 
-class AppServiceImpl;
+class AppServiceMojomImpl;
 
 struct IntentLaunchInfo {
   IntentLaunchInfo();
@@ -337,7 +337,7 @@
 
   // This proxy privately owns its instance of the App Service. This should not
   // be exposed except through the Mojo interface connected to |app_service_|.
-  std::unique_ptr<apps::AppServiceImpl> app_service_impl_;
+  std::unique_ptr<apps::AppServiceMojomImpl> app_service_mojom_impl_;
 
   mojo::Remote<apps::mojom::AppService> app_service_;
   apps::AppRegistryCache app_registry_cache_;
diff --git a/chrome/browser/apps/app_service/app_service_proxy_chromeos.cc b/chrome/browser/apps/app_service/app_service_proxy_chromeos.cc
index 7a301ac2..e34c165 100644
--- a/chrome/browser/apps/app_service/app_service_proxy_chromeos.cc
+++ b/chrome/browser/apps/app_service/app_service_proxy_chromeos.cc
@@ -31,7 +31,7 @@
 #include "components/app_restore/features.h"
 #include "components/app_restore/full_restore_save_handler.h"
 #include "components/app_restore/full_restore_utils.h"
-#include "components/services/app_service/app_service_impl.h"
+#include "components/services/app_service/app_service_mojom_impl.h"
 #include "components/services/app_service/public/cpp/app_capability_access_cache_wrapper.h"
 #include "components/services/app_service/public/cpp/app_registry_cache_wrapper.h"
 #include "components/services/app_service/public/cpp/types_util.h"
@@ -254,7 +254,7 @@
 }
 
 void AppServiceProxyChromeOs::FlushMojoCallsForTesting() {
-  app_service_impl_->FlushMojoCallsForTesting();
+  app_service_mojom_impl_->FlushMojoCallsForTesting();
   if (built_in_chrome_os_apps_) {
     built_in_chrome_os_apps_->FlushMojoCallsForTesting();
   }
diff --git a/chrome/browser/apps/app_service/app_service_proxy_desktop.cc b/chrome/browser/apps/app_service/app_service_proxy_desktop.cc
index 6a2aaf1..9837b5b7 100644
--- a/chrome/browser/apps/app_service/app_service_proxy_desktop.cc
+++ b/chrome/browser/apps/app_service/app_service_proxy_desktop.cc
@@ -7,7 +7,7 @@
 #include "chrome/browser/apps/app_service/publishers/extension_apps.h"
 #include "chrome/browser/web_applications/app_service/web_apps.h"
 #include "chrome/browser/web_applications/web_app_provider.h"
-#include "components/services/app_service/app_service_impl.h"
+#include "components/services/app_service/app_service_mojom_impl.h"
 
 namespace apps {
 
@@ -52,7 +52,7 @@
 }
 
 void AppServiceProxy::FlushMojoCallsForTesting() {
-  app_service_impl_->FlushMojoCallsForTesting();
+  app_service_mojom_impl_->FlushMojoCallsForTesting();
   receivers_.FlushForTesting();
 }
 
diff --git a/chrome/browser/apps/app_service/app_service_proxy_lacros.cc b/chrome/browser/apps/app_service/app_service_proxy_lacros.cc
index 3038a0b..7e2f4f8a 100644
--- a/chrome/browser/apps/app_service/app_service_proxy_lacros.cc
+++ b/chrome/browser/apps/app_service/app_service_proxy_lacros.cc
@@ -26,7 +26,7 @@
 #include "chrome/browser/web_applications/app_service/web_apps_publisher_host.h"
 #include "chrome/browser/web_applications/web_app_provider.h"
 #include "chromeos/lacros/lacros_service.h"
-#include "components/services/app_service/app_service_impl.h"
+#include "components/services/app_service/app_service_mojom_impl.h"
 #include "components/services/app_service/public/cpp/intent_filter_util.h"
 #include "components/services/app_service/public/cpp/intent_util.h"
 #include "components/services/app_service/public/cpp/types_util.h"
diff --git a/chrome/browser/apps/app_service/browser_app_instance_tracker_browsertest.cc b/chrome/browser/apps/app_service/browser_app_instance_tracker_browsertest.cc
index 185a7266..613fa3d 100644
--- a/chrome/browser/apps/app_service/browser_app_instance_tracker_browsertest.cc
+++ b/chrome/browser/apps/app_service/browser_app_instance_tracker_browsertest.cc
@@ -1016,14 +1016,12 @@
 IN_PROC_BROWSER_TEST_F(BrowserAppInstanceTrackerTest, ActivateTabInstance) {
   // Setup: a browser with 2 tabs (app A and a non-app tab).
   Browser* browser = nullptr;
-  aura::Window* window = nullptr;
 
   content::WebContents* web_contents_a;
   content::WebContents* web_contents_c;
 
   // Open app A in a tab.
   browser = CreateBrowser();
-  window = browser->window()->GetNativeWindow();
   web_contents_a = InsertForegroundTab(browser, "https://a.example.org");
 
   // Open a second tab with no app.
diff --git a/chrome/browser/apps/app_service/webapk/webapk_install_task.cc b/chrome/browser/apps/app_service/webapk/webapk_install_task.cc
index 6e2f857..6fd7dac2 100644
--- a/chrome/browser/apps/app_service/webapk/webapk_install_task.cc
+++ b/chrome/browser/apps/app_service/webapk/webapk_install_task.cc
@@ -11,6 +11,7 @@
 #include "base/command_line.h"
 #include "base/location.h"
 #include "base/logging.h"
+#include "base/metrics/histogram_functions.h"
 #include "base/strings/strcat.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/task/task_traits.h"
@@ -457,12 +458,19 @@
     std::unique_ptr<std::string> response_body) {
   timer_.Stop();
 
-  int response_code = -1;
-  if (url_loader_->ResponseInfo() && url_loader_->ResponseInfo()->headers)
-    response_code = url_loader_->ResponseInfo()->headers->response_code();
+  int response_or_error_code = -1;
+  if (url_loader_->ResponseInfo() && url_loader_->ResponseInfo()->headers) {
+    response_or_error_code =
+        url_loader_->ResponseInfo()->headers->response_code();
+  } else {
+    response_or_error_code = url_loader_->NetError();
+  }
+  base::UmaHistogramSparse(kWebApkMinterErrorCodeHistogram,
+                           response_or_error_code);
 
-  if (!response_body || response_code != net::HTTP_OK) {
-    LOG(WARNING) << "WebAPK server returned response code " << response_code;
+  if (!response_body || response_or_error_code != net::HTTP_OK) {
+    LOG(WARNING) << "WebAPK server request returned error "
+                 << response_or_error_code;
     DeliverResult(WebApkInstallStatus::kNetworkError);
     return;
   }
diff --git a/chrome/browser/apps/app_service/webapk/webapk_install_task_unittest.cc b/chrome/browser/apps/app_service/webapk/webapk_install_task_unittest.cc
index 7e62c02..ce0a554 100644
--- a/chrome/browser/apps/app_service/webapk/webapk_install_task_unittest.cc
+++ b/chrome/browser/apps/app_service/webapk/webapk_install_task_unittest.cc
@@ -224,6 +224,8 @@
                                apps::WebApkInstallStatus::kSuccess, 1);
   histograms.ExpectBucketCount(apps::kWebApkArcInstallResultHistogram,
                                arc::mojom::WebApkInstallResult::kSuccess, 1);
+  histograms.ExpectBucketCount(apps::kWebApkMinterErrorCodeHistogram,
+                               net::HTTP_OK, 1);
 }
 
 TEST_F(WebApkInstallTaskTest, ShareTarget) {
@@ -290,6 +292,8 @@
   ASSERT_EQ(apps::webapk_prefs::GetWebApkAppIds(profile()).size(), 0);
   histograms.ExpectBucketCount(apps::kWebApkInstallResultHistogram,
                                apps::WebApkInstallStatus::kNetworkError, 1);
+  histograms.ExpectBucketCount(apps::kWebApkMinterErrorCodeHistogram,
+                               net::HTTP_BAD_REQUEST, 1);
 }
 
 TEST_F(WebApkInstallTaskTest, FailedArcInstall) {
diff --git a/chrome/browser/apps/app_service/webapk/webapk_metrics.cc b/chrome/browser/apps/app_service/webapk/webapk_metrics.cc
index 92b9e9e..40bc57e 100644
--- a/chrome/browser/apps/app_service/webapk/webapk_metrics.cc
+++ b/chrome/browser/apps/app_service/webapk/webapk_metrics.cc
@@ -15,6 +15,8 @@
     "ChromeOS.WebAPK.Install.ArcInstallResult";
 const char kWebApkArcUpdateResultHistogram[] =
     "ChromeOS.WebAPK.Update.ArcInstallResult";
+const char kWebApkMinterErrorCodeHistogram[] =
+    "ChromeOS.WebAPK.MinterResponseOrErrorCode";
 
 void RecordWebApkInstallResult(bool is_update, WebApkInstallStatus result) {
   const char* histogram =
diff --git a/chrome/browser/apps/app_service/webapk/webapk_metrics.h b/chrome/browser/apps/app_service/webapk/webapk_metrics.h
index c5e0e64..c511b4d 100644
--- a/chrome/browser/apps/app_service/webapk/webapk_metrics.h
+++ b/chrome/browser/apps/app_service/webapk/webapk_metrics.h
@@ -37,6 +37,7 @@
 extern const char kWebApkUpdateResultHistogram[];
 extern const char kWebApkArcInstallResultHistogram[];
 extern const char kWebApkArcUpdateResultHistogram[];
+extern const char kWebApkMinterErrorCodeHistogram[];
 
 // Records the overall result of installing/updating a WebAPK to UMA.
 void RecordWebApkInstallResult(bool is_update, WebApkInstallStatus result);
diff --git a/chrome/browser/apps/guest_view/web_view_browsertest.cc b/chrome/browser/apps/guest_view/web_view_browsertest.cc
index 1648c25..b47600f 100644
--- a/chrome/browser/apps/guest_view/web_view_browsertest.cc
+++ b/chrome/browser/apps/guest_view/web_view_browsertest.cc
@@ -1840,6 +1840,14 @@
       content::CollectAllRenderFrameHosts(embedder_main_frame),
       testing::UnorderedElementsAre(embedder_main_frame, other_guest_main_frame,
                                     unattached_guest_main_frame));
+
+  // In either case, GetParentOrOuterDocument does not escape GuestViews.
+  EXPECT_EQ(nullptr, other_guest_main_frame->GetParentOrOuterDocument());
+  EXPECT_EQ(nullptr, unattached_guest_main_frame->GetParentOrOuterDocument());
+  EXPECT_EQ(other_guest_main_frame,
+            other_guest_main_frame->GetOutermostMainFrame());
+  EXPECT_EQ(unattached_guest_main_frame,
+            unattached_guest_main_frame->GetOutermostMainFrame());
 }
 
 IN_PROC_BROWSER_TEST_F(WebViewTest, Shim_TestContentLoadEvent) {
diff --git a/chrome/browser/ash/drive/drivefs_native_message_host.cc b/chrome/browser/ash/drive/drivefs_native_message_host.cc
index 5d353db..9afa036 100644
--- a/chrome/browser/ash/drive/drivefs_native_message_host.cc
+++ b/chrome/browser/ash/drive/drivefs_native_message_host.cc
@@ -21,6 +21,7 @@
 #include "extensions/browser/extension_registry.h"
 #include "extensions/common/api/messaging/messaging_endpoint.h"
 #include "extensions/common/api/messaging/port_id.h"
+#include "extensions/common/api/messaging/serialization_format.h"
 #include "extensions/common/permissions/permissions_data.h"
 #include "mojo/public/cpp/bindings/receiver.h"
 #include "mojo/public/cpp/bindings/remote.h"
@@ -206,7 +207,8 @@
   }
 
   const extensions::PortId port_id(base::UnguessableToken::Create(),
-                                   /* port_number= */ 1, /* is_opener= */ true);
+                                   /* port_number= */ 1, /* is_opener= */ true,
+                                   extensions::SerializationFormat::kJson);
   extensions::MessageService* const message_service =
       extensions::MessageService::Get(profile);
   auto native_message_host = CreateDriveFsInitiatedNativeMessageHost(
diff --git a/chrome/browser/ash/guest_os/vm_sk_forwarding_native_message_host.cc b/chrome/browser/ash/guest_os/vm_sk_forwarding_native_message_host.cc
index 9aad636e..f225807 100644
--- a/chrome/browser/ash/guest_os/vm_sk_forwarding_native_message_host.cc
+++ b/chrome/browser/ash/guest_os/vm_sk_forwarding_native_message_host.cc
@@ -24,6 +24,7 @@
 #include "extensions/browser/api/messaging/native_message_host.h"
 #include "extensions/browser/extension_registry.h"
 #include "extensions/common/api/messaging/messaging_endpoint.h"
+#include "extensions/common/api/messaging/serialization_format.h"
 #include "extensions/common/extension.h"
 #include "url/gurl.h"
 
@@ -118,7 +119,8 @@
     const std::string& json_message,
     base::OnceCallback<void(const std::string& response)> response_callback) {
   const extensions::PortId port_id(base::UnguessableToken::Create(),
-                                   1 /* port_number */, true /* is_opener */);
+                                   1 /* port_number */, true /* is_opener */,
+                                   extensions::SerializationFormat::kJson);
 
   extensions::MessageService* const message_service =
       extensions::MessageService::Get(profile);
diff --git a/chrome/browser/ash/input_method/suggestions_service_client.cc b/chrome/browser/ash/input_method/suggestions_service_client.cc
index e88ecd22..08fea08a 100644
--- a/chrome/browser/ash/input_method/suggestions_service_client.cc
+++ b/chrome/browser/ash/input_method/suggestions_service_client.cc
@@ -85,20 +85,18 @@
       ->GetMachineLearningService()
       .LoadTextSuggester(
           text_suggester_.BindNewPipeAndPassReceiver(), std::move(spec),
-          base::BindOnce(
-              [](base::WeakPtr<SuggestionsServiceClient> weak_this,
-                 chromeos::machine_learning::mojom::LoadModelResult result) {
-                if (weak_this) {
-                  weak_this->text_suggester_loaded_ =
-                      result ==
-                      chromeos::machine_learning::mojom::LoadModelResult::OK;
-                }
-              },
-              weak_ptr_factory_.GetWeakPtr()));
+          base::BindOnce(&SuggestionsServiceClient::OnTextSuggesterLoaded,
+                         weak_ptr_factory_.GetWeakPtr()));
 }
 
 SuggestionsServiceClient::~SuggestionsServiceClient() = default;
 
+void SuggestionsServiceClient::OnTextSuggesterLoaded(
+    chromeos::machine_learning::mojom::LoadModelResult result) {
+  text_suggester_loaded_ =
+      result == chromeos::machine_learning::mojom::LoadModelResult::OK;
+}
+
 void SuggestionsServiceClient::RequestSuggestions(
     const std::string& preceding_text,
     const ime::TextSuggestionMode& suggestion_mode,
diff --git a/chrome/browser/ash/input_method/suggestions_service_client.h b/chrome/browser/ash/input_method/suggestions_service_client.h
index 0bf05b6c..43eeb69 100644
--- a/chrome/browser/ash/input_method/suggestions_service_client.h
+++ b/chrome/browser/ash/input_method/suggestions_service_client.h
@@ -12,6 +12,7 @@
 #include "base/time/time.h"
 #include "chrome/browser/ash/input_method/suggestions_source.h"
 #include "chromeos/services/ime/public/cpp/suggestions.h"
+#include "chromeos/services/machine_learning/public/mojom/machine_learning_service.mojom.h"
 #include "chromeos/services/machine_learning/public/mojom/text_suggester.mojom.h"
 #include "mojo/public/cpp/bindings/remote.h"
 
@@ -33,6 +34,11 @@
   bool IsAvailable() override;
 
  private:
+  // Called once the text suggester model has been loaded and is (or is not)
+  // available for use.
+  void OnTextSuggesterLoaded(
+      chromeos::machine_learning::mojom::LoadModelResult result);
+
   // Called when results are returned from the suggestions service
   void OnSuggestionsReturned(
       base::TimeTicks time_request_was_made,
diff --git a/chrome/browser/ash/login/users/avatar/user_image_sync_observer.cc b/chrome/browser/ash/login/users/avatar/user_image_sync_observer.cc
index d78edec..28a8767 100644
--- a/chrome/browser/ash/login/users/avatar/user_image_sync_observer.cc
+++ b/chrome/browser/ash/login/users/avatar/user_image_sync_observer.cc
@@ -89,12 +89,10 @@
 
 void UserImageSyncObserver::OnInitialSync() {
   int synced_index;
-  bool local_image_updated = false;
   if (!GetSyncedImageIndex(&synced_index) || local_image_changed_) {
     UpdateSyncedImageFromLocal();
   } else if (IsIndexSupported(synced_index)) {
     UpdateLocalImageFromSynced();
-    local_image_updated = true;
   }
 }
 
diff --git a/chrome/browser/ash/ownership/owner_settings_service_ash.cc b/chrome/browser/ash/ownership/owner_settings_service_ash.cc
index 91d9de6..9035e6b 100644
--- a/chrome/browser/ash/ownership/owner_settings_service_ash.cc
+++ b/chrome/browser/ash/ownership/owner_settings_service_ash.cc
@@ -688,6 +688,8 @@
     //   kVariationsRestrictParameter
     //   kDeviceDisabled
     //   kDeviceDisabledMessage
+    //   ReportDeviceNetworkTelemetryCollectionRateMs
+    //   ReportDeviceNetworkTelemetryEventCheckingRateMs
 
     LOG(FATAL) << "Device setting " << path << " is read-only.";
   }
diff --git a/chrome/browser/ash/policy/core/device_policy_decoder.cc b/chrome/browser/ash/policy/core/device_policy_decoder.cc
index cc71403d..a5266db 100644
--- a/chrome/browser/ash/policy/core/device_policy_decoder.cc
+++ b/chrome/browser/ash/policy/core/device_policy_decoder.cc
@@ -694,6 +694,16 @@
   }
 }
 
+void DecodeIntegerReportingPolicy(PolicyMap* policies,
+                                  const std::string& policy_path,
+                                  google::protobuf::int64 int_value) {
+  std::unique_ptr<base::Value> value = DecodeIntegerValue(int_value);
+  if (value) {
+    policies->Set(policy_path, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+                  POLICY_SOURCE_CLOUD, std::move(*value), nullptr);
+  }
+}
+
 void DecodeReportingPolicies(const em::ChromeDeviceSettingsProto& policy,
                              PolicyMap* policies) {
   if (policy.has_device_reporting()) {
@@ -794,13 +804,8 @@
                     base::Value(container.report_board_status()), nullptr);
     }
     if (container.has_device_status_frequency()) {
-      std::unique_ptr<base::Value> value(
-          DecodeIntegerValue(container.device_status_frequency()));
-      if (value) {
-        policies->Set(key::kReportUploadFrequency, POLICY_LEVEL_MANDATORY,
-                      POLICY_SCOPE_MACHINE, POLICY_SOURCE_CLOUD,
-                      std::move(*value), nullptr);
-      }
+      DecodeIntegerReportingPolicy(policies, key::kReportUploadFrequency,
+                                   container.device_status_frequency());
     }
     if (container.has_report_cpu_info()) {
       policies->Set(key::kReportDeviceCpuInfo, POLICY_LEVEL_MANDATORY,
@@ -862,6 +867,16 @@
                     POLICY_SCOPE_MACHINE, POLICY_SOURCE_CLOUD,
                     base::Value(container.report_login_logout()), nullptr);
     }
+    if (container.has_report_network_telemetry_collection_rate_ms()) {
+      DecodeIntegerReportingPolicy(
+          policies, key::kReportDeviceNetworkTelemetryCollectionRateMs,
+          container.report_network_telemetry_collection_rate_ms());
+    }
+    if (container.has_report_network_telemetry_event_checking_rate_ms()) {
+      DecodeIntegerReportingPolicy(
+          policies, key::kReportDeviceNetworkTelemetryEventCheckingRateMs,
+          container.report_network_telemetry_event_checking_rate_ms());
+    }
   }
 
   if (policy.has_device_heartbeat_settings()) {
@@ -873,13 +888,8 @@
                     base::Value(container.heartbeat_enabled()), nullptr);
     }
     if (container.has_heartbeat_frequency()) {
-      std::unique_ptr<base::Value> value(
-          DecodeIntegerValue(container.heartbeat_frequency()));
-      if (value) {
-        policies->Set(key::kHeartbeatFrequency, POLICY_LEVEL_MANDATORY,
-                      POLICY_SCOPE_MACHINE, POLICY_SOURCE_CLOUD,
-                      std::move(*value), nullptr);
-      }
+      DecodeIntegerReportingPolicy(policies, key::kHeartbeatFrequency,
+                                   container.heartbeat_frequency());
     }
   }
 
diff --git a/chrome/browser/ash/policy/core/device_policy_decoder_unittest.cc b/chrome/browser/ash/policy/core/device_policy_decoder_unittest.cc
index f8b74ad..227b4865 100644
--- a/chrome/browser/ash/policy/core/device_policy_decoder_unittest.cc
+++ b/chrome/browser/ash/policy/core/device_policy_decoder_unittest.cc
@@ -67,6 +67,13 @@
  protected:
   std::unique_ptr<base::Value> GetWallpaperDict() const;
   std::unique_ptr<base::Value> GetBluetoothServiceAllowedList() const;
+  void DecodeDevicePolicyTestHelper(
+      const em::ChromeDeviceSettingsProto& device_policy,
+      const std::string& policy_path,
+      base::Value expected_value) const;
+  void DecodeUnsetDevicePolicyTestHelper(
+      const em::ChromeDeviceSettingsProto& device_policy,
+      const std::string& policy_path) const;
 };
 
 std::unique_ptr<base::Value> DevicePolicyDecoderTest::GetWallpaperDict() const {
@@ -87,6 +94,36 @@
   return list;
 }
 
+void DevicePolicyDecoderTest::DecodeDevicePolicyTestHelper(
+    const em::ChromeDeviceSettingsProto& device_policy,
+    const std::string& policy_path,
+    base::Value expected_value) const {
+  PolicyBundle bundle;
+  PolicyMap& policies = bundle.Get(PolicyNamespace(POLICY_DOMAIN_CHROME, ""));
+
+  base::WeakPtr<ExternalDataManager> external_data_manager;
+
+  DecodeDevicePolicy(device_policy, external_data_manager, &policies);
+
+  const base::Value* actual_value = policies.GetValue(policy_path);
+  ASSERT_NE(actual_value, nullptr);
+  EXPECT_EQ(*actual_value, expected_value);
+}
+
+void DevicePolicyDecoderTest::DecodeUnsetDevicePolicyTestHelper(
+    const em::ChromeDeviceSettingsProto& device_policy,
+    const std::string& policy_path) const {
+  PolicyBundle bundle;
+  PolicyMap& policies = bundle.Get(PolicyNamespace(POLICY_DOMAIN_CHROME, ""));
+
+  base::WeakPtr<ExternalDataManager> external_data_manager;
+
+  DecodeDevicePolicy(device_policy, external_data_manager, &policies);
+
+  const base::Value* actual_value = policies.GetValue(policy_path);
+  EXPECT_EQ(actual_value, nullptr);
+}
+
 TEST_F(DevicePolicyDecoderTest, DecodeJsonStringAndNormalizeJSONParseError) {
   std::string error;
   absl::optional<base::Value> decoded_json = DecodeJsonStringAndNormalize(
@@ -173,130 +210,136 @@
 }
 
 TEST_F(DevicePolicyDecoderTest, ReportDeviceLoginLogout) {
-  PolicyBundle bundle;
-  PolicyMap& policies = bundle.Get(PolicyNamespace(POLICY_DOMAIN_CHROME, ""));
-
-  base::WeakPtr<ExternalDataManager> external_data_manager;
-
   em::ChromeDeviceSettingsProto device_policy;
-  device_policy.mutable_device_reporting()->set_report_login_logout(true);
 
-  DecodeDevicePolicy(device_policy, external_data_manager, &policies);
+  DecodeUnsetDevicePolicyTestHelper(device_policy,
+                                    key::kReportDeviceLoginLogout);
 
-  const base::Value* report_device_login_logout_value =
-      policies.GetValue(key::kReportDeviceLoginLogout);
-  ASSERT_NE(report_device_login_logout_value, nullptr);
-  ASSERT_TRUE(report_device_login_logout_value->is_bool());
-  EXPECT_TRUE(report_device_login_logout_value->GetBool());
+  base::Value report_login_logout_value(true);
+  device_policy.mutable_device_reporting()->set_report_login_logout(
+      report_login_logout_value.GetBool());
+
+  DecodeDevicePolicyTestHelper(device_policy, key::kReportDeviceLoginLogout,
+                               std::move(report_login_logout_value));
+}
+
+TEST_F(DevicePolicyDecoderTest, ReportDeviceNetworkTelemetryCollectionRateMs) {
+  em::ChromeDeviceSettingsProto device_policy;
+
+  DecodeUnsetDevicePolicyTestHelper(
+      device_policy, key::kReportDeviceNetworkTelemetryCollectionRateMs);
+
+  base::Value collection_rate_ms_value(120000);
+  device_policy.mutable_device_reporting()
+      ->set_report_network_telemetry_collection_rate_ms(
+          collection_rate_ms_value.GetInt());
+
+  DecodeDevicePolicyTestHelper(
+      device_policy, key::kReportDeviceNetworkTelemetryCollectionRateMs,
+      std::move(collection_rate_ms_value));
+}
+
+TEST_F(DevicePolicyDecoderTest,
+       ReportDeviceNetworkTelemetryEventCheckingRateMs) {
+  em::ChromeDeviceSettingsProto device_policy;
+
+  DecodeUnsetDevicePolicyTestHelper(
+      device_policy, key::kReportDeviceNetworkTelemetryEventCheckingRateMs);
+
+  base::Value event_checking_rate_ms_value(80000);
+  device_policy.mutable_device_reporting()
+      ->set_report_network_telemetry_event_checking_rate_ms(
+          event_checking_rate_ms_value.GetInt());
+
+  DecodeDevicePolicyTestHelper(
+      device_policy, key::kReportDeviceNetworkTelemetryEventCheckingRateMs,
+      std::move(event_checking_rate_ms_value));
 }
 
 TEST_F(DevicePolicyDecoderTest, EnableDeviceGranularReporting) {
-  PolicyBundle bundle;
-  PolicyMap& policies = bundle.Get(PolicyNamespace(POLICY_DOMAIN_CHROME, ""));
-
-  base::WeakPtr<ExternalDataManager> external_data_manager;
-
   em::ChromeDeviceSettingsProto device_policy;
-  device_policy.mutable_device_reporting()->set_enable_granular_reporting(true);
 
-  DecodeDevicePolicy(device_policy, external_data_manager, &policies);
+  DecodeUnsetDevicePolicyTestHelper(device_policy,
+                                    key::kEnableDeviceGranularReporting);
 
-  const base::Value* enable_granular_reporting_value =
-      policies.GetValue(key::kEnableDeviceGranularReporting);
-  ASSERT_NE(enable_granular_reporting_value, nullptr);
-  ASSERT_TRUE(enable_granular_reporting_value->is_bool());
-  EXPECT_TRUE(enable_granular_reporting_value->GetBool());
+  base::Value enable_granular_reporting_value(true);
+  device_policy.mutable_device_reporting()->set_enable_granular_reporting(
+      enable_granular_reporting_value.GetBool());
+
+  DecodeDevicePolicyTestHelper(device_policy,
+                               key::kEnableDeviceGranularReporting,
+                               std::move(enable_granular_reporting_value));
 }
 
 TEST_F(DevicePolicyDecoderTest, ReportDeviceAudioStatus) {
-  PolicyBundle bundle;
-  PolicyMap& policies = bundle.Get(PolicyNamespace(POLICY_DOMAIN_CHROME, ""));
-
-  base::WeakPtr<ExternalDataManager> external_data_manager;
-
   em::ChromeDeviceSettingsProto device_policy;
-  device_policy.mutable_device_reporting()->set_report_audio_status(true);
 
-  DecodeDevicePolicy(device_policy, external_data_manager, &policies);
+  DecodeUnsetDevicePolicyTestHelper(device_policy,
+                                    key::kReportDeviceAudioStatus);
 
-  const base::Value* report_device_audio_status_value =
-      policies.GetValue(key::kReportDeviceAudioStatus);
-  ASSERT_NE(report_device_audio_status_value, nullptr);
-  ASSERT_TRUE(report_device_audio_status_value->is_bool());
-  EXPECT_TRUE(report_device_audio_status_value->GetBool());
+  base::Value report_audio_status_value(true);
+  device_policy.mutable_device_reporting()->set_report_audio_status(
+      report_audio_status_value.GetBool());
+
+  DecodeDevicePolicyTestHelper(device_policy, key::kReportDeviceAudioStatus,
+                               std::move(report_audio_status_value));
 }
 
 TEST_F(DevicePolicyDecoderTest, ReportDeviceSecurityStatus) {
-  PolicyBundle bundle;
-  PolicyMap& policies = bundle.Get(PolicyNamespace(POLICY_DOMAIN_CHROME, ""));
-
-  base::WeakPtr<ExternalDataManager> external_data_manager;
-
   em::ChromeDeviceSettingsProto device_policy;
-  device_policy.mutable_device_reporting()->set_report_security_status(true);
 
-  DecodeDevicePolicy(device_policy, external_data_manager, &policies);
+  DecodeUnsetDevicePolicyTestHelper(device_policy,
+                                    key::kReportDeviceSecurityStatus);
 
-  const base::Value* report_device_security_status_value =
-      policies.GetValue(key::kReportDeviceSecurityStatus);
-  ASSERT_NE(report_device_security_status_value, nullptr);
-  ASSERT_TRUE(report_device_security_status_value->is_bool());
-  EXPECT_TRUE(report_device_security_status_value->GetBool());
+  base::Value report_security_status_value(true);
+  device_policy.mutable_device_reporting()->set_report_security_status(
+      report_security_status_value.GetBool());
+
+  DecodeDevicePolicyTestHelper(device_policy, key::kReportDeviceSecurityStatus,
+                               std::move(report_security_status_value));
 }
 
 TEST_F(DevicePolicyDecoderTest, ReportDeviceNetworkConfiguration) {
-  PolicyBundle bundle;
-  PolicyMap& policies = bundle.Get(PolicyNamespace(POLICY_DOMAIN_CHROME, ""));
-
-  base::WeakPtr<ExternalDataManager> external_data_manager;
-
   em::ChromeDeviceSettingsProto device_policy;
+
+  DecodeUnsetDevicePolicyTestHelper(device_policy,
+                                    key::kReportDeviceNetworkConfiguration);
+
+  base::Value report_network_configuration_value(true);
   device_policy.mutable_device_reporting()->set_report_network_configuration(
-      true);
+      report_network_configuration_value.GetBool());
 
-  DecodeDevicePolicy(device_policy, external_data_manager, &policies);
-
-  const base::Value* report_device_network_configuration_value =
-      policies.GetValue(key::kReportDeviceNetworkConfiguration);
-  ASSERT_NE(report_device_network_configuration_value, nullptr);
-  ASSERT_TRUE(report_device_network_configuration_value->is_bool());
-  EXPECT_TRUE(report_device_network_configuration_value->GetBool());
+  DecodeDevicePolicyTestHelper(device_policy,
+                               key::kReportDeviceNetworkConfiguration,
+                               std::move(report_network_configuration_value));
 }
 
 TEST_F(DevicePolicyDecoderTest, ReportDeviceNetworkStatus) {
-  PolicyBundle bundle;
-  PolicyMap& policies = bundle.Get(PolicyNamespace(POLICY_DOMAIN_CHROME, ""));
-
-  base::WeakPtr<ExternalDataManager> external_data_manager;
-
   em::ChromeDeviceSettingsProto device_policy;
-  device_policy.mutable_device_reporting()->set_report_network_status(true);
 
-  DecodeDevicePolicy(device_policy, external_data_manager, &policies);
+  DecodeUnsetDevicePolicyTestHelper(device_policy,
+                                    key::kReportDeviceNetworkStatus);
 
-  const base::Value* report_device_network_status_value =
-      policies.GetValue(key::kReportDeviceNetworkStatus);
-  ASSERT_NE(report_device_network_status_value, nullptr);
-  ASSERT_TRUE(report_device_network_status_value->is_bool());
-  EXPECT_TRUE(report_device_network_status_value->GetBool());
+  base::Value report_network_status_value(true);
+  device_policy.mutable_device_reporting()->set_report_network_status(
+      report_network_status_value.GetBool());
+
+  DecodeDevicePolicyTestHelper(device_policy, key::kReportDeviceNetworkStatus,
+                               std::move(report_network_status_value));
 }
 
 TEST_F(DevicePolicyDecoderTest, kReportDeviceOsUpdateStatus) {
-  PolicyBundle bundle;
-  PolicyMap& policies = bundle.Get(PolicyNamespace(POLICY_DOMAIN_CHROME, ""));
-
-  base::WeakPtr<ExternalDataManager> external_data_manager;
-
   em::ChromeDeviceSettingsProto device_policy;
-  device_policy.mutable_device_reporting()->set_report_os_update_status(true);
 
-  DecodeDevicePolicy(device_policy, external_data_manager, &policies);
+  DecodeUnsetDevicePolicyTestHelper(device_policy,
+                                    key::kReportDeviceOsUpdateStatus);
 
-  const base::Value* report_device_os_update_status_value =
-      policies.GetValue(key::kReportDeviceOsUpdateStatus);
-  ASSERT_NE(report_device_os_update_status_value, nullptr);
-  ASSERT_TRUE(report_device_os_update_status_value->is_bool());
-  EXPECT_TRUE(report_device_os_update_status_value->GetBool());
+  base::Value report_os_update_status_value(true);
+  device_policy.mutable_device_reporting()->set_report_os_update_status(
+      report_os_update_status_value.GetBool());
+
+  DecodeDevicePolicyTestHelper(device_policy, key::kReportDeviceOsUpdateStatus,
+                               std::move(report_os_update_status_value));
 }
 
 TEST_F(DevicePolicyDecoderTest, DecodeServiceUUIDListSuccess) {
@@ -321,17 +364,19 @@
 TEST_F(DevicePolicyDecoderTest,
        DecodeLoginScreenPromptOnMultipleMatchingCertificates) {
   em::ChromeDeviceSettingsProto device_policy;
-  device_policy.mutable_login_screen_prompt_on_multiple_matching_certificates()
-      ->set_value(true);
 
-  PolicyMap policies;
-  DecodeDevicePolicy(device_policy, /*external_data_manager=*/{}, &policies);
-
-  const base::Value* policy_value = policies.GetValue(
+  DecodeUnsetDevicePolicyTestHelper(
+      device_policy,
       key::kDeviceLoginScreenPromptOnMultipleMatchingCertificates);
-  ASSERT_NE(policy_value, nullptr);
-  ASSERT_TRUE(policy_value->is_bool());
-  EXPECT_TRUE(policy_value->GetBool());
+
+  base::Value login_screen_prompt_value(true);
+  device_policy.mutable_login_screen_prompt_on_multiple_matching_certificates()
+      ->set_value(login_screen_prompt_value.GetBool());
+
+  DecodeDevicePolicyTestHelper(
+      device_policy,
+      key::kDeviceLoginScreenPromptOnMultipleMatchingCertificates,
+      std::move(login_screen_prompt_value));
 }
 
 }  // namespace policy
diff --git a/chrome/browser/ash/settings/device_settings_provider.cc b/chrome/browser/ash/settings/device_settings_provider.cc
index 3a48ae0..77487180 100644
--- a/chrome/browser/ash/settings/device_settings_provider.cc
+++ b/chrome/browser/ash/settings/device_settings_provider.cc
@@ -137,6 +137,8 @@
     kReportDeviceNetworkConfiguration,
     kReportDeviceNetworkInterfaces,
     kReportDeviceNetworkStatus,
+    kReportDeviceNetworkTelemetryCollectionRateMs,
+    kReportDeviceNetworkTelemetryEventCheckingRateMs,
     kReportDeviceSessionStatus,
     kReportDeviceSecurityStatus,
     kReportDeviceTimezoneInfo,
@@ -738,6 +740,17 @@
       new_values_cache->SetBoolean(kReportDeviceLoginLogout,
                                    reporting_policy.report_login_logout());
     }
+    if (reporting_policy.has_report_network_telemetry_collection_rate_ms()) {
+      new_values_cache->SetInteger(
+          kReportDeviceNetworkTelemetryCollectionRateMs,
+          reporting_policy.report_network_telemetry_collection_rate_ms());
+    }
+    if (reporting_policy
+            .has_report_network_telemetry_event_checking_rate_ms()) {
+      new_values_cache->SetInteger(
+          kReportDeviceNetworkTelemetryEventCheckingRateMs,
+          reporting_policy.report_network_telemetry_event_checking_rate_ms());
+    }
   }
 }
 
diff --git a/chrome/browser/ash/settings/device_settings_provider_unittest.cc b/chrome/browser/ash/settings/device_settings_provider_unittest.cc
index b23f7b13..0e8efed 100644
--- a/chrome/browser/ash/settings/device_settings_provider_unittest.cc
+++ b/chrome/browser/ash/settings/device_settings_provider_unittest.cc
@@ -116,6 +116,8 @@
     proto->set_report_app_info(enable_reporting);
     proto->set_report_print_jobs(enable_reporting);
     proto->set_report_login_logout(enable_reporting);
+    proto->set_report_network_telemetry_collection_rate_ms(frequency);
+    proto->set_report_network_telemetry_event_checking_rate_ms(frequency);
     proto->set_device_status_frequency(frequency);
     BuildAndInstallDevicePolicy();
   }
@@ -208,9 +210,17 @@
       EXPECT_EQ(expected_enable_value, *provider_->Get(setting))
           << "Value for " << setting << " does not match expected";
     }
+
+    const char* const reporting_frequency_settings[] = {
+        kReportUploadFrequency,
+        kReportDeviceNetworkTelemetryCollectionRateMs,
+        kReportDeviceNetworkTelemetryEventCheckingRateMs,
+    };
     const base::Value expected_frequency_value(expected_frequency);
-    EXPECT_EQ(expected_frequency_value,
-              *provider_->Get(kReportUploadFrequency));
+    for (auto* frequency_setting : reporting_frequency_settings) {
+      EXPECT_EQ(expected_frequency_value, *provider_->Get(frequency_setting))
+          << "Value for " << frequency_setting << " does not match expected";
+    }
   }
 
   // Helper routine to ensure log upload policy has been correctly
diff --git a/chrome/browser/ash/wilco_dtc_supportd/wilco_dtc_supportd_messaging.cc b/chrome/browser/ash/wilco_dtc_supportd/wilco_dtc_supportd_messaging.cc
index 039f4b4..54fd74c 100644
--- a/chrome/browser/ash/wilco_dtc_supportd/wilco_dtc_supportd_messaging.cc
+++ b/chrome/browser/ash/wilco_dtc_supportd/wilco_dtc_supportd_messaging.cc
@@ -29,6 +29,7 @@
 #include "extensions/browser/api/messaging/native_message_host.h"
 #include "extensions/browser/extension_registry.h"
 #include "extensions/common/api/messaging/messaging_endpoint.h"
+#include "extensions/common/api/messaging/serialization_format.h"
 #include "extensions/common/extension.h"
 #include "mojo/public/cpp/system/handle.h"
 #include "url/gurl.h"
@@ -348,7 +349,8 @@
     base::OnceCallback<void(const std::string& response)>
         send_response_callback) {
   const extensions::PortId port_id(base::UnguessableToken::Create(),
-                                   1 /* port_number */, true /* is_opener */);
+                                   1 /* port_number */, true /* is_opener */,
+                                   extensions::SerializationFormat::kJson);
   extensions::MessageService* const message_service =
       extensions::MessageService::Get(profile);
   auto native_message_host =
diff --git a/chrome/browser/chromeos/eche_app/eche_app_manager_factory.cc b/chrome/browser/chromeos/eche_app/eche_app_manager_factory.cc
index 46417b62..8fb62d0 100644
--- a/chrome/browser/chromeos/eche_app/eche_app_manager_factory.cc
+++ b/chrome/browser/chromeos/eche_app/eche_app_manager_factory.cc
@@ -29,6 +29,9 @@
 #include "chromeos/components/eche_app_ui/eche_uid_provider.h"
 #include "chromeos/components/eche_app_ui/system_info.h"
 #include "chromeos/components/phonehub/phone_hub_manager.h"
+#include "chromeos/services/secure_channel/presence_monitor_impl.h"
+#include "chromeos/services/secure_channel/public/cpp/client/presence_monitor_client_impl.h"
+#include "chromeos/services/secure_channel/public/cpp/shared/presence_monitor.h"
 #include "chromeos/strings/grit/chromeos_strings.h"
 #include "components/keyed_service/content/browser_context_dependency_manager.h"
 #include "components/pref_registry/pref_registry_syncable.h"
@@ -197,9 +200,17 @@
   if (!secure_channel_client)
     return nullptr;
 
+  auto presence_monitor =
+      std::make_unique<secure_channel::PresenceMonitorImpl>();
+  std::unique_ptr<secure_channel::PresenceMonitorClient>
+      presence_monitor_client =
+          secure_channel::PresenceMonitorClientImpl::Factory::Create(
+              std::move(presence_monitor));
+
   return new EcheAppManager(
       profile->GetPrefs(), GetSystemInfo(profile), phone_hub_manager,
       device_sync_client, multidevice_setup_client, secure_channel_client,
+      std::move(presence_monitor_client),
       base::BindRepeating(&LaunchEcheApp, profile),
       base::BindRepeating(&CloseEcheApp, profile),
       base::BindRepeating(&EcheAppManagerFactory::ShowNotification,
diff --git a/chrome/browser/chromeos/extensions/file_manager/private_api_misc.cc b/chrome/browser/chromeos/extensions/file_manager/private_api_misc.cc
index 3a7bdf74..47e4487 100644
--- a/chrome/browser/chromeos/extensions/file_manager/private_api_misc.cc
+++ b/chrome/browser/chromeos/extensions/file_manager/private_api_misc.cc
@@ -595,9 +595,17 @@
                                          DevToolsToggleAction::Inspect());
       break;
     case extensions::api::file_manager_private::INSPECTION_TYPE_BACKGROUND:
-      // Open inspector for background page.
-      extensions::devtools_util::InspectBackgroundPage(
-          extension(), Profile::FromBrowserContext(browser_context()));
+      // Open inspector for background page if extension pointer is not null.
+      // Files app SWA is not an extension and thus has no associated background
+      // page.
+      if (extension()) {
+        extensions::devtools_util::InspectBackgroundPage(
+            extension(), Profile::FromBrowserContext(browser_context()));
+      } else {
+        return RespondNow(
+            Error(base::StringPrintf("Inspection type(%d) not supported.",
+                                     static_cast<int>(params->type))));
+      }
       break;
     default:
       NOTREACHED();
diff --git a/chrome/browser/chromeos/extensions/file_manager/system_notification_manager.cc b/chrome/browser/chromeos/extensions/file_manager/system_notification_manager.cc
index 78cb4a9..cc68710 100644
--- a/chrome/browser/chromeos/extensions/file_manager/system_notification_manager.cc
+++ b/chrome/browser/chromeos/extensions/file_manager/system_notification_manager.cc
@@ -353,14 +353,12 @@
     base::Value::ListView& event_arguments) {
   std::unique_ptr<message_center::Notification> notification;
   file_manager_private::DriveConfirmDialogEvent dialog_event;
-  const char* id;
   std::u16string title =
       l10n_util::GetStringUTF16(IDS_FILE_BROWSER_DRIVE_DIRECTORY_LABEL);
   std::u16string message;
   if (file_manager_private::DriveConfirmDialogEvent::Populate(
           event_arguments[0], &dialog_event)) {
     std::vector<message_center::ButtonInfo> notification_buttons;
-    id = file_manager_private::ToString(dialog_event.type);
     scoped_refptr<message_center::NotificationDelegate> delegate =
         new message_center::HandleNotificationClickDelegate(base::BindRepeating(
             &SystemNotificationManager::HandleDriveDialogClick,
diff --git a/chrome/browser/chromeos/extensions/incoming_native_messaging_apitest.cc b/chrome/browser/chromeos/extensions/incoming_native_messaging_apitest.cc
index 5ea3807c..fa4a0f9 100644
--- a/chrome/browser/chromeos/extensions/incoming_native_messaging_apitest.cc
+++ b/chrome/browser/chromeos/extensions/incoming_native_messaging_apitest.cc
@@ -21,6 +21,7 @@
 #include "extensions/browser/api/messaging/native_message_host.h"
 #include "extensions/common/api/messaging/messaging_endpoint.h"
 #include "extensions/common/api/messaging/port_id.h"
+#include "extensions/common/api/messaging/serialization_format.h"
 #include "extensions/test/result_catcher.h"
 #include "ipc/ipc_message.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -75,7 +76,8 @@
       std::unique_ptr<extensions::NativeMessageHost> native_message_host) {
     auto* const message_service = extensions::MessageService::Get(profile());
     const extensions::PortId port_id(base::UnguessableToken::Create(),
-                                     1 /* port_number */, true /* is_opener */);
+                                     1 /* port_number */, true /* is_opener */,
+                                     extensions::SerializationFormat::kJson);
     auto native_message_port = std::make_unique<extensions::NativeMessagePort>(
         message_service->GetChannelDelegate(), port_id,
         std::move(native_message_host));
diff --git a/chrome/browser/commerce/merchant_viewer/android/BUILD.gn b/chrome/browser/commerce/merchant_viewer/android/BUILD.gn
index 0b2a36e3..d4e3ae3 100644
--- a/chrome/browser/commerce/merchant_viewer/android/BUILD.gn
+++ b/chrome/browser/commerce/merchant_viewer/android/BUILD.gn
@@ -180,6 +180,7 @@
     "//third_party/android_deps:espresso_java",
     "//third_party/android_deps:protobuf_lite_runtime_java",
     "//third_party/android_support_test_runner:runner_java",
+    "//third_party/androidx:androidx_annotation_annotation_java",
     "//third_party/androidx:androidx_appcompat_appcompat_resources_java",
     "//third_party/androidx:androidx_test_core_java",
     "//third_party/androidx:androidx_test_runner_java",
diff --git a/chrome/browser/commerce/merchant_viewer/android/java/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustBottomSheetCoordinator.java b/chrome/browser/commerce/merchant_viewer/android/java/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustBottomSheetCoordinator.java
index 3b07c40..eaafad6 100644
--- a/chrome/browser/commerce/merchant_viewer/android/java/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustBottomSheetCoordinator.java
+++ b/chrome/browser/commerce/merchant_viewer/android/java/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustBottomSheetCoordinator.java
@@ -74,8 +74,8 @@
     }
 
     /** Displays the details tab sheet. */
-    public void requestOpenSheet(GURL url, String title) {
-        setupSheet();
+    public void requestOpenSheet(GURL url, String title, Runnable onBottomSheetDismissed) {
+        setupSheet(onBottomSheetDismissed);
         mMediator.navigateToUrl(url, title);
         mBottomSheetController.requestShowContent(mSheetContent, true);
     }
@@ -85,7 +85,7 @@
         mBottomSheetController.hideContent(mSheetContent, true);
     }
 
-    private void setupSheet() {
+    private void setupSheet(Runnable onBottomSheetDismissed) {
         if (mSheetContent != null) {
             return;
         }
@@ -106,6 +106,13 @@
             public void onSheetContentChanged(BottomSheetContent newContent) {
                 if (newContent != mSheetContent) {
                     mMetrics.recordMetricsForBottomSheetClosed(mCloseReason);
+                    if (onBottomSheetDismissed != null
+                            && (mCloseReason == StateChangeReason.NONE
+                                    || mCloseReason == StateChangeReason.SWIPE
+                                    || mCloseReason == StateChangeReason.BACK_PRESS
+                                    || mCloseReason == StateChangeReason.TAP_SCRIM)) {
+                        onBottomSheetDismissed.run();
+                    }
                     destroySheet();
                 }
             }
diff --git a/chrome/browser/commerce/merchant_viewer/android/java/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustMessageViewModel.java b/chrome/browser/commerce/merchant_viewer/android/java/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustMessageViewModel.java
index e3a00b5..3bd604a9 100644
--- a/chrome/browser/commerce/merchant_viewer/android/java/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustMessageViewModel.java
+++ b/chrome/browser/commerce/merchant_viewer/android/java/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustMessageViewModel.java
@@ -41,8 +41,9 @@
         /**
          * Called when message primary action is tapped.
          * @param trustSignals The signal associated with this message.
+         * @param messageAssociatedUrl The url associated with this message context.
          */
-        void onMessagePrimaryAction(MerchantTrustSignals trustSignals);
+        void onMessagePrimaryAction(MerchantTrustSignals trustSignals, String messageAssociatedUrl);
     }
 
     public static PropertyModel create(Context context, MerchantTrustSignals trustSignals,
@@ -62,7 +63,9 @@
                 .with(MessageBannerProperties.ON_DISMISSED,
                         (reason) -> actionsHandler.onMessageDismissed(reason, messageAssociatedUrl))
                 .with(MessageBannerProperties.ON_PRIMARY_ACTION,
-                        () -> actionsHandler.onMessagePrimaryAction(trustSignals))
+                        ()
+                                -> actionsHandler.onMessagePrimaryAction(
+                                        trustSignals, messageAssociatedUrl))
                 .build();
     }
 
diff --git a/chrome/browser/commerce/merchant_viewer/android/java/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustMetrics.java b/chrome/browser/commerce/merchant_viewer/android/java/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustMetrics.java
index 57354de..3806ff8 100644
--- a/chrome/browser/commerce/merchant_viewer/android/java/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustMetrics.java
+++ b/chrome/browser/commerce/merchant_viewer/android/java/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustMetrics.java
@@ -37,6 +37,24 @@
         int MAX_VALUE = 2;
     }
 
+    /**
+     * Which ui the bottom sheet is opened from.
+     *
+     * Needs to stay in sync with MerchantTrustBottomSheetOpenedSource in enums.xml. These values
+     * are persisted to logs. Entries should not be renumbered and numeric values should never be
+     * reused.
+     */
+    @IntDef({BottomSheetOpenedSource.UNKNOWN, BottomSheetOpenedSource.FROM_MESSAGE,
+            BottomSheetOpenedSource.FROM_PAGE_INFO})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface BottomSheetOpenedSource {
+        int UNKNOWN = 0;
+        int FROM_MESSAGE = 1;
+        int FROM_PAGE_INFO = 2;
+        // Always update MAX_VALUE to match the last item in the list.
+        int MAX_VALUE = 2;
+    }
+
     // Metrics for merchant trust signal message.
     private boolean mDidRecordMessagePrepared;
     private boolean mDidRecordMessageShown;
diff --git a/chrome/browser/commerce/merchant_viewer/android/java/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustSignalsCoordinator.java b/chrome/browser/commerce/merchant_viewer/android/java/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustSignalsCoordinator.java
index 009da682..be39af0b 100644
--- a/chrome/browser/commerce/merchant_viewer/android/java/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustSignalsCoordinator.java
+++ b/chrome/browser/commerce/merchant_viewer/android/java/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustSignalsCoordinator.java
@@ -16,6 +16,7 @@
 
 import org.chromium.base.supplier.ObservableSupplier;
 import org.chromium.chrome.browser.feature_engagement.TrackerFactory;
+import org.chromium.chrome.browser.merchant_viewer.MerchantTrustMetrics.BottomSheetOpenedSource;
 import org.chromium.chrome.browser.merchant_viewer.MerchantTrustMetrics.MessageClearReason;
 import org.chromium.chrome.browser.merchant_viewer.proto.MerchantTrustSignalsOuterClass.MerchantTrustSignals;
 import org.chromium.chrome.browser.preferences.Pref;
@@ -233,24 +234,37 @@
     }
 
     @Override
-    public void onMessagePrimaryAction(MerchantTrustSignals trustSignals) {
+    public void onMessagePrimaryAction(
+            MerchantTrustSignals trustSignals, String messageAssociatedUrl) {
         mMetrics.recordMetricsForMessageTapped();
-        launchDetailsPage(new GURL(trustSignals.getMerchantDetailsPageUrl()));
+        launchDetailsPage(new GURL(trustSignals.getMerchantDetailsPageUrl()),
+                ()
+                        -> onBottomSheetDismissed(
+                                BottomSheetOpenedSource.FROM_MESSAGE, messageAssociatedUrl));
     }
 
     // PageInfoStoreInfoController.StoreInfoActionHandler implementation.
     @Override
     public void onStoreInfoClicked(MerchantTrustSignals trustSignals) {
-        launchDetailsPage(new GURL(trustSignals.getMerchantDetailsPageUrl()));
+        launchDetailsPage(new GURL(trustSignals.getMerchantDetailsPageUrl()),
+                () -> onBottomSheetDismissed(BottomSheetOpenedSource.FROM_PAGE_INFO, null));
         // If user has clicked the "Store info" row, send a signal to disable {@link
         // FeatureConstants.PAGE_INFO_STORE_INFO_FEATURE}.
         final Tracker tracker = TrackerFactory.getTrackerForProfile(mProfileSupplier.get());
         tracker.notifyEvent(EventConstants.PAGE_INFO_STORE_INFO_ROW_CLICKED);
     }
 
-    private void launchDetailsPage(GURL url) {
+    private void launchDetailsPage(GURL url, Runnable onBottomSheetDismissed) {
         mDetailsTabCoordinator.requestOpenSheet(url,
-                mContext.getResources().getString(R.string.merchant_viewer_preview_sheet_title));
+                mContext.getResources().getString(R.string.merchant_viewer_preview_sheet_title),
+                onBottomSheetDismissed);
+    }
+
+    private void onBottomSheetDismissed(
+            @BottomSheetOpenedSource int openSource, @Nullable String messageAssociatedUrl) {
+        if (openSource == BottomSheetOpenedSource.FROM_MESSAGE && messageAssociatedUrl != null) {
+            maybeShowStoreIcon(messageAssociatedUrl);
+        }
     }
 
     /**
diff --git a/chrome/browser/commerce/merchant_viewer/android/javatests/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustBottomSheetCoordinatorTest.java b/chrome/browser/commerce/merchant_viewer/android/javatests/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustBottomSheetCoordinatorTest.java
index 28c2ca1..19789ad 100644
--- a/chrome/browser/commerce/merchant_viewer/android/javatests/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustBottomSheetCoordinatorTest.java
+++ b/chrome/browser/commerce/merchant_viewer/android/javatests/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustBottomSheetCoordinatorTest.java
@@ -84,6 +84,9 @@
     @Mock
     private MerchantTrustBottomSheetMediator mMockMediator;
 
+    @Mock
+    private Runnable mMockOnBottomSheetDismissed;
+
     @Captor
     private ArgumentCaptor<EmptyBottomSheetObserver> mBottomSheetObserverCaptor;
 
@@ -119,8 +122,10 @@
     }
 
     private void requestOpenSheetAndVerify() {
-        TestThreadUtils.runOnUiThreadBlocking(
-                () -> { mDetailsTabCoordinator.requestOpenSheet(mMockGurl, DUMMY_SHEET_TITLE); });
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            mDetailsTabCoordinator.requestOpenSheet(
+                    mMockGurl, DUMMY_SHEET_TITLE, mMockOnBottomSheetDismissed);
+        });
         verify(mMockMediator, times(1))
                 .setupSheetWebContents(any(ThinWebView.class), any(PropertyModel.class));
         verify(mMockBottomSheetController, times(1))
@@ -148,6 +153,7 @@
                 () -> { mBottomSheetObserverCaptor.getValue().onSheetContentChanged(null); });
         verify(mMockMetrics, times(1))
                 .recordMetricsForBottomSheetClosed(eq(StateChangeReason.BACK_PRESS));
+        verify(mMockOnBottomSheetDismissed, times(1)).run();
         verify(mMockDecorView, times(1))
                 .removeOnLayoutChangeListener(any(OnLayoutChangeListener.class));
         verify(mMockBottomSheetController, times(1))
diff --git a/chrome/browser/commerce/merchant_viewer/android/javatests/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustSignalsCoordinatorTest.java b/chrome/browser/commerce/merchant_viewer/android/javatests/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustSignalsCoordinatorTest.java
index 3d5cd977..4686099e 100644
--- a/chrome/browser/commerce/merchant_viewer/android/javatests/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustSignalsCoordinatorTest.java
+++ b/chrome/browser/commerce/merchant_viewer/android/javatests/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustSignalsCoordinatorTest.java
@@ -27,6 +27,8 @@
 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.mockito.invocation.InvocationOnMock;
@@ -133,6 +135,9 @@
     @Mock
     private Tracker mMockTracker;
 
+    @Captor
+    private ArgumentCaptor<Runnable> mOnBottomSheetDismissedCaptor;
+
     private static final String FAKE_HOST = "fake_host";
     private static final String DIFFERENT_HOST = "different_host";
     private static final String FAKE_URL = "fake_url";
@@ -487,10 +492,13 @@
     @SmallTest
     @Test
     public void testOnMessagePrimaryAction() {
-        mCoordinator.onMessagePrimaryAction(mDummyMerchantTrustSignals);
+        mCoordinator.onMessagePrimaryAction(mDummyMerchantTrustSignals, FAKE_URL);
         verify(mMockMetrics, times(1)).recordMetricsForMessageTapped();
         verify(mMockDetailsTabCoordinator, times(1))
-                .requestOpenSheet(any(GURL.class), any(String.class));
+                .requestOpenSheet(any(GURL.class), any(String.class),
+                        mOnBottomSheetDismissedCaptor.capture());
+        mOnBottomSheetDismissedCaptor.getValue().run();
+        verify(mCoordinator, times(1)).maybeShowStoreIcon(eq(FAKE_URL));
     }
 
     @SmallTest
@@ -500,9 +508,12 @@
 
         mCoordinator.onStoreInfoClicked(mDummyMerchantTrustSignals);
         verify(mMockDetailsTabCoordinator, times(1))
-                .requestOpenSheet(any(GURL.class), any(String.class));
+                .requestOpenSheet(any(GURL.class), any(String.class),
+                        mOnBottomSheetDismissedCaptor.capture());
         verify(mMockTracker, times(1))
                 .notifyEvent(eq(EventConstants.PAGE_INFO_STORE_INFO_ROW_CLICKED));
+        mOnBottomSheetDismissedCaptor.getValue().run();
+        verify(mCoordinator, times(0)).maybeShowStoreIcon(any());
 
         TrackerFactory.setTrackerForTests(null);
     }
diff --git a/chrome/browser/component_updater/cros_component_installer_chromeos.cc b/chrome/browser/component_updater/cros_component_installer_chromeos.cc
index fe0e50b..15aa188 100644
--- a/chrome/browser/component_updater/cros_component_installer_chromeos.cc
+++ b/chrome/browser/component_updater/cros_component_installer_chromeos.cc
@@ -35,7 +35,7 @@
 
 // All downloadable Chrome OS components.
 const ComponentConfig kConfigs[] = {
-    {"cros-termina", ComponentConfig::PolicyType::kEnvVersion, "960.1",
+    {"cros-termina", ComponentConfig::PolicyType::kEnvVersion, "970.1",
      "e9d960f84f628e1f42d05de4046bb5b3154b6f1f65c08412c6af57a29aecaffb"},
     {"rtanalytics-light", ComponentConfig::PolicyType::kEnvVersion, "96.0",
      "69f09d33c439c2ab55bbbe24b47ab55cb3f6c0bd1f1ef46eefea3216ec925038"},
diff --git a/chrome/browser/component_updater/recovery_improved_component_installer.cc b/chrome/browser/component_updater/recovery_improved_component_installer.cc
index 42b026e..a7ba327 100644
--- a/chrome/browser/component_updater/recovery_improved_component_installer.cc
+++ b/chrome/browser/component_updater/recovery_improved_component_installer.cc
@@ -110,8 +110,13 @@
 void RecoveryComponentActionHandler::WaitForCommand(base::Process process) {
   int exit_code = 0;
   const base::TimeDelta kMaxWaitTime = base::Seconds(600);
-  const bool succeeded =
-      process.WaitForExitWithTimeout(kMaxWaitTime, &exit_code);
+  bool succeeded = false;
+  if (!process.IsValid()) {
+    exit_code =
+        static_cast<int>(update_client::InstallError::LAUNCH_PROCESS_FAILED)
+  } else {
+    succeeded = process.WaitForExitWithTimeout(kMaxWaitTime, &exit_code);
+  }
   base::DeletePathRecursively(unpack_path_);
   main_task_runner_->PostTask(
       FROM_HERE, base::BindOnce(std::move(callback_), succeeded, exit_code, 0));
diff --git a/chrome/browser/content_creation/reactions/internal/android/BUILD.gn b/chrome/browser/content_creation/reactions/internal/android/BUILD.gn
index aac4017..b4e3a20 100644
--- a/chrome/browser/content_creation/reactions/internal/android/BUILD.gn
+++ b/chrome/browser/content_creation/reactions/internal/android/BUILD.gn
@@ -10,12 +10,14 @@
     "java/src/org/chromium/chrome/browser/content_creation/reactions/LightweightReactionsCoordinatorFactory.java",
     "java/src/org/chromium/chrome/browser/content_creation/reactions/LightweightReactionsCoordinatorImpl.java",
     "java/src/org/chromium/chrome/browser/content_creation/reactions/LightweightReactionsDialog.java",
+    "java/src/org/chromium/chrome/browser/content_creation/reactions/ReactionGifDrawable.java",
     "java/src/org/chromium/chrome/browser/content_creation/reactions/ReactionServiceFactory.java",
     "java/src/org/chromium/chrome/browser/content_creation/reactions/scene/ReactionLayout.java",
     "java/src/org/chromium/chrome/browser/content_creation/reactions/scene/SceneCoordinator.java",
     "java/src/org/chromium/chrome/browser/content_creation/reactions/scene/SceneEditorDelegate.java",
     "java/src/org/chromium/chrome/browser/content_creation/reactions/toolbar/ToolbarControlsDelegate.java",
     "java/src/org/chromium/chrome/browser/content_creation/reactions/toolbar/ToolbarCoordinator.java",
+    "java/src/org/chromium/chrome/browser/content_creation/reactions/toolbar/ToolbarReactionsDelegate.java",
   ]
 
   deps = [
@@ -33,6 +35,7 @@
     "//third_party/androidx:androidx_appcompat_appcompat_java",
     "//third_party/androidx:androidx_appcompat_appcompat_resources_java",
     "//third_party/androidx:androidx_fragment_fragment_java",
+    "//third_party/gif_player:gif_player_java",
     "//ui/android:ui_no_recycler_view_java",
   ]
 
diff --git a/chrome/browser/content_creation/reactions/internal/android/java/src/org/chromium/chrome/browser/content_creation/reactions/LightweightReactionsCoordinatorImpl.java b/chrome/browser/content_creation/reactions/internal/android/java/src/org/chromium/chrome/browser/content_creation/reactions/LightweightReactionsCoordinatorImpl.java
index 010e411..435e65b 100644
--- a/chrome/browser/content_creation/reactions/internal/android/java/src/org/chromium/chrome/browser/content_creation/reactions/LightweightReactionsCoordinatorImpl.java
+++ b/chrome/browser/content_creation/reactions/internal/android/java/src/org/chromium/chrome/browser/content_creation/reactions/LightweightReactionsCoordinatorImpl.java
@@ -59,7 +59,7 @@
      * @param view The root {@link View} of the dialog.
      */
     private void onViewCreated(View view) {
-        mToolbarCoordinator = new ToolbarCoordinator(view, this);
+        mToolbarCoordinator = new ToolbarCoordinator(view, this, mSceneCoordinator);
     }
 
     // LightweightReactionsCoordinator implementation.
diff --git a/chrome/browser/content_creation/reactions/internal/android/java/src/org/chromium/chrome/browser/content_creation/reactions/ReactionGifDrawable.java b/chrome/browser/content_creation/reactions/internal/android/java/src/org/chromium/chrome/browser/content_creation/reactions/ReactionGifDrawable.java
new file mode 100644
index 0000000..449b05f
--- /dev/null
+++ b/chrome/browser/content_creation/reactions/internal/android/java/src/org/chromium/chrome/browser/content_creation/reactions/ReactionGifDrawable.java
@@ -0,0 +1,20 @@
+// 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.
+
+package org.chromium.chrome.browser.content_creation.reactions;
+
+import android.graphics.Bitmap;
+
+import jp.tomorrowkey.android.gifplayer.BaseGifDrawable;
+import jp.tomorrowkey.android.gifplayer.BaseGifImage;
+
+/**
+ * An extension of the BaseGifDrawable class that allows stepping through the frames of the GIF
+ * without updating the View, to facilitate decoding the GIF for exporting purposes.
+ */
+public class ReactionGifDrawable extends BaseGifDrawable {
+    public ReactionGifDrawable(BaseGifImage gifImage, Bitmap.Config bitmapConfig) {
+        super(gifImage, bitmapConfig);
+    }
+}
diff --git a/chrome/browser/content_creation/reactions/internal/android/java/src/org/chromium/chrome/browser/content_creation/reactions/scene/ReactionLayout.java b/chrome/browser/content_creation/reactions/internal/android/java/src/org/chromium/chrome/browser/content_creation/reactions/scene/ReactionLayout.java
index be386a2..136eefb6 100644
--- a/chrome/browser/content_creation/reactions/internal/android/java/src/org/chromium/chrome/browser/content_creation/reactions/scene/ReactionLayout.java
+++ b/chrome/browser/content_creation/reactions/internal/android/java/src/org/chromium/chrome/browser/content_creation/reactions/scene/ReactionLayout.java
@@ -53,7 +53,7 @@
         super.onFinishInflate();
         mReaction = findViewById(R.id.reaction_view);
         setUpCopyButton();
-        mDeleteButton = findViewById(R.id.delete_button);
+        setUpDeleteButton();
         mAdjustButton = findViewById(R.id.adjust_button);
     }
 
@@ -94,4 +94,9 @@
             }
         });
     }
+
+    private void setUpDeleteButton() {
+        mDeleteButton = findViewById(R.id.delete_button);
+        mDeleteButton.setOnClickListener(view -> mSceneEditorDelegate.removeReaction(this));
+    }
 }
diff --git a/chrome/browser/content_creation/reactions/internal/android/java/src/org/chromium/chrome/browser/content_creation/reactions/scene/SceneCoordinator.java b/chrome/browser/content_creation/reactions/internal/android/java/src/org/chromium/chrome/browser/content_creation/reactions/scene/SceneCoordinator.java
index 73a00de..68fc88e 100644
--- a/chrome/browser/content_creation/reactions/internal/android/java/src/org/chromium/chrome/browser/content_creation/reactions/scene/SceneCoordinator.java
+++ b/chrome/browser/content_creation/reactions/internal/android/java/src/org/chromium/chrome/browser/content_creation/reactions/scene/SceneCoordinator.java
@@ -11,6 +11,8 @@
 import androidx.appcompat.content.res.AppCompatResources;
 
 import org.chromium.chrome.browser.content_creation.reactions.internal.R;
+import org.chromium.chrome.browser.content_creation.reactions.toolbar.ToolbarReactionsDelegate;
+import org.chromium.components.content_creation.reactions.ReactionMetadata;
 import org.chromium.ui.LayoutInflaterUtils;
 import org.chromium.ui.base.ViewUtils;
 
@@ -20,7 +22,7 @@
 /**
  * Manages the scene UI and the reactions on the scene.
  */
-public class SceneCoordinator implements SceneEditorDelegate {
+public class SceneCoordinator implements SceneEditorDelegate, ToolbarReactionsDelegate {
     private static final int DEFAULT_REACTION_SIZE_DP = 100;
     private static final int REACTION_OFFSET_DP = 45;
     private static final int MAX_REACTION_COUNT = 10;
@@ -78,7 +80,7 @@
         markActiveStatus(reactionLayout, true);
     }
 
-    // SceneEditorCallback implementation.
+    // SceneEditorDelegate implementation.
     @Override
     public boolean canAddReaction() {
         return mReactionLayouts.size() < MAX_REACTION_COUNT;
@@ -104,6 +106,7 @@
 
     @Override
     public void removeReaction(ReactionLayout reactionLayout) {
+        markActiveStatus(reactionLayout, false);
         mSceneBackground.removeView(reactionLayout);
         mReactionLayouts.remove(reactionLayout);
     }
@@ -121,4 +124,10 @@
             mActiveReaction = null;
         }
     }
+
+    // ToolbarReactionsDelegate implementation.
+    @Override
+    public void onToolbarReactionTapped(ReactionMetadata reactionData) {
+        // No-op for now, will either add a new reaction or change the currently selected one.
+    }
 }
diff --git a/chrome/browser/content_creation/reactions/internal/android/java/src/org/chromium/chrome/browser/content_creation/reactions/toolbar/ToolbarCoordinator.java b/chrome/browser/content_creation/reactions/internal/android/java/src/org/chromium/chrome/browser/content_creation/reactions/toolbar/ToolbarCoordinator.java
index e5ba8b45..4df517f 100644
--- a/chrome/browser/content_creation/reactions/internal/android/java/src/org/chromium/chrome/browser/content_creation/reactions/toolbar/ToolbarCoordinator.java
+++ b/chrome/browser/content_creation/reactions/internal/android/java/src/org/chromium/chrome/browser/content_creation/reactions/toolbar/ToolbarCoordinator.java
@@ -13,12 +13,19 @@
  * Coordinator for the Lightweight Reactions toolbar.
  */
 public class ToolbarCoordinator {
-    public ToolbarCoordinator(View parentView, ToolbarControlsDelegate delegate) {
+    private final ToolbarControlsDelegate mControlsDelegate;
+    private final ToolbarReactionsDelegate mReactionsDelegate;
+
+    public ToolbarCoordinator(View parentView, ToolbarControlsDelegate controlsDelegate,
+            ToolbarReactionsDelegate reactionsDelegate) {
+        mControlsDelegate = controlsDelegate;
+        mReactionsDelegate = reactionsDelegate;
+
         RelativeLayout toolbarLayout = parentView.findViewById(R.id.lightweight_reactions_toolbar);
 
         View closeButton = toolbarLayout.findViewById(R.id.close_button);
-        closeButton.setOnClickListener(v -> delegate.cancelButtonTapped());
+        closeButton.setOnClickListener(v -> mControlsDelegate.cancelButtonTapped());
         View doneButton = toolbarLayout.findViewById(R.id.done_button);
-        doneButton.setOnClickListener(v -> delegate.doneButtonTapped());
+        doneButton.setOnClickListener(v -> mControlsDelegate.doneButtonTapped());
     }
 }
diff --git a/chrome/browser/content_creation/reactions/internal/android/java/src/org/chromium/chrome/browser/content_creation/reactions/toolbar/ToolbarReactionsDelegate.java b/chrome/browser/content_creation/reactions/internal/android/java/src/org/chromium/chrome/browser/content_creation/reactions/toolbar/ToolbarReactionsDelegate.java
new file mode 100644
index 0000000..ce9f74e9
--- /dev/null
+++ b/chrome/browser/content_creation/reactions/internal/android/java/src/org/chromium/chrome/browser/content_creation/reactions/toolbar/ToolbarReactionsDelegate.java
@@ -0,0 +1,17 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.content_creation.reactions.toolbar;
+
+import org.chromium.components.content_creation.reactions.ReactionMetadata;
+
+/**
+ * Interface used by the toolbar to delegate action handlers for tapping reactions.
+ */
+public interface ToolbarReactionsDelegate {
+    /**
+     * Invoked when the user taps a reaction.
+     */
+    public void onToolbarReactionTapped(ReactionMetadata reactionData);
+}
\ No newline at end of file
diff --git a/chrome/browser/extensions/api/cryptotoken_private/cryptotoken_private_api.cc b/chrome/browser/extensions/api/cryptotoken_private/cryptotoken_private_api.cc
index cf94fb6..19541a7 100644
--- a/chrome/browser/extensions/api/cryptotoken_private/cryptotoken_private_api.cc
+++ b/chrome/browser/extensions/api/cryptotoken_private/cryptotoken_private_api.cc
@@ -290,7 +290,8 @@
     return RespondNow(Error("cannot find specified tab"));
   }
 
-  content::RenderFrameHost* frame = web_contents->GetMainFrame();
+  content::RenderFrameHost* frame = RenderFrameHostForTabAndFrameId(
+      browser_context(), params->options.tab_id, params->options.frame_id);
   if (!frame) {
     return RespondNow(Error("cannot find frame"));
   }
diff --git a/chrome/browser/extensions/api/cryptotoken_private/cryptotoken_private_api_unittest.cc b/chrome/browser/extensions/api/cryptotoken_private/cryptotoken_private_api_unittest.cc
index 392b7ed8c..f500bee 100644
--- a/chrome/browser/extensions/api/cryptotoken_private/cryptotoken_private_api_unittest.cc
+++ b/chrome/browser/extensions/api/cryptotoken_private/cryptotoken_private_api_unittest.cc
@@ -270,6 +270,7 @@
     base::Value::DictStorage dict;
     dict.emplace("appId", app_id);
     dict.emplace("tabId", tab_id_);
+    dict.emplace("frameId", -1);  // Ignored.
     dict.emplace("origin", app_id);
     auto args = std::make_unique<base::Value>(base::Value::Type::LIST);
     args->Append(base::Value(std::move(dict)));
@@ -304,6 +305,7 @@
     base::Value::DictStorage dict;
     dict.emplace("appId", origin);
     dict.emplace("tabId", tab_id_);
+    dict.emplace("frameId", 0 /* main frame */);
     dict.emplace("origin", origin);
     auto args = std::make_unique<base::Value>(base::Value::Type::LIST);
     args->Append(base::Value(std::move(dict)));
diff --git a/chrome/browser/extensions/api/cryptotoken_private/cryptotoken_private_browsertest.cc b/chrome/browser/extensions/api/cryptotoken_private/cryptotoken_private_browsertest.cc
index 3bcbdb9..eaa2bb5 100644
--- a/chrome/browser/extensions/api/cryptotoken_private/cryptotoken_private_browsertest.cc
+++ b/chrome/browser/extensions/api/cryptotoken_private/cryptotoken_private_browsertest.cc
@@ -2,8 +2,12 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "base/base_switches.h"
+#include "base/strings/strcat.h"
 #include "base/strings/string_util.h"
+#include "base/test/scoped_feature_list.h"
 #include "base/test/with_feature_override.h"
+#include "build/build_config.h"
 #include "chrome/browser/extensions/component_loader.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser.h"
@@ -11,10 +15,15 @@
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chrome/test/base/ui_test_utils.h"
 #include "components/embedder_support/switches.h"
+#include "components/permissions/permission_request_manager.h"
+#include "components/permissions/test/permission_request_observer.h"
 #include "components/prefs/pref_service.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/web_contents.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/browser_test_utils.h"
 #include "content/public/test/url_loader_interceptor.h"
+#include "device/fido/features.h"
 #include "extensions/browser/pref_names.h"
 #include "extensions/common/extension_features.h"
 #include "net/dns/mock_host_resolver.h"
@@ -30,14 +39,26 @@
 namespace {
 
 constexpr char kCryptoTokenExtensionId[] = "kmendfapggjehodndflmmgagdbamhnfd";
+
+// Origin for running tests with an Origin Trial token. Hostname needs to be
+// from `net::EmbeddedTestServer::CERT_TEST_NAMES`.
 constexpr char kOriginTrialOrigin[] = "https://a.test";
 
+// Domain to serve files from. This should be different from `kOriginTrialToken`
+// domain. Needs to be from `net::EmbeddedTestServer::CERT_TEST_NAMES`.
+constexpr char kNonOriginTrialDomain[] = "b.test";
+
 class CryptotokenBrowserTest : public base::test::WithFeatureOverride,
                                public InProcessBrowserTest {
  protected:
   CryptotokenBrowserTest()
       : base::test::WithFeatureOverride(
-            extensions_features::kU2FSecurityKeyAPI) {}
+            extensions_features::kU2FSecurityKeyAPI) {
+#if defined(OS_WIN)
+    // Don't dispatch requests to the native Windows API.
+    scoped_feature_list_.InitAndDisableFeature(device::kWebAuthUseNativeWinApi);
+#endif
+  }
 
   void SetUpCommandLine(base::CommandLine* command_line) override {
     // The public key for the default privatey key used by the
@@ -73,13 +94,24 @@
 
   void TearDownOnMainThread() override { url_loader_interceptor_.reset(); }
 
+  // Returns the frame to use when attempting to connect to Cryptotoken in the
+  // methods below. Uses the main frame by default or
+  // |frame_to_use_for_connecting_| if a test overrides it.
+  content::RenderFrameHost* FrameToUseForConnecting() {
+    return frame_to_use_for_connecting_ ? frame_to_use_for_connecting_
+                                        : browser()
+                                              ->tab_strip_model()
+                                              ->GetActiveWebContents()
+                                              ->GetMainFrame();
+  }
+
   void ExpectChromeRuntimeIsUndefined() {
     const std::string script = base::StringPrintf(
         R"(let port = chrome.runtime.connect('%s',
               {});)",
         kCryptoTokenExtensionId);
-    const content::EvalJsResult result = content::EvalJs(
-        browser()->tab_strip_model()->GetActiveWebContents(), script);
+    const content::EvalJsResult result =
+        content::EvalJs(FrameToUseForConnecting(), script);
     EXPECT_THAT(
         result.error,
         testing::StartsWith("a JavaScript error:\nTypeError: Cannot read "
@@ -95,8 +127,8 @@
               });
       }))",
         kCryptoTokenExtensionId);
-    const content::EvalJsResult result = content::EvalJs(
-        browser()->tab_strip_model()->GetActiveWebContents(), script);
+    const content::EvalJsResult result =
+        content::EvalJs(FrameToUseForConnecting(), script);
     EXPECT_EQ(true, result);
   }
 
@@ -113,14 +145,89 @@
               });
       }))",
         kCryptoTokenExtensionId);
-    const content::EvalJsResult result = content::EvalJs(
-        browser()->tab_strip_model()->GetActiveWebContents(), script);
+    const content::EvalJsResult result =
+        content::EvalJs(FrameToUseForConnecting(), script);
     EXPECT_EQ("Could not establish connection. Receiving end does not exist.",
               result);
   }
 
+  // Indicates whether `ExpectSignSuccess()` should expect a U2F deprecation
+  // prompt to be shown.
+  enum class PromptExpectation {
+    kNoPrompt,
+    kShowPrompt,
+  };
+
+  void ExpectSignSuccess(const std::string& app_id,
+                         PromptExpectation prompt_expectation) {
+    content::WebContents* web_contents =
+        content::WebContents::FromRenderFrameHost(FrameToUseForConnecting());
+    if (prompt_expectation == PromptExpectation::kShowPrompt) {
+      // Automatically resolve permission prompts shown by Cryptotoken on the
+      // target frame.
+      permissions::PermissionRequestManager* request_manager =
+          permissions::PermissionRequestManager::FromWebContents(web_contents);
+      request_manager->set_auto_response_for_test(
+          permissions::PermissionRequestManager::DENY_ALL);
+    }
+
+    permissions::PermissionRequestObserver permission_request_observer(
+        web_contents);
+    const std::string script = base::StringPrintf(
+        R"(new Promise((resolve,reject) => {
+          chrome.runtime.sendMessage('%s',
+              {
+                type: 'u2f_sign_request',
+                appId: '%s',
+                challenge: 'aGVsbG8gd29ybGQ',
+                registeredKeys: [
+                  {
+                    version: 'U2F_V2',
+                    keyHandle: 'aGVsbG8gd29ybGQ',
+                    transports: ['usb'],
+                    appId: '%s',
+                  }
+                ],
+                timeoutSeconds: 3,
+                requestId: 1
+              },
+              (args) => {
+                  if (chrome.runtime.lastError !== undefined) {
+                    resolve('runtime error: ' + chrome.runtime.lastError);
+                    return;
+                  }
+                  if ('responseData' in args &&
+                      'errorCode' in args.responseData) {
+                    resolve('errorCode:'
+                            + args.responseData.errorCode
+                            + ",errorMessage:"
+                            + args.responseData.errorMessage);
+                    return;
+                  }
+                  reject(); // Requests can never succeed.
+              });
+      }))",
+        kCryptoTokenExtensionId, app_id.c_str(), app_id.c_str());
+    const content::EvalJsResult result =
+        content::EvalJs(FrameToUseForConnecting(), script);
+    if (prompt_expectation == PromptExpectation::kShowPrompt) {
+      // Denied prompt results in a DEVICE_INELIGIBLE error.
+      EXPECT_EQ("errorCode:4,errorMessage:The operation was not allowed",
+                result);
+      EXPECT_EQ(true, permission_request_observer.request_shown());
+    } else {
+      // Without a prompt, the request times out eventually. N.B. the
+      // `timeoutSeconds` parameter above needs to be high enough to allow time
+      // for the prompt to show in the kShowPrompt case, but not too high to
+      // cause test stalls and timeouts
+      EXPECT_EQ("errorCode:5,errorMessage:undefined", result);
+      EXPECT_EQ(false, permission_request_observer.request_shown());
+    }
+  }
+
   net::EmbeddedTestServer http_server_{net::EmbeddedTestServer::TYPE_HTTP};
   net::EmbeddedTestServer https_server_{net::EmbeddedTestServer::TYPE_HTTPS};
+  content::RenderFrameHost* frame_to_use_for_connecting_ = nullptr;
 
  private:
   // content::URLLoaderInterceptor callback
@@ -145,18 +252,24 @@
         "Origin-Trial: %s\n\n",
         kOriginTrialToken);
     content::URLLoaderInterceptor::WriteResponse(
-        headers, "<html><head></head><body>OK</body></html>",
+        headers,
+        "<html><head></head><body>OK\n"
+        "<iframe id=\"otIframe\"></iframe>"
+        "</body></html>",
         params->client.get());
     return true;
   }
 
+#if defined(OS_WIN)
+  base::test::ScopedFeatureList scoped_feature_list_;
+#endif
   std::unique_ptr<content::URLLoaderInterceptor> url_loader_interceptor_;
 };
 
 IN_PROC_BROWSER_TEST_P(CryptotokenBrowserTest, Connect) {
   // CryptoToken can only be connected to if the feature flag is enabled.
   ASSERT_TRUE(ui_test_utils::NavigateToURL(
-      browser(), https_server_.GetURL("a.test", "/empty.html")));
+      browser(), https_server_.GetURL(kNonOriginTrialDomain, "/empty.html")));
   if (IsParamFeatureEnabled()) {
     ExpectConnectSuccess();
   } else {
@@ -164,6 +277,17 @@
   }
 }
 
+IN_PROC_BROWSER_TEST_P(CryptotokenBrowserTest, SignShowsPrompt) {
+  if (!IsParamFeatureEnabled()) {
+    // Can't connect with the API disabled.
+    return;
+  }
+  GURL url = https_server_.GetURL(kNonOriginTrialDomain, "/empty.html");
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
+  std::string app_id = url::Origin::Create(url).Serialize();
+  ExpectSignSuccess(app_id, PromptExpectation::kShowPrompt);
+}
+
 IN_PROC_BROWSER_TEST_P(CryptotokenBrowserTest, ConnectWithOriginTrial) {
   // Connection succeeds regardless of feature flag state with the origin trial.
   ASSERT_TRUE(
@@ -171,20 +295,107 @@
   ExpectConnectSuccess();
 }
 
+IN_PROC_BROWSER_TEST_P(CryptotokenBrowserTest,
+                       SignWithOriginTrialDoesNotShowPrompt) {
+  GURL url = GURL(kOriginTrialOrigin);
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
+  std::string app_id = url::Origin::Create(url).Serialize();
+  ExpectSignSuccess(app_id, PromptExpectation::kNoPrompt);
+}
+
+IN_PROC_BROWSER_TEST_P(CryptotokenBrowserTest,
+                       ConnectWithOriginTrialInCrossOriginIframe) {
+  // Cross-origin iframe can connect with a trial token even if the parent frame
+  // is not enrolled in the trial.
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(
+      browser(), https_server_.GetURL(kNonOriginTrialDomain, "/iframe.html")));
+  content::WebContents* web_contents =
+      browser()->tab_strip_model()->GetActiveWebContents();
+  ASSERT_TRUE(content::NavigateIframeToURL(web_contents, "test",
+                                           GURL(kOriginTrialOrigin)));
+  frame_to_use_for_connecting_ = content::FrameMatchingPredicate(
+      web_contents->GetPrimaryPage(),
+      base::BindRepeating(&content::FrameIsChildOfMainFrame));
+  ExpectConnectSuccess();
+}
+
+IN_PROC_BROWSER_TEST_P(
+    CryptotokenBrowserTest,
+    SignWithOriginTrialInCrossOriginIframeDoesNotShowPrompt) {
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(
+      browser(), https_server_.GetURL(kNonOriginTrialDomain, "/iframe.html")));
+  content::WebContents* web_contents =
+      browser()->tab_strip_model()->GetActiveWebContents();
+  GURL url = GURL(kOriginTrialOrigin);
+  ASSERT_TRUE(content::NavigateIframeToURL(web_contents, "test", url));
+  frame_to_use_for_connecting_ = content::FrameMatchingPredicate(
+      web_contents->GetPrimaryPage(),
+      base::BindRepeating(&content::FrameIsChildOfMainFrame));
+
+  // Also make sure no permissions are showing on the main frame either.
+  permissions::PermissionRequestManager* request_manager =
+      permissions::PermissionRequestManager::FromWebContents(web_contents);
+  request_manager->set_auto_response_for_test(
+      permissions::PermissionRequestManager::DENY_ALL);
+  permissions::PermissionRequestObserver permission_request_observer(
+      web_contents);
+
+  std::string app_id = url::Origin::Create(url).Serialize();
+  ExpectSignSuccess(app_id, PromptExpectation::kNoPrompt);
+  EXPECT_FALSE(permission_request_observer.request_shown());
+}
+
+IN_PROC_BROWSER_TEST_P(CryptotokenBrowserTest,
+                       OriginTrailDoesNotAffectChildIframes) {
+  GURL parent_url = GURL(kOriginTrialOrigin);
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), parent_url));
+  content::WebContents* web_contents =
+      browser()->tab_strip_model()->GetActiveWebContents();
+  GURL child_url = https_server_.GetURL(kNonOriginTrialDomain, "/empty.html");
+  ASSERT_TRUE(
+      content::NavigateIframeToURL(web_contents, "otIframe", child_url));
+  frame_to_use_for_connecting_ = content::FrameMatchingPredicate(
+      web_contents->GetPrimaryPage(),
+      base::BindRepeating(&content::FrameIsChildOfMainFrame));
+
+  if (IsParamFeatureEnabled()) {
+    // With the feature flag enabled, the U2F API is active; but the OT that
+    // would suppress the prompt on the main frame, does not suppress the prompt
+    // on the child frame.
+    ExpectConnectSuccess();
+    std::string app_id = url::Origin::Create(child_url).Serialize();
+    ExpectSignSuccess(app_id, PromptExpectation::kShowPrompt);
+  } else {
+    // With the feature flag off, the U2F API is inactive on the child frame,
+    // even though it is enabled via OT on the parent frame.
+    ExpectConnectFailure();
+  }
+}
+
 IN_PROC_BROWSER_TEST_P(CryptotokenBrowserTest, ConnectWithEnterprisePolicy) {
   // Connection succeeds regardless of feature flag state with the enterprise
   // policy overriding deprecation changes.
   browser()->profile()->GetPrefs()->Set(
       extensions::pref_names::kU2fSecurityKeyApiEnabled, base::Value(true));
-  ASSERT_TRUE(
-      ui_test_utils::NavigateToURL(browser(), GURL(kOriginTrialOrigin)));
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(
+      browser(), https_server_.GetURL(kNonOriginTrialDomain, "/empty.html")));
   ExpectConnectSuccess();
 }
 
+IN_PROC_BROWSER_TEST_P(CryptotokenBrowserTest,
+                       SignWithEnterprisePolicyDoesNotShowPrompt) {
+  browser()->profile()->GetPrefs()->Set(
+      extensions::pref_names::kU2fSecurityKeyApiEnabled, base::Value(true));
+  GURL url = GURL(https_server_.GetURL(kNonOriginTrialDomain, "/empty.html"));
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
+  std::string app_id = url::Origin::Create(url).Serialize();
+  ExpectSignSuccess(app_id, PromptExpectation::kNoPrompt);
+}
+
 IN_PROC_BROWSER_TEST_P(CryptotokenBrowserTest, InsecureOriginCannotConnect) {
   // Connections from insecure origins always fail.
   ASSERT_TRUE(ui_test_utils::NavigateToURL(
-      browser(), http_server_.GetURL("a.test", "/empty.html")));
+      browser(), http_server_.GetURL(kNonOriginTrialDomain, "/empty.html")));
   ExpectChromeRuntimeIsUndefined();
 }
 
diff --git a/chrome/browser/extensions/api/declarative_net_request/declarative_net_request_unittest.cc b/chrome/browser/extensions/api/declarative_net_request/declarative_net_request_unittest.cc
index 42392bc4..45cf0bc 100644
--- a/chrome/browser/extensions/api/declarative_net_request/declarative_net_request_unittest.cc
+++ b/chrome/browser/extensions/api/declarative_net_request/declarative_net_request_unittest.cc
@@ -2249,10 +2249,7 @@
       disable_reason::DISABLE_BLOCKED_BY_POLICY, true);
 
   disable_extension_and_check_allocation(
-      disable_reason::DISABLE_REMOTELY_FOR_MALWARE, true);
-
-  disable_extension_and_check_allocation(
-      disable_reason::DISABLE_REMOTELY_FOR_MALWARE |
+      disable_reason::DISABLE_BLOCKED_BY_POLICY |
           disable_reason::DISABLE_USER_ACTION,
       true);
 
diff --git a/chrome/browser/extensions/api/messaging/native_message_port.cc b/chrome/browser/extensions/api/messaging/native_message_port.cc
index 2d3bd51..0ddc42a 100644
--- a/chrome/browser/extensions/api/messaging/native_message_port.cc
+++ b/chrome/browser/extensions/api/messaging/native_message_port.cc
@@ -13,6 +13,7 @@
 #include "chrome/browser/extensions/api/messaging/native_message_process_host.h"
 #include "content/public/browser/browser_thread.h"
 #include "extensions/common/api/messaging/message.h"
+#include "extensions/common/api/messaging/serialization_format.h"
 
 namespace extensions {
 
@@ -115,8 +116,11 @@
 void NativeMessagePort::PostMessageFromNativeHost(const std::string& message) {
   DCHECK(thread_checker_.CalledOnValidThread());
   if (weak_channel_delegate_) {
+    // Native messaging always uses JSON since a native host doesn't understand
+    // structured cloning serialization.
     weak_channel_delegate_->PostMessage(
-        port_id_, Message(message, false /* user_gesture */));
+        port_id_,
+        Message(message, SerializationFormat::kJson, false /* user_gesture */));
   }
 }
 
diff --git a/chrome/browser/extensions/api/messaging/native_messaging_launch_from_native.cc b/chrome/browser/extensions/api/messaging/native_messaging_launch_from_native.cc
index 1c0249b..498466b3 100644
--- a/chrome/browser/extensions/api/messaging/native_messaging_launch_from_native.cc
+++ b/chrome/browser/extensions/api/messaging/native_messaging_launch_from_native.cc
@@ -29,6 +29,7 @@
 #include "extensions/browser/event_router.h"
 #include "extensions/browser/extension_registry.h"
 #include "extensions/common/api/messaging/messaging_endpoint.h"
+#include "extensions/common/api/messaging/serialization_format.h"
 #include "extensions/common/permissions/permission_set.h"
 #include "extensions/common/permissions/permissions_data.h"
 
@@ -228,7 +229,8 @@
     return;
   }
   const extensions::PortId port_id(base::UnguessableToken::Create(),
-                                   1 /* port_number */, true /* is_opener */);
+                                   1 /* port_number */, true /* is_opener */,
+                                   extensions::SerializationFormat::kJson);
   extensions::MessageService* const message_service =
       extensions::MessageService::Get(profile);
   // TODO(crbug.com/967262): Apply policy for allow_user_level.
diff --git a/chrome/browser/extensions/extension_allowlist_unittest.cc b/chrome/browser/extensions/extension_allowlist_unittest.cc
index e262f4d5..600334c 100644
--- a/chrome/browser/extensions/extension_allowlist_unittest.cc
+++ b/chrome/browser/extensions/extension_allowlist_unittest.cc
@@ -149,8 +149,10 @@
   EXPECT_EQ(BitMapBlocklistState::BLOCKLISTED_MALWARE,
             blocklist_prefs::GetSafeBrowsingExtensionBlocklistState(
                 kExtensionId1, extension_prefs()));
-  EXPECT_EQ(disable_reason::DISABLE_REMOTELY_FOR_MALWARE |
-                disable_reason::DISABLE_NOT_ALLOWLISTED,
+  EXPECT_TRUE(blocklist_prefs::HasOmahaBlocklistState(
+      kExtensionId1, BitMapBlocklistState::BLOCKLISTED_MALWARE,
+      extension_prefs()));
+  EXPECT_EQ(disable_reason::DISABLE_NOT_ALLOWLISTED,
             extension_prefs()->GetDisableReasons(kExtensionId1));
   EXPECT_TRUE(IsBlocklisted(kExtensionId1));
 
@@ -164,7 +166,10 @@
   EXPECT_EQ(BitMapBlocklistState::BLOCKLISTED_MALWARE,
             blocklist_prefs::GetSafeBrowsingExtensionBlocklistState(
                 kExtensionId1, extension_prefs()));
-  EXPECT_EQ(disable_reason::DISABLE_REMOTELY_FOR_MALWARE,
+  EXPECT_TRUE(blocklist_prefs::HasOmahaBlocklistState(
+      kExtensionId1, BitMapBlocklistState::BLOCKLISTED_MALWARE,
+      extension_prefs()));
+  EXPECT_EQ(disable_reason::DISABLE_NONE,
             extension_prefs()->GetDisableReasons(kExtensionId1));
   EXPECT_TRUE(IsBlocklisted(kExtensionId1));
 
diff --git a/chrome/browser/extensions/extension_error_ui_default_unittest.cc b/chrome/browser/extensions/extension_error_ui_default_unittest.cc
index b1704103..43a7834 100644
--- a/chrome/browser/extensions/extension_error_ui_default_unittest.cc
+++ b/chrome/browser/extensions/extension_error_ui_default_unittest.cc
@@ -9,6 +9,7 @@
 #include "chrome/grit/generated_resources.h"
 #include "chrome/test/base/testing_profile.h"
 #include "content/public/test/browser_task_environment.h"
+#include "extensions/browser/blocklist_extension_prefs.h"
 #include "extensions/browser/disable_reason.h"
 #include "extensions/browser/extension_prefs.h"
 #include "extensions/browser/extension_system.h"
@@ -117,10 +118,9 @@
       extensions::ExtensionBuilder(
           "Bar", extensions::ExtensionBuilder::Type::PLATFORM_APP)
           .Build();
-  extensions::ExtensionPrefs::Get(delegate.GetContext())
-      ->AddDisableReason(
-          extension->id(),
-          extensions::disable_reason::DISABLE_REMOTELY_FOR_MALWARE);
+  extensions::blocklist_prefs::AddOmahaBlocklistState(
+      extension->id(), extensions::BitMapBlocklistState::BLOCKLISTED_MALWARE,
+      extensions::ExtensionPrefs::Get(delegate.GetContext()));
   delegate.InsertForbidden(extension);
 
   extensions::ExtensionErrorUIDefault ui(&delegate);
diff --git a/chrome/browser/extensions/extension_prefs_unittest.cc b/chrome/browser/extensions/extension_prefs_unittest.cc
index 9874822a..97ae419 100644
--- a/chrome/browser/extensions/extension_prefs_unittest.cc
+++ b/chrome/browser/extensions/extension_prefs_unittest.cc
@@ -28,6 +28,8 @@
 #include "content/public/browser/notification_source.h"
 #include "content/public/test/mock_notification_observer.h"
 #include "extensions/browser/blocklist_extension_prefs.h"
+#include "extensions/browser/blocklist_state.h"
+#include "extensions/browser/disable_reason.h"
 #include "extensions/browser/extension_pref_value_map.h"
 #include "extensions/browser/extension_prefs.h"
 #include "extensions/browser/install_flag.h"
@@ -1125,6 +1127,13 @@
     // The pref is not migrated to the new pref yet.
     EXPECT_FALSE(prefs()->IsBlocklistedExtensionAcknowledged(extension_->id()));
 
+    prefs()->SetExtensionDisabled(
+        extension_->id(),
+        disable_reason::DEPRECATED_DISABLE_REMOTELY_FOR_MALWARE);
+    // The pref is not migrated to the new pref yet.
+    EXPECT_FALSE(blocklist_prefs::HasOmahaBlocklistState(
+        extension_->id(), BitMapBlocklistState::BLOCKLISTED_MALWARE, prefs()));
+
     prefs()->MigrateOldBlocklistPrefs();
   }
 
@@ -1144,6 +1153,13 @@
     // The old pref should be cleared.
     EXPECT_FALSE(is_blocklist_acknowledged_pref);
     EXPECT_TRUE(prefs()->IsBlocklistedExtensionAcknowledged(extension_->id()));
+
+    EXPECT_TRUE(blocklist_prefs::HasOmahaBlocklistState(
+        extension_->id(), BitMapBlocklistState::BLOCKLISTED_MALWARE, prefs()));
+    // The old pref should be cleared.
+    EXPECT_FALSE(prefs()->HasDisableReason(
+        extension_->id(),
+        disable_reason::DEPRECATED_DISABLE_REMOTELY_FOR_MALWARE));
   }
 
  private:
diff --git a/chrome/browser/extensions/extension_service.cc b/chrome/browser/extensions/extension_service.cc
index a2ea327..04d17c22 100644
--- a/chrome/browser/extensions/extension_service.cc
+++ b/chrome/browser/extensions/extension_service.cc
@@ -875,8 +875,9 @@
     return;
   }
 
-  if (extension_prefs_->HasDisableReason(
-          extension_id, disable_reason::DISABLE_REMOTELY_FOR_MALWARE)) {
+  if (blocklist_prefs::HasOmahaBlocklistState(
+          extension_id, BitMapBlocklistState::BLOCKLISTED_MALWARE,
+          extension_prefs_)) {
     // The extension is already disabled. No work needs to be done.
     return;
   }
@@ -888,8 +889,9 @@
   // Add the extension to the blocklisted extensions set.
   UpdateBlocklistedExtensions({extension_id},
                               registry_->blocklisted_extensions().GetIDs());
-  extension_prefs_->AddDisableReason(
-      extension_id, disable_reason::DISABLE_REMOTELY_FOR_MALWARE);
+  blocklist_prefs::AddOmahaBlocklistState(
+      extension_id, BitMapBlocklistState::BLOCKLISTED_MALWARE,
+      extension_prefs_);
   // Show an error for the newly blocklisted extension.
   error_controller_->ShowErrorIfNeeded();
 }
@@ -897,12 +899,15 @@
 void ExtensionService::MaybeEnableRemotelyDisabledExtension(
     const std::string& extension_id) {
   CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
-  int disable_reasons = extension_prefs_->GetDisableReasons(extension_id);
-  if ((disable_reasons & disable_reason::DISABLE_REMOTELY_FOR_MALWARE) == 0)
+  if (!blocklist_prefs::HasOmahaBlocklistState(
+          extension_id, BitMapBlocklistState::BLOCKLISTED_MALWARE,
+          extension_prefs_)) {
     return;
+  }
 
-  extension_prefs_->RemoveDisableReason(
-      extension_id, disable_reason::DISABLE_REMOTELY_FOR_MALWARE);
+  blocklist_prefs::RemoveOmahaBlocklistState(
+      extension_id, BitMapBlocklistState::BLOCKLISTED_MALWARE,
+      extension_prefs_);
 
   ExtensionIdSet unchanged = registry_->blocklisted_extensions().GetIDs();
   DCHECK(base::Contains(unchanged, extension_id));
@@ -2217,8 +2222,9 @@
   // to |unchanged| set.
   for (const auto& extension_id :
        registry_->blocklisted_extensions().GetIDs()) {
-    if (extension_prefs_->HasDisableReason(
-            extension_id, disable_reason::DISABLE_REMOTELY_FOR_MALWARE)) {
+    if (blocklist_prefs::HasOmahaBlocklistState(
+            extension_id, BitMapBlocklistState::BLOCKLISTED_MALWARE,
+            extension_prefs_)) {
       unchanged.insert(extension_id);
     }
   }
diff --git a/chrome/browser/extensions/extension_service_unittest.cc b/chrome/browser/extensions/extension_service_unittest.cc
index 5cbc294b..42160dc 100644
--- a/chrome/browser/extensions/extension_service_unittest.cc
+++ b/chrome/browser/extensions/extension_service_unittest.cc
@@ -3525,8 +3525,8 @@
 
   ExtensionPrefs* prefs = ExtensionPrefs::Get(profile());
   service()->PerformActionBasedOnOmahaAttributes(good0, attributes);
-  EXPECT_EQ(disable_reason::DISABLE_REMOTELY_FOR_MALWARE,
-            prefs->GetDisableReasons(good0));
+  EXPECT_TRUE(blocklist_prefs::HasOmahaBlocklistState(
+      good0, BitMapBlocklistState::BLOCKLISTED_MALWARE, prefs));
   EXPECT_TRUE(DoesIntegerPrefExist(good0, kPrefBlocklistState));
   EXPECT_FALSE(registry()->enabled_extensions().Contains(good0));
   EXPECT_TRUE(registry()->blocklisted_extensions().Contains(good0));
@@ -3535,9 +3535,9 @@
   test_blocklist.Clear(false);
   task_environment()->RunUntilIdle();
 
-  // If the extension has a DISABLE_REMOTELY_FOR_MALWARE disable reason,
-  // the extension should still not be enabled even if it's no on the
-  // SB blocklist. This disable reason needs to be removed prior to
+  // If the extension has a BLOCKLISTED_MALWARE state in the Omaha blocklist
+  // pref, the extension should still not be enabled even if it's not on the SB
+  // blocklist. This state needs to be removed prior to
   // unblocklisting/re-enabling.
   EXPECT_FALSE(registry()->enabled_extensions().Contains(good0));
   EXPECT_TRUE(registry()->blocklisted_extensions().Contains(good0));
@@ -4738,8 +4738,8 @@
 
   ExtensionPrefs* prefs = ExtensionPrefs::Get(profile());
   service()->PerformActionBasedOnOmahaAttributes(good_crx, attributes);
-  EXPECT_EQ(disable_reason::DISABLE_REMOTELY_FOR_MALWARE,
-            prefs->GetDisableReasons(good_crx));
+  EXPECT_TRUE(blocklist_prefs::HasOmahaBlocklistState(
+      good_crx, BitMapBlocklistState::BLOCKLISTED_MALWARE, prefs));
   EXPECT_TRUE(blocklist_prefs::IsExtensionBlocklisted(good_crx, prefs));
 
   attributes.SetKey("_malware", base::Value(false));
@@ -4757,19 +4757,19 @@
   InstallCRX(data_dir().AppendASCII("good.crx"), INSTALL_NEW);
   EXPECT_TRUE(registry()->enabled_extensions().GetByID(good_crx));
 
+  base::Value attributes(base::Value::Type::DICTIONARY);
+  attributes.SetKey("_malware", base::Value(true));
   ExtensionPrefs* prefs = ExtensionPrefs::Get(profile());
-  service()->DisableExtension(good_crx,
-                              disable_reason::DISABLE_REMOTELY_FOR_MALWARE |
-                                  disable_reason::DISABLE_USER_ACTION);
+  service()->DisableExtension(good_crx, disable_reason::DISABLE_USER_ACTION);
   EXPECT_TRUE(registry()->disabled_extensions().GetByID(good_crx));
-  service()->BlocklistExtensionForTest(good_crx);
+  service()->PerformActionBasedOnOmahaAttributes(good_crx, attributes);
   EXPECT_TRUE(blocklist_prefs::IsExtensionBlocklisted(good_crx, prefs));
 
-  base::Value empty_attr(base::Value::Type::DICTIONARY);
-  service()->PerformActionBasedOnOmahaAttributes(good_crx, empty_attr);
+  attributes.SetKey("_malware", base::Value(false));
+  service()->PerformActionBasedOnOmahaAttributes(good_crx, attributes);
   EXPECT_TRUE(registry()->disabled_extensions().GetByID(good_crx));
-  EXPECT_FALSE(prefs->GetDisableReasons(good_crx) &
-               disable_reason::DISABLE_REMOTELY_FOR_MALWARE);
+  EXPECT_FALSE(blocklist_prefs::HasOmahaBlocklistState(
+      good_crx, BitMapBlocklistState::BLOCKLISTED_MALWARE, prefs));
   EXPECT_FALSE(blocklist_prefs::IsExtensionBlocklisted(good_crx, prefs));
 }
 
@@ -4787,11 +4787,6 @@
   EXPECT_TRUE(blocklist_prefs::IsExtensionBlocklisted(good0, prefs));
   EXPECT_TRUE(blocklist_prefs::IsExtensionBlocklisted(good1, prefs));
 
-  // Test that a disable reason can be added to a blocklisted extension.
-  prefs->AddDisableReason(good0, disable_reason::DISABLE_REMOTELY_FOR_MALWARE);
-  EXPECT_TRUE(prefs->HasDisableReason(
-      good0, disable_reason::DISABLE_REMOTELY_FOR_MALWARE));
-
   // Test that a blocklisted extension can be disabled.
   service()->DisableExtension(good1, disable_reason::DISABLE_BLOCKED_BY_POLICY);
   EXPECT_TRUE(prefs->HasDisableReason(
diff --git a/chrome/browser/extensions/omaha_attributes_handler_unittest.cc b/chrome/browser/extensions/omaha_attributes_handler_unittest.cc
index 06e4c227..c30adcd 100644
--- a/chrome/browser/extensions/omaha_attributes_handler_unittest.cc
+++ b/chrome/browser/extensions/omaha_attributes_handler_unittest.cc
@@ -210,8 +210,9 @@
 
   ExtensionPrefs* prefs = ExtensionPrefs::Get(profile());
   EXPECT_TRUE(state_tester.ExpectBlocklisted(kTestExtensionId));
-  EXPECT_EQ(disable_reason::DISABLE_REMOTELY_FOR_MALWARE |
-                disable_reason::DISABLE_GREYLIST,
+  EXPECT_TRUE(blocklist_prefs::HasOmahaBlocklistState(
+      kTestExtensionId, BitMapBlocklistState::BLOCKLISTED_MALWARE, prefs));
+  EXPECT_EQ(disable_reason::DISABLE_GREYLIST,
             prefs->GetDisableReasons(kTestExtensionId));
 
   // Remove malware.
diff --git a/chrome/browser/extensions/preinstalled_apps_browsertest.cc b/chrome/browser/extensions/preinstalled_apps_browsertest.cc
index 313f0791..d3df4bb 100644
--- a/chrome/browser/extensions/preinstalled_apps_browsertest.cc
+++ b/chrome/browser/extensions/preinstalled_apps_browsertest.cc
@@ -22,6 +22,7 @@
 #include "chrome/browser/web_applications/preinstalled_web_app_utils.h"
 #include "chrome/browser/web_applications/test/fake_os_integration_manager.h"
 #include "chrome/browser/web_applications/test/fake_web_app_provider.h"
+#include "chrome/browser/web_applications/test/web_app_install_test_utils.h"
 #include "chrome/browser/web_applications/web_app.h"
 #include "chrome/browser/web_applications/web_app_constants.h"
 #include "chrome/browser/web_applications/web_app_helpers.h"
@@ -185,6 +186,8 @@
   // We override this to also wait for the PreinstalledWebAppManager.
   void WaitForSystemReady() override {
     PreinstalledAppsBrowserTest::WaitForSystemReady();
+    web_app::test::WaitUntilReady(
+        web_app::WebAppProvider::GetForTest(browser()->profile()));
 
     // For web app migration tests, we want to set up extension app shortcut
     // locations to test that they are preserved.
diff --git a/chrome/browser/extensions/updater/extension_updater.cc b/chrome/browser/extensions/updater/extension_updater.cc
index 55514b8..01737b3c 100644
--- a/chrome/browser/extensions/updater/extension_updater.cc
+++ b/chrome/browser/extensions/updater/extension_updater.cc
@@ -37,6 +37,7 @@
 #include "content/public/browser/notification_service.h"
 #include "content/public/browser/notification_source.h"
 #include "crypto/sha2.h"
+#include "extensions/browser/blocklist_extension_prefs.h"
 #include "extensions/browser/extension_file_task_runner.h"
 #include "extensions/browser/extension_prefs.h"
 #include "extensions/browser/extension_registry.h"
@@ -448,9 +449,11 @@
                     params.fetch_priority, &update_check_params);
     ExtensionSet remotely_disabled_extensions;
     for (auto extension : registry_->blocklisted_extensions()) {
-      if (extension_prefs_->HasDisableReason(
-              extension->id(), disable_reason::DISABLE_REMOTELY_FOR_MALWARE))
+      if (blocklist_prefs::HasOmahaBlocklistState(
+              extension->id(), BitMapBlocklistState::BLOCKLISTED_MALWARE,
+              extension_prefs_)) {
         remotely_disabled_extensions.Insert(extension);
+      }
     }
     AddToDownloader(&remotely_disabled_extensions, pending_ids, request_id,
                     params.fetch_priority, &update_check_params);
diff --git a/chrome/browser/extensions/updater/extension_updater_unittest.cc b/chrome/browser/extensions/updater/extension_updater_unittest.cc
index 96755be..fe91ec3 100644
--- a/chrome/browser/extensions/updater/extension_updater_unittest.cc
+++ b/chrome/browser/extensions/updater/extension_updater_unittest.cc
@@ -2682,8 +2682,9 @@
   blocklist_prefs::SetSafeBrowsingExtensionBlocklistState(
       remotely_blocklisted_id, BitMapBlocklistState::BLOCKLISTED_MALWARE,
       service.extension_prefs());
-  service.extension_prefs()->AddDisableReason(
-      remotely_blocklisted_id, disable_reason::DISABLE_REMOTELY_FOR_MALWARE);
+  blocklist_prefs::AddOmahaBlocklistState(
+      remotely_blocklisted_id, BitMapBlocklistState::BLOCKLISTED_MALWARE,
+      service.extension_prefs());
 
   // We expect that both enabled and remotely blocklisted extensions are
   // auto-updated.
diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browser/flags/android/chrome_feature_list.cc
index 6a17bf2..aa66710 100644
--- a/chrome/browser/flags/android/chrome_feature_list.cc
+++ b/chrome/browser/flags/android/chrome_feature_list.cc
@@ -199,7 +199,6 @@
     &kChromeSurveyNextAndroid,
     &kCommandLineOnNonRooted,
     &kConditionalTabStripAndroid,
-    &kContentSuggestionsScrollToLoad,
     &kContextMenuEnableLensShoppingAllowlist,
     &kContextMenuGoogleLensChip,
     &kContextMenuSearchWithGoogleLens,
@@ -501,9 +500,6 @@
 const base::Feature kCommandLineOnNonRooted{"CommandLineOnNonRooted",
                                             base::FEATURE_DISABLED_BY_DEFAULT};
 
-const base::Feature kContentSuggestionsScrollToLoad{
-    "ContentSuggestionsScrollToLoad", base::FEATURE_DISABLED_BY_DEFAULT};
-
 const base::Feature kContextMenuEnableLensShoppingAllowlist{
     "ContextMenuEnableLensShoppingAllowlist",
     base::FEATURE_DISABLED_BY_DEFAULT};
diff --git a/chrome/browser/flags/android/chrome_feature_list.h b/chrome/browser/flags/android/chrome_feature_list.h
index 96fafc2..9b30c2d 100644
--- a/chrome/browser/flags/android/chrome_feature_list.h
+++ b/chrome/browser/flags/android/chrome_feature_list.h
@@ -61,7 +61,6 @@
 extern const base::Feature kContextMenuShopWithGoogleLens;
 extern const base::Feature kContextMenuSearchAndShopWithGoogleLens;
 extern const base::Feature kContextMenuTranslateWithGoogleLens;
-extern const base::Feature kContentSuggestionsScrollToLoad;
 extern const base::Feature kContextualSearchDebug;
 extern const base::Feature kContextualSearchForceCaption;
 extern const base::Feature kContextualSearchLegacyHttpPolicy;
diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
index c44ba310..2bd063b7 100644
--- a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
+++ b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
@@ -276,8 +276,6 @@
     public static final String COMMERCE_PRICE_TRACKING = "CommercePriceTracking";
     public static final String CONDITIONAL_TAB_STRIP_ANDROID = "ConditionalTabStripAndroid";
     public static final String CONTACTS_PICKER_SELECT_ALL = "ContactsPickerSelectAll";
-    public static final String CONTENT_SUGGESTIONS_SCROLL_TO_LOAD =
-            "ContentSuggestionsScrollToLoad";
     public static final String CONTEXT_MENU_ENABLE_LENS_SHOPPING_ALLOWLIST =
             "ContextMenuEnableLensShoppingAllowlist";
     public static final String CONTEXT_MENU_GOOGLE_LENS_CHIP = "ContextMenuGoogleLensChip";
diff --git a/chrome/browser/lacros/lacros_prefs.cc b/chrome/browser/lacros/lacros_prefs.cc
index e3b7e9f8..61ca315 100644
--- a/chrome/browser/lacros/lacros_prefs.cc
+++ b/chrome/browser/lacros/lacros_prefs.cc
@@ -23,6 +23,11 @@
   // browser settings. It could reasonably move to a browser-specific
   // location with suitable #ifdefs.
   registry->RegisterBooleanPref(::prefs::kSettingsShowOSBanner, true);
+
+  // The following two prefs are used in imageWriterPrivate extension api
+  // implementation code, which are supported in Lacros.
+  registry->RegisterBooleanPref(::prefs::kExternalStorageDisabled, false);
+  registry->RegisterBooleanPref(::prefs::kExternalStorageReadOnly, false);
 }
 
 }  // namespace lacros_prefs
diff --git a/chrome/browser/media/router/discovery/dial/dial_service_unittest.cc b/chrome/browser/media/router/discovery/dial/dial_service_unittest.cc
index e889484..33af47ba 100644
--- a/chrome/browser/media/router/discovery/dial/dial_service_unittest.cc
+++ b/chrome/browser/media/router/discovery/dial/dial_service_unittest.cc
@@ -17,7 +17,6 @@
 #include "net/base/ip_endpoint.h"
 #include "net/base/net_errors.h"
 #include "net/base/network_interfaces.h"
-#include "net/log/test_net_log.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -55,14 +54,13 @@
   DialServiceTest()
       : task_environment_(content::BrowserTaskEnvironment::IO_MAINLOOP),
         mock_ip_(net::IPAddress::IPv4AllZeros()),
-        dial_service_(&test_net_log_) {
+        dial_service_(net::NetLog::Get()) {
     dial_service_.AddObserver(&mock_observer_);
     dial_socket_ = dial_service_.CreateDialSocket();
   }
 
  protected:
   content::BrowserTaskEnvironment task_environment_;
-  net::RecordingTestNetLog test_net_log_;
   net::IPAddress mock_ip_;
   DialServiceImpl dial_service_;
   std::unique_ptr<DialServiceImpl::DialSocket> dial_socket_;
diff --git a/chrome/browser/media/webrtc/webrtc_getdisplaymedia_browsertest.cc b/chrome/browser/media/webrtc/webrtc_getdisplaymedia_browsertest.cc
index 9588b01..8839177 100644
--- a/chrome/browser/media/webrtc/webrtc_getdisplaymedia_browsertest.cc
+++ b/chrome/browser/media/webrtc/webrtc_getdisplaymedia_browsertest.cc
@@ -5,6 +5,7 @@
 #include <string>
 
 #include "base/path_service.h"
+#include "base/strings/strcat.h"
 #include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
 #include "build/build_config.h"
@@ -40,6 +41,8 @@
 static const char kMainHtmlFileName[] = "webrtc_getdisplaymedia_test.html";
 static const char kSameOriginRenamedTitle[] = "Renamed Same Origin Tab";
 
+enum class DisplaySurfaceType { kTab, kWindow, kScreen };
+
 enum class GetDisplayMediaVariant : int {
   kStandard = 0,
   kPreferCurrentTab = 1
@@ -61,6 +64,20 @@
 
 constexpr char kAppWindowTitle[] = "AppWindow Display Capture Test";
 
+std::string DisplaySurfaceTypeAsString(
+    DisplaySurfaceType display_surface_type) {
+  switch (display_surface_type) {
+    case DisplaySurfaceType::kTab:
+      return "browser";
+    case DisplaySurfaceType::kWindow:
+      return "window";
+    case DisplaySurfaceType::kScreen:
+      return "screen";
+  }
+  NOTREACHED();
+  return "error";
+}
+
 void RunGetDisplayMedia(content::WebContents* tab,
                         const std::string& constraints,
                         bool is_fake_ui,
@@ -594,3 +611,169 @@
       &result));
   EXPECT_EQ(result, "live");
 }
+
+class GetDisplayMediaVideoTrackBrowserTest
+    : public WebRtcTestBase,
+      public testing::WithParamInterface<
+          std::tuple<bool, bool, DisplaySurfaceType>> {
+ public:
+  GetDisplayMediaVideoTrackBrowserTest()
+      : conditional_focus_enabled_(std::get<0>(GetParam())),
+        region_capture_enabled_(std::get<1>(GetParam())),
+        display_surface_type_(std::get<2>(GetParam())) {}
+
+  ~GetDisplayMediaVideoTrackBrowserTest() override = default;
+
+  void SetUpInProcessBrowserTestFixture() override {
+    DetectErrorsInJavaScript();
+  }
+
+  void SetUpOnMainThread() override {
+    WebRtcTestBase::SetUpOnMainThread();
+
+    ASSERT_TRUE(embedded_test_server()->Start());
+
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+    // The picker itself shows previews which are unsupported in Lacros tests.
+    base::Value matchlist(base::Value::Type::LIST);
+    matchlist.Append("*");
+    browser()->profile()->GetPrefs()->Set(prefs::kTabCaptureAllowedByOrigins,
+                                          matchlist);
+#endif
+  }
+
+  // Unlike SetUp(), this is called from the test body. This allows skipping
+  // this test for (platform, test-case) combinations which are not supported.
+  void SetupTest() {
+    // Fire up the page.
+    tab_ = OpenTestPageInNewTab(kMainHtmlPage);
+
+    // Initiate the capture.
+    std::string result;
+    ASSERT_TRUE(content::ExecuteScriptAndExtractString(
+        tab_->GetMainFrame(),
+        "runGetDisplayMedia({video: true, audio: true}, "
+        "\"top-level-document\");",
+        &result));
+    ASSERT_EQ(result, "capture-success");
+  }
+
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    WebRtcTestBase::SetUpCommandLine(command_line);
+
+    std::vector<std::string> enabled_blink_features;
+    if (conditional_focus_enabled_) {
+      enabled_blink_features.push_back("ConditionalFocus");
+    }
+    if (region_capture_enabled_) {
+      enabled_blink_features.push_back("RegionCapture");
+    }
+    if (!enabled_blink_features.empty()) {
+      command_line->AppendSwitchASCII(
+          switches::kEnableBlinkFeatures,
+          base::JoinString(enabled_blink_features, ","));
+    }
+
+    command_line->AppendSwitch(switches::kUseFakeUIForMediaStream);
+    command_line->AppendSwitchASCII(
+        switches::kUseFakeDeviceForMediaStream,
+        base::StrCat({"display-media-type=",
+                      DisplaySurfaceTypeAsString(display_surface_type_)}));
+  }
+
+  std::string GetVideoTrackType() {
+    std::string result;
+    EXPECT_TRUE(content::ExecuteScriptAndExtractString(
+        tab_->GetMainFrame(), "getVideoTrackType();", &result));
+    return result;
+  }
+
+  std::string GetVideoCloneTrackType() {
+    std::string result;
+    EXPECT_TRUE(content::ExecuteScriptAndExtractString(
+        tab_->GetMainFrame(), "getVideoCloneTrackType();", &result));
+    return result;
+  }
+
+  bool HasAudioTrack() {
+    std::string result;
+    EXPECT_TRUE(content::ExecuteScriptAndExtractString(
+        tab_->GetMainFrame(), "hasAudioTrack();", &result));
+    EXPECT_TRUE(result == "true" || result == "false");
+    return result == "true";
+  }
+
+  std::string GetAudioTrackType() {
+    std::string result;
+    EXPECT_TRUE(content::ExecuteScriptAndExtractString(
+        tab_->GetMainFrame(), "getAudioTrackType();", &result));
+    return result;
+  }
+
+  std::string ExpectedVideoTrackType() const {
+    switch (display_surface_type_) {
+      case DisplaySurfaceType::kTab:
+        return region_capture_enabled_
+                   ? "BrowserCaptureMediaStreamTrack"
+                   : conditional_focus_enabled_ ? "FocusableMediaStreamTrack"
+                                                : "MediaStreamTrack";
+      case DisplaySurfaceType::kWindow:
+        return conditional_focus_enabled_ || region_capture_enabled_
+                   ? "FocusableMediaStreamTrack"
+                   : "MediaStreamTrack";
+      case DisplaySurfaceType::kScreen:
+        return "MediaStreamTrack";
+    }
+    NOTREACHED();
+    return "Error";
+  }
+
+ protected:
+  const bool conditional_focus_enabled_;
+  const bool region_capture_enabled_;
+  const DisplaySurfaceType display_surface_type_;
+
+ private:
+  content::WebContents* tab_ = nullptr;
+};
+
+INSTANTIATE_TEST_SUITE_P(
+    _,
+    GetDisplayMediaVideoTrackBrowserTest,
+    testing::Combine(/*conditional_focus_enabled=*/testing::Bool(),
+                     /*region_capture_enabled=*/testing::Bool(),
+                     /*display_surface_type=*/
+                     testing::Values(DisplaySurfaceType::kTab,
+                                     DisplaySurfaceType::kWindow,
+                                     DisplaySurfaceType::kScreen)),
+    [](const testing::TestParamInfo<
+        GetDisplayMediaVideoTrackBrowserTest::ParamType>& info) {
+      return base::StrCat(
+          {std::get<0>(info.param) ? "ConditionalFocus" : "",
+           std::get<1>(info.param) ? "RegionCapture" : "",
+           std::get<2>(info.param) == DisplaySurfaceType::kTab
+               ? "Tab"
+               : std::get<2>(info.param) == DisplaySurfaceType::kWindow
+                     ? "Window"
+                     : "Screen"});
+    });
+
+// Normally, each of these these would have its own test, but the number of
+// combinations and the setup time for browser-tests make this undesirable,
+// especially given the simplicity of each of these tests.
+// After both (a) Conditional Focus and (b) Region Capture ship, this can
+// simpplified to three non-parameterized tests (tab/window/screen).
+IN_PROC_BROWSER_TEST_P(GetDisplayMediaVideoTrackBrowserTest, RunCombinedTest) {
+  SetupTest();
+
+  // Test #1: The video track is of the expected type.
+  EXPECT_EQ(GetVideoTrackType(), ExpectedVideoTrackType());
+
+  // Test #2: Video clones are of the same type as the original.
+  EXPECT_EQ(GetVideoTrackType(), GetVideoCloneTrackType());
+
+  // Test #3: Audio tracks are all simply MediaStreamTrack.
+  if (HasAudioTrack()) {
+    EXPECT_EQ(GetAudioTrackType(), "MediaStreamTrack");
+  }
+}
diff --git a/chrome/browser/metrics/extensions_metrics_provider.cc b/chrome/browser/metrics/extensions_metrics_provider.cc
index 8e0fcaf..e3367268 100644
--- a/chrome/browser/metrics/extensions_metrics_provider.cc
+++ b/chrome/browser/metrics/extensions_metrics_provider.cc
@@ -254,8 +254,6 @@
        ExtensionInstallProto::CUSTODIAN_APPROVAL_REQUIRED},
       {extensions::disable_reason::DISABLE_BLOCKED_BY_POLICY,
        ExtensionInstallProto::BLOCKED_BY_POLICY},
-      {extensions::disable_reason::DISABLE_REMOTELY_FOR_MALWARE,
-       ExtensionInstallProto::DISABLE_REMOTELY_FOR_MALWARE},
       {extensions::disable_reason::DISABLE_REINSTALL,
        ExtensionInstallProto::REINSTALL},
       {extensions::disable_reason::DISABLE_NOT_ALLOWLISTED,
diff --git a/chrome/browser/metrics/extensions_metrics_provider_unittest.cc b/chrome/browser/metrics/extensions_metrics_provider_unittest.cc
index 3da5a04..e8a2989 100644
--- a/chrome/browser/metrics/extensions_metrics_provider_unittest.cc
+++ b/chrome/browser/metrics/extensions_metrics_provider_unittest.cc
@@ -319,17 +319,6 @@
       EXPECT_EQ(ExtensionInstallProto::CORRUPTED,
                 install.disable_reasons().Get(1));
     }
-    // Adding additional disable reasons should result in all reasons being
-    // reported.
-    prefs()->AddDisableReason(
-        extension->id(),
-        extensions::disable_reason::DISABLE_REMOTELY_FOR_MALWARE);
-    {
-      ExtensionInstallProto install = ConstructProto(*extension);
-      ASSERT_EQ(3, install.disable_reasons_size());
-      EXPECT_EQ(ExtensionInstallProto::DISABLE_REMOTELY_FOR_MALWARE,
-                install.disable_reasons().Get(2));
-    }
   }
 
   {
diff --git a/chrome/browser/optimization_guide/prediction/prediction_model_download_manager.cc b/chrome/browser/optimization_guide/prediction/prediction_model_download_manager.cc
index aec30c4..f893e4e9 100644
--- a/chrome/browser/optimization_guide/prediction/prediction_model_download_manager.cc
+++ b/chrome/browser/optimization_guide/prediction/prediction_model_download_manager.cc
@@ -403,7 +403,7 @@
 
     // ReplaceFile failed, log the error code and attempt to utilize base::Move
     // instead as the file could be on a different storage partition.
-    UMA_HISTOGRAM_ENUMERATION(
+    base::UmaHistogramExactLinear(
         "OptimizationGuide.PredictionModelDownloadManager.ReplaceFileError." +
             GetStringNameForOptimizationTarget(
                 model_info.optimization_target()),
diff --git a/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java b/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java
index 844735c..581f62d 100644
--- a/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java
+++ b/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java
@@ -862,9 +862,11 @@
     public static final String SIGNIN_ACCOUNT_RENAME_EVENT_INDEX =
             "prefs_sync_account_rename_event_index";
 
-    /** SigninPromo Show Count preference. */
-    public static final KeyPrefix SIGNIN_PROMO_SHOW_COUNT =
-            new KeyPrefix("Chrome.SigninPromo.ShowCount.*");
+    /** SyncPromo Show Count preference. */
+    public static final KeyPrefix SYNC_PROMO_SHOW_COUNT =
+            new KeyPrefix("Chrome.SyncPromo.ShowCount.*");
+    /** SyncPromo total shown count preference across all access points. */
+    public static final String SYNC_PROMO_TOTAL_SHOW_COUNT = "Chrome.SyncPromo.TotalShowCount";
 
     /**
      * Generic signin and sync promo preferences.
@@ -1127,9 +1129,10 @@
                 SHARING_LAST_SHARED_COMPONENT_NAME,
                 SHOW_START_SEGMENTATION_RESULT,
                 SIGNIN_PROMO_IMPRESSIONS_COUNT_NTP,
-                SIGNIN_PROMO_SHOW_COUNT.pattern(),
+                SYNC_PROMO_SHOW_COUNT.pattern(),
                 SIGNIN_PROMO_NTP_FIRST_SHOWN_TIME,
                 SIGNIN_PROMO_NTP_LAST_SHOWN_TIME,
+                SYNC_PROMO_TOTAL_SHOW_COUNT,
                 START_NEXT_SHOW_ON_STARTUP_DECISION_MS,
                 START_SHOW_ON_STARTUP,
                 TAP_FEED_CARDS_COUNT,
diff --git a/chrome/browser/resources/cryptotoken/enroller.js b/chrome/browser/resources/cryptotoken/enroller.js
index 2cd41ac0..ed75e75 100644
--- a/chrome/browser/resources/cryptotoken/enroller.js
+++ b/chrome/browser/resources/cryptotoken/enroller.js
@@ -417,6 +417,7 @@
             {
               'appId': appId,
               'tabId': messageSender.tab.id,
+              'frameId': 0,  // ignored
               'origin': sender.origin,
             },
             resolve);
@@ -897,7 +898,12 @@
 Enroller.prototype.checkU2fApiPermission_ = function(
     appId, challenge, request, attestationMode) {
   chrome.cryptotokenPrivate.canMakeU2fApiRequest(
-      {tabId: this.sender_.tabId, origin: this.sender_.origin, appId: appId},
+      {
+        tabId: this.sender_.tabId,
+        frameId: this.sender_.frameId,
+        origin: this.sender_.origin,
+        appId: appId
+      },
       (result) => {
         if (!result) {
           this.notifyError_({
diff --git a/chrome/browser/resources/cryptotoken/signer.js b/chrome/browser/resources/cryptotoken/signer.js
index 692a8aac..5921de98c 100644
--- a/chrome/browser/resources/cryptotoken/signer.js
+++ b/chrome/browser/resources/cryptotoken/signer.js
@@ -510,7 +510,12 @@
     this.doSignWebAuthnContinue_(decodedChallenge, credentialList, appid);
   } else {
     chrome.cryptotokenPrivate.canMakeU2fApiRequest(
-        {tabId: this.sender_.tabId, origin: this.sender_.origin, appId: appid},
+        {
+          tabId: this.sender_.tabId,
+          frameId: this.sender_.frameId,
+          origin: this.sender_.origin,
+          appId: appid
+        },
         (result) => {
           if (!result) {
             this.notifyError_({
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/app_notifications_page/app_notification_row.html b/chrome/browser/resources/settings/chromeos/os_apps_page/app_notifications_page/app_notification_row.html
index c5d9f2e..4be458a6 100644
--- a/chrome/browser/resources/settings/chromeos/os_apps_page/app_notifications_page/app_notification_row.html
+++ b/chrome/browser/resources/settings/chromeos/os_apps_page/app_notifications_page/app_notification_row.html
@@ -16,7 +16,7 @@
   }
 
   #appToggle {
-    padding: 0 20px 0 0;
+    margin-inline-end: 20px;
   }
 
   #appIcon {
diff --git a/chrome/browser/safe_browsing/chrome_password_protection_service.cc b/chrome/browser/safe_browsing/chrome_password_protection_service.cc
index 5e67260..89880c8 100644
--- a/chrome/browser/safe_browsing/chrome_password_protection_service.cc
+++ b/chrome/browser/safe_browsing/chrome_password_protection_service.cc
@@ -57,6 +57,7 @@
 #include "components/safe_browsing/content/browser/ui_manager.h"
 #include "components/safe_browsing/content/browser/web_ui/safe_browsing_ui.h"
 #include "components/safe_browsing/core/browser/db/database_manager.h"
+#include "components/safe_browsing/core/browser/realtime/policy_engine.h"
 #include "components/safe_browsing/core/browser/safe_browsing_metrics_collector.h"
 #include "components/safe_browsing/core/browser/sync/safe_browsing_primary_account_token_fetcher.h"
 #include "components/safe_browsing/core/browser/verdict_cache_manager.h"
@@ -1491,6 +1492,13 @@
   }
   ChromeUserPopulation::PageLoadToken token =
       cache_manager_->GetPageLoadToken(main_frame_url);
+  if (RealTimePolicyEngine::CanPerformFullURLLookup(
+          profile_->GetPrefs(), profile_->IsOffTheRecord(),
+          g_browser_process->variations_service())) {
+    base::UmaHistogramBoolean(
+        "SafeBrowsing.PageLoadToken.PasswordProtectionHasToken",
+        token.has_token_value());
+  }
   // It's possible that the token is not found because real time URL check is
   // not performed for this navigation. Create a new page load token in this
   // case.
diff --git a/chrome/browser/signin/ui/android/java/src/org/chromium/chrome/browser/signin/ui/SigninPromoController.java b/chrome/browser/signin/ui/android/java/src/org/chromium/chrome/browser/signin/ui/SigninPromoController.java
index da8d485..e4e7275 100644
--- a/chrome/browser/signin/ui/android/java/src/org/chromium/chrome/browser/signin/ui/SigninPromoController.java
+++ b/chrome/browser/signin/ui/android/java/src/org/chromium/chrome/browser/signin/ui/SigninPromoController.java
@@ -208,13 +208,11 @@
     public static String getPromoShowCountPreferenceName(@AccessPoint int accessPoint) {
         switch (accessPoint) {
             case SigninAccessPoint.BOOKMARK_MANAGER:
-                return ChromePreferenceKeys.SIGNIN_PROMO_SHOW_COUNT.createKey(BOOKMARKS);
+                return ChromePreferenceKeys.SYNC_PROMO_SHOW_COUNT.createKey(BOOKMARKS);
             case SigninAccessPoint.NTP_CONTENT_SUGGESTIONS:
-                return ChromePreferenceKeys.SIGNIN_PROMO_SHOW_COUNT.createKey(NTP);
-            case SigninAccessPoint.RECENT_TABS:
-                return ChromePreferenceKeys.SIGNIN_PROMO_SHOW_COUNT.createKey(RECENT_TABS);
+                return ChromePreferenceKeys.SYNC_PROMO_SHOW_COUNT.createKey(NTP);
             case SigninAccessPoint.SETTINGS:
-                return ChromePreferenceKeys.SIGNIN_PROMO_SHOW_COUNT.createKey(SETTINGS);
+                return ChromePreferenceKeys.SYNC_PROMO_SHOW_COUNT.createKey(SETTINGS);
             default:
                 throw new IllegalArgumentException(
                         "Unexpected value for access point: " + accessPoint);
@@ -426,8 +424,12 @@
 
     /** Increases promo show count by one. */
     public void increasePromoShowCount() {
+        if (mAccessPoint != SigninAccessPoint.RECENT_TABS) {
+            SharedPreferencesManager.getInstance().incrementInt(
+                    getPromoShowCountPreferenceName(mAccessPoint));
+        }
         SharedPreferencesManager.getInstance().incrementInt(
-                getPromoShowCountPreferenceName(mAccessPoint));
+                ChromePreferenceKeys.SYNC_PROMO_TOTAL_SHOW_COUNT);
 
         if (mAccessPoint == SigninAccessPoint.NTP_CONTENT_SUGGESTIONS) {
             final long currentTime = System.currentTimeMillis();
diff --git a/chrome/browser/ui/android/autofill/autofill_progress_dialog_view_android.cc b/chrome/browser/ui/android/autofill/autofill_progress_dialog_view_android.cc
index d7d48b3..4d3dcb0 100644
--- a/chrome/browser/ui/android/autofill/autofill_progress_dialog_view_android.cc
+++ b/chrome/browser/ui/android/autofill/autofill_progress_dialog_view_android.cc
@@ -39,7 +39,13 @@
   return dialog_view;
 }
 
-void AutofillProgressDialogViewAndroid::Dismiss() {
+void AutofillProgressDialogViewAndroid::Dismiss(
+    bool show_confirmation_before_closing) {
+  if (show_confirmation_before_closing) {
+    ShowConfirmation();
+    return;
+  }
+
   JNIEnv* env = base::android::AttachCurrentThread();
   if (!java_object_.is_null()) {
     Java_AutofillProgressDialogBridge_dismiss(env, java_object_);
diff --git a/chrome/browser/ui/android/autofill/autofill_progress_dialog_view_android.h b/chrome/browser/ui/android/autofill/autofill_progress_dialog_view_android.h
index f911cf39..52ec4b7 100644
--- a/chrome/browser/ui/android/autofill/autofill_progress_dialog_view_android.h
+++ b/chrome/browser/ui/android/autofill/autofill_progress_dialog_view_android.h
@@ -24,7 +24,7 @@
   ~AutofillProgressDialogViewAndroid() override;
 
   // AutofillProgressDialogView.
-  void Dismiss() override;
+  void Dismiss(bool show_confirmation_before_closing) override;
 
   // Called by the Java code when the progress dialog is dismissed.
   void OnDismissed(JNIEnv* env);
@@ -33,7 +33,7 @@
   void ShowDialog();
 
   // Show the confirmation icon and text.
-  void ShowConfirmation() override;
+  void ShowConfirmation();
 
  private:
   AutofillProgressDialogController* controller_;
diff --git a/chrome/browser/ui/android/autofill/internal/java/src/org/chromium/chrome/browser/ui/autofill/AutofillProgressDialogBridge.java b/chrome/browser/ui/android/autofill/internal/java/src/org/chromium/chrome/browser/ui/autofill/AutofillProgressDialogBridge.java
index a33f08b4..0c67c32 100644
--- a/chrome/browser/ui/android/autofill/internal/java/src/org/chromium/chrome/browser/ui/autofill/AutofillProgressDialogBridge.java
+++ b/chrome/browser/ui/android/autofill/internal/java/src/org/chromium/chrome/browser/ui/autofill/AutofillProgressDialogBridge.java
@@ -107,6 +107,8 @@
                     .setVisibility(View.VISIBLE);
             ((TextView) mProgressDialogContentView.findViewById(R.id.message))
                     .setText(confirmationMessage);
+            // TODO(crbug.com/1243475): Dismiss the Java View after some delay if confirmation has
+            // been shown.
         }
     }
 
diff --git a/chrome/browser/ui/android/night_mode/java/src/org/chromium/chrome/browser/night_mode/WebContentsDarkModeController.java b/chrome/browser/ui/android/night_mode/java/src/org/chromium/chrome/browser/night_mode/WebContentsDarkModeController.java
index 0aca9035..23de5b3 100644
--- a/chrome/browser/ui/android/night_mode/java/src/org/chromium/chrome/browser/night_mode/WebContentsDarkModeController.java
+++ b/chrome/browser/ui/android/night_mode/java/src/org/chromium/chrome/browser/night_mode/WebContentsDarkModeController.java
@@ -206,7 +206,7 @@
      */
     private static void recordAutoDarkSettingsChangeSource(
             @AutoDarkSettingsChangeSource int source, boolean enabled) {
-        String histogram = "Android.DarkTheme.AutoDark.SettingsChangeSource."
+        String histogram = "Android.DarkTheme.AutoDarkMode.SettingsChangeSource."
                 + (enabled ? "Enabled" : "Disabled");
         RecordHistogram.recordEnumeratedHistogram(
                 histogram, source, AutoDarkSettingsChangeSource.NUM_ENTRIES);
diff --git a/chrome/browser/ui/android/night_mode/java/src/org/chromium/chrome/browser/night_mode/WebContentsDarkModeControllerUnitTest.java b/chrome/browser/ui/android/night_mode/java/src/org/chromium/chrome/browser/night_mode/WebContentsDarkModeControllerUnitTest.java
index 753600e..81a5436 100644
--- a/chrome/browser/ui/android/night_mode/java/src/org/chromium/chrome/browser/night_mode/WebContentsDarkModeControllerUnitTest.java
+++ b/chrome/browser/ui/android/night_mode/java/src/org/chromium/chrome/browser/night_mode/WebContentsDarkModeControllerUnitTest.java
@@ -286,7 +286,7 @@
 
     private void assertAutoDarkModeChangeSourceRecorded(
             @AutoDarkSettingsChangeSource int source, boolean enabled, int expectedCounts) {
-        String histogramName = "Android.DarkTheme.AutoDark.SettingsChangeSource."
+        String histogramName = "Android.DarkTheme.AutoDarkMode.SettingsChangeSource."
                 + (enabled ? "Enabled" : "Disabled");
         int actualCount = RecordHistogram.getHistogramValueCountForTesting(histogramName, source);
         Assert.assertEquals("Histogram <" + histogramName + "> for sample <" + source
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings.grd b/chrome/browser/ui/android/strings/android_chrome_strings.grd
index c42d3a9f..c09efe1 100644
--- a/chrome/browser/ui/android/strings/android_chrome_strings.grd
+++ b/chrome/browser/ui/android/strings/android_chrome_strings.grd
@@ -2279,7 +2279,7 @@
         Preview page <ph name="BEGIN_NEW">&lt;new&gt;</ph>New<ph name="END_NEW">&lt;/new&gt;</ph>
       </message>
       <message name="IDS_CONTEXTMENU_READ_LATER" desc="Context sensitive menu item for marking a link to be read later. We're also labeling it *New* to draw attention to it when first released. [CHAR_LIMIT=30]">
-        Add to Reading List <ph name="BEGIN_NEW">&lt;new&gt;</ph>New<ph name="END_NEW">&lt;/new&gt;</ph>
+        Add to reading list <ph name="BEGIN_NEW">&lt;new&gt;</ph>New<ph name="END_NEW">&lt;/new&gt;</ph>
       </message>
       <message name="IDS_CONTEXTMENU_PERFORMANCE_INFO_FAST" desc="This string is shown in the context sensitive menu for links and is shown underneath the link URL. It indicates to the user that the link's target page is fast-loading and responsive. [CHAR_LIMIT=30]">
         Fast page
@@ -3225,14 +3225,14 @@
       <message name="IDS_MENU_EDIT_BOOKMARK" desc="Menu item for editing the content of the bookmark for the current page. [CHAR_LIMIT=27]">
         Edit Bookmark
       </message>
-      <message name="IDS_MENU_ADD_TO_READING_LIST" desc="Menu item for adding the current page to the 'Reading List' to read later. [CHAR_LIMIT=27]">
-        Add to Reading List
+      <message name="IDS_MENU_ADD_TO_READING_LIST" desc="Menu item for adding the current page to the reading list. [CHAR_LIMIT=27]">
+        Add to reading list
       </message>
-      <message name="IDS_MENU_DELETE_FROM_READING_LIST" desc="Menu item for removing the current page from the 'Reading List'. [CHAR_LIMIT=27]">
-        Delete from Reading List
+      <message name="IDS_MENU_DELETE_FROM_READING_LIST" desc="Menu item for removing the current page from the reading list. [CHAR_LIMIT=27]">
+        Delete from reading list
       </message>
-      <message name="IDS_MENU_EDIT_READING_LIST" desc="Menu item for opening and editing the 'Reading List'. Shown only when the current page is already on the Reading List. [CHAR_LIMIT=27]">
-        Edit Reading List
+      <message name="IDS_MENU_EDIT_READING_LIST" desc="Menu item for opening and editing the reading list. Shown only when the current page is already on the reading list. [CHAR_LIMIT=27]">
+        Edit reading list
       </message>
       <message name="IDS_MENU_FIND_IN_PAGE" desc="Menu item allowing users to find text within the current page. [CHAR_LIMIT=27]">
         Find in page
@@ -3455,7 +3455,7 @@
         Save this page for later and get a reminder
       </message>
       <message name="IDS_READING_LIST_SAVE_PAGES_FOR_LATER" desc="The text on the reading list in product help bubble to introduce the feature to the user.">
-        Add pages to your Reading List to get a reminder
+        Add pages to your reading list for later
       </message>
       <message name="IDS_READING_LIST_FIND_IN_BOOKMARKS" desc="The text to inform the user to find the reading list in bookmarks UI.">
         Find your reading list in Bookmarks
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_CONTEXTMENU_READ_LATER.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_CONTEXTMENU_READ_LATER.png.sha1
index cb17f7aa..eca993fa 100644
--- a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_CONTEXTMENU_READ_LATER.png.sha1
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_CONTEXTMENU_READ_LATER.png.sha1
@@ -1 +1 @@
-e8b9ef1faf769b5ac2aff34b9d2de2e3ad67216b
\ No newline at end of file
+f37cc0fb8a8a4955e1cedcc2f2a403040cf93c4e
\ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_MENU_ADD_TO_READING_LIST.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_MENU_ADD_TO_READING_LIST.png.sha1
index 7e2996e..d335978 100644
--- a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_MENU_ADD_TO_READING_LIST.png.sha1
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_MENU_ADD_TO_READING_LIST.png.sha1
@@ -1 +1 @@
-ae82e7ea79689aef45d76de26fa8baf61b961b3b
\ No newline at end of file
+f957c8672dd7f7bd3d133cd8ea74458b3c269e93
\ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_MENU_DELETE_FROM_READING_LIST.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_MENU_DELETE_FROM_READING_LIST.png.sha1
index adb7c28b..31cf388a3 100644
--- a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_MENU_DELETE_FROM_READING_LIST.png.sha1
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_MENU_DELETE_FROM_READING_LIST.png.sha1
@@ -1 +1 @@
-b080c9f217e7dff4dce75ca962b404efe215572a
\ No newline at end of file
+045ee313c247d75dba791334a0983198d6631e49
\ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_MENU_EDIT_READING_LIST.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_MENU_EDIT_READING_LIST.png.sha1
index e3eba9e..a51e205 100644
--- a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_MENU_EDIT_READING_LIST.png.sha1
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_MENU_EDIT_READING_LIST.png.sha1
@@ -1 +1 @@
-a843887c2997190f3c846699e12ed973ea88a481
\ No newline at end of file
+f74ab9e5c668d66c8bd760f0dda525fbbba20c87
\ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_READING_LIST_SAVE_PAGES_FOR_LATER.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_READING_LIST_SAVE_PAGES_FOR_LATER.png.sha1
index 202456aa..6edcd955 100644
--- a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_READING_LIST_SAVE_PAGES_FOR_LATER.png.sha1
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_READING_LIST_SAVE_PAGES_FOR_LATER.png.sha1
@@ -1 +1 @@
-89c19bdab51f56cc41109a57ca8c2227ceea5f5a
\ No newline at end of file
+35263b1e4ddad400502674b738a2d6cc423ced1f
\ No newline at end of file
diff --git a/chrome/browser/ui/app_list/app_list_sort_browsertest.cc b/chrome/browser/ui/app_list/app_list_sort_browsertest.cc
index 4dde6130..04dc28a0 100644
--- a/chrome/browser/ui/app_list/app_list_sort_browsertest.cc
+++ b/chrome/browser/ui/app_list/app_list_sort_browsertest.cc
@@ -7,6 +7,7 @@
 #include "ash/public/cpp/test/app_list_test_api.h"
 #include "ash/public/cpp/test/shell_test_api.h"
 #include "ash/shell.h"
+#include "base/feature_list.h"
 #include "chrome/browser/extensions/extension_browsertest.h"
 #include "chrome/browser/ui/app_list/app_list_client_impl.h"
 #include "chrome/browser/ui/app_list/app_list_model_updater.h"
@@ -47,8 +48,15 @@
     ash::AcceleratorController::Get()->PerformActionIfEnabled(
         ash::TOGGLE_APP_LIST_FULLSCREEN, {});
 
-    // Assume that there are two default apps.
-    ASSERT_EQ(2, app_list_test_api_.GetTopListItemCount());
+    const int default_app_count = app_list_test_api_.GetTopListItemCount();
+
+    if (base::FeatureList::IsEnabled(chromeos::features::kLacrosSupport)) {
+      // Assume that there are three default apps, one being the Lacros browser.
+      ASSERT_EQ(3, app_list_test_api_.GetTopListItemCount());
+    } else {
+      // Assume that there are two default apps.
+      ASSERT_EQ(2, app_list_test_api_.GetTopListItemCount());
+    }
 
     app1_id_ = LoadExtension(test_data_dir_.AppendASCII("app1"))->id();
     ASSERT_FALSE(app1_id_.empty());
@@ -58,7 +66,7 @@
     // app in this test.
     app3_id_ = LoadExtension(test_data_dir_.AppendASCII("app4"))->id();
     ASSERT_FALSE(app3_id_.empty());
-    EXPECT_EQ(5, app_list_test_api_.GetTopListItemCount());
+    EXPECT_EQ(default_app_count + 3, app_list_test_api_.GetTopListItemCount());
 
     event_generator_ = std::make_unique<ui::test::EventGenerator>(
         ash::Shell::GetPrimaryRootWindow());
diff --git a/chrome/browser/ui/app_list/search/arc/recommend_apps_fetcher_impl.cc b/chrome/browser/ui/app_list/search/arc/recommend_apps_fetcher_impl.cc
index 9d96433..ee085945 100644
--- a/chrome/browser/ui/app_list/search/arc/recommend_apps_fetcher_impl.cc
+++ b/chrome/browser/ui/app_list/search/arc/recommend_apps_fetcher_impl.cc
@@ -153,12 +153,10 @@
   // TODO(thanhdng): Add a UMA histogram here recording the time difference.
 
   std::unique_ptr<network::SimpleURLLoader> loader(std::move(app_list_loader_));
-  int response_code = 0;
   if (!loader->ResponseInfo() || !loader->ResponseInfo()->headers) {
     delegate_->OnLoadError();
     return;
   }
-  response_code = loader->ResponseInfo()->headers->response_code();
   // TODO(thanhndng): Add a UMA histogram here recording the response code.
 
   // If the recommended app list could not be downloaded, show an error message
diff --git a/chrome/browser/ui/app_list/search/chrome_search_result.cc b/chrome/browser/ui/app_list/search/chrome_search_result.cc
index 78e4ceb0..fee5f00 100644
--- a/chrome/browser/ui/app_list/search/chrome_search_result.cc
+++ b/chrome/browser/ui/app_list/search/chrome_search_result.cc
@@ -72,6 +72,11 @@
   SetSearchResultMetadata();
 }
 
+void ChromeSearchResult::SetBestMatch(bool best_match) {
+  metadata_->best_match = best_match;
+  SetSearchResultMetadata();
+}
+
 void ChromeSearchResult::SetDisplayType(DisplayType display_type) {
   metadata_->display_type = display_type;
   SetSearchResultMetadata();
diff --git a/chrome/browser/ui/app_list/search/chrome_search_result.h b/chrome/browser/ui/app_list/search/chrome_search_result.h
index 0d6c1c42e..d9a519b 100644
--- a/chrome/browser/ui/app_list/search/chrome_search_result.h
+++ b/chrome/browser/ui/app_list/search/chrome_search_result.h
@@ -66,6 +66,7 @@
   }
   const std::string& id() const { return metadata_->id; }
   Category category() const { return metadata_->category; }
+  bool best_match() const { return metadata_->best_match; }
   DisplayType display_type() const { return metadata_->display_type; }
   ash::AppListSearchResultType result_type() const {
     return metadata_->result_type;
@@ -100,6 +101,7 @@
   void SetRating(float rating);
   void SetFormattedPrice(const std::u16string& formatted_price);
   void SetCategory(Category category);
+  void SetBestMatch(bool best_match);
   void SetDisplayType(DisplayType display_type);
   void SetResultType(ResultType result_type);
   void SetMetricsType(MetricsType metrics_type);
diff --git a/chrome/browser/ui/app_list/search/ranking/category_item_ranker.cc b/chrome/browser/ui/app_list/search/ranking/category_item_ranker.cc
index ed42957..206c830 100644
--- a/chrome/browser/ui/app_list/search/ranking/category_item_ranker.cc
+++ b/chrome/browser/ui/app_list/search/ranking/category_item_ranker.cc
@@ -22,7 +22,9 @@
   category_scores_.clear();
 }
 
-void CategoryItemRanker::Rank(ResultsMap& results, ProviderType provider) {
+void CategoryItemRanker::Rank(ResultsMap& results,
+                              CategoriesMap& categories,
+                              ProviderType provider) {
   UpdateCategoryScore(results, provider);
   RescoreResults(results);
 }
diff --git a/chrome/browser/ui/app_list/search/ranking/category_item_ranker.h b/chrome/browser/ui/app_list/search/ranking/category_item_ranker.h
index cce9f50..19f2e1a 100644
--- a/chrome/browser/ui/app_list/search/ranking/category_item_ranker.h
+++ b/chrome/browser/ui/app_list/search/ranking/category_item_ranker.h
@@ -24,7 +24,9 @@
 
   // Ranker:
   void Start(const std::u16string& query) override;
-  void Rank(ResultsMap& results, ProviderType provider) override;
+  void Rank(ResultsMap& results,
+            CategoriesMap& categories,
+            ProviderType provider) override;
 
  private:
   // Updates the score of |provider|'s category in |category_scores_|.
diff --git a/chrome/browser/ui/app_list/search/ranking/category_usage_ranker.cc b/chrome/browser/ui/app_list/search/ranking/category_usage_ranker.cc
index 41a4637..9cf964c 100644
--- a/chrome/browser/ui/app_list/search/ranking/category_usage_ranker.cc
+++ b/chrome/browser/ui/app_list/search/ranking/category_usage_ranker.cc
@@ -50,17 +50,18 @@
   // |category_ranker_| has a small recency component to its scores, so the
   // categories will appear in reverse order to the Record calls. This must
   // record every category except unknown and best match.
-  category_ranker_->Record(CategoryToString(Category::kAssistant));
+  category_ranker_->Record(CategoryToString(Category::kSearchAndAssistant));
   category_ranker_->Record(CategoryToString(Category::kPlayStore));
-  category_ranker_->Record(CategoryToString(Category::kWeb));
-  category_ranker_->Record(CategoryToString(Category::kFiles));
-  category_ranker_->Record(CategoryToString(Category::kHelp));
   category_ranker_->Record(CategoryToString(Category::kSettings));
-  category_ranker_->Record(CategoryToString(Category::kApp));
+  category_ranker_->Record(CategoryToString(Category::kFiles));
+  category_ranker_->Record(CategoryToString(Category::kWeb));
+  category_ranker_->Record(CategoryToString(Category::kAppShortcuts));
+  category_ranker_->Record(CategoryToString(Category::kApps));
+  category_ranker_->Record(CategoryToString(Category::kHelp));
 
-  // Check we've recorded every category except unknown and best match.
+  // Check we've recorded every category except unknown.
   DCHECK_EQ(category_ranker_->Rank().size(),
-            static_cast<size_t>(Category::kMaxValue) - 1);
+            static_cast<size_t>(Category::kMaxValue));
 }
 
 CategoryUsageRanker::~CategoryUsageRanker() {}
@@ -93,7 +94,9 @@
   }
 }
 
-void CategoryUsageRanker::Rank(ResultsMap& results, ProviderType provider) {
+void CategoryUsageRanker::Rank(ResultsMap& results,
+                               CategoriesMap& categories,
+                               ProviderType provider) {
   // Update each result's score to be:
   //  kCategoryScoreFactor*(category rank) + (result relevance)
   const auto it = results.find(provider);
diff --git a/chrome/browser/ui/app_list/search/ranking/category_usage_ranker.h b/chrome/browser/ui/app_list/search/ranking/category_usage_ranker.h
index 5c0ee63..3941b0f 100644
--- a/chrome/browser/ui/app_list/search/ranking/category_usage_ranker.h
+++ b/chrome/browser/ui/app_list/search/ranking/category_usage_ranker.h
@@ -25,7 +25,9 @@
 
   // Ranker:
   void Start(const std::u16string& query) override;
-  void Rank(ResultsMap& results, ProviderType provider) override;
+  void Rank(ResultsMap& results,
+            CategoriesMap& categories,
+            ProviderType provider) override;
   void Train(const LaunchData& launch) override;
 
  private:
diff --git a/chrome/browser/ui/app_list/search/ranking/filtering_ranker.cc b/chrome/browser/ui/app_list/search/ranking/filtering_ranker.cc
index fcb1762..d13ce6e 100644
--- a/chrome/browser/ui/app_list/search/ranking/filtering_ranker.cc
+++ b/chrome/browser/ui/app_list/search/ranking/filtering_ranker.cc
@@ -24,7 +24,9 @@
   last_query_ = query;
 }
 
-void FilteringRanker::Rank(ResultsMap& results, ProviderType provider) {
+void FilteringRanker::Rank(ResultsMap& results,
+                           CategoriesMap& categories,
+                           ProviderType provider) {
   // Don't perform any filtering on zero-state.
   if (last_query_.empty())
     return;
diff --git a/chrome/browser/ui/app_list/search/ranking/filtering_ranker.h b/chrome/browser/ui/app_list/search/ranking/filtering_ranker.h
index 7018187..a2641d6 100644
--- a/chrome/browser/ui/app_list/search/ranking/filtering_ranker.h
+++ b/chrome/browser/ui/app_list/search/ranking/filtering_ranker.h
@@ -22,7 +22,9 @@
 
   // Ranker:
   void Start(const std::u16string& query) override;
-  void Rank(ResultsMap& results, ProviderType provider) override;
+  void Rank(ResultsMap& results,
+            CategoriesMap& categories,
+            ProviderType provider) override;
 
  private:
   std::u16string last_query_;
diff --git a/chrome/browser/ui/app_list/search/ranking/ranker.h b/chrome/browser/ui/app_list/search/ranking/ranker.h
index cf2df364..8714349 100644
--- a/chrome/browser/ui/app_list/search/ranking/ranker.h
+++ b/chrome/browser/ui/app_list/search/ranking/ranker.h
@@ -39,7 +39,9 @@
   //
   // The goal of a ranker should be to update scores in the Scoring structs
   // within |results|. Generally, one ranker should map to one score member.
-  virtual void Rank(ResultsMap& results, ProviderType provider) {}
+  virtual void Rank(ResultsMap& results,
+                    CategoriesMap& categories,
+                    ProviderType provider) {}
 
   // Called each time a user launches a result.
   virtual void Train(const LaunchData& launch) {}
diff --git a/chrome/browser/ui/app_list/search/ranking/ranker_delegate.cc b/chrome/browser/ui/app_list/search/ranking/ranker_delegate.cc
index 88d371f..ea236ef 100644
--- a/chrome/browser/ui/app_list/search/ranking/ranker_delegate.cc
+++ b/chrome/browser/ui/app_list/search/ranking/ranker_delegate.cc
@@ -16,9 +16,11 @@
     ranker->Start(query);
 }
 
-void RankerDelegate::Rank(ResultsMap& results, ProviderType provider) {
+void RankerDelegate::Rank(ResultsMap& results,
+                          CategoriesMap& categories,
+                          ProviderType provider) {
   for (auto& ranker : rankers_)
-    ranker->Rank(results, provider);
+    ranker->Rank(results, categories, provider);
 }
 
 void RankerDelegate::Train(const LaunchData& launch) {
diff --git a/chrome/browser/ui/app_list/search/ranking/ranker_delegate.h b/chrome/browser/ui/app_list/search/ranking/ranker_delegate.h
index 73edbe6..e233720 100644
--- a/chrome/browser/ui/app_list/search/ranking/ranker_delegate.h
+++ b/chrome/browser/ui/app_list/search/ranking/ranker_delegate.h
@@ -30,7 +30,9 @@
 
   // Ranker:
   void Start(const std::u16string& query) override;
-  void Rank(ResultsMap& results, ProviderType provider) override;
+  void Rank(ResultsMap& results,
+            CategoriesMap& categories,
+            ProviderType provider) override;
   void Train(const LaunchData& launch) override;
 
  private:
diff --git a/chrome/browser/ui/app_list/search/ranking/score_normalizing_ranker.cc b/chrome/browser/ui/app_list/search/ranking/score_normalizing_ranker.cc
index 53a489d..b864112 100644
--- a/chrome/browser/ui/app_list/search/ranking/score_normalizing_ranker.cc
+++ b/chrome/browser/ui/app_list/search/ranking/score_normalizing_ranker.cc
@@ -59,7 +59,8 @@
 
 ScoreNormalizingRanker::~ScoreNormalizingRanker() {}
 
-void ScoreNormalizingRanker::Rank(ResultsMap& results, ProviderType provider) {
-}
+void ScoreNormalizingRanker::Rank(ResultsMap& results,
+                                  CategoriesMap& categories,
+                                  ProviderType provider) {}
 
 }  // namespace app_list
diff --git a/chrome/browser/ui/app_list/search/ranking/score_normalizing_ranker.h b/chrome/browser/ui/app_list/search/ranking/score_normalizing_ranker.h
index e63d296..3c087486 100644
--- a/chrome/browser/ui/app_list/search/ranking/score_normalizing_ranker.h
+++ b/chrome/browser/ui/app_list/search/ranking/score_normalizing_ranker.h
@@ -31,7 +31,9 @@
   ScoreNormalizingRanker& operator=(const ScoreNormalizingRanker&) = delete;
 
   // Ranker:
-  void Rank(ResultsMap& results, ProviderType provider) override;
+  void Rank(ResultsMap& results,
+            CategoriesMap& categories,
+            ProviderType provider) override;
 
  private:
   // TODO(crbug.com/1247475): Score normalizers removed due to stability issues.
diff --git a/chrome/browser/ui/app_list/search/ranking/top_match_ranker.cc b/chrome/browser/ui/app_list/search/ranking/top_match_ranker.cc
index 3a138c6c..4caebd1 100644
--- a/chrome/browser/ui/app_list/search/ranking/top_match_ranker.cc
+++ b/chrome/browser/ui/app_list/search/ranking/top_match_ranker.cc
@@ -46,7 +46,9 @@
 
 TopMatchRanker::~TopMatchRanker() {}
 
-void TopMatchRanker::Rank(ResultsMap& results, ProviderType provider) {
+void TopMatchRanker::Rank(ResultsMap& results,
+                          CategoriesMap& categories,
+                          ProviderType provider) {
   const auto it = results.find(provider);
   DCHECK(it != results.end());
 
diff --git a/chrome/browser/ui/app_list/search/ranking/top_match_ranker.h b/chrome/browser/ui/app_list/search/ranking/top_match_ranker.h
index ec0ba31..83a10cc8 100644
--- a/chrome/browser/ui/app_list/search/ranking/top_match_ranker.h
+++ b/chrome/browser/ui/app_list/search/ranking/top_match_ranker.h
@@ -27,7 +27,9 @@
   TopMatchRanker& operator=(const TopMatchRanker&) = delete;
 
   // Ranker:
-  void Rank(ResultsMap& results, ProviderType provider) override;
+  void Rank(ResultsMap& results,
+            CategoriesMap& categories,
+            ProviderType provider) override;
 };
 
 }  // namespace app_list
diff --git a/chrome/browser/ui/app_list/search/ranking/types.cc b/chrome/browser/ui/app_list/search/ranking/types.cc
index 82726d6..1d2850e 100644
--- a/chrome/browser/ui/app_list/search/ranking/types.cc
+++ b/chrome/browser/ui/app_list/search/ranking/types.cc
@@ -4,6 +4,8 @@
 
 #include "chrome/browser/ui/app_list/search/ranking/types.h"
 
+#include <stddef.h>
+
 #include "base/strings/strcat.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/stringprintf.h"
diff --git a/chrome/browser/ui/app_list/search/ranking/types.h b/chrome/browser/ui/app_list/search/ranking/types.h
index 37733c76..94b50f6 100644
--- a/chrome/browser/ui/app_list/search/ranking/types.h
+++ b/chrome/browser/ui/app_list/search/ranking/types.h
@@ -7,7 +7,6 @@
 
 #include "ash/public/cpp/app_list/app_list_types.h"
 #include "base/files/file_path.h"
-#include "chrome/browser/ui/app_list/search/search_controller.h"
 
 namespace app_list {
 
diff --git a/chrome/browser/ui/app_list/search/ranking/util.cc b/chrome/browser/ui/app_list/search/ranking/util.cc
index 4a834217..b194949 100644
--- a/chrome/browser/ui/app_list/search/ranking/util.cc
+++ b/chrome/browser/ui/app_list/search/ranking/util.cc
@@ -27,8 +27,9 @@
     case ResultType::kInstalledApp:
     case ResultType::kInstantApp:
     case ResultType::kInternalApp:
+      return Category::kApps;
     case ResultType::kArcAppShortcut:
-      return Category::kApp;
+      return Category::kAppShortcuts;
     case ResultType::kOmnibox:
     case ResultType::kAnswerCard:
       return Category::kWeb;
@@ -39,9 +40,6 @@
     case ResultType::kFileSearch:
     case ResultType::kDriveSearch:
       return Category::kFiles;
-    case ResultType::kAssistantChip:
-    case ResultType::kAssistantText:
-      return Category::kAssistant;
     case ResultType::kOsSettings:
       return Category::kSettings;
     case ResultType::kHelpApp:
@@ -49,6 +47,9 @@
     case ResultType::kPlayStoreReinstallApp:
     case ResultType::kPlayStoreApp:
       return Category::kPlayStore;
+    case ResultType::kAssistantChip:
+    case ResultType::kAssistantText:
+      return Category::kSearchAndAssistant;
     // Never used in the search backend.
     case ResultType::kUnknown:
     // Suggested content toggle fake result type. Used only in ash, not in the
@@ -57,7 +58,7 @@
     // Deprecated.
     case ResultType::kLauncher:
       NOTREACHED();
-      return Category::kApp;
+      return Category::kApps;
   }
 }
 
@@ -65,23 +66,22 @@
   switch (category) {
     case Category::kUnknown:
       return u"(unknown) ";
-    case Category::kBestMatch:
-      // The debug string for best match is handled elsewhere.
-      return u"";
-    case Category::kApp:
+    case Category::kApps:
+      return u"(apps) ";
+    case Category::kAppShortcuts:
       return u"(apps) ";
     case Category::kWeb:
       return u"(web) ";
     case Category::kFiles:
       return u"(files) ";
-    case Category::kAssistant:
-      return u"(assistant) ";
     case Category::kSettings:
       return u"(settings) ";
     case Category::kHelp:
       return u"(help) ";
     case Category::kPlayStore:
       return u"(play store) ";
+    case Category::kSearchAndAssistant:
+      return u"(assistant) ";
   }
 }
 
diff --git a/chrome/browser/ui/app_list/search/ranking/util.h b/chrome/browser/ui/app_list/search/ranking/util.h
index e592fb5..9f43444 100644
--- a/chrome/browser/ui/app_list/search/ranking/util.h
+++ b/chrome/browser/ui/app_list/search/ranking/util.h
@@ -9,6 +9,7 @@
 
 #include "base/files/file_path.h"
 #include "chrome/browser/ui/app_list/search/ranking/types.h"
+#include "chrome/browser/ui/app_list/search/search_controller.h"
 
 class Profile;
 
@@ -21,8 +22,8 @@
 // Given a search result type, returns the category it should be placed in.
 Category ResultTypeToCategory(ResultType result_type);
 
-// TODO(crbug.com/1199206): Once the UI has support for categories this can be
-// removed.
+// TODO(crbug.com/1199206): Once the UI has support for categories the following
+// methods can be removed.
 
 // Given a category, returns a debug string of its name suitable for the interim
 // UI.
diff --git a/chrome/browser/ui/app_list/search/search_controller.h b/chrome/browser/ui/app_list/search/search_controller.h
index 09aa130..c4c40ba9 100644
--- a/chrome/browser/ui/app_list/search/search_controller.h
+++ b/chrome/browser/ui/app_list/search/search_controller.h
@@ -14,6 +14,7 @@
 
 #include "base/callback.h"
 #include "base/containers/flat_map.h"
+#include "base/logging.h"
 #include "base/macros.h"
 #include "base/observer_list_types.h"
 #include "chrome/browser/ui/app_list/search/mixer.h"
@@ -41,6 +42,7 @@
 
 using Results = std::vector<std::unique_ptr<ChromeSearchResult>>;
 using ResultsMap = base::flat_map<ProviderType, Results>;
+using CategoriesMap = base::flat_map<Category, double>;
 
 // Controller that collects query from given SearchBoxModel, dispatches it
 // to all search providers, then invokes the mixer to mix and to publish the
diff --git a/chrome/browser/ui/app_list/search/search_controller_impl_new.cc b/chrome/browser/ui/app_list/search/search_controller_impl_new.cc
index a5f670f1..7eceb562 100644
--- a/chrome/browser/ui/app_list/search/search_controller_impl_new.cc
+++ b/chrome/browser/ui/app_list/search/search_controller_impl_new.cc
@@ -32,6 +32,7 @@
 #include "chrome/browser/ui/app_list/search/ranking/ranker_delegate.h"
 #include "chrome/browser/ui/app_list/search/ranking/score_normalizing_ranker.h"
 #include "chrome/browser/ui/app_list/search/ranking/top_match_ranker.h"
+#include "chrome/browser/ui/app_list/search/ranking/util.h"
 #include "chrome/browser/ui/app_list/search/search_metrics_observer.h"
 #include "chrome/browser/ui/app_list/search/search_provider.h"
 #include "chrome/browser/ui/app_list/search/search_result_ranker/chip_ranker.h"
@@ -92,6 +93,7 @@
 
   last_query_ = query;
   results_.clear();
+  categories_.clear();
   for (Observer& observer : observer_list_)
     observer.OnResultsCleared();
 
@@ -162,7 +164,7 @@
   results_[provider_type] = std::move(results);
 
   // Update ranking of all results.
-  ranker_->Rank(results_, provider_type);
+  ranker_->Rank(results_, categories_, provider_type);
 
   // Compile a single list of results and sort by their relevance.
   std::vector<ChromeSearchResult*> all_results;
@@ -194,6 +196,15 @@
               return a->display_score() > b->display_score();
             });
 
+  // Create a vector of categories in display order.
+  std::vector<std::pair<Category, double>> sorted_category_pairs(
+      categories_.begin(), categories_.end());
+  std::sort(sorted_category_pairs.begin(), sorted_category_pairs.end(),
+            [](const auto& a, const auto& b) { return a.second > b.second; });
+  std::vector<Category> sorted_categories;
+  for (const auto& pair : sorted_category_pairs)
+    sorted_categories.push_back(pair.first);
+
   if (!observer_list_.empty()) {
     std::vector<const ChromeSearchResult*> observer_results;
     for (auto* result : all_results)
@@ -202,8 +213,7 @@
       observer.OnResultsAdded(last_query_, observer_results);
   }
 
-  // TODO(crbug.com/1199206): Send category ranks.
-  model_updater_->PublishSearchResults(all_results, /*categories=*/{});
+  model_updater_->PublishSearchResults(all_results, sorted_categories);
 }
 
 ChromeSearchResult* SearchControllerImplNew::FindSearchResult(
diff --git a/chrome/browser/ui/app_list/search/search_controller_impl_new.h b/chrome/browser/ui/app_list/search/search_controller_impl_new.h
index 71506cc..66947a0 100644
--- a/chrome/browser/ui/app_list/search/search_controller_impl_new.h
+++ b/chrome/browser/ui/app_list/search/search_controller_impl_new.h
@@ -101,6 +101,9 @@
   // Storage for all search results for the current query.
   ResultsMap results_;
 
+  // Storage for category scores for the current query.
+  CategoriesMap categories_;
+
   std::unique_ptr<SearchMetricsObserver> metrics_observer_;
   using Providers = std::vector<std::unique_ptr<SearchProvider>>;
   Providers providers_;
diff --git a/chrome/browser/ui/ash/capture_mode/capture_mode_browsertest.cc b/chrome/browser/ui/ash/capture_mode/capture_mode_browsertest.cc
index baf2dd77..2d50357 100644
--- a/chrome/browser/ui/ash/capture_mode/capture_mode_browsertest.cc
+++ b/chrome/browser/ui/ash/capture_mode/capture_mode_browsertest.cc
@@ -2,14 +2,20 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "ash/constants/ash_features.h"
 #include "ash/public/cpp/capture_mode/capture_mode_test_api.h"
+#include "ash/public/cpp/shell_window_ids.h"
 #include "ash/public/cpp/test/shell_test_api.h"
+#include "base/test/scoped_feature_list.h"
+#include "chrome/browser/ash/file_manager/file_manager_test_util.h"
+#include "chrome/browser/extensions/extension_browsertest.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_window.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "content/public/test/browser_test.h"
 #include "ui/aura/window.h"
 #include "ui/events/test/event_generator.h"
+#include "ui/wm/core/window_util.h"
 
 // Testing class to test CrOS capture mode, which is a feature to take
 // screenshots and record video.
@@ -31,3 +37,41 @@
   ash::CaptureModeTestApi().StartForWindow(/*for_video=*/false);
   EXPECT_TRUE(shell_test_api.IsContextMenuShown());
 }
+
+class AdvancedSettingsCaptureModeBrowserTest
+    : public extensions::ExtensionBrowserTest {
+ public:
+  AdvancedSettingsCaptureModeBrowserTest() {
+    scoped_feature_list_.InitAndEnableFeature(
+        ash::features::kImprovedScreenCaptureSettings);
+  }
+
+  ~AdvancedSettingsCaptureModeBrowserTest() override = default;
+
+  // extensions::ExtensionBrowserTest:
+  void SetUpOnMainThread() override {
+    extensions::ExtensionBrowserTest::SetUpOnMainThread();
+    CHECK(profile());
+    file_manager::test::AddDefaultComponentExtensionsOnMainThread(profile());
+  }
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+// Tests that the capture mode folder selection dialog window gets parented
+// correctly when a browser window is available.
+IN_PROC_BROWSER_TEST_F(AdvancedSettingsCaptureModeBrowserTest,
+                       FolderSelectionDialogParentedCorrectly) {
+  ASSERT_TRUE(browser());
+  ash::CaptureModeTestApi test_api;
+  test_api.StartForFullscreen(/*for_video=*/false);
+  test_api.SimulateOpeningFolderSelectionDialog();
+  auto* dialog_window = test_api.GetFolderSelectionDialogWindow();
+  ASSERT_TRUE(dialog_window);
+  auto* transient_root = wm::GetTransientRoot(dialog_window);
+  ASSERT_TRUE(transient_root);
+  EXPECT_EQ(transient_root->GetId(),
+            ash::kShellWindowId_CaptureModeFolderSelectionDialogOwner);
+  EXPECT_NE(transient_root, browser()->window()->GetNativeWindow());
+}
diff --git a/chrome/browser/ui/ash/thumbnail_loader.cc b/chrome/browser/ui/ash/thumbnail_loader.cc
index ea76ac2..c257891 100644
--- a/chrome/browser/ui/ash/thumbnail_loader.cc
+++ b/chrome/browser/ui/ash/thumbnail_loader.cc
@@ -24,6 +24,7 @@
 #include "extensions/browser/api/messaging/native_message_host.h"
 #include "extensions/common/api/messaging/messaging_endpoint.h"
 #include "extensions/common/api/messaging/port_id.h"
+#include "extensions/common/api/messaging/serialization_format.h"
 #include "extensions/common/extension.h"
 #include "net/traffic_annotation/network_traffic_annotation.h"
 #include "services/data_decoder/public/cpp/data_decoder.h"
@@ -286,7 +287,8 @@
       base::BindOnce(&ThumbnailLoader::OnThumbnailLoaded,
                      weak_factory_.GetWeakPtr(), request_id, request.size));
   const extensions::PortId port_id(base::UnguessableToken::Create(),
-                                   1 /* port_number */, true /* is_opener */);
+                                   1 /* port_number */, true /* is_opener */,
+                                   extensions::SerializationFormat::kJson);
   auto native_message_port = std::make_unique<extensions::NativeMessagePort>(
       message_service->GetChannelDelegate(), port_id,
       std::move(native_message_host));
diff --git a/chrome/browser/ui/autofill/chrome_autofill_client.cc b/chrome/browser/ui/autofill/chrome_autofill_client.cc
index 3fd7c23d..306e07f 100644
--- a/chrome/browser/ui/autofill/chrome_autofill_client.cc
+++ b/chrome/browser/ui/autofill/chrome_autofill_client.cc
@@ -770,6 +770,19 @@
   autofill_error_dialog_controller_.Show(error_dialog_type);
 }
 
+void ChromeAutofillClient::ShowAutofillProgressDialog(
+    base::OnceClosure cancel_callback) {
+  DCHECK(autofill_progress_dialog_controller_);
+  autofill_progress_dialog_controller_->ShowDialog(std::move(cancel_callback));
+}
+
+void ChromeAutofillClient::CloseAutofillProgressDialog(
+    bool show_confirmation_before_closing) {
+  DCHECK(autofill_progress_dialog_controller_);
+  autofill_progress_dialog_controller_->DismissDialog(
+      show_confirmation_before_closing);
+}
+
 bool ChromeAutofillClient::IsAutofillAssistantShowing() {
   auto* assistant_runtime_manager =
       autofill_assistant::RuntimeManager::GetForWebContents(web_contents());
@@ -899,7 +912,10 @@
           GetPersonalDataManager()->app_locale())),
       unmask_controller_(
           user_prefs::UserPrefs::Get(web_contents->GetBrowserContext())),
-      autofill_error_dialog_controller_(web_contents) {
+      autofill_error_dialog_controller_(web_contents),
+      autofill_progress_dialog_controller_(
+          std::make_unique<AutofillProgressDialogControllerImpl>(
+              web_contents)) {
   // TODO(crbug.com/928595): Replace the closure with a callback to the
   // renderer that indicates if log messages should be sent from the
   // renderer.
diff --git a/chrome/browser/ui/autofill/chrome_autofill_client.h b/chrome/browser/ui/autofill/chrome_autofill_client.h
index 0ec3d0119..52cc68d 100644
--- a/chrome/browser/ui/autofill/chrome_autofill_client.h
+++ b/chrome/browser/ui/autofill/chrome_autofill_client.h
@@ -18,6 +18,7 @@
 #include "chrome/browser/autofill/autofill_gstatic_reader.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/autofill/payments/autofill_error_dialog_controller_impl.h"
+#include "chrome/browser/ui/autofill/payments/autofill_progress_dialog_controller_impl.h"
 #include "components/autofill/core/browser/autofill_client.h"
 #include "components/autofill/core/browser/logging/log_manager.h"
 #include "components/autofill/core/browser/payments/legal_message_line.h"
@@ -161,6 +162,9 @@
       const std::u16string& cvc,
       const gfx::Image& card_image) override;
   void ShowVirtualCardErrorDialog(bool is_permanent_error) override;
+  void ShowAutofillProgressDialog(base::OnceClosure cancel_callback) override;
+  void CloseAutofillProgressDialog(
+      bool show_confirmation_before_closing) override;
   bool IsAutofillAssistantShowing() override;
   bool IsAutocompleteEnabled() override;
   void PropagateAutofillPredictions(
@@ -220,6 +224,8 @@
 #endif
   CardUnmaskPromptControllerImpl unmask_controller_;
   AutofillErrorDialogControllerImpl autofill_error_dialog_controller_;
+  std::unique_ptr<AutofillProgressDialogControllerImpl>
+      autofill_progress_dialog_controller_;
 
   WEB_CONTENTS_USER_DATA_KEY_DECL();
 };
diff --git a/chrome/browser/ui/autofill/payments/autofill_progress_dialog_controller_impl.cc b/chrome/browser/ui/autofill/payments/autofill_progress_dialog_controller_impl.cc
index 18a1421..378bfeee1 100644
--- a/chrome/browser/ui/autofill/payments/autofill_progress_dialog_controller_impl.cc
+++ b/chrome/browser/ui/autofill/payments/autofill_progress_dialog_controller_impl.cc
@@ -15,23 +15,45 @@
     : web_contents_(web_contents) {}
 
 AutofillProgressDialogControllerImpl::~AutofillProgressDialogControllerImpl() {
-  Dismiss();
+  if (autofill_progress_dialog_view_) {
+    autofill_progress_dialog_view_->Dismiss(
+        /*show_confirmation_before_closing=*/false);
+    autofill_progress_dialog_view_ = nullptr;
+  }
 }
 
-void AutofillProgressDialogControllerImpl::ShowDialog() {
+void AutofillProgressDialogControllerImpl::ShowDialog(
+    base::OnceClosure cancel_callback) {
   DCHECK(!autofill_progress_dialog_view_);
 
+  cancel_callback_ = std::move(cancel_callback);
   autofill_progress_dialog_view_ =
       AutofillProgressDialogView::CreateAndShow(this);
 }
 
-void AutofillProgressDialogControllerImpl::ShowConfirmation() {
-  DCHECK(autofill_progress_dialog_view_);
-  autofill_progress_dialog_view_->ShowConfirmation();
+void AutofillProgressDialogControllerImpl::DismissDialog(
+    bool show_confirmation_before_closing) {
+  if (!autofill_progress_dialog_view_)
+    return;
+
+  autofill_progress_dialog_view_->Dismiss(show_confirmation_before_closing);
+  autofill_progress_dialog_view_ = nullptr;
 }
 
 void AutofillProgressDialogControllerImpl::OnDismissed() {
-  autofill_progress_dialog_view_ = nullptr;
+  // If the |autofill_progress_dialog_view_| is not a nullptr. It means the
+  // dismissal was triggered by the user cancelling the flow. Thus we should
+  // invoke the |cancel_callback_|.
+  if (autofill_progress_dialog_view_) {
+    autofill_progress_dialog_view_ = nullptr;
+    std::move(cancel_callback_).Run();
+    // TODO(crbug.com/1243475): Add metrics.
+    return;
+  }
+
+  // Otherwise it was triggered by the backend components. The
+  // |cancel_callback_| will not be invoked but be reset.
+  cancel_callback_.Reset();
 }
 
 const std::u16string AutofillProgressDialogControllerImpl::GetTitle() {
@@ -59,9 +81,4 @@
   return web_contents_;
 }
 
-void AutofillProgressDialogControllerImpl::Dismiss() {
-  if (autofill_progress_dialog_view_)
-    autofill_progress_dialog_view_->Dismiss();
-}
-
 }  // namespace autofill
diff --git a/chrome/browser/ui/autofill/payments/autofill_progress_dialog_controller_impl.h b/chrome/browser/ui/autofill/payments/autofill_progress_dialog_controller_impl.h
index 1d39ff3..4f471d7 100644
--- a/chrome/browser/ui/autofill/payments/autofill_progress_dialog_controller_impl.h
+++ b/chrome/browser/ui/autofill/payments/autofill_progress_dialog_controller_impl.h
@@ -29,8 +29,8 @@
 
   ~AutofillProgressDialogControllerImpl() override;
 
-  void ShowDialog();
-  void ShowConfirmation();
+  void ShowDialog(base::OnceClosure cancel_callback);
+  void DismissDialog(bool show_confirmation_before_closing);
 
   // AutofillProgressDialogController.
   void OnDismissed() override;
@@ -46,12 +46,13 @@
   }
 
  private:
-  // Dismiss the progress bar dialog if showing.
-  void Dismiss();
-
   content::WebContents* web_contents_;
+
   // View that displays the error dialog.
   AutofillProgressDialogView* autofill_progress_dialog_view_ = nullptr;
+
+  // Callback function invoked when the cancel button is clicked.
+  base::OnceClosure cancel_callback_;
 };
 
 }  // namespace autofill
diff --git a/chrome/browser/ui/autofill/payments/autofill_progress_dialog_view.h b/chrome/browser/ui/autofill/payments/autofill_progress_dialog_view.h
index 8d9df405..fb2dd36 100644
--- a/chrome/browser/ui/autofill/payments/autofill_progress_dialog_view.h
+++ b/chrome/browser/ui/autofill/payments/autofill_progress_dialog_view.h
@@ -16,10 +16,7 @@
   virtual ~AutofillProgressDialogView() = default;
 
   // Called by the controller to dismiss the dialog.
-  virtual void Dismiss() = 0;
-
-  // Called by the controller to show the confirmation view and message.
-  virtual void ShowConfirmation() = 0;
+  virtual void Dismiss(bool show_confirmation_before_closing) = 0;
 
   // Factory function for creating and showing the view.
   static AutofillProgressDialogView* CreateAndShow(
diff --git a/chrome/browser/ui/browser_command_controller.cc b/chrome/browser/ui/browser_command_controller.cc
index b746f90..68088c50 100644
--- a/chrome/browser/ui/browser_command_controller.cc
+++ b/chrome/browser/ui/browser_command_controller.cc
@@ -1101,8 +1101,8 @@
   // Hosted app browser commands.
   const bool enable_copy_url =
       is_web_app_or_custom_tab ||
-      base::FeatureList::IsEnabled(sharing_hub::kSharingHubDesktopOmnibox) ||
-      base::FeatureList::IsEnabled(sharing_hub::kSharingHubDesktopAppMenu);
+      sharing_hub::SharingHubOmniboxEnabled(browser_->profile()) ||
+      sharing_hub::SharingHubAppMenuEnabled(browser_->profile());
   command_updater_.UpdateCommandEnabled(IDC_COPY_URL, enable_copy_url);
   command_updater_.UpdateCommandEnabled(IDC_OPEN_IN_CHROME,
                                         is_web_app_or_custom_tab);
diff --git a/chrome/browser/ui/toolbar/app_menu_model.cc b/chrome/browser/ui/toolbar/app_menu_model.cc
index c3627779..c335077 100644
--- a/chrome/browser/ui/toolbar/app_menu_model.cc
+++ b/chrome/browser/ui/toolbar/app_menu_model.cc
@@ -854,7 +854,7 @@
 
   AddItemWithStringId(IDC_PRINT, IDS_PRINT);
 
-  if (!base::FeatureList::IsEnabled(sharing_hub::kSharingHubDesktopAppMenu) ||
+  if (!sharing_hub::SharingHubAppMenuEnabled(browser()->profile()) ||
       browser_->profile()->IsIncognitoProfile() ||
       browser_->profile()->IsGuestSession()) {
     if (media_router::MediaRouterEnabled(browser()->profile()))
diff --git a/chrome/browser/ui/user_education/tutorial/tutorial.cc b/chrome/browser/ui/user_education/tutorial/tutorial.cc
index 894ce64..62d8265 100644
--- a/chrome/browser/ui/user_education/tutorial/tutorial.cc
+++ b/chrome/browser/ui/user_education/tutorial/tutorial.cc
@@ -109,10 +109,16 @@
 
         tutorial_service->HideCurrentBubbleIfShowing();
 
+        base::RepeatingClosure abort_callback = base::BindRepeating(
+            [](TutorialService* tutorial_service) {
+              tutorial_service->AbortTutorial();
+            },
+            base::Unretained(tutorial_service));
+
         std::unique_ptr<TutorialBubble> bubble =
             bubble_factory_registry->CreateBubbleForTrackedElement(
                 element, title_text_, body_text_, arrow_, progress_,
-                is_last_step_);
+                std::move(abort_callback), is_last_step_);
         tutorial_service->SetCurrentBubble(std::move(bubble));
       },
       base::Unretained(tutorial_service),
diff --git a/chrome/browser/ui/user_education/tutorial/tutorial_bubble_factory.cc b/chrome/browser/ui/user_education/tutorial/tutorial_bubble_factory.cc
index 67c22af..6e8bd0df 100644
--- a/chrome/browser/ui/user_education/tutorial/tutorial_bubble_factory.cc
+++ b/chrome/browser/ui/user_education/tutorial/tutorial_bubble_factory.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/ui/user_education/tutorial/tutorial_bubble_factory.h"
 
+#include "base/callback_forward.h"
 #include "chrome/browser/ui/user_education/tutorial/tutorial_bubble.h"
 #include "chrome/browser/ui/user_education/tutorial/tutorial_description.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
@@ -16,9 +17,10 @@
     absl::optional<std::u16string> body_text,
     TutorialDescription::Step::Arrow arrow,
     absl::optional<std::pair<int, int>> progress,
+    base::RepeatingClosure abort_callback,
     bool is_last_step) {
   if (!CanBuildBubbleForTrackedElement(element))
     return nullptr;
   return CreateBubble(element, title_text, body_text, arrow, progress,
-                      is_last_step);
+                      abort_callback, is_last_step);
 }
diff --git a/chrome/browser/ui/user_education/tutorial/tutorial_bubble_factory.h b/chrome/browser/ui/user_education/tutorial/tutorial_bubble_factory.h
index a0ceec8..8871aaf 100644
--- a/chrome/browser/ui/user_education/tutorial/tutorial_bubble_factory.h
+++ b/chrome/browser/ui/user_education/tutorial/tutorial_bubble_factory.h
@@ -5,6 +5,7 @@
 #ifndef CHROME_BROWSER_UI_USER_EDUCATION_TUTORIAL_TUTORIAL_BUBBLE_FACTORY_H_
 #define CHROME_BROWSER_UI_USER_EDUCATION_TUTORIAL_TUTORIAL_BUBBLE_FACTORY_H_
 
+#include "base/callback_forward.h"
 #include "chrome/browser/ui/user_education/tutorial/tutorial_bubble.h"
 #include "chrome/browser/ui/user_education/tutorial/tutorial_description.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
@@ -26,7 +27,8 @@
       absl::optional<std::u16string> body_text,
       TutorialDescription::Step::Arrow arrow,
       absl::optional<std::pair<int, int>> progress,
-      bool is_last_step);
+      base::RepeatingClosure abort_callback,
+      bool is_last_step = false);
 
  private:
   // Called by the Tutorial to show the bubble.
@@ -36,7 +38,8 @@
       absl::optional<std::u16string> body_text,
       TutorialDescription::Step::Arrow arrow,
       absl::optional<std::pair<int, int>> progress,
-      bool is_last_step) = 0;
+      base::RepeatingClosure abort_callback,
+      bool is_last_step = false) = 0;
 
   // Returns true iff the bubble owner can show a bubble for the TrackedElement.
   virtual bool CanBuildBubbleForTrackedElement(ui::TrackedElement* element) = 0;
diff --git a/chrome/browser/ui/user_education/tutorial/tutorial_bubble_factory_registry.cc b/chrome/browser/ui/user_education/tutorial/tutorial_bubble_factory_registry.cc
index c67cde1..4f123c4 100644
--- a/chrome/browser/ui/user_education/tutorial/tutorial_bubble_factory_registry.cc
+++ b/chrome/browser/ui/user_education/tutorial/tutorial_bubble_factory_registry.cc
@@ -25,11 +25,13 @@
     absl::optional<std::u16string> body_text,
     TutorialDescription::Step::Arrow arrow,
     absl::optional<std::pair<int, int>> progress,
+    base::RepeatingClosure abort_callback,
     bool is_last_step) {
   for (const auto& bubble_factory : bubble_factories_) {
     std::unique_ptr<TutorialBubble> bubble =
         bubble_factory->CreateBubbleIfElementIsValid(
-            element, title_text, body_text, arrow, progress, is_last_step);
+            element, title_text, body_text, arrow, progress, abort_callback,
+            is_last_step);
     if (bubble)
       return bubble;
   }
diff --git a/chrome/browser/ui/user_education/tutorial/tutorial_bubble_factory_registry.h b/chrome/browser/ui/user_education/tutorial/tutorial_bubble_factory_registry.h
index 70f9485..358acca 100644
--- a/chrome/browser/ui/user_education/tutorial/tutorial_bubble_factory_registry.h
+++ b/chrome/browser/ui/user_education/tutorial/tutorial_bubble_factory_registry.h
@@ -35,7 +35,8 @@
       absl::optional<std::u16string> body_text,
       TutorialDescription::Step::Arrow arrow,
       absl::optional<std::pair<int, int>> progress,
-      bool is_last_step);
+      base::RepeatingClosure abort_callback,
+      bool is_last_step = false);
 
  private:
   // the list of registered bubble factories
diff --git a/chrome/browser/ui/views/autofill/payments/autofill_progress_dialog_views.cc b/chrome/browser/ui/views/autofill/payments/autofill_progress_dialog_views.cc
index bfd4ee91..30256c8 100644
--- a/chrome/browser/ui/views/autofill/payments/autofill_progress_dialog_views.cc
+++ b/chrome/browser/ui/views/autofill/payments/autofill_progress_dialog_views.cc
@@ -17,6 +17,12 @@
 
 namespace autofill {
 
+namespace {
+// The delay before dismissing the dialog after the progress throber shows the
+// checkmark. This delay is for users to identify the status change.
+constexpr int kDelayBeforeDismissingDialogInSeconds = 1;
+}  // namespace
+
 AutofillProgressDialogViews::AutofillProgressDialogViews(
     AutofillProgressDialogController* controller)
     : controller_(controller) {
@@ -61,22 +67,23 @@
   return dialog_view;
 }
 
-void AutofillProgressDialogViews::Dismiss() {
-  if (controller_) {
-    controller_->OnDismissed();
-    controller_ = nullptr;
+void AutofillProgressDialogViews::Dismiss(
+    bool show_confirmation_before_closing) {
+  // If |show_confirmation_before_closing| is true, show the confirmation and
+  // close the widget with a delay.
+  if (show_confirmation_before_closing) {
+    label_->SetText(controller_->GetConfirmationMessage());
+    progress_throbber_->SetChecked(true);
+    base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+        FROM_HERE,
+        base::BindOnce(&AutofillProgressDialogViews::CloseWidget,
+                       weak_ptr_factory_.GetWeakPtr()),
+        base::Seconds(kDelayBeforeDismissingDialogInSeconds));
+    return;
   }
-  GetWidget()->CloseWithReason(views::Widget::ClosedReason::kUnspecified);
-}
 
-void AutofillProgressDialogViews::ShowConfirmation() {
-  label_->SetText(controller_->GetConfirmationMessage());
-  progress_throbber_->SetChecked(true);
-  base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
-      FROM_HERE,
-      base::BindOnce(&AutofillProgressDialogViews::Dismiss,
-                     weak_ptr_factory_.GetWeakPtr()),
-      base::Seconds(1));
+  // Otherwise close the widget directly.
+  CloseWidget();
 }
 
 void AutofillProgressDialogViews::AddedToWidget() {
@@ -98,4 +105,12 @@
   return controller_->GetTitle();
 }
 
+void AutofillProgressDialogViews::CloseWidget() {
+  if (controller_) {
+    controller_->OnDismissed();
+    controller_ = nullptr;
+  }
+  GetWidget()->CloseWithReason(views::Widget::ClosedReason::kUnspecified);
+}
+
 }  // namespace autofill
diff --git a/chrome/browser/ui/views/autofill/payments/autofill_progress_dialog_views.h b/chrome/browser/ui/views/autofill/payments/autofill_progress_dialog_views.h
index 91c7308..89a3259c 100644
--- a/chrome/browser/ui/views/autofill/payments/autofill_progress_dialog_views.h
+++ b/chrome/browser/ui/views/autofill/payments/autofill_progress_dialog_views.h
@@ -27,8 +27,7 @@
   ~AutofillProgressDialogViews() override;
 
   // AutofillProgressDialogView:
-  void Dismiss() override;
-  void ShowConfirmation() override;
+  void Dismiss(bool show_confirmation_before_closing) override;
 
   // DialogDelegate:
   void AddedToWidget() override;
@@ -36,6 +35,9 @@
   std::u16string GetWindowTitle() const override;
 
  private:
+  // Close the widget of this view, and notify controller.
+  void CloseWidget();
+
   AutofillProgressDialogController* controller_ = nullptr;
   views::Label* label_ = nullptr;
   views::Throbber* progress_throbber_ = nullptr;
diff --git a/chrome/browser/ui/views/autofill/payments/autofill_progress_dialog_views_browsertest.cc b/chrome/browser/ui/views/autofill/payments/autofill_progress_dialog_views_browsertest.cc
index 3b1824c..bbf1635 100644
--- a/chrome/browser/ui/views/autofill/payments/autofill_progress_dialog_views_browsertest.cc
+++ b/chrome/browser/ui/views/autofill/payments/autofill_progress_dialog_views_browsertest.cc
@@ -28,7 +28,9 @@
         browser()->tab_strip_model()->GetActiveWebContents());
   }
 
-  void ShowUi(const std::string& name) override { controller()->ShowDialog(); }
+  void ShowUi(const std::string& name) override {
+    controller()->ShowDialog(base::DoNothing());
+  }
 
   AutofillProgressDialogViews* GetDialogViews() {
     DCHECK(controller());
diff --git a/chrome/browser/ui/views/location_bar/location_bar_view.cc b/chrome/browser/ui/views/location_bar/location_bar_view.cc
index bd32df2..879e349 100644
--- a/chrome/browser/ui/views/location_bar/location_bar_view.cc
+++ b/chrome/browser/ui/views/location_bar/location_bar_view.cc
@@ -344,8 +344,7 @@
       params.types_enabled.push_back(PageActionIconType::kSharingHub);
     }
 #else
-    if (base::FeatureList::IsEnabled(sharing_hub::kSharingHubDesktopOmnibox) &&
-        !is_popup_mode_)
+    if (sharing_hub::SharingHubOmniboxEnabled(profile_) && !is_popup_mode_)
       params.types_enabled.push_back(PageActionIconType::kSharingHub);
 #endif
   }
diff --git a/chrome/browser/ui/views/select_file_dialog_extension.cc b/chrome/browser/ui/views/select_file_dialog_extension.cc
index bdfa062..576d28f 100644
--- a/chrome/browser/ui/views/select_file_dialog_extension.cc
+++ b/chrome/browser/ui/views/select_file_dialog_extension.cc
@@ -10,7 +10,7 @@
 #include <utility>
 
 #include "ash/constants/ash_features.h"
-#include "ash/public/cpp/capture_mode/capture_mode_api.h"
+#include "ash/public/cpp/shell_window_ids.h"
 #include "ash/public/cpp/tablet_mode.h"
 #include "base/bind.h"
 #include "base/callback.h"
@@ -48,6 +48,7 @@
 #include "extensions/browser/app_window/app_window.h"
 #include "extensions/browser/app_window/native_app_window.h"
 #include "extensions/browser/extension_system.h"
+#include "ui/aura/window.h"
 #include "ui/base/base_window.h"
 #include "ui/gfx/color_palette.h"
 #include "ui/shell_dialogs/select_file_policy.h"
@@ -442,8 +443,21 @@
   // The web contents to associate the dialog with.
   content::WebContents* web_contents = nullptr;
 
+  // The folder selection dialog created for capture mode should never be
+  // parented to a browser window (if one exists). https://crbug.com/1258842.
+  const bool is_for_capture_mode =
+      owner.window &&
+      owner.window->GetId() ==
+          ash::kShellWindowId_CaptureModeFolderSelectionDialogOwner;
+
+  const bool skip_finding_browser = is_for_capture_mode ||
+                                    owner.android_task_id.has_value() ||
+                                    owner.lacros_window_id.has_value();
+
+  can_resize_ = !ash::TabletMode::IsInTabletMode() && !is_for_capture_mode;
+
   // Obtain BaseWindow and WebContents if the owner window is browser.
-  if (!owner.android_task_id.has_value() && !owner.lacros_window_id.has_value())
+  if (!skip_finding_browser)
     FindRuntimeContext(owner.window, &base_window, &web_contents);
 
   if (web_contents)
@@ -475,11 +489,6 @@
   gfx::NativeWindow parent_window =
       base_window ? base_window->GetNativeWindow() : owner.window;
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-  can_resize_ =
-      !ash::TabletMode::IsInTabletMode() && !ash::IsCaptureModeSessionActive();
-#endif
-
   if (ash::features::IsFileManagerSwaEnabled()) {
     // SystemFilesAppDialogDelegate is a self-deleting class that calls the
     // delete operator once the dialog for which it was created has been closed.
diff --git a/chrome/browser/ui/views/toolbar/app_menu.cc b/chrome/browser/ui/views/toolbar/app_menu.cc
index 4a0cf37..3e71f202 100644
--- a/chrome/browser/ui/views/toolbar/app_menu.cc
+++ b/chrome/browser/ui/views/toolbar/app_menu.cc
@@ -984,11 +984,8 @@
   if (command_id == IDC_MORE_TOOLS_MENU)
     return true;
 
-  if (command_id == IDC_SHARING_HUB_MENU) {
-    DCHECK(
-        base::FeatureList::IsEnabled(sharing_hub::kSharingHubDesktopAppMenu));
+  if (command_id == IDC_SHARING_HUB_MENU)
     return true;
-  }
 
   // The items representing the cut menu (cut/copy/paste), zoom menu
   // (increment/decrement/reset) and extension toolbar view are always enabled.
diff --git a/chrome/browser/ui/views/user_education/feature_promo_bubble_view.h b/chrome/browser/ui/views/user_education/feature_promo_bubble_view.h
index 028babc..709e4e9 100644
--- a/chrome/browser/ui/views/user_education/feature_promo_bubble_view.h
+++ b/chrome/browser/ui/views/user_education/feature_promo_bubble_view.h
@@ -89,9 +89,11 @@
     absl::optional<base::TimeDelta> timeout_after_interaction;
 
     // Used to call feature specific logic on dismiss.
+    // (TODO) dpenning: move to using a OnceClosure.
     absl::optional<base::RepeatingClosure> dismiss_callback;
 
     // Used to call feature specific logic on timeout.
+    // (TODO) dpenning: move to using a OnceClosure.
     base::RepeatingClosure timeout_callback;
   };
 
@@ -138,6 +140,8 @@
   base::TimeDelta timeout_after_interaction_;
 
   base::OneShotTimer auto_close_timer_;
+
+  // (TODO) dpenning: move to using a OnceClosure.
   base::RepeatingClosure timeout_callback_;
 };
 
diff --git a/chrome/browser/ui/views/user_education/tutorial_bubble_factory_views.cc b/chrome/browser/ui/views/user_education/tutorial_bubble_factory_views.cc
index 59147d6..b55f7b9 100644
--- a/chrome/browser/ui/views/user_education/tutorial_bubble_factory_views.cc
+++ b/chrome/browser/ui/views/user_education/tutorial_bubble_factory_views.cc
@@ -46,6 +46,7 @@
     absl::optional<std::u16string> body_text,
     TutorialDescription::Step::Arrow arrow,
     absl::optional<std::pair<int, int>> progress,
+    base::RepeatingClosure abort_callback,
     bool is_last_step) {
   if (!element->IsA<views::TrackedElementViews>())
     return nullptr;
@@ -98,6 +99,11 @@
   params.timeout_after_interaction =
       is_last_step ? kDelayWithInteraction : kDelayZero;
 
+  if (abort_callback) {
+    params.has_close_button = true;
+    params.dismiss_callback = abort_callback;
+  }
+
   return std::make_unique<TutorialBubbleViews>(
       owner_impl->ShowBubble(std::move(params), base::BindOnce([] {})));
 }
diff --git a/chrome/browser/ui/views/user_education/tutorial_bubble_factory_views.h b/chrome/browser/ui/views/user_education/tutorial_bubble_factory_views.h
index a929281..dbdb7b0f 100644
--- a/chrome/browser/ui/views/user_education/tutorial_bubble_factory_views.h
+++ b/chrome/browser/ui/views/user_education/tutorial_bubble_factory_views.h
@@ -30,7 +30,8 @@
       absl::optional<std::u16string> body_text,
       TutorialDescription::Step::Arrow arrow,
       absl::optional<std::pair<int, int>> progress,
-      bool is_last_step) override;
+      base::RepeatingClosure abort_callback,
+      bool is_last_step = false) override;
 
   bool CanBuildBubbleForTrackedElement(ui::TrackedElement* element) override;
 };
diff --git a/chrome/browser/ui/webui/extensions/extensions_internals_source.cc b/chrome/browser/ui/webui/extensions/extensions_internals_source.cc
index c57ea396..836be23 100644
--- a/chrome/browser/ui/webui/extensions/extensions_internals_source.cc
+++ b/chrome/browser/ui/webui/extensions/extensions_internals_source.cc
@@ -160,10 +160,6 @@
   }
   if (disable_reasons & extensions::disable_reason::DISABLE_BLOCKED_BY_POLICY)
     disable_reasons_value.Append("DISABLE_BLOCKED_BY_POLICY");
-  if (disable_reasons &
-      extensions::disable_reason::DISABLE_REMOTELY_FOR_MALWARE) {
-    disable_reasons_value.Append("DISABLE_REMOTELY_FOR_MALWARE");
-  }
   return disable_reasons_value;
 }
 // The JSON we generate looks like this:
diff --git a/chrome/browser/ui/webui/settings/about_handler.cc b/chrome/browser/ui/webui/settings/about_handler.cc
index 5863f43..777be39 100644
--- a/chrome/browser/ui/webui/settings/about_handler.cc
+++ b/chrome/browser/ui/webui/settings/about_handler.cc
@@ -479,9 +479,10 @@
   }
 
   std::u16string channel;
-  bool is_powerwash_allowed;
-  if (!args->GetString(0, &channel) ||
-      !args->GetBoolean(1, &is_powerwash_allowed)) {
+  bool is_powerwash_allowed = false;
+  if (args->GetString(0, &channel) && args->GetList()[1].is_bool()) {
+    is_powerwash_allowed = args->GetList()[1].GetBool();
+  } else {
     LOG(ERROR) << "Can't parse SetChannel() args";
     return;
   }
diff --git a/chrome/browser/ui/webui/settings/chrome_cleanup_handler_win.cc b/chrome/browser/ui/webui/settings/chrome_cleanup_handler_win.cc
index 3efe0ec..64d4778 100644
--- a/chrome/browser/ui/webui/settings/chrome_cleanup_handler_win.cc
+++ b/chrome/browser/ui/webui/settings/chrome_cleanup_handler_win.cc
@@ -204,7 +204,8 @@
 void ChromeCleanupHandler::HandleStartScanning(const base::ListValue* args) {
   CHECK_EQ(1U, args->GetList().size());
   bool allow_logs_upload = false;
-  args->GetBoolean(0, &allow_logs_upload);
+  if (args->GetList()[0].is_bool())
+    allow_logs_upload = args->GetList()[0].GetBool();
 
   // If this operation is not allowed the UI should be disabled.
   CHECK(controller_->IsAllowedByPolicy());
@@ -230,7 +231,8 @@
 void ChromeCleanupHandler::HandleStartCleanup(const base::ListValue* args) {
   CHECK_EQ(1U, args->GetList().size());
   bool allow_logs_upload = false;
-  args->GetBoolean(0, &allow_logs_upload);
+  if (args->GetList()[0].is_bool())
+    allow_logs_upload = args->GetList()[0].GetBool();
 
   // The state is propagated to all open tabs and should be consistent.
   DCHECK_EQ(controller_->logs_enabled(profile_), allow_logs_upload);
@@ -251,7 +253,8 @@
     const base::ListValue* args) {
   CHECK_EQ(1U, args->GetList().size());
   bool details_section_visible = false;
-  args->GetBoolean(0, &details_section_visible);
+  if (args->GetList()[0].is_bool())
+    details_section_visible = args->GetList()[0].GetBool();
 
   if (details_section_visible) {
     base::RecordAction(
diff --git a/chrome/browser/ui/webui/settings/chromeos/accessibility_handler.cc b/chrome/browser/ui/webui/settings/chromeos/accessibility_handler.cc
index 552d6cb..9f1bce8 100644
--- a/chrome/browser/ui/webui/settings/chromeos/accessibility_handler.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/accessibility_handler.cc
@@ -98,16 +98,18 @@
 void AccessibilityHandler::HandleSetStartupSoundEnabled(
     const base::ListValue* args) {
   DCHECK_EQ(1U, args->GetList().size());
-  bool enabled;
-  args->GetBoolean(0, &enabled);
+  bool enabled = false;
+  if (args->GetList()[0].is_bool())
+    enabled = args->GetList()[0].GetBool();
   AccessibilityManager::Get()->SetStartupSoundEnabled(enabled);
 }
 
 void AccessibilityHandler::HandleRecordSelectedShowShelfNavigationButtonsValue(
     const base::ListValue* args) {
   DCHECK_EQ(1U, args->GetList().size());
-  bool enabled;
-  args->GetBoolean(0, &enabled);
+  bool enabled = false;
+  if (args->GetList()[0].is_bool())
+    enabled = args->GetList()[0].GetBool();
 
   a11y_nav_buttons_toggle_metrics_reporter_timer_.Start(
       FROM_HERE, base::Seconds(10),
diff --git a/chrome/browser/ui/webui/settings/chromeos/android_apps_handler.cc b/chrome/browser/ui/webui/settings/chromeos/android_apps_handler.cc
index ddaa8ca..8c4a8bb 100644
--- a/chrome/browser/ui/webui/settings/chromeos/android_apps_handler.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/android_apps_handler.cc
@@ -108,7 +108,8 @@
 void AndroidAppsHandler::ShowAndroidAppsSettings(const base::ListValue* args) {
   CHECK_EQ(1U, args->GetList().size());
   bool activated_from_keyboard = false;
-  args->GetBoolean(0, &activated_from_keyboard);
+  if (args->GetList()[0].is_bool())
+    activated_from_keyboard = args->GetList()[0].GetBool();
   int flags = activated_from_keyboard ? ui::EF_NONE : ui::EF_LEFT_MOUSE_BUTTON;
 
   app_service_proxy_->Launch(
diff --git a/chrome/browser/ui/webui/settings/chromeos/device_stylus_handler.cc b/chrome/browser/ui/webui/settings/chromeos/device_stylus_handler.cc
index 9543bdf..62a907c0 100644
--- a/chrome/browser/ui/webui/settings/chromeos/device_stylus_handler.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/device_stylus_handler.cc
@@ -138,7 +138,8 @@
 void StylusHandler::HandleSetPreferredNoteTakingAppEnabledOnLockScreen(
     const base::ListValue* args) {
   bool enabled = false;
-  CHECK(args->GetBoolean(0, &enabled));
+  CHECK(args->GetList()[0].is_bool());
+  enabled = args->GetList()[0].GetBool();
 
   NoteTakingHelper::Get()->SetPreferredAppEnabledOnLockScreen(
       Profile::FromWebUI(web_ui()), enabled);
diff --git a/chrome/browser/ui/webui/settings/chromeos/multidevice_handler.cc b/chrome/browser/ui/webui/settings/chromeos/multidevice_handler.cc
index 7c084ad..214076d 100644
--- a/chrome/browser/ui/webui/settings/chromeos/multidevice_handler.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/multidevice_handler.cc
@@ -351,7 +351,8 @@
 void MultideviceHandler::HandleSetSmartLockSignInEnabled(
     const base::ListValue* args) {
   bool enabled = false;
-  CHECK(args->GetBoolean(0, &enabled));
+  if (args->GetList()[0].is_bool())
+    enabled = args->GetList()[0].GetBool();
 
   std::string auth_token;
   bool auth_token_present = args->GetString(1, &auth_token);
diff --git a/chrome/browser/ui/webui/settings/people_handler.cc b/chrome/browser/ui/webui/settings/people_handler.cc
index c649d95d8..5d3936c 100644
--- a/chrome/browser/ui/webui/settings/people_handler.cc
+++ b/chrome/browser/ui/webui/settings/people_handler.cc
@@ -613,7 +613,8 @@
 
 void PeopleHandler::HandleSignout(const base::ListValue* args) {
   bool delete_profile = false;
-  args->GetBoolean(0, &delete_profile);
+  if (args->GetList()[0].is_bool())
+    delete_profile = args->GetList()[0].GetBool();
   base::FilePath profile_path = profile_->GetPath();
 
   if (!signin_util::IsUserSignoutAllowedForProfile(profile_)) {
diff --git a/chrome/browser/ui/webui/settings/protocol_handlers_handler.cc b/chrome/browser/ui/webui/settings/protocol_handlers_handler.cc
index 9515ab3..80c2efe 100644
--- a/chrome/browser/ui/webui/settings/protocol_handlers_handler.cc
+++ b/chrome/browser/ui/webui/settings/protocol_handlers_handler.cc
@@ -190,7 +190,8 @@
 void ProtocolHandlersHandler::HandleSetHandlersEnabled(
     const base::ListValue* args) {
   bool enabled = true;
-  CHECK(args->GetBoolean(0, &enabled));
+  CHECK(args->GetList()[0].is_bool());
+  enabled = args->GetList()[0].GetBool();
   if (enabled)
     GetProtocolHandlerRegistry()->Enable();
   else
diff --git a/chrome/browser/ui/webui/settings/reset_settings_handler.cc b/chrome/browser/ui/webui/settings/reset_settings_handler.cc
index 6449021c..72f8d4e 100644
--- a/chrome/browser/ui/webui/settings/reset_settings_handler.cc
+++ b/chrome/browser/ui/webui/settings/reset_settings_handler.cc
@@ -138,7 +138,8 @@
   std::string callback_id;
   CHECK(args->GetString(0, &callback_id));
   bool send_settings = false;
-  CHECK(args->GetBoolean(1, &send_settings));
+  CHECK(args->GetList()[1].is_bool());
+  send_settings = args->GetList()[1].GetBool();
   std::string request_origin_string;
   CHECK(args->GetString(2, &request_origin_string));
   reset_report::ChromeResetReport::ResetRequestOrigin request_origin =
diff --git a/chrome/browser/ui/webui/settings/site_settings_handler.cc b/chrome/browser/ui/webui/settings/site_settings_handler.cc
index d6518df..8223325 100644
--- a/chrome/browser/ui/webui/settings/site_settings_handler.cc
+++ b/chrome/browser/ui/webui/settings/site_settings_handler.cc
@@ -1127,8 +1127,8 @@
   CHECK(args->GetString(1, &secondary_pattern_string));
   std::string type;
   CHECK(args->GetString(2, &type));
-  bool incognito;
-  CHECK(args->GetBoolean(3, &incognito));
+  CHECK(args->GetList()[3].is_bool());
+  bool incognito = args->GetList()[3].GetBool();
 
   ContentSettingsType content_type =
       site_settings::ContentSettingsTypeFromGroupName(type);
@@ -1193,8 +1193,8 @@
   CHECK(args->GetString(2, &type));
   std::string value;
   CHECK(args->GetString(3, &value));
-  bool incognito;
-  CHECK(args->GetBoolean(4, &incognito));
+  CHECK(args->GetList()[4].is_bool());
+  bool incognito = args->GetList()[4].GetBool();
 
   ContentSettingsType content_type =
       site_settings::ContentSettingsTypeFromGroupName(type);
@@ -1458,8 +1458,8 @@
     return;
 
   CHECK_EQ(1U, args->GetList().size());
-  bool value;
-  CHECK(args->GetBoolean(0, &value));
+  CHECK(args->GetList()[0].is_bool());
+  bool value = args->GetList()[0].GetBool();
 
   profile_->GetPrefs()->SetBoolean(prefs::kBlockAutoplayEnabled, value);
 }
diff --git a/chrome/browser/ui/webui/webapks/webapks_handler.cc b/chrome/browser/ui/webui/webapks/webapks_handler.cc
index 248c458..bb2ca76 100644
--- a/chrome/browser/ui/webui/webapks/webapks_handler.cc
+++ b/chrome/browser/ui/webui/webapks/webapks_handler.cc
@@ -11,7 +11,7 @@
 #include "chrome/browser/android/shortcut_helper.h"
 #include "content/public/browser/web_ui.h"
 #include "third_party/blink/public/common/manifest/manifest_util.h"
-#include "ui/android/color_helpers.h"
+#include "ui/android/color_utils_android.h"
 #include "ui/gfx/color_utils.h"
 
 WebApksHandler::WebApksHandler()
diff --git a/chrome/browser/web_applications/web_app_constants.h b/chrome/browser/web_applications/web_app_constants.h
index 6f1764a2..11c5be6 100644
--- a/chrome/browser/web_applications/web_app_constants.h
+++ b/chrome/browser/web_applications/web_app_constants.h
@@ -286,9 +286,15 @@
 
 // A result how `WebAppIconDownloader` processed the list of icon urls.
 enum class IconsDownloadedResult {
-  // Icon downloading requests fulfilled.
+  // At least one (or all if `WebAppIconDownloader::FailAllIfAnyFail()`
+  // was called) icon urls were downloaded into the given `icons_map`.
   kCompleted,
-  // All icon downloading requests cancelled.
+  // There was an error downloading the icons, and `icons_map` is empty.
+  // Errors can include:
+  // * All icon downloads failed.
+  // * At least one icon download failed and
+  //  `WebAppIconDownloader::FailAllIfAnyFail()` was called.
+  // * Unexpected navigations or state changes on the `web_contents`.
   kCancelled,
 };
 
diff --git a/chrome/browser/web_applications/web_app_icon_downloader.cc b/chrome/browser/web_applications/web_app_icon_downloader.cc
index 5c9a3eb..10b8ded 100644
--- a/chrome/browser/web_applications/web_app_icon_downloader.cc
+++ b/chrome/browser/web_applications/web_app_icon_downloader.cc
@@ -109,7 +109,7 @@
     const GURL& image_url,
     const std::vector<SkBitmap>& bitmaps,
     const std::vector<gfx::Size>& original_bitmap_sizes) {
-  // Request may have been canceled by DidFinishNavigation().
+  // Request may have been canceled by PrimaryPageChanged().
   if (in_progress_requests_.erase(id) == 0)
     return;
 
@@ -144,7 +144,6 @@
     CompleteCallback();
 }
 
-// content::WebContentsObserver overrides:
 void WebAppIconDownloader::PrimaryPageChanged(content::Page& page) {
   CancelDownloads();
 }
diff --git a/chrome/browser/web_applications/web_app_icon_downloader.h b/chrome/browser/web_applications/web_app_icon_downloader.h
index cdf3479e..3bf69ebf 100644
--- a/chrome/browser/web_applications/web_app_icon_downloader.h
+++ b/chrome/browser/web_applications/web_app_icon_downloader.h
@@ -86,7 +86,7 @@
                           const std::vector<SkBitmap>& bitmaps,
                           const std::vector<gfx::Size>& original_bitmap_sizes);
 
-  // content::WebContentsObserver overrides:
+  // content::WebContentsObserver:
   void PrimaryPageChanged(content::Page& page) override;
   void DidUpdateFaviconURL(
       content::RenderFrameHost* rfh,
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index 24e33fa..1875f07 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-main-1634050758-81ac571898085b700004c0e280809306718f9da2.profdata
+chrome-win32-main-1634072245-4c09bdcbc793607463f7ca39634458b6e4988617.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index 0bd63ad..103b759 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-main-1634050758-7b5caff70f9b52b0f1ba9a90b5545e65e5dac729.profdata
+chrome-win64-main-1634072245-1b1c585a799cb124650c63f35c2823e95a980cc8.profdata
diff --git a/chrome/common/extensions/api/cryptotoken_private.idl b/chrome/common/extensions/api/cryptotoken_private.idl
index 1fa079c..3b61724 100644
--- a/chrome/common/extensions/api/cryptotoken_private.idl
+++ b/chrome/common/extensions/api/cryptotoken_private.idl
@@ -19,9 +19,12 @@
     DOMString appId;
     // The origin of the caller.
     DOMString origin;
-    // Identifies the tab in which the registration is occuring so that any
+    // Identifies the tab in which the request is occuring so that any
     // permissions prompt is correctly located.
     long tabId;
+    // Identifies the frame in which the frame is occuring. Required
+    // but ignored for `canAppIdGetAttestation`.
+    long frameId;
   };
 
   interface Functions {
diff --git a/chrome/renderer/extensions/extension_hooks_delegate.cc b/chrome/renderer/extensions/extension_hooks_delegate.cc
index 4063e16..520d643 100644
--- a/chrome/renderer/extensions/extension_hooks_delegate.cc
+++ b/chrome/renderer/extensions/extension_hooks_delegate.cc
@@ -228,8 +228,10 @@
   }
 
   v8::Local<v8::Value> v8_message = arguments[1];
+
   std::unique_ptr<Message> message = messaging_util::MessageFromV8(
-      script_context->v8_context(), v8_message, &error);
+      script_context->v8_context(), v8_message,
+      messaging_util::GetSerializationFormat(*script_context), &error);
   if (!message) {
     RequestResult result(RequestResult::INVALID_INVOCATION);
     result.error = std::move(error);
diff --git a/chrome/renderer/extensions/extension_hooks_delegate_unittest.cc b/chrome/renderer/extensions/extension_hooks_delegate_unittest.cc
index fa3a666c..ab27f50 100644
--- a/chrome/renderer/extensions/extension_hooks_delegate_unittest.cc
+++ b/chrome/renderer/extensions/extension_hooks_delegate_unittest.cc
@@ -7,6 +7,7 @@
 #include "base/strings/stringprintf.h"
 #include "content/public/common/child_process_host.h"
 #include "extensions/common/api/messaging/messaging_endpoint.h"
+#include "extensions/common/api/messaging/serialization_format.h"
 #include "extensions/common/extension_builder.h"
 #include "extensions/common/extension_messages.h"
 #include "extensions/common/value_builder.h"
@@ -178,7 +179,7 @@
 
   const std::string kChannel = "chrome.extension.sendRequest";
   base::UnguessableToken other_context_id = base::UnguessableToken::Create();
-  const PortId port_id(other_context_id, 0, false);
+  const PortId port_id(other_context_id, 0, false, SerializationFormat::kJson);
 
   ExtensionMsg_TabConnectionInfo tab_connection_info;
   tab_connection_info.frame_id = 0;
@@ -207,8 +208,9 @@
 
   // Post the message to the receiver. Since the receiver doesn't respond, the
   // channel should remain open.
-  messaging_service()->DeliverMessage(script_context_set(), port_id,
-                                      Message("\"message\"", false), nullptr);
+  messaging_service()->DeliverMessage(
+      script_context_set(), port_id,
+      Message("\"message\"", SerializationFormat::kJson, false), nullptr);
   ::testing::Mock::VerifyAndClearExpectations(ipc_message_sender());
   EXPECT_TRUE(
       messaging_service()->HasPortForTesting(script_context(), port_id));
diff --git a/chrome/renderer/extensions/tabs_hooks_delegate.cc b/chrome/renderer/extensions/tabs_hooks_delegate.cc
index c2b3bfd..3bcf0615 100644
--- a/chrome/renderer/extensions/tabs_hooks_delegate.cc
+++ b/chrome/renderer/extensions/tabs_hooks_delegate.cc
@@ -84,8 +84,10 @@
   int tab_id = messaging_util::ExtractIntegerId(arguments[0]);
   v8::Local<v8::Value> v8_message = arguments[1];
   std::string error;
+
   std::unique_ptr<Message> message = messaging_util::MessageFromV8(
-      script_context->v8_context(), v8_message, &error);
+      script_context->v8_context(), v8_message,
+      messaging_util::GetSerializationFormat(*script_context), &error);
   if (!message) {
     RequestResult result(RequestResult::INVALID_INVOCATION);
     result.error = std::move(error);
@@ -119,8 +121,10 @@
   v8::Local<v8::Value> v8_message = arguments[1];
   DCHECK(!v8_message.IsEmpty());
   std::string error;
+
   std::unique_ptr<Message> message = messaging_util::MessageFromV8(
-      script_context->v8_context(), v8_message, &error);
+      script_context->v8_context(), v8_message,
+      messaging_util::GetSerializationFormat(*script_context), &error);
   if (!message) {
     RequestResult result(RequestResult::INVALID_INVOCATION);
     result.error = std::move(error);
@@ -154,7 +158,8 @@
 
   gin::Handle<GinPort> port = messaging_service_->Connect(
       script_context, MessageTarget::ForTab(tab_id, options.frame_id),
-      options.channel_name);
+      options.channel_name,
+      messaging_util::GetSerializationFormat(*script_context));
   DCHECK(!port.IsEmpty());
 
   RequestResult result(RequestResult::HANDLED);
diff --git a/chrome/services/sharing/nearby/nearby_connections_unittest.cc b/chrome/services/sharing/nearby/nearby_connections_unittest.cc
index ef9b8cb..f491c3ca 100644
--- a/chrome/services/sharing/nearby/nearby_connections_unittest.cc
+++ b/chrome/services/sharing/nearby/nearby_connections_unittest.cc
@@ -1000,8 +1000,7 @@
         initiated_run_loop.Quit();
       });
 
-  ClientProxy* client_proxy =
-      StartAdvertising(fake_connection_life_cycle_listener, endpoint_data);
+  StartAdvertising(fake_connection_life_cycle_listener, endpoint_data);
   initiated_run_loop.Run();
 
   base::RunLoop accepted_run_loop;
@@ -1012,8 +1011,7 @@
       });
 
   FakePayloadListener fake_payload_listener;
-  client_proxy =
-      AcceptConnection(fake_payload_listener, endpoint_data.remote_endpoint_id);
+  AcceptConnection(fake_payload_listener, endpoint_data.remote_endpoint_id);
   accepted_run_loop.Run();
 }
 
diff --git a/chrome/test/android/javatests/src/org/chromium/chrome/test/util/browser/webapps/WebApkIntentDataProviderBuilder.java b/chrome/test/android/javatests/src/org/chromium/chrome/test/util/browser/webapps/WebApkIntentDataProviderBuilder.java
index d5056df..0b179ae 100644
--- a/chrome/test/android/javatests/src/org/chromium/chrome/test/util/browser/webapps/WebApkIntentDataProviderBuilder.java
+++ b/chrome/test/android/javatests/src/org/chromium/chrome/test/util/browser/webapps/WebApkIntentDataProviderBuilder.java
@@ -9,11 +9,11 @@
 
 import org.chromium.blink.mojom.DisplayMode;
 import org.chromium.chrome.browser.browserservices.intents.BrowserServicesIntentDataProvider;
-import org.chromium.chrome.browser.browserservices.intents.WebappConstants;
 import org.chromium.chrome.browser.webapps.WebApkIntentDataProviderFactory;
 import org.chromium.components.webapps.ShortcutSource;
 import org.chromium.components.webapps.WebApkDistributor;
 import org.chromium.device.mojom.ScreenOrientationLockType;
+import org.chromium.ui.util.ColorUtils;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -54,8 +54,7 @@
     public BrowserServicesIntentDataProvider build() {
         return WebApkIntentDataProviderFactory.create(new Intent(), mUrl, mScope, null, null, null,
                 null, mDisplayMode, ScreenOrientationLockType.DEFAULT, ShortcutSource.UNKNOWN,
-                WebappConstants.MANIFEST_COLOR_INVALID_OR_MISSING,
-                WebappConstants.MANIFEST_COLOR_INVALID_OR_MISSING, Color.WHITE,
+                ColorUtils.INVALID_COLOR, ColorUtils.INVALID_COLOR, Color.WHITE,
                 false /* isPrimaryIconMaskable */, false /* isSplashIconMaskable */,
                 mWebApkPackageName, /* shellApkVersion */ 1, mManifestUrl, mUrl,
                 WebApkDistributor.BROWSER,
diff --git a/chrome/test/base/testing_profile_manager.cc b/chrome/test/base/testing_profile_manager.cc
index 7ce2420c..de4379c 100644
--- a/chrome/test/base/testing_profile_manager.cc
+++ b/chrome/test/base/testing_profile_manager.cc
@@ -204,6 +204,17 @@
 
 void TestingProfileManager::DeleteAllTestingProfiles() {
   DCHECK(called_set_up_);
+
+  ProfileAttributesStorage& storage =
+      profile_manager_->GetProfileAttributesStorage();
+  for (auto& name_profile_pair : testing_profiles_) {
+    TestingProfile* profile = name_profile_pair.second;
+    if (profile->IsGuestSession() || profile->IsSystemProfile()) {
+      // Guest and System profiles aren't added to Storage.
+      continue;
+    }
+    storage.RemoveProfile(profile->GetPath());
+  }
   profile_manager_->profiles_info_.clear();
 }
 
diff --git a/chrome/test/data/policy/policy_test_cases.json b/chrome/test/data/policy/policy_test_cases.json
index 8f81e4c71..a3bb9bc5 100644
--- a/chrome/test/data/policy/policy_test_cases.json
+++ b/chrome/test/data/policy/policy_test_cases.json
@@ -11094,6 +11094,12 @@
   "ReportUploadFrequency": {
     "reason_for_missing_test": "Maps into CrosSettings"
   },
+  "ReportDeviceNetworkTelemetryCollectionRateMs": {
+    "reason_for_missing_test": "Maps into CrosSettings"
+  },
+  "ReportDeviceNetworkTelemetryEventCheckingRateMs": {
+    "reason_for_missing_test": "Maps into CrosSettings"
+  },
   "DeviceAllowNewUsers": {
     "reason_for_missing_test": "Maps into CrosSettings"
   },
diff --git a/chrome/test/data/updater/Keystone.ticketstore b/chrome/test/data/updater/Keystone.ticketstore
new file mode 100644
index 0000000..ceab3f7
--- /dev/null
+++ b/chrome/test/data/updater/Keystone.ticketstore
Binary files differ
diff --git a/chrome/test/data/webrtc/webrtc_getdisplaymedia_test.html b/chrome/test/data/webrtc/webrtc_getdisplaymedia_test.html
index 66787c32..ea2ee12 100644
--- a/chrome/test/data/webrtc/webrtc_getdisplaymedia_test.html
+++ b/chrome/test/data/webrtc/webrtc_getdisplaymedia_test.html
@@ -9,14 +9,17 @@
   'use strict';
   var settings;
   var video_track;
-  var has_audio_track = false;
+  var audio_track;
+  var stored_media_stream;
 
   function handleSuccess(stream) {
-    has_audio_track = stream.getAudioTracks().length == 1;
     settings = stream.getVideoTracks()[0].getSettings();
     var video = document.querySelector('video');
     video.srcObject = stream;
     video_track = stream.getVideoTracks()[0];
+    if (stream.getAudioTracks().length > 0) {
+      audio_track = stream.getAudioTracks()[0];
+    }
     video.play();
     returnToTest("capture-success");
   }
@@ -38,7 +41,28 @@
   }
 
   function hasAudioTrack() {
-    returnToTest(has_audio_track ? "true" : "false");
+    returnToTest(`${!!audio_track}`);
+  }
+
+  function getVideoTrackType() {
+    if (!video_track) {
+      returnToTest("error-no-video-track");
+    }
+    returnToTest(video_track.constructor.name);
+  }
+
+  function getVideoCloneTrackType() {
+    if (!video_track) {
+      returnToTest("error-no-video-track-to-clone");
+    }
+    returnToTest(video_track.clone().constructor.name);
+  }
+
+  function getAudioTrackType() {
+    if (!audio_track) {
+      returnToTest("error-no-audio-track");
+    }
+    returnToTest(audio_track.constructor.name);
   }
 
   function getDisplaySurfaceSetting() {
@@ -87,10 +111,6 @@
       returnToTest("ended");
     }
   }
-
-  // function readLastResultFromEmbedded() {
-  //   // Blocks until the result is reported by the embedded page.
-  // }
   </script>
 </head>
 <body>
diff --git a/chrome/test/data/webui/chromeos/shortcut_customization/shortcut_customization_test.js b/chrome/test/data/webui/chromeos/shortcut_customization/shortcut_customization_test.js
index db564d6..6b8b741 100644
--- a/chrome/test/data/webui/chromeos/shortcut_customization/shortcut_customization_test.js
+++ b/chrome/test/data/webui/chromeos/shortcut_customization/shortcut_customization_test.js
@@ -309,6 +309,33 @@
     assertEquals(']', actualAccelerator.key_display);
   });
 
+  test('RemoveAccelerator', async () => {
+    await flushTasks();
+
+    // Open dialog for first accelerator in View Desk subsection.
+    await openDialogForAcceleratorInSubsection_(/*View Desk*/ 1);
+    let editDialog = page.shadowRoot.querySelector('#editDialog');
+    assertTrue(!!editDialog);
+
+    // Grab the first accelerator from Virtual Desks subsection.
+    let acceleratorList = editDialog.shadowRoot.querySelector('cr-dialog')
+                              .querySelectorAll('accelerator-edit-view');
+    assertEquals(1, acceleratorList.length);
+    const editView = acceleratorList[0];
+
+    // Click on remove button.
+    editView.shadowRoot.querySelector('#deleteButton').click();
+
+    await flushTasks();
+
+    // Requery the accelerator elements.
+    acceleratorList = editDialog.shadowRoot.querySelector('cr-dialog')
+                          .querySelectorAll('accelerator-edit-view');
+
+    // Expect that the accelerator has now been removed.
+    assertEquals(0, acceleratorList.length);
+  });
+
   suite('FakeMojoProviderTest', () => {
     test('SettingGettingTestProvider', () => {
       // TODO(zentaro): Replace with fake when built.
diff --git a/chrome/test/data/webui/cr_components/chromeos/bluetooth/bluetooth_base_page_test.js b/chrome/test/data/webui/cr_components/chromeos/bluetooth/bluetooth_base_page_test.js
index b8d32dd9..657b033 100644
--- a/chrome/test/data/webui/cr_components/chromeos/bluetooth/bluetooth_base_page_test.js
+++ b/chrome/test/data/webui/cr_components/chromeos/bluetooth/bluetooth_base_page_test.js
@@ -49,12 +49,19 @@
     flush();
   }
 
-  test('Title is shown', function() {
+  test('Title and loading indicator are shown', function() {
     const title = bluetoothBasePage.shadowRoot.querySelector('#title');
     assertTrue(!!title);
     assertEquals(
         bluetoothBasePage.i18n('bluetoothPairNewDevice'),
         title.textContent.trim());
+
+    const getProgress = () =>
+        bluetoothBasePage.shadowRoot.querySelector('paper-progress');
+    assertFalse(!!getProgress());
+    bluetoothBasePage.showScanProgress = true;
+    flush();
+    assertTrue(!!getProgress());
   });
 
   test('Button states', function() {
diff --git a/chrome/updater/BUILD.gn b/chrome/updater/BUILD.gn
index 2f2761f..63953b1 100644
--- a/chrome/updater/BUILD.gn
+++ b/chrome/updater/BUILD.gn
@@ -224,6 +224,7 @@
         "//chrome/updater/win:app_install_controller",
         "//chrome/updater/win/ui",
       ]
+
       sources += [
         "activity_impl_win.cc",
         "app/app_install_win.cc",
@@ -235,6 +236,7 @@
         "app/server/win/server.h",
         "app/server/win/service_main.cc",
         "app/server/win/service_main.h",
+        "app/server/win/wrl_classes.cc",
         "device_management/dm_storage_win.cc",
         "lib_util_win.cc",
         "policy/win/group_policy_manager.cc",
@@ -273,7 +275,6 @@
         "win/win_constants.h",
         "win/win_util.cc",
         "win/win_util.h",
-        "win/wrl_module.h",
       ]
       deps += [
         "//chrome/installer/util:with_no_strings",
@@ -548,6 +549,9 @@
 
     if (is_mac) {
       sources += [
+        "mac/keystone/ks_tickets.h",
+        "mac/keystone/ks_tickets.mm",
+        "mac/keystone/ks_tickets_unittest.mm",
         "mac/keystone/ksadmin_unittest.cc",
         "mac/net/network_unittest.cc",
         "mac/scoped_xpc_service_mock.h",
@@ -573,6 +577,7 @@
       ]
 
       data = [
+        "//chrome/test/data/updater/Keystone.ticketstore",
         "//chrome/test/data/updater/updater_setup_test_dmg.dmg",
         "//chrome/test/data/updater/updater_qualification_app_dmg.crx",
       ]
diff --git a/chrome/updater/app/server/win/server.cc b/chrome/updater/app/server/win/server.cc
index c663f36..3c93976 100644
--- a/chrome/updater/app/server/win/server.cc
+++ b/chrome/updater/app/server/win/server.cc
@@ -4,10 +4,11 @@
 
 #include "chrome/updater/app/server/win/server.h"
 
-#include <wrl/implements.h>
+#include <wrl/module.h>
 
 #include <algorithm>
 #include <memory>
+#include <string>
 #include <utility>
 
 #include "base/bind.h"
@@ -17,6 +18,8 @@
 #include "base/files/file_path.h"
 #include "base/logging.h"
 #include "base/memory/ref_counted.h"
+#include "base/strings/strcat.h"
+#include "base/strings/utf_string_conversions.h"
 #include "base/system/sys_info.h"
 #include "base/task/task_traits.h"
 #include "base/task/thread_pool.h"
@@ -36,7 +39,6 @@
 #include "chrome/updater/win/setup/setup_util.h"
 #include "chrome/updater/win/setup/uninstall.h"
 #include "chrome/updater/win/win_constants.h"
-#include "chrome/updater/win/wrl_module.h"
 #include "components/prefs/pref_service.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
@@ -47,6 +49,18 @@
   return base::CommandLine::ForCurrentProcess()->HasSwitch(kComServiceSwitch);
 }
 
+std::wstring GetCOMGroup(const std::wstring& prefix, UpdaterScope scope) {
+  return base::StrCat({prefix, base::ASCIIToWide(UpdaterScopeToString(scope))});
+}
+
+std::wstring COMGroup(UpdaterScope scope) {
+  return GetCOMGroup(L"Active", scope);
+}
+
+std::wstring COMGroupInternal(UpdaterScope scope) {
+  return GetCOMGroup(L"Internal", scope);
+}
+
 }  // namespace
 
 // Returns a leaky singleton of the App instance.
@@ -84,99 +98,26 @@
 }
 
 HRESULT ComServerApp::RegisterClassObjects() {
-  Microsoft::WRL::ComPtr<IUnknown> factory;
-  unsigned int flags = Microsoft::WRL::ModuleType::OutOfProc;
-
-  HRESULT hr = Microsoft::WRL::Details::CreateClassFactory<
-      Microsoft::WRL::SimpleClassFactory<UpdaterImpl>>(
-      &flags, nullptr, __uuidof(IClassFactory), &factory);
-  if (FAILED(hr)) {
-    LOG(ERROR) << "Factory creation for UpdaterImpl failed; hr: " << hr;
-    return hr;
-  }
-
-  Microsoft::WRL::ComPtr<IClassFactory> class_factory_updater;
-  hr = factory.As(&class_factory_updater);
-  if (FAILED(hr)) {
-    LOG(ERROR) << "IClassFactory object creation failed; hr: " << hr;
-    return hr;
-  }
-  factory.Reset();
-
-  hr = Microsoft::WRL::Details::CreateClassFactory<
-      Microsoft::WRL::SimpleClassFactory<LegacyOnDemandImpl>>(
-      &flags, nullptr, __uuidof(IClassFactory), &factory);
-  if (FAILED(hr)) {
-    LOG(ERROR) << "Factory creation for LegacyOnDemandImpl failed; hr: " << hr;
-    return hr;
-  }
-
-  Microsoft::WRL::ComPtr<IClassFactory> class_factory_legacy_ondemand;
-  hr = factory.As(&class_factory_legacy_ondemand);
-  if (FAILED(hr)) {
-    LOG(ERROR) << "IClassFactory object creation failed; hr: " << hr;
-    return hr;
-  }
-
-  // The pointer in this array is unowned. Do not release it.
-  IClassFactory* class_factories[] = {class_factory_updater.Get(),
-                                      class_factory_legacy_ondemand.Get()};
-  std::vector<CLSID> class_ids = GetActiveServers(updater_scope());
-  std::vector<DWORD> cookies(class_ids.size());
-  hr = Microsoft::WRL::Module<Microsoft::WRL::OutOfProc>::GetModule()
-           .RegisterCOMObject(nullptr, &class_ids[0], class_factories,
-                              &cookies[0], class_ids.size());
-  if (FAILED(hr)) {
-    LOG(ERROR) << "RegisterCOMObject failed; hr: " << hr;
-    return hr;
-  }
-
-  cookies.swap(cookies_);
-  return hr;
+  // Register COM class objects that are under either the ActiveSystem or the
+  // ActiveUser group.
+  // See wrl_classes.cc for details on the COM classes within the group.
+  return Microsoft::WRL::Module<Microsoft::WRL::OutOfProc>::GetModule()
+      .RegisterObjects(COMGroup(updater_scope()).c_str());
 }
 
 HRESULT ComServerApp::RegisterInternalClassObjects() {
-  Microsoft::WRL::ComPtr<IUnknown> factory;
-  unsigned int flags = Microsoft::WRL::ModuleType::OutOfProc;
-
-  HRESULT hr = Microsoft::WRL::Details::CreateClassFactory<
-      Microsoft::WRL::SimpleClassFactory<UpdaterInternalImpl>>(
-      &flags, nullptr, __uuidof(IClassFactory), &factory);
-  if (FAILED(hr)) {
-    LOG(ERROR) << "Factory creation for UpdaterInternalImpl failed; hr: " << hr;
-    return hr;
-  }
-
-  Microsoft::WRL::ComPtr<IClassFactory> class_factory_updater_internal;
-  hr = factory.As(&class_factory_updater_internal);
-  if (FAILED(hr)) {
-    LOG(ERROR) << "IClassFactory object creation failed; hr: " << hr;
-    return hr;
-  }
-  factory.Reset();
-
-  // The pointer in this array is unowned. Do not release it.
-  IClassFactory* class_factories[] = {class_factory_updater_internal.Get()};
-  std::vector<CLSID> class_ids = GetSideBySideServers(updater_scope());
-  std::vector<DWORD> cookies(class_ids.size());
-  hr = Microsoft::WRL::Module<Microsoft::WRL::OutOfProc>::GetModule()
-           .RegisterCOMObject(nullptr, &class_ids[0], class_factories,
-                              &cookies[0], class_ids.size());
-  if (FAILED(hr)) {
-    LOG(ERROR) << "RegisterCOMObject failed; hr: " << hr;
-    return hr;
-  }
-
-  cookies.swap(cookies_);
-  return hr;
+  // Register COM class objects that are under either the InternalSystem or the
+  // InternalUser group.
+  // See wrl_classes.cc for details on the COM classes within the group.
+  return Microsoft::WRL::Module<Microsoft::WRL::OutOfProc>::GetModule()
+      .RegisterObjects(COMGroupInternal(updater_scope()).c_str());
 }
 
 void ComServerApp::UnregisterClassObjects() {
-  auto& module = Microsoft::WRL::Module<Microsoft::WRL::OutOfProc>::GetModule();
   const HRESULT hr =
-      module.UnregisterCOMObject(nullptr, cookies_.data(), cookies_.size());
-  if (FAILED(hr))
-    LOG(ERROR) << "UnregisterCOMObject failed; hr: " << hr;
+      Microsoft::WRL::Module<Microsoft::WRL::OutOfProc>::GetModule()
+          .UnregisterObjects();
+  LOG_IF(ERROR, FAILED(hr)) << "UnregisterObjects failed; hr: " << hr;
 }
 
 void ComServerApp::CreateWRLModule() {
diff --git a/chrome/updater/app/server/win/server.h b/chrome/updater/app/server/win/server.h
index fc78fba0..689b796 100644
--- a/chrome/updater/app/server/win/server.h
+++ b/chrome/updater/app/server/win/server.h
@@ -7,8 +7,6 @@
 
 #include <windows.h>
 
-#include <vector>
-
 #include "base/callback_forward.h"
 #include "base/check.h"
 #include "base/memory/scoped_refptr.h"
@@ -94,9 +92,6 @@
   // Handles COM setup and registration.
   void Start(base::OnceCallback<HRESULT()> register_callback);
 
-  // Identifier of registered class objects used for unregistration.
-  std::vector<DWORD> cookies_;
-
   // While this object lives, COM can be used by all threads in the program.
   base::win::ScopedCOMInitializer com_initializer_;
 
diff --git a/chrome/updater/app/server/win/service_main.cc b/chrome/updater/app/server/win/service_main.cc
index e2c2ffd..6cd814e 100644
--- a/chrome/updater/app/server/win/service_main.cc
+++ b/chrome/updater/app/server/win/service_main.cc
@@ -6,6 +6,7 @@
 
 #include <atlsecurity.h>
 #include <sddl.h>
+#include <wrl/module.h>
 
 #include <string>
 #include <type_traits>
@@ -21,7 +22,6 @@
 #include "chrome/updater/constants.h"
 #include "chrome/updater/win/win_constants.h"
 #include "chrome/updater/win/win_util.h"
-#include "chrome/updater/win/wrl_module.h"
 
 namespace updater {
 
diff --git a/chrome/updater/app/server/win/wrl_classes.cc b/chrome/updater/app/server/win/wrl_classes.cc
new file mode 100644
index 0000000..334dfb8
--- /dev/null
+++ b/chrome/updater/app/server/win/wrl_classes.cc
@@ -0,0 +1,60 @@
+// 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 <wrl/module.h>
+
+#include "chrome/updater/app/server/win/com_classes.h"
+#include "chrome/updater/app/server/win/com_classes_legacy.h"
+
+namespace updater {
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wmissing-braces"
+#pragma clang diagnostic ignored "-Wc++98-compat-extra-semi"
+
+// CoCreatableClassWithFactoryEx takes three parameters, `className`, `factory`,
+// and `serverName`:
+// * `className` identifies the COM class from the IDL file, and not the
+// corresponding C++ implementation class.
+// * `factory` identifies the class factory that creates the COM object, and is
+// typically Microsoft::WRL::SimpleClassFactory<>.
+// * `serverName` is used below to group COM classes that need to be
+// registered/unregistered together in a single process by using
+// Microsoft::WRL::Module::RegisterObjects and
+// Microsoft::WRL::Module::UnregisterObjects, respectively.
+//
+// Below, we are registering the following groups of COM objects:
+// * ActiveSystem
+// * ActiveUser
+// * InternalSystem
+// * InternalUser
+CoCreatableClassWithFactoryEx(UpdaterSystemClass,
+                              Microsoft::WRL::SimpleClassFactory<UpdaterImpl>,
+                              ActiveSystem);
+CoCreatableClassWithFactoryEx(
+    GoogleUpdate3WebSystemClass,
+    Microsoft::WRL::SimpleClassFactory<LegacyOnDemandImpl>,
+    ActiveSystem);
+
+CoCreatableClassWithFactoryEx(UpdaterUserClass,
+                              Microsoft::WRL::SimpleClassFactory<UpdaterImpl>,
+                              ActiveUser);
+CoCreatableClassWithFactoryEx(
+    GoogleUpdate3WebUserClass,
+    Microsoft::WRL::SimpleClassFactory<LegacyOnDemandImpl>,
+    ActiveUser);
+
+CoCreatableClassWithFactoryEx(
+    UpdaterInternalSystemClass,
+    Microsoft::WRL::SimpleClassFactory<UpdaterInternalImpl>,
+    InternalSystem);
+
+CoCreatableClassWithFactoryEx(
+    UpdaterInternalUserClass,
+    Microsoft::WRL::SimpleClassFactory<UpdaterInternalImpl>,
+    InternalUser);
+
+#pragma clang diagnostic pop
+
+}  // namespace updater
diff --git a/chrome/updater/mac/BUILD.gn b/chrome/updater/mac/BUILD.gn
index 2ddd6b91..f8db594 100644
--- a/chrome/updater/mac/BUILD.gn
+++ b/chrome/updater/mac/BUILD.gn
@@ -131,6 +131,8 @@
 executable("ksadmin") {
   output_name = "ksadmin"
   sources = [
+    "keystone/ks_tickets.h",
+    "keystone/ks_tickets.mm",
     "keystone/ksadmin.h",
     "keystone/ksadmin.mm",
     "keystone/ksadmin_main.cc",
diff --git a/chrome/updater/mac/keystone/ks_tickets.h b/chrome/updater/mac/keystone/ks_tickets.h
new file mode 100644
index 0000000..a46d72a
--- /dev/null
+++ b/chrome/updater/mac/keystone/ks_tickets.h
@@ -0,0 +1,20 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_UPDATER_MAC_KEYSTONE_KS_TICKETS_H_
+#define CHROME_UPDATER_MAC_KEYSTONE_KS_TICKETS_H_
+
+#import <Foundation/Foundation.h>
+
+@class KSPathExistenceChecker;
+
+// KSTicketStore holds a class method for reading an NSDictionary of NSString
+// to KSTickets.
+@interface KSTicketStore : NSObject
+
++ (nullable NSDictionary*)readStoreWithPath:(nonnull NSString*)path;
+
+@end
+
+#endif  // CHROME_UPDATER_MAC_KEYSTONE_KS_TICKETS_H_
diff --git a/chrome/updater/mac/keystone/ks_tickets.mm b/chrome/updater/mac/keystone/ks_tickets.mm
new file mode 100644
index 0000000..42f75e1c
--- /dev/null
+++ b/chrome/updater/mac/keystone/ks_tickets.mm
@@ -0,0 +1,286 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "chrome/updater/mac/keystone/ks_tickets.h"
+
+#import <Foundation/Foundation.h>
+
+#include "base/logging.h"
+#include "base/notreached.h"
+#include "base/strings/sys_string_conversions.h"
+
+@interface KSPathExistenceChecker : NSObject <NSSecureCoding> {
+ @private
+  NSString* path_;
+}
+@end
+
+@interface KSTicket : NSObject <NSSecureCoding>
+@end
+
+@implementation KSTicketStore
+
++ (nullable NSDictionary*)readStoreWithPath:(nonnull NSString*)path {
+  if (![[NSFileManager defaultManager] fileExistsAtPath:path]) {
+    VLOG(0) << "Ticket store does not exist at "
+            << base::SysNSStringToUTF8(path);
+    return [NSDictionary dictionary];
+  }
+  NSError* readError = nil;
+  NSData* storeData = [NSData dataWithContentsOfFile:path
+                                             options:0  // Use normal IO
+                                               error:&readError];
+  if (!storeData) {
+    VLOG(0) << "Failed to decode ticket store at "
+            << base::SysNSStringToUTF8(path) << ": " << readError;
+    return nil;
+  }
+  if (![storeData length])
+    return [NSDictionary dictionary];
+  NSDictionary* store = nil;
+  @try {  // Unarchiver can throw
+    NSKeyedUnarchiver* unpacker =
+        [[NSKeyedUnarchiver alloc] initForReadingWithData:storeData];
+    unpacker.requiresSecureCoding = YES;
+    NSSet* classes =
+        [NSSet setWithObjects:[NSDictionary class], [KSTicket class],
+                              [KSPathExistenceChecker class], [NSArray class],
+                              [NSURL class], nil];
+    store = [unpacker decodeObjectOfClasses:classes
+                                     forKey:NSKeyedArchiveRootObjectKey];
+  } @catch (id e) {
+    VLOG(0) << base::SysNSStringToUTF8(
+        [NSString stringWithFormat:@"Ticket exception %@", e]);
+    return nil;
+  }
+  if (!store || ![store isKindOfClass:[NSDictionary class]]) {
+    VLOG(0) << "Ticket store is not a dictionary.";
+    return nil;
+  }
+  return store;
+}
+
+@end
+
+@implementation KSPathExistenceChecker
+
++ (BOOL)supportsSecureCoding {
+  return YES;
+}
+
+- (void)dealloc {
+  [path_ release];
+  [super dealloc];
+}
+
+- (id)initWithCoder:(NSCoder*)coder {
+  if ((self = [super init])) {
+    @try {
+      path_ = [[coder decodeObjectOfClass:[NSString class]
+                                   forKey:@"path"] retain];
+    } @catch (id e) {
+      VLOG(0) << base::SysNSStringToUTF8(
+          [NSString stringWithFormat:@"Coder exception %@", e]);
+      return nil;
+    }
+  }
+  return self;
+}
+
+- (void)encodeWithCoder:(NSCoder*)coder {
+  NOTREACHED() << "KSPathExistenceChecker::encodeWithCoder not implemented.";
+}
+
+- (NSString*)description {
+  // Formatting must stay the same in ksadmin output.
+  return [NSString
+      stringWithFormat:@"<%@:0x222222222222 path=%@>", [self class], path_];
+}
+
+@end
+
+NSString* const kKSTicketCohortKey = @"Cohort";
+NSString* const kKSTicketCohortHintKey = @"CohortHint";
+NSString* const kKSTicketCohortNameKey = @"CohortName";
+
+@implementation KSTicket {
+ @private
+  NSString* productID_;
+  NSString* version_;
+  KSPathExistenceChecker* existenceChecker_;
+  NSURL* serverURL_;
+  NSDate* creationDate_;
+  NSString* serverType_;
+  NSString* tag_;
+  NSString* tagPath_;
+  NSString* tagKey_;
+  NSString* brandPath_;
+  NSString* brandKey_;
+  NSString* versionPath_;
+  NSString* versionKey_;
+  NSString* cohort_;
+  NSString* cohortHint_;
+  NSString* cohortName_;
+  int32_t ticketVersion_;
+}
+
++ (BOOL)supportsSecureCoding {
+  return YES;
+}
+
+// Tries to obtain the server URL which may be NSURL or NSString object.
+// Verifies the read objects and guarantees that the returned object is NSURL.
+// The method may throw.
+- (NSURL*)decodeServerURL:(NSCoder*)decoder {
+  id serverURL = [decoder decodeObjectOfClasses:[NSSet setWithArray:@[
+                            [NSString class],
+                            [NSURL class],
+                          ]]
+                                         forKey:@"server_url"];
+  if (!serverURL)
+    return nil;
+  if ([serverURL isKindOfClass:[NSString class]]) {
+    return [NSURL URLWithString:serverURL];  // May throw
+  }
+  return (NSURL*)serverURL;
+}
+
+- (id)initWithCoder:(NSCoder*)coder {
+  if ((self = [super init])) {
+    @try {
+      productID_ = [[coder decodeObjectOfClass:[NSString class]
+                                        forKey:@"product_id"] retain];
+      version_ = [[coder decodeObjectOfClass:[NSString class]
+                                      forKey:@"version"] retain];
+      existenceChecker_ =
+          [[coder decodeObjectOfClass:[KSPathExistenceChecker class]
+                               forKey:@"existence_checker"] retain];
+      serverURL_ = [[self decodeServerURL:coder] retain];
+      creationDate_ = [[coder decodeObjectOfClass:[NSDate class]
+                                           forKey:@"creation_date"] retain];
+      serverType_ = [[coder decodeObjectOfClass:[NSString class]
+                                         forKey:@"serverType"] retain];
+      tag_ = [[coder decodeObjectOfClass:[NSString class]
+                                  forKey:@"tag"] retain];
+      tagPath_ = [[coder decodeObjectOfClass:[NSString class]
+                                      forKey:@"tagPath"] retain];
+      tagKey_ = [[coder decodeObjectOfClass:[NSString class]
+                                     forKey:@"tagKey"] retain];
+      brandPath_ = [[coder decodeObjectOfClass:[NSString class]
+                                        forKey:@"brandPath"] retain];
+      brandKey_ = [[coder decodeObjectOfClass:[NSString class]
+                                       forKey:@"brandKey"] retain];
+      versionPath_ = [[coder decodeObjectOfClass:[NSString class]
+                                          forKey:@"versionPath"] retain];
+      versionKey_ = [[coder decodeObjectOfClass:[NSString class]
+                                         forKey:@"versionKey"] retain];
+      cohort_ = [[coder decodeObjectOfClass:[NSString class]
+                                     forKey:kKSTicketCohortKey] retain];
+      cohortHint_ = [[coder decodeObjectOfClass:[NSString class]
+                                         forKey:kKSTicketCohortHintKey] retain];
+      cohortName_ = [[coder decodeObjectOfClass:[NSString class]
+                                         forKey:kKSTicketCohortNameKey] retain];
+      ticketVersion_ = [coder decodeInt32ForKey:@"ticketVersion"];
+    } @catch (id e) {
+      VLOG(0) << base::SysNSStringToUTF8(
+          [NSString stringWithFormat:@"Coder exception %@", e]);
+      return nil;
+    }
+  }
+  return self;
+}
+
+- (void)dealloc {
+  [productID_ release];
+  [version_ release];
+  [existenceChecker_ release];
+  [serverURL_ release];
+  [creationDate_ release];
+  [serverType_ release];
+  [tag_ release];
+  [tagPath_ release];
+  [tagKey_ release];
+  [brandPath_ release];
+  [brandKey_ release];
+  [versionPath_ release];
+  [versionKey_ release];
+  [cohort_ release];
+  [cohortHint_ release];
+  [cohortName_ release];
+
+  [super dealloc];
+}
+
+- (void)encodeWithCoder:(NSCoder*)coder {
+  NOTREACHED() << "KSTicket::encodeWithCoder not implemented.";
+}
+
+- (NSUInteger)hash {
+  return [productID_ hash] + [version_ hash] + [existenceChecker_ hash] +
+         [serverURL_ hash] + [creationDate_ hash];
+}
+
+- (NSString*)description {
+  // Keep the description stable.  Clients depend on the output formatting
+  // as "fieldname=value" without any additional quoting. We cannot use
+  // KSDescription() here because of these legacy formatting restrictions. In
+  // particular, ksadmin output must not be substantially changed.
+  NSString* serverTypeString = @"";
+  if (serverType_) {
+    serverTypeString =
+        [NSString stringWithFormat:@"\n\tserverType=%@", serverType_];
+  }
+  NSString* tagString = @"";
+  if (tag_) {
+    tagString = [NSString stringWithFormat:@"\n\ttag=%@", tag_];
+  }
+  NSString* tagPathString = @"";
+  if (tagPath_ && tagKey_) {
+    tagPathString = [NSString
+        stringWithFormat:@"\n\ttagPath=%@\n\ttagKey=%@", tagPath_, tagKey_];
+  }
+  NSString* brandPathString = @"";
+  if (brandPath_ && brandKey_) {
+    brandPathString =
+        [NSString stringWithFormat:@"\n\tbrandPath=%@\n\tbrandKey=%@",
+                                   brandPath_, brandKey_];
+  }
+  NSString* versionPathString = @"";
+  if (versionPath_ && versionKey_) {
+    versionPathString =
+        [NSString stringWithFormat:@"\n\tversionPath=%@\n\tversionKey=%@",
+                                   versionPath_, versionKey_];
+  }
+  NSString* cohortString = @"";
+  if ([cohort_ length]) {
+    cohortString = [NSString stringWithFormat:@"\n\tcohort=%@", cohort_];
+    if ([cohortName_ length]) {
+      cohortString = [cohortString
+          stringByAppendingFormat:@"\n\tcohortName=%@", cohortName_];
+    }
+  }
+  NSString* cohortHintString = @"";
+  if ([cohortHint_ length]) {
+    cohortHintString =
+        [NSString stringWithFormat:@"\n\tcohortHint=%@", cohortHint_];
+  }
+  NSString* ticketVersionString =
+      [NSString stringWithFormat:@"\n\tticketVersion=%d", ticketVersion_];
+  // Dates used to be parsed and stored as GMT and printed in GMT. That
+  // changed in 10.7 to be GMT with timezone information, so use a custom
+  // description string that matches our old output.
+  NSDateFormatter* dateFormatter = [[[NSDateFormatter alloc] init] autorelease];
+  [dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
+  [dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
+  NSString* gmtDate = [dateFormatter stringFromDate:creationDate_];
+  return [NSString
+      stringWithFormat:@"<%@:0x222222222222\n\tproductID=%@\n\tversion=%@\n\t"
+                       @"xc=%@%@\n\turl=%@\n\tcreationDate=%@%@%@%@%@%@%@%@\n>",
+                       [self class], productID_, version_, existenceChecker_,
+                       serverTypeString, serverURL_, gmtDate, tagString,
+                       tagPathString, brandPathString, versionPathString,
+                       cohortString, cohortHintString, ticketVersionString];
+}
+
+@end
diff --git a/chrome/updater/mac/keystone/ks_tickets_unittest.mm b/chrome/updater/mac/keystone/ks_tickets_unittest.mm
new file mode 100644
index 0000000..9753f0b
--- /dev/null
+++ b/chrome/updater/mac/keystone/ks_tickets_unittest.mm
@@ -0,0 +1,120 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "chrome/updater/mac/keystone/ks_tickets.h"
+
+#include "base/files/file_path.h"
+#include "base/path_service.h"
+#include "base/strings/sys_string_conversions.h"
+#include "chrome/common/chrome_paths.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace updater {
+
+TEST(KSTicketsTest, Decode) {
+  @autoreleasepool {
+    base::FilePath test_data_path;
+    ASSERT_TRUE(base::PathService::Get(chrome::DIR_TEST_DATA, &test_data_path));
+    NSDictionary* store = [KSTicketStore
+        readStoreWithPath:base::SysUTF8ToNSString(
+                              test_data_path
+                                  .Append(FILE_PATH_LITERAL("updater"))
+                                  .Append(
+                                      FILE_PATH_LITERAL("Keystone.ticketstore"))
+                                  .AsUTF8Unsafe())];
+
+    ASSERT_TRUE(store);
+    EXPECT_EQ([store count], 5ul);
+    EXPECT_EQ(
+        base::SysNSStringToUTF8(
+            [[store objectForKey:@"com.google.chrome.canary"] description]),
+        "<KSTicket:0x222222222222\n"
+        "\tproductID=com.google.Chrome.canary\n"
+        "\tversion=96.0.4662.0\n"
+        "\txc=<KSPathExistenceChecker:0x222222222222 "
+        "path=/Applications/Google Chrome Canary.app>\n"
+        "\tserverType=Omaha\n"
+        "\turl=https://tools.google.com/service/update2\n"
+        "\tcreationDate=2020-11-16 20:11:41\n"
+        "\ttag=canary\n"
+        "\ttagPath=/Applications/Google Chrome Canary.app/Contents/Info.plist\n"
+        "\ttagKey=KSChannelID\n"
+        "\tversionPath="
+        "/Applications/Google Chrome Canary.app/Contents/Info.plist\n"
+        "\tversionKey=KSVersion\n"
+        "\tcohort=1:0:\n"
+        "\tcohortName=Canary\n"
+        "\tticketVersion=1\n"
+        ">");
+    EXPECT_EQ(
+        base::SysNSStringToUTF8([[store
+            objectForKey:@"com.google.chrome_remote_desktop"] description]),
+        "<KSTicket:0x222222222222\n"
+        "\tproductID=com.google.chrome_remote_desktop\n"
+        "\tversion=94.0.4606.27\n"
+        "\txc=<KSPathExistenceChecker:0x222222222222 "
+        "path=/Library/LaunchAgents/org.chromium.chromoting.plist>\n"
+        "\tserverType=Omaha\n"
+        "\turl=https://tools.google.com/service/update2\n"
+        "\tcreationDate=2020-12-14 20:31:58\n"
+        "\tcohort=1:10ql:\n"
+        "\tcohortName=Stable\n"
+        "\tticketVersion=1\n"
+        ">");
+
+    EXPECT_EQ(base::SysNSStringToUTF8([[store
+                  objectForKey:@"com.google.secureconnect"] description]),
+              "<KSTicket:0x222222222222\n"
+              "\tproductID=com.google.SecureConnect\n"
+              "\tversion=2.1.8\n"
+              "\txc=<KSPathExistenceChecker:0x222222222222 "
+              "path=/Library/Application Support/Google/Endpoint "
+              "Verification/ApiHelper>\n"
+              "\tserverType=Omaha\n"
+              "\turl=https://tools.google.com/service/update2\n"
+              "\tcreationDate=2019-12-16 17:25:15\n"
+              "\tcohort=1::\n"
+              "\tticketVersion=1\n"
+              ">");
+
+    EXPECT_EQ(
+        base::SysNSStringToUTF8(
+            [[store objectForKey:@"com.google.chrome"] description]),
+        "<KSTicket:0x222222222222\n"
+        "\tproductID=com.google.Chrome\n"
+        "\tversion=94.0.4606.71\n"
+        "\txc=<KSPathExistenceChecker:0x222222222222 "
+        "path=/Applications/Google Chrome.app>\n"
+        "\tserverType=Omaha\n"
+        "\turl=https://tools.google.com/service/update2\n"
+        "\tcreationDate=2019-12-19 17:15:37\n"
+        "\ttagPath=/Applications/Google Chrome.app/Contents/Info.plist\n"
+        "\ttagKey=KSChannelID\n"
+        "\tbrandPath=/Library/Google/Google Chrome Brand.plist\n"
+        "\tbrandKey=KSBrandID\n"
+        "\tversionPath=/Applications/Google Chrome.app/Contents/Info.plist\n"
+        "\tversionKey=KSVersion\n"
+        "\tcohort=1:1y5:\n"
+        "\tcohortName=Stable\n"
+        "\tticketVersion=1\n"
+        ">");
+
+    EXPECT_EQ(base::SysNSStringToUTF8(
+                  [[store objectForKey:@"com.google.keystone"] description]),
+              "<KSTicket:0x222222222222\n"
+              "\tproductID=com.google.Keystone\n"
+              "\tversion=1.3.16.180\n"
+              "\txc=<KSPathExistenceChecker:0x222222222222 "
+              "path=/Library/Google/GoogleSoftwareUpdate/"
+              "GoogleSoftwareUpdate.bundle>\n"
+              "\turl=https://tools.google.com/service/update2\n"
+              "\tcreationDate=2019-12-16 17:18:45\n"
+              "\tcohort=1:0:\n"
+              "\tcohortName=Everyone\n"
+              "\tticketVersion=1\n"
+              ">");
+  }
+}
+
+}  // namespace updater
diff --git a/chrome/updater/mac/keystone/ksadmin.mm b/chrome/updater/mac/keystone/ksadmin.mm
index 9749c21..d8de9ce 100644
--- a/chrome/updater/mac/keystone/ksadmin.mm
+++ b/chrome/updater/mac/keystone/ksadmin.mm
@@ -20,11 +20,14 @@
 #include "base/memory/scoped_refptr.h"
 #include "base/message_loop/message_pump_type.h"
 #include "base/strings/string_util.h"
+#include "base/strings/sys_string_conversions.h"
 #include "base/task/single_thread_task_executor.h"
 #include "base/task/thread_pool.h"
 #include "base/task/thread_pool/thread_pool_instance.h"
 #include "base/threading/thread_restrictions.h"
 #include "chrome/updater/app/app.h"
+#include "chrome/updater/mac/keystone/ks_tickets.h"
+#include "chrome/updater/mac/mac_util.h"
 #include "chrome/updater/mac/update_service_proxy.h"
 #include "chrome/updater/registration_data.h"
 #include "chrome/updater/update_service.h"
@@ -87,12 +90,36 @@
   return result;
 }
 
+bool HasSwitch(const std::string& arg,
+               const std::map<std::string, std::string>& switches) {
+  if (base::Contains(switches, arg))
+    return true;
+  const static std::map<std::string, std::vector<std::string>> aliases = {
+      {kCommandDelete, {"d"}},        {kCommandInstall, {"i"}},
+      {kCommandList, {"l"}},          {kCommandKsadminVersion, {"k"}},
+      {kCommandPrintTag, {"G"}},      {kCommandPrintTickets, {"print", "p"}},
+      {kCommandRegister, {"r"}},      {kCommandSystemStore, {"S"}},
+      {kCommandUserInitiated, {"F"}}, {kCommandUserStore, {"U"}},
+  };
+  if (!base::Contains(aliases, arg))
+    return false;
+  for (const auto& alias : aliases.at(arg)) {
+    if (base::Contains(switches, alias))
+      return true;
+  }
+  return false;
+}
+
+UpdaterScope Scope(const std::map<std::string, std::string>& switches) {
+  return HasSwitch(kCommandSystemStore, switches) ? UpdaterScope::kSystem
+                                                  : UpdaterScope::kUser;
+}
+
 class KSAdminApp : public App {
  public:
   explicit KSAdminApp(const std::map<std::string, std::string>& switches)
-      : service_proxy_(
-            base::MakeRefCounted<UpdateServiceProxy>(GetUpdaterScope())),
-        switches_(switches) {}
+      : switches_(switches),
+        service_proxy_(base::MakeRefCounted<UpdateServiceProxy>(Scope())) {}
 
  private:
   ~KSAdminApp() override = default;
@@ -107,11 +134,12 @@
   void PrintVersion();
   void PrintTickets();
 
-  bool HasSwitch(const std::string& arg);
-  std::string SwitchValue(const std::string& arg);
+  UpdaterScope Scope() const;
+  bool HasSwitch(const std::string& arg) const;
+  std::string SwitchValue(const std::string& arg) const;
 
-  scoped_refptr<UpdateServiceProxy> service_proxy_;
   const std::map<std::string, std::string> switches_;
+  scoped_refptr<UpdateServiceProxy> service_proxy_;
 };
 
 void KSAdminApp::PrintUsage(const std::string& error_message) {
@@ -204,26 +232,11 @@
           base::BindOnce(&KSAdminApp::Shutdown, this)));
 }
 
-bool KSAdminApp::HasSwitch(const std::string& arg) {
-  if (base::Contains(switches_, arg))
-    return true;
-  const static std::map<std::string, std::vector<std::string>> aliases = {
-      {kCommandDelete, {"d"}},        {kCommandInstall, {"i"}},
-      {kCommandList, {"l"}},          {kCommandKsadminVersion, {"k"}},
-      {kCommandPrintTag, {"G"}},      {kCommandPrintTickets, {"print", "p"}},
-      {kCommandRegister, {"r"}},      {kCommandSystemStore, {"S"}},
-      {kCommandUserInitiated, {"F"}}, {kCommandUserStore, {"U"}},
-  };
-  if (!base::Contains(aliases, arg))
-    return false;
-  for (const auto& alias : aliases.at(arg)) {
-    if (base::Contains(switches_, alias))
-      return true;
-  }
-  return false;
+bool KSAdminApp::HasSwitch(const std::string& arg) const {
+  return updater::HasSwitch(arg, switches_);
 }
 
-std::string KSAdminApp::SwitchValue(const std::string& arg) {
+std::string KSAdminApp::SwitchValue(const std::string& arg) const {
   if (base::Contains(switches_, arg))
     return switches_.at(arg);
   const static std::map<std::string, std::string> aliases = {
@@ -239,6 +252,10 @@
   return base::Contains(switches_, alias) ? switches_.at(alias) : "";
 }
 
+UpdaterScope KSAdminApp::Scope() const {
+  return updater::Scope(switches_);
+}
+
 void KSAdminApp::Delete() {
   // TODO(crbug.com/1250524): Implement.
   Shutdown(1);
@@ -255,8 +272,26 @@
 }
 
 void KSAdminApp::PrintTickets() {
-  // TODO(crbug.com/1250524): Implement.
-  Shutdown(1);
+  // TODO(crbug.com/1250524): Print tickets owned by Chromium Updater. If there
+  // are any, suppress printing any legacy tickets.
+  @autoreleasepool {
+    const NSDictionary* store = [KSTicketStore
+        readStoreWithPath:base::SysUTF8ToNSString(
+                              GetKeystoneFolderPath(Scope())
+                                  ->Append(FILE_PATH_LITERAL("TicketStore"))
+                                  .Append(
+                                      FILE_PATH_LITERAL("Keystone.ticketstore"))
+                                  .AsUTF8Unsafe())];
+    if (store.count > 0) {
+      for (const id key in store) {
+        printf("%s\n",
+               base::SysNSStringToUTF8([store[key] description]).c_str());
+      }
+    } else {
+      printf("No tickets\n");
+    }
+  }
+  Shutdown(0);
 }
 
 void KSAdminApp::FirstTaskRun() {
@@ -289,12 +324,11 @@
 int KSAdminAppMain(int argc, char* argv[]) {
   base::AtExitManager exit_manager;
   base::CommandLine::Init(argc, argv);
-  // TODO(crbug.com/1250524): GetUpdaterScope() won't work: callers do not pass
-  // --system but rather --system-store. Check also other GetUpdaterScope calls
-  // in this code.
-  updater::InitLogging(GetUpdaterScope(), FILE_PATH_LITERAL("updater.log"));
+  std::map<std::string, std::string> command_line =
+      ParseCommandLine(argc, argv);
+  updater::InitLogging(Scope(command_line), FILE_PATH_LITERAL("updater.log"));
   base::SingleThreadTaskExecutor main_task_executor(base::MessagePumpType::UI);
-  return base::MakeRefCounted<KSAdminApp>(ParseCommandLine(argc, argv))->Run();
+  return base::MakeRefCounted<KSAdminApp>(command_line)->Run();
 }
 
 }  // namespace updater
diff --git a/chrome/updater/service_factory_win.cc b/chrome/updater/service_factory_win.cc
index 1ded800a..0d3920b 100644
--- a/chrome/updater/service_factory_win.cc
+++ b/chrome/updater/service_factory_win.cc
@@ -2,12 +2,13 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <wrl/module.h>
+
 #include "base/logging.h"
 #include "base/memory/ref_counted.h"
 #include "chrome/updater/updater_scope.h"
 #include "chrome/updater/win/update_service_internal_proxy.h"
 #include "chrome/updater/win/update_service_proxy.h"
-#include "chrome/updater/win/wrl_module.h"
 
 namespace updater {
 namespace {
diff --git a/chrome/updater/updater_scope.h b/chrome/updater/updater_scope.h
index bbda4d0..84255c2b 100644
--- a/chrome/updater/updater_scope.h
+++ b/chrome/updater/updater_scope.h
@@ -6,6 +6,7 @@
 #define CHROME_UPDATER_UPDATER_SCOPE_H_
 
 #include <ostream>
+#include <string>
 
 namespace updater {
 
@@ -18,15 +19,19 @@
   kSystem = 2,
 };
 
-inline std::ostream& operator<<(std::ostream& os, UpdaterScope scope) {
+inline std::string UpdaterScopeToString(UpdaterScope scope) {
   switch (scope) {
     case UpdaterScope::kUser:
-      return os << "User";
+      return "User";
     case UpdaterScope::kSystem:
-      return os << "System";
+      return "System";
   }
 }
 
+inline std::ostream& operator<<(std::ostream& os, UpdaterScope scope) {
+  return os << UpdaterScopeToString(scope).c_str();
+}
+
 // Returns the scope of the updater, which is either per-system or per-user.
 // The updater scope is determined from command line arguments of the process,
 // the presence and content of the --tag argument, and the integrity level
diff --git a/chrome/updater/win/wrl_module.h b/chrome/updater/win/wrl_module.h
deleted file mode 100644
index a563076..0000000
--- a/chrome/updater/win/wrl_module.h
+++ /dev/null
@@ -1,14 +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 CHROME_UPDATER_WIN_WRL_MODULE_H_
-#define CHROME_UPDATER_WIN_WRL_MODULE_H_
-
-#if !defined(__WRL_CLASSIC_COM_STRICT__)
-#error "WRL must not depend on WinRT."
-#endif
-
-#include <wrl/module.h>
-
-#endif  // CHROME_UPDATER_WIN_WRL_MODULE_H_
diff --git a/chromecast/base/chromecast_switches.cc b/chromecast/base/chromecast_switches.cc
index 68b80b1..04c0e1c 100644
--- a/chromecast/base/chromecast_switches.cc
+++ b/chromecast/base/chromecast_switches.cc
@@ -186,6 +186,9 @@
 // Whether to enable the drawing of rounded window corners in the root window.
 const char kEnableRoundedWindowCorners[] = "enable-rounded-window-corners";
 
+// Whether in hospitality mode
+const char kManagedMode[] = "managed-mode";
+
 // Endpoint that the mixer service listens on. This is a path for a UNIX domain
 // socket (default is /tmp/mixer-service).
 const char kMixerServiceEndpoint[] = "mixer-service-endpoint";
diff --git a/chromecast/base/chromecast_switches.h b/chromecast/base/chromecast_switches.h
index d06ee62..7ccb5a2 100644
--- a/chromecast/base/chromecast_switches.h
+++ b/chromecast/base/chromecast_switches.h
@@ -84,6 +84,7 @@
 extern const char kBackGestureHorizontalThreshold[];
 extern const char kEnableTopDragGesture[];
 extern const char kEnableRoundedWindowCorners[];
+extern const char kManagedMode[];
 
 // Background color used when Chromium hasn't rendered anything yet.
 extern const char kCastAppBackgroundColor[];
diff --git a/chromecast/browser/BUILD.gn b/chromecast/browser/BUILD.gn
index 52159fe..0c484130 100644
--- a/chromecast/browser/BUILD.gn
+++ b/chromecast/browser/BUILD.gn
@@ -584,6 +584,8 @@
     "cast_web_contents.h",
     "cast_web_view.cc",
     "cast_web_view.h",
+    "gesture_router.cc",
+    "gesture_router.h",
   ]
 
   # Need to expose this so that internal public_configs are propagated.
@@ -592,6 +594,7 @@
     ":client",
     "//chromecast/browser/mojom",
     "//chromecast/common/mojom",
+    "//chromecast/mojo",
     "//content/public/browser",
   ]
 
@@ -601,7 +604,9 @@
     "//chromecast/graphics",
     "//chromecast/ui:back_gesture_router",
     "//chromecast/ui/mojom",
+    "//content/public/browser",
     "//content/public/common",
+    "//mojo/public/cpp/bindings",
     "//ui/events",
     "//url",
   ]
diff --git a/chromecast/browser/android/cast_content_window_android.cc b/chromecast/browser/android/cast_content_window_android.cc
index d37e553..ec2f56e 100644
--- a/chromecast/browser/android/cast_content_window_android.cc
+++ b/chromecast/browser/android/cast_content_window_android.cc
@@ -181,8 +181,8 @@
     const base::android::JavaParamRef<jobject>& callback) {
   auto wrapper =
       std::make_unique<GestureConsumedCallbackWrapper>(env, callback);
-  if (delegate_) {
-    delegate_->ConsumeGesture(
+  if (gesture_router()) {
+    gesture_router()->ConsumeGesture(
         static_cast<GestureType>(gesture_type),
         base::BindOnce(&GestureConsumedCallbackWrapper::Invoke,
                        std::move(wrapper)));
diff --git a/chromecast/browser/cast_content_gesture_handler.cc b/chromecast/browser/cast_content_gesture_handler.cc
index 85b19b8..795a086 100644
--- a/chromecast/browser/cast_content_gesture_handler.cc
+++ b/chromecast/browser/cast_content_gesture_handler.cc
@@ -15,7 +15,7 @@
 }  // namespace
 
 CastContentGestureHandler::CastContentGestureHandler(
-    base::WeakPtr<CastContentWindow::Delegate> delegate,
+    GestureRouter* delegate,
     bool enable_top_drag_gesture)
     : priority_(Priority::NONE),
       enable_top_drag_gesture_(enable_top_drag_gesture),
@@ -26,8 +26,7 @@
   DCHECK(delegate_);
 }
 
-CastContentGestureHandler::CastContentGestureHandler(
-    base::WeakPtr<CastContentWindow::Delegate> delegate)
+CastContentGestureHandler::CastContentGestureHandler(GestureRouter* delegate)
     : CastContentGestureHandler(
           delegate,
           GetSwitchValueBoolean(switches::kEnableTopDragGesture, false)) {}
diff --git a/chromecast/browser/cast_content_gesture_handler.h b/chromecast/browser/cast_content_gesture_handler.h
index 2071c67d..c1308970 100644
--- a/chromecast/browser/cast_content_gesture_handler.h
+++ b/chromecast/browser/cast_content_gesture_handler.h
@@ -8,7 +8,7 @@
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
 #include "base/timer/elapsed_timer.h"
-#include "chromecast/browser/cast_content_window.h"
+#include "chromecast/browser/gesture_router.h"
 #include "chromecast/graphics/gestures/cast_gesture_handler.h"
 
 namespace chromecast {
@@ -17,8 +17,9 @@
 // CastContentWindow::Delegate.
 class CastContentGestureHandler : public CastGestureHandler {
  public:
-  explicit CastContentGestureHandler(
-      base::WeakPtr<CastContentWindow::Delegate> delegate);
+  explicit CastContentGestureHandler(GestureRouter* delegate);
+  CastContentGestureHandler(GestureRouter* delegate,
+                            bool enable_top_drag_gesture);
   ~CastContentGestureHandler() override;
 
   // CastGestureHandler implementation:
@@ -33,9 +34,6 @@
   void SetPriority(Priority priority);
 
  private:
-  friend class CastContentGestureHandlerTest;
-  CastContentGestureHandler(base::WeakPtr<CastContentWindow::Delegate> delegate,
-                            bool enable_top_drag_gesture);
   GestureType GestureForSwipeOrigin(CastSideSwipeOrigin swipe_origin);
 
   Priority priority_;
@@ -44,7 +42,7 @@
 
   // Number of pixels past swipe origin to consider as a back gesture.
   const int back_horizontal_threshold_;
-  base::WeakPtr<CastContentWindow::Delegate> const delegate_;
+  GestureRouter* const delegate_;
   base::ElapsedTimer current_swipe_time_;
 };
 
diff --git a/chromecast/browser/cast_content_gesture_handler_test.cc b/chromecast/browser/cast_content_gesture_handler_test.cc
index 87ee316..20523211 100644
--- a/chromecast/browser/cast_content_gesture_handler_test.cc
+++ b/chromecast/browser/cast_content_gesture_handler_test.cc
@@ -40,211 +40,200 @@
 constexpr gfx::Point kOngoingRightGesturePoint1(400, 50);
 constexpr gfx::Point kRightGestureEndPoint(200, 60);
 
+class MockGestureHandler : public mojom::GestureHandler {
+ public:
+  MockGestureHandler() = default;
+  ~MockGestureHandler() override = default;
+
+  MOCK_METHOD(void, OnBackGesture, (OnBackGestureCallback), (override));
+  MOCK_METHOD(void, OnBackGestureProgress, (const gfx::Point&), (override));
+  MOCK_METHOD(void, OnTopDragGestureProgress, (const gfx::Point&), (override));
+  MOCK_METHOD(void, OnTopDragGestureDone, (), (override));
+  MOCK_METHOD(void,
+              OnRightDragGestureProgress,
+              (const gfx::Point&),
+              (override));
+  MOCK_METHOD(void, OnRightDragGestureDone, (), (override));
+  MOCK_METHOD(void, OnBackGestureCancel, (), (override));
+  MOCK_METHOD(void, OnTapGesture, (), (override));
+  MOCK_METHOD(void, OnTapDownGesture, (), (override));
+};
+
 }  // namespace
 
 class CastContentGestureHandlerTest : public testing::Test {
  public:
-  CastContentGestureHandlerTest()
-      : delegate_(new testing::StrictMock<MockCastContentWindowDelegate>),
-        dispatcher_(delegate_->AsWeakPtr(), true) {}
+  CastContentGestureHandlerTest() {}
+
+  void SetUp() final {
+    gesture_router_ = std::make_unique<GestureRouter>();
+    dispatcher_ = std::make_unique<CastContentGestureHandler>(
+        gesture_router_.get(), true);
+    gesture_router_->SetHandler(&handler_);
+  }
 
  protected:
-  std::unique_ptr<testing::StrictMock<MockCastContentWindowDelegate>> delegate_;
-  CastContentGestureHandler dispatcher_;
+  std::unique_ptr<GestureRouter> gesture_router_;
+  std::unique_ptr<CastContentGestureHandler> dispatcher_;
+  MockGestureHandler handler_;
 };
 
 // Verify the simple case of a left swipe with the right horizontal leads to
 // back.
 TEST_F(CastContentGestureHandlerTest, VerifySimpleBackSuccess) {
-  EXPECT_CALL(*delegate_, CanHandleGesture(Eq(GestureType::TOP_DRAG)))
-      .WillRepeatedly(Return(false));
-
-  EXPECT_CALL(*delegate_, CanHandleGesture(Eq(GestureType::GO_BACK)))
-      .WillRepeatedly(Return(true));
-  EXPECT_CALL(*delegate_, GestureProgress(Eq(GestureType::GO_BACK),
-                                          Eq(kOngoingBackGesturePoint1)));
-  EXPECT_CALL(*delegate_, ConsumeGesture(Eq(GestureType::GO_BACK), _))
-      .WillRepeatedly(
-          [](auto, auto callback) { std::move(callback).Run(true); });
-  dispatcher_.CanHandleSwipe(CastSideSwipeOrigin::LEFT);
-  dispatcher_.HandleSideSwipe(CastSideSwipeEvent::BEGIN,
-                              CastSideSwipeOrigin::LEFT, kLeftSidePoint);
-  dispatcher_.HandleSideSwipe(CastSideSwipeEvent::CONTINUE,
-                              CastSideSwipeOrigin::LEFT,
-                              kOngoingBackGesturePoint1);
-  dispatcher_.HandleSideSwipe(CastSideSwipeEvent::END,
-                              CastSideSwipeOrigin::LEFT,
-                              kValidBackGestureEndPoint);
+  gesture_router_->SetCanTopDrag(false);
+  gesture_router_->SetCanGoBack(true);
+  EXPECT_CALL(handler_, OnBackGestureProgress(Eq(kOngoingBackGesturePoint1)));
+  EXPECT_CALL(handler_, OnBackGesture(_));
+  ASSERT_TRUE(dispatcher_->CanHandleSwipe(CastSideSwipeOrigin::LEFT));
+  dispatcher_->HandleSideSwipe(CastSideSwipeEvent::BEGIN,
+                               CastSideSwipeOrigin::LEFT, kLeftSidePoint);
+  dispatcher_->HandleSideSwipe(CastSideSwipeEvent::CONTINUE,
+                               CastSideSwipeOrigin::LEFT,
+                               kOngoingBackGesturePoint1);
+  dispatcher_->HandleSideSwipe(CastSideSwipeEvent::END,
+                               CastSideSwipeOrigin::LEFT,
+                               kValidBackGestureEndPoint);
 }
 
 // Verify that if the finger is not lifted, that's not a back gesture.
 TEST_F(CastContentGestureHandlerTest, VerifyNoDispatchOnNoLift) {
-  EXPECT_CALL(*delegate_, CanHandleGesture(Eq(GestureType::TOP_DRAG)))
-      .WillRepeatedly(Return(false));
-
-  EXPECT_CALL(*delegate_, CanHandleGesture(Eq(GestureType::GO_BACK)))
-      .WillRepeatedly(Return(true));
-  EXPECT_CALL(*delegate_, ConsumeGesture(Eq(GestureType::GO_BACK), _)).Times(0);
-  EXPECT_CALL(*delegate_, GestureProgress(Eq(GestureType::GO_BACK),
-                                          Eq(kValidBackGestureEndPoint)));
-  EXPECT_CALL(*delegate_,
-              GestureProgress(Eq(GestureType::GO_BACK), Eq(kPastTheEndPoint1)));
-  dispatcher_.CanHandleSwipe(CastSideSwipeOrigin::LEFT);
-  dispatcher_.HandleSideSwipe(CastSideSwipeEvent::BEGIN,
-                              CastSideSwipeOrigin::LEFT, kLeftSidePoint);
-  dispatcher_.HandleSideSwipe(CastSideSwipeEvent::CONTINUE,
-                              CastSideSwipeOrigin::LEFT,
-                              kValidBackGestureEndPoint);
-  dispatcher_.HandleSideSwipe(CastSideSwipeEvent::CONTINUE,
-                              CastSideSwipeOrigin::LEFT, kPastTheEndPoint1);
+  gesture_router_->SetCanTopDrag(false);
+  gesture_router_->SetCanGoBack(true);
+  EXPECT_CALL(handler_, OnBackGestureProgress(Eq(kValidBackGestureEndPoint)));
+  EXPECT_CALL(handler_, OnBackGesture(_)).Times(0);
+  EXPECT_CALL(handler_, OnBackGestureProgress(Eq(kPastTheEndPoint1)));
+  ASSERT_TRUE(dispatcher_->CanHandleSwipe(CastSideSwipeOrigin::LEFT));
+  dispatcher_->HandleSideSwipe(CastSideSwipeEvent::BEGIN,
+                               CastSideSwipeOrigin::LEFT, kLeftSidePoint);
+  dispatcher_->HandleSideSwipe(CastSideSwipeEvent::CONTINUE,
+                               CastSideSwipeOrigin::LEFT,
+                               kValidBackGestureEndPoint);
+  dispatcher_->HandleSideSwipe(CastSideSwipeEvent::CONTINUE,
+                               CastSideSwipeOrigin::LEFT, kPastTheEndPoint1);
 }
 
 // Verify that multiple 'continue' events still only lead to one back
 // invocation.
 TEST_F(CastContentGestureHandlerTest, VerifyOnlySingleDispatch) {
-  EXPECT_CALL(*delegate_, CanHandleGesture(Eq(GestureType::TOP_DRAG)))
-      .WillRepeatedly(Return(false));
+  gesture_router_->SetCanTopDrag(false);
+  gesture_router_->SetCanGoBack(true);
 
-  EXPECT_CALL(*delegate_, CanHandleGesture(Eq(GestureType::GO_BACK)))
-      .WillRepeatedly(Return(true));
-  EXPECT_CALL(*delegate_, GestureProgress(Eq(GestureType::GO_BACK),
-                                          Eq(kValidBackGestureEndPoint)));
-  EXPECT_CALL(*delegate_,
-              GestureProgress(Eq(GestureType::GO_BACK), Eq(kPastTheEndPoint1)));
-  EXPECT_CALL(*delegate_, ConsumeGesture(Eq(GestureType::GO_BACK), _))
-      .WillRepeatedly(
-          [](auto, auto callback) { std::move(callback).Run(true); });
-  dispatcher_.CanHandleSwipe(CastSideSwipeOrigin::LEFT);
-  dispatcher_.HandleSideSwipe(CastSideSwipeEvent::BEGIN,
-                              CastSideSwipeOrigin::LEFT, kLeftSidePoint);
-  dispatcher_.HandleSideSwipe(CastSideSwipeEvent::CONTINUE,
-                              CastSideSwipeOrigin::LEFT,
-                              kValidBackGestureEndPoint);
-  dispatcher_.HandleSideSwipe(CastSideSwipeEvent::CONTINUE,
-                              CastSideSwipeOrigin::LEFT, kPastTheEndPoint1);
-  dispatcher_.HandleSideSwipe(CastSideSwipeEvent::END,
-                              CastSideSwipeOrigin::LEFT, kPastTheEndPoint2);
+  EXPECT_CALL(handler_, OnBackGestureProgress(Eq(kValidBackGestureEndPoint)));
+  EXPECT_CALL(handler_, OnBackGestureProgress(Eq(kPastTheEndPoint1)));
+  EXPECT_CALL(handler_, OnBackGesture(_));
+  ASSERT_TRUE(dispatcher_->CanHandleSwipe(CastSideSwipeOrigin::LEFT));
+  dispatcher_->HandleSideSwipe(CastSideSwipeEvent::BEGIN,
+                               CastSideSwipeOrigin::LEFT, kLeftSidePoint);
+  dispatcher_->HandleSideSwipe(CastSideSwipeEvent::CONTINUE,
+                               CastSideSwipeOrigin::LEFT,
+                               kValidBackGestureEndPoint);
+  dispatcher_->HandleSideSwipe(CastSideSwipeEvent::CONTINUE,
+                               CastSideSwipeOrigin::LEFT, kPastTheEndPoint1);
+  dispatcher_->HandleSideSwipe(CastSideSwipeEvent::END,
+                               CastSideSwipeOrigin::LEFT, kPastTheEndPoint2);
 }
 
 // Verify that if the delegate says it doesn't handle back that we won't try to
 // ask them to consume it.
 TEST_F(CastContentGestureHandlerTest, VerifyDelegateDoesNotConsumeUnwanted) {
-  EXPECT_CALL(*delegate_, CanHandleGesture(Eq(GestureType::TOP_DRAG)))
-      .WillRepeatedly(Return(false));
-
-  EXPECT_CALL(*delegate_, CanHandleGesture(Eq(GestureType::GO_BACK)))
-      .WillRepeatedly(Return(false));
-  dispatcher_.CanHandleSwipe(CastSideSwipeOrigin::LEFT);
-  dispatcher_.HandleSideSwipe(CastSideSwipeEvent::BEGIN,
-                              CastSideSwipeOrigin::LEFT, kLeftSidePoint);
-  dispatcher_.HandleSideSwipe(CastSideSwipeEvent::CONTINUE,
-                              CastSideSwipeOrigin::LEFT,
-                              kValidBackGestureEndPoint);
-  dispatcher_.HandleSideSwipe(CastSideSwipeEvent::END,
-                              CastSideSwipeOrigin::LEFT, kPastTheEndPoint2);
+  gesture_router_->SetCanTopDrag(false);
+  gesture_router_->SetCanGoBack(false);
+  ASSERT_FALSE(dispatcher_->CanHandleSwipe(CastSideSwipeOrigin::LEFT));
+  dispatcher_->HandleSideSwipe(CastSideSwipeEvent::BEGIN,
+                               CastSideSwipeOrigin::LEFT, kLeftSidePoint);
+  dispatcher_->HandleSideSwipe(CastSideSwipeEvent::CONTINUE,
+                               CastSideSwipeOrigin::LEFT,
+                               kValidBackGestureEndPoint);
+  dispatcher_->HandleSideSwipe(CastSideSwipeEvent::END,
+                               CastSideSwipeOrigin::LEFT, kPastTheEndPoint2);
 }
 
 // Verify that a not-left gesture doesn't lead to a swipe.
 TEST_F(CastContentGestureHandlerTest, VerifyNotLeftSwipeIsNotBack) {
-  EXPECT_CALL(*delegate_, CanHandleGesture(Eq(GestureType::TOP_DRAG)))
-      .WillRepeatedly(Return(false));
+  gesture_router_->SetCanTopDrag(false);
 
-  dispatcher_.CanHandleSwipe(CastSideSwipeOrigin::TOP);
-  dispatcher_.HandleSideSwipe(CastSideSwipeEvent::BEGIN,
-                              CastSideSwipeOrigin::TOP, kTopSidePoint);
-  dispatcher_.HandleSideSwipe(CastSideSwipeEvent::CONTINUE,
-                              CastSideSwipeOrigin::TOP,
-                              kOngoingTopGesturePoint2);
+  ASSERT_FALSE(dispatcher_->CanHandleSwipe(CastSideSwipeOrigin::TOP));
+  dispatcher_->HandleSideSwipe(CastSideSwipeEvent::BEGIN,
+                               CastSideSwipeOrigin::TOP, kTopSidePoint);
+  dispatcher_->HandleSideSwipe(CastSideSwipeEvent::CONTINUE,
+                               CastSideSwipeOrigin::TOP,
+                               kOngoingTopGesturePoint2);
 }
 
 // Verify that if the gesture doesn't go far enough horizontally that we will
 // not consider it a swipe.
 TEST_F(CastContentGestureHandlerTest, VerifyNotFarEnoughRightIsNotBack) {
-  EXPECT_CALL(*delegate_, CanHandleGesture(Eq(GestureType::TOP_DRAG)))
-      .WillRepeatedly(Return(false));
+  gesture_router_->SetCanTopDrag(false);
+  gesture_router_->SetCanGoBack(true);
 
-  EXPECT_CALL(*delegate_, CanHandleGesture(Eq(GestureType::GO_BACK)))
-      .WillRepeatedly(Return(true));
-  EXPECT_CALL(*delegate_, GestureProgress(Eq(GestureType::GO_BACK),
-                                          Eq(kOngoingBackGesturePoint1)));
-  EXPECT_CALL(*delegate_, CancelGesture(Eq(GestureType::GO_BACK)));
-  dispatcher_.CanHandleSwipe(CastSideSwipeOrigin::LEFT);
-  dispatcher_.HandleSideSwipe(CastSideSwipeEvent::BEGIN,
-                              CastSideSwipeOrigin::LEFT, kLeftSidePoint);
-  dispatcher_.HandleSideSwipe(CastSideSwipeEvent::CONTINUE,
-                              CastSideSwipeOrigin::LEFT,
-                              kOngoingBackGesturePoint1);
-  dispatcher_.HandleSideSwipe(CastSideSwipeEvent::END,
-                              CastSideSwipeOrigin::LEFT,
-                              kOngoingBackGesturePoint2);
+  EXPECT_CALL(handler_, OnBackGestureProgress(Eq(kOngoingBackGesturePoint1)));
+  EXPECT_CALL(handler_, OnBackGestureCancel());
+  ASSERT_TRUE(dispatcher_->CanHandleSwipe(CastSideSwipeOrigin::LEFT));
+  dispatcher_->HandleSideSwipe(CastSideSwipeEvent::BEGIN,
+                               CastSideSwipeOrigin::LEFT, kLeftSidePoint);
+  dispatcher_->HandleSideSwipe(CastSideSwipeEvent::CONTINUE,
+                               CastSideSwipeOrigin::LEFT,
+                               kOngoingBackGesturePoint1);
+  dispatcher_->HandleSideSwipe(CastSideSwipeEvent::END,
+                               CastSideSwipeOrigin::LEFT,
+                               kOngoingBackGesturePoint2);
 }
 
 // Verify that if the gesture ends before going far enough, that's also not a
 // swipe.
 TEST_F(CastContentGestureHandlerTest, VerifyNotFarEnoughRightAndEndIsNotBack) {
-  EXPECT_CALL(*delegate_, CanHandleGesture(Eq(GestureType::TOP_DRAG)))
-      .WillRepeatedly(Return(false));
+  gesture_router_->SetCanTopDrag(false);
+  gesture_router_->SetCanGoBack(true);
 
-  EXPECT_CALL(*delegate_, CanHandleGesture(Eq(GestureType::GO_BACK)))
-      .WillRepeatedly(Return(true));
-  EXPECT_CALL(*delegate_, GestureProgress(Eq(GestureType::GO_BACK),
-                                          Eq(kOngoingBackGesturePoint1)));
-  EXPECT_CALL(*delegate_, CancelGesture(Eq(GestureType::GO_BACK)));
-  dispatcher_.CanHandleSwipe(CastSideSwipeOrigin::LEFT);
-  dispatcher_.HandleSideSwipe(CastSideSwipeEvent::BEGIN,
-                              CastSideSwipeOrigin::LEFT, kLeftSidePoint);
-  dispatcher_.HandleSideSwipe(CastSideSwipeEvent::CONTINUE,
-                              CastSideSwipeOrigin::LEFT,
-                              kOngoingBackGesturePoint1);
-  dispatcher_.HandleSideSwipe(CastSideSwipeEvent::END,
-                              CastSideSwipeOrigin::LEFT,
-                              kOngoingBackGesturePoint2);
+  EXPECT_CALL(handler_, OnBackGestureProgress(Eq(kOngoingBackGesturePoint1)));
+  EXPECT_CALL(handler_, OnBackGestureCancel());
+  ASSERT_TRUE(dispatcher_->CanHandleSwipe(CastSideSwipeOrigin::LEFT));
+  dispatcher_->HandleSideSwipe(CastSideSwipeEvent::BEGIN,
+                               CastSideSwipeOrigin::LEFT, kLeftSidePoint);
+  dispatcher_->HandleSideSwipe(CastSideSwipeEvent::CONTINUE,
+                               CastSideSwipeOrigin::LEFT,
+                               kOngoingBackGesturePoint1);
+  dispatcher_->HandleSideSwipe(CastSideSwipeEvent::END,
+                               CastSideSwipeOrigin::LEFT,
+                               kOngoingBackGesturePoint2);
 }
 
 // Verify simple top-down drag.
 TEST_F(CastContentGestureHandlerTest, VerifySimpleTopSuccess) {
-  EXPECT_CALL(*delegate_, CanHandleGesture(Eq(GestureType::TOP_DRAG)))
-      .WillRepeatedly(Return(true));
-  EXPECT_CALL(*delegate_, CanHandleGesture(Eq(GestureType::GO_BACK)))
-      .WillRepeatedly(Return(false));
+  gesture_router_->SetCanTopDrag(true);
+  gesture_router_->SetCanGoBack(false);
 
-  EXPECT_CALL(*delegate_, GestureProgress(Eq(GestureType::TOP_DRAG),
-                                          Eq(kOngoingTopGesturePoint1)));
-  EXPECT_CALL(*delegate_, ConsumeGesture(Eq(GestureType::TOP_DRAG), _))
-      .WillRepeatedly(
-          [](auto, auto callback) { std::move(callback).Run(true); });
-  dispatcher_.CanHandleSwipe(CastSideSwipeOrigin::TOP);
-  dispatcher_.HandleSideSwipe(CastSideSwipeEvent::BEGIN,
-                              CastSideSwipeOrigin::TOP, kTopSidePoint);
-  dispatcher_.HandleSideSwipe(CastSideSwipeEvent::CONTINUE,
-                              CastSideSwipeOrigin::TOP,
-                              kOngoingTopGesturePoint1);
-  dispatcher_.HandleSideSwipe(CastSideSwipeEvent::END,
-                              CastSideSwipeOrigin::LEFT, kTopGestureEndPoint);
+  EXPECT_CALL(handler_, OnTopDragGestureProgress(Eq(kOngoingTopGesturePoint1)));
+  EXPECT_CALL(handler_, OnTopDragGestureDone());
+  ASSERT_TRUE(dispatcher_->CanHandleSwipe(CastSideSwipeOrigin::TOP));
+  dispatcher_->HandleSideSwipe(CastSideSwipeEvent::BEGIN,
+                               CastSideSwipeOrigin::TOP, kTopSidePoint);
+  dispatcher_->HandleSideSwipe(CastSideSwipeEvent::CONTINUE,
+                               CastSideSwipeOrigin::TOP,
+                               kOngoingTopGesturePoint1);
+  dispatcher_->HandleSideSwipe(CastSideSwipeEvent::END,
+                               CastSideSwipeOrigin::TOP, kTopGestureEndPoint);
 }
 
 // Verify simple right-to-left drag.
 TEST_F(CastContentGestureHandlerTest, VerifySimpleRightSuccess) {
-  EXPECT_CALL(*delegate_, CanHandleGesture(Eq(GestureType::RIGHT_DRAG)))
-      .WillRepeatedly(Return(true));
-  EXPECT_CALL(*delegate_, CanHandleGesture(Eq(GestureType::GO_BACK)))
-      .WillRepeatedly(Return(false));
-  EXPECT_CALL(*delegate_, CanHandleGesture(Eq(GestureType::TOP_DRAG)))
-      .WillRepeatedly(Return(false));
+  gesture_router_->SetCanRightDrag(true);
+  gesture_router_->SetCanTopDrag(false);
+  gesture_router_->SetCanGoBack(false);
 
-  EXPECT_CALL(*delegate_, GestureProgress(Eq(GestureType::RIGHT_DRAG),
-                                          Eq(kOngoingRightGesturePoint1)));
-  EXPECT_CALL(*delegate_, ConsumeGesture(Eq(GestureType::RIGHT_DRAG), _))
-      .WillRepeatedly(
-          [](auto, auto callback) { std::move(callback).Run(true); });
-  dispatcher_.CanHandleSwipe(CastSideSwipeOrigin::RIGHT);
-  dispatcher_.HandleSideSwipe(CastSideSwipeEvent::BEGIN,
-                              CastSideSwipeOrigin::RIGHT, kRightSidePoint);
-  dispatcher_.HandleSideSwipe(CastSideSwipeEvent::CONTINUE,
-                              CastSideSwipeOrigin::RIGHT,
-                              kOngoingRightGesturePoint1);
-  dispatcher_.HandleSideSwipe(CastSideSwipeEvent::END,
-                              CastSideSwipeOrigin::LEFT, kRightGestureEndPoint);
+  EXPECT_CALL(handler_,
+              OnRightDragGestureProgress(Eq(kOngoingRightGesturePoint1)));
+  EXPECT_CALL(handler_, OnRightDragGestureDone());
+  ASSERT_TRUE(dispatcher_->CanHandleSwipe(CastSideSwipeOrigin::RIGHT));
+  dispatcher_->HandleSideSwipe(CastSideSwipeEvent::BEGIN,
+                               CastSideSwipeOrigin::RIGHT, kRightSidePoint);
+  dispatcher_->HandleSideSwipe(CastSideSwipeEvent::CONTINUE,
+                               CastSideSwipeOrigin::RIGHT,
+                               kOngoingRightGesturePoint1);
+  dispatcher_->HandleSideSwipe(CastSideSwipeEvent::END,
+                               CastSideSwipeOrigin::RIGHT,
+                               kRightGestureEndPoint);
 }
 
 }  // namespace chromecast
diff --git a/chromecast/browser/cast_content_window.cc b/chromecast/browser/cast_content_window.cc
index bff258e..c7a90734 100644
--- a/chromecast/browser/cast_content_window.cc
+++ b/chromecast/browser/cast_content_window.cc
@@ -8,10 +8,24 @@
 
 CastContentWindow::CastContentWindow(base::WeakPtr<Delegate> delegate,
                                      mojom::CastWebViewParamsPtr params)
-    : delegate_(delegate), params_(std::move(params)) {}
+    : delegate_(delegate), params_(std::move(params)) {
+  RegisterBackGestureRouter(gesture_router());
+}
 
 CastContentWindow::~CastContentWindow() = default;
 
+void CastContentWindow::SetCastWebContents(CastWebContents* cast_web_contents) {
+  cast_web_contents_ = cast_web_contents;
+  // Must provide binder callbacks with WeakPtr since CastContentWindow + these
+  // interface implementations are destroyed before CastWebContents.
+  cast_web_contents_->local_interfaces()->AddBinder(base::BindRepeating(
+      &CastContentWindow::BindReceiver, weak_factory_.GetWeakPtr()));
+  cast_web_contents_->local_interfaces()->AddBinder(base::BindRepeating(
+      &CastContentWindow::BindActivityWindow, weak_factory_.GetWeakPtr()));
+  cast_web_contents_->local_interfaces()->AddBinder(
+      gesture_router()->GetBinder());
+}
+
 void CastContentWindow::AddObserver(Observer* observer) {
   observer_list_.AddObserver(observer);
 }
@@ -25,6 +39,19 @@
   receiver_.Bind(std::move(receiver));
 }
 
+void CastContentWindow::BindActivityWindow(
+    mojo::PendingReceiver<mojom::ActivityWindow> receiver) {
+  activity_window_receiver_.Bind(std::move(receiver));
+}
+
+void CastContentWindow::Show() {
+  RequestVisibility(VisibilityPriority::STICKY_ACTIVITY);
+}
+
+void CastContentWindow::Hide() {
+  RequestMoveOut();
+}
+
 mojom::MediaControlUi* CastContentWindow::media_controls() {
   return nullptr;
 }
diff --git a/chromecast/browser/cast_content_window.h b/chromecast/browser/cast_content_window.h
index 200a4d5..2b6974ef 100644
--- a/chromecast/browser/cast_content_window.h
+++ b/chromecast/browser/cast_content_window.h
@@ -11,9 +11,11 @@
 #include "base/observer_list.h"
 #include "base/observer_list_types.h"
 #include "chromecast/browser/cast_web_contents.h"
+#include "chromecast/browser/gesture_router.h"
 #include "chromecast/browser/mojom/cast_content_window.mojom.h"
 #include "chromecast/browser/mojom/cast_web_service.mojom.h"
 #include "chromecast/browser/visibility_types.h"
+#include "chromecast/common/mojom/activity_window.mojom.h"
 #include "chromecast/graphics/gestures/cast_gesture_handler.h"
 #include "chromecast/ui/back_gesture_router.h"
 #include "chromecast/ui/mojom/media_control_ui.mojom.h"
@@ -27,32 +29,14 @@
 // Class that represents the "window" a WebContents is displayed in cast_shell.
 // For Linux, this represents an Aura window. For Android, this is a Activity.
 // See CastContentWindowAura and CastContentWindowAndroid.
-class CastContentWindow : public mojom::CastContentWindow {
+class CastContentWindow : public mojom::CastContentWindow,
+                          public mojom::ActivityWindow {
  public:
   class Delegate {
    public:
     // Notify window destruction.
     virtual void OnWindowDestroyed() {}
 
-    using GestureHandledCallback = base::OnceCallback<void(bool)>;
-
-    // Check to see if the gesture can be handled by the delegate. This is
-    // called prior to ConsumeGesture().
-    virtual bool CanHandleGesture(GestureType gesture_type) = 0;
-
-    // Called while a system UI gesture is in progress.
-    virtual void GestureProgress(GestureType gesture_type,
-                                 const gfx::Point& touch_location) {}
-
-    // Called when an in-progress system UI gesture is cancelled (for example
-    // when the finger is lifted before the completion of the gesture.)
-    virtual void CancelGesture(GestureType gesture_type) {}
-
-    // Consume and handle a completed UI gesture. Invokes the callback with a
-    // boolean indicating whether the gesture was handled or not.
-    virtual void ConsumeGesture(GestureType gesture_type,
-                                GestureHandledCallback handled_callback) = 0;
-
     // Notify visibility change for this window.
     virtual void OnVisibilityChange(VisibilityType visibility_type) {}
 
@@ -74,11 +58,10 @@
   ~CastContentWindow() override;
 
   // |cast_web_contents| must outlive the CastContentWindow.
-  void SetCastWebContents(CastWebContents* cast_web_contents) {
-    cast_web_contents_ = cast_web_contents;
-  }
+  void SetCastWebContents(CastWebContents* cast_web_contents);
 
   CastWebContents* cast_web_contents() { return cast_web_contents_; }
+  GestureRouter* gesture_router() { return &gesture_router_; }
 
   // mojom::CastContentWindow implementation:
   void CreateWindow(mojom::ZOrder z_order,
@@ -90,6 +73,10 @@
   void SetActivityContext(base::Value activity_context) override = 0;
   void SetHostContext(base::Value host_context) override = 0;
 
+  // mojom::ActivityWindow implementation:
+  void Show() override;
+  void Hide() override;
+
   // Notify the window that its visibility type has changed. This should only
   // ever be called by the window manager.
   // TODO(seantopping): Make this private to the window manager.
@@ -114,6 +101,9 @@
   void BindReceiver(mojo::PendingReceiver<mojom::CastContentWindow> receiver);
 
  protected:
+  void BindActivityWindow(
+      mojo::PendingReceiver<mojom::ActivityWindow> receiver);
+
   // Camel case due to conflict with WebContentsObserver::web_contents().
   content::WebContents* WebContents() {
     return cast_web_contents() ? cast_web_contents()->web_contents() : nullptr;
@@ -123,8 +113,11 @@
   base::WeakPtr<Delegate> delegate_;
   mojom::CastWebViewParamsPtr params_;
 
+  GestureRouter gesture_router_;
   mojo::Receiver<mojom::CastContentWindow> receiver_{this};
+  mojo::Receiver<mojom::ActivityWindow> activity_window_receiver_{this};
   base::ObserverList<Observer> observer_list_;
+  base::WeakPtrFactory<CastContentWindow> weak_factory_{this};
 };
 
 }  // namespace chromecast
diff --git a/chromecast/browser/cast_content_window_aura.cc b/chromecast/browser/cast_content_window_aura.cc
index f69bef18d..8efd130 100644
--- a/chromecast/browser/cast_content_window_aura.cc
+++ b/chromecast/browser/cast_content_window_aura.cc
@@ -93,7 +93,7 @@
     : CastContentWindow(delegate, std::move(params)),
       window_manager_(window_manager),
       gesture_dispatcher_(
-          std::make_unique<CastContentGestureHandler>(delegate_)),
+          std::make_unique<CastContentGestureHandler>(gesture_router())),
       window_(nullptr),
       has_screen_access_(false),
       resize_window_when_navigation_starts_(true) {}
diff --git a/chromecast/browser/cast_web_contents.h b/chromecast/browser/cast_web_contents.h
index c79b720..3a0e0a8 100644
--- a/chromecast/browser/cast_web_contents.h
+++ b/chromecast/browser/cast_web_contents.h
@@ -18,6 +18,7 @@
 #include "chromecast/browser/mojom/cast_web_contents.mojom.h"
 #include "chromecast/browser/web_types.h"
 #include "chromecast/common/mojom/feature_manager.mojom.h"
+#include "chromecast/mojo/interface_bundle.h"
 #include "content/public/common/media_playback_renderer_type.mojom.h"
 #include "mojo/public/cpp/bindings/generic_pending_receiver.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
@@ -255,6 +256,9 @@
   // registry or any registered InterfaceProvider.
   virtual bool TryBindReceiver(mojo::GenericPendingReceiver& receiver) = 0;
 
+  // Locally-registered interfaces which are exposed to render frames.
+  virtual InterfaceBundle* local_interfaces() = 0;
+
   // Used for owner to pass its |InterfaceProvider| pointers to CastWebContents.
   // It is owner's responsibility to make sure each |InterfaceProvider| pointer
   // has distinct mojo interface set.
diff --git a/chromecast/browser/cast_web_contents_impl.cc b/chromecast/browser/cast_web_contents_impl.cc
index 833aca76..7486d64b 100644
--- a/chromecast/browser/cast_web_contents_impl.cc
+++ b/chromecast/browser/cast_web_contents_impl.cc
@@ -444,6 +444,11 @@
 
 bool CastWebContentsImpl::TryBindReceiver(
     mojo::GenericPendingReceiver& receiver) {
+  // First try binding local interfaces.
+  if (local_interfaces_.TryBindReceiver(receiver)) {
+    return true;
+  }
+
   const std::string interface_name = *receiver.interface_name();
   mojo::ScopedMessagePipeHandle interface_pipe = receiver.PassPipe();
 
@@ -468,6 +473,10 @@
   return true;
 }
 
+InterfaceBundle* CastWebContentsImpl::local_interfaces() {
+  return &local_interfaces_;
+}
+
 void CastWebContentsImpl::RegisterInterfaceProvider(
     const InterfaceSet& interface_set,
     service_manager::InterfaceProvider* interface_provider) {
diff --git a/chromecast/browser/cast_web_contents_impl.h b/chromecast/browser/cast_web_contents_impl.h
index 4b9dbe5..032dbe0 100644
--- a/chromecast/browser/cast_web_contents_impl.h
+++ b/chromecast/browser/cast_web_contents_impl.h
@@ -86,6 +86,7 @@
       const InterfaceSet& interface_set,
       service_manager::InterfaceProvider* interface_provider) override;
   bool TryBindReceiver(mojo::GenericPendingReceiver& receiver) override;
+  InterfaceBundle* local_interfaces() override;
   void BlockMediaLoading(bool blocked) override;
   void BlockMediaStarting(bool blocked) override;
   void EnableBackgroundVideoPlayback(bool enabled) override;
@@ -219,6 +220,7 @@
 
   base::ObserverList<CastWebContentsObserver>::Unchecked observer_list_;
 
+  InterfaceBundle local_interfaces_;
   RemoteInterfaces remote_interfaces_;
 
   const scoped_refptr<base::SequencedTaskRunner> task_runner_;
diff --git a/chromecast/browser/gesture_router.cc b/chromecast/browser/gesture_router.cc
new file mode 100644
index 0000000..67f10e5
--- /dev/null
+++ b/chromecast/browser/gesture_router.cc
@@ -0,0 +1,238 @@
+// 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 "chromecast/browser/gesture_router.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "chromecast/base/chromecast_switches.h"
+
+namespace chromecast {
+
+GestureRouter::GestureRouter()
+    : gesture_source_receiver_(this),
+      handler_(nullptr),
+      can_go_back_(false),
+      can_top_drag_(false),
+      can_right_drag_(false),
+      managed_mode_(GetSwitchValueBoolean(switches::kManagedMode, false)),
+      weak_factory_(this) {}
+
+GestureRouter::~GestureRouter() = default;
+
+bool GestureRouter::CanHandleGesture(GestureType gesture_type) {
+  switch (gesture_type) {
+    case GestureType::GO_BACK:
+      return CanGoBack();
+    case GestureType::TAP:
+      return true;
+    case GestureType::TAP_DOWN:
+      return true;
+    case GestureType::TOP_DRAG:
+      return CanTopDrag();
+    case GestureType::RIGHT_DRAG:
+      return CanRightDrag();
+    default:
+      return false;
+  }
+}
+
+void GestureRouter::GestureProgress(GestureType gesture_type,
+                                    const gfx::Point& touch_location) {
+  switch (gesture_type) {
+    case GestureType::GO_BACK:
+      if (!CanGoBack())
+        return;
+      SendBackGestureProgress(touch_location);
+      break;
+    case GestureType::TOP_DRAG:
+      if (!CanTopDrag())
+        return;
+      SendTopDragGestureProgress(touch_location);
+      break;
+    case GestureType::RIGHT_DRAG:
+      if (!CanRightDrag())
+        return;
+      SendRightDragGestureProgress(touch_location);
+      break;
+    default:
+      return;
+  }
+}
+
+void GestureRouter::CancelGesture(GestureType gesture_type) {
+  switch (gesture_type) {
+    case GestureType::GO_BACK:
+      if (!CanGoBack())
+        return;
+      SendBackGestureCancel();
+      break;
+    default:
+      return;
+  }
+}
+
+void GestureRouter::ConsumeGesture(GestureType gesture_type,
+                                   GestureHandledCallback handled_callback) {
+  switch (gesture_type) {
+    case GestureType::NO_GESTURE:
+      std::move(handled_callback).Run(false);
+      break;
+    case GestureType::GO_BACK:
+      if (!CanGoBack()) {
+        std::move(handled_callback).Run(false);
+        return;
+      }
+      SendBackGesture(std::move(handled_callback));
+      break;
+    case GestureType::TAP:
+      SendTapGesture();
+      std::move(handled_callback).Run(true);
+      break;
+    case GestureType::TAP_DOWN:
+      SendTapDownGesture();
+      std::move(handled_callback).Run(true);
+      break;
+    case GestureType::TOP_DRAG:
+      SendTopDragGestureDone();
+      std::move(handled_callback).Run(true);
+      break;
+    case GestureType::RIGHT_DRAG:
+      SendRightDragGestureDone();
+      std::move(handled_callback).Run(true);
+      break;
+    default:
+      std::move(handled_callback).Run(false);
+  }
+}
+
+void GestureRouter::SetConsumeGestureCallback(ConsumerCallback callback) {
+  consumer_callback_ = callback;
+}
+
+void GestureRouter::SetHandler(mojom::GestureHandler* handler) {
+  handler_ = handler;
+}
+
+void GestureRouter::Subscribe(
+    mojo::PendingRemote<mojom::GestureHandler> pending_handler_remote) {
+  if (handler_remote_.is_bound()) {
+    handler_remote_.reset();
+  }
+  handler_remote_.Bind(std::move(pending_handler_remote));
+  SetHandler(handler_remote_.get());
+}
+
+void GestureRouter::SetCanGoBack(bool can_go_back) {
+  can_go_back_ = can_go_back;
+  if (delegate_)
+    delegate_->SetCanGoBack(can_go_back_);
+}
+
+bool GestureRouter::CanGoBack() const {
+  return can_go_back_ && (consumer_callback_ || handler_);
+}
+
+void GestureRouter::SendBackGesture(
+    base::OnceCallback<void(bool)> was_handled_callback) {
+  if (consumer_callback_) {
+    consumer_callback_.Run(GestureType::GO_BACK,
+                           std::move(was_handled_callback));
+    return;
+  }
+  if (!handler_)
+    return;
+  handler_->OnBackGesture(std::move(was_handled_callback));
+}
+
+void GestureRouter::SendBackGestureProgress(const gfx::Point& touch_location) {
+  if (!handler_)
+    return;
+  handler_->OnBackGestureProgress(touch_location);
+}
+
+void GestureRouter::SendBackGestureCancel() {
+  if (!handler_)
+    return;
+  handler_->OnBackGestureCancel();
+}
+
+void GestureRouter::SetCanTopDrag(bool can_top_drag) {
+  can_top_drag_ = can_top_drag;
+}
+
+bool GestureRouter::CanTopDrag() const {
+  return handler_ && can_top_drag_ && !managed_mode_;
+}
+
+void GestureRouter::SendTopDragGestureDone() {
+  if (!handler_)
+    return;
+  handler_->OnTopDragGestureDone();
+}
+
+void GestureRouter::SendTopDragGestureProgress(
+    const gfx::Point& touch_location) {
+  if (!handler_)
+    return;
+  handler_->OnTopDragGestureProgress(touch_location);
+}
+
+void GestureRouter::SetCanRightDrag(bool can_right_drag) {
+  can_right_drag_ = can_right_drag;
+}
+
+bool GestureRouter::CanRightDrag() const {
+  return handler_ && can_right_drag_;
+}
+
+void GestureRouter::SendRightDragGestureDone() {
+  if (!handler_)
+    return;
+  handler_->OnRightDragGestureDone();
+}
+
+void GestureRouter::SendRightDragGestureProgress(
+    const gfx::Point& touch_location) {
+  if (!handler_)
+    return;
+  handler_->OnRightDragGestureProgress(touch_location);
+}
+
+void GestureRouter::SendTapGesture() {
+  if (!handler_)
+    return;
+  handler_->OnTapGesture();
+}
+
+void GestureRouter::SendTapDownGesture() {
+  if (!handler_)
+    return;
+  handler_->OnTapDownGesture();
+}
+
+void GestureRouter::SetBackGestureDelegate(Delegate* delegate) {
+  delegate_ = delegate;
+  if (delegate_)
+    delegate_->SetCanGoBack(can_go_back_);
+}
+
+base::RepeatingCallback<void(mojo::PendingReceiver<mojom::GestureSource>)>
+GestureRouter::GetBinder() {
+  return base::BindRepeating(&GestureRouter::BindGestureSource,
+                             weak_factory_.GetWeakPtr());
+}
+
+void GestureRouter::BindGestureSource(
+    mojo::PendingReceiver<mojom::GestureSource> request) {
+  if (gesture_source_receiver_.is_bound()) {
+    LOG(WARNING) << "Gesture router is being re-bound.";
+    gesture_source_receiver_.reset();
+  }
+  gesture_source_receiver_.Bind(std::move(request));
+}
+
+}  // namespace chromecast
diff --git a/chromecast/browser/gesture_router.h b/chromecast/browser/gesture_router.h
new file mode 100644
index 0000000..b0a4999
--- /dev/null
+++ b/chromecast/browser/gesture_router.h
@@ -0,0 +1,110 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMECAST_BROWSER_GESTURE_ROUTER_H_
+#define CHROMECAST_BROWSER_GESTURE_ROUTER_H_
+
+#include "base/callback_forward.h"
+#include "base/memory/weak_ptr.h"
+#include "chromecast/browser/visibility_types.h"
+#include "chromecast/common/mojom/gesture.mojom.h"
+#include "chromecast/ui/back_gesture_router.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/remote.h"
+
+namespace chromecast {
+
+// This class exposes Cast UI gesture hooks to JavaScript. The page notifies
+// its ability to handle specific gestures, which allows this class to perform
+// necessary routing.
+class GestureRouter : private mojom::GestureSource, public BackGestureRouter {
+ public:
+  GestureRouter();
+  GestureRouter(const GestureRouter&) = delete;
+  GestureRouter& operator=(const GestureRouter&) = delete;
+  ~GestureRouter() override;
+
+  using GestureHandledCallback = base::OnceCallback<void(bool)>;
+
+  using ConsumerCallback =
+      base::RepeatingCallback<void(GestureType, GestureHandledCallback)>;
+
+  // Check to see if the gesture can be handled by JS. This is called prior to
+  // ConsumeGesture().
+  bool CanHandleGesture(GestureType gesture_type);
+
+  // Called while a system UI gesture is in progress.
+  void GestureProgress(GestureType gesture_type,
+                       const gfx::Point& touch_location);
+
+  // Called when an in-progress system UI gesture is cancelled (for example
+  // when the finger is lifted before the completion of the gesture.)
+  void CancelGesture(GestureType gesture_type);
+
+  // Consume and handle a completed UI gesture. Invokes the callback with a
+  // boolean indicating whether the gesture was handled or not.
+  void ConsumeGesture(GestureType gesture_type,
+                      GestureHandledCallback handled_callback);
+
+  // Registers an in-process gesture consumer. If set, this supersedes the
+  // mojo handler path. This can be called multiple times, overriding a
+  // previously registered callback.
+  void SetConsumeGestureCallback(ConsumerCallback callback);
+
+  // mojom::GestureSource implementation:
+  void Subscribe(mojo::PendingRemote<mojom::GestureHandler>
+                     pending_handler_remote) override;
+  void SetCanGoBack(bool can_go_back) override;
+  void SetCanTopDrag(bool can_top_drag) override;
+  void SetCanRightDrag(bool can_right_drag) override;
+
+  bool CanGoBack() const;
+  void SendBackGesture(base::OnceCallback<void(bool)> was_handled_callback);
+  void SendBackGestureProgress(const gfx::Point& touch_location);
+  void SendBackGestureCancel();
+  bool CanTopDrag() const;
+  void SendTopDragGestureProgress(const gfx::Point& touch_location);
+  void SendTopDragGestureDone();
+  bool CanRightDrag() const;
+  void SendRightDragGestureProgress(const gfx::Point& touch_location);
+  void SendRightDragGestureDone();
+  void SendTapGesture();
+  void SendTapDownGesture();
+
+  // BackGestureRouter overrides:
+  void SetBackGestureDelegate(Delegate* delegate) override;
+
+  base::RepeatingCallback<void(mojo::PendingReceiver<mojom::GestureSource>)>
+  GetBinder();
+
+ private:
+  friend class CastContentGestureHandlerTest;
+  friend class CastContentWindowEmbeddedTest;
+
+  // Helper method for unit tests. Instead of having to register an async
+  // subscriber, tests can use SetHandler() to register a mock instead. This
+  // can be called multiple times.
+  void SetHandler(mojom::GestureHandler* handler);
+
+  void BindGestureSource(mojo::PendingReceiver<mojom::GestureSource> request);
+
+  mojo::Receiver<mojom::GestureSource> gesture_source_receiver_;
+  mojo::Remote<mojom::GestureHandler> handler_remote_;
+  mojom::GestureHandler* handler_;
+
+  bool can_go_back_;
+  bool can_top_drag_;
+  bool can_right_drag_;
+  bool managed_mode_;  // If in managed mode, disable top drag.
+  Delegate* delegate_ = nullptr;
+
+  ConsumerCallback consumer_callback_;
+
+  base::WeakPtrFactory<GestureRouter> weak_factory_;
+};
+
+}  // namespace chromecast
+
+#endif  // CHROMECAST_BROWSER_GESTURE_ROUTER_H_
diff --git a/chromecast/browser/service/cast_service_simple.cc b/chromecast/browser/service/cast_service_simple.cc
index 2a08934..276915f7 100644
--- a/chromecast/browser/service/cast_service_simple.cc
+++ b/chromecast/browser/service/cast_service_simple.cc
@@ -90,16 +90,6 @@
 
 void CastServiceSimple::OnWindowDestroyed() {}
 
-bool CastServiceSimple::CanHandleGesture(GestureType gesture_type) {
-  return false;
-}
-
-void CastServiceSimple::ConsumeGesture(
-    GestureType gesture_type,
-    GestureHandledCallback handled_callback) {
-  std::move(handled_callback).Run(false);
-}
-
 void CastServiceSimple::OnVisibilityChange(VisibilityType visibility_type) {}
 
 }  // namespace shell
diff --git a/chromecast/browser/service/cast_service_simple.h b/chromecast/browser/service/cast_service_simple.h
index c1d70bd..f3d64aef 100644
--- a/chromecast/browser/service/cast_service_simple.h
+++ b/chromecast/browser/service/cast_service_simple.h
@@ -45,9 +45,6 @@
 
   // CastContentWindow::Delegate implementation:
   void OnWindowDestroyed() override;
-  bool CanHandleGesture(GestureType gesture_type) override;
-  void ConsumeGesture(GestureType gesture_type,
-                      GestureHandledCallback handled_callback) override;
   void OnVisibilityChange(VisibilityType visibility_type) override;
 
  private:
diff --git a/chromecast/browser/test/cast_browser_test.cc b/chromecast/browser/test/cast_browser_test.cc
index e9cb887..a3c70465 100644
--- a/chromecast/browser/test/cast_browser_test.cc
+++ b/chromecast/browser/test/cast_browser_test.cc
@@ -90,14 +90,5 @@
 
 void CastBrowserTest::OnVisibilityChange(VisibilityType visibility_type) {}
 
-bool CastBrowserTest::CanHandleGesture(GestureType gesture_type) {
-  return false;
-}
-
-void CastBrowserTest::ConsumeGesture(GestureType gesture_type,
-                                     GestureHandledCallback handled_callback) {
-  std::move(handled_callback).Run(false);
-}
-
 }  // namespace shell
 }  // namespace chromecast
diff --git a/chromecast/browser/test/cast_browser_test.h b/chromecast/browser/test/cast_browser_test.h
index 3e494c85..5fc8f367 100644
--- a/chromecast/browser/test/cast_browser_test.h
+++ b/chromecast/browser/test/cast_browser_test.h
@@ -53,9 +53,6 @@
   // CastWebView::Delegate implementation:
   void OnWindowDestroyed() override;
   void OnVisibilityChange(VisibilityType visibility_type) override;
-  bool CanHandleGesture(GestureType gesture_type) override;
-  void ConsumeGesture(GestureType gesture_type,
-                      GestureHandledCallback handled_callback) override;
 
   std::unique_ptr<CastWebViewFactory> web_view_factory_;
   std::unique_ptr<CastWebService> web_service_;
diff --git a/chromecast/browser/test/mock_cast_content_window_delegate.h b/chromecast/browser/test/mock_cast_content_window_delegate.h
index 4c0b157..ce5b1f988 100644
--- a/chromecast/browser/test/mock_cast_content_window_delegate.h
+++ b/chromecast/browser/test/mock_cast_content_window_delegate.h
@@ -18,14 +18,8 @@
   MockCastContentWindowDelegate();
   ~MockCastContentWindowDelegate() override;
 
-  MOCK_METHOD1(CanHandleGesture, bool(GestureType gesture_type));
-  MOCK_METHOD2(ConsumeGesture,
-               void(GestureType gesture_type,
-                    GestureHandledCallback handled_callback));
-  MOCK_METHOD1(CancelGesture, void(GestureType gesture_type));
-  MOCK_METHOD2(GestureProgress,
-               void(GestureType gesture_type,
-                    const gfx::Point& touch_location));
+  MOCK_METHOD(void, OnWindowDestroyed, (), (override));
+  MOCK_METHOD(void, OnVisibilityChange, (VisibilityType), (override));
 };
 
 }  // namespace chromecast
diff --git a/chromecast/browser/test/mock_cast_web_view.h b/chromecast/browser/test/mock_cast_web_view.h
index e998db15..4563d69 100644
--- a/chromecast/browser/test/mock_cast_web_view.h
+++ b/chromecast/browser/test/mock_cast_web_view.h
@@ -64,6 +64,7 @@
               (override));
   MOCK_METHOD(void, SetEnabledForRemoteDebugging, (bool), (override));
   MOCK_METHOD(void, GetMainFramePid, (GetMainFramePidCallback), (override));
+  MOCK_METHOD(InterfaceBundle*, local_interfaces, (), (override));
   MOCK_METHOD(void,
               RegisterInterfaceProvider,
               (const InterfaceSet&, service_manager::InterfaceProvider*),
diff --git a/chromecast/browser/webview/cast_content_window_embedded.cc b/chromecast/browser/webview/cast_content_window_embedded.cc
index 9f72bcf..a5c179a 100644
--- a/chromecast/browser/webview/cast_content_window_embedded.cc
+++ b/chromecast/browser/webview/cast_content_window_embedded.cc
@@ -163,8 +163,8 @@
 
   if (request.navigation && request.navigation.value() ==
                                 CastWindowEmbedder::NavigationType::GO_BACK) {
-    if (delegate_ && delegate_->CanHandleGesture(GestureType::GO_BACK)) {
-      delegate_->ConsumeGesture(
+    if (gesture_router()->CanHandleGesture(GestureType::GO_BACK)) {
+      gesture_router()->ConsumeGesture(
           GestureType::GO_BACK,
           base::BindOnce(&CastContentWindowEmbedded::ConsumeGestureCompleted,
                          base::Unretained(this)));
@@ -201,17 +201,18 @@
   }
 
   if (request.back_gesture_progress_event) {
-    if (delegate_ && delegate_->CanHandleGesture(GestureType::GO_BACK))
-      delegate_->GestureProgress(
+    if (gesture_router()->CanHandleGesture(GestureType::GO_BACK)) {
+      gesture_router()->GestureProgress(
           GestureType::GO_BACK,
           gfx::Point(request.back_gesture_progress_event.value().x,
                      request.back_gesture_progress_event.value().y));
+    }
     return;
   }
 
   if (request.back_gesture_cancel_event) {
-    if (delegate_ && delegate_->CanHandleGesture(GestureType::GO_BACK))
-      delegate_->CancelGesture(GestureType::GO_BACK);
+    if (gesture_router()->CanHandleGesture(GestureType::GO_BACK))
+      gesture_router()->CancelGesture(GestureType::GO_BACK);
     return;
   }
 }
diff --git a/chromecast/browser/webview/cast_content_window_embedded_unittest.cc b/chromecast/browser/webview/cast_content_window_embedded_unittest.cc
index 95014353..dee0064 100644
--- a/chromecast/browser/webview/cast_content_window_embedded_unittest.cc
+++ b/chromecast/browser/webview/cast_content_window_embedded_unittest.cc
@@ -125,18 +125,17 @@
       .Times(1)
       .WillOnce(Return(fake_window_id /* window_id */));
 
-  EXPECT_CALL(*mock_cast_content_window_delegate_,
-              CanHandleGesture(GestureType::GO_BACK))
-      .Times(1)
-      .WillOnce(Return(true));
+  cast_content_window_embedded_ = std::make_unique<CastContentWindowEmbedded>(
+      mock_cast_content_window_delegate_->AsWeakPtr(), GenerateParams(),
+      cast_window_embedder_.get(), false /* force_720p_resolution */);
 
-  // Delegate of the CastContentWindow indicts that it has handled the event.
-  EXPECT_CALL(*mock_cast_content_window_delegate_, ConsumeGesture(_, _))
-      .Times(1)
-      .WillOnce([](GestureType gesture_type,
-                   base::OnceCallback<void(bool)> consume_gesture_completed) {
-        std::move(consume_gesture_completed).Run(true);
-      });
+  cast_content_window_embedded_->gesture_router()->SetCanGoBack(true);
+  cast_content_window_embedded_->gesture_router()->SetConsumeGestureCallback(
+      base::BindRepeating(
+          [](GestureType gesture_type,
+             base::OnceCallback<void(bool)> consume_gesture_completed) {
+            std::move(consume_gesture_completed).Run(true);
+          }));
 
   // CastWindowEmbedder shall receive the ack of event handling
   EXPECT_CALL(
@@ -145,10 +144,6 @@
           fake_window_id, _, true, CastWindowEmbedder::NavigationType::GO_BACK))
       .Times(1);
 
-  cast_content_window_embedded_ = std::make_unique<CastContentWindowEmbedded>(
-      mock_cast_content_window_delegate_->AsWeakPtr(), GenerateParams(),
-      cast_window_embedder_.get(), false /* force_720p_resolution */);
-
   CastWindowEmbedder::EmbedderWindowEvent window_event;
   window_event.window_id = 1;
   window_event.navigation = CastWindowEmbedder::NavigationType::GO_BACK;
@@ -167,19 +162,20 @@
       .Times(1)
       .WillOnce(Return(fake_window_id /* window_id */));
 
-  EXPECT_CALL(*mock_cast_content_window_delegate_,
-              CanHandleGesture(GestureType::GO_BACK))
-      .Times(1)
-      .WillOnce(Return(true));
+  cast_content_window_embedded_ = std::make_unique<CastContentWindowEmbedded>(
+      mock_cast_content_window_delegate_->AsWeakPtr(), GenerateParams(),
+      cast_window_embedder_.get(), false /* force_720p_resolution */);
+
+  cast_content_window_embedded_->gesture_router()->SetCanGoBack(true);
 
   // Delegate of the CastContentWindow indicts that it dit not handle the
   // event.
-  EXPECT_CALL(*mock_cast_content_window_delegate_, ConsumeGesture(_, _))
-      .Times(1)
-      .WillOnce([](GestureType gesture_type,
-                   base::OnceCallback<void(bool)> consume_gesture_completed) {
-        std::move(consume_gesture_completed).Run(false /* handled */);
-      });
+  cast_content_window_embedded_->gesture_router()->SetConsumeGestureCallback(
+      base::BindRepeating(
+          [](GestureType gesture_type,
+             base::OnceCallback<void(bool)> consume_gesture_completed) {
+            std::move(consume_gesture_completed).Run(false);
+          }));
 
   // CastWindowEmbedder shall receive the ack of event handling
   EXPECT_CALL(*cast_window_embedder_,
@@ -188,10 +184,6 @@
                   CastWindowEmbedder::NavigationType::GO_BACK))
       .Times(1);
 
-  cast_content_window_embedded_ = std::make_unique<CastContentWindowEmbedded>(
-      mock_cast_content_window_delegate_->AsWeakPtr(), GenerateParams(),
-      cast_window_embedder_.get(), false /* force_720p_resolution */);
-
   CastWindowEmbedder::EmbedderWindowEvent window_event;
   window_event.window_id = 1;
   window_event.navigation = CastWindowEmbedder::NavigationType::GO_BACK;
@@ -210,15 +202,11 @@
       .Times(1)
       .WillOnce(Return(fake_window_id /* window_id */));
 
-  EXPECT_CALL(*mock_cast_content_window_delegate_,
-              CanHandleGesture(GestureType::GO_BACK))
-      .Times(1)
-      .WillOnce(Return(false));
+  cast_content_window_embedded_ = std::make_unique<CastContentWindowEmbedded>(
+      mock_cast_content_window_delegate_->AsWeakPtr(), GenerateParams(),
+      cast_window_embedder_.get(), false /* force_720p_resolution */);
 
-  // Delegate of the CastContentWindow indicts that it dit not handled the
-  // event.
-  EXPECT_CALL(*mock_cast_content_window_delegate_, ConsumeGesture(_, _))
-      .Times(0);
+  cast_content_window_embedded_->gesture_router()->SetCanGoBack(false);
 
   // CastWindowEmbedder shall receive the ack of event handling
   EXPECT_CALL(*cast_window_embedder_,
@@ -227,10 +215,6 @@
                   CastWindowEmbedder::NavigationType::GO_BACK))
       .Times(1);
 
-  cast_content_window_embedded_ = std::make_unique<CastContentWindowEmbedded>(
-      mock_cast_content_window_delegate_->AsWeakPtr(), GenerateParams(),
-      cast_window_embedder_.get(), false /* force_720p_resolution */);
-
   CastWindowEmbedder::EmbedderWindowEvent window_event;
   window_event.window_id = 1;
   window_event.navigation = CastWindowEmbedder::NavigationType::GO_BACK;
diff --git a/chromecast/cast_core/runtime_application_base.cc b/chromecast/cast_core/runtime_application_base.cc
index 7c2d08e..8f6d425 100644
--- a/chromecast/cast_core/runtime_application_base.cc
+++ b/chromecast/cast_core/runtime_application_base.cc
@@ -158,10 +158,6 @@
   return web_service_->CreateWebViewInternal(create_params, std::move(params));
 }
 
-bool RuntimeApplicationBase::CanHandleGesture(GestureType gesture_type) {
-  return false;
-}
-
 void RuntimeApplicationBase::StopApplication() {
   is_application_stopped_ = true;
 
@@ -194,10 +190,4 @@
   set_cast_session_id(std::string());
 }
 
-void RuntimeApplicationBase::ConsumeGesture(
-    GestureType gesture_type,
-    GestureHandledCallback handled_callback) {
-  std::move(handled_callback).Run(false);
-}
-
 }  // namespace chromecast
diff --git a/chromecast/cast_core/runtime_application_base.h b/chromecast/cast_core/runtime_application_base.h
index 6fb5c8e..56dde1b 100644
--- a/chromecast/cast_core/runtime_application_base.h
+++ b/chromecast/cast_core/runtime_application_base.h
@@ -80,11 +80,6 @@
   // Called following Launch() on |task_runner_|.
   void FinishLaunch(std::string core_application_service_endpoint);
 
-  // CastWebView::Delegate implementation:
-  bool CanHandleGesture(GestureType gesture_type) override;
-  void ConsumeGesture(GestureType gesture_type,
-                      GestureHandledCallback handled_callback) override;
-
   // RuntimeMessagePortApplicationServiceDelegate implementation:
   void PostMessage(const cast::web::Message& request,
                    cast::web::MessagePortStatus* response,
diff --git a/chromecast/common/feature_constants.cc b/chromecast/common/feature_constants.cc
index b4ff4883..b4a8e17 100644
--- a/chromecast/common/feature_constants.cc
+++ b/chromecast/common/feature_constants.cc
@@ -10,6 +10,17 @@
 const char kEnableTrackControlAppRendererFeatureUse[] =
     "track_control_renderer_feature_use";
 const char kEnablePlayready[] = "playready";
+const char kEnableDevMode[] = "dev_mode";
+const char kDevModeOrigin[] = "dev_mode_origin";
+const char kEnableAccessibilityControls[] = "accessibility_controls";
+const char kEnableSystemGestures[] = "system_gestures";
+const char kEnableWindowControls[] = "enable_window_controls";
+const char kEnableSettingsUiMojo[] = "enable_settings_ui_mojo";
+const char kDisableBackgroundTabTimerThrottle[] =
+    "disable_background_tab_timer_throttle";
+const char kDisableBackgroundSuspend[] = "disable_background_suspend";
+const char kEnableAssistantMessagePipe[] = "enable_assistant_message_pipe";
+const char kEnableDemoStandaloneMode[] = "enable_demo_standalone_mode";
 
 const char kKeyAppId[] = "app_id";
 const char kKeyAllowInsecureContent[] = "allow_insecure_content";
diff --git a/chromecast/common/feature_constants.h b/chromecast/common/feature_constants.h
index 2b821574..1e667c9d 100644
--- a/chromecast/common/feature_constants.h
+++ b/chromecast/common/feature_constants.h
@@ -20,6 +20,37 @@
 // Insecure content is allowed for the app.
 extern const char kKeyAllowInsecureContent[];
 
+// If dev mode is enabled, kDevModeOrigin will be set with origin url
+extern const char kEnableDevMode[];
+extern const char kDevModeOrigin[];
+
+// Permit changes to accessibility control settings?
+// (color inversion, high contrast text, etc.)
+extern const char kEnableAccessibilityControls[];
+
+// Permit subscription to platform system gesture events?
+extern const char kEnableSystemGestures[];
+
+// Enables window APIs for the webpage (show/hide, etc.)
+extern const char kEnableWindowControls[];
+
+// Enable mojo connection for the settings UI.
+extern const char kEnableSettingsUiMojo[];
+
+// Disable blink background tab timer throttling for making sure application in
+// in mini tile mode runs smoothly.
+extern const char kDisableBackgroundTabTimerThrottle[];
+
+// Sets RenderFrameMediaPlaybackOptions::kIsBackgroundMediaSuspendEnabled to
+// false.
+extern const char kDisableBackgroundSuspend[];
+
+// Enable sending/receiving messages to/from libassistant
+extern const char kEnableAssistantMessagePipe[];
+
+// Enable a standalone demo app to control privileged features.
+extern const char kEnableDemoStandaloneMode[];
+
 }  // namespace feature
 }  // namespace chromecast
 
diff --git a/chromecast/mojo/interface_bundle.h b/chromecast/mojo/interface_bundle.h
index 94a2fec4..20afa1f 100644
--- a/chromecast/mojo/interface_bundle.h
+++ b/chromecast/mojo/interface_bundle.h
@@ -12,6 +12,7 @@
 #include "base/sequence_checker.h"
 #include "chromecast/mojo/binder_factory.h"
 #include "chromecast/mojo/mojom/remote_interfaces.mojom.h"
+#include "mojo/public/cpp/bindings/generic_pending_receiver.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/receiver_set.h"
 #include "mojo/public/cpp/bindings/remote.h"
@@ -123,6 +124,17 @@
   void AddClient(
       mojo::PendingReceiver<mojom::RemoteInterfaces> receiver) override;
 
+  // Attempt to bind a generic receiver. Succeeds if there is an available
+  // implementation or binder callback registered.
+  bool TryBindReceiver(mojo::GenericPendingReceiver& receiver) {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    if (local_interfaces_.HasInterface(*receiver.interface_name())) {
+      local_interfaces_.Bind(*receiver.interface_name(), receiver.PassPipe());
+      return true;
+    }
+    return false;
+  }
+
  private:
   // For interfaces that are provided as a local pointer without any binding
   // logic, we can use MultiBinderFactory to expose a binding surface.
diff --git a/chromecast/renderer/BUILD.gn b/chromecast/renderer/BUILD.gn
index 41454ad..07344599 100644
--- a/chromecast/renderer/BUILD.gn
+++ b/chromecast/renderer/BUILD.gn
@@ -36,16 +36,26 @@
   sources = [
     "activity_filtering_websocket_handshake_throttle.cc",
     "activity_filtering_websocket_handshake_throttle.h",
+    "assistant_bindings.cc",
+    "assistant_bindings.h",
+    "cast_accessibility_bindings.cc",
+    "cast_accessibility_bindings.h",
     "cast_activity_url_filter_manager.cc",
     "cast_activity_url_filter_manager.h",
     "cast_content_renderer_client.cc",
     "cast_content_renderer_client.h",
     "cast_content_settings_client.cc",
     "cast_content_settings_client.h",
+    "cast_demo_bindings.cc",
+    "cast_demo_bindings.h",
     "cast_url_loader_throttle_provider.cc",
     "cast_url_loader_throttle_provider.h",
     "cast_websocket_handshake_throttle_provider.cc",
     "cast_websocket_handshake_throttle_provider.h",
+    "cast_window_manager_bindings.cc",
+    "cast_window_manager_bindings.h",
+    "feature_manager.cc",
+    "feature_manager.h",
     "feature_manager_on_associated_interface.cc",
     "feature_manager_on_associated_interface.h",
     "identification_settings_manager_renderer.cc",
@@ -57,10 +67,13 @@
     "native_bindings_helper.h",
     "queryable_data_store.cc",
     "queryable_data_store.h",
+    "settings_ui_bindings.cc",
+    "settings_ui_bindings.h",
   ]
 
   public_deps = [
     "//chromecast:chromecast_buildflags",
+    "//chromecast/browser/mojom",
     "//chromecast/common/mojom",
     "//content/public/renderer",
     "//media",
diff --git a/chromecast/renderer/DEPS b/chromecast/renderer/DEPS
index 8610ff45..592c935 100644
--- a/chromecast/renderer/DEPS
+++ b/chromecast/renderer/DEPS
@@ -1,4 +1,5 @@
 include_rules = [
+  "+chromecast/browser/mojom",
   "+chromecast/common",
   "+chromecast/crash",
   "+chromecast/media",
diff --git a/chromecast/renderer/assistant_bindings.cc b/chromecast/renderer/assistant_bindings.cc
new file mode 100644
index 0000000..55484f70
--- /dev/null
+++ b/chromecast/renderer/assistant_bindings.cc
@@ -0,0 +1,169 @@
+// 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 "chromecast/renderer/assistant_bindings.h"
+
+#include "base/check.h"
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
+#include "base/logging.h"
+#include "base/stl_util.h"
+#include "base/time/time.h"
+#include "content/public/renderer/render_frame.h"
+#include "content/public/renderer/render_thread.h"
+#include "third_party/blink/public/common/browser_interface_broker_proxy.h"
+#include "third_party/blink/public/web/blink.h"
+#include "third_party/blink/public/web/web_local_frame.h"
+
+namespace chromecast {
+namespace shell {
+
+namespace {
+
+const int kMaxMessageQueueSize = 50;
+static const int64_t kDelayBetweenReconnectionInMillis = 100;
+
+const char kSetAssistantMessageHandlerMethodName[] =
+    "setAssistantMessageHandler";
+const char kSendAssistantRequestMethodName[] = "sendAssistantRequest";
+
+}  // namespace
+
+AssistantBindings::AssistantBindings(content::RenderFrame* frame,
+                                     const base::Value& feature_config)
+    : CastBinding(frame),
+      feature_config_(feature_config.Clone()),
+      message_client_binding_(this),
+      weak_factory_(this) {
+  weak_this_ = weak_factory_.GetWeakPtr();
+}
+
+AssistantBindings::~AssistantBindings() {}
+
+void AssistantBindings::OnMessage(base::Value message) {
+  if (assistant_message_handler_.IsEmpty()) {
+    return;
+  }
+
+  v8::Isolate* isolate = blink::MainThreadIsolate();
+  v8::MicrotasksScope microtasks_scope(
+      isolate, v8::MicrotasksScope::kDoNotRunMicrotasks);
+  v8::HandleScope handle_scope(isolate);
+  blink::WebLocalFrame* web_frame = render_frame()->GetWebFrame();
+  v8::Local<v8::Context> context = web_frame->MainWorldScriptContext();
+  v8::Context::Scope context_scope(context);
+
+  v8::Local<v8::Function> handler = v8::Local<v8::Function>::New(
+      isolate, std::move(assistant_message_handler_));
+
+  std::string json;
+  base::JSONWriter::Write(message, &json);
+  v8::Local<v8::Value> message_val =
+      gin::Converter<std::string>::ToV8(isolate, json);
+
+  v8::Local<v8::Value> argv[] = {message_val};
+  web_frame->CallFunctionEvenIfScriptDisabled(handler, context->Global(),
+                                              base::size(argv), argv);
+
+  assistant_message_handler_ =
+      v8::UniquePersistent<v8::Function>(isolate, handler);
+}
+
+void AssistantBindings::Install(v8::Local<v8::Object> cast_platform,
+                                v8::Isolate* isolate) {
+  DVLOG(1) << "Installing AssistantBindings";
+
+  InstallBinding(isolate, cast_platform, kSetAssistantMessageHandlerMethodName,
+                 &AssistantBindings::SetAssistantMessageHandler,
+                 base::Unretained(this));
+  InstallBinding(isolate, cast_platform, kSendAssistantRequestMethodName,
+                 &AssistantBindings::SendAssistantRequest,
+                 base::Unretained(this));
+}
+
+void AssistantBindings::SetAssistantMessageHandler(
+    v8::Local<v8::Function> assistant_message_handler) {
+  v8::Isolate* isolate = blink::MainThreadIsolate();
+  assistant_message_handler_ =
+      v8::UniquePersistent<v8::Function>(isolate, assistant_message_handler);
+  ReconnectMessagePipe();
+}
+
+void AssistantBindings::SendAssistantRequest(const std::string& request) {
+  if (assistant_message_handler_.IsEmpty()) {
+    v8::Isolate* isolate = blink::MainThreadIsolate();
+    isolate->ThrowException(
+        v8::String::NewFromUtf8(isolate,
+                                "Error: assistant message handler is not set.",
+                                v8::NewStringType::kInternalized)
+            .ToLocalChecked());
+    return;
+  }
+  if (!message_pipe_.is_bound()) {
+    if (v8_to_assistant_queue_.size() < kMaxMessageQueueSize) {
+      v8_to_assistant_queue_.push_back(request);
+    } else {
+      LOG(WARNING) << "Messages sending to assistant overflow, will drop "
+                      "upcoming messages";
+    }
+    return;
+  }
+
+  v8_to_assistant_queue_.push_back(request);
+  FlushV8ToAssistantQueue();
+}
+
+void AssistantBindings::ReconnectMessagePipe() {
+  if (message_client_binding_.is_bound())
+    message_client_binding_.reset();
+  if (message_pipe_.is_bound())
+    message_pipe_.reset();
+  LOG(INFO) << "Creating message pipe";
+  base::Value* app_id =
+      feature_config_.FindKeyOfType("app_id", base::Value::Type::STRING);
+  DCHECK(app_id) << "Couldn't get app_id from feature config";
+  GetMojoInterface()->CreateMessagePipe(
+      app_id->GetString(), message_client_binding_.BindNewPipeAndPassRemote(),
+      message_pipe_.BindNewPipeAndPassReceiver());
+
+  reconnect_assistant_timer_.Stop();
+}
+
+void AssistantBindings::OnAssistantConnectionError() {
+  LOG(WARNING) << "Disconnected from assistant. Will reconnect every "
+               << kDelayBetweenReconnectionInMillis << " milliseconds";
+  assistant_.reset();
+  reconnect_assistant_timer_.Start(
+      FROM_HERE,
+      base::TimeDelta::FromMilliseconds(kDelayBetweenReconnectionInMillis),
+      this, &AssistantBindings::ReconnectMessagePipe);
+}
+
+void AssistantBindings::FlushV8ToAssistantQueue() {
+  DCHECK(message_pipe_.is_bound());
+
+  for (auto& request : v8_to_assistant_queue_) {
+    auto value = base::JSONReader::Read(request);
+    if (!value) {
+      LOG(ERROR) << "Unable to parse Assistant message JSON.";
+      continue;
+    }
+    message_pipe_->SendMessage(std::move(*value));
+  }
+  v8_to_assistant_queue_.clear();
+}
+
+const mojo::Remote<chromecast::mojom::AssistantMessageService>&
+AssistantBindings::GetMojoInterface() {
+  if (!assistant_.is_bound()) {
+    render_frame()->GetBrowserInterfaceBroker()->GetInterface(
+        assistant_.BindNewPipeAndPassReceiver());
+    assistant_.set_disconnect_handler(base::BindOnce(
+        &AssistantBindings::OnAssistantConnectionError, weak_this_));
+  }
+  return assistant_;
+}
+
+}  // namespace shell
+}  // namespace chromecast
diff --git a/chromecast/renderer/assistant_bindings.h b/chromecast/renderer/assistant_bindings.h
new file mode 100644
index 0000000..d243d92
--- /dev/null
+++ b/chromecast/renderer/assistant_bindings.h
@@ -0,0 +1,74 @@
+// 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 CHROMECAST_RENDERER_ASSISTANT_BINDINGS_H_
+#define CHROMECAST_RENDERER_ASSISTANT_BINDINGS_H_
+
+#include <string>
+#include <vector>
+
+#include "base/memory/weak_ptr.h"
+#include "base/timer/timer.h"
+#include "chromecast/common/mojom/assistant_messenger.mojom.h"
+#include "chromecast/renderer/native_bindings_helper.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/remote.h"
+
+namespace chromecast {
+namespace shell {
+
+// When enabled, these bindings can be used to open a message channel with the
+// Assistant. These bindings are only enabled for a small set of first-party
+// apps.
+class AssistantBindings : public CastBinding,
+                          public chromecast::mojom::AssistantMessageClient {
+ public:
+  AssistantBindings(content::RenderFrame* frame,
+                    const base::Value& feature_config);
+  ~AssistantBindings() override;
+  AssistantBindings(const AssistantBindings&) = delete;
+  AssistantBindings& operator=(const AssistantBindings&) = delete;
+
+ private:
+  friend class ::chromecast::CastBinding;
+
+  // chromecast::mojom::AssistantMessageClient implementation:
+  void OnMessage(base::Value message) override;
+
+  // CastBinding implementation:
+  void Install(v8::Local<v8::Object> cast_platform,
+               v8::Isolate* isolate) override;
+
+  // Binding methods
+  void SetAssistantMessageHandler(
+      v8::Local<v8::Function> assistant_message_handler);
+  void SendAssistantRequest(const std::string& request);
+
+  void ReconnectMessagePipe();
+  void OnAssistantConnectionError();
+
+  void FlushV8ToAssistantQueue();
+
+  const mojo::Remote<chromecast::mojom::AssistantMessageService>&
+  GetMojoInterface();
+
+  base::RepeatingTimer reconnect_assistant_timer_;
+  mojo::Remote<chromecast::mojom::AssistantMessageService> assistant_;
+  base::Value feature_config_;
+
+  mojo::Receiver<chromecast::mojom::AssistantMessageClient>
+      message_client_binding_;
+  mojo::Remote<chromecast::mojom::AssistantMessagePipe> message_pipe_;
+  std::vector<std::string> v8_to_assistant_queue_;
+
+  v8::UniquePersistent<v8::Function> assistant_message_handler_;
+
+  base::WeakPtr<AssistantBindings> weak_this_;
+  base::WeakPtrFactory<AssistantBindings> weak_factory_;
+};
+
+}  // namespace shell
+}  // namespace chromecast
+
+#endif  // CHROMECAST_RENDERER_ASSISTANT_BINDINGS_H_
diff --git a/chromecast/renderer/cast_accessibility_bindings.cc b/chromecast/renderer/cast_accessibility_bindings.cc
new file mode 100644
index 0000000..cbe9791
--- /dev/null
+++ b/chromecast/renderer/cast_accessibility_bindings.cc
@@ -0,0 +1,243 @@
+// 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 "chromecast/renderer/cast_accessibility_bindings.h"
+
+#include <vector>
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "chromecast/common/feature_constants.h"
+#include "chromecast/renderer/feature_manager.h"
+#include "content/public/renderer/render_frame.h"
+#include "gin/data_object_builder.h"
+#include "third_party/blink/public/common/browser_interface_broker_proxy.h"
+#include "third_party/blink/public/web/blink.h"
+#include "third_party/blink/public/web/web_local_frame.h"
+
+namespace chromecast {
+namespace shell {
+
+namespace {
+
+constexpr char kBindingsObjectName[] = "accessibility";
+constexpr char kSetColorInversionName[] = "setColorInversion";
+constexpr char kSetScreenReaderName[] = "setScreenReader";
+constexpr char kSetMagnificationGestureName[] = "setMagnificationGesture";
+constexpr char kGetAccessibilitySettings[] = "getAccessibilitySettings";
+constexpr char kIsColorInversionEnabled[] = "isColorInversionEnabled";
+constexpr char kIsScreenReaderEnabled[] = "isScreenReaderEnabled";
+constexpr char kIsMagnificationGestureEnabled[] =
+    "isMagnificationGestureEnabled";
+constexpr char kScreenReaderSettingChangedHandler[] =
+    "setScreenReaderSettingChangedHandler";
+constexpr char kColorInversionSettingChangedHandler[] =
+    "setColorInversionSettingChangedHandler";
+constexpr char kMagnificationGestureSettingChangedHandler[] =
+    "setMagnificationGestureSettingChangedHandler";
+
+void OnAccessibilityServiceConnectionError() {
+  LOG(ERROR) << "Connection error talking to accessibility manager.";
+}
+
+}  // namespace
+
+CastAccessibilityBindings::CastAccessibilityBindings(
+    content::RenderFrame* render_frame,
+    const FeatureManager* feature_manager)
+    : CastBinding(render_frame), feature_manager_(feature_manager) {
+  registry_.AddInterface(base::BindRepeating(
+      &CastAccessibilityBindings::OnCastAccessibilityClientRequest,
+      base::Unretained(this)));
+}
+
+CastAccessibilityBindings::~CastAccessibilityBindings() {}
+
+void CastAccessibilityBindings::OnInterfaceRequestForFrame(
+    const std::string& interface_name,
+    mojo::ScopedMessagePipeHandle* interface_pipe) {
+  registry_.TryBindInterface(interface_name, interface_pipe);
+}
+
+void CastAccessibilityBindings::Install(v8::Local<v8::Object> cast_platform,
+                                        v8::Isolate* isolate) {
+  v8::Local<v8::Object> AccessibilityObject =
+      EnsureObjectExists(isolate, cast_platform, kBindingsObjectName);
+
+  if (feature_manager_->FeatureEnabled(feature::kEnableAccessibilityControls)) {
+    InstallBinding(isolate, AccessibilityObject, kSetColorInversionName,
+                   &CastAccessibilityBindings::SetColorInversion,
+                   base::Unretained(this));
+    InstallBinding(isolate, AccessibilityObject, kSetScreenReaderName,
+                   &CastAccessibilityBindings::SetScreenReader,
+                   base::Unretained(this));
+    InstallBinding(isolate, AccessibilityObject, kSetMagnificationGestureName,
+                   &CastAccessibilityBindings::SetMagnificationGesture,
+                   base::Unretained(this));
+  }
+  // Getters and callback are always available.
+  InstallBinding(isolate, AccessibilityObject, kGetAccessibilitySettings,
+                 &CastAccessibilityBindings::GetAccessibilitySettings,
+                 base::Unretained(this));
+  InstallBinding(
+      isolate, AccessibilityObject, kScreenReaderSettingChangedHandler,
+      &CastAccessibilityBindings::SetScreenReaderSettingChangedHandler,
+      base::Unretained(this));
+  InstallBinding(
+      isolate, AccessibilityObject, kColorInversionSettingChangedHandler,
+      &CastAccessibilityBindings::SetColorInversionSettingChangedHandler,
+      base::Unretained(this));
+  InstallBinding(
+      isolate, AccessibilityObject, kMagnificationGestureSettingChangedHandler,
+      &CastAccessibilityBindings::SetMagnificationGestureSettingChangedHandler,
+      base::Unretained(this));
+}
+
+void CastAccessibilityBindings::SetColorInversion(bool enable) {
+  if (!BindAccessibility())
+    return;
+  accessibility_service_->SetColorInversion(enable);
+}
+
+void CastAccessibilityBindings::SetScreenReader(bool enable) {
+  if (!BindAccessibility())
+    return;
+  accessibility_service_->SetScreenReader(enable);
+}
+
+void CastAccessibilityBindings::SetMagnificationGesture(bool enable) {
+  if (!BindAccessibility())
+    return;
+  accessibility_service_->SetMagnificationGestureEnabled(enable);
+}
+
+v8::Local<v8::Value> CastAccessibilityBindings::GetAccessibilitySettings() {
+  DVLOG(2) << __FUNCTION__;
+  v8::Isolate* isolate = blink::MainThreadIsolate();
+
+  if (!BindAccessibility())
+    return gin::ConvertToV8(isolate, false);
+
+  v8::Local<v8::Promise::Resolver> resolver =
+      v8::Promise::Resolver::New(isolate->GetCurrentContext()).ToLocalChecked();
+
+  PersistedResolver unique_resolver =
+      v8::Global<v8::Promise::Resolver>(isolate, resolver);
+  PersistedContext context(isolate, isolate->GetCurrentContext());
+
+  accessibility_service_->GetAccessibilitySettings(base::BindOnce(
+      &CastAccessibilityBindings::OnGetAccessibilitySettings,
+      base::Unretained(this), std::move(unique_resolver), std::move(context)));
+  return resolver->GetPromise();
+}
+
+void CastAccessibilityBindings::SetScreenReaderSettingChangedHandler(
+    v8::Local<v8::Function> handler) {
+  v8::Isolate* isolate = blink::MainThreadIsolate();
+  screen_reader_setting_changed_handler_ =
+      v8::UniquePersistent<v8::Function>(isolate, handler);
+}
+
+void CastAccessibilityBindings::SetColorInversionSettingChangedHandler(
+    v8::Local<v8::Function> handler) {
+  v8::Isolate* isolate = blink::MainThreadIsolate();
+  color_inversion_setting_changed_handler_ =
+      v8::UniquePersistent<v8::Function>(isolate, handler);
+}
+
+void CastAccessibilityBindings::SetMagnificationGestureSettingChangedHandler(
+    v8::Local<v8::Function> handler) {
+  v8::Isolate* isolate = blink::MainThreadIsolate();
+  magnification_gesture_setting_changed_handler_ =
+      v8::UniquePersistent<v8::Function>(isolate, handler);
+}
+
+bool CastAccessibilityBindings::BindAccessibility() {
+  if (!accessibility_service_) {
+    render_frame()->GetBrowserInterfaceBroker()->GetInterface(
+        accessibility_service_.BindNewPipeAndPassReceiver());
+    if (!accessibility_service_) {
+      LOG(ERROR)
+          << "Couldn't establish connection to cast window manager service";
+      return false;
+    }
+    accessibility_service_.set_disconnect_handler(
+        base::BindRepeating(&OnAccessibilityServiceConnectionError));
+  }
+  return true;
+}
+
+void CastAccessibilityBindings::OnGetAccessibilitySettings(
+    PersistedResolver resolver,
+    PersistedContext original_context,
+    mojom::AccessibilitySettingsPtr settings) {
+  v8::Isolate* isolate = blink::MainThreadIsolate();
+  v8::MicrotasksScope microtasks_scope(
+      isolate, v8::MicrotasksScope::kDoNotRunMicrotasks);
+  v8::HandleScope handle_scope(isolate);
+  v8::Local<v8::Context> context = original_context.Get(isolate);
+  v8::Context::Scope context_scope(context);
+
+  v8::Local<v8::Object> obj =
+      gin::DataObjectBuilder(isolate)
+          .Set(kIsColorInversionEnabled, settings->color_inversion_enabled)
+          .Set(kIsScreenReaderEnabled, settings->screen_reader_enabled)
+          .Set(kIsMagnificationGestureEnabled,
+               settings->magnification_gesture_enabled)
+          .Build();
+
+  resolver.Get(isolate)->Resolve(context, obj).ToChecked();
+}
+
+void CastAccessibilityBindings::AccessibilitySettingChanged(
+    v8::UniquePersistent<v8::Function>* handler_function,
+    bool new_value) {
+  if (handler_function->IsEmpty()) {
+    return;
+  }
+
+  v8::Isolate* isolate = blink::MainThreadIsolate();
+  v8::HandleScope handle_scope(isolate);
+  blink::WebLocalFrame* web_frame = render_frame()->GetWebFrame();
+  v8::Local<v8::Context> context = web_frame->MainWorldScriptContext();
+  v8::Context::Scope context_scope(context);
+  v8::Local<v8::Function> handler =
+      v8::Local<v8::Function>::New(isolate, std::move(*handler_function));
+
+  v8::Local<v8::Boolean> arg = v8::Boolean::New(isolate, new_value);
+
+  std::vector<v8::Local<v8::Value>> args{arg};
+
+  v8::MaybeLocal<v8::Value> maybe_result =
+      handler->Call(context, context->Global(), args.size(), args.data());
+
+  *handler_function = v8::UniquePersistent<v8::Function>(isolate, handler);
+
+  v8::Local<v8::Value> result;
+  ignore_result(maybe_result.ToLocal(&result));
+}
+
+void CastAccessibilityBindings::ScreenReaderSettingChanged(bool new_value) {
+  AccessibilitySettingChanged(&screen_reader_setting_changed_handler_,
+                              new_value);
+}
+
+void CastAccessibilityBindings::ColorInversionSettingChanged(bool new_value) {
+  AccessibilitySettingChanged(&color_inversion_setting_changed_handler_,
+                              new_value);
+}
+
+void CastAccessibilityBindings::MagnificationGestureSettingChanged(
+    bool new_value) {
+  AccessibilitySettingChanged(&magnification_gesture_setting_changed_handler_,
+                              new_value);
+}
+
+void CastAccessibilityBindings::OnCastAccessibilityClientRequest(
+    mojo::PendingReceiver<shell::mojom::CastAccessibilityClient> request) {
+  bindings_.Add(this, std::move(request));
+}
+
+}  // namespace shell
+}  // namespace chromecast
diff --git a/chromecast/renderer/cast_accessibility_bindings.h b/chromecast/renderer/cast_accessibility_bindings.h
new file mode 100644
index 0000000..ecace20
--- /dev/null
+++ b/chromecast/renderer/cast_accessibility_bindings.h
@@ -0,0 +1,100 @@
+// 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 CHROMECAST_RENDERER_CAST_ACCESSIBILITY_BINDINGS_H_
+#define CHROMECAST_RENDERER_CAST_ACCESSIBILITY_BINDINGS_H_
+
+#include "base/memory/ptr_util.h"
+#include "chromecast/common/mojom/accessibility.mojom.h"
+#include "chromecast/renderer/native_bindings_helper.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/receiver_set.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "services/service_manager/public/cpp/binder_registry.h"
+#include "v8/include/v8.h"
+
+namespace chromecast {
+
+class FeatureManager;
+
+namespace shell {
+
+// Renderer side implementation of JS available bindings for accessibility
+// related functions.
+class CastAccessibilityBindings : public CastBinding,
+                                  public mojom::CastAccessibilityClient {
+ public:
+  CastAccessibilityBindings(content::RenderFrame* render_frame,
+                            const FeatureManager* feature_manager);
+  ~CastAccessibilityBindings() override;
+  CastAccessibilityBindings(const CastAccessibilityBindings&) = delete;
+  CastAccessibilityBindings& operator=(const CastAccessibilityBindings&) =
+      delete;
+
+ private:
+  // content::RenderFrameObserver implementation:
+  void OnInterfaceRequestForFrame(
+      const std::string& interface_name,
+      mojo::ScopedMessagePipeHandle* interface_pipe) override;
+
+  // Typedefs for v8 objects that need to run between tasks.
+  // v8::Global has move semantics, it does not imply unique ownership over an
+  // v8 object. You can create multiple v8::Globals from the same v8::Local.
+  using PersistedContext = v8::Global<v8::Context>;
+  using PersistedResolver = v8::Global<v8::Promise::Resolver>;
+
+  // CastBinding implementation:
+  void Install(v8::Local<v8::Object> cast_platform,
+               v8::Isolate* isolate) override;
+
+  // Bind incoming accessibility requests with this implementation.
+  void OnCastAccessibilityClientRequest(
+      mojo::PendingReceiver<shell::mojom::CastAccessibilityClient> request);
+
+  void OnConnectionError();
+  bool BindAccessibility();
+
+  void SetColorInversion(bool enable);
+  void SetScreenReader(bool enable);
+  void SetMagnificationGesture(bool enable);
+
+  v8::Local<v8::Value> GetAccessibilitySettings();
+
+  void SetScreenReaderSettingChangedHandler(v8::Local<v8::Function> handler);
+  void SetColorInversionSettingChangedHandler(v8::Local<v8::Function> handler);
+  void SetMagnificationGestureSettingChangedHandler(
+      v8::Local<v8::Function> handler);
+
+  void OnGetAccessibilitySettings(PersistedResolver resolver,
+                                  PersistedContext original_context,
+                                  mojom::AccessibilitySettingsPtr settings);
+
+  void AccessibilitySettingChanged(
+      v8::UniquePersistent<v8::Function>* handler_function,
+      bool new_value);
+
+  // mojom::CastAccessibilityClient implementation
+  void ScreenReaderSettingChanged(bool new_value) override;
+  void ColorInversionSettingChanged(bool new_value) override;
+  void MagnificationGestureSettingChanged(bool new_value) override;
+
+  mojo::Remote<mojom::CastAccessibilityService> accessibility_service_;
+
+  const FeatureManager* feature_manager_;
+
+  v8::UniquePersistent<v8::Function> screen_reader_setting_changed_handler_;
+  v8::UniquePersistent<v8::Function> color_inversion_setting_changed_handler_;
+  v8::UniquePersistent<v8::Function>
+      magnification_gesture_setting_changed_handler_;
+
+  service_manager::BinderRegistry registry_;
+
+  mojo::ReceiverSet<shell::mojom::CastAccessibilityClient> bindings_;
+};
+
+}  // namespace shell
+}  // namespace chromecast
+
+#endif  // CHROMECAST_RENDERER_CAST_ACCESSIBILITY_BINDINGS_H_
diff --git a/chromecast/renderer/cast_demo_bindings.cc b/chromecast/renderer/cast_demo_bindings.cc
new file mode 100644
index 0000000..76d44c0
--- /dev/null
+++ b/chromecast/renderer/cast_demo_bindings.cc
@@ -0,0 +1,373 @@
+// 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 "chromecast/renderer/cast_demo_bindings.h"
+
+#include "base/check.h"
+#include "content/public/renderer/render_frame.h"
+#include "content/public/renderer/v8_value_converter.h"
+#include "third_party/blink/public/common/browser_interface_broker_proxy.h"
+#include "third_party/blink/public/web/blink.h"
+#include "third_party/blink/public/web/web_local_frame.h"
+
+namespace chromecast {
+namespace shell {
+
+namespace {
+const int64_t kDelayBetweenReconnectionInMillis = 100;
+
+const char kDemoObjectName[] = "demo";
+
+const char kRecordEventName[] = "recordEvent";
+const char kSetRetailerName[] = "setRetailer";
+const char kSetStoreIdName[] = "setStoreId";
+const char kGetRetailerName[] = "getRetailer";
+const char kGetStoreIdName[] = "getStoreId";
+const char kSetDefaultVolumeName[] = "setDefaultVolume";
+const char kGetDefaultVolumeName[] = "getDefaultVolume";
+const char kApplyDefaultVolumeName[] = "applyDefaultVolume";
+const char kSetWifiConnectionName[] = "setWifiConnection";
+const char kGetAvailableWifiNetworksName[] = "getAvailableWifiNetworks";
+const char kGetWifiConnectionStateName[] = "getWifiConnectionState";
+const char kRegisterVolumeChangeHandlerName[] = "registerVolumeChangeHandler";
+const char kPersistLocalStorageName[] = "persistLocalStorage";
+
+const char kSetVolumeName[] = "setVolume";
+}  // namespace
+
+CastDemoBindings::CastDemoBindings(content::RenderFrame* render_frame)
+    : CastBinding(render_frame), binding_(this), weak_factory_(this) {}
+
+CastDemoBindings::~CastDemoBindings() {}
+
+void CastDemoBindings::Install(v8::Local<v8::Object> cast_platform,
+                               v8::Isolate* isolate) {
+  v8::Local<v8::Object> demo_object =
+      EnsureObjectExists(isolate, cast_platform, kDemoObjectName);
+
+  InstallBinding(isolate, demo_object, kRecordEventName,
+                 &CastDemoBindings::RecordEvent, base::Unretained(this));
+  InstallBinding(isolate, demo_object, kSetRetailerName,
+                 &CastDemoBindings::SetRetailerName, base::Unretained(this));
+  InstallBinding(isolate, demo_object, kSetStoreIdName,
+                 &CastDemoBindings::SetStoreId, base::Unretained(this));
+  InstallBinding(isolate, demo_object, kGetRetailerName,
+                 &CastDemoBindings::GetRetailerName, base::Unretained(this));
+  InstallBinding(isolate, demo_object, kGetStoreIdName,
+                 &CastDemoBindings::GetStoreId, base::Unretained(this));
+  InstallBinding(isolate, demo_object, kSetDefaultVolumeName,
+                 &CastDemoBindings::SetDefaultVolumeLevel,
+                 base::Unretained(this));
+  InstallBinding(isolate, demo_object, kGetDefaultVolumeName,
+                 &CastDemoBindings::GetDefaultVolumeLevel,
+                 base::Unretained(this));
+  InstallBinding(isolate, demo_object, kApplyDefaultVolumeName,
+                 &CastDemoBindings::ApplyDefaultVolume, base::Unretained(this));
+  InstallBinding(isolate, demo_object, kSetWifiConnectionName,
+                 &CastDemoBindings::SetWifiCredentials, base::Unretained(this));
+  InstallBinding(isolate, demo_object, kGetAvailableWifiNetworksName,
+                 &CastDemoBindings::GetAvailableWifiNetworks,
+                 base::Unretained(this));
+  InstallBinding(isolate, demo_object, kGetWifiConnectionStateName,
+                 &CastDemoBindings::GetConnectionStatus,
+                 base::Unretained(this));
+  InstallBinding(isolate, demo_object, kRegisterVolumeChangeHandlerName,
+                 &CastDemoBindings::SetVolumeChangeHandler,
+                 base::Unretained(this));
+  InstallBinding(isolate, demo_object, kPersistLocalStorageName,
+                 &CastDemoBindings::PersistLocalStorage,
+                 base::Unretained(this));
+
+  InstallBinding(isolate, demo_object, kSetVolumeName,
+                 &CastDemoBindings::SetVolume, base::Unretained(this));
+}
+
+void CastDemoBindings::RecordEvent(const std::string& event_name,
+                                   v8::Local<v8::Value> v8_data) {
+  v8::Isolate* v8_isolate = blink::MainThreadIsolate();
+  v8::HandleScope v8_handle_scope(v8_isolate);
+  blink::WebLocalFrame* web_frame = render_frame()->GetWebFrame();
+  v8::Local<v8::Context> v8_context = web_frame->MainWorldScriptContext();
+  v8::Context::Scope v8_context_scope(v8_context);
+
+  std::unique_ptr<content::V8ValueConverter> v8_converter =
+      content::V8ValueConverter::Create();
+  v8_converter->SetDateAllowed(true);
+  v8_converter->SetRegExpAllowed(true);
+  std::unique_ptr<base::Value> data_ptr =
+      v8_converter->FromV8Value(v8_data, v8_context);
+
+  base::Value data;
+  if (data_ptr) {
+    data = base::Value::FromUniquePtrValue(std::move(data_ptr));
+  }
+  GetCastDemo()->RecordEvent(event_name, std::move(data));
+}
+
+void CastDemoBindings::SetRetailerName(const std::string& retailer_name) {
+  GetCastDemo()->SetRetailerName(retailer_name);
+}
+
+void CastDemoBindings::SetStoreId(const std::string& store_id) {
+  GetCastDemo()->SetStoreId(store_id);
+}
+
+v8::Local<v8::Value> CastDemoBindings::GetRetailerName() {
+  v8::Isolate* isolate = blink::MainThreadIsolate();
+  auto context = isolate->GetCurrentContext();
+  v8::Local<v8::Promise::Resolver> resolver =
+      v8::Promise::Resolver::New(context).ToLocalChecked();
+  v8::Global<v8::Promise::Resolver> unique_resolver =
+      v8::Global<v8::Promise::Resolver>(isolate, resolver);
+  v8::Global<v8::Context> persisted_context =
+      v8::Global<v8::Context>(isolate, context);
+
+  GetCastDemo()->GetRetailerName(base::BindOnce(
+      &CastDemoBindings::OnGetRetailerName, base::Unretained(this),
+      std::move(unique_resolver), std::move(persisted_context)));
+  return resolver->GetPromise();
+}
+
+void CastDemoBindings::OnGetRetailerName(
+    v8::Global<v8::Promise::Resolver> resolver,
+    v8::Global<v8::Context> original_context,
+    const std::string& retailer_name) {
+  v8::Isolate* isolate = blink::MainThreadIsolate();
+  v8::MicrotasksScope microtasks_scope(
+      isolate, v8::MicrotasksScope::kDoNotRunMicrotasks);
+  v8::HandleScope handle_scope(isolate);
+  v8::Local<v8::Context> context = original_context.Get(isolate);
+  v8::Context::Scope context_scope(context);
+
+  resolver.Get(isolate)
+      ->Resolve(context, gin::ConvertToV8(isolate, retailer_name))
+      .ToChecked();
+}
+
+v8::Local<v8::Value> CastDemoBindings::GetStoreId() {
+  v8::Isolate* isolate = blink::MainThreadIsolate();
+  auto context = isolate->GetCurrentContext();
+  v8::Local<v8::Promise::Resolver> resolver =
+      v8::Promise::Resolver::New(context).ToLocalChecked();
+  v8::Global<v8::Promise::Resolver> unique_resolver =
+      v8::Global<v8::Promise::Resolver>(isolate, resolver);
+  v8::Global<v8::Context> persisted_context =
+      v8::Global<v8::Context>(isolate, context);
+
+  GetCastDemo()->GetStoreId(
+      base::BindOnce(&CastDemoBindings::OnGetStoreId, base::Unretained(this),
+                     std::move(unique_resolver), std::move(persisted_context)));
+  return resolver->GetPromise();
+}
+
+void CastDemoBindings::OnGetStoreId(v8::Global<v8::Promise::Resolver> resolver,
+                                    v8::Global<v8::Context> original_context,
+                                    const std::string& store_id) {
+  v8::Isolate* isolate = blink::MainThreadIsolate();
+  v8::MicrotasksScope microtasks_scope(
+      isolate, v8::MicrotasksScope::kDoNotRunMicrotasks);
+  v8::HandleScope handle_scope(isolate);
+  v8::Local<v8::Context> context = original_context.Get(isolate);
+  v8::Context::Scope context_scope(context);
+
+  resolver.Get(isolate)
+      ->Resolve(context, gin::ConvertToV8(isolate, store_id))
+      .ToChecked();
+}
+
+void CastDemoBindings::SetVolume(float level) {
+  // This method is deprecated. Provide a workable implementation to support
+  // development using old content.
+  SetDefaultVolumeLevel(level);
+  ApplyDefaultVolume();
+}
+
+void CastDemoBindings::SetDefaultVolumeLevel(float level) {
+  GetCastDemo()->SetDefaultVolumeLevel(level);
+}
+
+v8::Local<v8::Value> CastDemoBindings::GetDefaultVolumeLevel() {
+  v8::Isolate* isolate = blink::MainThreadIsolate();
+  auto context = isolate->GetCurrentContext();
+  v8::Local<v8::Promise::Resolver> resolver =
+      v8::Promise::Resolver::New(context).ToLocalChecked();
+  v8::Global<v8::Promise::Resolver> unique_resolver =
+      v8::Global<v8::Promise::Resolver>(isolate, resolver);
+  v8::Global<v8::Context> persisted_context =
+      v8::Global<v8::Context>(isolate, context);
+
+  GetCastDemo()->GetDefaultVolumeLevel(base::BindOnce(
+      &CastDemoBindings::OnGetDefaultVolumeLevel, base::Unretained(this),
+      std::move(unique_resolver), std::move(persisted_context)));
+  return resolver->GetPromise();
+}
+
+void CastDemoBindings::OnGetDefaultVolumeLevel(
+    v8::Global<v8::Promise::Resolver> resolver,
+    v8::Global<v8::Context> original_context,
+    float level) {
+  v8::Isolate* isolate = blink::MainThreadIsolate();
+  v8::MicrotasksScope microtasks_scope(
+      isolate, v8::MicrotasksScope::kDoNotRunMicrotasks);
+  v8::HandleScope handle_scope(isolate);
+  v8::Local<v8::Context> context = original_context.Get(isolate);
+  v8::Context::Scope context_scope(context);
+
+  resolver.Get(isolate)
+      ->Resolve(context, gin::ConvertToV8(isolate, level))
+      .ToChecked();
+}
+
+void CastDemoBindings::ApplyDefaultVolume() {
+  GetCastDemo()->ApplyDefaultVolume();
+}
+
+void CastDemoBindings::SetWifiCredentials(const std::string& ssid,
+                                          const std::string& psk) {
+  GetCastDemo()->SetWifiCredentials(ssid, psk);
+}
+
+v8::Local<v8::Value> CastDemoBindings::GetAvailableWifiNetworks() {
+  v8::Isolate* isolate = blink::MainThreadIsolate();
+  auto context = isolate->GetCurrentContext();
+  v8::Local<v8::Promise::Resolver> resolver =
+      v8::Promise::Resolver::New(context).ToLocalChecked();
+  v8::Global<v8::Promise::Resolver> unique_resolver =
+      v8::Global<v8::Promise::Resolver>(isolate, resolver);
+  v8::Global<v8::Context> persisted_context =
+      v8::Global<v8::Context>(isolate, context);
+
+  GetCastDemo()->GetAvailableWifiNetworks(base::BindOnce(
+      &CastDemoBindings::OnGetAvailableWifiNetworks, base::Unretained(this),
+      std::move(unique_resolver), std::move(persisted_context)));
+  return resolver->GetPromise();
+}
+
+void CastDemoBindings::OnGetAvailableWifiNetworks(
+    v8::Global<v8::Promise::Resolver> resolver,
+    v8::Global<v8::Context> original_context,
+    base::Value network_list) {
+  v8::Isolate* isolate = blink::MainThreadIsolate();
+  v8::MicrotasksScope microtasks_scope(
+      isolate, v8::MicrotasksScope::kDoNotRunMicrotasks);
+  v8::HandleScope handle_scope(isolate);
+  v8::Local<v8::Context> context = original_context.Get(isolate);
+  v8::Context::Scope context_scope(context);
+
+  std::unique_ptr<content::V8ValueConverter> v8_converter =
+      content::V8ValueConverter::Create();
+  v8::Local<v8::Value> v8_value =
+      v8_converter->ToV8Value(&network_list, context);
+
+  resolver.Get(isolate)
+      ->Resolve(context, gin::ConvertToV8(isolate, v8_value))
+      .ToChecked();
+}
+
+v8::Local<v8::Value> CastDemoBindings::GetConnectionStatus() {
+  v8::Isolate* isolate = blink::MainThreadIsolate();
+  auto context = isolate->GetCurrentContext();
+  v8::Local<v8::Promise::Resolver> resolver =
+      v8::Promise::Resolver::New(context).ToLocalChecked();
+  v8::Global<v8::Promise::Resolver> unique_resolver =
+      v8::Global<v8::Promise::Resolver>(isolate, resolver);
+  v8::Global<v8::Context> persisted_context =
+      v8::Global<v8::Context>(isolate, context);
+
+  GetCastDemo()->GetConnectionStatus(base::BindOnce(
+      &CastDemoBindings::OnGetConnectionStatus, base::Unretained(this),
+      std::move(unique_resolver), std::move(persisted_context)));
+  return resolver->GetPromise();
+}
+
+void CastDemoBindings::OnGetConnectionStatus(
+    v8::Global<v8::Promise::Resolver> resolver,
+    v8::Global<v8::Context> original_context,
+    base::Value status) {
+  v8::Isolate* isolate = blink::MainThreadIsolate();
+  v8::MicrotasksScope microtasks_scope(
+      isolate, v8::MicrotasksScope::kDoNotRunMicrotasks);
+  v8::HandleScope handle_scope(isolate);
+  v8::Local<v8::Context> context = original_context.Get(isolate);
+  v8::Context::Scope context_scope(context);
+
+  std::unique_ptr<content::V8ValueConverter> v8_converter =
+      content::V8ValueConverter::Create();
+  v8::Local<v8::Value> v8_value = v8_converter->ToV8Value(&status, context);
+
+  resolver.Get(isolate)
+      ->Resolve(context, gin::ConvertToV8(isolate, v8_value))
+      .ToChecked();
+}
+
+void CastDemoBindings::SetVolumeChangeHandler(
+    v8::Local<v8::Function> volume_change_handler) {
+  v8::Isolate* isolate = blink::MainThreadIsolate();
+  volume_change_handler_ =
+      v8::UniquePersistent<v8::Function>(isolate, volume_change_handler);
+}
+
+void CastDemoBindings::VolumeChanged(float level) {
+  if (volume_change_handler_.IsEmpty()) {
+    return;
+  }
+
+  v8::Isolate* isolate = blink::MainThreadIsolate();
+  v8::HandleScope handle_scope(isolate);
+  blink::WebLocalFrame* web_frame = render_frame()->GetWebFrame();
+  v8::Local<v8::Context> context = web_frame->MainWorldScriptContext();
+  v8::Context::Scope context_scope(context);
+  v8::Local<v8::Function> handler =
+      v8::Local<v8::Function>::New(isolate, std::move(volume_change_handler_));
+
+  std::vector<v8::Local<v8::Value>> args{gin::ConvertToV8(isolate, level)};
+
+  v8::MaybeLocal<v8::Value> maybe_result =
+      handler->Call(context, context->Global(), args.size(), args.data());
+
+  volume_change_handler_ = v8::UniquePersistent<v8::Function>(isolate, handler);
+
+  v8::Local<v8::Value> result;
+  ignore_result(maybe_result.ToLocal(&result));
+}
+
+void CastDemoBindings::PersistLocalStorage() {
+  GetCastDemo()->PersistLocalStorage();
+}
+
+void CastDemoBindings::ReconnectMojo() {
+  render_frame()->GetBrowserInterfaceBroker()->GetInterface(
+      cast_demo_.BindNewPipeAndPassReceiver());
+  DCHECK(cast_demo_.is_bound());
+  cast_demo_.set_disconnect_handler(base::BindOnce(
+      &CastDemoBindings::OnMojoConnectionError, base::Unretained(this)));
+
+  if (binding_.is_bound()) {
+    binding_.reset();
+  }
+
+  mojo::PendingRemote<mojom::CastDemoVolumeChangeObserver> pending_remote;
+  binding_.Bind(pending_remote.InitWithNewPipeAndPassReceiver());
+  cast_demo_->AddVolumeChangeObserver(std::move(pending_remote));
+}
+
+void CastDemoBindings::OnMojoConnectionError() {
+  LOG(WARNING) << "Disconnected from Demo Mojo. Will retry every "
+               << kDelayBetweenReconnectionInMillis << " milliseconds.";
+  base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
+      FROM_HERE,
+      base::BindOnce(&CastDemoBindings::ReconnectMojo,
+                     weak_factory_.GetWeakPtr()),
+      base::TimeDelta::FromMilliseconds(kDelayBetweenReconnectionInMillis));
+}
+
+const mojo::Remote<mojom::CastDemo>& CastDemoBindings::GetCastDemo() {
+  if (!cast_demo_.is_bound()) {
+    ReconnectMojo();
+  }
+  return cast_demo_;
+}
+
+}  // namespace shell
+}  // namespace chromecast
diff --git a/chromecast/renderer/cast_demo_bindings.h b/chromecast/renderer/cast_demo_bindings.h
new file mode 100644
index 0000000..2306bfe
--- /dev/null
+++ b/chromecast/renderer/cast_demo_bindings.h
@@ -0,0 +1,97 @@
+// 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 CHROMECAST_RENDERER_CAST_DEMO_BINDINGS_H_
+#define CHROMECAST_RENDERER_CAST_DEMO_BINDINGS_H_
+
+#include <string>
+
+#include "base/memory/weak_ptr.h"
+#include "chromecast/common/mojom/cast_demo.mojom.h"
+#include "chromecast/renderer/native_bindings_helper.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "v8/include/v8.h"
+
+namespace chromecast {
+namespace shell {
+
+// Enabled only when the device is in demonstration mode. This is only enabled
+// for the demonstration JS app.
+class CastDemoBindings : public CastBinding,
+                         public mojom::CastDemoVolumeChangeObserver {
+ public:
+  explicit CastDemoBindings(content::RenderFrame* render_frame);
+  CastDemoBindings(const CastDemoBindings&) = delete;
+  CastDemoBindings& operator=(const CastDemoBindings&) = delete;
+
+ private:
+  friend class CastBinding;
+
+  ~CastDemoBindings() override;
+
+  // CastBinding implementation:
+  void Install(v8::Local<v8::Object> cast_platform,
+               v8::Isolate* isolate) override;
+
+  // Methods to be called from v8: (See mojom for details)
+  void RecordEvent(const std::string& event_name, v8::Local<v8::Value> data);
+  void SetRetailerName(const std::string& retailer_name);
+  void SetStoreId(const std::string& store_id);
+  v8::Local<v8::Value> GetRetailerName();
+  v8::Local<v8::Value> GetStoreId();
+  void SetDefaultVolumeLevel(float level);
+  v8::Local<v8::Value> GetDefaultVolumeLevel();
+  void ApplyDefaultVolume();
+  void SetWifiCredentials(const std::string& ssid, const std::string& psk);
+  v8::Local<v8::Value> GetAvailableWifiNetworks();
+  v8::Local<v8::Value> GetConnectionStatus();
+  void SetVolumeChangeHandler(v8::Local<v8::Function> volume_change_handler);
+  void PersistLocalStorage();
+
+  // Deprecated
+  void SetVolume(float level);
+
+  // Methods to return values to v8:
+  void OnGetRetailerName(v8::Global<v8::Promise::Resolver> resolver,
+                         v8::Global<v8::Context> original_context,
+                         const std::string& retailer_name);
+  void OnGetStoreId(v8::Global<v8::Promise::Resolver> resolver,
+                    v8::Global<v8::Context> original_context,
+                    const std::string& store_id);
+  void OnGetDefaultVolumeLevel(v8::Global<v8::Promise::Resolver> resolver,
+                               v8::Global<v8::Context> original_context,
+                               float level);
+  void OnGetAvailableWifiNetworks(v8::Global<v8::Promise::Resolver> resolver,
+                                  v8::Global<v8::Context> original_context,
+                                  base::Value network_list);
+  void OnGetConnectionStatus(v8::Global<v8::Promise::Resolver> resolver,
+                             v8::Global<v8::Context> original_context,
+                             base::Value status);
+
+  // mojom::CastDemoVolumeChangeObserver implementation:
+  void VolumeChanged(float level) override;
+
+  void ReconnectMojo();
+  void OnMojoConnectionError();
+
+  // Returns a reference to |cast_demo_|, and binds it to a mojo pipe if
+  // necessary.
+  const mojo::Remote<mojom::CastDemo>& GetCastDemo();
+
+  // The pointer to the remote mojom::CastDemo interface.  Do not access this
+  // member directly; instead, use GetCastDemo().
+  mojo::Remote<mojom::CastDemo> cast_demo_;
+
+  mojo::Receiver<mojom::CastDemoVolumeChangeObserver> binding_;
+
+  v8::UniquePersistent<v8::Function> volume_change_handler_;
+
+  base::WeakPtrFactory<CastDemoBindings> weak_factory_;
+};
+
+}  // namespace shell
+}  // namespace chromecast
+
+#endif  // CHROMECAST_RENDERER_CAST_DEMO_BINDINGS_H_
diff --git a/chromecast/renderer/cast_window_manager_bindings.cc b/chromecast/renderer/cast_window_manager_bindings.cc
new file mode 100644
index 0000000..897f8b5
--- /dev/null
+++ b/chromecast/renderer/cast_window_manager_bindings.cc
@@ -0,0 +1,340 @@
+// 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 "chromecast/renderer/cast_window_manager_bindings.h"
+
+#include <vector>
+
+#include "base/check.h"
+#include "build/build_config.h"
+#include "chromecast/base/cast_features.h"
+#include "chromecast/common/feature_constants.h"
+#include "chromecast/common/mojom/gesture.mojom.h"
+#include "chromecast/renderer/feature_manager.h"
+#include "content/public/renderer/render_frame.h"
+#include "content/public/renderer/render_view.h"
+#include "gin/data_object_builder.h"
+#include "third_party/blink/public/common/browser_interface_broker_proxy.h"
+#include "third_party/blink/public/web/blink.h"
+#include "third_party/blink/public/web/web_local_frame.h"
+#include "third_party/blink/public/web/web_view.h"
+
+namespace chromecast {
+namespace shell {
+
+namespace {
+
+const char kBindingsObjectName[] = "windowManager";
+const char kOnBackGestureName[] = "onBackGesture";
+const char kOnBackGestureProgressName[] = "onBackGestureProgress";
+const char kOnBackGestureCancelName[] = "onBackGestureCancel";
+const char kOnTopDragGestureDoneName[] = "onTopDragGestureDone";
+const char kOnTopDragGestureProgressName[] = "onTopDragGestureProgress";
+const char kOnRightDragGestureDoneName[] = "onRightDragGestureDone";
+const char kOnRightDragGestureProgressName[] = "onRightDragGestureProgress";
+const char kOnTapGestureName[] = "onTapGesture";
+const char kOnTapDownGestureName[] = "onTapDownGesture";
+const char kCanGoBackName[] = "canGoBack";
+const char kCanTopDragName[] = "canTopDrag";
+const char kCanRightDragName[] = "canRightDrag";
+const char kMinimize[] = "minimize";
+const char kMaximize[] = "maximize";
+
+#if !defined(OS_ANDROID)
+const char kDisplayControlsName[] = "displayControls";
+#endif
+
+void OnGestureSourceDisconnectionError() {
+  LOG(ERROR) << "Connection error talking to system gesture source.";
+}
+
+void OnWindowDisconnect() {
+  LOG(ERROR) << "Connection error talking to platform activity.";
+}
+
+}  // namespace
+
+CastWindowManagerBindings::CastWindowManagerBindings(
+    content::RenderFrame* render_frame,
+    const FeatureManager* feature_manager)
+    : CastBinding(render_frame),
+      feature_manager_(feature_manager),
+      handler_receiver_(this) {
+  DCHECK(feature_manager_);
+}
+
+CastWindowManagerBindings::~CastWindowManagerBindings() {}
+
+void CastWindowManagerBindings::Install(v8::Local<v8::Object> cast_platform,
+                                        v8::Isolate* isolate) {
+  if (feature_manager_->FeatureEnabled(feature::kEnableSystemGestures)) {
+    v8::Local<v8::Object> windowManagerObject =
+        EnsureObjectExists(isolate, cast_platform, kBindingsObjectName);
+
+    // On back bindings.
+    InstallBinding(isolate, windowManagerObject, kCanGoBackName,
+                   &CastWindowManagerBindings::SetCanGoBack,
+                   base::Unretained(this));
+    InstallBinding(isolate, windowManagerObject, kOnBackGestureName,
+                   &CastWindowManagerBindings::SetV8Callback,
+                   base::Unretained(this), &on_back_gesture_callback_);
+    InstallBinding(isolate, windowManagerObject, kOnBackGestureProgressName,
+                   &CastWindowManagerBindings::SetV8Callback,
+                   base::Unretained(this), &on_back_gesture_progress_callback_);
+    InstallBinding(isolate, windowManagerObject, kOnBackGestureCancelName,
+                   &CastWindowManagerBindings::SetV8Callback,
+                   base::Unretained(this), &on_back_gesture_cancel_callback_);
+
+    // Top drag bindings.
+    InstallBinding(isolate, windowManagerObject, kCanTopDragName,
+                   &CastWindowManagerBindings::SetCanTopDrag,
+                   base::Unretained(this));
+    InstallBinding(isolate, windowManagerObject, kOnTopDragGestureDoneName,
+                   &CastWindowManagerBindings::SetV8Callback,
+                   base::Unretained(this), &on_top_drag_gesture_done_callback_);
+    InstallBinding(isolate, windowManagerObject, kOnTopDragGestureProgressName,
+                   &CastWindowManagerBindings::SetV8Callback,
+                   base::Unretained(this),
+                   &on_top_drag_gesture_progress_callback_);
+
+    // Right drag bindings.
+    InstallBinding(isolate, windowManagerObject, kCanRightDragName,
+                   &CastWindowManagerBindings::SetCanRightDrag,
+                   base::Unretained(this));
+    InstallBinding(isolate, windowManagerObject, kOnRightDragGestureDoneName,
+                   &CastWindowManagerBindings::SetV8Callback,
+                   base::Unretained(this),
+                   &on_right_drag_gesture_done_callback_);
+    InstallBinding(
+        isolate, windowManagerObject, kOnRightDragGestureProgressName,
+        &CastWindowManagerBindings::SetV8Callback, base::Unretained(this),
+        &on_right_drag_gesture_progress_callback_);
+
+    // 'Tap' bindings.
+    InstallBinding(isolate, windowManagerObject, kOnTapGestureName,
+                   &CastWindowManagerBindings::SetV8Callback,
+                   base::Unretained(this), &on_tap_gesture_callback_);
+    InstallBinding(isolate, windowManagerObject, kOnTapDownGestureName,
+                   &CastWindowManagerBindings::SetV8Callback,
+                   base::Unretained(this), &on_tap_down_gesture_callback_);
+  }
+  if (feature_manager_->FeatureEnabled(feature::kEnableWindowControls)) {
+    v8::Local<v8::Object> windowManagerObject =
+        EnsureObjectExists(isolate, cast_platform, kBindingsObjectName);
+
+    InstallBinding(isolate, windowManagerObject, kMaximize,
+                   &CastWindowManagerBindings::Show, base::Unretained(this));
+    InstallBinding(isolate, windowManagerObject, kMinimize,
+                   &CastWindowManagerBindings::Hide, base::Unretained(this));
+  }
+}
+
+v8::Local<v8::Value> CastWindowManagerBindings::SetV8Callback(
+    v8::UniquePersistent<v8::Function>* callback_function,
+    v8::Local<v8::Function> callback) {
+  v8::Isolate* isolate = blink::MainThreadIsolate();
+
+  *callback_function = v8::UniquePersistent<v8::Function>(isolate, callback);
+
+  return v8::Undefined(isolate);
+}
+
+void CastWindowManagerBindings::Show() {
+  BindWindow();
+  window_->Show();
+}
+
+void CastWindowManagerBindings::Hide() {
+  BindWindow();
+  window_->Hide();
+}
+
+void CastWindowManagerBindings::SetCanGoBack(bool can_go_back) {
+  BindGestureSource();
+  gesture_source_->SetCanGoBack(can_go_back);
+}
+
+void CastWindowManagerBindings::SetCanTopDrag(bool can_top_drag) {
+  BindGestureSource();
+  gesture_source_->SetCanTopDrag(can_top_drag);
+}
+
+void CastWindowManagerBindings::SetCanRightDrag(bool can_right_drag) {
+  BindGestureSource();
+  gesture_source_->SetCanRightDrag(can_right_drag);
+}
+
+void CastWindowManagerBindings::OnTouchInputSupportSet(
+    PersistedResolver resolver,
+    PersistedContext original_context,
+    bool resolve_promise,
+    bool display_controls) {
+  DVLOG(2) << __FUNCTION__;
+  v8::Isolate* isolate = blink::MainThreadIsolate();
+  v8::MicrotasksScope microtasks_scope(
+      isolate, v8::MicrotasksScope::kDoNotRunMicrotasks);
+  v8::HandleScope handle_scope(isolate);
+  v8::Local<v8::Context> context = original_context.Get(isolate);
+  v8::Context::Scope context_scope(context);
+
+  if (resolve_promise) {
+    resolver.Get(isolate)
+        ->Resolve(context,
+
+#if !defined(OS_ANDROID)
+                  gin::DataObjectBuilder(isolate)
+                      .Set(kDisplayControlsName, display_controls)
+                      .Build()
+#else
+                  v8::Undefined(isolate)
+#endif
+                      )
+        .ToChecked();
+  } else {
+    resolver.Get(isolate)->Reject(context, v8::Undefined(isolate)).ToChecked();
+  }
+}
+
+void CastWindowManagerBindings::BindGestureSource() {
+  if (gesture_source_.is_bound() && gesture_source_.is_connected())
+    return;
+  gesture_source_.reset();
+  render_frame()->GetBrowserInterfaceBroker()->GetInterface(
+      gesture_source_.BindNewPipeAndPassReceiver());
+  gesture_source_.set_disconnect_handler(
+      base::BindRepeating(&OnGestureSourceDisconnectionError));
+  handler_receiver_.reset();
+  gesture_source_->Subscribe(handler_receiver_.BindNewPipeAndPassRemote());
+}
+
+void CastWindowManagerBindings::BindWindow() {
+  if (window_.is_bound() && window_.is_connected())
+    return;
+  window_.reset();
+  render_frame()->GetBrowserInterfaceBroker()->GetInterface(
+      window_.BindNewPipeAndPassReceiver());
+  window_.set_disconnect_handler(base::BindRepeating(&OnWindowDisconnect));
+}
+
+void CastWindowManagerBindings::InvokeV8Callback(
+    v8::UniquePersistent<v8::Function>* callback_function) {
+  if (callback_function->IsEmpty()) {
+    return;
+  }
+
+  v8::Isolate* isolate = blink::MainThreadIsolate();
+  v8::HandleScope handle_scope(isolate);
+  blink::WebLocalFrame* web_frame = render_frame()->GetWebFrame();
+  v8::Local<v8::Context> context = web_frame->MainWorldScriptContext();
+  v8::Context::Scope context_scope(context);
+  v8::Local<v8::Function> handler =
+      v8::Local<v8::Function>::New(isolate, std::move(*callback_function));
+
+  v8::MaybeLocal<v8::Value> maybe_result =
+      handler->Call(context, context->Global(), 0, nullptr);
+
+  *callback_function = v8::UniquePersistent<v8::Function>(isolate, handler);
+
+  v8::Local<v8::Value> result;
+  ignore_result(maybe_result.ToLocal(&result));
+}
+
+void CastWindowManagerBindings::InvokeV8Callback(
+    v8::UniquePersistent<v8::Function>* callback_function,
+    const gfx::Point& touch_location) {
+  if (callback_function->IsEmpty()) {
+    return;
+  }
+
+  v8::Isolate* isolate = blink::MainThreadIsolate();
+  v8::HandleScope handle_scope(isolate);
+  blink::WebLocalFrame* web_frame = render_frame()->GetWebFrame();
+  v8::Local<v8::Context> context = web_frame->MainWorldScriptContext();
+  v8::Context::Scope context_scope(context);
+  v8::Local<v8::Function> handler =
+      v8::Local<v8::Function>::New(isolate, std::move(*callback_function));
+
+  v8::Local<v8::Number> touch_x = v8::Integer::New(isolate, touch_location.x());
+  v8::Local<v8::Number> touch_y = v8::Integer::New(isolate, touch_location.y());
+
+  std::vector<v8::Local<v8::Value>> args{touch_x, touch_y};
+
+  v8::MaybeLocal<v8::Value> maybe_result =
+      handler->Call(context, context->Global(), args.size(), args.data());
+
+  *callback_function = v8::UniquePersistent<v8::Function>(isolate, handler);
+
+  v8::Local<v8::Value> result;
+  ignore_result(maybe_result.ToLocal(&result));
+}
+
+void CastWindowManagerBindings::OnBackGesture(
+    ::chromecast::mojom::GestureHandler::OnBackGestureCallback callback) {
+  // Note: Can't use InvokeV8Callback here because of the OnBackGestureCallback
+  // argument. So we have this boilerplate here until we can get rid of the
+  // unused callback argument.
+  if (on_back_gesture_callback_.IsEmpty()) {
+    std::move(callback).Run(false);
+    return;
+  }
+
+  v8::Isolate* isolate = blink::MainThreadIsolate();
+  v8::HandleScope handle_scope(isolate);
+  blink::WebLocalFrame* web_frame = render_frame()->GetWebFrame();
+  v8::Local<v8::Context> context = web_frame->MainWorldScriptContext();
+  v8::Context::Scope context_scope(context);
+  v8::Local<v8::Function> handler = v8::Local<v8::Function>::New(
+      isolate, std::move(on_back_gesture_callback_));
+  auto result = handler->Call(context, context->Global(), 0, nullptr);
+
+  on_back_gesture_callback_ =
+      v8::UniquePersistent<v8::Function>(isolate, handler);
+  if (result.IsEmpty()) {
+    LOG(ERROR) << "No value from callback execution; ";
+    std::move(callback).Run(false);
+    return;
+  }
+
+  auto callback_return_value = result.ToLocalChecked();
+  bool return_boolean = callback_return_value->BooleanValue(isolate);
+  std::move(callback).Run(return_boolean);
+}
+
+void CastWindowManagerBindings::OnBackGestureProgress(
+    const gfx::Point& touch_location) {
+  InvokeV8Callback(&on_back_gesture_progress_callback_, touch_location);
+}
+
+void CastWindowManagerBindings::OnBackGestureCancel() {
+  InvokeV8Callback(&on_back_gesture_cancel_callback_);
+}
+
+void CastWindowManagerBindings::OnTopDragGestureDone() {
+  InvokeV8Callback(&on_top_drag_gesture_done_callback_);
+}
+
+void CastWindowManagerBindings::OnTopDragGestureProgress(
+    const gfx::Point& touch_location) {
+  InvokeV8Callback(&on_top_drag_gesture_progress_callback_, touch_location);
+}
+
+void CastWindowManagerBindings::OnRightDragGestureDone() {
+  InvokeV8Callback(&on_right_drag_gesture_done_callback_);
+}
+
+void CastWindowManagerBindings::OnRightDragGestureProgress(
+    const gfx::Point& touch_location) {
+  InvokeV8Callback(&on_right_drag_gesture_progress_callback_, touch_location);
+}
+
+void CastWindowManagerBindings::OnTapGesture() {
+  InvokeV8Callback(&on_tap_gesture_callback_);
+}
+
+void CastWindowManagerBindings::OnTapDownGesture() {
+  InvokeV8Callback(&on_tap_down_gesture_callback_);
+}
+
+}  // namespace shell
+}  // namespace chromecast
diff --git a/chromecast/renderer/cast_window_manager_bindings.h b/chromecast/renderer/cast_window_manager_bindings.h
new file mode 100644
index 0000000..dfb4731
--- /dev/null
+++ b/chromecast/renderer/cast_window_manager_bindings.h
@@ -0,0 +1,108 @@
+// 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 CHROMECAST_RENDERER_CAST_WINDOW_MANAGER_BINDINGS_H_
+#define CHROMECAST_RENDERER_CAST_WINDOW_MANAGER_BINDINGS_H_
+
+#include "base/memory/ptr_util.h"
+#include "chromecast/browser/mojom/cast_content_window.mojom.h"
+#include "chromecast/common/mojom/activity_window.mojom.h"
+#include "chromecast/common/mojom/gesture.mojom.h"
+#include "chromecast/renderer/native_bindings_helper.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "v8/include/v8.h"
+
+namespace chromecast {
+
+class FeatureManager;
+
+namespace shell {
+
+// Renderer side implementation of cast system gesture.
+// Implements the mojo service which receives notification of gesture events,
+// and also sets up the JS callback bindings to application code.
+class CastWindowManagerBindings : public CastBinding,
+                                  public ::chromecast::mojom::GestureHandler {
+ public:
+  CastWindowManagerBindings(content::RenderFrame* render_frame,
+                            const FeatureManager* feature_manager);
+  CastWindowManagerBindings(const CastWindowManagerBindings&) = delete;
+  CastWindowManagerBindings& operator=(const CastWindowManagerBindings&) =
+      delete;
+
+  // ::chromecast::mojom::GestureHandler implementation:
+  void OnBackGesture(OnBackGestureCallback callback) override;
+  void OnBackGestureProgress(const gfx::Point& touch_location) override;
+  void OnBackGestureCancel() override;
+  void OnTopDragGestureProgress(const gfx::Point& touch_location) override;
+  void OnTopDragGestureDone() override;
+  void OnRightDragGestureProgress(const gfx::Point& touch_location) override;
+  void OnRightDragGestureDone() override;
+  void OnTapGesture() override;
+  void OnTapDownGesture() override;
+
+ private:
+  friend class CastBinding;
+
+  ~CastWindowManagerBindings() override;
+
+  // Typedefs for v8 objects that need to run between tasks.
+  // v8::Global has move semantics, it does not imply unique ownership over an
+  // v8 object. You can create multiple v8::Globals from the same v8::Local.
+  using PersistedContext = v8::Global<v8::Context>;
+  using PersistedResolver = v8::Global<v8::Promise::Resolver>;
+
+  v8::Local<v8::Value> SetV8Callback(
+      v8::UniquePersistent<v8::Function>* callback_function,
+      v8::Local<v8::Function> callback);
+  void InvokeV8Callback(v8::UniquePersistent<v8::Function>* callback_function);
+  void InvokeV8Callback(v8::UniquePersistent<v8::Function>* callback_function,
+                        const gfx::Point& touch_location);
+
+  // CastBinding implementation:
+  void Install(v8::Local<v8::Object> cast_platform,
+               v8::Isolate* isolate) override;
+
+  void BindGestureSource();
+  void BindWindow();
+
+  void Show();
+  void Hide();
+  void SetCanGoBack(bool can_go_back);
+  void SetCanTopDrag(bool can_top_drag);
+  void SetCanRightDrag(bool can_right_drag);
+
+  void OnTouchInputSupportSet(PersistedResolver resolver,
+                              PersistedContext original_context,
+                              bool resolve_promise,
+                              bool display_controls);
+
+  const FeatureManager* feature_manager_;
+
+  mojo::Remote<::chromecast::mojom::GestureSource> gesture_source_;
+  mojo::Remote<::chromecast::mojom::ActivityWindow> window_;
+
+  // Receiver handle bound to self.
+  mojo::Receiver<::chromecast::mojom::GestureHandler> handler_receiver_;
+
+  v8::UniquePersistent<v8::Function> on_back_gesture_callback_;
+  v8::UniquePersistent<v8::Function> on_back_gesture_progress_callback_;
+  v8::UniquePersistent<v8::Function> on_back_gesture_cancel_callback_;
+
+  v8::UniquePersistent<v8::Function> on_top_drag_gesture_done_callback_;
+  v8::UniquePersistent<v8::Function> on_top_drag_gesture_progress_callback_;
+
+  v8::UniquePersistent<v8::Function> on_right_drag_gesture_done_callback_;
+  v8::UniquePersistent<v8::Function> on_right_drag_gesture_progress_callback_;
+
+  v8::UniquePersistent<v8::Function> on_tap_gesture_callback_;
+
+  v8::UniquePersistent<v8::Function> on_tap_down_gesture_callback_;
+};
+
+}  // namespace shell
+}  // namespace chromecast
+
+#endif  // CHROMECAST_RENDERER_CAST_WINDOW_MANAGER_BINDINGS_H_
diff --git a/chromecast/renderer/extensions/extension_hooks_delegate.cc b/chromecast/renderer/extensions/extension_hooks_delegate.cc
index d9cdb57..3e7b878 100644
--- a/chromecast/renderer/extensions/extension_hooks_delegate.cc
+++ b/chromecast/renderer/extensions/extension_hooks_delegate.cc
@@ -218,7 +218,8 @@
 
   v8::Local<v8::Value> v8_message = arguments[1];
   std::unique_ptr<Message> message = messaging_util::MessageFromV8(
-      script_context->v8_context(), v8_message, &error);
+      script_context->v8_context(), v8_message,
+      messaging_util::GetSerializationFormat(*script_context), &error);
   if (!message) {
     RequestResult result(RequestResult::INVALID_INVOCATION);
     result.error = std::move(error);
diff --git a/chromecast/renderer/extensions/tabs_hooks_delegate.cc b/chromecast/renderer/extensions/tabs_hooks_delegate.cc
index c8e7663..ffcbbeab 100644
--- a/chromecast/renderer/extensions/tabs_hooks_delegate.cc
+++ b/chromecast/renderer/extensions/tabs_hooks_delegate.cc
@@ -89,7 +89,8 @@
   v8::Local<v8::Value> v8_message = arguments[1];
   std::string error;
   std::unique_ptr<Message> message = messaging_util::MessageFromV8(
-      script_context->v8_context(), v8_message, &error);
+      script_context->v8_context(), v8_message,
+      messaging_util::GetSerializationFormat(*script_context), &error);
   if (!message) {
     RequestResult result(RequestResult::INVALID_INVOCATION);
     result.error = std::move(error);
@@ -124,7 +125,8 @@
   DCHECK(!v8_message.IsEmpty());
   std::string error;
   std::unique_ptr<Message> message = messaging_util::MessageFromV8(
-      script_context->v8_context(), v8_message, &error);
+      script_context->v8_context(), v8_message,
+      messaging_util::GetSerializationFormat(*script_context), &error);
   if (!message) {
     RequestResult result(RequestResult::INVALID_INVOCATION);
     result.error = std::move(error);
@@ -158,7 +160,8 @@
 
   gin::Handle<GinPort> port = messaging_service_->Connect(
       script_context, MessageTarget::ForTab(tab_id, options.frame_id),
-      options.channel_name);
+      options.channel_name,
+      messaging_util::GetSerializationFormat(*script_context));
   DCHECK(!port.IsEmpty());
 
   RequestResult result(RequestResult::HANDLED);
diff --git a/chromecast/renderer/feature_manager.cc b/chromecast/renderer/feature_manager.cc
new file mode 100644
index 0000000..734847b
--- /dev/null
+++ b/chromecast/renderer/feature_manager.cc
@@ -0,0 +1,185 @@
+// 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 "chromecast/renderer/feature_manager.h"
+
+#include <iostream>
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/check.h"
+#include "base/logging.h"
+#include "base/values.h"
+#include "chromecast/base/cast_features.h"
+#include "chromecast/common/feature_constants.h"
+#include "chromecast/renderer/assistant_bindings.h"
+#include "chromecast/renderer/cast_accessibility_bindings.h"
+#include "chromecast/renderer/cast_demo_bindings.h"
+#include "chromecast/renderer/cast_window_manager_bindings.h"
+#include "chromecast/renderer/settings_ui_bindings.h"
+#include "content/public/renderer/render_frame.h"
+#include "content/public/renderer/render_frame_media_playback_options.h"
+#include "services/network/public/cpp/is_potentially_trustworthy.h"
+#include "third_party/blink/public/common/associated_interfaces/associated_interface_registry.h"
+#include "third_party/blink/public/platform/web_runtime_features.h"
+#include "third_party/blink/public/platform/web_security_origin.h"
+#include "third_party/blink/public/platform/web_string.h"
+#include "third_party/blink/public/web/web_local_frame.h"
+#include "third_party/blink/public/web/web_security_policy.h"
+
+namespace chromecast {
+
+FeatureManager::FeatureManager(content::RenderFrame* render_frame)
+    : content::RenderFrameObserver(render_frame),
+      configured_(false),
+      can_install_bindings_(false),
+      dev_origin_(GURL::EmptyGURL()),
+      secure_origin_set_(false) {
+  registry_.AddInterface(base::BindRepeating(
+      &FeatureManager::OnFeatureManagerRequest, base::Unretained(this)));
+}
+
+FeatureManager::~FeatureManager() {}
+
+void FeatureManager::OnInterfaceRequestForFrame(
+    const std::string& interface_name,
+    mojo::ScopedMessagePipeHandle* interface_pipe) {
+  registry_.TryBindInterface(interface_name, interface_pipe);
+}
+
+void FeatureManager::OnDestruct() {
+  delete this;
+}
+
+void FeatureManager::DidClearWindowObject() {
+  can_install_bindings_ = true;
+  if (!configured_)
+    return;
+
+  EnableBindings();
+}
+
+void FeatureManager::ConfigureFeatures(
+    std::vector<chromecast::shell::mojom::FeaturePtr> features) {
+  if (configured_)
+    return;
+  configured_ = true;
+  for (auto& feature : features) {
+    // If we want to add enabled/disabled status to FeaturePtr, we can overlap
+    // previous setting via [] operator
+    features_map_[feature->name] = std::move(feature);
+  }
+
+  ConfigureFeaturesInternal();
+
+  if (!can_install_bindings_)
+    return;
+  EnableBindings();
+}
+
+void FeatureManager::ConfigureFeaturesInternal() {
+  if (FeatureEnabled(feature::kEnableDevMode)) {
+    base::Value& dev_mode_config =
+        (features_map_.find(feature::kEnableDevMode)->second)->config;
+    base::Value* dev_mode_origin =
+        dev_mode_config.FindKey(feature::kDevModeOrigin);
+    DCHECK(dev_mode_origin);
+    dev_origin_ = GURL(dev_mode_origin->GetString());
+    DCHECK(dev_origin_.is_valid());
+  }
+
+  if (FeatureEnabled(feature::kDisableBackgroundSuspend)) {
+    auto options = render_frame()->GetRenderFrameMediaPlaybackOptions();
+    options.is_background_suspend_enabled = false;
+    render_frame()->SetRenderFrameMediaPlaybackOptions(options);
+  }
+
+  // Call feature-specific functions.
+  SetupAdditionalSecureOrigin();
+
+  // Disable timer throttling for background tabs before the frame is painted.
+  if (FeatureEnabled(feature::kDisableBackgroundTabTimerThrottle)) {
+    blink::WebRuntimeFeatures::EnableTimerThrottlingForBackgroundTabs(false);
+  }
+  if (FeatureEnabled(feature::kEnableSettingsUiMojo)) {
+    v8_bindings_.insert(new shell::SettingsUiBindings(render_frame()));
+  }
+
+  // Window manager bindings will install themselves depending on the specific
+  // feature flags enabled, so we pass the feature manager through to let it
+  // decide.
+  v8_bindings_.insert(
+      new shell::CastWindowManagerBindings(render_frame(), this));
+  // Accessibility bindings will install themselves depending on the specific
+  // feature flags enabled, so we pass the feature manager through to let it
+  // decide.
+  v8_bindings_.insert(
+      new shell::CastAccessibilityBindings(render_frame(), this));
+
+  if (FeatureEnabled(feature::kEnableDemoStandaloneMode)) {
+    v8_bindings_.insert(new shell::CastDemoBindings(render_frame()));
+  }
+
+  if (FeatureEnabled(feature::kEnableAssistantMessagePipe)) {
+    auto& feature = GetFeature(feature::kEnableAssistantMessagePipe);
+    v8_bindings_.insert(
+        new shell::AssistantBindings(render_frame(), feature->config));
+  }
+}
+
+void FeatureManager::EnableBindings() {
+  LOG(INFO) << "Enabling bindings: " << *this;
+  for (auto* binding : v8_bindings_) {
+    binding->TryInstall();
+  }
+}
+
+void FeatureManager::OnFeatureManagerRequest(
+    mojo::PendingReceiver<shell::mojom::FeatureManager> request) {
+  bindings_.Add(this, std::move(request));
+}
+
+bool FeatureManager::FeatureEnabled(const std::string& feature) const {
+  return features_map_.find(feature) != features_map_.end();
+}
+
+const chromecast::shell::mojom::FeaturePtr& FeatureManager::GetFeature(
+    const std::string& feature) const {
+  auto itor = features_map_.find(feature);
+  DCHECK(itor != features_map_.end());
+  return itor->second;
+}
+
+void FeatureManager::SetupAdditionalSecureOrigin() {
+  if (!dev_origin_.is_valid()) {
+    return;
+  }
+
+  // Secure origin should be only set once, otherwise it will cause CHECK
+  // failure when race between origin safelist changing and thread creation
+  // happens (b/63583734).
+  if (secure_origin_set_) {
+    return;
+  }
+
+  secure_origin_set_ = true;
+
+  LOG(INFO) << "Treat origin " << dev_origin_ << " as secure origin";
+
+  blink::WebSecurityPolicy::AddSchemeToSecureContextSafelist(
+      blink::WebString::FromASCII(dev_origin_.scheme()));
+
+  network::SecureOriginAllowlist::GetInstance().SetAuxiliaryAllowlist(
+      dev_origin_.spec(), nullptr);
+}
+
+std::ostream& operator<<(std::ostream& os, const FeatureManager& features) {
+  for (auto& feature : features.features_map_) {
+    os << feature.first << " ";
+  }
+  return os;
+}
+
+}  // namespace chromecast
diff --git a/chromecast/renderer/feature_manager.h b/chromecast/renderer/feature_manager.h
new file mode 100644
index 0000000..fb05dd3a
--- /dev/null
+++ b/chromecast/renderer/feature_manager.h
@@ -0,0 +1,94 @@
+// 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 CHROMECAST_RENDERER_FEATURE_MANAGER_H_
+#define CHROMECAST_RENDERER_FEATURE_MANAGER_H_
+
+#include <iosfwd>
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/containers/flat_set.h"
+#include "base/values.h"
+#include "chromecast/common/mojom/feature_manager.mojom.h"
+#include "chromecast/renderer/native_bindings_helper.h"
+#include "content/public/renderer/render_frame_observer.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/receiver_set.h"
+#include "services/service_manager/public/cpp/binder_registry.h"
+#include "url/gurl.h"
+
+namespace chromecast {
+
+// Receives messages from the browser process to enable/disable Cast
+// application-facing features. Features usually have an associated V8 binding
+// which exposes a platform capability to the app.
+class FeatureManager : public content::RenderFrameObserver,
+                       public shell::mojom::FeatureManager {
+ public:
+  explicit FeatureManager(content::RenderFrame* render_frame);
+  FeatureManager(const FeatureManager&) = delete;
+  FeatureManager& operator=(const FeatureManager&) = delete;
+  ~FeatureManager() override;
+
+  const GURL& dev_origin() const { return dev_origin_; }
+  bool configured() const { return configured_; }
+
+  bool FeatureEnabled(const std::string& feature) const;
+
+  const chromecast::shell::mojom::FeaturePtr& GetFeature(
+      const std::string& feature) const;
+
+  friend std::ostream& operator<<(std::ostream& os,
+                                  const FeatureManager& features);
+
+ protected:
+  // Allows a derived class to add its own features at the end of
+  // mojom::FeatureManager::ConfigureFeatures().
+  virtual void ConfigureFeaturesInternal();
+
+  // Map for storing enabled features, name -> FeaturePtr.
+  using FeaturesMap =
+      std::map<std::string, chromecast::shell::mojom::FeaturePtr>;
+  FeaturesMap features_map_;
+
+  base::flat_set<CastBinding*> v8_bindings_;
+
+ private:
+  // content::RenderFrameObserver implementation:
+  void OnInterfaceRequestForFrame(
+      const std::string& interface_name,
+      mojo::ScopedMessagePipeHandle* interface_pipe) override;
+  void DidClearWindowObject() override;
+  void OnDestruct() override;
+
+  // shell::mojom::FeatureManager implementation
+  void ConfigureFeatures(
+      std::vector<chromecast::shell::mojom::FeaturePtr> features) override;
+
+  // Bind the incoming request with this implementation
+  void OnFeatureManagerRequest(
+      mojo::PendingReceiver<shell::mojom::FeatureManager> request);
+
+  void EnableBindings();
+  void SetupAdditionalSecureOrigin();
+
+  // Flag for when the configuration message is received from the browser.
+  bool configured_;
+  bool can_install_bindings_;
+
+  // Origin enabled for development
+  GURL dev_origin_;
+  bool secure_origin_set_;
+
+  service_manager::BinderRegistry registry_;
+
+  mojo::ReceiverSet<shell::mojom::FeatureManager> bindings_;
+};
+
+}  // namespace chromecast
+
+#endif  // CHROMECAST_RENDERER_FEATURE_MANAGER_H_
diff --git a/chromecast/renderer/settings_ui_bindings.cc b/chromecast/renderer/settings_ui_bindings.cc
new file mode 100644
index 0000000..740aaca
--- /dev/null
+++ b/chromecast/renderer/settings_ui_bindings.cc
@@ -0,0 +1,171 @@
+// 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 "chromecast/renderer/settings_ui_bindings.h"
+
+#include <vector>
+
+#include "base/bind.h"
+#include "base/check.h"
+#include "base/location.h"
+#include "content/public/renderer/render_frame.h"
+#include "third_party/blink/public/common/browser_interface_broker_proxy.h"
+#include "third_party/blink/public/web/blink.h"
+#include "third_party/blink/public/web/web_local_frame.h"
+
+namespace chromecast {
+namespace shell {
+
+namespace {
+
+const int64_t kDelayBetweenReconnectionInMillis = 100;
+
+const char kSettingsUiName[] = "settings_ui";
+const char kSetSideSwipeHandler[] = "setSideSwipeHandler";
+const char kSetPlatformInfoHandler[] = "setPlatformInfoHandler";
+const char kRequestVisible[] = "requestVisible";
+
+}  // namespace
+
+SettingsUiBindings::SettingsUiBindings(content::RenderFrame* frame)
+    : CastBinding(frame), binding_(this), weak_factory_(this) {
+  ReconnectMojo();
+}
+
+SettingsUiBindings::~SettingsUiBindings() {}
+
+void SettingsUiBindings::HandleSideSwipe(
+    chromecast::mojom::SideSwipeEvent event,
+    chromecast::mojom::SideSwipeOrigin origin,
+    const gfx::Point& touch_location) {
+  if (side_swipe_handler_.IsEmpty()) {
+    return;
+  }
+
+  v8::Isolate* isolate = blink::MainThreadIsolate();
+  v8::MicrotasksScope microtasks_scope(
+      isolate, v8::MicrotasksScope::kDoNotRunMicrotasks);
+  v8::HandleScope handle_scope(isolate);
+  blink::WebLocalFrame* web_frame = render_frame()->GetWebFrame();
+  v8::Local<v8::Context> context = web_frame->MainWorldScriptContext();
+  v8::Context::Scope context_scope(context);
+  v8::Local<v8::Function> handler =
+      v8::Local<v8::Function>::New(isolate, std::move(side_swipe_handler_));
+
+  v8::Local<v8::Number> touch_event =
+      v8::Integer::New(isolate, static_cast<int>(event));
+  v8::Local<v8::Number> touch_origin =
+      v8::Integer::New(isolate, static_cast<int>(origin));
+  v8::Local<v8::Number> touch_x = v8::Integer::New(isolate, touch_location.x());
+  v8::Local<v8::Number> touch_y = v8::Integer::New(isolate, touch_location.y());
+
+  std::vector<v8::Local<v8::Value>> args{touch_event, touch_origin, touch_x,
+                                         touch_y};
+
+  v8::MaybeLocal<v8::Value> maybe_result =
+      handler->Call(context, context->Global(), args.size(), args.data());
+
+  side_swipe_handler_ = v8::UniquePersistent<v8::Function>(isolate, handler);
+
+  v8::Local<v8::Value> result;
+  ignore_result(maybe_result.ToLocal(&result));
+}
+
+void SettingsUiBindings::SendPlatformInfo(
+    const std::string& platform_info_json) {
+  if (platform_info_handler_.IsEmpty()) {
+    pending_platform_info_json_ = platform_info_json;
+    return;
+  }
+
+  v8::Isolate* isolate = blink::MainThreadIsolate();
+  v8::MicrotasksScope microtasks_scope(
+      isolate, v8::MicrotasksScope::kDoNotRunMicrotasks);
+  v8::HandleScope handle_scope(isolate);
+  blink::WebLocalFrame* web_frame = render_frame()->GetWebFrame();
+  v8::Local<v8::Context> context = web_frame->MainWorldScriptContext();
+  v8::Context::Scope context_scope(context);
+  v8::Local<v8::Function> handler =
+      v8::Local<v8::Function>::New(isolate, std::move(platform_info_handler_));
+
+  v8::Local<v8::String> platform_info =
+      v8::String::NewFromUtf8(isolate, platform_info_json.data(),
+                              v8::NewStringType::kInternalized)
+          .ToLocalChecked();
+
+  std::vector<v8::Local<v8::Value>> args{platform_info};
+
+  v8::MaybeLocal<v8::Value> maybe_result =
+      handler->Call(context, context->Global(), args.size(), args.data());
+
+  platform_info_handler_ = v8::UniquePersistent<v8::Function>(isolate, handler);
+
+  v8::Local<v8::Value> result;
+  ignore_result(maybe_result.ToLocal(&result));
+}
+
+void SettingsUiBindings::Install(v8::Local<v8::Object> cast_platform,
+                                 v8::Isolate* isolate) {
+  v8::Local<v8::Object> settings_ui =
+      EnsureObjectExists(isolate, cast_platform, kSettingsUiName);
+
+  InstallBinding(isolate, settings_ui, kSetSideSwipeHandler,
+                 &SettingsUiBindings::SetSideSwipeHandler,
+                 base::Unretained(this));
+  InstallBinding(isolate, settings_ui, kSetPlatformInfoHandler,
+                 &SettingsUiBindings::SetPlatformInfoHandler,
+                 base::Unretained(this));
+  InstallBinding(isolate, settings_ui, kRequestVisible,
+                 &SettingsUiBindings::RequestVisible, base::Unretained(this));
+}
+
+void SettingsUiBindings::SetSideSwipeHandler(
+    v8::Local<v8::Function> side_swipe_handler) {
+  v8::Isolate* isolate = blink::MainThreadIsolate();
+  side_swipe_handler_ =
+      v8::UniquePersistent<v8::Function>(isolate, side_swipe_handler);
+}
+
+void SettingsUiBindings::SetPlatformInfoHandler(
+    v8::Local<v8::Function> platform_info_handler) {
+  v8::Isolate* isolate = blink::MainThreadIsolate();
+  platform_info_handler_ =
+      v8::UniquePersistent<v8::Function>(isolate, platform_info_handler);
+  if (!pending_platform_info_json_.empty()) {
+    SendPlatformInfo(pending_platform_info_json_);
+    pending_platform_info_json_.clear();
+  }
+}
+
+void SettingsUiBindings::RequestVisible(bool visible) {
+  if (settings_platform_ptr_) {
+    settings_platform_ptr_->RequestVisible(visible);
+  }
+}
+
+void SettingsUiBindings::ReconnectMojo() {
+  if (binding_.is_bound())
+    binding_.reset();
+  if (settings_platform_ptr_.is_bound())
+    settings_platform_ptr_.reset();
+
+  render_frame()->GetBrowserInterfaceBroker()->GetInterface(
+      settings_platform_ptr_.BindNewPipeAndPassReceiver());
+  settings_platform_ptr_.set_disconnect_handler(base::BindOnce(
+      &SettingsUiBindings::OnMojoConnectionError, weak_factory_.GetWeakPtr()));
+  settings_platform_ptr_->Connect(binding_.BindNewPipeAndPassRemote());
+  mojo_reconnect_timer_.Stop();
+}
+
+void SettingsUiBindings::OnMojoConnectionError() {
+  LOG(WARNING) << "Disconnected from settings UI MOJO. Will reconnect every "
+               << kDelayBetweenReconnectionInMillis << " milliseconds.";
+  mojo_reconnect_timer_.Start(
+      FROM_HERE,
+      base::TimeDelta::FromMilliseconds(kDelayBetweenReconnectionInMillis),
+      this, &SettingsUiBindings::ReconnectMojo);
+}
+
+}  // namespace shell
+}  // namespace chromecast
diff --git a/chromecast/renderer/settings_ui_bindings.h b/chromecast/renderer/settings_ui_bindings.h
new file mode 100644
index 0000000..b5b75d9d
--- /dev/null
+++ b/chromecast/renderer/settings_ui_bindings.h
@@ -0,0 +1,69 @@
+// 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 CHROMECAST_RENDERER_SETTINGS_UI_BINDINGS_H_
+#define CHROMECAST_RENDERER_SETTINGS_UI_BINDINGS_H_
+
+#include <string>
+
+#include "base/memory/weak_ptr.h"
+#include "base/timer/timer.h"
+#include "chromecast/common/mojom/settings_ui.mojom.h"
+#include "chromecast/renderer/native_bindings_helper.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "v8/include/v8.h"
+
+namespace chromecast {
+namespace shell {
+
+// Exposed to the platform settings UI app on display assistants. Handles
+// certain swipe events to show/hide the settings menu. This also receives
+// platform information periodically to update the device properties in the
+// settings UI.
+class SettingsUiBindings : public CastBinding,
+                           public chromecast::mojom::SettingsClient {
+ public:
+  explicit SettingsUiBindings(content::RenderFrame* frame);
+  ~SettingsUiBindings() override;
+  SettingsUiBindings(const SettingsUiBindings&) = delete;
+  SettingsUiBindings& operator=(const SettingsUiBindings&) = delete;
+
+ private:
+  friend class ::chromecast::CastBinding;
+
+  // mojom::SettingsClient implementation:
+  void HandleSideSwipe(chromecast::mojom::SideSwipeEvent event,
+                       chromecast::mojom::SideSwipeOrigin origin,
+                       const gfx::Point& touch_location) override;
+  void SendPlatformInfo(const std::string& platform_info_json) override;
+
+  // CastBinding implementation:
+  void Install(v8::Local<v8::Object> cast_platform,
+               v8::Isolate* isolate) override;
+
+  // Binding methods
+  void SetSideSwipeHandler(v8::Local<v8::Function> side_swipe_handler);
+  void SetPlatformInfoHandler(v8::Local<v8::Function> platform_info_handler);
+  void RequestVisible(bool visible);
+
+  void ReconnectMojo();
+  void OnMojoConnectionError();
+
+  std::string pending_platform_info_json_;
+
+  v8::UniquePersistent<v8::Function> side_swipe_handler_;
+  v8::UniquePersistent<v8::Function> platform_info_handler_;
+
+  mojo::Remote<chromecast::mojom::SettingsPlatform> settings_platform_ptr_;
+  mojo::Receiver<chromecast::mojom::SettingsClient> binding_;
+  base::RepeatingTimer mojo_reconnect_timer_;
+
+  base::WeakPtrFactory<SettingsUiBindings> weak_factory_;
+};
+
+}  // namespace shell
+}  // namespace chromecast
+
+#endif  // CHROMECAST_RENDERER_SETTINGS_UI_BINDINGS_H_
diff --git a/chromeos/chromeos_strings.grd b/chromeos/chromeos_strings.grd
index 5500cf9..604e047 100644
--- a/chromeos/chromeos_strings.grd
+++ b/chromeos/chromeos_strings.grd
@@ -1065,13 +1065,13 @@
         Can't resolve DNS from Android apps
       </message>
       <message name="IDS_NETWORK_DIAGNOSTICS_NO_IP_ADDRESS_TEXT" desc="The text shown when a network's IP Address is missing.">
-        Unable to obtain IP address
+        IP address is unavailable
       </message>
       <message name="IDS_NETWORK_DIAGNOSTICS_VISIT_SETTINGS_TO_CONFIGURE_LINK_TEXT" desc="Help text that directs users to settings to configure their network.">
-        Visit settings to configure
+        To set up, go to Settings
       </message>
       <message name="IDS_NETWORK_DIAGNOSTICS_MISSING_NAME_SERVERS_TEXT" desc="The text shown when no name servers are configured.">
-        No name servers are configured
+        DNS isn't set up
       </message>
       <message name="IDS_DIAGNOSTICS_NETWORK_SIGNAL_STRENGTH_WEAK" desc="Label used to describe the signal strength of a network connection when the signal is weak.">
         Weak (<ph name="SIGNAL_STRENGTH">$1<ex>(20)</ex></ph>)
diff --git a/chromeos/chromeos_strings_grd/IDS_NETWORK_DIAGNOSTICS_MISSING_NAME_SERVERS_TEXT.png.sha1 b/chromeos/chromeos_strings_grd/IDS_NETWORK_DIAGNOSTICS_MISSING_NAME_SERVERS_TEXT.png.sha1
index e88da440..0072e72 100644
--- a/chromeos/chromeos_strings_grd/IDS_NETWORK_DIAGNOSTICS_MISSING_NAME_SERVERS_TEXT.png.sha1
+++ b/chromeos/chromeos_strings_grd/IDS_NETWORK_DIAGNOSTICS_MISSING_NAME_SERVERS_TEXT.png.sha1
@@ -1 +1 @@
-73746946099c4c974852e63234501effa4f798f1
\ No newline at end of file
+2f4cd218cecf2d8eb157f7bd3351bf17ebc14542
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_NETWORK_DIAGNOSTICS_NO_IP_ADDRESS_TEXT.png.sha1 b/chromeos/chromeos_strings_grd/IDS_NETWORK_DIAGNOSTICS_NO_IP_ADDRESS_TEXT.png.sha1
index 6a08a142..235cdd08 100644
--- a/chromeos/chromeos_strings_grd/IDS_NETWORK_DIAGNOSTICS_NO_IP_ADDRESS_TEXT.png.sha1
+++ b/chromeos/chromeos_strings_grd/IDS_NETWORK_DIAGNOSTICS_NO_IP_ADDRESS_TEXT.png.sha1
@@ -1 +1 @@
-56b002c1ffe8572c90dc532fd18de231a2823ae5
\ No newline at end of file
+d4521a2b262d7345cf7c470069a9dc378f5918ef
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_NETWORK_DIAGNOSTICS_VISIT_SETTINGS_TO_CONFIGURE_LINK_TEXT.png.sha1 b/chromeos/chromeos_strings_grd/IDS_NETWORK_DIAGNOSTICS_VISIT_SETTINGS_TO_CONFIGURE_LINK_TEXT.png.sha1
index 6a08a142..235cdd08 100644
--- a/chromeos/chromeos_strings_grd/IDS_NETWORK_DIAGNOSTICS_VISIT_SETTINGS_TO_CONFIGURE_LINK_TEXT.png.sha1
+++ b/chromeos/chromeos_strings_grd/IDS_NETWORK_DIAGNOSTICS_VISIT_SETTINGS_TO_CONFIGURE_LINK_TEXT.png.sha1
@@ -1 +1 @@
-56b002c1ffe8572c90dc532fd18de231a2823ae5
\ No newline at end of file
+d4521a2b262d7345cf7c470069a9dc378f5918ef
\ No newline at end of file
diff --git a/chromeos/components/eche_app_ui/BUILD.gn b/chromeos/components/eche_app_ui/BUILD.gn
index 03e52027..16e1b22 100644
--- a/chromeos/components/eche_app_ui/BUILD.gn
+++ b/chromeos/components/eche_app_ui/BUILD.gn
@@ -32,6 +32,8 @@
     "eche_notification_click_handler.h",
     "eche_notification_generator.cc",
     "eche_notification_generator.h",
+    "eche_presence_manager.cc",
+    "eche_presence_manager.h",
     "eche_recent_app_click_handler.cc",
     "eche_recent_app_click_handler.h",
     "eche_signaler.cc",
@@ -67,7 +69,8 @@
     "//chromeos/resources:eche_bundle_resources",
     "//chromeos/services/device_sync/public/cpp:cpp",
     "//chromeos/services/multidevice_setup/public/cpp:cpp",
-    "//chromeos/services/secure_channel/public/cpp/client:client",
+    "//chromeos/services/secure_channel/public/cpp/client",
+    "//chromeos/services/secure_channel/public/cpp/shared",
     "//components/prefs",
     "//content/public/browser",
     "//mojo/public/js:resources",
diff --git a/chromeos/components/eche_app_ui/eche_app_manager.cc b/chromeos/components/eche_app_ui/eche_app_manager.cc
index 3b1bc3d..96803be 100644
--- a/chromeos/components/eche_app_ui/eche_app_manager.cc
+++ b/chromeos/components/eche_app_ui/eche_app_manager.cc
@@ -8,6 +8,7 @@
 #include "base/system/sys_info.h"
 #include "chromeos/components/eche_app_ui/eche_message_receiver.h"
 #include "chromeos/components/eche_app_ui/eche_notification_generator.h"
+#include "chromeos/components/eche_app_ui/eche_presence_manager.h"
 #include "chromeos/components/eche_app_ui/eche_signaler.h"
 #include "chromeos/components/eche_app_ui/eche_uid_provider.h"
 #include "chromeos/components/eche_app_ui/launch_app_helper.h"
@@ -32,6 +33,8 @@
     device_sync::DeviceSyncClient* device_sync_client,
     multidevice_setup::MultiDeviceSetupClient* multidevice_setup_client,
     secure_channel::SecureChannelClient* secure_channel_client,
+    std::unique_ptr<secure_channel::PresenceMonitorClient>
+        presence_monitor_client,
     LaunchAppHelper::LaunchEcheAppFunction launch_eche_app_function,
     LaunchAppHelper::CloseEcheAppFunction close_eche_app_function,
     LaunchAppHelper::LaunchNotificationFunction launch_notification_function)
@@ -64,6 +67,12 @@
                                           connection_manager_.get())),
       signaler_(std::make_unique<EcheSignaler>(eche_connector_.get(),
                                                connection_manager_.get())),
+      eche_presence_manager_(std::make_unique<EchePresenceManager>(
+          feature_status_provider_.get(),
+          device_sync_client,
+          multidevice_setup_client,
+          std::move(presence_monitor_client),
+          eche_connector_.get())),
       uid_(std::make_unique<EcheUidProvider>(pref_service)),
       eche_recent_app_click_handler_(
           std::make_unique<EcheRecentAppClickHandler>(
@@ -105,16 +114,17 @@
 // NOTE: These should be destroyed in the opposite order of how these objects
 // are initialized in the constructor.
 void EcheAppManager::Shutdown() {
+  system_info_provider_.reset();
+  message_receiver_.reset();
   notification_generator_.reset();
   eche_recent_app_click_handler_.reset();
   uid_.reset();
-  system_info_provider_.reset();
+  eche_presence_manager_.reset();
   signaler_.reset();
   eche_connector_.reset();
   eche_notification_click_handler_.reset();
   launch_app_helper_.reset();
   feature_status_provider_.reset();
-  message_receiver_.reset();
   connection_manager_.reset();
 }
 
diff --git a/chromeos/components/eche_app_ui/eche_app_manager.h b/chromeos/components/eche_app_ui/eche_app_manager.h
index 28d7312..0895615 100644
--- a/chromeos/components/eche_app_ui/eche_app_manager.h
+++ b/chromeos/components/eche_app_ui/eche_app_manager.h
@@ -6,6 +6,7 @@
 #define CHROMEOS_COMPONENTS_ECHE_APP_UI_ECHE_APP_MANAGER_H_
 
 #include <stdint.h>
+#include <memory>
 
 #include "chromeos/components/eche_app_ui/eche_connector.h"
 #include "chromeos/components/eche_app_ui/eche_feature_status_provider.h"
@@ -15,6 +16,7 @@
 #include "chromeos/components/eche_app_ui/mojom/eche_app.mojom.h"
 #include "chromeos/components/phonehub/phone_hub_manager.h"
 #include "chromeos/services/network_config/public/mojom/cros_network_config.mojom.h"
+#include "chromeos/services/secure_channel/public/cpp/client/presence_monitor_client_impl.h"
 #include "components/keyed_service/core/keyed_service.h"
 #include "mojo/public/cpp/bindings/remote.h"
 
@@ -37,12 +39,13 @@
 
 namespace eche_app {
 
-class SystemInfo;
-class EcheSignaler;
-class SystemInfoProvider;
-class EcheUidProvider;
-class EcheNotificationGenerator;
 class EcheMessageReceiver;
+class EcheNotificationGenerator;
+class EchePresenceManager;
+class EcheSignaler;
+class EcheUidProvider;
+class SystemInfo;
+class SystemInfoProvider;
 
 // Implements the core logic of the EcheApp and exposes interfaces via its
 // public API. Implemented as a KeyedService since it depends on other
@@ -55,6 +58,8 @@
                  device_sync::DeviceSyncClient*,
                  multidevice_setup::MultiDeviceSetupClient*,
                  secure_channel::SecureChannelClient*,
+                 std::unique_ptr<secure_channel::PresenceMonitorClient>
+                     presence_monitor_client,
                  LaunchAppHelper::LaunchEcheAppFunction,
                  LaunchAppHelper::CloseEcheAppFunction,
                  LaunchAppHelper::LaunchNotificationFunction);
@@ -86,6 +91,7 @@
       eche_notification_click_handler_;
   std::unique_ptr<EcheConnector> eche_connector_;
   std::unique_ptr<EcheSignaler> signaler_;
+  std::unique_ptr<EchePresenceManager> eche_presence_manager_;
   std::unique_ptr<EcheUidProvider> uid_;
   std::unique_ptr<EcheRecentAppClickHandler> eche_recent_app_click_handler_;
   std::unique_ptr<EcheNotificationGenerator> notification_generator_;
diff --git a/chromeos/components/eche_app_ui/eche_connector.cc b/chromeos/components/eche_app_ui/eche_connector.cc
index 51099673..aa2aff72 100644
--- a/chromeos/components/eche_app_ui/eche_connector.cc
+++ b/chromeos/components/eche_app_ui/eche_connector.cc
@@ -10,7 +10,6 @@
 #include "chromeos/components/multidevice/software_feature.h"
 #include "chromeos/components/multidevice/software_feature_state.h"
 #include "chromeos/components/phonehub/phone_hub_manager.h"
-#include "chromeos/services/device_sync/public/cpp/device_sync_client.h"
 #include "chromeos/services/secure_channel/public/cpp/client/connection_manager.h"
 
 namespace chromeos {
@@ -84,12 +83,12 @@
   const FeatureStatus feature_status =
       eche_feature_status_provider_->GetStatus();
   if (feature_status == FeatureStatus::kConnected && !queue_.empty()) {
+    PA_LOG(INFO) << "Flushing message queue";
     FlushQueue();
   }
 }
 
 void EcheConnector::FlushQueue() {
-  PA_LOG(INFO) << "Flushing message queue";
   const int size = queue_.size();
   for (int i = 0; i < size; i++) {
     connection_manager_->SendMessage(queue_.front());
diff --git a/chromeos/components/eche_app_ui/eche_presence_manager.cc b/chromeos/components/eche_app_ui/eche_presence_manager.cc
new file mode 100644
index 0000000..57f31cd
--- /dev/null
+++ b/chromeos/components/eche_app_ui/eche_presence_manager.cc
@@ -0,0 +1,146 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromeos/components/eche_app_ui/eche_presence_manager.h"
+
+#include "chromeos/components/eche_app_ui/eche_connector.h"
+#include "chromeos/components/eche_app_ui/proto/exo_messages.pb.h"
+#include "chromeos/components/multidevice/logging/logging.h"
+#include "chromeos/components/multidevice/remote_device_ref.h"
+#include "chromeos/services/device_sync/public/cpp/device_sync_client.h"
+#include "chromeos/services/secure_channel/public/cpp/client/presence_monitor_client.h"
+
+namespace chromeos {
+namespace eche_app {
+
+namespace {
+
+// How often to check whether the last seen time is below the maximum age.
+constexpr base::TimeDelta kTimerInterval = base::Seconds(30);
+// The maximum age of the last seen time before the connection must be
+// terminated.
+constexpr base::TimeDelta kMaximumLastSeenAge = base::Minutes(5);
+
+}  // namespace
+
+EchePresenceManager::EchePresenceManager(
+    EcheFeatureStatusProvider* eche_feature_status_provider,
+    device_sync::DeviceSyncClient* device_sync_client,
+    multidevice_setup::MultiDeviceSetupClient* multidevice_setup_client,
+    std::unique_ptr<secure_channel::PresenceMonitorClient>
+        presence_monitor_client,
+    EcheConnector* eche_connector)
+    : eche_feature_status_provider_(eche_feature_status_provider),
+      device_sync_client_(device_sync_client),
+      multidevice_setup_client_(multidevice_setup_client),
+      presence_monitor_client_(std::move(presence_monitor_client)),
+      eche_connector_(eche_connector) {
+  eche_feature_status_provider_->AddObserver(this);
+  presence_monitor_client_->SetPresenceMonitorCallbacks(
+      base::BindRepeating(&EchePresenceManager::OnReady,
+                          weak_ptr_factory_.GetWeakPtr()),
+      base::BindRepeating(&EchePresenceManager::OnDeviceSeen,
+                          weak_ptr_factory_.GetWeakPtr()));
+}
+
+EchePresenceManager::~EchePresenceManager() {
+  eche_feature_status_provider_->RemoveObserver(this);
+}
+
+void EchePresenceManager::OnFeatureStatusChanged() {
+  UpdateMonitoringStatus();
+}
+
+void EchePresenceManager::OnReady() {
+  UpdateMonitoringStatus();
+}
+
+void EchePresenceManager::UpdateMonitoringStatus() {
+  const FeatureStatus feature_status =
+      eche_feature_status_provider_->GetStatus();
+  switch (feature_status) {
+    case FeatureStatus::kIneligible:
+      ABSL_FALLTHROUGH_INTENDED;
+    case FeatureStatus::kDisabled:
+      ABSL_FALLTHROUGH_INTENDED;
+    case FeatureStatus::kDependentFeature:
+      ABSL_FALLTHROUGH_INTENDED;
+    case FeatureStatus::kDependentFeaturePending:
+      ABSL_FALLTHROUGH_INTENDED;
+    case FeatureStatus::kDisconnected:
+      ABSL_FALLTHROUGH_INTENDED;
+    case FeatureStatus::kConnecting:
+      StopMonitoring();
+      break;
+
+    case FeatureStatus::kConnected:
+      StartMonitoring();
+      break;
+  }
+}
+
+void EchePresenceManager::StartMonitoring() {
+  if (is_monitoring_) {
+    return;
+  }
+
+  const absl::optional<multidevice::RemoteDeviceRef> remote_device_ref =
+      multidevice_setup_client_->GetHostStatus().second;
+  const absl::optional<multidevice::RemoteDeviceRef> local_device_ref =
+      device_sync_client_->GetLocalDeviceMetadata();
+  if (!remote_device_ref || !local_device_ref) {
+    return;
+  }
+
+  // Before we start monitoring we don't know when the device was last seen, so
+  // set it to something far out of the expected time window to start. This will
+  // be properly set once we see the first advertisement, and if one is not seen
+  // before the first check, will return that the devices are out of proximity.
+  device_last_seen_time_ = base::TimeTicks::UnixEpoch();
+  is_monitoring_ = true;
+
+  if (timer_.IsRunning()) {
+    timer_.Reset();
+  } else {
+    timer_.Start(FROM_HERE, kTimerInterval,
+                 base::BindRepeating(&EchePresenceManager::OnTimerExpired,
+                                     weak_ptr_factory_.GetWeakPtr()));
+  }
+
+  presence_monitor_client_->StartMonitoring(remote_device_ref.value(),
+                                            local_device_ref.value());
+}
+
+void EchePresenceManager::StopMonitoring() {
+  if (!is_monitoring_) {
+    return;
+  }
+
+  timer_.Stop();
+  presence_monitor_client_->StopMonitoring();
+  is_monitoring_ = false;
+}
+
+void EchePresenceManager::OnTimerExpired() {
+  if ((base::TimeTicks::Now() - device_last_seen_time_) > kMaximumLastSeenAge) {
+    PA_LOG(INFO) << "Proximity has not been maintained; stopping monitoring";
+    StopMonitoring();
+  } else {
+    PA_LOG(INFO) << "Proximity has been maintained; sending ping";
+    proto::ProximityPing ping;
+    proto::ExoMessage message;
+    *message.mutable_proximity_ping() = std::move(ping);
+    eche_connector_->SendMessage(message.SerializeAsString());
+  }
+}
+
+void EchePresenceManager::OnDeviceSeen() {
+  // It is the responsibility of the scanner to ensure outdated advertisements
+  // are not forwarded through, so we will treat all received advertisements as
+  // valid.
+  device_last_seen_time_ = base::TimeTicks::Now();
+}
+
+}  // namespace eche_app
+}  // namespace chromeos
diff --git a/chromeos/components/eche_app_ui/eche_presence_manager.h b/chromeos/components/eche_app_ui/eche_presence_manager.h
new file mode 100644
index 0000000..4935e88da
--- /dev/null
+++ b/chromeos/components/eche_app_ui/eche_presence_manager.h
@@ -0,0 +1,83 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMEOS_COMPONENTS_ECHE_APP_UI_ECHE_PRESENCE_MANAGER_H_
+#define CHROMEOS_COMPONENTS_ECHE_APP_UI_ECHE_PRESENCE_MANAGER_H_
+
+#include <memory>
+
+#include "base/memory/weak_ptr.h"
+#include "base/timer/timer.h"
+#include "chromeos/components/eche_app_ui/eche_feature_status_provider.h"
+#include "chromeos/components/eche_app_ui/feature_status_provider.h"
+
+namespace chromeos {
+
+namespace device_sync {
+
+class DeviceSyncClient;
+
+}  // namespace device_sync
+
+namespace multidevice_setup {
+
+class MultiDeviceSetupClient;
+
+}  // namespace multidevice_setup
+
+namespace secure_channel {
+
+class PresenceMonitorClient;
+
+}  // namespace secure_channel
+
+namespace eche_app {
+
+class EcheConnector;
+
+// Control presence monitoring and the sending of keepalives.
+class EchePresenceManager : public FeatureStatusProvider::Observer {
+ public:
+  EchePresenceManager(
+      EcheFeatureStatusProvider* eche_feature_status_provider,
+      device_sync::DeviceSyncClient* device_sync_client,
+      multidevice_setup::MultiDeviceSetupClient* multidevice_setup_client,
+      std::unique_ptr<secure_channel::PresenceMonitorClient>
+          presence_monitor_client,
+      EcheConnector* eche_connector);
+  ~EchePresenceManager() override;
+
+  EchePresenceManager(const EchePresenceManager&) = delete;
+  EchePresenceManager& operator=(const EchePresenceManager&) = delete;
+
+ private:
+  // FeatureStatusProvider::Observer:
+  void OnFeatureStatusChanged() override;
+
+  void OnReady();
+  void OnDeviceSeen();
+
+  void UpdateMonitoringStatus();
+  void StartMonitoring();
+  void StopMonitoring();
+  void OnTimerExpired();
+
+  EcheFeatureStatusProvider* eche_feature_status_provider_;
+  device_sync::DeviceSyncClient* device_sync_client_;
+  multidevice_setup::MultiDeviceSetupClient* multidevice_setup_client_;
+  std::unique_ptr<secure_channel::PresenceMonitorClient>
+      presence_monitor_client_;
+  EcheConnector* eche_connector_;
+  base::RepeatingTimer timer_;
+
+  bool is_monitoring_ = false;
+  base::TimeTicks device_last_seen_time_;
+
+  base::WeakPtrFactory<EchePresenceManager> weak_ptr_factory_{this};
+};
+
+}  // namespace eche_app
+}  // namespace chromeos
+
+#endif  // CHROMEOS_COMPONENTS_ECHE_APP_UI_ECHE_PRESENCE_MANAGER_H_
diff --git a/chromeos/components/multidevice/remote_device_ref.h b/chromeos/components/multidevice/remote_device_ref.h
index d943418..65047f36 100644
--- a/chromeos/components/multidevice/remote_device_ref.h
+++ b/chromeos/components/multidevice/remote_device_ref.h
@@ -23,6 +23,7 @@
 }  // namespace multidevice_setup
 
 namespace secure_channel {
+class PresenceMonitorClientImpl;
 class SecureChannelClientImpl;
 }  // namespace secure_channel
 
@@ -92,6 +93,7 @@
  private:
   friend class multidevice_setup::MultiDeviceSetupImpl;
   friend class secure_channel::SecureChannelClientImpl;
+  friend class secure_channel::PresenceMonitorClientImpl;
   friend class RemoteDeviceCache;
   friend class RemoteDeviceRefBuilder;
   friend class RemoteDeviceRefTest;
diff --git a/chromeos/services/bluetooth_config/OWNERS b/chromeos/services/bluetooth_config/OWNERS
index 5189b388..33cd4c86 100644
--- a/chromeos/services/bluetooth_config/OWNERS
+++ b/chromeos/services/bluetooth_config/OWNERS
@@ -1 +1,2 @@
-khorimoto@chromium.org
+gordonseto@google.com
+khorimoto@chromium.org
\ No newline at end of file
diff --git a/chromeos/services/libassistant/conversation_controller.cc b/chromeos/services/libassistant/conversation_controller.cc
index d24f753..8bb637a 100644
--- a/chromeos/services/libassistant/conversation_controller.cc
+++ b/chromeos/services/libassistant/conversation_controller.cc
@@ -11,9 +11,11 @@
 #include "base/thread_annotations.h"
 #include "base/threading/sequenced_task_runner_handle.h"
 #include "chromeos/assistant/internal/internal_util.h"
+#include "chromeos/assistant/internal/proto/shared/proto/v2/internal_options.pb.h"
 #include "chromeos/services/assistant/public/cpp/features.h"
 #include "chromeos/services/libassistant/grpc/assistant_client.h"
 #include "chromeos/services/libassistant/public/mojom/conversation_controller.mojom.h"
+#include "chromeos/services/libassistant/util.h"
 #include "chromeos/strings/grit/chromeos_strings.h"
 #include "libassistant/shared/internal_api/assistant_manager_delegate.h"
 #include "libassistant/shared/internal_api/assistant_manager_internal.h"
@@ -257,15 +259,13 @@
 void ConversationController::OnAssistantClientRunning(
     AssistantClient* assistant_client) {
   // Only when Libassistant is running we can start sending queries.
-  assistant_manager_ = assistant_client->assistant_manager();
-  assistant_manager_internal_ = assistant_client->assistant_manager_internal();
+  assistant_client_ = assistant_client;
   requests_are_allowed_ = true;
 }
 
 void ConversationController::OnDestroyingAssistantClient(
     AssistantClient* assistant_client) {
-  assistant_manager_ = nullptr;
-  assistant_manager_internal_ = nullptr;
+  assistant_client_ = nullptr;
 }
 
 void ConversationController::SendTextQuery(const std::string& query,
@@ -275,28 +275,27 @@
 
   DCHECK(requests_are_allowed_)
       << "Should not receive requests before Libassistant is running";
-  if (!assistant_manager_internal_)
+  if (!assistant_client_)
     return;
 
   MaybeStopPreviousInteraction();
 
   // Configs |VoicelessOptions|.
-  assistant_client::VoicelessOptions options;
-  options.is_user_initiated = true;
+  ::assistant::api::VoicelessOptions options;
+  options.set_is_user_initiated(true);
   if (!allow_tts) {
-    options.modality =
-        assistant_client::VoicelessOptions::Modality::TYPING_MODALITY;
+    options.set_modality(::assistant::api::VoicelessOptions::TYPING_MODALITY);
   }
   // Remember the interaction metadata, and pass the generated conversation id
   // to LibAssistant.
-  options.conversation_turn_id =
-      assistant_manager_delegate_->AddPendingTextInteraction(query, source);
+  options.set_conversation_turn_id(
+      assistant_manager_delegate_->AddPendingTextInteraction(query, source));
 
   // Builds text interaction.
-  std::string interaction = assistant::CreateTextQueryInteraction(query);
+  auto interaction = CreateTextQueryInteraction(query);
 
-  assistant_manager_internal_->SendVoicelessInteraction(
-      interaction, /*description=*/"text_query", options, [](auto) {});
+  assistant_client_->SendVoicelessInteraction(
+      interaction, /*description=*/"text_query", options, base::DoNothing());
 }
 
 void ConversationController::StartVoiceInteraction() {
@@ -304,21 +303,21 @@
 
   DCHECK(requests_are_allowed_)
       << "Should not receive requests before Libassistant is running";
-  if (!assistant_manager_) {
+  if (!assistant_client_) {
     VLOG(1) << "Starting voice interaction without assistant manager.";
     return;
   }
 
   MaybeStopPreviousInteraction();
 
-  assistant_manager_->StartAssistantInteraction();
+  assistant_client_->StartVoiceInteraction();
 }
 
 void ConversationController::StartEditReminderInteraction(
     const std::string& client_id) {
   DCHECK(requests_are_allowed_)
       << "Should not receive requests before Libassistant is running";
-  if (!assistant_manager_internal_)
+  if (!assistant_client_)
     return;
 
   // Cancels any ongoing StopInteraction posted by StopActiveInteraction()
@@ -328,9 +327,12 @@
   // See b/182948180.
   MaybeStopPreviousInteraction();
 
-  SendVoicelessInteraction(assistant::CreateEditReminderInteraction(client_id),
-                           /*description=*/std::string(),
-                           /*is_user_initiated=*/true);
+  ::assistant::api::VoicelessOptions options;
+  options.set_is_user_initiated(true);
+
+  assistant_client_->SendVoicelessInteraction(
+      CreateEditReminderInteraction(client_id),
+      /*description=*/std::string(), options, base::DoNothing());
 }
 
 void ConversationController::StartScreenContextInteraction(
@@ -338,7 +340,7 @@
     const std::vector<uint8_t>& screenshot) {
   DCHECK(requests_are_allowed_)
       << "Should not receive requests before Libassistant is running";
-  if (!assistant_manager_internal_)
+  if (!assistant_client_)
     return;
 
   MaybeStopPreviousInteraction();
@@ -364,11 +366,11 @@
   context_protos.emplace_back(
       chromeos::assistant::CreateContextProto(screenshot,
                                               /*is_first_query=*/true));
-  assistant_manager_internal_->SendScreenContextRequest(context_protos);
+  assistant_client_->SendScreenContextRequest(context_protos);
 }
 
 void ConversationController::StopActiveInteraction(bool cancel_conversation) {
-  if (!assistant_manager_internal_) {
+  if (!assistant_client_) {
     VLOG(1) << "Stopping interaction without assistant manager.";
     return;
   }
@@ -378,12 +380,11 @@
   // stability as Libassistant might misbehave when it's forcefully stopped.
   auto stop_callback = [](base::WeakPtr<ConversationController> weak_this,
                           bool cancel_conversation) {
-    if (!weak_this || !weak_this->assistant_manager_internal_) {
+    if (!weak_this || !weak_this->assistant_client_) {
       return;
     }
     VLOG(1) << "Stopping Assistant interaction.";
-    weak_this->assistant_manager_internal_->StopAssistantInteractionInternal(
-        cancel_conversation);
+    weak_this->assistant_client_->StopAssistantInteraction(cancel_conversation);
   };
 
   stop_interaction_closure_ =
@@ -400,55 +401,59 @@
     int32_t action_index) {
   DCHECK(requests_are_allowed_)
       << "Should not receive requests before Libassistant is running";
-  if (!assistant_manager_internal_)
+  if (!assistant_client_)
     return;
 
-  const std::string request_interaction =
-      assistant::SerializeNotificationRequestInteraction(
-          notification.server_id, notification.consistency_token,
-          notification.opaque_token, action_index);
+  auto request_interaction = CreateNotificationRequestInteraction(
+      notification.server_id, notification.consistency_token,
+      notification.opaque_token, action_index);
 
-  SendVoicelessInteraction(request_interaction,
-                           /*description=*/"RequestNotification",
-                           /*is_user_initiated=*/true);
+  ::assistant::api::VoicelessOptions options;
+  options.set_is_user_initiated(true);
+
+  assistant_client_->SendVoicelessInteraction(
+      request_interaction,
+      /*description=*/"RequestNotification", options, base::DoNothing());
 }
 
 void ConversationController::DismissNotification(
     AssistantNotification notification) {
   DCHECK(requests_are_allowed_)
       << "Should not receive requests before Libassistant is running";
-  if (!assistant_manager_internal_)
+  if (!assistant_client_)
     return;
 
-  const std::string dismissed_interaction =
-      assistant::SerializeNotificationDismissedInteraction(
-          notification.server_id, notification.consistency_token,
-          notification.opaque_token, {notification.grouping_key});
+  auto dismissed_interaction = CreateNotificationDismissedInteraction(
+      notification.server_id, notification.consistency_token,
+      notification.opaque_token, {notification.grouping_key});
 
-  assistant_client::VoicelessOptions options;
-  options.obfuscated_gaia_id = notification.obfuscated_gaia_id;
+  ::assistant::api::VoicelessOptions options;
+  options.set_obfuscated_gaia_id(notification.obfuscated_gaia_id);
 
-  assistant_manager_internal_->SendVoicelessInteraction(
+  assistant_client_->SendVoicelessInteraction(
       dismissed_interaction, /*description=*/"DismissNotification", options,
-      [](auto) {});
+      base::DoNothing());
 }
 
 void ConversationController::SendAssistantFeedback(
     const AssistantFeedback& feedback) {
   DCHECK(requests_are_allowed_)
       << "Should not receive requests before Libassistant is running";
-  if (!assistant_manager_internal_)
+  if (!assistant_client_)
     return;
 
   std::string raw_image_data(feedback.screenshot_png.begin(),
                              feedback.screenshot_png.end());
-  const std::string interaction = assistant::CreateSendFeedbackInteraction(
-      feedback.assistant_debug_info_allowed, feedback.description,
-      raw_image_data);
+  auto interaction =
+      CreateSendFeedbackInteraction(feedback.assistant_debug_info_allowed,
+                                    feedback.description, raw_image_data);
 
-  SendVoicelessInteraction(interaction,
-                           /*description=*/"send feedback with details",
-                           /*is_user_initiated=*/false);
+  ::assistant::api::VoicelessOptions options;
+  options.set_is_user_initiated(false);
+
+  assistant_client_->SendVoicelessInteraction(
+      interaction, /*description=*/"send feedback with details", options,
+      base::DoNothing());
 }
 
 void ConversationController::AddRemoteObserver(
@@ -516,15 +521,14 @@
   // Note that we will always set |provider_found| to true since the preceding
   // OnVerifyAndroidApp() should already confirm that the requested provider is
   // available on the device.
-  std::string interaction_proto =
-      assistant::CreateOpenProviderResponseInteraction(
-          interaction.interaction_id, /*provider_found=*/true);
-  assistant_client::VoicelessOptions options;
-  options.obfuscated_gaia_id = interaction.user_id;
+  auto interaction_proto = CreateOpenProviderResponseInteraction(
+      interaction.interaction_id, /*provider_found=*/true);
+  ::assistant::api::VoicelessOptions options;
+  options.set_obfuscated_gaia_id(interaction.user_id);
 
-  assistant_manager_internal_->SendVoicelessInteraction(
+  assistant_client_->SendVoicelessInteraction(
       interaction_proto, /*description=*/"open_provider_response", options,
-      [](auto) {});
+      base::DoNothing());
 }
 
 // Called from Libassistant thread.
@@ -582,16 +586,5 @@
   stop_interaction_closure_->callback().Run();
 }
 
-void ConversationController::SendVoicelessInteraction(
-    const std::string& interaction,
-    const std::string& description,
-    bool is_user_initiated) {
-  assistant_client::VoicelessOptions voiceless_options;
-  voiceless_options.is_user_initiated = is_user_initiated;
-
-  assistant_manager_internal_->SendVoicelessInteraction(
-      interaction, description, voiceless_options, [](auto) {});
-}
-
 }  // namespace libassistant
 }  // namespace chromeos
diff --git a/chromeos/services/libassistant/conversation_controller.h b/chromeos/services/libassistant/conversation_controller.h
index 51f5ac0..80843e52 100644
--- a/chromeos/services/libassistant/conversation_controller.h
+++ b/chromeos/services/libassistant/conversation_controller.h
@@ -21,11 +21,6 @@
 #include "mojo/public/cpp/bindings/receiver.h"
 #include "mojo/public/cpp/bindings/remote_set.h"
 
-namespace assistant_client {
-class AssistantManager;
-class AssistantManagerInternal;
-}  // namespace assistant_client
-
 namespace chromeos {
 namespace assistant {
 namespace action {
@@ -35,6 +30,8 @@
 
 namespace libassistant {
 
+class AssistantClient;
+
 class COMPONENT_EXPORT(LIBASSISTANT_SERVICE) ConversationController
     : public mojom::ConversationController,
       public AssistantClientObserver,
@@ -117,19 +114,17 @@
 
   void MaybeStopPreviousInteraction();
 
-  void SendVoicelessInteraction(const std::string& interaction,
-                                const std::string& description,
-                                bool is_user_initiated);
-
   mojo::Receiver<mojom::ConversationController> receiver_;
   mojo::RemoteSet<mojom::ConversationObserver> observers_;
   mojo::RemoteSet<mojom::AuthenticationStateObserver>
       authentication_state_observers_;
   mojo::Remote<mojom::NotificationDelegate> notification_delegate_;
 
-  assistant_client::AssistantManager* assistant_manager_ = nullptr;
-  assistant_client::AssistantManagerInternal* assistant_manager_internal_ =
-      nullptr;
+  // Owned by ServiceController.
+  // Set in `OnAssistantClientCreated()` and unset in
+  // `OnDestroyingAssistantClient()`.
+  AssistantClient* assistant_client_ = nullptr;
+
   // False until libassistant is running for the first time.
   // Any request that comes in before that is an error and will be DCHECK'ed.
   bool requests_are_allowed_ = false;
diff --git a/chromeos/services/libassistant/conversation_controller_unittest.cc b/chromeos/services/libassistant/conversation_controller_unittest.cc
index f8349d527..6fcc419 100644
--- a/chromeos/services/libassistant/conversation_controller_unittest.cc
+++ b/chromeos/services/libassistant/conversation_controller_unittest.cc
@@ -5,8 +5,6 @@
 #include "chromeos/services/libassistant/conversation_controller.h"
 #include "base/run_loop.h"
 #include "base/test/task_environment.h"
-#include "chromeos/assistant/internal/test_support/fake_assistant_manager.h"
-#include "chromeos/assistant/internal/test_support/fake_assistant_manager_internal.h"
 #include "chromeos/assistant/test_support/expect_utils.h"
 #include "chromeos/services/libassistant/test_support/fake_assistant_client.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -17,79 +15,54 @@
 
 namespace {
 
-class AssistantManagerInternalMock
-    : public assistant::FakeAssistantManagerInternal {
+class AssistantClientMock : public FakeAssistantClient {
  public:
-  AssistantManagerInternalMock() = default;
-  AssistantManagerInternalMock(const AssistantManagerInternalMock&) = delete;
-  AssistantManagerInternalMock& operator=(const AssistantManagerInternalMock&) =
-      delete;
-  ~AssistantManagerInternalMock() override = default;
+  AssistantClientMock(
+      std::unique_ptr<assistant::FakeAssistantManager> assistant_manager,
+      assistant::FakeAssistantManagerInternal* assistant_manager_internal)
+      : FakeAssistantClient(std::move(assistant_manager),
+                            assistant_manager_internal) {}
+  ~AssistantClientMock() override = default;
 
-  // assistant::FakeAssistantManagerInternal implementation:
-  MOCK_METHOD(void,
-              StopAssistantInteractionInternal,
-              (bool cancel_conversation));
+  // AssistantClient:
+  MOCK_METHOD(void, StartVoiceInteraction, ());
+  MOCK_METHOD(void, StopAssistantInteraction, (bool cancel_conversation));
   MOCK_METHOD(void,
               SendVoicelessInteraction,
-              (const std::string&,
-               const std::string&,
-               const assistant_client::VoicelessOptions& options,
-               assistant_client::SuccessCallbackInternal on_done));
-};
-
-class AssistantManagerMock : public assistant::FakeAssistantManager {
- public:
-  AssistantManagerMock() = default;
-  AssistantManagerMock(const AssistantManagerMock&) = delete;
-  AssistantManagerMock& operator=(const AssistantManagerMock&) = delete;
-  ~AssistantManagerMock() override = default;
-
-  // assistant::FakeAssistantManager implementation:
-  MOCK_METHOD(void, StartAssistantInteraction, ());
+              (const ::assistant::api::Interaction& interaction,
+               const std::string& description,
+               const ::assistant::api::VoicelessOptions& options,
+               base::OnceCallback<void(bool)> on_done));
 };
 
 }  // namespace
 
 class ConversationControllerTest : public ::testing::Test {
  public:
-  ConversationControllerTest() {
-    auto fake_assistant_manager = std::make_unique<AssistantManagerMock>();
-    assistant_client_ = std::make_unique<FakeAssistantClient>(
-        std::move(fake_assistant_manager), &assistant_manager_internal_);
-  }
+  ConversationControllerTest() = default;
   ConversationControllerTest(const ConversationControllerTest&) = delete;
   ConversationControllerTest& operator=(const ConversationControllerTest&) =
       delete;
   ~ConversationControllerTest() override = default;
 
   void StartLibassistant() {
-    controller_.OnAssistantClientRunning(assistant_client_.get());
+    controller_.OnAssistantClientRunning(&assistant_client_);
   }
 
   ConversationController& controller() { return controller_; }
 
-  AssistantManagerMock& assistant_manager_mock() {
-    return *(reinterpret_cast<AssistantManagerMock*>(
-        assistant_client_->assistant_manager()));
-  }
-
-  AssistantManagerInternalMock& assistant_manager_internal_mock() {
-    return *(reinterpret_cast<AssistantManagerInternalMock*>(
-        assistant_client_->assistant_manager_internal()));
-  }
+  AssistantClientMock& assistant_client_mock() { return assistant_client_; }
 
  private:
   base::test::SingleThreadTaskEnvironment environment_;
   ConversationController controller_;
-  AssistantManagerInternalMock assistant_manager_internal_;
-  std::unique_ptr<FakeAssistantClient> assistant_client_;
+  AssistantClientMock assistant_client_{nullptr, nullptr};
 };
 
 TEST_F(ConversationControllerTest, ShouldStartVoiceInteraction) {
   StartLibassistant();
 
-  EXPECT_CALL(assistant_manager_mock(), StartAssistantInteraction());
+  EXPECT_CALL(assistant_client_mock(), StartVoiceInteraction());
 
   controller().StartVoiceInteraction();
 }
@@ -97,32 +70,26 @@
 TEST_F(ConversationControllerTest, ShouldStopInteractionAfterDelay) {
   StartLibassistant();
 
-  EXPECT_CALL(assistant_manager_internal_mock(),
-              StopAssistantInteractionInternal)
-      .Times(0);
+  EXPECT_CALL(assistant_client_mock(), StopAssistantInteraction).Times(0);
 
   controller().StopActiveInteraction(true);
-  testing::Mock::VerifyAndClearExpectations(&assistant_manager_internal_mock());
+  testing::Mock::VerifyAndClearExpectations(&assistant_client_mock());
 
-  WAIT_FOR_CALL(assistant_manager_internal_mock(),
-                StopAssistantInteractionInternal);
+  WAIT_FOR_CALL(assistant_client_mock(), StopAssistantInteraction);
 }
 
 TEST_F(ConversationControllerTest,
        ShouldStopInteractionImmediatelyBeforeNewVoiceInteraction) {
   StartLibassistant();
 
-  EXPECT_CALL(assistant_manager_internal_mock(),
-              StopAssistantInteractionInternal)
-      .Times(0);
+  EXPECT_CALL(assistant_client_mock(), StopAssistantInteraction).Times(0);
 
   controller().StopActiveInteraction(true);
-  testing::Mock::VerifyAndClearExpectations(&assistant_manager_internal_mock());
+  testing::Mock::VerifyAndClearExpectations(&assistant_client_mock());
 
-  ::testing::Expectation stop = EXPECT_CALL(assistant_manager_internal_mock(),
-                                            StopAssistantInteractionInternal)
-                                    .Times(1);
-  EXPECT_CALL(assistant_manager_mock(), StartAssistantInteraction)
+  ::testing::Expectation stop =
+      EXPECT_CALL(assistant_client_mock(), StopAssistantInteraction).Times(1);
+  EXPECT_CALL(assistant_client_mock(), StartVoiceInteraction)
       .Times(1)
       .After(stop);
   controller().StartVoiceInteraction();
@@ -132,17 +99,14 @@
        ShouldStopInteractionImmediatelyBeforeNewEditReminderInteraction) {
   StartLibassistant();
 
-  EXPECT_CALL(assistant_manager_internal_mock(),
-              StopAssistantInteractionInternal)
-      .Times(0);
+  EXPECT_CALL(assistant_client_mock(), StopAssistantInteraction).Times(0);
 
   controller().StopActiveInteraction(true);
-  testing::Mock::VerifyAndClearExpectations(&assistant_manager_internal_mock());
+  testing::Mock::VerifyAndClearExpectations(&assistant_client_mock());
 
-  ::testing::Expectation stop = EXPECT_CALL(assistant_manager_internal_mock(),
-                                            StopAssistantInteractionInternal)
-                                    .Times(1);
-  EXPECT_CALL(assistant_manager_internal_mock(), SendVoicelessInteraction)
+  ::testing::Expectation stop =
+      EXPECT_CALL(assistant_client_mock(), StopAssistantInteraction).Times(1);
+  EXPECT_CALL(assistant_client_mock(), SendVoicelessInteraction)
       .Times(1)
       .After(stop);
   controller().StartEditReminderInteraction("client-id");
diff --git a/chromeos/services/libassistant/grpc/assistant_client.h b/chromeos/services/libassistant/grpc/assistant_client.h
index acfc75b..6b8fa6f6 100644
--- a/chromeos/services/libassistant/grpc/assistant_client.h
+++ b/chromeos/services/libassistant/grpc/assistant_client.h
@@ -105,11 +105,6 @@
 
   virtual void AddExperimentIds(const std::vector<std::string>& exp_ids) = 0;
 
-  virtual void SendVoicelessInteraction(
-      const ::assistant::api::Interaction& interaction,
-      const std::string& description,
-      const ::assistant::api::VoicelessOptions& options,
-      base::OnceCallback<void(bool)> on_done) = 0;
 
   // Speaker Id Enrollment methods.
   virtual void AddSpeakerIdEnrollmentEventObserver(
@@ -142,8 +137,17 @@
       GrpcServicesObserver<OnDeviceStateEventRequest>* observer) = 0;
 
   // Conversation methods.
+  virtual void SendVoicelessInteraction(
+      const ::assistant::api::Interaction& interaction,
+      const std::string& description,
+      const ::assistant::api::VoicelessOptions& options,
+      base::OnceCallback<void(bool)> on_done) = 0;
   virtual void RegisterActionModule(
       assistant_client::ActionModule* action_module) = 0;
+  virtual void SendScreenContextRequest(
+      const std::vector<std::string>& context_protos) = 0;
+  virtual void StartVoiceInteraction() = 0;
+  virtual void StopAssistantInteraction(bool cancel_conversation) = 0;
 
   // Settings-related functionality during bootup:
   virtual void SetAuthenticationInfo(const AuthTokens& tokens) = 0;
diff --git a/chromeos/services/libassistant/grpc/assistant_client_v1.cc b/chromeos/services/libassistant/grpc/assistant_client_v1.cc
index 7f7090ad..c82ef0d 100644
--- a/chromeos/services/libassistant/grpc/assistant_client_v1.cc
+++ b/chromeos/services/libassistant/grpc/assistant_client_v1.cc
@@ -266,7 +266,7 @@
 }
 
 AssistantClientV1::~AssistantClientV1() {
-  // Some listeners (e.g. MediaManagerListener) require that they outlive 
+  // Some listeners (e.g. MediaManagerListener) require that they outlive
   // `assistant_manager_`. Reset `assistant_manager_` in the parent class first
   // before any listener in this class gets destructed.
   ResetAssistantManager();
@@ -295,20 +295,6 @@
   assistant_manager_internal()->AddExtraExperimentIds(exp_ids);
 }
 
-void AssistantClientV1::SendVoicelessInteraction(
-    const ::assistant::api::Interaction& interaction,
-    const std::string& description,
-    const ::assistant::api::VoicelessOptions& options,
-    base::OnceCallback<void(bool)> on_done) {
-  assistant_client::VoicelessOptions voiceless_options;
-  PopulateVoicelessOptionsFromProto(options, &voiceless_options);
-  assistant_manager_internal()->SendVoicelessInteraction(
-      interaction.SerializeAsString(), description, voiceless_options,
-      [callback = std::move(on_done)](bool result) mutable {
-        std::move(callback).Run(result);
-      });
-}
-
 void AssistantClientV1::AddSpeakerIdEnrollmentEventObserver(
     GrpcServicesObserver<OnSpeakerIdEnrollmentEventRequest>* observer) {
   speaker_event_observer_list_.AddObserver(observer);
@@ -389,11 +375,39 @@
   device_state_event_observer_list_.AddObserver(observer);
 }
 
+void AssistantClientV1::SendVoicelessInteraction(
+    const ::assistant::api::Interaction& interaction,
+    const std::string& description,
+    const ::assistant::api::VoicelessOptions& options,
+    base::OnceCallback<void(bool)> on_done) {
+  assistant_client::VoicelessOptions voiceless_options;
+  PopulateVoicelessOptionsFromProto(options, &voiceless_options);
+  assistant_manager_internal()->SendVoicelessInteraction(
+      interaction.SerializeAsString(), description, voiceless_options,
+      [callback = std::move(on_done)](bool result) mutable {
+        std::move(callback).Run(result);
+      });
+}
+
 void AssistantClientV1::RegisterActionModule(
     assistant_client::ActionModule* action_module) {
   assistant_manager_internal()->RegisterActionModule(action_module);
 }
 
+void AssistantClientV1::SendScreenContextRequest(
+    const std::vector<std::string>& context_protos) {
+  assistant_manager_internal()->SendScreenContextRequest(context_protos);
+}
+
+void AssistantClientV1::StartVoiceInteraction() {
+  assistant_manager()->StartAssistantInteraction();
+}
+
+void AssistantClientV1::StopAssistantInteraction(bool cancel_conversation) {
+  assistant_manager_internal()->StopAssistantInteractionInternal(
+      cancel_conversation);
+}
+
 void AssistantClientV1::SetAuthenticationInfo(const AuthTokens& tokens) {
   assistant_manager()->SetAuthTokens(tokens);
 }
diff --git a/chromeos/services/libassistant/grpc/assistant_client_v1.h b/chromeos/services/libassistant/grpc/assistant_client_v1.h
index fcf587c4..7e1963d7 100644
--- a/chromeos/services/libassistant/grpc/assistant_client_v1.h
+++ b/chromeos/services/libassistant/grpc/assistant_client_v1.h
@@ -33,11 +33,6 @@
       assistant_client::ChromeOSApiDelegate* delegate) override;
   bool StartGrpcServices() override;
   void AddExperimentIds(const std::vector<std::string>& exp_ids) override;
-  void SendVoicelessInteraction(
-      const ::assistant::api::Interaction& interaction,
-      const std::string& description,
-      const ::assistant::api::VoicelessOptions& options,
-      base::OnceCallback<void(bool)> on_done) override;
   void AddSpeakerIdEnrollmentEventObserver(
       GrpcServicesObserver<OnSpeakerIdEnrollmentEventRequest>* observer)
       override;
@@ -60,8 +55,17 @@
   void SetExternalPlaybackState(const MediaStatus& status_proto) override;
   void AddDeviceStateEventObserver(
       GrpcServicesObserver<OnDeviceStateEventRequest>* observer) override;
+  void SendVoicelessInteraction(
+      const ::assistant::api::Interaction& interaction,
+      const std::string& description,
+      const ::assistant::api::VoicelessOptions& options,
+      base::OnceCallback<void(bool)> on_done) override;
   void RegisterActionModule(
       assistant_client::ActionModule* action_module) override;
+  void SendScreenContextRequest(
+      const std::vector<std::string>& context_protos) override;
+  void StartVoiceInteraction() override;
+  void StopAssistantInteraction(bool cancel_conversation) override;
   void SetAuthenticationInfo(const AuthTokens& tokens) override;
   void SetInternalOptions(const std::string& locale,
                           bool spoken_feedback_enabled) override;
diff --git a/chromeos/services/libassistant/test_support/fake_assistant_client.cc b/chromeos/services/libassistant/test_support/fake_assistant_client.cc
index b27a4b5f..3c7e773c 100644
--- a/chromeos/services/libassistant/test_support/fake_assistant_client.cc
+++ b/chromeos/services/libassistant/test_support/fake_assistant_client.cc
@@ -34,12 +34,6 @@
 void FakeAssistantClient::AddExperimentIds(
     const std::vector<std::string>& exp_ids) {}
 
-void FakeAssistantClient::SendVoicelessInteraction(
-    const ::assistant::api::Interaction& interaction,
-    const std::string& description,
-    const ::assistant::api::VoicelessOptions& options,
-    base::OnceCallback<void(bool)> on_done) {}
-
 void FakeAssistantClient::AddSpeakerIdEnrollmentEventObserver(
     GrpcServicesObserver<OnSpeakerIdEnrollmentEventRequest>* observer) {}
 
@@ -74,9 +68,22 @@
 void FakeAssistantClient::AddDeviceStateEventObserver(
     GrpcServicesObserver<OnDeviceStateEventRequest>* observer) {}
 
+void FakeAssistantClient::SendVoicelessInteraction(
+    const ::assistant::api::Interaction& interaction,
+    const std::string& description,
+    const ::assistant::api::VoicelessOptions& options,
+    base::OnceCallback<void(bool)> on_done) {}
+
 void FakeAssistantClient::RegisterActionModule(
     assistant_client::ActionModule* action_module) {}
 
+void FakeAssistantClient::SendScreenContextRequest(
+    const std::vector<std::string>& context_protos) {}
+
+void FakeAssistantClient::StartVoiceInteraction() {}
+
+void FakeAssistantClient::StopAssistantInteraction(bool cancel_conversation) {}
+
 void FakeAssistantClient::SetInternalOptions(const std::string& locale,
                                              bool spoken_feedback_enabled) {}
 
diff --git a/chromeos/services/libassistant/test_support/fake_assistant_client.h b/chromeos/services/libassistant/test_support/fake_assistant_client.h
index b683d32..77452816 100644
--- a/chromeos/services/libassistant/test_support/fake_assistant_client.h
+++ b/chromeos/services/libassistant/test_support/fake_assistant_client.h
@@ -34,11 +34,6 @@
       assistant_client::ChromeOSApiDelegate* delegate) override;
   bool StartGrpcServices() override;
   void AddExperimentIds(const std::vector<std::string>& exp_ids) override;
-  void SendVoicelessInteraction(
-      const ::assistant::api::Interaction& interaction,
-      const std::string& description,
-      const ::assistant::api::VoicelessOptions& options,
-      base::OnceCallback<void(bool)> on_done) override;
   void AddSpeakerIdEnrollmentEventObserver(
       GrpcServicesObserver<OnSpeakerIdEnrollmentEventRequest>* observer)
       override;
@@ -61,8 +56,17 @@
   void SetExternalPlaybackState(const MediaStatus& status_proto) override;
   void AddDeviceStateEventObserver(
       GrpcServicesObserver<OnDeviceStateEventRequest>* observer) override;
+  void SendVoicelessInteraction(
+      const ::assistant::api::Interaction& interaction,
+      const std::string& description,
+      const ::assistant::api::VoicelessOptions& options,
+      base::OnceCallback<void(bool)> on_done) override;
   void RegisterActionModule(
       assistant_client::ActionModule* action_module) override;
+  void SendScreenContextRequest(
+      const std::vector<std::string>& context_protos) override;
+  void StartVoiceInteraction() override;
+  void StopAssistantInteraction(bool cancel_conversation) override;
   void SetInternalOptions(const std::string& locale,
                           bool spoken_feedback_enabled) override;
   void SetAuthenticationInfo(const AuthTokens& tokens) override;
diff --git a/chromeos/services/libassistant/util.cc b/chromeos/services/libassistant/util.cc
index 1f840ef7..fcdfb3f 100644
--- a/chromeos/services/libassistant/util.cc
+++ b/chromeos/services/libassistant/util.cc
@@ -349,5 +349,76 @@
       .Proto();
 }
 
+Interaction CreateNotificationRequestInteraction(
+    const std::string& notification_id,
+    const std::string& consistent_token,
+    const std::string& opaque_token,
+    const int action_index) {
+  auto request_param = assistant::CreateNotificationRequestParam(
+      notification_id, consistent_token, opaque_token, action_index);
+
+  return V1InteractionBuilder()
+      .SetClientInputName(assistant::kClientInputRequestNotification)
+      .AddClientInputParams(assistant::kNotificationRequestParamsKey,
+                            request_param)
+      .Proto();
+}
+
+Interaction CreateNotificationDismissedInteraction(
+    const std::string& notification_id,
+    const std::string& consistent_token,
+    const std::string& opaque_token,
+    const std::vector<std::string>& grouping_keys) {
+  auto dismiss_param = assistant::CreateNotificationDismissedParam(
+      notification_id, consistent_token, opaque_token, grouping_keys);
+
+  return V1InteractionBuilder()
+      .SetClientInputName(assistant::kClientInputDismissNotification)
+      .AddClientInputParams(assistant::kNotificationDismissParamsKey,
+                            dismiss_param)
+      .Proto();
+}
+
+Interaction CreateEditReminderInteraction(const std::string& reminder_id) {
+  auto intent_input = assistant::CreateEditReminderParam(reminder_id);
+
+  return V1InteractionBuilder()
+      .SetClientInputName(assistant::kClientInputEditReminder)
+      .AddClientInputParams(assistant::kEditReminderParamsKey, intent_input)
+      .Proto();
+}
+
+Interaction CreateOpenProviderResponseInteraction(const int interaction_id,
+                                                  const bool provider_found) {
+  return V1InteractionBuilder()
+      .SetInResponseTo(interaction_id)
+      .SetStatusCodeFromEntityFound(provider_found)
+      .Proto();
+}
+
+Interaction CreateSendFeedbackInteraction(
+    bool assistant_debug_info_allowed,
+    const std::string& feedback_description,
+    const std::string& screenshot_png) {
+  auto feedback_arg = assistant::CreateFeedbackParam(
+      assistant_debug_info_allowed, feedback_description, screenshot_png);
+
+  return V1InteractionBuilder()
+      .SetClientInputName(assistant::kClientInputText)
+      .AddClientInputParams(
+          assistant::kTextParamsKey,
+          assistant::CreateTextParam(assistant::kFeedbackText))
+      .AddClientInputParams(assistant::kFeedbackParamsKey, feedback_arg)
+      .Proto();
+}
+
+Interaction CreateTextQueryInteraction(const std::string& query) {
+  return V1InteractionBuilder()
+      .SetClientInputName(assistant::kClientInputText)
+      .AddClientInputParams(assistant::kTextParamsKey,
+                            assistant::CreateTextParam(query))
+      .Proto();
+}
+
 }  // namespace libassistant
 }  // namespace chromeos
diff --git a/chromeos/services/libassistant/util.h b/chromeos/services/libassistant/util.h
index ce795004..61ab09c 100644
--- a/chromeos/services/libassistant/util.h
+++ b/chromeos/services/libassistant/util.h
@@ -45,6 +45,35 @@
     int interaction_id,
     const std::vector<chromeos::assistant::DeviceSetting>& device_settings);
 
+// `action_index` is the index of the actions and buttons.
+::assistant::api::Interaction CreateNotificationRequestInteraction(
+    const std::string& notification_id,
+    const std::string& consistent_token,
+    const std::string& opaque_token,
+    const int action_index);
+
+// `grouping_keys` are the keys to group multiple notifications together.
+::assistant::api::Interaction CreateNotificationDismissedInteraction(
+    const std::string& notification_id,
+    const std::string& consistent_token,
+    const std::string& opaque_token,
+    const std::vector<std::string>& grouping_keys);
+
+::assistant::api::Interaction CreateEditReminderInteraction(
+    const std::string& reminder_id);
+
+::assistant::api::Interaction CreateOpenProviderResponseInteraction(
+    const int interaction_id,
+    const bool provider_found);
+
+::assistant::api::Interaction CreateSendFeedbackInteraction(
+    bool assistant_debug_info_allowed,
+    const std::string& feedback_description,
+    const std::string& screenshot_png = std::string());
+
+::assistant::api::Interaction CreateTextQueryInteraction(
+    const std::string& query);
+
 }  // namespace libassistant
 }  // namespace chromeos
 
diff --git a/chromeos/services/secure_channel/BUILD.gn b/chromeos/services/secure_channel/BUILD.gn
index 85cf6e8b8..ca8faab 100644
--- a/chromeos/services/secure_channel/BUILD.gn
+++ b/chromeos/services/secure_channel/BUILD.gn
@@ -134,6 +134,10 @@
     "pending_connection_request_delegate.h",
     "pending_nearby_initiator_connection_request.cc",
     "pending_nearby_initiator_connection_request.h",
+    "presence_monitor_delegate.cc",
+    "presence_monitor_delegate.h",
+    "presence_monitor_impl.cc",
+    "presence_monitor_impl.h",
     "raw_eid_generator.h",
     "raw_eid_generator_impl.cc",
     "raw_eid_generator_impl.h",
diff --git a/chromeos/services/secure_channel/presence_monitor_delegate.cc b/chromeos/services/secure_channel/presence_monitor_delegate.cc
new file mode 100644
index 0000000..fe802ea
--- /dev/null
+++ b/chromeos/services/secure_channel/presence_monitor_delegate.cc
@@ -0,0 +1,74 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromeos/services/secure_channel/presence_monitor_delegate.h"
+
+#include "chromeos/components/multidevice/logging/logging.h"
+#include "chromeos/components/multidevice/remote_device_cache.h"
+#include "chromeos/services/secure_channel/ble_scanner_impl.h"
+#include "chromeos/services/secure_channel/ble_synchronizer.h"
+#include "chromeos/services/secure_channel/bluetooth_helper_impl.h"
+#include "chromeos/services/secure_channel/connection_role.h"
+#include "chromeos/services/secure_channel/public/cpp/shared/connection_priority.h"
+#include "device/bluetooth/bluetooth_adapter.h"
+
+namespace chromeos {
+namespace secure_channel {
+
+PresenceMonitorDelegate::PresenceMonitorDelegate(
+    scoped_refptr<device::BluetoothAdapter> bluetooth_adapter,
+    PresenceMonitor::DeviceSeenCallback device_seen_callback)
+    : bluetooth_adapter_(bluetooth_adapter),
+      device_seen_callback_(std::move(device_seen_callback)),
+      remote_device_cache_(multidevice::RemoteDeviceCache::Factory::Create()),
+      bluetooth_helper_(
+          BluetoothHelperImpl::Factory::Create(remote_device_cache_.get())),
+      ble_synchronizer_(BleSynchronizer::Factory::Create(bluetooth_adapter_)),
+      ble_scanner_(BleScannerImpl::Factory::Create(bluetooth_helper_.get(),
+                                                   ble_synchronizer_.get(),
+                                                   bluetooth_adapter_)) {
+  ble_scanner_->AddObserver(this);
+}
+
+PresenceMonitorDelegate::~PresenceMonitorDelegate() {
+  ble_scanner_->RemoveObserver(this);
+}
+
+void PresenceMonitorDelegate::StartMonitoring(
+    const multidevice::RemoteDevice& remote_device,
+    const multidevice::RemoteDevice& local_device) {
+  PA_LOG(INFO) << "Starting monitoring proximity";
+  remote_device_id_ = remote_device.GetDeviceId();
+  local_device_id_ = local_device.GetDeviceId();
+
+  remote_device_cache_->SetRemoteDevices({remote_device, local_device});
+
+  ble_scanner_->AddScanRequest(ConnectionAttemptDetails(
+      remote_device_id_, local_device_id_,
+      ConnectionMedium::kBluetoothLowEnergy, ConnectionRole::kListenerRole));
+}
+
+void PresenceMonitorDelegate::StopMonitoring() {
+  PA_LOG(INFO) << "Stopping monitoring proximity";
+  ble_scanner_->RemoveScanRequest(ConnectionAttemptDetails(
+      remote_device_id_, local_device_id_,
+      ConnectionMedium::kBluetoothLowEnergy, ConnectionRole::kListenerRole));
+  remote_device_id_.clear();
+  local_device_id_.clear();
+}
+
+void PresenceMonitorDelegate::OnReceivedAdvertisement(
+    multidevice::RemoteDeviceRef remote_device_ref,
+    device::BluetoothDevice* bluetooth_device,
+    ConnectionMedium connection_medium,
+    ConnectionRole connection_role,
+    const std::vector<uint8_t>& eid) {
+  // It is the responsibility of the scanner to ensure outdated advertisements
+  // are not forwarded through, so we will treat all received advertisements as
+  // valid.
+  device_seen_callback_.Run();
+}
+
+}  // namespace secure_channel
+}  // namespace chromeos
diff --git a/chromeos/services/secure_channel/presence_monitor_delegate.h b/chromeos/services/secure_channel/presence_monitor_delegate.h
new file mode 100644
index 0000000..5389ee4
--- /dev/null
+++ b/chromeos/services/secure_channel/presence_monitor_delegate.h
@@ -0,0 +1,65 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMEOS_SERVICES_SECURE_CHANNEL_PRESENCE_MONITOR_DELEGATE_H_
+#define CHROMEOS_SERVICES_SECURE_CHANNEL_PRESENCE_MONITOR_DELEGATE_H_
+
+#include "chromeos/services/secure_channel/ble_scanner.h"
+#include "chromeos/services/secure_channel/public/cpp/shared/presence_monitor.h"
+
+namespace device {
+class BluetoothAdapter;
+class BluetoothDevice;
+}  // namespace device
+
+namespace chromeos {
+
+namespace multidevice {
+class RemoteDeviceCache;
+class RemoteDeviceRef;
+}  // namespace multidevice
+
+namespace secure_channel {
+
+class BleSynchronizerBase;
+class BluetoothHelper;
+
+// Monitors device proximity while a secure channel is active.
+class PresenceMonitorDelegate : public BleScanner::Observer {
+ public:
+  PresenceMonitorDelegate(
+      scoped_refptr<device::BluetoothAdapter> bluetooth_adapter,
+      PresenceMonitor::DeviceSeenCallback device_seen_callback);
+  ~PresenceMonitorDelegate() override;
+
+  PresenceMonitorDelegate(const PresenceMonitorDelegate&) = delete;
+  PresenceMonitorDelegate& operator=(const PresenceMonitorDelegate&) = delete;
+
+  void StartMonitoring(const multidevice::RemoteDevice& remote_device,
+                       const multidevice::RemoteDevice& local_device);
+  void StopMonitoring();
+
+ private:
+  // BleScanner::Observer:
+  void OnReceivedAdvertisement(multidevice::RemoteDeviceRef remote_device,
+                               device::BluetoothDevice* bluetooth_device,
+                               ConnectionMedium connection_medium,
+                               ConnectionRole connection_role,
+                               const std::vector<uint8_t>& eid) override;
+
+  scoped_refptr<device::BluetoothAdapter> bluetooth_adapter_;
+  PresenceMonitor::DeviceSeenCallback device_seen_callback_;
+  std::unique_ptr<multidevice::RemoteDeviceCache> remote_device_cache_;
+  std::unique_ptr<BluetoothHelper> bluetooth_helper_;
+  std::unique_ptr<BleSynchronizerBase> ble_synchronizer_;
+  std::unique_ptr<BleScanner> ble_scanner_;
+
+  std::string remote_device_id_;
+  std::string local_device_id_;
+};
+
+}  // namespace secure_channel
+
+}  // namespace chromeos
+#endif  // CHROMEOS_SERVICES_SECURE_CHANNEL_PRESENCE_MONITOR_DELEGATE_H_
diff --git a/chromeos/services/secure_channel/presence_monitor_impl.cc b/chromeos/services/secure_channel/presence_monitor_impl.cc
new file mode 100644
index 0000000..c61d9fe
--- /dev/null
+++ b/chromeos/services/secure_channel/presence_monitor_impl.cc
@@ -0,0 +1,52 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromeos/services/secure_channel/presence_monitor_impl.h"
+
+#include "chromeos/components/multidevice/logging/logging.h"
+#include "device/bluetooth/bluetooth_adapter.h"
+#include "device/bluetooth/bluetooth_adapter_factory.h"
+
+namespace chromeos {
+namespace secure_channel {
+
+PresenceMonitorImpl::PresenceMonitorImpl() = default;
+
+PresenceMonitorImpl::~PresenceMonitorImpl() = default;
+
+void PresenceMonitorImpl::SetPresenceMonitorCallbacks(
+    ReadyCallback ready_callback,
+    DeviceSeenCallback device_seen_callback) {
+  if (!presence_monitor_delegate_) {
+    device::BluetoothAdapterFactory::Get()->GetAdapter(base::BindOnce(
+        &PresenceMonitorImpl::OnAdapterReceived, weak_ptr_factory_.GetWeakPtr(),
+        std::move(ready_callback), std::move(device_seen_callback)));
+  }
+}
+
+void PresenceMonitorImpl::StartMonitoring(
+    const multidevice::RemoteDevice& remote_device,
+    const multidevice::RemoteDevice& local_device) {
+  if (presence_monitor_delegate_) {
+    presence_monitor_delegate_->StartMonitoring(remote_device, local_device);
+  }
+}
+
+void PresenceMonitorImpl::StopMonitoring() {
+  if (presence_monitor_delegate_) {
+    presence_monitor_delegate_->StopMonitoring();
+  }
+}
+
+void PresenceMonitorImpl::OnAdapterReceived(
+    PresenceMonitor::ReadyCallback ready_callback,
+    PresenceMonitor::DeviceSeenCallback device_seen_callback,
+    scoped_refptr<device::BluetoothAdapter> bluetooth_adapter) {
+  presence_monitor_delegate_ = std::make_unique<PresenceMonitorDelegate>(
+      std::move(bluetooth_adapter), std::move(device_seen_callback));
+  ready_callback.Run();
+}
+
+}  // namespace secure_channel
+}  // namespace chromeos
diff --git a/chromeos/services/secure_channel/presence_monitor_impl.h b/chromeos/services/secure_channel/presence_monitor_impl.h
new file mode 100644
index 0000000..ee64db2
--- /dev/null
+++ b/chromeos/services/secure_channel/presence_monitor_impl.h
@@ -0,0 +1,57 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMEOS_SERVICES_SECURE_CHANNEL_PRESENCE_MONITOR_IMPL_H_
+#define CHROMEOS_SERVICES_SECURE_CHANNEL_PRESENCE_MONITOR_IMPL_H_
+
+#include "chromeos/services/secure_channel/presence_monitor_delegate.h"
+#include "chromeos/services/secure_channel/public/cpp/shared/presence_monitor.h"
+
+namespace device {
+
+class BluetoothAdapter;
+
+}  // namespace device
+
+namespace chromeos {
+
+namespace multidevice {
+
+struct RemoteDevice;
+
+}  // namespace multidevice
+
+namespace secure_channel {
+
+// Monitors device proximity while a secure channel is active.
+class PresenceMonitorImpl : public PresenceMonitor {
+ public:
+  PresenceMonitorImpl();
+  ~PresenceMonitorImpl() override;
+
+  PresenceMonitorImpl(const PresenceMonitorImpl&) = delete;
+  PresenceMonitorImpl& operator=(const PresenceMonitorImpl&) = delete;
+
+  // PresenceMonitor:
+  void SetPresenceMonitorCallbacks(
+      PresenceMonitor::ReadyCallback ready_callback,
+      PresenceMonitor::DeviceSeenCallback device_seen_callback) override;
+  void StartMonitoring(const multidevice::RemoteDevice& remote_device,
+                       const multidevice::RemoteDevice& local_device) override;
+  void StopMonitoring() override;
+
+ private:
+  void OnAdapterReceived(
+      PresenceMonitor::ReadyCallback ready_callback,
+      PresenceMonitor::DeviceSeenCallback device_seen_callback,
+      scoped_refptr<device::BluetoothAdapter> bluetooth_adapter);
+
+  std::unique_ptr<PresenceMonitorDelegate> presence_monitor_delegate_;
+
+  base::WeakPtrFactory<PresenceMonitorImpl> weak_ptr_factory_{this};
+};
+
+}  // namespace secure_channel
+}  // namespace chromeos
+#endif  // CHROMEOS_SERVICES_SECURE_CHANNEL_PRESENCE_MONITOR_IMPL_H_
diff --git a/chromeos/services/secure_channel/public/cpp/client/BUILD.gn b/chromeos/services/secure_channel/public/cpp/client/BUILD.gn
index ecd31c9b..ec1c7c8 100644
--- a/chromeos/services/secure_channel/public/cpp/client/BUILD.gn
+++ b/chromeos/services/secure_channel/public/cpp/client/BUILD.gn
@@ -18,6 +18,9 @@
     "connection_manager_impl.h",
     "nearby_connector.cc",
     "nearby_connector.h",
+    "presence_monitor_client.h",
+    "presence_monitor_client_impl.cc",
+    "presence_monitor_client_impl.h",
     "secure_channel_client.h",
     "secure_channel_client_impl.cc",
     "secure_channel_client_impl.h",
diff --git a/chromeos/services/secure_channel/public/cpp/client/presence_monitor_client.h b/chromeos/services/secure_channel/public/cpp/client/presence_monitor_client.h
new file mode 100644
index 0000000..85af752
--- /dev/null
+++ b/chromeos/services/secure_channel/public/cpp/client/presence_monitor_client.h
@@ -0,0 +1,46 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMEOS_SERVICES_SECURE_CHANNEL_PUBLIC_CPP_CLIENT_PRESENCE_MONITOR_CLIENT_H_
+#define CHROMEOS_SERVICES_SECURE_CHANNEL_PUBLIC_CPP_CLIENT_PRESENCE_MONITOR_CLIENT_H_
+
+#include "chromeos/components/multidevice/remote_device_ref.h"
+#include "chromeos/services/secure_channel/public/cpp/shared/presence_monitor.h"
+
+namespace chromeos {
+namespace secure_channel {
+
+// Provides clients access to the PresenceMonitor API.
+class PresenceMonitorClient {
+ public:
+  virtual ~PresenceMonitorClient() = default;
+
+  virtual void SetPresenceMonitorCallbacks(
+      PresenceMonitor::ReadyCallback ready_callback,
+      PresenceMonitor::DeviceSeenCallback device_seen_callback) = 0;
+  virtual void StartMonitoring(
+      const multidevice::RemoteDeviceRef& remote_device_ref,
+      const multidevice::RemoteDeviceRef& local_device_ref) = 0;
+  virtual void StopMonitoring() = 0;
+
+ protected:
+  PresenceMonitorClient() = default;
+
+ private:
+  PresenceMonitorClient(const PresenceMonitorClient&) = delete;
+  PresenceMonitorClient& operator=(const PresenceMonitorClient&) = delete;
+};
+
+}  // namespace secure_channel
+}  // namespace chromeos
+
+// TODO(https://crbug.com/1164001): remove after the //chrome/browser/chromeos
+// source migration is finished.
+namespace ash {
+namespace secure_channel {
+using ::chromeos::secure_channel::PresenceMonitorClient;
+}
+}  // namespace ash
+
+#endif  // CHROMEOS_SERVICES_SECURE_CHANNEL_PUBLIC_CPP_CLIENT_PRESENCE_MONITOR_CLIENT_H_
diff --git a/chromeos/services/secure_channel/public/cpp/client/presence_monitor_client_impl.cc b/chromeos/services/secure_channel/public/cpp/client/presence_monitor_client_impl.cc
new file mode 100644
index 0000000..2751a558
--- /dev/null
+++ b/chromeos/services/secure_channel/public/cpp/client/presence_monitor_client_impl.cc
@@ -0,0 +1,62 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromeos/services/secure_channel/public/cpp/client/presence_monitor_client_impl.h"
+
+#include "base/bind.h"
+#include "base/memory/ptr_util.h"
+
+namespace chromeos {
+namespace secure_channel {
+
+// static
+PresenceMonitorClientImpl::Factory*
+    PresenceMonitorClientImpl::Factory::test_factory_ = nullptr;
+
+// static
+std::unique_ptr<PresenceMonitorClient>
+PresenceMonitorClientImpl::Factory::Create(
+    std::unique_ptr<PresenceMonitor> presence_monitor) {
+  if (test_factory_) {
+    return test_factory_->CreateInstance(std::move(presence_monitor));
+  }
+
+  return base::WrapUnique(
+      new PresenceMonitorClientImpl(std::move(presence_monitor)));
+}
+
+// static
+void PresenceMonitorClientImpl::Factory::SetFactoryForTesting(
+    Factory* test_factory) {
+  test_factory_ = test_factory;
+}
+
+PresenceMonitorClientImpl::Factory::~Factory() = default;
+
+PresenceMonitorClientImpl::PresenceMonitorClientImpl(
+    std::unique_ptr<PresenceMonitor> presence_monitor)
+    : presence_monitor_(std::move(presence_monitor)) {}
+
+PresenceMonitorClientImpl::~PresenceMonitorClientImpl() = default;
+
+void PresenceMonitorClientImpl::SetPresenceMonitorCallbacks(
+    PresenceMonitor::ReadyCallback ready_callback,
+    PresenceMonitor::DeviceSeenCallback device_seen_callback) {
+  presence_monitor_->SetPresenceMonitorCallbacks(
+      std::move(ready_callback), std::move(device_seen_callback));
+}
+
+void PresenceMonitorClientImpl::StartMonitoring(
+    const multidevice::RemoteDeviceRef& remote_device,
+    const multidevice::RemoteDeviceRef& local_device) {
+  presence_monitor_->StartMonitoring(remote_device.GetRemoteDevice(),
+                                     local_device.GetRemoteDevice());
+}
+
+void PresenceMonitorClientImpl::StopMonitoring() {
+  presence_monitor_->StopMonitoring();
+}
+
+}  // namespace secure_channel
+}  // namespace chromeos
diff --git a/chromeos/services/secure_channel/public/cpp/client/presence_monitor_client_impl.h b/chromeos/services/secure_channel/public/cpp/client/presence_monitor_client_impl.h
new file mode 100644
index 0000000..ecb0deec
--- /dev/null
+++ b/chromeos/services/secure_channel/public/cpp/client/presence_monitor_client_impl.h
@@ -0,0 +1,62 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMEOS_SERVICES_SECURE_CHANNEL_PUBLIC_CPP_CLIENT_PRESENCE_MONITOR_CLIENT_IMPL_H_
+#define CHROMEOS_SERVICES_SECURE_CHANNEL_PUBLIC_CPP_CLIENT_PRESENCE_MONITOR_CLIENT_IMPL_H_
+
+#include <memory>
+
+#include "chromeos/services/secure_channel/public/cpp/client/presence_monitor_client.h"
+#include "chromeos/services/secure_channel/public/cpp/shared/presence_monitor.h"
+
+namespace chromeos {
+
+namespace multidevice {
+
+class RemoteDeviceRef;
+
+}  // namespace multidevice
+
+namespace secure_channel {
+
+// Provides clients access to the PresenceMonitor API.
+class PresenceMonitorClientImpl : public PresenceMonitorClient {
+ public:
+  class Factory {
+   public:
+    static std::unique_ptr<PresenceMonitorClient> Create(
+        std::unique_ptr<PresenceMonitor> presence_monitor);
+    static void SetFactoryForTesting(Factory* test_factory);
+
+   protected:
+    virtual ~Factory();
+    virtual std::unique_ptr<PresenceMonitorClient> CreateInstance(
+        std::unique_ptr<PresenceMonitor> presence_monitor) = 0;
+
+   private:
+    static Factory* test_factory_;
+  };
+
+  ~PresenceMonitorClientImpl() override;
+
+ private:
+  explicit PresenceMonitorClientImpl(
+      std::unique_ptr<PresenceMonitor> presence_monitor);
+
+  // PresenceMonitorClient:
+  void SetPresenceMonitorCallbacks(
+      PresenceMonitor::ReadyCallback ready_callback,
+      PresenceMonitor::DeviceSeenCallback device_seen_callback) override;
+  void StartMonitoring(
+      const multidevice::RemoteDeviceRef& remote_device_ref,
+      const multidevice::RemoteDeviceRef& local_device_ref) override;
+  void StopMonitoring() override;
+
+  std::unique_ptr<PresenceMonitor> presence_monitor_;
+};
+
+}  // namespace secure_channel
+}  // namespace chromeos
+
+#endif  // CHROMEOS_SERVICES_SECURE_CHANNEL_PUBLIC_CPP_CLIENT_PRESENCE_MONITOR_CLIENT_IMPL_H_
diff --git a/chromeos/services/secure_channel/public/cpp/shared/BUILD.gn b/chromeos/services/secure_channel/public/cpp/shared/BUILD.gn
index 21aa4c4..d8b26ee 100644
--- a/chromeos/services/secure_channel/public/cpp/shared/BUILD.gn
+++ b/chromeos/services/secure_channel/public/cpp/shared/BUILD.gn
@@ -8,6 +8,7 @@
     "connection_medium.h",
     "connection_priority.cc",
     "connection_priority.h",
+    "presence_monitor.h",
   ]
 
   deps = [ "//base" ]
diff --git a/chromeos/services/secure_channel/public/cpp/shared/presence_monitor.h b/chromeos/services/secure_channel/public/cpp/shared/presence_monitor.h
new file mode 100644
index 0000000..63c80ab25
--- /dev/null
+++ b/chromeos/services/secure_channel/public/cpp/shared/presence_monitor.h
@@ -0,0 +1,43 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMEOS_SERVICES_SECURE_CHANNEL_PUBLIC_CPP_SHARED_PRESENCE_MONITOR_H_
+#define CHROMEOS_SERVICES_SECURE_CHANNEL_PUBLIC_CPP_SHARED_PRESENCE_MONITOR_H_
+
+#include "base/callback.h"
+
+namespace chromeos {
+
+namespace multidevice {
+struct RemoteDevice;
+}  // namespace multidevice
+
+namespace secure_channel {
+
+// Monitors device proximity while a secure channel is active.
+class PresenceMonitor {
+ public:
+  using ReadyCallback = base::RepeatingCallback<void()>;
+  using DeviceSeenCallback = base::RepeatingCallback<void()>;
+
+  virtual ~PresenceMonitor() = default;
+
+  PresenceMonitor(const PresenceMonitor&) = delete;
+  PresenceMonitor& operator=(const PresenceMonitor&) = delete;
+
+  virtual void SetPresenceMonitorCallbacks(
+      ReadyCallback ready_callback,
+      DeviceSeenCallback device_seen_callback) = 0;
+  virtual void StartMonitoring(
+      const multidevice::RemoteDevice& remote_device,
+      const multidevice::RemoteDevice& local_device) = 0;
+  virtual void StopMonitoring() = 0;
+
+ protected:
+  PresenceMonitor() = default;
+};
+
+}  // namespace secure_channel
+}  // namespace chromeos
+#endif  // CHROMEOS_SERVICES_SECURE_CHANNEL_PUBLIC_CPP_SHARED_PRESENCE_MONITOR_H_
diff --git a/chromeos/settings/cros_settings_names.cc b/chromeos/settings/cros_settings_names.cc
index c8cebd7..cfc6d7ed 100644
--- a/chromeos/settings/cros_settings_names.cc
+++ b/chromeos/settings/cros_settings_names.cc
@@ -254,6 +254,14 @@
 const char kSystemLogUploadEnabled[] =
     "cros.device_status.system_log_upload_enabled";
 
+// How frequently the networks health telemetry are collected.
+const char kReportDeviceNetworkTelemetryCollectionRateMs[] =
+    "cros.telemetry_reporting.report_network_telemetry_collection_rate_ms";
+
+// How frequently the networks data are checked for events.
+const char kReportDeviceNetworkTelemetryEventCheckingRateMs[] =
+    "cros.telemetry_reporting.report_network_telemetry_event_checking_rate_ms";
+
 // This policy should not appear in the protobuf ever but is used internally to
 // signal that we are running in a "safe-mode" for policy recovery.
 const char kPolicyMissingMitigationMode[] =
diff --git a/chromeos/settings/cros_settings_names.h b/chromeos/settings/cros_settings_names.h
index 3f91560..64ebf3a 100644
--- a/chromeos/settings/cros_settings_names.h
+++ b/chromeos/settings/cros_settings_names.h
@@ -132,6 +132,10 @@
 COMPONENT_EXPORT(CHROMEOS_SETTINGS) extern const char kReportDevicePrintJobs[];
 COMPONENT_EXPORT(CHROMEOS_SETTINGS)
 extern const char kReportDeviceLoginLogout[];
+COMPONENT_EXPORT(CHROMEOS_SETTINGS)
+extern const char kReportDeviceNetworkTelemetryCollectionRateMs[];
+COMPONENT_EXPORT(CHROMEOS_SETTINGS)
+extern const char kReportDeviceNetworkTelemetryEventCheckingRateMs[];
 
 COMPONENT_EXPORT(CHROMEOS_SETTINGS) extern const char kHeartbeatEnabled[];
 COMPONENT_EXPORT(CHROMEOS_SETTINGS) extern const char kHeartbeatFrequency[];
@@ -414,6 +418,8 @@
 using ::chromeos::kReportDeviceNetworkConfiguration;
 using ::chromeos::kReportDeviceNetworkInterfaces;
 using ::chromeos::kReportDeviceNetworkStatus;
+using ::chromeos::kReportDeviceNetworkTelemetryCollectionRateMs;
+using ::chromeos::kReportDeviceNetworkTelemetryEventCheckingRateMs;
 using ::chromeos::kReportDevicePowerStatus;
 using ::chromeos::kReportDevicePrintJobs;
 using ::chromeos::kReportDeviceSecurityStatus;
diff --git a/components/autofill/core/browser/autofill_client.cc b/components/autofill/core/browser/autofill_client.cc
index 8126fe6..2747e5f 100644
--- a/components/autofill/core/browser/autofill_client.cc
+++ b/components/autofill/core/browser/autofill_client.cc
@@ -87,6 +87,18 @@
   // ChromeAutofillClient (Chrome Desktop & Android) implements this.
 }
 
+void AutofillClient::ShowAutofillProgressDialog(
+    base::OnceClosure cancel_callback) {
+  // This is overridden by platform subclasses. Currently only
+  // ChromeAutofillClient (Chrome Desktop & Android) implements this.
+}
+
+void AutofillClient::CloseAutofillProgressDialog(
+    bool show_confirmation_before_closing) {
+  // This is overridden by platform subclasses. Currently only
+  // ChromeAutofillClient (Chrome Desktop & Android) implements this.
+}
+
 bool AutofillClient::IsAutofillAssistantShowing() {
   return false;
 }
diff --git a/components/autofill/core/browser/autofill_client.h b/components/autofill/core/browser/autofill_client.h
index dec2a6f..8fa66e20 100644
--- a/components/autofill/core/browser/autofill_client.h
+++ b/components/autofill/core/browser/autofill_client.h
@@ -584,6 +584,12 @@
   // error dialog with virtual card related messages.
   virtual void ShowVirtualCardErrorDialog(bool is_permanent_error);
 
+  // Show/dismiss the progress dialog which contains a throbber and a text
+  // message indicating that something is in progress.
+  virtual void ShowAutofillProgressDialog(base::OnceClosure cancel_callback);
+  virtual void CloseAutofillProgressDialog(
+      bool show_confirmation_before_closing);
+
   // Returns true if the Autofill Assistant UI is currently being shown.
   virtual bool IsAutofillAssistantShowing();
 
diff --git a/components/autofill/core/browser/payments/credit_card_access_manager.cc b/components/autofill/core/browser/payments/credit_card_access_manager.cc
index ab01dfb..255960f 100644
--- a/components/autofill/core/browser/payments/credit_card_access_manager.cc
+++ b/components/autofill/core/browser/payments/credit_card_access_manager.cc
@@ -914,7 +914,9 @@
 
 void CreditCardAccessManager::FetchVirtualCard() {
   is_authentication_in_progress_ = true;
-  // TODO(crbug.com/1243475): Show pending dialog when the request is ongoing.
+  client_->ShowAutofillProgressDialog(
+      base::BindOnce(&CreditCardAccessManager::OnVirtualCardUnmaskCancelled,
+                     weak_ptr_factory_.GetWeakPtr()));
 
   // Send a risk-based unmasking request to server to attempt to fetch the card.
   absl::optional<GURL> last_committed_url_origin =
@@ -953,9 +955,12 @@
     AutofillClient::PaymentsRpcResult result,
     payments::PaymentsClient::UnmaskResponseDetails& response_details) {
   virtual_card_unmask_response_details_ = response_details;
-  // TODO(crbug.com/1243475): Dismiss the pending dialog.
   if (result == AutofillClient::PaymentsRpcResult::kSuccess) {
     if (!response_details.real_pan.empty()) {
+      // Show confirmation on the progress dialog and then dismiss it.
+      client_->CloseAutofillProgressDialog(
+          /*show_confirmation_before_closing=*/true);
+
       // If the real pan is not empty, then complete card information has been
       // fetched from the server (this is ensured in Payments Client). Pass the
       // unmasked card to |accessor_| and end the session.
@@ -976,6 +981,9 @@
 
     // Otherwise further authentication is required to unmask the card.
     DCHECK(!response_details.context_token.empty());
+    // Close the progress dialog without showing the confirmation.
+    client_->CloseAutofillProgressDialog(
+        /*show_confirmation_before_closing=*/false);
     GetAuthenticationType(
         IsFidoAuthEnabled(response_details.fido_request_options.has_value()));
     return;
@@ -985,6 +993,9 @@
   // dialog. If RPC result is kVcnRetrievalPermanentFailure we show VCN
   // permanent error dialog, and for all other cases we show VCN temporary
   // error dialog.
+  // Close the progress dialog without showing the confirmation.
+  client_->CloseAutofillProgressDialog(
+      /*show_confirmation_before_closing=*/false);
   accessor_->OnCreditCardFetched(CreditCardFetchResult::kTransientError);
   client_->ShowVirtualCardErrorDialog(
       result ==
@@ -1024,7 +1035,13 @@
   OnDidGetAuthenticationType(selected_authentication_type);
 }
 
+void CreditCardAccessManager::OnVirtualCardUnmaskCancelled() {
+  // TODO(crbug.com/1243475): Add metrics for user cancellation.
+  Reset();
+}
+
 void CreditCardAccessManager::Reset() {
+  weak_ptr_factory_.InvalidateWeakPtrs();
   unmask_auth_flow_type_ = UnmaskAuthFlowType::kNone;
   is_authentication_in_progress_ = false;
   preflight_call_timestamp_ = absl::nullopt;
diff --git a/components/autofill/core/browser/payments/credit_card_access_manager.h b/components/autofill/core/browser/payments/credit_card_access_manager.h
index 3faeeac..179e0813 100644
--- a/components/autofill/core/browser/payments/credit_card_access_manager.h
+++ b/components/autofill/core/browser/payments/credit_card_access_manager.h
@@ -352,6 +352,10 @@
   void OnUserAcceptedAuthenticationSelectionDialog(
       const CardUnmaskChallengeOption& selected_challenge_option);
 
+  // Callback function invoked when the user has cancelled the virtual card
+  // unmasking.
+  void OnVirtualCardUnmaskCancelled();
+
   // Reset all the member variables of |this| and restore initial states.
   void Reset();
 
diff --git a/components/exo/shell_surface_base.cc b/components/exo/shell_surface_base.cc
index 07f004d..7faaecf 100644
--- a/components/exo/shell_surface_base.cc
+++ b/components/exo/shell_surface_base.cc
@@ -1396,7 +1396,7 @@
     if (root_surface()) {
       size = root_surface()->content_size();
       if (client_submits_surfaces_in_pixel_coordinates()) {
-        int dsf = std::ceil(host_window()->layer()->device_scale_factor());
+        float dsf = host_window()->layer()->device_scale_factor();
         size = gfx::ScaleToRoundedSize(size, 1.0f / dsf);
       }
     }
diff --git a/components/exo/surface_tree_host.cc b/components/exo/surface_tree_host.cc
index 430b7d9..e1dba1b9 100644
--- a/components/exo/surface_tree_host.cc
+++ b/components/exo/surface_tree_host.cc
@@ -328,8 +328,8 @@
   // synchronization.
   if (client_submits_surfaces_in_pixel_coordinates_) {
     gfx::Transform tr;
-    float s = ceil(host_window_->layer()->device_scale_factor());
-    tr.Scale(1.0f / s, 1.0f / s);
+    float scale = host_window_->layer()->device_scale_factor();
+    tr.Scale(1.0f / scale, 1.0f / scale);
     host_window_->SetTransform(tr);
   }
   const bool fills_bounds_opaquely =
diff --git a/components/leveldb_proto/internal/proto_leveldb_wrapper_metrics.h b/components/leveldb_proto/internal/proto_leveldb_wrapper_metrics.h
index 2f8ae4f..1b55906b 100644
--- a/components/leveldb_proto/internal/proto_leveldb_wrapper_metrics.h
+++ b/components/leveldb_proto/internal/proto_leveldb_wrapper_metrics.h
@@ -16,8 +16,9 @@
 // Static metrics recording helper functions for ProtoLevelDBWrapper.
 //
 // When adding database clients that require UMA metrics recording, ensure that
-// the client name is added as a suffix in histograms.xml for the appropriate
-// ProtoDB.* metrics.
+// the client name is added as a LevelDBClient variant in
+// //tools/metrics/histograms/metadata/leveldb_proto/histograms.xml for the
+// appropriate ProtoDB.* metrics.
 class ProtoLevelDBWrapperMetrics {
  public:
   static void RecordInit(const std::string& client,
@@ -37,4 +38,4 @@
 
 }  // namespace leveldb_proto
 
-#endif  // COMPONENTS_LEVELDB_PROTO_INTERNAL_PROTO_LEVELDB_WRAPPER_METRICS_H_
\ No newline at end of file
+#endif  // COMPONENTS_LEVELDB_PROTO_INTERNAL_PROTO_LEVELDB_WRAPPER_METRICS_H_
diff --git a/components/leveldb_proto/public/shared_proto_database_client_list.cc b/components/leveldb_proto/public/shared_proto_database_client_list.cc
index a125045..2dd09cd 100644
--- a/components/leveldb_proto/public/shared_proto_database_client_list.cc
+++ b/components/leveldb_proto/public/shared_proto_database_client_list.cc
@@ -19,8 +19,9 @@
 // static
 std::string SharedProtoDatabaseClientList::ProtoDbTypeToString(
     ProtoDbType db_type) {
-  // Please update the suffix LevelDBClients in histograms.xml to match the
-  // strings returned here.
+  // Please update the variant LevelDBClient in
+  // //tools/metrics/histograms/metadata/leveldb_proto/histograms.xml
+  // to match the strings returned here.
   switch (db_type) {
     case ProtoDbType::TEST_DATABASE0:
       return "TestDatabase0";
diff --git a/components/leveldb_proto/public/shared_proto_database_client_list.h b/components/leveldb_proto/public/shared_proto_database_client_list.h
index 0e3a88f..dc7f30a 100644
--- a/components/leveldb_proto/public/shared_proto_database_client_list.h
+++ b/components/leveldb_proto/public/shared_proto_database_client_list.h
@@ -18,8 +18,9 @@
 // The enum values are used to index into the shared database. Do not rearrange
 // or reuse the integer values. Add new database types at the end of the enum,
 // and update the string mapping in ProtoDbTypeToString(). Also update the
-// suffix LevelDBClients in histogram_suffixes_list.xml to match the strings for
-// the types.
+// variant LevelDBClient in
+// //tools/metrics/histograms/metadata/leveldb_proto/histograms.xml to match the
+// strings for the types.
 enum class ProtoDbType {
   TEST_DATABASE0 = 0,
   TEST_DATABASE1 = 1,
diff --git a/components/metrics/generate_expired_histograms_array.gni b/components/metrics/generate_expired_histograms_array.gni
index 39b384af..deb6e74b 100644
--- a/components/metrics/generate_expired_histograms_array.gni
+++ b/components/metrics/generate_expired_histograms_array.gni
@@ -89,6 +89,7 @@
       "//tools/metrics/histograms/metadata/installer/histograms.xml",
       "//tools/metrics/histograms/metadata/interstitial/histograms.xml",
       "//tools/metrics/histograms/metadata/ios/histograms.xml",
+      "//tools/metrics/histograms/metadata/leveldb_proto/histograms.xml",
       "//tools/metrics/histograms/metadata/local/histograms.xml",
       "//tools/metrics/histograms/metadata/login/histograms.xml",
       "//tools/metrics/histograms/metadata/media/histograms.xml",
diff --git a/components/nacl/loader/nonsfi/nonsfi_sandbox_unittest.cc b/components/nacl/loader/nonsfi/nonsfi_sandbox_unittest.cc
index 55241d4..e7aaa4e0 100644
--- a/components/nacl/loader/nonsfi/nonsfi_sandbox_unittest.cc
+++ b/components/nacl/loader/nonsfi/nonsfi_sandbox_unittest.cc
@@ -35,6 +35,7 @@
 #include "base/logging.h"
 #include "base/macros.h"
 #include "base/posix/eintr_wrapper.h"
+#include "base/rand_util.h"
 #include "base/system/sys_info.h"
 #include "base/threading/thread.h"
 #include "base/time/time.h"
@@ -598,6 +599,17 @@
   sandbox::Syscall::InvalidCall();
 }
 
+BPF_TEST_C(NaClNonSfiSandboxTest,
+           random,
+           nacl::nonsfi::NaClNonSfiBPFSandboxPolicy) {
+  // Ensure that UrandomFD is valid.
+  int urandom_fd = base::GetUrandomFD();
+  BPF_ASSERT_NE(-1, urandom_fd);
+
+  // The test should pass if the base::Rand*() don't crash.
+  base::RandDouble();
+}
+
 // The following tests check for several restrictions in tgkill(). A delegate is
 // needed to be able to call getpid() from inside the process that will be
 // sandboxed, but before the sandbox is installed.
diff --git a/components/nacl/loader/nonsfi/run_all_unittests.cc b/components/nacl/loader/nonsfi/run_all_unittests.cc
index 09ea459..7da3e83 100644
--- a/components/nacl/loader/nonsfi/run_all_unittests.cc
+++ b/components/nacl/loader/nonsfi/run_all_unittests.cc
@@ -4,6 +4,7 @@
 
 #include "base/at_exit.h"
 #include "base/bind.h"
+#include "base/rand_util.h"
 #include "base/test/launcher/unit_test_launcher.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -18,5 +19,10 @@
 int main(int argc, char** argv) {
   base::AtExitManager at_exit;
   testing::InitGoogleTest(&argc, argv);
+
+  // Force early initialisation of /dev/urandom FD as it can't be initialised
+  // from a sandbox.
+  base::GetUrandomFD();
+
   return base::LaunchUnitTests(argc, argv, base::BindOnce(&RunAllTestsImpl));
 }
diff --git a/components/policy/proto/chrome_device_policy.proto b/components/policy/proto/chrome_device_policy.proto
index 39f19b11..0607f58 100644
--- a/components/policy/proto/chrome_device_policy.proto
+++ b/components/policy/proto/chrome_device_policy.proto
@@ -161,6 +161,12 @@
   // This is a internal flag that will be used to control whether enable
   // granular device reporting is enabled
   optional bool enable_granular_reporting = 32 [default = true];
+
+  // Network telemetry policies.
+  optional int64 report_network_telemetry_collection_rate_ms = 33
+      [default = 600000];
+  optional int64 report_network_telemetry_event_checking_rate_ms = 34
+      [default = 60000];
 }
 
 message EphemeralUsersEnabledProto {
diff --git a/components/policy/resources/policy_templates.json b/components/policy/resources/policy_templates.json
index 03d4e3a..99b8b0aa 100644
--- a/components/policy/resources/policy_templates.json
+++ b/components/policy/resources/policy_templates.json
@@ -976,6 +976,8 @@
         'HeartbeatFrequency',
         'LogUploadEnabled',
         'DeviceMetricsReportingEnabled',
+        'ReportDeviceNetworkTelemetryCollectionRateMs',
+        'ReportDeviceNetworkTelemetryEventCheckingRateMs',
       ],
     },
     {
@@ -10439,6 +10441,48 @@
       'arc_support': 'This policy has no effect on the logging done by Android.',
     },
     {
+      'name': 'ReportDeviceNetworkTelemetryCollectionRateMs',
+      'owners': ['anasr@google.com', 'cros-reporting-team@google.com'],
+      'type': 'int',
+      'schema': { 'type': 'integer', 'minimum': 60000 },
+      'future_on': ['chrome_os'],
+      'supported_chrome_os_management': ['google_cloud'],
+      'device_only': True,
+      'features': {
+        'dynamic_refresh': True,
+      },
+      'example_value': 600000,
+      'default': 600000,
+      'id': 914,
+      'caption': '''Network telemetry collection rate in milliseconds.''',
+      'tags': ['admin-sharing'],
+      'desc': '''Rate at which network data is sampled and collected. The minimum allowed is 1 minute.
+
+      If not set, the default rate of 10 minutes applies.''',
+      'arc_support': 'This policy has no effect on the logging done by Android.',
+    },
+    {
+      'name': 'ReportDeviceNetworkTelemetryEventCheckingRateMs',
+      'owners': ['anasr@google.com', 'cros-reporting-team@google.com'],
+      'type': 'int',
+      'schema': { 'type': 'integer', 'minimum': 60000 },
+      'future_on': ['chrome_os'],
+      'supported_chrome_os_management': ['google_cloud'],
+      'device_only': True,
+      'features': {
+        'dynamic_refresh': True,
+      },
+      'example_value': 60000,
+      'default': 60000,
+      'id': 915,
+      'caption': '''Network events checking rate in milliseconds.''',
+      'tags': ['admin-sharing'],
+      'desc': '''Rate at which network data is polled and checked for events. The minimum allowed is 1 minute.
+
+      If not set, the default rate of 1 minute applies.''',
+      'arc_support': 'This policy has no effect on the logging done by Android.',
+    },
+    {
       'name': 'ReportDeviceUsers',
       'owners': ['stepco@chromium.org', 'cros-reporting-team@google.com', 'lbaraz@chromium.org'],
       'type': 'main',
@@ -28759,6 +28803,8 @@
     'ReportDeviceSystemInfo': 'device_reporting.report_system_info',
     'ReportDevicePrintJobs': 'device_reporting.report_print_jobs',
     'ReportDeviceLoginLogout': 'device_reporting.report_login_logout',
+    'ReportDeviceNetworkTelemetryCollectionRateMs': 'device_reporting.report_network_telemetry_collection_rate_ms',
+    'ReportDeviceNetworkTelemetryEventCheckingRateMs': 'device_reporting.report_network_telemetry_event_checking_rate_ms',
     'ReportUploadFrequency': 'device_reporting.device_status_frequency',
     'DeviceLoginScreenPowerManagement': 'login_screen_power_management.login_screen_power_management',
     'DeviceChromeVariations': 'device_chrome_variations_type.value',
@@ -29332,6 +29378,6 @@
   'placeholders': [],
   'deleted_policy_ids': [114, 115, 204, 205, 206, 412, 476, 544, 546, 562, 569, 578, 583, 585, 586, 587, 588, 589, 590, 591, 600, 668, 669, 872],
   'deleted_atomic_policy_group_ids': [19],
-  'highest_id_currently_used': 913,
+  'highest_id_currently_used': 915,
   'highest_atomic_group_id_currently_used': 41
 }
diff --git a/components/reporting/storage/storage.cc b/components/reporting/storage/storage.cc
index af3db57..edbbf00 100644
--- a/components/reporting/storage/storage.cc
+++ b/components/reporting/storage/storage.cc
@@ -82,6 +82,7 @@
 constexpr base::FilePath::CharType kEncryptionKeyFilePrefix[] =
     FILE_PATH_LITERAL("EncryptionKey.");
 const int32_t kEncryptionKeyMaxFileSize = 256;
+const uint64_t kQueueSize = 2 * 1024LL * 1024LL;
 
 // Failed upload retry delay: if an upload fails and there are no more incoming
 // events, collected events will not get uploaded for an indefinite time (see
@@ -97,33 +98,39 @@
                      QueueOptions(options)
                          .set_subdirectory(kSecurityQueueSubdir)
                          .set_file_prefix(kSecurityQueuePrefix)
-                         .set_upload_retry_delay(kFailedUploadRetryDelay)),
+                         .set_upload_retry_delay(kFailedUploadRetryDelay)
+                         .set_max_single_file_size(kQueueSize)),
       std::make_pair(IMMEDIATE,
                      QueueOptions(options)
                          .set_subdirectory(kImmediateQueueSubdir)
                          .set_file_prefix(kImmediateQueuePrefix)
-                         .set_upload_retry_delay(kFailedUploadRetryDelay)),
+                         .set_upload_retry_delay(kFailedUploadRetryDelay)
+                         .set_max_single_file_size(kQueueSize)),
       std::make_pair(FAST_BATCH,
                      QueueOptions(options)
                          .set_subdirectory(kFastBatchQueueSubdir)
                          .set_file_prefix(kFastBatchQueuePrefix)
-                         .set_upload_period(kFastBatchUploadPeriod)),
+                         .set_upload_period(kFastBatchUploadPeriod)
+                         .set_max_single_file_size(kQueueSize)),
       std::make_pair(SLOW_BATCH,
                      QueueOptions(options)
                          .set_subdirectory(kSlowBatchQueueSubdir)
                          .set_file_prefix(kSlowBatchQueuePrefix)
-                         .set_upload_period(kSlowBatchUploadPeriod)),
+                         .set_upload_period(kSlowBatchUploadPeriod)
+                         .set_max_single_file_size(kQueueSize)),
       std::make_pair(BACKGROUND_BATCH,
                      QueueOptions(options)
                          .set_subdirectory(kBackgroundQueueSubdir)
                          .set_file_prefix(kBackgroundQueuePrefix)
-                         .set_upload_period(kBackgroundQueueUploadPeriod)),
+                         .set_upload_period(kBackgroundQueueUploadPeriod)
+                         .set_max_single_file_size(kQueueSize)),
       std::make_pair(MANUAL_BATCH,
                      QueueOptions(options)
                          .set_subdirectory(kManualQueueSubdir)
                          .set_file_prefix(kManualQueuePrefix)
                          .set_upload_period(kManualUploadPeriod)
-                         .set_upload_retry_delay(kFailedUploadRetryDelay)),
+                         .set_upload_retry_delay(kFailedUploadRetryDelay)
+                         .set_max_single_file_size(kQueueSize)),
   };
 }
 
diff --git a/components/reporting/storage/storage_configuration.cc b/components/reporting/storage/storage_configuration.cc
index df4fc36b..8167ba4e 100644
--- a/components/reporting/storage/storage_configuration.cc
+++ b/components/reporting/storage/storage_configuration.cc
@@ -12,4 +12,7 @@
     default;
 StorageOptions::~StorageOptions() = default;
 
+QueueOptions::QueueOptions(const StorageOptions& storage_options)
+      : storage_options_(storage_options) {}
+QueueOptions::QueueOptions(const QueueOptions& options) = default;
 }  // namespace reporting
diff --git a/components/reporting/storage/storage_configuration.h b/components/reporting/storage/storage_configuration.h
index 2327b91..183598d0 100644
--- a/components/reporting/storage/storage_configuration.h
+++ b/components/reporting/storage/storage_configuration.h
@@ -48,10 +48,6 @@
     max_total_memory_size_ = max_total_memory_size;
     return *this;
   }
-  StorageOptions& set_single_file_size(uint64_t single_file_size) {
-    single_file_size_ = single_file_size;
-    return *this;
-  }
   const base::FilePath& directory() const { return directory_; }
   base::StringPiece signature_verification_public_key() const {
     return signature_verification_public_key_;
@@ -59,7 +55,6 @@
   size_t max_record_size() const { return max_record_size_; }
   uint64_t max_total_files_size() const { return max_total_files_size_; }
   uint64_t max_total_memory_size() const { return max_total_memory_size_; }
-  uint64_t single_file_size() const { return single_file_size_; }
 
  private:
   // Subdirectory of the location assigned for this Storage.
@@ -77,12 +72,6 @@
 
   // Maximum memory usage (reading buffers).
   uint64_t max_total_memory_size_ = 4 * 1024LL * 1024LL;  // 4 MiB
-
-  // Cut-off size of an individual file in all queues.
-  // When file exceeds this size, the new file is created
-  // for further records. Note that each file must have at least
-  // one record before it is closed, regardless of that record size.
-  uint64_t single_file_size_ = 1 * 1024LL * 1024LL;  // 1 MiB
 };
 
 // Single queue options class allowing to set parameters individually, e.g.:
@@ -93,9 +82,8 @@
 // storage_options must outlive QueueOptions.
 class QueueOptions {
  public:
-  explicit QueueOptions(const StorageOptions& storage_options)
-      : storage_options_(storage_options) {}
-  QueueOptions(const QueueOptions& options) = default;
+  explicit QueueOptions(const StorageOptions& storage_options);
+  QueueOptions(const QueueOptions& options);
   //   QueueOptions& operator=(const QueueOptions& options) = default;
   QueueOptions& set_subdirectory(
       const base::FilePath::StringType& subdirectory) {
@@ -114,6 +102,10 @@
     upload_retry_delay_ = upload_retry_delay;
     return *this;
   }
+  QueueOptions& set_max_single_file_size(uint64_t max_single_file_size) {
+    max_single_file_size_ = max_single_file_size;
+    return *this;
+  }
   const base::FilePath& directory() const { return directory_; }
   const base::FilePath::StringType& file_prefix() const { return file_prefix_; }
   size_t max_record_size() const { return storage_options_.max_record_size(); }
@@ -123,9 +115,7 @@
   size_t max_total_memory_size() const {
     return storage_options_.max_total_memory_size();
   }
-  uint64_t single_file_size() const {
-    return storage_options_.single_file_size();
-  }
+  uint64_t max_single_file_size() const { return max_single_file_size_; }
   base::TimeDelta upload_period() const { return upload_period_; }
   base::TimeDelta upload_retry_delay() const { return upload_retry_delay_; }
 
@@ -146,6 +136,11 @@
   // Retry delay for a failed upload. If 0, not retried at all
   // (should only be set to 0 in periodic queues).
   base::TimeDelta upload_retry_delay_;
+  // Cut-off file size of an individual queue
+  // When file exceeds this size, the new file is created
+  // for further records. Note that each file must have at least
+  // one record before it is closed, regardless of that record size.
+  uint64_t max_single_file_size_ = 1 * 1024LL * 1024LL; // 1 MiB
 };
 
 }  // namespace reporting
diff --git a/components/reporting/storage/storage_queue.cc b/components/reporting/storage/storage_queue.cc
index ad63bdb9..7b54c5b 100644
--- a/components/reporting/storage/storage_queue.cc
+++ b/components/reporting/storage/storage_queue.cc
@@ -452,7 +452,7 @@
   scoped_refptr<SingleFile> last_file = files_.rbegin()->second;
   if (last_file->size() > 0 &&  // Cannot have a file with no records.
       last_file->size() + size + sizeof(RecordHeader) + FRAME_SIZE >
-          options_.single_file_size()) {
+          options_.max_single_file_size()) {
     // The last file will become too large, asynchronously close it and add
     // new.
     last_file->Close();
diff --git a/components/reporting/storage/storage_queue_stress_test.cc b/components/reporting/storage/storage_queue_stress_test.cc
index f9115022..822b6709 100644
--- a/components/reporting/storage/storage_queue_stress_test.cc
+++ b/components/reporting/storage/storage_queue_stress_test.cc
@@ -130,8 +130,7 @@
         {CompressionModule::kCompressReportingFeature}, {});
 
     ASSERT_TRUE(location_.CreateUniqueTempDir());
-    options_.set_directory(base::FilePath(location_.GetPath()))
-        .set_single_file_size(GetParam());
+    options_.set_directory(base::FilePath(location_.GetPath()));
   }
 
   void TearDown() override {
@@ -180,7 +179,8 @@
   QueueOptions BuildStorageQueueOptionsImmediate() const {
     return QueueOptions(options_)
         .set_subdirectory(FILE_PATH_LITERAL("D1"))
-        .set_file_prefix(FILE_PATH_LITERAL("F0001"));
+        .set_file_prefix(FILE_PATH_LITERAL("F0001"))
+        .set_max_single_file_size(GetParam());
   }
 
   QueueOptions BuildStorageQueueOptionsPeriodic(
diff --git a/components/reporting/storage/storage_queue_unittest.cc b/components/reporting/storage/storage_queue_unittest.cc
index cfd1505..64f872ce 100644
--- a/components/reporting/storage/storage_queue_unittest.cc
+++ b/components/reporting/storage/storage_queue_unittest.cc
@@ -462,8 +462,7 @@
  protected:
   void SetUp() override {
     ASSERT_TRUE(location_.CreateUniqueTempDir());
-    options_.set_directory(base::FilePath(location_.GetPath()))
-        .set_single_file_size(GetParam());
+    options_.set_directory(base::FilePath(location_.GetPath()));
     EXPECT_CALL(set_mock_uploader_expectations_, Call(_, NotNull()))
         .WillRepeatedly(Invoke([](UploaderInterface::UploadReason reason,
                                   TestUploader* test_uploader) {
@@ -528,7 +527,8 @@
     return QueueOptions(options_)
         .set_subdirectory(FILE_PATH_LITERAL("D1"))
         .set_file_prefix(FILE_PATH_LITERAL("F0001"))
-        .set_upload_retry_delay(upload_retry_delay);
+        .set_upload_retry_delay(upload_retry_delay)
+        .set_max_single_file_size(GetParam());
   }
 
   QueueOptions BuildStorageQueueOptionsPeriodic(
@@ -860,7 +860,7 @@
   // Set uploader expectations. Previous data is all lost.
   // The expected results depend on the test configuration.
   test::TestCallbackAutoWaiter waiter;
-  switch (options.single_file_size()) {
+  switch (options.max_single_file_size()) {
     case 1:  // single record in file - deletion killed the first record
       EXPECT_CALL(set_mock_uploader_expectations_,
                   Call(Eq(UploaderInterface::PERIODIC), NotNull()))
diff --git a/components/reporting/storage/storage_unittest.cc b/components/reporting/storage/storage_unittest.cc
index b21f6d9..3c99bf9 100644
--- a/components/reporting/storage/storage_unittest.cc
+++ b/components/reporting/storage/storage_unittest.cc
@@ -785,8 +785,7 @@
 
   StorageOptions BuildTestStorageOptions() const {
     auto options = StorageOptions()
-                       .set_directory(base::FilePath(location_.GetPath()))
-                       .set_single_file_size(is_encryption_enabled());
+                       .set_directory(base::FilePath(location_.GetPath()));
     if (is_encryption_enabled()) {
       // Encryption enabled.
       options.set_signature_verification_public_key(std::string(
diff --git a/components/safe_browsing/core/browser/realtime/url_lookup_service_base.cc b/components/safe_browsing/core/browser/realtime/url_lookup_service_base.cc
index 8071aab46..9a8b0d9 100644
--- a/components/safe_browsing/core/browser/realtime/url_lookup_service_base.cc
+++ b/components/safe_browsing/core/browser/realtime/url_lookup_service_base.cc
@@ -532,6 +532,9 @@
       // the current mainframe URL, because the mainframe URL has to be
       // committed before subframe navigation starts.
       token = cache_manager_->GetPageLoadToken(last_committed_url);
+      RecordBooleanWithAndWithoutSuffix(
+          "SafeBrowsing.PageLoadToken.RealTimeCheckHasToken", GetMetricSuffix(),
+          token.has_token_value());
       // It's possible that the token is not found because the last committed
       // URL is not checked by real time URL check. Create a new page load token
       // in this case.
diff --git a/components/safe_browsing/core/browser/verdict_cache_manager.cc b/components/safe_browsing/core/browser/verdict_cache_manager.cc
index 085864f..7a4bce6d 100644
--- a/components/safe_browsing/core/browser/verdict_cache_manager.cc
+++ b/components/safe_browsing/core/browser/verdict_cache_manager.cc
@@ -605,7 +605,6 @@
 ChromeUserPopulation::PageLoadToken VerdictCacheManager::GetPageLoadToken(
     const GURL& url) {
   std::string hostname = url.host();
-  // TODO(crbug.com/1240403): Log the proportion of tokens that are not found.
   return base::Contains(page_load_token_map_, hostname)
              ? page_load_token_map_[hostname]
              : ChromeUserPopulation::PageLoadToken();
@@ -711,7 +710,8 @@
                base::Time::FromJavaTime(token.token_time_msec()) >
            base::Minutes(kPageLoadTokenExpireMinute);
   });
-  // TODO(crbug.com/1240403): Log the number of entries in page_load_token_map_;
+  base::UmaHistogramCounts10000("SafeBrowsing.PageLoadToken.TokenCount",
+                                page_load_token_map_.size());
 }
 
 // Overridden from history::HistoryServiceObserver.
diff --git a/components/services/app_service/BUILD.gn b/components/services/app_service/BUILD.gn
index 651ebd1..c3c4428e 100644
--- a/components/services/app_service/BUILD.gn
+++ b/components/services/app_service/BUILD.gn
@@ -4,8 +4,8 @@
 
 source_set("lib") {
   sources = [
-    "app_service_impl.cc",
-    "app_service_impl.h",
+    "app_service_mojom_impl.cc",
+    "app_service_mojom_impl.h",
   ]
 
   deps = [
@@ -24,7 +24,7 @@
 source_set("unit_tests") {
   testonly = true
 
-  sources = [ "app_service_impl_unittest.cc" ]
+  sources = [ "app_service_mojom_impl_unittest.cc" ]
 
   deps = [
     ":lib",
diff --git a/components/services/app_service/app_service_impl.cc b/components/services/app_service/app_service_mojom_impl.cc
similarity index 80%
rename from components/services/app_service/app_service_impl.cc
rename to components/services/app_service/app_service_mojom_impl.cc
index 023910b..1e36405 100644
--- a/components/services/app_service/app_service_impl.cc
+++ b/components/services/app_service/app_service_mojom_impl.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/services/app_service/app_service_impl.h"
+#include "components/services/app_service/app_service_mojom_impl.h"
 
 #include <iterator>
 #include <utility>
@@ -106,9 +106,10 @@
 
 namespace apps {
 
-AppServiceImpl::AppServiceImpl(const base::FilePath& profile_dir,
-                               base::OnceClosure read_completed_for_testing,
-                               base::OnceClosure write_completed_for_testing)
+AppServiceMojomImpl::AppServiceMojomImpl(
+    const base::FilePath& profile_dir,
+    base::OnceClosure read_completed_for_testing,
+    base::OnceClosure write_completed_for_testing)
     : profile_dir_(profile_dir),
       should_write_preferred_apps_to_file_(false),
       writing_preferred_apps_(false),
@@ -120,19 +121,19 @@
   InitializePreferredApps();
 }
 
-AppServiceImpl::~AppServiceImpl() = default;
+AppServiceMojomImpl::~AppServiceMojomImpl() = default;
 
-void AppServiceImpl::BindReceiver(
+void AppServiceMojomImpl::BindReceiver(
     mojo::PendingReceiver<apps::mojom::AppService> receiver) {
   receivers_.Add(this, std::move(receiver));
 }
 
-void AppServiceImpl::FlushMojoCallsForTesting() {
+void AppServiceMojomImpl::FlushMojoCallsForTesting() {
   subscribers_.FlushForTesting();
   receivers_.FlushForTesting();
 }
 
-void AppServiceImpl::RegisterPublisher(
+void AppServiceMojomImpl::RegisterPublisher(
     mojo::PendingRemote<apps::mojom::Publisher> publisher_remote,
     apps::mojom::AppType app_type) {
   mojo::Remote<apps::mojom::Publisher> publisher(std::move(publisher_remote));
@@ -146,13 +147,13 @@
 
   // Add the new publisher to the set.
   publisher.set_disconnect_handler(
-      base::BindOnce(&AppServiceImpl::OnPublisherDisconnected,
+      base::BindOnce(&AppServiceMojomImpl::OnPublisherDisconnected,
                      base::Unretained(this), app_type));
   auto result = publishers_.emplace(app_type, std::move(publisher));
   CHECK(result.second);
 }
 
-void AppServiceImpl::RegisterSubscriber(
+void AppServiceMojomImpl::RegisterSubscriber(
     mojo::PendingRemote<apps::mojom::Subscriber> subscriber_remote,
     apps::mojom::ConnectOptionsPtr opts) {
   // Connect the new subscriber with every registered publisher.
@@ -173,13 +174,13 @@
   subscribers_.Add(std::move(subscriber));
 }
 
-void AppServiceImpl::LoadIcon(apps::mojom::AppType app_type,
-                              const std::string& app_id,
-                              apps::mojom::IconKeyPtr icon_key,
-                              apps::mojom::IconType icon_type,
-                              int32_t size_hint_in_dip,
-                              bool allow_placeholder_icon,
-                              LoadIconCallback callback) {
+void AppServiceMojomImpl::LoadIcon(apps::mojom::AppType app_type,
+                                   const std::string& app_id,
+                                   apps::mojom::IconKeyPtr icon_key,
+                                   apps::mojom::IconType icon_type,
+                                   int32_t size_hint_in_dip,
+                                   bool allow_placeholder_icon,
+                                   LoadIconCallback callback) {
   auto iter = publishers_.find(app_type);
   if (iter == publishers_.end()) {
     std::move(callback).Run(apps::mojom::IconValue::New());
@@ -190,11 +191,11 @@
                          std::move(callback));
 }
 
-void AppServiceImpl::Launch(apps::mojom::AppType app_type,
-                            const std::string& app_id,
-                            int32_t event_flags,
-                            apps::mojom::LaunchSource launch_source,
-                            apps::mojom::WindowInfoPtr window_info) {
+void AppServiceMojomImpl::Launch(apps::mojom::AppType app_type,
+                                 const std::string& app_id,
+                                 int32_t event_flags,
+                                 apps::mojom::LaunchSource launch_source,
+                                 apps::mojom::WindowInfoPtr window_info) {
   auto iter = publishers_.find(app_type);
   if (iter == publishers_.end()) {
     return;
@@ -202,11 +203,12 @@
   iter->second->Launch(app_id, event_flags, launch_source,
                        std::move(window_info));
 }
-void AppServiceImpl::LaunchAppWithFiles(apps::mojom::AppType app_type,
-                                        const std::string& app_id,
-                                        int32_t event_flags,
-                                        apps::mojom::LaunchSource launch_source,
-                                        apps::mojom::FilePathsPtr file_paths) {
+void AppServiceMojomImpl::LaunchAppWithFiles(
+    apps::mojom::AppType app_type,
+    const std::string& app_id,
+    int32_t event_flags,
+    apps::mojom::LaunchSource launch_source,
+    apps::mojom::FilePathsPtr file_paths) {
   CHECK(file_paths);
   auto iter = publishers_.find(app_type);
   if (iter == publishers_.end()) {
@@ -216,7 +218,7 @@
                                    std::move(file_paths));
 }
 
-void AppServiceImpl::LaunchAppWithIntent(
+void AppServiceMojomImpl::LaunchAppWithIntent(
     apps::mojom::AppType app_type,
     const std::string& app_id,
     int32_t event_flags,
@@ -231,9 +233,9 @@
                                     launch_source, std::move(window_info));
 }
 
-void AppServiceImpl::SetPermission(apps::mojom::AppType app_type,
-                                   const std::string& app_id,
-                                   apps::mojom::PermissionPtr permission) {
+void AppServiceMojomImpl::SetPermission(apps::mojom::AppType app_type,
+                                        const std::string& app_id,
+                                        apps::mojom::PermissionPtr permission) {
   auto iter = publishers_.find(app_type);
   if (iter == publishers_.end()) {
     return;
@@ -241,11 +243,12 @@
   iter->second->SetPermission(app_id, std::move(permission));
 }
 
-void AppServiceImpl::Uninstall(apps::mojom::AppType app_type,
-                               const std::string& app_id,
-                               apps::mojom::UninstallSource uninstall_source,
-                               bool clear_site_data,
-                               bool report_abuse) {
+void AppServiceMojomImpl::Uninstall(
+    apps::mojom::AppType app_type,
+    const std::string& app_id,
+    apps::mojom::UninstallSource uninstall_source,
+    bool clear_site_data,
+    bool report_abuse) {
   auto iter = publishers_.find(app_type);
   if (iter == publishers_.end()) {
     return;
@@ -254,8 +257,8 @@
                           report_abuse);
 }
 
-void AppServiceImpl::PauseApp(apps::mojom::AppType app_type,
-                              const std::string& app_id) {
+void AppServiceMojomImpl::PauseApp(apps::mojom::AppType app_type,
+                                   const std::string& app_id) {
   auto iter = publishers_.find(app_type);
   if (iter == publishers_.end()) {
     return;
@@ -263,8 +266,8 @@
   iter->second->PauseApp(app_id);
 }
 
-void AppServiceImpl::UnpauseApp(apps::mojom::AppType app_type,
-                                const std::string& app_id) {
+void AppServiceMojomImpl::UnpauseApp(apps::mojom::AppType app_type,
+                                     const std::string& app_id) {
   auto iter = publishers_.find(app_type);
   if (iter == publishers_.end()) {
     return;
@@ -272,8 +275,8 @@
   iter->second->UnpauseApp(app_id);
 }
 
-void AppServiceImpl::StopApp(apps::mojom::AppType app_type,
-                             const std::string& app_id) {
+void AppServiceMojomImpl::StopApp(apps::mojom::AppType app_type,
+                                  const std::string& app_id) {
   auto iter = publishers_.find(app_type);
   if (iter == publishers_.end()) {
     return;
@@ -281,11 +284,11 @@
   iter->second->StopApp(app_id);
 }
 
-void AppServiceImpl::GetMenuModel(apps::mojom::AppType app_type,
-                                  const std::string& app_id,
-                                  apps::mojom::MenuType menu_type,
-                                  int64_t display_id,
-                                  GetMenuModelCallback callback) {
+void AppServiceMojomImpl::GetMenuModel(apps::mojom::AppType app_type,
+                                       const std::string& app_id,
+                                       apps::mojom::MenuType menu_type,
+                                       int64_t display_id,
+                                       GetMenuModelCallback callback) {
   auto iter = publishers_.find(app_type);
   if (iter == publishers_.end()) {
     std::move(callback).Run(apps::mojom::MenuItems::New());
@@ -296,11 +299,12 @@
                              std::move(callback));
 }
 
-void AppServiceImpl::ExecuteContextMenuCommand(apps::mojom::AppType app_type,
-                                               const std::string& app_id,
-                                               int command_id,
-                                               const std::string& shortcut_id,
-                                               int64_t display_id) {
+void AppServiceMojomImpl::ExecuteContextMenuCommand(
+    apps::mojom::AppType app_type,
+    const std::string& app_id,
+    int command_id,
+    const std::string& shortcut_id,
+    int64_t display_id) {
   auto iter = publishers_.find(app_type);
   if (iter == publishers_.end()) {
     return;
@@ -310,8 +314,8 @@
                                           display_id);
 }
 
-void AppServiceImpl::OpenNativeSettings(apps::mojom::AppType app_type,
-                                        const std::string& app_id) {
+void AppServiceMojomImpl::OpenNativeSettings(apps::mojom::AppType app_type,
+                                             const std::string& app_id) {
   auto iter = publishers_.find(app_type);
   if (iter == publishers_.end()) {
     return;
@@ -319,11 +323,12 @@
   iter->second->OpenNativeSettings(app_id);
 }
 
-void AppServiceImpl::AddPreferredApp(apps::mojom::AppType app_type,
-                                     const std::string& app_id,
-                                     apps::mojom::IntentFilterPtr intent_filter,
-                                     apps::mojom::IntentPtr intent,
-                                     bool from_publisher) {
+void AppServiceMojomImpl::AddPreferredApp(
+    apps::mojom::AppType app_type,
+    const std::string& app_id,
+    apps::mojom::IntentFilterPtr intent_filter,
+    apps::mojom::IntentPtr intent,
+    bool from_publisher) {
   // TODO(crbug.com/853604): Make sure the ARC preference init happens after
   // this. Might need to change the interface to call that after read completed.
   // Might also need to record the change before data read and make the update
@@ -368,8 +373,8 @@
   }
 }
 
-void AppServiceImpl::RemovePreferredApp(apps::mojom::AppType app_type,
-                                        const std::string& app_id) {
+void AppServiceMojomImpl::RemovePreferredApp(apps::mojom::AppType app_type,
+                                             const std::string& app_id) {
   // TODO(crbug.com/853604): Make sure the ARC preference init happens after
   // this. Might need to change the interface to call that after read completed.
   // Might also need to record the change before data read and make the update
@@ -394,7 +399,7 @@
   LogPreferredAppUpdateAction(PreferredAppsUpdateAction::kDeleteForAppId);
 }
 
-void AppServiceImpl::RemovePreferredAppForFilter(
+void AppServiceMojomImpl::RemovePreferredAppForFilter(
     apps::mojom::AppType app_type,
     const std::string& app_id,
     apps::mojom::IntentFilterPtr intent_filter) {
@@ -418,7 +423,7 @@
   LogPreferredAppUpdateAction(PreferredAppsUpdateAction::kDeleteForFilter);
 }
 
-void AppServiceImpl::SetSupportedLinksPreference(
+void AppServiceMojomImpl::SetSupportedLinksPreference(
     apps::mojom::AppType app_type,
     const std::string& app_id,
     std::vector<apps::mojom::IntentFilterPtr> all_link_filters) {
@@ -493,7 +498,7 @@
   }
 }
 
-void AppServiceImpl::RemoveSupportedLinksPreference(
+void AppServiceMojomImpl::RemoveSupportedLinksPreference(
     apps::mojom::AppType app_type,
     const std::string& app_id) {
   if (!preferred_apps_.IsInitialized()) {
@@ -524,9 +529,9 @@
                                                   /*open_in_app=*/false);
 }
 
-void AppServiceImpl::SetResizeLocked(apps::mojom::AppType app_type,
-                                     const std::string& app_id,
-                                     mojom::OptionalBool locked) {
+void AppServiceMojomImpl::SetResizeLocked(apps::mojom::AppType app_type,
+                                          const std::string& app_id,
+                                          mojom::OptionalBool locked) {
   auto iter = publishers_.find(app_type);
   if (iter == publishers_.end()) {
     return;
@@ -534,9 +539,9 @@
   iter->second->SetResizeLocked(app_id, locked);
 }
 
-void AppServiceImpl::SetWindowMode(apps::mojom::AppType app_type,
-                                   const std::string& app_id,
-                                   apps::mojom::WindowMode window_mode) {
+void AppServiceMojomImpl::SetWindowMode(apps::mojom::AppType app_type,
+                                        const std::string& app_id,
+                                        apps::mojom::WindowMode window_mode) {
   auto iter = publishers_.find(app_type);
   if (iter == publishers_.end()) {
     return;
@@ -544,19 +549,20 @@
   iter->second->SetWindowMode(app_id, window_mode);
 }
 
-PreferredAppsList& AppServiceImpl::GetPreferredAppsForTesting() {
+PreferredAppsList& AppServiceMojomImpl::GetPreferredAppsForTesting() {
   return preferred_apps_;
 }
 
-void AppServiceImpl::OnPublisherDisconnected(apps::mojom::AppType app_type) {
+void AppServiceMojomImpl::OnPublisherDisconnected(
+    apps::mojom::AppType app_type) {
   publishers_.erase(app_type);
 }
 
-void AppServiceImpl::InitializePreferredApps() {
+void AppServiceMojomImpl::InitializePreferredApps() {
   ReadFromJSON(profile_dir_);
 }
 
-void AppServiceImpl::WriteToJSON(
+void AppServiceMojomImpl::WriteToJSON(
     const base::FilePath& profile_dir,
     const apps::PreferredAppsList& preferred_apps) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
@@ -580,11 +586,11 @@
       FROM_HERE,
       base::BindOnce(&WriteDataBlocking,
                      profile_dir.Append(kPreferredAppsDirname), json_string),
-      base::BindOnce(&AppServiceImpl::WriteCompleted,
+      base::BindOnce(&AppServiceMojomImpl::WriteCompleted,
                      weak_ptr_factory_.GetWeakPtr()));
 }
 
-void AppServiceImpl::WriteCompleted() {
+void AppServiceMojomImpl::WriteCompleted() {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   writing_preferred_apps_ = false;
   if (!should_write_preferred_apps_to_file_) {
@@ -600,17 +606,17 @@
   WriteToJSON(profile_dir_, preferred_apps_);
 }
 
-void AppServiceImpl::ReadFromJSON(const base::FilePath& profile_dir) {
+void AppServiceMojomImpl::ReadFromJSON(const base::FilePath& profile_dir) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   task_runner_->PostTaskAndReplyWithResult(
       FROM_HERE,
       base::BindOnce(&ReadDataBlocking,
                      profile_dir.Append(kPreferredAppsDirname)),
-      base::BindOnce(&AppServiceImpl::ReadCompleted,
+      base::BindOnce(&AppServiceMojomImpl::ReadCompleted,
                      weak_ptr_factory_.GetWeakPtr()));
 }
 
-void AppServiceImpl::ReadCompleted(std::string preferred_apps_string) {
+void AppServiceMojomImpl::ReadCompleted(std::string preferred_apps_string) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   bool preferred_apps_upgraded = false;
   if (preferred_apps_string.empty()) {
diff --git a/components/services/app_service/app_service_impl.h b/components/services/app_service/app_service_mojom_impl.h
similarity index 92%
rename from components/services/app_service/app_service_impl.h
rename to components/services/app_service/app_service_mojom_impl.h
index 336b135..e1e322ac 100644
--- a/components/services/app_service/app_service_impl.h
+++ b/components/services/app_service/app_service_mojom_impl.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef COMPONENTS_SERVICES_APP_SERVICE_APP_SERVICE_IMPL_H_
-#define COMPONENTS_SERVICES_APP_SERVICE_APP_SERVICE_IMPL_H_
+#ifndef COMPONENTS_SERVICES_APP_SERVICE_APP_SERVICE_MOJOM_IMPL_H_
+#define COMPONENTS_SERVICES_APP_SERVICE_APP_SERVICE_MOJOM_IMPL_H_
 
 #include <map>
 
@@ -26,17 +26,17 @@
 // The implementation of the apps::mojom::AppService Mojo interface.
 //
 // See components/services/app_service/README.md.
-class AppServiceImpl : public apps::mojom::AppService {
+class AppServiceMojomImpl : public apps::mojom::AppService {
  public:
-  AppServiceImpl(
+  AppServiceMojomImpl(
       const base::FilePath& profile_dir,
       base::OnceClosure read_completed_for_testing = base::OnceClosure(),
       base::OnceClosure write_completed_for_testing = base::OnceClosure());
 
-  AppServiceImpl(const AppServiceImpl&) = delete;
-  AppServiceImpl& operator=(const AppServiceImpl&) = delete;
+  AppServiceMojomImpl(const AppServiceMojomImpl&) = delete;
+  AppServiceMojomImpl& operator=(const AppServiceMojomImpl&) = delete;
 
-  ~AppServiceImpl() override;
+  ~AppServiceMojomImpl() override;
 
   void BindReceiver(mojo::PendingReceiver<apps::mojom::AppService> receiver);
 
@@ -172,9 +172,9 @@
 
   base::OnceClosure write_completed_for_testing_;
 
-  base::WeakPtrFactory<AppServiceImpl> weak_ptr_factory_{this};
+  base::WeakPtrFactory<AppServiceMojomImpl> weak_ptr_factory_{this};
 };
 
 }  // namespace apps
 
-#endif  // COMPONENTS_SERVICES_APP_SERVICE_APP_SERVICE_IMPL_H_
+#endif  // COMPONENTS_SERVICES_APP_SERVICE_APP_SERVICE_MOJOM_IMPL_H_
diff --git a/components/services/app_service/app_service_impl_unittest.cc b/components/services/app_service/app_service_mojom_impl_unittest.cc
similarity index 96%
rename from components/services/app_service/app_service_impl_unittest.cc
rename to components/services/app_service/app_service_mojom_impl_unittest.cc
index d0fd0eec..d5028586 100644
--- a/components/services/app_service/app_service_impl_unittest.cc
+++ b/components/services/app_service/app_service_mojom_impl_unittest.cc
@@ -11,7 +11,7 @@
 #include "base/callback.h"
 #include "base/files/scoped_temp_dir.h"
 #include "base/run_loop.h"
-#include "components/services/app_service/app_service_impl.h"
+#include "components/services/app_service/app_service_mojom_impl.h"
 #include "components/services/app_service/public/cpp/app_capability_access_cache.h"
 #include "components/services/app_service/public/cpp/intent_filter_util.h"
 #include "components/services/app_service/public/cpp/intent_test_util.h"
@@ -33,7 +33,7 @@
 
 class FakePublisher : public apps::PublisherBase {
  public:
-  FakePublisher(AppServiceImpl* impl,
+  FakePublisher(AppServiceMojomImpl* impl,
                 apps::mojom::AppType app_type,
                 std::vector<std::string> initial_app_ids)
       : app_type_(app_type), known_app_ids_(std::move(initial_app_ids)) {
@@ -74,7 +74,8 @@
         subscribers_, app_id, accessing_camera, accessing_microphone);
   }
 
-  void UninstallApps(std::vector<std::string> app_ids, AppServiceImpl* impl) {
+  void UninstallApps(std::vector<std::string> app_ids,
+                     AppServiceMojomImpl* impl) {
     for (auto& subscriber : subscribers_) {
       CallOnApps(subscriber.get(), app_ids, /*uninstall=*/true);
     }
@@ -170,7 +171,7 @@
 
 class FakeSubscriber : public apps::mojom::Subscriber {
  public:
-  explicit FakeSubscriber(AppServiceImpl* impl) {
+  explicit FakeSubscriber(AppServiceMojomImpl* impl) {
     mojo::PendingRemote<apps::mojom::Subscriber> remote;
     receivers_.Add(this, remote.InitWithNewPipeAndPassReceiver());
     impl->RegisterSubscriber(std::move(remote), nullptr);
@@ -250,17 +251,17 @@
   apps::PreferredAppsList preferred_apps_;
 };
 
-class AppServiceImplTest : public testing::Test {
+class AppServiceMojomImplTest : public testing::Test {
  protected:
   content::BrowserTaskEnvironment task_environment_;
   base::ScopedTempDir temp_dir_;
 };
 
-TEST_F(AppServiceImplTest, PubSub) {
+TEST_F(AppServiceMojomImplTest, PubSub) {
   const int size_hint_in_dip = 64;
 
   ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
-  AppServiceImpl impl(temp_dir_.GetPath());
+  AppServiceMojomImpl impl(temp_dir_.GetPath());
 
   // Start with one subscriber.
   FakeSubscriber sub0(&impl);
@@ -388,10 +389,10 @@
   }
 }
 
-TEST_F(AppServiceImplTest, PreferredApps) {
+TEST_F(AppServiceMojomImplTest, PreferredApps) {
   // Test Initialize.
   ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
-  AppServiceImpl impl(temp_dir_.GetPath());
+  AppServiceMojomImpl impl(temp_dir_.GetPath());
   impl.GetPreferredAppsForTesting().Init();
 
   const char kAppId1[] = "abcdefg";
@@ -491,7 +492,7 @@
             sub1.PreferredApps().FindPreferredAppForUrl(another_filter_url));
 }
 
-TEST_F(AppServiceImplTest, PreferredAppsPersistency) {
+TEST_F(AppServiceMojomImplTest, PreferredAppsPersistency) {
   ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
 
   const char kAppId1[] = "abcdefg";
@@ -500,9 +501,8 @@
   {
     base::RunLoop run_loop_read;
     base::RunLoop run_loop_write;
-    AppServiceImpl impl(temp_dir_.GetPath(),
-                        run_loop_read.QuitClosure(),
-                        run_loop_write.QuitClosure());
+    AppServiceMojomImpl impl(temp_dir_.GetPath(), run_loop_read.QuitClosure(),
+                             run_loop_write.QuitClosure());
     impl.FlushMojoCallsForTesting();
     run_loop_read.Run();
     impl.AddPreferredApp(apps::mojom::AppType::kUnknown, kAppId1,
@@ -515,8 +515,7 @@
   // Create a new impl to initialize preferred apps from the disk.
   {
     base::RunLoop run_loop_read;
-    AppServiceImpl impl(temp_dir_.GetPath(),
-                        run_loop_read.QuitClosure());
+    AppServiceMojomImpl impl(temp_dir_.GetPath(), run_loop_read.QuitClosure());
     impl.FlushMojoCallsForTesting();
     run_loop_read.Run();
     EXPECT_EQ(kAppId1, impl.GetPreferredAppsForTesting().FindPreferredAppForUrl(
@@ -524,10 +523,10 @@
   }
 }
 
-TEST_F(AppServiceImplTest, PreferredAppsSetSupportedLinks) {
+TEST_F(AppServiceMojomImplTest, PreferredAppsSetSupportedLinks) {
   // Test Initialize.
   ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
-  AppServiceImpl impl(temp_dir_.GetPath());
+  AppServiceMojomImpl impl(temp_dir_.GetPath());
   impl.GetPreferredAppsForTesting().Init();
 
   const char kAppId1[] = "abcdefg";
@@ -613,10 +612,10 @@
 }
 
 // Test that app with overlapped supported links works properly.
-TEST_F(AppServiceImplTest, PreferredAppsOverlap) {
+TEST_F(AppServiceMojomImplTest, PreferredAppsOverlap) {
   // Test Initialize.
   ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
-  AppServiceImpl impl(temp_dir_.GetPath());
+  AppServiceMojomImpl impl(temp_dir_.GetPath());
   impl.GetPreferredAppsForTesting().Init();
 
   const char kAppId1[] = "abcdefg";
@@ -708,10 +707,10 @@
 }
 
 // Test that duplicated entry will not be added.
-TEST_F(AppServiceImplTest, PreferredAppsDuplicated) {
+TEST_F(AppServiceMojomImplTest, PreferredAppsDuplicated) {
   // Test Initialize.
   ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
-  AppServiceImpl impl(temp_dir_.GetPath());
+  AppServiceMojomImpl impl(temp_dir_.GetPath());
   impl.GetPreferredAppsForTesting().Init();
 
   const char kAppId1[] = "abcdefg";
@@ -746,10 +745,10 @@
 }
 
 // Test that duplicated entry will not be added for supported links.
-TEST_F(AppServiceImplTest, PreferredAppsDuplicatedSupportedLink) {
+TEST_F(AppServiceMojomImplTest, PreferredAppsDuplicatedSupportedLink) {
   // Test Initialize.
   ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
-  AppServiceImpl impl(temp_dir_.GetPath());
+  AppServiceMojomImpl impl(temp_dir_.GetPath());
   impl.GetPreferredAppsForTesting().Init();
 
   const char kAppId1[] = "abcdefg";
diff --git a/components/subresource_filter/content/browser/content_subresource_filter_throttle_manager.cc b/components/subresource_filter/content/browser/content_subresource_filter_throttle_manager.cc
index 253f336..bfaa0c3 100644
--- a/components/subresource_filter/content/browser/content_subresource_filter_throttle_manager.cc
+++ b/components/subresource_filter/content/browser/content_subresource_filter_throttle_manager.cc
@@ -366,11 +366,12 @@
       navigation_handle->HasCommitted());
   blink::mojom::FilterListResult latest_filter_list_result =
       EnsureFrameAdEvidence(navigation_handle).latest_filter_list_result();
-  // TODO(1061899): Calculate this without using the WebContents.
   bool is_same_domain_to_main_frame =
       net::registry_controlled_domains::SameDomainOrHost(
           navigation_handle->GetURL(),
-          navigation_handle->GetWebContents()->GetLastCommittedURL(),
+          navigation_handle->GetRenderFrameHost()
+              ->GetOutermostMainFrame()
+              ->GetLastCommittedURL(),
           net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES);
   bool is_restricted_navigation =
       latest_filter_list_result ==
diff --git a/components/thin_webview/internal/compositor_view_impl.cc b/components/thin_webview/internal/compositor_view_impl.cc
index 71b35de4..08ce986 100644
--- a/components/thin_webview/internal/compositor_view_impl.cc
+++ b/components/thin_webview/internal/compositor_view_impl.cc
@@ -13,7 +13,7 @@
 #include "components/thin_webview/internal/jni_headers/CompositorViewImpl_jni.h"
 #include "content/public/browser/android/compositor.h"
 #include "third_party/skia/include/core/SkColor.h"
-#include "ui/android/color_helpers.h"
+#include "ui/android/color_utils_android.h"
 #include "ui/android/window_android.h"
 
 using base::android::JavaParamRef;
diff --git a/components/update_client/update_client_errors.h b/components/update_client/update_client_errors.h
index 61d70b2..58d6736f 100644
--- a/components/update_client/update_client_errors.h
+++ b/components/update_client/update_client_errors.h
@@ -85,6 +85,8 @@
   CLEAN_INSTALL_DIR_FAILED = 15,
   INSTALL_VERIFICATION_FAILED = 16,
   MISSING_INSTALL_PARAMS = 17,
+  // If LaunchProcess is attempted on unsupported non-desktop skus e.g. xbox
+  LAUNCH_PROCESS_FAILED = 18,
   CUSTOM_ERROR_BASE = 100,  // Specific installer errors go above this value.
 };
 
diff --git a/components/webapps/browser/android/webapk/webapk_proto_builder.cc b/components/webapps/browser/android/webapk/webapk_proto_builder.cc
index efaf223..269a6d1 100644
--- a/components/webapps/browser/android/webapk/webapk_proto_builder.cc
+++ b/components/webapps/browser/android/webapk/webapk_proto_builder.cc
@@ -19,7 +19,7 @@
 #include "third_party/blink/public/common/manifest/manifest_util.h"
 #include "third_party/blink/public/mojom/manifest/manifest.mojom.h"
 #include "third_party/skia/include/core/SkBitmap.h"
-#include "ui/android/color_helpers.h"
+#include "ui/android/color_utils_android.h"
 #include "ui/gfx/codec/png_codec.h"
 
 namespace webapps {
diff --git a/content/browser/accessibility/accessibility_tree_formatter_blink.cc b/content/browser/accessibility/accessibility_tree_formatter_blink.cc
index baf409b..2ec96e9 100644
--- a/content/browser/accessibility/accessibility_tree_formatter_blink.cc
+++ b/content/browser/accessibility/accessibility_tree_formatter_blink.cc
@@ -721,11 +721,12 @@
        attr_index <= static_cast<int32_t>(ax::mojom::FloatAttribute::kMaxValue);
        ++attr_index) {
     auto attr = static_cast<ax::mojom::FloatAttribute>(attr_index);
-    double float_value;
-    if (!dict.GetDouble(ui::ToString(attr), &float_value))
+    absl::optional<double> float_value =
+        dict.FindDoublePath(ui::ToString(attr));
+    if (!float_value)
       continue;
     WriteAttribute(
-        false, base::StringPrintf("%s=%.2f", ui::ToString(attr), float_value),
+        false, base::StringPrintf("%s=%.2f", ui::ToString(attr), *float_value),
         &line);
   }
 
diff --git a/content/browser/back_forward_cache_browsertest.cc b/content/browser/back_forward_cache_browsertest.cc
index 211baf25..86c272d8 100644
--- a/content/browser/back_forward_cache_browsertest.cc
+++ b/content/browser/back_forward_cache_browsertest.cc
@@ -1674,6 +1674,29 @@
           return RenderFrameHost::FrameIterationAction::kStop;
         }));
   }
+
+  EXPECT_EQ(nullptr, rfh_a->GetParentOrOuterDocument());
+  EXPECT_EQ(rfh_a, rfh_b->GetParentOrOuterDocument());
+  EXPECT_EQ(rfh_b, rfh_c->GetParentOrOuterDocument());
+  EXPECT_EQ(rfh_a, rfh_d->GetParentOrOuterDocument());
+  EXPECT_EQ(nullptr, rfh_e->GetParentOrOuterDocument());
+  // The outermost document of a bfcached page is the bfcached main
+  // RenderFrameHost, not the primary main RenderFrameHost.
+  EXPECT_EQ(rfh_a, rfh_a->GetOutermostMainFrame());
+  EXPECT_EQ(rfh_a, rfh_b->GetOutermostMainFrame());
+  EXPECT_EQ(rfh_a, rfh_c->GetOutermostMainFrame());
+  EXPECT_EQ(rfh_a, rfh_d->GetOutermostMainFrame());
+  EXPECT_EQ(rfh_e, rfh_e->GetOutermostMainFrame());
+  EXPECT_EQ(nullptr, rfh_a->GetParentOrOuterDocumentOrEmbedder());
+  EXPECT_EQ(rfh_a, rfh_b->GetParentOrOuterDocumentOrEmbedder());
+  EXPECT_EQ(rfh_b, rfh_c->GetParentOrOuterDocumentOrEmbedder());
+  EXPECT_EQ(rfh_a, rfh_d->GetParentOrOuterDocumentOrEmbedder());
+  EXPECT_EQ(nullptr, rfh_e->GetParentOrOuterDocumentOrEmbedder());
+  EXPECT_EQ(rfh_a, rfh_a->GetOutermostMainFrameOrEmbedder());
+  EXPECT_EQ(rfh_a, rfh_b->GetOutermostMainFrameOrEmbedder());
+  EXPECT_EQ(rfh_a, rfh_c->GetOutermostMainFrameOrEmbedder());
+  EXPECT_EQ(rfh_a, rfh_d->GetOutermostMainFrameOrEmbedder());
+  EXPECT_EQ(rfh_e, rfh_e->GetOutermostMainFrameOrEmbedder());
 }
 
 // Tests that |RenderFrameHostImpl::ForEachRenderFrameHostIncludingSpeculative|
diff --git a/content/browser/cache_storage/cache_storage_dispatcher_host.cc b/content/browser/cache_storage/cache_storage_dispatcher_host.cc
index 73c78c0..a0a41be 100644
--- a/content/browser/cache_storage/cache_storage_dispatcher_host.cc
+++ b/content/browser/cache_storage/cache_storage_dispatcher_host.cc
@@ -208,9 +208,12 @@
                     content::CacheStorageCache::InitState::Initialized
               : false;
 
+    bool range_request =
+        request->headers.contains(std::string(net::HttpRequestHeaders::kRange));
+
     auto cb = base::BindOnce(
         [](base::WeakPtr<CacheImpl> self, base::TimeTicks start_time,
-           bool ignore_search, bool in_related_fetch_event,
+           bool ignore_search, bool in_related_fetch_event, bool range_request,
            bool cache_initialized, int64_t trace_id,
            blink::mojom::CacheStorageCache::MatchCallback callback,
            blink::mojom::CacheStorageError error,
@@ -270,7 +273,7 @@
               CacheStorageTracedValue(response));
 
           blink::mojom::MatchResultPtr result;
-          if (in_related_fetch_event) {
+          if (in_related_fetch_event && !range_request) {
             result = EagerlyReadResponseBody(std::move(response));
           } else {
             result =
@@ -279,8 +282,8 @@
           std::move(callback).Run(std::move(result));
         },
         weak_factory_.GetWeakPtr(), base::TimeTicks::Now(),
-        match_options->ignore_search, in_related_fetch_event, cache_initialized,
-        trace_id, std::move(callback));
+        match_options->ignore_search, in_related_fetch_event, range_request,
+        cache_initialized, trace_id, std::move(callback));
 
     if (!cache) {
       std::move(cb).Run(CacheStorageError::kErrorNotFound, nullptr);
diff --git a/content/browser/fenced_frame/fenced_frame_browsertest.cc b/content/browser/fenced_frame/fenced_frame_browsertest.cc
index dc27469..fda6fa7 100644
--- a/content/browser/fenced_frame/fenced_frame_browsertest.cc
+++ b/content/browser/fenced_frame/fenced_frame_browsertest.cc
@@ -78,6 +78,11 @@
   EXPECT_EQ(nullptr, inner_fenced_frame_rfh->GetParent());
   EXPECT_EQ(inner_fenced_frame_rfh->GetParentOrOuterDocument(),
             primary_rfh.get());
+  EXPECT_EQ(inner_fenced_frame_rfh->GetOutermostMainFrame(), primary_rfh.get());
+  EXPECT_EQ(inner_fenced_frame_rfh->GetParentOrOuterDocumentOrEmbedder(),
+            primary_rfh.get());
+  EXPECT_EQ(inner_fenced_frame_rfh->GetOutermostMainFrameOrEmbedder(),
+            primary_rfh.get());
 
   // Test `RenderFrameHostImpl::IsInPrimaryMainFrame`.
   EXPECT_TRUE(primary_rfh->IsInPrimaryMainFrame());
diff --git a/content/browser/prerender/prerender_browsertest.cc b/content/browser/prerender/prerender_browsertest.cc
index c3515c63..48a96f4 100644
--- a/content/browser/prerender/prerender_browsertest.cc
+++ b/content/browser/prerender/prerender_browsertest.cc
@@ -1603,6 +1603,34 @@
               testing::UnorderedElementsAre(initiator_render_frame_host,
                                             prerendered_render_frame_host,
                                             rfh_sub_1, rfh_sub_2, rfh_sub_1_1));
+
+  EXPECT_EQ(nullptr, initiator_render_frame_host->GetParentOrOuterDocument());
+  EXPECT_EQ(nullptr, prerendered_render_frame_host->GetParentOrOuterDocument());
+  EXPECT_EQ(prerendered_render_frame_host,
+            rfh_sub_1->GetParentOrOuterDocument());
+  EXPECT_EQ(rfh_sub_1, rfh_sub_1_1->GetParentOrOuterDocument());
+  EXPECT_EQ(prerendered_render_frame_host,
+            rfh_sub_2->GetParentOrOuterDocument());
+  EXPECT_EQ(initiator_render_frame_host,
+            initiator_render_frame_host->GetOutermostMainFrame());
+  EXPECT_EQ(initiator_render_frame_host,
+            initiator_render_frame_host->GetOutermostMainFrameOrEmbedder());
+  // The outermost document of a prerendered page is the prerendered main
+  // RenderFrameHost, not the primary main RenderFrameHost.
+  EXPECT_EQ(prerendered_render_frame_host,
+            prerendered_render_frame_host->GetOutermostMainFrame());
+  EXPECT_EQ(prerendered_render_frame_host, rfh_sub_1->GetOutermostMainFrame());
+  EXPECT_EQ(prerendered_render_frame_host,
+            rfh_sub_1_1->GetOutermostMainFrame());
+  EXPECT_EQ(prerendered_render_frame_host, rfh_sub_2->GetOutermostMainFrame());
+  EXPECT_EQ(prerendered_render_frame_host,
+            prerendered_render_frame_host->GetOutermostMainFrameOrEmbedder());
+  EXPECT_EQ(prerendered_render_frame_host,
+            rfh_sub_1->GetOutermostMainFrameOrEmbedder());
+  EXPECT_EQ(prerendered_render_frame_host,
+            rfh_sub_1_1->GetOutermostMainFrameOrEmbedder());
+  EXPECT_EQ(prerendered_render_frame_host,
+            rfh_sub_2->GetOutermostMainFrameOrEmbedder());
 }
 
 // Tests that a prerendering page cannot change the visible URL of the
diff --git a/content/browser/renderer_host/cross_process_frame_connector.cc b/content/browser/renderer_host/cross_process_frame_connector.cc
index de006fd..53b91df 100644
--- a/content/browser/renderer_host/cross_process_frame_connector.cc
+++ b/content/browser/renderer_host/cross_process_frame_connector.cc
@@ -46,7 +46,7 @@
   // be on the correct display. All subsequent updates to |screen_infos_|
   // ultimately come from the root, so it makes sense to do it here as well.
   screen_infos_ = current_child_frame_host()
-                      ->GetOutermostMainFrame()
+                      ->GetOutermostMainFrameOrEmbedder()
                       ->GetRenderWidgetHost()
                       ->GetScreenInfos();
 }
@@ -454,7 +454,7 @@
     return nullptr;
 
   RenderFrameHostImpl* root =
-      current_child_frame_host()->GetOutermostMainFrame();
+      current_child_frame_host()->GetOutermostMainFrameOrEmbedder();
   return static_cast<RenderWidgetHostViewBase*>(root->GetView());
 }
 
diff --git a/content/browser/renderer_host/frame_tree.cc b/content/browser/renderer_host/frame_tree.cc
index ca95742..5faf43ff 100644
--- a/content/browser/renderer_host/frame_tree.cc
+++ b/content/browser/renderer_host/frame_tree.cc
@@ -625,7 +625,10 @@
   // The accessibility tree data for the root of the frame tree keeps
   // track of the focused frame too, so update that every time the
   // focused frame changes.
-  root()->current_frame_host()->GetOutermostMainFrame()->UpdateAXTreeData();
+  root()
+      ->current_frame_host()
+      ->GetOutermostMainFrameOrEmbedder()
+      ->UpdateAXTreeData();
 }
 
 scoped_refptr<RenderViewHostImpl> FrameTree::CreateRenderViewHost(
diff --git a/content/browser/renderer_host/input/touch_selection_controller_client_aura_browsertest.cc b/content/browser/renderer_host/input/touch_selection_controller_client_aura_browsertest.cc
index 6b124c3..7170533f 100644
--- a/content/browser/renderer_host/input/touch_selection_controller_client_aura_browsertest.cc
+++ b/content/browser/renderer_host/input/touch_selection_controller_client_aura_browsertest.cc
@@ -48,13 +48,12 @@
   base::DictionaryValue* root;
   if (!value->GetAsDictionary(&root))
     return false;
-  double x, y;
-  if (!root->GetDouble("x", &x))
+  absl::optional<double> x = root->FindDoubleKey("x");
+  absl::optional<double> y = root->FindDoubleKey("y");
+  if (!x || !y)
     return false;
-  if (!root->GetDouble("y", &y))
-    return false;
-  point->set_x(x);
-  point->set_y(y);
+  point->set_x(*x);
+  point->set_y(*y);
   return true;
 }
 
diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc
index a2c0654f..2198d47e 100644
--- a/content/browser/renderer_host/render_frame_host_impl.cc
+++ b/content/browser/renderer_host/render_frame_host_impl.cc
@@ -8685,6 +8685,17 @@
   return !GetParent() && GetPage().IsPrimary();
 }
 
+RenderFrameHostImpl* RenderFrameHostImpl::GetOutermostMainFrame() {
+  RenderFrameHostImpl* current = this;
+  while (true) {
+    RenderFrameHostImpl* parent_or_outer_doc =
+        current->GetParentOrOuterDocument();
+    if (!parent_or_outer_doc)
+      return current;
+    current = parent_or_outer_doc;
+  };
+}
+
 bool RenderFrameHostImpl::CanAccessFilesOfPageState(
     const blink::PageState& state) {
   return ChildProcessSecurityPolicyImpl::GetInstance()->CanReadAllFiles(
@@ -12055,7 +12066,7 @@
   return nullptr;
 }
 
-RenderFrameHostImpl* RenderFrameHostImpl::GetOutermostMainFrame() {
+RenderFrameHostImpl* RenderFrameHostImpl::GetOutermostMainFrameOrEmbedder() {
   RenderFrameHostImpl* current = this;
   while (true) {
     RenderFrameHostImpl* parent = current->GetParentOrOuterDocumentOrEmbedder();
@@ -12430,8 +12441,9 @@
 
   // The accessibility tree for the outermost root frame contains references
   // to the focused frame via its AXTreeID, so ensure that we update that.
-  if (GetOutermostMainFrame() != this)
-    GetOutermostMainFrame()->UpdateAXTreeData();
+  RenderFrameHostImpl* outermost = GetOutermostMainFrameOrEmbedder();
+  if (outermost != this)
+    outermost->UpdateAXTreeData();
 }
 
 bool RenderFrameHostImpl::DocumentUsedWebOTP() {
diff --git a/content/browser/renderer_host/render_frame_host_impl.h b/content/browser/renderer_host/render_frame_host_impl.h
index 6212c8d..a96eadb6 100644
--- a/content/browser/renderer_host/render_frame_host_impl.h
+++ b/content/browser/renderer_host/render_frame_host_impl.h
@@ -329,6 +329,7 @@
   RenderFrameHostImpl* GetMainFrame() override;
   PageImpl& GetPage() override;
   bool IsInPrimaryMainFrame() override;
+  RenderFrameHostImpl* GetOutermostMainFrame() override;
   bool IsFencedFrameRoot() override;
   void ForEachRenderFrameHost(FrameIterationCallback on_frame) override;
   void ForEachRenderFrameHost(
@@ -1878,8 +1879,14 @@
     return media_device_id_salt_base_;
   }
 
-  // Returns the global root RenderFrameHostImpl in the outermost WebContents.
-  RenderFrameHostImpl* GetOutermostMainFrame();
+  // Returns the topmost ancestor RenderFrameHost. This includes any parents (in
+  // the case of subframes), any outer documents (e.g. fenced frame owners), and
+  // any GuestViews. See also GetOutermostMainFrame which does not escape
+  // GuestViews and GetParentOrOuterDocumentOrEmbedder for more details.
+  // Note that this may be different from getting the WebContents' primary main
+  // frame. For example, if `this` is in a bfcached or prerendered page, this
+  // will return the cached/prerendered page's main RenderFrameHost.
+  RenderFrameHostImpl* GetOutermostMainFrameOrEmbedder();
 
   void set_inner_tree_main_frame_tree_node_id(int id) {
     inner_tree_main_frame_tree_node_id_ = id;
diff --git a/content/browser/renderer_host/render_frame_host_impl_browsertest.cc b/content/browser/renderer_host/render_frame_host_impl_browsertest.cc
index 85ea3cf7..524ca1eb 100644
--- a/content/browser/renderer_host/render_frame_host_impl_browsertest.cc
+++ b/content/browser/renderer_host/render_frame_host_impl_browsertest.cc
@@ -5549,6 +5549,19 @@
         }));
     EXPECT_THAT(visited_frames, testing::ElementsAre(rfh_a, rfh_b, rfh_d));
   }
+
+  EXPECT_EQ(nullptr, rfh_a->GetParentOrOuterDocument());
+  EXPECT_EQ(rfh_a, rfh_b->GetParentOrOuterDocument());
+  EXPECT_EQ(rfh_b, rfh_c->GetParentOrOuterDocument());
+  EXPECT_EQ(rfh_a, rfh_d->GetParentOrOuterDocument());
+  EXPECT_EQ(rfh_a, rfh_a->GetOutermostMainFrame());
+  EXPECT_EQ(rfh_a, rfh_b->GetOutermostMainFrame());
+  EXPECT_EQ(rfh_a, rfh_c->GetOutermostMainFrame());
+  EXPECT_EQ(rfh_a, rfh_d->GetOutermostMainFrame());
+  EXPECT_EQ(rfh_a, rfh_a->GetOutermostMainFrameOrEmbedder());
+  EXPECT_EQ(rfh_a, rfh_b->GetOutermostMainFrameOrEmbedder());
+  EXPECT_EQ(rfh_a, rfh_c->GetOutermostMainFrameOrEmbedder());
+  EXPECT_EQ(rfh_a, rfh_d->GetOutermostMainFrameOrEmbedder());
 }
 
 // Tests that RenderFrameHost::ForEachRenderFrameHost does not expose
@@ -5817,8 +5830,12 @@
   EXPECT_THAT(CollectAllRenderFrameHosts(rfh_a),
               testing::ElementsAre(rfh_a, rfh_b));
   EXPECT_EQ(nullptr, rfh_b->GetParent());
+  // Note that since this is a generic test inner WebContents, whether it's
+  // considered an outer document or embedder is just an implementation detail.
   EXPECT_EQ(nullptr, rfh_b->GetParentOrOuterDocument());
+  EXPECT_EQ(rfh_b, rfh_b->GetOutermostMainFrame());
   EXPECT_EQ(rfh_a, rfh_b->GetParentOrOuterDocumentOrEmbedder());
+  EXPECT_EQ(rfh_a, rfh_b->GetOutermostMainFrameOrEmbedder());
 }
 
 IN_PROC_BROWSER_TEST_F(RenderFrameHostImplBrowserTest,
diff --git a/content/browser/service_worker/service_worker_version_browsertest.cc b/content/browser/service_worker/service_worker_version_browsertest.cc
index 3537c2308..57708e2 100644
--- a/content/browser/service_worker/service_worker_version_browsertest.cc
+++ b/content/browser/service_worker/service_worker_version_browsertest.cc
@@ -393,11 +393,20 @@
       const std::string& path,
       ServiceWorkerFetchDispatcher::FetchEventResult* result,
       blink::mojom::FetchAPIResponsePtr* response) {
+    FetchOnRegisteredWorker(path, "", result, response);
+  }
+
+  void FetchOnRegisteredWorker(
+      const std::string& path,
+      const std::string& range_header,
+      ServiceWorkerFetchDispatcher::FetchEventResult* result,
+      blink::mojom::FetchAPIResponsePtr* response) {
     bool prepare_result = false;
     FetchResult fetch_result;
     fetch_result.status = blink::ServiceWorkerStatusCode::kErrorFailed;
     base::RunLoop fetch_run_loop;
-    Fetch(fetch_run_loop.QuitClosure(), path, &prepare_result, &fetch_result);
+    Fetch(fetch_run_loop.QuitClosure(), path, range_header, &prepare_result,
+          &fetch_result);
     fetch_run_loop.Run();
     ASSERT_TRUE(prepare_result);
     *result = fetch_result.result;
@@ -622,6 +631,7 @@
 
   void Fetch(base::OnceClosure done,
              const std::string& path,
+             const std::string& range_header,
              bool* prepare_result,
              FetchResult* result) {
     ASSERT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::UI));
@@ -635,6 +645,9 @@
     auto request = blink::mojom::FetchAPIRequest::New();
     request->url = url;
     request->method = "GET";
+    if (!range_header.empty()) {
+      request->headers[net::HttpRequestHeaders::kRange] = range_header;
+    }
     fetch_dispatcher_ = std::make_unique<ServiceWorkerFetchDispatcher>(
         std::move(request), destination, std::string() /* client_id */,
         version_, std::move(prepare_callback), std::move(fetch_callback),
@@ -1497,7 +1510,14 @@
 class CacheStorageEagerReadingTest : public ServiceWorkerVersionBrowserTest {
  public:
   void SetupServiceWorkerAndDoFetch(
-      std::string fetch_url,
+      const std::string& fetch_url,
+      blink::mojom::FetchAPIResponsePtr* response_out) {
+    SetupServiceWorkerAndDoFetch(fetch_url, "", response_out);
+  }
+
+  void SetupServiceWorkerAndDoFetch(
+      const std::string& fetch_url,
+      const std::string& range_header,
       blink::mojom::FetchAPIResponsePtr* response_out) {
     StartServerAndNavigateToSetup();
     ASSERT_EQ(Install("/service_worker/cached_fetch_event.js"),
@@ -1505,7 +1525,7 @@
     ASSERT_EQ(Activate(), blink::ServiceWorkerStatusCode::kOk);
 
     ServiceWorkerFetchDispatcher::FetchEventResult result;
-    FetchOnRegisteredWorker(fetch_url, &result, response_out);
+    FetchOnRegisteredWorker(fetch_url, range_header, &result, response_out);
   }
 
   void ExpectNormalCacheResponse(blink::mojom::FetchAPIResponsePtr response) {
@@ -1569,4 +1589,12 @@
   ExpectNormalCacheResponse(std::move(response));
 }
 
+IN_PROC_BROWSER_TEST_F(CacheStorageEagerReadingTest,
+                       CacheMatchInRelatedFetchEventWithRangeRequest) {
+  blink::mojom::FetchAPIResponsePtr response;
+  SetupServiceWorkerAndDoFetch(kCacheMatchURL, "bytes=0-8", &response);
+  EXPECT_TRUE(response);
+  ExpectNormalCacheResponse(std::move(response));
+}
+
 }  // namespace content
diff --git a/content/browser/site_per_process_hit_test_browsertest.cc b/content/browser/site_per_process_hit_test_browsertest.cc
index 403e4c98..6e94cbe 100644
--- a/content/browser/site_per_process_hit_test_browsertest.cc
+++ b/content/browser/site_per_process_hit_test_browsertest.cc
@@ -650,13 +650,12 @@
   base::DictionaryValue* root;
   if (!value->GetAsDictionary(&root))
     return false;
-  double x, y;
-  if (!root->GetDouble("x", &x))
+  absl::optional<double> x = root->FindDoubleKey("x");
+  absl::optional<double> y = root->FindDoubleKey("y");
+  if (!x || !y)
     return false;
-  if (!root->GetDouble("y", &y))
-    return false;
-  point->set_x(x);
-  point->set_y(y);
+  point->set_x(*x);
+  point->set_y(*y);
   return true;
 }
 
diff --git a/content/public/browser/idle_manager.h b/content/public/browser/idle_manager.h
index fd5b7dff..ee36970 100644
--- a/content/public/browser/idle_manager.h
+++ b/content/public/browser/idle_manager.h
@@ -15,26 +15,10 @@
 
 namespace content {
 
-class IdleTimeProvider;
-
 // Provides an interface for handling mojo connections from the renderer,
 // keeping track of clients that are monitoring the user's idle state.
 class IdleManager {
  public:
-  // Provides an interface for calculating a user's idle time and screen state.
-  class IdleTimeProvider {
-   public:
-    IdleTimeProvider() = default;
-    virtual ~IdleTimeProvider() = default;
-
-    IdleTimeProvider(const IdleTimeProvider&) = delete;
-    IdleTimeProvider& operator=(const IdleTimeProvider&) = delete;
-
-    // See ui/base/idle/idle.h for the semantics of these methods.
-    virtual base::TimeDelta CalculateIdleTime() = 0;
-    virtual bool CheckIdleStateIsLocked() = 0;
-  };
-
   IdleManager() = default;
   virtual ~IdleManager() = default;
 
diff --git a/content/public/browser/render_frame_host.h b/content/public/browser/render_frame_host.h
index a11af1d36..179302b 100644
--- a/content/public/browser/render_frame_host.h
+++ b/content/public/browser/render_frame_host.h
@@ -286,6 +286,17 @@
   // the primary main frame.
   virtual bool IsInPrimaryMainFrame() = 0;
 
+  // Returns the topmost ancestor RenderFrameHost of this RenderFrameHost. This
+  // includes any parents (in the case of subframes) and any outer documents
+  // (e.g. fenced frame owners), but does not traverse out of GuestViews.
+  // This can be used instead of GetMainFrame in cases where we want to escape
+  // inner pages. See also GetParentOrOuterDocument for more details on the
+  // distinction of "parents" and "outer documents."
+  // Note that this may be different from getting the WebContents' primary main
+  // frame. For example, if `this` is in a bfcached or prerendered page, this
+  // will return the cached/prerendered page's main RenderFrameHost.
+  virtual RenderFrameHost* GetOutermostMainFrame() = 0;
+
   // Fenced frames (meta-bug https://crbug.com/1111084):
   // Returns true if this document is the root of a fenced frame tree.
   //
diff --git a/content/renderer/skia_benchmarking_extension.cc b/content/renderer/skia_benchmarking_extension.cc
index e100ed8..120c2f9 100644
--- a/content/renderer/skia_benchmarking_extension.cc
+++ b/content/renderer/skia_benchmarking_extension.cc
@@ -186,7 +186,7 @@
 
     const base::DictionaryValue* params_dict = nullptr;
     if (params_value.get() && params_value->GetAsDictionary(&params_dict)) {
-      params_dict->GetDouble("scale", &scale);
+      scale = params_dict->FindDoubleKey("scale").value_or(scale);
       params_dict->GetInteger("stop", &stop_index);
 
       const base::Value* clip_value = nullptr;
diff --git a/content/test/data/accessibility/html/input-search-expected-blink.txt b/content/test/data/accessibility/html/input-search-expected-blink.txt
index 051c7b8..6756c6e 100644
--- a/content/test/data/accessibility/html/input-search-expected-blink.txt
+++ b/content/test/data/accessibility/html/input-search-expected-blink.txt
@@ -2,9 +2,6 @@
 ++genericContainer ignored
 ++++genericContainer
 ++++++searchBox value='Search terms'
-++++++++genericContainer ignored
-++++++++++genericContainer ignored
-++++++++++++genericContainer
-++++++++++++++staticText name='Search terms'
-++++++++++++++++inlineTextBox name='Search terms'
-++++++++++genericContainer ignored
\ No newline at end of file
+++++++++genericContainer
+++++++++++staticText name='Search terms'
+++++++++++++inlineTextBox name='Search terms'
diff --git a/content/test/data/accessibility/html/input-types-expected-blink.txt b/content/test/data/accessibility/html/input-types-expected-blink.txt
index 1e7143b..9497946 100644
--- a/content/test/data/accessibility/html/input-types-expected-blink.txt
+++ b/content/test/data/accessibility/html/input-types-expected-blink.txt
@@ -65,10 +65,7 @@
 ++++++++staticText name='Search: '
 ++++++++++inlineTextBox name='Search: '
 ++++++++searchBox editable name='Search:'
-++++++++++genericContainer ignored
-++++++++++++genericContainer ignored
-++++++++++++++genericContainer editable
-++++++++++++genericContainer ignored
+++++++++++genericContainer editable
 ++++++labelText
 ++++++++staticText name='Submit: '
 ++++++++++inlineTextBox name='Submit: '
diff --git a/content/test/data/accessibility/html/input-types-with-placeholder-expected-blink.txt b/content/test/data/accessibility/html/input-types-with-placeholder-expected-blink.txt
index 63b620a1..2ef1299 100644
--- a/content/test/data/accessibility/html/input-types-with-placeholder-expected-blink.txt
+++ b/content/test/data/accessibility/html/input-types-with-placeholder-expected-blink.txt
@@ -22,11 +22,7 @@
 ++++++++staticText name='Search: '
 ++++++++++inlineTextBox name='Search: '
 ++++++++searchBox editable name='Search:' placeholder='search'
-++++++++++genericContainer ignored
-++++++++++genericContainer ignored
-++++++++++++genericContainer ignored
-++++++++++++++genericContainer editable
-++++++++++++genericContainer ignored
+++++++++++genericContainer editable
 ++++++labelText
 ++++++++staticText name='Tel: '
 ++++++++++inlineTextBox name='Tel: '
@@ -44,4 +40,4 @@
 ++++++++++inlineTextBox name='Url: '
 ++++++++textField editable name='Url:' placeholder='https://www.example.com'
 ++++++++++genericContainer ignored
-++++++++++genericContainer editable
\ No newline at end of file
+++++++++++genericContainer editable
diff --git a/content/test/data/accessibility/html/input-types-with-value-and-placeholder-expected-blink.txt b/content/test/data/accessibility/html/input-types-with-value-and-placeholder-expected-blink.txt
index 0b32733..7e1ab2a4 100644
--- a/content/test/data/accessibility/html/input-types-with-value-and-placeholder-expected-blink.txt
+++ b/content/test/data/accessibility/html/input-types-with-value-and-placeholder-expected-blink.txt
@@ -30,12 +30,9 @@
 ++++++++++inlineTextBox name='Search: '
 ++++++++searchBox editable name='Search:' placeholder='placeholder' value='search'
 ++++++++++genericContainer ignored invisible
-++++++++++genericContainer ignored
-++++++++++++genericContainer ignored
-++++++++++++++genericContainer editable
-++++++++++++++++staticText editable name='search'
-++++++++++++++++++inlineTextBox editable name='search'
-++++++++++++genericContainer ignored
+++++++++++genericContainer editable
+++++++++++++staticText editable name='search'
+++++++++++++++inlineTextBox editable name='search'
 ++++++labelText
 ++++++++staticText name='Tel: '
 ++++++++++inlineTextBox name='Tel: '
@@ -59,4 +56,4 @@
 ++++++++++genericContainer ignored invisible
 ++++++++++genericContainer editable
 ++++++++++++staticText editable name='https://www.example.com'
-++++++++++++++inlineTextBox editable name='https://www.example.com'
\ No newline at end of file
+++++++++++++++inlineTextBox editable name='https://www.example.com'
diff --git a/content/test/data/accessibility/html/input-types-with-value-expected-blink.txt b/content/test/data/accessibility/html/input-types-with-value-expected-blink.txt
index 4718483..115a019 100644
--- a/content/test/data/accessibility/html/input-types-with-value-expected-blink.txt
+++ b/content/test/data/accessibility/html/input-types-with-value-expected-blink.txt
@@ -26,12 +26,9 @@
 ++++++++staticText name='Search: '
 ++++++++++inlineTextBox name='Search: '
 ++++++++searchBox editable name='Search:' value='search'
-++++++++++genericContainer ignored
-++++++++++++genericContainer ignored
-++++++++++++++genericContainer editable
-++++++++++++++++staticText editable name='search'
-++++++++++++++++++inlineTextBox editable name='search'
-++++++++++++genericContainer ignored
+++++++++++genericContainer editable
+++++++++++++staticText editable name='search'
+++++++++++++++inlineTextBox editable name='search'
 ++++++labelText
 ++++++++staticText name='Tel: '
 ++++++++++inlineTextBox name='Tel: '
diff --git a/content/test/data/service_worker/cached_fetch_event.js b/content/test/data/service_worker/cached_fetch_event.js
index 890e51f..3f44d7f 100644
--- a/content/test/data/service_worker/cached_fetch_event.js
+++ b/content/test/data/service_worker/cached_fetch_event.js
@@ -15,6 +15,6 @@
 self.addEventListener('fetch', evt => {
   evt.respondWith(async function() {
     const c = await caches.open(name);
-    return c.match(resource);
+    return c.match(new Request(resource, { headers: evt.request.headers }));
   }());
 });
diff --git a/content/test/gpu/unexpected_pass_finder.py b/content/test/gpu/unexpected_pass_finder.py
index 7e94185f..c13df5a 100755
--- a/content/test/gpu/unexpected_pass_finder.py
+++ b/content/test/gpu/unexpected_pass_finder.py
@@ -206,9 +206,9 @@
   stale_message = ''
   if args.remove_stale_expectations:
     stale_expectations = []
-    for _, expectation_map in stale.items():
+    for expectation_file, expectation_map in stale.items():
       stale_expectations.extend(expectation_map.keys())
-    stale_expectations.extend(unused_expectations)
+      stale_expectations.extend(unused_expectations.get(expectation_file, []))
     affected_urls |= expectations_instance.RemoveExpectationsFromFile(
         stale_expectations, args.expectation_file)
     stale_message += ('Stale expectations removed from %s. Stale comments, '
diff --git a/extensions/browser/api/declarative_net_request/rules_monitor_service.cc b/extensions/browser/api/declarative_net_request/rules_monitor_service.cc
index 0706ac67..237ed89 100644
--- a/extensions/browser/api/declarative_net_request/rules_monitor_service.cc
+++ b/extensions/browser/api/declarative_net_request/rules_monitor_service.cc
@@ -88,8 +88,7 @@
                                      UnloadedExtensionReason reason) {
   if (reason == UnloadedExtensionReason::DISABLE) {
     static constexpr int kReleaseAllocationDisableReasons =
-        disable_reason::DISABLE_BLOCKED_BY_POLICY |
-        disable_reason::DISABLE_REMOTELY_FOR_MALWARE;
+        disable_reason::DISABLE_BLOCKED_BY_POLICY;
 
     // Release allocation on reload of an unpacked extension and treat it as a
     // new install since the extension directory's contents may have changed.
diff --git a/extensions/browser/disable_reason.h b/extensions/browser/disable_reason.h
index 1c62515..622b56d 100644
--- a/extensions/browser/disable_reason.h
+++ b/extensions/browser/disable_reason.h
@@ -45,8 +45,9 @@
   // Blocked due to management policy.
   DISABLE_BLOCKED_BY_POLICY = 1 << 16,
   // DISABLE_BLOCKED_MATURE = 1 << 17, // Deprecated.
-  // Remotely disabled due to malware.
-  DISABLE_REMOTELY_FOR_MALWARE = 1 << 18,
+  // TODO(crbug.com/1193695): Replaced by kPrefOmahaBlocklistState. Remove this
+  // entirely once clients are migrated over, around M99.
+  DEPRECATED_DISABLE_REMOTELY_FOR_MALWARE = 1 << 18,
   DISABLE_REINSTALL = 1 << 19,
   // Disabled by Safe Browsing extension allowlist enforcement.
   DISABLE_NOT_ALLOWLISTED = 1 << 20,
diff --git a/extensions/browser/extension_prefs.cc b/extensions/browser/extension_prefs.cc
index 83efb76..e92591e 100644
--- a/extensions/browser/extension_prefs.cc
+++ b/extensions/browser/extension_prefs.cc
@@ -2697,6 +2697,19 @@
       legacy_pref_cleared = true;
     }
 
+    if (HasDisableReason(
+            extension_id,
+            disable_reason::DEPRECATED_DISABLE_REMOTELY_FOR_MALWARE)) {
+      // Migrate the old value.
+      blocklist_prefs::AddOmahaBlocklistState(
+          extension_id, BitMapBlocklistState::BLOCKLISTED_MALWARE, this);
+      // Clear the legacy pref.
+      RemoveDisableReason(
+          extension_id,
+          disable_reason::DEPRECATED_DISABLE_REMOTELY_FOR_MALWARE);
+      legacy_pref_cleared = true;
+    }
+
     if (legacy_pref_cleared)
       DeleteExtensionPrefsIfPrefEmpty(extension_id);
   }
diff --git a/extensions/common/api/messaging/message.h b/extensions/common/api/messaging/message.h
index c3ff586..8c6cc97 100644
--- a/extensions/common/api/messaging/message.h
+++ b/extensions/common/api/messaging/message.h
@@ -5,26 +5,32 @@
 #ifndef EXTENSIONS_COMMON_API_MESSAGING_MESSAGE_H_
 #define EXTENSIONS_COMMON_API_MESSAGING_MESSAGE_H_
 
+#include "extensions/common/api/messaging/serialization_format.h"
+
 namespace extensions {
 
 // A message consists of both the data itself as well as a user gesture state.
 struct Message {
   std::string data;
+  SerializationFormat format = SerializationFormat::kJson;
   bool user_gesture = false;
   bool from_privileged_context = false;
 
   Message() = default;
   Message(const std::string& data,
+          SerializationFormat format,
           bool user_gesture,
           bool from_privileged_context = false)
       : data(data),
+        format(format),
         user_gesture(user_gesture),
         from_privileged_context(from_privileged_context) {}
 
   bool operator==(const Message& other) const {
     // Skipping the equality check for |from_privileged_context| here
     // because this field is used only for histograms.
-    return data == other.data && user_gesture == other.user_gesture;
+    return data == other.data && user_gesture == other.user_gesture &&
+           format == other.format;
   }
 };
 
diff --git a/extensions/common/api/messaging/port_id.cc b/extensions/common/api/messaging/port_id.cc
index 105ff9f..df7f6435 100644
--- a/extensions/common/api/messaging/port_id.cc
+++ b/extensions/common/api/messaging/port_id.cc
@@ -11,8 +11,12 @@
 PortId::PortId() {}
 PortId::PortId(const base::UnguessableToken& context_id,
                int port_number,
-               bool is_opener)
-    : context_id(context_id), port_number(port_number), is_opener(is_opener) {}
+               bool is_opener,
+               SerializationFormat format)
+    : context_id(context_id),
+      port_number(port_number),
+      is_opener(is_opener),
+      serialization_format(format) {}
 PortId::~PortId() {}
 PortId::PortId(PortId&& other) = default;
 PortId::PortId(const PortId& other) = default;
@@ -20,12 +24,14 @@
 
 bool PortId::operator==(const PortId& other) const {
   return context_id == other.context_id && port_number == other.port_number &&
-         is_opener == other.is_opener;
+         is_opener == other.is_opener &&
+         serialization_format == other.serialization_format;
 }
 
 bool PortId::operator<(const PortId& other) const {
-  return std::tie(context_id, port_number, is_opener) <
-         std::tie(other.context_id, other.port_number, other.is_opener);
+  return std::tie(context_id, port_number, is_opener, serialization_format) <
+         std::tie(other.context_id, other.port_number, other.is_opener,
+                  other.serialization_format);
 }
 
 }  // namespace extensions
diff --git a/extensions/common/api/messaging/port_id.h b/extensions/common/api/messaging/port_id.h
index e44aa1f..1856efa 100644
--- a/extensions/common/api/messaging/port_id.h
+++ b/extensions/common/api/messaging/port_id.h
@@ -8,6 +8,7 @@
 #include <utility>
 
 #include "base/unguessable_token.h"
+#include "extensions/common/api/messaging/serialization_format.h"
 
 namespace extensions {
 
@@ -26,6 +27,8 @@
 //                port with the number '1' in each.
 // - is_opener: Whether or not this port id is for the opener port (false
 //              indicating it is the receiver port).
+// Additionally, this also holds `serialization_format` which is the
+// preferred SerializationFormat to be used for messages sent by this port.
 // A few more notes:
 // - There should only be a single existent opener port. That is, in all the
 //   contexts, there should only be one with a given context_id, port_number,
@@ -41,16 +44,21 @@
 //   this means that multiple contexts could have the same id. However, GUIDs
 //   are sufficiently random as to be globally unique in practice (the chance
 //   of a duplicate is about the same as the sun exploding right now).
+// - The `serialization_format` for a port may sometimes differ from that of the
+//   Messages sent on the port. For example this can happen if a message can't
+//   be structure cloned in which case we fallback to JSON.
 struct PortId {
   // See class comments for the description of these fields.
   base::UnguessableToken context_id;
   int port_number = 0;
   bool is_opener = false;
+  SerializationFormat serialization_format = SerializationFormat::kJson;
 
   PortId();
   PortId(const base::UnguessableToken& context_id,
          int port_number,
-         bool is_opener);
+         bool is_opener,
+         SerializationFormat format);
   ~PortId();
   PortId(PortId&& other);
   PortId(const PortId& other);
@@ -64,7 +72,7 @@
   // receiver port returns the ID of the opener, and a call on the opener port
   // returns the ID of all receivers.
   PortId GetOppositePortId() const {
-    return PortId(context_id, port_number, !is_opener);
+    return PortId(context_id, port_number, !is_opener, serialization_format);
   }
 
   bool operator==(const PortId& other) const;
diff --git a/extensions/common/api/messaging/serialization_format.h b/extensions/common/api/messaging/serialization_format.h
new file mode 100644
index 0000000..69eb17f
--- /dev/null
+++ b/extensions/common/api/messaging/serialization_format.h
@@ -0,0 +1,18 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_API_MESSAGING_SERIALIZATION_FORMAT_H_
+#define EXTENSIONS_COMMON_API_MESSAGING_SERIALIZATION_FORMAT_H_
+
+namespace extensions {
+
+enum class SerializationFormat {
+  kStructuredCloned,
+  kJson,
+  kLast = kJson,
+};
+
+}  // namespace extensions
+
+#endif  // EXTENSIONS_COMMON_API_MESSAGING_SERIALIZATION_FORMAT_H_
diff --git a/extensions/common/extension_features.cc b/extensions/common/extension_features.cc
index 69ebb98..d18fe4d 100644
--- a/extensions/common/extension_features.cc
+++ b/extensions/common/extension_features.cc
@@ -82,4 +82,11 @@
 // crbug.com/1173354.
 const base::Feature kAllowWasmInMV3{"AllowWasmInMV3",
                                     base::FEATURE_DISABLED_BY_DEFAULT};
+
+// When enabled, causes Manifest V3 (and greater) extensions to use structured
+// cloning (instead of JSON serialization) for extension messaging, except when
+// communicating with native messaging hosts.
+const base::Feature kStructuredCloningForMV3Messaging{
+    "StructuredCloningForMV3Messaging", base::FEATURE_DISABLED_BY_DEFAULT};
+
 }  // namespace extensions_features
diff --git a/extensions/common/extension_features.h b/extensions/common/extension_features.h
index 9abf282..b2975d0 100644
--- a/extensions/common/extension_features.h
+++ b/extensions/common/extension_features.h
@@ -32,6 +32,8 @@
 
 extern const base::Feature kAllowWasmInMV3;
 
+extern const base::Feature kStructuredCloningForMV3Messaging;
+
 }  // namespace extensions_features
 
 #endif  // EXTENSIONS_COMMON_EXTENSION_FEATURES_H_
diff --git a/extensions/common/extension_messages.h b/extensions/common/extension_messages.h
index 0ac92f3..5ca3dd9 100644
--- a/extensions/common/extension_messages.h
+++ b/extensions/common/extension_messages.h
@@ -25,6 +25,7 @@
 #include "extensions/common/api/messaging/messaging_endpoint.h"
 #include "extensions/common/api/messaging/port_context.h"
 #include "extensions/common/api/messaging/port_id.h"
+#include "extensions/common/api/messaging/serialization_format.h"
 #include "extensions/common/common_param_traits.h"
 #include "extensions/common/constants.h"
 #include "extensions/common/draggable_region.h"
@@ -66,6 +67,9 @@
 IPC_ENUM_TRAITS_MAX_VALUE(extensions::MessagingEndpoint::Type,
                           extensions::MessagingEndpoint::Type::kLast)
 
+IPC_ENUM_TRAITS_MAX_VALUE(extensions::SerializationFormat,
+                          extensions::SerializationFormat::kLast)
+
 // Parameters structure for ExtensionHostMsg_AddAPIActionToActivityLog and
 // ExtensionHostMsg_AddEventToActivityLog.
 IPC_STRUCT_BEGIN(ExtensionHostMsg_APIActionOrEvent_Params)
@@ -255,6 +259,7 @@
 
 IPC_STRUCT_TRAITS_BEGIN(extensions::Message)
   IPC_STRUCT_TRAITS_MEMBER(data)
+  IPC_STRUCT_TRAITS_MEMBER(format)
   IPC_STRUCT_TRAITS_MEMBER(user_gesture)
 IPC_STRUCT_TRAITS_END()
 
@@ -262,6 +267,7 @@
   IPC_STRUCT_TRAITS_MEMBER(context_id)
   IPC_STRUCT_TRAITS_MEMBER(port_number)
   IPC_STRUCT_TRAITS_MEMBER(is_opener)
+  IPC_STRUCT_TRAITS_MEMBER(serialization_format)
 IPC_STRUCT_TRAITS_END()
 
 IPC_STRUCT_TRAITS_BEGIN(extensions::EventFilteringInfo)
diff --git a/extensions/renderer/bindings/api_binding_hooks.cc b/extensions/renderer/bindings/api_binding_hooks.cc
index 2fa599a..b6c9406 100644
--- a/extensions/renderer/bindings/api_binding_hooks.cc
+++ b/extensions/renderer/bindings/api_binding_hooks.cc
@@ -377,21 +377,6 @@
   return GetJSHookInterfaceObject(api_name_, context, true);
 }
 
-v8::Local<v8::Function> APIBindingHooks::GetCustomJSCallback(
-    const std::string& name,
-    v8::Local<v8::Context> context) {
-  v8::Local<v8::Object> hooks =
-      GetJSHookInterfaceObject(api_name_, context, false);
-  if (hooks.IsEmpty())
-    return v8::Local<v8::Function>();
-  JSHookInterface* hook_interface = nullptr;
-  gin::Converter<JSHookInterface*>::FromV8(context->GetIsolate(), hooks,
-                                           &hook_interface);
-  CHECK(hook_interface);
-
-  return hook_interface->GetCustomCallback(name, context->GetIsolate());
-}
-
 bool APIBindingHooks::CreateCustomEvent(v8::Local<v8::Context> context,
                                         const std::string& event_name,
                                         v8::Local<v8::Value>* event_out) {
diff --git a/extensions/renderer/bindings/api_binding_hooks.h b/extensions/renderer/bindings/api_binding_hooks.h
index f0f35c2..d2dd8933 100644
--- a/extensions/renderer/bindings/api_binding_hooks.h
+++ b/extensions/renderer/bindings/api_binding_hooks.h
@@ -68,10 +68,6 @@
   // Returns a JS interface that can be used to register hooks.
   v8::Local<v8::Object> GetJSHookInterface(v8::Local<v8::Context> context);
 
-  // Gets the custom-set JS callback for the given method, if one exists.
-  v8::Local<v8::Function> GetCustomJSCallback(const std::string& method_name,
-                                              v8::Local<v8::Context> context);
-
   // Creates a new JS event for the given |event_name|, if a custom event is
   // provided. Returns true if an event was created.
   bool CreateCustomEvent(v8::Local<v8::Context> context,
diff --git a/extensions/renderer/gin_port.cc b/extensions/renderer/gin_port.cc
index a7dd9a0..bf9aa0a 100644
--- a/extensions/renderer/gin_port.cc
+++ b/extensions/renderer/gin_port.cc
@@ -157,8 +157,8 @@
   }
 
   std::string error;
-  std::unique_ptr<Message> message =
-      messaging_util::MessageFromV8(context, v8_message, &error);
+  std::unique_ptr<Message> message = messaging_util::MessageFromV8(
+      context, v8_message, port_id_.serialization_format, &error);
   // NOTE(devlin): JS-based bindings just log to the console here and return,
   // rather than throwing an error. But it really seems like it should be an
   // error. Let's see how this goes.
diff --git a/extensions/renderer/gin_port.h b/extensions/renderer/gin_port.h
index 77a4af1..30a7cdc 100644
--- a/extensions/renderer/gin_port.h
+++ b/extensions/renderer/gin_port.h
@@ -132,14 +132,14 @@
   State state_ = kActive;
 
   // The associated port id.
-  PortId port_id_;
+  const PortId port_id_;
 
   // The routing id associated with the port's context's render frame.
   // TODO(devlin/lazyboy): This won't work with service workers.
-  int routing_id_;
+  const int routing_id_;
 
   // The port's name.
-  std::string name_;
+  const std::string name_;
 
   // The associated APIEventHandler. Guaranteed to outlive this object.
   APIEventHandler* const event_handler_;
diff --git a/extensions/renderer/gin_port_unittest.cc b/extensions/renderer/gin_port_unittest.cc
index 161ee88..8ea5d376 100644
--- a/extensions/renderer/gin_port_unittest.cc
+++ b/extensions/renderer/gin_port_unittest.cc
@@ -12,6 +12,7 @@
 #include "content/public/common/content_features.h"
 #include "extensions/common/api/messaging/message.h"
 #include "extensions/common/api/messaging/port_id.h"
+#include "extensions/common/api/messaging/serialization_format.h"
 #include "extensions/renderer/bindings/api_binding_test.h"
 #include "extensions/renderer/bindings/api_binding_test_util.h"
 #include "extensions/renderer/bindings/api_event_handler.h"
@@ -115,7 +116,8 @@
   v8::HandleScope handle_scope(isolate());
   v8::Local<v8::Context> context = MainContext();
 
-  PortId port_id(base::UnguessableToken::Create(), 0, true);
+  PortId port_id(base::UnguessableToken::Create(), 0, true,
+                 SerializationFormat::kJson);
   gin::Handle<GinPort> port = CreatePort(context, port_id);
 
   v8::Local<v8::Object> port_obj = port.ToV8().As<v8::Object>();
@@ -129,7 +131,8 @@
   v8::HandleScope handle_scope(isolate());
   v8::Local<v8::Context> context = MainContext();
 
-  PortId port_id(base::UnguessableToken::Create(), 0, true);
+  PortId port_id(base::UnguessableToken::Create(), 0, true,
+                 SerializationFormat::kJson);
   gin::Handle<GinPort> port = CreatePort(context, port_id);
 
   v8::Local<v8::Object> port_obj = port.ToV8().As<v8::Object>();
@@ -150,7 +153,8 @@
   v8::Local<v8::Value> args[] = {port_obj};
   RunFunctionOnGlobal(test_function, context, base::size(args), args);
 
-  port->DispatchOnMessage(context, Message(R"({"foo":42})", false));
+  port->DispatchOnMessage(
+      context, Message(R"({"foo":42})", SerializationFormat::kJson, false));
 
   EXPECT_EQ("true", GetStringPropertyFromObject(context->Global(), context,
                                                 "messageValid"));
@@ -163,7 +167,8 @@
   v8::HandleScope handle_scope(isolate());
   v8::Local<v8::Context> context = MainContext();
 
-  PortId port_id(base::UnguessableToken::Create(), 0, true);
+  PortId port_id(base::UnguessableToken::Create(), 0, true,
+                 SerializationFormat::kJson);
   gin::Handle<GinPort> port = CreatePort(context, port_id);
 
   v8::Local<v8::Object> port_obj = port.ToV8().As<v8::Object>();
@@ -200,7 +205,9 @@
     // Simple message; should succeed.
     const char kFunction[] =
         "(function(port) { port.postMessage({data: [42]}); })";
-    test_post_message(kFunction, port_id, Message(R"({"data":[42]})", false));
+    test_post_message(
+        kFunction, port_id,
+        Message(R"({"data":[42]})", SerializationFormat::kJson, false));
 
     // TODO(mustaq): We need a test with Message.user_gesture == true.
   }
@@ -208,7 +215,8 @@
   {
     // Simple non-object message; should succeed.
     const char kFunction[] = "(function(port) { port.postMessage('hello'); })";
-    test_post_message(kFunction, port_id, Message(R"("hello")", false));
+    test_post_message(kFunction, port_id,
+                      Message(R"("hello")", SerializationFormat::kJson, false));
   }
 
   {
@@ -216,14 +224,17 @@
     // stringify result "undefined"); should succeed.
     const char kFunction[] =
         "(function(port) { port.postMessage('undefined'); })";
-    test_post_message(kFunction, port_id, Message(R"("undefined")", false));
+    test_post_message(
+        kFunction, port_id,
+        Message(R"("undefined")", SerializationFormat::kJson, false));
   }
 
   {
     // We change undefined to null; see comment in gin_port.cc.
     const char kFunction[] =
         "(function(port) { port.postMessage(undefined); })";
-    test_post_message(kFunction, port_id, Message("null", false));
+    test_post_message(kFunction, port_id,
+                      Message("null", SerializationFormat::kJson, false));
   }
 
   {
@@ -261,7 +272,8 @@
   v8::HandleScope handle_scope(isolate());
   v8::Local<v8::Context> context = MainContext();
 
-  PortId port_id(base::UnguessableToken::Create(), 0, true);
+  PortId port_id(base::UnguessableToken::Create(), 0, true,
+                 SerializationFormat::kJson);
   gin::Handle<GinPort> port = CreatePort(context, port_id);
 
   v8::Local<v8::Object> port_obj = port.ToV8().As<v8::Object>();
@@ -289,7 +301,8 @@
   v8::HandleScope handle_scope(isolate());
   v8::Local<v8::Context> context = MainContext();
 
-  PortId port_id(base::UnguessableToken::Create(), 0, true);
+  PortId port_id(base::UnguessableToken::Create(), 0, true,
+                 SerializationFormat::kJson);
   gin::Handle<GinPort> port = CreatePort(context, port_id);
 
   v8::Local<v8::Object> port_obj = port.ToV8().As<v8::Object>();
@@ -310,7 +323,8 @@
   v8::HandleScope handle_scope(isolate());
   v8::Local<v8::Context> context = MainContext();
 
-  PortId port_id(base::UnguessableToken::Create(), 0, true);
+  PortId port_id(base::UnguessableToken::Create(), 0, true,
+                 SerializationFormat::kJson);
   gin::Handle<GinPort> port = CreatePort(context, port_id);
 
   v8::Local<v8::Object> port_obj = port.ToV8().As<v8::Object>();
@@ -336,7 +350,8 @@
   v8::HandleScope handle_scope(isolate());
   v8::Local<v8::Context> context = MainContext();
 
-  PortId port_id(base::UnguessableToken::Create(), 0, true);
+  PortId port_id(base::UnguessableToken::Create(), 0, true,
+                 SerializationFormat::kJson);
   gin::Handle<GinPort> port = CreatePort(context, port_id);
 
   v8::Local<v8::Object> port_obj = port.ToV8().As<v8::Object>();
@@ -368,7 +383,8 @@
   v8::HandleScope handle_scope(isolate());
   v8::Local<v8::Context> context = MainContext();
 
-  PortId port_id(base::UnguessableToken::Create(), 0, true);
+  PortId port_id(base::UnguessableToken::Create(), 0, true,
+                 SerializationFormat::kJson);
 
   {
     gin::Handle<GinPort> port = CreatePort(context, port_id);
@@ -393,7 +409,8 @@
   v8::HandleScope handle_scope(isolate());
   v8::Local<v8::Context> context = MainContext();
 
-  PortId port_id(base::UnguessableToken::Create(), 0, true);
+  PortId port_id(base::UnguessableToken::Create(), 0, true,
+                 SerializationFormat::kJson);
   gin::Handle<GinPort> port = CreatePort(context, port_id);
 
   v8::Local<v8::Object> port_obj = port.ToV8().As<v8::Object>();
@@ -435,7 +452,8 @@
   v8::HandleScope handle_scope(isolate());
   v8::Local<v8::Context> context = MainContext();
 
-  PortId port_id(base::UnguessableToken::Create(), 0, true);
+  PortId port_id(base::UnguessableToken::Create(), 0, true,
+                 SerializationFormat::kJson);
   gin::Handle<GinPort> port = CreatePort(context, port_id);
 
   v8::Local<v8::Object> port_obj = port.ToV8().As<v8::Object>();
diff --git a/extensions/renderer/messaging_util.cc b/extensions/renderer/messaging_util.cc
index a812649a..d85957d 100644
--- a/extensions/renderer/messaging_util.cc
+++ b/extensions/renderer/messaging_util.cc
@@ -12,7 +12,9 @@
 #include "base/strings/stringprintf.h"
 #include "components/crx_file/id_util.h"
 #include "extensions/common/api/messaging/message.h"
+#include "extensions/common/api/messaging/serialization_format.h"
 #include "extensions/common/extension.h"
+#include "extensions/common/extension_features.h"
 #include "extensions/common/manifest.h"
 #include "extensions/common/manifest_handlers/background_info.h"
 #include "extensions/renderer/get_script_context.h"
@@ -79,7 +81,8 @@
   bool has_unrestricted_user_activation =
       web_frame && web_frame->HasTransientUserActivation() &&
       !web_frame->LastActivationWasRestricted();
-  return std::make_unique<Message>(message, has_unrestricted_user_activation,
+  return std::make_unique<Message>(message, SerializationFormat::kJson,
+                                   has_unrestricted_user_activation,
                                    privileged_context);
 }
 
@@ -100,7 +103,9 @@
 
 std::unique_ptr<Message> MessageFromV8(v8::Local<v8::Context> context,
                                        v8::Local<v8::Value> value,
+                                       SerializationFormat format,
                                        std::string* error_out) {
+  // TODO(crbug.com/248548): Incorporate `format` while serializing the message.
   DCHECK(!value.IsEmpty());
   v8::Isolate* isolate = context->GetIsolate();
   v8::Context::Scope context_scope(context);
@@ -141,6 +146,9 @@
 
 v8::Local<v8::Value> MessageToV8(v8::Local<v8::Context> context,
                                  const Message& message) {
+  // TODO(crbug.com/248548): Incorporate `message.format` while deserializing
+  // the message.
+
   v8::Isolate* isolate = context->GetIsolate();
   v8::Context::Scope context_scope(context);
 
@@ -164,6 +172,19 @@
   return 0;
 }
 
+SerializationFormat GetSerializationFormat(
+    const ScriptContext& script_context) {
+  if (!base::FeatureList::IsEnabled(
+          extensions_features::kStructuredCloningForMV3Messaging)) {
+    return SerializationFormat::kJson;
+  }
+
+  const Extension* extension = script_context.extension();
+  return extension && extension->manifest_version() >= 3
+             ? SerializationFormat::kStructuredCloned
+             : SerializationFormat::kJson;
+}
+
 MessageOptions ParseMessageOptions(v8::Local<v8::Context> context,
                                    v8::Local<v8::Object> v8_options,
                                    int flags) {
diff --git a/extensions/renderer/messaging_util.h b/extensions/renderer/messaging_util.h
index 3ef6669..007b3a8 100644
--- a/extensions/renderer/messaging_util.h
+++ b/extensions/renderer/messaging_util.h
@@ -16,6 +16,7 @@
 }
 
 namespace extensions {
+enum class SerializationFormat;
 class ScriptContext;
 struct Message;
 
@@ -40,6 +41,7 @@
 // will populate |error_out|.
 std::unique_ptr<Message> MessageFromV8(v8::Local<v8::Context> context,
                                        v8::Local<v8::Value> value,
+                                       SerializationFormat format,
                                        std::string* error);
 
 // Converts a message to a v8 value. This is expected not to fail, since it
@@ -52,6 +54,11 @@
 // |value| is either an int32 or -0.
 int ExtractIntegerId(v8::Local<v8::Value> value);
 
+// Returns the preferred serialization format for the given `context`. Note
+// extension native messaging clients shouldn't call this as they should always
+// use JSON.
+SerializationFormat GetSerializationFormat(const ScriptContext& context);
+
 // Flags for ParseMessageOptions().
 enum ParseOptionsFlags {
   NO_FLAGS = 0,
diff --git a/extensions/renderer/messaging_util_unittest.cc b/extensions/renderer/messaging_util_unittest.cc
index eee132e9..73b2132d 100644
--- a/extensions/renderer/messaging_util_unittest.cc
+++ b/extensions/renderer/messaging_util_unittest.cc
@@ -9,6 +9,7 @@
 #include "base/cxx17_backports.h"
 #include "base/strings/stringprintf.h"
 #include "extensions/common/api/messaging/message.h"
+#include "extensions/common/api/messaging/serialization_format.h"
 #include "extensions/common/extension_builder.h"
 #include "extensions/renderer/bindings/api_binding_test.h"
 #include "extensions/renderer/bindings/api_binding_test_util.h"
@@ -31,8 +32,8 @@
   v8::Local<v8::Value> long_message =
       V8ValueFromScriptSource(context, "'a'.repeat(1024 *1024 * 65)");
   std::string error;
-  std::unique_ptr<Message> message =
-      messaging_util::MessageFromV8(context, long_message, &error);
+  std::unique_ptr<Message> message = messaging_util::MessageFromV8(
+      context, long_message, SerializationFormat::kJson, &error);
   EXPECT_FALSE(message);
   EXPECT_EQ(kMessageTooLongError, error);
 }
diff --git a/extensions/renderer/native_renderer_messaging_service.cc b/extensions/renderer/native_renderer_messaging_service.cc
index b79c49844..ec20ebb 100644
--- a/extensions/renderer/native_renderer_messaging_service.cc
+++ b/extensions/renderer/native_renderer_messaging_service.cc
@@ -164,7 +164,8 @@
 gin::Handle<GinPort> NativeRendererMessagingService::Connect(
     ScriptContext* script_context,
     const MessageTarget& target,
-    const std::string& channel_name) {
+    const std::string& channel_name,
+    SerializationFormat format) {
   if (!ScriptContextIsValid(script_context))
     return gin::Handle<GinPort>();
 
@@ -174,9 +175,10 @@
     return gin::Handle<GinPort>();
 
   bool is_opener = true;
-  gin::Handle<GinPort> port = CreatePort(
-      script_context, channel_name,
-      PortId(script_context->context_id(), data->next_port_id++, is_opener));
+  gin::Handle<GinPort> port =
+      CreatePort(script_context, channel_name,
+                 PortId(script_context->context_id(), data->next_port_id++,
+                        is_opener, format));
 
   bindings_system_->GetIPCMessageSender()->SendOpenMessageChannel(
       script_context, port->port_id(), target, channel_name);
@@ -196,7 +198,16 @@
       script_context->v8_context(), kCreateIfMissing);
 
   bool is_opener = true;
-  PortId port_id(script_context->context_id(), data->next_port_id++, is_opener);
+
+  // TODO(crbug.com/248548): Instead of inferring the SerializationFormat from
+  // Message, it'd be better to have the clients pass it directly. This is
+  // because, in case of `kStructuredCloned` to `kJson` fallback, the format for
+  // the ports will also be `kJson`. This is inconsistent with what we do for
+  // ports for long-lived channels where the port's `SerializationFormat` is
+  // always the same as that passed by messaging clients and is independent of
+  // any fallback behavior.
+  PortId port_id(script_context->context_id(), data->next_port_id++, is_opener,
+                 message.format);
 
   one_time_message_handler_.SendMessage(
       script_context, port_id, target, method_name, message, response_callback);
diff --git a/extensions/renderer/native_renderer_messaging_service.h b/extensions/renderer/native_renderer_messaging_service.h
index cfdbee8..ec09c0d 100644
--- a/extensions/renderer/native_renderer_messaging_service.h
+++ b/extensions/renderer/native_renderer_messaging_service.h
@@ -22,6 +22,7 @@
 }
 
 namespace extensions {
+enum class SerializationFormat;
 class NativeExtensionBindingsSystem;
 class ScriptContextSetIterable;
 struct Message;
@@ -109,7 +110,8 @@
   // Creates and opens a new message port in the specified context.
   gin::Handle<GinPort> Connect(ScriptContext* script_context,
                                const MessageTarget& target,
-                               const std::string& name);
+                               const std::string& name,
+                               SerializationFormat format);
 
   // Sends a one-time message, as is used by runtime.sendMessage.
   void SendOneTimeMessage(ScriptContext* script_context,
diff --git a/extensions/renderer/native_renderer_messaging_service_unittest.cc b/extensions/renderer/native_renderer_messaging_service_unittest.cc
index 831f326..c9f7517 100644
--- a/extensions/renderer/native_renderer_messaging_service_unittest.cc
+++ b/extensions/renderer/native_renderer_messaging_service_unittest.cc
@@ -12,6 +12,7 @@
 #include "components/crx_file/id_util.h"
 #include "content/public/common/child_process_host.h"
 #include "extensions/common/api/messaging/messaging_endpoint.h"
+#include "extensions/common/api/messaging/serialization_format.h"
 #include "extensions/common/extension.h"
 #include "extensions/common/extension_builder.h"
 #include "extensions/common/extension_messages.h"
@@ -80,7 +81,7 @@
   v8::HandleScope handle_scope(isolate());
 
   base::UnguessableToken other_context_id = base::UnguessableToken::Create();
-  const PortId port_id(other_context_id, 0, false);
+  const PortId port_id(other_context_id, 0, false, SerializationFormat::kJson);
 
   EXPECT_FALSE(
       messaging_service()->HasPortForTesting(script_context(), port_id));
@@ -105,7 +106,7 @@
   v8::Local<v8::Context> context = MainContext();
 
   base::UnguessableToken other_context_id = base::UnguessableToken::Create();
-  const PortId port_id(other_context_id, 0, false);
+  const PortId port_id(other_context_id, 0, false, SerializationFormat::kJson);
   EXPECT_FALSE(
       messaging_service()->HasPortForTesting(script_context(), port_id));
 
@@ -164,8 +165,8 @@
   v8::Local<v8::Context> context = MainContext();
 
   base::UnguessableToken other_context_id = base::UnguessableToken::Create();
-  const PortId port_id1(other_context_id, 0, false);
-  const PortId port_id2(other_context_id, 1, false);
+  const PortId port_id1(other_context_id, 0, false, SerializationFormat::kJson);
+  const PortId port_id2(other_context_id, 1, false, SerializationFormat::kJson);
 
   gin::Handle<GinPort> port1 = messaging_service()->CreatePortForTesting(
       script_context(), "channel1", port_id1);
@@ -205,8 +206,9 @@
             GetStringPropertyFromObject(global, context, kPort2Message));
 
   const char kMessageString[] = R"({"data":"hello"})";
-  messaging_service()->DeliverMessage(script_context_set(), port_id1,
-                                      Message(kMessageString, false), nullptr);
+  messaging_service()->DeliverMessage(
+      script_context_set(), port_id1,
+      Message(kMessageString, SerializationFormat::kJson, false), nullptr);
 
   // Only port1 should have been notified of the message (ports only receive
   // messages directed to themselves).
@@ -221,8 +223,8 @@
   v8::Local<v8::Context> context = MainContext();
 
   base::UnguessableToken other_context_id = base::UnguessableToken::Create();
-  const PortId port_id1(other_context_id, 0, false);
-  const PortId port_id2(other_context_id, 1, false);
+  const PortId port_id1(other_context_id, 0, false, SerializationFormat::kJson);
+  const PortId port_id2(other_context_id, 1, false, SerializationFormat::kJson);
 
   gin::Handle<GinPort> port1 = messaging_service()->CreatePortForTesting(
       script_context(), "channel1", port_id1);
@@ -274,7 +276,7 @@
   v8::Local<v8::Context> context = MainContext();
 
   base::UnguessableToken other_context_id = base::UnguessableToken::Create();
-  const PortId port_id(other_context_id, 0, false);
+  const PortId port_id(other_context_id, 0, false, SerializationFormat::kJson);
 
   gin::Handle<GinPort> port = messaging_service()->CreatePortForTesting(
       script_context(), "channel", port_id);
@@ -288,9 +290,10 @@
       FunctionFromString(context, kDispatchMessage);
   v8::Local<v8::Value> args[] = {port_object};
 
-  EXPECT_CALL(
-      *ipc_message_sender(),
-      SendPostMessageToPort(port_id, Message(R"({"data":"hello"})", false)));
+  EXPECT_CALL(*ipc_message_sender(),
+              SendPostMessageToPort(
+                  port_id, Message(R"({"data":"hello"})",
+                                   SerializationFormat::kJson, false)));
   RunFunctionOnGlobal(post_message, context, base::size(args), args);
   ::testing::Mock::VerifyAndClearExpectations(ipc_message_sender());
 }
@@ -300,7 +303,7 @@
   v8::Local<v8::Context> context = MainContext();
 
   base::UnguessableToken other_context_id = base::UnguessableToken::Create();
-  const PortId port_id(other_context_id, 0, false);
+  const PortId port_id(other_context_id, 0, false, SerializationFormat::kJson);
 
   gin::Handle<GinPort> port = messaging_service()->CreatePortForTesting(
       script_context(), "channel", port_id);
@@ -324,13 +327,14 @@
   v8::HandleScope handle_scope(isolate());
 
   const std::string kChannel = "channel";
-  PortId expected_port_id(script_context()->context_id(), 0, true);
+  PortId expected_port_id(script_context()->context_id(), 0, true,
+                          SerializationFormat::kJson);
   MessageTarget target(MessageTarget::ForExtension(extension()->id()));
   EXPECT_CALL(*ipc_message_sender(),
               SendOpenMessageChannel(script_context(), expected_port_id, target,
                                      kChannel));
-  gin::Handle<GinPort> new_port =
-      messaging_service()->Connect(script_context(), target, "channel");
+  gin::Handle<GinPort> new_port = messaging_service()->Connect(
+      script_context(), target, "channel", SerializationFormat::kJson);
   ::testing::Mock::VerifyAndClearExpectations(ipc_message_sender());
   ASSERT_FALSE(new_port.IsEmpty());
 
@@ -347,7 +351,8 @@
   v8::Local<v8::Context> context = MainContext();
 
   const std::string kChannel = "chrome.runtime.sendMessage";
-  PortId port_id(script_context()->context_id(), 0, true);
+  PortId port_id(script_context()->context_id(), 0, true,
+                 SerializationFormat::kJson);
   const char kEchoArgs[] =
       "(function() { this.replyArgs = Array.from(arguments); })";
   v8::Local<v8::Function> response_callback =
@@ -355,7 +360,7 @@
 
   // Send a message and expect a reply. A new port should be created, and should
   // remain open (waiting for the response).
-  const Message message("\"hi\"", false);
+  const Message message("\"hi\"", SerializationFormat::kJson, false);
   MessageTarget target(MessageTarget::ForExtension(extension()->id()));
   EXPECT_CALL(
       *ipc_message_sender(),
@@ -371,8 +376,9 @@
   // port should be closed.
   EXPECT_CALL(*ipc_message_sender(),
               SendCloseMessagePort(MSG_ROUTING_NONE, port_id, true));
-  messaging_service()->DeliverMessage(script_context_set(), port_id,
-                                      Message("\"reply\"", false), nullptr);
+  messaging_service()->DeliverMessage(
+      script_context_set(), port_id,
+      Message("\"reply\"", SerializationFormat::kJson, false), nullptr);
   ::testing::Mock::VerifyAndClearExpectations(ipc_message_sender());
   EXPECT_EQ("[\"reply\"]", GetStringPropertyFromObject(context->Global(),
                                                        context, "replyArgs"));
@@ -401,7 +407,7 @@
 
   const std::string kChannel = "chrome.runtime.sendMessage";
   base::UnguessableToken other_context_id = base::UnguessableToken::Create();
-  const PortId port_id(other_context_id, 0, false);
+  const PortId port_id(other_context_id, 0, false, SerializationFormat::kJson);
 
   ExtensionMsg_TabConnectionInfo tab_connection_info;
   tab_connection_info.frame_id = 0;
@@ -430,13 +436,15 @@
 
   // Post the message to the receiver. The receiver should respond, and the
   // port should close.
-  EXPECT_CALL(
-      *ipc_message_sender(),
-      SendPostMessageToPort(port_id, Message(R"({"data":"hi"})", false)));
+  EXPECT_CALL(*ipc_message_sender(),
+              SendPostMessageToPort(
+                  port_id, Message(R"({"data":"hi"})",
+                                   SerializationFormat::kJson, false)));
   EXPECT_CALL(*ipc_message_sender(),
               SendCloseMessagePort(MSG_ROUTING_NONE, port_id, true));
-  messaging_service()->DeliverMessage(script_context_set(), port_id,
-                                      Message("\"message\"", false), nullptr);
+  messaging_service()->DeliverMessage(
+      script_context_set(), port_id,
+      Message("\"message\"", SerializationFormat::kJson, false), nullptr);
   ::testing::Mock::VerifyAndClearExpectations(ipc_message_sender());
   EXPECT_FALSE(
       messaging_service()->HasPortForTesting(script_context(), port_id));
@@ -466,9 +474,10 @@
 
   base::UnguessableToken other_context_id = base::UnguessableToken::Create();
   int next_port_id = 0;
-  const PortId on_message_port_id(other_context_id, ++next_port_id, false);
+  const PortId on_message_port_id(other_context_id, ++next_port_id, false,
+                                  SerializationFormat::kJson);
   const PortId on_message_external_port_id(other_context_id, ++next_port_id,
-                                           false);
+                                           false, SerializationFormat::kJson);
 
   auto open_port = [this](const PortId& port_id, const ExtensionId& source_id) {
     ExtensionMsg_TabConnectionInfo tab_connection_info;
@@ -503,8 +512,9 @@
       crx_file::id_util::GenerateId("different");
   open_port(on_message_external_port_id, other_extension);
 
-  messaging_service()->DeliverMessage(script_context_set(), on_message_port_id,
-                                      Message("\"onMessage\"", false), nullptr);
+  messaging_service()->DeliverMessage(
+      script_context_set(), on_message_port_id,
+      Message("\"onMessage\"", SerializationFormat::kJson, false), nullptr);
   EXPECT_EQ("\"onMessage\"",
             GetStringPropertyFromObject(context->Global(), context,
                                         "onMessageReceived"));
@@ -514,7 +524,8 @@
 
   messaging_service()->DeliverMessage(
       script_context_set(), on_message_external_port_id,
-      Message("\"onMessageExternal\"", false), nullptr);
+      Message("\"onMessageExternal\"", SerializationFormat::kJson, false),
+      nullptr);
   EXPECT_EQ("\"onMessage\"",
             GetStringPropertyFromObject(context->Global(), context,
                                         "onMessageReceived"));
diff --git a/extensions/renderer/one_time_message_handler.cc b/extensions/renderer/one_time_message_handler.cc
index 7bd539b7..0099b03 100644
--- a/extensions/renderer/one_time_message_handler.cc
+++ b/extensions/renderer/one_time_message_handler.cc
@@ -283,11 +283,12 @@
   OneTimeReceiver& port = iter->second;
 
   // This port is a receiver, so we invoke the onMessage event and provide a
-  // callback through which the port can respond. The port stays open until
-  // we receive a response.
+  // callback through which the port can respond. The port stays open until we
+  // receive a response.
   // TODO(devlin): With chrome.runtime.sendMessage, we actually require that a
   // listener return `true` if they intend to respond asynchronously; otherwise
   // we close the port.
+
   auto callback = std::make_unique<OneTimeMessageCallback>(
       base::BindOnce(&OneTimeMessageHandler::OnOneTimeMessageResponse,
                      weak_factory_.GetWeakPtr(), target_port_id));
@@ -471,8 +472,8 @@
     value = v8::Undefined(isolate);
 
   std::string error;
-  std::unique_ptr<Message> message =
-      messaging_util::MessageFromV8(context, value, &error);
+  std::unique_ptr<Message> message = messaging_util::MessageFromV8(
+      context, value, port_id.serialization_format, &error);
   if (!message) {
     arguments->ThrowTypeError(error);
     return;
diff --git a/extensions/renderer/one_time_message_handler_unittest.cc b/extensions/renderer/one_time_message_handler_unittest.cc
index 22ba5a5..1448081 100644
--- a/extensions/renderer/one_time_message_handler_unittest.cc
+++ b/extensions/renderer/one_time_message_handler_unittest.cc
@@ -12,6 +12,7 @@
 #include "base/strings/stringprintf.h"
 #include "extensions/common/api/messaging/message.h"
 #include "extensions/common/api/messaging/port_id.h"
+#include "extensions/common/api/messaging/serialization_format.h"
 #include "extensions/common/extension.h"
 #include "extensions/common/extension_builder.h"
 #include "extensions/renderer/bindings/api_binding_test_util.h"
@@ -92,8 +93,9 @@
 // Tests sending a message without expecting a reply, as in
 // chrome.runtime.sendMessage({foo: 'bar'});
 TEST_F(OneTimeMessageHandlerTest, SendMessageAndDontExpectReply) {
-  const PortId port_id(script_context()->context_id(), 0, true);
-  const Message message("\"Hello\"", false);
+  const PortId port_id(script_context()->context_id(), 0, true,
+                       SerializationFormat::kJson);
+  const Message message("\"Hello\"", SerializationFormat::kJson, false);
 
   // We should open a message port, send a message, and then close it
   // immediately.
@@ -116,8 +118,9 @@
 // Tests sending a message and expecting a reply, as in
 // chrome.runtime.sendMessage({foo: 'bar'}, function(reply) { ... });
 TEST_F(OneTimeMessageHandlerTest, SendMessageAndExpectReply) {
-  const PortId port_id(script_context()->context_id(), 0, true);
-  const Message message("\"Hello\"", false);
+  const PortId port_id(script_context()->context_id(), 0, true,
+                       SerializationFormat::kJson);
+  const Message message("\"Hello\"", SerializationFormat::kJson, false);
 
   v8::HandleScope handle_scope(isolate());
   v8::Local<v8::Context> context = MainContext();
@@ -152,7 +155,7 @@
   // Deliver the reply; the message port should close.
   EXPECT_CALL(*ipc_message_sender(),
               SendCloseMessagePort(MSG_ROUTING_NONE, port_id, true));
-  const Message reply("\"Hi\"", false);
+  const Message reply("\"Hi\"", SerializationFormat::kJson, false);
   message_handler()->DeliverMessage(script_context(), reply, port_id);
   ::testing::Mock::VerifyAndClearExpectations(ipc_message_sender());
   EXPECT_FALSE(message_handler()->HasPort(script_context(), port_id));
@@ -166,8 +169,9 @@
 // Tests disconnecting an opener (initiator of a sendMessage() call); this can
 // happen when no receiving end exists (i.e., no listener to runtime.onMessage).
 TEST_F(OneTimeMessageHandlerTest, DisconnectOpener) {
-  const PortId port_id(script_context()->context_id(), 0, true);
-  const Message message("\"Hello\"", false);
+  const PortId port_id(script_context()->context_id(), 0, true,
+                       SerializationFormat::kJson);
+  const Message message("\"Hello\"", SerializationFormat::kJson, false);
 
   v8::HandleScope handle_scope(isolate());
   v8::Local<v8::Context> context = MainContext();
@@ -221,7 +225,7 @@
   EXPECT_EQ("undefined", GetGlobalProperty(context, "eventSender"));
 
   base::UnguessableToken other_context_id = base::UnguessableToken::Create();
-  const PortId port_id(other_context_id, 0, false);
+  const PortId port_id(other_context_id, 0, false, SerializationFormat::kJson);
 
   EXPECT_FALSE(message_handler()->HasPort(script_context(), port_id));
   v8::Local<v8::Object> sender = gin::DataObjectBuilder(isolate())
@@ -234,7 +238,7 @@
   EXPECT_EQ("undefined", GetGlobalProperty(context, "eventMessage"));
   EXPECT_EQ("undefined", GetGlobalProperty(context, "eventSender"));
 
-  const Message message("\"Hi\"", false);
+  const Message message("\"Hi\"", SerializationFormat::kJson, false);
   message_handler()->DeliverMessage(script_context(), message, port_id);
 
   EXPECT_EQ("\"Hi\"", GetGlobalProperty(context, "eventMessage"));
@@ -267,7 +271,7 @@
   RunFunctionOnGlobal(add_listener, context, 0, nullptr);
 
   base::UnguessableToken other_context_id = base::UnguessableToken::Create();
-  const PortId port_id(other_context_id, 0, false);
+  const PortId port_id(other_context_id, 0, false, SerializationFormat::kJson);
 
   EXPECT_FALSE(message_handler()->HasPort(script_context(), port_id));
   v8::Local<v8::Object> sender = v8::Object::New(isolate());
@@ -275,13 +279,14 @@
                                  messaging_util::kOnMessageEvent);
   EXPECT_TRUE(message_handler()->HasPort(script_context(), port_id));
 
-  const Message message("\"Hi\"", false);
+  const Message message("\"Hi\"", SerializationFormat::kJson, false);
 
   // When the listener replies, we should post the reply to the message port and
   // close the channel.
-  EXPECT_CALL(
-      *ipc_message_sender(),
-      SendPostMessageToPort(port_id, Message(R"({"data":"hey"})", false)));
+  EXPECT_CALL(*ipc_message_sender(),
+              SendPostMessageToPort(
+                  port_id, Message(R"({"data":"hey"})",
+                                   SerializationFormat::kJson, false)));
   EXPECT_CALL(*ipc_message_sender(),
               SendCloseMessagePort(MSG_ROUTING_NONE, port_id, true));
   message_handler()->DeliverMessage(script_context(), message, port_id);
@@ -308,12 +313,12 @@
   RunFunctionOnGlobal(add_listener, context, 0, nullptr);
 
   base::UnguessableToken other_context_id = base::UnguessableToken::Create();
-  const PortId port_id(other_context_id, 0, false);
+  const PortId port_id(other_context_id, 0, false, SerializationFormat::kJson);
 
   v8::Local<v8::Object> sender = v8::Object::New(isolate());
   message_handler()->AddReceiver(script_context(), port_id, sender,
                                  messaging_util::kOnMessageEvent);
-  const Message message("\"Hi\"", false);
+  const Message message("\"Hi\"", SerializationFormat::kJson, false);
 
   message_handler()->DeliverMessage(script_context(), message, port_id);
 
@@ -325,8 +330,10 @@
   v8::Local<v8::Value> reply_arg = V8ValueFromScriptSource(context, "'hi'");
   v8::Local<v8::Value> args[] = {reply_arg};
 
-  EXPECT_CALL(*ipc_message_sender(),
-              SendPostMessageToPort(port_id, Message("\"hi\"", false)));
+  EXPECT_CALL(
+      *ipc_message_sender(),
+      SendPostMessageToPort(
+          port_id, Message("\"hi\"", SerializationFormat::kJson, false)));
   EXPECT_CALL(*ipc_message_sender(),
               SendCloseMessagePort(MSG_ROUTING_NONE, port_id, true));
   RunFunction(reply.As<v8::Function>(), context, base::size(args), args);
@@ -356,7 +363,8 @@
   RunFunctionOnGlobal(add_listener, context, 0, nullptr);
 
   base::UnguessableToken sender_context_id = base::UnguessableToken::Create();
-  const PortId original_port_id(sender_context_id, 0, false);
+  const PortId original_port_id(sender_context_id, 0, false,
+                                SerializationFormat::kJson);
 
   v8::Local<v8::Object> sender = v8::Object::New(isolate());
   message_handler()->AddReceiver(script_context(), original_port_id, sender,
@@ -365,9 +373,10 @@
   // On delivering the message, we expect the listener to open a new message
   // channel by using sendMessage(). The original message channel will be
   // closed.
-  const PortId listener_created_port_id(script_context()->context_id(), 0,
-                                        true);
-  const Message listener_sent_message("\"foo\"", false);
+  const PortId listener_created_port_id(script_context()->context_id(), 0, true,
+                                        SerializationFormat::kJson);
+  const Message listener_sent_message("\"foo\"", SerializationFormat::kJson,
+                                      false);
   MessageTarget target(MessageTarget::ForExtension(extension()->id()));
   EXPECT_CALL(
       *ipc_message_sender(),
@@ -379,7 +388,7 @@
   EXPECT_CALL(*ipc_message_sender(),
               SendCloseMessagePort(MSG_ROUTING_NONE, original_port_id, false));
 
-  const Message message("\"Hi\"", false);
+  const Message message("\"Hi\"", SerializationFormat::kJson, false);
   message_handler()->DeliverMessage(script_context(), message,
                                     original_port_id);
   ::testing::Mock::VerifyAndClearExpectations(ipc_message_sender());
@@ -403,8 +412,9 @@
 
   // Running the function should send one message ('foo'), which will wait for
   // a reply.
-  const PortId original_port_id(script_context()->context_id(), 0, true);
-  const Message original_message("\"foo\"", false);
+  const PortId original_port_id(script_context()->context_id(), 0, true,
+                                SerializationFormat::kJson);
+  const Message original_message("\"foo\"", SerializationFormat::kJson, false);
   MessageTarget target(MessageTarget::ForExtension(extension()->id()));
   EXPECT_CALL(*ipc_message_sender(),
               SendOpenMessageChannel(script_context(), original_port_id, target,
@@ -416,15 +426,18 @@
 
   // Upon delivering the reply to the sender, it should send a second message
   // ('bar'). The original message channel should be closed.
-  const PortId new_port_id(script_context()->context_id(), 1, true);
+  const PortId new_port_id(script_context()->context_id(), 1, true,
+                           SerializationFormat::kJson);
   EXPECT_CALL(*ipc_message_sender(),
               SendOpenMessageChannel(script_context(), new_port_id, target,
                                      messaging_util::kSendMessageChannel));
-  EXPECT_CALL(*ipc_message_sender(),
-              SendPostMessageToPort(new_port_id, Message("\"bar\"", false)));
+  EXPECT_CALL(
+      *ipc_message_sender(),
+      SendPostMessageToPort(
+          new_port_id, Message("\"bar\"", SerializationFormat::kJson, false)));
   EXPECT_CALL(*ipc_message_sender(),
               SendCloseMessagePort(MSG_ROUTING_NONE, original_port_id, true));
-  const Message reply("\"reply\"", false);
+  const Message reply("\"reply\"", SerializationFormat::kJson, false);
   message_handler()->DeliverMessage(script_context(), reply, original_port_id);
   ::testing::Mock::VerifyAndClearExpectations(ipc_message_sender());
 }
@@ -445,12 +458,12 @@
   RunFunctionOnGlobal(add_listener, context, 0, nullptr);
 
   base::UnguessableToken other_context_id = base::UnguessableToken::Create();
-  const PortId port_id(other_context_id, 0, false);
+  const PortId port_id(other_context_id, 0, false, SerializationFormat::kJson);
 
   v8::Local<v8::Object> sender = v8::Object::New(isolate());
   message_handler()->AddReceiver(script_context(), port_id, sender,
                                  messaging_util::kOnMessageEvent);
-  const Message message("\"Hi\"", false);
+  const Message message("\"Hi\"", SerializationFormat::kJson, false);
 
   EXPECT_CALL(*ipc_message_sender(),
               SendCloseMessagePort(MSG_ROUTING_NONE, port_id, false));
@@ -489,7 +502,7 @@
       "function(message, reply, sender) { throw new Error('hi!'); }");
 
   base::UnguessableToken other_context_id = base::UnguessableToken::Create();
-  const PortId port_id(other_context_id, 0, false);
+  const PortId port_id(other_context_id, 0, false, SerializationFormat::kJson);
 
   v8::Local<v8::Object> sender = v8::Object::New(isolate());
   message_handler()->AddReceiver(script_context(), port_id, sender,
@@ -500,7 +513,7 @@
 
   // Dispatch the message. Since none of these listeners return `true`, the port
   // should close.
-  const Message message("\"Hi\"", false);
+  const Message message("\"Hi\"", SerializationFormat::kJson, false);
   EXPECT_CALL(*ipc_message_sender(),
               SendCloseMessagePort(MSG_ROUTING_NONE, port_id, false));
   message_handler()->DeliverMessage(script_context(), message, port_id);
diff --git a/extensions/renderer/runtime_hooks_delegate.cc b/extensions/renderer/runtime_hooks_delegate.cc
index b75d29e..da108c6 100644
--- a/extensions/renderer/runtime_hooks_delegate.cc
+++ b/extensions/renderer/runtime_hooks_delegate.cc
@@ -11,6 +11,7 @@
 #include "content/public/renderer/render_frame.h"
 #include "content/public/renderer/v8_value_converter.h"
 #include "extensions/common/api/messaging/message.h"
+#include "extensions/common/api/messaging/serialization_format.h"
 #include "extensions/common/constants.h"
 #include "extensions/common/extension.h"
 #include "extensions/common/manifest.h"
@@ -223,8 +224,9 @@
   v8::Local<v8::Context> v8_context = script_context->v8_context();
 
   v8::Local<v8::Value> v8_message = arguments[1];
-  std::unique_ptr<Message> message =
-      messaging_util::MessageFromV8(v8_context, v8_message, &error);
+  std::unique_ptr<Message> message = messaging_util::MessageFromV8(
+      v8_context, v8_message,
+      messaging_util::GetSerializationFormat(*script_context), &error);
   if (!message) {
     RequestResult result(RequestResult::INVALID_INVOCATION);
     result.error = std::move(error);
@@ -258,8 +260,12 @@
   v8::Local<v8::Value> v8_message = arguments[1];
   DCHECK(!v8_message.IsEmpty());
   std::string error;
-  std::unique_ptr<Message> message = messaging_util::MessageFromV8(
-      script_context->v8_context(), v8_message, &error);
+
+  // Native messaging always uses JSON since a native host doesn't understand
+  // structured cloning serialization.
+  std::unique_ptr<Message> message =
+      messaging_util::MessageFromV8(script_context->v8_context(), v8_message,
+                                    SerializationFormat::kJson, &error);
   if (!message) {
     RequestResult result(RequestResult::INVALID_INVOCATION);
     result.error = std::move(error);
@@ -301,7 +307,8 @@
 
   gin::Handle<GinPort> port = messaging_service_->Connect(
       script_context, MessageTarget::ForExtension(target_id),
-      options.channel_name);
+      options.channel_name,
+      messaging_util::GetSerializationFormat(*script_context));
   DCHECK(!port.IsEmpty());
 
   RequestResult result(RequestResult::HANDLED);
@@ -317,9 +324,13 @@
 
   std::string application_name =
       gin::V8ToString(script_context->isolate(), arguments[0]);
+
+  // Native messaging always uses JSON since a native host doesn't understand
+  // structured cloning serialization.
+  auto format = SerializationFormat::kJson;
   gin::Handle<GinPort> port = messaging_service_->Connect(
       script_context, MessageTarget::ForNativeApp(application_name),
-      std::string());
+      std::string(), format);
 
   RequestResult result(RequestResult::HANDLED);
   result.return_value = port.ToV8();
diff --git a/extensions/renderer/runtime_hooks_delegate_unittest.cc b/extensions/renderer/runtime_hooks_delegate_unittest.cc
index 66780e9..3ab4e84f 100644
--- a/extensions/renderer/runtime_hooks_delegate_unittest.cc
+++ b/extensions/renderer/runtime_hooks_delegate_unittest.cc
@@ -9,6 +9,7 @@
 #include "base/strings/stringprintf.h"
 #include "components/crx_file/id_util.h"
 #include "content/public/common/child_process_host.h"
+#include "extensions/common/api/messaging/serialization_format.h"
 #include "extensions/common/extension.h"
 #include "extensions/common/extension_builder.h"
 #include "extensions/common/extension_messages.h"
@@ -367,7 +368,8 @@
     constexpr char kAddPortTemplate[] =
         "(function() { return chrome.runtime.connectNative(%s); })";
     PortId expected_port_id(script_context()->context_id(),
-                            next_context_port_id++, true);
+                            next_context_port_id++, true,
+                            SerializationFormat::kJson);
     MessageTarget expected_target(
         MessageTarget::ForNativeApp(expected_app_name));
     EXPECT_CALL(*ipc_message_sender(),
@@ -415,13 +417,14 @@
         "(function() { chrome.runtime.sendNativeMessage(%s); })";
 
     PortId expected_port_id(script_context()->context_id(),
-                            next_context_port_id++, true);
+                            next_context_port_id++, true,
+                            SerializationFormat::kJson);
     MessageTarget expected_target(
         MessageTarget::ForNativeApp(expected_application_name));
     EXPECT_CALL(*ipc_message_sender(),
                 SendOpenMessageChannel(script_context(), expected_port_id,
                                        expected_target, kEmptyExpectedChannel));
-    Message message(expected_message, false);
+    Message message(expected_message, SerializationFormat::kJson, false);
     EXPECT_CALL(*ipc_message_sender(),
                 SendPostMessageToPort(expected_port_id, message));
     // Note: we don't close native message ports immediately. See comment in
diff --git a/extensions/renderer/send_message_tester.cc b/extensions/renderer/send_message_tester.cc
index 1732ea4..0a943e5 100644
--- a/extensions/renderer/send_message_tester.cc
+++ b/extensions/renderer/send_message_tester.cc
@@ -5,6 +5,7 @@
 #include "extensions/renderer/send_message_tester.h"
 
 #include "base/strings/stringprintf.h"
+#include "extensions/common/api/messaging/serialization_format.h"
 #include "extensions/renderer/bindings/api_binding_test_util.h"
 #include "extensions/renderer/messaging_util.h"
 #include "extensions/renderer/native_extension_bindings_system_test_base.h"
@@ -54,7 +55,8 @@
 
   constexpr char kAddPortTemplate[] =
       "(function() { return chrome.%s.connect(%s); })";
-  PortId expected_port_id(script_context_->context_id(), next_port_id_++, true);
+  PortId expected_port_id(script_context_->context_id(), next_port_id_++, true,
+                          SerializationFormat::kJson);
   EXPECT_CALL(*ipc_sender_,
               SendOpenMessageChannel(script_context_, expected_port_id,
                                      expected_target, expected_channel));
@@ -91,12 +93,13 @@
       break;
   }
 
-  PortId expected_port_id(script_context_->context_id(), next_port_id_++, true);
+  PortId expected_port_id(script_context_->context_id(), next_port_id_++, true,
+                          SerializationFormat::kJson);
 
   EXPECT_CALL(*ipc_sender_,
               SendOpenMessageChannel(script_context_, expected_port_id,
                                      expected_target, expected_channel));
-  Message message(expected_message, false);
+  Message message(expected_message, SerializationFormat::kJson, false);
   EXPECT_CALL(*ipc_sender_, SendPostMessageToPort(expected_port_id, message));
 
   if (expected_port_status == CLOSED) {
diff --git a/infra/archive_config/win32-archive-rel.json b/infra/archive_config/win32-archive-rel.json
new file mode 100644
index 0000000..92212f2
--- /dev/null
+++ b/infra/archive_config/win32-archive-rel.json
@@ -0,0 +1,158 @@
+{
+	"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",
+                "nacl64.exe",
+                "nacl_irt_x86_32.nexe",
+                "nacl_irt_x86_64.nexe",
+                "notification_helper.exe",
+                "resources.pak",
+                "swiftshader\\libEGL.dll",
+                "swiftshader\\libGLESv2.dll",
+                "v8_context_snapshot.bin",
+                "vk_swiftshader.dll",
+                "vk_swiftshader_icd.json",
+                "vulkan-1.dll"
+            ],
+            "file_globs": ["*.manifest"],
+            "dirs": ["locales"],
+            "gcs_bucket": "chromium-browser-snapshots",
+            "gcs_path": "experimental/Win/{%position%}/chrome-win.zip",
+            "archive_type": "ARCHIVE_TYPE_ZIP"
+        },
+        {
+            "files": [
+                "chrome.dll.pdb",
+                "chrome.exe.pdb",
+                "chrome_elf.dll.pdb",
+                "chrome_proxy.exe.pdb",
+                "chrome_pwa_launcher.exe.pdb",
+                "elevation_service.exe.pdb",
+                "eventlog_provider.dll.pdb",
+                "gaia1_0.dll.pdb",
+                "gcapi_dll.dll.pdb",
+                "gcp_setup.exe.pdb",
+                "libEGL.dll.pdb",
+                "libGLESv2.dll.pdb",
+                "mini_installer.exe.pdb",
+                "mojo_core.dll.pdb",
+                "nacl64.exe.pdb",
+                "notification_helper.exe.pdb",
+                "setup.exe.pdb",
+                "swiftshader\\libEGL.dll.pdb",
+                "swiftshader\\libGLESv2.dll.pdb",
+                "vk_swiftshader.dll.pdb",
+                "vulkan-1.dll.pdb"
+            ],
+            "gcs_bucket": "chromium-browser-snapshots",
+            "gcs_path": "experimental/Win/{%position%}/chrome-win32-syms.zip",
+            "archive_type": "ARCHIVE_TYPE_ZIP"
+        },
+        {
+            "files": [
+                "chromedriver.exe.pdb"
+            ],
+            "gcs_bucket": "chromium-browser-snapshots",
+            "gcs_path": "experimental/Win/{%position%}/chromedriver_win32-syms.zip",
+            "archive_type": "ARCHIVE_TYPE_ZIP"
+        },
+        {
+            "files": [
+                "chromedriver.exe"
+            ],
+            "gcs_bucket": "chromium-browser-snapshots",
+            "gcs_path": "experimental/Win/{%position%}/chromedriver_win32.zip",
+            "archive_type": "ARCHIVE_TYPE_ZIP"
+        },
+        {
+            "files": [
+                "blink_deprecated_test_plugin.dll",
+                "blink_test_plugin.dll",
+                "content_shell.exe",
+                "content_shell.pak",
+                "icudtl.dat",
+                "v8_context_snapshot.bin"
+            ],
+            "dirs": ["resources"],
+            "gcs_bucket": "chromium-browser-snapshots",
+            "gcs_path": "experimental/Win/{%position%}/content-shell.zip",
+            "archive_type": "ARCHIVE_TYPE_ZIP"
+        },
+        {
+            "dirs": ["gen\\third_party\\devtools-frontend\\src\\front_end"],
+            "gcs_bucket": "chromium-browser-snapshots",
+            "gcs_path": "experimental/Win/{%position%}/devtools-frontend.zip",
+            "archive_type": "ARCHIVE_TYPE_ZIP"
+        },
+        {
+            "files": [
+                "gcapi.h",
+                "gcapi_dll.dll",
+                "gcapi_dll.dll.lib"
+            ],
+            "gcs_bucket": "chromium-browser-snapshots",
+            "gcs_path": "experimental/Win/{%position%}/gcapi.zip",
+            "archive_type": "ARCHIVE_TYPE_ZIP"
+        },
+        {
+            "files": [
+                "actions.xml",
+                "histograms.xml",
+                "ukm.xml"
+            ],
+            "gcs_bucket": "chromium-browser-snapshots",
+            "gcs_path": "experimental/Win/{%position%}/metrics-metadata.zip",
+            "archive_type": "ARCHIVE_TYPE_ZIP"
+        },
+        {
+            "files": [
+                "mini_installer.exe"
+            ],
+            "gcs_bucket": "chromium-browser-snapshots",
+            "gcs_path": "experimental/Win/{%position%}",
+            "archive_type": "ARCHIVE_TYPE_FILES"
+        },
+        {
+            "dirs": ["pnacl"],
+            "gcs_bucket": "chromium-browser-snapshots",
+            "gcs_path": "experimental/Win/{%position%}/pnacl.zip",
+            "archive_type": "ARCHIVE_TYPE_ZIP"
+        },
+        {
+            "files": [
+                "remoting-me2me-host-win.zip"
+            ],
+            "gcs_bucket": "chromium-browser-snapshots",
+            "gcs_path": "experimental/Win/{%position%}",
+            "archive_type": "ARCHIVE_TYPE_FILES"
+        },
+        {
+            "files": [
+                "updater.exe",
+                "UpdaterSetup.exe"
+            ],
+            "dirs": ["UpdaterSigning"],
+            "gcs_bucket": "chromium-browser-snapshots",
+            "gcs_path": "experimental/Win/{%position%}/updater.zip",
+            "archive_type": "ARCHIVE_TYPE_ZIP"
+        }
+    ]
+}
\ No newline at end of file
diff --git "a/infra/config/generated/builders/ci/UBSan vptr Release \050reclient shadow\051/properties.textpb" "b/infra/config/generated/builders/ci/UBSan vptr Release \050reclient shadow\051/properties.textpb"
new file mode 100644
index 0000000..989e5cbf
--- /dev/null
+++ "b/infra/config/generated/builders/ci/UBSan vptr Release \050reclient shadow\051/properties.textpb"
@@ -0,0 +1,20 @@
+{
+  "$build/reclient": {
+    "instance": "rbe-chromium-trusted",
+    "jobs": 250,
+    "metrics_project": "chromium-reclient-metrics"
+  },
+  "$kitchen": {
+    "devshell": true,
+    "emulate_gce": true,
+    "git_auth": true
+  },
+  "$recipe_engine/resultdb/test_presentation": {
+    "column_keys": [],
+    "grouping_keys": [
+      "status",
+      "v.test_suite"
+    ]
+  },
+  "recipe": "chromium"
+}
\ No newline at end of file
diff --git a/infra/config/generated/builders/ci/win32-archive-rel/properties.textpb b/infra/config/generated/builders/ci/win32-archive-rel/properties.textpb
index d85f8fe78..e5d1a1d 100644
--- a/infra/config/generated/builders/ci/win32-archive-rel/properties.textpb
+++ b/infra/config/generated/builders/ci/win32-archive-rel/properties.textpb
@@ -1,4 +1,12 @@
 {
+  "$build/archive": {
+    "source_side_spec_path": [
+      "src",
+      "infra",
+      "archive_config",
+      "win32-archive-rel.json"
+    ]
+  },
   "$build/goma": {
     "enable_ats": true,
     "rpc_extra_params": "?prod",
diff --git a/infra/config/generated/builders/try/linux-headless-shell-rel/properties.textpb b/infra/config/generated/builders/try/linux-headless-shell-rel/properties.textpb
new file mode 100644
index 0000000..8f6e9de
--- /dev/null
+++ b/infra/config/generated/builders/try/linux-headless-shell-rel/properties.textpb
@@ -0,0 +1,20 @@
+{
+  "$build/goma": {
+    "enable_ats": true,
+    "rpc_extra_params": "?prod",
+    "server_host": "goma.chromium.org",
+    "use_luci_auth": true
+  },
+  "$kitchen": {
+    "devshell": true,
+    "git_auth": true
+  },
+  "$recipe_engine/resultdb/test_presentation": {
+    "column_keys": [],
+    "grouping_keys": [
+      "status",
+      "v.test_suite"
+    ]
+  },
+  "recipe": "chromium_trybot"
+}
\ No newline at end of file
diff --git a/infra/config/generated/builders/try/win10-rel-orchestrator/properties.textpb b/infra/config/generated/builders/try/win10-rel-orchestrator/properties.textpb
index 6f28b25..d41a371 100644
--- a/infra/config/generated/builders/try/win10-rel-orchestrator/properties.textpb
+++ b/infra/config/generated/builders/try/win10-rel-orchestrator/properties.textpb
@@ -1,6 +1,6 @@
 {
   "$build/chromium_orchestrator": {
-    "compilator": "win10-rel-compilator",
+    "compilator": "win10_chromium_x64_rel_ng-compilator",
     "compilator_watcher_git_revision": "d5bee0e7798a40c3c6261c3dbc14becf1fbb693f"
   },
   "$build/code_coverage": {
diff --git a/infra/config/generated/luci/commit-queue.cfg b/infra/config/generated/luci/commit-queue.cfg
index 3a0281b..47e2b8e0 100644
--- a/infra/config/generated/luci/commit-queue.cfg
+++ b/infra/config/generated/luci/commit-queue.cfg
@@ -1229,6 +1229,10 @@
         includable_only: true
       }
       builders {
+        name: "chromium/try/linux-headless-shell-rel"
+        includable_only: true
+      }
+      builders {
         name: "chromium/try/linux-inverse-fieldtrials-fyi-rel"
         includable_only: true
       }
@@ -1837,6 +1841,10 @@
         location_regexp_exclude: ".+/[+]/infra/config/.+"
       }
       builders {
+        name: "chromium/try/win10_chromium_x64_rel_ng-compilator"
+        includable_only: true
+      }
+      builders {
         name: "chromium/try/win10_chromium_x64_rel_ng_exp"
         includable_only: true
       }
diff --git a/infra/config/generated/luci/cr-buildbucket.cfg b/infra/config/generated/luci/cr-buildbucket.cfg
index a81191c..f60853f 100644
--- a/infra/config/generated/luci/cr-buildbucket.cfg
+++ b/infra/config/generated/luci/cr-buildbucket.cfg
@@ -21927,6 +21927,91 @@
       }
     }
     builders {
+      name: "UBSan vptr Release (reclient shadow)"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-18.04"
+      dimensions: "pool:luci.chromium.ci"
+      dimensions: "ssd:0"
+      exe {
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/main"
+        cmd: "luciexe"
+      }
+      properties:
+        '{'
+        '  "$build/reclient": {'
+        '    "instance": "rbe-chromium-trusted",'
+        '    "jobs": 250,'
+        '    "metrics_project": "chromium-reclient-metrics"'
+        '  },'
+        '  "$kitchen": {'
+        '    "devshell": true,'
+        '    "emulate_gce": true,'
+        '    "git_auth": true'
+        '  },'
+        '  "$recipe_engine/resultdb/test_presentation": {'
+        '    "column_keys": [],'
+        '    "grouping_keys": ['
+        '      "status",'
+        '      "v.test_suite"'
+        '    ]'
+        '  },'
+        '  "builder_group": "chromium.fyi",'
+        '  "recipe": "chromium"'
+        '}'
+      execution_timeout_secs: 36000
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+      experiments {
+        key: "chromium.chromium_tests.use_gitiles_trigger"
+        value: 100
+      }
+      experiments {
+        key: "chromium.chromium_tests.use_rdb_results"
+        value: 100
+      }
+      experiments {
+        key: "luci.use_realms"
+        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:|content/test:fuchsia_)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: "VR Linux"
       swarming_host: "chromium-swarm.appspot.com"
       swarming_tags: "vpython:native-python-wrapper"
@@ -47026,6 +47111,14 @@
       }
       properties:
         '{'
+        '  "$build/archive": {'
+        '    "source_side_spec_path": ['
+        '      "src",'
+        '      "infra",'
+        '      "archive_config",'
+        '      "win32-archive-rel.json"'
+        '    ]'
+        '  },'
         '  "$build/goma": {'
         '    "enable_ats": true,'
         '    "rpc_extra_params": "?prod",'
@@ -70229,6 +70322,98 @@
       }
     }
     builders {
+      name: "linux-headless-shell-rel"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-18.04"
+      dimensions: "pool:luci.chromium.try"
+      dimensions: "ssd:0"
+      exe {
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/main"
+        cmd: "luciexe"
+      }
+      properties:
+        '{'
+        '  "$build/goma": {'
+        '    "enable_ats": true,'
+        '    "rpc_extra_params": "?prod",'
+        '    "server_host": "goma.chromium.org",'
+        '    "use_luci_auth": true'
+        '  },'
+        '  "$kitchen": {'
+        '    "devshell": true,'
+        '    "git_auth": true'
+        '  },'
+        '  "$recipe_engine/resultdb/test_presentation": {'
+        '    "column_keys": [],'
+        '    "grouping_keys": ['
+        '      "status",'
+        '      "v.test_suite"'
+        '    ]'
+        '  },'
+        '  "builder_group": "tryserver.chromium.linux",'
+        '  "recipe": "chromium_trybot"'
+        '}'
+      execution_timeout_secs: 14400
+      expiration_secs: 7200
+      grace_period {
+        seconds: 120
+      }
+      caches {
+        name: "win_toolchain"
+        path: "win_toolchain"
+      }
+      build_numbers: YES
+      service_account: "chromium-try-builder@chops-service-accounts.iam.gserviceaccount.com"
+      task_template_canary_percentage {
+        value: 5
+      }
+      experiments {
+        key: "chromium.chromium_tests.use_rdb_results"
+        value: 100
+      }
+      experiments {
+        key: "luci.use_realms"
+        value: 100
+      }
+      resultdb {
+        enable: true
+        bq_exports {
+          project: "chrome-luci-data"
+          dataset: "chromium"
+          table: "try_test_results"
+          test_results {}
+        }
+        bq_exports {
+          project: "chrome-luci-data"
+          dataset: "chromium"
+          table: "gpu_try_test_results"
+          test_results {
+            predicate {
+              test_id_regexp: "ninja://(chrome/test:|content/test:fuchsia_)telemetry_gpu_integration_test[^/]*/.+"
+            }
+          }
+        }
+        bq_exports {
+          project: "chrome-luci-data"
+          dataset: "chromium"
+          table: "blink_web_tests_try_test_results"
+          test_results {
+            predicate {
+              test_id_regexp: "ninja://[^/]*blink_web_tests/.+"
+            }
+          }
+        }
+        history_options {
+          use_invocation_timestamp: true
+        }
+      }
+    }
+    builders {
       name: "linux-inverse-fieldtrials-fyi-rel"
       swarming_host: "chromium-swarm.appspot.com"
       swarming_tags: "vpython:native-python-wrapper"
@@ -80782,7 +80967,7 @@
       properties:
         '{'
         '  "$build/chromium_orchestrator": {'
-        '    "compilator": "win10-rel-compilator",'
+        '    "compilator": "win10_chromium_x64_rel_ng-compilator",'
         '    "compilator_watcher_git_revision": "d5bee0e7798a40c3c6261c3dbc14becf1fbb693f"'
         '  },'
         '  "$build/code_coverage": {'
@@ -81342,6 +81527,109 @@
       }
     }
     builders {
+      name: "win10_chromium_x64_rel_ng-compilator"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "cores:32"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Windows-10"
+      dimensions: "pool:luci.chromium.try"
+      dimensions: "ssd:1"
+      exe {
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/main"
+        cmd: "luciexe"
+      }
+      properties:
+        '{'
+        '  "$build/code_coverage": {'
+        '    "coverage_test_types": ['
+        '      "unit",'
+        '      "overall"'
+        '    ],'
+        '    "use_clang_coverage": true'
+        '  },'
+        '  "$build/goma": {'
+        '    "enable_ats": false,'
+        '    "jobs": 300,'
+        '    "rpc_extra_params": "?prod",'
+        '    "server_host": "goma.chromium.org",'
+        '    "use_luci_auth": true'
+        '  },'
+        '  "$kitchen": {'
+        '    "devshell": true,'
+        '    "git_auth": true'
+        '  },'
+        '  "$recipe_engine/resultdb/test_presentation": {'
+        '    "column_keys": [],'
+        '    "grouping_keys": ['
+        '      "status",'
+        '      "v.test_suite"'
+        '    ]'
+        '  },'
+        '  "builder_group": "tryserver.chromium.win",'
+        '  "orchestrator": {'
+        '    "builder_group": "tryserver.chromium.win",'
+        '    "builder_name": "win10_chromium_x64_rel_ng"'
+        '  },'
+        '  "recipe": "chromium/compilator"'
+        '}'
+      execution_timeout_secs: 14400
+      expiration_secs: 7200
+      grace_period {
+        seconds: 120
+      }
+      caches {
+        name: "win_toolchain"
+        path: "win_toolchain"
+      }
+      build_numbers: YES
+      service_account: "chromium-try-builder@chops-service-accounts.iam.gserviceaccount.com"
+      task_template_canary_percentage {
+        value: 5
+      }
+      experiments {
+        key: "chromium.chromium_tests.use_rdb_results"
+        value: 100
+      }
+      experiments {
+        key: "luci.use_realms"
+        value: 100
+      }
+      resultdb {
+        enable: true
+        bq_exports {
+          project: "chrome-luci-data"
+          dataset: "chromium"
+          table: "try_test_results"
+          test_results {}
+        }
+        bq_exports {
+          project: "chrome-luci-data"
+          dataset: "chromium"
+          table: "gpu_try_test_results"
+          test_results {
+            predicate {
+              test_id_regexp: "ninja://(chrome/test:|content/test:fuchsia_)telemetry_gpu_integration_test[^/]*/.+"
+            }
+          }
+        }
+        bq_exports {
+          project: "chrome-luci-data"
+          dataset: "chromium"
+          table: "blink_web_tests_try_test_results"
+          test_results {
+            predicate {
+              test_id_regexp: "ninja://[^/]*blink_web_tests/.+"
+            }
+          }
+        }
+        history_options {
+          use_invocation_timestamp: true
+        }
+      }
+    }
+    builders {
       name: "win10_chromium_x64_rel_ng_exp"
       swarming_host: "chromium-swarm.appspot.com"
       swarming_tags: "vpython:native-python-wrapper"
diff --git a/infra/config/generated/luci/luci-milo.cfg b/infra/config/generated/luci/luci-milo.cfg
index b4776df..45f3ebd 100644
--- a/infra/config/generated/luci/luci-milo.cfg
+++ b/infra/config/generated/luci/luci-milo.cfg
@@ -6616,6 +6616,11 @@
     short_name: "rel"
   }
   builders {
+    name: "buildbucket/luci.chromium.ci/UBSan vptr Release (reclient shadow)"
+    category: "linux UBSan"
+    short_name: "vpt"
+  }
+  builders {
     name: "buildbucket/luci.chromium.ci/ASAN Debug (reclient)"
     category: "linux asan"
     short_name: "dbg"
@@ -14750,6 +14755,9 @@
     name: "buildbucket/luci.chromium.try/linux-gcc-rel"
   }
   builders {
+    name: "buildbucket/luci.chromium.try/linux-headless-shell-rel"
+  }
+  builders {
     name: "buildbucket/luci.chromium.try/linux-inverse-fieldtrials-fyi-rel"
   }
   builders {
@@ -15113,6 +15121,9 @@
     name: "buildbucket/luci.chromium.try/win10_chromium_x64_rel_ng"
   }
   builders {
+    name: "buildbucket/luci.chromium.try/win10_chromium_x64_rel_ng-compilator"
+  }
+  builders {
     name: "buildbucket/luci.chromium.try/win10_chromium_x64_rel_ng_exp"
   }
   builders {
@@ -15775,6 +15786,9 @@
     name: "buildbucket/luci.chromium.try/linux-gcc-rel"
   }
   builders {
+    name: "buildbucket/luci.chromium.try/linux-headless-shell-rel"
+  }
+  builders {
     name: "buildbucket/luci.chromium.try/linux-inverse-fieldtrials-fyi-rel"
   }
   builders {
@@ -16225,6 +16239,9 @@
     name: "buildbucket/luci.chromium.try/win10_chromium_x64_rel_ng"
   }
   builders {
+    name: "buildbucket/luci.chromium.try/win10_chromium_x64_rel_ng-compilator"
+  }
+  builders {
     name: "buildbucket/luci.chromium.try/win10_chromium_x64_rel_ng_exp"
   }
   builders {
diff --git a/infra/config/generated/luci/luci-scheduler.cfg b/infra/config/generated/luci/luci-scheduler.cfg
index 28afa9b..c0d88904 100644
--- a/infra/config/generated/luci/luci-scheduler.cfg
+++ b/infra/config/generated/luci/luci-scheduler.cfg
@@ -3324,6 +3324,20 @@
   }
 }
 job {
+  id: "UBSan vptr Release (reclient shadow)"
+  realm: "ci"
+  acl_sets: "ci"
+  triggering_policy {
+    kind: GREEDY_BATCHING
+    max_concurrent_invocations: 4
+  }
+  buildbucket {
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci"
+    builder: "UBSan vptr Release (reclient shadow)"
+  }
+}
+job {
   id: "VR Linux"
   realm: "ci"
   acl_sets: "ci"
@@ -7445,6 +7459,7 @@
   triggers: "UBSan Release"
   triggers: "UBSan Release (reclient)"
   triggers: "UBSan vptr Release"
+  triggers: "UBSan vptr Release (reclient shadow)"
   triggers: "VR Linux"
   triggers: "VR Linux (reclient)"
   triggers: "WebKit Linux ASAN"
diff --git a/infra/config/subprojects/chromium/ci.star b/infra/config/subprojects/chromium/ci.star
index c651d024..77b154c 100644
--- a/infra/config/subprojects/chromium/ci.star
+++ b/infra/config/subprojects/chromium/ci.star
@@ -1884,6 +1884,17 @@
     cores = 32,
     main_console_view = "main",
     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",
+                "win32-archive-rel.json",
+            ],
+        },
+    },
 )
 
 ci.chromium_builder(
@@ -4387,6 +4398,22 @@
 )
 
 ci.fyi_builder(
+    name = "UBSan vptr Release (reclient shadow)",
+    console_view_entry = consoles.console_view_entry(
+        category = "linux UBSan",
+        short_name = "vpt",
+    ),
+    triggering_policy = scheduler.greedy_batching(
+        max_concurrent_invocations = 4,
+    ),
+    goma_backend = None,
+    reclient_jobs = 250,
+    reclient_instance = rbe_instance.DEFAULT,
+    configure_kitchen = True,
+    kitchen_emulate_gce = True,
+)
+
+ci.fyi_builder(
     name = "VR Linux (reclient)",
     console_view_entry = consoles.console_view_entry(
         category = "linux",
diff --git a/infra/config/subprojects/chromium/try.star b/infra/config/subprojects/chromium/try.star
index 4a0e03d..c142701 100644
--- a/infra/config/subprojects/chromium/try.star
+++ b/infra/config/subprojects/chromium/try.star
@@ -1261,6 +1261,10 @@
 )
 
 try_.chromium_linux_builder(
+    name = "linux-headless-shell-rel",
+)
+
+try_.chromium_linux_builder(
     name = "linux-inverse-fieldtrials-fyi-rel",
 )
 
@@ -1974,7 +1978,7 @@
     coverage_test_types = ["unit", "overall"],
     properties = {
         "$build/chromium_orchestrator": {
-            "compilator": "win10-rel-compilator",
+            "compilator": "win10_chromium_x64_rel_ng-compilator",
             "compilator_watcher_git_revision": compilator_watcher_git_revision,
         },
     },
@@ -2003,6 +2007,24 @@
 )
 
 try_.chromium_win_builder(
+    name = "win10_chromium_x64_rel_ng-compilator",
+    builderless = None,
+    os = os.WINDOWS_10,
+    cores = 32,
+    ssd = True,
+    goma_jobs = goma.jobs.J300,
+    executable = "recipe:chromium/compilator",
+    use_clang_coverage = True,
+    coverage_test_types = ["unit", "overall"],
+    properties = {
+        "orchestrator": {
+            "builder_name": "win10_chromium_x64_rel_ng",
+            "builder_group": "tryserver.chromium.win",
+        },
+    },
+)
+
+try_.chromium_win_builder(
     name = "win10_chromium_x64_rel_ng_exp",
     builderless = False,
     os = os.WINDOWS_ANY,
diff --git a/ios/chrome/browser/ui/settings/bandwidth_management_table_view_controller_unittest.mm b/ios/chrome/browser/ui/settings/bandwidth_management_table_view_controller_unittest.mm
index d45a8989..445567bd 100644
--- a/ios/chrome/browser/ui/settings/bandwidth_management_table_view_controller_unittest.mm
+++ b/ios/chrome/browser/ui/settings/bandwidth_management_table_view_controller_unittest.mm
@@ -23,7 +23,6 @@
 #include "ios/chrome/test/ios_chrome_scoped_testing_local_state.h"
 #include "ios/chrome/test/testing_application_context.h"
 #include "ios/web/public/test/web_task_environment.h"
-#include "net/log/test_net_log.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/base/l10n/l10n_util.h"
 
@@ -66,7 +65,6 @@
   }
 
   web::WebTaskEnvironment task_environment_;
-  net::RecordingTestNetLog net_log_;
   IOSChromeScopedTestingLocalState local_state_;
 
   std::unique_ptr<TestChromeBrowserState> chrome_browser_state_;
diff --git a/ios/chrome/browser/ui/settings/password/password_details/add_password_mediator.mm b/ios/chrome/browser/ui/settings/password/password_details/add_password_mediator.mm
index 9c1237a..8c68b17 100644
--- a/ios/chrome/browser/ui/settings/password/password_details/add_password_mediator.mm
+++ b/ios/chrome/browser/ui/settings/password/password_details/add_password_mediator.mm
@@ -5,6 +5,7 @@
 #import "ios/chrome/browser/ui/settings/password/password_details/add_password_mediator.h"
 
 #include "base/bind.h"
+#include "base/ranges/algorithm.h"
 #include "base/strings/sys_string_conversions.h"
 #include "base/task/cancelable_task_tracker.h"
 #include "base/task/post_task.h"
@@ -24,26 +25,28 @@
 #error "This file requires ARC support."
 #endif
 
+using base::SysNSStringToUTF8;
 using base::SysNSStringToUTF16;
 using base::SysUTF8ToNSString;
 
 namespace {
-// Checks for existing credentials with the same website and username.
+// Checks for existing credentials with the same url and username.
 bool CheckForDuplicates(
-    NSString* website,
+    GURL url,
     NSString* username,
     const password_manager::SavedPasswordsPresenter::SavedPasswordsView&
         credentials) {
-  GURL gurl = net::GURLWithNSURL([NSURL URLWithString:website]);
   std::string signon_realm = password_manager::GetSignonRealm(
-      password_manager_util::StripAuthAndParams(gurl));
+      password_manager_util::StripAuthAndParams(url));
   std::u16string username_value = SysNSStringToUTF16(username);
-  for (const auto& form : credentials) {
-    if (form.signon_realm == signon_realm &&
-        form.username_value == username_value) {
-      return true;
-    }
-  }
+  auto have_equal_username_and_realm =
+      [&signon_realm,
+       &username_value](const password_manager::PasswordForm& form) {
+        return signon_realm == form.signon_realm &&
+               username_value == form.username_value;
+      };
+  if (base::ranges::any_of(credentials, have_equal_username_and_realm))
+    return true;
   return false;
 }
 }
@@ -68,6 +71,9 @@
 @property(nonatomic, assign) scoped_refptr<base::SequencedTaskRunner>
     sequencedTaskRunner;
 
+// Stores the url entered in the website field.
+@property(nonatomic, assign) GURL URL;
+
 @end
 
 @implementation AddPasswordMediator
@@ -106,14 +112,12 @@
 
 - (void)passwordDetailsViewController:
             (PasswordDetailsTableViewController*)viewController
-        didAddPasswordDetailsWithSite:(NSString*)website
-                             username:(NSString*)username
+                didAddPasswordDetails:(NSString*)username
                              password:(NSString*)password {
   password_manager::PasswordForm passwordForm;
-  GURL gurl = net::GURLWithNSURL([NSURL URLWithString:website]);
-  DCHECK(gurl.is_valid());
+  DCHECK([self isURLValid]);
 
-  passwordForm.url = password_manager_util::StripAuthAndParams(gurl);
+  passwordForm.url = self.URL;
   passwordForm.signon_realm =
       password_manager::GetSignonRealm(passwordForm.url);
   passwordForm.username_value = SysNSStringToUTF16(username);
@@ -126,24 +130,29 @@
   [self.delegate dismissPasswordDetailsTableViewController];
 }
 
-- (void)checkForDuplicatesWithSite:(NSString*)website
-                          username:(NSString*)username {
+- (void)checkForDuplicates:(NSString*)username {
   _validationTaskTracker->TryCancelAll();
+  if (![self isURLValid]) {
+    return;
+  }
+
   __weak __typeof(self) weakSelf = self;
   _validationTaskTracker->PostTaskAndReplyWithResult(
       _sequencedTaskRunner.get(), FROM_HERE,
-      base::BindOnce(&CheckForDuplicates, website, username,
+      base::BindOnce(&CheckForDuplicates, self.URL, username,
                      _manager->GetAllCredentials()),
       base::BindOnce(^(bool duplicateFound) {
         [weakSelf.consumer onDuplicateCheckCompletion:duplicateFound];
       }));
 }
 
-- (void)showExistingCredentialWithSite:(NSString*)website
-                              username:(NSString*)username {
-  GURL gurl = net::GURLWithNSURL([NSURL URLWithString:website]);
+- (void)showExistingCredential:(NSString*)username {
+  if (![self isURLValid]) {
+    return;
+  }
+
   std::string signon_realm = password_manager::GetSignonRealm(
-      password_manager_util::StripAuthAndParams(gurl));
+      password_manager_util::StripAuthAndParams(self.URL));
   std::u16string username_value = SysNSStringToUTF16(username);
   for (const auto& form : _manager->GetAllCredentials()) {
     if (form.signon_realm == signon_realm &&
@@ -159,6 +168,15 @@
   [self.delegate dismissPasswordDetailsTableViewController];
 }
 
+- (void)setWebsiteURL:(NSString*)website {
+  self.URL = password_manager_util::ConstructGURLWithScheme(
+      SysNSStringToUTF8(website));
+}
+
+- (BOOL)isURLValid {
+  return self.URL.is_valid() && self.URL.SchemeIsHTTPOrHTTPS();
+}
+
 - (BOOL)isUsernameReused:(NSString*)newUsername {
   return NO;
 }
diff --git a/ios/chrome/browser/ui/settings/password/password_details/password_details_mediator.mm b/ios/chrome/browser/ui/settings/password/password_details/password_details_mediator.mm
index 23da21d..0c6676c 100644
--- a/ios/chrome/browser/ui/settings/password/password_details/password_details_mediator.mm
+++ b/ios/chrome/browser/ui/settings/password/password_details/password_details_mediator.mm
@@ -90,19 +90,16 @@
 
 - (void)passwordDetailsViewController:
             (PasswordDetailsTableViewController*)viewController
-        didAddPasswordDetailsWithSite:(NSString*)website
-                             username:(NSString*)username
+                didAddPasswordDetails:(NSString*)username
                              password:(NSString*)password {
   NOTREACHED();
 }
 
-- (void)checkForDuplicatesWithSite:(NSString*)website
-                          username:(NSString*)username {
+- (void)checkForDuplicates:(NSString*)username {
   NOTREACHED();
 }
 
-- (void)showExistingCredentialWithSite:(NSString*)website
-                              username:(NSString*)username {
+- (void)showExistingCredential:(NSString*)username {
   NOTREACHED();
 }
 
@@ -110,6 +107,14 @@
   NOTREACHED();
 }
 
+- (void)setWebsiteURL:(NSString*)website {
+  NOTREACHED();
+}
+
+- (BOOL)isURLValid {
+  return YES;
+}
+
 - (BOOL)isUsernameReused:(NSString*)newUsername {
   // It is more efficient to check set of the usernames for the same origin
   // instead of delegating this to the |_manager|.
diff --git a/ios/chrome/browser/ui/settings/password/password_details/password_details_table_view_controller.mm b/ios/chrome/browser/ui/settings/password/password_details/password_details_table_view_controller.mm
index 711265a..2d8c563d 100644
--- a/ios/chrome/browser/ui/settings/password/password_details/password_details_table_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/password/password_details/password_details_table_view_controller.mm
@@ -635,7 +635,7 @@
 
 - (void)onDuplicateCheckCompletion:(BOOL)duplicateFound {
   self.navigationItem.rightBarButtonItem.enabled =
-      !duplicateFound && self.shouldEnableSave;
+      !duplicateFound && self.shouldEnableSave && [self.delegate isURLValid];
   if (duplicateFound == self.isDuplicatedCredential) {
     return;
   }
@@ -691,18 +691,21 @@
 }
 
 - (void)tableViewItemDidChange:(TableViewTextEditItem*)tableViewItem {
-  // TODO(crbug.com/1226006): Add validations for the site.
-  if (self.credentialType == CredentialTypeNew) {
-    [self.delegate
-        checkForDuplicatesWithSite:self.websiteTextItem.textFieldValue
-                          username:self.usernameTextItem.textFieldValue];
+  if (tableViewItem == self.websiteTextItem &&
+      self.credentialType == CredentialTypeNew) {
+    [self.delegate setWebsiteURL:self.websiteTextItem.textFieldValue];
   }
 
   self.shouldEnableSave = [self checkIfValidSite] &
                           [self checkIfValidUsername] &
                           [self checkIfValidPassword];
   self.navigationItem.rightBarButtonItem.enabled =
-      !self.isDuplicatedCredential && self.shouldEnableSave;
+      !self.isDuplicatedCredential && self.shouldEnableSave &&
+      [self.delegate isURLValid];
+
+  if (self.credentialType == CredentialTypeNew) {
+    [self.delegate checkForDuplicates:self.usernameTextItem.textFieldValue];
+  }
 }
 
 - (void)tableViewItemDidEndEditing:(TableViewTextEditItem*)tableViewItem {
@@ -728,8 +731,7 @@
 - (void)didTapSaveButton:(id)sender {
   [self.delegate
       passwordDetailsViewController:self
-      didAddPasswordDetailsWithSite:self.websiteTextItem.textFieldValue
-                           username:self.usernameTextItem.textFieldValue
+              didAddPasswordDetails:self.usernameTextItem.textFieldValue
                            password:self.passwordTextItem.textFieldValue];
 }
 
@@ -954,10 +956,8 @@
           }
 
           [strongSelf.delegate
-              showExistingCredentialWithSite:strongSelf.websiteTextItem
-                                                 .textFieldValue
-                                    username:strongSelf.usernameTextItem
-                                                 .textFieldValue];
+              showExistingCredential:strongSelf.usernameTextItem
+                                         .textFieldValue];
         };
 
     // TODO(crbug.com/1226006): Use i18n string.
diff --git a/ios/chrome/browser/ui/settings/password/password_details/password_details_table_view_controller_delegate.h b/ios/chrome/browser/ui/settings/password/password_details/password_details_table_view_controller_delegate.h
index 203358a..89dc0103 100644
--- a/ios/chrome/browser/ui/settings/password/password_details/password_details_table_view_controller_delegate.h
+++ b/ios/chrome/browser/ui/settings/password/password_details/password_details_table_view_controller_delegate.h
@@ -18,23 +18,26 @@
 // Called when user finished adding a new password credential.
 - (void)passwordDetailsViewController:
             (PasswordDetailsTableViewController*)viewController
-        didAddPasswordDetailsWithSite:(NSString*)website
-                             username:(NSString*)username
+                didAddPasswordDetails:(NSString*)username
                              password:(NSString*)password;
 
 // Called on every keystroke to check whether duplicates exist before adding a
 // new credential.
-- (void)checkForDuplicatesWithSite:(NSString*)website
-                          username:(NSString*)username;
+- (void)checkForDuplicates:(NSString*)username;
 
 // Called when an existing credential is to be displayed in the add credential
 // flow.
-- (void)showExistingCredentialWithSite:(NSString*)website
-                              username:(NSString*)username;
+- (void)showExistingCredential:(NSString*)username;
 
 // Called when the user cancels the add password view.
 - (void)didCancelAddPasswordDetails;
 
+// Called every time the text in the website field is updated.
+- (void)setWebsiteURL:(NSString*)website;
+
+// Returns whether the website URL has http(s) scheme and is valid or not.
+- (BOOL)isURLValid;
+
 // Checks if the username is reused for the same domain.
 - (BOOL)isUsernameReused:(NSString*)newUsername;
 
diff --git a/ios/chrome/browser/ui/settings/password/password_details/password_details_table_view_controller_unittest.mm b/ios/chrome/browser/ui/settings/password/password_details/password_details_table_view_controller_unittest.mm
index fff39f0..4c2adbb 100644
--- a/ios/chrome/browser/ui/settings/password/password_details/password_details_table_view_controller_unittest.mm
+++ b/ios/chrome/browser/ui/settings/password/password_details/password_details_table_view_controller_unittest.mm
@@ -101,22 +101,26 @@
 
 - (void)passwordDetailsViewController:
             (PasswordDetailsTableViewController*)viewController
-        didAddPasswordDetailsWithSite:(NSString*)website
-                             username:(NSString*)username
+                didAddPasswordDetails:(NSString*)username
                              password:(NSString*)password {
 }
 
-- (void)checkForDuplicatesWithSite:(NSString*)website
-                          username:(NSString*)username {
+- (void)checkForDuplicates:(NSString*)username {
 }
 
-- (void)showExistingCredentialWithSite:(NSString*)website
-                              username:(NSString*)username {
+- (void)showExistingCredential:(NSString*)username {
 }
 
 - (void)didCancelAddPasswordDetails {
 }
 
+- (void)setWebsiteURL:(NSString*)website {
+}
+
+- (BOOL)isURLValid {
+  return YES;
+}
+
 @end
 
 @interface FakeSnackbarImplementation : NSObject <SnackbarCommands>
diff --git a/ios/google_internal/frameworks/LSApplicationQuerySchemes.plist.sha1 b/ios/google_internal/frameworks/LSApplicationQuerySchemes.plist.sha1
index 8b348fc..7ef68ca4 100644
--- a/ios/google_internal/frameworks/LSApplicationQuerySchemes.plist.sha1
+++ b/ios/google_internal/frameworks/LSApplicationQuerySchemes.plist.sha1
@@ -1 +1 @@
-bc4db87578a140fcdb8bf7dbf8002805cd34501e
\ No newline at end of file
+5a871452dc8ef97d86fd171571e3995b950fbadd
\ No newline at end of file
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 4e0071f..208938b 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 @@
-7153d40778308782401995fb334fe4eeb7e23333
\ No newline at end of file
+7339a4100a10ddb3163cf593bb2959cf74cb8fdb
\ 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 cf34afe..8bd95447 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 @@
-b4bfd9873bf419a037f758d8e220893bd0248fa1
\ No newline at end of file
+f6ab320b8c1124872aaad26ead05a4a761d8cc76
\ 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 dd21299..fd14bb1 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 @@
-fc7aac0a6837934e5daadf9a159fbc1cfd182bb8
\ No newline at end of file
+c7669b52f19b45417f3fc0f3b3b0279ce59d3678
\ 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 d7a4289..af0d5f1 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 @@
-f71759f0f4f631fbac419326d00f94237fb497ae
\ No newline at end of file
+8cc9e970531ee5229be7fd093111f73d575326ee
\ 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 204688b..f303762 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 @@
-d7c926231dd1c7df0efabf50592cd064bc0eff2b
\ No newline at end of file
+887bac2596570a24df058ddd72da35c6fe36d6b0
\ 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 c682dfd..1f691ee2 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 @@
-b71455454d69c64663cd6bd4b58f01e9e9700c61
\ No newline at end of file
+a0eecee6bb16e4b568c81b4069cdb0ab83016944
\ 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 fbd34441..580a7fc 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 @@
-93760ff578e7e222929bf36f4cfbf6f1dc76b051
\ No newline at end of file
+9b4ff184392a474da3cb81114c2f04a303660f44
\ 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 540db00..148fbae 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 @@
-a169d6873358267cc43e626f597b856686ec33fd
\ No newline at end of file
+48ce863422aa17d2bd0a2276692b01e013653631
\ 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 96325b60..30e6d01 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 @@
-dc00f722b9937dbe0cce9c4aa8bc2d06de9fc945
\ No newline at end of file
+87f0dd0f3af960fd0ec285c4e20c409d43227610
\ 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 8692d779..8184ad76 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 @@
-2c12ec88e6ab8efddc2d0ff8784a65a8b3691e83
\ No newline at end of file
+be12838eceb30a1fe382ba56db04e5c29427e570
\ No newline at end of file
diff --git a/ios/web/BUILD.gn b/ios/web/BUILD.gn
index 100b11e..ccce2dd7 100644
--- a/ios/web/BUILD.gn
+++ b/ios/web/BUILD.gn
@@ -618,16 +618,6 @@
   assert_no_deps = ios_assert_no_deps
 }
 
-js_compile_bundle("main_frame_web_bundle") {
-  visibility = [ ":js_resources" ]
-  closure_entry_point = "__crWeb.mainFrameWebBundle"
-
-  sources = [
-    "web_state/js/resources/main_frame_web_bundle.js",
-    "web_state/js/resources/navigation.js",
-  ]
-}
-
 js_compile_bundle("all_frames_web_bundle") {
   visibility = [ ":js_resources" ]
   closure_entry_point = "__crWeb.allFramesWebBundle"
@@ -668,10 +658,7 @@
 }
 
 js_compile_checked("js_resources") {
-  public_deps = [
-    ":all_frames_web_bundle",
-    ":main_frame_web_bundle",
-  ]
+  public_deps = [ ":all_frames_web_bundle" ]
 
   js_modules = [
     "//ios/web/web_state/js/resources/base.js",
diff --git a/ios/web/js_messaging/BUILD.gn b/ios/web/js_messaging/BUILD.gn
index 1d0802a4..c0160371 100644
--- a/ios/web/js_messaging/BUILD.gn
+++ b/ios/web/js_messaging/BUILD.gn
@@ -102,6 +102,7 @@
     "//ios/web/js_features/context_menu",
     "//ios/web/js_features/scroll_helper",
     "//ios/web/js_features/window_error",
+    "//ios/web/navigation:navigation_feature",
     "//ios/web/navigation:session_restore_feature",
     "//ios/web/public",
     "//ios/web/public/js_messaging",
diff --git a/ios/web/js_messaging/java_script_feature_util_impl.mm b/ios/web/js_messaging/java_script_feature_util_impl.mm
index dc112e84..721f457e 100644
--- a/ios/web/js_messaging/java_script_feature_util_impl.mm
+++ b/ios/web/js_messaging/java_script_feature_util_impl.mm
@@ -17,6 +17,7 @@
 #import "ios/web/js_features/window_error/window_error_java_script_feature.h"
 #import "ios/web/js_messaging/script_command_java_script_feature.h"
 #import "ios/web/js_messaging/web_frames_manager_java_script_feature.h"
+#import "ios/web/navigation/navigation_java_script_feature.h"
 #import "ios/web/navigation/session_restore_java_script_feature.h"
 #include "ios/web/public/js_messaging/java_script_feature.h"
 #import "ios/web/public/web_client.h"
@@ -112,6 +113,7 @@
       GetFaviconJavaScriptFeature(),
       GetScrollHelperJavaScriptFeature(),
       GetWindowErrorJavaScriptFeature(),
+      NavigationJavaScriptFeature::GetInstance(),
       ScriptCommandJavaScriptFeature::GetInstance(),
       SessionRestoreJavaScriptFeature::FromBrowserState(browser_state),
       TextFragmentsJavaScriptFeature::GetInstance(),
diff --git a/ios/web/js_messaging/page_script_util.mm b/ios/web/js_messaging/page_script_util.mm
index f7e55d5..39764987 100644
--- a/ios/web/js_messaging/page_script_util.mm
+++ b/ios/web/js_messaging/page_script_util.mm
@@ -53,12 +53,7 @@
       GetWebClient()->GetDocumentStartScriptForMainFrame(browser_state);
   DCHECK(embedder_page_script);
 
-  NSString* web_bundle = GetPageScript(@"main_frame_web_bundle");
-  DCHECK(web_bundle);
-
-  NSString* script =
-      [NSString stringWithFormat:@"%@; %@", web_bundle, embedder_page_script];
-  return MakeScriptInjectableOnce(@"start_main_frame", script);
+  return MakeScriptInjectableOnce(@"start_main_frame", embedder_page_script);
 }
 
 NSString* GetDocumentStartScriptForAllFrames(BrowserState* browser_state) {
diff --git a/ios/web/js_messaging/resources/window_id.js b/ios/web/js_messaging/resources/window_id.js
index 563a48a..35ebced 100644
--- a/ios/web/js_messaging/resources/window_id.js
+++ b/ios/web/js_messaging/resources/window_id.js
@@ -13,4 +13,8 @@
 
 // Send messages queued since message.js injection.
 __gCrWeb.message.invokeQueues();
+
+const event = new Event('__gCrWebWindowIdInjected');
+window.dispatchEvent(event);
+
 }());
diff --git a/ios/web/navigation/BUILD.gn b/ios/web/navigation/BUILD.gn
index be659082..3a02462 100644
--- a/ios/web/navigation/BUILD.gn
+++ b/ios/web/navigation/BUILD.gn
@@ -5,6 +5,7 @@
 import("//build/buildflag_header.gni")
 import("//ios/build/config.gni")
 import("//ios/features.gni")
+import("//ios/web/js_compile.gni")
 
 buildflag_header("block_universal_links_buildflags") {
   header = "block_universal_links_buildflags.h"
@@ -103,6 +104,36 @@
   configs += [ "//build/config/compiler:enable_arc" ]
 }
 
+source_set("navigation_feature") {
+  deps = [
+    ":navigation_js",
+    ":navigation_listeners_js",
+    "//base",
+    "//ios/web/public/js_messaging",
+    "//ios/web/web_state:web_state_impl_header",
+    "//ios/web/web_state/ui:web_controller_header",
+  ]
+  sources = [
+    "navigation_java_script_feature.h",
+    "navigation_java_script_feature.mm",
+  ]
+  configs += [ "//build/config/compiler:enable_arc" ]
+}
+
+js_compile_bundle("navigation_js") {
+  visibility = [ ":navigation_feature" ]
+  closure_entry_point = "__crWeb.navigation"
+
+  sources = [ "resources/navigation.js" ]
+}
+
+js_compile_bundle("navigation_listeners_js") {
+  visibility = [ ":navigation_feature" ]
+  closure_entry_point = "__crWeb.navigationListeners"
+
+  sources = [ "resources/navigation_listeners.js" ]
+}
+
 source_set("session_restore_feature") {
   deps = [
     "//base",
diff --git a/ios/web/navigation/crw_js_navigation_handler.h b/ios/web/navigation/crw_js_navigation_handler.h
index edb68b7..4e00936 100644
--- a/ios/web/navigation/crw_js_navigation_handler.h
+++ b/ios/web/navigation/crw_js_navigation_handler.h
@@ -8,35 +8,42 @@
 #import <WebKit/WebKit.h>
 
 #import "ios/web/web_state/ui/crw_web_view_handler.h"
-#import "ios/web/web_state/ui/crw_web_view_handler_delegate.h"
 #include "url/gurl.h"
 
-@class CRWJSNavigationHandler;
+namespace base {
+class Value;
+}  // namespace base
 
-@protocol CRWJSNavigationHandlerDelegate <CRWWebViewHandlerDelegate>
-
-// Returns the current URL of web view.
-- (GURL)currentURLForJSNavigationHandler:
-    (CRWJSNavigationHandler*)navigationHandler;
-
-// Finds all the scrollviews in the view hierarchy and makes sure they do not
-// interfere with scroll to top when tapping the statusbar.
-- (void)JSNavigationHandlerOptOutScrollsToTopForSubviews:
-    (CRWJSNavigationHandler*)navigationHandler;
-
-@end
+namespace web {
+class UserInteractionState;
+class WebStateImpl;
+}  // namespace web
 
 // Handles JS messages related to navigation(e.g. window.history.forward).
 @interface CRWJSNavigationHandler : CRWWebViewHandler
 
-- (instancetype)init NS_UNAVAILABLE;
-- (instancetype)initWithDelegate:(id<CRWJSNavigationHandlerDelegate>)delegate
-    NS_DESIGNATED_INITIALIZER;
-
 // Whether the web page is currently performing window.history.pushState or
 // window.history.replaceState.
 @property(nonatomic, assign) BOOL changingHistoryState;
 
+// Handles a navigation will change state message for the current webpage.
+- (void)handleNavigationWillChangeState;
+
+// Handles a navigation did push state message for the current webpage.
+- (void)handleNavigationDidPushStateMessage:(base::Value*)message
+                                   webState:(web::WebStateImpl*)webStateImpl
+                             hasUserGesture:(BOOL)hasUserGesture
+                       userInteractionState:
+                           (web::UserInteractionState*)userInteractionState
+                                 currentURL:(GURL)currentURL;
+
+// Handles a navigation did replace state message for the current webpage.
+- (void)handleNavigationDidReplaceStateMessage:(base::Value*)message
+                                      webState:(web::WebStateImpl*)webStateImpl
+                                hasUserGesture:(BOOL)hasUserGesture
+                          userInteractionState:
+                              (web::UserInteractionState*)userInteractionState
+                                    currentURL:(GURL)currentURL;
 @end
 
 #endif  // IOS_WEB_NAVIGATION_CRW_JS_NAVIGATION_HANDLER_H_
diff --git a/ios/web/navigation/crw_js_navigation_handler.mm b/ios/web/navigation/crw_js_navigation_handler.mm
index cdd3ab01..be4353fd 100644
--- a/ios/web/navigation/crw_js_navigation_handler.mm
+++ b/ios/web/navigation/crw_js_navigation_handler.mm
@@ -11,7 +11,6 @@
 #import "ios/web/navigation/navigation_context_impl.h"
 #import "ios/web/navigation/navigation_item_impl.h"
 #import "ios/web/navigation/navigation_manager_impl.h"
-#include "ios/web/public/js_messaging/web_frame.h"
 #import "ios/web/web_state/user_interaction_state.h"
 #import "ios/web/web_state/web_state_impl.h"
 #import "net/base/mac/url_conversions.h"
@@ -21,7 +20,6 @@
 #endif
 
 namespace {
-const char kCommandPrefix[] = "navigation";
 
 // URLs that are fed into WKWebView as history push/replace get escaped,
 // potentially changing their format. Code that attempts to determine whether a
@@ -36,130 +34,55 @@
 
 }  // namespace
 
-@interface CRWJSNavigationHandler () {
-  // Subscription for JS message.
-  base::CallbackListSubscription _subscription;
-}
-
-@property(nonatomic, weak) id<CRWJSNavigationHandlerDelegate> delegate;
-
-// Returns WebStateImpl from self.delegate.
-@property(nonatomic, readonly, assign) web::WebStateImpl* webStateImpl;
-// Returns NavigationManagerImpl from self.webStateImpl.
-@property(nonatomic, readonly, assign)
-    web::NavigationManagerImpl* navigationManagerImpl;
-// Returns UserInteractionState from self.delegate.
-@property(nonatomic, readonly, assign)
-    web::UserInteractionState* userInteractionState;
-// Returns WKWebView from self.delegate.
-@property(nonatomic, readonly, weak) WKWebView* webView;
-// Returns current URL from self.delegate.
-@property(nonatomic, readonly, assign) GURL currentURL;
-
-@end
-
 @implementation CRWJSNavigationHandler
 
 #pragma mark - Public
 
-- (instancetype)initWithDelegate:(id<CRWJSNavigationHandlerDelegate>)delegate {
-  if (self = [super init]) {
-    _delegate = delegate;
-
-    __weak CRWJSNavigationHandler* weakSelf = self;
-    auto navigationStateCallback = ^(const base::Value& message, const GURL&,
-                                     bool /* user_is_interacting */,
-                                     web::WebFrame* senderFrame) {
-      if (!senderFrame->IsMainFrame())
-        return;
-
-      const std::string* command = message.FindStringKey("command");
-      if (!command)
-        return;
-
-      if (*command == "navigation.hashchange") {
-        [weakSelf handleNavigationHashChangeInFrame:senderFrame];
-      } else if (*command == "navigation.willChangeState") {
-        [weakSelf handleNavigationWillChangeStateInFrame:senderFrame];
-      } else if (*command == "navigation.didPushState") {
-        [weakSelf handleNavigationDidPushStateMessage:message
-                                              inFrame:senderFrame];
-      } else if (*command == "navigation.didReplaceState") {
-        [weakSelf handleNavigationDidReplaceStateMessage:message
-                                                 inFrame:senderFrame];
-      }
-    };
-
-    _subscription = self.webStateImpl->AddScriptCommandCallback(
-        base::BindRepeating(navigationStateCallback), kCommandPrefix);
-  }
-  return self;
-}
-
-#pragma mark - Private
-
-- (web::WebStateImpl*)webStateImpl {
-  return [self.delegate webStateImplForWebViewHandler:self];
-}
-
-- (web::NavigationManagerImpl*)navigationManagerImpl {
-  return &(self.webStateImpl->GetNavigationManagerImpl());
-}
-
-- (web::UserInteractionState*)userInteractionState {
-  return [self.delegate userInteractionStateForWebViewHandler:self];
-}
-
-- (WKWebView*)webView {
-  return [self.delegate webViewForWebViewHandler:self];
-}
-
-- (GURL)currentURL {
-  return [self.delegate currentURLForJSNavigationHandler:self];
-}
-
-// Handles the navigation.hashchange event emitted from |senderFrame|.
-- (void)handleNavigationHashChangeInFrame:(web::WebFrame*)senderFrame {
-  self.navigationManagerImpl->GetCurrentItemImpl()->SetIsCreatedFromHashChange(
-      true);
-}
-
-// Handles the navigation.willChangeState message sent from |senderFrame|.
-- (void)handleNavigationWillChangeStateInFrame:(web::WebFrame*)senderFrame {
+- (void)handleNavigationWillChangeState {
   self.changingHistoryState = YES;
 }
 
-// Handles the navigation.didChangeState message sent from |senderFrame|.
-- (void)handleNavigationDidPushStateMessage:(const base::Value&)message
-                                    inFrame:(web::WebFrame*)senderFrame {
+- (void)handleNavigationDidPushStateMessage:(base::Value*)message
+                                   webState:(web::WebStateImpl*)webStateImpl
+                             hasUserGesture:(BOOL)hasUserGesture
+                       userInteractionState:
+                           (web::UserInteractionState*)userInteractionState
+                                 currentURL:(GURL)currentURL {
+  if (!webStateImpl || webStateImpl->IsBeingDestroyed()) {
+    // Ignore messages received after WebState is being destroyed.
+    return;
+  }
+
   DCHECK(self.changingHistoryState);
   self.changingHistoryState = NO;
 
+  const web::NavigationManagerImpl& navigationManagerImpl =
+      webStateImpl->GetNavigationManagerImpl();
+
   // If there is a pending entry, a new navigation has been registered but
   // hasn't begun loading.  Since the pushState message is coming from the
   // previous page, ignore it and allow the previously registered navigation to
   // continue.  This can ocur if a pushState is issued from an anchor tag
   // onClick event, as the click would have already been registered.
-  if (self.navigationManagerImpl->GetPendingItem()) {
+  if (navigationManagerImpl.GetPendingItem()) {
     return;
   }
 
-  const std::string* pageURL = message.FindStringKey("pageUrl");
-  const std::string* baseURL = message.FindStringKey("baseUrl");
+  const std::string* pageURL = message->FindStringKey("pageUrl");
+  const std::string* baseURL = message->FindStringKey("baseUrl");
   if (!pageURL || !baseURL) {
     DLOG(WARNING) << "JS message parameter not found: pageUrl or baseUrl";
     return;
   }
   GURL pushURL = web::history_state_util::GetHistoryStateChangeUrl(
-      self.currentURL, GURL(*baseURL), *pageURL);
+      currentURL, GURL(*baseURL), *pageURL);
   // UIWebView seems to choke on unicode characters that haven't been
   // escaped; escape the URL now so the expected load URL is correct.
   pushURL = URLEscapedForHistory(pushURL);
   if (!pushURL.is_valid())
     return;
 
-  web::NavigationItemImpl* navItem =
-      self.navigationManagerImpl->GetCurrentItemImpl();
+  web::NavigationItemImpl* navItem = navigationManagerImpl.GetCurrentItemImpl();
   // PushState happened before first navigation entry or called when the
   // navigation entry does not contain a valid URL.
   if (!navItem || !navItem->GetURL().is_valid())
@@ -171,17 +94,17 @@
     // just before the pushState.
     return;
   }
-  const std::string* stateObjectJSON = message.FindStringKey("stateObject");
+  const std::string* stateObjectJSON = message->FindStringKey("stateObject");
   if (!stateObjectJSON) {
     DLOG(WARNING) << "JS message parameter not found: stateObject";
     return;
   }
   NSString* stateObject = base::SysUTF8ToNSString(*stateObjectJSON);
 
-  int currentIndex = self.navigationManagerImpl->GetIndexOfItem(navItem);
+  int currentIndex = navigationManagerImpl.GetIndexOfItem(navItem);
   if (currentIndex > 0) {
     web::NavigationItem* previousItem =
-        self.navigationManagerImpl->GetItemAtIndex(currentIndex - 1);
+        navigationManagerImpl.GetItemAtIndex(currentIndex - 1);
     web::UserAgentType userAgent = previousItem->GetUserAgentType();
     if (userAgent != web::UserAgentType::NONE) {
       navItem->SetUserAgentType(userAgent);
@@ -192,42 +115,51 @@
   // input and should not be added to the history stack.
   // TODO(crbug.com/549301): Improve transition detection.
   ui::PageTransition transition =
-      self.userInteractionState->UserInteractionRegisteredSincePageLoaded()
+      userInteractionState->UserInteractionRegisteredSincePageLoaded()
           ? ui::PAGE_TRANSITION_LINK
           : ui::PAGE_TRANSITION_CLIENT_REDIRECT;
   [self pushStateWithPageURL:pushURL
                  stateObject:stateObject
                   transition:transition
-              hasUserGesture:self.userInteractionState->IsUserInteracting(
-                                 self.webView)];
-  [self.delegate webViewHandlerUpdateSSLStatusForCurrentNavigationItem:self];
+              hasUserGesture:hasUserGesture
+        userInteractionState:userInteractionState
+                    webState:webStateImpl];
 }
 
-// Handles the navigation.didReplaceState message sent from |senderFrame|.
-- (void)handleNavigationDidReplaceStateMessage:(const base::Value&)message
-                                       inFrame:(web::WebFrame*)senderFrame {
+- (void)handleNavigationDidReplaceStateMessage:(base::Value*)message
+                                      webState:(web::WebStateImpl*)webStateImpl
+                                hasUserGesture:(BOOL)hasUserGesture
+                          userInteractionState:
+                              (web::UserInteractionState*)userInteractionState
+                                    currentURL:(GURL)currentURL {
+  if (!webStateImpl || webStateImpl->IsBeingDestroyed()) {
+    // Ignore messages received after WebState is being destroyed.
+    return;
+  }
+
   DCHECK(self.changingHistoryState);
   self.changingHistoryState = NO;
 
-  const std::string* pageURL = message.FindStringKey("pageUrl");
-  const std::string* baseURL = message.FindStringKey("baseUrl");
+  const std::string* pageURL = message->FindStringKey("pageUrl");
+  const std::string* baseURL = message->FindStringKey("baseUrl");
   if (!pageURL || !baseURL) {
     DLOG(WARNING) << "JS message parameter not found: pageUrl or baseUrl";
     return;
   }
   GURL replaceURL = web::history_state_util::GetHistoryStateChangeUrl(
-      self.currentURL, GURL(*baseURL), *pageURL);
+      currentURL, GURL(*baseURL), *pageURL);
   // UIWebView seems to choke on unicode characters that haven't been
   // escaped; escape the URL now so the expected load URL is correct.
   replaceURL = URLEscapedForHistory(replaceURL);
   if (!replaceURL.is_valid())
     return;
 
-  web::NavigationItemImpl* navItem =
-      self.navigationManagerImpl->GetCurrentItemImpl();
+  const web::NavigationManagerImpl& navigationManagerImpl =
+      webStateImpl->GetNavigationManagerImpl();
+  web::NavigationItemImpl* navItem = navigationManagerImpl.GetCurrentItemImpl();
   // ReplaceState happened before first navigation entry or called right
   // after window.open when the url is empty/not valid.
-  if (!navItem || (self.navigationManagerImpl->GetItemCount() <= 1 &&
+  if (!navItem || (navigationManagerImpl.GetItemCount() <= 1 &&
                    navItem->GetURL().is_empty()))
     return;
   if (!web::history_state_util::IsHistoryStateChangeValid(navItem->GetURL(),
@@ -237,7 +169,7 @@
     // new URL is loaded just before the replaceState.
     return;
   }
-  const std::string* stateObjectJSON = message.FindStringKey("stateObject");
+  const std::string* stateObjectJSON = message->FindStringKey("stateObject");
   if (!stateObjectJSON) {
     DLOG(WARNING) << "JS message parameter not found: stateObject";
     return;
@@ -245,10 +177,12 @@
   NSString* stateObject = base::SysUTF8ToNSString(*stateObjectJSON);
   [self replaceStateWithPageURL:replaceURL
                     stateObject:stateObject
-                 hasUserGesture:self.userInteractionState->IsUserInteracting(
-                                    self.webView)];
+                 hasUserGesture:hasUserGesture
+                       webState:webStateImpl];
 }
 
+#pragma mark - Private
+
 // Adds a new NavigationItem with the given URL and state object to the
 // history stack. A state object is a serialized generic JavaScript object
 // that contains details of the UI's state for a given NavigationItem/URL.
@@ -257,33 +191,37 @@
 - (void)pushStateWithPageURL:(const GURL&)pageURL
                  stateObject:(NSString*)stateObject
                   transition:(ui::PageTransition)transition
-              hasUserGesture:(BOOL)hasUserGesture {
+              hasUserGesture:(BOOL)hasUserGesture
+        userInteractionState:(web::UserInteractionState*)userInteractionState
+                    webState:(web::WebStateImpl*)webStateImpl {
   std::unique_ptr<web::NavigationContextImpl> context =
       web::NavigationContextImpl::CreateNavigationContext(
-          self.webStateImpl, pageURL, hasUserGesture, transition,
+          webStateImpl, pageURL, hasUserGesture, transition,
           /*is_renderer_initiated=*/true);
   context->SetIsSameDocument(true);
-  self.webStateImpl->OnNavigationStarted(context.get());
+  webStateImpl->OnNavigationStarted(context.get());
   context->SetHasCommitted(true);
-  self.webStateImpl->OnNavigationFinished(context.get());
-  self.userInteractionState->SetUserInteractionRegisteredSincePageLoaded(false);
+  webStateImpl->OnNavigationFinished(context.get());
+  userInteractionState->SetUserInteractionRegisteredSincePageLoaded(false);
 }
 
 // Assigns the given URL and state object to the current NavigationItem.
 - (void)replaceStateWithPageURL:(const GURL&)pageURL
                     stateObject:(NSString*)stateObject
-                 hasUserGesture:(BOOL)hasUserGesture {
+                 hasUserGesture:(BOOL)hasUserGesture
+                       webState:(web::WebStateImpl*)webStateImpl {
   std::unique_ptr<web::NavigationContextImpl> context =
       web::NavigationContextImpl::CreateNavigationContext(
-          self.webStateImpl, pageURL, hasUserGesture,
+          webStateImpl, pageURL, hasUserGesture,
           ui::PageTransition::PAGE_TRANSITION_CLIENT_REDIRECT,
           /*is_renderer_initiated=*/true);
   context->SetIsSameDocument(true);
-  self.webStateImpl->OnNavigationStarted(context.get());
-  self.navigationManagerImpl->UpdateCurrentItemForReplaceState(pageURL,
-                                                               stateObject);
+  webStateImpl->OnNavigationStarted(context.get());
+  web::NavigationManagerImpl& navigationManagerImpl =
+      webStateImpl->GetNavigationManagerImpl();
+  navigationManagerImpl.UpdateCurrentItemForReplaceState(pageURL, stateObject);
   context->SetHasCommitted(true);
-  self.webStateImpl->OnNavigationFinished(context.get());
+  webStateImpl->OnNavigationFinished(context.get());
 }
 
 @end
diff --git a/ios/web/navigation/navigation_java_script_feature.h b/ios/web/navigation/navigation_java_script_feature.h
new file mode 100644
index 0000000..5918cb1
--- /dev/null
+++ b/ios/web/navigation/navigation_java_script_feature.h
@@ -0,0 +1,38 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_WEB_NAVIGATION_NAVIGATION_JAVA_SCRIPT_FEATURE_H_
+#define IOS_WEB_NAVIGATION_NAVIGATION_JAVA_SCRIPT_FEATURE_H_
+
+#include "base/no_destructor.h"
+#import "ios/web/public/js_messaging/java_script_feature.h"
+
+namespace web {
+
+// A feature which receives messages about the main frame page navigation.
+class NavigationJavaScriptFeature : public web::JavaScriptFeature {
+ public:
+  // This feature holds no state, so only a single static instance is ever
+  // needed.
+  static NavigationJavaScriptFeature* GetInstance();
+
+ private:
+  friend class base::NoDestructor<NavigationJavaScriptFeature>;
+
+  NavigationJavaScriptFeature();
+  ~NavigationJavaScriptFeature() override;
+
+  NavigationJavaScriptFeature(const NavigationJavaScriptFeature&) = delete;
+  NavigationJavaScriptFeature& operator=(const NavigationJavaScriptFeature&) =
+      delete;
+
+  // JavaScriptFeature:
+  absl::optional<std::string> GetScriptMessageHandlerName() const override;
+  void ScriptMessageReceived(web::WebState* web_state,
+                             const web::ScriptMessage& message) override;
+};
+
+}  // namespace web
+
+#endif  // IOS_WEB_NAVIGATION_NAVIGATION_JAVA_SCRIPT_FEATURE_H_
diff --git a/ios/web/navigation/navigation_java_script_feature.mm b/ios/web/navigation/navigation_java_script_feature.mm
new file mode 100644
index 0000000..e536da4e
--- /dev/null
+++ b/ios/web/navigation/navigation_java_script_feature.mm
@@ -0,0 +1,86 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "ios/web/navigation/navigation_java_script_feature.h"
+#import "ios/web/public/js_messaging/java_script_feature_util.h"
+#import "ios/web/public/js_messaging/script_message.h"
+#import "ios/web/web_state/ui/crw_web_controller.h"
+#import "ios/web/web_state/web_state_impl.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+namespace web {
+
+namespace {
+
+const char kScriptName[] = "navigation_js";
+const char kListenersScriptName[] = "navigation_listeners_js";
+const char kScriptHandlerName[] = "NavigationEventMessage";
+
+}  // namespace
+
+// static
+NavigationJavaScriptFeature* NavigationJavaScriptFeature::GetInstance() {
+  static base::NoDestructor<NavigationJavaScriptFeature> instance;
+  return instance.get();
+}
+
+NavigationJavaScriptFeature::NavigationJavaScriptFeature()
+    : JavaScriptFeature(
+          ContentWorld::kPageContentWorld,
+          {FeatureScript::CreateWithFilename(
+               kScriptName,
+               FeatureScript::InjectionTime::kDocumentStart,
+               FeatureScript::TargetFrames::kMainFrame,
+               FeatureScript::ReinjectionBehavior::kInjectOncePerWindow),
+           FeatureScript::CreateWithFilename(
+               kListenersScriptName,
+               FeatureScript::InjectionTime::kDocumentStart,
+               FeatureScript::TargetFrames::kMainFrame,
+               FeatureScript::ReinjectionBehavior::
+                   kReinjectOnDocumentRecreation)},
+          {web::java_script_features::GetCommonJavaScriptFeature(),
+           web::java_script_features::GetMessageJavaScriptFeature()}) {}
+
+NavigationJavaScriptFeature::~NavigationJavaScriptFeature() = default;
+
+absl::optional<std::string>
+NavigationJavaScriptFeature::GetScriptMessageHandlerName() const {
+  return kScriptHandlerName;
+}
+
+void NavigationJavaScriptFeature::ScriptMessageReceived(
+    web::WebState* web_state,
+    const web::ScriptMessage& message) {
+  if (!message.body() || !message.body()->is_dict()) {
+    // Ignore malformed responses.
+    return;
+  }
+
+  if (!message.is_main_frame()) {
+    return;
+  }
+
+  const std::string* command = message.body()->FindStringKey("command");
+  if (!command) {
+    return;
+  }
+
+  CRWWebController* web_controller =
+      static_cast<WebStateImpl*>(web_state)->GetWebController();
+
+  if (*command == "hashchange") {
+    [web_controller handleNavigationHashChange];
+  } else if (*command == "willChangeState") {
+    [web_controller handleNavigationWillChangeState];
+  } else if (*command == "didPushState") {
+    [web_controller handleNavigationDidPushStateMessage:message.body()];
+  } else if (*command == "didReplaceState") {
+    [web_controller handleNavigationDidReplaceStateMessage:message.body()];
+  }
+}
+
+}  // namespace web
diff --git a/ios/web/web_state/js/resources/navigation.js b/ios/web/navigation/resources/navigation.js
similarity index 64%
rename from ios/web/web_state/js/resources/navigation.js
rename to ios/web/navigation/resources/navigation.js
index 2a712b6..860e3022 100644
--- a/ios/web/web_state/js/resources/navigation.js
+++ b/ios/web/navigation/resources/navigation.js
@@ -8,7 +8,7 @@
 
 goog.provide('__crWeb.navigation');
 
-/** Beginning of anonymouse object */
+/** Beginning of anonymous object */
 (function() {
 
 /**
@@ -25,9 +25,35 @@
   // https://heycam.github.io/webidl/#datacloneerror
   this.name = 'DataCloneError';
   this.code = 25;
-  this.message = "Cyclic structures are not supported.";
+  this.message = 'Cyclic structures are not supported.';
 }
 
+// Stores queued messages until they can be sent to the "NavigationEventMessage"
+// handler.
+var queuedMessages = [];
+
+// Attempts to send any queued messages. Messages will be only be removed once
+// they have been sent.
+function sendQueuedMessages() {
+  while (queuedMessages.length > 0) {
+    try {
+      __gCrWeb.common.sendWebKitMessage(
+          'NavigationEventMessage', queuedMessages[0]);
+      queuedMessages.shift();
+    } catch (e) {
+      // 'NavigationEventMessage' message handler is not currently registered.
+      // Send the message later when possible.
+      break;
+    }
+  }
+};
+
+// Queues the |message| and triggers the queue to be sent.
+function queueNavigationEventMessage(message) {
+  queuedMessages.push(message);
+  sendQueuedMessages();
+};
+
 /**
  * Intercepts window.history methods so native code can differentiate between
  * same-document navigation that are state navigations vs. hash navigations.
@@ -37,7 +63,7 @@
  * called for same-document navigation.
  */
 window.history.pushState = function(stateObject, pageTitle, pageUrl) {
-  __gCrWeb.message.invokeOnHost({'command': 'navigation.willChangeState'});
+  queueNavigationEventMessage({'command': 'willChangeState'});
 
   // JSONStringify throws an exception when given a cyclical object. This
   // internal implementation detail should not be exposed to callers of
@@ -52,8 +78,8 @@
   }
   pageUrl = pageUrl || window.location.href;
   originalWindowHistoryPushState.call(history, stateObject, pageTitle, pageUrl);
-  __gCrWeb.message.invokeOnHost({
-    'command': 'navigation.didPushState',
+  queueNavigationEventMessage({
+    'command': 'didPushState',
     'stateObject': serializedState,
     'baseUrl': document.baseURI,
     'pageUrl': pageUrl.toString()
@@ -61,12 +87,12 @@
 };
 
 window.history.replaceState = function(stateObject, pageTitle, pageUrl) {
-  __gCrWeb.message.invokeOnHost({'command': 'navigation.willChangeState'});
+  queueNavigationEventMessage({'command': 'willChangeState'});
 
- // JSONStringify throws an exception when given a cyclical object. This
- // internal implementation detail should not be exposed to callers of
- // replaceState. Instead, throw a standard exception when stringification
- // fails.
+  // JSONStringify throws an exception when given a cyclical object. This
+  // internal implementation detail should not be exposed to callers of
+  // replaceState. Instead, throw a standard exception when stringification
+  // fails.
   try {
     // Calling stringify() on undefined causes a JSON parse error.
     var serializedState = typeof (stateObject) == 'undefined' ?
@@ -78,20 +104,20 @@
   pageUrl = pageUrl || window.location.href;
   originalWindowHistoryReplaceState.call(
       history, stateObject, pageTitle, pageUrl);
-  __gCrWeb.message.invokeOnHost({
-    'command': 'navigation.didReplaceState',
+  queueNavigationEventMessage({
+    'command': 'didReplaceState',
     'stateObject': serializedState,
     'baseUrl': document.baseURI,
     'pageUrl': pageUrl.toString()
   });
 };
 
-window.addEventListener('hashchange', function(evt) {
-  __gCrWeb.message.invokeOnHost({'command': 'navigation.hashchange'});
+window.addEventListener('__gCrWebWindowIdInjected', function() {
+  sendQueuedMessages();
 });
 
 /** Flush the message queue. */
 if (__gCrWeb.message) {
   __gCrWeb.message.invokeQueues();
 }
-}());  // End of anonymouse object
+}());  // End of anonymous object
diff --git a/ios/web/navigation/resources/navigation_listeners.js b/ios/web/navigation/resources/navigation_listeners.js
new file mode 100644
index 0000000..a684bbe
--- /dev/null
+++ b/ios/web/navigation/resources/navigation_listeners.js
@@ -0,0 +1,18 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview Navigation listener to report hash change.
+ */
+
+goog.provide('__crWeb.navigationListeners');
+
+/** Beginning of anonymous object */
+(function() {
+
+window.addEventListener('hashchange', function(evt) {
+  __gCrWeb.common.sendWebKitMessage(
+      'NavigationEventMessage', {'command': 'hashchange'});
+});
+}());  // End of anonymous object
diff --git a/ios/web/web_state/js/resources/main_frame_web_bundle.js b/ios/web/web_state/js/resources/main_frame_web_bundle.js
deleted file mode 100644
index 7825ced7..0000000
--- a/ios/web/web_state/js/resources/main_frame_web_bundle.js
+++ /dev/null
@@ -1,14 +0,0 @@
-// Copyright 2015 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.
-
-// Set of scripts required by web layer backed up by WKWebView.
-goog.provide('__crWeb.mainFrameWebBundle');
-
-// Requires __crWeb.form provided by __crWeb.allFramesWebBundle.
-
-// DEPRECATED
-// Do NOT add new features here, but rather add them using an instance of
-// JavaScriptFeature. Please see the documentation at
-// //ios/web/public/js_messaging/README.md
-goog.require('__crWeb.navigation');
diff --git a/ios/web/web_state/ui/crw_web_controller.h b/ios/web/web_state/ui/crw_web_controller.h
index 18e6bc90..494015e7 100644
--- a/ios/web/web_state/ui/crw_web_controller.h
+++ b/ios/web/web_state/ui/crw_web_controller.h
@@ -11,6 +11,10 @@
 #import "ios/web/web_state/ui/crw_touch_tracking_recognizer.h"
 #import "ios/web/web_state/ui/crw_web_view_navigation_proxy.h"
 
+namespace base {
+class Value;
+}  // namespace base
+
 namespace web {
 
 enum class NavigationInitiationType;
@@ -194,6 +198,20 @@
 // TODO(crbug.com/905939): Remove WindowID.
 - (void)injectWindowID;
 
+#pragma mark Navigation Message Handlers
+
+// Handles a navigation hash change message for the current webpage.
+- (void)handleNavigationHashChange;
+
+// Handles a navigation will change message for the current webpage.
+- (void)handleNavigationWillChangeState;
+
+// Handles a navigation did push state message for the current webpage.
+- (void)handleNavigationDidPushStateMessage:(base::Value*)message;
+
+// Handles a navigation did replace state message for the current webpage.
+- (void)handleNavigationDidReplaceStateMessage:(base::Value*)message;
+
 #pragma mark CRWJSInjectionEvaluator
 
 // Do not use these executeJavaScript functions directly, prefer
diff --git a/ios/web/web_state/ui/crw_web_controller.mm b/ios/web/web_state/ui/crw_web_controller.mm
index a4cb924..241e80f 100644
--- a/ios/web/web_state/ui/crw_web_controller.mm
+++ b/ios/web/web_state/ui/crw_web_controller.mm
@@ -97,7 +97,6 @@
                                 CRWWebControllerContainerViewDelegate,
                                 CRWWebViewNavigationObserverDelegate,
                                 CRWWebRequestControllerDelegate,
-                                CRWJSNavigationHandlerDelegate,
                                 CRWWebViewScrollViewProxyObserver,
                                 CRWWKNavigationHandlerDelegate,
                                 CRWWKUIHandlerDelegate,
@@ -150,7 +149,7 @@
 @property(nonatomic, readonly, strong)
     CRWWKNavigationHandler* navigationHandler;
 @property(nonatomic, readonly, strong)
-    CRWJSNavigationHandler* JSNavigationHandler;
+    CRWJSNavigationHandler* jsNavigationHandler;
 // The WKUIDelegate handler class.
 @property(nonatomic, readonly, strong) CRWWKUIHandler* UIHandler;
 
@@ -307,8 +306,7 @@
 
     _navigationHandler = [[CRWWKNavigationHandler alloc] initWithDelegate:self];
 
-    _JSNavigationHandler =
-        [[CRWJSNavigationHandler alloc] initWithDelegate:self];
+    _jsNavigationHandler = [[CRWJSNavigationHandler alloc] init];
 
     _UIHandler = [[CRWWKUIHandler alloc] init];
     _UIHandler.delegate = self;
@@ -543,7 +541,7 @@
   _SSLStatusUpdater = nil;
   [self.navigationHandler close];
   [self.UIHandler close];
-  [self.JSNavigationHandler close];
+  [self.jsNavigationHandler close];
   [self.requestController close];
   self.swipeRecognizerProvider = nil;
   [self.requestController close];
@@ -895,6 +893,34 @@
   return nil;
 }
 
+- (void)handleNavigationHashChange {
+  self.navigationManagerImpl->GetCurrentItemImpl()->SetIsCreatedFromHashChange(
+      true);
+}
+
+- (void)handleNavigationWillChangeState {
+  [self.jsNavigationHandler handleNavigationWillChangeState];
+}
+
+- (void)handleNavigationDidPushStateMessage:(base::Value*)message {
+  [self.jsNavigationHandler
+      handleNavigationDidPushStateMessage:message
+                                 webState:_webStateImpl
+                           hasUserGesture:self.isUserInteracting
+                     userInteractionState:&_userInteractionState
+                               currentURL:self.currentURL];
+  [self updateSSLStatusForCurrentNavigationItem];
+}
+
+- (void)handleNavigationDidReplaceStateMessage:(base::Value*)message {
+  [self.jsNavigationHandler
+      handleNavigationDidReplaceStateMessage:message
+                                    webState:_webStateImpl
+                              hasUserGesture:self.isUserInteracting
+                        userInteractionState:&_userInteractionState
+                                  currentURL:self.currentURL];
+}
+
 #pragma mark - JavaScript
 
 - (void)injectWindowID {
@@ -1810,7 +1836,7 @@
   // |newNavigationContext| only exists if this method has to create a new
   // context object.
   std::unique_ptr<web::NavigationContextImpl> newNavigationContext;
-  if (!self.JSNavigationHandler.changingHistoryState) {
+  if (!self.jsNavigationHandler.changingHistoryState) {
     if ([self.navigationHandler
             contextForPendingMainFrameNavigationWithURL:newURL]) {
       // NavigationManager::LoadURLWithParams() was called with URL that has
@@ -1831,7 +1857,7 @@
 
   [self setDocumentURL:newURL context:newNavigationContext.get()];
 
-  if (!self.JSNavigationHandler.changingHistoryState) {
+  if (!self.jsNavigationHandler.changingHistoryState) {
     // Pass either newly created context (if it exists) or context that already
     // existed before.
     web::NavigationContextImpl* navigationContext = newNavigationContext.get();
@@ -1950,23 +1976,6 @@
   return nil;
 }
 
-#pragma mark - CRWJSNavigationHandlerDelegate
-
-- (GURL)currentURLForJSNavigationHandler:
-    (CRWJSNavigationHandler*)navigationHandler {
-  return self.currentURL;
-}
-
-- (void)JSNavigationHandlerUpdateSSLStatusForCurrentNavigationItem:
-    (CRWJSNavigationHandler*)navigationHandler {
-  [self updateSSLStatusForCurrentNavigationItem];
-}
-
-- (void)JSNavigationHandlerOptOutScrollsToTopForSubviews:
-    (CRWJSNavigationHandler*)navigationHandler {
-  return [self optOutScrollsToTopForSubviews];
-}
-
 #pragma mark - UIDropInteractionDelegate
 
 - (BOOL)dropInteraction:(UIDropInteraction*)interaction
diff --git a/media/gpu/v4l2/test/v4l2_stateless_decoder.cc b/media/gpu/v4l2/test/v4l2_stateless_decoder.cc
index f50619a..1feaf99 100644
--- a/media/gpu/v4l2/test/v4l2_stateless_decoder.cc
+++ b/media/gpu/v4l2/test/v4l2_stateless_decoder.cc
@@ -128,5 +128,14 @@
   if (!dec->Initialize())
     LOG(FATAL) << "Initialization for decoding failed.";
 
+  for (int i = 0; i < n_frames || n_frames == 0; i++) {
+    LOG(INFO) << "Frame " << i << "...";
+    const Vp9Decoder::Result res = dec->DecodeNextFrame();
+    if (res == Vp9Decoder::kEOStream) {
+      LOG(INFO) << "End of stream.";
+      break;
+    }
+  }
+
   return EXIT_SUCCESS;
 }
diff --git a/media/gpu/v4l2/test/vp9_decoder.cc b/media/gpu/v4l2/test/vp9_decoder.cc
index bc49144..0979511 100644
--- a/media/gpu/v4l2/test/vp9_decoder.cc
+++ b/media/gpu/v4l2/test/vp9_decoder.cc
@@ -125,15 +125,13 @@
   return true;
 }
 
-Vp9Parser::Result Vp9Decoder::ReadNextFrame(Vp9FrameHeader* vp9_frame_header,
+Vp9Parser::Result Vp9Decoder::ReadNextFrame(Vp9FrameHeader& vp9_frame_header,
                                             gfx::Size& size) {
-  DCHECK(vp9_frame_header);
-
   // TODO(jchinlee): reexamine this loop for cleanup.
   while (true) {
     std::unique_ptr<DecryptConfig> null_config;
     Vp9Parser::Result res =
-        vp9_parser_->ParseNextFrame(vp9_frame_header, &size, &null_config);
+        vp9_parser_->ParseNextFrame(&vp9_frame_header, &size, &null_config);
     if (res == Vp9Parser::kEOStream) {
       IvfFrameHeader ivf_frame_header{};
       const uint8_t* ivf_frame_data;
@@ -150,5 +148,26 @@
   }
 }
 
+Vp9Decoder::Result Vp9Decoder::DecodeNextFrame() {
+  gfx::Size size;
+  Vp9FrameHeader frame_hdr{};
+
+  const Vp9Parser::Result parser_res = ReadNextFrame(frame_hdr, size);
+  switch (parser_res) {
+    case Vp9Parser::kInvalidStream:
+      LOG_ASSERT(false) << "Failed to parse frame";
+      return Vp9Decoder::kError;
+    case Vp9Parser::kAwaitingRefresh:
+      LOG_ASSERT(false) << "Unsupported parser return value";
+      return Vp9Decoder::kError;
+    case Vp9Parser::kEOStream:
+      return Vp9Decoder::kEOStream;
+    case Vp9Parser::kOk:
+      return Vp9Decoder::kOk;
+  }
+  NOTREACHED();
+  return Vp9Decoder::kError;
+}
+
 }  // namespace v4l2_test
 }  // namespace media
diff --git a/media/gpu/v4l2/test/vp9_decoder.h b/media/gpu/v4l2/test/vp9_decoder.h
index 60269e3..884bdfe 100644
--- a/media/gpu/v4l2/test/vp9_decoder.h
+++ b/media/gpu/v4l2/test/vp9_decoder.h
@@ -20,6 +20,13 @@
 // A Vp9Decoder decodes VP9-encoded IVF streams using v4l2 ioctl calls.
 class Vp9Decoder {
  public:
+  // Result of decoding the current frame.
+  enum Result {
+    kOk,
+    kError,
+    kEOStream,
+  };
+
   Vp9Decoder(const Vp9Decoder&) = delete;
   Vp9Decoder& operator=(const Vp9Decoder&) = delete;
   ~Vp9Decoder();
@@ -34,6 +41,9 @@
   // https://www.kernel.org/doc/html/v5.10/userspace-api/media/v4l/dev-stateless-decoder.html#initialization
   bool Initialize();
 
+  // Parses next frame from IVF stream and decodes the frame.
+  Vp9Decoder::Result DecodeNextFrame();
+
  private:
   Vp9Decoder(std::unique_ptr<IvfParser> ivf_parser,
              std::unique_ptr<V4L2IoctlShim> v4l2_ioctl,
@@ -42,7 +52,7 @@
 
   // Reads next frame from IVF stream and its size into |vp9_frame_header|
   // and |size| respectively.
-  Vp9Parser::Result ReadNextFrame(Vp9FrameHeader* vp9_frame_header,
+  Vp9Parser::Result ReadNextFrame(Vp9FrameHeader& vp9_frame_header,
                                   gfx::Size& size);
 
   // Parser for the IVF stream to decode.
diff --git a/net/cert/multi_log_ct_verifier_unittest.cc b/net/cert/multi_log_ct_verifier_unittest.cc
index a26fbcd..bf944c2 100644
--- a/net/cert/multi_log_ct_verifier_unittest.cc
+++ b/net/cert/multi_log_ct_verifier_unittest.cc
@@ -70,8 +70,9 @@
     ASSERT_TRUE(embedded_sct_chain_.get());
   }
 
-  bool CheckForEmbeddedSCTInNetLog(const RecordingTestNetLog& net_log) {
-    auto entries = net_log.GetEntries();
+  bool CheckForEmbeddedSCTInNetLog(
+      const RecordingNetLogObserver& net_log_observer) {
+    auto entries = net_log_observer.GetEntries();
     if (entries.size() != 2)
       return false;
 
@@ -118,15 +119,15 @@
   // |kLogDescription|.
   bool CheckPrecertificateVerification(scoped_refptr<X509Certificate> chain) {
     SignedCertificateTimestampAndStatusList scts;
-    RecordingTestNetLog test_net_log;
+    RecordingNetLogObserver net_log_observer(NetLogCaptureMode::kDefault);
     NetLogWithSource net_log = NetLogWithSource::Make(
-        &test_net_log, NetLogSourceType::SSL_CONNECT_JOB);
+        NetLog::Get(), NetLogSourceType::SSL_CONNECT_JOB);
     verifier_->Verify(kHostname, chain.get(), base::StringPiece(),
                       base::StringPiece(), &scts, net_log);
     return ct::CheckForSingleVerifiedSCTInResult(scts, kLogDescription) &&
            ct::CheckForSCTOrigin(
                scts, ct::SignedCertificateTimestamp::SCT_EMBEDDED) &&
-           CheckForEmbeddedSCTInNetLog(test_net_log);
+           CheckForEmbeddedSCTInNetLog(net_log_observer);
   }
 
   // Histogram-related helper methods
diff --git a/net/cookies/cookie_monster_unittest.cc b/net/cookies/cookie_monster_unittest.cc
index de6187a..bdb8e0c 100644
--- a/net/cookies/cookie_monster_unittest.cc
+++ b/net/cookies/cookie_monster_unittest.cc
@@ -369,7 +369,7 @@
     const int more_than_enough_cookies = domain_max_cookies + 10;
     // Add a bunch of cookies on a single host, should purge them.
     {
-      auto cm = std::make_unique<CookieMonster>(nullptr, &net_log_);
+      auto cm = std::make_unique<CookieMonster>(nullptr, net::NetLog::Get());
       for (int i = 0; i < more_than_enough_cookies; ++i) {
         std::string cookie = base::StringPrintf("a%03d=b", i);
         EXPECT_TRUE(SetCookie(cm.get(), http_www_foo_.url(), cookie));
@@ -390,7 +390,7 @@
     // between them.  We shouldn't go above kDomainMaxCookies for both together.
     GURL url_google_specific(http_www_foo_.Format("http://www.gmail.%D"));
     {
-      auto cm = std::make_unique<CookieMonster>(nullptr, &net_log_);
+      auto cm = std::make_unique<CookieMonster>(nullptr, net::NetLog::Get());
       for (int i = 0; i < more_than_enough_cookies; ++i) {
         std::string cookie_general = base::StringPrintf("a%03d=b", i);
         EXPECT_TRUE(SetCookie(cm.get(), http_www_foo_.url(), cookie_general));
@@ -426,7 +426,7 @@
     // Test histogram for the number of registrable domains affected by domain
     // purge.
     {
-      auto cm = std::make_unique<CookieMonster>(nullptr, &net_log_);
+      auto cm = std::make_unique<CookieMonster>(nullptr, net::NetLog::Get());
       GURL url;
       for (int domain_num = 0; domain_num < 3; ++domain_num) {
         url = GURL(base::StringPrintf("http://domain%d.test", domain_num));
@@ -612,7 +612,7 @@
     std::unique_ptr<CookieMonster> cm;
 
     if (alt_host_entries == nullptr) {
-      cm = std::make_unique<CookieMonster>(nullptr, &net_log_);
+      cm = std::make_unique<CookieMonster>(nullptr, net::NetLog::Get());
     } else {
       // When generating all of these cookies on alternate hosts, they need to
       // be all older than the max "safe" date for GC, which is currently 30
@@ -657,7 +657,7 @@
     DCHECK_EQ(150U, CookieMonster::kDomainMaxCookies -
                         CookieMonster::kDomainPurgeCookies);
 
-    auto cm = std::make_unique<CookieMonster>(nullptr, &net_log_);
+    auto cm = std::make_unique<CookieMonster>(nullptr, net::NetLog::Get());
     // Key:
     // Round 1 => LN; round 2 => LS; round 3 => MN.
     // Round 4 => HN; round 5 => MS; round 6 => HS
@@ -723,7 +723,7 @@
     DCHECK_EQ(150U, CookieMonster::kDomainMaxCookies -
                         CookieMonster::kDomainPurgeCookies);
 
-    auto cm = std::make_unique<CookieMonster>(nullptr, &net_log_);
+    auto cm = std::make_unique<CookieMonster>(nullptr, net::NetLog::Get());
     // Key:
     // Round 1 => LN; round 2 => LS; round 3 => MN.
     // Round 4 => HN; round 5 => MS; round 6 => HS
@@ -783,7 +783,7 @@
     DCHECK_EQ(150U, CookieMonster::kDomainMaxCookies -
                         CookieMonster::kDomainPurgeCookies);
 
-    auto cm = std::make_unique<CookieMonster>(nullptr, &net_log_);
+    auto cm = std::make_unique<CookieMonster>(nullptr, net::NetLog::Get());
     // Key:
     // Round 1 => LN; round 2 => LS; round 3 => MN.
     // Round 4 => HN; round 5 => MS; round 6 => HS
@@ -905,7 +905,7 @@
   void TestPartitionedCookiesGarbageCollectionHelper() {
     DCHECK_EQ(10u, CookieMonster::kPerPartitionDomainMaxCookies);
     int max_cookies = CookieMonster::kPerPartitionDomainMaxCookies;
-    auto cm = std::make_unique<CookieMonster>(nullptr, &net_log_);
+    auto cm = std::make_unique<CookieMonster>(nullptr, net::NetLog::Get());
 
     auto cookie_partition_key =
         CookiePartitionKey::FromURLForTesting(GURL("https://toplevelsite.com"));
@@ -927,7 +927,7 @@
   // Function for creating a CM with a number of cookies in it,
   // no store (and hence no ability to affect access time).
   CookieMonster* CreateMonsterForGC(int num_cookies) {
-    CookieMonster* cm(new CookieMonster(nullptr, &net_log_));
+    CookieMonster* cm(new CookieMonster(nullptr, net::NetLog::Get()));
     base::Time creation_time = base::Time::Now();
     for (int i = 0; i < num_cookies; i++) {
       std::unique_ptr<CanonicalCookie> cc(
@@ -961,7 +961,7 @@
 
     return false;
   }
-  RecordingTestNetLog net_log_;
+  RecordingNetLogObserver net_log_;
 };
 
 using CookieMonsterTest = CookieMonsterTestBase<CookieMonsterTestTraits>;
@@ -994,8 +994,8 @@
   DeferredCookieTaskTest() {
     persistent_store_ = base::MakeRefCounted<MockPersistentCookieStore>();
     persistent_store_->set_store_load_commands(true);
-    cookie_monster_ =
-        std::make_unique<CookieMonster>(persistent_store_.get(), &net_log_);
+    cookie_monster_ = std::make_unique<CookieMonster>(persistent_store_.get(),
+                                                      net::NetLog::Get());
   }
 
   // Defines a cookie to be returned from PersistentCookieStore::Load
@@ -1410,7 +1410,8 @@
 
 TEST_F(CookieMonsterTest, TestCookieDeleteAll) {
   scoped_refptr<MockPersistentCookieStore> store(new MockPersistentCookieStore);
-  std::unique_ptr<CookieMonster> cm(new CookieMonster(store.get(), &net_log_));
+  std::unique_ptr<CookieMonster> cm(
+      new CookieMonster(store.get(), net::NetLog::Get()));
   CookieOptions options = CookieOptions::MakeAllInclusive();
 
   EXPECT_TRUE(SetCookie(cm.get(), http_www_foo_.url(), kValidCookieLine));
@@ -1453,7 +1454,7 @@
 }
 
 TEST_F(CookieMonsterTest, TestCookieDeleteAllCreatedInTimeRangeTimestamps) {
-  auto cm = std::make_unique<CookieMonster>(nullptr, &net_log_);
+  auto cm = std::make_unique<CookieMonster>(nullptr, net::NetLog::Get());
 
   Time now = Time::Now();
 
@@ -1541,7 +1542,7 @@
 
 TEST_F(CookieMonsterTest,
        TestCookieDeleteAllCreatedInTimeRangeTimestampsWithInfo) {
-  auto cm = std::make_unique<CookieMonster>(nullptr, &net_log_);
+  auto cm = std::make_unique<CookieMonster>(nullptr, net::NetLog::Get());
 
   Time now = Time::Now();
 
@@ -1629,7 +1630,7 @@
 }
 
 TEST_F(CookieMonsterTest, TestCookieDeleteMatchingCookies) {
-  auto cm = std::make_unique<CookieMonster>(nullptr, &net_log_);
+  auto cm = std::make_unique<CookieMonster>(nullptr, net::NetLog::Get());
   Time now = Time::Now();
 
   // Nothing has been added so nothing should be deleted.
@@ -1704,7 +1705,7 @@
 
 TEST_F(CookieMonsterTest, TestLastAccess) {
   std::unique_ptr<CookieMonster> cm(
-      new CookieMonster(nullptr, kLastAccessThreshold, &net_log_));
+      new CookieMonster(nullptr, kLastAccessThreshold, net::NetLog::Get()));
 
   EXPECT_TRUE(SetCookie(cm.get(), http_www_foo_.url(), "A=B"));
   const Time last_access_date(GetFirstCookieAccessDate(cm.get()));
@@ -1762,9 +1763,10 @@
 }
 
 TEST_F(CookieMonsterTest, SetCookieableSchemes) {
-  auto cm = std::make_unique<CookieMonster>(nullptr, &net_log_);
+  auto cm = std::make_unique<CookieMonster>(nullptr, net::NetLog::Get());
 
-  std::unique_ptr<CookieMonster> cm_foo(new CookieMonster(nullptr, &net_log_));
+  std::unique_ptr<CookieMonster> cm_foo(
+      new CookieMonster(nullptr, net::NetLog::Get()));
 
   // Only cm_foo should allow foo:// cookies.
   std::vector<std::string> schemes;
@@ -1826,7 +1828,7 @@
 
 TEST_F(CookieMonsterTest, GetAllCookiesForURL) {
   std::unique_ptr<CookieMonster> cm(
-      new CookieMonster(nullptr, kLastAccessThreshold, &net_log_));
+      new CookieMonster(nullptr, kLastAccessThreshold, net::NetLog::Get()));
 
   // Create an httponly cookie.
   CookieOptions options = CookieOptions::MakeAllInclusive();
@@ -1943,7 +1945,7 @@
 
 TEST_F(CookieMonsterTest, GetExcludedCookiesForURL) {
   std::unique_ptr<CookieMonster> cm(
-      new CookieMonster(nullptr, kLastAccessThreshold, &net_log_));
+      new CookieMonster(nullptr, kLastAccessThreshold, net::NetLog::Get()));
 
   // Create an httponly cookie.
   CookieOptions options = CookieOptions::MakeAllInclusive();
@@ -2016,7 +2018,7 @@
 }
 
 TEST_F(CookieMonsterTest, GetAllCookiesForURLPathMatching) {
-  auto cm = std::make_unique<CookieMonster>(nullptr, &net_log_);
+  auto cm = std::make_unique<CookieMonster>(nullptr, net::NetLog::Get());
 
   CookieOptions options = CookieOptions::MakeAllInclusive();
 
@@ -2055,7 +2057,7 @@
 }
 
 TEST_F(CookieMonsterTest, GetExcludedCookiesForURLPathMatching) {
-  auto cm = std::make_unique<CookieMonster>(nullptr, &net_log_);
+  auto cm = std::make_unique<CookieMonster>(nullptr, net::NetLog::Get());
 
   CookieOptions options = CookieOptions::MakeAllInclusive();
 
@@ -2091,7 +2093,7 @@
 }
 
 TEST_F(CookieMonsterTest, CookieSorting) {
-  auto cm = std::make_unique<CookieMonster>(nullptr, &net_log_);
+  auto cm = std::make_unique<CookieMonster>(nullptr, net::NetLog::Get());
 
   base::Time system_time = base::Time::Now();
   for (const char* cookie_line :
@@ -2118,7 +2120,7 @@
 }
 
 TEST_F(CookieMonsterTest, InheritCreationDate) {
-  auto cm = std::make_unique<CookieMonster>(nullptr, &net_log_);
+  auto cm = std::make_unique<CookieMonster>(nullptr, net::NetLog::Get());
 
   base::Time the_not_so_distant_past(base::Time::Now() - base::Seconds(1000));
   EXPECT_TRUE(SetCookieWithCreationTime(cm.get(), http_www_foo_.url(),
@@ -2149,7 +2151,7 @@
 // Check that GetAllCookiesForURL() does not return expired cookies and deletes
 // them.
 TEST_F(CookieMonsterTest, DeleteExpiredCookiesOnGet) {
-  auto cm = std::make_unique<CookieMonster>(nullptr, &net_log_);
+  auto cm = std::make_unique<CookieMonster>(nullptr, net::NetLog::Get());
 
   EXPECT_TRUE(SetCookie(cm.get(), http_www_foo_.url(), "A=B;"));
 
@@ -2246,7 +2248,8 @@
   // Inject our initial cookies into the mock PersistentCookieStore.
   store->SetLoadExpectation(true, std::move(initial_cookies));
 
-  std::unique_ptr<CookieMonster> cm(new CookieMonster(store.get(), &net_log_));
+  std::unique_ptr<CookieMonster> cm(
+      new CookieMonster(store.get(), net::NetLog::Get()));
 
   // Verify that duplicates were not imported for path "/".
   // (If this had failed, GetCookies() would have also returned X=1, X=2, X=4).
@@ -2291,7 +2294,8 @@
   initial_cookies.push_back(std::move(cc));
 
   scoped_refptr<MockPersistentCookieStore> store(new MockPersistentCookieStore);
-  std::unique_ptr<CookieMonster> cm(new CookieMonster(store.get(), &net_log_));
+  std::unique_ptr<CookieMonster> cm(
+      new CookieMonster(store.get(), net::NetLog::Get()));
 
   store->SetLoadExpectation(true, std::move(initial_cookies));
 
@@ -2343,7 +2347,8 @@
   // Inject our initial cookies into the mock PersistentCookieStore.
   store->SetLoadExpectation(true, std::move(initial_cookies));
 
-  std::unique_ptr<CookieMonster> cm(new CookieMonster(store.get(), &net_log_));
+  std::unique_ptr<CookieMonster> cm(
+      new CookieMonster(store.get(), net::NetLog::Get()));
 
   CookieList list(GetAllCookies(cm.get()));
   EXPECT_EQ(2U, list.size());
@@ -2400,7 +2405,8 @@
   // Inject our initial cookies into the mock PersistentCookieStore.
   store->SetLoadExpectation(true, std::move(initial_cookies));
 
-  std::unique_ptr<CookieMonster> cm(new CookieMonster(store.get(), &net_log_));
+  std::unique_ptr<CookieMonster> cm(
+      new CookieMonster(store.get(), net::NetLog::Get()));
 
   CookieList list(GetAllCookies(cm.get()));
   EXPECT_EQ(2U, list.size());
@@ -2413,7 +2419,7 @@
 }
 
 TEST_F(CookieMonsterTest, PredicateSeesAllCookies) {
-  auto cm = std::make_unique<CookieMonster>(nullptr, &net_log_);
+  auto cm = std::make_unique<CookieMonster>(nullptr, net::NetLog::Get());
 
   const base::Time now = PopulateCmForPredicateCheck(cm.get());
   // We test that we can see all cookies with |delete_info|. This includes
@@ -2441,7 +2447,7 @@
 // Mainly a test of GetEffectiveDomain, or more specifically, of the
 // expected behavior of GetEffectiveDomain within the CookieMonster.
 TEST_F(CookieMonsterTest, GetKey) {
-  auto cm = std::make_unique<CookieMonster>(nullptr, &net_log_);
+  auto cm = std::make_unique<CookieMonster>(nullptr, net::NetLog::Get());
 
   // This test is really only interesting if GetKey() actually does something.
   EXPECT_EQ("foo.com", cm->GetKey("www.foo.com"));
@@ -2488,7 +2494,7 @@
   // Create new cookies and flush them to the store.
   {
     std::unique_ptr<CookieMonster> cmout(
-        new CookieMonster(store.get(), &net_log_));
+        new CookieMonster(store.get(), net::NetLog::Get()));
     for (const auto& cookie : input_info) {
       EXPECT_TRUE(SetCanonicalCookie(
           cmout.get(),
@@ -2508,7 +2514,7 @@
   // Create a new cookie monster and make sure that everything is correct
   {
     std::unique_ptr<CookieMonster> cmin(
-        new CookieMonster(store.get(), &net_log_));
+        new CookieMonster(store.get(), net::NetLog::Get()));
     CookieList cookies(GetAllCookies(cmin.get()));
     ASSERT_EQ(2u, cookies.size());
     // Ordering is path length, then creation time.  So second cookie
@@ -2542,7 +2548,7 @@
       base::MakeRefCounted<MockPersistentCookieStore>();
 
   {
-    CookieMonster cmout(store.get(), &net_log_);
+    CookieMonster cmout(store.get(), net::NetLog::Get());
     GURL url("http://www.example.com/");
     EXPECT_TRUE(
         SetCookieWithCreationTime(&cmout, url, "A=1; max-age=600", current));
@@ -2564,7 +2570,7 @@
 
   // Now read them in. Should get two cookies, not one.
   {
-    CookieMonster cmin(store2.get(), &net_log_);
+    CookieMonster cmin(store2.get(), net::NetLog::Get());
     CookieList cookies(GetAllCookies(&cmin));
     ASSERT_EQ(2u, cookies.size());
   }
@@ -2573,7 +2579,7 @@
 TEST_F(CookieMonsterTest, CookieListOrdering) {
   // Put a random set of cookies into a monster and make sure
   // they're returned in the right order.
-  auto cm = std::make_unique<CookieMonster>(nullptr, &net_log_);
+  auto cm = std::make_unique<CookieMonster>(nullptr, net::NetLog::Get());
 
   EXPECT_TRUE(
       SetCookie(cm.get(), GURL("http://d.c.b.a.foo.com/aa/x.html"), "c=1"));
@@ -2712,7 +2718,8 @@
 
   scoped_refptr<MockPersistentCookieStore> store(new MockPersistentCookieStore);
   store->set_store_load_commands(true);
-  std::unique_ptr<CookieMonster> cm(new CookieMonster(store.get(), &net_log_));
+  std::unique_ptr<CookieMonster> cm(
+      new CookieMonster(store.get(), net::NetLog::Get()));
 
   auto cookie = CanonicalCookie::Create(
       kUrl, "a=b", base::Time::Now(), absl::nullopt /* server_time */,
@@ -2761,7 +2768,8 @@
 
   scoped_refptr<MockPersistentCookieStore> store(new MockPersistentCookieStore);
   store->set_store_load_commands(true);
-  std::unique_ptr<CookieMonster> cm(new CookieMonster(store.get(), &net_log_));
+  std::unique_ptr<CookieMonster> cm(
+      new CookieMonster(store.get(), net::NetLog::Get()));
 
   ResultSavingCookieCallback<uint32_t> delete_callback;
   cm->DeleteAllAsync(delete_callback.MakeCallback());
@@ -2800,7 +2808,8 @@
 
   scoped_refptr<MockPersistentCookieStore> store(new MockPersistentCookieStore);
   store->set_store_load_commands(true);
-  std::unique_ptr<CookieMonster> cm(new CookieMonster(store.get(), &net_log_));
+  std::unique_ptr<CookieMonster> cm(
+      new CookieMonster(store.get(), net::NetLog::Get()));
 
   GetAllCookiesCallback get_cookies_callback1;
   cm->GetAllCookiesAsync(get_cookies_callback1.MakeCallback());
@@ -2850,7 +2859,8 @@
 
   scoped_refptr<MockPersistentCookieStore> store(new MockPersistentCookieStore);
   store->set_store_load_commands(true);
-  std::unique_ptr<CookieMonster> cm(new CookieMonster(store.get(), &net_log_));
+  std::unique_ptr<CookieMonster> cm(
+      new CookieMonster(store.get(), net::NetLog::Get()));
 
   // Get all cookies task that queues a task to set a cookie when executed.
   auto cookie = CanonicalCookie::Create(
@@ -2894,7 +2904,7 @@
 TEST_F(CookieMonsterTest, FlushStore) {
   auto counter = base::MakeRefCounted<CallbackCounter>();
   auto store = base::MakeRefCounted<FlushablePersistentStore>();
-  auto cm = std::make_unique<CookieMonster>(store, &net_log_);
+  auto cm = std::make_unique<CookieMonster>(store, net::NetLog::Get());
 
   ASSERT_EQ(0, store->flush_count());
   ASSERT_EQ(0, counter->callback_count());
@@ -2929,7 +2939,7 @@
   ASSERT_EQ(2, counter->callback_count());
 
   // If there's no backing store, FlushStore() is always a safe no-op.
-  cm = std::make_unique<CookieMonster>(nullptr, &net_log_);
+  cm = std::make_unique<CookieMonster>(nullptr, net::NetLog::Get());
   GetAllCookies(cm.get());  // Force init.
   cm->FlushStore(base::DoNothing());
   base::RunLoop().RunUntilIdle();
@@ -2944,7 +2954,7 @@
 
 TEST_F(CookieMonsterTest, SetAllCookies) {
   scoped_refptr<FlushablePersistentStore> store(new FlushablePersistentStore());
-  auto cm = std::make_unique<CookieMonster>(store.get(), &net_log_);
+  auto cm = std::make_unique<CookieMonster>(store.get(), net::NetLog::Get());
   cm->SetPersistSessionCookies(true);
 
   EXPECT_TRUE(SetCookie(cm.get(), http_www_foo_.url(), "U=V; path=/"));
@@ -3014,7 +3024,7 @@
 // Check that DeleteAll does flush (as a quick check that flush_count() works).
 TEST_F(CookieMonsterTest, DeleteAll) {
   scoped_refptr<FlushablePersistentStore> store(new FlushablePersistentStore());
-  auto cm = std::make_unique<CookieMonster>(store.get(), &net_log_);
+  auto cm = std::make_unique<CookieMonster>(store.get(), net::NetLog::Get());
   cm->SetPersistSessionCookies(true);
 
   EXPECT_TRUE(SetCookie(cm.get(), http_www_foo_.url(), "X=Y; path=/"));
@@ -3041,7 +3051,7 @@
 }
 
 TEST_F(CookieMonsterTest, HistogramCheck) {
-  auto cm = std::make_unique<CookieMonster>(nullptr, &net_log_);
+  auto cm = std::make_unique<CookieMonster>(nullptr, net::NetLog::Get());
 
   // Should match call in InitializeHistograms, but doesn't really matter
   // since the histogram should have been initialized by the CM construction
@@ -3086,7 +3096,8 @@
 // CookieStore if the "persist session cookies" option is on.
 TEST_F(CookieMonsterTest, PersistSessionCookies) {
   scoped_refptr<MockPersistentCookieStore> store(new MockPersistentCookieStore);
-  std::unique_ptr<CookieMonster> cm(new CookieMonster(store.get(), &net_log_));
+  std::unique_ptr<CookieMonster> cm(
+      new CookieMonster(store.get(), net::NetLog::Get()));
   cm->SetPersistSessionCookies(true);
 
   // All cookies set with SetCookie are session cookies.
@@ -3123,7 +3134,8 @@
 // Test the commands sent to the persistent cookie store.
 TEST_F(CookieMonsterTest, PersisentCookieStorageTest) {
   scoped_refptr<MockPersistentCookieStore> store(new MockPersistentCookieStore);
-  std::unique_ptr<CookieMonster> cm(new CookieMonster(store.get(), &net_log_));
+  std::unique_ptr<CookieMonster> cm(
+      new CookieMonster(store.get(), net::NetLog::Get()));
 
   // Add a cookie.
   EXPECT_TRUE(SetCookie(cm.get(), http_www_foo_.url(),
@@ -3218,7 +3230,8 @@
   // Inject our initial cookies into the mock PersistentCookieStore.
   store->SetLoadExpectation(true, std::move(initial_cookies));
 
-  std::unique_ptr<CookieMonster> cm(new CookieMonster(store.get(), &net_log_));
+  std::unique_ptr<CookieMonster> cm(
+      new CookieMonster(store.get(), net::NetLog::Get()));
 
   EXPECT_EQ(
       "foo=bar; hello=world",
@@ -3231,7 +3244,8 @@
   const std::string cookie_source_histogram = "Cookie.CookieSourceScheme";
 
   scoped_refptr<MockPersistentCookieStore> store(new MockPersistentCookieStore);
-  std::unique_ptr<CookieMonster> cm(new CookieMonster(store.get(), &net_log_));
+  std::unique_ptr<CookieMonster> cm(
+      new CookieMonster(store.get(), net::NetLog::Get()));
 
   histograms.ExpectTotalCount(cookie_source_histogram, 0);
 
@@ -3317,7 +3331,7 @@
       absl::nullopt /* cookie_partition_key */));
   store->SetLoadExpectation(true /* return_value */,
                             std::move(initial_cookies));
-  auto cm = std::make_unique<CookieMonster>(store.get(), &net_log_);
+  auto cm = std::make_unique<CookieMonster>(store.get(), net::NetLog::Get());
   {
     base::HistogramTester histogram_tester;
     // Access the cookies to trigger loading from the persistent store.
@@ -3462,7 +3476,8 @@
 
 TEST_F(CookieMonsterTest, MaybeDeleteEquivalentCookieAndUpdateStatus) {
   scoped_refptr<MockPersistentCookieStore> store(new MockPersistentCookieStore);
-  std::unique_ptr<CookieMonster> cm(new CookieMonster(store.get(), &net_log_));
+  std::unique_ptr<CookieMonster> cm(
+      new CookieMonster(store.get(), net::NetLog::Get()));
 
   // Set a secure, httponly cookie from a secure origin
   auto preexisting_cookie = CanonicalCookie::Create(
@@ -3568,7 +3583,8 @@
 TEST_F(CookieMonsterTest,
        MaybeDeleteEquivalentCookieAndUpdateStatus_PartitionedCookies) {
   scoped_refptr<MockPersistentCookieStore> store(new MockPersistentCookieStore);
-  std::unique_ptr<CookieMonster> cm(new CookieMonster(store.get(), &net_log_));
+  std::unique_ptr<CookieMonster> cm(
+      new CookieMonster(store.get(), net::NetLog::Get()));
 
   // Test adding two cookies with the same name, domain, and path but different
   // partition keys.
@@ -3611,7 +3627,8 @@
 // multiple reasons (Secure and HttpOnly).
 TEST_F(CookieMonsterTest, SkipDontOverwriteForMultipleReasons) {
   scoped_refptr<MockPersistentCookieStore> store(new MockPersistentCookieStore);
-  std::unique_ptr<CookieMonster> cm(new CookieMonster(store.get(), &net_log_));
+  std::unique_ptr<CookieMonster> cm(
+      new CookieMonster(store.get(), net::NetLog::Get()));
 
   // Set a secure, httponly cookie from a secure origin
   auto preexisting_cookie = CanonicalCookie::Create(
@@ -3649,7 +3666,8 @@
 // cookie should not be set.
 TEST_F(CookieMonsterTest, DontDeleteEquivalentCookieIfSetIsRejected) {
   scoped_refptr<MockPersistentCookieStore> store(new MockPersistentCookieStore);
-  std::unique_ptr<CookieMonster> cm(new CookieMonster(store.get(), &net_log_));
+  std::unique_ptr<CookieMonster> cm(
+      new CookieMonster(store.get(), net::NetLog::Get()));
 
   auto preexisting_cookie = CanonicalCookie::Create(
       http_www_foo_.url(), "cookie=foo", base::Time::Now(),
@@ -3675,7 +3693,7 @@
 }
 
 TEST_F(CookieMonsterTest, SetSecureCookies) {
-  auto cm = std::make_unique<CookieMonster>(nullptr, &net_log_);
+  auto cm = std::make_unique<CookieMonster>(nullptr, net::NetLog::Get());
 
   GURL http_url("http://www.foo.com");
   GURL http_superdomain_url("http://foo.com");
@@ -3842,7 +3860,7 @@
 }
 
 TEST_F(CookieMonsterTest, SetSamePartyCookies) {
-  CookieMonster cm(nullptr, &net_log_);
+  CookieMonster cm(nullptr, net::NetLog::Get());
   GURL http_url("http://www.foo.com");
   GURL http_superdomain_url("http://foo.com");
   GURL https_url("https://www.foo.com");
@@ -3899,7 +3917,7 @@
 // Check domain-match criterion: If either cookie domain matches the other,
 // don't set the insecure cookie.
 TEST_F(CookieMonsterTest, LeaveSecureCookiesAlone_DomainMatch) {
-  auto cm = std::make_unique<CookieMonster>(nullptr, &net_log_);
+  auto cm = std::make_unique<CookieMonster>(nullptr, net::NetLog::Get());
 
   // These domains will domain-match each other.
   const char* kRegistrableDomain = "foo.com";
@@ -4070,7 +4088,7 @@
 // Check path-match criterion: If the new cookie is for the same path or a
 // subdirectory of the preexisting cookie's path, don't set the new cookie.
 TEST_F(CookieMonsterTest, LeaveSecureCookiesAlone_PathMatch) {
-  auto cm = std::make_unique<CookieMonster>(nullptr, &net_log_);
+  auto cm = std::make_unique<CookieMonster>(nullptr, net::NetLog::Get());
 
   // A path that is later in this list will path-match all the paths before it.
   const char* kPaths[] = {"/", "/1", "/1/2", "/1/2/3"};
@@ -4887,7 +4905,8 @@
   const char kHistogramName[] = "Cookie.DomainSet";
 
   scoped_refptr<MockPersistentCookieStore> store(new MockPersistentCookieStore);
-  std::unique_ptr<CookieMonster> cm(new CookieMonster(store.get(), &net_log_));
+  std::unique_ptr<CookieMonster> cm(
+      new CookieMonster(store.get(), net::NetLog::Get()));
 
   histograms.ExpectTotalCount(kHistogramName, 0);
 
@@ -4915,7 +4934,8 @@
   const char kHistogramNameLocal[] = "Cookie.Port.Read.Localhost";
 
   scoped_refptr<MockPersistentCookieStore> store(new MockPersistentCookieStore);
-  std::unique_ptr<CookieMonster> cm(new CookieMonster(store.get(), &net_log_));
+  std::unique_ptr<CookieMonster> cm(
+      new CookieMonster(store.get(), net::NetLog::Get()));
 
   histograms.ExpectTotalCount(kHistogramName, 0);
 
@@ -4968,7 +4988,8 @@
   const char kHistogramNameLocal[] = "Cookie.Port.Set.Localhost";
 
   scoped_refptr<MockPersistentCookieStore> store(new MockPersistentCookieStore);
-  std::unique_ptr<CookieMonster> cm(new CookieMonster(store.get(), &net_log_));
+  std::unique_ptr<CookieMonster> cm(
+      new CookieMonster(store.get(), net::NetLog::Get()));
 
   histograms.ExpectTotalCount(kHistogramName, 0);
 
@@ -5022,7 +5043,8 @@
       "Cookie.Port.ReadDiffersFromSet.DomainSet";
 
   scoped_refptr<MockPersistentCookieStore> store(new MockPersistentCookieStore);
-  std::unique_ptr<CookieMonster> cm(new CookieMonster(store.get(), &net_log_));
+  std::unique_ptr<CookieMonster> cm(
+      new CookieMonster(store.get(), net::NetLog::Get()));
 
   histograms.ExpectTotalCount(kHistogramName, 0);
 
@@ -5121,7 +5143,8 @@
   const char kHistogramName[] = "Cookie.CookieSourceSchemeName";
 
   scoped_refptr<MockPersistentCookieStore> store(new MockPersistentCookieStore);
-  std::unique_ptr<CookieMonster> cm(new CookieMonster(store.get(), &net_log_));
+  std::unique_ptr<CookieMonster> cm(
+      new CookieMonster(store.get(), net::NetLog::Get()));
 
   histograms.ExpectTotalCount(kHistogramName, 0);
 
diff --git a/net/dns/dns_transaction_unittest.cc b/net/dns/dns_transaction_unittest.cc
index df6b1e4..30fe99a 100644
--- a/net/dns/dns_transaction_unittest.cc
+++ b/net/dns/dns_transaction_unittest.cc
@@ -47,7 +47,6 @@
 #include "net/log/net_log.h"
 #include "net/log/net_log_capture_mode.h"
 #include "net/log/net_log_with_source.h"
-#include "net/log/test_net_log.h"
 #include "net/proxy_resolution/proxy_config_service_fixed.h"
 #include "net/socket/socket_test_util.h"
 #include "net/test/gtest_util.h"
@@ -338,8 +337,9 @@
                         ResolveContext* context) {
     std::unique_ptr<DnsTransaction> transaction = factory->CreateTransaction(
         hostname, qtype, CompletionCallback(),
-        NetLogWithSource::Make(&net_log_, net::NetLogSourceType::NONE), secure,
-        factory->GetSecureDnsModeForTest(), context, true /* fast_timeout */);
+        NetLogWithSource::Make(net::NetLog::Get(), net::NetLogSourceType::NONE),
+        secure, factory->GetSecureDnsModeForTest(), context,
+        true /* fast_timeout */);
     transaction->SetRequestPriority(DEFAULT_PRIORITY);
     EXPECT_EQ(qtype, transaction->GetType());
     StartTransaction(std::move(transaction));
@@ -401,7 +401,6 @@
 
   bool has_completed() const { return completed_; }
   const DnsResponse* response() const { return response_; }
-  NetLog* net_log() { return &net_log_; }
 
   // Runs until the completion callback is called. Transaction must have already
   // been started or this will never complete.
@@ -420,7 +419,6 @@
   bool cancel_in_callback_ = false;
   base::RunLoop transaction_complete_run_loop_;
   bool completed_ = false;
-  TestNetLog net_log_;
 };
 
 // Callback that allows a test to modify HttpResponseinfo
@@ -2590,7 +2588,7 @@
                       false /* enqueue_transaction_id */);
   TransactionHelper helper0(kT0RecordCount);
   CountingObserver observer;
-  helper0.net_log()->AddObserver(&observer, NetLogCaptureMode::kEverything);
+  NetLog::Get()->AddObserver(&observer, NetLogCaptureMode::kEverything);
   helper0.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
                            true /* secure */, resolve_context_.get());
   helper0.RunUntilComplete();
diff --git a/net/dns/host_resolver_manager_fuzzer.cc b/net/dns/host_resolver_manager_fuzzer.cc
index 7c0ed59..cc17b8cb 100644
--- a/net/dns/host_resolver_manager_fuzzer.cc
+++ b/net/dns/host_resolver_manager_fuzzer.cc
@@ -25,6 +25,7 @@
 #include "net/dns/host_resolver.h"
 #include "net/dns/public/dns_query_type.h"
 #include "net/dns/public/host_resolver_source.h"
+#include "net/log/net_log.h"
 #include "net/log/net_log_with_source.h"
 #include "net/log/test_net_log.h"
 #include "net/net_buildflags.h"
@@ -236,7 +237,9 @@
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
   {
     FuzzedDataProvider data_provider(data, size);
-    net::RecordingTestNetLog net_log;
+    // Including an observer; even though the recorded results aren't currently
+    // used, it'll ensure the netlogging code is fuzzed as well.
+    net::RecordingNetLogObserver net_log_observer;
 
     net::HostResolver::ManagerOptions options;
     options.max_concurrent_resolves =
@@ -244,8 +247,8 @@
     options.insecure_dns_client_enabled = data_provider.ConsumeBool();
     bool enable_caching = data_provider.ConsumeBool();
     std::unique_ptr<net::ContextHostResolver> host_resolver =
-        net::CreateFuzzedContextHostResolver(options, &net_log, &data_provider,
-                                             enable_caching);
+        net::CreateFuzzedContextHostResolver(options, net::NetLog::Get(),
+                                             &data_provider, enable_caching);
 
     std::vector<std::unique_ptr<DnsRequest>> dns_requests;
     bool done = false;
diff --git a/net/dns/host_resolver_manager_unittest.cc b/net/dns/host_resolver_manager_unittest.cc
index 5890ed4..e4e7350 100644
--- a/net/dns/host_resolver_manager_unittest.cc
+++ b/net/dns/host_resolver_manager_unittest.cc
@@ -2497,15 +2497,15 @@
       nullptr /* net_log */);
 
   // Verify that two consecutive calls return the same value.
-  RecordingTestNetLog test_net_log;
+  RecordingNetLogObserver net_log_observer;
   NetLogWithSource net_log =
-      NetLogWithSource::Make(&test_net_log, NetLogSourceType::NONE);
+      NetLogWithSource::Make(net::NetLog::Get(), NetLogSourceType::NONE);
   bool result1 = IsIPv6Reachable(net_log);
   bool result2 = IsIPv6Reachable(net_log);
   EXPECT_EQ(result1, result2);
 
   // Filter reachability check events and verify that there are two of them.
-  auto probe_event_list = test_net_log.GetEntriesWithType(
+  auto probe_event_list = net_log_observer.GetEntriesWithType(
       NetLogEventType::HOST_RESOLVER_MANAGER_IPV6_REACHABILITY_CHECK);
   ASSERT_EQ(2U, probe_event_list.size());
 
diff --git a/net/http/http_network_transaction_unittest.cc b/net/http/http_network_transaction_unittest.cc
index 1d0d919..bff22d0 100644
--- a/net/http/http_network_transaction_unittest.cc
+++ b/net/http/http_network_transaction_unittest.cc
@@ -911,10 +911,9 @@
   request.traffic_annotation =
       net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS);
 
-  RecordingTestNetLog log;
   MockHostResolver* resolver = new MockHostResolver();
   resolver->rules()->AddSimulatedTimeoutFailure("www.example.org");
-  session_deps_.net_log = &log;
+  session_deps_.net_log = net::NetLog::Get();
   session_deps_.host_resolver.reset(resolver);
   std::unique_ptr<HttpNetworkSession> session = CreateSession(&session_deps_);
   HttpNetworkTransaction trans(DEFAULT_PRIORITY, session.get());
@@ -1886,8 +1885,7 @@
   request.traffic_annotation =
       net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS);
 
-  RecordingTestNetLog net_log;
-  session_deps_.net_log = &net_log;
+  session_deps_.net_log = net::NetLog::Get();
   std::unique_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
 
   // Written data for successfully sending both requests.
@@ -1987,8 +1985,7 @@
     request.upload_data_stream = &upload_data_stream;
   }
 
-  RecordingTestNetLog net_log;
-  session_deps_.net_log = &net_log;
+  session_deps_.net_log = net::NetLog::Get();
   std::unique_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
 
   SSLSocketDataProvider ssl1(ASYNC, OK);
@@ -2464,8 +2461,7 @@
   request.traffic_annotation =
       net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS);
 
-  RecordingTestNetLog net_log;
-  session_deps_.net_log = &net_log;
+  session_deps_.net_log = net::NetLog::Get();
   std::unique_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
 
   const char* request_data =
@@ -2888,8 +2884,7 @@
   request.traffic_annotation =
       net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS);
 
-  RecordingTestNetLog log;
-  session_deps_.net_log = &log;
+  session_deps_.net_log = net::NetLog::Get();
   std::unique_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
   HttpNetworkTransaction trans(DEFAULT_PRIORITY, session.get());
 
@@ -2994,9 +2989,8 @@
   request.traffic_annotation =
       net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS);
 
-  RecordingTestNetLog log;
   MockHostResolver* resolver = new MockHostResolver();
-  session_deps_.net_log = &log;
+  session_deps_.net_log = net::NetLog::Get();
   session_deps_.host_resolver.reset(resolver);
   std::unique_ptr<HttpNetworkSession> session = CreateSession(&session_deps_);
   HttpNetworkTransaction trans(DEFAULT_PRIORITY, session.get());
@@ -3108,8 +3102,7 @@
   request.traffic_annotation =
       net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS);
 
-  RecordingTestNetLog log;
-  session_deps_.net_log = &log;
+  session_deps_.net_log = net::NetLog::Get();
   std::unique_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
   HttpNetworkTransaction trans(DEFAULT_PRIORITY, session.get());
 
@@ -3228,8 +3221,7 @@
     request.traffic_annotation =
         net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS);
 
-    RecordingTestNetLog log;
-    session_deps_.net_log = &log;
+    session_deps_.net_log = net::NetLog::Get();
     std::unique_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
 
     MockWrite data_writes[] = {
@@ -6392,14 +6384,13 @@
   request.traffic_annotation =
       net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS);
 
-  RecordingTestNetLog log;
   // Configure against https proxy server "proxy:70".
   session_deps_.proxy_resolution_service =
       ConfiguredProxyResolutionService::CreateFixed(
           "https://proxy:70", TRAFFIC_ANNOTATION_FOR_TESTS);
   MockHostResolver* resolver = new MockHostResolver();
   resolver->rules()->AddSimulatedTimeoutFailure("proxy");
-  session_deps_.net_log = &log;
+  session_deps_.net_log = net::NetLog::Get();
   session_deps_.host_resolver.reset(resolver);
   std::unique_ptr<HttpNetworkSession> session = CreateSession(&session_deps_);
   HttpNetworkTransaction trans(DEFAULT_PRIORITY, session.get());
@@ -11259,8 +11250,7 @@
   session_deps_.proxy_resolution_service =
       ConfiguredProxyResolutionService::CreateFixedFromPacResult(
           "HTTPS proxy:70", TRAFFIC_ANNOTATION_FOR_TESTS);
-  RecordingTestNetLog net_log;
-  session_deps_.net_log = &net_log;
+  session_deps_.net_log = net::NetLog::Get();
 
   HttpRequestInfo request;
   request.method = "GET";
@@ -11324,8 +11314,7 @@
   session_deps_.proxy_resolution_service =
       ConfiguredProxyResolutionService::CreateFixedFromPacResult(
           "HTTPS proxy:70", TRAFFIC_ANNOTATION_FOR_TESTS);
-  RecordingTestNetLog net_log;
-  session_deps_.net_log = &net_log;
+  session_deps_.net_log = net::NetLog::Get();
 
   const base::TimeDelta kTimeIncrement = base::Seconds(4);
   session_deps_.host_resolver->set_ondemand_mode(true);
@@ -11392,8 +11381,7 @@
   session_deps_.proxy_resolution_service =
       ConfiguredProxyResolutionService::CreateFixedFromPacResult(
           "HTTPS proxy:70", TRAFFIC_ANNOTATION_FOR_TESTS);
-  RecordingTestNetLog net_log;
-  session_deps_.net_log = &net_log;
+  session_deps_.net_log = net::NetLog::Get();
 
   HttpRequestInfo request;
   request.method = "GET";
@@ -11440,8 +11428,7 @@
   session_deps_.proxy_resolution_service =
       ConfiguredProxyResolutionService::CreateFixedFromAutoDetectedPacResult(
           "HTTPS proxy:70", TRAFFIC_ANNOTATION_FOR_TESTS);
-  RecordingTestNetLog net_log;
-  session_deps_.net_log = &net_log;
+  session_deps_.net_log = net::NetLog::Get();
 
   HttpRequestInfo request;
   request.load_flags = LOAD_MAIN_FRAME_DEPRECATED;
@@ -11488,8 +11475,7 @@
   session_deps_.proxy_resolution_service =
       ConfiguredProxyResolutionService::CreateFixed(
           "https://proxy:70", TRAFFIC_ANNOTATION_FOR_TESTS);
-  RecordingTestNetLog net_log;
-  session_deps_.net_log = &net_log;
+  session_deps_.net_log = net::NetLog::Get();
 
   const base::TimeDelta kTimeIncrement = base::Seconds(4);
   session_deps_.host_resolver->set_ondemand_mode(true);
@@ -12310,8 +12296,7 @@
   session_deps_.proxy_resolution_service =
       ConfiguredProxyResolutionService::CreateFixedFromPacResult(
           "SOCKS myproxy:1080", TRAFFIC_ANNOTATION_FOR_TESTS);
-  RecordingTestNetLog net_log;
-  session_deps_.net_log = &net_log;
+  session_deps_.net_log = net::NetLog::Get();
 
   std::unique_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
   HttpNetworkTransaction trans(DEFAULT_PRIORITY, session.get());
@@ -12367,8 +12352,7 @@
   session_deps_.proxy_resolution_service =
       ConfiguredProxyResolutionService::CreateFixedFromPacResult(
           "SOCKS myproxy:1080", TRAFFIC_ANNOTATION_FOR_TESTS);
-  RecordingTestNetLog net_log;
-  session_deps_.net_log = &net_log;
+  session_deps_.net_log = net::NetLog::Get();
 
   std::unique_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
   HttpNetworkTransaction trans(DEFAULT_PRIORITY, session.get());
@@ -12429,8 +12413,7 @@
   session_deps_.proxy_resolution_service =
       ConfiguredProxyResolutionService::CreateFixed(
           "socks4://myproxy:1080", TRAFFIC_ANNOTATION_FOR_TESTS);
-  RecordingTestNetLog net_log;
-  session_deps_.net_log = &net_log;
+  session_deps_.net_log = net::NetLog::Get();
 
   std::unique_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
   HttpNetworkTransaction trans(DEFAULT_PRIORITY, session.get());
@@ -12485,8 +12468,7 @@
   session_deps_.proxy_resolution_service =
       ConfiguredProxyResolutionService::CreateFixedFromPacResult(
           "SOCKS5 myproxy:1080", TRAFFIC_ANNOTATION_FOR_TESTS);
-  RecordingTestNetLog net_log;
-  session_deps_.net_log = &net_log;
+  session_deps_.net_log = net::NetLog::Get();
 
   std::unique_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
   HttpNetworkTransaction trans(DEFAULT_PRIORITY, session.get());
@@ -12554,8 +12536,7 @@
   session_deps_.proxy_resolution_service =
       ConfiguredProxyResolutionService::CreateFixedFromPacResult(
           "SOCKS5 myproxy:1080", TRAFFIC_ANNOTATION_FOR_TESTS);
-  RecordingTestNetLog net_log;
-  session_deps_.net_log = &net_log;
+  session_deps_.net_log = net::NetLog::Get();
 
   std::unique_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
   HttpNetworkTransaction trans(DEFAULT_PRIORITY, session.get());
@@ -14711,14 +14692,12 @@
   auto proxy_resolver_factory = std::make_unique<CapturingProxyResolverFactory>(
       &capturing_proxy_resolver);
 
-  RecordingTestNetLog net_log;
-
   session_deps_.proxy_resolution_service =
       std::make_unique<ConfiguredProxyResolutionService>(
           std::move(proxy_config_service), std::move(proxy_resolver_factory),
-          &net_log, /*quick_check_enabled=*/true);
+          net::NetLog::Get(), /*quick_check_enabled=*/true);
 
-  session_deps_.net_log = &net_log;
+  session_deps_.net_log = net::NetLog::Get();
 
   // Configure alternative service with a hostname that is not bypassed by the
   // proxy.
@@ -14798,8 +14777,7 @@
           std::make_unique<CapturingProxyResolverFactory>(
               &capturing_proxy_resolver),
           nullptr, /*quick_check_enabled=*/true);
-  RecordingTestNetLog net_log;
-  session_deps_.net_log = &net_log;
+  session_deps_.net_log = net::NetLog::Get();
 
   HttpRequestInfo request;
   request.method = "GET";
@@ -18067,8 +18045,7 @@
   session_deps_.proxy_resolution_service =
       ConfiguredProxyResolutionService::CreateFixedFromPacResult(
           "HTTPS proxy:70", TRAFFIC_ANNOTATION_FOR_TESTS);
-  RecordingTestNetLog log;
-  session_deps_.net_log = &log;
+  session_deps_.net_log = net::NetLog::Get();
   SSLSocketDataProvider ssl1(ASYNC, OK);  // to the proxy
   ssl1.next_proto = kProtoHTTP2;
   session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl1);
diff --git a/net/http/http_proxy_client_socket_fuzzer.cc b/net/http/http_proxy_client_socket_fuzzer.cc
index 12d2fd8f6..9a7b0e6 100644
--- a/net/http/http_proxy_client_socket_fuzzer.cc
+++ b/net/http/http_proxy_client_socket_fuzzer.cc
@@ -24,6 +24,7 @@
 #include "net/http/http_auth_handler_digest.h"
 #include "net/http/http_auth_handler_factory.h"
 #include "net/http/http_auth_scheme.h"
+#include "net/log/net_log.h"
 #include "net/log/test_net_log.h"
 #include "net/socket/fuzzed_socket.h"
 #include "net/socket/next_proto.h"
@@ -35,14 +36,14 @@
 // |data| is used to create a FuzzedSocket to fuzz reads and writes, see that
 // class for details.
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-  // Use a test NetLog, to exercise logging code.
-  net::RecordingTestNetLog test_net_log;
-
   FuzzedDataProvider data_provider(data, size);
+  // Including an observer; even though the recorded results aren't currently
+  // used, it'll ensure the netlogging code is fuzzed as well.
+  net::RecordingNetLogObserver net_log_observer;
 
   net::TestCompletionCallback callback;
   std::unique_ptr<net::FuzzedSocket> fuzzed_socket(
-      new net::FuzzedSocket(&data_provider, &test_net_log));
+      new net::FuzzedSocket(&data_provider, net::NetLog::Get()));
   CHECK_EQ(net::OK, fuzzed_socket->Connect(callback.callback()));
 
   // Create auth handler supporting basic and digest schemes.  Other schemes can
diff --git a/net/log/file_net_log_observer_unittest.cc b/net/log/file_net_log_observer_unittest.cc
index 611266e2d..4785a058 100644
--- a/net/log/file_net_log_observer_unittest.cc
+++ b/net/log/file_net_log_observer_unittest.cc
@@ -23,13 +23,13 @@
 #include "base/values.h"
 #include "build/build_config.h"
 #include "net/base/test_completion_callback.h"
+#include "net/log/net_log.h"
 #include "net/log/net_log_entry.h"
 #include "net/log/net_log_event_type.h"
 #include "net/log/net_log_source.h"
 #include "net/log/net_log_source_type.h"
 #include "net/log/net_log_util.h"
 #include "net/log/net_log_values.h"
-#include "net/log/test_net_log.h"
 #include "net/test/test_with_task_environment.h"
 #include "net/url_request/url_request.h"
 #include "net/url_request/url_request_context.h"
@@ -246,7 +246,7 @@
                                                     std::move(constants));
     }
 
-    logger_->StartObserving(&net_log_);
+    logger_->StartObserving(NetLog::Get());
   }
 
   void CreateAndStartObservingPreExisting(
@@ -268,7 +268,7 @@
           std::move(file), NetLogCaptureMode::kDefault, std::move(constants));
     }
 
-    logger_->StartObserving(&net_log_);
+    logger_->StartObserving(NetLog::Get());
   }
 
   bool LogFileExists() {
@@ -280,7 +280,6 @@
   }
 
  protected:
-  TestNetLog net_log_;
   std::unique_ptr<FileNetLogObserver> logger_;
   base::ScopedTempDir temp_dir_;
   base::ScopedTempDir scratch_dir_;  // used for bounded + preexisting
@@ -308,7 +307,7 @@
     logger_ = FileNetLogObserver::CreateBoundedForTests(
         log_path_, total_file_size, num_files, NetLogCaptureMode::kDefault,
         std::move(constants));
-    logger_->StartObserving(&net_log_);
+    logger_->StartObserving(NetLog::Get());
   }
 
   // Returns the path for an internally directory created for bounded logs (this
@@ -332,7 +331,6 @@
 
 
  protected:
-  TestNetLog net_log_;
   std::unique_ptr<FileNetLogObserver> logger_;
   base::FilePath log_path_;
 
@@ -495,7 +493,7 @@
   else
     logger_ = FileNetLogObserver::CreateUnboundedPreExisting(
         std::move(file), NetLogCaptureMode::kDefault, nullptr);
-  logger_->StartObserving(&net_log_);
+  logger_->StartObserving(NetLog::Get());
 
   // Send dummy event.
   AddEntries(logger_.get(), 1, kDummyEventSize);
@@ -975,7 +973,7 @@
   logger_ = FileNetLogObserver::CreateBoundedPreExisting(
       scratch_dir.GetPath(), std::move(file), kLargeFileSize,
       NetLogCaptureMode::kDefault, nullptr);
-  logger_->StartObserving(&net_log_);
+  logger_->StartObserving(NetLog::Get());
 
   base::ThreadPoolInstance::Get()->FlushForTesting();
   EXPECT_TRUE(base::PathExists(log_path_));
@@ -1046,7 +1044,7 @@
   for (size_t i = 0; i < kNumThreads; ++i) {
     threads[i]->task_runner()->PostTask(
         FROM_HERE,
-        base::BindOnce(&AddEntriesViaNetLog, base::Unretained(&net_log_),
+        base::BindOnce(&AddEntriesViaNetLog, base::Unretained(NetLog::Get()),
                        kNumEventsAddedPerThread));
   }
 
@@ -1082,7 +1080,7 @@
   for (size_t i = 0; i < kNumThreads; ++i) {
     threads[i]->task_runner()->PostTask(
         FROM_HERE,
-        base::BindOnce(&AddEntriesViaNetLog, base::Unretained(&net_log_),
+        base::BindOnce(&AddEntriesViaNetLog, base::Unretained(NetLog::Get()),
                        kNumEventsAddedPerThread));
   }
 
diff --git a/net/log/net_log_unittest.cc b/net/log/net_log_unittest.cc
index 6bab0011..c5a9f16 100644
--- a/net/log/net_log_unittest.cc
+++ b/net/log/net_log_unittest.cc
@@ -39,25 +39,25 @@
 TEST(NetLogTest, BasicGlobalEvents) {
   base::test::TaskEnvironment task_environment{
       base::test::TaskEnvironment::TimeSource::MOCK_TIME};
-  RecordingTestNetLog net_log;
-  auto entries = net_log.GetEntries();
+  RecordingNetLogObserver net_log_observer;
+  auto entries = net_log_observer.GetEntries();
   EXPECT_EQ(0u, entries.size());
 
   task_environment.FastForwardBy(base::Seconds(1234));
   base::TimeTicks ticks0 = base::TimeTicks::Now();
 
-  net_log.AddGlobalEntry(NetLogEventType::CANCELLED);
+  NetLog::Get()->AddGlobalEntry(NetLogEventType::CANCELLED);
 
   task_environment.FastForwardBy(base::Seconds(5678));
   base::TimeTicks ticks1 = base::TimeTicks::Now();
   EXPECT_LE(ticks0, ticks1);
 
-  net_log.AddGlobalEntry(NetLogEventType::FAILED);
+  NetLog::Get()->AddGlobalEntry(NetLogEventType::FAILED);
 
   task_environment.FastForwardBy(base::Seconds(91011));
   EXPECT_LE(ticks1, base::TimeTicks::Now());
 
-  entries = net_log.GetEntries();
+  entries = net_log_observer.GetEntries();
   ASSERT_EQ(2u, entries.size());
 
   EXPECT_EQ(NetLogEventType::CANCELLED, entries[0].type);
@@ -81,15 +81,15 @@
 TEST(NetLogTest, BasicEventsWithSource) {
   base::test::TaskEnvironment task_environment{
       base::test::TaskEnvironment::TimeSource::MOCK_TIME};
-  RecordingTestNetLog net_log;
-  auto entries = net_log.GetEntries();
+  RecordingNetLogObserver net_log_observer;
+  auto entries = net_log_observer.GetEntries();
   EXPECT_EQ(0u, entries.size());
 
   task_environment.FastForwardBy(base::Seconds(9876));
   base::TimeTicks source0_start_ticks = base::TimeTicks::Now();
 
   NetLogWithSource source0 =
-      NetLogWithSource::Make(&net_log, NetLogSourceType::URL_REQUEST);
+      NetLogWithSource::Make(NetLog::Get(), NetLogSourceType::URL_REQUEST);
   task_environment.FastForwardBy(base::Seconds(1));
   base::TimeTicks source0_event0_ticks = base::TimeTicks::Now();
   source0.BeginEvent(NetLogEventType::REQUEST_ALIVE);
@@ -98,7 +98,7 @@
   base::TimeTicks source1_start_ticks = base::TimeTicks::Now();
 
   NetLogWithSource source1 =
-      NetLogWithSource::Make(&net_log, NetLogSourceType::SOCKET);
+      NetLogWithSource::Make(NetLog::Get(), NetLogSourceType::SOCKET);
   task_environment.FastForwardBy(base::Seconds(1));
   base::TimeTicks source1_event0_ticks = base::TimeTicks::Now();
   source1.BeginEvent(NetLogEventType::SOCKET_ALIVE);
@@ -112,7 +112,7 @@
 
   task_environment.FastForwardBy(base::Seconds(123));
 
-  entries = net_log.GetEntries();
+  entries = net_log_observer.GetEntries();
   ASSERT_EQ(4u, entries.size());
 
   EXPECT_EQ(NetLogEventType::REQUEST_ALIVE, entries[0].type);
@@ -157,18 +157,17 @@
       NetLogCaptureMode::kEverything,
   };
 
-  RecordingTestNetLog net_log;
+  RecordingNetLogObserver net_log_observer;
 
   for (NetLogCaptureMode mode : kModes) {
-    net_log.SetObserverCaptureMode(mode);
-    EXPECT_EQ(mode, net_log.GetObserver()->capture_mode());
+    net_log_observer.SetObserverCaptureMode(mode);
 
-    net_log.AddGlobalEntry(NetLogEventType::SOCKET_ALIVE,
-                           [&](NetLogCaptureMode capture_mode) {
-                             return NetCaptureModeParams(capture_mode);
-                           });
+    NetLog::Get()->AddGlobalEntry(NetLogEventType::SOCKET_ALIVE,
+                                  [&](NetLogCaptureMode capture_mode) {
+                                    return NetCaptureModeParams(capture_mode);
+                                  });
 
-    auto entries = net_log.GetEntries();
+    auto entries = net_log_observer.GetEntries();
 
     ASSERT_EQ(1u, entries.size());
     EXPECT_EQ(NetLogEventType::SOCKET_ALIVE, entries[0].type);
@@ -181,7 +180,7 @@
     ASSERT_EQ(CaptureModeToInt(mode),
               GetIntegerValueFromParams(entries[0], "capture_mode"));
 
-    net_log.Clear();
+    net_log_observer.Clear();
   }
 }
 
@@ -336,18 +335,15 @@
 
 // Makes sure that events on multiple threads are dispatched to all observers.
 TEST(NetLogTest, NetLogEventThreads) {
-  TestNetLog net_log;
-
-  // Attach some observers.  Since they're created after |net_log|, they'll
-  // safely detach themselves on destruction.
+  // Attach some observers.  They'll safely detach themselves on destruction.
   CountingObserver observers[3];
   for (size_t i = 0; i < base::size(observers); ++i) {
-    net_log.AddObserver(&observers[i], NetLogCaptureMode::kEverything);
+    NetLog::Get()->AddObserver(&observers[i], NetLogCaptureMode::kEverything);
   }
 
   // Run a bunch of threads to completion, each of which will emit events to
   // |net_log|.
-  RunTestThreads<AddEventsTestThread>(&net_log);
+  RunTestThreads<AddEventsTestThread>(NetLog::Get());
 
   // Check that each observer saw the emitted events.
   const int kTotalEvents = kThreads * kEvents;
@@ -357,70 +353,69 @@
 
 // Test adding and removing a single observer.
 TEST(NetLogTest, NetLogAddRemoveObserver) {
-  TestNetLog net_log;
   CountingObserver observer;
 
-  AddEvent(&net_log);
+  AddEvent(NetLog::Get());
   EXPECT_EQ(0, observer.count());
   EXPECT_EQ(NULL, observer.net_log());
-  EXPECT_FALSE(net_log.IsCapturing());
+  EXPECT_FALSE(NetLog::Get()->IsCapturing());
 
   // Add the observer and add an event.
-  net_log.AddObserver(&observer, NetLogCaptureMode::kIncludeSensitive);
-  EXPECT_TRUE(net_log.IsCapturing());
-  EXPECT_EQ(&net_log, observer.net_log());
+  NetLog::Get()->AddObserver(&observer, NetLogCaptureMode::kIncludeSensitive);
+  EXPECT_TRUE(NetLog::Get()->IsCapturing());
+  EXPECT_EQ(NetLog::Get(), observer.net_log());
   EXPECT_EQ(NetLogCaptureMode::kIncludeSensitive, observer.capture_mode());
-  EXPECT_TRUE(net_log.IsCapturing());
+  EXPECT_TRUE(NetLog::Get()->IsCapturing());
 
-  AddEvent(&net_log);
+  AddEvent(NetLog::Get());
   EXPECT_EQ(1, observer.count());
 
-  AddEvent(&net_log);
+  AddEvent(NetLog::Get());
   EXPECT_EQ(2, observer.count());
 
   // Remove observer and add an event.
-  net_log.RemoveObserver(&observer);
+  NetLog::Get()->RemoveObserver(&observer);
   EXPECT_EQ(NULL, observer.net_log());
-  EXPECT_FALSE(net_log.IsCapturing());
+  EXPECT_FALSE(NetLog::Get()->IsCapturing());
 
-  AddEvent(&net_log);
+  AddEvent(NetLog::Get());
   EXPECT_EQ(2, observer.count());
 
   // Add the observer a final time, this time with a different capture mdoe, and
   // add an event.
-  net_log.AddObserver(&observer, NetLogCaptureMode::kEverything);
-  EXPECT_EQ(&net_log, observer.net_log());
+  NetLog::Get()->AddObserver(&observer, NetLogCaptureMode::kEverything);
+  EXPECT_EQ(NetLog::Get(), observer.net_log());
   EXPECT_EQ(NetLogCaptureMode::kEverything, observer.capture_mode());
-  EXPECT_TRUE(net_log.IsCapturing());
+  EXPECT_TRUE(NetLog::Get()->IsCapturing());
 
-  AddEvent(&net_log);
+  AddEvent(NetLog::Get());
   EXPECT_EQ(3, observer.count());
 }
 
 // Test adding and removing two observers at different log levels.
 TEST(NetLogTest, NetLogTwoObservers) {
-  TestNetLog net_log;
   LoggingObserver observer[2];
 
   // Add first observer.
-  net_log.AddObserver(&observer[0], NetLogCaptureMode::kIncludeSensitive);
-  EXPECT_EQ(&net_log, observer[0].net_log());
+  NetLog::Get()->AddObserver(&observer[0],
+                             NetLogCaptureMode::kIncludeSensitive);
+  EXPECT_EQ(NetLog::Get(), observer[0].net_log());
   EXPECT_EQ(NULL, observer[1].net_log());
   EXPECT_EQ(NetLogCaptureMode::kIncludeSensitive, observer[0].capture_mode());
-  EXPECT_TRUE(net_log.IsCapturing());
+  EXPECT_TRUE(NetLog::Get()->IsCapturing());
 
   // Add second observer observer.
-  net_log.AddObserver(&observer[1], NetLogCaptureMode::kEverything);
-  EXPECT_EQ(&net_log, observer[0].net_log());
-  EXPECT_EQ(&net_log, observer[1].net_log());
+  NetLog::Get()->AddObserver(&observer[1], NetLogCaptureMode::kEverything);
+  EXPECT_EQ(NetLog::Get(), observer[0].net_log());
+  EXPECT_EQ(NetLog::Get(), observer[1].net_log());
   EXPECT_EQ(NetLogCaptureMode::kIncludeSensitive, observer[0].capture_mode());
   EXPECT_EQ(NetLogCaptureMode::kEverything, observer[1].capture_mode());
-  EXPECT_TRUE(net_log.IsCapturing());
+  EXPECT_TRUE(NetLog::Get()->IsCapturing());
 
   // Add event and make sure both observers receive it at their respective log
   // levels.
   absl::optional<int> param;
-  AddEvent(&net_log);
+  AddEvent(NetLog::Get());
   ASSERT_EQ(1U, observer[0].GetNumValues());
   param = observer[0].GetValue(0)->FindIntKey("params");
   ASSERT_TRUE(param);
@@ -431,25 +426,25 @@
   EXPECT_EQ(CaptureModeToInt(observer[1].capture_mode()), param.value());
 
   // Remove second observer.
-  net_log.RemoveObserver(&observer[1]);
-  EXPECT_EQ(&net_log, observer[0].net_log());
+  NetLog::Get()->RemoveObserver(&observer[1]);
+  EXPECT_EQ(NetLog::Get(), observer[0].net_log());
   EXPECT_EQ(NULL, observer[1].net_log());
   EXPECT_EQ(NetLogCaptureMode::kIncludeSensitive, observer[0].capture_mode());
-  EXPECT_TRUE(net_log.IsCapturing());
+  EXPECT_TRUE(NetLog::Get()->IsCapturing());
 
   // Add event and make sure only second observer gets it.
-  AddEvent(&net_log);
+  AddEvent(NetLog::Get());
   EXPECT_EQ(2U, observer[0].GetNumValues());
   EXPECT_EQ(1U, observer[1].GetNumValues());
 
   // Remove first observer.
-  net_log.RemoveObserver(&observer[0]);
+  NetLog::Get()->RemoveObserver(&observer[0]);
   EXPECT_EQ(NULL, observer[0].net_log());
   EXPECT_EQ(NULL, observer[1].net_log());
-  EXPECT_FALSE(net_log.IsCapturing());
+  EXPECT_FALSE(NetLog::Get()->IsCapturing());
 
   // Add event and make sure neither observer gets it.
-  AddEvent(&net_log);
+  AddEvent(NetLog::Get());
   EXPECT_EQ(2U, observer[0].GetNumValues());
   EXPECT_EQ(1U, observer[1].GetNumValues());
 }
@@ -457,11 +452,9 @@
 // Makes sure that adding and removing observers simultaneously on different
 // threads works.
 TEST(NetLogTest, NetLogAddRemoveObserverThreads) {
-  TestNetLog net_log;
-
   // Run a bunch of threads to completion, each of which will repeatedly add
   // and remove an observer, and set its logging level.
-  RunTestThreads<AddRemoveObserverTestThread>(&net_log);
+  RunTestThreads<AddRemoveObserverTestThread>(NetLog::Get());
 }
 
 // Tests that serializing a NetLogEntry with empty parameters omits a value for
diff --git a/net/log/net_log_util_unittest.cc b/net/log/net_log_util_unittest.cc
index 35d3f9a..7492acd 100644
--- a/net/log/net_log_util_unittest.cc
+++ b/net/log/net_log_util_unittest.cc
@@ -94,8 +94,7 @@
   // Using same context for each iteration makes sure deleted requests don't
   // appear in the list, or result in crashes.
   TestURLRequestContext context(true);
-  TestNetLog net_log;
-  context.set_net_log(&net_log);
+  context.set_net_log(NetLog::Get());
   context.Init();
   TestDelegate delegate;
   for (size_t num_requests = 0; num_requests < 5; ++num_requests) {
@@ -107,9 +106,9 @@
     }
     std::set<URLRequestContext*> contexts;
     contexts.insert(&context);
-    RecordingTestNetLog test_net_log;
-    CreateNetLogEntriesForActiveObjects(contexts, test_net_log.GetObserver());
-    auto entry_list = test_net_log.GetEntries();
+    RecordingNetLogObserver net_log_observer;
+    CreateNetLogEntriesForActiveObjects(contexts, &net_log_observer);
+    auto entry_list = net_log_observer.GetEntries();
     ASSERT_EQ(num_requests, entry_list.size());
 
     for (size_t i = 0; i < num_requests; ++i) {
@@ -125,23 +124,21 @@
 
   TestDelegate delegate;
   for (size_t num_requests = 0; num_requests < 5; ++num_requests) {
-    TestNetLog net_log;
     std::vector<std::unique_ptr<TestURLRequestContext>> contexts;
     std::vector<std::unique_ptr<URLRequest>> requests;
     std::set<URLRequestContext*> context_set;
     for (size_t i = 0; i < num_requests; ++i) {
       contexts.push_back(std::make_unique<TestURLRequestContext>(true));
-      contexts[i]->set_net_log(&net_log);
+      contexts[i]->set_net_log(NetLog::Get());
       contexts[i]->Init();
       context_set.insert(contexts[i].get());
       requests.push_back(
           contexts[i]->CreateRequest(GURL("about:hats"), DEFAULT_PRIORITY,
                                      &delegate, TRAFFIC_ANNOTATION_FOR_TESTS));
     }
-    RecordingTestNetLog test_net_log;
-    CreateNetLogEntriesForActiveObjects(context_set,
-                                        test_net_log.GetObserver());
-    auto entry_list = test_net_log.GetEntries();
+    RecordingNetLogObserver net_log_observer;
+    CreateNetLogEntriesForActiveObjects(context_set, &net_log_observer);
+    auto entry_list = net_log_observer.GetEntries();
     ASSERT_EQ(num_requests, entry_list.size());
 
     for (size_t i = 0; i < num_requests; ++i) {
diff --git a/net/log/trace_net_log_observer_unittest.cc b/net/log/trace_net_log_observer_unittest.cc
index 5cb1837..e517ce5 100644
--- a/net/log/trace_net_log_observer_unittest.cc
+++ b/net/log/trace_net_log_observer_unittest.cc
@@ -21,6 +21,7 @@
 #include "base/trace_event/trace_event.h"
 #include "base/trace_event/trace_event_impl.h"
 #include "base/values.h"
+#include "net/log/net_log.h"
 #include "net/log/net_log_event_type.h"
 #include "net/log/net_log_source_type.h"
 #include "net/log/net_log_with_source.h"
@@ -39,6 +40,7 @@
 
 struct TraceEntryInfo {
   std::string category;
+  // The netlog source id formatted as a hexadecimal string.
   std::string id;
   std::string phase;
   std::string name;
@@ -179,7 +181,7 @@
 
   size_t trace_events_size() const { return trace_events_->GetList().size(); }
 
-  RecordingTestNetLog* net_log() { return &net_log_; }
+  RecordingNetLogObserver* net_log_observer() { return &net_log_observer_; }
 
   TraceNetLogObserver* trace_net_log_observer() const {
     return trace_net_log_observer_.get();
@@ -189,13 +191,13 @@
   std::unique_ptr<base::Value> trace_events_;
   base::trace_event::TraceResultBuffer trace_buffer_;
   base::trace_event::TraceResultBuffer::SimpleOutput json_output_;
-  RecordingTestNetLog net_log_;
+  RecordingNetLogObserver net_log_observer_;
   std::unique_ptr<TraceNetLogObserver> trace_net_log_observer_;
 };
 
 TEST_F(TraceNetLogObserverTest, TracingNotEnabled) {
-  trace_net_log_observer()->WatchForTraceStart(net_log());
-  net_log()->AddGlobalEntry(NetLogEventType::REQUEST_ALIVE);
+  trace_net_log_observer()->WatchForTraceStart(NetLog::Get());
+  NetLog::Get()->AddGlobalEntry(NetLogEventType::REQUEST_ALIVE);
 
   EndTraceAndFlush();
   trace_net_log_observer()->StopWatchForTraceStart();
@@ -206,14 +208,14 @@
 // This test will result in a deadlock if EnabledStateObserver instead
 // of AsyncEnabledStateObserver is used. Regression test for crbug.com/760817.
 TEST_F(TraceNetLogObserverTest, TracingDisabledDuringOnAddEntry) {
-  trace_net_log_observer()->WatchForTraceStart(net_log());
+  trace_net_log_observer()->WatchForTraceStart(NetLog::Get());
   TraceLog* trace_log = TraceLog::GetInstance();
   trace_log->SetTraceBufferForTesting(base::WrapUnique(
       base::trace_event::TraceBuffer::CreateTraceBufferVectorOfSize(1)));
   EnableTraceLogWithNetLog();
   // TraceLog will disable itself when an event makes the TraceBuffer full.
   while (!trace_log->BufferIsFull()) {
-    net_log()->AddGlobalEntry(NetLogEventType::REQUEST_ALIVE);
+    NetLog::Get()->AddGlobalEntry(NetLogEventType::REQUEST_ALIVE);
   }
 
   base::RunLoop().RunUntilIdle();
@@ -225,18 +227,18 @@
 }
 
 TEST_F(TraceNetLogObserverTest, TraceEventCaptured) {
-  auto entries = net_log()->GetEntries();
+  auto entries = net_log_observer()->GetEntries();
   EXPECT_TRUE(entries.empty());
 
-  trace_net_log_observer()->WatchForTraceStart(net_log());
+  trace_net_log_observer()->WatchForTraceStart(NetLog::Get());
   EnableTraceLogWithNetLog();
   NetLogWithSource net_log_with_source =
-      NetLogWithSource::Make(net_log(), net::NetLogSourceType::NONE);
-  net_log()->AddGlobalEntry(NetLogEventType::CANCELLED);
+      NetLogWithSource::Make(NetLog::Get(), net::NetLogSourceType::NONE);
+  NetLog::Get()->AddGlobalEntry(NetLogEventType::CANCELLED);
   net_log_with_source.BeginEvent(NetLogEventType::URL_REQUEST_START_JOB);
   net_log_with_source.EndEvent(NetLogEventType::REQUEST_ALIVE);
 
-  entries = net_log()->GetEntries();
+  entries = net_log_observer()->GetEntries();
   EXPECT_EQ(3u, entries.size());
   EndTraceAndFlush();
   trace_net_log_observer()->StopWatchForTraceStart();
@@ -254,7 +256,7 @@
   TraceEntryInfo actual_item2 = GetTraceEntryInfoFromValue(*item2);
   TraceEntryInfo actual_item3 = GetTraceEntryInfoFromValue(*item3);
   EXPECT_EQ(kNetLogTracingCategory, actual_item1.category);
-  EXPECT_EQ(base::StringPrintf("0x%d", entries[0].source.id), actual_item1.id);
+  EXPECT_EQ(base::StringPrintf("0x%x", entries[0].source.id), actual_item1.id);
   EXPECT_EQ(std::string(1, TRACE_EVENT_PHASE_NESTABLE_ASYNC_INSTANT),
             actual_item1.phase);
   EXPECT_EQ(NetLog::EventTypeToString(NetLogEventType::CANCELLED),
@@ -263,7 +265,7 @@
             actual_item1.source_type);
 
   EXPECT_EQ(kNetLogTracingCategory, actual_item2.category);
-  EXPECT_EQ(base::StringPrintf("0x%d", entries[1].source.id), actual_item2.id);
+  EXPECT_EQ(base::StringPrintf("0x%x", entries[1].source.id), actual_item2.id);
   EXPECT_EQ(std::string(1, TRACE_EVENT_PHASE_NESTABLE_ASYNC_BEGIN),
             actual_item2.phase);
   EXPECT_EQ(NetLog::EventTypeToString(NetLogEventType::URL_REQUEST_START_JOB),
@@ -272,7 +274,7 @@
             actual_item2.source_type);
 
   EXPECT_EQ(kNetLogTracingCategory, actual_item3.category);
-  EXPECT_EQ(base::StringPrintf("0x%d", entries[2].source.id), actual_item3.id);
+  EXPECT_EQ(base::StringPrintf("0x%x", entries[2].source.id), actual_item3.id);
   EXPECT_EQ(std::string(1, TRACE_EVENT_PHASE_NESTABLE_ASYNC_END),
             actual_item3.phase);
   EXPECT_EQ(NetLog::EventTypeToString(NetLogEventType::REQUEST_ALIVE),
@@ -282,18 +284,18 @@
 }
 
 TEST_F(TraceNetLogObserverTest, EnableAndDisableTracing) {
-  trace_net_log_observer()->WatchForTraceStart(net_log());
+  trace_net_log_observer()->WatchForTraceStart(NetLog::Get());
   EnableTraceLogWithNetLog();
-  net_log()->AddGlobalEntry(NetLogEventType::CANCELLED);
+  NetLog::Get()->AddGlobalEntry(NetLogEventType::CANCELLED);
   DisableTraceLog();
-  net_log()->AddGlobalEntry(NetLogEventType::REQUEST_ALIVE);
+  NetLog::Get()->AddGlobalEntry(NetLogEventType::REQUEST_ALIVE);
   EnableTraceLogWithNetLog();
-  net_log()->AddGlobalEntry(NetLogEventType::URL_REQUEST_START_JOB);
+  NetLog::Get()->AddGlobalEntry(NetLogEventType::URL_REQUEST_START_JOB);
 
   EndTraceAndFlush();
   trace_net_log_observer()->StopWatchForTraceStart();
 
-  auto entries = net_log()->GetEntries();
+  auto entries = net_log_observer()->GetEntries();
   EXPECT_EQ(3u, entries.size());
   EXPECT_EQ(2u, trace_events_size());
   const base::Value* item1 = &trace_events()->GetList()[0];
@@ -304,7 +306,7 @@
   TraceEntryInfo actual_item1 = GetTraceEntryInfoFromValue(*item1);
   TraceEntryInfo actual_item2 = GetTraceEntryInfoFromValue(*item2);
   EXPECT_EQ(kNetLogTracingCategory, actual_item1.category);
-  EXPECT_EQ(base::StringPrintf("0x%d", entries[0].source.id), actual_item1.id);
+  EXPECT_EQ(base::StringPrintf("0x%x", entries[0].source.id), actual_item1.id);
   EXPECT_EQ(std::string(1, TRACE_EVENT_PHASE_NESTABLE_ASYNC_INSTANT),
             actual_item1.phase);
   EXPECT_EQ(NetLog::EventTypeToString(NetLogEventType::CANCELLED),
@@ -313,7 +315,7 @@
             actual_item1.source_type);
 
   EXPECT_EQ(kNetLogTracingCategory, actual_item2.category);
-  EXPECT_EQ(base::StringPrintf("0x%d", entries[2].source.id), actual_item2.id);
+  EXPECT_EQ(base::StringPrintf("0x%x", entries[2].source.id), actual_item2.id);
   EXPECT_EQ(std::string(1, TRACE_EVENT_PHASE_NESTABLE_ASYNC_INSTANT),
             actual_item2.phase);
   EXPECT_EQ(NetLog::EventTypeToString(NetLogEventType::URL_REQUEST_START_JOB),
@@ -323,16 +325,16 @@
 }
 
 TEST_F(TraceNetLogObserverTest, DestroyObserverWhileTracing) {
-  trace_net_log_observer()->WatchForTraceStart(net_log());
+  trace_net_log_observer()->WatchForTraceStart(NetLog::Get());
   EnableTraceLogWithNetLog();
-  net_log()->AddGlobalEntry(NetLogEventType::CANCELLED);
+  NetLog::Get()->AddGlobalEntry(NetLogEventType::CANCELLED);
   trace_net_log_observer()->StopWatchForTraceStart();
   set_trace_net_log_observer(nullptr);
-  net_log()->AddGlobalEntry(NetLogEventType::REQUEST_ALIVE);
+  NetLog::Get()->AddGlobalEntry(NetLogEventType::REQUEST_ALIVE);
 
   EndTraceAndFlush();
 
-  auto entries = net_log()->GetEntries();
+  auto entries = net_log_observer()->GetEntries();
   EXPECT_EQ(2u, entries.size());
   EXPECT_EQ(1u, trace_events_size());
 
@@ -341,7 +343,7 @@
 
   TraceEntryInfo actual_item1 = GetTraceEntryInfoFromValue(*item1);
   EXPECT_EQ(kNetLogTracingCategory, actual_item1.category);
-  EXPECT_EQ(base::StringPrintf("0x%d", entries[0].source.id), actual_item1.id);
+  EXPECT_EQ(base::StringPrintf("0x%x", entries[0].source.id), actual_item1.id);
   EXPECT_EQ(std::string(1, TRACE_EVENT_PHASE_NESTABLE_ASYNC_INSTANT),
             actual_item1.phase);
   EXPECT_EQ(NetLog::EventTypeToString(NetLogEventType::CANCELLED),
@@ -351,16 +353,16 @@
 }
 
 TEST_F(TraceNetLogObserverTest, DestroyObserverWhileNotTracing) {
-  trace_net_log_observer()->WatchForTraceStart(net_log());
-  net_log()->AddGlobalEntry(NetLogEventType::CANCELLED);
+  trace_net_log_observer()->WatchForTraceStart(NetLog::Get());
+  NetLog::Get()->AddGlobalEntry(NetLogEventType::CANCELLED);
   trace_net_log_observer()->StopWatchForTraceStart();
   set_trace_net_log_observer(nullptr);
-  net_log()->AddGlobalEntry(NetLogEventType::REQUEST_ALIVE);
-  net_log()->AddGlobalEntry(NetLogEventType::URL_REQUEST_START_JOB);
+  NetLog::Get()->AddGlobalEntry(NetLogEventType::REQUEST_ALIVE);
+  NetLog::Get()->AddGlobalEntry(NetLogEventType::URL_REQUEST_START_JOB);
 
   EndTraceAndFlush();
 
-  auto entries = net_log()->GetEntries();
+  auto entries = net_log_observer()->GetEntries();
   EXPECT_EQ(3u, entries.size());
   EXPECT_EQ(0u, trace_events_size());
 }
@@ -369,15 +371,15 @@
   set_trace_net_log_observer(nullptr);
   EnableTraceLogWithNetLog();
   set_trace_net_log_observer(new TraceNetLogObserver());
-  trace_net_log_observer()->WatchForTraceStart(net_log());
-  net_log()->AddGlobalEntry(NetLogEventType::CANCELLED);
+  trace_net_log_observer()->WatchForTraceStart(NetLog::Get());
+  NetLog::Get()->AddGlobalEntry(NetLogEventType::CANCELLED);
   trace_net_log_observer()->StopWatchForTraceStart();
-  net_log()->AddGlobalEntry(NetLogEventType::REQUEST_ALIVE);
-  net_log()->AddGlobalEntry(NetLogEventType::URL_REQUEST_START_JOB);
+  NetLog::Get()->AddGlobalEntry(NetLogEventType::REQUEST_ALIVE);
+  NetLog::Get()->AddGlobalEntry(NetLogEventType::URL_REQUEST_START_JOB);
 
   EndTraceAndFlush();
 
-  auto entries = net_log()->GetEntries();
+  auto entries = net_log_observer()->GetEntries();
   EXPECT_EQ(3u, entries.size());
   EXPECT_EQ(1u, trace_events_size());
 }
@@ -389,31 +391,31 @@
   EnableTraceLogWithoutNetLog();
 
   set_trace_net_log_observer(new TraceNetLogObserver());
-  trace_net_log_observer()->WatchForTraceStart(net_log());
-  net_log()->AddGlobalEntry(NetLogEventType::CANCELLED);
+  trace_net_log_observer()->WatchForTraceStart(NetLog::Get());
+  NetLog::Get()->AddGlobalEntry(NetLogEventType::CANCELLED);
   trace_net_log_observer()->StopWatchForTraceStart();
-  net_log()->AddGlobalEntry(NetLogEventType::REQUEST_ALIVE);
-  net_log()->AddGlobalEntry(NetLogEventType::URL_REQUEST_START_JOB);
+  NetLog::Get()->AddGlobalEntry(NetLogEventType::REQUEST_ALIVE);
+  NetLog::Get()->AddGlobalEntry(NetLogEventType::URL_REQUEST_START_JOB);
 
   EndTraceAndFlush();
 
-  auto entries = net_log()->GetEntries();
+  auto entries = net_log_observer()->GetEntries();
   EXPECT_EQ(3u, entries.size());
   EXPECT_EQ(0u, trace_events_size());
 }
 
 TEST_F(TraceNetLogObserverTest, EventsWithAndWithoutParameters) {
-  trace_net_log_observer()->WatchForTraceStart(net_log());
+  trace_net_log_observer()->WatchForTraceStart(NetLog::Get());
   EnableTraceLogWithNetLog();
 
-  net_log()->AddGlobalEntryWithStringParams(NetLogEventType::CANCELLED, "foo",
-                                            "bar");
-  net_log()->AddGlobalEntry(NetLogEventType::REQUEST_ALIVE);
+  NetLog::Get()->AddGlobalEntryWithStringParams(NetLogEventType::CANCELLED,
+                                                "foo", "bar");
+  NetLog::Get()->AddGlobalEntry(NetLogEventType::REQUEST_ALIVE);
 
   EndTraceAndFlush();
   trace_net_log_observer()->StopWatchForTraceStart();
 
-  auto entries = net_log()->GetEntries();
+  auto entries = net_log_observer()->GetEntries();
   EXPECT_EQ(2u, entries.size());
   EXPECT_EQ(2u, trace_events_size());
   const base::Value* item1 = &trace_events()->GetList()[0];
@@ -423,8 +425,9 @@
 
   TraceEntryInfo actual_item1 = GetTraceEntryInfoFromValue(*item1);
   TraceEntryInfo actual_item2 = GetTraceEntryInfoFromValue(*item2);
+
   EXPECT_EQ(kNetLogTracingCategory, actual_item1.category);
-  EXPECT_EQ(base::StringPrintf("0x%d", entries[0].source.id), actual_item1.id);
+  EXPECT_EQ(base::StringPrintf("0x%x", entries[0].source.id), actual_item1.id);
   EXPECT_EQ(std::string(1, TRACE_EVENT_PHASE_NESTABLE_ASYNC_INSTANT),
             actual_item1.phase);
   EXPECT_EQ(NetLog::EventTypeToString(NetLogEventType::CANCELLED),
@@ -433,7 +436,7 @@
             actual_item1.source_type);
 
   EXPECT_EQ(kNetLogTracingCategory, actual_item2.category);
-  EXPECT_EQ(base::StringPrintf("0x%d", entries[1].source.id), actual_item2.id);
+  EXPECT_EQ(base::StringPrintf("0x%x", entries[1].source.id), actual_item2.id);
   EXPECT_EQ(std::string(1, TRACE_EVENT_PHASE_NESTABLE_ASYNC_INSTANT),
             actual_item2.phase);
   EXPECT_EQ(NetLog::EventTypeToString(NetLogEventType::REQUEST_ALIVE),
@@ -453,16 +456,15 @@
 TEST(TraceNetLogObserverCategoryTest, DisabledCategory) {
   base::test::TaskEnvironment task_environment;
   TraceNetLogObserver observer;
-  TestNetLog net_log;
-  observer.WatchForTraceStart(&net_log);
+  observer.WatchForTraceStart(NetLog::Get());
 
-  EXPECT_FALSE(net_log.IsCapturing());
+  EXPECT_FALSE(NetLog::Get()->IsCapturing());
 
   EnableTraceLogWithoutNetLog();
 
-  EXPECT_FALSE(net_log.IsCapturing());
+  EXPECT_FALSE(NetLog::Get()->IsCapturing());
   observer.StopWatchForTraceStart();
-  EXPECT_FALSE(net_log.IsCapturing());
+  EXPECT_FALSE(NetLog::Get()->IsCapturing());
 
   DisableTraceLog();
 }
@@ -470,16 +472,15 @@
 TEST(TraceNetLogObserverCategoryTest, EnabledCategory) {
   base::test::TaskEnvironment task_environment;
   TraceNetLogObserver observer;
-  TestNetLog net_log;
-  observer.WatchForTraceStart(&net_log);
+  observer.WatchForTraceStart(NetLog::Get());
 
-  EXPECT_FALSE(net_log.IsCapturing());
+  EXPECT_FALSE(NetLog::Get()->IsCapturing());
 
   EnableTraceLogWithNetLog();
 
-  EXPECT_TRUE(net_log.IsCapturing());
+  EXPECT_TRUE(NetLog::Get()->IsCapturing());
   observer.StopWatchForTraceStart();
-  EXPECT_FALSE(net_log.IsCapturing());
+  EXPECT_FALSE(NetLog::Get()->IsCapturing());
 
   DisableTraceLog();
 }
diff --git a/net/proxy_resolution/configured_proxy_resolution_service_unittest.cc b/net/proxy_resolution/configured_proxy_resolution_service_unittest.cc
index 08e02858..732de85 100644
--- a/net/proxy_resolution/configured_proxy_resolution_service_unittest.cc
+++ b/net/proxy_resolution/configured_proxy_resolution_service_unittest.cc
@@ -27,6 +27,7 @@
 #include "net/base/proxy_server.h"
 #include "net/base/proxy_string_util.h"
 #include "net/base/test_completion_callback.h"
+#include "net/log/net_log.h"
 #include "net/log/net_log_event_type.h"
 #include "net/log/net_log_with_source.h"
 #include "net/log/test_net_log.h"
@@ -3010,10 +3011,11 @@
   MockAsyncProxyResolverFactory* factory =
       new MockAsyncProxyResolverFactory(true);
 
-  RecordingTestNetLog log;
+  RecordingNetLogObserver observer;
 
   ConfiguredProxyResolutionService service(base::WrapUnique(config_service),
-                                           base::WrapUnique(factory), &log,
+                                           base::WrapUnique(factory),
+                                           net::NetLog::Get(),
                                            /*quick_check_enabled=*/true);
 
   MockPacFileFetcher* fetcher = new MockPacFileFetcher;
@@ -3110,7 +3112,7 @@
   // Check that the expected events were output to the log stream. In particular
   // PROXY_CONFIG_CHANGED should have only been emitted once (for the initial
   // setup), and NOT a second time when the IP address changed.
-  auto entries = log.GetEntries();
+  auto entries = observer.GetEntries();
 
   EXPECT_TRUE(LogContainsEntryWithType(entries, 0,
                                        NetLogEventType::PROXY_CONFIG_CHANGED));
diff --git a/net/proxy_resolution/pac_file_decider_unittest.cc b/net/proxy_resolution/pac_file_decider_unittest.cc
index c809cc60..00efda1db 100644
--- a/net/proxy_resolution/pac_file_decider_unittest.cc
+++ b/net/proxy_resolution/pac_file_decider_unittest.cc
@@ -21,6 +21,7 @@
 #include "net/base/net_errors.h"
 #include "net/base/test_completion_callback.h"
 #include "net/dns/mock_host_resolver.h"
+#include "net/log/net_log.h"
 #include "net/log/net_log_event_type.h"
 #include "net/log/test_net_log.h"
 #include "net/log/test_net_log_util.h"
@@ -205,8 +206,8 @@
   Rules::Rule rule = rules.AddSuccessRule("http://custom/proxy.pac");
 
   TestCompletionCallback callback;
-  RecordingTestNetLog log;
-  PacFileDecider decider(&fetcher, &dhcp_fetcher, &log);
+  RecordingNetLogObserver observer;
+  PacFileDecider decider(&fetcher, &dhcp_fetcher, net::NetLog::Get());
   EXPECT_THAT(decider.Start(ProxyConfigWithAnnotation(
                                 config, TRAFFIC_ANNOTATION_FOR_TESTS),
                             base::TimeDelta(), true, callback.callback()),
@@ -215,7 +216,7 @@
   EXPECT_FALSE(decider.script_data().from_auto_detect);
 
   // Check the NetLog was filled correctly.
-  auto entries = log.GetEntries();
+  auto entries = observer.GetEntries();
 
   EXPECT_EQ(4u, entries.size());
   EXPECT_TRUE(
@@ -243,8 +244,8 @@
   rules.AddFailDownloadRule("http://custom/proxy.pac");
 
   TestCompletionCallback callback;
-  RecordingTestNetLog log;
-  PacFileDecider decider(&fetcher, &dhcp_fetcher, &log);
+  RecordingNetLogObserver observer;
+  PacFileDecider decider(&fetcher, &dhcp_fetcher, net::NetLog::Get());
   EXPECT_THAT(decider.Start(ProxyConfigWithAnnotation(
                                 config, TRAFFIC_ANNOTATION_FOR_TESTS),
                             base::TimeDelta(), true, callback.callback()),
@@ -252,7 +253,7 @@
   EXPECT_FALSE(decider.script_data().data);
 
   // Check the NetLog was filled correctly.
-  auto entries = log.GetEntries();
+  auto entries = observer.GetEntries();
 
   EXPECT_EQ(4u, entries.size());
   EXPECT_TRUE(
@@ -544,9 +545,9 @@
   Rules::Rule rule = rules.AddSuccessRule("http://custom/proxy.pac");
 
   TestCompletionCallback callback;
-  RecordingTestNetLog log;
+  RecordingNetLogObserver observer;
 
-  PacFileDecider decider(&fetcher, &dhcp_fetcher, &log);
+  PacFileDecider decider(&fetcher, &dhcp_fetcher, net::NetLog::Get());
   EXPECT_THAT(decider.Start(ProxyConfigWithAnnotation(
                                 config, TRAFFIC_ANNOTATION_FOR_TESTS),
                             base::TimeDelta(), true, callback.callback()),
@@ -562,7 +563,7 @@
   // Check the NetLog was filled correctly.
   // (Note that various states are repeated since both WPAD and custom
   // PAC scripts are tried).
-  auto entries = log.GetEntries();
+  auto entries = observer.GetEntries();
 
   EXPECT_EQ(10u, entries.size());
   EXPECT_TRUE(
@@ -655,8 +656,9 @@
   rules.AddFailDownloadRule("http://custom/proxy.pac");
 
   TestCompletionCallback callback;
-  RecordingTestNetLog log;
-  PacFileDecider decider(&fetcher, &dhcp_fetcher, &log);
+
+  RecordingNetLogObserver observer;
+  PacFileDecider decider(&fetcher, &dhcp_fetcher, net::NetLog::Get());
   EXPECT_THAT(decider.Start(ProxyConfigWithAnnotation(
                                 config, TRAFFIC_ANNOTATION_FOR_TESTS),
                             base::Milliseconds(1), true, callback.callback()),
@@ -666,7 +668,7 @@
   EXPECT_FALSE(decider.script_data().data);
 
   // Check the NetLog was filled correctly.
-  auto entries = log.GetEntries();
+  auto entries = observer.GetEntries();
 
   EXPECT_EQ(6u, entries.size());
   EXPECT_TRUE(
@@ -697,8 +699,8 @@
   rules.AddFailDownloadRule("http://custom/proxy.pac");
 
   TestCompletionCallback callback;
-  RecordingTestNetLog log;
-  PacFileDecider decider(&fetcher, &dhcp_fetcher, &log);
+  RecordingNetLogObserver observer;
+  PacFileDecider decider(&fetcher, &dhcp_fetcher, net::NetLog::Get());
   EXPECT_THAT(decider.Start(ProxyConfigWithAnnotation(
                                 config, TRAFFIC_ANNOTATION_FOR_TESTS),
                             base::Seconds(-5), true, callback.callback()),
@@ -706,7 +708,7 @@
   EXPECT_FALSE(decider.script_data().data);
 
   // Check the NetLog was filled correctly.
-  auto entries = log.GetEntries();
+  auto entries = observer.GetEntries();
 
   EXPECT_EQ(4u, entries.size());
   EXPECT_TRUE(
diff --git a/net/quic/dedicated_web_transport_http3_client_test.cc b/net/quic/dedicated_web_transport_http3_client_test.cc
index daf6077..ff36421 100644
--- a/net/quic/dedicated_web_transport_http3_client_test.cc
+++ b/net/quic/dedicated_web_transport_http3_client_test.cc
@@ -11,8 +11,6 @@
 #include "net/base/schemeful_site.h"
 #include "net/cert/mock_cert_verifier.h"
 #include "net/dns/mock_host_resolver.h"
-#include "net/log/test_net_log.h"
-#include "net/log/test_net_log_util.h"
 #include "net/proxy_resolution/configured_proxy_resolution_service.h"
 #include "net/quic/crypto/proof_source_chromium.h"
 #include "net/test/test_data_directory.h"
@@ -124,7 +122,7 @@
         HostPortPair("test.example.com", 0));
     builder.set_quic_context(std::move(quic_context));
 
-    builder.set_net_log(&net_log_);
+    builder.set_net_log(NetLog::Get());
     context_ = builder.Build();
 
     // By default, quit on error instead of waiting for RunLoop() to time out.
@@ -182,7 +180,6 @@
   ::testing::NiceMock<MockVisitor> visitor_;
   std::unique_ptr<QuicSimpleServer> server_;
   std::unique_ptr<base::RunLoop> run_loop_;
-  RecordingTestNetLog net_log_;
   quic::test::QuicTestBackend backend_;
 
   int port_ = 0;
diff --git a/net/quic/quic_chromium_client_session_test.cc b/net/quic/quic_chromium_client_session_test.cc
index fac8c17..bdf6673 100644
--- a/net/quic/quic_chromium_client_session_test.cc
+++ b/net/quic/quic_chromium_client_session_test.cc
@@ -180,8 +180,8 @@
     if (socket_data_)
       socket_factory_.AddSocketDataProvider(socket_data_.get());
     std::unique_ptr<DatagramClientSocket> socket =
-        socket_factory_.CreateDatagramClientSocket(DatagramSocket::DEFAULT_BIND,
-                                                   &net_log_, NetLogSource());
+        socket_factory_.CreateDatagramClientSocket(
+            DatagramSocket::DEFAULT_BIND, NetLog::Get(), NetLogSource());
     socket->Connect(kIpEndPoint);
     QuicChromiumPacketWriter* writer = new net::QuicChromiumPacketWriter(
         socket.get(), base::ThreadTaskRunnerHandle::Get().get());
@@ -214,7 +214,7 @@
         std::make_unique<quic::QuicClientPushPromiseIndex>(),
         &test_push_delegate_, base::DefaultTickClock::GetInstance(),
         base::ThreadTaskRunnerHandle::Get().get(),
-        /*socket_performance_watcher=*/nullptr, &net_log_);
+        /*socket_performance_watcher=*/nullptr, NetLog::Get());
     if (connectivity_monitor_) {
       connectivity_monitor_->SetInitialDefaultNetwork(default_network_);
       session_->AddConnectivityObserver(connectivity_monitor_.get());
@@ -296,7 +296,6 @@
   QuicFlagSaver flags_;  // Save/restore all QUIC flag values.
   quic::QuicConfig config_;
   quic::QuicCryptoClientConfig crypto_config_;
-  RecordingTestNetLog net_log_;
   RecordingBoundTestNetLog bound_test_net_log_;
   MockClientSocketFactory socket_factory_;
   std::unique_ptr<MockRead> default_read_;
@@ -469,7 +468,7 @@
 
   NetLogWithSource session_net_log = session_->net_log();
   EXPECT_EQ(NetLogSourceType::QUIC_SESSION, session_net_log.source().type);
-  EXPECT_EQ(&net_log_, session_net_log.net_log());
+  EXPECT_EQ(NetLog::Get(), session_net_log.net_log());
 
   std::unique_ptr<QuicChromiumClientSession::Handle> handle =
       session_->CreateHandle(destination_);
@@ -1946,7 +1945,7 @@
   // Create connected socket.
   std::unique_ptr<DatagramClientSocket> new_socket =
       socket_factory_.CreateDatagramClientSocket(DatagramSocket::RANDOM_BIND,
-                                                 &net_log_, NetLogSource());
+                                                 NetLog::Get(), NetLogSource());
   EXPECT_THAT(new_socket->Connect(kIpEndPoint), IsOk());
 
   // Create reader and writer.
@@ -2055,8 +2054,8 @@
 
     // Create connected socket.
     std::unique_ptr<DatagramClientSocket> new_socket =
-        socket_factory_.CreateDatagramClientSocket(DatagramSocket::RANDOM_BIND,
-                                                   &net_log_, NetLogSource());
+        socket_factory_.CreateDatagramClientSocket(
+            DatagramSocket::RANDOM_BIND, NetLog::Get(), NetLogSource());
     EXPECT_THAT(new_socket->Connect(kIpEndPoint), IsOk());
 
     // Create reader and writer.
@@ -2099,7 +2098,7 @@
   // Create connected socket.
   std::unique_ptr<DatagramClientSocket> new_socket =
       socket_factory_.CreateDatagramClientSocket(DatagramSocket::RANDOM_BIND,
-                                                 &net_log_, NetLogSource());
+                                                 NetLog::Get(), NetLogSource());
   EXPECT_THAT(new_socket->Connect(kIpEndPoint), IsOk());
 
   // Create reader and writer.
@@ -2189,7 +2188,7 @@
   // Create connected socket.
   std::unique_ptr<DatagramClientSocket> new_socket =
       socket_factory_.CreateDatagramClientSocket(DatagramSocket::RANDOM_BIND,
-                                                 &net_log_, NetLogSource());
+                                                 NetLog::Get(), NetLogSource());
   EXPECT_THAT(new_socket->Connect(kIpEndPoint), IsOk());
 
   // Create reader and writer.
diff --git a/net/quic/quic_connectivity_probing_manager_test.cc b/net/quic/quic_connectivity_probing_manager_test.cc
index 3189a1e..a39d726 100644
--- a/net/quic/quic_connectivity_probing_manager_test.cc
+++ b/net/quic/quic_connectivity_probing_manager_test.cc
@@ -120,7 +120,7 @@
     socket_factory_.AddSocketDataProvider(socket_data_.get());
     // Create a connected socket for probing.
     socket_ = socket_factory_.CreateDatagramClientSocket(
-        DatagramSocket::DEFAULT_BIND, &net_log_, NetLogSource());
+        DatagramSocket::DEFAULT_BIND, NetLog::Get(), NetLogSource());
     EXPECT_THAT(socket_->Connect(kIpEndPoint), IsOk());
     IPEndPoint self_address;
     socket_->GetLocalAddress(&self_address);
@@ -157,7 +157,6 @@
 
   quic::MockClock clock_;
   MockClientSocketFactory socket_factory_;
-  RecordingTestNetLog net_log_;
   RecordingBoundTestNetLog bound_test_net_log_;
 };
 
diff --git a/net/server/http_server_fuzzer.cc b/net/server/http_server_fuzzer.cc
index 5ebebd8..00fefb1 100644
--- a/net/server/http_server_fuzzer.cc
+++ b/net/server/http_server_fuzzer.cc
@@ -8,6 +8,7 @@
 #include "base/macros.h"
 #include "base/run_loop.h"
 #include "net/base/net_errors.h"
+#include "net/log/net_log.h"
 #include "net/log/test_net_log.h"
 #include "net/server/http_server.h"
 #include "net/socket/fuzzed_server_socket.h"
@@ -101,11 +102,14 @@
 //
 // |data| is used to create a FuzzedServerSocket.
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-  net::RecordingTestNetLog test_net_log;
+  // Including an observer; even though the recorded results aren't currently
+  // used, it'll ensure the netlogging code is fuzzed as well.
+  net::RecordingNetLogObserver net_log_observer;
   FuzzedDataProvider data_provider(data, size);
 
   std::unique_ptr<net::ServerSocket> server_socket(
-      std::make_unique<net::FuzzedServerSocket>(&data_provider, &test_net_log));
+      std::make_unique<net::FuzzedServerSocket>(&data_provider,
+                                                net::NetLog::Get()));
   CHECK_EQ(net::OK,
            server_socket->ListenWithAddressAndPort("127.0.0.1", 80, 5));
 
diff --git a/net/socket/client_socket_pool_base_unittest.cc b/net/socket/client_socket_pool_base_unittest.cc
index d2fd3ee..83fb8a0 100644
--- a/net/socket/client_socket_pool_base_unittest.cc
+++ b/net/socket/client_socket_pool_base_unittest.cc
@@ -700,7 +700,7 @@
   // It should be logged for the provided source and have the indicated reason.
   void ExpectSocketClosedWithReason(NetLogSource expected_source,
                                     const char* expected_reason) {
-    auto entries = net_log_.GetEntriesForSourceWithType(
+    auto entries = observer_.GetEntriesForSourceWithType(
         expected_source, NetLogEventType::SOCKET_POOL_CLOSING_SOCKET,
         NetLogEventPhase::NONE);
     ASSERT_EQ(1u, entries.size());
@@ -720,7 +720,6 @@
   // synchronous completions are not registered by this count.
   size_t completion_count() const { return test_base_.completion_count(); }
 
-  RecordingTestNetLog net_log_;
   const CommonConnectJobParams common_connect_job_params_{
       nullptr /* client_socket_factory */,
       nullptr /* host_resolver */,
@@ -734,11 +733,12 @@
       nullptr /* ssl_client_context */,
       nullptr /* socket_performance_watcher_factory */,
       nullptr /* network_quality_estimator */,
-      &net_log_,
+      NetLog::Get(),
       nullptr /* websocket_endpoint_lock_manager */};
   bool connect_backup_jobs_enabled_;
   MockClientSocketFactory client_socket_factory_;
   TestConnectJobFactory* connect_job_factory_;
+  RecordingNetLogObserver observer_;
   // These parameters are never actually used to create a TransportConnectJob.
   scoped_refptr<ClientSocketPool::SocketParams> params_;
   std::unique_ptr<TransportClientSocketPool> pool_;
diff --git a/net/socket/connect_job_unittest.cc b/net/socket/connect_job_unittest.cc
index 459c143..5a726bd0 100644
--- a/net/socket/connect_job_unittest.cc
+++ b/net/socket/connect_job_unittest.cc
@@ -108,13 +108,13 @@
             nullptr /* ssl_client_context */,
             nullptr /* socket_performance_watcher_factory */,
             nullptr /* network_quality_estimator */,
-            &net_log_,
+            NetLog::Get(),
             nullptr /* websocket_endpoint_lock_manager */) {}
   ~ConnectJobTest() override = default;
 
  protected:
   base::test::TaskEnvironment task_environment_;
-  RecordingTestNetLog net_log_;
+  RecordingNetLogObserver net_log_observer_;
   const CommonConnectJobParams common_connect_job_params_;
   TestConnectJobDelegate delegate_;
 };
@@ -185,7 +185,7 @@
   // Have to delete the job for it to log the end event.
   job.reset();
 
-  auto entries = net_log_.GetEntries();
+  auto entries = net_log_observer_.GetEntries();
 
   EXPECT_EQ(6u, entries.size());
   EXPECT_TRUE(LogContainsBeginEvent(entries, 0, NetLogEventType::CONNECT_JOB));
diff --git a/net/socket/socks5_client_socket_fuzzer.cc b/net/socket/socks5_client_socket_fuzzer.cc
index eb480d356..5ea5f3f9 100644
--- a/net/socket/socks5_client_socket_fuzzer.cc
+++ b/net/socket/socks5_client_socket_fuzzer.cc
@@ -14,6 +14,7 @@
 #include "net/base/address_list.h"
 #include "net/base/net_errors.h"
 #include "net/base/test_completion_callback.h"
+#include "net/log/net_log.h"
 #include "net/log/test_net_log.h"
 #include "net/socket/fuzzed_socket.h"
 #include "net/socket/socks5_client_socket.h"
@@ -25,14 +26,15 @@
 // |data| is used to create a FuzzedSocket to fuzz reads and writes, see that
 // class for details.
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-  // Use a test NetLog, to exercise logging code.
-  net::RecordingTestNetLog test_net_log;
+  // Including an observer; even though the recorded results aren't currently
+  // used, it'll ensure the netlogging code is fuzzed as well.
+  net::RecordingNetLogObserver net_log_observer;
 
   FuzzedDataProvider data_provider(data, size);
 
   net::TestCompletionCallback callback;
   std::unique_ptr<net::FuzzedSocket> fuzzed_socket(
-      new net::FuzzedSocket(&data_provider, &test_net_log));
+      new net::FuzzedSocket(&data_provider, net::NetLog::Get()));
   CHECK_EQ(net::OK, fuzzed_socket->Connect(callback.callback()));
 
   net::SOCKS5ClientSocket socket(std::move(fuzzed_socket),
diff --git a/net/socket/socks5_client_socket_unittest.cc b/net/socket/socks5_client_socket_unittest.cc
index e81ec66a..f85099a6 100644
--- a/net/socket/socks5_client_socket_unittest.cc
+++ b/net/socket/socks5_client_socket_unittest.cc
@@ -63,7 +63,7 @@
 
  protected:
   const uint16_t kNwPort;
-  RecordingTestNetLog net_log_;
+  RecordingNetLogObserver net_log_observer_;
   std::unique_ptr<SOCKS5ClientSocket> user_sock_;
   AddressList address_list_;
   // Filled in by BuildMockSocket() and owned by its return value
@@ -134,7 +134,7 @@
       MockRead(ASYNC, payload_read.data(), payload_read.size()) };
 
   user_sock_ =
-      BuildMockSocket(data_reads, data_writes, "localhost", 80, &net_log_);
+      BuildMockSocket(data_reads, data_writes, "localhost", 80, NetLog::Get());
 
   // At this state the TCP connection is completed but not the SOCKS handshake.
   EXPECT_TRUE(tcp_sock_->IsConnected());
@@ -144,7 +144,7 @@
   EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
   EXPECT_FALSE(user_sock_->IsConnected());
 
-  auto net_log_entries = net_log_.GetEntries();
+  auto net_log_entries = net_log_observer_.GetEntries();
   EXPECT_TRUE(LogContainsBeginEvent(net_log_entries, 0,
                                     NetLogEventType::SOCKS5_CONNECT));
 
@@ -153,7 +153,7 @@
   EXPECT_THAT(rv, IsOk());
   EXPECT_TRUE(user_sock_->IsConnected());
 
-  net_log_entries = net_log_.GetEntries();
+  net_log_entries = net_log_observer_.GetEntries();
   EXPECT_TRUE(LogContainsEndEvent(net_log_entries, -1,
                                   NetLogEventType::SOCKS5_CONNECT));
 
@@ -261,11 +261,11 @@
         MockRead(ASYNC, kSOCKS5GreetResponse, kSOCKS5GreetResponseLength),
         MockRead(ASYNC, kSOCKS5OkResponse, kSOCKS5OkResponseLength) };
     user_sock_ =
-        BuildMockSocket(data_reads, data_writes, hostname, 80, &net_log_);
+        BuildMockSocket(data_reads, data_writes, hostname, 80, NetLog::Get());
     int rv = user_sock_->Connect(callback_.callback());
     EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
 
-    auto net_log_entries = net_log_.GetEntries();
+    auto net_log_entries = net_log_observer_.GetEntries();
     EXPECT_TRUE(LogContainsBeginEvent(net_log_entries, 0,
                                       NetLogEventType::SOCKS5_CONNECT));
 
@@ -273,7 +273,7 @@
     EXPECT_THAT(rv, IsOk());
     EXPECT_TRUE(user_sock_->IsConnected());
 
-    net_log_entries = net_log_.GetEntries();
+    net_log_entries = net_log_observer_.GetEntries();
     EXPECT_TRUE(LogContainsEndEvent(net_log_entries, -1,
                                     NetLogEventType::SOCKS5_CONNECT));
   }
@@ -290,17 +290,17 @@
         MockRead(ASYNC, partial2, base::size(partial2)),
         MockRead(ASYNC, kSOCKS5OkResponse, kSOCKS5OkResponseLength)};
     user_sock_ =
-        BuildMockSocket(data_reads, data_writes, hostname, 80, &net_log_);
+        BuildMockSocket(data_reads, data_writes, hostname, 80, NetLog::Get());
     int rv = user_sock_->Connect(callback_.callback());
     EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
 
-    auto net_log_entries = net_log_.GetEntries();
+    auto net_log_entries = net_log_observer_.GetEntries();
     EXPECT_TRUE(LogContainsBeginEvent(net_log_entries, 0,
                                       NetLogEventType::SOCKS5_CONNECT));
     rv = callback_.WaitForResult();
     EXPECT_THAT(rv, IsOk());
     EXPECT_TRUE(user_sock_->IsConnected());
-    net_log_entries = net_log_.GetEntries();
+    net_log_entries = net_log_observer_.GetEntries();
     EXPECT_TRUE(LogContainsEndEvent(net_log_entries, -1,
                                     NetLogEventType::SOCKS5_CONNECT));
   }
@@ -317,16 +317,16 @@
         MockRead(ASYNC, kSOCKS5GreetResponse, kSOCKS5GreetResponseLength),
         MockRead(ASYNC, kSOCKS5OkResponse, kSOCKS5OkResponseLength) };
     user_sock_ =
-        BuildMockSocket(data_reads, data_writes, hostname, 80, &net_log_);
+        BuildMockSocket(data_reads, data_writes, hostname, 80, NetLog::Get());
     int rv = user_sock_->Connect(callback_.callback());
     EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
-    auto net_log_entries = net_log_.GetEntries();
+    auto net_log_entries = net_log_observer_.GetEntries();
     EXPECT_TRUE(LogContainsBeginEvent(net_log_entries, 0,
                                       NetLogEventType::SOCKS5_CONNECT));
     rv = callback_.WaitForResult();
     EXPECT_THAT(rv, IsOk());
     EXPECT_TRUE(user_sock_->IsConnected());
-    net_log_entries = net_log_.GetEntries();
+    net_log_entries = net_log_observer_.GetEntries();
     EXPECT_TRUE(LogContainsEndEvent(net_log_entries, -1,
                                     NetLogEventType::SOCKS5_CONNECT));
   }
@@ -345,16 +345,16 @@
     };
 
     user_sock_ =
-        BuildMockSocket(data_reads, data_writes, hostname, 80, &net_log_);
+        BuildMockSocket(data_reads, data_writes, hostname, 80, NetLog::Get());
     int rv = user_sock_->Connect(callback_.callback());
     EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
-    auto net_log_entries = net_log_.GetEntries();
+    auto net_log_entries = net_log_observer_.GetEntries();
     EXPECT_TRUE(LogContainsBeginEvent(net_log_entries, 0,
                                       NetLogEventType::SOCKS5_CONNECT));
     rv = callback_.WaitForResult();
     EXPECT_THAT(rv, IsOk());
     EXPECT_TRUE(user_sock_->IsConnected());
-    net_log_entries = net_log_.GetEntries();
+    net_log_entries = net_log_observer_.GetEntries();
     EXPECT_TRUE(LogContainsEndEvent(net_log_entries, -1,
                                     NetLogEventType::SOCKS5_CONNECT));
   }
@@ -362,10 +362,9 @@
 
 TEST_F(SOCKS5ClientSocketTest, Tag) {
   StaticSocketDataProvider data;
-  RecordingTestNetLog log;
   MockTaggingStreamSocket* tagging_sock =
       new MockTaggingStreamSocket(std::unique_ptr<StreamSocket>(
-          new MockTCPClientSocket(address_list_, &log, &data)));
+          new MockTCPClientSocket(address_list_, NetLog::Get(), &data)));
 
   // |socket| takes ownership of |tagging_sock|, but keep a non-owning pointer
   // to it.
diff --git a/net/socket/socks_client_socket_fuzzer.cc b/net/socket/socks_client_socket_fuzzer.cc
index 3bce834..d8e877a 100644
--- a/net/socket/socks_client_socket_fuzzer.cc
+++ b/net/socket/socks_client_socket_fuzzer.cc
@@ -17,6 +17,7 @@
 #include "net/dns/host_resolver.h"
 #include "net/dns/mock_host_resolver.h"
 #include "net/dns/public/secure_dns_policy.h"
+#include "net/log/net_log.h"
 #include "net/log/test_net_log.h"
 #include "net/socket/fuzzed_socket.h"
 #include "net/socket/socks_client_socket.h"
@@ -27,8 +28,9 @@
 // |data| is used to create a FuzzedSocket to fuzz reads and writes, see that
 // class for details.
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-  // Use a test NetLog, to exercise logging code.
-  net::RecordingTestNetLog test_net_log;
+  // Including an observer; even though the recorded results aren't currently
+  // used, it'll ensure the netlogging code is fuzzed as well.
+  net::RecordingNetLogObserver net_log_observer;
 
   FuzzedDataProvider data_provider(data, size);
 
@@ -53,7 +55,7 @@
 
   net::TestCompletionCallback callback;
   std::unique_ptr<net::FuzzedSocket> fuzzed_socket(
-      new net::FuzzedSocket(&data_provider, &test_net_log));
+      new net::FuzzedSocket(&data_provider, net::NetLog::Get()));
   CHECK_EQ(net::OK, fuzzed_socket->Connect(callback.callback()));
 
   net::SOCKSClientSocket socket(
diff --git a/net/socket/socks_client_socket_unittest.cc b/net/socket/socks_client_socket_unittest.cc
index de50155..ace5905 100644
--- a/net/socket/socks_client_socket_unittest.cc
+++ b/net/socket/socks_client_socket_unittest.cc
@@ -114,10 +114,10 @@
     MockRead data_reads[] = {
         MockRead(ASYNC, kSOCKS4OkReply, kSOCKS4OkReplyLength),
         MockRead(ASYNC, payload_read.data(), payload_read.size())};
-    RecordingTestNetLog log;
+    RecordingNetLogObserver log_observer;
 
     user_sock_ = BuildMockSocket(data_reads, data_writes, host_resolver_.get(),
-                                 "localhost", 80, &log);
+                                 "localhost", 80, NetLog::Get());
 
     // At this state the TCP connection is completed but not the SOCKS
     // handshake.
@@ -127,7 +127,7 @@
     int rv = user_sock_->Connect(callback_.callback());
     EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
 
-    auto entries = log.GetEntries();
+    auto entries = log_observer.GetEntries();
     EXPECT_TRUE(
         LogContainsBeginEvent(entries, 0, NetLogEventType::SOCKS_CONNECT));
     EXPECT_FALSE(user_sock_->IsConnected());
@@ -135,7 +135,7 @@
     rv = callback_.WaitForResult();
     EXPECT_THAT(rv, IsOk());
     EXPECT_TRUE(user_sock_->IsConnected());
-    entries = log.GetEntries();
+    entries = log_observer.GetEntries();
     EXPECT_TRUE(
         LogContainsEndEvent(entries, -1, NetLogEventType::SOCKS_CONNECT));
 
@@ -233,15 +233,15 @@
                   kSOCKS4OkRequestLocalHostPort80Length)};
     MockRead data_reads[] = {
         MockRead(SYNCHRONOUS, test.fail_reply, base::size(test.fail_reply))};
-    RecordingTestNetLog log;
+    RecordingNetLogObserver log_observer;
 
     user_sock_ = BuildMockSocket(data_reads, data_writes, host_resolver_.get(),
-                                 "localhost", 80, &log);
+                                 "localhost", 80, NetLog::Get());
 
     int rv = user_sock_->Connect(callback_.callback());
     EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
 
-    auto entries = log.GetEntries();
+    auto entries = log_observer.GetEntries();
     EXPECT_TRUE(
         LogContainsBeginEvent(entries, 0, NetLogEventType::SOCKS_CONNECT));
 
@@ -249,7 +249,7 @@
     EXPECT_EQ(test.fail_code, rv);
     EXPECT_FALSE(user_sock_->IsConnected());
     EXPECT_TRUE(tcp_sock_->IsConnected());
-    entries = log.GetEntries();
+    entries = log_observer.GetEntries();
     EXPECT_TRUE(
         LogContainsEndEvent(entries, -1, NetLogEventType::SOCKS_CONNECT));
   }
@@ -266,21 +266,21 @@
   MockRead data_reads[] = {
       MockRead(ASYNC, kSOCKSPartialReply1, base::size(kSOCKSPartialReply1)),
       MockRead(ASYNC, kSOCKSPartialReply2, base::size(kSOCKSPartialReply2))};
-  RecordingTestNetLog log;
+  RecordingNetLogObserver log_observer;
 
   user_sock_ = BuildMockSocket(data_reads, data_writes, host_resolver_.get(),
-                               "localhost", 80, &log);
+                               "localhost", 80, NetLog::Get());
 
   int rv = user_sock_->Connect(callback_.callback());
   EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
-  auto entries = log.GetEntries();
+  auto entries = log_observer.GetEntries();
   EXPECT_TRUE(
       LogContainsBeginEvent(entries, 0, NetLogEventType::SOCKS_CONNECT));
 
   rv = callback_.WaitForResult();
   EXPECT_THAT(rv, IsOk());
   EXPECT_TRUE(user_sock_->IsConnected());
-  entries = log.GetEntries();
+  entries = log_observer.GetEntries();
   EXPECT_TRUE(LogContainsEndEvent(entries, -1, NetLogEventType::SOCKS_CONNECT));
 }
 
@@ -300,21 +300,21 @@
   };
   MockRead data_reads[] = {
       MockRead(ASYNC, kSOCKS4OkReply, kSOCKS4OkReplyLength)};
-  RecordingTestNetLog log;
+  RecordingNetLogObserver log_observer;
 
   user_sock_ = BuildMockSocket(data_reads, data_writes, host_resolver_.get(),
-                               "localhost", 80, &log);
+                               "localhost", 80, NetLog::Get());
 
   int rv = user_sock_->Connect(callback_.callback());
   EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
-  auto entries = log.GetEntries();
+  auto entries = log_observer.GetEntries();
   EXPECT_TRUE(
       LogContainsBeginEvent(entries, 0, NetLogEventType::SOCKS_CONNECT));
 
   rv = callback_.WaitForResult();
   EXPECT_THAT(rv, IsOk());
   EXPECT_TRUE(user_sock_->IsConnected());
-  entries = log.GetEntries();
+  entries = log_observer.GetEntries();
   EXPECT_TRUE(LogContainsEndEvent(entries, -1, NetLogEventType::SOCKS_CONNECT));
 }
 
@@ -327,21 +327,21 @@
       MockRead(ASYNC, kSOCKS4OkReply, kSOCKS4OkReplyLength - 2),
       // close connection unexpectedly
       MockRead(SYNCHRONOUS, 0)};
-  RecordingTestNetLog log;
+  RecordingNetLogObserver log_observer;
 
   user_sock_ = BuildMockSocket(data_reads, data_writes, host_resolver_.get(),
-                               "localhost", 80, &log);
+                               "localhost", 80, NetLog::Get());
 
   int rv = user_sock_->Connect(callback_.callback());
   EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
-  auto entries = log.GetEntries();
+  auto entries = log_observer.GetEntries();
   EXPECT_TRUE(
       LogContainsBeginEvent(entries, 0, NetLogEventType::SOCKS_CONNECT));
 
   rv = callback_.WaitForResult();
   EXPECT_THAT(rv, IsError(ERR_CONNECTION_CLOSED));
   EXPECT_FALSE(user_sock_->IsConnected());
-  entries = log.GetEntries();
+  entries = log_observer.GetEntries();
   EXPECT_TRUE(LogContainsEndEvent(entries, -1, NetLogEventType::SOCKS_CONNECT));
 }
 
@@ -352,14 +352,15 @@
 
   host_resolver_->rules()->AddSimulatedTimeoutFailure(hostname);
 
-  RecordingTestNetLog log;
+  RecordingNetLogObserver log_observer;
 
-  user_sock_ = BuildMockSocket(base::span<MockRead>(), base::span<MockWrite>(),
-                               host_resolver_.get(), hostname, 80, &log);
+  user_sock_ =
+      BuildMockSocket(base::span<MockRead>(), base::span<MockWrite>(),
+                      host_resolver_.get(), hostname, 80, NetLog::Get());
 
   int rv = user_sock_->Connect(callback_.callback());
   EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
-  auto entries = log.GetEntries();
+  auto entries = log_observer.GetEntries();
   EXPECT_TRUE(
       LogContainsBeginEvent(entries, 0, NetLogEventType::SOCKS_CONNECT));
 
@@ -368,7 +369,7 @@
   EXPECT_THAT(user_sock_->GetResolveErrorInfo().error,
               IsError(ERR_DNS_TIMED_OUT));
   EXPECT_FALSE(user_sock_->IsConnected());
-  entries = log.GetEntries();
+  entries = log_observer.GetEntries();
   EXPECT_TRUE(LogContainsEndEvent(entries, -1, NetLogEventType::SOCKS_CONNECT));
 }
 
@@ -428,10 +429,9 @@
 
 TEST_F(SOCKSClientSocketTest, Tag) {
   StaticSocketDataProvider data;
-  RecordingTestNetLog log;
   MockTaggingStreamSocket* tagging_sock =
       new MockTaggingStreamSocket(std::unique_ptr<StreamSocket>(
-          new MockTCPClientSocket(address_list_, &log, &data)));
+          new MockTCPClientSocket(address_list_, NetLog::Get(), &data)));
 
   std::unique_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
   // |connection| takes ownership of |tagging_sock|, but keep a
@@ -454,10 +454,10 @@
   for (auto secure_dns_policy :
        {SecureDnsPolicy::kAllow, SecureDnsPolicy::kDisable}) {
     StaticSocketDataProvider data;
-    RecordingTestNetLog log;
     MockHostResolver host_resolver;
     SOCKSClientSocket socket(
-        std::make_unique<MockTCPClientSocket>(address_list_, &log, &data),
+        std::make_unique<MockTCPClientSocket>(address_list_, NetLog::Get(),
+                                              &data),
         HostPortPair("localhost", 80), NetworkIsolationKey(), DEFAULT_PRIORITY,
         &host_resolver, secure_dns_policy, TRAFFIC_ANNOTATION_FOR_TESTS);
 
diff --git a/net/socket/ssl_client_socket_unittest.cc b/net/socket/ssl_client_socket_unittest.cc
index 675f010..9197611 100644
--- a/net/socket/ssl_client_socket_unittest.cc
+++ b/net/socket/ssl_client_socket_unittest.cc
@@ -866,8 +866,8 @@
       const SSLConfig& ssl_config,
       const HostPortPair& host_port_pair,
       int* result) {
-    std::unique_ptr<StreamSocket> transport(
-        new TCPClientSocket(addr_, nullptr, nullptr, &log_, NetLogSource()));
+    std::unique_ptr<StreamSocket> transport(new TCPClientSocket(
+        addr_, nullptr, nullptr, NetLog::Get(), NetLogSource()));
     int rv = callback_.GetResult(transport->Connect(callback_.callback()));
     if (rv != OK) {
       LOG(ERROR) << "Could not connect to SpawnedTestServer";
@@ -916,7 +916,7 @@
     return result;
   }
 
-  RecordingTestNetLog log_;
+  RecordingNetLogObserver log_observer_;
   ClientSocketFactory* socket_factory_;
   std::unique_ptr<TestSSLConfigService> ssl_config_service_;
   std::unique_ptr<MockCertVerifier> cert_verifier_;
@@ -1573,9 +1573,8 @@
       StartEmbeddedTestServer(EmbeddedTestServer::CERT_OK, GetServerConfig()));
 
   TestCompletionCallback callback;
-  RecordingTestNetLog log;
-  std::unique_ptr<StreamSocket> transport(
-      new TCPClientSocket(addr(), nullptr, nullptr, &log, NetLogSource()));
+  std::unique_ptr<StreamSocket> transport(new TCPClientSocket(
+      addr(), nullptr, nullptr, NetLog::Get(), NetLogSource()));
   int rv = callback.GetResult(transport->Connect(callback.callback()));
   EXPECT_THAT(rv, IsOk());
 
@@ -1586,13 +1585,13 @@
 
   rv = sock->Connect(callback.callback());
 
-  auto entries = log.GetEntries();
+  auto entries = log_observer_.GetEntries();
   EXPECT_TRUE(LogContainsBeginEvent(entries, 5, NetLogEventType::SSL_CONNECT));
   if (rv == ERR_IO_PENDING)
     rv = callback.WaitForResult();
   EXPECT_THAT(rv, IsOk());
   EXPECT_TRUE(sock->IsConnected());
-  entries = log.GetEntries();
+  entries = log_observer_.GetEntries();
   EXPECT_TRUE(LogContainsEndEvent(entries, -1, NetLogEventType::SSL_CONNECT));
 
   sock->Disconnect();
@@ -1623,7 +1622,7 @@
   // test that the handshake has finished. This is because it may be
   // desirable to disconnect the socket before showing a user prompt, since
   // the user may take indefinitely long to respond.
-  auto entries = log_.GetEntries();
+  auto entries = log_observer_.GetEntries();
   EXPECT_TRUE(LogContainsEndEvent(entries, -1, NetLogEventType::SSL_CONNECT));
 }
 
@@ -1651,8 +1650,8 @@
       ct_policy_enforcer_.get(), ssl_client_session_cache_.get(), nullptr);
 
   TestCompletionCallback callback;
-  auto transport = std::make_unique<TCPClientSocket>(addr(), nullptr, nullptr,
-                                                     &log_, NetLogSource());
+  auto transport = std::make_unique<TCPClientSocket>(
+      addr(), nullptr, nullptr, NetLog::Get(), NetLogSource());
   int rv = callback.GetResult(transport->Connect(callback.callback()));
   ASSERT_THAT(rv, IsOk());
 
@@ -1686,7 +1685,7 @@
   // test that the handshake has finished. This is because it may be
   // desirable to disconnect the socket before showing a user prompt, since
   // the user may take indefinitely long to respond.
-  auto entries = log_.GetEntries();
+  auto entries = log_observer_.GetEntries();
   EXPECT_TRUE(LogContainsEndEvent(entries, -1, NetLogEventType::SSL_CONNECT));
 }
 
@@ -1731,7 +1730,7 @@
   ASSERT_TRUE(CreateAndConnectSSLClientSocket(SSLConfig(), &rv));
   EXPECT_THAT(rv, IsError(ERR_SSL_CLIENT_AUTH_CERT_NEEDED));
 
-  auto entries = log_.GetEntries();
+  auto entries = log_observer_.GetEntries();
   EXPECT_TRUE(LogContainsEndEvent(entries, -1, NetLogEventType::SSL_CONNECT));
   EXPECT_FALSE(sock_->IsConnected());
 }
@@ -2466,10 +2465,9 @@
       StartEmbeddedTestServer(EmbeddedTestServer::CERT_OK, GetServerConfig()));
 
   TestCompletionCallback callback;
-  RecordingTestNetLog log;
-  log.SetObserverCaptureMode(NetLogCaptureMode::kEverything);
-  std::unique_ptr<StreamSocket> transport(
-      new TCPClientSocket(addr(), nullptr, nullptr, &log, NetLogSource()));
+  log_observer_.SetObserverCaptureMode(NetLogCaptureMode::kEverything);
+  std::unique_ptr<StreamSocket> transport(new TCPClientSocket(
+      addr(), nullptr, nullptr, NetLog::Get(), NetLogSource()));
   int rv = callback.GetResult(transport->Connect(callback.callback()));
   EXPECT_THAT(rv, IsOk());
 
@@ -2490,7 +2488,7 @@
                   callback.callback(), TRAFFIC_ANNOTATION_FOR_TESTS));
   EXPECT_EQ(static_cast<int>(base::size(request_text) - 1), rv);
 
-  auto entries = log.GetEntries();
+  auto entries = log_observer_.GetEntries();
   size_t last_index = ExpectLogContainsSomewhereAfter(
       entries, 5, NetLogEventType::SSL_SOCKET_BYTES_SENT,
       NetLogEventPhase::NONE);
@@ -2502,7 +2500,7 @@
     if (rv <= 0)
       break;
 
-    entries = log.GetEntries();
+    entries = log_observer_.GetEntries();
     last_index = ExpectLogContainsSomewhereAfter(
         entries, last_index + 1, NetLogEventType::SSL_SOCKET_BYTES_RECEIVED,
         NetLogEventPhase::NONE);
@@ -2766,7 +2764,7 @@
   EXPECT_THAT(rv, IsOk());
   EXPECT_TRUE(sock_->IsConnected());
 
-  auto entries = log_.GetEntries();
+  auto entries = log_observer_.GetEntries();
   EXPECT_TRUE(LogContainsEndEvent(entries, -1, NetLogEventType::SSL_CONNECT));
 
   SSLInfo ssl_info;
@@ -3199,8 +3197,8 @@
   sock_.reset();
 
   // Using a different HostPortPair uses a different session cache key.
-  std::unique_ptr<StreamSocket> transport(
-      new TCPClientSocket(addr(), nullptr, nullptr, &log_, NetLogSource()));
+  std::unique_ptr<StreamSocket> transport(new TCPClientSocket(
+      addr(), nullptr, nullptr, NetLog::Get(), NetLogSource()));
   TestCompletionCallback callback;
   ASSERT_THAT(callback.GetResult(transport->Connect(callback.callback())),
               IsOk());
@@ -3258,8 +3256,8 @@
     for (int i = 0; i < 3; i++) {
       SCOPED_TRACE(i);
 
-      std::unique_ptr<StreamSocket> transport(
-          new TCPClientSocket(addr(), nullptr, nullptr, &log_, NetLogSource()));
+      std::unique_ptr<StreamSocket> transport(new TCPClientSocket(
+          addr(), nullptr, nullptr, NetLog::Get(), NetLogSource()));
       TestCompletionCallback callback;
       ASSERT_THAT(callback.GetResult(transport->Connect(callback.callback())),
                   IsOk());
@@ -5522,9 +5520,8 @@
   ASSERT_TRUE(
       StartEmbeddedTestServer(EmbeddedTestServer::CERT_OK, SSLServerConfig()));
 
-  RecordingTestNetLog log;
-  std::unique_ptr<StreamSocket> transport(
-      new TCPClientSocket(addr(), nullptr, nullptr, &log, NetLogSource()));
+  std::unique_ptr<StreamSocket> transport(new TCPClientSocket(
+      addr(), nullptr, nullptr, NetLog::Get(), NetLogSource()));
 
   MockTaggingStreamSocket* tagging_sock =
       new MockTaggingStreamSocket(std::move(transport));
@@ -6235,6 +6232,65 @@
       0);
 }
 
+// Test that the server_name extension (SNI) is sent on DNS names, and not IP
+// literals.
+TEST_F(SSLClientSocketTest, ServerName) {
+  absl::optional<std::string> got_server_name;
+  bool ran_callback = false;
+  auto reset_callback_state = [&] {
+    got_server_name = absl::nullopt;
+    ran_callback = false;
+  };
+
+  // Start a server which records the server name.
+  SSLServerConfig server_config;
+  server_config.client_hello_callback_for_testing =
+      base::BindLambdaForTesting([&](const SSL_CLIENT_HELLO* client_hello) {
+        const char* server_name =
+            SSL_get_servername(client_hello->ssl, TLSEXT_NAMETYPE_host_name);
+        if (server_name) {
+          got_server_name = server_name;
+        } else {
+          got_server_name = absl::nullopt;
+        }
+        ran_callback = true;
+      });
+  ASSERT_TRUE(
+      StartEmbeddedTestServer(EmbeddedTestServer::CERT_OK, server_config));
+
+  // The client should send the server_name extension for DNS names.
+  uint16_t port = host_port_pair().port();
+  int rv;
+  ASSERT_TRUE(CreateAndConnectSSLClientSocketWithHost(
+      SSLConfig(), HostPortPair("example.com", port), &rv));
+  ASSERT_THAT(rv, IsOk());
+  EXPECT_TRUE(ran_callback);
+  EXPECT_EQ(got_server_name, "example.com");
+
+  // The client should not send the server_name extension for IPv4 and IPv6
+  // literals. See https://crbug.com/500981.
+  reset_callback_state();
+  ASSERT_TRUE(CreateAndConnectSSLClientSocketWithHost(
+      SSLConfig(), HostPortPair("1.2.3.4", port), &rv));
+  ASSERT_THAT(rv, IsOk());
+  EXPECT_TRUE(ran_callback);
+  EXPECT_EQ(got_server_name, absl::nullopt);
+
+  reset_callback_state();
+  ASSERT_TRUE(CreateAndConnectSSLClientSocketWithHost(
+      SSLConfig(), HostPortPair("::1", port), &rv));
+  ASSERT_THAT(rv, IsOk());
+  EXPECT_TRUE(ran_callback);
+  EXPECT_EQ(got_server_name, absl::nullopt);
+
+  reset_callback_state();
+  ASSERT_TRUE(CreateAndConnectSSLClientSocketWithHost(
+      SSLConfig(), HostPortPair("2001:db8::42", port), &rv));
+  ASSERT_THAT(rv, IsOk());
+  EXPECT_TRUE(ran_callback);
+  EXPECT_EQ(got_server_name, absl::nullopt);
+}
+
 class SSLClientSocketAlpsTest
     : public SSLClientSocketTest,
       public ::testing::WithParamInterface<std::tuple<bool, bool>> {
diff --git a/net/socket/transport_client_socket_pool_unittest.cc b/net/socket/transport_client_socket_pool_unittest.cc
index 7782fd2..2560b3c 100644
--- a/net/socket/transport_client_socket_pool_unittest.cc
+++ b/net/socket/transport_client_socket_pool_unittest.cc
@@ -35,8 +35,8 @@
 #include "net/http/http_network_session.h"
 #include "net/http/http_proxy_connect_job.h"
 #include "net/http/transport_security_state.h"
+#include "net/log/net_log.h"
 #include "net/log/net_log_with_source.h"
-#include "net/log/test_net_log.h"
 #include "net/proxy_resolution/configured_proxy_resolution_service.h"
 #include "net/socket/client_socket_handle.h"
 #include "net/socket/connect_job.h"
@@ -118,7 +118,7 @@
                   NetworkIsolationKey(),
                   SecureDnsPolicy::kAllow),
         params_(ClientSocketPool::SocketParams::CreateForHttpForTesting()),
-        client_socket_factory_(&net_log_) {
+        client_socket_factory_(NetLog::Get()) {
     std::unique_ptr<MockCertVerifier> cert_verifier =
         std::make_unique<MockCertVerifier>();
     cert_verifier->set_default_result(OK);
@@ -190,7 +190,6 @@
   size_t completion_count() const { return test_base_.completion_count(); }
 
   bool connect_backup_jobs_enabled_;
-  RecordingTestNetLog net_log_;
 
   // |group_id_| and |params_| correspond to the same group.
   const ClientSocketPool::GroupId group_id_;
diff --git a/net/socket/transport_client_socket_unittest.cc b/net/socket/transport_client_socket_unittest.cc
index eeb9d6d..ba922cb 100644
--- a/net/socket/transport_client_socket_unittest.cc
+++ b/net/socket/transport_client_socket_unittest.cc
@@ -91,7 +91,7 @@
  protected:
   base::RunLoop connect_loop_;
   uint16_t listen_port_;
-  RecordingTestNetLog net_log_;
+  RecordingNetLogObserver net_log_observer_;
   ClientSocketFactory* const socket_factory_;
   std::unique_ptr<StreamSocket> sock_;
   std::unique_ptr<StreamSocket> connected_sock_;
@@ -119,7 +119,7 @@
   AddressList addr = AddressList::CreateFromIPAddress(
       IPAddress::IPv4Localhost(), listen_port_);
   sock_ = socket_factory_->CreateTransportClientSocket(
-      addr, nullptr, nullptr, &net_log_, NetLogSource());
+      addr, nullptr, nullptr, NetLog::Get(), NetLogSource());
 }
 
 int TransportClientSocketTest::DrainClientSocket(
@@ -236,7 +236,7 @@
   // Wait for |listen_sock_| to accept a connection.
   connect_loop_.Run();
 
-  auto net_log_entries = net_log_.GetEntries();
+  auto net_log_entries = net_log_observer_.GetEntries();
   EXPECT_TRUE(
       LogContainsBeginEvent(net_log_entries, 0, NetLogEventType::SOCKET_ALIVE));
   EXPECT_TRUE(
@@ -249,7 +249,7 @@
   }
 
   EXPECT_TRUE(sock_->IsConnected());
-  net_log_entries = net_log_.GetEntries();
+  net_log_entries = net_log_observer_.GetEntries();
   EXPECT_TRUE(
       LogContainsEndEvent(net_log_entries, -1, NetLogEventType::TCP_CONNECT));
 
diff --git a/net/socket/transport_connect_job_unittest.cc b/net/socket/transport_connect_job_unittest.cc
index 67a7165a..4b82255d 100644
--- a/net/socket/transport_connect_job_unittest.cc
+++ b/net/socket/transport_connect_job_unittest.cc
@@ -20,7 +20,7 @@
 #include "net/base/test_completion_callback.h"
 #include "net/dns/mock_host_resolver.h"
 #include "net/dns/public/secure_dns_policy.h"
-#include "net/log/test_net_log.h"
+#include "net/log/net_log.h"
 #include "net/socket/connect_job_test_util.h"
 #include "net/socket/connection_attempts.h"
 #include "net/socket/stream_socket.h"
@@ -41,7 +41,7 @@
  public:
   TransportConnectJobTest()
       : WithTaskEnvironment(base::test::TaskEnvironment::TimeSource::MOCK_TIME),
-        client_socket_factory_(&net_log_),
+        client_socket_factory_(NetLog::Get()),
         common_connect_job_params_(
             &client_socket_factory_,
             &host_resolver_,
@@ -55,7 +55,7 @@
             nullptr /* ssl_client_context */,
             nullptr /* socket_performance_watcher_factory */,
             nullptr /* network_quality_estimator */,
-            &net_log_,
+            NetLog::Get(),
             nullptr /* websocket_endpoint_lock_manager */) {}
 
   ~TransportConnectJobTest() override {}
@@ -68,7 +68,6 @@
   }
 
  protected:
-  RecordingTestNetLog net_log_;
   MockHostResolver host_resolver_;
   MockTransportClientSocketFactory client_socket_factory_;
   const CommonConnectJobParams common_connect_job_params_;
diff --git a/net/socket/udp_socket_posix_unittest.cc b/net/socket/udp_socket_posix_unittest.cc
index be20d83..6f61ed3 100644
--- a/net/socket/udp_socket_posix_unittest.cc
+++ b/net/socket/udp_socket_posix_unittest.cc
@@ -5,8 +5,10 @@
 #include "net/socket/udp_socket_posix.h"
 
 #include "base/bind.h"
+#include "build/build_config.h"
 #include "net/base/completion_repeating_callback.h"
 #include "net/base/net_errors.h"
+#include "net/log/net_log.h"
 #include "net/log/test_net_log.h"
 #include "net/log/test_net_log_util.h"
 #include "net/socket/datagram_socket.h"
@@ -131,7 +133,7 @@
   UDPSocketPosixTest()
       : TestWithTaskEnvironment(
             base::test::TaskEnvironment::TimeSource::MOCK_TIME),
-        socket_(DatagramSocket::DEFAULT_BIND, &client_log_, NetLogSource()),
+        socket_(DatagramSocket::DEFAULT_BIND, NetLog::Get(), NetLogSource()),
         callback_fired_(false) {
     write_callback_ = base::BindRepeating(&UDPSocketPosixTest::OnWriteComplete,
                                           weak_factory_.GetWeakPtr());
@@ -218,7 +220,7 @@
         .WillOnce(Return(kNumMsgs));
   }
 
-  RecordingTestNetLog client_log_;
+  RecordingNetLogObserver net_log_observer_;
   MockUDPSocketPosix socket_;
   DatagramBuffers buffers_;
   bool callback_fired_;
@@ -344,7 +346,7 @@
   socket_.DidSendBuffers(std::move(send_result));
   EXPECT_EQ(0u, socket_.GetUnwrittenBuffers().size());
   VerifyBuffersDequeued();
-  auto client_entries = client_log_.GetEntries();
+  auto client_entries = net_log_observer_.GetEntries();
   EXPECT_EQ(4u, client_entries.size());
   EXPECT_TRUE(
       LogContainsBeginEvent(client_entries, 0, NetLogEventType::SOCKET_ALIVE));
@@ -367,7 +369,7 @@
   socket_.SetWriteCallback(write_callback_);
   socket_.DidSendBuffers(std::move(send_result));
   EXPECT_EQ(0u, socket_.GetUnwrittenBuffers().size());
-  auto client_entries = client_log_.GetEntries();
+  auto client_entries = net_log_observer_.GetEntries();
   EXPECT_EQ(4u, client_entries.size());
   EXPECT_TRUE(
       LogContainsBeginEvent(client_entries, 0, NetLogEventType::SOCKET_ALIVE));
@@ -391,7 +393,7 @@
   socket_.SetWriteCallback(write_callback_);
   socket_.DidSendBuffers(std::move(send_result));
   EXPECT_EQ(2u, socket_.GetUnwrittenBuffers().size());
-  auto client_entries = client_log_.GetEntries();
+  auto client_entries = net_log_observer_.GetEntries();
   EXPECT_EQ(2u, client_entries.size());
   EXPECT_TRUE(
       LogContainsBeginEvent(client_entries, 0, NetLogEventType::SOCKET_ALIVE));
@@ -409,7 +411,7 @@
   socket_.SetWriteCallback(write_callback_);
   socket_.DidSendBuffers(std::move(send_result));
   EXPECT_EQ(2u, socket_.GetUnwrittenBuffers().size());
-  auto client_entries = client_log_.GetEntries();
+  auto client_entries = net_log_observer_.GetEntries();
   EXPECT_EQ(2u, client_entries.size());
   EXPECT_TRUE(
       LogContainsBeginEvent(client_entries, 0, NetLogEventType::SOCKET_ALIVE));
@@ -428,7 +430,7 @@
   EXPECT_CALL(socket_, InternalWatchFileDescriptor()).WillOnce(Return(true));
   socket_.DidSendBuffers(std::move(send_result));
   EXPECT_EQ(2u, socket_.GetUnwrittenBuffers().size());
-  auto client_entries = client_log_.GetEntries();
+  auto client_entries = net_log_observer_.GetEntries();
   EXPECT_EQ(2u, client_entries.size());
   EXPECT_TRUE(
       LogContainsBeginEvent(client_entries, 0, NetLogEventType::SOCKET_ALIVE));
@@ -448,7 +450,7 @@
       .WillOnce(InvokeWithoutArgs(WatcherSetInvalidHandle));
   socket_.DidSendBuffers(std::move(send_result));
   EXPECT_EQ(2u, socket_.GetUnwrittenBuffers().size());
-  auto client_entries = client_log_.GetEntries();
+  auto client_entries = net_log_observer_.GetEntries();
   EXPECT_EQ(3u, client_entries.size());
   EXPECT_TRUE(
       LogContainsBeginEvent(client_entries, 0, NetLogEventType::SOCKET_ALIVE));
@@ -471,7 +473,7 @@
   socket_.DidSendBuffers(std::move(send_result));
   buffers_ = socket_.GetUnwrittenBuffers();
   EXPECT_EQ(2u, buffers_.size());
-  auto client_entries = client_log_.GetEntries();
+  auto client_entries = net_log_observer_.GetEntries();
   EXPECT_EQ(2u, client_entries.size());
   EXPECT_TRUE(
       LogContainsBeginEvent(client_entries, 0, NetLogEventType::SOCKET_ALIVE));
@@ -489,7 +491,7 @@
   socket_.DidSendBuffers(std::move(send_result2));
 
   EXPECT_EQ(0u, socket_.GetUnwrittenBuffers().size());
-  client_entries = client_log_.GetEntries();
+  client_entries = net_log_observer_.GetEntries();
   EXPECT_EQ(4u, client_entries.size());
   EXPECT_TRUE(
       LogContainsBeginEvent(client_entries, 0, NetLogEventType::SOCKET_ALIVE));
@@ -515,7 +517,7 @@
   socket_.DidSendBuffers(std::move(send_result));
   buffers_ = socket_.GetUnwrittenBuffers();
   EXPECT_EQ(2u, buffers_.size());
-  auto client_entries = client_log_.GetEntries();
+  auto client_entries = net_log_observer_.GetEntries();
   EXPECT_EQ(2u, client_entries.size());
   EXPECT_TRUE(
       LogContainsBeginEvent(client_entries, 0, NetLogEventType::SOCKET_ALIVE));
@@ -533,7 +535,7 @@
   socket_.DidSendBuffers(std::move(send_result2));
 
   EXPECT_EQ(2u, socket_.GetUnwrittenBuffers().size());
-  client_entries = client_log_.GetEntries();
+  client_entries = net_log_observer_.GetEntries();
   EXPECT_EQ(2u, client_entries.size());
   EXPECT_TRUE(
       LogContainsBeginEvent(client_entries, 0, NetLogEventType::SOCKET_ALIVE));
@@ -552,7 +554,7 @@
   ResetWriteCallback();
   socket_.SetWriteCallback(write_callback_);
   socket_.DidSendBuffers(std::move(send_result));
-  auto client_entries = client_log_.GetEntries();
+  auto client_entries = net_log_observer_.GetEntries();
   EXPECT_EQ(3u, client_entries.size());
   EXPECT_TRUE(
       LogContainsBeginEvent(client_entries, 0, NetLogEventType::SOCKET_ALIVE));
diff --git a/net/socket/udp_socket_unittest.cc b/net/socket/udp_socket_unittest.cc
index 6ce015f..5350e8396 100644
--- a/net/socket/udp_socket_unittest.cc
+++ b/net/socket/udp_socket_unittest.cc
@@ -166,12 +166,11 @@
 
 void UDPSocketTest::ConnectTest(bool use_nonblocking_io) {
   std::string simple_message("hello world!");
-
+  RecordingNetLogObserver net_log_observer;
   // Setup the server to listen.
   IPEndPoint server_address(IPAddress::IPv4Localhost(), 0 /* port */);
-  RecordingTestNetLog server_log;
   std::unique_ptr<UDPServerSocket> server(
-      new UDPServerSocket(&server_log, NetLogSource()));
+      new UDPServerSocket(NetLog::Get(), NetLogSource()));
   if (use_nonblocking_io)
     server->UseNonBlockingIO();
   server->AllowAddressReuse();
@@ -180,9 +179,8 @@
   ASSERT_THAT(server->GetLocalAddress(&server_address), IsOk());
 
   // Setup the client.
-  RecordingTestNetLog client_log;
-  auto client = std::make_unique<UDPClientSocket>(DatagramSocket::DEFAULT_BIND,
-                                                  &client_log, NetLogSource());
+  auto client = std::make_unique<UDPClientSocket>(
+      DatagramSocket::DEFAULT_BIND, NetLog::Get(), NetLogSource());
   if (use_nonblocking_io)
     client->UseNonBlockingIO();
 
@@ -221,12 +219,16 @@
   EXPECT_EQ(simple_message.length(), static_cast<size_t>(read_result));
   EXPECT_EQ(simple_message, std::string(buffer_->data(), read_result));
 
+  NetLogSource server_net_log_source = server->NetLog().source();
+  NetLogSource client_net_log_source = client->NetLog().source();
+
   // Delete sockets so they log their final events.
   server.reset();
   client.reset();
 
   // Check the server's log.
-  auto server_entries = server_log.GetEntries();
+  auto server_entries =
+      net_log_observer.GetEntriesForSource(server_net_log_source);
   ASSERT_EQ(6u, server_entries.size());
   EXPECT_TRUE(
       LogContainsBeginEvent(server_entries, 0, NetLogEventType::SOCKET_ALIVE));
@@ -246,7 +248,8 @@
       LogContainsEndEvent(server_entries, 5, NetLogEventType::SOCKET_ALIVE));
 
   // Check the client's log.
-  auto client_entries = client_log.GetEntries();
+  auto client_entries =
+      net_log_observer.GetEntriesForSource(client_net_log_source);
   EXPECT_EQ(7u, client_entries.size());
   EXPECT_TRUE(
       LogContainsBeginEvent(client_entries, 0, NetLogEventType::SOCKET_ALIVE));
@@ -332,11 +335,10 @@
   IPEndPoint listen_address;
   ASSERT_TRUE(CreateUDPAddress("0.0.0.0", 0 /* port */, &listen_address));
 
-  RecordingTestNetLog server1_log, server2_log;
   std::unique_ptr<UDPServerSocket> server1(
-      new UDPServerSocket(&server1_log, NetLogSource()));
+      new UDPServerSocket(NetLog::Get(), NetLogSource()));
   std::unique_ptr<UDPServerSocket> server2(
-      new UDPServerSocket(&server2_log, NetLogSource()));
+      new UDPServerSocket(NetLog::Get(), NetLogSource()));
   server1->AllowAddressReuse();
   server1->AllowBroadcast();
   server2->AllowAddressReuse();
diff --git a/net/socket/websocket_transport_client_socket_pool_unittest.cc b/net/socket/websocket_transport_client_socket_pool_unittest.cc
index 707eee65..0c4204c 100644
--- a/net/socket/websocket_transport_client_socket_pool_unittest.cc
+++ b/net/socket/websocket_transport_client_socket_pool_unittest.cc
@@ -29,7 +29,7 @@
 #include "net/base/test_completion_callback.h"
 #include "net/dns/mock_host_resolver.h"
 #include "net/dns/public/secure_dns_policy.h"
-#include "net/log/test_net_log.h"
+#include "net/log/net_log.h"
 #include "net/socket/client_socket_handle.h"
 #include "net/socket/connect_job.h"
 #include "net/socket/connect_job_test_util.h"
@@ -79,7 +79,7 @@
                   SecureDnsPolicy::kAllow),
         params_(ClientSocketPool::SocketParams::CreateForHttpForTesting()),
         host_resolver_(new MockHostResolver),
-        client_socket_factory_(&net_log_),
+        client_socket_factory_(NetLog::Get()),
         common_connect_job_params_(
             &client_socket_factory_,
             host_resolver_.get(),
@@ -143,7 +143,6 @@
   }
   size_t completion_count() const { return test_base_.completion_count(); }
 
-  RecordingTestNetLog net_log_;
   // |group_id_| and |params_| correspond to the same socket parameters.
   const ClientSocketPool::GroupId group_id_;
   scoped_refptr<ClientSocketPool::SocketParams> params_;
diff --git a/net/spdy/spdy_http_stream_unittest.cc b/net/spdy/spdy_http_stream_unittest.cc
index 0248de38..4ce6a2b 100644
--- a/net/spdy/spdy_http_stream_unittest.cc
+++ b/net/spdy/spdy_http_stream_unittest.cc
@@ -25,7 +25,6 @@
 #include "net/http/http_response_headers.h"
 #include "net/http/http_response_info.h"
 #include "net/log/net_log_with_source.h"
-#include "net/log/test_net_log.h"
 #include "net/socket/socket_tag.h"
 #include "net/socket/socket_test_util.h"
 #include "net/spdy/spdy_http_utils.h"
@@ -139,7 +138,7 @@
              NetworkIsolationKey(),
              SecureDnsPolicy::kAllow),
         ssl_(SYNCHRONOUS, OK) {
-    session_deps_.net_log = &net_log_;
+    session_deps_.net_log = NetLog::Get();
   }
 
   ~SpdyHttpStreamTest() override = default;
@@ -167,7 +166,6 @@
   }
 
   SpdyTestUtil spdy_util_;
-  RecordingTestNetLog net_log_;
   SpdySessionDependencies session_deps_;
   const GURL url_;
   const HostPortPair host_port_pair_;
diff --git a/net/test/embedded_test_server/embedded_test_server_unittest.cc b/net/test/embedded_test_server/embedded_test_server_unittest.cc
index 3ddc3500..ac39163 100644
--- a/net/test/embedded_test_server/embedded_test_server_unittest.cc
+++ b/net/test/embedded_test_server/embedded_test_server_unittest.cc
@@ -24,7 +24,6 @@
 #include "net/base/test_completion_callback.h"
 #include "net/http/http_response_headers.h"
 #include "net/log/net_log_source.h"
-#include "net/log/test_net_log.h"
 #include "net/socket/client_socket_factory.h"
 #include "net/socket/stream_socket.h"
 #include "net/test/embedded_test_server/embedded_test_server_connection_listener.h"
@@ -314,13 +313,12 @@
 TEST_P(EmbeddedTestServerTest, ConnectionListenerAccept) {
   ASSERT_TRUE(server_->Start());
 
-  RecordingTestNetLog net_log;
   net::AddressList address_list;
   EXPECT_TRUE(server_->GetAddressList(&address_list));
 
   std::unique_ptr<StreamSocket> socket =
       ClientSocketFactory::GetDefaultFactory()->CreateTransportClientSocket(
-          address_list, nullptr, nullptr, &net_log, NetLogSource());
+          address_list, nullptr, nullptr, NetLog::Get(), NetLogSource());
   TestCompletionCallback callback;
   ASSERT_THAT(callback.GetResult(socket->Connect(callback.callback())), IsOk());
 
diff --git a/net/url_request/url_request_http_job_unittest.cc b/net/url_request/url_request_http_job_unittest.cc
index 0113bd20..70417b6 100644
--- a/net/url_request/url_request_http_job_unittest.cc
+++ b/net/url_request/url_request_http_job_unittest.cc
@@ -345,7 +345,7 @@
  protected:
   URLRequestHttpJobTest() : context_(true) {
     context_.set_http_transaction_factory(&network_layer_);
-    context_.set_net_log(&net_log_);
+    context_.set_net_log(NetLog::Get());
     context_.Init();
 
     req_ =
@@ -357,7 +357,7 @@
 
   TestURLRequestContext context_;
   TestDelegate delegate_;
-  RecordingTestNetLog net_log_;
+  RecordingNetLogObserver net_log_observer_;
   std::unique_ptr<URLRequest> req_;
 };
 
@@ -1280,12 +1280,12 @@
         url, DEFAULT_PRIORITY, &d, TRAFFIC_ANNOTATION_FOR_TESTS,
         is_for_websockets));
 
-    net_log_.Clear();
+    net_log_observer_.Clear();
     r->Start();
     d.RunUntilComplete();
 
     if (test.upgrade_expected) {
-      auto entries = net_log_.GetEntriesWithType(
+      auto entries = net_log_observer_.GetEntriesWithType(
           net::NetLogEventType::URL_REQUEST_REDIRECT_JOB);
       int redirects = entries.size();
       for (const auto& entry : entries) {
diff --git a/net/url_request/url_request_quic_unittest.cc b/net/url_request/url_request_quic_unittest.cc
index 46f75a4..c69ca3ca 100644
--- a/net/url_request/url_request_quic_unittest.cc
+++ b/net/url_request/url_request_quic_unittest.cc
@@ -26,7 +26,6 @@
 #include "net/dns/mock_host_resolver.h"
 #include "net/http/transport_security_state.h"
 #include "net/log/net_log_event_type.h"
-#include "net/log/test_net_log.h"
 #include "net/log/test_net_log_util.h"
 #include "net/quic/crypto/proof_source_chromium.h"
 #include "net/quic/quic_context.h"
@@ -143,7 +142,7 @@
     context_->set_host_resolver(host_resolver_.get());
     context_->set_http_network_session_params(std::move(params));
     context_->set_cert_verifier(&cert_verifier_);
-    context_->set_net_log(&net_log_);
+    context_->set_net_log(NetLog::Get());
     transport_security_state_.SetExpectCTReporter(&expect_ct_reporter_);
     context_->set_transport_security_state(&transport_security_state_);
   }
@@ -225,8 +224,6 @@
            std::string(path);
   }
 
-  RecordingTestNetLog net_log_;
-
  private:
   void StartQuicServer(quic::ParsedQuicVersion version) {
     // Set up in-memory cache.
diff --git a/net/url_request/url_request_unittest.cc b/net/url_request/url_request_unittest.cc
index 72b600a..01f32f7a 100644
--- a/net/url_request/url_request_unittest.cc
+++ b/net/url_request/url_request_unittest.cc
@@ -15,6 +15,7 @@
 #include "base/memory/ptr_util.h"
 #include "build/build_config.h"
 #include "net/dns/public/secure_dns_policy.h"
+#include "net/log/net_log.h"
 #include "url/url_constants.h"
 
 #if defined(OS_WIN)
@@ -672,7 +673,7 @@
       : job_factory_(std::make_unique<URLRequestJobFactory>()),
         default_context_(std::make_unique<TestURLRequestContext>(true)) {
     default_context_->set_network_delegate(&default_network_delegate_);
-    default_context_->set_net_log(&net_log_);
+    default_context_->set_net_log(NetLog::Get());
   }
 
   ~URLRequestTest() override {
@@ -716,7 +717,7 @@
   }
 
  protected:
-  RecordingTestNetLog net_log_;
+  RecordingNetLogObserver net_log_observer_;
   TestNetworkDelegate default_network_delegate_;  // Must outlive URLRequest.
   std::unique_ptr<URLRequestJobFactory> job_factory_;
   std::unique_ptr<TestURLRequestContext> default_context_;
@@ -2092,7 +2093,7 @@
 
     EXPECT_EQ(0, network_delegate.blocked_annotate_cookies_count());
     EXPECT_EQ(0, network_delegate.blocked_set_cookie_count());
-    auto entries = net_log_.GetEntries();
+    auto entries = net_log_observer_.GetEntries();
     for (const auto& entry : entries) {
       EXPECT_NE(entry.type,
                 NetLogEventType::COOKIE_GET_BLOCKED_BY_NETWORK_DELEGATE);
@@ -2116,7 +2117,7 @@
 
     EXPECT_EQ(1, network_delegate.blocked_annotate_cookies_count());
     EXPECT_EQ(0, network_delegate.blocked_set_cookie_count());
-    auto entries = net_log_.GetEntries();
+    auto entries = net_log_observer_.GetEntries();
     ExpectLogContainsSomewhereAfter(
         entries, 0, NetLogEventType::COOKIE_GET_BLOCKED_BY_NETWORK_DELEGATE,
         NetLogEventPhase::NONE);
@@ -2146,7 +2147,7 @@
 
     EXPECT_EQ(0, network_delegate.blocked_annotate_cookies_count());
     EXPECT_EQ(0, network_delegate.blocked_set_cookie_count());
-    auto entries = net_log_.GetEntries();
+    auto entries = net_log_observer_.GetEntries();
     for (const auto& entry : entries) {
       EXPECT_NE(entry.type,
                 NetLogEventType::COOKIE_SET_BLOCKED_BY_NETWORK_DELEGATE);
@@ -2168,7 +2169,7 @@
 
     EXPECT_EQ(0, network_delegate.blocked_annotate_cookies_count());
     EXPECT_EQ(2, network_delegate.blocked_set_cookie_count());
-    auto entries = net_log_.GetEntries();
+    auto entries = net_log_observer_.GetEntries();
     ExpectLogContainsSomewhereAfter(
         entries, 0, NetLogEventType::COOKIE_SET_BLOCKED_BY_NETWORK_DELEGATE,
         NetLogEventPhase::NONE);
@@ -5170,7 +5171,7 @@
   TestDelegate request_delegate;
   TestURLRequestContext context(true);
   context.set_network_delegate(nullptr);
-  context.set_net_log(&net_log_);
+  context.set_net_log(NetLog::Get());
   context.Init();
 
   {
@@ -5192,7 +5193,7 @@
     EXPECT_EQ(OK, request_delegate.request_status());
   }
 
-  auto entries = net_log_.GetEntries();
+  auto entries = net_log_observer_.GetEntries();
   size_t log_position = ExpectLogContainsSomewhereAfter(
       entries, 0, NetLogEventType::DELEGATE_INFO, NetLogEventPhase::BEGIN);
 
@@ -5211,7 +5212,7 @@
   AsyncLoggingNetworkDelegate network_delegate;
   TestURLRequestContext context(true);
   context.set_network_delegate(&network_delegate);
-  context.set_net_log(&net_log_);
+  context.set_net_log(NetLog::Get());
   context.Init();
 
   {
@@ -5233,7 +5234,7 @@
   EXPECT_EQ(1, network_delegate.destroyed_requests());
 
   size_t log_position = 0;
-  auto entries = net_log_.GetEntries();
+  auto entries = net_log_observer_.GetEntries();
   static const NetLogEventType kExpectedEvents[] = {
       NetLogEventType::NETWORK_DELEGATE_BEFORE_URL_REQUEST,
       NetLogEventType::NETWORK_DELEGATE_BEFORE_START_TRANSACTION,
@@ -5265,7 +5266,7 @@
   AsyncLoggingNetworkDelegate network_delegate;
   TestURLRequestContext context(true);
   context.set_network_delegate(&network_delegate);
-  context.set_net_log(&net_log_);
+  context.set_net_log(NetLog::Get());
   context.Init();
 
   {
@@ -5287,7 +5288,7 @@
   EXPECT_EQ(1, network_delegate.destroyed_requests());
 
   size_t log_position = 0;
-  auto entries = net_log_.GetEntries();
+  auto entries = net_log_observer_.GetEntries();
   static const NetLogEventType kExpectedEvents[] = {
       NetLogEventType::NETWORK_DELEGATE_BEFORE_URL_REQUEST,
       NetLogEventType::NETWORK_DELEGATE_BEFORE_START_TRANSACTION,
@@ -5338,7 +5339,7 @@
       AsyncLoggingUrlRequestDelegate::NO_CANCEL);
   TestURLRequestContext context(true);
   context.set_network_delegate(nullptr);
-  context.set_net_log(&net_log_);
+  context.set_net_log(NetLog::Get());
   context.Init();
 
   {
@@ -5358,7 +5359,7 @@
     EXPECT_EQ(OK, request_delegate.request_status());
   }
 
-  auto entries = net_log_.GetEntries();
+  auto entries = net_log_observer_.GetEntries();
 
   size_t log_position = 0;
 
@@ -5395,7 +5396,7 @@
       AsyncLoggingUrlRequestDelegate::NO_CANCEL);
   TestURLRequestContext context(true);
   context.set_network_delegate(nullptr);
-  context.set_net_log(&net_log_);
+  context.set_net_log(NetLog::Get());
   context.Init();
 
   {
@@ -5410,7 +5411,7 @@
     EXPECT_EQ(OK, request_delegate.request_status());
   }
 
-  auto entries = net_log_.GetEntries();
+  auto entries = net_log_observer_.GetEntries();
 
   // Delegate info should only have been logged in OnReceivedRedirect and
   // OnResponseStarted.
@@ -5449,10 +5450,10 @@
 
   for (auto cancel_stage : kCancelStages) {
     AsyncLoggingUrlRequestDelegate request_delegate(cancel_stage);
-    RecordingTestNetLog net_log;
+    RecordingNetLogObserver net_log_observer;
     TestURLRequestContext context(true);
     context.set_network_delegate(nullptr);
-    context.set_net_log(&net_log);
+    context.set_net_log(NetLog::Get());
     context.Init();
 
     {
@@ -5469,7 +5470,7 @@
       base::RunLoop().RunUntilIdle();
     }
 
-    auto entries = net_log.GetEntries();
+    auto entries = net_log_observer.GetEntries();
 
     // Delegate info is always logged in both OnReceivedRedirect and
     // OnResponseStarted.  In the CANCEL_ON_RECEIVED_REDIRECT, the
@@ -7717,10 +7718,10 @@
   FilteringTestNetworkDelegate network_delegate;
   network_delegate.SetCookieFilter("not_stored_cookie");
   network_delegate.set_block_annotate_cookies();
-  RecordingTestNetLog net_log;
+  RecordingNetLogObserver net_log_observer;
   TestURLRequestContext context(true);
   context.set_network_delegate(&network_delegate);
-  context.set_net_log(&net_log);
+  context.set_net_log(net::NetLog::Get());
   context.Init();
   // Make sure cookies blocked from being stored are caught, and those that are
   // accepted are reported as well.
@@ -7749,8 +7750,8 @@
     EXPECT_TRUE(
         req->maybe_stored_cookies()[2].access_result.status.IsInclude());
     EXPECT_EQ("path_cookie", req->maybe_stored_cookies()[2].cookie->Name());
-    auto entries =
-        net_log.GetEntriesWithType(NetLogEventType::COOKIE_INCLUSION_STATUS);
+    auto entries = net_log_observer.GetEntriesWithType(
+        NetLogEventType::COOKIE_INCLUSION_STATUS);
     EXPECT_EQ(3u, entries.size());
     EXPECT_EQ("{\"domain\":\"" + set_cookie_test_url.host() +
                   "\",\"name\":\"not_stored_cookie\",\"operation\":\"store\","
@@ -7766,7 +7767,7 @@
             "\",\"name\":\"path_cookie\",\"operation\":\"store\","
             "\"path\":\"/set-cookie\",\"status\":\"INCLUDE, DO_NOT_WARN\"}",
         SerializeNetLogValueToJson(entries[2].params));
-    net_log.Clear();
+    net_log_observer.Clear();
   }
   {
     TestDelegate d;
@@ -7792,8 +7793,8 @@
         req->maybe_sent_cookies()[1]
             .access_result.status.HasExactlyExclusionReasonsForTesting(
                 {net::CookieInclusionStatus::EXCLUDE_USER_PREFERENCES}));
-    auto entries =
-        net_log.GetEntriesWithType(NetLogEventType::COOKIE_INCLUSION_STATUS);
+    auto entries = net_log_observer.GetEntriesWithType(
+        NetLogEventType::COOKIE_INCLUSION_STATUS);
     EXPECT_EQ(2u, entries.size());
     EXPECT_EQ("{\"domain\":\"" + set_cookie_test_url.host() +
                   "\",\"name\":\"path_cookie\",\"operation\":\"send\",\"path\":"
@@ -7805,13 +7806,13 @@
             "\",\"name\":\"stored_cookie\",\"operation\":\"send\",\"path\":\"/"
             "\",\"status\":\"EXCLUDE_USER_PREFERENCES, DO_NOT_WARN\"}",
         SerializeNetLogValueToJson(entries[1].params));
-    net_log.Clear();
+    net_log_observer.Clear();
   }
   {
     TestDelegate d;
     // Ensure that the log does not contain cookie names when not set to collect
     // sensitive data.
-    net_log.SetObserverCaptureMode(NetLogCaptureMode::kDefault);
+    net_log_observer.SetObserverCaptureMode(NetLogCaptureMode::kDefault);
 
     GURL test_url = test_server.GetURL("/echoheader?Cookie");
     std::unique_ptr<URLRequest> req(context.CreateFirstPartyRequest(
@@ -7819,8 +7820,8 @@
     req->Start();
     d.RunUntilComplete();
 
-    auto entries =
-        net_log.GetEntriesWithType(NetLogEventType::COOKIE_INCLUSION_STATUS);
+    auto entries = net_log_observer.GetEntriesWithType(
+        NetLogEventType::COOKIE_INCLUSION_STATUS);
     EXPECT_EQ(2u, entries.size());
 
     // Ensure that the potentially-sensitive |name|, |domain|, and |path| fields
@@ -7834,8 +7835,9 @@
         "DO_NOT_WARN\"}",
         SerializeNetLogValueToJson(entries[1].params));
 
-    net_log.Clear();
-    net_log.SetObserverCaptureMode(NetLogCaptureMode::kIncludeSensitive);
+    net_log_observer.Clear();
+    net_log_observer.SetObserverCaptureMode(
+        NetLogCaptureMode::kIncludeSensitive);
   }
 
   network_delegate.unset_block_annotate_cookies();
@@ -7858,8 +7860,8 @@
                         {net::CookieInclusionStatus::EXCLUDE_NOT_ON_PATH}));
     EXPECT_EQ("stored_cookie", req->maybe_sent_cookies()[1].cookie.Name());
     EXPECT_TRUE(req->maybe_sent_cookies()[1].access_result.status.IsInclude());
-    auto entries =
-        net_log.GetEntriesWithType(NetLogEventType::COOKIE_INCLUSION_STATUS);
+    auto entries = net_log_observer.GetEntriesWithType(
+        NetLogEventType::COOKIE_INCLUSION_STATUS);
     EXPECT_EQ(2u, entries.size());
     EXPECT_EQ(
         "{\"domain\":\"" + set_cookie_test_url.host() +
@@ -7870,7 +7872,7 @@
                   "\",\"name\":\"stored_cookie\",\"operation\":\"send\","
                   "\"path\":\"/\",\"status\":\"INCLUDE, DO_NOT_WARN\"}",
               SerializeNetLogValueToJson(entries[1].params));
-    net_log.Clear();
+    net_log_observer.Clear();
   }
 }
 
diff --git a/pdf/pdfium/pdfium_form_filler.cc b/pdf/pdfium/pdfium_form_filler.cc
index 829962e..229cdf8 100644
--- a/pdf/pdfium/pdfium_form_filler.cc
+++ b/pdf/pdfium/pdfium_form_filler.cc
@@ -30,9 +30,11 @@
 
 int g_last_timer_id = 0;
 
+#if defined(PDF_ENABLE_V8)
 std::string WideStringToString(FPDF_WIDESTRING wide_string) {
   return base::UTF16ToUTF8(reinterpret_cast<const char16_t*>(wide_string));
 }
+#endif
 
 }  // namespace
 
diff --git a/pdf/pdfium/pdfium_form_filler_unittest.cc b/pdf/pdfium/pdfium_form_filler_unittest.cc
index 24b6e9dc..c8f0df3 100644
--- a/pdf/pdfium/pdfium_form_filler_unittest.cc
+++ b/pdf/pdfium/pdfium_form_filler_unittest.cc
@@ -4,6 +4,9 @@
 
 #include "pdf/pdfium/pdfium_form_filler.h"
 
+#include <vector>
+
+#include "base/cxx17_backports.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "build/build_config.h"
 #include "gin/public/isolate_holder.h"
@@ -23,7 +26,10 @@
 
 namespace {
 
+using ::testing::Contains;
 using ::testing::InSequence;
+using ::testing::Not;
+using ::testing::Return;
 
 class FormFillerTestClient : public TestClient {
  public:
@@ -34,6 +40,7 @@
 
   // Mock PDFEngine::Client methods.
   MOCK_METHOD(void, Beep, (), (override));
+  MOCK_METHOD(std::string, GetURL, (), (override));
   MOCK_METHOD(void, ScrollToX, (int), (override));
   MOCK_METHOD(void, ScrollToY, (int), (override));
   MOCK_METHOD(void,
@@ -67,11 +74,18 @@
         &engine->form_filler_, uri, modifiers);
   }
 
+#if defined(PDF_ENABLE_V8)
   void TriggerBeep(PDFiumEngine* engine) {
     ASSERT_TRUE(engine);
     engine->form_filler_.Form_Beep(&engine->form_filler_,
                                    JSPLATFORM_BEEP_DEFAULT);
   }
+
+  int TriggerGetFilePath(PDFiumEngine& engine, void* file_path, int length) {
+    return engine.form_filler_.Form_GetFilePath(&engine.form_filler_, file_path,
+                                                length);
+  }
+#endif  // defined(PDF_ENABLE_V8)
 };
 
 TEST_F(FormFillerTest, DoURIActionWithKeyboardModifier) {
@@ -175,17 +189,18 @@
   }
 }
 
-class FormFillerIsolateTest : public FormFillerTest {
+#if defined(PDF_ENABLE_V8)
+class FormFillerJavaScriptTest : public FormFillerTest {
  public:
-  FormFillerIsolateTest() {
+  FormFillerJavaScriptTest() {
     // Needed for setting up V8.
     InitializeSDK(/*enable_v8=*/true, FontMappingMode::kNoMapping);
   }
 
-  ~FormFillerIsolateTest() override { ShutdownSDK(); }
+  ~FormFillerJavaScriptTest() override { ShutdownSDK(); }
 };
 
-TEST_F(FormFillerIsolateTest, IsolateScoping) {
+TEST_F(FormFillerJavaScriptTest, IsolateScoping) {
   // Enter the embedder's isolate so it can be captured when the
   // `PDFiumFormFiller` is created.
   v8::Isolate* embedder_isolate = blink::MainThreadIsolate();
@@ -211,4 +226,60 @@
   EXPECT_EQ(v8::Isolate::TryGetCurrent(), pdfium_test_isolate);
 }
 
+TEST_F(FormFillerJavaScriptTest, GetFilePath) {
+  constexpr char kTestPath[] = "https://www.example.com/path/to/the.pdf";
+  constexpr int kTestPathSize = static_cast<int>(base::size(kTestPath));
+
+  FormFillerTestClient client;
+  EXPECT_CALL(client, GetURL).Times(2).WillRepeatedly(Return(kTestPath));
+  PDFiumEngine engine(&client, PDFiumFormFiller::ScriptOption::kJavaScript);
+
+  // TODO(dhoss): The return value should be `kTestPathSize`.
+  EXPECT_EQ(TriggerGetFilePath(engine, /*file_path=*/nullptr, /*length=*/0),
+            kTestPathSize - 1);
+
+  std::vector<char> buffer(kTestPathSize, 'X');
+  EXPECT_EQ(TriggerGetFilePath(engine, buffer.data(), buffer.size()),
+            kTestPathSize - 1);
+
+  // TODO(dhoss): `buffer.data()` should be null terminated.
+  EXPECT_STRNE(buffer.data(), kTestPath);
+}
+
+TEST_F(FormFillerJavaScriptTest, GetFilePathEmpty) {
+  FormFillerTestClient client;
+  EXPECT_CALL(client, GetURL).Times(2).WillRepeatedly(Return(std::string()));
+  PDFiumEngine engine(&client, PDFiumFormFiller::ScriptOption::kJavaScript);
+
+  // TODO(dhoss): The return value should be 1.
+  EXPECT_EQ(TriggerGetFilePath(engine, /*file_path=*/nullptr, /*length=*/0), 0);
+
+  char buffer[] = "buffer";
+  EXPECT_EQ(TriggerGetFilePath(engine, buffer, /*length=*/1), 0);
+
+  // TODO(dhoss): `buffer` should be "" (i.e., its first character should be a
+  // null terminator).
+  EXPECT_STRNE(buffer, "");
+}
+
+TEST_F(FormFillerJavaScriptTest, GetFilePathShortBuffer) {
+  constexpr char kTestPath[] = "https://www.example.com/path/to/the.pdf";
+  constexpr int kTestPathSize = static_cast<int>(base::size(kTestPath));
+
+  FormFillerTestClient client;
+  EXPECT_CALL(client, GetURL).WillRepeatedly(Return(kTestPath));
+  PDFiumEngine engine(&client, PDFiumFormFiller::ScriptOption::kJavaScript);
+
+  std::vector<char> buffer(kTestPathSize - 1, 'X');
+
+  // TODO(dhoss): The return value should be `kTestPathSize`.
+  EXPECT_EQ(TriggerGetFilePath(engine, buffer.data(), buffer.size()),
+            kTestPathSize - 1);
+
+  // TODO(dhoss): Nothing should be copied over. The buffer size is too small to
+  // contain a trailing null.
+  EXPECT_THAT(buffer, Not(Contains('X').Times(buffer.size())));
+}
+#endif  // defined(PDF_ENABLE_V8)
+
 }  // namespace chrome_pdf
diff --git a/services/device/generic_sensor/platform_sensor.cc b/services/device/generic_sensor/platform_sensor.cc
index d0ccc94..b0eea1e 100644
--- a/services/device/generic_sensor/platform_sensor.cc
+++ b/services/device/generic_sensor/platform_sensor.cc
@@ -9,6 +9,7 @@
 #include "base/bind.h"
 #include "base/callback.h"
 #include "base/check.h"
+#include "base/containers/cxx20_erase.h"
 #include "base/threading/sequenced_task_runner_handle.h"
 #include "services/device/generic_sensor/platform_sensor_provider.h"
 #include "services/device/generic_sensor/platform_sensor_util.h"
@@ -72,23 +73,16 @@
     return false;
 
   auto& config_list = client_entry->second;
-  auto config_entry = std::find(config_list.begin(), config_list.end(), config);
-  if (config_entry == config_list.end())
+  if (base::Erase(config_list, config) == 0)
     return false;
 
-  config_list.erase(config_entry);
-
   return UpdateSensorInternal(config_map_);
 }
 
 bool PlatformSensor::StopListening(Client* client) {
   DCHECK(client);
-  auto client_entry = config_map_.find(client);
-  if (client_entry == config_map_.end())
+  if (config_map_.erase(client) == 0)
     return false;
-
-  config_map_.erase(client_entry);
-
   return UpdateSensorInternal(config_map_);
 }
 
diff --git a/services/network/proxy_resolver_factory_mojo_unittest.cc b/services/network/proxy_resolver_factory_mojo_unittest.cc
index 6f900e1..9ba74aea 100644
--- a/services/network/proxy_resolver_factory_mojo_unittest.cc
+++ b/services/network/proxy_resolver_factory_mojo_unittest.cc
@@ -24,6 +24,7 @@
 #include "net/base/network_isolation_key.h"
 #include "net/base/test_completion_callback.h"
 #include "net/dns/mock_host_resolver.h"
+#include "net/log/net_log.h"
 #include "net/log/net_log_event_type.h"
 #include "net/log/net_log_with_source.h"
 #include "net/log/test_net_log.h"
@@ -517,7 +518,7 @@
             factory_remote.InitWithNewPipeAndPassReceiver());
     proxy_resolver_factory_mojo_ = std::make_unique<ProxyResolverFactoryMojo>(
         std::move(factory_remote), &host_resolver_, base::NullCallback(),
-        &net_log_);
+        net::NetLog::Get());
   }
 
   std::unique_ptr<Request> MakeRequest(
@@ -557,7 +558,7 @@
 
   base::test::TaskEnvironment task_environment_;
   net::HangingHostResolver host_resolver_;
-  net::RecordingTestNetLog net_log_;
+  net::RecordingNetLogObserver net_log_observer_;
   std::unique_ptr<MockMojoProxyResolverFactory> mock_proxy_resolver_factory_;
   std::unique_ptr<net::ProxyResolverFactory> proxy_resolver_factory_mojo_;
 
@@ -567,7 +568,7 @@
 
 TEST_F(ProxyResolverFactoryMojoTest, CreateProxyResolver) {
   CreateProxyResolver();
-  CheckCapturedNetLogEntries(kScriptData, net_log_.GetEntries());
+  CheckCapturedNetLogEntries(kScriptData, net_log_observer_.GetEntries());
 }
 
 TEST_F(ProxyResolverFactoryMojoTest, CreateProxyResolver_Empty) {
@@ -775,7 +776,7 @@
   mock_proxy_resolver_.AddGetProxyAction(GetProxyForUrlAction::ReturnServers(
       url, ProxyServersFromPacString("DIRECT")));
   CreateProxyResolver();
-  net_log_.Clear();
+  net_log_observer_.Clear();
 
   std::unique_ptr<Request> request(MakeRequest(GURL(kExampleUrl)));
   EXPECT_THAT(request->Resolve(), IsError(net::ERR_IO_PENDING));
@@ -783,7 +784,7 @@
 
   EXPECT_EQ("DIRECT", request->results().ToPacString());
 
-  CheckCapturedNetLogEntries(url.spec(), net_log_.GetEntries());
+  CheckCapturedNetLogEntries(url.spec(), net_log_observer_.GetEntries());
   CheckCapturedNetLogEntries(url.spec(), request->net_log().GetEntries());
 }
 
diff --git a/services/network/proxy_service_mojo_unittest.cc b/services/network/proxy_service_mojo_unittest.cc
index 25b3bc9..6da977b 100644
--- a/services/network/proxy_service_mojo_unittest.cc
+++ b/services/network/proxy_service_mojo_unittest.cc
@@ -20,6 +20,7 @@
 #include "net/base/network_isolation_key.h"
 #include "net/base/test_completion_callback.h"
 #include "net/dns/mock_host_resolver.h"
+#include "net/log/net_log.h"
 #include "net/log/net_log_event_type.h"
 #include "net/log/net_log_with_source.h"
 #include "net/log/test_net_log.h"
@@ -127,8 +128,8 @@
                     TRAFFIC_ANNOTATION_FOR_TESTS)),
             base::WrapUnique(fetcher_),
             std::make_unique<net::DoNothingDhcpPacFileFetcher>(),
-            &mock_host_resolver_, &net_log_, true /* pac_quick_check_enabled */,
-            &network_delegate_);
+            &mock_host_resolver_, net::NetLog::Get(),
+            true /* pac_quick_check_enabled */, &network_delegate_);
   }
 
   base::test::TaskEnvironment task_environment_;
@@ -137,7 +138,7 @@
   net::MockHostResolver mock_host_resolver_;
   // Owned by |proxy_resolution_service_|.
   net::MockPacFileFetcher* fetcher_;
-  net::RecordingTestNetLog net_log_;
+  net::RecordingNetLogObserver net_log_observer_;
   std::unique_ptr<net::ConfiguredProxyResolutionService>
       proxy_resolution_service_;
 };
@@ -209,7 +210,7 @@
   EXPECT_EQ(0u, mock_host_resolver_.num_resolve());
 
   CheckCapturedNetLogEntries(test_net_log.GetEntries());
-  CheckCapturedNetLogEntries(net_log_.GetEntries());
+  CheckCapturedNetLogEntries(net_log_observer_.GetEntries());
 }
 
 TEST_F(ProxyServiceMojoTest, ErrorOnInitialization) {
@@ -234,7 +235,7 @@
   EXPECT_EQ("DIRECT", info.ToPacString());
   EXPECT_EQ(0u, mock_host_resolver_.num_resolve());
 
-  CheckCapturedNetLogEntries(net_log_.GetEntries());
+  CheckCapturedNetLogEntries(net_log_observer_.GetEntries());
 }
 
 }  // namespace network
diff --git a/services/network/trust_tokens/test/trust_token_test_util.cc b/services/network/trust_tokens/test/trust_token_test_util.cc
index cb272ea..cff86a00 100644
--- a/services/network/trust_tokens/test/trust_token_test_util.cc
+++ b/services/network/trust_tokens/test/trust_token_test_util.cc
@@ -13,8 +13,9 @@
 namespace network {
 
 TestURLRequestMaker::TestURLRequestMaker() {
-  context_.set_net_log(&net_log_);
+  context_.set_net_log(net::NetLog::Get());
 }
+
 TestURLRequestMaker::~TestURLRequestMaker() = default;
 
 std::unique_ptr<net::URLRequest> TestURLRequestMaker::MakeURLRequest(
diff --git a/services/network/trust_tokens/test/trust_token_test_util.h b/services/network/trust_tokens/test/trust_token_test_util.h
index 727f9056..cb696c4 100644
--- a/services/network/trust_tokens/test/trust_token_test_util.h
+++ b/services/network/trust_tokens/test/trust_token_test_util.h
@@ -13,9 +13,6 @@
 #include "base/json/json_string_value_serializer.h"
 #include "base/strings/string_piece.h"
 #include "base/test/task_environment.h"
-#include "net/log/net_log.h"
-#include "net/log/test_net_log.h"
-#include "net/log/test_net_log_util.h"
 #include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
 #include "net/url_request/url_request_test_util.h"
 #include "services/network/public/mojom/trust_tokens.mojom.h"
@@ -36,8 +33,6 @@
   TestURLRequestMaker();
   virtual ~TestURLRequestMaker();
 
-  net::NetLog* net_log() const { return context_.net_log(); }
-
   TestURLRequestMaker(const TestURLRequestMaker&) = delete;
   TestURLRequestMaker& operator=(const TestURLRequestMaker&) = delete;
 
@@ -45,7 +40,6 @@
   std::unique_ptr<net::URLRequest> MakeURLRequest(base::StringPiece spec);
 
  protected:
-  net::RecordingTestNetLog net_log_;
   net::TestDelegate delegate_;
   net::TestURLRequestContext context_;
 };
diff --git a/services/network/trust_tokens/trust_token_request_helper_factory_unittest.cc b/services/network/trust_tokens/trust_token_request_helper_factory_unittest.cc
index e751480f..ea4868e 100644
--- a/services/network/trust_tokens/trust_token_request_helper_factory_unittest.cc
+++ b/services/network/trust_tokens/trust_token_request_helper_factory_unittest.cc
@@ -67,7 +67,6 @@
   const mojom::TrustTokenParams& suitable_signing_params() const {
     return *suitable_params_;
   }
-  const net::NetLog& net_log() const { return *maker_.net_log(); }
 
   std::unique_ptr<net::URLRequest> CreateSuitableRequest() {
     auto ret = maker_.MakeURLRequest("https://destination.example");
diff --git a/services/network/trust_tokens/trust_token_request_signing_helper_unittest.cc b/services/network/trust_tokens/trust_token_request_signing_helper_unittest.cc
index 7bb8197..762b116f 100644
--- a/services/network/trust_tokens/trust_token_request_signing_helper_unittest.cc
+++ b/services/network/trust_tokens/trust_token_request_signing_helper_unittest.cc
@@ -24,6 +24,7 @@
 #include "components/cbor/writer.h"
 #include "net/base/request_priority.h"
 #include "net/http/structured_headers.h"
+#include "net/log/net_log.h"
 #include "net/log/test_net_log.h"
 #include "net/log/test_net_log_util.h"
 #include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
@@ -693,11 +694,11 @@
 
   // FailingSigner will fail to sign the request, so we should see the operation
   // fail.
-  net::RecordingTestNetLog net_log;
+  net::RecordingNetLogObserver net_log_observer;
   TrustTokenRequestSigningHelper helper(
       store.get(), std::move(params), std::make_unique<FailingSigner>(),
       std::make_unique<TrustTokenRequestCanonicalizer>(),
-      net::NetLogWithSource::Make(&net_log,
+      net::NetLogWithSource::Make(net::NetLog::Get(),
                                   net::NetLogSourceType::URL_REQUEST));
 
   auto my_request = MakeURLRequest("https://destination.com/");
@@ -712,7 +713,7 @@
   EXPECT_THAT(*my_request, Not(Header("Sec-Signature")));
   EXPECT_THAT(*my_request, Header("Sec-Redemption-Record", IsEmpty()));
   EXPECT_TRUE(base::ranges::any_of(
-      net_log.GetEntriesWithType(
+      net_log_observer.GetEntriesWithType(
           net::NetLogEventType::TRUST_TOKEN_OPERATION_BEGIN_SIGNING),
       [](const net::NetLogEntry& entry) {
         absl::optional<std::string> key = net::GetOptionalStringValueFromParams(
diff --git a/services/network/url_loader.cc b/services/network/url_loader.cc
index 316b24c..ea4c59cb 100644
--- a/services/network/url_loader.cc
+++ b/services/network/url_loader.cc
@@ -961,6 +961,10 @@
     return;
   }
 
+  // Set seen_raw_request_headers_ to false in order to make sure this redirect
+  // also calls the devtools observer.
+  seen_raw_request_headers_ = false;
+
   // Removing headers can't make the set of pre-existing headers unsafe, but
   // adding headers can.
   if (!AreRequestHeadersSafe(modified_headers) ||
@@ -1135,7 +1139,7 @@
   auto response = network::mojom::URLResponseHead::New();
   PopulateResourceResponse(url_request_.get(), is_load_timing_enabled_,
                            options_, response.get());
-
+  DispatchOnRawResponse();
   ReportFlaggedResponseCookies();
 
   const CrossOriginEmbedderPolicy kEmpty;
@@ -1279,6 +1283,7 @@
   response_ = network::mojom::URLResponseHead::New();
   PopulateResourceResponse(url_request_.get(), is_load_timing_enabled_,
                            options_, response_.get());
+  DispatchOnRawResponse();
 
   // Parse and remove the Trust Tokens response headers, if any are expected,
   // potentially failing the request if an error occurs.
@@ -1945,7 +1950,10 @@
 void URLLoader::SetRawRequestHeadersAndNotify(
     net::HttpRawRequestHeaders headers) {
   auto* devtools_observer = GetDevToolsObserver();
-  if (devtools_observer && devtools_request_id()) {
+  // If we have seen_raw_request_headers_, then don't notify DevTools to prevent
+  // duplicate ExtraInfo events.
+  if (!seen_raw_request_headers_ && devtools_observer &&
+      devtools_request_id()) {
     std::vector<network::mojom::HttpRawHeaderPairPtr> header_array;
     header_array.reserve(headers.headers().size());
 
@@ -1956,21 +1964,7 @@
       pair->value = header.second;
       header_array.push_back(std::move(pair));
     }
-
-    mojom::ClientSecurityStatePtr client_security_state;
-    if (factory_params_->client_security_state) {
-      client_security_state = factory_params_->client_security_state->Clone();
-    } else if (request_client_security_state_) {
-      client_security_state = request_client_security_state_->Clone();
-    }
-
-    net::LoadTimingInfo load_timing_info;
-    url_request_->GetLoadTimingInfo(&load_timing_info);
-
-    devtools_observer->OnRawRequest(
-        devtools_request_id().value(), url_request_->maybe_sent_cookies(),
-        std::move(header_array), load_timing_info.request_start,
-        std::move(client_security_state));
+    DispatchOnRawRequest(std::move(header_array));
   }
 
   if (auto* cookie_observer = GetCookieAccessObserver()) {
@@ -1992,9 +1986,89 @@
           devtools_request_id()));
     }
   }
+}
 
-  if (devtools_request_id())
-    raw_request_headers_.Assign(std::move(headers));
+void URLLoader::DispatchOnRawRequest(
+    std::vector<network::mojom::HttpRawHeaderPairPtr> headers) {
+  auto* devtools_observer = GetDevToolsObserver();
+  DCHECK(devtools_observer && devtools_request_id());
+
+  seen_raw_request_headers_ = true;
+
+  mojom::ClientSecurityStatePtr client_security_state;
+  if (factory_params_->client_security_state) {
+    client_security_state = factory_params_->client_security_state->Clone();
+  } else if (request_client_security_state_) {
+    client_security_state = request_client_security_state_->Clone();
+  }
+
+  net::LoadTimingInfo load_timing_info;
+  url_request_->GetLoadTimingInfo(&load_timing_info);
+
+  devtools_observer->OnRawRequest(
+      devtools_request_id().value(), url_request_->maybe_sent_cookies(),
+      std::move(headers), load_timing_info.request_start,
+      std::move(client_security_state));
+}
+
+bool URLLoader::DispatchOnRawResponse() {
+  auto* devtools_observer = GetDevToolsObserver();
+  if (!devtools_observer || !devtools_request_id() ||
+      !url_request_->response_headers()) {
+    return false;
+  }
+
+  std::vector<network::mojom::HttpRawHeaderPairPtr> header_array;
+
+  // This is gated by enable_reporting_raw_headers_ to be backwards compatible
+  // with the old report_raw_headers behavior, where we wouldn't even send
+  // raw_response_headers_ to the trusted browser process based devtools
+  // instrumentation. This is observed in the case of HSTS redirects, where
+  // url_request_->response_headers has the HSTS redirect headers, like
+  // Non-Authoritative-Reason, but raw_response_headers_ has something else
+  // which doesn't include HSTS information. This is tested by
+  // DevToolsTest.TestRawHeadersWithRedirectAndHSTS.
+  // TODO(crbug.com/1234823): Remove enable_reporting_raw_headers_
+  const net::HttpResponseHeaders* response_headers =
+      raw_response_headers_ && enable_reporting_raw_headers_
+          ? raw_response_headers_.get()
+          : url_request_->response_headers();
+
+  size_t iterator = 0;
+  std::string name, value;
+  while (response_headers->EnumerateHeaderLines(&iterator, &name, &value)) {
+    network::mojom::HttpRawHeaderPairPtr pair =
+        network::mojom::HttpRawHeaderPair::New();
+    pair->key = name;
+    pair->value = value;
+    header_array.push_back(std::move(pair));
+  }
+
+  // Only send the "raw" header text when the headers were actually send in
+  // text form (i.e. not QUIC or SPDY)
+  absl::optional<std::string> raw_response_headers;
+
+  const net::HttpResponseInfo& response_info = url_request_->response_info();
+
+  if (!response_info.DidUseQuic() && !response_info.was_fetched_via_spdy) {
+    raw_response_headers =
+        absl::make_optional(net::HttpUtil::ConvertHeadersBackToHTTPResponse(
+            response_headers->raw_headers()));
+  }
+
+  if (!seen_raw_request_headers_) {
+    // If we send OnRawResponse(), make sure we send OnRawRequest() event if
+    // we haven't had the callback from net, to make the client life easier.
+    DispatchOnRawRequest({});
+  }
+
+  devtools_observer->OnRawResponse(
+      devtools_request_id().value(), url_request_->maybe_stored_cookies(),
+      std::move(header_array), raw_response_headers,
+      IPEndPointToIPAddressSpace(response_info.remote_endpoint),
+      response_headers->response_code());
+
+  return true;
 }
 
 void URLLoader::SendUploadProgress(const net::UploadProgress& progress) {
@@ -2159,54 +2233,6 @@
 }
 
 void URLLoader::ReportFlaggedResponseCookies() {
-  auto* devtools_observer = GetDevToolsObserver();
-  if (devtools_observer && devtools_request_id() &&
-      url_request_->response_headers()) {
-    std::vector<network::mojom::HttpRawHeaderPairPtr> header_array;
-
-    // This is gated by enable_reporting_raw_headers_ to be backwards compatible
-    // with the old report_raw_headers behavior, where we wouldn't even send
-    // raw_response_headers_ to the trusted browser process based devtools
-    // instrumentation. This is observed in the case of HSTS redirects, where
-    // url_request_->response_headers has the HSTS redirect headers, like
-    // Non-Authoritative-Reason, but raw_response_headers_ has something else
-    // which doesn't include HSTS information. This is tested by
-    // DevToolsTest.TestRawHeadersWithRedirectAndHSTS.
-    // TODO(crbug.com/1234823): Remove enable_reporting_raw_headers_
-    const net::HttpResponseHeaders* response_headers =
-        raw_response_headers_ && enable_reporting_raw_headers_
-            ? raw_response_headers_.get()
-            : url_request_->response_headers();
-
-    size_t iterator = 0;
-    std::string name, value;
-    while (response_headers->EnumerateHeaderLines(&iterator, &name, &value)) {
-      network::mojom::HttpRawHeaderPairPtr pair =
-          network::mojom::HttpRawHeaderPair::New();
-      pair->key = name;
-      pair->value = value;
-      header_array.push_back(std::move(pair));
-    }
-
-    // Only send the "raw" header text when the headers were actually send in
-    // text form (i.e. not QUIC or SPDY)
-    absl::optional<std::string> raw_response_headers;
-
-    const net::HttpResponseInfo& response_info = url_request_->response_info();
-
-    if (!response_info.DidUseQuic() && !response_info.was_fetched_via_spdy) {
-      raw_response_headers =
-          absl::make_optional(net::HttpUtil::ConvertHeadersBackToHTTPResponse(
-              response_headers->raw_headers()));
-    }
-
-    devtools_observer->OnRawResponse(
-        devtools_request_id().value(), url_request_->maybe_stored_cookies(),
-        std::move(header_array), raw_response_headers,
-        IPEndPointToIPAddressSpace(response_info.remote_endpoint),
-        response_headers->response_code());
-  }
-
   if (auto* cookie_observer = GetCookieAccessObserver()) {
     std::vector<mojom::CookieOrLineWithAccessResultPtr> reported_cookies;
     for (const auto& cookie_line_and_access_result :
diff --git a/services/network/url_loader.h b/services/network/url_loader.h
index 8cc5c3f..2da6bab0 100644
--- a/services/network/url_loader.h
+++ b/services/network/url_loader.h
@@ -24,7 +24,6 @@
 #include "mojo/public/cpp/system/simple_watcher.h"
 #include "net/base/load_states.h"
 #include "net/base/network_delegate.h"
-#include "net/http/http_raw_request_headers.h"
 #include "net/traffic_annotation/network_traffic_annotation.h"
 #include "net/url_request/url_request.h"
 #include "services/network/keepalive_statistics_recorder.h"
@@ -342,6 +341,9 @@
   void SetRawResponseHeaders(scoped_refptr<const net::HttpResponseHeaders>);
   void NotifyEarlyResponse(scoped_refptr<const net::HttpResponseHeaders>);
   void SetRawRequestHeadersAndNotify(net::HttpRawRequestHeaders);
+  void DispatchOnRawRequest(
+      std::vector<network::mojom::HttpRawHeaderPairPtr> headers);
+  bool DispatchOnRawResponse();
   void SendUploadProgress(const net::UploadProgress& progress);
   void OnUploadProgressACK();
   void OnSSLCertificateErrorResponse(const net::SSLInfo& ssl_info,
@@ -473,7 +475,7 @@
       resource_scheduler_request_handle_;
 
   bool enable_reporting_raw_headers_ = false;
-  net::HttpRawRequestHeaders raw_request_headers_;
+  bool seen_raw_request_headers_ = false;
   scoped_refptr<const net::HttpResponseHeaders> raw_response_headers_;
 
   std::unique_ptr<UploadProgressTracker> upload_progress_tracker_;
diff --git a/services/network/web_transport_unittest.cc b/services/network/web_transport_unittest.cc
index 4407ed87..efa20126 100644
--- a/services/network/web_transport_unittest.cc
+++ b/services/network/web_transport_unittest.cc
@@ -296,7 +296,7 @@
 
     network_context_.url_request_context()->set_cert_verifier(&cert_verifier_);
     network_context_.url_request_context()->set_host_resolver(&host_resolver_);
-    network_context_.url_request_context()->set_net_log(&net_log_);
+    network_context_.url_request_context()->set_net_log(net::NetLog::Get());
     auto* quic_context = network_context_.url_request_context()->quic_context();
     quic_context->params()->supported_versions.push_back(version_);
     quic_context->params()->origins_to_force_quic_on.insert(
@@ -342,7 +342,7 @@
   const url::Origin& origin() const { return origin_; }
   const NetworkContext& network_context() const { return network_context_; }
   NetworkContext& mutable_network_context() { return network_context_; }
-  net::RecordingTestNetLog& net_log() { return net_log_; }
+  net::RecordingNetLogObserver& net_log_observer() { return net_log_observer_; }
 
   void RunPendingTasks() {
     base::RunLoop run_loop;
@@ -361,7 +361,7 @@
 
   net::MockCertVerifier cert_verifier_;
   net::MockHostResolver host_resolver_;
-  net::RecordingTestNetLog net_log_;
+  net::RecordingNetLogObserver net_log_observer_;
 
   NetworkContext network_context_;
 
@@ -574,8 +574,9 @@
   EXPECT_TRUE(client.has_received_fin_for(incoming_stream_id));
   EXPECT_FALSE(client.has_seen_mojo_connection_error());
 
-  std::vector<net::NetLogEntry> resets_sent = net_log().GetEntriesWithType(
-      net::NetLogEventType::QUIC_SESSION_RST_STREAM_FRAME_SENT);
+  std::vector<net::NetLogEntry> resets_sent =
+      net_log_observer().GetEntriesWithType(
+          net::NetLogEventType::QUIC_SESSION_RST_STREAM_FRAME_SENT);
   EXPECT_EQ(0u, resets_sent.size());
 }
 
diff --git a/testing/buildbot/chromium.android.json b/testing/buildbot/chromium.android.json
index 0778bd5..a1fc6b5 100644
--- a/testing/buildbot/chromium.android.json
+++ b/testing/buildbot/chromium.android.json
@@ -32290,6 +32290,7 @@
           "angle_unittests",
           "-v"
         ],
+        "ci_only": true,
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -33536,6 +33537,7 @@
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices"
         ],
+        "ci_only": true,
         "merge": {
           "args": [
             "--bucket",
@@ -34839,6 +34841,7 @@
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices"
         ],
+        "ci_only": true,
         "merge": {
           "args": [
             "--bucket",
diff --git a/testing/buildbot/chromium.fyi.json b/testing/buildbot/chromium.fyi.json
index 22e26823..be3f445 100644
--- a/testing/buildbot/chromium.fyi.json
+++ b/testing/buildbot/chromium.fyi.json
@@ -508,6 +508,11 @@
       "chromium_builder_asan"
     ]
   },
+  "UBSan vptr Release (reclient shadow)": {
+    "additional_compile_targets": [
+      "chromium_builder_asan"
+    ]
+  },
   "VR Linux": {
     "additional_compile_targets": [
       "vr_common_perftests"
diff --git a/testing/buildbot/gn_isolate_map.pyl b/testing/buildbot/gn_isolate_map.pyl
index 4bc2b0bf..ea0c5c4 100644
--- a/testing/buildbot/gn_isolate_map.pyl
+++ b/testing/buildbot/gn_isolate_map.pyl
@@ -1449,8 +1449,7 @@
   "performance_web_engine_test_suite": {
     "args": [
       "../../content/test/gpu/run_telemetry_benchmark_fuchsia.py",
-      "--system-log-template",
-      "system_log",
+      "--per-test-logs-dir",
     ],
     "label": "//content/test:performance_web_engine_test_suite",
     "script": "//testing/scripts/run_performance_tests.py",
diff --git a/testing/buildbot/test_suite_exceptions.pyl b/testing/buildbot/test_suite_exceptions.pyl
index e65bb233..69e4815 100644
--- a/testing/buildbot/test_suite_exceptions.pyl
+++ b/testing/buildbot/test_suite_exceptions.pyl
@@ -129,6 +129,12 @@
       'linux-lacros-tester-fyi-rel',
       'linux-lacros-dbg-tests-fyi',
     ],
+    'modifications': {
+      # https://crbug.com/1248257
+      'android-lollipop-arm-rel': {
+        'ci_only': True,
+      },
+    },
   },
   'angle_white_box_tests': {
     'modifications': {
@@ -1655,6 +1661,10 @@
           '--test-launcher-filter-file=../../testing/buildbot/filters/android.emulator_11.gl_tests.filter',
         ],
       },
+      # https://crbug.com/1248257
+      'android-lollipop-arm-rel': {
+        'ci_only': True,
+      },
       'android-marshmallow-x86-rel': {
         'args': [
           '--test-launcher-filter-file=../../testing/buildbot/filters/android.emulator.gl_tests.filter',
@@ -3097,6 +3107,12 @@
     'remove_from': [
       'android-pie-x86-rel',
     ],
+    'modifications': {
+      # https://crbug.com/1248257
+      'android-lollipop-arm-rel': {
+        'ci_only': True,
+      },
+    },
   },
   'vr_pixeltests': {
     'remove_from': [
diff --git a/testing/buildbot/tryserver.chromium.android.json b/testing/buildbot/tryserver.chromium.android.json
index 20caee5..25ccea218d 100644
--- a/testing/buildbot/tryserver.chromium.android.json
+++ b/testing/buildbot/tryserver.chromium.android.json
@@ -148,50 +148,6 @@
         "test": "blink_unittests",
         "test_id_prefix": "ninja://third_party/blink/renderer/controller:blink_unittests/"
       }
-    ],
-    "isolated_scripts": [
-      {
-        "args": [
-          "--num-retries=3",
-          "--release",
-          "--android",
-          "--disable-breakpad",
-          "--additional-driver-flag=--use-gpu-in-tests"
-        ],
-        "isolate_name": "blink_web_tests",
-        "merge": {
-          "args": [
-            "--verbose"
-          ],
-          "script": "//third_party/blink/tools/merge_web_test_results.py"
-        },
-        "name": "blink_web_tests",
-        "resultdb": {
-          "enable": true
-        },
-        "results_handler": "layout tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "device_os": "LMY48M",
-              "device_os_type": "userdebug",
-              "device_type": "hammerhead",
-              "os": "Android"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
-          "shards": 12
-        },
-        "test_id_prefix": "ninja://:blink_web_tests/"
-      }
     ]
   }
 }
diff --git a/testing/buildbot/waterfalls.pyl b/testing/buildbot/waterfalls.pyl
index fa255027..5fbe4e5 100644
--- a/testing/buildbot/waterfalls.pyl
+++ b/testing/buildbot/waterfalls.pyl
@@ -2737,6 +2737,11 @@
           'chromium_builder_asan',
         ],
       },
+      'UBSan vptr Release (reclient shadow)': {
+        'additional_compile_targets': [
+          'chromium_builder_asan',
+        ],
+      },
       'VR Linux': {
         'mixins': [
           'has_native_resultdb_integration',
@@ -6698,7 +6703,6 @@
         ],
         'test_suites': {
           'gtest_tests': 'chromium_android_webkit_gtests',
-          'isolated_scripts': 'chromium_webkit_isolated_scripts',
         },
         'os_type': 'android',
       },
diff --git a/testing/scripts/run_performance_tests.py b/testing/scripts/run_performance_tests.py
index 5b85cb04..64d02f9 100755
--- a/testing/scripts/run_performance_tests.py
+++ b/testing/scripts/run_performance_tests.py
@@ -461,13 +461,12 @@
     return selection_args
 
   def _generate_syslog_args(self):
-    if self._options.system_log_template:
+    if self._options.per_test_logs_dir:
       isolated_out_dir = os.path.dirname(
           self._options.isolated_script_test_output)
-      return ['--system-log-file', os.path.join(
+      return ['--logs-dir', os.path.join(
           isolated_out_dir,
-          self.benchmark,
-          self._options.system_log_template)]
+          self.benchmark)]
     return []
 
 
@@ -609,9 +608,9 @@
                       ' to run in lieu of indexing into our benchmark bot maps',
                       required=False)
   # crbug.com/1236245: This allows for per-benchmark device logs.
-  parser.add_argument('--system-log-template',
-                      help='File name template for system logs for each '
-                      'benchmark', required=False)
+  parser.add_argument('--per-test-logs-dir',
+                      help='Require --logs-dir args for test', required=False,
+                      default=False)
   # Some executions may have a different sharding scheme and/or set of tests.
   # These files must live in src/tools/perf/core/shard_maps
   parser.add_argument('--test-shard-map-filename', type=str, required=False)
diff --git a/testing/unexpected_passes_common/data_types.py b/testing/unexpected_passes_common/data_types.py
index b3f6527e..959241af 100644
--- a/testing/unexpected_passes_common/data_types.py
+++ b/testing/unexpected_passes_common/data_types.py
@@ -5,6 +5,7 @@
 
 from __future__ import print_function
 
+import collections
 import copy
 import fnmatch
 import logging
@@ -432,28 +433,31 @@
     empty dictionary, that test entry will also be removed.
 
     Returns:
-      A list containing any Expectations that were removed.
+      A dict from expectation file name (str) to set of unused expectations
+      (str) from that file.
     """
     logging.info('Filtering out unused expectations')
-    unused_expectations = []
-    for _, expectation, builder_map in self.IterBuilderStepMaps():
+    unused = collections.defaultdict(list)
+    unused_count = 0
+    for (expectation_file, expectation,
+         builder_map) in self.IterBuilderStepMaps():
       if not builder_map:
-        unused_expectations.append(expectation)
-    for unused in unused_expectations:
-      for _, expectation_map in self.items():
-        if unused in expectation_map:
-          del expectation_map[unused]
-    logging.debug('Found %d unused expectations', len(unused_expectations))
+        unused[expectation_file].append(expectation)
+        unused_count += 1
+    for expectation_file, expectations in unused.items():
+      for e in expectations:
+        del self[expectation_file][e]
+    logging.debug('Found %d unused expectations', unused_count)
 
-    empty_tests = []
-    for test_name, expectation_map in self.items():
+    empty_files = []
+    for expectation_file, expectation_map in self.items():
       if not expectation_map:
-        empty_tests.append(test_name)
-    for empty in empty_tests:
+        empty_files.append(expectation_file)
+    for empty in empty_files:
       del self[empty]
-    logging.debug('Found %d empty tests: %s', len(empty_tests), empty_tests)
+    logging.debug('Found %d empty files: %s', len(empty_files), empty_files)
 
-    return unused_expectations
+    return unused
 
 
 class ExpectationBuilderMap(BaseTypedMap):
diff --git a/testing/unexpected_passes_common/data_types_unittest.py b/testing/unexpected_passes_common/data_types_unittest.py
index caa9c0f7..2c76637 100755
--- a/testing/unexpected_passes_common/data_types_unittest.py
+++ b/testing/unexpected_passes_common/data_types_unittest.py
@@ -1141,10 +1141,12 @@
             }),
         }),
     })
+    expected_unused = {
+        'expectation_file':
+        [data_types.Expectation('foo/test', ['linux'], ['Failure'])]
+    }
     unused_expectations = expectation_map.FilterOutUnusedExpectations()
-    self.assertEqual(
-        unused_expectations,
-        [data_types.Expectation('foo/test', ['linux'], ['Failure'])])
+    self.assertEqual(unused_expectations, expected_unused)
     self.assertEqual(expectation_map, expected_expectation_map)
 
   def testUnusedAndEmpty(self):
@@ -1156,9 +1158,12 @@
             data_types.BuilderStepMap(),
         }),
     })
+    expected_unused = {
+        'expectation_file':
+        [data_types.Expectation('foo/test', ['win'], ['Failure'])]
+    }
     unused_expectations = expectation_map.FilterOutUnusedExpectations()
-    self.assertEqual(unused_expectations,
-                     [data_types.Expectation('foo/test', ['win'], ['Failure'])])
+    self.assertEqual(unused_expectations, expected_unused)
     self.assertEqual(expectation_map, {})
 
 
diff --git a/testing/unexpected_passes_common/expectations.py b/testing/unexpected_passes_common/expectations.py
index d106064..572cb7e 100644
--- a/testing/unexpected_passes_common/expectations.py
+++ b/testing/unexpected_passes_common/expectations.py
@@ -41,6 +41,11 @@
       for e in list_parser.expectations:
         if 'Skip' in e.raw_results:
           continue
+        # Expectations that only have a Pass expectation (usually used to
+        # override a broader, failing expectation) are not handled by the
+        # unexpected pass finder, so ignore those.
+        if e.raw_results == ['Pass']:
+          continue
         expectation = data_types.Expectation(e.test, e.tags, e.raw_results,
                                              e.reason)
         assert expectation not in expectations_for_file
diff --git a/testing/unexpected_passes_common/expectations_unittest.py b/testing/unexpected_passes_common/expectations_unittest.py
index 4d2779f6..bd56d4c 100755
--- a/testing/unexpected_passes_common/expectations_unittest.py
+++ b/testing/unexpected_passes_common/expectations_unittest.py
@@ -23,13 +23,14 @@
 
 FAKE_EXPECTATION_FILE_CONTENTS = """\
 # tags: [ win linux ]
-# results: [ Failure RetryOnFailure Skip ]
+# results: [ Failure RetryOnFailure Skip Pass ]
 crbug.com/1234 [ win ] foo/test [ Failure ]
 
 [ linux ] foo/test [ Failure ]
 
 crbug.com/2345 [ linux ] bar/* [ RetryOnFailure ]
 crbug.com/3456 [ linux ] some/bad/test [ Skip ]
+crbug.com/4567 [ linux ] some/good/test [ Pass ]
 """
 
 SECONDARY_FAKE_EXPECTATION_FILE_CONTENTS = """\
@@ -342,12 +343,13 @@
     self.assertEqual(modified_urls, set(['crbug.com/1234', 'crbug.com/4567']))
     expected_file_contents = """\
 # tags: [ win linux ]
-# results: [ Failure RetryOnFailure Skip ]
+# results: [ Failure RetryOnFailure Skip Pass ]
 
 [ linux ] foo/test [ Failure ]
 
 crbug.com/2345 [ linux ] bar/* [ RetryOnFailure ]
 crbug.com/3456 [ linux ] some/bad/test [ Skip ]
+crbug.com/4567 [ linux ] some/good/test [ Pass ]
 """
     with open(self.filename) as f:
       self.assertEqual(f.read(), expected_file_contents)
diff --git a/testing/unexpected_passes_common/queries.py b/testing/unexpected_passes_common/queries.py
index 46a9fd4..157c5b91 100644
--- a/testing/unexpected_passes_common/queries.py
+++ b/testing/unexpected_passes_common/queries.py
@@ -17,6 +17,7 @@
 import six
 
 from typ import expectations_parser
+from typ import json_results
 from unexpected_passes_common import builders as builders_module
 from unexpected_passes_common import data_types
 from unexpected_passes_common import multiprocessing_utils
@@ -271,22 +272,24 @@
 
     def run_cmd_in_thread(inputs):
       cmd, query = inputs
+      query = query.encode('utf-8')
       with open(os.devnull, 'w') as devnull:
-        processes_lock.acquire()
-        # Starting many queries at once causes us to hit rate limits much more
-        # frequently, so stagger query starts to help avoid that.
-        time.sleep(QUERY_DELAY)
-        p = subprocess.Popen(cmd,
-                             stdout=subprocess.PIPE,
-                             stderr=devnull,
-                             stdin=subprocess.PIPE)
-        processes.add(p)
-        processes_lock.release()
+        with processes_lock:
+          # Starting many queries at once causes us to hit rate limits much more
+          # frequently, so stagger query starts to help avoid that.
+          time.sleep(QUERY_DELAY)
+          p = subprocess.Popen(cmd,
+                               stdout=subprocess.PIPE,
+                               stderr=devnull,
+                               stdin=subprocess.PIPE)
+          processes.add(p)
 
         # We pass in the query via stdin instead of including it on the
         # commandline because we can run into command length issues in large
         # query mode.
         stdout, _ = p.communicate(query)
+        if not isinstance(stdout, six.string_types):
+          stdout = stdout.decode('utf-8')
         if p.returncode:
           # When running many queries in parallel, it's possible to hit the
           # rate limit for the account if we're unlucky, so try again if we do.
@@ -509,6 +512,10 @@
 
 
 def _ConvertActualResultToExpectationFileFormat(actual_result):
+  # Web tests use ResultDB's ABORT value for both test timeouts and device
+  # failures, but Abort is not defined in typ. So, map it to timeout now.
+  if actual_result == 'ABORT':
+    actual_result = json_results.ResultType.Timeout
   # The result reported to ResultDB is in the format PASS/FAIL, while the
   # expected results in an expectation file are in the format Pass/Failure.
   return expectations_parser.RESULT_TAGS[actual_result]
diff --git a/testing/unexpected_passes_common/queries_unittest.py b/testing/unexpected_passes_common/queries_unittest.py
index 9dd7223..12bb284 100755
--- a/testing/unexpected_passes_common/queries_unittest.py
+++ b/testing/unexpected_passes_common/queries_unittest.py
@@ -34,6 +34,10 @@
     with self.assertRaises(AssertionError):
       queries._StripPrefixFromBuildId('build-1-2')
 
+  def testConvertActualResultToExpectationFileFormatAbort(self):
+    self.assertEqual(
+        queries._ConvertActualResultToExpectationFileFormat('ABORT'), 'Timeout')
+
 
 class QueryGeneratorUnittest(unittest.TestCase):
   def testSplitQueryGeneratorInitialSplit(self):
diff --git a/testing/unexpected_passes_common/result_output.py b/testing/unexpected_passes_common/result_output.py
index 8d8d9c23..ba38a3e 100644
--- a/testing/unexpected_passes_common/result_output.py
+++ b/testing/unexpected_passes_common/result_output.py
@@ -183,8 +183,8 @@
     ummatched_results: Any unmatched results found while filling
         |test_expectation_map|, as returned by
         queries.FillExpectationMapFor[Ci|Try]Builders().
-    unused_expectations: A list of any unmatched Expectations that were pulled
-        out of |test_expectation_map|.
+    unused_expectations: A dict from expectation file (str) to list of
+        unmatched Expectations that were pulled out of |test_expectation_map|
     output_format: A string denoting the format to output to. Valid values are
         "print" and "html".
     file_handle: An optional open file-like object to output to. If not
@@ -199,7 +199,7 @@
   active_str_dict = _ConvertTestExpectationMapToStringDict(active_dict)
   unmatched_results_str_dict = _ConvertUnmatchedResultsToStringDict(
       unmatched_results)
-  unused_expectations_str_list = _ConvertUnusedExpectationsToStringList(
+  unused_expectations_str_list = _ConvertUnusedExpectationsToStringDict(
       unused_expectations)
 
   if output_format == 'print':
@@ -225,7 +225,9 @@
     should_close_file = False
     if not file_handle:
       should_close_file = True
-      file_handle = tempfile.NamedTemporaryFile(delete=False, suffix='.html')
+      file_handle = tempfile.NamedTemporaryFile(delete=False,
+                                                suffix='.html',
+                                                mode='w')
 
     file_handle.write(HTML_HEADER)
     if stale_dict:
@@ -441,23 +443,34 @@
   return output_dict
 
 
-def _ConvertUnusedExpectationsToStringList(unused_expectations):
-  """Converts |unused_expectations| to a list of strings for reporting.
+def _ConvertUnusedExpectationsToStringDict(unused_expectations):
+  """Converts |unused_expectations| to a dict of strings for reporting.
 
   Args:
-    unused_expectations: A list of data_types.Expectation that didn't have any
-        matching results.
+    unused_expectations: A dict mapping expectation file (str) to lists of
+        data_types.Expectation who did not have any matching results.
 
   Returns:
-    A list of strings, each one corresponding to an element in
-    |unused_expectations|. Strings are in a format similar to what would be
-    present as a line in an expectation file.
+    A string dictionary representation of |unused_expectations| in the following
+    format:
+    {
+      expectation_file: [
+        expectation1,
+        expectation2,
+      ],
+    }
+    The expectations are in a format similar to what would be present as a line
+    in an expectation file.
   """
-  output_list = []
-  for e in unused_expectations:
-    output_list.append('[ %s ] %s [ %s ]' %
-                       (' '.join(e.tags), e.test, ' '.join(e.expected_results)))
-  return output_list
+  output_dict = {}
+  for expectation_file, expectations in unused_expectations.items():
+    expectation_str_list = []
+    for e in expectations:
+      expectation_str_list.append(
+          '[ %s ] %s [ %s ]' %
+          (' '.join(e.tags), e.test, ' '.join(e.expected_results)))
+    output_dict[expectation_file] = expectation_str_list
+  return output_dict
 
 
 def _FormatExpectation(expectation):
diff --git a/testing/unexpected_passes_common/result_output_unittest.py b/testing/unexpected_passes_common/result_output_unittest.py
index 37755508..dc5f0f8 100755
--- a/testing/unexpected_passes_common/result_output_unittest.py
+++ b/testing/unexpected_passes_common/result_output_unittest.py
@@ -11,6 +11,8 @@
 import tempfile
 import unittest
 
+import six
+
 from pyfakefs import fake_filesystem_unittest
 
 from unexpected_passes_common import data_types
@@ -140,7 +142,7 @@
     })
     # TODO(crbug.com/1198237): Remove the Python 2 version once we are fully
     # switched to Python 3.
-    if sys.version_info[0] == 2:
+    if six.PY2:
       expected_output = {
           'expectation_file': {
               'foo/test': {
@@ -224,6 +226,54 @@
     self.assertEqual(str_dict, expected_output)
 
 
+class ConvertUnusedExpectationsToStringDictUnittest(unittest.TestCase):
+  def testEmptyDict(self):
+    """Tests that nothing blows up when given an empty dict."""
+    self.assertEqual(result_output._ConvertUnusedExpectationsToStringDict({}),
+                     {})
+
+  def testBasic(self):
+    """Basic functionality test."""
+    unused = {
+        'foo_file': [
+            data_types.Expectation('foo/test', ['win', 'nvidia'],
+                                   ['Failure', 'Timeout']),
+        ],
+        'bar_file': [
+            data_types.Expectation('bar/test', ['win'], ['Failure']),
+            data_types.Expectation('bar/test2', ['win'], ['RetryOnFailure'])
+        ],
+    }
+    if six.PY2:
+      expected_output = {
+          'foo_file': [
+              '[ win nvidia ] foo/test [ Failure Timeout ]',
+          ],
+          'bar_file': [
+              '[ win ] bar/test [ Failure ]',
+              '[ win ] bar/test2 [ RetryOnFailure ]',
+          ],
+      }
+    else:
+      # Set ordering does not appear to be stable between test runs, as we can
+      # get either order of tags. So, generate them now instead of hard coding
+      # them.
+      tags = ' '.join(set(['win', 'nvidia']))
+      results = ' '.join(set(['Failure', 'Timeout']))
+      expected_output = {
+          'foo_file': [
+              '[ %s ] foo/test [ %s ]' % (tags, results),
+          ],
+          'bar_file': [
+              '[ win ] bar/test [ Failure ]',
+              '[ win ] bar/test2 [ RetryOnFailure ]',
+          ],
+      }
+    self.assertEqual(
+        result_output._ConvertUnusedExpectationsToStringDict(unused),
+        expected_output)
+
+
 class HtmlToFileUnittest(fake_filesystem_unittest.TestCase):
   def setUp(self):
     self.setUpPyfakefs()
@@ -268,7 +318,7 @@
     # pylint: disable=line-too-long
     # TODO(crbug.com/1198237): Remove the Python 2 version once we've fully
     # switched to Python 3.
-    if sys.version_info[0] == 2:
+    if six.PY2:
       expected_output = """\
 <button type="button" class="collapsible_group">foo</button>
 <div class="content">
@@ -410,7 +460,7 @@
 
     # TODO(crbug.com/1198237): Keep the Python 3 version once we are fully
     # switched.
-    if sys.version_info[0] == 2:
+    if six.PY2:
       expected_output = """\
 foo
   "RetryOnFailure" expectation on "win intel"
@@ -491,7 +541,7 @@
     with self.assertRaises(RuntimeError):
       result_output.OutputResults(data_types.TestExpectationMap(),
                                   data_types.TestExpectationMap(),
-                                  data_types.TestExpectationMap(), {}, [],
+                                  data_types.TestExpectationMap(), {}, {},
                                   'asdf')
 
   def testOutputResultsSmoketest(self):
@@ -535,16 +585,18 @@
                               'build_id'),
         ],
     }
-    unmatched_expectations = [
-        data_types.Expectation('foo', ['linux'], 'RetryOnFailure')
-    ]
+    unmatched_expectations = {
+        'foo_file': [
+            data_types.Expectation('foo', ['linux'], 'RetryOnFailure'),
+        ],
+    }
 
     stale, semi_stale, active = expectation_map.SplitByStaleness()
 
-    result_output.OutputResults(stale, semi_stale, active, {}, [], 'print',
+    result_output.OutputResults(stale, semi_stale, active, {}, {}, 'print',
                                 self._file_handle)
     result_output.OutputResults(stale, semi_stale, active, unmatched_results,
-                                [], 'print', self._file_handle)
+                                {}, 'print', self._file_handle)
     result_output.OutputResults(stale, semi_stale, active, {},
                                 unmatched_expectations, 'print',
                                 self._file_handle)
@@ -552,10 +604,10 @@
                                 unmatched_expectations, 'print',
                                 self._file_handle)
 
-    result_output.OutputResults(stale, semi_stale, active, {}, [], 'html',
+    result_output.OutputResults(stale, semi_stale, active, {}, {}, 'html',
                                 self._file_handle)
     result_output.OutputResults(stale, semi_stale, active, unmatched_results,
-                                [], 'html', self._file_handle)
+                                {}, 'html', self._file_handle)
     result_output.OutputResults(stale, semi_stale, active, {},
                                 unmatched_expectations, 'html',
                                 self._file_handle)
diff --git a/testing/unexpected_passes_common/unittest_utils.py b/testing/unexpected_passes_common/unittest_utils.py
index f6600b6..0f8e26b7 100644
--- a/testing/unexpected_passes_common/unittest_utils.py
+++ b/testing/unexpected_passes_common/unittest_utils.py
@@ -160,7 +160,7 @@
   def _GetExpectationFileTagHeader(self):
     return """\
 # tags: [ linux mac win ]
-# results: [ Failure RetryOnFailure Skip ]
+# results: [ Failure RetryOnFailure Skip Pass ]
 """
 
 
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index ecd534e..338ec13 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -3074,6 +3074,32 @@
             ]
         }
     ],
+    "DnsHttpsSvcbSchemeUpgrade": [
+        {
+            "platforms": [
+                "windows",
+                "mac",
+                "linux",
+                "chromeos",
+                "chromeos_lacros",
+                "android"
+            ],
+            "experiments": [
+                {
+                    "name": "UseDnsHttpsSvcb",
+                    "params": {
+                        "UseDnsHttpsSvcbEnableInsecure": "true",
+                        "UseDnsHttpsSvcbExtraTimeAbsolute": "500ms",
+                        "UseDnsHttpsSvcbExtraTimePercent": "50",
+                        "UseDnsHttpsSvcbHttpUpgrade": "true"
+                    },
+                    "enable_features": [
+                        "UseDnsHttpsSvcb"
+                    ]
+                }
+            ]
+        }
+    ],
     "DnsHttpssvc": [
         {
             "platforms": [
diff --git a/third_party/blink/renderer/bindings/core/v8/local_window_proxy.cc b/third_party/blink/renderer/bindings/core/v8/local_window_proxy.cc
index b0d5db6..c64289c4 100644
--- a/third_party/blink/renderer/bindings/core/v8/local_window_proxy.cc
+++ b/third_party/blink/renderer/bindings/core/v8/local_window_proxy.cc
@@ -61,6 +61,8 @@
 #include "third_party/blink/renderer/platform/bindings/v8_dom_wrapper.h"
 #include "third_party/blink/renderer/platform/bindings/v8_private_property.h"
 #include "third_party/blink/renderer/platform/heap/handle.h"
+#include "third_party/blink/renderer/platform/heap/heap.h"
+#include "third_party/blink/renderer/platform/instrumentation/histogram.h"
 #include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
 #include "third_party/blink/renderer/platform/weborigin/reporting_disposition.h"
 #include "third_party/blink/renderer/platform/weborigin/security_origin.h"
@@ -225,8 +227,18 @@
     Document* document = GetFrame()->GetDocument();
 
     v8::Local<v8::Object> global_proxy = global_proxy_.NewLocal(isolate);
-    context = V8ContextSnapshot::CreateContextFromSnapshot(
-        isolate, World(), &extension_configuration, global_proxy, document);
+    {
+      DEFINE_STATIC_LOCAL(
+          CustomCountHistogram, main_frame_hist,
+          ("Blink.Binding.CreateV8ContextForMainFrame", 0, 10000000, 50));
+      DEFINE_STATIC_LOCAL(
+          CustomCountHistogram, non_main_frame_hist,
+          ("Blink.Binding.CreateV8ContextForNonMainFrame", 0, 10000000, 50));
+      ScopedUsHistogramTimer timer(
+          GetFrame()->IsMainFrame() ? main_frame_hist : non_main_frame_hist);
+      context = V8ContextSnapshot::CreateContextFromSnapshot(
+          isolate, World(), &extension_configuration, global_proxy, document);
+    }
     context_was_created_from_snapshot_ = !context.IsEmpty();
 
     // Even if we enable V8 context snapshot feature, we may hit this branch
diff --git a/third_party/blink/renderer/bindings/generated_in_core.gni b/third_party/blink/renderer/bindings/generated_in_core.gni
index b3b72cd0..3024d19 100644
--- a/third_party/blink/renderer/bindings/generated_in_core.gni
+++ b/third_party/blink/renderer/bindings/generated_in_core.gni
@@ -424,6 +424,8 @@
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_form_state_restore_mode.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_fullscreen_navigation_ui.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_fullscreen_navigation_ui.h",
+  "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_highlight_type.cc",
+  "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_highlight_type.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_image_color_space.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_image_color_space.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_image_data_storage_format.cc",
diff --git a/third_party/blink/renderer/bindings/generated_in_modules.gni b/third_party/blink/renderer/bindings/generated_in_modules.gni
index 7d783e3..784009f 100644
--- a/third_party/blink/renderer/bindings/generated_in_modules.gni
+++ b/third_party/blink/renderer/bindings/generated_in_modules.gni
@@ -1491,6 +1491,8 @@
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_bluetooth_uuid.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_broadcast_channel.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_broadcast_channel.h",
+  "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_browser_capture_media_stream_track.cc",
+  "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_browser_capture_media_stream_track.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_cache.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_cache.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_cache_storage.cc",
diff --git a/third_party/blink/renderer/bindings/idl_in_modules.gni b/third_party/blink/renderer/bindings/idl_in_modules.gni
index 2737f423..66a5961 100644
--- a/third_party/blink/renderer/bindings/idl_in_modules.gni
+++ b/third_party/blink/renderer/bindings/idl_in_modules.gni
@@ -407,6 +407,7 @@
           "//third_party/blink/renderer/modules/mediasource/url_media_source.idl",
           "//third_party/blink/renderer/modules/mediasource/video_playback_quality.idl",
           "//third_party/blink/renderer/modules/mediasource/video_track_source_buffer.idl",
+          "//third_party/blink/renderer/modules/mediastream/browser_capture_media_stream_track.idl",
           "//third_party/blink/renderer/modules/mediastream/capture_handle.idl",
           "//third_party/blink/renderer/modules/mediastream/capture_handle_change_event.idl",
           "//third_party/blink/renderer/modules/mediastream/capture_handle_change_event_init.idl",
@@ -416,8 +417,8 @@
           "//third_party/blink/renderer/modules/mediastream/constrain_double_range.idl",
           "//third_party/blink/renderer/modules/mediastream/constrain_long_range.idl",
           "//third_party/blink/renderer/modules/mediastream/double_range.idl",
-          "//third_party/blink/renderer/modules/mediastream/input_device_info.idl",
           "//third_party/blink/renderer/modules/mediastream/focusable_media_stream_track.idl",
+          "//third_party/blink/renderer/modules/mediastream/input_device_info.idl",
           "//third_party/blink/renderer/modules/mediastream/long_range.idl",
           "//third_party/blink/renderer/modules/mediastream/media_device_info.idl",
           "//third_party/blink/renderer/modules/mediastream/media_devices.idl",
diff --git a/third_party/blink/renderer/core/highlight/highlight.h b/third_party/blink/renderer/core/highlight/highlight.h
index 035cba90..0c496f1 100644
--- a/third_party/blink/renderer/core/highlight/highlight.h
+++ b/third_party/blink/renderer/core/highlight/highlight.h
@@ -38,6 +38,9 @@
   const int32_t& priority() const { return priority_; }
   void setPriority(const int32_t&);
 
+  AtomicString type() const { return type_; }
+  void setType(const AtomicString& type) { type_ = type; }
+
   bool Contains(AbstractRange*) const;
 
   class IterationSource final : public HighlightSetIterable::IterationSource {
@@ -70,6 +73,7 @@
  private:
   HeapLinkedHashSet<Member<AbstractRange>> highlight_ranges_;
   int32_t priority_ = 0;
+  AtomicString type_ = "highlight";
   // Since a Highlight can be registered many times under different names in
   // many HighlightRegistries, we need to keep track of the number of times
   // it's present in each registry. If the Highlight is not registered anywhere,
diff --git a/third_party/blink/renderer/core/highlight/highlight.idl b/third_party/blink/renderer/core/highlight/highlight.idl
index 931241a..2272698 100644
--- a/third_party/blink/renderer/core/highlight/highlight.idl
+++ b/third_party/blink/renderer/core/highlight/highlight.idl
@@ -2,6 +2,12 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+enum HighlightType {
+  "highlight",
+  "spelling-error",
+  "grammar-error"
+};
+
 [
   RuntimeEnabled = HighlightAPI,
   Exposed = Window
@@ -9,4 +15,5 @@
   constructor(AbstractRange... initRanges);
   setlike<AbstractRange>;
   attribute long priority;
+  attribute HighlightType type;
 };
diff --git a/third_party/blink/renderer/core/layout/ng/exclusions/ng_exclusion_space.h b/third_party/blink/renderer/core/layout/ng/exclusions/ng_exclusion_space.h
index 4a93e01e..80457bf7 100644
--- a/third_party/blink/renderer/core/layout/ng/exclusions/ng_exclusion_space.h
+++ b/third_party/blink/renderer/core/layout/ng/exclusions/ng_exclusion_space.h
@@ -24,6 +24,8 @@
 // see NGExclusionSpace below. NGExclusionSpace was designed to be cheap
 // to construct and cheap to copy if empty.
 class CORE_EXPORT NGExclusionSpaceInternal final {
+  USING_FAST_MALLOC(NGExclusionSpaceInternal);
+
  public:
   NGExclusionSpaceInternal();
   NGExclusionSpaceInternal(const NGExclusionSpaceInternal&);
diff --git a/third_party/blink/renderer/core/layout/ng/grid/ng_grid_data.h b/third_party/blink/renderer/core/layout/ng/grid/ng_grid_data.h
index 8c37fa5..327b3fb 100644
--- a/third_party/blink/renderer/core/layout/ng/grid/ng_grid_data.h
+++ b/third_party/blink/renderer/core/layout/ng/grid/ng_grid_data.h
@@ -20,6 +20,9 @@
 // This means that although splitting the sets by their track count would not
 // give the correct result.
 struct NGGridData {
+  USING_FAST_MALLOC(NGGridData);
+
+ public:
   using RangeData = NGGridLayoutAlgorithmTrackCollection::Range;
 
   wtf_size_t row_start;
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_item.h b/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_item.h
index 1aac29f..f357ad5 100644
--- a/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_item.h
+++ b/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_item.h
@@ -28,6 +28,9 @@
 
 // Data for SVG text in addition to NGFragmentItem.
 struct NGSvgFragmentData {
+  USING_FAST_MALLOC(NGSvgFragmentData);
+
+ public:
   scoped_refptr<const ShapeResultView> shape_result;
   NGTextOffset text_offset;
   FloatRect rect;
diff --git a/third_party/blink/renderer/core/layout/ng/ng_physical_fragment.h b/third_party/blink/renderer/core/layout/ng/ng_physical_fragment.h
index 7507798b..5dc93824 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_physical_fragment.h
+++ b/third_party/blink/renderer/core/layout/ng/ng_physical_fragment.h
@@ -589,6 +589,9 @@
   bool ChildrenValid() const { return children_valid_; }
 
   struct OutOfFlowData {
+    USING_FAST_MALLOC(OutOfFlowData);
+
+   public:
     Vector<NGPhysicalOutOfFlowPositionedNode> oof_positioned_descendants;
   };
 
diff --git a/third_party/blink/renderer/core/layout/ng/svg/resolved_text_layout_attributes_iterator.h b/third_party/blink/renderer/core/layout/ng/svg/resolved_text_layout_attributes_iterator.h
index c210a40..28d0176 100644
--- a/third_party/blink/renderer/core/layout/ng/svg/resolved_text_layout_attributes_iterator.h
+++ b/third_party/blink/renderer/core/layout/ng/svg/resolved_text_layout_attributes_iterator.h
@@ -30,6 +30,8 @@
 // AdvanceTo(42) returns the NGSvgCharacterData at [2].
 // AdvanceTo(43 or greater) returns the default NGSvgCharacterData.
 class ResolvedTextLayoutAttributesIterator final {
+  USING_FAST_MALLOC(ResolvedTextLayoutAttributesIterator);
+
  public:
   explicit ResolvedTextLayoutAttributesIterator(
       const Vector<std::pair<unsigned, NGSvgCharacterData>>& resolved)
diff --git a/third_party/blink/renderer/core/layout/ng/table/ng_table_fragment_data.h b/third_party/blink/renderer/core/layout/ng/table/ng_table_fragment_data.h
index f89b5edb..5528864c 100644
--- a/third_party/blink/renderer/core/layout/ng/table/ng_table_fragment_data.h
+++ b/third_party/blink/renderer/core/layout/ng/table/ng_table_fragment_data.h
@@ -43,6 +43,9 @@
   // Column/row location is used for collapsed border painting.
   // Only present if borders are collapsed.
   struct CollapsedBordersGeometry {
+    USING_FAST_MALLOC(CollapsedBordersGeometry);
+
+   public:
     Vector<LayoutUnit> columns;  // Column offsets from table grid border.
     Vector<LayoutUnit> rows;     // Row offsets from table grid border.
 
diff --git a/third_party/blink/renderer/core/page/scrolling/text_fragment_selector_generator.cc b/third_party/blink/renderer/core/page/scrolling/text_fragment_selector_generator.cc
index 4e8b7b06..f838a160 100644
--- a/third_party/blink/renderer/core/page/scrolling/text_fragment_selector_generator.cc
+++ b/third_party/blink/renderer/core/page/scrolling/text_fragment_selector_generator.cc
@@ -31,10 +31,10 @@
 // Returns true if text from beginning of |node| until |pos_offset| can be
 // considered empty. Otherwise, return false.
 bool IsFirstVisiblePosition(Node* node, unsigned pos_offset) {
-  auto range_start = Position::FirstPositionInNode(*node);
-  auto range_end = Position(node, pos_offset);
+  auto range_start = PositionInFlatTree::FirstPositionInNode(*node);
+  auto range_end = PositionInFlatTree(node, pos_offset);
   return node->getNodeType() == Node::kElementNode || pos_offset == 0 ||
-         PlainText(EphemeralRange(range_start, range_end))
+         PlainText(EphemeralRangeInFlatTree(range_start, range_end))
              .StripWhiteSpace()
              .IsEmpty();
 }
@@ -42,11 +42,11 @@
 // Returns true if text from |pos_offset| until end of |node| can be considered
 // empty. Otherwise, return false.
 bool IsLastVisiblePosition(Node* node, unsigned pos_offset) {
-  auto range_start = Position(node, pos_offset);
-  auto range_end = Position::LastPositionInNode(*node);
+  auto range_start = PositionInFlatTree(node, pos_offset);
+  auto range_end = PositionInFlatTree::LastPositionInNode(*node);
   return node->getNodeType() == Node::kElementNode ||
          pos_offset == node->textContent().length() ||
-         PlainText(EphemeralRange(range_start, range_end))
+         PlainText(EphemeralRangeInFlatTree(range_start, range_end))
              .StripWhiteSpace()
              .IsEmpty();
 }
diff --git a/third_party/blink/renderer/core/page/scrolling/text_fragment_selector_generator_test.cc b/third_party/blink/renderer/core/page/scrolling/text_fragment_selector_generator_test.cc
index 457528e..bf3b9b20 100644
--- a/third_party/blink/renderer/core/page/scrolling/text_fragment_selector_generator_test.cc
+++ b/third_party/blink/renderer/core/page/scrolling/text_fragment_selector_generator_test.cc
@@ -20,6 +20,7 @@
 #include "third_party/blink/renderer/core/editing/ephemeral_range.h"
 #include "third_party/blink/renderer/core/editing/iterators/text_iterator.h"
 #include "third_party/blink/renderer/core/frame/local_frame.h"
+#include "third_party/blink/renderer/core/html/html_div_element.h"
 #include "third_party/blink/renderer/core/page/scrolling/text_fragment_handler.h"
 #include "third_party/blink/renderer/core/testing/scoped_fake_ukm_recorder.h"
 #include "third_party/blink/renderer/core/testing/sim/sim_request.h"
@@ -1220,6 +1221,32 @@
   VerifySelector(start, end, "button-,paragraph,-text");
 }
 
+// Ensure generation works correctly when the range begins anchored to a shadow
+// host. The shadow root has more children than the shadow host so this ensures
+// we're using flat tree node traversals.
+TEST_F(TextFragmentSelectorGeneratorTest, RangeBeginsOnShadowHost) {
+  SimRequest request("https://example.com/test.html", "text/html");
+  LoadURL("https://example.com/test.html");
+  request.Complete(R"HTML(
+    <!DOCTYPE html>
+  <div id='host'></div>
+  the quick brown fox jumped over the lazy dog.
+  )HTML");
+
+  Element* host = GetDocument().getElementById("host");
+  ShadowRoot& root = host->AttachShadowRootInternal(ShadowRootType::kOpen);
+  root.appendChild(MakeGarbageCollected<HTMLDivElement>(root.GetDocument()));
+  root.appendChild(MakeGarbageCollected<HTMLDivElement>(root.GetDocument()));
+
+  Compositor().BeginFrame();
+
+  const auto& start = Position(host, PositionAnchorType::kAfterChildren);
+  const auto& end = Position(host->nextSibling(), 12);
+  ASSERT_EQ("the quick", PlainText(EphemeralRange(start, end)));
+
+  VerifySelector(start, end, "the%20quick,-brown%20fox%20jumped");
+}
+
 // Basic test case for |GetNextTextBlock|.
 TEST_F(TextFragmentSelectorGeneratorTest, GetPreviousTextBlock) {
   SimRequest request("https://example.com/test.html", "text/html");
diff --git a/third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.cc b/third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.cc
index af4d655..c5770b86 100644
--- a/third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.cc
+++ b/third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.cc
@@ -1939,7 +1939,8 @@
   const LayoutObject* layout_object = fragment.GetLayoutObject();
   bool skip_children =
       layout_object &&
-      layout_object == hit_test.result->GetHitTestRequest().GetStopNode();
+      (layout_object == hit_test.result->GetHitTestRequest().GetStopNode() ||
+       layout_object->ChildPaintBlockedByDisplayLock());
   if (!skip_children && box_fragment_.ShouldClipOverflowAlongEitherAxis()) {
     // PaintLayer::HitTestContentsForFragments checked the fragments'
     // foreground rect for intersection if a layer is self painting,
diff --git a/third_party/blink/renderer/modules/accessibility/ax_object.cc b/third_party/blink/renderer/modules/accessibility/ax_object.cc
index f0339f41..730534436 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_object.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_object.cc
@@ -2556,16 +2556,21 @@
     if (IsA<HTMLMediaElement>(owner))
       return true;
 
-    // Do not include ignored descendants of an <input type="number"> because
-    // they interfere with AXPosition code that assumes a plain input field
-    // structure. Specifically, caret moved events will not be emitted for the
-    // final offset because the associated tree position for that offset is an
-    // ignored node. In some cases platform accessibility code will instead
-    // incorrectly emit a caret moved event for the AXPosition which follows the
-    // input.
+    // Do not include ignored descendants of an <input type="search"> or
+    // <input type="number"> because they interfere with AXPosition code that
+    // assumes a plain input field structure. Specifically, due to the ignored
+    // node at the end of textfield, end of editable text position will get
+    // adjusted to past text field or caret moved events will not be emitted for
+    // the final offset because the associated tree position. In some cases
+    // platform accessibility code will instead incorrectly emit a caret moved
+    // event for the AXPosition which follows the input.
     if (IsA<HTMLInputElement>(owner) &&
-        DynamicTo<HTMLInputElement>(owner)->type() == input_type_names::kNumber)
+        (DynamicTo<HTMLInputElement>(owner)->type() ==
+             input_type_names::kSearch ||
+         DynamicTo<HTMLInputElement>(owner)->type() ==
+             input_type_names::kNumber)) {
       return false;
+    }
   }
 
   Element* element = GetElement();
diff --git a/third_party/blink/renderer/modules/mediastream/BUILD.gn b/third_party/blink/renderer/modules/mediastream/BUILD.gn
index 8f245e7..5b24736 100644
--- a/third_party/blink/renderer/modules/mediastream/BUILD.gn
+++ b/third_party/blink/renderer/modules/mediastream/BUILD.gn
@@ -10,6 +10,8 @@
     "apply_constraints_processor.h",
     "apply_constraints_request.cc",
     "apply_constraints_request.h",
+    "browser_capture_media_stream_track.cc",
+    "browser_capture_media_stream_track.h",
     "capture_handle_change_event.cc",
     "capture_handle_change_event.h",
     "dom_window_media_stream.h",
diff --git a/third_party/blink/renderer/modules/mediastream/browser_capture_media_stream_track.cc b/third_party/blink/renderer/modules/mediastream/browser_capture_media_stream_track.cc
new file mode 100644
index 0000000..4f885813
--- /dev/null
+++ b/third_party/blink/renderer/modules/mediastream/browser_capture_media_stream_track.cc
@@ -0,0 +1,74 @@
+// 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 "third_party/blink/renderer/modules/mediastream/browser_capture_media_stream_track.h"
+
+#include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
+#include "third_party/blink/renderer/core/frame/local_dom_window.h"
+
+namespace blink {
+
+BrowserCaptureMediaStreamTrack::BrowserCaptureMediaStreamTrack(
+    ExecutionContext* execution_context,
+    MediaStreamComponent* component,
+    base::OnceClosure callback,
+    const String& descriptor_id,
+    bool is_clone)
+    : BrowserCaptureMediaStreamTrack(execution_context,
+                                     component,
+                                     component->Source()->GetReadyState(),
+                                     std::move(callback),
+                                     descriptor_id,
+                                     is_clone) {}
+
+BrowserCaptureMediaStreamTrack::BrowserCaptureMediaStreamTrack(
+    ExecutionContext* execution_context,
+    MediaStreamComponent* component,
+    MediaStreamSource::ReadyState ready_state,
+    base::OnceClosure callback,
+    const String& descriptor_id,
+    bool is_clone)
+    : FocusableMediaStreamTrack(execution_context,
+                                component,
+                                ready_state,
+                                std::move(callback),
+                                descriptor_id,
+                                is_clone) {}
+
+ScriptPromise BrowserCaptureMediaStreamTrack::cropTo(
+    ScriptState* script_state,
+    const String& crop_id,
+    ExceptionState& exception_state) {
+  // TODO(crbug.com/1247761): Implement.
+  auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
+  ScriptPromise promise = resolver->Promise();
+  resolver->Reject(MakeGarbageCollected<DOMException>(
+      DOMExceptionCode::kInvalidStateError, "Not implemented."));
+  return promise;
+}
+
+BrowserCaptureMediaStreamTrack* BrowserCaptureMediaStreamTrack::clone(
+    ScriptState* script_state) {
+  // Instantiate the clone.
+  BrowserCaptureMediaStreamTrack* cloned_track =
+      MakeGarbageCollected<BrowserCaptureMediaStreamTrack>(
+          ExecutionContext::From(script_state), Component()->Clone(),
+          GetReadyState(), base::DoNothing(), descriptor_id(),
+          /*is_clone=*/true);
+
+  // Copy state.
+  CloneInternal(cloned_track);
+
+  return cloned_track;
+}
+
+void BrowserCaptureMediaStreamTrack::CloneInternal(
+    BrowserCaptureMediaStreamTrack* cloned_track) {
+  // Clone parent classes' state.
+  FocusableMediaStreamTrack::CloneInternal(cloned_track);
+
+  // TODO(crbug.com/1247761): Clone cropping-related state.
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/modules/mediastream/browser_capture_media_stream_track.h b/third_party/blink/renderer/modules/mediastream/browser_capture_media_stream_track.h
new file mode 100644
index 0000000..ad93ff0e
--- /dev/null
+++ b/third_party/blink/renderer/modules/mediastream/browser_capture_media_stream_track.h
@@ -0,0 +1,47 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_MEDIASTREAM_BROWSER_CAPTURE_MEDIA_STREAM_TRACK_H_
+#define THIRD_PARTY_BLINK_RENDERER_MODULES_MEDIASTREAM_BROWSER_CAPTURE_MEDIA_STREAM_TRACK_H_
+
+#include "third_party/blink/renderer/core/dom/dom_exception.h"
+#include "third_party/blink/renderer/modules/mediastream/focusable_media_stream_track.h"
+
+namespace blink {
+
+class BrowserCaptureMediaStreamTrack final : public FocusableMediaStreamTrack {
+  DEFINE_WRAPPERTYPEINFO();
+
+ public:
+  BrowserCaptureMediaStreamTrack(ExecutionContext* execution_context,
+                                 MediaStreamComponent* component,
+                                 base::OnceClosure callback,
+                                 const String& descriptor_id,
+                                 bool is_clone = false);
+
+  BrowserCaptureMediaStreamTrack(ExecutionContext* execution_context,
+                                 MediaStreamComponent* component,
+                                 MediaStreamSource::ReadyState ready_state,
+                                 base::OnceClosure callback,
+                                 const String& descriptor_id,
+                                 bool is_clone = false);
+
+  ScriptPromise cropTo(ScriptState*, const String&, ExceptionState&);
+
+  BrowserCaptureMediaStreamTrack* clone(ScriptState*) override;
+
+ protected:
+  // Given a partially built MediaStreamTrack, finishes the job of making it
+  // into a clone of |this|.
+  // Useful for sub-classes (caveat below), as they need to clone both state
+  // from this class as well as of their own class.
+  // Caveat: This class is final, and has no sub-classes. We continue the
+  // pattern from the parent classes for clarity, and to make things easier
+  // if we do in the future sub-class further.
+  void CloneInternal(BrowserCaptureMediaStreamTrack*);
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_MODULES_MEDIASTREAM_BROWSER_CAPTURE_MEDIA_STREAM_TRACK_H_
diff --git a/third_party/blink/renderer/modules/mediastream/browser_capture_media_stream_track.idl b/third_party/blink/renderer/modules/mediastream/browser_capture_media_stream_track.idl
new file mode 100644
index 0000000..353b367
--- /dev/null
+++ b/third_party/blink/renderer/modules/mediastream/browser_capture_media_stream_track.idl
@@ -0,0 +1,22 @@
+// 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.
+
+// https://eladalon1983.github.io/region-capture/
+// TODO(crbug.com/1247761): Link to more official spec once published.
+[
+    Exposed = Window,
+    RuntimeEnabled = RegionCapture
+]
+interface BrowserCaptureMediaStreamTrack : FocusableMediaStreamTrack {
+  // 1. If |cropTarget| is a non-empty string, start cropping the track
+  //    to the coordinates of the element represented by |cropTarget|.
+  //    Return a Promise that resolves once cropping has been fully initiated
+  //    and the browser guarantees all subsequent frames produced on this track
+  //    will be cropped.
+  // 2. If |cropTarget| is empty, stop cropping.
+  //    Return a Promise that resolves when this instruction has been fully
+  //    propagated and subsequent frames are guaranteed to be uncropped.
+  [CallWith = ScriptState, RaisesException, MeasureAs = RegionCapture]
+  Promise<void> cropTo(DOMString crop_id);
+};
diff --git a/third_party/blink/renderer/modules/mediastream/focusable_media_stream_track.cc b/third_party/blink/renderer/modules/mediastream/focusable_media_stream_track.cc
index 18b705f..ac08f83 100644
--- a/third_party/blink/renderer/modules/mediastream/focusable_media_stream_track.cc
+++ b/third_party/blink/renderer/modules/mediastream/focusable_media_stream_track.cc
@@ -42,25 +42,6 @@
       descriptor_id_(descriptor_id) {
 }
 
-FocusableMediaStreamTrack* FocusableMediaStreamTrack::clone(
-    ScriptState* script_state) {
-  MediaStreamComponent* const cloned_component = Component()->Clone();
-  FocusableMediaStreamTrack* cloned_track =
-      MakeGarbageCollected<FocusableMediaStreamTrack>(
-          ExecutionContext::From(script_state), cloned_component,
-          GetReadyState(), base::DoNothing(), descriptor_id_,
-          /*is_clone=*/true);
-  MediaStreamTrack::DidCloneMediaStreamTrack(Component(), cloned_component);
-  cloned_track->CloneImageCaptureFrom(*this);
-
-#if !defined(OS_ANDROID)
-  // Copied for completeness, but should never be read on clones.
-  cloned_track->focus_called_ = focus_called_;
-#endif
-
-  return cloned_track;
-}
-
 #if !defined(OS_ANDROID)
 void FocusableMediaStreamTrack::CloseFocusWindowOfOpportunity() {
   promise_settled_ = true;
@@ -112,4 +93,32 @@
 #endif
 }
 
+FocusableMediaStreamTrack* FocusableMediaStreamTrack::clone(
+    ScriptState* script_state) {
+  // Instantiate the clone.
+  FocusableMediaStreamTrack* cloned_track =
+      MakeGarbageCollected<FocusableMediaStreamTrack>(
+          ExecutionContext::From(script_state), Component()->Clone(),
+          GetReadyState(), base::DoNothing(), descriptor_id_,
+          /*is_clone=*/true);
+
+  // Copy state.
+  FocusableMediaStreamTrack::CloneInternal(cloned_track);
+
+  return cloned_track;
+}
+
+void FocusableMediaStreamTrack::CloneInternal(
+    FocusableMediaStreamTrack* cloned_track) {
+  // Clone parent classes' state.
+  MediaStreamTrack::CloneInternal(cloned_track);
+
+  // Clone own state.
+#if !defined(OS_ANDROID)
+  // Copied for completeness, but should never be read on clones.
+  cloned_track->focus_called_ = focus_called_;
+  cloned_track->promise_settled_ = promise_settled_;
+#endif
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/mediastream/focusable_media_stream_track.h b/third_party/blink/renderer/modules/mediastream/focusable_media_stream_track.h
index 061405ef..74e878b 100644
--- a/third_party/blink/renderer/modules/mediastream/focusable_media_stream_track.h
+++ b/third_party/blink/renderer/modules/mediastream/focusable_media_stream_track.h
@@ -11,7 +11,7 @@
 
 namespace blink {
 
-class FocusableMediaStreamTrack final : public MediaStreamTrack {
+class FocusableMediaStreamTrack : public MediaStreamTrack {
   DEFINE_WRAPPERTYPEINFO();
 
  public:
@@ -28,9 +28,6 @@
                             const String& descriptor_id,
                             bool is_clone = false);
 
-  // Clones raise an error if focus() is called.
-  FocusableMediaStreamTrack* clone(ScriptState*) override;
-
 #if !defined(OS_ANDROID)
   void CloseFocusWindowOfOpportunity() override;
 #endif
@@ -39,6 +36,18 @@
              V8CaptureStartFocusBehavior focus_behavior,
              ExceptionState& exception_state);
 
+  // Clones raise an error if focus() is called.
+  FocusableMediaStreamTrack* clone(ScriptState*) override;
+
+ protected:
+  // Given a partially built FocusableMediaStreamTrack, finishes the job
+  // of making it into a clone of |this|.
+  // Useful for sub-classes, as they need to clone both state from
+  // this class as well as of their own class.
+  void CloneInternal(FocusableMediaStreamTrack*);
+
+  const String& descriptor_id() const { return descriptor_id_; }
+
  private:
 #if !defined(OS_ANDROID)
   // Clones may not be focus()-ed.
diff --git a/third_party/blink/renderer/modules/mediastream/media_stream.cc b/third_party/blink/renderer/modules/mediastream/media_stream.cc
index d62f75a..98c4494 100644
--- a/third_party/blink/renderer/modules/mediastream/media_stream.cc
+++ b/third_party/blink/renderer/modules/mediastream/media_stream.cc
@@ -28,6 +28,7 @@
 #include "third_party/blink/public/platform/task_type.h"
 #include "third_party/blink/renderer/core/execution_context/execution_context.h"
 #include "third_party/blink/renderer/core/frame/deprecation.h"
+#include "third_party/blink/renderer/modules/mediastream/browser_capture_media_stream_track.h"
 #include "third_party/blink/renderer/modules/mediastream/focusable_media_stream_track.h"
 #include "third_party/blink/renderer/modules/mediastream/media_stream_track_event.h"
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
@@ -39,44 +40,53 @@
 
 namespace {
 
-bool IsFocusable(const ExecutionContext* context,
-                 const MediaStreamComponent* component) {
-  if (!context || !RuntimeEnabledFeatures::ConditionalFocusEnabled(context)) {
-    return false;
-  }
-
-  if (!component) {
-    return false;
-  }
-
+// Returns the DisplayCaptureSurfaceType for display-capture tracks,
+// absl::nullopt for non-display-capture tracks.
+absl::optional<media::mojom::DisplayCaptureSurfaceType> GetDisplayCaptureType(
+    const MediaStreamComponent* component) {
   const MediaStreamTrackPlatform* const platform_track =
       component->GetPlatformTrack();
   if (!platform_track) {
-    return false;
+    return absl::nullopt;
   }
 
   MediaStreamTrackPlatform::Settings settings;
   component->GetPlatformTrack()->GetSettings(settings);
-
-  if (!settings.display_surface.has_value()) {
-    return false;
-  }
-
-  const media::mojom::DisplayCaptureSurfaceType display_surface =
-      settings.display_surface.value();
-  return display_surface == media::mojom::DisplayCaptureSurfaceType::BROWSER ||
-         display_surface == media::mojom::DisplayCaptureSurfaceType::WINDOW;
+  return settings.display_surface;
 }
 
 MediaStreamTrack* MakeMediaStreamTrack(ExecutionContext* context,
                                        MediaStreamComponent* component,
                                        base::OnceClosure callback,
                                        const String& descriptor_id) {
-  return IsFocusable(context, component)
-             ? MakeGarbageCollected<FocusableMediaStreamTrack>(
-                   context, component, std::move(callback), descriptor_id)
-             : MakeGarbageCollected<MediaStreamTrack>(context, component,
-                                                      std::move(callback));
+  DCHECK(context);
+  DCHECK(component);
+
+  const absl::optional<media::mojom::DisplayCaptureSurfaceType>
+      display_surface_type = GetDisplayCaptureType(component);
+  const bool is_tab_capture =
+      (display_surface_type ==
+       media::mojom::DisplayCaptureSurfaceType::BROWSER);
+  const bool is_window_capture =
+      (display_surface_type == media::mojom::DisplayCaptureSurfaceType::WINDOW);
+
+  if (is_tab_capture && RuntimeEnabledFeatures::RegionCaptureEnabled(context)) {
+    // Note:
+    // * ConditionalFocus is `implied_by` RegionCapture.
+    // * BrowserCaptureMediaStreamTrack a subclass of FocusableMediaStreamTrack.
+    // Therefore, tab-capture with ConditionalFocus/RegionCapture active
+    // instantiates a track on which focus() is exposed - as intended.
+    DCHECK(RuntimeEnabledFeatures::ConditionalFocusEnabled(context));
+    return MakeGarbageCollected<BrowserCaptureMediaStreamTrack>(
+        context, component, std::move(callback), descriptor_id);
+  } else if ((is_tab_capture || is_window_capture) &&
+             RuntimeEnabledFeatures::ConditionalFocusEnabled(context)) {
+    return MakeGarbageCollected<FocusableMediaStreamTrack>(
+        context, component, std::move(callback), descriptor_id);
+  } else {
+    return MakeGarbageCollected<MediaStreamTrack>(context, component,
+                                                  std::move(callback));
+  }
 }
 
 }  // namespace
diff --git a/third_party/blink/renderer/modules/mediastream/media_stream_track.cc b/third_party/blink/renderer/modules/mediastream/media_stream_track.cc
index f32ff46..0b973ca 100644
--- a/third_party/blink/renderer/modules/mediastream/media_stream_track.cc
+++ b/third_party/blink/renderer/modules/mediastream/media_stream_track.cc
@@ -199,6 +199,24 @@
     native_track->SetEnabled(component->Enabled());
 }
 
+void DidCloneMediaStreamTrack(MediaStreamComponent* original,
+                              MediaStreamComponent* clone) {
+  DCHECK(clone);
+  DCHECK(!clone->GetPlatformTrack());
+  DCHECK(clone->Source());
+
+  switch (clone->Source()->GetType()) {
+    case MediaStreamSource::kTypeAudio:
+      // TODO(crbug.com/704136): Use per thread task runner.
+      MediaStreamUtils::CreateNativeAudioMediaStreamTrack(
+          clone, Thread::MainThread()->GetTaskRunner());
+      break;
+    case MediaStreamSource::kTypeVideo:
+      CloneNativeVideoMediaStreamTrack(original, clone);
+      break;
+  }
+}
+
 }  // namespace
 
 MediaStreamTrack::MediaStreamTrack(ExecutionContext* context,
@@ -439,12 +457,15 @@
 
 MediaStreamTrack* MediaStreamTrack::clone(ScriptState* script_state) {
   SendLogMessage(String::Format("%s()", __func__));
-  MediaStreamComponent* cloned_component = Component()->Clone();
+
+  // Instantiate the clone.
   MediaStreamTrack* cloned_track = MakeGarbageCollected<MediaStreamTrack>(
-      ExecutionContext::From(script_state), cloned_component, ready_state_,
+      ExecutionContext::From(script_state), Component()->Clone(), ready_state_,
       base::DoNothing());
-  DidCloneMediaStreamTrack(Component(), cloned_component);
-  cloned_track->CloneImageCaptureFrom(*this);
+
+  // Copy state.
+  CloneInternal(cloned_track);
+
   return cloned_track;
 }
 
@@ -898,29 +919,17 @@
   EventTargetWithInlineData::Trace(visitor);
 }
 
-void MediaStreamTrack::DidCloneMediaStreamTrack(MediaStreamComponent* original,
-                                                MediaStreamComponent* clone) {
-  DCHECK(clone);
-  DCHECK(!clone->GetPlatformTrack());
-  DCHECK(clone->Source());
+void MediaStreamTrack::CloneInternal(MediaStreamTrack* cloned_track) {
+  DCHECK(cloned_track);
 
-  switch (clone->Source()->GetType()) {
-    case MediaStreamSource::kTypeAudio:
-      // TODO(crbug.com/704136): Use per thread task runner.
-      MediaStreamUtils::CreateNativeAudioMediaStreamTrack(
-          clone, Thread::MainThread()->GetTaskRunner());
-      break;
-    case MediaStreamSource::kTypeVideo:
-      CloneNativeVideoMediaStreamTrack(original, clone);
-      break;
+  DidCloneMediaStreamTrack(Component(), cloned_track->Component());
+
+  DCHECK(!cloned_track->image_capture_);
+  if (image_capture_) {
+    cloned_track->image_capture_ = image_capture_->Clone();
   }
 }
 
-void MediaStreamTrack::CloneImageCaptureFrom(const MediaStreamTrack& original) {
-  image_capture_ =
-      original.image_capture_ ? original.image_capture_->Clone() : nullptr;
-}
-
 void MediaStreamTrack::EnsureFeatureHandleForScheduler() {
   if (feature_handle_for_scheduler_)
     return;
diff --git a/third_party/blink/renderer/modules/mediastream/media_stream_track.h b/third_party/blink/renderer/modules/mediastream/media_stream_track.h
index 4a49daa4..54c82ee 100644
--- a/third_party/blink/renderer/modules/mediastream/media_stream_track.h
+++ b/third_party/blink/renderer/modules/mediastream/media_stream_track.h
@@ -138,11 +138,11 @@
   void Trace(Visitor*) const override;
 
  protected:
-  static void DidCloneMediaStreamTrack(MediaStreamComponent* original,
-                                       MediaStreamComponent* clone);
-
-  // Clones |image_capture_| from |original| into |this|.
-  void CloneImageCaptureFrom(const MediaStreamTrack& original);
+  // Given a partially built MediaStreamTrack, finishes the job of making it
+  // into a clone of |this|.
+  // Useful for sub-classes, as they need to clone both state from
+  // this class as well as of their own class.
+  void CloneInternal(MediaStreamTrack*);
 
  private:
   friend class CanvasCaptureMediaStreamTrack;
diff --git a/third_party/blink/renderer/modules/permissions/permission_status.cc b/third_party/blink/renderer/modules/permissions/permission_status.cc
index 81bee19..9196d62 100644
--- a/third_party/blink/renderer/modules/permissions/permission_status.cc
+++ b/third_party/blink/renderer/modules/permissions/permission_status.cc
@@ -77,6 +77,10 @@
   return PermissionStatusToString(status_);
 }
 
+String PermissionStatus::name() const {
+  return PermissionNameToString(descriptor_->name);
+}
+
 void PermissionStatus::StartListening() {
   DCHECK(!receiver_.is_bound());
   mojo::PendingRemote<mojom::blink::PermissionObserver> observer;
diff --git a/third_party/blink/renderer/modules/permissions/permission_status.h b/third_party/blink/renderer/modules/permissions/permission_status.h
index c911541..cd0c79b95 100644
--- a/third_party/blink/renderer/modules/permissions/permission_status.h
+++ b/third_party/blink/renderer/modules/permissions/permission_status.h
@@ -61,6 +61,8 @@
 
   String state() const;
 
+  String name() const;
+
   DEFINE_ATTRIBUTE_EVENT_LISTENER(change, kChange)
 
   void Trace(Visitor*) const override;
diff --git a/third_party/blink/renderer/modules/permissions/permission_status.idl b/third_party/blink/renderer/modules/permissions/permission_status.idl
index 160fa54a..10ec901 100644
--- a/third_party/blink/renderer/modules/permissions/permission_status.idl
+++ b/third_party/blink/renderer/modules/permissions/permission_status.idl
@@ -16,6 +16,7 @@
     Exposed=(Window,Worker),
     RuntimeEnabled=Permissions
 ] interface PermissionStatus : EventTarget {
+    readonly attribute PermissionName name;
     readonly attribute PermissionState state;
              attribute EventHandler onchange;
 };
diff --git a/third_party/blink/renderer/modules/permissions/permission_utils.cc b/third_party/blink/renderer/modules/permissions/permission_utils.cc
index 981d876..bd9b0e8a 100644
--- a/third_party/blink/renderer/modules/permissions/permission_utils.cc
+++ b/third_party/blink/renderer/modules/permissions/permission_utils.cc
@@ -53,6 +53,59 @@
   return "denied";
 }
 
+String PermissionNameToString(mojom::blink::PermissionName name) {
+  switch (name) {
+    case mojom::blink::PermissionName::GEOLOCATION:
+      return "geolocation";
+    case mojom::blink::PermissionName::NOTIFICATIONS:
+      return "notifications";
+    case mojom::blink::PermissionName::MIDI:
+      return "midi";
+    case mojom::blink::PermissionName::PROTECTED_MEDIA_IDENTIFIER:
+      return "protected_media_identifier";
+    case mojom::blink::PermissionName::DURABLE_STORAGE:
+      return "durable_storage";
+    case mojom::blink::PermissionName::AUDIO_CAPTURE:
+      return "audio_capture";
+    case mojom::blink::PermissionName::VIDEO_CAPTURE:
+      return "video_capture";
+    case mojom::blink::PermissionName::BACKGROUND_SYNC:
+      return "background_sync";
+    case mojom::blink::PermissionName::SENSORS:
+      return "sensors";
+    case mojom::blink::PermissionName::ACCESSIBILITY_EVENTS:
+      return "accessibility_events";
+    case mojom::blink::PermissionName::CLIPBOARD_READ:
+      return "clipboard_read";
+    case mojom::blink::PermissionName::CLIPBOARD_WRITE:
+      return "clipboard_write";
+    case mojom::blink::PermissionName::PAYMENT_HANDLER:
+      return "payment_handler";
+    case mojom::blink::PermissionName::BACKGROUND_FETCH:
+      return "background_fetch";
+    case mojom::blink::PermissionName::IDLE_DETECTION:
+      return "idle_detection";
+    case mojom::blink::PermissionName::PERIODIC_BACKGROUND_SYNC:
+      return "periodic_background_sync";
+    case mojom::blink::PermissionName::SCREEN_WAKE_LOCK:
+      return "screen_wake_lock";
+    case mojom::blink::PermissionName::SYSTEM_WAKE_LOCK:
+      return "system_wake_lock";
+    case mojom::blink::PermissionName::NFC:
+      return "nfc";
+    case mojom::blink::PermissionName::STORAGE_ACCESS:
+      return "storage_access";
+    case mojom::blink::PermissionName::WINDOW_PLACEMENT:
+      return "window_placement";
+    case mojom::blink::PermissionName::FONT_ACCESS:
+      return "font_access";
+    case mojom::blink::PermissionName::DISPLAY_CAPTURE:
+      return "display_capture";
+  }
+  NOTREACHED();
+  return "unknown";
+}
+
 PermissionDescriptorPtr CreatePermissionDescriptor(PermissionName name) {
   auto descriptor = MojoPermissionDescriptor::New();
   descriptor->name = name;
diff --git a/third_party/blink/renderer/modules/permissions/permission_utils.h b/third_party/blink/renderer/modules/permissions/permission_utils.h
index 07f8e74..5aebc09 100644
--- a/third_party/blink/renderer/modules/permissions/permission_utils.h
+++ b/third_party/blink/renderer/modules/permissions/permission_utils.h
@@ -23,6 +23,8 @@
 
 String PermissionStatusToString(mojom::blink::PermissionStatus);
 
+String PermissionNameToString(mojom::blink::PermissionName);
+
 mojom::blink::PermissionDescriptorPtr CreatePermissionDescriptor(
     mojom::blink::PermissionName);
 
diff --git a/third_party/blink/renderer/platform/graphics/canvas_resource_provider.cc b/third_party/blink/renderer/platform/graphics/canvas_resource_provider.cc
index cd6011c..10932ce 100644
--- a/third_party/blink/renderer/platform/graphics/canvas_resource_provider.cc
+++ b/third_party/blink/renderer/platform/graphics/canvas_resource_provider.cc
@@ -1001,6 +1001,18 @@
     shared_image_usage_flags &= ~gpu::SHARED_IMAGE_USAGE_SCANOUT;
   }
 
+#if defined(OS_MAC)
+  if ((shared_image_usage_flags & gpu::SHARED_IMAGE_USAGE_SCANOUT) &&
+      is_accelerated &&
+      adjusted_params.GetSkColorType() == kRGBA_8888_SkColorType) {
+    // GPU-accelerated scannout usage on Mac uses IOSurface.  Must switch from
+    // RGBA_8888 to BGRA_8888 in that case.
+    adjusted_params = CanvasResourceParams(adjusted_params.ColorSpace(),
+                                           kBGRA_8888_SkColorType,
+                                           adjusted_params.GetSkAlphaType());
+  }
+#endif
+
   auto provider = std::make_unique<CanvasResourceProviderSharedImage>(
       size, filter_quality, adjusted_params, context_provider_wrapper,
       is_origin_top_left, is_accelerated, skia_use_dawn,
diff --git a/third_party/blink/renderer/platform/graphics/canvas_resource_provider_test.cc b/third_party/blink/renderer/platform/graphics/canvas_resource_provider_test.cc
index 4971786..50933a0 100644
--- a/third_party/blink/renderer/platform/graphics/canvas_resource_provider_test.cc
+++ b/third_party/blink/renderer/platform/graphics/canvas_resource_provider_test.cc
@@ -4,6 +4,7 @@
 
 #include "third_party/blink/renderer/platform/graphics/canvas_resource_provider.h"
 
+#include "build/build_config.h"
 #include "components/viz/common/resources/release_callback.h"
 #include "components/viz/test/test_context_provider.h"
 #include "components/viz/test/test_gles2_interface.h"
@@ -93,8 +94,12 @@
   EXPECT_TRUE(provider->SupportsSingleBuffering());
   EXPECT_EQ(provider->ColorParams().ColorSpace(), kColorParams.ColorSpace());
   // As it is an CanvasResourceProviderSharedImage and an accelerated canvas, it
-  // will internally force it to kRGBA8
+  // will internally force it to RGBA8, or BGRA8 on MacOS
+#if defined(OS_MAC)
+  EXPECT_EQ(provider->ColorParams().GetSkColorType(), kBGRA_8888_SkColorType);
+#else
   EXPECT_EQ(provider->ColorParams().GetSkColorType(), kRGBA_8888_SkColorType);
+#endif
   EXPECT_EQ(provider->ColorParams().GetSkAlphaType(),
             kColorParams.GetSkAlphaType());
 
@@ -182,8 +187,12 @@
   EXPECT_FALSE(provider->SupportsSingleBuffering());
   EXPECT_EQ(provider->ColorParams().ColorSpace(), kColorParams.ColorSpace());
   // As it is an CanvasResourceProviderSharedImage and an accelerated canvas, it
-  // will internally force it to kRGBA8
+  // will internally force it to RGBA8, or BGRA8 on MacOS
+#if defined(OS_MAC)
+  EXPECT_EQ(provider->ColorParams().GetSkColorType(), kBGRA_8888_SkColorType);
+#else
   EXPECT_EQ(provider->ColorParams().GetSkColorType(), kRGBA_8888_SkColorType);
+#endif
   EXPECT_EQ(provider->ColorParams().GetSkAlphaType(),
             kColorParams.GetSkAlphaType());
 
@@ -364,8 +373,12 @@
   EXPECT_TRUE(provider->SupportsSingleBuffering());
   EXPECT_EQ(provider->ColorParams().ColorSpace(), kColorParams.ColorSpace());
   // As it is an CanvasResourceProviderSharedImage and an accelerated canvas, it
-  // will internally force it to kRGBA8
+  // will internally force it to RGBA8, or BGRA8 on MacOS
+#if defined(OS_MAC)
+  EXPECT_EQ(provider->ColorParams().GetSkColorType(), kBGRA_8888_SkColorType);
+#else
   EXPECT_EQ(provider->ColorParams().GetSkColorType(), kRGBA_8888_SkColorType);
+#endif
   EXPECT_EQ(provider->ColorParams().GetSkAlphaType(),
             kColorParams.GetSkAlphaType());
 
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index 227ebe54..904bd78 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -442,8 +442,8 @@
     {
       name: "ConditionalFocus",
       origin_trial_feature_name: "ConditionalFocus",
-      depends_on: ["GetDisplayMedia"],
       status: {"Android": "", "default": "experimental"},
+      implied_by: ["RegionCapture"],
     },
     {
       name: "ConsolidatedMovementXY",
@@ -1847,7 +1847,7 @@
     },
     {
       name: "RegionCapture",
-      status: "experimental",
+      status: {"Android": "", "default": "experimental"},
     },
     {
       name: "RemotePlayback",
diff --git a/third_party/blink/tools/blinkpy/web_tests/controllers/web_test_finder.py b/third_party/blink/tools/blinkpy/web_tests/controllers/web_test_finder.py
index 522d86a9..66fcb0cd 100644
--- a/third_party/blink/tools/blinkpy/web_tests/controllers/web_test_finder.py
+++ b/third_party/blink/tools/blinkpy/web_tests/controllers/web_test_finder.py
@@ -183,7 +183,8 @@
                     line = self._strip_comments(line)
                     if not line:
                         continue
-                    is_glob = line[-1] == '*' and line[-2] != '\\'
+                    is_glob = line[-1] == '*' and (len(line) == 1
+                                                   or line[-2] != '\\')
                     if line[0] == '-':
                         if is_glob:
                             negative_globs.append(line)
diff --git a/third_party/blink/tools/blinkpy/web_tests/stale_expectation_removal/builders.py b/third_party/blink/tools/blinkpy/web_tests/stale_expectation_removal/builders.py
index f7e47cf..0a20f1a 100644
--- a/third_party/blink/tools/blinkpy/web_tests/stale_expectation_removal/builders.py
+++ b/third_party/blink/tools/blinkpy/web_tests/stale_expectation_removal/builders.py
@@ -26,8 +26,13 @@
         }
 
     def GetFakeCiBuilders(self):
+        # Some of these are weird in that they're explicitly defined trybots
+        # instead of a mirror of a CI bot.
         return {
             # chromium.fyi
+            'linux-blink-optional-highdpi-rel-dummy': {
+                'linux-blink-optional-highdpi-rel',
+            },
             'linux-blink-rel-dummy': {
                 'linux-blink-rel',
                 'v8_linux_blink_rel',
@@ -47,6 +52,9 @@
             'mac11.0-blink-rel-dummy': {
                 'mac11.0-blink-rel',
             },
+            'mac11.0.arm64-blink-rel-dummy': {
+                'mac11.0.arm64-blink-rel',
+            },
             'WebKit Linux composite_after_paint Dummy Builder': {
                 'linux_layout_tests_composite_after_paint',
             },
@@ -59,9 +67,29 @@
             'win10.20h2-blink-rel-dummy': {
                 'win10.20h2-blink-rel',
             },
+            # tryserver.chromium.linux
+            # Explicit trybot.
+            'linux-blink-web-tests-force-accessibility-rel': {
+                'linux-blink-web-tests-force-accessibility-rel',
+            },
+            # Explicit trybot.
+            'linux-layout-tests-edit-ng': {
+                'linux-layout-tests-edit-ng',
+            },
         }
 
     def GetNonChromiumBuilders(self):
         return {
+            'devtools_frontend_linux_blink_light_rel',
+            'devtools_frontend_linux_blink_rel',
+            'DevTools Linux',
             'DevTools Linux (chromium)',
+            # Could be used in the future, but has never run any builds.
+            'linux-exp-code-coverage',
+            'ToTMacOfficial',
+            'V8 Blink Linux',
+            'V8 Blink Linux Debug',
+            'V8 Blink Linux Future',
+            'V8 Blink Mac',
+            'V8 Blink Win',
         }
diff --git a/third_party/blink/tools/blinkpy/web_tests/stale_expectation_removal/constants.py b/third_party/blink/tools/blinkpy/web_tests/stale_expectation_removal/constants.py
new file mode 100644
index 0000000..d1b22ef
--- /dev/null
+++ b/third_party/blink/tools/blinkpy/web_tests/stale_expectation_removal/constants.py
@@ -0,0 +1,10 @@
+# Copyright 2021 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+"""Constants for stale expectation removal."""
+
+import os
+
+WEB_TEST_ROOT_DIR = os.path.realpath(
+    os.path.join(os.path.dirname(__file__), '..', '..', '..', '..',
+                 'web_tests'))
diff --git a/third_party/blink/tools/blinkpy/web_tests/stale_expectation_removal/expectations.py b/third_party/blink/tools/blinkpy/web_tests/stale_expectation_removal/expectations.py
index d6f726c..4bdbadb 100644
--- a/third_party/blink/tools/blinkpy/web_tests/stale_expectation_removal/expectations.py
+++ b/third_party/blink/tools/blinkpy/web_tests/stale_expectation_removal/expectations.py
@@ -5,13 +5,12 @@
 
 import os
 
+from blinkpy.web_tests.stale_expectation_removal import constants
+
 from unexpected_passes_common import expectations
 
-WEB_TEST_ROOT_DIR = os.path.realpath(
-    os.path.join(os.path.dirname(__file__), '..', '..', '..', '..',
-                 'web_tests'))
-
-MAIN_EXPECTATION_FILE = os.path.join(WEB_TEST_ROOT_DIR, 'TestExpectations')
+MAIN_EXPECTATION_FILE = os.path.join(constants.WEB_TEST_ROOT_DIR,
+                                     'TestExpectations')
 
 TOP_LEVEL_EXPECTATION_FILES = {
     'ASANExpectations',
@@ -20,6 +19,7 @@
     # NeverFixTests omitted since they're never expected to be
     # unsuppressed.
     'SlowTests',
+    'TestExpectations',
     'W3CImportExpectations',
     'WPTOverrideExpectations',
     'WebDriverExpectations',
@@ -43,8 +43,8 @@
             self._expectation_filepaths = []
             for ef in self._GetTopLevelExpectationFiles():
                 self._expectation_filepaths.append(
-                    os.path.join(WEB_TEST_ROOT_DIR, ef))
-            flag_directory = os.path.join(WEB_TEST_ROOT_DIR,
+                    os.path.join(constants.WEB_TEST_ROOT_DIR, ef))
+            flag_directory = os.path.join(constants.WEB_TEST_ROOT_DIR,
                                           'FlagExpectations')
             for ef in self._GetFlagSpecificExpectationFiles():
                 self._expectation_filepaths.append(
@@ -58,7 +58,7 @@
     def _GetFlagSpecificExpectationFiles(self):
         if self._flag_specific_expectation_files is None:
             self._flag_specific_expectation_files = set()
-            flag_directory = os.path.join(WEB_TEST_ROOT_DIR,
+            flag_directory = os.path.join(constants.WEB_TEST_ROOT_DIR,
                                           'FlagExpectations')
             for ef in os.listdir(flag_directory):
                 if ef != 'README.txt':
diff --git a/third_party/blink/tools/blinkpy/web_tests/stale_expectation_removal/queries.py b/third_party/blink/tools/blinkpy/web_tests/stale_expectation_removal/queries.py
new file mode 100644
index 0000000..40bfefb
--- /dev/null
+++ b/third_party/blink/tools/blinkpy/web_tests/stale_expectation_removal/queries.py
@@ -0,0 +1,185 @@
+# 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.
+"""Web test-specific impl of the unexpected passes' queries module."""
+
+import os
+import posixpath
+
+from blinkpy.web_tests.stale_expectation_removal import constants
+
+from unexpected_passes_common import queries as queries_module
+
+# The target number of results/rows per query when running in large query mode.
+# Higher values = longer individual query times and higher chances of running
+# out of memory in BigQuery. Lower values = more parallelization overhead and
+# more issues with rate limit errors.
+TARGET_RESULTS_PER_QUERY = 20000
+
+# This query gets us all results for tests that have had results with a
+# Failure, Timeout, or Crash expectation in the past |@num_samples| builds on
+# |@builder_name|. Whether these are CI or try results depends on whether
+# |builder_type| is "ci" or "try".
+BQ_QUERY_TEMPLATE = """\
+WITH
+  builds AS (
+    SELECT
+      DISTINCT exported.id build_inv_id,
+      partition_time
+    FROM `chrome-luci-data.chromium.blink_web_tests_{builder_type}_test_results` tr
+    WHERE
+      exported.realm = "chromium:{builder_type}"
+      AND STRUCT("builder", @builder_name) IN UNNEST(variant)
+    ORDER BY partition_time DESC
+    LIMIT @num_builds
+  ),
+  results AS (
+    SELECT
+      exported.id,
+      test_id,
+      status,
+      (
+        SELECT value
+        FROM tr.tags
+        WHERE key = "step_name") as step_name,
+      ARRAY(
+        SELECT value
+        FROM tr.tags
+        WHERE key = "typ_tag") as typ_tags,
+      ARRAY(
+        SELECT value
+        FROM tr.tags
+        WHERE key = "raw_typ_expectation") as typ_expectations,
+      ARRAY(
+        SELECT value
+        FROM tr.tags
+        WHERE key = "web_tests_used_expectations_file") as expectation_files
+    FROM
+      `chrome-luci-data.chromium.blink_web_tests_{builder_type}_test_results` tr,
+      builds b
+    WHERE
+      exported.id = build_inv_id
+      AND status != "SKIP"
+      {test_filter_clause}
+  )
+SELECT *
+FROM results
+WHERE
+  "Failure" IN UNNEST(typ_expectations)
+  OR "Crash" IN UNNEST(typ_expectations)
+  OR "Timeout" IN UNNEST(typ_expectations)
+"""
+
+# Very similar to above, but used to get the names of tests that are of
+# interest for use as a filter.
+TEST_FILTER_QUERY_TEMPLATE = """\
+WITH
+  builds AS (
+    SELECT
+      DISTINCT exported.id build_inv_id,
+      partition_time
+    FROM
+      `chrome-luci-data.chromium.blink_web_tests_{builder_type}_test_results` tr
+    WHERE
+      exported.realm = "chromium:{builder_type}"
+      AND STRUCT("builder", @builder_name) IN UNNEST(variant)
+    ORDER BY partition_time DESC
+    LIMIT 50
+  ),
+  results AS (
+    SELECT
+      exported.id,
+      test_id,
+      ARRAY(
+        SELECT value
+        FROM tr.tags
+        WHERE key = "raw_typ_expectation") as typ_expectations
+    FROM
+      `chrome-luci-data.chromium.blink_web_tests_{builder_type}_test_results` tr,
+      builds b
+    WHERE
+      exported.id = build_inv_id
+      AND status != "SKIP"
+  )
+SELECT DISTINCT r.test_id
+FROM results r
+WHERE
+  "Failure" IN UNNEST(typ_expectations)
+  OR "Crash" IN UNNEST(typ_expectations)
+  OR "Timeout" IN UNNEST(typ_expectations)
+"""
+
+KNOWN_TEST_ID_PREFIXES = [
+    'ninja://:blink_web_tests/',
+    'ninja://:webgpu_blink_web_tests',
+]
+
+
+class WebTestBigQueryQuerier(queries_module.BigQueryQuerier):
+    def _GetRelevantExpectationFilesForQueryResult(self, query_result):
+        # Files in the query are either relative to the web tests directory or
+        # are an absolute path. The paths are always POSIX-style. We don't
+        # handle absolute paths since those typically point to temporary files
+        # which will not exist locally.
+        filepaths = []
+        for f in query_result.get('expectation_files', []):
+            if posixpath.isabs(f):
+                continue
+            f = f.replace('/', os.sep)
+            f = os.path.join(constants.WEB_TEST_ROOT_DIR, f)
+            filepaths.append(f)
+        return filepaths
+
+    def _ShouldSkipOverResult(self, _):
+        return False
+
+    def _GetQueryGeneratorForBuilder(self, builder, builder_type):
+        # Look for all tests.
+        if not self._large_query_mode:
+            return WebTestFixedQueryGenerator(builder_type, '')
+
+        query = TEST_FILTER_QUERY_TEMPLATE.format(builder_type=builder_type)
+        query_results = self._RunBigQueryCommandsForJsonOutput(
+            query, {'': {
+                'builder_name': builder
+            }})
+        test_ids = ['"%s"' % r['test_id'] for r in query_results]
+
+        if not test_ids:
+            return None
+
+        # Only consider specific test cases that were found to have active
+        # expectations in the above query. Also perform any initial query
+        # splitting.
+        target_num_ids = TARGET_RESULTS_PER_QUERY / self._num_samples
+        return WebTestSplitQueryGenerator(builder_type, test_ids,
+                                          target_num_ids)
+
+    def _StripPrefixFromTestId(self, test_id):
+        # Web test IDs provided by ResultDB are the test name known by the test
+        # runner prefixed by one of the following:
+        #   "ninja://:blink_web_tests/"
+        #   "ninja://:webgpu_blink_web_tests/"
+        for prefix in KNOWN_TEST_ID_PREFIXES:
+            if test_id.startswith(prefix):
+                return test_id.replace(prefix, '')
+        raise RuntimeError('Unable to strip prefix from test ID %s' % test_id)
+
+
+class WebTestFixedQueryGenerator(queries_module.FixedQueryGenerator):
+    def GetQueries(self):
+        return QueryGeneratorImpl(self.GetClauses(), self._builder_type)
+
+
+class WebTestSplitQueryGenerator(queries_module.SplitQueryGenerator):
+    def GetQueries(self):
+        return QueryGeneratorImpl(self.GetClauses(), self._builder_type)
+
+
+def QueryGeneratorImpl(test_filter_clauses, builder_type):
+    queries = []
+    for tfc in test_filter_clauses:
+        queries.append(
+            BQ_QUERY_TEMPLATE.format(builder_type=builder_type,
+                                     test_filter_clause=tfc))
+    return queries
diff --git a/third_party/blink/tools/blinkpy/web_tests/stale_expectation_removal/queries_unittest.py b/third_party/blink/tools/blinkpy/web_tests/stale_expectation_removal/queries_unittest.py
new file mode 100755
index 0000000..124b64a
--- /dev/null
+++ b/third_party/blink/tools/blinkpy/web_tests/stale_expectation_removal/queries_unittest.py
@@ -0,0 +1,116 @@
+#!/usr/bin/env vpython3
+# Copyright 2021 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import os
+import unittest
+import six
+
+if six.PY3:
+    import unittest.mock as mock
+
+from blinkpy.web_tests.stale_expectation_removal import constants
+from blinkpy.web_tests.stale_expectation_removal import queries
+from blinkpy.web_tests.stale_expectation_removal import unittest_utils as wt_uu
+
+
+class GetRelevantExpectationFilesForQueryResultUnittest(unittest.TestCase):
+    def testNoFiles(self):
+        """Tests that no reported expectation files are handled properly."""
+        query_result = {}
+        querier = wt_uu.CreateGenericWebTestQuerier()
+        self.assertEqual(
+            querier._GetRelevantExpectationFilesForQueryResult(query_result),
+            [])
+
+    def testAbsolutePath(self):
+        """Tests that absolute paths are ignored."""
+        query_result = {
+            'expectation_files': ['/posix/path', '/c:/windows/path']
+        }
+        querier = wt_uu.CreateGenericWebTestQuerier()
+        self.assertEqual(
+            querier._GetRelevantExpectationFilesForQueryResult(query_result),
+            [])
+
+    def testRelativePath(self):
+        """Tests that relative paths are properly reconstructed."""
+        query_result = {
+            'expectation_files':
+            ['TestExpectations', 'flag-specific/someflag']
+        }
+        querier = wt_uu.CreateGenericWebTestQuerier()
+        expected_files = [
+            os.path.join(constants.WEB_TEST_ROOT_DIR, 'TestExpectations'),
+            os.path.join(constants.WEB_TEST_ROOT_DIR, 'flag-specific',
+                         'someflag'),
+        ]
+        self.assertEqual(
+            querier._GetRelevantExpectationFilesForQueryResult(query_result),
+            expected_files)
+
+
+@unittest.skipIf(six.PY2, 'Script and unittest are Python 3-only')
+class GetQueryGeneratorForBuilderUnittest(unittest.TestCase):
+    def setUp(self):
+        self._query_patcher = mock.patch(
+            'blinkpy.web_tests.stale_expectation_removal.queries.'
+            'WebTestBigQueryQuerier._RunBigQueryCommandsForJsonOutput')
+        self._query_mock = self._query_patcher.start()
+        self.addCleanup(self._query_patcher.stop)
+
+    def testNoLargeQueryMode(self):
+        """Tests that the expected clause is returned in normal mode."""
+        querier = wt_uu.CreateGenericWebTestQuerier()
+        query_generator = querier._GetQueryGeneratorForBuilder('', '')
+        self.assertEqual(len(query_generator.GetClauses()), 1)
+        self.assertEqual(query_generator.GetClauses()[0], '')
+        self.assertIsInstance(query_generator,
+                              queries.WebTestFixedQueryGenerator)
+        self._query_mock.assert_not_called()
+
+    def testLargeQueryModeNoTests(self):
+        """Tests that a special value is returned if no tests are found."""
+        querier = wt_uu.CreateGenericWebTestQuerier(large_query_mode=True)
+        self._query_mock.return_value = []
+        query_generator = querier._GetQueryGeneratorForBuilder('', '')
+        self.assertIsNone(query_generator)
+        self._query_mock.assert_called_once()
+
+    def testLargeQueryModeFoundTests(self):
+        """Tests that a clause containing found tests is returned."""
+        querier = wt_uu.CreateGenericWebTestQuerier(large_query_mode=True)
+        self._query_mock.return_value = [
+            {
+                'test_id': 'foo_test',
+            },
+            {
+                'test_id': 'bar_test',
+            },
+        ]
+        query_generator = querier._GetQueryGeneratorForBuilder('', '')
+        self.assertEqual(query_generator.GetClauses(),
+                         ['AND test_id IN UNNEST(["foo_test", "bar_test"])'])
+        self.assertIsInstance(query_generator,
+                              queries.WebTestSplitQueryGenerator)
+
+
+class StripPrefixFromTestIdUnittest(unittest.TestCase):
+    def testUnknownPrefix(self):
+        """Tests that an error is raised if an unknown prefix is found."""
+        querier = wt_uu.CreateGenericWebTestQuerier()
+        with self.assertRaises(RuntimeError):
+            querier._StripPrefixFromTestId('foobar')
+
+    def testKnownPrefixes(self):
+        """Tests that all known prefixes are properly stripped."""
+        querier = wt_uu.CreateGenericWebTestQuerier()
+        test_ids = [prefix + 'a' for prefix in queries.KNOWN_TEST_ID_PREFIXES]
+        for t in test_ids:
+            stripped = querier._StripPrefixFromTestId(t)
+            self.assertEqual(stripped, 'a')
+
+
+if __name__ == '__main__':
+    unittest.main(verbosity=2)
diff --git a/third_party/blink/tools/blinkpy/web_tests/stale_expectation_removal/remove_stale_expectations.py b/third_party/blink/tools/blinkpy/web_tests/stale_expectation_removal/remove_stale_expectations.py
index ac07e380..0c3a887b 100644
--- a/third_party/blink/tools/blinkpy/web_tests/stale_expectation_removal/remove_stale_expectations.py
+++ b/third_party/blink/tools/blinkpy/web_tests/stale_expectation_removal/remove_stale_expectations.py
@@ -10,7 +10,9 @@
 
 from blinkpy.web_tests.stale_expectation_removal import builders
 from blinkpy.web_tests.stale_expectation_removal import expectations
+from blinkpy.web_tests.stale_expectation_removal import queries
 from unexpected_passes_common import builders as common_builders
+from unexpected_passes_common import result_output
 
 
 def ParseArgs():
@@ -89,6 +91,23 @@
     test_expectation_map = expectations_instance.CreateTestExpectationMap(
         expectations_instance.GetExpectationFilepaths(), None)
     ci_builders = builders_instance.GetCiBuilders(None)
+
+    querier = queries.WebTestBigQueryQuerier(None, args.project,
+                                             args.num_samples,
+                                             args.large_query_mode)
+    # Unmatched results are mainly useful for script maintainers, as they don't
+    # provide any additional information for the purposes of finding
+    # unexpectedly passing tests or unused expectations.
+    unmatched = querier.FillExpectationMapForCiBuilders(
+        test_expectation_map, ci_builders)
+    try_builders = builders_instance.GetTryBuilders(ci_builders)
+    unmatched.update(
+        querier.FillExpectationMapForTryBuilders(test_expectation_map,
+                                                 try_builders))
+    unused_expectations = test_expectation_map.FilterOutUnusedExpectations()
+    stale, semi_stale, active = test_expectation_map.SplitByStaleness()
+    result_output.OutputResults(stale, semi_stale, active, unmatched,
+                                unused_expectations, args.output_format)
     return 0
 
 
diff --git a/third_party/blink/tools/blinkpy/web_tests/stale_expectation_removal/unittest_utils.py b/third_party/blink/tools/blinkpy/web_tests/stale_expectation_removal/unittest_utils.py
new file mode 100644
index 0000000..7527bac
--- /dev/null
+++ b/third_party/blink/tools/blinkpy/web_tests/stale_expectation_removal/unittest_utils.py
@@ -0,0 +1,12 @@
+# 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.
+
+from blinkpy.web_tests.stale_expectation_removal import queries
+from unexpected_passes_common import unittest_utils as uu
+
+
+def CreateGenericWebTestQuerier(*args, **kwargs):
+    return uu.CreateGenericQuerier(cls=queries.WebTestBigQueryQuerier,
+                                   *args,
+                                   **kwargs)
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 0a07e2c..0c958156 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -420,6 +420,9 @@
 crbug.com/1170337 [ Mac10.13 ] external/wpt/html/canvas/offscreen/text/2d.text.draw.fill.maxWidth.NaN.worker.html [ Timeout ]
 crbug.com/1170337 [ Mac10.13 ] external/wpt/html/canvas/offscreen/text/2d.text.draw.fill.maxWidth.zero.worker.html [ Skip Timeout ]
 
+# Off by one pixel error on ref test
+crbug.com/1259367 [ Mac ] printing/offscreencanvas-webgl-printing.html [ Failure ]
+
 # Subpixel differences due to compositing on Mac10.14+.
 crbug.com/997202 [ Mac10.14 ] external/wpt/svg/extensibility/foreignObject/foreign-object-scale-scroll.html [ Failure ]
 crbug.com/997202 [ Mac10.15 ] external/wpt/svg/extensibility/foreignObject/foreign-object-scale-scroll.html [ Failure ]
@@ -1713,8 +1716,6 @@
 crbug.com/1234199 [ Mac ] external/wpt/html/semantics/embedded-content/the-object-element/object-events.html [ Skip ]
 crbug.com/1232504 [ Mac11.0 ] external/wpt/html/semantics/embedded-content/media-elements/interfaces/TextTrackCue/endTime.html [ Failure Timeout ]
 crbug.com/1232504 [ Mac11.0 ] external/wpt/html/semantics/embedded-content/media-elements/preserves-pitch.html [ Timeout ]
-crbug.com/1240701 virtual/dialogfocus-old-behavior/external/wpt/html/semantics/interactive-elements/the-dialog-element/modal-dialog-display-contents.html [ Failure ]
-crbug.com/1240701 external/wpt/html/semantics/interactive-elements/the-dialog-element/modal-dialog-display-contents.html [ Failure ]
 
 # XML nodes aren't match by .class selectors:
 crbug.com/649444 external/wpt/css/selectors/xml-class-selector.xml [ Failure ]
@@ -7052,7 +7053,7 @@
 crbug.com/1249176 [ Mac11-arm64 ] external/wpt/css/css-shapes/shape-outside/shape-image/shape-image-014.html [ Failure Pass ]
 crbug.com/1249176 [ Mac11-arm64 ] virtual/gpu-rasterization/images/color-profile-image-filter-all.html [ Failure Pass ]
 crbug.com/1249176 [ Mac11-arm64 ] virtual/scalefactor200withoutzoom/external/wpt/largest-contentful-paint/multiple-redirects-TAO.html [ Failure Pass ]
-crbug.com/1249176 [ Mac11-arm64 ] virtual/webrtc-wpt-plan-b/external/wpt/webrtc/protocol/split.https.html [ Timeout Pass ]
+crbug.com/1249176 [ Mac11-arm64 ] virtual/webrtc-wpt-plan-b/external/wpt/webrtc/protocol/split.https.html [ Pass Timeout ]
 
 # mac-arm CI 10-01-2021
 crbug.com/1249176 [ Mac11-arm64 ] editing/text-iterator/beforematch-async.html [ Failure ]
@@ -7221,4 +7222,5 @@
 crbug.com/1259133 [ Mac10.14 ] virtual/no-alloc-direct-call/external/wpt/html/canvas/offscreen/manual/the-offscreen-canvas/offscreencanvas.commit.html [ Failure Pass ]
 crbug.com/1259167 [ Mac ] virtual/threaded/external/wpt/scroll-animations/scroll-animation-inactive-timeline.html [ Failure Pass ]
 crbug.com/1259188 [ Mac10.14 ] virtual/gpu-rasterization/images/color-profile-animate.html [ Failure Pass ]
-crbug.com/1259255 [ Win7 ] virtual/scroll-unification/fast/events/mouse-cursor-no-mousemove.html [ Failure Pass ]
+crbug.com/1197465 [ Win7 ] virtual/scroll-unification/fast/events/mouse-cursor-no-mousemove.html [ Failure Pass ]
+crbug.com/1259277 [ Debug Linux ] virtual/scroll-unification/fast/events/message-port-multi.html [ Failure Pass ]
diff --git a/third_party/blink/web_tests/android/WebLayerWPTOverrideExpectations b/third_party/blink/web_tests/android/WebLayerWPTOverrideExpectations
index 1e759a8..59d5a73 100644
--- a/third_party/blink/web_tests/android/WebLayerWPTOverrideExpectations
+++ b/third_party/blink/web_tests/android/WebLayerWPTOverrideExpectations
@@ -807,6 +807,7 @@
 crbug.com/1050754 external/wpt/external/wpt/css/css-shapes/shape-outside/values/shape-margin-001.html [ Failure ]
 crbug.com/1050754 external/wpt/external/wpt/css/css-shapes/shape-outside/values/shape-outside-shape-arguments-000.html [ Failure ]
 crbug.com/1050754 external/wpt/external/wpt/css/cssom-view/scrollintoview.html [ Failure ]
+crbug.com/1050754 external/wpt/external/wpt/editing/other/editable-state-and-focus-in-shadow-dom-in-designMode.tentative.html [ Timeout ]
 crbug.com/1050754 external/wpt/external/wpt/fetch/api/basic/status.h2.any.html [ Failure ]
 crbug.com/1050754 external/wpt/external/wpt/fetch/api/basic/status.h2.any.worker.html [ Failure ]
 crbug.com/1050754 external/wpt/external/wpt/fetch/api/headers/headers-normalize.any.sharedworker.html [ Failure ]
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 31e27a5..4464ce6d 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
@@ -639,6 +639,13 @@
      ]
     },
     "css-inline": {
+     "change-inline-change-abspos-crash.html": [
+      "7752cc6592da6b017788157372329a5cb591d63e",
+      [
+       null,
+       {}
+      ]
+     ],
      "inline-002-crash.html": [
       "a10eee9907f3ca27b3265452aeb19e2b9710b55d",
       [
@@ -78036,6 +78043,73 @@
        {}
       ]
      ],
+     "grid": {
+      "grid-container-fragmentation-001.html": [
+       "0b954f2814850a23cf77a4f0595c7a49235e33f6",
+       [
+        null,
+        [
+         [
+          "/css/reference/ref-filled-green-100px-square.xht",
+          "=="
+         ]
+        ],
+        {}
+       ]
+      ],
+      "grid-container-fragmentation-002.html": [
+       "aec5085991c0b0f4df95b7bf6b1e354f15be92e5",
+       [
+        null,
+        [
+         [
+          "/css/reference/ref-filled-green-100px-square.xht",
+          "=="
+         ]
+        ],
+        {}
+       ]
+      ],
+      "grid-container-fragmentation-003.html": [
+       "659d9eaac5fe52785356d0e80bf37e18c2dd1e4b",
+       [
+        null,
+        [
+         [
+          "/css/reference/ref-filled-green-100px-square.xht",
+          "=="
+         ]
+        ],
+        {}
+       ]
+      ],
+      "grid-container-fragmentation-004.html": [
+       "3e276060711faeb56d6576e4748b67c2334ae0d7",
+       [
+        null,
+        [
+         [
+          "/css/reference/ref-filled-green-100px-square.xht",
+          "=="
+         ]
+        ],
+        {}
+       ]
+      ],
+      "grid-container-fragmentation-005.html": [
+       "a110275ff87dff4300a57322e4a0bc01f5eae20a",
+       [
+        null,
+        [
+         [
+          "/css/reference/ref-filled-green-100px-square.xht",
+          "=="
+         ]
+        ],
+        {}
+       ]
+      ]
+     },
      "ink-overflow-002.html": [
       "8af605efea87b618007641729bd3b579fcb654eb",
       [
@@ -79115,6 +79189,19 @@
        {}
       ]
      ],
+     "out-of-flow-in-multicolumn-075.html": [
+      "34cf1ea74dda8a56c49a59ded805af83de84a75f",
+      [
+       null,
+       [
+        [
+         "/css/reference/ref-filled-green-100px-square.xht",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
      "overflow-clip-000.html": [
       "72b10f5cdd3092a042f1f90bff04e9428d61608e",
       [
@@ -229854,18 +229941,6 @@
       "ffac42763f9f7221b78b6d685e49ce7854537b20",
       []
      ],
-     "layer-basic-expected.txt": [
-      "0cc2db7f0a0591080afbd6c80738c0d9c780385f",
-      []
-     ],
-     "layer-import-expected.txt": [
-      "1fc7e1b2e9146c0291d5797f452844a6f2ac4a61",
-      []
-     ],
-     "layer-keyframes-override-expected.txt": [
-      "1d5989fa0aa52c39c6d888fd58118f07007bd03d",
-      []
-     ],
      "layer-stylesheet-sharing-ref.html": [
       "fe004e5bda989248b67aad3b5c20f8a280447914",
       []
@@ -263914,7 +263989,7 @@
      }
     },
     "historical-expected.txt": [
-     "4c0f1de272a71260bffdf9500383d81fafba50e2",
+     "4303f9b60573df10f4920ee4c83fea4795cb7433",
      []
     ],
     "idlharness.window_exclude=Node-expected.txt": [
@@ -264657,7 +264732,7 @@
     ],
     "include": {
      "editor-test-utils.js": [
-      "3ca014a472fa45991e38d565a10fe9dc9afa6ff8",
+      "9ce5ff12226783a4147c2b344962c5a6def26727",
       []
      ],
      "implementation.js": [
@@ -264702,6 +264777,10 @@
       "e8a01d24d6815990f64169aff48b2977567bb3be",
       []
      ],
+     "editable-state-and-focus-in-shadow-dom-in-designMode.tentative-expected.txt": [
+      "3b6b947a443781c22448f24ddb3ea7865b2e8d48",
+      []
+     ],
      "editing-around-select-element.tentative_delete-expected.txt": [
       "6c018e9e1a8bcff1c72b6cd7b04b2c31938bfe92",
       []
@@ -294850,7 +294929,7 @@
      []
     ],
     "testharness.js": [
-     "b4bed2b31fe327247cc505c456d3a7c38d2bd320",
+     "6fe5580ae7a1ab1a8439d8accfd74b8c92c3cf2b",
      []
     ],
     "testharness.js.headers": [
@@ -341539,49 +341618,49 @@
       ]
      ],
      "layer-basic.html": [
-      "f92e142f91759049bae0f996866aff3cfb94c7ca",
+      "e214bffc25f5fc3b56480ec9c8704a80bf109c17",
       [
        null,
        {}
       ]
      ],
      "layer-counter-style-override.html": [
-      "7ffd3583da8b2d3d6eaee2b9f8defc8570d32081",
+      "1720898457b52344b270b28f435ebbaff5600923",
       [
        null,
        {}
       ]
      ],
      "layer-font-face-override.html": [
-      "8275b0815a241fdfcaac57aca4aba06b144afe8d",
+      "d35caca0129d16585f483b70d0fc746de189f29e",
       [
        null,
        {}
       ]
      ],
      "layer-import.html": [
-      "935659ac1a3eb7d3d015138e86b2d81238d111c9",
+      "0406f02927a87654b7636e01469488eb32e13e84",
       [
        null,
        {}
       ]
      ],
      "layer-keyframes-override.html": [
-      "f8963031826564d474bca1fcb818835d41d280b2",
+      "d0f4044f1e7f4610b7cf9f08f9012d9483c6f2dc",
       [
        null,
        {}
       ]
      ],
      "layer-property-override.html": [
-      "f0f8d833ac2e32225baa256a9afd3db5562f7a1a",
+      "9d3f9cb926a22b7c78d29c5510ab1ab56294bd78",
       [
        null,
        {}
       ]
      ],
      "layer-scroll-timeline-override.html": [
-      "9a50914e74448739b37f0b24193451e6a90995ef",
+      "59b8590b5f19277acc719600c99136aef2e532ca",
       [
        null,
        {}
@@ -341618,7 +341697,7 @@
       ]
      },
      "presentational-hints-cascade.html": [
-      "729dc71666a903f9368f8cc24f4ad6836af24fdc",
+      "c3188fd0d7bcd7085094e285c62c3fd8f8e213d8",
       [
        null,
        {}
@@ -369443,7 +369522,7 @@
      ]
     },
     "historical.html": [
-     "8a71e364e5ef30a0f9d9621d3eb018a34d42ae4c",
+     "7ef7e8032c7f46d66cb24db053949c38392fe288",
      [
       null,
       {}
@@ -372095,6 +372174,15 @@
        }
       ]
      ],
+     "editable-state-and-focus-in-shadow-dom-in-designMode.tentative.html": [
+      "88e6d291299c2fec70b91d5459c88ac855381f75",
+      [
+       null,
+       {
+        "testdriver": true
+       }
+      ]
+     ],
      "editing-around-select-element.tentative.html": [
       "9182216efd715112932755a1ecc56a6cb3c3adf3",
       [
diff --git a/third_party/blink/web_tests/external/wpt/css/css-contain/content-visibility/content-visibility-hit-test-contents-crash.html b/third_party/blink/web_tests/external/wpt/css/css-contain/content-visibility/content-visibility-hit-test-contents-crash.html
new file mode 100644
index 0000000..737d2af
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-contain/content-visibility/content-visibility-hit-test-contents-crash.html
@@ -0,0 +1,27 @@
+<!doctype html>
+<link rel=author name="Vladimir Levin" href="mailto:vmpstr@chromium.org">
+<link rel=assert content="Hit testing around hidden elements should not crash regardless of contents">
+
+<style>
+.box { width: 100px; height: 100px; border: 1px solid black; }
+.hidden { content-visibility: hidden }
+</style>
+
+<div id=container class="box hidden">
+  content
+  <dialog id=dialog>
+    dialog
+    <div id=inner></div>
+  </dialog>
+</div>
+text
+
+<script>
+function runTest() {
+  inner.getBoundingClientRect();
+  document.elementFromPoint(20, 109);
+  document.elementFromPoint(20, 20);
+}
+
+onload = () => requestAnimationFrame(() => requestAnimationFrame(runTest));
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-highlight-api/Highlight-type-attribute.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-highlight-api/Highlight-type-attribute.tentative.html
new file mode 100644
index 0000000..97e3aaa3
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-highlight-api/Highlight-type-attribute.tentative.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<title> Highlight type attribute tests </title>
+<link rel="help" href="https://drafts.csswg.org/css-highlight-api-1/">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+  test(() => {
+    let customHighlight = new Highlight();
+    assert_equals(customHighlight.type, "highlight", 'Highlight uses \"highlight\" as default type.');
+
+    customHighlight.type = "type-not-listed-in-HighlightType-enum";
+    assert_equals(customHighlight.type, "highlight", 'Highlight type doesn\'t change after assigning an invalid value.');
+
+    customHighlight.type = "spelling-error";
+    assert_equals(customHighlight.type, "spelling-error", 'Highlight type changes to the assigned value if it\'s part of the HighlightType enum (\"spelling-error\"").');
+
+    customHighlight.type = "grammar-error";
+    assert_equals(customHighlight.type, "grammar-error", 'Highlight type changes to the assigned value if it\'s part of the HighlightType enum (\"grammar-error\").');
+  }, 'Highlight has a mutable \'type\' attribute that is a HighlightType enum.');
+</script>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/dom/historical-expected.txt b/third_party/blink/web_tests/external/wpt/dom/historical-expected.txt
index 4c0f1de..4303f9b6 100644
--- a/third_party/blink/web_tests/external/wpt/dom/historical-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/dom/historical-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 76 tests; 72 PASS, 4 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 77 tests; 72 PASS, 5 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS Historical DOM features must be removed: DOMConfiguration
 PASS Historical DOM features must be removed: DOMCursor
 FAIL Historical DOM features must be removed: DOMError assert_equals: expected (undefined) undefined but got (function) function "function DOMError() { [native code] }"
@@ -76,5 +76,6 @@
 PASS Event should not have this constant: SELECT
 PASS Event should not have this constant: CHANGE
 PASS Event.prototype should not have this property: getPreventDefault
+FAIL Event.prototype should not have this property: path Illegal invocation
 Harness: the test ran to completion.
 
diff --git a/third_party/blink/web_tests/external/wpt/dom/historical.html b/third_party/blink/web_tests/external/wpt/dom/historical.html
index 8a71e36..7ef7e80 100644
--- a/third_party/blink/web_tests/external/wpt/dom/historical.html
+++ b/third_party/blink/web_tests/external/wpt/dom/historical.html
@@ -190,6 +190,7 @@
 
 var EventPrototypeRemoved = [
   "getPreventDefault",
+  "path"
 ]
 EventPrototypeRemoved.forEach(name => {
   test(() => {
diff --git a/third_party/blink/web_tests/external/wpt/editing/include/editor-test-utils.js b/third_party/blink/web_tests/external/wpt/editing/include/editor-test-utils.js
index 3ca014a..9ce5ff12 100644
--- a/third_party/blink/web_tests/external/wpt/editing/include/editor-test-utils.js
+++ b/third_party/blink/web_tests/external/wpt/editing/include/editor-test-utils.js
@@ -73,6 +73,27 @@
     return this.sendKey(kEnd, modifier);
   }
 
+  sendSelectAllShortcutKey() {
+    return this.sendKey(
+      "a",
+      (() => {
+        // Gecko for Linux defines only Alt-A as a shortcut key for select all,
+        // although in most environment, Ctrl-A works as so too, but it depends
+        // on the OS settings.
+        if (
+          this.window.navigator.userAgent.includes("Linux") &&
+          this.window.navigator.userAgent.includes("Gecko") &&
+          !this.window.navigator.userAgent.includes("KHTML")
+        ) {
+          return this.kAlt;
+        }
+        return this.window.navigator.platform.includes("Mac")
+          ? this.kMeta
+          : this.kControl;
+      })()
+    );
+  }
+
   // Similar to `setupDiv` in editing/include/tests.js, this method sets
   // innerHTML value of this.editingHost, and sets multiple selection ranges
   // specified with the markers.
@@ -142,7 +163,7 @@
           return {
             marker: scanResult[0],
             container: textNode,
-            offset: scanResult.index + offset
+            offset: scanResult.index + offset,
           };
         };
         if (startContainer.nodeType === Node.TEXT_NODE) {
@@ -181,7 +202,7 @@
           return {
             marker: scanResult[0],
             container: textNode,
-            offset: scanResult.index + offset
+            offset: scanResult.index + offset,
           };
         };
         if (startContainer.nodeType === Node.TEXT_NODE) {
diff --git a/third_party/blink/web_tests/external/wpt/editing/other/editable-state-and-focus-in-shadow-dom-in-designMode.tentative-expected.txt b/third_party/blink/web_tests/external/wpt/editing/other/editable-state-and-focus-in-shadow-dom-in-designMode.tentative-expected.txt
new file mode 100644
index 0000000..3b6b947a
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/editing/other/editable-state-and-focus-in-shadow-dom-in-designMode.tentative-expected.txt
@@ -0,0 +1,24 @@
+This is a testharness.js-based test.
+PASS Waiting for load
+FAIL Collapse selection into text in the open shadow DOM promise_test: Unhandled rejection with value: object "Error: we do not support keydown and keyup actions, please use test_driver.send_keys"
+FAIL Collapse selection into text in <div contenteditable> in the open shadow DOM promise_test: Unhandled rejection with value: object "Error: we do not support keydown and keyup actions, please use test_driver.send_keys"
+FAIL Set focus to <object> in the open shadow DOM promise_test: Unhandled rejection with value: object "Error: we do not support keydown and keyup actions, please use test_driver.send_keys"
+FAIL Set focus to <p tabindex="0"> in the open shadow DOM promise_test: Unhandled rejection with value: object "Error: we do not support keydown and keyup actions, please use test_driver.send_keys"
+FAIL SelectAll in the open shadow DOM promise_test: Unhandled rejection with value: object "Error: we do not support keydown and keyup actions, please use test_driver.send_keys"
+FAIL SelectAll in the <div contenteditable> in the open shadow DOM promise_test: Unhandled rejection with value: object "Error: we do not support keydown and keyup actions, please use test_driver.send_keys"
+FAIL Collapse selection into text in the closed shadow DOM promise_test: Unhandled rejection with value: object "Error: we do not support keydown and keyup actions, please use test_driver.send_keys"
+FAIL Collapse selection into text in <div contenteditable> in the closed shadow DOM promise_test: Unhandled rejection with value: object "Error: we do not support keydown and keyup actions, please use test_driver.send_keys"
+FAIL Set focus to <object> in the closed shadow DOM promise_test: Unhandled rejection with value: object "Error: we do not support keydown and keyup actions, please use test_driver.send_keys"
+FAIL Set focus to <p tabindex="0"> in the closed shadow DOM promise_test: Unhandled rejection with value: object "Error: we do not support keydown and keyup actions, please use test_driver.send_keys"
+FAIL SelectAll in the closed shadow DOM promise_test: Unhandled rejection with value: object "Error: we do not support keydown and keyup actions, please use test_driver.send_keys"
+FAIL SelectAll in the <div contenteditable> in the closed shadow DOM promise_test: Unhandled rejection with value: object "Error: we do not support keydown and keyup actions, please use test_driver.send_keys"
+PASS Focus after Collapse selection into text in the open shadow DOM
+PASS Focus after Collapse selection into text in <div contenteditable> in the open shadow DOM
+PASS Focus after Set focus to <object> in the open shadow DOM
+PASS Focus after Set focus to <p tabindex="0"> in the open shadow DOM
+PASS Focus after Collapse selection into text in the closed shadow DOM
+PASS Focus after Collapse selection into text in <div contenteditable> in the closed shadow DOM
+PASS Focus after Set focus to <object> in the closed shadow DOM
+PASS Focus after Set focus to <p tabindex="0"> in the closed shadow DOM
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/editing/other/editable-state-and-focus-in-shadow-dom-in-designMode.tentative.html b/third_party/blink/web_tests/external/wpt/editing/other/editable-state-and-focus-in-shadow-dom-in-designMode.tentative.html
new file mode 100644
index 0000000..88e6d2912
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/editing/other/editable-state-and-focus-in-shadow-dom-in-designMode.tentative.html
@@ -0,0 +1,252 @@
+<!doctype html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Testing editable state and focus in shadow DOM in design mode</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="../include/editor-test-utils.js"></script>
+</head>
+<body>
+<h3>open</h3>
+<my-shadow data-mode="open"></my-shadow>
+<h3>closed</h3>
+<my-shadow data-mode="closed"></my-shadow>
+
+<script>
+"use strict";
+
+document.designMode = "on";
+const utils = new EditorTestUtils(document.body);
+
+class MyShadow extends HTMLElement {
+  #defaultInnerHTML =
+    "<style>:focus { outline: 3px red solid; }</style>" +
+    "<div>text" +
+      "<div contenteditable=\"\">editable</div>" +
+      "<object tabindex=\"0\">object</object>" +
+      "<p tabindex=\"0\">paragraph</p>" +
+    "</div>";
+  #shadowRoot;
+
+  constructor() {
+    super();
+    this.#shadowRoot = this.attachShadow({mode: this.getAttribute("data-mode")});
+    this.#shadowRoot.innerHTML = this.#defaultInnerHTML;
+  }
+
+  reset() {
+    this.#shadowRoot.innerHTML = this.#defaultInnerHTML;
+    this.#shadowRoot.querySelector("div").getBoundingClientRect();
+  }
+
+  focusText() {
+    this.focus();
+    const div = this.#shadowRoot.querySelector("div");
+    getSelection().collapse(div.firstChild || div, 0);
+  }
+
+  focusContentEditable() {
+    this.focus();
+    const contenteditable = this.#shadowRoot.querySelector("div[contenteditable]");
+    contenteditable.focus();
+    getSelection().collapse(contenteditable.firstChild || contenteditable, 0);
+  }
+
+  focusObject() {
+    this.focus();
+    this.#shadowRoot.querySelector("object[tabindex]").focus();
+  }
+
+  focusParagraph() {
+    this.focus();
+    const tabbableP = this.#shadowRoot.querySelector("p[tabindex]");
+    tabbableP.focus();
+    getSelection().collapse(tabbableP.firstChild || tabbableP, 0);
+  }
+
+  getInnerHTML() {
+    return this.#shadowRoot.innerHTML;
+  }
+
+  getDefaultInnerHTML() {
+    return this.#defaultInnerHTML;
+  }
+
+  getFocusedElementName() {
+    return this.#shadowRoot.querySelector(":focus")?.tagName.toLocaleLowerCase() || "";
+  }
+
+  getSelectedRange() {
+    // XXX There is no standardized way to retrieve selected ranges in
+    //     shadow trees, therefore, we use non-standardized API for now
+    //     since the main purpose of this test is checking the behavior of
+    //     selection changes in shadow trees, not checking the selection API.
+    const selection =
+      this.#shadowRoot.getSelection !== undefined
+        ? this.#shadowRoot.getSelection()
+        : getSelection();
+    return selection.getRangeAt(0);
+  }
+}
+
+customElements.define("my-shadow", MyShadow);
+
+function getRangeDescription(range) {
+  function getNodeDescription(node) {
+    if (!node) {
+      return "null";
+    }
+    switch (node.nodeType) {
+      case Node.TEXT_NODE:
+      case Node.COMMENT_NODE:
+      case Node.CDATA_SECTION_NODE:
+        return `${node.nodeName} "${node.data}"`;
+      case Node.ELEMENT_NODE:
+        return `<${node.nodeName.toLowerCase()}>`;
+      default:
+        return `${node.nodeName}`;
+    }
+  }
+  if (range === null) {
+    return "null";
+  }
+  if (range === undefined) {
+    return "undefined";
+  }
+  return range.startContainer == range.endContainer &&
+    range.startOffset == range.endOffset
+    ? `(${getNodeDescription(range.startContainer)}, ${range.startOffset})`
+    : `(${getNodeDescription(range.startContainer)}, ${
+        range.startOffset
+      }) - (${getNodeDescription(range.endContainer)}, ${range.endOffset})`;
+}
+
+promise_test(async () => {
+  await new Promise(resolve => addEventListener("load", resolve, {once: true}));
+  assert_true(true, "Load event is fired");
+}, "Waiting for load");
+
+/**
+ * The expected result of this test is based on Blink and Gecko's behavior.
+ */
+
+for (const mode of ["open", "closed"]) {
+  const host = document.querySelector(`my-shadow[data-mode=${mode}]`);
+  promise_test(async (t) => {
+    host.reset();
+    host.focusText();
+    test(() => {
+      assert_equals(
+        host.getFocusedElementName(),
+        "",
+        `No element should have focus after ${t.name}`
+      );
+    }, `Focus after ${t.name}`);
+    await utils.sendKey("A");
+    test(() => {
+      assert_equals(
+        host.getInnerHTML(),
+        host.getDefaultInnerHTML(),
+        `The shadow DOM shouldn't be modified after ${t.name}`
+      );
+    }, `Typing "A" after ${t.name}`);
+  }, `Collapse selection into text in the ${mode} shadow DOM`);
+
+  promise_test(async (t) => {
+    host.reset();
+    host.focusContentEditable();
+    test(() => {
+      assert_equals(
+        host.getFocusedElementName(),
+        "div",
+        `<div contenteditable> should have focus after ${t.name}`
+      );
+    }, `Focus after ${t.name}`);
+    await utils.sendKey("A");
+    test(() => {
+      assert_equals(
+        host.getInnerHTML(),
+        host.getDefaultInnerHTML().replace("<div contenteditable=\"\">", "<div contenteditable=\"\">A"),
+        `The shadow DOM shouldn't be modified after ${t.name}`
+      );
+    }, `Typing "A" after ${t.name}`);
+  }, `Collapse selection into text in <div contenteditable> in the ${mode} shadow DOM`);
+
+  promise_test(async (t) => {
+    host.reset();
+    host.focusObject();
+    test(() => {
+      assert_equals(
+        host.getFocusedElementName(),
+        "object",
+        `The <object> element should have focus after ${t.name}`
+      );
+    }, `Focus after ${t.name}`);
+    await utils.sendKey("A");
+    test(() => {
+      assert_equals(
+        host.getInnerHTML(),
+        host.getDefaultInnerHTML(),
+        `The shadow DOM shouldn't be modified after ${t.name}`
+      );
+    }, `Typing "A" after ${t.name}`);
+  }, `Set focus to <object> in the ${mode} shadow DOM`);
+
+  promise_test(async (t) => {
+    host.reset();
+    host.focusParagraph();
+    test(() => {
+      assert_equals(
+        host.getFocusedElementName(),
+        "p",
+        `The <p tabindex="0"> element should have focus after ${t.name}`
+      );
+    }, `Focus after ${t.name}`);
+    await utils.sendKey("A");
+    test(() => {
+      assert_equals(
+        host.getInnerHTML(),
+        host.getDefaultInnerHTML(),
+        `The shadow DOM shouldn't be modified after ${t.name}`
+      );
+    }, `Typing "A" after ${t.name}`);
+  }, `Set focus to <p tabindex="0"> in the ${mode} shadow DOM`);
+
+  promise_test(async (t) => {
+    host.reset();
+    host.focusParagraph();
+    await utils.sendSelectAllShortcutKey();
+    assert_in_array(
+      getRangeDescription(host.getSelectedRange()),
+      [
+        // Feel free to add reasonable select all result in the <my-shadow>.
+        "(#document-fragment, 0) - (#document-fragment, 2)",
+        "(#text \"text\", 0) - (#text \"paragraph\", 9)",
+      ],
+      `Only all children of the ${mode} shadow DOM should be selected`
+    );
+    getSelection().collapse(document.body, 0);
+  }, `SelectAll in the ${mode} shadow DOM`);
+
+  promise_test(async (t) => {
+    host.reset();
+    host.focusContentEditable();
+    await utils.sendSelectAllShortcutKey();
+    assert_in_array(
+      getRangeDescription(host.getSelectedRange()),
+      [
+        // Feel free to add reasonable select all result in the <div contenteditable>.
+        "(<div>, 0) - (<div>, 1)",
+        "(#text \"editable\", 0) - (#text \"editable\", 8)",
+      ]
+    );
+    getSelection().collapse(document.body, 0);
+  }, `SelectAll in the <div contenteditable> in the ${mode} shadow DOM`);
+}
+</script>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/cross-origin-isolated.sub.https.html b/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/cross-origin-isolated.sub.https.html
index e10d3452..a521934 100644
--- a/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/cross-origin-isolated.sub.https.html
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/cross-origin-isolated.sub.https.html
@@ -19,7 +19,7 @@
 
   // Child frames of cross-origin isolated pages must also be cross-origin
   // isolated, and thus also origin-keyed. Make sure the implementation doesn't
-  // treat them specially in some wierd way, for the purposes of this
+  // treat them specially in some weird way, for the purposes of this
   // implication.
   testGetter(0, true, "child");
 
diff --git a/third_party/blink/web_tests/external/wpt/permissions/idlharness.any-expected.txt b/third_party/blink/web_tests/external/wpt/permissions/idlharness.any-expected.txt
deleted file mode 100644
index 1da992c..0000000
--- a/third_party/blink/web_tests/external/wpt/permissions/idlharness.any-expected.txt
+++ /dev/null
@@ -1,50 +0,0 @@
-This is a testharness.js-based test.
-PASS idl_test setup
-PASS idl_test validation
-PASS Partial interface Navigator: original interface defined
-PASS Partial interface Navigator: valid exposure set
-PASS Partial interface Navigator: member names are unique
-PASS Partial interface WorkerNavigator: original interface defined
-PASS Partial interface WorkerNavigator: valid exposure set
-PASS Partial interface WorkerNavigator: member names are unique
-PASS Partial interface mixin NavigatorID: member names are unique
-PASS Navigator includes NavigatorID: member names are unique
-PASS Navigator includes NavigatorLanguage: member names are unique
-PASS Navigator includes NavigatorOnLine: member names are unique
-PASS Navigator includes NavigatorContentUtils: member names are unique
-PASS Navigator includes NavigatorCookies: member names are unique
-PASS Navigator includes NavigatorPlugins: member names are unique
-PASS Navigator includes NavigatorConcurrentHardware: member names are unique
-PASS WorkerNavigator includes NavigatorID: member names are unique
-PASS WorkerNavigator includes NavigatorLanguage: member names are unique
-PASS WorkerNavigator includes NavigatorOnLine: member names are unique
-PASS WorkerNavigator includes NavigatorConcurrentHardware: member names are unique
-PASS Permissions interface: existence and properties of interface object
-PASS Permissions interface object length
-PASS Permissions interface object name
-PASS Permissions interface: existence and properties of interface prototype object
-PASS Permissions interface: existence and properties of interface prototype object's "constructor" property
-PASS Permissions interface: existence and properties of interface prototype object's @@unscopables property
-PASS Permissions interface: operation query(object)
-PASS Permissions must be primary interface of navigator.permissions
-PASS Stringification of navigator.permissions
-PASS Permissions interface: navigator.permissions must inherit property "query(object)" with the proper type
-PASS Permissions interface: calling query(object) on navigator.permissions with too few arguments must throw TypeError
-PASS PermissionStatus interface: existence and properties of interface object
-PASS PermissionStatus interface object length
-PASS PermissionStatus interface object name
-PASS PermissionStatus interface: existence and properties of interface prototype object
-PASS PermissionStatus interface: existence and properties of interface prototype object's "constructor" property
-PASS PermissionStatus interface: existence and properties of interface prototype object's @@unscopables property
-PASS PermissionStatus interface: attribute state
-FAIL PermissionStatus interface: attribute name assert_true: The prototype object must have a property "name" expected true got false
-PASS PermissionStatus interface: attribute onchange
-PASS PermissionStatus must be primary interface of permissionStatus
-PASS Stringification of permissionStatus
-PASS PermissionStatus interface: permissionStatus must inherit property "state" with the proper type
-FAIL PermissionStatus interface: permissionStatus must inherit property "name" with the proper type assert_inherits: property "name" not found in prototype chain
-PASS PermissionStatus interface: permissionStatus must inherit property "onchange" with the proper type
-PASS Navigator interface: attribute permissions
-PASS Navigator interface: navigator must inherit property "permissions" with the proper type
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/permissions/idlharness.any.worker-expected.txt b/third_party/blink/web_tests/external/wpt/permissions/idlharness.any.worker-expected.txt
deleted file mode 100644
index 8fff624a..0000000
--- a/third_party/blink/web_tests/external/wpt/permissions/idlharness.any.worker-expected.txt
+++ /dev/null
@@ -1,50 +0,0 @@
-This is a testharness.js-based test.
-PASS idl_test setup
-PASS idl_test validation
-PASS Partial interface Navigator: original interface defined
-PASS Partial interface Navigator: valid exposure set
-PASS Partial interface Navigator: member names are unique
-PASS Partial interface WorkerNavigator: original interface defined
-PASS Partial interface WorkerNavigator: valid exposure set
-PASS Partial interface WorkerNavigator: member names are unique
-PASS Partial interface mixin NavigatorID: member names are unique
-PASS Navigator includes NavigatorID: member names are unique
-PASS Navigator includes NavigatorLanguage: member names are unique
-PASS Navigator includes NavigatorOnLine: member names are unique
-PASS Navigator includes NavigatorContentUtils: member names are unique
-PASS Navigator includes NavigatorCookies: member names are unique
-PASS Navigator includes NavigatorPlugins: member names are unique
-PASS Navigator includes NavigatorConcurrentHardware: member names are unique
-PASS WorkerNavigator includes NavigatorID: member names are unique
-PASS WorkerNavigator includes NavigatorLanguage: member names are unique
-PASS WorkerNavigator includes NavigatorOnLine: member names are unique
-PASS WorkerNavigator includes NavigatorConcurrentHardware: member names are unique
-PASS Permissions interface: existence and properties of interface object
-PASS Permissions interface object length
-PASS Permissions interface object name
-PASS Permissions interface: existence and properties of interface prototype object
-PASS Permissions interface: existence and properties of interface prototype object's "constructor" property
-PASS Permissions interface: existence and properties of interface prototype object's @@unscopables property
-PASS Permissions interface: operation query(object)
-PASS Permissions must be primary interface of navigator.permissions
-PASS Stringification of navigator.permissions
-PASS Permissions interface: navigator.permissions must inherit property "query(object)" with the proper type
-PASS Permissions interface: calling query(object) on navigator.permissions with too few arguments must throw TypeError
-PASS PermissionStatus interface: existence and properties of interface object
-PASS PermissionStatus interface object length
-PASS PermissionStatus interface object name
-PASS PermissionStatus interface: existence and properties of interface prototype object
-PASS PermissionStatus interface: existence and properties of interface prototype object's "constructor" property
-PASS PermissionStatus interface: existence and properties of interface prototype object's @@unscopables property
-PASS PermissionStatus interface: attribute state
-FAIL PermissionStatus interface: attribute name assert_true: The prototype object must have a property "name" expected true got false
-PASS PermissionStatus interface: attribute onchange
-PASS PermissionStatus must be primary interface of permissionStatus
-PASS Stringification of permissionStatus
-PASS PermissionStatus interface: permissionStatus must inherit property "state" with the proper type
-FAIL PermissionStatus interface: permissionStatus must inherit property "name" with the proper type assert_inherits: property "name" not found in prototype chain
-PASS PermissionStatus interface: permissionStatus must inherit property "onchange" with the proper type
-PASS WorkerNavigator interface: attribute permissions
-PASS WorkerNavigator interface: navigator must inherit property "permissions" with the proper type
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/permissions/permissionsstatus-name-expected.txt b/third_party/blink/web_tests/external/wpt/permissions/permissionsstatus-name-expected.txt
deleted file mode 100644
index 94dd7854..0000000
--- a/third_party/blink/web_tests/external/wpt/permissions/permissionsstatus-name-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL Test PermissionStatus's name attribute. assert_equals: Name was geolocation expected (string) "geolocation" but got (undefined) undefined
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/resources/testharness.js b/third_party/blink/web_tests/external/wpt/resources/testharness.js
index b4bed2b..6fe5580 100644
--- a/third_party/blink/web_tests/external/wpt/resources/testharness.js
+++ b/third_party/blink/web_tests/external/wpt/resources/testharness.js
@@ -3414,7 +3414,12 @@
                                                 ["span", {"class":status_class(status)},
                                                  status
                                                 ],
-                                               ]
+                                               ],
+                                               ["button",
+                                                {"onclick": "let evt = new Event('__test_restart'); " +
+                                                 "let canceled = !window.dispatchEvent(evt);" +
+                                                 "if (!canceled) { location.reload() }"},
+                                                "Rerun"]
                                               ]];
 
                                     if (harness_status.status === harness_status.ERROR) {
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/network/multiple-redirects-extrainfo-expected.txt b/third_party/blink/web_tests/http/tests/inspector-protocol/network/multiple-redirects-extrainfo-expected.txt
new file mode 100644
index 0000000..7bcd4a71b
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/network/multiple-redirects-extrainfo-expected.txt
@@ -0,0 +1,27 @@
+Verifies that ExtraInfo events are emitted for each redirect in a chain in subsequent requests.
+
+requestWillBeSents: 2
+  url: http://127.0.0.1:8000/inspector-protocol/resources/redirect2.php
+  url: http://127.0.0.1:8000/inspector-protocol/resources/final.html
+responseReceiveds: 1
+  url: http://127.0.0.1:8000/inspector-protocol/resources/final.html
+requestWillBeSentExtraInfos: 2
+  has headers: true
+  has headers: true
+responseReceivedExtraInfos: 2
+  has headers: true
+  has headers: true
+
+requestWillBeSents: 2
+  url: http://127.0.0.1:8000/inspector-protocol/resources/redirect2.php
+  url: http://127.0.0.1:8000/inspector-protocol/resources/final.html
+responseReceiveds: 1
+  url: http://127.0.0.1:8000/inspector-protocol/resources/final.html
+requestWillBeSentExtraInfos: 2
+  has headers: true
+  has headers: false
+responseReceivedExtraInfos: 2
+  has headers: true
+  has headers: true
+
+
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/network/multiple-redirects-extrainfo.js b/third_party/blink/web_tests/http/tests/inspector-protocol/network/multiple-redirects-extrainfo.js
new file mode 100644
index 0000000..33be1fb
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/network/multiple-redirects-extrainfo.js
@@ -0,0 +1,112 @@
+(async function(testRunner) {
+  let {page, session, dp} = await testRunner.startBlank(
+    'Verifies that ExtraInfo events are emitted for each redirect in a chain in subsequent requests.\n');
+
+  await dp.Network.enable();
+
+  const requests = new Map();
+  function pushEvent(name, event) {
+    if (!requests.has(event.params.requestId)) {
+      requests.set(event.params.requestId, {});
+    }
+    const request = requests.get(event.params.requestId);
+    if (!request[name]) {
+      request[name] = [];
+    }
+    request[name].push(event);
+  }
+
+  dp.Network.onRequestWillBeSent(event => {
+    pushEvent('requestWillBeSent', event);
+  });
+
+  const responseReceivedsPromise = new Promise(resolve => {
+    let responseReceivedCount = 0;
+    dp.Network.onResponseReceived(event => {
+      pushEvent('responseReceived', event);
+      responseReceivedCount++;
+      if (responseReceivedCount === 2)
+        resolve();
+    });
+  });
+
+  dp.Network.onRequestWillBeSentExtraInfo(event => {
+    pushEvent('requestWillBeSentExtraInfo', event);
+  });
+
+  const extraInfosPromise = new Promise(resolve => {
+    let extraInfoCount = 0;
+    dp.Network.onResponseReceivedExtraInfo(event => {
+      pushEvent('responseReceivedExtraInfo', event);
+      extraInfoCount++;
+      if (extraInfoCount === 4)
+        resolve();
+    });
+  });
+
+
+  const path = '/inspector-protocol/resources/redirect2.php';
+  session.evaluate(`
+    {
+      let xhr = new XMLHttpRequest();
+      xhr.open('GET', '${path}');
+      xhr.send();
+    }
+  `);
+  session.evaluate(`
+    {
+      let xhr = new XMLHttpRequest();
+      xhr.open('GET', '${path}');
+      xhr.send();
+    }
+  `);
+
+  await responseReceivedsPromise;
+  await extraInfosPromise;
+
+  for (const [requestId, request] of requests) {
+    const requestWillBeSents = request.requestWillBeSent;
+    if (requestWillBeSents) {
+      testRunner.log(`requestWillBeSents: ${requestWillBeSents.length}`);
+      for (let i = 0; i < requestWillBeSents.length; i++) {
+        const requestWillBeSent = requestWillBeSents[i];
+        testRunner.log(`  url: ${requestWillBeSent.params.request.url}`);
+      }
+    } else {
+      testRunner.log(`requestWilBeSents: none`);
+    }
+
+    const responseReceiveds = request.responseReceived;
+    if (responseReceiveds) {
+      testRunner.log(`responseReceiveds: ${responseReceiveds.length}`);
+      for (let i = 0; i < responseReceiveds.length; i++) {
+        const responseReceived = responseReceiveds[i];
+        testRunner.log(`  url: ${responseReceived.params.response.url}`);
+      }
+    } else {
+      testRunner.log(`responseReceiveds: none`);
+    }
+
+    const requestWillBeSentExtraInfos = request.requestWillBeSentExtraInfo;
+    if (requestWillBeSentExtraInfos) {
+      testRunner.log(`requestWillBeSentExtraInfos: ${requestWillBeSentExtraInfos.length}`);
+      for (let i = 0; i < requestWillBeSentExtraInfos.length; i++) {
+        const requestWillBeSentExtraInfo = requestWillBeSentExtraInfos[i];
+        testRunner.log(`  has headers: ${Object.keys(requestWillBeSentExtraInfo.params.headers).length > 0}`);
+      }
+    }
+
+    const responseReceivedExtraInfos = request.responseReceivedExtraInfo;
+    if (responseReceivedExtraInfos) {
+      testRunner.log(`responseReceivedExtraInfos: ${responseReceivedExtraInfos.length}`);
+      for (let i = 0; i < responseReceivedExtraInfos.length; i++) {
+        const responseReceivedExtraInfo = responseReceivedExtraInfos[i];
+        testRunner.log(`  has headers: ${Object.keys(responseReceivedExtraInfo.params.headers).length > 0}`);
+      }
+    }
+
+    testRunner.log('');
+  }
+
+  testRunner.completeTest();
+})
diff --git a/third_party/blink/web_tests/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt b/third_party/blink/web_tests/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
index 535dc051..bf92efe 100644
--- a/third_party/blink/web_tests/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
+++ b/third_party/blink/web_tests/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
@@ -1195,6 +1195,7 @@
     method unregister
 interface PermissionStatus : EventTarget
     attribute @@toStringTag
+    getter name
     getter onchange
     getter state
     method constructor
diff --git a/third_party/blink/web_tests/virtual/stable/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt b/third_party/blink/web_tests/virtual/stable/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
index 484ac31..07da2b38 100644
--- a/third_party/blink/web_tests/virtual/stable/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
+++ b/third_party/blink/web_tests/virtual/stable/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
@@ -1067,6 +1067,7 @@
     method unregister
 interface PermissionStatus : EventTarget
     attribute @@toStringTag
+    getter name
     getter onchange
     getter state
     method constructor
diff --git a/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-dedicated-worker-expected.txt b/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-dedicated-worker-expected.txt
index 41d858a..9253b364 100644
--- a/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-dedicated-worker-expected.txt
+++ b/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-dedicated-worker-expected.txt
@@ -1065,6 +1065,7 @@
 [Worker]     method unregister
 [Worker] interface PermissionStatus : EventTarget
 [Worker]     attribute @@toStringTag
+[Worker]     getter name
 [Worker]     getter onchange
 [Worker]     getter state
 [Worker]     method constructor
diff --git a/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-expected.txt b/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-expected.txt
index 986c961..9226a89 100644
--- a/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-expected.txt
+++ b/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-expected.txt
@@ -5374,6 +5374,7 @@
     method constructor
 interface PermissionStatus : EventTarget
     attribute @@toStringTag
+    getter name
     getter onchange
     getter state
     method constructor
diff --git a/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-shared-worker-expected.txt b/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-shared-worker-expected.txt
index c11908c9..d09cdf5 100644
--- a/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-shared-worker-expected.txt
+++ b/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-shared-worker-expected.txt
@@ -974,6 +974,7 @@
 [Worker]     method unregister
 [Worker] interface PermissionStatus : EventTarget
 [Worker]     attribute @@toStringTag
+[Worker]     getter name
 [Worker]     getter onchange
 [Worker]     getter state
 [Worker]     method constructor
diff --git a/third_party/blink/web_tests/webexposed/global-interface-listing-dedicated-worker-expected.txt b/third_party/blink/web_tests/webexposed/global-interface-listing-dedicated-worker-expected.txt
index e0fd675..d415ba2 100644
--- a/third_party/blink/web_tests/webexposed/global-interface-listing-dedicated-worker-expected.txt
+++ b/third_party/blink/web_tests/webexposed/global-interface-listing-dedicated-worker-expected.txt
@@ -1234,6 +1234,7 @@
 [Worker]     method unregister
 [Worker] interface PermissionStatus : EventTarget
 [Worker]     attribute @@toStringTag
+[Worker]     getter name
 [Worker]     getter onchange
 [Worker]     getter state
 [Worker]     method constructor
diff --git a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
index 96920780..ce08ed4 100644
--- a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
+++ b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
@@ -672,6 +672,10 @@
     method postMessage
     setter onmessage
     setter onmessageerror
+interface BrowserCaptureMediaStreamTrack : FocusableMediaStreamTrack
+    attribute @@toStringTag
+    method constructor
+    method cropTo
 interface ByteLengthQueuingStrategy
     attribute @@toStringTag
     getter highWaterMark
@@ -4525,6 +4529,7 @@
     attribute @@toStringTag
     getter priority
     getter size
+    getter type
     method @@iterator
     method add
     method clear
@@ -4536,6 +4541,7 @@
     method keys
     method values
     setter priority
+    setter type
 interface HighlightRegistry
     attribute @@toStringTag
     getter size
@@ -6381,6 +6387,7 @@
     method constructor
 interface PermissionStatus : EventTarget
     attribute @@toStringTag
+    getter name
     getter onchange
     getter state
     method constructor
diff --git a/third_party/blink/web_tests/webexposed/global-interface-listing-shared-worker-expected.txt b/third_party/blink/web_tests/webexposed/global-interface-listing-shared-worker-expected.txt
index 558e5af..ff68c1c 100644
--- a/third_party/blink/web_tests/webexposed/global-interface-listing-shared-worker-expected.txt
+++ b/third_party/blink/web_tests/webexposed/global-interface-listing-shared-worker-expected.txt
@@ -1096,6 +1096,7 @@
 [Worker]     method unregister
 [Worker] interface PermissionStatus : EventTarget
 [Worker]     attribute @@toStringTag
+[Worker]     getter name
 [Worker]     getter onchange
 [Worker]     getter state
 [Worker]     method constructor
diff --git a/third_party/freetype/README.chromium b/third_party/freetype/README.chromium
index a49239e8..91aa4ad4 100644
--- a/third_party/freetype/README.chromium
+++ b/third_party/freetype/README.chromium
@@ -1,7 +1,7 @@
 Name: FreeType
 URL: http://www.freetype.org/
-Version: VER-2-11-0-118-ge294a95ca
-Revision: e294a95ca85f4d1aa2cd1a6e00e572acd7f03871
+Version: VER-2-11-0-119-gede96b239
+Revision: ede96b239b90bf9c9d9a01f06005ae09fb4fa19b
 CPEPrefix: cpe:/a:freetype:freetype:2.10.4
 License: Custom license "inspired by the BSD, Artistic, and IJG (Independent
          JPEG Group) licenses"
diff --git a/tools/mb/mb_config.pyl b/tools/mb/mb_config.pyl
index 1e4d273a..5e746fc8 100644
--- a/tools/mb/mb_config.pyl
+++ b/tools/mb/mb_config.pyl
@@ -311,6 +311,7 @@
       'TSAN Release (g-ip) (reclient)':  'tsan_disable_nacl_release_bot_reclient',
       'TSAN Release (reclient)':  'tsan_disable_nacl_release_bot_reclient',
       'UBSan Release (reclient)': 'ubsan_release_bot_reclient',
+      'UBSan vptr Release (reclient shadow)': 'ubsan_vptr_release_bot_reclient',
       'VR Linux': 'vr_release_bot',
       'VR Linux (reclient)': 'vr_release_bot_reclient',
       'Win 10 Fast Ring': 'release_trybot_minimal_symbols',
@@ -960,6 +961,7 @@
       'linux-chromeos-js-code-coverage': 'chromeos_with_codecs_release_trybot_js_cpp_code_coverage',
       'linux-chromeos-compile-dbg': 'chromeos_with_codecs_debug_bot',
       'linux-chromeos-dbg': 'chromeos_with_codecs_debug_bot',
+      'linux-lacros-dbg': 'lacros_on_linux_debug_bot',
       'linux-lacros-rel': 'lacros_on_linux_release_trybot',
       'linux-lacros-rel-rts': 'lacros_on_linux_release_trybot',
       'linux-cfm-rel': 'linux_cfm_release_trybot',
@@ -1040,6 +1042,7 @@
       'linux-extended-tracing-rel': 'release_trybot_extended_tracing',
       'linux-inverse-fieldtrials-fyi-rel': 'gpu_tests_release_trybot_no_symbols_use_dummy_lastchange_invert_fieldtrials',
       'linux-gcc-rel': 'release_bot_x86_minimal_symbols_no_clang_cxx11',
+      'linux-headless-shell-rel': 'headless_shell_release_bot',
       'linux-lacros-fyi-rel': 'lacros_on_linux_release_trybot',
       'linux-lacros-version-skew-fyi': 'lacros_on_linux_release_not_build_ash_bot',
       'linux-libfuzzer-asan-rel': 'libfuzzer_asan_release_trybot',
@@ -3000,6 +3003,14 @@
       'ubsan_vptr', 'ubsan_no_recover_hack', 'release_bot',
     ],
 
+    'ubsan_vptr_release_bot_reclient': [
+      'ubsan_vptr', 'release_bot_reclient',
+    ],
+
+    'ubsan_vptr_release_bot_reclient': [
+      'ubsan_vptr', 'ubsan_no_recover_hack', 'release_bot_reclient',
+    ],
+
     'v8_future_debug_bot': [
       'v8_future', 'debug_bot',
     ],
diff --git a/tools/mb/mb_config_expectations/chromium.fyi.json b/tools/mb/mb_config_expectations/chromium.fyi.json
index 565d40d3..8da5b2350 100644
--- a/tools/mb/mb_config_expectations/chromium.fyi.json
+++ b/tools/mb/mb_config_expectations/chromium.fyi.json
@@ -419,6 +419,17 @@
       "use_remoteexec": true
     }
   },
+  "UBSan vptr Release (reclient shadow)": {
+    "gn_args": {
+      "dcheck_always_on": false,
+      "is_component_build": false,
+      "is_debug": false,
+      "is_ubsan_no_recover": true,
+      "is_ubsan_vptr": true,
+      "use_rbe": true,
+      "use_remoteexec": true
+    }
+  },
   "VR Linux": {
     "gn_args": {
       "dcheck_always_on": false,
diff --git a/tools/mb/mb_config_expectations/tryserver.chromium.chromiumos.json b/tools/mb/mb_config_expectations/tryserver.chromium.chromiumos.json
index e8b3b8c..eb9d1d8bf 100644
--- a/tools/mb/mb_config_expectations/tryserver.chromium.chromiumos.json
+++ b/tools/mb/mb_config_expectations/tryserver.chromium.chromiumos.json
@@ -227,6 +227,17 @@
       "use_goma": true
     }
   },
+  "linux-lacros-dbg": {
+    "gn_args": {
+      "also_build_ash_chrome": true,
+      "chromeos_is_browser_only": true,
+      "is_component_build": true,
+      "is_debug": true,
+      "symbol_level": 1,
+      "target_os": "chromeos",
+      "use_goma": true
+    }
+  },
   "linux-lacros-rel": {
     "gn_args": {
       "also_build_ash_chrome": true,
diff --git a/tools/mb/mb_config_expectations/tryserver.chromium.linux.json b/tools/mb/mb_config_expectations/tryserver.chromium.linux.json
index d4e910d..501aaaff 100644
--- a/tools/mb/mb_config_expectations/tryserver.chromium.linux.json
+++ b/tools/mb/mb_config_expectations/tryserver.chromium.linux.json
@@ -596,6 +596,19 @@
       "use_goma": false
     }
   },
+  "linux-headless-shell-rel": {
+    "args_file": "//build/args/headless.gn",
+    "gn_args": {
+      "dcheck_always_on": false,
+      "enable_ffmpeg_video_decoders": false,
+      "is_component_build": false,
+      "is_debug": false,
+      "media_use_ffmpeg": false,
+      "media_use_libvpx": false,
+      "proprietary_codecs": false,
+      "use_goma": true
+    }
+  },
   "linux-inverse-fieldtrials-fyi-rel": {
     "gn_args": {
       "blink_enable_generated_code_formatting": false,
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index afd4a01..9bf4933 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -26552,6 +26552,8 @@
   <int value="911" label="PreconfiguredDeskTemplates"/>
   <int value="912" label="FastPairEnabled"/>
   <int value="913" label="SandboxExternalProtocol"/>
+  <int value="914" label="ReportDeviceNetworkTelemetryCollectionRateMs"/>
+  <int value="915" label="ReportDeviceNetworkTelemetryEventCheckingRateMs"/>
 </enum>
 
 <enum name="EnterprisePolicyDeviceIdValidity">
@@ -27317,7 +27319,7 @@
   <int value="32768" label="CUSTODIAN_APPROVAL_REQUIRED"/>
   <int value="65536" label="BLOCKED_BY_POLICY"/>
   <int value="131072" label="DEPRECATED_BLOCKED_MATURE"/>
-  <int value="262144" label="DISABLE_REMOTELY_FOR_MALWARE"/>
+  <int value="262144" label="DEPRECATED_DISABLE_REMOTELY_FOR_MALWARE"/>
   <int value="524288" label="DISABLE_REINSTALL"/>
   <int value="1048576" label="DISABLE_NOT_ALLOWLISTED"/>
 </enum>
@@ -56373,6 +56375,12 @@
   <int value="7" label="Could not identify the source language"/>
 </enum>
 
+<enum name="MerchantTrustBottomSheetOpenedSource">
+  <int value="0" label="Unknown"/>
+  <int value="1" label="From message"/>
+  <int value="2" label="From page info"/>
+</enum>
+
 <enum name="MerchantTrustMessageClearReason">
   <int value="0" label="Unknown"/>
   <int value="1" label="Navigate to same domain"/>
diff --git a/tools/metrics/histograms/histograms_index.txt b/tools/metrics/histograms/histograms_index.txt
index c598890e..47cbd84 100644
--- a/tools/metrics/histograms/histograms_index.txt
+++ b/tools/metrics/histograms/histograms_index.txt
@@ -56,6 +56,7 @@
 tools/metrics/histograms/metadata/interstitial/histograms.xml
 tools/metrics/histograms/metadata/invalidation/histograms.xml
 tools/metrics/histograms/metadata/ios/histograms.xml
+tools/metrics/histograms/metadata/leveldb_proto/histograms.xml
 tools/metrics/histograms/metadata/local/histograms.xml
 tools/metrics/histograms/metadata/login/histograms.xml
 tools/metrics/histograms/metadata/media/histograms.xml
diff --git a/tools/metrics/histograms/metadata/android/histograms.xml b/tools/metrics/histograms/metadata/android/histograms.xml
index e3628dd..6d5dec3 100644
--- a/tools/metrics/histograms/metadata/android/histograms.xml
+++ b/tools/metrics/histograms/metadata/android/histograms.xml
@@ -505,6 +505,10 @@
   <summary>
     Measures time from {type} child process starts to right before main. Only
     recorded on Android N+. Zygote measurements are recorded on Q+.
+
+    Zygote measurements capture the CPU time (thread time) spent to create the
+    app zygote process, before the zygote preload starts. For other processes it
+    measures the time (uptimeMillis) to create the child process service.
   </summary>
   <token key="type">
     <variant name=".All" summary="all (excluding zygote)"/>
diff --git a/tools/metrics/histograms/metadata/blink/histograms.xml b/tools/metrics/histograms/metadata/blink/histograms.xml
index e59bbb14..973a1d4 100644
--- a/tools/metrics/histograms/metadata/blink/histograms.xml
+++ b/tools/metrics/histograms/metadata/blink/histograms.xml
@@ -115,6 +115,42 @@
   </summary>
 </histogram>
 
+<histogram name="Blink.Binding.CreateV8ContextForMainFrame"
+    units="microseconds" expires_after="2022-02-01">
+  <owner>sky@chromium.org</owner>
+  <owner>yukishiino@chromium.org</owner>
+  <summary>
+    Warning: this histogram was expired from ~4/2019 to ~10/2021.
+
+    Time (in microseconds) spent to create a v8::Context instance during a page
+    loading in the main-frame window.
+
+    Warning: This metric may include reports from clients with low-resolution
+    clocks (i.e. on Windows, ref. |TimeTicks::IsHighResolution()|). Such reports
+    will cause this metric to have an abnormal distribution. When considering
+    revising this histogram, see UMA_HISTOGRAM_CUSTOM_MICROSECONDS_TIMES for the
+    solution.
+  </summary>
+</histogram>
+
+<histogram name="Blink.Binding.CreateV8ContextForNonMainFrame"
+    units="microseconds" expires_after="2022-02-01">
+  <owner>sky@chromium.org</owner>
+  <owner>yukishiino@chromium.org</owner>
+  <summary>
+    Warning: this histogram was expired from ~4/2019 to ~10/2021.
+
+    Time spent (in microseconds) to create a v8::Context instance during a page
+    loading in a non-main-frame window, e.g. iframe.
+
+    Warning: This metric may include reports from clients with low-resolution
+    clocks (i.e. on Windows, ref. |TimeTicks::IsHighResolution()|). Such reports
+    will cause this metric to have an abnormal distribution. When considering
+    revising this histogram, see UMA_HISTOGRAM_CUSTOM_MICROSECONDS_TIMES for the
+    solution.
+  </summary>
+</histogram>
+
 <histogram name="Blink.Canvas.2DPrintingAsVector" enum="BooleanSuccess"
     expires_after="2022-01-31">
   <owner>fserb@chromium.org</owner>
@@ -961,9 +997,9 @@
 </histogram>
 
 <histogram name="Blink.CSSPaintValue.PaintOffThread"
-    enum="BooleanCompositorCSSPaint" expires_after="2021-09-08">
+    enum="BooleanCompositorCSSPaint" expires_after="2022-03-08">
   <owner>xidachen@chromium.org</owner>
-  <owner>smcgruer@chromium.org</owner>
+  <owner>kevers@chromium.org</owner>
   <summary>
     Records if a CSS Paint is painted on the compositor thread or has fallen
     back to the main thread.
diff --git a/tools/metrics/histograms/metadata/chromeos/histograms.xml b/tools/metrics/histograms/metadata/chromeos/histograms.xml
index 6450d30..34207bd 100644
--- a/tools/metrics/histograms/metadata/chromeos/histograms.xml
+++ b/tools/metrics/histograms/metadata/chromeos/histograms.xml
@@ -1954,6 +1954,17 @@
   </summary>
 </histogram>
 
+<histogram name="ChromeOS.WebAPK.MinterResponseOrErrorCode"
+    enum="CombinedHttpResponseAndNetErrorCode" expires_after="2022-04-14">
+  <owner>tsergeant@chromium.org</owner>
+  <owner>chromeos-apps-foundation-team@google.com</owner>
+  <summary>
+    HTTP response code or net error code for requests made to the WebAPK minter
+    service. Logged after a request to generate a WebAPK finishes, which happens
+    when a PWA which supports Web Share Target is installed or updated.
+  </summary>
+</histogram>
+
 <histogram name="ChromeOS.WebAPK.UnlinkedWebAPKCount" units="count"
     expires_after="2022-03-14">
   <owner>tsergeant@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/histogram_suffixes_list.xml b/tools/metrics/histograms/metadata/histogram_suffixes_list.xml
index 171afcf..1508994 100644
--- a/tools/metrics/histograms/metadata/histogram_suffixes_list.xml
+++ b/tools/metrics/histograms/metadata/histogram_suffixes_list.xml
@@ -7009,6 +7009,8 @@
 </histogram_suffixes>
 
 <histogram_suffixes name="LevelDBClients" separator=".">
+<!-- Obsolete. Use LevelDBClient variant in leveldb_proto/histograms.xml instead. -->
+
   <suffix name="BudgetManager"
       label="Database for storing budget information for origins."/>
   <suffix name="CachedImageFetcherDatabase"
@@ -7101,19 +7103,10 @@
       label="Database for video tutorials (deprecated)."/>
   <suffix name="VideoTutorialsV2Database"
       label="Database for video tutorials."/>
+<!-- The following two histograms are obsolete. -->
+
   <affected-histogram name="LevelDB.ApproximateMemoryUse"/>
-  <affected-histogram name="LevelDB.ApproximateMemTableMemoryUse"/>
   <affected-histogram name="LevelDB.Open"/>
-  <affected-histogram name="ProtoDB.DestroySuccess"/>
-  <affected-histogram name="ProtoDB.GetErrorStatus"/>
-  <affected-histogram name="ProtoDB.GetFound"/>
-  <affected-histogram name="ProtoDB.GetSuccess"/>
-  <affected-histogram name="ProtoDB.InitStatus"/>
-  <affected-histogram name="ProtoDB.LoadEntriesSuccess"/>
-  <affected-histogram name="ProtoDB.LoadKeysAndEntriesSuccess"/>
-  <affected-histogram name="ProtoDB.LoadKeysSuccess"/>
-  <affected-histogram name="ProtoDB.UpdateErrorStatus"/>
-  <affected-histogram name="ProtoDB.UpdateSuccess"/>
 </histogram_suffixes>
 
 <histogram_suffixes name="LevelDBEnvBackupRestore" separator="">
@@ -10800,16 +10793,16 @@
 </histogram_suffixes>
 
 <histogram_suffixes name="OptimizationGuide_OptimizationTargets" separator=".">
-  <suffix name="SegmentationChromeStartAndroid" label="Chrome Start Android"/>
-  <suffix name="SegmentationDummyFeature" label="Dummy feature"/>
   <suffix name="LanguageDetection" label="Language detection"/>
   <suffix name="ModelValidation" label="Model validation triggered via CLI"/>
   <suffix name="NotificationPermissions" label="Notification permissions"/>
   <suffix name="PageEntities" label="Page entities"/>
   <suffix name="PageTopics" label="Page topics"/>
   <suffix name="PainfulPageLoad" label="Painful page load"/>
-  <suffix name="SegmentationQueryTiles" label="Query tiles"/>
+  <suffix name="SegmentationChromeStartAndroid" label="Chrome Start Android"/>
+  <suffix name="SegmentationDummyFeature" label="Dummy feature"/>
   <suffix name="SegmentationNewTab" label="Segmentation: New tab page user"/>
+  <suffix name="SegmentationQueryTiles" label="Query tiles"/>
   <suffix name="SegmentationShare" label="Segmentation: Share user"/>
   <suffix name="SegmentationVoice" label="Segmentation: Voice user"/>
   <affected-histogram name="OptimizationGuide.IsPredictionModelValid"/>
@@ -14782,6 +14775,7 @@
       label="Records metrics for consumer real time URL lookup service."/>
   <suffix name="Enterprise"
       label="Records metrics for enterprise real time URL lookup service."/>
+  <affected-histogram name="SafeBrowsing.PageLoadToken.RealTimeCheckHasToken"/>
   <affected-histogram name="SafeBrowsing.RT.Backoff.State"/>
   <affected-histogram name="SafeBrowsing.RT.GetCache.Time"/>
   <affected-histogram name="SafeBrowsing.RT.GetCacheResult"/>
diff --git a/tools/metrics/histograms/metadata/leveldb_proto/OWNERS b/tools/metrics/histograms/metadata/leveldb_proto/OWNERS
new file mode 100644
index 0000000..ade5e7b
--- /dev/null
+++ b/tools/metrics/histograms/metadata/leveldb_proto/OWNERS
@@ -0,0 +1,5 @@
+per-file OWNERS=file://tools/metrics/histograms/metadata/METRIC_REVIEWER_OWNERS
+
+# Prefer sending CLs to the owners listed below.
+# Use chromium-metrics-reviews@google.com as a backup.
+nyquist@chromium.org
diff --git a/tools/metrics/histograms/metadata/leveldb_proto/histograms.xml b/tools/metrics/histograms/metadata/leveldb_proto/histograms.xml
new file mode 100644
index 0000000..277eb94
--- /dev/null
+++ b/tools/metrics/histograms/metadata/leveldb_proto/histograms.xml
@@ -0,0 +1,236 @@
+<!--
+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.
+-->
+
+<!--
+This file is used to generate a comprehensive list of histograms related to
+leveldb_proto along with a detailed description for each histogram.
+
+For best practices on writing histogram descriptions, see
+https://chromium.googlesource.com/chromium/src.git/+/HEAD/tools/metrics/histograms/README.md
+
+Please send CLs to chromium-metrics-reviews@google.com rather than to specific
+individuals. These CLs will be automatically reassigned to a reviewer within
+about 5 minutes. This approach helps the metrics team to load-balance incoming
+reviews. Googlers can read more about this at go/gwsq-gerrit.
+-->
+
+<histogram-configuration>
+
+<histograms>
+
+<variants name="LevelDBClient">
+  <variant name="BudgetManager"
+      summary="Database for storing budget information for origins."/>
+  <variant name="CachedImageFetcherDatabase"
+      summary="Database for CachedImageFetcher metadata."/>
+  <variant name="CartDatabase" summary="Database for chrome cart."/>
+  <variant name="CommerceSubscriptionDatabase"
+      summary="Database for Chrome commerce subscriptions."/>
+  <variant name="CouponDatabase" summary="Database for coupons in Chrome."/>
+  <variant name="DomDistillerStore" summary="Databases for DomDistillerStore">
+    <obsolete>
+      Deprecated since 2019-10.
+    </obsolete>
+  </variant>
+  <variant name="DownloadDB" summary="Databases for in-progress download."/>
+  <variant name="DownloadService" summary="Databases for download service."/>
+  <variant name="FeatureEngagementTrackerAvailabilityStore"
+      summary="Database for FeatureEngagementTracker feature availability."/>
+  <variant name="FeatureEngagementTrackerEventStore"
+      summary="Database for FeatureEngagementTracker events."/>
+  <variant name="FeedContentDatabase"
+      summary="Database for Feed content storage."/>
+  <variant name="FeedImageDatabase" summary="Databases for Feed Image Loader.">
+    <obsolete>
+      Deprecated since 11/18.
+    </obsolete>
+  </variant>
+  <variant name="FeedJournalDatabase"
+      summary="Database for Feed journal storage."/>
+  <variant name="FeedKeyValueDatabase"
+      summary="Database for key value cache used in feed rendering."/>
+  <variant name="FeedStorageDatabase" summary="Databases for Feed Storage.">
+    <obsolete>
+      Deprecated since 08/18.
+    </obsolete>
+  </variant>
+  <variant name="FeedStreamDatabase"
+      summary="Database for Feed v2 stream and content storage."/>
+  <variant name="GCMKeyStore" summary="Databases for GCMKeyStore"/>
+  <variant name="ImageManager" summary="Databases for ImageManager"/>
+  <variant name="MerchantTrustSignalDatabase"
+      summary="Database for Chrome merchant trust signals events."/>
+  <variant name="Metadata" summary="Metadata of shared databases"/>
+  <variant name="NearbySharePublicCertificateDatabase"
+      summary="Database for Nearby Share public certificates."/>
+  <variant name="NotificationSchedulerIcons"
+      summary="Notification scheduler icons database."/>
+  <variant name="NotificationSchedulerImpressions"
+      summary="Notification scheduler impression database."/>
+  <variant name="NotificationSchedulerNotifications"
+      summary="Notification scheduler notification database."/>
+  <variant name="NTPSnippetImages"
+      summary="Database for RemoteSuggestion images."/>
+  <variant name="NTPSnippets"
+      summary="Database for RemoteSuggestion snippets."/>
+  <variant name="OfflinePageMetadataStore"
+      summary="Databases for OfflinePageMetadataStore"/>
+  <variant name="PersistedStateDatabase"
+      summary="Database for NonCriticalPersistedTabData"/>
+  <variant name="PreviewsHintCacheStore"
+      summary="Databases for Previews Hints"/>
+  <variant name="PrintJobDatabase" summary="Database for print job metadata."/>
+  <variant name="SegmentInfoDatabase"
+      summary="Segmentation platform metadata database."/>
+  <variant name="SharedDb" summary="Shared database"/>
+  <variant name="ShareHistoryDatabase"
+      summary="Database for third-party share history."/>
+  <variant name="ShareRankingDatabase"
+      summary="Database for third-party share rankings."/>
+  <variant name="SignalDatabase"
+      summary="Segmentation platform signal database."/>
+  <variant name="SignalStorageConfigDatabase"
+      summary="Segmentation platform signal storage config."/>
+  <variant name="StrikeService" summary="Database for strike service."/>
+  <variant name="TabStateDatabase"
+      summary="Database for NonCriticalPersistedTabData">
+    <obsolete>
+      Deprecated since 10/2020 in favor of PersistedStateDatabase
+    </obsolete>
+  </variant>
+  <variant name="UpboardingQueryTileStore"
+      summary="Database for Upboarding query tiles."/>
+  <variant name="UsageReportsBufferBackend"
+      summary="The result of the first attempt to open the usage reports
+               buffer backend database."/>
+  <variant name="UsageStatsSuspension"
+      summary="UsageStats database for Suspensions."/>
+  <variant name="UsageStatsTokenMapping"
+      summary="UsageStats database for TokenMappings."/>
+  <variant name="UsageStatsWebsiteEvent"
+      summary="UsageStats database for WebsiteEvents."/>
+  <variant name="VideoDecodeStatsDB" summary="Database for video decode stats"/>
+  <variant name="VideoTutorialsDatabase"
+      summary="Database for video tutorials (deprecated)."/>
+  <variant name="VideoTutorialsV2Database"
+      summary="Database for video tutorials."/>
+</variants>
+
+<histogram name="LevelDB.ApproximateMemTableMemoryUse.{LevelDBClient}"
+    units="bytes" expires_after="2022-04-11">
+  <owner>nyquist@chromium.org</owner>
+  <owner>salg@google.com</owner>
+  <owner>chrome-owp-storage@google.com</owner>
+  <summary>
+    The approximate MemTable memory use of a LevelDB in bytes. Recorded right
+    after initializing an on-disk database.
+  </summary>
+  <token key="LevelDBClient" variants="LevelDBClient"/>
+</histogram>
+
+<histogram name="ProtoDB.DestroySuccess.{LevelDBClient}" enum="BooleanSuccess"
+    expires_after="2022-04-11">
+  <owner>nyquist@chromium.org</owner>
+  <owner>ssid@chromium.org</owner>
+  <summary>Whether a ProtoDB Destroy call was successful or not.</summary>
+  <token key="LevelDBClient" variants="LevelDBClient"/>
+</histogram>
+
+<histogram name="ProtoDB.GetErrorStatus.{LevelDBClient}" enum="LevelDBStatus"
+    expires_after="2022-04-11">
+  <owner>nyquist@chromium.org</owner>
+  <owner>ssid@chromium.org</owner>
+  <summary>
+    The LevelDB Status returned from a failed ProtoDatabase Get call.
+  </summary>
+  <token key="LevelDBClient" variants="LevelDBClient"/>
+</histogram>
+
+<histogram name="ProtoDB.GetFound.{LevelDBClient}" enum="Boolean"
+    expires_after="2022-04-11">
+  <owner>nyquist@chromium.org</owner>
+  <owner>ssid@chromium.org</owner>
+  <summary>Whether a ProtoDB Get call found what was requested.</summary>
+  <token key="LevelDBClient" variants="LevelDBClient"/>
+</histogram>
+
+<histogram name="ProtoDB.GetSuccess.{LevelDBClient}" enum="BooleanSuccess"
+    expires_after="2022-04-11">
+  <owner>nyquist@chromium.org</owner>
+  <owner>ssid@chromium.org</owner>
+  <summary>Whether a ProtoDB Get call was successful or not.</summary>
+  <token key="LevelDBClient" variants="LevelDBClient"/>
+</histogram>
+
+<histogram name="ProtoDB.InitStatus.{LevelDBClient}" enum="LevelDBStatus"
+    expires_after="2022-04-17">
+  <owner>nyquist@chromium.org</owner>
+  <owner>ssid@chromium.org</owner>
+  <summary>The LevelDB Status from a ProtoDatabase Init call.</summary>
+  <token key="LevelDBClient" variants="LevelDBClient"/>
+</histogram>
+
+<histogram name="ProtoDB.LoadEntriesSuccess.{LevelDBClient}"
+    enum="BooleanSuccess" expires_after="2022-04-11">
+  <owner>nyquist@chromium.org</owner>
+  <owner>ssid@chromium.org</owner>
+  <summary>Whether a ProtoDB LoadEntries call was successful or not.</summary>
+  <token key="LevelDBClient" variants="LevelDBClient"/>
+</histogram>
+
+<histogram name="ProtoDB.LoadKeysAndEntriesSuccess.{LevelDBClient}"
+    enum="BooleanSuccess" expires_after="2022-04-11">
+  <owner>nyquist@chromium.org</owner>
+  <owner>ssid@chromium.org</owner>
+  <summary>
+    Whether a ProtoDB LoadKeysAndEntries call was successful or not.
+  </summary>
+  <token key="LevelDBClient" variants="LevelDBClient"/>
+</histogram>
+
+<histogram name="ProtoDB.LoadKeysSuccess.{LevelDBClient}" enum="BooleanSuccess"
+    expires_after="2022-04-11">
+  <owner>nyquist@chromium.org</owner>
+  <owner>ssid@chromium.org</owner>
+  <summary>Whether a ProtoDB LoadKeys call was successful or not.</summary>
+  <token key="LevelDBClient" variants="LevelDBClient"/>
+</histogram>
+
+<histogram name="ProtoDB.SharedDbInitStatus" enum="ProtoDatabaseInitState"
+    expires_after="2022-04-17">
+  <owner>ssid@chromium.org</owner>
+  <owner>salg@chromium.org</owner>
+  <summary>
+    Tracks the init state progress of a proto database. An enum value is
+    recorded for each state of progression through the initialization process.
+    Shows the number of users hitting each stage. The enum values starting with
+    success and failure indicate that the final output of initialization is a
+    success or failure. See
+    //components/leveldb_proto/internal/proto_init_state_description.md.
+  </summary>
+</histogram>
+
+<histogram name="ProtoDB.UpdateErrorStatus.{LevelDBClient}"
+    enum="LevelDBStatus" expires_after="2022-04-11">
+  <owner>nyquist@chromium.org</owner>
+  <owner>ssid@chromium.org</owner>
+  <summary>
+    The LevelDB Status returned from a failed Protodatabase UpdateEntries call.
+  </summary>
+  <token key="LevelDBClient" variants="LevelDBClient"/>
+</histogram>
+
+<histogram name="ProtoDB.UpdateSuccess.{LevelDBClient}" enum="BooleanSuccess"
+    expires_after="2022-04-11">
+  <owner>nyquist@chromium.org</owner>
+  <owner>ssid@chromium.org</owner>
+  <summary>Whether a ProtoDB UpdateEntries call was successful or not.</summary>
+  <token key="LevelDBClient" variants="LevelDBClient"/>
+</histogram>
+
+</histograms>
+
+</histogram-configuration>
diff --git a/tools/metrics/histograms/metadata/obsolete_histograms.xml b/tools/metrics/histograms/metadata/obsolete_histograms.xml
index 35c9d498..dd22d40 100644
--- a/tools/metrics/histograms/metadata/obsolete_histograms.xml
+++ b/tools/metrics/histograms/metadata/obsolete_histograms.xml
@@ -5849,42 +5849,6 @@
   </summary>
 </histogram>
 
-<histogram name="Blink.Binding.CreateV8ContextForMainFrame"
-    units="microseconds" expires_after="2019-04-16">
-  <obsolete>
-    Removed as of 04/2019 with the removal of field trial experiment settings.
-  </obsolete>
-  <owner>peria@chromium.org</owner>
-  <summary>
-    Time spent to create a v8::Context instance during a page loading in the
-    main-frame window.
-
-    Warning: This metric may include reports from clients with low-resolution
-    clocks (i.e. on Windows, ref. |TimeTicks::IsHighResolution()|). Such reports
-    will cause this metric to have an abnormal distribution. When considering
-    revising this histogram, see UMA_HISTOGRAM_CUSTOM_MICROSECONDS_TIMES for the
-    solution.
-  </summary>
-</histogram>
-
-<histogram name="Blink.Binding.CreateV8ContextForNonMainFrame"
-    units="microseconds" expires_after="2019-04-16">
-  <obsolete>
-    Removed as of 04/2019 with the removal of field trial experiment settings.
-  </obsolete>
-  <owner>peria@chromium.org</owner>
-  <summary>
-    Time spent to create a v8::Context instance during a page loading in a
-    non-main-frame window, e.g. iframe.
-
-    Warning: This metric may include reports from clients with low-resolution
-    clocks (i.e. on Windows, ref. |TimeTicks::IsHighResolution()|). Such reports
-    will cause this metric to have an abnormal distribution. When considering
-    revising this histogram, see UMA_HISTOGRAM_CUSTOM_MICROSECONDS_TIMES for the
-    solution.
-  </summary>
-</histogram>
-
 <histogram name="Blink.Binding.InitializeMainLocalWindowProxy"
     units="microseconds" expires_after="M80">
   <obsolete>
diff --git a/tools/metrics/histograms/metadata/others/histograms.xml b/tools/metrics/histograms/metadata/others/histograms.xml
index 9a650f6..17de5e9 100644
--- a/tools/metrics/histograms/metadata/others/histograms.xml
+++ b/tools/metrics/histograms/metadata/others/histograms.xml
@@ -9453,17 +9453,6 @@
   </summary>
 </histogram>
 
-<histogram name="LevelDB.ApproximateMemTableMemoryUse" units="bytes"
-    expires_after="2021-08-24">
-  <owner>nyquist@chromium.org</owner>
-  <owner>salg@google.com</owner>
-  <owner>chrome-owp-storage@google.com</owner>
-  <summary>
-    The approximate MemTable memory use of a LevelDB in bytes. Recorded right
-    after initializing an on-disk database.
-  </summary>
-</histogram>
-
 <histogram name="LevelDBEnv.DeleteTableBackupFile" enum="BooleanSuccess"
     expires_after="2022-08-01">
   <owner>cmumford@chromium.org</owner>
@@ -13575,95 +13564,6 @@
   </summary>
 </histogram>
 
-<histogram name="ProtoDB.DestroySuccess" enum="BooleanSuccess"
-    expires_after="2021-08-24">
-  <owner>nyquist@chromium.org</owner>
-  <owner>ssid@chromium.org</owner>
-  <summary>Whether a ProtoDB Destroy call was successful or not.</summary>
-</histogram>
-
-<histogram name="ProtoDB.GetErrorStatus" enum="LevelDBStatus"
-    expires_after="2021-08-24">
-  <owner>nyquist@chromium.org</owner>
-  <owner>ssid@chromium.org</owner>
-  <summary>
-    The LevelDB Status returned from a failed ProtoDatabase Get call.
-  </summary>
-</histogram>
-
-<histogram name="ProtoDB.GetFound" enum="Boolean" expires_after="2021-08-24">
-  <owner>nyquist@chromium.org</owner>
-  <owner>ssid@chromium.org</owner>
-  <summary>Whether a ProtoDB Get call found what was requested.</summary>
-</histogram>
-
-<histogram name="ProtoDB.GetSuccess" enum="BooleanSuccess"
-    expires_after="2021-08-24">
-  <owner>nyquist@chromium.org</owner>
-  <owner>ssid@chromium.org</owner>
-  <summary>Whether a ProtoDB Get call was successful or not.</summary>
-</histogram>
-
-<histogram name="ProtoDB.InitStatus" enum="LevelDBStatus"
-    expires_after="2022-04-17">
-  <owner>nyquist@chromium.org</owner>
-  <owner>ssid@chromium.org</owner>
-  <summary>The LevelDB Status from a ProtoDatabase Init call.</summary>
-</histogram>
-
-<histogram name="ProtoDB.LoadEntriesSuccess" enum="BooleanSuccess"
-    expires_after="2021-08-24">
-  <owner>nyquist@chromium.org</owner>
-  <owner>ssid@chromium.org</owner>
-  <summary>Whether a ProtoDB LoadEntries call was successful or not.</summary>
-</histogram>
-
-<histogram name="ProtoDB.LoadKeysAndEntriesSuccess" enum="BooleanSuccess"
-    expires_after="2021-08-24">
-  <owner>nyquist@chromium.org</owner>
-  <owner>ssid@chromium.org</owner>
-  <summary>
-    Whether a ProtoDB LoadKeysAndEntries call was successful or not.
-  </summary>
-</histogram>
-
-<histogram name="ProtoDB.LoadKeysSuccess" enum="BooleanSuccess"
-    expires_after="2021-08-24">
-  <owner>nyquist@chromium.org</owner>
-  <owner>ssid@chromium.org</owner>
-  <summary>Whether a ProtoDB LoadKeys call was successful or not.</summary>
-</histogram>
-
-<histogram name="ProtoDB.SharedDbInitStatus" enum="ProtoDatabaseInitState"
-    expires_after="2022-04-17">
-  <owner>ssid@chromium.org</owner>
-  <owner>salg@chromium.org</owner>
-  <summary>
-    Tracks the init state progress of a proto database. An enum value is
-    recorded for each state of progression through the initialization process.
-    Shows the number of users hitting each stage. The enum values starting with
-    success and failure indicate that the final output of initialization is a
-    success or failure. See
-    //components/leveldb_proto/internal/proto_init_state_description.md.
-  </summary>
-</histogram>
-
-<histogram name="ProtoDB.UpdateErrorStatus" enum="LevelDBStatus"
-    expires_after="2021-08-24">
-  <owner>nyquist@chromium.org</owner>
-  <owner>ssid@chromium.org</owner>
-  <summary>
-    The LevelDB Status returned from a failed Protodatabase UpdateEntries call.
-  </summary>
-</histogram>
-
-<histogram name="ProtoDB.UpdateSuccess" enum="BooleanSuccess"
-    expires_after="2021-08-24">
-  <owner>nyquist@chromium.org</owner>
-  <owner>ssid@chromium.org</owner>
-  <summary>Whether a ProtoDB UpdateEntries call was successful or not.</summary>
-</histogram>
-
 <histogram name="ProxyOverriddenBubble.UserSelection"
     units="ExtensionBubbleAction" expires_after="2020-12-31">
   <obsolete>
diff --git a/tools/metrics/histograms/metadata/safe_browsing/histograms.xml b/tools/metrics/histograms/metadata/safe_browsing/histograms.xml
index 2164c64c7..d4bb5e6 100644
--- a/tools/metrics/histograms/metadata/safe_browsing/histograms.xml
+++ b/tools/metrics/histograms/metadata/safe_browsing/histograms.xml
@@ -1038,6 +1038,36 @@
   </summary>
 </histogram>
 
+<histogram name="SafeBrowsing.PageLoadToken.PasswordProtectionHasToken"
+    units="BooleanExists" expires_after="2022-10-08">
+  <owner>xinghuilu@chromium.org</owner>
+  <owner>chrome-safebrowsing-alerts@google.com</owner>
+  <summary>
+    Records whether a page load token is found before password protection
+    request is sent. Logged only when real time URL check is enabled.
+  </summary>
+</histogram>
+
+<histogram name="SafeBrowsing.PageLoadToken.RealTimeCheckHasToken"
+    units="BooleanExists" expires_after="2022-10-08">
+  <owner>xinghuilu@chromium.org</owner>
+  <owner>chrome-safebrowsing-alerts@google.com</owner>
+  <summary>
+    Records whether a page load token is found before real time URL check
+    request is sent. Logged only when the check is on a subframe URL.
+  </summary>
+</histogram>
+
+<histogram name="SafeBrowsing.PageLoadToken.TokenCount" units="entries"
+    expires_after="2022-10-08">
+  <owner>xinghuilu@chromium.org</owner>
+  <owner>chrome-safebrowsing-alerts@google.com</owner>
+  <summary>
+    Records the number of page load tokens stored in verdict cache manager.
+    Logged each time after a periodic clean up on expired tokens is performed.
+  </summary>
+</histogram>
+
 <histogram name="SafeBrowsing.PakIntegrity.{PakFile}" enum="BooleanSuccess"
     expires_after="2022-05-01">
   <owner>rsesek@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/sharing/histograms.xml b/tools/metrics/histograms/metadata/sharing/histograms.xml
index 96c56ab..26366f8 100644
--- a/tools/metrics/histograms/metadata/sharing/histograms.xml
+++ b/tools/metrics/histograms/metadata/sharing/histograms.xml
@@ -584,21 +584,6 @@
   </summary>
 </histogram>
 
-<histogram name="Sharing.ShareTargetUpdate.Version"
-    units="FileTypePolicies Version" expires_after="M98">
-  <owner>jeffreycohen@chromium.org</owner>
-  <owner>kristipark@chromium.org</owner>
-  <owner>src/chrome/browser/share/OWNERS</owner>
-  <summary>
-    Integer version number citing which version of the proto data chrome just
-    loaded. Latest version is in share_targets.asciipb.
-
-    This is for the file types loaded from the component-update system. This
-    includes both those loaded from disk shortly after startup, and those
-    received over the network when the component version changes
-  </summary>
-</histogram>
-
 <histogram name="Sharing.SharingHubAndroid.Opened" enum="ShareOrigin"
     expires_after="2022-04-17">
   <owner>sophey@chromium.org</owner>
@@ -667,7 +652,7 @@
 </histogram>
 
 <histogram name="Sharing.SharingHubAndroid.{DetailedContentType}.{ShareStatus}"
-    enum="LinkToggleState" expires_after="M98">
+    enum="LinkToggleState" expires_after="M101">
   <owner>sophey@chromium.org</owner>
   <owner>src/chrome/browser/share/OWNERS</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/stability/histograms.xml b/tools/metrics/histograms/metadata/stability/histograms.xml
index 3383785..b9dd8f06 100644
--- a/tools/metrics/histograms/metadata/stability/histograms.xml
+++ b/tools/metrics/histograms/metadata/stability/histograms.xml
@@ -653,7 +653,7 @@
 </histogram>
 
 <histogram name="Stability.MobileSessionShutdownType"
-    enum="MobileSessionShutdownType" expires_after="2021-10-25">
+    enum="MobileSessionShutdownType" expires_after="2022-05-01">
   <owner>michaeldo@chromium.org</owner>
   <owner>olivierrobin@chromium.org</owner>
   <summary>
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json
index 357aa4f..922cbdd3 100644
--- a/tools/perf/core/perfetto_binary_roller/binary_deps.json
+++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -10,7 +10,7 @@
         },
         "mac": {
             "hash": "c23e575ca7971342be4b254789513a3d9e75e19f",
-            "remote_path": "perfetto_binaries/trace_processor_shell/mac/9709a3d80cc9e2ac3e6d808638334f7510ddc59a/trace_processor_shell"
+            "remote_path": "perfetto_binaries/trace_processor_shell/mac/251c5dd034fdc7ce2578f2b5fd112e2fc0fa0e8e/trace_processor_shell"
         },
         "linux_arm64": {
             "hash": "5074025a2898ec41a872e70a5719e417acb0a380",
diff --git a/ui/android/BUILD.gn b/ui/android/BUILD.gn
index d63d20f..6496d91a 100644
--- a/ui/android/BUILD.gn
+++ b/ui/android/BUILD.gn
@@ -13,7 +13,6 @@
   output_name = "ui_android"
   sources = [
     "animation_utils.h",
-    "color_helpers.h",
     "color_utils_android.cc",
     "color_utils_android.h",
     "delegated_frame_host_android.cc",
@@ -519,7 +518,7 @@
     # Clipboard unittests are run here for Android as gtests on Android are not
     # sharded. On other OSs these are run as part of interactive_ui_tests.
     "//ui/base/clipboard/clipboard_unittest.cc",
-    "color_helpers_unittest.cc",
+    "color_utils_android_unittest.cc",
     "overscroll_refresh_unittest.cc",
     "resources/resource_manager_impl_unittest.cc",
     "run_all_unittests.cc",
diff --git a/ui/android/color_helpers.h b/ui/android/color_helpers.h
deleted file mode 100644
index c1f4fb6..0000000
--- a/ui/android/color_helpers.h
+++ /dev/null
@@ -1,11 +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 UI_ANDROID_COLOR_HELPERS_H_
-#define UI_ANDROID_COLOR_HELPERS_H_
-
-// TODO(pkotwicz): Replace color_helpers.h includes with color_utils_android.h
-#include "ui/android/color_utils_android.h"
-
-#endif  // UI_ANDROID_COLOR_HELPERS_H_
diff --git a/ui/android/color_helpers_unittest.cc b/ui/android/color_utils_android_unittest.cc
similarity index 94%
rename from ui/android/color_helpers_unittest.cc
rename to ui/android/color_utils_android_unittest.cc
index 5acf395..7c593d7 100644
--- a/ui/android/color_helpers_unittest.cc
+++ b/ui/android/color_utils_android_unittest.cc
@@ -1,8 +1,8 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
+// 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/android/color_helpers.h"
+#include "ui/android/color_utils_android.h"
 
 #include <stdint.h>
 
diff --git a/ui/file_manager/file_manager/foreground/js/file_manager_commands.js b/ui/file_manager/file_manager/foreground/js/file_manager_commands.js
index 974616d..2ece26a 100644
--- a/ui/file_manager/file_manager/foreground/js/file_manager_commands.js
+++ b/ui/file_manager/file_manager/foreground/js/file_manager_commands.js
@@ -2715,8 +2715,10 @@
 CommandHandler.COMMANDS_['inspect-background'] =
     new class extends FilesCommand {
   execute(event, fileManager) {
-    chrome.fileManagerPrivate.openInspector(
-        chrome.fileManagerPrivate.InspectionType.BACKGROUND);
+    if (!window.isSWA) {
+      chrome.fileManagerPrivate.openInspector(
+          chrome.fileManagerPrivate.InspectionType.BACKGROUND);
+    }
   }
 };
 
diff --git a/ui/message_center/notification_list.cc b/ui/message_center/notification_list.cc
index df78298..fccba33 100644
--- a/ui/message_center/notification_list.cc
+++ b/ui/message_center/notification_list.cc
@@ -22,9 +22,8 @@
 
 namespace {
 
-bool ShouldShowNotificationAsPopup(
-    const Notification& notification,
-    const NotificationBlockers& blockers) {
+bool ShouldShowNotificationAsPopup(const Notification& notification,
+                                   const NotificationBlockers& blockers) {
   for (auto* blocker : blockers) {
     if (!blocker->ShouldShowNotificationAsPopup(notification))
       return false;
@@ -62,9 +61,7 @@
 }
 
 NotificationList::NotificationList(MessageCenter* message_center)
-    : message_center_(message_center),
-      quiet_mode_(false) {
-}
+    : message_center_(message_center), quiet_mode_(false) {}
 
 NotificationList::~NotificationList() = default;
 
@@ -145,6 +142,17 @@
   return notifications;
 }
 
+NotificationList::Notifications NotificationList::GetNotificationsByOriginUrl(
+    const GURL& source_url) const {
+  Notifications notifications;
+  for (const auto& tuple : notifications_) {
+    Notification* notification = tuple.first.get();
+    if (notification->origin_url() == source_url)
+      notifications.insert(notification);
+  }
+  return notifications;
+}
+
 bool NotificationList::SetNotificationIcon(const std::string& notification_id,
                                            const gfx::Image& image) {
   auto iter = GetNotification(notification_id);
@@ -186,9 +194,9 @@
   return false;
 }
 
-NotificationList::PopupNotifications
-NotificationList::GetPopupNotifications(const NotificationBlockers& blockers,
-                                        std::list<std::string>* blocked) {
+NotificationList::PopupNotifications NotificationList::GetPopupNotifications(
+    const NotificationBlockers& blockers,
+    std::list<std::string>* blocked) {
   PopupNotifications result;
   size_t default_priority_popup_count = 0;
 
@@ -225,8 +233,8 @@
   return result;
 }
 
-void NotificationList::MarkSinglePopupAsShown(
-    const std::string& id, bool mark_notification_as_read) {
+void NotificationList::MarkSinglePopupAsShown(const std::string& id,
+                                              bool mark_notification_as_read) {
   auto iter = GetNotification(id);
   DCHECK(iter != notifications_.end());
 
diff --git a/ui/message_center/notification_list.h b/ui/message_center/notification_list.h
index c73cc732..df8b8d2 100644
--- a/ui/message_center/notification_list.h
+++ b/ui/message_center/notification_list.h
@@ -107,6 +107,9 @@
   // Returns all notifications that have a matching |app_id|.
   Notifications GetNotificationsByAppId(const std::string& app_id) const;
 
+  // Returns all notifications that have a matching `origin_url`.
+  Notifications GetNotificationsByOriginUrl(const GURL& origin_url) const;
+
   // Returns true if the notification exists and was updated.
   bool SetNotificationIcon(const std::string& notification_id,
                            const gfx::Image& image);
diff --git a/ui/message_center/notification_list_unittest.cc b/ui/message_center/notification_list_unittest.cc
index b54f3a6..667c74b 100644
--- a/ui/message_center/notification_list_unittest.cc
+++ b/ui/message_center/notification_list_unittest.cc
@@ -414,6 +414,71 @@
   }
 }
 
+// Tests that GetNotificationsByOriginUrl returns notifications regardless of
+// their visibility.
+TEST_F(NotificationListTest, GetNotificationsByOriginUrl) {
+  const GURL kUrl1(u"http://www.kurl1.com");
+  const GURL kUrl2(u"http://www.kUrl2.com");
+
+  {
+    // Add a notification for `kurl1`.
+    const std::string id1("id1");
+    std::unique_ptr<Notification> notification(
+        new Notification(NOTIFICATION_TYPE_PROGRESS, id1, u"updated",
+                         u"updated", gfx::Image(), std::u16string(), kUrl1,
+                         NotifierId(), RichNotificationData(), nullptr));
+    notification_list_->AddNotification(std::move(notification));
+    EXPECT_EQ(1u,
+              notification_list_->GetNotificationsByOriginUrl(kUrl1).size());
+
+    // Mark the popup as shown but not read.
+    notification_list_->MarkSinglePopupAsShown(id1, false);
+    EXPECT_EQ(1u,
+              notification_list_->GetNotificationsByOriginUrl(kUrl1).size());
+
+    // Mark the popup as shown and read.
+    notification_list_->MarkSinglePopupAsShown(id1, true);
+    EXPECT_EQ(1u,
+              notification_list_->GetNotificationsByOriginUrl(kUrl1).size());
+
+    // Remove the notification.
+    notification_list_->RemoveNotification(id1);
+    EXPECT_EQ(0u,
+              notification_list_->GetNotificationsByOriginUrl(kUrl1).size());
+
+    // Add two notifications for `kurl1` and one for `kUrl2`.
+    notification = std::make_unique<Notification>(
+        NOTIFICATION_TYPE_PROGRESS, id1, u"updated", u"updated", gfx::Image(),
+        std::u16string(), kUrl1, NotifierId(), RichNotificationData(), nullptr);
+    notification_list_->AddNotification(std::move(notification));
+
+    const std::string id2("id2");
+    notification = std::make_unique<Notification>(
+        NOTIFICATION_TYPE_PROGRESS, id2, u"updated", u"updated", gfx::Image(),
+        std::u16string(), kUrl1, NotifierId(), RichNotificationData(), nullptr);
+    notification_list_->AddNotification(std::move(notification));
+    EXPECT_EQ(2u,
+              notification_list_->GetNotificationsByOriginUrl(kUrl1).size());
+
+    const std::string id3("id3");
+    notification = std::make_unique<Notification>(
+        NOTIFICATION_TYPE_PROGRESS, id3, u"updated", u"updated", gfx::Image(),
+        std::u16string(), kUrl2, NotifierId(), RichNotificationData(), nullptr);
+    notification_list_->AddNotification(std::move(notification));
+    EXPECT_EQ(2u,
+              notification_list_->GetNotificationsByOriginUrl(kUrl1).size());
+    EXPECT_EQ(1u,
+              notification_list_->GetNotificationsByOriginUrl(kUrl2).size());
+  }
+
+  for (GURL url : {kUrl1, kUrl2}) {
+    for (auto* notification :
+         notification_list_->GetNotificationsByOriginUrl(url)) {
+      EXPECT_EQ(url, notification->origin_url());
+    }
+  }
+}
+
 TEST_F(NotificationListTest, HasPopupsWithPriority) {
   ASSERT_EQ(0u, notification_list_->NotificationCount(blockers_));
 
diff --git a/ui/ozone/platform/drm/common/drm_util.cc b/ui/ozone/platform/drm/common/drm_util.cc
index ba79507..5e8b118 100644
--- a/ui/ozone/platform/drm/common/drm_util.cc
+++ b/ui/ozone/platform/drm/common/drm_util.cc
@@ -50,23 +50,20 @@
   return false;
 }
 
-// Return a CRTC compatible with |connector| and not already used in |displays|.
+// Returns a CRTC compatible with |connector| and not already used in |displays|
+// and the CRTC that's currently connected to the connector.
 // If there are multiple compatible CRTCs, the one that supports the majority of
-// planes will be returned.
-uint32_t GetCrtc(
+// planes will be returned as best CRTC.
+std::pair<uint32_t /* best_crtc */, uint32_t /* connected_crtc */> GetCrtcs(
     int fd,
     drmModeConnector* connector,
     drmModeRes* resources,
-    const std::vector<std::unique_ptr<HardwareDisplayControllerInfo>>&
-        displays) {
-  ScopedDrmPlaneResPtr plane_resources(drmModeGetPlaneResources(fd));
-  std::vector<ScopedDrmPlanePtr> planes;
-  for (uint32_t i = 0; i < plane_resources->count_planes; i++)
-    planes.emplace_back(drmModeGetPlane(fd, plane_resources->planes[i]));
-
+    const std::vector<std::unique_ptr<HardwareDisplayControllerInfo>>& displays,
+    const std::vector<ScopedDrmPlanePtr>& planes) {
   DCHECK_GE(32, resources->count_crtcs);
+  int most_crtc_planes = -1;
   uint32_t best_crtc = 0;
-  int best_crtc_planes = -1;
+  uint32_t connected_crtc = 0;
 
   // Try to find an encoder for the connector.
   for (int i = 0; i < connector->count_encoders; ++i) {
@@ -74,6 +71,9 @@
     if (!encoder)
       continue;
 
+    if (connector->encoder_id == encoder->encoder_id)
+      connected_crtc = encoder->crtc_id;
+
     for (int j = 0; j < resources->count_crtcs; ++j) {
       // Check if the encoder is compatible with this CRTC
       int crtc_bit = 1 << j;
@@ -85,20 +85,16 @@
           planes.begin(), planes.end(), [crtc_bit](const ScopedDrmPlanePtr& p) {
             return p->possible_crtcs & crtc_bit;
           });
-
-      uint32_t assigned_crtc = 0;
-      if (connector->encoder_id == encoder->encoder_id)
-        assigned_crtc = encoder->crtc_id;
-      if (supported_planes > best_crtc_planes ||
-          (supported_planes == best_crtc_planes &&
-           assigned_crtc == resources->crtcs[j])) {
-        best_crtc_planes = supported_planes;
+      if (supported_planes > most_crtc_planes ||
+          (supported_planes == most_crtc_planes &&
+           connected_crtc == resources->crtcs[j])) {
+        most_crtc_planes = supported_planes;
         best_crtc = resources->crtcs[j];
       }
     }
   }
 
-  return best_crtc;
+  return std::make_pair(best_crtc, connected_crtc);
 }
 
 // Computes the refresh rate for the specific mode. If we have enough
@@ -329,11 +325,12 @@
 
 HardwareDisplayControllerInfo::~HardwareDisplayControllerInfo() = default;
 
-std::vector<std::unique_ptr<HardwareDisplayControllerInfo>>
-GetAvailableDisplayControllerInfos(int fd) {
+std::pair<HardwareDisplayControllerInfoList, std::vector<uint32_t>>
+GetDisplayInfosAndInvalidCrtcs(int fd) {
   ScopedDrmResourcesPtr resources(drmModeGetResources(fd));
   DCHECK(resources) << "Failed to get DRM resources";
   std::vector<std::unique_ptr<HardwareDisplayControllerInfo>> displays;
+  std::vector<uint32_t> invalid_crtcs;
 
   std::vector<ScopedDrmConnectorPtr> connectors;
   std::vector<drmModeConnector*> available_connectors;
@@ -383,12 +380,24 @@
                             c1_crtcs != c2_crtcs;
                    });
 
+  ScopedDrmPlaneResPtr plane_resources(drmModeGetPlaneResources(fd));
+  std::vector<ScopedDrmPlanePtr> planes;
+  for (uint32_t i = 0; i < plane_resources->count_planes; i++)
+    planes.emplace_back(drmModeGetPlane(fd, plane_resources->planes[i]));
+
   for (auto* c : available_connectors) {
-    uint32_t crtc_id = GetCrtc(fd, c, resources.get(), displays);
-    if (!crtc_id)
+    uint32_t best_crtc, connected_crtc;
+    std::tie(best_crtc, connected_crtc) =
+        GetCrtcs(fd, c, resources.get(), displays, planes);
+    if (!best_crtc)
       continue;
 
-    ScopedDrmCrtcPtr crtc(drmModeGetCrtc(fd, crtc_id));
+    // If the currently connected CRTC isn't the best CRTC for the connector,
+    // add the CRTC to the list of Invalid CRTCs.
+    if (connected_crtc && connected_crtc != best_crtc)
+      invalid_crtcs.push_back((connected_crtc));
+
+    ScopedDrmCrtcPtr crtc(drmModeGetCrtc(fd, best_crtc));
     auto iter = std::find_if(connectors.begin(), connectors.end(),
                              [c](const ScopedDrmConnectorPtr& connector) {
                                return connector.get() == c;
@@ -401,7 +410,12 @@
         std::move(*iter), std::move(crtc), index));
   }
 
-  return displays;
+  return std::make_pair(std::move(displays), std::move(invalid_crtcs));
+}
+
+std::vector<std::unique_ptr<HardwareDisplayControllerInfo>>
+GetAvailableDisplayControllerInfos(int fd) {
+  return GetDisplayInfosAndInvalidCrtcs(fd).first;
 }
 
 bool SameMode(const drmModeModeInfo& lhs, const drmModeModeInfo& rhs) {
diff --git a/ui/ozone/platform/drm/common/drm_util.h b/ui/ozone/platform/drm/common/drm_util.h
index 51f3c35..9e91fae 100644
--- a/ui/ozone/platform/drm/common/drm_util.h
+++ b/ui/ozone/platform/drm/common/drm_util.h
@@ -60,10 +60,18 @@
   uint8_t index_;
 };
 
+using HardwareDisplayControllerInfoList =
+    std::vector<std::unique_ptr<HardwareDisplayControllerInfo>>;
+
 // Looks-up and parses the native display configurations returning all available
-// displays.
-std::vector<std::unique_ptr<HardwareDisplayControllerInfo>>
-GetAvailableDisplayControllerInfos(int fd);
+// displays and CRTCs that weren't picked as best CRTC for each connector.
+// TODO(markyacoub): Create unit tests that tests the different bits and pieces
+// that this function goes through.
+std::pair<HardwareDisplayControllerInfoList, std::vector<uint32_t>>
+GetDisplayInfosAndInvalidCrtcs(int fd);
+
+// Returns the display infos parsed in |GetDisplayInfosAndInvalidCrtcs|
+HardwareDisplayControllerInfoList GetAvailableDisplayControllerInfos(int fd);
 
 bool SameMode(const drmModeModeInfo& lhs, const drmModeModeInfo& rhs);
 
diff --git a/ui/ozone/platform/drm/gpu/drm_gpu_display_manager.cc b/ui/ozone/platform/drm/gpu/drm_gpu_display_manager.cc
index 633510bb..cdd8db4 100644
--- a/ui/ozone/platform/drm/gpu/drm_gpu_display_manager.cc
+++ b/ui/ozone/platform/drm/gpu/drm_gpu_display_manager.cc
@@ -15,10 +15,10 @@
 #include "ui/display/types/display_snapshot.h"
 #include "ui/display/types/gamma_ramp_rgb_entry.h"
 #include "ui/gfx/linux/drm_util_linux.h"
-#include "ui/ozone/platform/drm/common/drm_util.h"
 #include "ui/ozone/platform/drm/gpu/drm_device.h"
 #include "ui/ozone/platform/drm/gpu/drm_device_manager.h"
 #include "ui/ozone/platform/drm/gpu/drm_display.h"
+#include "ui/ozone/platform/drm/gpu/drm_gpu_util.h"
 #include "ui/ozone/platform/drm/gpu/screen_manager.h"
 
 namespace ui {
@@ -132,7 +132,7 @@
     // Receiving a signal that DRM state was updated. Need to reset the plane
     // manager's resource cache since IDs may have changed.
     drm->plane_manager()->ResetConnectorsCache(drm->GetResources());
-    auto display_infos = GetAvailableDisplayControllerInfos(drm->get_fd());
+    auto display_infos = GetDisplayInfosAndUpdateCrtcs(drm->get_fd());
     for (const auto& display_info : display_infos) {
       auto it = std::find_if(
           old_displays.begin(), old_displays.end(),
diff --git a/ui/ozone/platform/drm/gpu/drm_gpu_util.cc b/ui/ozone/platform/drm/gpu/drm_gpu_util.cc
index 4e70e45f..0ecc85f 100644
--- a/ui/ozone/platform/drm/gpu/drm_gpu_util.cc
+++ b/ui/ozone/platform/drm/gpu/drm_gpu_util.cc
@@ -140,4 +140,18 @@
   return false;
 }
 
+HardwareDisplayControllerInfoList GetDisplayInfosAndUpdateCrtcs(int fd) {
+  HardwareDisplayControllerInfoList displays;
+  std::vector<uint32_t> invalid_crtcs;
+  std::tie(displays, invalid_crtcs) = GetDisplayInfosAndInvalidCrtcs(fd);
+  // Disable invalid CRTCs to allow the preferred CRTCs to be enabled later
+  // instead.
+  for (uint32_t crtc : invalid_crtcs) {
+    drmModeSetCrtc(fd, crtc, 0, 0, 0, nullptr, 0, nullptr);
+    LOG(ERROR) << "Disabled unpreferred CRTC " << crtc;
+  }
+
+  return displays;
+}
+
 }  // namespace ui
diff --git a/ui/ozone/platform/drm/gpu/drm_gpu_util.h b/ui/ozone/platform/drm/gpu/drm_gpu_util.h
index ab04da74..815ae51 100644
--- a/ui/ozone/platform/drm/gpu/drm_gpu_util.h
+++ b/ui/ozone/platform/drm/gpu/drm_gpu_util.h
@@ -5,6 +5,7 @@
 #ifndef UI_OZONE_PLATFORM_DRM_GPU_DRM_GPU_UTIL_H_
 #define UI_OZONE_PLATFORM_DRM_GPU_DRM_GPU_UTIL_H_
 
+#include "ui/ozone/platform/drm/common//drm_util.h"
 #include "ui/ozone/platform/drm/common/scoped_drm_types.h"
 #include "ui/ozone/platform/drm/gpu/drm_device.h"
 
@@ -39,6 +40,11 @@
 // Check DRM driver name match.
 bool IsDriverName(const char* device_file_name, const char* driver);
 
+// Returns the display infos parsed in
+// |GetDisplayInfosAndInvalidCrtcs| and disables the invalid CRTCs
+// that weren't picked as preferred CRTCs.
+HardwareDisplayControllerInfoList GetDisplayInfosAndUpdateCrtcs(int fd);
+
 }  // namespace ui
 
 #endif  // UI_OZONE_PLATFORM_DRM_GPU_DRM_GPU_UTIL_H_
diff --git a/ui/ozone/platform/drm/gpu/hardware_display_plane_manager.cc b/ui/ozone/platform/drm/gpu/hardware_display_plane_manager.cc
index e3add6a..3768b20 100644
--- a/ui/ozone/platform/drm/gpu/hardware_display_plane_manager.cc
+++ b/ui/ozone/platform/drm/gpu/hardware_display_plane_manager.cc
@@ -357,7 +357,6 @@
     return false;
   }
 
-  DisableConnectedConnectorsToCrtcs(resources);
   ResetConnectorsCache(resources);
 
   unsigned int num_crtcs_with_out_fence_ptr = 0;
@@ -414,29 +413,6 @@
   return true;
 }
 
-void HardwareDisplayPlaneManager::DisableConnectedConnectorsToCrtcs(
-    const ScopedDrmResourcesPtr& resources) {
-  // Should only be called when no CRTC state has been set yet because we
-  // hard-disable CRTCs.
-  DCHECK(crtc_state_.empty());
-
-  for (int i = 0; i < resources->count_connectors; ++i) {
-    ScopedDrmConnectorPtr connector =
-        drm_->GetConnector(resources->connectors[i]);
-    if (!connector)
-      continue;
-    // Disable Zombie connectors (disconnected connectors but holding to an
-    // encoder).
-    if (connector->encoder_id &&
-        connector->connection == DRM_MODE_DISCONNECTED) {
-      ScopedDrmEncoderPtr encoder(
-          drmModeGetEncoder(drm_->get_fd(), connector->encoder_id));
-      if (encoder)
-        drm_->DisableCrtc(encoder->crtc_id);
-    }
-  }
-}
-
 const HardwareDisplayPlaneManager::CrtcState&
 HardwareDisplayPlaneManager::GetCrtcStateForCrtcId(uint32_t crtc_id) {
   return CrtcStateForCrtcId(crtc_id);
diff --git a/ui/ozone/platform/drm/gpu/hardware_display_plane_manager.h b/ui/ozone/platform/drm/gpu/hardware_display_plane_manager.h
index b1defe85..fa052c5 100644
--- a/ui/ozone/platform/drm/gpu/hardware_display_plane_manager.h
+++ b/ui/ozone/platform/drm/gpu/hardware_display_plane_manager.h
@@ -209,16 +209,6 @@
   void UpdateCrtcAndPlaneStatesAfterModeset(
       const CommitRequest& commit_request);
 
-  // As the CRTC is being initialized, all connectors connected to it should
-  // be disabled. This is a workaround for a bug on Hatch where Puff enables
-  // a connector in dev mode before Chrome even starts. The kernel maps the HW
-  // state at initial modeset (with a dangling connector attached to a CRTC).
-  // When an Atomic Modeset is performed, it fails to modeset as the CRTC is
-  // already attached to another dead connector. (Analysis: crbug/1067121#c5)
-  // TODO(b/168154314): Remove this call when the bug is fixed.
-  void DisableConnectedConnectorsToCrtcs(
-      const ScopedDrmResourcesPtr& resources);
-
   virtual bool InitializePlanes() = 0;
 
   virtual bool SetPlaneData(HardwareDisplayPlaneList* plane_list,
diff --git a/ui/ozone/platform/wayland/common/wayland_util.cc b/ui/ozone/platform/wayland/common/wayland_util.cc
index de0bbbf..834de30b 100644
--- a/ui/ozone/platform/wayland/common/wayland_util.cc
+++ b/ui/ozone/platform/wayland/common/wayland_util.cc
@@ -272,7 +272,7 @@
   return gfx::ScaleToRoundedRect(
       wl::TranslateBoundsToParentCoordinates(window->GetBounds(),
                                              parent_window->GetBounds()),
-      1.0 / window->window_scale());
+      1.0f / window->window_scale());
 }
 
 std::vector<gfx::Rect> CreateRectsFromSkPath(const SkPath& path) {
@@ -288,7 +288,7 @@
   return rects;
 }
 
-SkPath ConvertPathToDIP(const SkPath& path_in_pixels, const int32_t scale) {
+SkPath ConvertPathToDIP(const SkPath& path_in_pixels, float scale) {
   SkScalar sk_scale = SkFloatToScalar(1.0f / scale);
   gfx::Transform transform;
   transform.Scale(sk_scale, sk_scale);
diff --git a/ui/ozone/platform/wayland/common/wayland_util.h b/ui/ozone/platform/wayland/common/wayland_util.h
index 9546e29..ecdc446e 100644
--- a/ui/ozone/platform/wayland/common/wayland_util.h
+++ b/ui/ozone/platform/wayland/common/wayland_util.h
@@ -89,7 +89,7 @@
 std::vector<gfx::Rect> CreateRectsFromSkPath(const SkPath& path);
 
 // Returns converted SkPath in DIPs from the one in pixels.
-SkPath ConvertPathToDIP(const SkPath& path_in_pixels, const int32_t scale);
+SkPath ConvertPathToDIP(const SkPath& path_in_pixels, float scale);
 
 }  // namespace wl
 
diff --git a/ui/ozone/platform/wayland/host/wayland_output.cc b/ui/ozone/platform/wayland/host/wayland_output.cc
index 7f52891..1765da540f 100644
--- a/ui/ozone/platform/wayland/host/wayland_output.cc
+++ b/ui/ozone/platform/wayland/host/wayland_output.cc
@@ -92,11 +92,11 @@
     const gfx::Size logical_size = xdg_output_->logical_size();
     if (!logical_size.IsEmpty()) {
       if (logical_size.width() >= logical_size.height()) {
-        scale_factor_ = ceil(rect_in_physical_pixels_.width() /
-                             static_cast<float>(logical_size.width()));
+        scale_factor_ = rect_in_physical_pixels_.width() /
+                        static_cast<float>(logical_size.width());
       } else {
-        scale_factor_ = ceil(rect_in_physical_pixels_.height() /
-                             static_cast<float>(logical_size.height()));
+        scale_factor_ = rect_in_physical_pixels_.height() /
+                        static_cast<float>(logical_size.height());
       }
     }
   }
diff --git a/ui/ozone/platform/wayland/host/wayland_output.h b/ui/ozone/platform/wayland/host/wayland_output.h
index fcf87cee..6b566a3 100644
--- a/ui/ozone/platform/wayland/host/wayland_output.h
+++ b/ui/ozone/platform/wayland/host/wayland_output.h
@@ -32,7 +32,7 @@
    public:
     virtual void OnOutputHandleMetrics(uint32_t output_id,
                                        const gfx::Rect& new_bounds,
-                                       int32_t scale_factor,
+                                       float scale_factor,
                                        int32_t transform) = 0;
 
    protected:
@@ -52,7 +52,7 @@
 
   uint32_t output_id() const { return output_id_; }
   bool has_output(wl_output* output) const { return output_.get() == output; }
-  int32_t scale_factor() const { return scale_factor_; }
+  float scale_factor() const { return scale_factor_; }
   int32_t transform() const { return transform_; }
   gfx::Rect bounds() const { return rect_in_physical_pixels_; }
 
@@ -92,7 +92,7 @@
   const uint32_t output_id_ = 0;
   wl::Object<wl_output> output_;
   std::unique_ptr<XDGOutput> xdg_output_;
-  int32_t scale_factor_ = kDefaultScaleFactor;
+  float scale_factor_ = kDefaultScaleFactor;
   int32_t transform_ = WL_OUTPUT_TRANSFORM_NORMAL;
   gfx::Rect rect_in_physical_pixels_;
 
diff --git a/ui/ozone/platform/wayland/host/wayland_output_manager.cc b/ui/ozone/platform/wayland/host/wayland_output_manager.cc
index 848c9706..34600e65 100644
--- a/ui/ozone/platform/wayland/host/wayland_output_manager.cc
+++ b/ui/ozone/platform/wayland/host/wayland_output_manager.cc
@@ -114,7 +114,7 @@
 
 void WaylandOutputManager::OnOutputHandleMetrics(uint32_t output_id,
                                                  const gfx::Rect& new_bounds,
-                                                 int32_t scale_factor,
+                                                 float scale_factor,
                                                  int32_t transform) {
   if (wayland_screen_) {
     wayland_screen_->OnOutputAddedOrUpdated(output_id, new_bounds,
diff --git a/ui/ozone/platform/wayland/host/wayland_output_manager.h b/ui/ozone/platform/wayland/host/wayland_output_manager.h
index ddecbde2..8ef08d5a 100644
--- a/ui/ozone/platform/wayland/host/wayland_output_manager.h
+++ b/ui/ozone/platform/wayland/host/wayland_output_manager.h
@@ -55,7 +55,7 @@
   // WaylandOutput::Delegate:
   void OnOutputHandleMetrics(uint32_t output_id,
                              const gfx::Rect& new_bounds,
-                             int32_t scale_factor,
+                             float scale_factor,
                              int32_t transform) override;
 
   using OutputList = base::flat_map<uint32_t, std::unique_ptr<WaylandOutput>>;
diff --git a/ui/ozone/platform/wayland/host/wayland_popup.cc b/ui/ozone/platform/wayland/host/wayland_popup.cc
index 57480f7..b3faa8e 100644
--- a/ui/ozone/platform/wayland/host/wayland_popup.cc
+++ b/ui/ozone/platform/wayland/host/wayland_popup.cc
@@ -10,6 +10,7 @@
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "ui/base/ui_base_types.h"
 #include "ui/display/display.h"
+#include "ui/gfx/geometry/point.h"
 #include "ui/gfx/geometry/rect.h"
 #include "ui/gfx/geometry/transform.h"
 #include "ui/ozone/platform/wayland/common/wayland_object.h"
@@ -76,10 +77,11 @@
     set_frame_insets_px(*parent_insets_px);
     // Popups should have the same offset for their geometry as their parents
     // have, otherwise Wayland draws them incorrectly.
-    shell_popup_->SetWindowGeometry({parent_insets_px->left() / window_scale(),
-                                     parent_insets_px->top() / window_scale(),
-                                     params.bounds.width(),
-                                     params.bounds.height()});
+    const gfx::Point p = gfx::ScaleToRoundedPoint(
+        {parent_insets_px->left(), parent_insets_px->top()},
+        1.f / window_scale());
+    shell_popup_->SetWindowGeometry(
+        {p.x(), p.y(), params.bounds.width(), params.bounds.height()});
   }
 
   parent_window()->set_child_window(this);
diff --git a/ui/ozone/platform/wayland/host/wayland_screen.cc b/ui/ozone/platform/wayland/host/wayland_screen.cc
index c998095..2cfe030 100644
--- a/ui/ozone/platform/wayland/host/wayland_screen.cc
+++ b/ui/ozone/platform/wayland/host/wayland_screen.cc
@@ -108,7 +108,7 @@
 
 void WaylandScreen::OnOutputAddedOrUpdated(uint32_t output_id,
                                            const gfx::Rect& bounds,
-                                           int32_t scale,
+                                           float scale,
                                            int32_t transform) {
   AddOrUpdateDisplay(output_id, bounds, scale, transform);
 }
@@ -133,7 +133,7 @@
 
 void WaylandScreen::AddOrUpdateDisplay(uint32_t output_id,
                                        const gfx::Rect& new_bounds,
-                                       int32_t scale_factor,
+                                       float scale_factor,
                                        int32_t transform) {
   display::Display changed_display(output_id);
   if (!display::Display::HasForceDeviceScaleFactor()) {
diff --git a/ui/ozone/platform/wayland/host/wayland_screen.h b/ui/ozone/platform/wayland/host/wayland_screen.h
index 6cf8b1a0..916951e 100644
--- a/ui/ozone/platform/wayland/host/wayland_screen.h
+++ b/ui/ozone/platform/wayland/host/wayland_screen.h
@@ -41,7 +41,7 @@
 
   void OnOutputAddedOrUpdated(uint32_t output_id,
                               const gfx::Rect& bounds,
-                              int32_t output_scale,
+                              float output_scale,
                               int32_t output_transform);
   void OnOutputRemoved(uint32_t output_id);
 
@@ -75,7 +75,7 @@
  private:
   void AddOrUpdateDisplay(uint32_t output_id,
                           const gfx::Rect& bounds,
-                          int32_t scale,
+                          float scale,
                           int32_t transform);
 
   WaylandConnection* connection_ = nullptr;
diff --git a/ui/ozone/platform/wayland/host/wayland_subsurface.cc b/ui/ozone/platform/wayland/host/wayland_subsurface.cc
index 1ff7728..585e758 100644
--- a/ui/ozone/platform/wayland/host/wayland_subsurface.cc
+++ b/ui/ozone/platform/wayland/host/wayland_subsurface.cc
@@ -17,11 +17,11 @@
 // Returns DIP bounds of the subsurface relative to the parent surface.
 gfx::Rect AdjustSubsurfaceBounds(const gfx::Rect& bounds_px,
                                  const gfx::Rect& parent_bounds_px,
-                                 int32_t buffer_scale) {
+                                 float buffer_scale) {
   const auto bounds_dip =
-      gfx::ScaleToEnclosingRect(bounds_px, 1.0 / buffer_scale);
+      gfx::ScaleToEnclosingRect(bounds_px, 1.0f / buffer_scale);
   const auto parent_bounds_dip =
-      gfx::ScaleToEnclosingRect(parent_bounds_px, 1.0 / buffer_scale);
+      gfx::ScaleToEnclosingRect(parent_bounds_px, 1.0f / buffer_scale);
   return wl::TranslateBoundsToParentCoordinates(bounds_dip, parent_bounds_dip);
 }
 
@@ -90,7 +90,7 @@
 void WaylandSubsurface::ConfigureAndShowSurface(
     const gfx::Rect& bounds_px,
     const gfx::Rect& parent_bounds_px,
-    int32_t buffer_scale,
+    float buffer_scale,
     const WaylandSurface* reference_below,
     const WaylandSurface* reference_above) {
   Show();
diff --git a/ui/ozone/platform/wayland/host/wayland_subsurface.h b/ui/ozone/platform/wayland/host/wayland_subsurface.h
index f4a8808..e5c696d 100644
--- a/ui/ozone/platform/wayland/host/wayland_subsurface.h
+++ b/ui/ozone/platform/wayland/host/wayland_subsurface.h
@@ -38,7 +38,7 @@
   //     reference subsurface.
   void ConfigureAndShowSurface(const gfx::Rect& bounds_px,
                                const gfx::Rect& parent_bounds_px,
-                               int32_t buffer_scale,
+                               float buffer_scale,
                                const WaylandSurface* reference_below,
                                const WaylandSurface* reference_above);
 
diff --git a/ui/ozone/platform/wayland/host/wayland_surface.cc b/ui/ozone/platform/wayland/host/wayland_surface.cc
index 587b058f..969f6a5c 100644
--- a/ui/ozone/platform/wayland/host/wayland_surface.cc
+++ b/ui/ozone/platform/wayland/host/wayland_surface.cc
@@ -283,10 +283,10 @@
   wl_surface_set_buffer_transform(surface_.get(), wl_transform);
 }
 
-void WaylandSurface::SetSurfaceBufferScale(int32_t scale) {
-  DCHECK_GE(scale, 1);
+void WaylandSurface::SetSurfaceBufferScale(float scale) {
+  DCHECK_GE(scale, 1.0f);
   if (!SurfaceSubmissionInPixelCoordinates()) {
-    wl_surface_set_buffer_scale(surface_.get(), scale);
+    wl_surface_set_buffer_scale(surface_.get(), static_cast<int>(scale));
     buffer_scale_ = scale;
   }
   connection_->ScheduleFlush();
diff --git a/ui/ozone/platform/wayland/host/wayland_surface.h b/ui/ozone/platform/wayland/host/wayland_surface.h
index 0fffb72..fa2d36434 100644
--- a/ui/ozone/platform/wayland/host/wayland_surface.h
+++ b/ui/ozone/platform/wayland/host/wayland_surface.h
@@ -97,7 +97,7 @@
   // process) for the next submitted buffer. This helps Wayland compositor to
   // determine buffer size in dip (GPU operates in pixels. So, when buffers are
   // created, their requested size is in pixels).
-  void SetSurfaceBufferScale(int32_t scale);
+  void SetSurfaceBufferScale(float scale);
 
   // Sets the region that is opaque on this surface in physical pixels. This is
   // expected to be called whenever the region that the surface span changes or
diff --git a/ui/ozone/platform/wayland/host/wayland_window.cc b/ui/ozone/platform/wayland/host/wayland_window.cc
index 04099ae..73d3d65 100644
--- a/ui/ozone/platform/wayland/host/wayland_window.cc
+++ b/ui/ozone/platform/wayland/host/wayland_window.cc
@@ -119,10 +119,10 @@
   if (!output)
     return;
 
-  int32_t new_scale = output->scale_factor();
+  float new_scale = output->scale_factor();
   ui_scale_ = output->GetUIScaleFactor();
 
-  int32_t old_scale = window_scale();
+  float old_scale = window_scale();
   window_scale_ = new_scale;
 
   // We need to keep DIP size of the window the same whenever the scale changes.
@@ -138,8 +138,8 @@
   return accelerated_widget_;
 }
 
-void WaylandWindow::SetWindowScale(int32_t new_scale) {
-  DCHECK_GE(new_scale, 0);
+void WaylandWindow::SetWindowScale(float new_scale) {
+  DCHECK_GE(new_scale, 0.f);
   window_scale_ = new_scale;
 }
 
@@ -278,7 +278,7 @@
 }
 
 gfx::Rect WaylandWindow::GetBoundsInDIP() const {
-  return gfx::ScaleToRoundedRect(bounds_px_, 1.0 / window_scale());
+  return gfx::ScaleToRoundedRect(bounds_px_, 1.0f / window_scale());
 }
 
 void WaylandWindow::SetTitle(const std::u16string& title) {}
diff --git a/ui/ozone/platform/wayland/host/wayland_window.h b/ui/ozone/platform/wayland/host/wayland_window.h
index 0bc0fdc..cc97979 100644
--- a/ui/ozone/platform/wayland/host/wayland_window.h
+++ b/ui/ozone/platform/wayland/host/wayland_window.h
@@ -112,8 +112,8 @@
   // Sets the window_scale for this window with respect to a display this window
   // is located at. This determines how events can be translated and how size of
   // the surface is treated (px to DIP conversion and vice versa.)
-  void SetWindowScale(int32_t new_scale);
-  int32_t window_scale() const { return window_scale_; }
+  void SetWindowScale(float new_scale);
+  float window_scale() const { return window_scale_; }
   float ui_scale() const { return ui_scale_; }
 
   // A preferred output is the one with the largest scale. This is needed to
@@ -363,7 +363,7 @@
   // We need it to place and size the menus properly.
   float ui_scale_ = 1.0f;
   // Current scale factor of the output where the window is located at.
-  int32_t window_scale_ = 1;
+  float window_scale_ = 1.f;
 
   // Stores current opacity of the window. Set on ::Initialize call.
   ui::PlatformWindowOpacity opacity_;
diff --git a/ui/ozone/platform/wayland/host/wayland_window_unittest.cc b/ui/ozone/platform/wayland/host/wayland_window_unittest.cc
index c9689a1..2892fb8ff 100644
--- a/ui/ozone/platform/wayland/host/wayland_window_unittest.cc
+++ b/ui/ozone/platform/wayland/host/wayland_window_unittest.cc
@@ -2992,7 +2992,7 @@
   EXPECT_TRUE(mock_surface_subsurface);
   wayland_subsurface->ConfigureAndShowSurface(
       subsurface_bounds, gfx::Rect(0, 0, 640, 480) /*parent_bounds_px*/,
-      1 /*buffer_scale*/, nullptr, nullptr);
+      1.f /*buffer_scale*/, nullptr, nullptr);
   connection_->ScheduleFlush();
 
   Sync();
diff --git a/ui/views/controls/menu/menu_controller.cc b/ui/views/controls/menu/menu_controller.cc
index 5a554d1..92a51e8 100644
--- a/ui/views/controls/menu/menu_controller.cc
+++ b/ui/views/controls/menu/menu_controller.cc
@@ -2974,25 +2974,23 @@
 
 void MenuController::RepostEventAndCancel(SubmenuView* source,
                                           const ui::LocatedEvent* event) {
-  // Cancel can lead to the deletion |source| so we save the view and window to
-  // be used when reposting the event.
   gfx::Point screen_loc(event->location());
   View::ConvertPointToScreen(source->GetScrollViewContainer(), &screen_loc);
 
-#if defined(OS_WIN) || BUILDFLAG(IS_CHROMEOS_ASH)
-  gfx::NativeView native_view = source->GetWidget()->GetNativeView();
-  gfx::NativeWindow window = nullptr;
-  if (native_view) {
-    display::Screen* screen = display::Screen::GetScreen();
-    window = screen->GetWindowAtScreenPoint(screen_loc);
-  }
-#endif
-
 #if defined(OS_WIN)
   if (event->IsMouseEvent() || event->IsTouchEvent()) {
     base::WeakPtr<MenuController> this_ref = AsWeakPtr();
     if (state_.item) {
+      // This must be done before we ReleaseCapture() below, which can lead to
+      // deleting the `source`.
+      gfx::NativeView native_view = source->GetWidget()->GetNativeView();
+      gfx::NativeWindow window =
+          native_view
+              ? display::Screen::GetScreen()->GetWindowAtScreenPoint(screen_loc)
+              : nullptr;
+
       state_.item->GetRootMenuItem()->GetSubmenu()->ReleaseCapture();
+
       // We're going to close and we own the event capture. We need to repost
       // the event, otherwise the window the user clicked on won't get the
       // event.
diff --git a/ui/webui/resources/cr_components/chromeos/bluetooth/bluetooth_base_page.html b/ui/webui/resources/cr_components/chromeos/bluetooth/bluetooth_base_page.html
index 32a9fd4..7333517 100644
--- a/ui/webui/resources/cr_components/chromeos/bluetooth/bluetooth_base_page.html
+++ b/ui/webui/resources/cr_components/chromeos/bluetooth/bluetooth_base_page.html
@@ -4,6 +4,15 @@
     height: 276px;
   }
 
+  paper-progress {
+    left: 0;
+    position: absolute;
+    top: 0;
+    width: 100%;
+    --paper-progress-active-color: var(--google-blue-600);
+    --paper-progress-container-color: rgba(var(--google-blue-600-rgb), .24);
+  }
+
   #buttonBar {
     align-self: flex-end;
     margin-top: 16px;
@@ -13,7 +22,7 @@
     display: flex;
     flex-direction: column;
     height: 100%;
-    margin: 8px;
+    margin: 0, 8px, 8px, 8px;
   }
 
   #title {
@@ -32,6 +41,10 @@
   }
 </style>
 <div id="container">
+  <template is="dom-if" if="[[showScanProgress]]" restamp>
+    <paper-progress indeterminate>
+    </paper-progress>
+  </template>
   <h3 id="title">[[i18n('bluetoothPairNewDevice')]]</h3>
   <slot name="page-body" id="pageBody"></slot>
   <div id="buttonBar">
diff --git a/ui/webui/resources/cr_components/chromeos/bluetooth/bluetooth_base_page.js b/ui/webui/resources/cr_components/chromeos/bluetooth/bluetooth_base_page.js
index 4aaeddf..65dfd1e 100644
--- a/ui/webui/resources/cr_components/chromeos/bluetooth/bluetooth_base_page.js
+++ b/ui/webui/resources/cr_components/chromeos/bluetooth/bluetooth_base_page.js
@@ -9,6 +9,7 @@
 
 import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
 import 'chrome://resources/polymer/v3_0/iron-flex-layout/iron-flex-layout-classes.js';
+import 'chrome://resources/polymer/v3_0/paper-progress/paper-progress.js';
 import 'chrome://resources/cr_elements/shared_vars_css.m.js';
 
 import {I18nBehavior, I18nBehaviorInterface} from '//resources/js/i18n_behavior.m.js';
@@ -49,6 +50,12 @@
         },
       },
 
+      /** @type {boolean} */
+      showScanProgress: {
+        type: Boolean,
+        value: false,
+      },
+
       /**
        * Used to access |ButtonName| type in HTML.
        * @type {!ButtonName}
diff --git a/ui/webui/resources/cr_components/chromeos/bluetooth/bluetooth_pairing_device_selection_page.html b/ui/webui/resources/cr_components/chromeos/bluetooth/bluetooth_pairing_device_selection_page.html
index 5ee8700..22c06622c 100644
--- a/ui/webui/resources/cr_components/chromeos/bluetooth/bluetooth_pairing_device_selection_page.html
+++ b/ui/webui/resources/cr_components/chromeos/bluetooth/bluetooth_pairing_device_selection_page.html
@@ -8,7 +8,7 @@
     margin-top: 24px;
   }
 </style>
-<bluetooth-base-page  button-bar-state="[[buttonBarState_]]">
+<bluetooth-base-page show-scan-progress  button-bar-state="[[buttonBarState_]]">
   <div slot="page-body" id="pageBody">
     <localized-link
         localized-string="[[i18nAdvanced('bluetoothPairingLearnMoreLabel')]]">
diff --git a/weblayer/browser/android/javatests/BUILD.gn b/weblayer/browser/android/javatests/BUILD.gn
index 82f31eea..8b6ebe59 100644
--- a/weblayer/browser/android/javatests/BUILD.gn
+++ b/weblayer/browser/android/javatests/BUILD.gn
@@ -181,7 +181,7 @@
     forward_variables_from(invoker, "*")
 
     android_manifest = "AndroidManifest.xml"
-    min_sdk_version = 21
+    min_sdk_version = default_min_sdk_version
 
     if (!defined(additional_apks)) {
       additional_apks = []
diff --git a/weblayer/browser/webapps/weblayer_webapps_client.cc b/weblayer/browser/webapps/weblayer_webapps_client.cc
index 7597bf9..c0ce917 100644
--- a/weblayer/browser/webapps/weblayer_webapps_client.cc
+++ b/weblayer/browser/webapps/weblayer_webapps_client.cc
@@ -18,7 +18,7 @@
 #include "base/guid.h"
 #include "components/webapps/browser/android/add_to_homescreen_params.h"
 #include "components/webapps/browser/android/shortcut_info.h"
-#include "ui/android/color_helpers.h"
+#include "ui/android/color_utils_android.h"
 #include "ui/gfx/android/java_bitmap.h"
 #include "url/gurl.h"
 #endif
diff --git a/weblayer/shell/android/BUILD.gn b/weblayer/shell/android/BUILD.gn
index e30ff09..fad4f5a1 100644
--- a/weblayer/shell/android/BUILD.gn
+++ b/weblayer/shell/android/BUILD.gn
@@ -80,7 +80,7 @@
     ]
     apk_name = invoker.apk_name
     android_manifest = _weblayer_shell_manifest
-    min_sdk_version = 21
+    min_sdk_version = default_min_sdk_version
     target_sdk_version = 28
     android_manifest_dep = ":$_manifest_target"
 
@@ -268,7 +268,7 @@
 
   apk_name = "WebLayerSupport"
   android_manifest = weblayer_support_manifest
-  min_sdk_version = 21
+  min_sdk_version = default_min_sdk_version
   target_sdk_version = 28
   android_manifest_dep = ":weblayer_support_manifest"
   shared_resources = true