diff --git a/DEPS b/DEPS
index b9824936..62ddcc3 100644
--- a/DEPS
+++ b/DEPS
@@ -229,7 +229,7 @@
   #
   # CQ_INCLUDE_TRYBOTS=luci.chrome.try:lacros-amd64-generic-chrome-skylab
   # CQ_INCLUDE_TRYBOTS=luci.chrome.try:lacros-arm-generic-chrome-skylab
-  'lacros_sdk_version': '15274.0.0',
+  'lacros_sdk_version': '15275.0.0',
 
   # Generate location tag metadata to include in tests result data uploaded
   # to ResultDB. This isn't needed on some configs and the tool that generates
@@ -299,7 +299,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': 'c466f3cc8f9ec2426a150538e3e25eb408fcbc26',
+  'skia_revision': '5e69caecd1662c38c14fb5317ef3c60e5b447068',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
@@ -307,7 +307,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': '001ba94e3d7ccfcc4de4b5fee6a14b2fa7159832',
+  'angle_revision': 'ef6f212741f86526b45525500eab8edabfacaba8',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -370,7 +370,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling catapult
   # and whatever else without interference from each other.
-  'catapult_revision': 'bf0782db65682f3918886ba69807c03fe515c2e8',
+  'catapult_revision': 'c6c22477986b41016ba45c0bd6fbe56a3a6a211a',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -418,7 +418,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.
-  'quiche_revision': 'bb8ac619a1e6563a94cf2abd0e59df9d795fa8a3',
+  'quiche_revision': 'df493d7b57cb3ebba5e9bc6f6ccf9fefeb51ade0',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ios_webkit
   # and whatever else without interference from each other.
@@ -478,7 +478,7 @@
 
   # If you change this, also update the libc++ revision in
   # //buildtools/deps_revisions.gni.
-  'libcxx_revision':       '2fc3d704672fbd3e85fad8492d39e02d49412891',
+  'libcxx_revision':       '2948540a20cbb5b2192119f791b04dd62ca7af1c',
 
   # GN CIPD package version.
   'gn_version': 'git_revision:5e19d2fb166fbd4f6f32147fbb2f497091a54ad8',
@@ -771,7 +771,7 @@
 
   'src/clank': {
     'url': 'https://chrome-internal.googlesource.com/clank/internal/apps.git' + '@' +
-    'e5a34e1311d08d400344132fe2c9f950d95b09dd',
+    'c4059e60bbab0bd98f495fce64739433b234b799',
     'condition': 'checkout_android and checkout_src_internal',
   },
 
@@ -1123,7 +1123,7 @@
     Var('boringssl_git') + '/boringssl.git' + '@' +  Var('boringssl_revision'),
 
   'src/third_party/breakpad/breakpad':
-    Var('chromium_git') + '/breakpad/breakpad.git' + '@' + 'cc7abac08b0c52e6581b9c9c4226816b17a4c26d',
+    Var('chromium_git') + '/breakpad/breakpad.git' + '@' + '80430d73aee1fe591bea1475ba3b7fc30c67dfc2',
 
   'src/third_party/byte_buddy': {
       'packages': [
@@ -1211,7 +1211,7 @@
     Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'),
 
   'src/third_party/devtools-frontend-internal': {
-      'url': 'https://chrome-internal.googlesource.com/devtools/devtools-internal.git' + '@' + 'aa28943dc8caddd449134912571d51f0f50d671e',
+      'url': 'https://chrome-internal.googlesource.com/devtools/devtools-internal.git' + '@' + 'f6289dce686d0e4dc0f00e89bd451be7c07b1e1a',
     'condition': 'checkout_src_internal',
   },
 
@@ -1623,7 +1623,7 @@
     Var('chromium_git') + '/external/github.com/cisco/openh264' + '@' + 'db956674bbdfbaab5acdd3fdb4117c2fef5527e9',
 
   'src/third_party/openscreen/src':
-    Var('chromium_git') + '/openscreen' + '@' + '9942fb6d07c9c72b9dcee9e777c544e256f1fb61',
+    Var('chromium_git') + '/openscreen' + '@' + '76d25f8ec350f778a73d9040e6289e9dc78a2167',
 
   'src/third_party/openxr/src': {
     'url': Var('chromium_git') + '/external/github.com/KhronosGroup/OpenXR-SDK' + '@' + 'bf21ccb1007bb531b45d9978919a56ea5059c245',
@@ -1640,7 +1640,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + '42498077f54f5e3093b865b22cf6822f14c73222',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + '39f3c505498164c3ad9ca31a4f5924de5d6aa8d0',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1822,10 +1822,10 @@
     Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + 'd1b65aa5a88f6efd900604dfcda840154e9f16e2',
 
   'src/third_party/webgpu-cts/src':
-    Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + 'e0e7353ccfe5e97a53aed37b4d3d49799fc8de5c',
+    Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + 'e583fa45095a98e12cc28748b30c2b1a9e5babcf',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + '2e1a9a4ae0234d4b1ea7a6fd4188afa1fb20379d',
+    Var('webrtc_git') + '/src.git' + '@' + 'adddc10684259341975292f97227fd3475f0433e',
 
   # Wuffs' canonical repository is at github.com/google/wuffs, but we use
   # Skia's mirror of Wuffs, the same as in upstream Skia's DEPS file.
@@ -1895,7 +1895,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@3d104f2aeaf16bf5d185c1f4459b7ca0ff55cdcf',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@f104cd966e5513ce48b931a2dc8a8808a653c255',
     'condition': 'checkout_src_internal',
   },
 
@@ -1936,7 +1936,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/media_app/app',
-        'version': 'OEBt9gPj59-k8q24vYKZksVSx7x0ium6Vv_5VqjOqcQC',
+        'version': '0UTlluPAiKIWx1AV31tqb0ghjdO_4tLoZXL2sA-8CUIC',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
@@ -1947,7 +1947,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/projector_app/app',
-        'version': 'FEgnPkcHkeDWehL6Lgt_7aeLRFMDt0iL7Dkl0xWEaf4C',
+        'version': 'r3WZGfNcq6NNemjXUw3EkQEb76_BLcQgkTELM0rbAqUC',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
@@ -4862,7 +4862,7 @@
   {
     'name': 'Fetch PGO profiles for mac',
     'pattern': '.',
-    'condition': 'checkout_pgo_profiles and (checkout_mac or checkout_fuchsia)',
+    'condition': 'checkout_pgo_profiles and checkout_mac',
     'action': [ 'python3',
                 'src/tools/update_pgo_profiles.py',
                 '--target=mac',
@@ -4873,7 +4873,7 @@
   {
     'name': 'Fetch PGO profiles for mac arm',
     'pattern': '.',
-    'condition': 'checkout_pgo_profiles and (checkout_mac or checkout_android or checkout_fuchsia)',
+    'condition': 'checkout_pgo_profiles and (checkout_mac or checkout_android)',
     'action': [ 'python3',
                 'src/tools/update_pgo_profiles.py',
                 '--target=mac-arm',
diff --git a/android_webview/BUILD.gn b/android_webview/BUILD.gn
index 7575e4ee..6e539cc 100644
--- a/android_webview/BUILD.gn
+++ b/android_webview/BUILD.gn
@@ -16,6 +16,7 @@
 import("//chrome/android/trichrome.gni")
 import("//components/safe_browsing/buildflags.gni")
 import("//components/spellcheck/spellcheck_build_features.gni")
+import("//components/supervised_user/buildflags.gni")
 import("//device/vr/buildflags/buildflags.gni")
 import("//pdf/features.gni")
 import("//printing/buildflags/buildflags.gni")
@@ -1113,6 +1114,7 @@
   defines = [
     "enable_printing=$enable_printing",
     "safe_browsing_mode=$safe_browsing_mode",
+    "enable_supervised_users=$enable_supervised_users",
   ]
 
   # See :generate_webui_resources for an explanation of the allowlist
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index aba1cadb..a760f72 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -1940,6 +1940,8 @@
     "system/video_conference/bubble/bubble_view_ids.h",
     "system/video_conference/bubble/return_to_app_button.cc",
     "system/video_conference/bubble/return_to_app_button.h",
+    "system/video_conference/bubble/set_value_effects_view.cc",
+    "system/video_conference/bubble/set_value_effects_view.h",
     "system/video_conference/bubble/toggle_effects_view.cc",
     "system/video_conference/bubble/toggle_effects_view.h",
     "system/video_conference/effects/fake_video_conference_effects.cc",
diff --git a/ash/app_list/views/app_list_view_unittest.cc b/ash/app_list/views/app_list_view_unittest.cc
index 21f6e33..b970370 100644
--- a/ash/app_list/views/app_list_view_unittest.cc
+++ b/ash/app_list/views/app_list_view_unittest.cc
@@ -671,6 +671,7 @@
     result->set_display_score(score);
     result->SetTitle(ASCIIToUTF16(title));
     result->set_best_match(true);
+    result->SetCategory(ash::AppListSearchResultCategory::kWeb);
     GetSearchModel()->results()->Add(std::move(result));
     base::RunLoop().RunUntilIdle();
   }
@@ -1002,8 +1003,8 @@
   TestFocusTraversal(backward_view_list, ui::VKEY_UP, false);
 }
 
-// Tests that focus changes update the search box text.
-TEST_F(AppListViewFocusTest, SearchBoxTextUpdatesOnResultFocus) {
+// Tests that focus changes does not update the search box text.
+TEST_F(AppListViewFocusTest, SearchBoxTextDoesNotUpdateOnResultFocus) {
   Show();
   views::Textfield* search_box = search_box_view()->search_box();
   search_box->InsertText(
@@ -1019,18 +1020,24 @@
   // Change focus to the next result
   SimulateKeyPress(ui::VKEY_TAB, false);
 
-  EXPECT_EQ(search_box->GetText(), u"TestResult2");
+  EXPECT_EQ(search_box->GetText(), u"TestText");
+  EXPECT_EQ(search_box_view()->GetSearchBoxGhostTextForTest(),
+            "TestResult2 - Websites");
 
   SimulateKeyPress(ui::VKEY_TAB, true);
 
-  EXPECT_EQ(search_box->GetText(), u"TestResult1");
+  EXPECT_EQ(search_box->GetText(), u"TestText");
+  EXPECT_EQ(search_box_view()->GetSearchBoxGhostTextForTest(),
+            "TestResult1 - Websites");
 
   SimulateKeyPress(ui::VKEY_TAB, false);
 
   // Change focus to the final result
   SimulateKeyPress(ui::VKEY_TAB, false);
 
-  EXPECT_EQ(search_box->GetText(), u"TestResult3");
+  EXPECT_EQ(search_box->GetText(), u"TestText");
+  EXPECT_EQ(search_box_view()->GetSearchBoxGhostTextForTest(),
+            "TestResult3 - Websites");
 }
 
 // Tests that ctrl-A selects all text in the searchbox when the SearchBoxView is
diff --git a/ash/constants/ash_features.cc b/ash/constants/ash_features.cc
index 32d9149..5745207 100644
--- a/ash/constants/ash_features.cc
+++ b/ash/constants/ash_features.cc
@@ -251,7 +251,7 @@
 // Enables or disables extended autocomplete results.
 BASE_FEATURE(kAutocompleteExtendedSuggestions,
              "AutocompleteExtendedSuggestions",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 
 // Enables params tuning experiment for autocorrect on ChromeOS.
 BASE_FEATURE(kAutocorrectParamsTuning,
@@ -507,7 +507,7 @@
 // the user.
 BASE_FEATURE(kChromadAvailable,
              "ChromadAvailable",
-             base::FEATURE_ENABLED_BY_DEFAULT);
+             base::FEATURE_DISABLED_BY_DEFAULT);
 
 // Enables or disables always using device-activity-status data to filter
 // eligible host phones.
diff --git a/ash/projector/projector_controller_impl.cc b/ash/projector/projector_controller_impl.cc
index f670cc7c..05972743 100644
--- a/ash/projector/projector_controller_impl.cc
+++ b/ash/projector/projector_controller_impl.cc
@@ -237,8 +237,13 @@
 void ProjectorControllerImpl::OnTranscriptionError() {
   is_speech_recognition_on_ = false;
 
-  ProjectorUiController::ShowFailureNotification(
-      IDS_ASH_PROJECTOR_FAILURE_MESSAGE_TRANSCRIPTION);
+  // TODO(b/261093550) Investigate the real reason why
+  // we get a speech recognition error after we notify it to
+  // stop.
+  if (!pending_speech_recognition_stop_) {
+    ProjectorUiController::ShowFailureNotification(
+        IDS_ASH_PROJECTOR_FAILURE_MESSAGE_TRANSCRIPTION);
+  }
 
   auto* capture_mode_controller = CaptureModeController::Get();
   if (capture_mode_controller->is_recording_in_progress()) {
@@ -247,11 +252,13 @@
   } else {
     MaybeWrapUpRecording();
   }
+
+  pending_speech_recognition_stop_ = false;
 }
 
 void ProjectorControllerImpl::OnSpeechRecognitionStopped() {
   is_speech_recognition_on_ = false;
-
+  pending_speech_recognition_stop_ = false;
   // Try to wrap up recording. This can be no-op if DLP check is not completed.
   MaybeWrapUpRecording();
 }
@@ -495,6 +502,7 @@
   DCHECK(!is_speech_recognition_on_);
   client_->StartSpeechRecognition();
   is_speech_recognition_on_ = true;
+  pending_speech_recognition_stop_ = false;
 }
 
 void ProjectorControllerImpl::MaybeStopSpeechRecognition() {
@@ -507,6 +515,7 @@
   DCHECK(client_->GetSpeechRecognitionAvailability().IsAvailable());
 
   client_->StopSpeechRecognition();
+  pending_speech_recognition_stop_ = true;
 }
 
 void ProjectorControllerImpl::OnContainerFolderCreated(
diff --git a/ash/projector/projector_controller_impl.h b/ash/projector/projector_controller_impl.h
index 3990484e..d93587c0 100644
--- a/ash/projector/projector_controller_impl.h
+++ b/ash/projector/projector_controller_impl.h
@@ -195,6 +195,10 @@
   std::unique_ptr<ProjectorUiController> ui_controller_;
   std::unique_ptr<ProjectorMetadataController> metadata_controller_;
 
+  // Whether ProjectorController has informed its client to stop
+  // speech recognition.
+  bool pending_speech_recognition_stop_ = false;
+
   // Whether speech recognition is taking place or not.
   bool is_speech_recognition_on_ = false;
 
diff --git a/ash/system/video_conference/bubble/bubble_view.cc b/ash/system/video_conference/bubble/bubble_view.cc
index c83ffe5b..1978bea 100644
--- a/ash/system/video_conference/bubble/bubble_view.cc
+++ b/ash/system/video_conference/bubble/bubble_view.cc
@@ -7,6 +7,7 @@
 #include "ash/system/tray/tray_bubble_view.h"
 #include "ash/system/video_conference/bubble/bubble_view_ids.h"
 #include "ash/system/video_conference/bubble/return_to_app_button.h"
+#include "ash/system/video_conference/bubble/set_value_effects_view.h"
 #include "ash/system/video_conference/bubble/toggle_effects_view.h"
 #include "ash/system/video_conference/effects/video_conference_tray_effects_manager.h"
 #include "ash/system/video_conference/video_conference_tray_controller.h"
@@ -37,6 +38,10 @@
   if (controller->effects_manager().HasToggleEffects())
     AddChildView(std::make_unique<ToggleEffectsView>(controller));
 
+  if (controller->effects_manager().HasSetValueEffects()) {
+    AddChildView(std::make_unique<SetValueEffectsView>(controller));
+  }
+
   SetBorder(views::CreateEmptyBorder(
       gfx::Insets::VH(kBorderInsetDimension, kBorderInsetDimension)));
 }
diff --git a/ash/system/video_conference/bubble/bubble_view_ids.h b/ash/system/video_conference/bubble/bubble_view_ids.h
index 6c49072..0b9d68d 100644
--- a/ash/system/video_conference/bubble/bubble_view_ids.h
+++ b/ash/system/video_conference/bubble/bubble_view_ids.h
@@ -29,9 +29,16 @@
   // `kToggleEffectsContainer`.
   kToggleEffectsButton,
 
-  // Button for setting an individual value of a "set-value" VC effect, a child
-  // of `kSetValueEffectsContainer`.
-  kSetValueButton,
+  // Buttons for setting an individual value of a "set-value" VC effect,
+  // children of `kSetValueEffectsContainer`. Since the number of values for a
+  // given effect can't be known at compile-time, an allowable range of IDs
+  // [`kSetValueButtonMin`..`kSetValueButtonMax`] is allocated, with IDs clamped
+  // at kSetValueButtonMax. If a specific ID doesn't matter, then just use
+  // `kSetValueButton`.
+  kSetValueButtonMin = kToggleEffectsButton + 1,
+  kSetValueButton = kSetValueButtonMin,
+  kSetValueButtonMax = kSetValueButtonMin + 100,
+  kNextAvailableId = kSetValueButtonMax + 1,
 };
 
 }  // namespace ash::video_conference
diff --git a/ash/system/video_conference/bubble/bubble_view_unittest.cc b/ash/system/video_conference/bubble/bubble_view_unittest.cc
index aea661c..3c89fb4 100644
--- a/ash/system/video_conference/bubble/bubble_view_unittest.cc
+++ b/ash/system/video_conference/bubble/bubble_view_unittest.cc
@@ -46,6 +46,7 @@
     // Instantiate these fake effects, to be registered/unregistered as needed.
     office_bunny_ =
         std::make_unique<fake_video_conference::OfficeBunnyEffect>();
+    shaggy_fur_ = std::make_unique<fake_video_conference::ShaggyFurEffect>();
 
     set_create_global_cras_audio_handler(false);
     AshTestBase::SetUp();
@@ -54,11 +55,21 @@
   void TearDown() override {
     AshTestBase::TearDown();
     office_bunny_.reset();
+    shaggy_fur_.reset();
     controller_.reset();
     CrasAudioHandler::Shutdown();
     CrasAudioClient::Shutdown();
   }
 
+  views::View* GetSetValueEffectButton(int index) {
+    // Map `index` to a `BubbleViewID`, for lookup.
+    BubbleViewID id =
+        static_cast<BubbleViewID>(index + BubbleViewID::kSetValueButtonMin);
+    DCHECK_GE(id, BubbleViewID::kSetValueButtonMin);
+    DCHECK_LE(id, BubbleViewID::kSetValueButtonMax);
+    return bubble_view()->GetViewByID(id);
+  }
+
   VideoConferenceTray* video_conference_tray() {
     return StatusAreaWidgetTestHelper::GetStatusAreaWidget()
         ->video_conference_tray();
@@ -79,6 +90,11 @@
         video_conference::BubbleViewID::kToggleEffectsView);
   }
 
+  views::View* set_value_effects_view() {
+    return bubble_view()->GetViewByID(
+        video_conference::BubbleViewID::kSetValueEffectsView);
+  }
+
   views::View* return_to_app() {
     return bubble_view()->GetViewByID(
         video_conference::BubbleViewID::kReturnToApp);
@@ -93,10 +109,15 @@
     return office_bunny_.get();
   }
 
+  ash::fake_video_conference::ShaggyFurEffect* shaggy_fur() {
+    return shaggy_fur_.get();
+  }
+
  private:
   base::test::ScopedFeatureList scoped_feature_list_;
   std::unique_ptr<FakeVideoConferenceTrayController> controller_;
   std::unique_ptr<ash::fake_video_conference::OfficeBunnyEffect> office_bunny_;
+  std::unique_ptr<ash::fake_video_conference::ShaggyFurEffect> shaggy_fur_;
 };
 
 TEST_F(BubbleViewTest, NoEffects) {
@@ -174,4 +195,80 @@
   EXPECT_EQ(office_bunny()->num_activations_for_testing(), 1);
 }
 
+TEST_F(BubbleViewTest, RegisterSetValueEffect) {
+  // Open up the bubble, no set-value effects present.
+  LeftClickOn(toggle_bubble_button());
+  EXPECT_FALSE(toggle_effects_view());
+
+  // Close the bubble.
+  LeftClickOn(toggle_bubble_button());
+
+  // Add one set-value effect.
+  controller()->effects_manager().RegisterDelegate(shaggy_fur());
+
+  // Open up the bubble, set-value effects container view is present/visible.
+  LeftClickOn(toggle_bubble_button());
+  EXPECT_TRUE(set_value_effects_view());
+  EXPECT_TRUE(set_value_effects_view()->GetVisible());
+}
+
+TEST_F(BubbleViewTest, UnregisterSetValueEffect) {
+  // Add one set-value effect.
+  controller()->effects_manager().RegisterDelegate(shaggy_fur());
+
+  // Open up the bubble, set-value effects are present/visible.
+  LeftClickOn(toggle_bubble_button());
+  EXPECT_TRUE(set_value_effects_view());
+  EXPECT_TRUE(set_value_effects_view()->GetVisible());
+
+  // Take down the bubble.
+  LeftClickOn(toggle_bubble_button());
+
+  // Remove the set-value effect.
+  controller()->effects_manager().UnregisterDelegate(shaggy_fur());
+
+  // Open up the bubble again, no effects present.
+  LeftClickOn(toggle_bubble_button());
+  EXPECT_FALSE(set_value_effects_view());
+}
+
+TEST_F(BubbleViewTest, SetValueButtonClicked) {
+  // Verify that the delegate hosts a single effect which has at least two
+  // values.
+  EXPECT_EQ(shaggy_fur()->GetNumEffects(), 1);
+  EXPECT_GE(shaggy_fur()->GetEffect(0)->GetNumStates(), 2);
+
+  // Add one set-value effect.
+  controller()->effects_manager().RegisterDelegate(shaggy_fur());
+
+  // Click to open the bubble, effect value 0 button is present/visible.
+  LeftClickOn(toggle_bubble_button());
+  views::View* button = GetSetValueEffectButton(0);
+  EXPECT_TRUE(button);
+  EXPECT_TRUE(button->GetVisible());
+
+  // Effect button for value 0 has not yet been clicked.
+  EXPECT_EQ(shaggy_fur()->GetNumActivationsForTesting(0), 0);
+
+  // Click the effect value 0 button, verify that the value has been "activated"
+  // once.
+  LeftClickOn(button);
+  EXPECT_EQ(shaggy_fur()->GetNumActivationsForTesting(0), 1);
+
+  // Now test another button, confirm that set-value effect button 1 is
+  // present/visible.
+  button = GetSetValueEffectButton(1);
+  EXPECT_TRUE(button);
+  EXPECT_TRUE(button->GetVisible());
+
+  // Effect button for value 1 has not yet been clicked.
+  EXPECT_EQ(shaggy_fur()->GetNumActivationsForTesting(1), 0);
+
+  // Click the effect value 1 button, verify that value 1 has been "activated"
+  // once, and confirm that value 0 has still only been activated once i.e. we
+  // just activated value 1 and not value 0.
+  LeftClickOn(button);
+  EXPECT_EQ(shaggy_fur()->GetNumActivationsForTesting(1), 1);
+  EXPECT_EQ(shaggy_fur()->GetNumActivationsForTesting(0), 1);
+}
 }  // namespace ash::video_conference
\ No newline at end of file
diff --git a/ash/system/video_conference/bubble/set_value_effects_view.cc b/ash/system/video_conference/bubble/set_value_effects_view.cc
new file mode 100644
index 0000000..7c6bec1
--- /dev/null
+++ b/ash/system/video_conference/bubble/set_value_effects_view.cc
@@ -0,0 +1,85 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/system/video_conference/bubble/set_value_effects_view.h"
+
+#include "ash/system/video_conference/bubble/bubble_view_ids.h"
+#include "ash/system/video_conference/effects/video_conference_tray_effects_manager_types.h"
+#include "ash/system/video_conference/video_conference_tray_controller.h"
+#include "ui/views/background.h"
+#include "ui/views/border.h"
+#include "ui/views/controls/button/radio_button.h"
+#include "ui/views/controls/label.h"
+#include "ui/views/controls/separator.h"
+#include "ui/views/layout/flex_layout.h"
+
+namespace ash::video_conference {
+
+namespace {
+
+// A view with label (for the effect name) that allows the user to select from
+// one of several integer values. TODO(b/253273036) Implement this as a
+// tab-slider view instead of a radio switch.
+class ValueButtonContainer : public views::View {
+ public:
+  explicit ValueButtonContainer(const VcHostedEffect* effect) {
+    views::FlexLayout* layout =
+        SetLayoutManager(std::make_unique<views::FlexLayout>());
+    layout->SetOrientation(views::LayoutOrientation::kVertical);
+    layout->SetMainAxisAlignment(views::LayoutAlignment::kCenter);
+    layout->SetCrossAxisAlignment(views::LayoutAlignment::kStretch);
+
+    if (!effect->label_text().empty()) {
+      AddChildView(std::make_unique<views::Label>(effect->label_text()));
+    }
+
+    // Add a button for each state.
+    for (int i = 0; i < effect->GetNumStates(); ++i) {
+      const VcEffectState* state = effect->GetState(/*index=*/i);
+      std::unique_ptr<views::RadioButton> state_button =
+          std::make_unique<views::RadioButton>(state->label_text(),
+                                               /*group_id=*/effect->id());
+      state_button->SetCallback(state->button_callback());
+
+      // See comments above `kSetValueButton*` in `BubbleViewID` for details
+      // on how the IDs of these buttons are set.
+      state_button->SetID(i <= BubbleViewID::kSetValueButtonMax -
+                                      BubbleViewID::kSetValueButtonMin
+                              ? BubbleViewID::kSetValueButtonMin + i
+                              : BubbleViewID::kSetValueButtonMax);
+
+      AddChildView(std::move(state_button));
+    }
+
+    SetBorder(views::CreateEmptyBorder(gfx::Insets::VH(10, 10)));
+    SetBackground(views::CreateRoundedRectBackground(gfx::kGoogleGreen800,
+                                                     /*radius=*/10));
+  }
+
+  ValueButtonContainer(const ValueButtonContainer&) = delete;
+  ValueButtonContainer& operator=(const ValueButtonContainer&) = delete;
+
+  ~ValueButtonContainer() override = default;
+};
+
+}  // namespace
+
+SetValueEffectsView::SetValueEffectsView(
+    VideoConferenceTrayController* controller) {
+  SetID(BubbleViewID::kSetValueEffectsView);
+
+  views::FlexLayout* layout =
+      SetLayoutManager(std::make_unique<views::FlexLayout>());
+  layout->SetOrientation(views::LayoutOrientation::kVertical);
+  layout->SetMainAxisAlignment(views::LayoutAlignment::kCenter);
+  layout->SetCrossAxisAlignment(views::LayoutAlignment::kStretch);
+
+  if (controller->effects_manager().HasSetValueEffects()) {
+    for (auto* effect : controller->effects_manager().GetSetValueEffects()) {
+      AddChildView(std::make_unique<ValueButtonContainer>(effect));
+    }
+  }
+}
+
+}  // namespace ash::video_conference
\ No newline at end of file
diff --git a/ash/system/video_conference/bubble/set_value_effects_view.h b/ash/system/video_conference/bubble/set_value_effects_view.h
new file mode 100644
index 0000000..f27a0d3
--- /dev/null
+++ b/ash/system/video_conference/bubble/set_value_effects_view.h
@@ -0,0 +1,34 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ASH_SYSTEM_VIDEO_CONFERENCE_BUBBLE_SET_VALUE_EFFECTS_VIEW_H_
+#define ASH_SYSTEM_VIDEO_CONFERENCE_BUBBLE_SET_VALUE_EFFECTS_VIEW_H_
+
+#include "ui/views/view.h"
+
+namespace ash {
+
+class VideoConferenceTrayController;
+
+namespace video_conference {
+
+// The set-value effects view, that resides in the video conference bubble. It
+// functions as a "factory" that constructs and hosts selector-views for effects
+// that are set to one of several integral values. The selector-views host the
+// individual effects and are registered with the
+// `VideoConferenceTrayEffectsManager`, which is in turn owned by the passed-in
+// controller.
+class SetValueEffectsView : public views::View {
+ public:
+  explicit SetValueEffectsView(VideoConferenceTrayController* controller);
+  SetValueEffectsView(const SetValueEffectsView&) = delete;
+  SetValueEffectsView& operator=(const SetValueEffectsView&) = delete;
+  ~SetValueEffectsView() override = default;
+};
+
+}  // namespace video_conference
+
+}  // namespace ash
+
+#endif  // ASH_SYSTEM_VIDEO_CONFERENCE_BUBBLE_SET_VALUE_EFFECTS_VIEW_H_
diff --git a/ash/system/video_conference/effects/fake_video_conference_effects.cc b/ash/system/video_conference/effects/fake_video_conference_effects.cc
index 78f203b..683399f8 100644
--- a/ash/system/video_conference/effects/fake_video_conference_effects.cc
+++ b/ash/system/video_conference/effects/fake_video_conference_effects.cc
@@ -74,7 +74,7 @@
     : SimpleToggleEffect(
           /*label_text=*/u"Greenhouse") {}
 
-// Delegate that hosts a set-value effect.
+// Delegates that host a set-value effect.
 
 ShaggyFurEffect::ShaggyFurEffect() {
   std::unique_ptr<VcHostedEffect> effect =
@@ -116,8 +116,9 @@
   AddEffect(std::move(effect));
 
   // Initialize click counts.
-  for (int i = 0; i < static_cast<int>(FurShagginess::kMaxNumValues); ++i)
+  for (int i = 0; i < static_cast<int>(FurShagginess::kMaxNumValues); ++i) {
     num_activations_for_testing_.push_back(0);
+  }
 }
 
 ShaggyFurEffect::~ShaggyFurEffect() = default;
@@ -136,4 +137,77 @@
   return num_activations_for_testing_[value];
 }
 
+SuperCutnessEffect::SuperCutnessEffect() {
+  std::unique_ptr<VcHostedEffect> effect =
+      std::make_unique<VcHostedEffect>(VcEffectType::kSetValue);
+  std::unique_ptr<VcEffectState> ugly_dog_state =
+      std::make_unique<VcEffectState>(
+          /*icon=*/nullptr,
+          /*label_text=*/u"Ugly Dog",
+          /*accessible_name_id=*/IDS_PRIVACY_NOTIFICATION_TITLE_CAMERA,
+          /*button_callback=*/
+          base::BindRepeating(&SuperCutnessEffect::OnEffectControlActivated,
+                              base::Unretained(this),
+                              /*effect_id=*/0,
+                              /*value=*/static_cast<int>(HowCute::kUglyDog)));
+  std::unique_ptr<VcEffectState> teddy_bear_state =
+      std::make_unique<VcEffectState>(
+          /*icon=*/nullptr,
+          /*label_text=*/u"Teddy Bear",
+          /*accessible_name_id=*/IDS_PRIVACY_NOTIFICATION_TITLE_CAMERA,
+          /*button_callback=*/
+          base::BindRepeating(&SuperCutnessEffect::OnEffectControlActivated,
+                              base::Unretained(this),
+                              /*effect_id=*/0,
+                              /*value=*/static_cast<int>(HowCute::kTeddyBear)));
+  std::unique_ptr<VcEffectState> zara_state = std::make_unique<VcEffectState>(
+      /*icon=*/nullptr,
+      /*label_text=*/u"Zara",
+      /*accessible_name_id=*/IDS_PRIVACY_NOTIFICATION_TITLE_CAMERA,
+      /*button_callback=*/
+      base::BindRepeating(&SuperCutnessEffect::OnEffectControlActivated,
+                          base::Unretained(this),
+                          /*effect_id=*/0,
+                          /*value=*/static_cast<int>(HowCute::kZara)));
+  std::unique_ptr<VcEffectState> inscrutable_state =
+      std::make_unique<VcEffectState>(
+          /*icon=*/nullptr,
+          /*label_text=*/u"Inscrutable",
+          /*accessible_name_id=*/IDS_PRIVACY_NOTIFICATION_TITLE_CAMERA,
+          /*button_callback=*/
+          base::BindRepeating(
+              &SuperCutnessEffect::OnEffectControlActivated,
+              base::Unretained(this),
+              /*effect_id=*/0,
+              /*value=*/static_cast<int>(HowCute::kInscrutable)));
+  effect->AddState(std::move(ugly_dog_state));
+  effect->AddState(std::move(teddy_bear_state));
+  effect->AddState(std::move(zara_state));
+  effect->AddState(std::move(inscrutable_state));
+  effect->set_label_text(u"Super Cuteness");
+  effect->set_id(200);
+  AddEffect(std::move(effect));
+
+  // Initialize click counts.
+  for (int i = 0; i < static_cast<int>(HowCute::kMaxNumValues); ++i) {
+    num_activations_for_testing_.push_back(0);
+  }
+}
+
+SuperCutnessEffect::~SuperCutnessEffect() = default;
+
+int SuperCutnessEffect::GetEffectState(int effect_id) {
+  return static_cast<int>(HowCute::kTeddyBear);
+}
+
+void SuperCutnessEffect::OnEffectControlActivated(int effect_id, int value) {
+  DCHECK(value >= 0 && value < static_cast<int>(HowCute::kMaxNumValues));
+  ++num_activations_for_testing_[value];
+}
+
+int SuperCutnessEffect::GetNumActivationsForTesting(int value) {
+  DCHECK(value >= 0 && value < static_cast<int>(HowCute::kMaxNumValues));
+  return num_activations_for_testing_[value];
+}
+
 }  // namespace ash::fake_video_conference
diff --git a/ash/system/video_conference/effects/fake_video_conference_effects.h b/ash/system/video_conference/effects/fake_video_conference_effects.h
index 99c8048..75672b05 100644
--- a/ash/system/video_conference/effects/fake_video_conference_effects.h
+++ b/ash/system/video_conference/effects/fake_video_conference_effects.h
@@ -150,6 +150,37 @@
   std::vector<int> num_activations_for_testing_;
 };
 
+class ASH_EXPORT SuperCutnessEffect : public VcEffectsDelegate {
+ public:
+  enum class HowCute {
+    kUglyDog = 0,
+    kTeddyBear = 1,
+    kZara = 2,
+    kInscrutable = 3,
+    kMaxNumValues = 4,
+  };
+
+  SuperCutnessEffect();
+
+  SuperCutnessEffect(const SuperCutnessEffect&) = delete;
+  SuperCutnessEffect& operator=(const SuperCutnessEffect&) = delete;
+
+  ~SuperCutnessEffect() override;
+
+  // VcEffectsDelegate:
+  int GetEffectState(int effect_id) override;
+  void OnEffectControlActivated(int effect_id, int value) override;
+
+  // Returns the number of times the button/state for `value` has been
+  // activated.
+  int GetNumActivationsForTesting(int value);
+
+ private:
+  // Number of times each value has been clicked, one count for each value in
+  // `HowCute`.
+  std::vector<int> num_activations_for_testing_;
+};
+
 }  // namespace ash::fake_video_conference
 
 #endif  // ASH_SYSTEM_VIDEO_CONFERENCE_EFFECTS_FAKE_VIDEO_CONFERENCE_EFFECTS_H_
diff --git a/ash/webui/common/resources/network/network_proxy.d.ts b/ash/webui/common/resources/network/network_proxy.d.ts
index 983fe59..1fc89dd 100644
--- a/ash/webui/common/resources/network/network_proxy.d.ts
+++ b/ash/webui/common/resources/network/network_proxy.d.ts
@@ -2,4 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-export {};
+import {LegacyElementMixin} from 'chrome://resources/polymer/v3_0/polymer/lib/legacy/legacy-element-mixin.js';
+interface NetworkProxyElement extends LegacyElementMixin, HTMLElement {
+  reset(): void;
+}
+export {NetworkProxyElement};
diff --git a/ash/webui/shortcut_customization_ui/backend/BUILD.gn b/ash/webui/shortcut_customization_ui/backend/BUILD.gn
index 0712361..02cbe730 100644
--- a/ash/webui/shortcut_customization_ui/backend/BUILD.gn
+++ b/ash/webui/shortcut_customization_ui/backend/BUILD.gn
@@ -17,6 +17,7 @@
     "//ash/public/cpp",
     "//ash/public/mojom",
     "//ash/webui/shortcut_customization_ui/mojom",
+    "//chromeos/strings",
     "//components/prefs:prefs",
     "//ui/base/ime/ash",
     "//ui/chromeos/events",
diff --git a/ash/webui/shortcut_customization_ui/backend/accelerator_configuration_provider.cc b/ash/webui/shortcut_customization_ui/backend/accelerator_configuration_provider.cc
index 7868183..9b1dbaf3 100644
--- a/ash/webui/shortcut_customization_ui/backend/accelerator_configuration_provider.cc
+++ b/ash/webui/shortcut_customization_ui/backend/accelerator_configuration_provider.cc
@@ -19,6 +19,7 @@
 #include "base/notreached.h"
 #include "base/strings/string_split.h"
 #include "base/strings/utf_string_conversions.h"
+#include "chromeos/strings/grit/chromeos_strings.h"
 #include "components/prefs/pref_service.h"
 #include "mojo/public/cpp/bindings/clone_traits.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
@@ -59,6 +60,16 @@
           {ui::KeyboardCode::VKEY_DICTATE, u"ToggleDictation"},
           {ui::KeyboardCode::VKEY_WLAN, u"ToggleWifi"},
           {ui::KeyboardCode::VKEY_EMOJI_PICKER, u"EmojiPicker"},
+          {ui::KeyboardCode::VKEY_SPACE,
+           l10n_util::GetStringUTF16(IDS_SHORTCUT_CUSTOMIZATION_KEY_SPACE)},
+          {ui::KeyboardCode::VKEY_TAB,
+           l10n_util::GetStringUTF16(IDS_SHORTCUT_CUSTOMIZATION_KEY_TAB)},
+          {ui::KeyboardCode::VKEY_ESCAPE,
+           l10n_util::GetStringUTF16(IDS_SHORTCUT_CUSTOMIZATION_KEY_ESCAPE)},
+          {ui::KeyboardCode::VKEY_RETURN,
+           l10n_util::GetStringUTF16(IDS_SHORTCUT_CUSTOMIZATION_KEY_RETURN)},
+          {ui::KeyboardCode::VKEY_BACK,
+           l10n_util::GetStringUTF16(IDS_SHORTCUT_CUSTOMIZATION_KEY_BACKSPACE)},
       }));
   return *key_display_map;
 }
@@ -66,7 +77,7 @@
 mojom::DefaultAcceleratorPropertiesPtr CreateDefaultAcceleratorProps(
     const ui::Accelerator& accelerator) {
   return mojom::DefaultAcceleratorProperties::New(
-      accelerator, KeycodeToKeyString(accelerator.key_code()));
+      accelerator, shortcut_ui::GetKeyDisplay(accelerator.key_code()));
 }
 
 std::u16string LookupAcceleratorDescription(mojom::AcceleratorSource source,
@@ -429,20 +440,15 @@
 }
 
 std::u16string GetKeyDisplay(ui::KeyboardCode key_code) {
-  // TODO(cambickel): All key_displays should be lowercase
-  const std::u16string key_display = KeycodeToKeyString(key_code);
-  if (key_display.empty()) {
-    // If key_display is empty and there's an entry for this key_code in our
-    // map, return that entry's value.
-    auto it = GetKeyDisplayMap().find(key_code);
-    if (it != GetKeyDisplayMap().end()) {
-      return it->second;
-    }
-  } else if (key_display == u" ") {
-    // If key_display is a space character, instead return the string "Space".
-    return l10n_util::GetStringUTF16(IDS_KSV_KEY_SPACE);
+  // If there's an entry for this key_code in our
+  // map, return that entry's value.
+  auto it = GetKeyDisplayMap().find(key_code);
+  if (it != GetKeyDisplayMap().end()) {
+    return it->second;
+  } else {
+    // Otherwise, get the key_display from a util function.
+    return KeycodeToKeyString(key_code);
   }
-  return key_display;
 }
 
 }  // namespace shortcut_ui
diff --git a/ash/webui/shortcut_customization_ui/backend/accelerator_configuration_provider_unittest.cc b/ash/webui/shortcut_customization_ui/backend/accelerator_configuration_provider_unittest.cc
index 9d3494a..018c2b5 100644
--- a/ash/webui/shortcut_customization_ui/backend/accelerator_configuration_provider_unittest.cc
+++ b/ash/webui/shortcut_customization_ui/backend/accelerator_configuration_provider_unittest.cc
@@ -103,7 +103,7 @@
       expected_accelerator ==
       actual_info->layout_properties->get_default_accelerator()->accelerator;
   const bool key_display_equals =
-      KeycodeToKeyString(expected_accelerator.key_code()) ==
+      shortcut_ui::GetKeyDisplay(expected_accelerator.key_code()) ==
       actual_info->layout_properties->get_default_accelerator()->key_display;
   return accelerator_equals && key_display_equals;
 }
@@ -598,11 +598,14 @@
 
 TEST_F(AcceleratorConfigurationProviderTest, TestGetKeyDisplay) {
   EXPECT_EQ(u"c", GetKeyDisplay(ui::VKEY_C));
-  EXPECT_EQ(u"Tab", GetKeyDisplay(ui::VKEY_TAB));
   EXPECT_EQ(u"MicrophoneMuteToggle",
             GetKeyDisplay(ui::VKEY_MICROPHONE_MUTE_TOGGLE));
   EXPECT_EQ(u"ToggleWifi", GetKeyDisplay(ui::VKEY_WLAN));
-  EXPECT_EQ(u"Space", GetKeyDisplay(ui::VKEY_SPACE));
+  EXPECT_EQ(u"tab", GetKeyDisplay(ui::VKEY_TAB));
+  EXPECT_EQ(u"esc", GetKeyDisplay(ui::VKEY_ESCAPE));
+  EXPECT_EQ(u"backspace", GetKeyDisplay(ui::VKEY_BACK));
+  EXPECT_EQ(u"enter", GetKeyDisplay(ui::VKEY_RETURN));
+  EXPECT_EQ(u"space", GetKeyDisplay(ui::VKEY_SPACE));
 }
 
 TEST_F(AcceleratorConfigurationProviderTest, TextAcceleratorParsing) {
diff --git a/base/android/jni_generator/OWNERS b/base/android/jni_generator/OWNERS
index 410f01e..0afb537 100644
--- a/base/android/jni_generator/OWNERS
+++ b/base/android/jni_generator/OWNERS
@@ -1,2 +1,3 @@
 agrieve@chromium.org
 mthiesse@chromium.org
+smaier@chromium.org
diff --git a/base/files/file_util_win.cc b/base/files/file_util_win.cc
index 4e56f7d..8f70dd39 100644
--- a/base/files/file_util_win.cc
+++ b/base/files/file_util_win.cc
@@ -1014,44 +1014,18 @@
   return false;
 }
 
-namespace {
-
-// ::PrefetchVirtualMemory() is only available on Windows 8 and above. Chrome
-// supports Windows 7, so we need to check for the function's presence
-// dynamically.
-using PrefetchVirtualMemoryPtr = decltype(&::PrefetchVirtualMemory);
-
-// Returns null if ::PrefetchVirtualMemory() is not available.
-PrefetchVirtualMemoryPtr GetPrefetchVirtualMemoryPtr() {
-  HMODULE kernel32_dll = ::GetModuleHandleA("kernel32.dll");
-  return reinterpret_cast<PrefetchVirtualMemoryPtr>(
-      GetProcAddress(kernel32_dll, "PrefetchVirtualMemory"));
-}
-
-}  // namespace
-
 bool PreReadFile(const FilePath& file_path,
                  bool is_executable,
                  int64_t max_bytes) {
   DCHECK_GE(max_bytes, 0);
 
-  // On Win8 and higher use ::PrefetchVirtualMemory(). This is better than a
-  // simple data file read, more from a RAM perspective than CPU. This is
-  // because reading the file as data results in double mapping to
-  // Image/executable pages for all pages of code executed.
-  static PrefetchVirtualMemoryPtr prefetch_virtual_memory =
-      GetPrefetchVirtualMemoryPtr();
-
-  if (prefetch_virtual_memory == nullptr)
-    return internal::PreReadFileSlow(file_path, max_bytes);
-
   if (max_bytes == 0) {
-    // PrefetchVirtualMemory() fails when asked to read zero bytes.
+    // ::PrefetchVirtualMemory() fails when asked to read zero bytes.
     // base::MemoryMappedFile::Initialize() fails on an empty file.
     return true;
   }
 
-  // PrefetchVirtualMemory() fails if the file is opened with write access.
+  // ::PrefetchVirtualMemory() fails if the file is opened with write access.
   MemoryMappedFile::Access access = is_executable
                                         ? MemoryMappedFile::READ_CODE_IMAGE
                                         : MemoryMappedFile::READ_ONLY;
@@ -1063,7 +1037,11 @@
       std::min(base::saturated_cast<::SIZE_T>(max_bytes),
                base::saturated_cast<::SIZE_T>(mapped_file.length()));
   ::_WIN32_MEMORY_RANGE_ENTRY address_range = {mapped_file.data(), length};
-  if (!prefetch_virtual_memory(::GetCurrentProcess(),
+  // Use ::PrefetchVirtualMemory(). This is better than a
+  // simple data file read, more from a RAM perspective than CPU. This is
+  // because reading the file as data results in double mapping to
+  // Image/executable pages for all pages of code executed.
+  if (!::PrefetchVirtualMemory(::GetCurrentProcess(),
                                /*NumberOfEntries=*/1, &address_range,
                                /*Flags=*/0)) {
     return internal::PreReadFileSlow(file_path, max_bytes);
diff --git a/base/test/gtest_xml_unittest_result_printer.cc b/base/test/gtest_xml_unittest_result_printer.cc
index dbe37d7..2d6b9f30 100644
--- a/base/test/gtest_xml_unittest_result_printer.cc
+++ b/base/test/gtest_xml_unittest_result_printer.cc
@@ -48,7 +48,7 @@
   if (output_file_ && !open_failed_) {
     fprintf(output_file_.get(), "</testsuites>\n");
     fflush(output_file_);
-    CloseFile(output_file_);
+    CloseFile(output_file_.ExtractAsDangling());
   }
 }
 
diff --git a/base/trace_event/builtin_categories.h b/base/trace_event/builtin_categories.h
index 37bbac58..686b0af 100644
--- a/base/trace_event/builtin_categories.h
+++ b/base/trace_event/builtin_categories.h
@@ -180,6 +180,7 @@
   X("wakeup.flow")                                                       \
   X("wayland")                                                           \
   X("webaudio")                                                          \
+  X("webengine.fidl")                                                    \
   X("weblayer")                                                          \
   X("WebCore")                                                           \
   X("webrtc")                                                            \
diff --git a/build/config/compiler/pgo/BUILD.gn b/build/config/compiler/pgo/BUILD.gn
index 900ae06..1ebe80c 100644
--- a/build/config/compiler/pgo/BUILD.gn
+++ b/build/config/compiler/pgo/BUILD.gn
@@ -57,12 +57,6 @@
       # Temporarily use mac-arm profile until Android native PGO support works.
       # TODO(crbug.com/1308749): fix this.
       _pgo_target = "mac-arm"
-    } else if (is_fuchsia) {
-      if (target_cpu == "arm64") {
-        _pgo_target = "mac-arm"
-      } else {
-        _pgo_target = "mac"
-      }
     }
 
     if (_pgo_target == "win64") {
@@ -72,7 +66,7 @@
     } else if (_pgo_target == "mac-arm") {
       inputs = [ "//chrome/build/mac-arm.pgo.txt" ]
     } else if (_pgo_target == "mac") {
-      inputs = [ "//chrome/build/mac.pgo.txt" ]
+      inputs = [ "//chrome/build/mac-arm.pgo.txt" ]
     } else if (_pgo_target == "linux") {
       inputs = [ "//chrome/build/linux.pgo.txt" ]
     }
diff --git a/build/config/compiler/pgo/pgo.gni b/build/config/compiler/pgo/pgo.gni
index 9e9a0c5..eac98c6 100644
--- a/build/config/compiler/pgo/pgo.gni
+++ b/build/config/compiler/pgo/pgo.gni
@@ -24,7 +24,7 @@
       # TODO(crbug.com/1336055): Update this now-outdated condition with regard
       # to chromecast and determine whether chromeos_is_browser_only is
       # obsolete.
-      (is_high_end_android || is_win || is_mac || is_fuchsia ||
+      (is_high_end_android || is_win || is_mac ||
        (is_linux && !is_castos && !chromeos_is_browser_only))) {
     chrome_pgo_phase = 2
   }
diff --git a/build/config/fuchsia/test/minimum.shard.test-cml b/build/config/fuchsia/test/minimum.shard.test-cml
index 3f3784a..e0e36d7 100644
--- a/build/config/fuchsia/test/minimum.shard.test-cml
+++ b/build/config/fuchsia/test/minimum.shard.test-cml
@@ -67,6 +67,12 @@
         "fuchsia.sys.Loader",
       ],
     },
+    {
+      protocol: [
+        "fuchsia.tracing.perfetto.ProducerConnector",
+      ],
+      availability: "optional",
+    },
   ],
   facets: {
       "fuchsia.test": {
diff --git a/build/fuchsia/test/compatible_utils.py b/build/fuchsia/test/compatible_utils.py
index 39b4664..0399fd4 100644
--- a/build/fuchsia/test/compatible_utils.py
+++ b/build/fuchsia/test/compatible_utils.py
@@ -36,8 +36,8 @@
     When running unattended, confirmation prompts and the like are suppressed.
     """
 
-    # Chromium tests only for the presence of the variable, so match that here.
-    return 'CHROME_HEADLESS' in os.environ
+    # TODO(crbug/1401387): Change to mixin based approach.
+    return 'SWARMING_SERVER' in os.environ
 
 
 def get_host_arch() -> str:
diff --git a/build/fuchsia/test/compatible_utils_unittests.py b/build/fuchsia/test/compatible_utils_unittests.py
index 88c570f..109fb16 100755
--- a/build/fuchsia/test/compatible_utils_unittests.py
+++ b/build/fuchsia/test/compatible_utils_unittests.py
@@ -19,7 +19,7 @@
 
     def test_running_unattended_returns_true_if_headless_set(self) -> None:
         """Test |running_unattended| returns True if CHROME_HEADLESS is set."""
-        with mock.patch('os.environ', {'CHROME_HEADLESS': 0}):
+        with mock.patch('os.environ', {'SWARMING_SERVER': 0}):
             self.assertTrue(compatible_utils.running_unattended())
 
         with mock.patch('os.environ', {'FOO_HEADLESS': 0}):
diff --git a/build/fuchsia/test/flash_device_unittests.py b/build/fuchsia/test/flash_device_unittests.py
index fb7e126..b6e881b 100755
--- a/build/fuchsia/test/flash_device_unittests.py
+++ b/build/fuchsia/test/flash_device_unittests.py
@@ -31,15 +31,19 @@
         self._sdk_hash_patcher = mock.patch('flash_device.get_sdk_hash',
                                             return_value=(_TEST_PRODUCT,
                                                           _TEST_VERSION))
+        self._swarming_patcher = mock.patch('flash_device.running_unattended',
+                                            return_value=False)
         self._check_patcher = mock.patch('flash_device.check_ssh_config_file')
         self._config_mock = self._config_patcher.start()
         self._ffx_mock = self._ffx_patcher.start()
         self._sdk_hash_mock = self._sdk_hash_patcher.start()
         self._check_patcher_mock = self._check_patcher.start()
+        self._swarming_mock = self._swarming_patcher.start()
         self.addCleanup(self._config_mock.stop)
         self.addCleanup(self._ffx_mock.stop)
         self.addCleanup(self._sdk_hash_mock.stop)
         self.addCleanup(self._check_patcher_mock.stop)
+        self.addCleanup(self._swarming_mock.stop)
 
     def test_update_required_on_ignore_returns_immediately(self) -> None:
         """Test |os_check|='ignore' skips all checks."""
@@ -118,10 +122,9 @@
         """Test update when |os_check| is 'check' and system info does not
         match."""
 
+        self._swarming_mock.return_value = True
         with mock.patch('os.path.exists', return_value=True), \
                 mock.patch('flash_device._add_exec_to_flash_binaries'), \
-                mock.patch('flash_device.running_unattended',
-                           return_value=True), \
                 mock.patch('flash_device.subprocess.run'):
             self._ffx_mock.return_value.stdout = \
                 '[{"title": "Build", "child": [{"value": "wrong.version"}, ' \
@@ -222,10 +225,11 @@
 
     def test_update_raises_error_if_unattended_with_no_target(self) -> None:
         """Test update calls pave if specified."""
+
+        self._swarming_mock.return_value = True
         with mock.patch('time.sleep'), \
             mock.patch('flash_device.pave'), \
-            mock.patch('os.path.exists', return_value=True), \
-            mock.patch('flash_device.running_unattended', return_value=True):
+            mock.patch('os.path.exists', return_value=True):
             self.assertRaises(AssertionError,
                               flash_device.update,
                               _TEST_IMAGE_DIR,
@@ -236,11 +240,10 @@
     def test_update_on_swarming(self) -> None:
         """Test update on swarming bots."""
 
+        self._swarming_mock.return_value = True
         with mock.patch('time.sleep'), \
              mock.patch('os.path.exists', return_value=True), \
              mock.patch('flash_device._add_exec_to_flash_binaries'), \
-             mock.patch('flash_device.running_unattended',
-                        return_value = True), \
              mock.patch('subprocess.run'):
             flash_device.update(_TEST_IMAGE_DIR,
                                 'update',
diff --git a/build/fuchsia/test/run_test.py b/build/fuchsia/test/run_test.py
index 7979024..f7cfe7bf 100755
--- a/build/fuchsia/test/run_test.py
+++ b/build/fuchsia/test/run_test.py
@@ -78,8 +78,8 @@
         if running_unattended():
             set_ffx_isolate_dir(
                 stack.enter_context(tempfile.TemporaryDirectory()))
-        stack.enter_context(
-            ScopedFfxConfig('repository.server.listen', '"[::]:0"'))
+            stack.enter_context(
+                ScopedFfxConfig('repository.server.listen', '"[::]:0"'))
         log_manager = stack.enter_context(LogManager(runner_args.logs_dir))
         if runner_args.device:
             update(runner_args.system_image_dir, runner_args.os_check,
diff --git a/build/fuchsia/test/start_emulator.py b/build/fuchsia/test/start_emulator.py
index 9c96d66..4cbb5773 100755
--- a/build/fuchsia/test/start_emulator.py
+++ b/build/fuchsia/test/start_emulator.py
@@ -53,11 +53,17 @@
 
 def main():
     """Stand-alone function for starting an emulator."""
+
+    logging.basicConfig(level=logging.INFO)
     parser = argparse.ArgumentParser()
     register_emulator_args(parser, True)
     register_log_args(parser)
     args = parser.parse_args()
-    with create_emulator_from_args(args):
+    with create_emulator_from_args(args) as target_id:
+        logging.info(
+            'Emulator successfully started. You can now run Chrome '
+            'Fuchsia tests with --target-id=%s to target this emulator.',
+            target_id)
         try:
             while True:
                 time.sleep(10000)
diff --git a/buildtools/deps_revisions.gni b/buildtools/deps_revisions.gni
index 249ad4f2..8fb1d584 100644
--- a/buildtools/deps_revisions.gni
+++ b/buildtools/deps_revisions.gni
@@ -5,5 +5,5 @@
 declare_args() {
   # Used to cause full rebuilds on libc++ rolls. This should be kept in sync
   # with the libcxx_revision vars in //DEPS.
-  libcxx_revision = "2fc3d704672fbd3e85fad8492d39e02d49412891"
+  libcxx_revision = "2948540a20cbb5b2192119f791b04dd62ca7af1c"
 }
diff --git a/cc/paint/oop_pixeltest.cc b/cc/paint/oop_pixeltest.cc
index bf212821b..dcb668c 100644
--- a/cc/paint/oop_pixeltest.cc
+++ b/cc/paint/oop_pixeltest.cc
@@ -112,7 +112,7 @@
 
     raster_context_provider_ =
         base::MakeRefCounted<viz::TestInProcessContextProvider>(
-            viz::TestContextType::kGpuRaster, /*support_locking=*/true,
+            viz::TestContextType::kGpuRaster, /*support_locking=*/false,
             &gr_shader_cache_, &activity_flags_);
     gpu::ContextResult result =
         raster_context_provider_->BindToCurrentSequence();
@@ -162,10 +162,6 @@
 
   SkBitmap Raster(scoped_refptr<DisplayItemList> display_item_list,
                   const RasterOptions& options) {
-    GURL url("https://example.com/foo");
-    viz::TestInProcessContextProvider::ScopedRasterContextLock lock(
-        raster_context_provider_.get(), url.possibly_invalid_spec().c_str());
-
     absl::optional<PlaybackImageProvider::Settings> settings;
     settings.emplace(PlaybackImageProvider::Settings());
     settings->raster_mode = options.image_provider_raster_mode;
diff --git a/cc/paint/transfer_cache_unittest.cc b/cc/paint/transfer_cache_unittest.cc
index fea67a7..1b33fab 100644
--- a/cc/paint/transfer_cache_unittest.cc
+++ b/cc/paint/transfer_cache_unittest.cc
@@ -10,6 +10,7 @@
 #include "cc/paint/image_transfer_cache_entry.h"
 #include "cc/paint/raw_memory_transfer_cache_entry.h"
 #include "cc/paint/transfer_cache_entry.h"
+#include "components/viz/test/test_gpu_memory_buffer_manager.h"
 #include "components/viz/test/test_gpu_service_holder.h"
 #include "components/viz/test/test_in_process_context_provider.h"
 #include "gpu/command_buffer/client/client_transfer_cache.h"
diff --git a/cc/test/layer_tree_pixel_test.cc b/cc/test/layer_tree_pixel_test.cc
index 547f72a9..cd0db942 100644
--- a/cc/test/layer_tree_pixel_test.cc
+++ b/cc/test/layer_tree_pixel_test.cc
@@ -24,6 +24,7 @@
 #include "components/viz/common/features.h"
 #include "components/viz/common/frame_sinks/copy_output_request.h"
 #include "components/viz/common/frame_sinks/copy_output_result.h"
+#include "components/viz/common/gpu/raster_context_provider.h"
 #include "components/viz/service/display/software_output_device.h"
 #include "components/viz/service/display_embedder/skia_output_surface_dependency_impl.h"
 #include "components/viz/service/display_embedder/skia_output_surface_impl.h"
@@ -122,6 +123,8 @@
   if (!use_software_renderer()) {
     viz::RasterContextProvider* worker_context_provider =
         host_impl->layer_tree_frame_sink()->worker_context_provider();
+    viz::RasterContextProvider::ScopedRasterContextLock lock(
+        worker_context_provider);
     EXPECT_EQ(use_accelerated_raster(),
               worker_context_provider->ContextCapabilities().gpu_rasterization);
     EXPECT_EQ(
diff --git a/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/AutofillKeyboardAccessoryIntegrationTest.java b/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/AutofillKeyboardAccessoryIntegrationTest.java
index 44228ba1..49e3d516 100644
--- a/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/AutofillKeyboardAccessoryIntegrationTest.java
+++ b/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/AutofillKeyboardAccessoryIntegrationTest.java
@@ -18,7 +18,6 @@
 import static org.chromium.chrome.browser.keyboard_accessory.tab_layout_component.KeyboardAccessoryTabTestHelper.isKeyboardAccessoryTabLayout;
 
 import android.app.Activity;
-import android.os.Build.VERSION_CODES;
 import android.view.View;
 
 import androidx.annotation.IntDef;
@@ -38,7 +37,6 @@
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.CriteriaHelper;
-import org.chromium.base.test.util.DisableIf;
 import org.chromium.chrome.browser.ChromeWindow;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
@@ -204,11 +202,7 @@
     @Test
     @MediumTest
     @ParameterAnnotations.UseMethodParameter(FeatureParamProvider.class)
-    @DisableIf.
-    Build(sdk_is_greater_than = VERSION_CODES.LOLLIPOP_MR1, sdk_is_less_than = VERSION_CODES.N,
-            message = "Flaky on Marshmallow https://crbug.com/1102302")
-    public void
-    testSelectSuggestionHidesKeyboardAccessory(@EnabledFeature int enabledFeature)
+    public void testSelectSuggestionHidesKeyboardAccessory(@EnabledFeature int enabledFeature)
             throws ExecutionException, TimeoutException {
         loadTestPage(FakeKeyboard::new, enabledFeature);
         mHelper.clickNodeAndShowKeyboard("NAME_FIRST", 1);
@@ -236,8 +230,6 @@
     @Test
     @SmallTest
     // clang-format off
-    @DisableIf.Build(hardware_is = "bullhead", sdk_is_greater_than = VERSION_CODES.LOLLIPOP_MR1,
-        sdk_is_less_than = VERSION_CODES.N, message = "https://crbug.com/1216008")
     public void testPressingBackButtonHidesAccessoryWithAutofillSuggestions()
             throws TimeoutException, ExecutionException {
         // clang-format on
@@ -265,8 +257,6 @@
     @Test
     @MediumTest
     // clang-format off
-    @DisableIf.Build(hardware_is = "bullhead", sdk_is_greater_than = VERSION_CODES.LOLLIPOP_MR1,
-        sdk_is_less_than = VERSION_CODES.N, message = "https://crbug.com/1216008")
     public void testSheetHasMinimumSizeWhenTriggeredBySuggestion() throws TimeoutException {
         // clang-format on
         MultiWindowUtils.getInstance().setIsInMultiWindowModeForTesting(true);
diff --git a/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/ManualFillingIntegrationTest.java b/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/ManualFillingIntegrationTest.java
index 9e1147a..e46a04e 100644
--- a/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/ManualFillingIntegrationTest.java
+++ b/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/ManualFillingIntegrationTest.java
@@ -27,7 +27,6 @@
 import static org.chromium.chrome.browser.keyboard_accessory.ManualFillingTestHelper.whenDisplayed;
 import static org.chromium.chrome.browser.keyboard_accessory.tab_layout_component.KeyboardAccessoryTabTestHelper.isKeyboardAccessoryTabLayout;
 
-import android.os.Build.VERSION_CODES;
 import android.view.View;
 import android.view.ViewGroup;
 
@@ -42,7 +41,6 @@
 import org.chromium.base.task.PostTask;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.CriteriaHelper;
-import org.chromium.base.test.util.DisableIf;
 import org.chromium.base.test.util.DisabledTest;
 import org.chromium.base.test.util.Restriction;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
@@ -109,11 +107,7 @@
 
     @Test
     @SmallTest
-    @DisableIf.
-    Build(sdk_is_greater_than = VERSION_CODES.LOLLIPOP_MR1, sdk_is_less_than = VERSION_CODES.N,
-            message = "Flaky on Marshmallow https://crbug.com/1102302")
-    public void
-    testKeyboardAccessoryDisappearsWithKeyboard() throws TimeoutException {
+    public void testKeyboardAccessoryDisappearsWithKeyboard() throws TimeoutException {
         mHelper.loadTestPage(false);
 
         // Focus the field to bring up the accessory.
@@ -210,11 +204,7 @@
     @Test
     @SmallTest
     @Features.DisableFeatures(ChromeFeatureList.AUTOFILL_KEYBOARD_ACCESSORY)
-    @DisableIf.
-    Build(sdk_is_greater_than = VERSION_CODES.LOLLIPOP_MR1, sdk_is_less_than = VERSION_CODES.N,
-            message = "Flaky on Marshmallow https://crbug.com/1102302")
-    public void
-    testAccessoryHiddenAfterTappingAutoGenerationButton() throws TimeoutException {
+    public void testAccessoryHiddenAfterTappingAutoGenerationButton() throws TimeoutException {
         mHelper.loadTestPage(false);
 
         // Focus the field to bring up the accessory and add the generation button.
@@ -258,11 +248,7 @@
     @Test
     @SmallTest
     @Features.DisableFeatures({ChromeFeatureList.AUTOFILL_KEYBOARD_ACCESSORY})
-    @DisableIf.
-    Build(sdk_is_greater_than = VERSION_CODES.LOLLIPOP_MR1, sdk_is_less_than = VERSION_CODES.N,
-            message = "Flaky on Marshmallow https://crbug.com/1102302")
-    public void
-    testSelectingNonPasswordInputDismissesAccessory() throws TimeoutException {
+    public void testSelectingNonPasswordInputDismissesAccessory() throws TimeoutException {
         mHelper.loadTestPage(false);
 
         // Focus the password field to bring up the accessory.
@@ -350,11 +336,7 @@
 
     @Test
     @SmallTest
-    @DisableIf.
-    Build(sdk_is_greater_than = VERSION_CODES.LOLLIPOP_MR1, sdk_is_less_than = VERSION_CODES.N,
-            message = "Flaky on Marshmallow https://crbug.com/1102302")
-    public void
-    testPressingBackButtonHidesAccessorySheet() throws TimeoutException {
+    public void testPressingBackButtonHidesAccessorySheet() throws TimeoutException {
         mHelper.loadTestPage(false);
 
         // Focus the field to bring up the accessory.
@@ -420,11 +402,7 @@
 
     @Test
     @SmallTest
-    @DisableIf.
-    Build(sdk_is_greater_than = VERSION_CODES.LOLLIPOP_MR1, sdk_is_less_than = VERSION_CODES.N,
-            message = "Flaky on Marshmallow https://crbug.com/1102302")
-    public void
-    testInfobarStaysHiddenWhenOpeningSheet() throws TimeoutException {
+    public void testInfobarStaysHiddenWhenOpeningSheet() throws TimeoutException {
         mHelper.loadTestPage(false);
 
         // Initialize and wait for the infobar.
@@ -473,11 +451,7 @@
 
     @Test
     @SmallTest
-    @DisableIf.
-    Build(sdk_is_greater_than = VERSION_CODES.LOLLIPOP_MR1, sdk_is_less_than = VERSION_CODES.N,
-            message = "Flaky on Marshmallow https://crbug.com/1102302")
-    public void
-    testMovesUpSnackbar() throws TimeoutException {
+    public void testMovesUpSnackbar() throws TimeoutException {
         final String kSnackbarText = "snackbar";
 
         mHelper.loadTestPage(false);
@@ -516,11 +490,7 @@
 
     @Test
     @SmallTest
-    @DisableIf.
-    Build(sdk_is_greater_than = VERSION_CODES.LOLLIPOP_MR1, sdk_is_less_than = VERSION_CODES.N,
-            message = "Flaky on Marshmallow https://crbug.com/1102302")
-    public void
-    testInfobarReopensOnPressingBack() throws TimeoutException {
+    public void testInfobarReopensOnPressingBack() throws TimeoutException {
         mHelper.loadTestPage(false);
 
         // Initialize and wait for the infobar.
diff --git a/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/PasswordAccessoryIntegrationTest.java b/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/PasswordAccessoryIntegrationTest.java
index 7281b4a..4dac0fd3a 100644
--- a/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/PasswordAccessoryIntegrationTest.java
+++ b/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/PasswordAccessoryIntegrationTest.java
@@ -21,8 +21,6 @@
 import static org.chromium.chrome.browser.keyboard_accessory.ManualFillingTestHelper.whenDisplayed;
 import static org.chromium.chrome.browser.keyboard_accessory.tab_layout_component.KeyboardAccessoryTabTestHelper.isKeyboardAccessoryTabLayout;
 
-import android.os.Build.VERSION_CODES;
-
 import androidx.test.filters.SmallTest;
 
 import org.junit.After;
@@ -76,11 +74,7 @@
 
     @Test
     @SmallTest
-    @DisableIf.
-    Build(sdk_is_greater_than = VERSION_CODES.LOLLIPOP_MR1, sdk_is_less_than = VERSION_CODES.N,
-            message = "Flaky on Marshmallow https://crbug.com/1102302")
-    public void
-    testPasswordSheetDisplaysProvidedItems() throws TimeoutException {
+    public void testPasswordSheetDisplaysProvidedItems() throws TimeoutException {
         mHelper.loadTestPage(false);
         mHelper.cacheCredentials("mayapark@gmail.com", "SomeHiddenPassword");
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/SwipeRefreshHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/SwipeRefreshHandler.java
index ac15ef9..011c39f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/SwipeRefreshHandler.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/SwipeRefreshHandler.java
@@ -17,7 +17,6 @@
 import org.chromium.base.metrics.RecordUserAction;
 import org.chromium.base.task.PostTask;
 import org.chromium.chrome.R;
-import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.gesturenav.HistoryNavigationCoordinator;
 import org.chromium.chrome.browser.tab.EmptyTabObserver;
 import org.chromium.chrome.browser.tab.Tab;
@@ -196,8 +195,7 @@
         if (type == OverscrollAction.PULL_TO_REFRESH) {
             if (mSwipeRefreshLayout == null) initSwipeRefreshLayout(mTab.getContext());
             attachSwipeRefreshLayoutIfNecessary();
-            return mSwipeRefreshLayout.start(ChromeFeatureList.isEnabled(
-                    ChromeFeatureList.OPTIMIZE_LAYOUTS_FOR_PULL_REFRESH));
+            return mSwipeRefreshLayout.start();
         } else if (type == OverscrollAction.HISTORY_NAVIGATION) {
             if (mNavigationCoordinator != null) {
                 mNavigationCoordinator.startGesture();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/bookmarks/BookmarkFolderSelectActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/app/bookmarks/BookmarkFolderSelectActivity.java
index 7717297..1e5e2e50 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/app/bookmarks/BookmarkFolderSelectActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/app/bookmarks/BookmarkFolderSelectActivity.java
@@ -299,10 +299,13 @@
     private void finishActivity(List<BookmarkId> bookmarks) {
         // This means BookmarkFolderSelectActivity was called for a result.
         if (getCallingActivity() != null) {
-            assert bookmarks.size() == 1;
-            Intent result = new Intent();
-            result.putExtra(INTENT_BOOKMARK_MOVE_RESULT, bookmarks.get(0).toString());
-            setResult(Activity.RESULT_OK, result);
+            if (bookmarks.size() == 1) {
+                Intent result = new Intent();
+                result.putExtra(INTENT_BOOKMARK_MOVE_RESULT, bookmarks.get(0).toString());
+                setResult(Activity.RESULT_OK, result);
+            } else {
+                setResult(Activity.RESULT_CANCELED);
+            }
         }
         finish();
     }
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 0b141794..3ac971e3 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
@@ -47,9 +47,7 @@
 
     /** Returns whether the root folder should be used as the default location. */
     public static boolean shouldUseRootFolderAsDefaultForReadLater() {
-        return isReadingListEnabled()
-                && ChromeFeatureList.getFieldTrialParamByFeatureAsBoolean(
-                        ChromeFeatureList.READ_LATER, "use_root_bookmark_as_default", true);
+        return isReadingListEnabled();
     }
 
     /**
@@ -75,6 +73,7 @@
     }
 
     private static boolean isReadingListEnabled() {
+        // This feature is enabled by default in native.
         return FeatureList.isInitialized()
                 && ChromeFeatureList.isEnabled(ChromeFeatureList.READ_LATER)
                 && ChromeFeatureList.getFieldTrialParamByFeatureAsInt(
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/LayerTitleCache.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/LayerTitleCache.java
index 5510e31..b528231f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/LayerTitleCache.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/LayerTitleCache.java
@@ -19,6 +19,7 @@
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tab.TabFavicon;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
+import org.chromium.chrome.browser.tasks.tab_management.TabUiFeatureUtilities;
 import org.chromium.chrome.browser.ui.favicon.FaviconHelper;
 import org.chromium.chrome.browser.ui.favicon.FaviconHelper.DefaultFaviconHelper;
 import org.chromium.chrome.browser.ui.favicon.FaviconHelper.FaviconImageCallback;
@@ -134,7 +135,23 @@
             title.register();
         }
 
-        title.set(titleBitmapFactory.getTitleBitmap(mContext, titleString),
+        // Boolean determines if tab title text needs to be bolded.
+        boolean isBold = false;
+
+        // Bold title text for TSR detached.
+        if (TabUiFeatureUtilities.isTabStripDetachedEnabled()) {
+            if (mTabModelSelector == null) {
+                return titleString;
+            }
+
+            // Get currently selected tab id.
+            int selectedTabId = mTabModelSelector.getCurrentTabId();
+
+            // Selected tab title text should be bolded.
+            isBold = tabId == selectedTabId;
+        }
+
+        title.set(titleBitmapFactory.getTitleBitmap(mContext, titleString, isBold),
                 titleBitmapFactory.getFaviconBitmap(originalFavicon), fetchFaviconFromHistory);
 
         if (mNativeLayerTitleCache != 0) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/content/TitleBitmapFactory.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/content/TitleBitmapFactory.java
index 48ff0c1..0a37dd5 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/content/TitleBitmapFactory.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/content/TitleBitmapFactory.java
@@ -10,6 +10,7 @@
 import android.graphics.Canvas;
 import android.graphics.Paint;
 import android.graphics.Paint.FontMetrics;
+import android.graphics.Typeface;
 import android.text.Layout;
 import android.text.TextPaint;
 import android.text.TextUtils;
@@ -112,13 +113,20 @@
      *
      * @param context   Android's UI context.
      * @param title     The title of the tab.
+     * @param isBold    Whether the tab title text should be bolded.
      * @return          The Bitmap with the title.
      */
-    public Bitmap getTitleBitmap(Context context, String title) {
+    public Bitmap getTitleBitmap(Context context, String title, boolean isBold) {
         try {
             boolean drawText = !TextUtils.isEmpty(title);
             int textWidth =
                     drawText ? (int) Math.ceil(Layout.getDesiredWidth(title, mTextPaint)) : 0;
+
+            // Bold tab title text.
+            if (isBold) {
+                mTextPaint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.BOLD));
+            }
+
             // Minimum 1 width bitmap to avoid createBitmap function's IllegalArgumentException,
             // when textWidth == 0.
             Bitmap b = Bitmap.createBitmap(Math.max(Math.min(mMaxWidth, textWidth), 1), mViewHeight,
@@ -128,6 +136,10 @@
                 c.drawText(title, 0, Math.min(MAX_NUM_TITLE_CHAR, title.length()), 0,
                         Math.round((mViewHeight - mTextHeight) / 2.0f + mTextYOffset), mTextPaint);
             }
+
+            // Set bolded tab title text back to normal.
+            mTextPaint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.NORMAL));
+
             return b;
         } catch (OutOfMemoryError ex) {
             Log.e(TAG, "OutOfMemoryError while building title texture.");
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/segmentation_platform/PriceTrackingActionProviderTest.java b/chrome/android/java/src/org/chromium/chrome/browser/segmentation_platform/PriceTrackingActionProviderTest.java
index dbde53c..c837a88 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/segmentation_platform/PriceTrackingActionProviderTest.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/segmentation_platform/PriceTrackingActionProviderTest.java
@@ -10,8 +10,6 @@
 
 import android.os.Handler;
 
-import com.google.common.base.Optional;
-
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Rule;
@@ -37,6 +35,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Optional;
 
 /**
  * Unit tests for {@link PriceTrackingActionProvider}
@@ -83,7 +82,7 @@
 
     private void setPriceTrackingBackendResult(boolean hasProductInfo) {
         ProductInfo testProductInfo =
-                new ProductInfo(null, null, 0, 0, null, 0, null, Optional.absent());
+                new ProductInfo(null, null, 0, 0, null, 0, null, Optional.empty());
         Mockito.doAnswer(invocation -> {
                    ProductInfoCallback callback = invocation.getArgument(1);
                    callback.onResult(
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ui/system/StatusBarColorController.java b/chrome/android/java/src/org/chromium/chrome/browser/ui/system/StatusBarColorController.java
index d6004c0..5185d8c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ui/system/StatusBarColorController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ui/system/StatusBarColorController.java
@@ -191,7 +191,6 @@
             @Override
             public void onTabModelSelected(TabModel newModel, TabModel oldModel) {
                 mIsIncognito = newModel.isIncognito();
-
                 // When opening a new Incognito Tab from a normal Tab (or vice versa), the
                 // status bar color is updated. However, this update is triggered after the
                 // animation, so we update here for the duration of the new Tab animation.
@@ -331,7 +330,11 @@
     public void setTabModelSelector(TabModelSelector tabModelSelector) {
         assert mTabModelSelector == null : "mTabModelSelector should only be set once.";
         mTabModelSelector = tabModelSelector;
-        if (mTabModelSelector != null) mTabModelSelector.addObserver(mTabModelSelectorObserver);
+        if (mTabModelSelector != null) {
+            mTabModelSelector.addObserver(mTabModelSelectorObserver);
+            mIsIncognito = mTabModelSelector.isIncognitoSelected();
+            updateStatusBarColor();
+        }
     }
 
     /**
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/app/bookmarks/BookmarkEditTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/app/bookmarks/BookmarkEditTest.java
index 9565ed7..73f92848 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/app/bookmarks/BookmarkEditTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/app/bookmarks/BookmarkEditTest.java
@@ -257,6 +257,28 @@
                 mBookmarkEditActivity.getFolderTextView().getText());
     }
 
+    @Test
+    @MediumTest
+    @Feature({"Bookmark"})
+    public void testChangeFolderWhenBookmarkRemoved() throws ExecutionException, TimeoutException {
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> mBookmarkEditActivity.getFolderTextView().performClick());
+        waitForMoveFolderActivity();
+
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            Assert.assertTrue("Expected BookmarkFolderSelectActivity.",
+                    ApplicationStatus.getLastTrackedFocusedActivity()
+                                    instanceof BookmarkFolderSelectActivity);
+            mBookmarkModel.deleteBookmark(mBookmarkId);
+        });
+        // clang-format off
+        CriteriaHelper.pollUiThread(() ->
+                !(ApplicationStatus.getLastTrackedFocusedActivity()
+                      instanceof BookmarkFolderSelectActivity),
+                "Timed out waiting for BookmarkFolderSelectActivity to close");
+        // clang-format on
+    }
+
     private BookmarkItem getBookmarkItem(BookmarkId bookmarkId) throws ExecutionException {
         return TestThreadUtils.runOnUiThreadBlocking(
                 () -> mBookmarkModel.getBookmarkById(bookmarkId));
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/app/bookmarks/BookmarkTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/app/bookmarks/BookmarkTest.java
index edcde3b..4aa714a 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/app/bookmarks/BookmarkTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/app/bookmarks/BookmarkTest.java
@@ -250,6 +250,19 @@
                 () -> mManager.getDragStateDelegate().setA11yStateForTesting(false));
     }
 
+    void openRootFolder() {
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> mManager.openFolder(mBookmarkModel.getRootFolderId()));
+        RecyclerViewTestUtils.waitForStableRecyclerView(mItemsContainer);
+    }
+
+    void openMobileBookmarks() {
+        openRootFolder();
+
+        onView(withText("Mobile bookmarks")).perform(click());
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+    }
+
     private boolean isItemPresentInBookmarkList(final String expectedTitle) {
         return TestThreadUtils.runOnUiThreadBlockingNoException(new Callable<Boolean>() {
             @Override
@@ -362,10 +375,11 @@
 
     @Test
     @SmallTest
-    @Features.EnableFeatures({ChromeFeatureList.READ_LATER + ":use_root_bookmark_as_default/false"})
     public void testOpenBookmark() throws InterruptedException, ExecutionException {
         addBookmark(TEST_PAGE_TITLE_GOOGLE, mTestPage);
         openBookmarkManager();
+        openMobileBookmarks();
+
         Assert.assertTrue("Grid view does not contain added bookmark: ",
                 isItemPresentInBookmarkList(TEST_PAGE_TITLE_GOOGLE));
         final View title = getViewWithText(mItemsContainer, TEST_PAGE_TITLE_GOOGLE);
@@ -398,9 +412,10 @@
 
     @Test
     @SmallTest
-    @Features.EnableFeatures({ChromeFeatureList.READ_LATER + ":use_root_bookmark_as_default/false"})
     public void testopenBookmarkManager() throws InterruptedException {
         openBookmarkManager();
+        openMobileBookmarks();
+
         BookmarkTestUtil.waitForBookmarkModelLoaded();
         BookmarkDelegate delegate = getBookmarkManager();
 
@@ -689,13 +704,13 @@
 
     @Test
     @MediumTest
-    @Features.EnableFeatures({ChromeFeatureList.READ_LATER + ":use_root_bookmark_as_default/false"})
     public void testEndIconVisibilityInSearchMode() throws Exception {
         BookmarkId testId = addFolder(TEST_FOLDER_TITLE);
         addFolder(TEST_TITLE_A);
 
         BookmarkPromoHeader.forcePromoStateForTests(SyncPromoState.PROMO_FOR_SYNC_TURNED_OFF_STATE);
         openBookmarkManager();
+        openMobileBookmarks();
 
         View searchButton = mManager.getToolbarForTests().findViewById(R.id.search_menu_id);
 
@@ -742,7 +757,6 @@
 
     @Test
     @MediumTest
-    @Features.EnableFeatures({ChromeFeatureList.READ_LATER + ":use_root_bookmark_as_default/false"})
     public void testSmallDrag_Up_BookmarksOnly() throws Exception {
         List<BookmarkId> initial = new ArrayList<>();
         List<BookmarkId> expected = new ArrayList<>();
@@ -767,6 +781,7 @@
 
         BookmarkPromoHeader.forcePromoStateForTests(SyncPromoState.PROMO_FOR_SYNC_TURNED_OFF_STATE);
         openBookmarkManager();
+        openMobileBookmarks();
 
         // Callback occurs upon changes inside of the bookmark model.
         CallbackHelper modelReorderHelper = new CallbackHelper();
@@ -806,7 +821,6 @@
 
     @Test
     @MediumTest
-    @Features.EnableFeatures({ChromeFeatureList.READ_LATER + ":use_root_bookmark_as_default/false"})
     public void testSmallDrag_Down_FoldersOnly() throws Exception {
         List<BookmarkId> initial = new ArrayList<>();
         List<BookmarkId> expected = new ArrayList<>();
@@ -832,6 +846,7 @@
 
         BookmarkPromoHeader.forcePromoStateForTests(SyncPromoState.PROMO_FOR_SYNC_TURNED_OFF_STATE);
         openBookmarkManager();
+        openMobileBookmarks();
 
         // Callback occurs upon changes inside of the bookmark model.
         CallbackHelper modelReorderHelper = new CallbackHelper();
@@ -872,7 +887,6 @@
 
     @Test
     @MediumTest
-    @Features.EnableFeatures({ChromeFeatureList.READ_LATER + ":use_root_bookmark_as_default/false"})
     public void testSmallDrag_Down_MixedFoldersAndBookmarks() throws Exception {
         List<BookmarkId> initial = new ArrayList<>();
         List<BookmarkId> expected = new ArrayList<>();
@@ -895,6 +909,7 @@
 
         BookmarkPromoHeader.forcePromoStateForTests(SyncPromoState.PROMO_FOR_SYNC_TURNED_OFF_STATE);
         openBookmarkManager();
+        openMobileBookmarks();
 
         // Callback occurs upon changes inside of the bookmark model.
         CallbackHelper modelReorderHelper = new CallbackHelper();
@@ -934,12 +949,12 @@
 
     @Test
     @MediumTest
-    @Features.EnableFeatures({ChromeFeatureList.READ_LATER + ":use_root_bookmark_as_default/false"})
     public void testPromoDraggability() throws Exception {
         BookmarkId testId = addFolder(TEST_FOLDER_TITLE);
 
         BookmarkPromoHeader.forcePromoStateForTests(SyncPromoState.PROMO_FOR_SYNC_TURNED_OFF_STATE);
         openBookmarkManager();
+        openMobileBookmarks();
 
         ViewHolder promo = mItemsContainer.findViewHolderForAdapterPosition(0);
 
@@ -955,11 +970,11 @@
 
     @Test
     @MediumTest
-    @Features.EnableFeatures({ChromeFeatureList.READ_LATER + ":use_root_bookmark_as_default/false"})
     public void testPartnerFolderDraggability() throws Exception {
         BookmarkId testId = addFolderWithPartner(TEST_FOLDER_TITLE);
         BookmarkPromoHeader.forcePromoStateForTests(SyncPromoState.PROMO_FOR_SYNC_TURNED_OFF_STATE);
         openBookmarkManager();
+        openMobileBookmarks();
 
         ViewHolder partner = mItemsContainer.findViewHolderForAdapterPosition(2);
 
@@ -975,13 +990,13 @@
 
     @Test
     @MediumTest
-    @Features.EnableFeatures({ChromeFeatureList.READ_LATER + ":use_root_bookmark_as_default/false"})
     public void testUnselectedItemDraggability() throws Exception {
         BookmarkId aId = addBookmark("a", mTestUrlA);
         addFolder(TEST_FOLDER_TITLE);
 
         BookmarkPromoHeader.forcePromoStateForTests(SyncPromoState.PROMO_FOR_SYNC_TURNED_OFF_STATE);
         openBookmarkManager();
+        openMobileBookmarks();
 
         ViewHolder test = mItemsContainer.findViewHolderForAdapterPosition(1);
         Assert.assertEquals("Wrong bookmark item selected.", TEST_FOLDER_TITLE,
@@ -1030,12 +1045,13 @@
 
     @Test
     @MediumTest
-    @Features.EnableFeatures({ChromeFeatureList.READ_LATER + ":use_root_bookmark_as_default/false"})
     public void testMoveUpMenuItem() throws Exception {
         addBookmark(TEST_PAGE_TITLE_GOOGLE, mTestUrlA);
         addFolder(TEST_FOLDER_TITLE);
         BookmarkPromoHeader.forcePromoStateForTests(SyncPromoState.PROMO_FOR_SYNC_TURNED_OFF_STATE);
+
         openBookmarkManager();
+        openMobileBookmarks();
 
         View google = mItemsContainer.findViewHolderForAdapterPosition(2).itemView;
         Assert.assertEquals("Wrong bookmark item selected.", TEST_PAGE_TITLE_GOOGLE,
@@ -1057,12 +1073,12 @@
 
     @Test
     @MediumTest
-    @Features.EnableFeatures({ChromeFeatureList.READ_LATER + ":use_root_bookmark_as_default/false"})
     public void testMoveDownMenuItem() throws Exception {
         addBookmark(TEST_PAGE_TITLE_GOOGLE, mTestUrlA);
         addFolder(TEST_FOLDER_TITLE);
         BookmarkPromoHeader.forcePromoStateForTests(SyncPromoState.PROMO_FOR_SYNC_TURNED_OFF_STATE);
         openBookmarkManager();
+        openMobileBookmarks();
 
         View testFolder = mItemsContainer.findViewHolderForAdapterPosition(1).itemView;
         Assert.assertEquals("Wrong bookmark item selected.", TEST_FOLDER_TITLE,
@@ -1084,12 +1100,12 @@
 
     @Test
     @MediumTest
-    @Features.EnableFeatures({ChromeFeatureList.READ_LATER + ":use_root_bookmark_as_default/false"})
     public void testMoveDownGoneForBottomElement() throws Exception {
         addBookmarkWithPartner(TEST_PAGE_TITLE_GOOGLE, mTestUrlA);
         addFolderWithPartner(TEST_FOLDER_TITLE);
         BookmarkPromoHeader.forcePromoStateForTests(SyncPromoState.PROMO_FOR_SYNC_TURNED_OFF_STATE);
         openBookmarkManager();
+        openMobileBookmarks();
 
         View google = mItemsContainer.findViewHolderForAdapterPosition(2).itemView;
         Assert.assertEquals("Wrong bookmark item selected.", TEST_PAGE_TITLE_GOOGLE,
@@ -1101,12 +1117,12 @@
 
     @Test
     @MediumTest
-    @Features.EnableFeatures({ChromeFeatureList.READ_LATER + ":use_root_bookmark_as_default/false"})
     public void testMoveUpGoneForTopElement() throws Exception {
         addBookmark(TEST_PAGE_TITLE_GOOGLE, mTestUrlA);
         addFolder(TEST_FOLDER_TITLE);
         BookmarkPromoHeader.forcePromoStateForTests(SyncPromoState.PROMO_FOR_SYNC_TURNED_OFF_STATE);
         openBookmarkManager();
+        openMobileBookmarks();
 
         View testFolder = mItemsContainer.findViewHolderForAdapterPosition(1).itemView;
         Assert.assertEquals("Wrong bookmark item selected.", TEST_FOLDER_TITLE,
@@ -1142,11 +1158,11 @@
 
     @Test
     @MediumTest
-    @Features.EnableFeatures({ChromeFeatureList.READ_LATER + ":use_root_bookmark_as_default/false"})
     public void testMoveButtonsGoneWithOneBookmark() throws Exception {
         addFolder(TEST_FOLDER_TITLE);
         BookmarkPromoHeader.forcePromoStateForTests(SyncPromoState.PROMO_FOR_SYNC_TURNED_OFF_STATE);
         openBookmarkManager();
+        openMobileBookmarks();
 
         View testFolder = mItemsContainer.findViewHolderForAdapterPosition(1).itemView;
         Assert.assertEquals("Wrong bookmark item selected.", TEST_FOLDER_TITLE,
@@ -1241,11 +1257,11 @@
     @Test
     @MediumTest
     @DisabledTest(message = "crbug.com/1369091")
-    @Features.EnableFeatures({ChromeFeatureList.READ_LATER + ":use_root_bookmark_as_default/false"})
     public void testShowInFolder_NoScroll() throws Exception {
         addFolder(TEST_FOLDER_TITLE);
         BookmarkPromoHeader.forcePromoStateForTests(SyncPromoState.PROMO_FOR_SYNC_TURNED_OFF_STATE);
         openBookmarkManager();
+        openMobileBookmarks();
 
         // Enter search mode.
         enterSearch();
@@ -1513,7 +1529,6 @@
 
     @Test
     @MediumTest
-    @Features.EnableFeatures({ChromeFeatureList.READ_LATER + ":use_root_bookmark_as_default/false"})
     public void testBookmarksDoesNotRecordLaunchMetrics() throws Throwable {
         Assert.assertEquals(1,
                 RecordHistogram.getHistogramTotalCountForTesting(
@@ -1521,6 +1536,7 @@
 
         addBookmark(TEST_PAGE_TITLE_GOOGLE, mTestPage);
         openBookmarkManager();
+
         pressBack();
         BookmarkTestUtil.waitForTabbedActivity();
         Assert.assertEquals(1,
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/app/appmenu/AppMenuPropertiesDelegateUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/app/appmenu/AppMenuPropertiesDelegateUnitTest.java
index 9091160..52c1b88 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/app/appmenu/AppMenuPropertiesDelegateUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/app/appmenu/AppMenuPropertiesDelegateUnitTest.java
@@ -27,8 +27,6 @@
 import androidx.annotation.NonNull;
 import androidx.test.filters.SmallTest;
 
-import com.google.common.base.Optional;
-
 import org.hamcrest.Matchers;
 import org.junit.After;
 import org.junit.Assert;
@@ -110,6 +108,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Optional;
 
 /**
  * Unit tests for {@link AppMenuPropertiesDelegateImpl}.
@@ -858,7 +857,7 @@
                 .getBookmarksOfType(eq(PowerBookmarkType.SHOPPING));
         Long clusterId = 1L;
         doReturn(new ShoppingService.ProductInfo(
-                         "", new GURL(""), clusterId, 0, "", 0, "", Optional.absent()))
+                         "", new GURL(""), clusterId, 0, "", 0, "", Optional.empty()))
                 .when(mShoppingService)
                 .getAvailableProductInfoForUrl(any());
         PowerBookmarkMeta meta =
diff --git a/chrome/app/OWNERS b/chrome/app/OWNERS
index 46750333..e542c3c 100644
--- a/chrome/app/OWNERS
+++ b/chrome/app/OWNERS
@@ -40,8 +40,6 @@
 per-file settings_google_chrome_strings.grdp=sauski@google.com
 per-file settings_strings.grdp=sauski@google.com
 
-per-file supervised_user_error_page_strings.grdp=file://chrome/browser/supervised_user/OWNERS
-
 per-file vr_strings.grdp=file://chrome/browser/vr/OWNERS
 
 per-file password_manager_ui_strings.grdp=file://chrome/browser/password_manager/OWNERS
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index 19b282f..b1cc7c3c 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -346,11 +346,6 @@
         <part file="vr_strings.grdp" />
       </if>
 
-      <!-- Supervised user error page -->
-      <if expr="enable_supervised_users">
-        <part file="supervised_user_error_page_strings.grdp" />
-      </if>
-
       <!-- TODO add all of your "string table" messages here.  Remember to
       change nontranslateable parts of the messages into placeholders (using the
       <ph> element).  You can also use the 'grit add' tool to help you identify
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index d7c913b..da4fa38 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -3461,8 +3461,8 @@
       ]
       deps += [
         "//chrome/browser/supervised_user:jni_headers",
-        "//chrome/browser/supervised_user/supervised_user_error_page",
-        "//chrome/browser/supervised_user/supervised_user_features",
+        "//components/supervised_user/core/browser",
+        "//components/supervised_user/core/common",
       ]
     }
 
@@ -5173,7 +5173,6 @@
       "//chrome/browser/nearby_sharing/scheduling",
       "//chrome/browser/policy:onc",
       "//chrome/browser/resources/chromeos:app_icon_resources",
-      "//chrome/browser/supervised_user:supervised_user_unscaled_resources",
       "//chrome/browser/ui/ash/system_web_apps",
       "//chrome/browser/ui/quick_answers",
       "//chrome/browser/ui/webui/ash/add_supervision:mojo_bindings",
@@ -7762,9 +7761,9 @@
     }
     deps += [
       "//chrome/browser/supervised_user/kids_chrome_management:proto",
-      "//chrome/browser/supervised_user/supervised_user_error_page",
-      "//chrome/browser/supervised_user/supervised_user_features",
       "//chrome/common:supervised_user_commands_mojom",
+      "//components/supervised_user/core/browser",
+      "//components/supervised_user/core/common",
     ]
   }
   if (enable_supervised_users && enable_extensions) {
@@ -8072,7 +8071,6 @@
       "//chrome/browser/resources/chromeos/set_time_dialog:html_wrapper_files",
       "//chrome/browser/resources/chromeos/smb_shares:web_components",
       "//chrome/browser/resources/chromeos/vm:web_components",
-      "//chrome/browser/supervised_user:supervised_user_unscaled_resources",
       "//chrome/browser/ui/webui/ash/add_supervision:mojo_bindings_js",
       "//chrome/browser/ui/webui/ash/crostini_installer:mojo_bindings_js",
       "//chrome/browser/ui/webui/ash/crostini_upgrader:mojo_bindings_js",
diff --git a/chrome/browser/DEPS b/chrome/browser/DEPS
index dcd016c..41af8cc 100644
--- a/chrome/browser/DEPS
+++ b/chrome/browser/DEPS
@@ -328,7 +328,8 @@
   "+components/subresource_filter/core/browser",
   "+components/subresource_filter/core/common",
   "+components/subresource_filter/core/mojom",
-  "+components/supervised_user_error_page",
+  "+components/supervised_user/core/browser",
+  "+components/supervised_user/core/common",
   "+components/sync",
   "+components/sync_bookmarks",
   "+components/sync_device_info",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index de5d0fbc..78261653 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -291,7 +291,7 @@
 #endif
 
 #if BUILDFLAG(ENABLE_SUPERVISED_USERS)
-#include "chrome/browser/supervised_user/supervised_user_features/supervised_user_features.h"  // nogncheck
+#include "components/supervised_user/core/common/features.h"  // nogncheck
 #endif  // ENABLE_SUPERVISED_USERS
 
 #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS_ASH)
@@ -6571,7 +6571,7 @@
 #endif
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
-    {"cros-labs-float-window", flag_descriptions::kFloatWindow,
+    {"float-window", flag_descriptions::kFloatWindow,
      flag_descriptions::kFloatWindowDescription, kOsCrOS,
      FEATURE_VALUE_TYPE(chromeos::wm::features::kFloatWindow)},
     {"cros-labs-overview-desk-navigation",
@@ -9514,7 +9514,14 @@
      flag_descriptions::kServiceWorkerBypassFetchHandlerForMainResourceName,
      flag_descriptions::
          kServiceWorkerBypassFetchHandlerForMainResourceDescription,
-     kOsAll, FEATURE_VALUE_TYPE(features::kServiceWorkerBypassFetchHandler)}
+     kOsAll, FEATURE_VALUE_TYPE(features::kServiceWorkerBypassFetchHandler)},
+
+    {"autofill-remove-card-expiration-and-type-titles",
+     flag_descriptions::kAutofillRemoveCardExpirationAndTypeTitlesName,
+     flag_descriptions::kAutofillRemoveCardExpirationAndTypeTitlesDescription,
+     kOsDesktop,
+     FEATURE_VALUE_TYPE(
+         autofill::features::kAutofillRemoveCardExpirationAndTypeTitles)},
     // NOTE: Adding a new flag requires adding a corresponding entry to enum
     // "LoginCustomFlags" in tools/metrics/histograms/enums.xml. See "Flag
     // Histograms" in tools/metrics/histograms/README.md (run the
diff --git a/chrome/browser/apps/app_service/app_service_proxy_ash.cc b/chrome/browser/apps/app_service/app_service_proxy_ash.cc
index 63b6e5d..37fef41 100644
--- a/chrome/browser/apps/app_service/app_service_proxy_ash.cc
+++ b/chrome/browser/apps/app_service/app_service_proxy_ash.cc
@@ -26,12 +26,12 @@
 #include "chrome/browser/ash/profiles/profile_helper.h"
 #include "chrome/browser/chromeos/policy/dlp/dlp_rules_manager_factory.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/supervised_user/grit/supervised_user_unscaled_resources.h"
 #include "chrome/browser/web_applications/web_app_utils.h"
 #include "components/account_id/account_id.h"
 #include "components/app_constants/constants.h"
 #include "components/app_restore/full_restore_save_handler.h"
 #include "components/app_restore/full_restore_utils.h"
+#include "components/grit/components_resources.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"
diff --git a/chrome/browser/ash/BUILD.gn b/chrome/browser/ash/BUILD.gn
index 99d19c28..a4ee918e 100644
--- a/chrome/browser/ash/BUILD.gn
+++ b/chrome/browser/ash/BUILD.gn
@@ -3584,7 +3584,6 @@
     "//chrome/browser/resources:component_extension_resources",
     "//chrome/browser/resources/chromeos:app_icon_resources",
     "//chrome/browser/resources/settings/chromeos:resources",
-    "//chrome/browser/supervised_user/supervised_user_features",
     "//chrome/browser/ui/ash/system_web_apps",
     "//chrome/browser/ui/webui/ash/cloud_upload:mojo_bindings",
     "//chrome/browser/ui/webui/ash/crostini_upgrader:mojo_bindings",
@@ -3737,6 +3736,7 @@
     "//components/spellcheck/browser",
     "//components/startup_metric_utils/browser",
     "//components/strings:components_strings",
+    "//components/supervised_user/core/common",
     "//components/sync/base",
     "//components/sync/chromeos",
     "//components/sync/engine",
diff --git a/chrome/browser/ash/app_list/app_list_client_impl.cc b/chrome/browser/ash/app_list/app_list_client_impl.cc
index 23846cb..a7bc86c 100644
--- a/chrome/browser/ash/app_list/app_list_client_impl.cc
+++ b/chrome/browser/ash/app_list/app_list_client_impl.cc
@@ -212,6 +212,7 @@
   app_list::LaunchData launch_data;
   launch_data.id = result_id;
   launch_data.result_type = result->result_type();
+  launch_data.category = result->category();
   launch_data.launch_type = launch_type;
   launch_data.launched_from = launched_from;
   launch_data.suggestion_index = suggestion_index;
diff --git a/chrome/browser/ash/app_list/arc/arc_app_utils.cc b/chrome/browser/ash/app_list/arc/arc_app_utils.cc
index c114ff5b5..778bdba 100644
--- a/chrome/browser/ash/app_list/arc/arc_app_utils.cc
+++ b/chrome/browser/ash/app_list/arc/arc_app_utils.cc
@@ -32,6 +32,7 @@
 #include "chrome/browser/ash/app_list/arc/intent.h"
 #include "chrome/browser/ash/app_list/search/ranking/launch_data.h"
 #include "chrome/browser/ash/app_list/search/search_controller.h"
+#include "chrome/browser/ash/app_list/search/types.h"
 #include "chrome/browser/ash/arc/arc_migration_guide_notification.h"
 #include "chrome/browser/ash/arc/arc_util.h"
 #include "chrome/browser/ash/arc/boot_phase_monitor/arc_boot_phase_monitor_bridge.h"
@@ -783,6 +784,7 @@
   launch_data.id =
       ConstructArcAppShortcutUrl(arc_shelf_id.app_id(), shortcut_id),
   launch_data.result_type = ash::AppListSearchResultType::kArcAppShortcut;
+  launch_data.category = app_list::Category::kAppShortcuts;
   app_list_client_impl->search_controller()->Train(std::move(launch_data));
 }
 
diff --git a/chrome/browser/ash/app_list/search/ranking/ftrl_ranker.cc b/chrome/browser/ash/app_list/search/ranking/ftrl_ranker.cc
index 24dbe60..091bf7012 100644
--- a/chrome/browser/ash/app_list/search/ranking/ftrl_ranker.cc
+++ b/chrome/browser/ash/app_list/search/ranking/ftrl_ranker.cc
@@ -41,7 +41,7 @@
       ftrl_->Train(launch.id);
       break;
     case FtrlRanker::RankingKind::kCategories:
-      ftrl_->Train(CategoryToString(ResultTypeToCategory(launch.result_type)));
+      ftrl_->Train(CategoryToString(launch.category));
       break;
   }
 }
diff --git a/chrome/browser/ash/app_list/search/ranking/ftrl_ranker_unittest.cc b/chrome/browser/ash/app_list/search/ranking/ftrl_ranker_unittest.cc
index ae4cc3e..6cebeada 100644
--- a/chrome/browser/ash/app_list/search/ranking/ftrl_ranker_unittest.cc
+++ b/chrome/browser/ash/app_list/search/ranking/ftrl_ranker_unittest.cc
@@ -133,7 +133,7 @@
 
   for (int i = 0; i < 10; ++i) {
     ranker.UpdateCategoryRanks(results, categories, ResultType::kInstalledApp);
-    ranker.Train(MakeLaunchData("a", ResultType::kInstalledApp));
+    ranker.Train(MakeLaunchData("a", Category::kApps));
   }
 
   // The weights of the FTRL optimizer should reflect that the good ranker is
@@ -149,7 +149,7 @@
   // particularly good job at predicting "c"'s category.
   for (int i = 0; i < 10; ++i) {
     ranker.UpdateResultRanks(results, ResultType::kInstalledApp);
-    ranker.Train(MakeLaunchData("c", ResultType::kFileSearch));
+    ranker.Train(MakeLaunchData("c", Category::kFiles));
   }
 
   // The weights of the 'bad' expert should have recovered.
diff --git a/chrome/browser/ash/app_list/search/ranking/launch_data.h b/chrome/browser/ash/app_list/search/ranking/launch_data.h
index f375c73b..29a8b184 100644
--- a/chrome/browser/ash/app_list/search/ranking/launch_data.h
+++ b/chrome/browser/ash/app_list/search/ranking/launch_data.h
@@ -8,6 +8,7 @@
 #include <string>
 
 #include "ash/public/cpp/app_list/app_list_types.h"
+#include "chrome/browser/ash/app_list/search/types.h"
 
 namespace app_list {
 
@@ -24,6 +25,8 @@
       ash::AppListSearchResultType::kUnknown;
   ash::AppListLaunchedFrom launched_from =
       ash::AppListLaunchedFrom::kLaunchedFromShelf;
+  // The category of the result.
+  Category category = Category::kUnknown;
   // The type of app launched.
   ash::AppListLaunchType launch_type = ash::AppListLaunchType::kSearchResult;
   // The index of the suggested app launched, if applicable.
diff --git a/chrome/browser/ash/app_list/search/ranking/mrfu_ranker.cc b/chrome/browser/ash/app_list/search/ranking/mrfu_ranker.cc
index ce23194..1a083546 100644
--- a/chrome/browser/ash/app_list/search/ranking/mrfu_ranker.cc
+++ b/chrome/browser/ash/app_list/search/ranking/mrfu_ranker.cc
@@ -101,7 +101,7 @@
       ash::AppListLaunchedFrom::kLaunchedFromSearchBox) {
     return;
   }
-  mrfu_->Use(CategoryToString(ResultTypeToCategory(launch.result_type)));
+  mrfu_->Use(CategoryToString(launch.category));
 }
 
 void MrfuCategoryRanker::SetDefaultCategoryScores() {
diff --git a/chrome/browser/ash/app_list/search/ranking/mrfu_ranker_unittest.cc b/chrome/browser/ash/app_list/search/ranking/mrfu_ranker_unittest.cc
index 54a1f086..2bba79d 100644
--- a/chrome/browser/ash/app_list/search/ranking/mrfu_ranker_unittest.cc
+++ b/chrome/browser/ash/app_list/search/ranking/mrfu_ranker_unittest.cc
@@ -111,10 +111,10 @@
   Wait();
 
   // Train so that settings should be first, followed by apps.
-  ranker.Train(MakeLaunchData("a", ResultType::kInstalledApp));
-  ranker.Train(MakeLaunchData("c", ResultType::kOsSettings));
-  ranker.Train(MakeLaunchData("d", ResultType::kOsSettings));
-  ranker.Train(MakeLaunchData("b", ResultType::kInstalledApp));
+  ranker.Train(MakeLaunchData("a", Category::kApps));
+  ranker.Train(MakeLaunchData("c", Category::kSettings));
+  ranker.Train(MakeLaunchData("d", Category::kSettings));
+  ranker.Train(MakeLaunchData("b", Category::kApps));
 
   ResultsMap results;
   results[ResultType::kInstalledApp] =
diff --git a/chrome/browser/ash/app_list/search/ranking/util.cc b/chrome/browser/ash/app_list/search/ranking/util.cc
index 1c30ae6..ad444fe18 100644
--- a/chrome/browser/ash/app_list/search/ranking/util.cc
+++ b/chrome/browser/ash/app_list/search/ranking/util.cc
@@ -29,46 +29,4 @@
   return static_cast<Category>(number);
 }
 
-Category ResultTypeToCategory(ResultType result_type) {
-  switch (result_type) {
-    case ResultType::kInstalledApp:
-    case ResultType::kZeroStateApp:
-    case ResultType::kInstantApp:
-    case ResultType::kInternalApp:
-    case ResultType::kGames:
-      return Category::kApps;
-    case ResultType::kArcAppShortcut:
-      return Category::kAppShortcuts;
-    case ResultType::kOmnibox:
-    case ResultType::kAnswerCard:
-    case ResultType::kOpenTab:
-      return Category::kWeb;
-    case ResultType::kZeroStateFile:
-    case ResultType::kZeroStateDrive:
-    case ResultType::kFileSearch:
-    case ResultType::kDriveSearch:
-      return Category::kFiles;
-    case ResultType::kOsSettings:
-    case ResultType::kPersonalization:
-      return Category::kSettings;
-    case ResultType::kHelpApp:
-    case ResultType::kZeroStateHelpApp:
-    case ResultType::kKeyboardShortcut:
-      return Category::kHelp;
-    case ResultType::kPlayStoreReinstallApp:
-    case ResultType::kPlayStoreApp:
-      return Category::kPlayStore;
-    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
-    // search backend.
-    case ResultType::kInternalPrivacyInfo:
-    // Deprecated.
-    case ResultType::kLauncher:
-      return Category::kUnknown;
-  }
-}
-
 }  // namespace app_list
diff --git a/chrome/browser/ash/app_list/search/ranking/util.h b/chrome/browser/ash/app_list/search/ranking/util.h
index 3373fe6..cb0815f 100644
--- a/chrome/browser/ash/app_list/search/ranking/util.h
+++ b/chrome/browser/ash/app_list/search/ranking/util.h
@@ -22,13 +22,6 @@
 
 Category StringToCategory(const std::string& value);
 
-// TODO(crbug.com/1378862): This can be removed once LaunchData contains the
-// result category.
-//
-// This is slightly inconsistent with the true result->category mapping, because
-// Omnibox results can either be in the kWeb or kSearchAndAssistant category.
-Category ResultTypeToCategory(ResultType result_type);
-
 }  // namespace app_list
 
 #endif  // CHROME_BROWSER_ASH_APP_LIST_SEARCH_RANKING_UTIL_H_
diff --git a/chrome/browser/ash/app_list/search/test/ranking_test_util.cc b/chrome/browser/ash/app_list/search/test/ranking_test_util.cc
index 15bb1d5a..b767edd6 100644
--- a/chrome/browser/ash/app_list/search/test/ranking_test_util.cc
+++ b/chrome/browser/ash/app_list/search/test/ranking_test_util.cc
@@ -38,11 +38,11 @@
 }
 
 LaunchData RankerTestBase::MakeLaunchData(const std::string& id,
-                                          ResultType result_type) {
+                                          Category category) {
   LaunchData launch;
   launch.launched_from = ash::AppListLaunchedFrom::kLaunchedFromSearchBox;
   launch.id = id;
-  launch.result_type = result_type;
+  launch.category = category;
   return launch;
 }
 
diff --git a/chrome/browser/ash/app_list/search/test/ranking_test_util.h b/chrome/browser/ash/app_list/search/test/ranking_test_util.h
index 1bc01c5b..8fbdbe1e 100644
--- a/chrome/browser/ash/app_list/search/test/ranking_test_util.h
+++ b/chrome/browser/ash/app_list/search/test/ranking_test_util.h
@@ -34,7 +34,7 @@
                       Category category = Category::kUnknown);
 
   LaunchData MakeLaunchData(const std::string& id,
-                            ResultType result_type = ResultType::kUnknown);
+                            Category category = Category::kUnknown);
 
   template <typename T>
   T ReadProtoFromDisk() {
diff --git a/chrome/browser/ash/arc/app_shortcuts/arc_app_shortcuts_menu_builder.cc b/chrome/browser/ash/arc/app_shortcuts/arc_app_shortcuts_menu_builder.cc
index 8c3aa682..657d683b 100644
--- a/chrome/browser/ash/arc/app_shortcuts/arc_app_shortcuts_menu_builder.cc
+++ b/chrome/browser/ash/arc/app_shortcuts/arc_app_shortcuts_menu_builder.cc
@@ -14,6 +14,7 @@
 #include "chrome/browser/ash/app_list/arc/arc_app_utils.h"
 #include "chrome/browser/ash/app_list/search/ranking/launch_data.h"
 #include "chrome/browser/ash/app_list/search/search_controller.h"
+#include "chrome/browser/ash/app_list/search/types.h"
 #include "chrome/browser/ash/arc/app_shortcuts/arc_app_shortcuts_request.h"
 #include "chrome/browser/profiles/profile.h"
 #include "ui/base/models/image_model.h"
@@ -81,6 +82,7 @@
   launch_data.id = ConstructArcAppShortcutUrl(
       app_id_, app_shortcut_items_->at(index).shortcut_id),
   launch_data.result_type = ash::AppListSearchResultType::kArcAppShortcut;
+  launch_data.category = app_list::Category::kAppShortcuts;
   app_list_client_impl->search_controller()->Train(std::move(launch_data));
 }
 
diff --git a/chrome/browser/ash/child_accounts/time_limits/app_service_wrapper_unittest.cc b/chrome/browser/ash/child_accounts/time_limits/app_service_wrapper_unittest.cc
index 3c452cb..3c65cc7 100644
--- a/chrome/browser/ash/child_accounts/time_limits/app_service_wrapper_unittest.cc
+++ b/chrome/browser/ash/child_accounts/time_limits/app_service_wrapper_unittest.cc
@@ -150,8 +150,8 @@
     }
 
     if (app_id.app_type() == apps::AppType::kChromeApp) {
-      scoped_refptr<extensions::Extension> ext = CreateExtension(
-          app_id.app_id(), app_name, url.value(), false /*is_bookmark_app*/);
+      scoped_refptr<extensions::Extension> ext =
+          CreateExtension(app_id.app_id(), app_name, url.value());
       extension_service_->AddExtension(ext.get());
       task_environment_.RunUntilIdle();
       return;
@@ -166,14 +166,6 @@
       task_environment_.RunUntilIdle();
       return;
     }
-
-    if (app_id.app_type() == apps::AppType::kWeb) {
-      scoped_refptr<extensions::Extension> web_app = CreateExtension(
-          app_id.app_id(), app_name, url.value(), true /*is_bookmark_app*/);
-      extension_service_->AddExtension(web_app.get());
-      task_environment_.RunUntilIdle();
-      return;
-    }
   }
 
   void SimulateAppUninstalled(const AppId& app_id) {
diff --git a/chrome/browser/ash/child_accounts/time_limits/app_time_test_utils.cc b/chrome/browser/ash/child_accounts/time_limits/app_time_test_utils.cc
index 5464ff4..89ef203 100644
--- a/chrome/browser/ash/child_accounts/time_limits/app_time_test_utils.cc
+++ b/chrome/browser/ash/child_accounts/time_limits/app_time_test_utils.cc
@@ -38,8 +38,7 @@
 scoped_refptr<extensions::Extension> CreateExtension(
     const std::string& extension_id,
     const std::string& name,
-    const std::string& url,
-    bool is_bookmark_app) {
+    const std::string& url) {
   base::Value::Dict manifest;
   manifest.Set(extensions::manifest_keys::kName, name);
   manifest.Set(extensions::manifest_keys::kVersion, "1");
@@ -48,8 +47,7 @@
 
   std::string error;
   extensions::Extension::InitFromValueFlags flags =
-      is_bookmark_app ? extensions::Extension::FROM_BOOKMARK
-                      : extensions::Extension::NO_FLAGS;
+      extensions::Extension::NO_FLAGS;
   scoped_refptr<extensions::Extension> extension =
       extensions::Extension::Create(
           base::FilePath(), extensions::mojom::ManifestLocation::kUnpacked,
diff --git a/chrome/browser/ash/child_accounts/time_limits/app_time_test_utils.h b/chrome/browser/ash/child_accounts/time_limits/app_time_test_utils.h
index bfc9e6c3..f6e26dfd 100644
--- a/chrome/browser/ash/child_accounts/time_limits/app_time_test_utils.h
+++ b/chrome/browser/ash/child_accounts/time_limits/app_time_test_utils.h
@@ -26,8 +26,7 @@
 scoped_refptr<extensions::Extension> CreateExtension(
     const std::string& extension_id,
     const std::string& name,
-    const std::string& url,
-    bool is_bookmark_app = false);
+    const std::string& url);
 
 }  // namespace app_time
 }  // namespace ash
diff --git a/chrome/browser/ash/dbus/fusebox_service_provider.cc b/chrome/browser/ash/dbus/fusebox_service_provider.cc
index 6cc4e251..7a05074 100644
--- a/chrome/browser/ash/dbus/fusebox_service_provider.cc
+++ b/chrome/browser/ash/dbus/fusebox_service_provider.cc
@@ -14,14 +14,6 @@
 // This file provides the "D-Bus protocol logic" half of the FuseBox server,
 // coupled with the "business logic" half in fusebox_server.cc.
 
-// The "fusebox_staging" concept is described in
-// chrome/browser/ash/fusebox/fusebox_staging.proto
-//
-// TODO(b/255520194): remove this section.
-namespace fusebox_staging {
-const char kStat2Method[] = "Stat2";
-}  // namespace fusebox_staging
-
 namespace ash {
 
 namespace {
@@ -64,7 +56,7 @@
   ExportProtoMethod(fusebox::kRead2Method, &fusebox::Server::Read2);
   ExportProtoMethod(fusebox::kReadDir2Method, &fusebox::Server::ReadDir2);
   ExportProtoMethod(fusebox::kRmDirMethod, &fusebox::Server::RmDir);
-  ExportProtoMethod(fusebox_staging::kStat2Method, &fusebox::Server::Stat2);
+  ExportProtoMethod(fusebox::kStat2Method, &fusebox::Server::Stat2);
   ExportProtoMethod(fusebox::kTruncateMethod, &fusebox::Server::Truncate);
   ExportProtoMethod(fusebox::kUnlinkMethod, &fusebox::Server::Unlink);
   ExportProtoMethod(fusebox::kWrite2Method, &fusebox::Server::Write2);
diff --git a/chrome/browser/ash/fusebox/fusebox_server.cc b/chrome/browser/ash/fusebox/fusebox_server.cc
index 8ee5c21..f42e089 100644
--- a/chrome/browser/ash/fusebox/fusebox_server.cc
+++ b/chrome/browser/ash/fusebox/fusebox_server.cc
@@ -162,7 +162,7 @@
 // looks unused, but we need to keep the storage::FileSystemContext reference
 // alive until the callbacks are run.
 
-void FillInDirEntryProto(fusebox_staging::DirEntryProto* dir_entry_proto,
+void FillInDirEntryProto(DirEntryProto* dir_entry_proto,
                          const base::File::Info& info,
                          bool read_only) {
   dir_entry_proto->set_mode_bits(
@@ -185,13 +185,13 @@
   int posix_error_code = FileErrorToErrno(error_code);
   if (posix_error_code) {
     std::move(on_failure).Run();
-    fusebox_staging::CreateResponseProto response_proto;
+    CreateResponseProto response_proto;
     response_proto.set_posix_error_code(posix_error_code);
     std::move(callback).Run(response_proto);
     return;
   }
 
-  fusebox_staging::CreateResponseProto response_proto;
+  CreateResponseProto response_proto;
   response_proto.set_fuse_handle(fuse_handle);
   FillInDirEntryProto(response_proto.mutable_stat(), info, read_only);
   std::move(callback).Run(response_proto);
@@ -210,7 +210,7 @@
   int posix_error_code = FileErrorToErrno(error_code);
   if (posix_error_code) {
     std::move(on_failure).Run();
-    fusebox_staging::CreateResponseProto response_proto;
+    CreateResponseProto response_proto;
     response_proto.set_posix_error_code(posix_error_code);
     std::move(callback).Run(response_proto);
     return;
@@ -246,13 +246,13 @@
 
   int posix_error_code = FileErrorToErrno(error_code);
   if (posix_error_code) {
-    fusebox_staging::MkDirResponseProto response_proto;
+    MkDirResponseProto response_proto;
     response_proto.set_posix_error_code(posix_error_code);
     std::move(callback).Run(response_proto);
     return;
   }
 
-  fusebox_staging::MkDirResponseProto response_proto;
+  MkDirResponseProto response_proto;
   FillInDirEntryProto(response_proto.mutable_stat(), info, read_only);
   std::move(callback).Run(response_proto);
 }
@@ -267,7 +267,7 @@
 
   int posix_error_code = FileErrorToErrno(error_code);
   if (posix_error_code) {
-    fusebox_staging::MkDirResponseProto response_proto;
+    MkDirResponseProto response_proto;
     response_proto.set_posix_error_code(posix_error_code);
     std::move(callback).Run(response_proto);
     return;
@@ -296,7 +296,7 @@
                              base::File::Error error_code) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
-  fusebox_staging::Read2ResponseProto response_proto;
+  Read2ResponseProto response_proto;
   response_proto.set_posix_error_code(FileErrorToErrno(error_code));
   std::move(callback).Run(response_proto);
 }
@@ -306,7 +306,7 @@
                              int length) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
-  fusebox_staging::Read2ResponseProto response_proto;
+  Read2ResponseProto response_proto;
   if (length < 0) {
     response_proto.set_posix_error_code(NetErrorToErrno(length));
   } else {
@@ -325,13 +325,13 @@
 
   int posix_error_code = FileErrorToErrno(error_code);
   if (posix_error_code) {
-    fusebox_staging::RmDirResponseProto response_proto;
+    RmDirResponseProto response_proto;
     response_proto.set_posix_error_code(posix_error_code);
     std::move(callback).Run(response_proto);
     return;
   }
 
-  fusebox_staging::RmDirResponseProto response_proto;
+  RmDirResponseProto response_proto;
   std::move(callback).Run(response_proto);
 }
 
@@ -345,13 +345,13 @@
 
   int posix_error_code = FileErrorToErrno(error_code);
   if (posix_error_code) {
-    fusebox_staging::TruncateResponseProto response_proto;
+    TruncateResponseProto response_proto;
     response_proto.set_posix_error_code(posix_error_code);
     std::move(callback).Run(response_proto);
     return;
   }
 
-  fusebox_staging::TruncateResponseProto response_proto;
+  TruncateResponseProto response_proto;
   FillInDirEntryProto(response_proto.mutable_stat(), info, read_only);
   std::move(callback).Run(response_proto);
 }
@@ -366,7 +366,7 @@
 
   int posix_error_code = FileErrorToErrno(error_code);
   if (posix_error_code) {
-    fusebox_staging::TruncateResponseProto response_proto;
+    TruncateResponseProto response_proto;
     response_proto.set_posix_error_code(posix_error_code);
     std::move(callback).Run(response_proto);
     return;
@@ -399,13 +399,13 @@
 
   int posix_error_code = FileErrorToErrno(error_code);
   if (posix_error_code) {
-    fusebox_staging::UnlinkResponseProto response_proto;
+    UnlinkResponseProto response_proto;
     response_proto.set_posix_error_code(posix_error_code);
     std::move(callback).Run(response_proto);
     return;
   }
 
-  fusebox_staging::UnlinkResponseProto response_proto;
+  UnlinkResponseProto response_proto;
   std::move(callback).Run(response_proto);
 }
 
@@ -413,7 +413,7 @@
                               base::File::Error error_code) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
-  fusebox_staging::Write2ResponseProto response_proto;
+  Write2ResponseProto response_proto;
   response_proto.set_posix_error_code(FileErrorToErrno(error_code));
   std::move(callback).Run(response_proto);
 }
@@ -421,7 +421,7 @@
 void RunWrite2CallbackTypical(Server::Write2Callback callback, int length) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
-  fusebox_staging::Write2ResponseProto response_proto;
+  Write2ResponseProto response_proto;
   if (length < 0) {
     response_proto.set_posix_error_code(NetErrorToErrno(length));
   }
@@ -438,13 +438,13 @@
 
   int posix_error_code = FileErrorToErrno(error_code);
   if (posix_error_code) {
-    fusebox_staging::Stat2ResponseProto response_proto;
+    Stat2ResponseProto response_proto;
     response_proto.set_posix_error_code(posix_error_code);
     std::move(callback).Run(response_proto);
     return;
   }
 
-  fusebox_staging::Stat2ResponseProto response_proto;
+  Stat2ResponseProto response_proto;
   FillInDirEntryProto(response_proto.mutable_stat(), info, read_only);
   std::move(callback).Run(response_proto);
 }
@@ -613,20 +613,18 @@
 
 Server::FuseFileMapEntry::~FuseFileMapEntry() = default;
 
-void Server::FuseFileMapEntry::DoRead2(
-    const fusebox_staging::Read2RequestProto& request,
-    Server::Read2Callback callback) {
+void Server::FuseFileMapEntry::DoRead2(const Read2RequestProto& request,
+                                       Server::Read2Callback callback) {
   int64_t offset = request.has_offset() ? request.offset() : 0;
   int64_t length = request.has_length() ? request.length() : 0;
   seqbnd_read_writer_.AsyncCall(&Server::ReadWriter::Read)
       .WithArgs(fs_context_, offset, length, std::move(callback));
 }
 
-void Server::FuseFileMapEntry::DoWrite2(
-    const fusebox_staging::Write2RequestProto& request,
-    Server::Write2Callback callback) {
+void Server::FuseFileMapEntry::DoWrite2(const Write2RequestProto& request,
+                                        Server::Write2Callback callback) {
   if (!request.has_data() || request.data().empty()) {
-    fusebox_staging::Write2ResponseProto response_proto;
+    Write2ResponseProto response_proto;
     std::move(callback).Run(response_proto);
     return;
   }
@@ -775,7 +773,7 @@
   return base::Value(std::move(dict));
 }
 
-void Server::Close2(const fusebox_staging::Close2RequestProto& request_proto,
+void Server::Close2(const Close2RequestProto& request_proto,
                     Close2Callback callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
@@ -783,7 +781,7 @@
       request_proto.has_fuse_handle() ? request_proto.fuse_handle() : 0;
   auto iter = fuse_file_map_.find(fuse_handle);
   if (iter == fuse_file_map_.end()) {
-    fusebox_staging::Close2ResponseProto response_proto;
+    Close2ResponseProto response_proto;
     response_proto.set_posix_error_code(ENOENT);
     std::move(callback).Run(response_proto);
     return;
@@ -796,18 +794,18 @@
 
   fuse_file_map_.erase(iter);
 
-  fusebox_staging::Close2ResponseProto response_proto;
+  Close2ResponseProto response_proto;
   std::move(callback).Run(response_proto);
 
   if (!pending_reads.empty()) {
-    fusebox_staging::Read2ResponseProto read2_response_proto;
+    Read2ResponseProto read2_response_proto;
     read2_response_proto.set_posix_error_code(EBUSY);
     for (auto& pending_read : pending_reads) {
       std::move(pending_read.second).Run(read2_response_proto);
     }
   }
   if (!pending_writes.empty()) {
-    fusebox_staging::Write2ResponseProto write2_esponse_proto;
+    Write2ResponseProto write2_esponse_proto;
     write2_esponse_proto.set_posix_error_code(EBUSY);
     for (auto& pending_read : pending_writes) {
       std::move(pending_read.second).Run(write2_esponse_proto);
@@ -815,7 +813,7 @@
   }
 }
 
-void Server::Create(const fusebox_staging::CreateRequestProto& request_proto,
+void Server::Create(const CreateRequestProto& request_proto,
                     CreateCallback callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
@@ -825,12 +823,12 @@
 
   auto common = ParseFileSystemURL(moniker_map_, prefix_map_, fs_url_as_string);
   if (common.error_code != base::File::Error::FILE_OK) {
-    fusebox_staging::CreateResponseProto response_proto;
+    CreateResponseProto response_proto;
     response_proto.set_posix_error_code(FileErrorToErrno(common.error_code));
     std::move(callback).Run(response_proto);
     return;
   } else if (common.read_only) {
-    fusebox_staging::CreateResponseProto response_proto;
+    CreateResponseProto response_proto;
     response_proto.set_posix_error_code(EACCES);
     std::move(callback).Run(response_proto);
     return;
@@ -861,7 +859,7 @@
           common.fs_url, exclusive, std::move(outer_callback)));
 }
 
-void Server::MkDir(const fusebox_staging::MkDirRequestProto& request_proto,
+void Server::MkDir(const MkDirRequestProto& request_proto,
                    MkDirCallback callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
@@ -871,12 +869,12 @@
 
   auto common = ParseFileSystemURL(moniker_map_, prefix_map_, fs_url_as_string);
   if (common.error_code != base::File::Error::FILE_OK) {
-    fusebox_staging::MkDirResponseProto response_proto;
+    MkDirResponseProto response_proto;
     response_proto.set_posix_error_code(FileErrorToErrno(common.error_code));
     std::move(callback).Run(response_proto);
     return;
   } else if (common.read_only) {
-    fusebox_staging::MkDirResponseProto response_proto;
+    MkDirResponseProto response_proto;
     response_proto.set_posix_error_code(EACCES);
     std::move(callback).Run(response_proto);
     return;
@@ -899,41 +897,41 @@
                      std::move(outer_callback)));
 }
 
-void Server::Open2(const fusebox_staging::Open2RequestProto& request_proto,
+void Server::Open2(const Open2RequestProto& request_proto,
                    Open2Callback callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
   std::string fs_url_as_string = request_proto.has_file_system_url()
                                      ? request_proto.file_system_url()
                                      : std::string();
-  fusebox_staging::AccessMode access_mode =
-      request_proto.has_access_mode() ? request_proto.access_mode()
-                                      : fusebox_staging::AccessMode::NO_ACCESS;
+  AccessMode access_mode = request_proto.has_access_mode()
+                               ? request_proto.access_mode()
+                               : AccessMode::NO_ACCESS;
 
   auto common = ParseFileSystemURL(moniker_map_, prefix_map_, fs_url_as_string);
   if (common.error_code != base::File::Error::FILE_OK) {
-    fusebox_staging::Open2ResponseProto response_proto;
+    Open2ResponseProto response_proto;
     response_proto.set_posix_error_code(FileErrorToErrno(common.error_code));
     std::move(callback).Run(response_proto);
     return;
   }
 
-  bool readable = (access_mode == fusebox_staging::AccessMode::READ_ONLY) ||
-                  (access_mode == fusebox_staging::AccessMode::READ_WRITE);
-  bool writable = !common.read_only &&
-                  ((access_mode == fusebox_staging::AccessMode::WRITE_ONLY) ||
-                   (access_mode == fusebox_staging::AccessMode::READ_WRITE));
+  bool readable = (access_mode == AccessMode::READ_ONLY) ||
+                  (access_mode == AccessMode::READ_WRITE);
+  bool writable =
+      !common.read_only && ((access_mode == AccessMode::WRITE_ONLY) ||
+                            (access_mode == AccessMode::READ_WRITE));
 
   uint64_t fuse_handle = InsertFuseFileMapEntry(
       FuseFileMapEntry(std::move(common.fs_context), std::move(common.fs_url),
                        readable, writable));
 
-  fusebox_staging::Open2ResponseProto response_proto;
+  Open2ResponseProto response_proto;
   response_proto.set_fuse_handle(fuse_handle);
   std::move(callback).Run(response_proto);
 }
 
-void Server::Read2(const fusebox_staging::Read2RequestProto& request_proto,
+void Server::Read2(const Read2RequestProto& request_proto,
                    Read2Callback callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
@@ -941,12 +939,12 @@
       request_proto.has_fuse_handle() ? request_proto.fuse_handle() : 0;
   auto iter = fuse_file_map_.find(fuse_handle);
   if (iter == fuse_file_map_.end()) {
-    fusebox_staging::Read2ResponseProto response_proto;
+    Read2ResponseProto response_proto;
     response_proto.set_posix_error_code(ENOENT);
     std::move(callback).Run(response_proto);
     return;
   } else if (!iter->second.readable_) {
-    fusebox_staging::Read2ResponseProto response_proto;
+    Read2ResponseProto response_proto;
     response_proto.set_posix_error_code(EACCES);
     std::move(callback).Run(response_proto);
     return;
@@ -1024,7 +1022,7 @@
           common.fs_url, std::move(outer_callback)));
 }
 
-void Server::RmDir(const fusebox_staging::RmDirRequestProto& request_proto,
+void Server::RmDir(const RmDirRequestProto& request_proto,
                    RmDirCallback callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
@@ -1034,12 +1032,12 @@
 
   auto common = ParseFileSystemURL(moniker_map_, prefix_map_, fs_url_as_string);
   if (common.error_code != base::File::Error::FILE_OK) {
-    fusebox_staging::RmDirResponseProto response_proto;
+    RmDirResponseProto response_proto;
     response_proto.set_posix_error_code(FileErrorToErrno(common.error_code));
     std::move(callback).Run(response_proto);
     return;
   } else if (common.read_only) {
-    fusebox_staging::RmDirResponseProto response_proto;
+    RmDirResponseProto response_proto;
     response_proto.set_posix_error_code(EACCES);
     std::move(callback).Run(response_proto);
     return;
@@ -1059,7 +1057,7 @@
                      common.fs_url, std::move(outer_callback)));
 }
 
-void Server::Stat2(const fusebox_staging::Stat2RequestProto& request_proto,
+void Server::Stat2(const Stat2RequestProto& request_proto,
                    Stat2Callback callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
@@ -1071,13 +1069,13 @@
   if (common.is_moniker_root) {
     constexpr bool is_directory = true;
     constexpr bool read_only = true;
-    fusebox_staging::Stat2ResponseProto response_proto;
-    fusebox_staging::DirEntryProto* stat = response_proto.mutable_stat();
+    Stat2ResponseProto response_proto;
+    DirEntryProto* stat = response_proto.mutable_stat();
     stat->set_mode_bits(Server::MakeModeBits(is_directory, read_only));
     std::move(callback).Run(response_proto);
     return;
   } else if (common.error_code != base::File::Error::FILE_OK) {
-    fusebox_staging::Stat2ResponseProto response_proto;
+    Stat2ResponseProto response_proto;
     response_proto.set_posix_error_code(FileErrorToErrno(common.error_code));
     std::move(callback).Run(response_proto);
     return;
@@ -1102,9 +1100,8 @@
           common.fs_url, metadata_fields, std::move(outer_callback)));
 }
 
-void Server::Truncate(
-    const fusebox_staging::TruncateRequestProto& request_proto,
-    TruncateCallback callback) {
+void Server::Truncate(const TruncateRequestProto& request_proto,
+                      TruncateCallback callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
   std::string fs_url_as_string = request_proto.has_file_system_url()
@@ -1113,12 +1110,12 @@
 
   auto common = ParseFileSystemURL(moniker_map_, prefix_map_, fs_url_as_string);
   if (common.error_code != base::File::Error::FILE_OK) {
-    fusebox_staging::TruncateResponseProto response_proto;
+    TruncateResponseProto response_proto;
     response_proto.set_posix_error_code(FileErrorToErrno(common.error_code));
     std::move(callback).Run(response_proto);
     return;
   } else if (common.read_only) {
-    fusebox_staging::TruncateResponseProto response_proto;
+    TruncateResponseProto response_proto;
     response_proto.set_posix_error_code(EACCES);
     std::move(callback).Run(response_proto);
     return;
@@ -1140,7 +1137,7 @@
           std::move(outer_callback)));
 }
 
-void Server::Unlink(const fusebox_staging::UnlinkRequestProto& request_proto,
+void Server::Unlink(const UnlinkRequestProto& request_proto,
                     UnlinkCallback callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
@@ -1150,12 +1147,12 @@
 
   auto common = ParseFileSystemURL(moniker_map_, prefix_map_, fs_url_as_string);
   if (common.error_code != base::File::Error::FILE_OK) {
-    fusebox_staging::UnlinkResponseProto response_proto;
+    UnlinkResponseProto response_proto;
     response_proto.set_posix_error_code(FileErrorToErrno(common.error_code));
     std::move(callback).Run(response_proto);
     return;
   } else if (common.read_only) {
-    fusebox_staging::UnlinkResponseProto response_proto;
+    UnlinkResponseProto response_proto;
     response_proto.set_posix_error_code(EACCES);
     std::move(callback).Run(response_proto);
     return;
@@ -1175,7 +1172,7 @@
           common.fs_url, std::move(outer_callback)));
 }
 
-void Server::Write2(const fusebox_staging::Write2RequestProto& request_proto,
+void Server::Write2(const Write2RequestProto& request_proto,
                     Write2Callback callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
@@ -1183,18 +1180,18 @@
       request_proto.has_fuse_handle() ? request_proto.fuse_handle() : 0;
   auto iter = fuse_file_map_.find(fuse_handle);
   if (iter == fuse_file_map_.end()) {
-    fusebox_staging::Write2ResponseProto response_proto;
+    Write2ResponseProto response_proto;
     response_proto.set_posix_error_code(ENOENT);
     std::move(callback).Run(response_proto);
     return;
   } else if (!iter->second.writable_) {
-    fusebox_staging::Write2ResponseProto response_proto;
+    Write2ResponseProto response_proto;
     response_proto.set_posix_error_code(EACCES);
     std::move(callback).Run(response_proto);
     return;
   } else if (request_proto.has_data() &&
              (request_proto.data().size() > INT_MAX)) {
-    fusebox_staging::Write2ResponseProto response_proto;
+    Write2ResponseProto response_proto;
     response_proto.set_posix_error_code(EMSGSIZE);
     std::move(callback).Run(response_proto);
     return;
@@ -1312,15 +1309,14 @@
           std::move(scoped_temp_dir)));
 }
 
-void Server::OnRead2(
-    uint64_t fuse_handle,
-    Read2Callback callback,
-    const fusebox_staging::Read2ResponseProto& response_proto) {
+void Server::OnRead2(uint64_t fuse_handle,
+                     Read2Callback callback,
+                     const Read2ResponseProto& response_proto) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
   auto iter = fuse_file_map_.find(fuse_handle);
   if (iter == fuse_file_map_.end()) {
-    fusebox_staging::Read2ResponseProto enoent_response_proto;
+    Read2ResponseProto enoent_response_proto;
     enoent_response_proto.set_posix_error_code(ENOENT);
     std::move(callback).Run(enoent_response_proto);
     return;
@@ -1373,15 +1369,14 @@
   }
 }
 
-void Server::OnWrite2(
-    uint64_t fuse_handle,
-    Write2Callback callback,
-    const fusebox_staging::Write2ResponseProto& response_proto) {
+void Server::OnWrite2(uint64_t fuse_handle,
+                      Write2Callback callback,
+                      const Write2ResponseProto& response_proto) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
   auto iter = fuse_file_map_.find(fuse_handle);
   if (iter == fuse_file_map_.end()) {
-    fusebox_staging::Write2ResponseProto enoent_response_proto;
+    Write2ResponseProto enoent_response_proto;
     enoent_response_proto.set_posix_error_code(ENOENT);
     std::move(callback).Run(enoent_response_proto);
     return;
diff --git a/chrome/browser/ash/fusebox/fusebox_server.h b/chrome/browser/ash/fusebox/fusebox_server.h
index 7a66406..3561260bd 100644
--- a/chrome/browser/ash/fusebox/fusebox_server.h
+++ b/chrome/browser/ash/fusebox/fusebox_server.h
@@ -89,34 +89,29 @@
   // storage::FileSystemURL (in string form).
 
   // Close2 closes a virtual file opened by Open2.
-  using Close2Callback = base::OnceCallback<void(
-      const fusebox_staging::Close2ResponseProto& response)>;
-  void Close2(const fusebox_staging::Close2RequestProto& request,
-              Close2Callback callback);
+  using Close2Callback =
+      base::OnceCallback<void(const Close2ResponseProto& response)>;
+  void Close2(const Close2RequestProto& request, Close2Callback callback);
 
   // Create creates a file (not a directory).
-  using CreateCallback = base::OnceCallback<void(
-      const fusebox_staging::CreateResponseProto& response)>;
-  void Create(const fusebox_staging::CreateRequestProto& request,
-              CreateCallback callback);
+  using CreateCallback =
+      base::OnceCallback<void(const CreateResponseProto& response)>;
+  void Create(const CreateRequestProto& request, CreateCallback callback);
 
   // MkDir is analogous to "/usr/bin/mkdir".
-  using MkDirCallback = base::OnceCallback<void(
-      const fusebox_staging::MkDirResponseProto& response)>;
-  void MkDir(const fusebox_staging::MkDirRequestProto& request,
-             MkDirCallback callback);
+  using MkDirCallback =
+      base::OnceCallback<void(const MkDirResponseProto& response)>;
+  void MkDir(const MkDirRequestProto& request, MkDirCallback callback);
 
   // Open2 opens a virtual file for reading and/or writing.
-  using Open2Callback = base::OnceCallback<void(
-      const fusebox_staging::Open2ResponseProto& response)>;
-  void Open2(const fusebox_staging::Open2RequestProto& request,
-             Open2Callback callback);
+  using Open2Callback =
+      base::OnceCallback<void(const Open2ResponseProto& response)>;
+  void Open2(const Open2RequestProto& request, Open2Callback callback);
 
   // Read2 reads from a virtual file opened by Open2.
-  using Read2Callback = base::OnceCallback<void(
-      const fusebox_staging::Read2ResponseProto& response)>;
-  void Read2(const fusebox_staging::Read2RequestProto& request,
-             Read2Callback callback);
+  using Read2Callback =
+      base::OnceCallback<void(const Read2ResponseProto& response)>;
+  void Read2(const Read2RequestProto& request, Read2Callback callback);
 
   // ReadDir2 lists the directory's children. The results will be sent back in
   // the responses of one or more request-response RPC pairs. The first request
@@ -133,34 +128,29 @@
   void ReadDir2(const ReadDir2RequestProto& request, ReadDir2Callback callback);
 
   // RmDir is analogous to "/usr/bin/rmdir".
-  using RmDirCallback = base::OnceCallback<void(
-      const fusebox_staging::RmDirResponseProto& response)>;
-  void RmDir(const fusebox_staging::RmDirRequestProto& request,
-             RmDirCallback callback);
+  using RmDirCallback =
+      base::OnceCallback<void(const RmDirResponseProto& response)>;
+  void RmDir(const RmDirRequestProto& request, RmDirCallback callback);
 
   // Stat2 returns the file or directory's metadata.
-  using Stat2Callback = base::OnceCallback<void(
-      const fusebox_staging::Stat2ResponseProto& response)>;
-  void Stat2(const fusebox_staging::Stat2RequestProto& request,
-             Stat2Callback callback);
+  using Stat2Callback =
+      base::OnceCallback<void(const Stat2ResponseProto& response)>;
+  void Stat2(const Stat2RequestProto& request, Stat2Callback callback);
 
   // Truncate sets a file's size.
-  using TruncateCallback = base::OnceCallback<void(
-      const fusebox_staging::TruncateResponseProto& response)>;
-  void Truncate(const fusebox_staging::TruncateRequestProto& request,
-                TruncateCallback callback);
+  using TruncateCallback =
+      base::OnceCallback<void(const TruncateResponseProto& response)>;
+  void Truncate(const TruncateRequestProto& request, TruncateCallback callback);
 
   // Unlink deletes a file.
-  using UnlinkCallback = base::OnceCallback<void(
-      const fusebox_staging::UnlinkResponseProto& response)>;
-  void Unlink(const fusebox_staging::UnlinkRequestProto& request,
-              UnlinkCallback callback);
+  using UnlinkCallback =
+      base::OnceCallback<void(const UnlinkResponseProto& response)>;
+  void Unlink(const UnlinkRequestProto& request, UnlinkCallback callback);
 
   // Write2 writes to a virtual file opened by Open2.
-  using Write2Callback = base::OnceCallback<void(
-      const fusebox_staging::Write2ResponseProto& response)>;
-  void Write2(const fusebox_staging::Write2RequestProto& request,
-              Write2Callback callback);
+  using Write2Callback =
+      base::OnceCallback<void(const Write2ResponseProto& response)>;
+  void Write2(const Write2RequestProto& request, Write2Callback callback);
 
   // File operation D-Bus methods above. Meta D-Bus methods below, which do not
   // map 1:1 to FUSE or C standard library file operations.
@@ -206,10 +196,8 @@
 
   // ----
 
-  using PendingRead2 =
-      std::pair<fusebox_staging::Read2RequestProto, Read2Callback>;
-  using PendingWrite2 =
-      std::pair<fusebox_staging::Write2RequestProto, Write2Callback>;
+  using PendingRead2 = std::pair<Read2RequestProto, Read2Callback>;
+  using PendingWrite2 = std::pair<Write2RequestProto, Write2Callback>;
 
   // Lives entirely on the I/O thread, as enforced by base::SequenceBound.
   struct ReadWriter {
@@ -262,10 +250,8 @@
     FuseFileMapEntry(FuseFileMapEntry&&);
     ~FuseFileMapEntry();
 
-    void DoRead2(const fusebox_staging::Read2RequestProto& request,
-                 Read2Callback callback);
-    void DoWrite2(const fusebox_staging::Write2RequestProto& request,
-                  Write2Callback callback);
+    void DoRead2(const Read2RequestProto& request, Read2Callback callback);
+    void DoWrite2(const Write2RequestProto& request, Write2Callback callback);
 
     const scoped_refptr<storage::FileSystemContext> fs_context_;
     const bool readable_;
@@ -343,7 +329,7 @@
 
   void OnRead2(uint64_t fuse_handle,
                Read2Callback callback,
-               const fusebox_staging::Read2ResponseProto& response);
+               const Read2ResponseProto& response);
 
   void OnReadDirectory(scoped_refptr<storage::FileSystemContext> fs_context,
                        bool read_only,
@@ -354,7 +340,7 @@
 
   void OnWrite2(uint64_t fuse_handle,
                 Write2Callback callback,
-                const fusebox_staging::Write2ResponseProto& response);
+                const Write2ResponseProto& response);
 
   // Removes the entry (if present) for the given map key.
   void EraseFuseFileMapEntry(uint64_t fuse_handle);
diff --git a/chrome/browser/ash/fusebox/fusebox_staging.proto b/chrome/browser/ash/fusebox/fusebox_staging.proto
index fa60976d..5a5e864 100644
--- a/chrome/browser/ash/fusebox/fusebox_staging.proto
+++ b/chrome/browser/ash/fusebox/fusebox_staging.proto
@@ -19,162 +19,5 @@
 
 package fusebox_staging;
 
-enum AccessMode {
-  NO_ACCESS = 0;
-  READ_ONLY = 1;
-  WRITE_ONLY = 2;
-  READ_WRITE = 3;
-}
-
-message DirEntryProto {
-  // Entry name.
-  optional string name = 2;
-  // POSIX style (S_IFREG | rwxr-x---) bits.
-  optional uint32 mode_bits = 3;
-  // File size in bytes.
-  optional int64 size = 4;
-  // Modification time (microseconds since the Windows epoch, like base::Time).
-  optional int64 mtime = 5;
-  // Access time (microseconds since the Windows epoch, like base::Time).
-  optional int64 atime = 6;
-  // Creation time (microseconds since the Windows epoch, like base::Time).
-  optional int64 ctime = 7;
-}
-
-// Close2 closes a fuse_handle previously returned by Open2.
-
-message Close2RequestProto {
-  optional uint64 fuse_handle = 2;
-}
-
-message Close2ResponseProto {
-  optional int32 posix_error_code = 1;
-}
-
-// Create creates and opens (in the Open2 sense) a file. It is exclusive (it
-// fails if the name already exists).
-//
-// There are no mode_bits in the request proto, as there's no mode_bits arg to
-// the storage::FileSystemOperationRunner::CreateFile method.
-
-message CreateRequestProto {
-  optional string file_system_url = 3;
-}
-
-message CreateResponseProto {
-  optional int32 posix_error_code = 1;
-  optional uint64 fuse_handle = 2;
-  optional DirEntryProto stat = 3;
-}
-
-// MkDir is exclusive (it fails if the name already exists) and non-recursive
-// (it's plain "mkdir", not "mkdir -p").
-//
-// There are no mode_bits in the request proto, as there's no mode_bits arg to
-// the storage::FileSystemOperationRunner::CreateDirectory method.
-
-message MkDirRequestProto {
-  optional string file_system_url = 3;
-}
-
-message MkDirResponseProto {
-  optional int32 posix_error_code = 1;
-  optional DirEntryProto stat = 3;
-}
-
-// Open2 opens a virtual file for reading and/or writing. It returns a
-// fuse_handle, which is like a file descriptor but for the file server side,
-// not the file client side.
-//
-// The "2" suffix is because the subsequent Read2 / Write2 / Close2 calls pass
-// a number (fuse_handle) instead of a string (a name / path / URL) as used by
-// the "version 1" Read / Write / Close methods. The same file can be opened
-// multiple times concurrently, producing multiple different handles that share
-// the same file_system_url.
-//
-// The fuse_handle uint64 numbers are generated by the Fusebox server. They are
-// not guaranteed to be sequential or increasing. Zero is an invalid value. The
-// high bit (also known as the 1<<63 bit) is also always zero for valid values,
-// so that the Fusebox client (which is itself a FUSE server) can re-purpose
-// large uint64 values (e.g. for tracking FUSE requests that do not need a
-// round-trip to the Fusebox server).
-
-message Open2RequestProto {
-  optional string file_system_url = 3;
-  optional AccessMode access_mode = 4;
-}
-
-message Open2ResponseProto {
-  optional int32 posix_error_code = 1;
-  optional uint64 fuse_handle = 2;
-}
-
-// Read2 reads from a fuse_handle previously returned by Open2.
-
-message Read2RequestProto {
-  optional uint64 fuse_handle = 2;
-  optional int64 offset = 4;
-  optional int64 length = 5;
-}
-
-message Read2ResponseProto {
-  optional int32 posix_error_code = 1;
-  optional bytes data = 3;
-}
-
-// RmDir truly deletes (it does not "move to trash", an undo-able operation)
-// and it is non-recursive (it's plain "rmdir", not "rmdir -p" or "rm -r").
-
-message RmDirRequestProto {
-  optional string file_system_url = 3;
-}
-
-message RmDirResponseProto {
-  optional int32 posix_error_code = 1;
-}
-
-// Stat2 returns file state.
-
-message Stat2RequestProto {
-  optional string file_system_url = 3;
-}
-
-message Stat2ResponseProto {
-  optional int32 posix_error_code = 1;
-  optional DirEntryProto stat = 3;
-}
-
-// Truncate sets a file's size.
-
-message TruncateRequestProto {
-  optional string file_system_url = 3;
-  optional int64 length = 5;
-}
-
-message TruncateResponseProto {
-  optional int32 posix_error_code = 1;
-  optional DirEntryProto stat = 3;
-}
-
-// Unlink deletes a file, like "rm". It truly deletes (it does not "move to
-// trash", an undo-able operation).
-
-message UnlinkRequestProto {
-  optional string file_system_url = 3;
-}
-
-message UnlinkResponseProto {
-  optional int32 posix_error_code = 1;
-}
-
-// Write2 writes to a fuse_handle previously returned by Open2.
-
-message Write2RequestProto {
-  optional uint64 fuse_handle = 2;
-  optional int64 offset = 4;
-  optional bytes data = 5;
-}
-
-message Write2ResponseProto {
-  optional int32 posix_error_code = 1;
-}
+// There are no fusebox_staging definitions at this time. They're all in the
+// canonical fusebox.proto file.
diff --git a/chrome/browser/ash/login/active_directory_login_browsertest.cc b/chrome/browser/ash/login/active_directory_login_browsertest.cc
index 6b8a39d..767a137 100644
--- a/chrome/browser/ash/login/active_directory_login_browsertest.cc
+++ b/chrome/browser/ash/login/active_directory_login_browsertest.cc
@@ -4,11 +4,13 @@
 
 #include <string>
 
+#include "ash/constants/ash_features.h"
 #include "base/base_paths.h"
 #include "base/environment.h"
 #include "base/path_service.h"
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/test/scoped_feature_list.h"
 #include "chrome/browser/ash/authpolicy/kerberos_files_handler.h"
 #include "chrome/browser/ash/login/test/active_directory_login_mixin.h"
 #include "chrome/browser/ash/login/test/device_state_mixin.h"
@@ -64,7 +66,13 @@
       :  // Using the same realm as supervised user domain. Should be treated
          // as normal realm.
         test_realm_(user_manager::kSupervisedUserDomain),
-        test_user_(kTestActiveDirectoryUser + ("@" + test_realm_)) {}
+        test_user_(kTestActiveDirectoryUser + ("@" + test_realm_)) {
+    // All tests related to Active Directory login don't make sense when the
+    // kChromadAvailable feature is disabled. We also don't need to verify that
+    // the device is disabled in that case, because the Chromad disabling
+    // feature is already tested in `device_disabling_manager_unittest.cc`.
+    scoped_feature_list_.InitAndEnableFeature(features::kChromadAvailable);
+  }
 
   ActiveDirectoryLoginTest(const ActiveDirectoryLoginTest&) = delete;
   ActiveDirectoryLoginTest& operator=(const ActiveDirectoryLoginTest&) = delete;
@@ -82,6 +90,9 @@
       &mixin_host_,
       DeviceStateMixin::State::OOBE_COMPLETED_ACTIVE_DIRECTORY_ENROLLED};
   ActiveDirectoryLoginMixin ad_login_{&mixin_host_};
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
 };
 
 class ActiveDirectoryLoginAutocompleteTest : public ActiveDirectoryLoginTest {
diff --git a/chrome/browser/ash/login/existing_user_controller_browsertest.cc b/chrome/browser/ash/login/existing_user_controller_browsertest.cc
index c72b777d..869565c 100644
--- a/chrome/browser/ash/login/existing_user_controller_browsertest.cc
+++ b/chrome/browser/ash/login/existing_user_controller_browsertest.cc
@@ -878,7 +878,13 @@
 class ExistingUserControllerActiveDirectoryTest
     : public ExistingUserControllerTest {
  public:
-  ExistingUserControllerActiveDirectoryTest() = default;
+  ExistingUserControllerActiveDirectoryTest() {
+    // All tests related to Active Directory login don't make sense when the
+    // kChromadAvailable feature is disabled. We also don't need to verify that
+    // the device is disabled in that case, because the Chromad disabling
+    // feature is already tested in `device_disabling_manager_unittest.cc`.
+    scoped_feature_list_.InitAndEnableFeature(features::kChromadAvailable);
+  }
 
   // Overriden from ExistingUserControllerTest:
   void SetUp() override {
@@ -1012,6 +1018,7 @@
 
  private:
   testing::NiceMock<policy::MockConfigurationPolicyProvider> policy_provider_;
+  base::test::ScopedFeatureList scoped_feature_list_;
 };
 
 class ExistingUserControllerActiveDirectoryTestCreateProfileDir
diff --git a/chrome/browser/ash/login/screens/parental_handoff_screen.cc b/chrome/browser/ash/login/screens/parental_handoff_screen.cc
index 2082829..632a04e 100644
--- a/chrome/browser/ash/login/screens/parental_handoff_screen.cc
+++ b/chrome/browser/ash/login/screens/parental_handoff_screen.cc
@@ -11,10 +11,10 @@
 #include "chrome/browser/ash/login/wizard_context.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_manager.h"
-#include "chrome/browser/supervised_user/supervised_user_features/supervised_user_features.h"
 #include "chrome/browser/ui/webui/ash/login/parental_handoff_screen_handler.h"
 #include "chrome/grit/chromium_strings.h"
 #include "chrome/grit/generated_resources.h"
+#include "components/supervised_user/core/common/features.h"
 #include "components/user_manager/user.h"
 #include "components/user_manager/user_manager.h"
 #include "ui/base/l10n/l10n_util.h"
diff --git a/chrome/browser/ash/login/screens/sync_consent_browsertest.cc b/chrome/browser/ash/login/screens/sync_consent_browsertest.cc
index 4e94c1a9..96bd2d7 100644
--- a/chrome/browser/ash/login/screens/sync_consent_browsertest.cc
+++ b/chrome/browser/ash/login/screens/sync_consent_browsertest.cc
@@ -624,6 +624,14 @@
 // not use sync.
 class SyncConsentActiveDirectoryTest : public OobeBaseTest {
  public:
+  SyncConsentActiveDirectoryTest() {
+    // All tests related to Active Directory login don't make sense when the
+    // kChromadAvailable feature is disabled. We also don't need to verify that
+    // the device is disabled in that case, because the Chromad disabling
+    // feature is already tested in `device_disabling_manager_unittest.cc`.
+    scoped_feature_list_.InitAndEnableFeature(features::kChromadAvailable);
+  }
+
   ~SyncConsentActiveDirectoryTest() override = default;
 
  protected:
@@ -632,6 +640,9 @@
       DeviceStateMixin::State::OOBE_COMPLETED_ACTIVE_DIRECTORY_ENROLLED};
   ActiveDirectoryLoginMixin ad_login_{&mixin_host_};
   base::HistogramTester histogram_tester_;
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
 };
 
 IN_PROC_BROWSER_TEST_F(SyncConsentActiveDirectoryTest, LoginDoesNotStartSync) {
diff --git a/chrome/browser/banners/test_app_banner_manager_desktop.cc b/chrome/browser/banners/test_app_banner_manager_desktop.cc
index 4124bc3..debb92b0 100644
--- a/chrome/browser/banners/test_app_banner_manager_desktop.cc
+++ b/chrome/browser/banners/test_app_banner_manager_desktop.cc
@@ -8,6 +8,7 @@
 #include <utility>
 
 #include "base/run_loop.h"
+#include "base/strings/stringprintf.h"
 #include "base/task/single_thread_task_runner.h"
 #include "components/webapps/browser/installable/installable_data.h"
 #include "content/public/browser/web_contents.h"
@@ -84,6 +85,7 @@
 
 void TestAppBannerManagerDesktop::OnDidGetManifest(
     const InstallableData& result) {
+  debug_log_.Append("OnDidGetManifest");
   AppBannerManagerDesktop::OnDidGetManifest(result);
 
   // AppBannerManagerDesktop does not call |OnDidPerformInstallableCheck| to
@@ -94,6 +96,7 @@
 }
 void TestAppBannerManagerDesktop::OnDidPerformInstallableWebAppCheck(
     const InstallableData& result) {
+  debug_log_.Append("OnDidPerformInstallableWebAppCheck");
   AppBannerManagerDesktop::OnDidPerformInstallableWebAppCheck(result);
   SetInstallable(result.NoBlockingErrors());
 }
@@ -105,11 +108,13 @@
 
 void TestAppBannerManagerDesktop::OnDidPerformWorkerCheck(
     const InstallableData& result) {
+  debug_log_.Append("OnDidPerformWorkerCheck");
   AppBannerManagerDesktop::OnDidPerformWorkerCheck(result);
   SetPromotable(result.NoBlockingErrors());
 }
 
 void TestAppBannerManagerDesktop::ResetCurrentPageData() {
+  debug_log_.Append("ResetCurrentPageData");
   AppBannerManagerDesktop::ResetCurrentPageData();
   installable_.reset();
   promotable_ = false;
@@ -139,6 +144,7 @@
 void TestAppBannerManagerDesktop::DidFinishLoad(
     content::RenderFrameHost* render_frame_host,
     const GURL& validated_url) {
+  debug_log_.Append("DidFinishLoad");
   if (ShouldIgnore(render_frame_host, validated_url)) {
     SetInstallable(false);
     return;
@@ -148,6 +154,8 @@
 }
 
 void TestAppBannerManagerDesktop::UpdateState(AppBannerManager::State state) {
+  debug_log_.Append(
+      base::StringPrintf("State updated to %d", static_cast<int>(state)));
   AppBannerManager::UpdateState(state);
 
   if (state == AppBannerManager::State::PENDING_ENGAGEMENT ||
@@ -159,13 +167,18 @@
 }
 
 void TestAppBannerManagerDesktop::SetInstallable(bool installable) {
-  DCHECK(!installable_.has_value() || installable_ == installable);
+  debug_log_.Append(base::StringPrintf("SetInstallable(%d)", installable));
+  DCHECK(!installable_.has_value() || installable_ == installable)
+      << "Cannot set installable to " << installable << ", already set to "
+      << installable_.value() << ". Debug log:\n"
+      << debug_log_.DebugString();
   installable_ = installable;
   if (installable_quit_closure_)
     std::move(installable_quit_closure_).Run();
 }
 
 void TestAppBannerManagerDesktop::SetPromotable(bool promotable) {
+  debug_log_.Append(base::StringPrintf("SetPromotable(%d)", promotable));
   DCHECK(waiting_for_worker_);
   waiting_for_worker_ = false;
   promotable_ = promotable;
diff --git a/chrome/browser/banners/test_app_banner_manager_desktop.h b/chrome/browser/banners/test_app_banner_manager_desktop.h
index b109aba..a9542661 100644
--- a/chrome/browser/banners/test_app_banner_manager_desktop.h
+++ b/chrome/browser/banners/test_app_banner_manager_desktop.h
@@ -5,6 +5,7 @@
 #ifndef CHROME_BROWSER_BANNERS_TEST_APP_BANNER_MANAGER_DESKTOP_H_
 #define CHROME_BROWSER_BANNERS_TEST_APP_BANNER_MANAGER_DESKTOP_H_
 
+#include "base/values.h"
 #include "chrome/browser/banners/app_banner_manager_desktop.h"
 
 #include "third_party/abseil-cpp/absl/types/optional.h"
@@ -77,6 +78,7 @@
   void OnFinished();
 
   absl::optional<bool> installable_;
+  base::Value::List debug_log_;
   bool waiting_for_worker_;
   bool promotable_;
   base::OnceClosure tear_down_quit_closure_;
diff --git a/chrome/browser/browser_resources.grd b/chrome/browser/browser_resources.grd
index c4de49b..7741559 100644
--- a/chrome/browser/browser_resources.grd
+++ b/chrome/browser/browser_resources.grd
@@ -62,10 +62,6 @@
         <include name="IDR_VIDEO_PLAYER_JS" file="resources\video_tutorials\video_player.js" type="BINDATA" />
       </if>
 
-      <if expr="enable_supervised_users">
-        <part file="resources/supervised_user_error_page_resources.grdp" />
-      </if>
-
       <if expr="enable_hangout_services_extension">
         <!-- Hangout Services extension, included in Google Chrome builds only. -->
         <include name="IDR_HANGOUT_SERVICES_MANIFEST" file="resources\hangout_services\manifest.json" type="BINDATA" />
diff --git a/chrome/browser/browsing_topics/browsing_topics_internals_browsertest.cc b/chrome/browser/browsing_topics/browsing_topics_internals_browsertest.cc
index 3e0ded92..7e29fdf4 100644
--- a/chrome/browser/browsing_topics/browsing_topics_internals_browsertest.cc
+++ b/chrome/browser/browsing_topics/browsing_topics_internals_browsertest.cc
@@ -394,9 +394,10 @@
 
     auto page_content_annotations_service =
         std::make_unique<optimization_guide::PageContentAnnotationsService>(
-            "en-US", optimization_guide_model_providers_.at(profile).get(),
-            history_service, nullptr, nullptr, base::FilePath(), nullptr,
-            nullptr);
+            nullptr, "en-US",
+            optimization_guide_model_providers_.at(profile).get(),
+            history_service, nullptr, nullptr, nullptr, base::FilePath(),
+            nullptr, nullptr);
 
     page_content_annotations_service->OverridePageContentAnnotatorForTesting(
         &test_page_content_annotator_);
diff --git a/chrome/browser/browsing_topics/browsing_topics_service_browsertest.cc b/chrome/browser/browsing_topics/browsing_topics_service_browsertest.cc
index eab36be9..003dd89 100644
--- a/chrome/browser/browsing_topics/browsing_topics_service_browsertest.cc
+++ b/chrome/browser/browsing_topics/browsing_topics_service_browsertest.cc
@@ -367,9 +367,10 @@
 
     auto page_content_annotations_service =
         std::make_unique<optimization_guide::PageContentAnnotationsService>(
-            "en-US", optimization_guide_model_providers_.at(profile).get(),
-            history_service, nullptr, nullptr, base::FilePath(), nullptr,
-            nullptr);
+            nullptr, "en-US",
+            optimization_guide_model_providers_.at(profile).get(),
+            history_service, nullptr, nullptr, nullptr, base::FilePath(),
+            nullptr, nullptr);
 
     page_content_annotations_service->OverridePageContentAnnotatorForTesting(
         &test_page_content_annotator_);
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index 3d4e42d..4755474c 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -176,7 +176,6 @@
     "//chrome/browser/resources/chromeos:app_icon_resources",
     "//chrome/browser/resources/chromeos/cloud_upload:resources_grit",
     "//chrome/browser/resources/settings:resources_grit",
-    "//chrome/browser/supervised_user/supervised_user_features",
     "//chrome/browser/ui/ash/system_web_apps",
     "//chrome/browser/ui/webui/ash/crostini_upgrader:mojo_bindings",
     "//chrome/browser/ui/webui/bluetooth_internals:mojo_bindings",
@@ -467,6 +466,7 @@
     "//components/spellcheck/common:spellcheck_result",
     "//components/storage_monitor",
     "//components/strings",
+    "//components/supervised_user/core/common",
     "//components/sync",
     "//components/sync_device_info",
     "//components/sync_preferences",
diff --git a/chrome/browser/devtools/chrome_devtools_manager_delegate.cc b/chrome/browser/devtools/chrome_devtools_manager_delegate.cc
index 2bdd7e7..b0d88480 100644
--- a/chrome/browser/devtools/chrome_devtools_manager_delegate.cc
+++ b/chrome/browser/devtools/chrome_devtools_manager_delegate.cc
@@ -60,16 +60,17 @@
 bool GetExtensionInfo(content::WebContents* wc,
                       std::string* name,
                       std::string* type) {
-  Profile* profile = Profile::FromBrowserContext(wc->GetBrowserContext());
-  if (!profile)
+  auto* process_manager =
+      extensions::ProcessManager::Get(wc->GetBrowserContext());
+  if (!process_manager) {
     return false;
+  }
   const extensions::Extension* extension =
-      extensions::ProcessManager::Get(profile)->GetExtensionForWebContents(wc);
+      process_manager->GetExtensionForWebContents(wc);
   if (!extension)
     return false;
   extensions::ExtensionHost* extension_host =
-      extensions::ProcessManager::Get(profile)->GetBackgroundHostForExtension(
-          extension->id());
+      process_manager->GetBackgroundHostForExtension(extension->id());
   if (extension_host && extension_host->host_contents() == wc) {
     *name = extension->name();
     *type = ChromeDevToolsManagerDelegate::kTypeBackgroundPage;
@@ -166,8 +167,11 @@
     content::RenderFrameHost* rfh) {
   Profile* profile =
       Profile::FromBrowserContext(rfh->GetProcess()->GetBrowserContext());
-  return AllowInspection(profile, extensions::ProcessManager::Get(profile)
-                                      ->GetExtensionForRenderFrameHost(rfh));
+  auto* process_manager = extensions::ProcessManager::Get(profile);
+  return AllowInspection(
+      profile, process_manager
+                   ? process_manager->GetExtensionForRenderFrameHost(rfh)
+                   : nullptr);
 }
 
 // static
@@ -176,10 +180,10 @@
     content::WebContents* web_contents) {
   const extensions::Extension* extension = nullptr;
   if (web_contents) {
-    extension =
-        extensions::ProcessManager::Get(
-            Profile::FromBrowserContext(web_contents->GetBrowserContext()))
-            ->GetExtensionForWebContents(web_contents);
+    if (auto* process_manager = extensions::ProcessManager::Get(
+            web_contents->GetBrowserContext())) {
+      extension = process_manager->GetExtensionForWebContents(web_contents);
+    }
   }
   return AllowInspection(profile, extension);
 }
diff --git a/chrome/browser/dips/dips_bounce_detector.h b/chrome/browser/dips/dips_bounce_detector.h
index 2798605..067ec05 100644
--- a/chrome/browser/dips/dips_bounce_detector.h
+++ b/chrome/browser/dips/dips_bounce_detector.h
@@ -13,6 +13,7 @@
 #include "base/memory/raw_ptr.h"
 #include "chrome/browser/dips/cookie_access_filter.h"
 #include "chrome/browser/dips/dips_redirect_info.h"
+#include "chrome/browser/dips/dips_service.h"
 #include "chrome/browser/dips/dips_utils.h"
 #include "content/public/browser/cookie_access_details.h"
 #include "content/public/browser/navigation_handle.h"
@@ -27,8 +28,6 @@
 class TickClock;
 }  // namespace base
 
-class DIPSService;
-
 // ClientBounceDetectionState is owned by the DIPSBounceDetector and stores
 // data needed to detect stateful client-side redirects.
 class ClientBounceDetectionState {
@@ -208,6 +207,10 @@
 
   void SetClockForTesting(base::Clock* clock) {
     detector_.SetClockForTesting(clock);
+    DCHECK(dips_service_);
+    dips_service_->storage()
+        ->AsyncCall(&DIPSStorage::SetClockForTesting)
+        .WithArgs(clock);
   }
 
  private:
diff --git a/chrome/browser/dips/dips_database.cc b/chrome/browser/dips/dips_database.cc
index f3840da..079167d 100644
--- a/chrome/browser/dips/dips_database.cc
+++ b/chrome/browser/dips/dips_database.cc
@@ -16,6 +16,7 @@
 #include "base/strings/strcat.h"
 #include "base/threading/thread_restrictions.h"
 #include "base/time/time.h"
+#include "chrome/browser/dips/dips_features.h"
 #include "chrome/browser/dips/dips_utils.h"
 #include "sql/database.h"
 #include "sql/error_delegate_util.h"
@@ -43,8 +44,6 @@
 
 // See comments at declaration of these variables in dips_database.h
 // for details.
-
-const base::TimeDelta DIPSDatabase::kMaxAge = base::Days(180);
 const base::TimeDelta DIPSDatabase::kMetricsInterval = base::Hours(24);
 
 DIPSDatabase::DIPSDatabase(const absl::optional<base::FilePath>& db_path)
@@ -53,6 +52,7 @@
           sql::DatabaseOptions{.exclusive_locking = true,
                                .page_size = 4096,
                                .cache_size = 32})) {
+  DCHECK(base::FeatureList::IsEnabled(dips::kFeature));
   base::AssertLongCPUWorkAllowed();
   if (db_path.has_value()) {
     DCHECK(!db_path->empty())
@@ -178,13 +178,13 @@
 
   base::UmaHistogramExactLinear("Privacy.DIPS.DatabaseInit", attempts, 3);
 
-  last_health_metrics_time_ = base::Time::Now();
+  last_health_metrics_time_ = clock_->Now();
   ComputeDatabaseMetrics();
 
   return status;
 }
 
-void DIPSDatabase::ComputeDatabaseMetrics() const {
+void DIPSDatabase::ComputeDatabaseMetrics() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   base::TimeTicks start_time = base::TimeTicks::Now();
 
@@ -200,14 +200,14 @@
                           base::TimeTicks::Now() - start_time);
 }
 
-bool DIPSDatabase::CheckDBInit() const {
+bool DIPSDatabase::CheckDBInit() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   if (!db_ || !db_->is_open())
     return false;
 
   // Computing these metrics may be costly, so we only do it every
   // |kMetricsInterval|.
-  base::Time now = base::Time::Now();
+  base::Time now = clock_->Now();
   if (now > last_health_metrics_time_ + kMetricsInterval) {
     last_health_metrics_time_ = now;
     ComputeDatabaseMetrics();
@@ -278,6 +278,15 @@
   if (!statement.Step()) {
     return absl::nullopt;
   }
+  // If the last interaction has expired, treat this entry as not in the
+  // database so that callers rewrite the entry for `site` as if it was deleted.
+  absl::optional<base::Time> last_user_interaction =
+      ToOptionalTime(statement.ColumnTime(4));
+  if (last_user_interaction.has_value() &&
+      last_user_interaction.value() + dips::kInteractionTtl.Get() <
+          clock_->Now()) {
+    return absl::nullopt;
+  }
 
   return StateValue{TimestampRange{ToOptionalTime(statement.ColumnTime(1)),
                                    ToOptionalTime(statement.ColumnTime(2))},
@@ -289,7 +298,7 @@
                                    ToOptionalTime(statement.ColumnTime(8))}};
 }
 
-std::vector<std::string> DIPSDatabase::GetAllSitesForTesting() const {
+std::vector<std::string> DIPSDatabase::GetAllSitesForTesting() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   if (!CheckDBInit())
     return {};
@@ -310,12 +319,12 @@
 
 std::vector<std::string> DIPSDatabase::GetSitesThatBounced(
     base::Time range_start,
-    base::Time last_interaction) const {
+    base::Time last_interaction) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   if (!CheckDBInit())
     return {};
+  ClearRowsWithExpiredInteractions();
 
-  DCHECK(last_interaction < range_start);
   static constexpr char kReadSql[] =  // clang-format off
       "SELECT site FROM bounces "
         "WHERE (last_stateful_bounce_time > ? "
@@ -341,12 +350,12 @@
 
 std::vector<std::string> DIPSDatabase::GetSitesThatBouncedWithState(
     base::Time range_start,
-    base::Time last_interaction) const {
+    base::Time last_interaction) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   if (!CheckDBInit())
     return {};
+  ClearRowsWithExpiredInteractions();
 
-  DCHECK(last_interaction < range_start);
   static constexpr char kReadSql[] =  // clang-format off
       "SELECT site FROM bounces "
         "WHERE last_stateful_bounce_time > ? AND "
@@ -371,12 +380,12 @@
 
 std::vector<std::string> DIPSDatabase::GetSitesThatUsedStorage(
     base::Time range_start,
-    base::Time last_interaction) const {
+    base::Time last_interaction) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   if (!CheckDBInit())
     return {};
+  ClearRowsWithExpiredInteractions();
 
-  DCHECK(last_interaction < range_start);
   static constexpr char kReadSql[] =  // clang-format off
       "SELECT site FROM bounces "
         "WHERE (last_site_storage_time > ? OR "
@@ -399,10 +408,34 @@
   return sites;
 }
 
+size_t DIPSDatabase::ClearRowsWithExpiredInteractions() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(clock_);
+  if (!CheckDBInit()) {
+    return false;
+  }
+
+  static constexpr char kClearAllExpiredSql[] =
+      "DELETE FROM bounces WHERE last_user_interaction_time < ? AND "
+      "last_user_interaction_time > 0";
+
+  DCHECK(db_->IsSQLValid(kClearAllExpiredSql));
+  sql::Statement statement(
+      db_->GetCachedStatement(SQL_FROM_HERE, kClearAllExpiredSql));
+
+  statement.BindTime(0, clock_->Now() - dips::kInteractionTtl.Get());
+  if (!statement.Run()) {
+    return 0;
+  }
+
+  return db_->GetLastChangeCount();
+}
+
 bool DIPSDatabase::RemoveRow(const std::string& site) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   if (!CheckDBInit())
     return false;
+  ClearRowsWithExpiredInteractions();
 
   static constexpr char kRemoveSql[] = "DELETE FROM bounces WHERE site=?";
   DCHECK(db_->IsSQLValid(kRemoveSql));
@@ -440,6 +473,7 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   if (!CheckDBInit())
     return false;
+  ClearRowsWithExpiredInteractions();
 
   sql::Transaction transaction(db_.get());
   if (!transaction.Begin())
@@ -482,6 +516,7 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   if (!CheckDBInit())
     return false;
+  ClearRowsWithExpiredInteractions();
 
   if (type == DIPSEventRemovalType::kAll) {
     static constexpr char kAllTypesSql[] =  // clang-format off
@@ -624,6 +659,7 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   if (!CheckDBInit())
     return false;
+  ClearRowsWithExpiredInteractions();
 
   if ((type & DIPSEventRemovalType::kHistory) ==
       DIPSEventRemovalType::kHistory) {
@@ -700,6 +736,7 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   if (!CheckDBInit())
     return false;
+  ClearRowsWithExpiredInteractions();
 
   if ((type & DIPSEventRemovalType::kHistory) ==
       DIPSEventRemovalType::kHistory) {
@@ -824,10 +861,11 @@
   return s_clean.Run();
 }
 
-size_t DIPSDatabase::GetEntryCount() const {
+size_t DIPSDatabase::GetEntryCount() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   if (!CheckDBInit())
     return 0;
+  ClearRowsWithExpiredInteractions();
 
   sql::Statement s_entry_count(
       db_->GetCachedStatement(SQL_FROM_HERE, "SELECT COUNT(*) FROM bounces"));
@@ -847,7 +885,6 @@
     return 0;
 
   DCHECK_GT(purge_goal, 0);
-  num_deleted += GarbageCollectExpired();
 
   // If expiration did not purge enough entries, remove entries with the oldest
   // |MAX(last_user_interaction_time,last_site_storage_time)| values until the
@@ -859,29 +896,6 @@
   return num_deleted;
 }
 
-size_t DIPSDatabase::GarbageCollectExpired() {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  if (!CheckDBInit())
-    return 0;
-
-  base::Time safe_date(base::Time::Now() - kMaxAge);
-
-  static constexpr char kExpireByInteractionSql[] =  // clang-format off
-        "DELETE FROM bounces WHERE last_user_interaction_time<? AND "
-                                  "last_user_interaction_time>0";
-  // clang-format on
-  DCHECK(db_->IsSQLValid(kExpireByInteractionSql));
-
-  sql::Statement s_expire_by_interaction(
-      db_->GetCachedStatement(SQL_FROM_HERE, kExpireByInteractionSql));
-  s_expire_by_interaction.BindTime(0, safe_date);
-
-  if (!s_expire_by_interaction.Run())
-    return 0;
-
-  return db_->GetLastChangeCount();
-}
-
 size_t DIPSDatabase::GarbageCollectOldest(int purge_goal) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   if (!CheckDBInit())
diff --git a/chrome/browser/dips/dips_database.h b/chrome/browser/dips/dips_database.h
index 2904f0a..e49590c 100644
--- a/chrome/browser/dips/dips_database.h
+++ b/chrome/browser/dips/dips_database.h
@@ -10,6 +10,8 @@
 #include <string>
 #include <vector>
 
+#include "base/time/clock.h"
+#include "base/time/default_clock.h"
 #include "chrome/browser/dips/dips_utils.h"
 #include "sql/database.h"
 #include "sql/init_status.h"
@@ -21,13 +23,6 @@
 // Encapsulates an SQL database that holds DIPS info.
 class DIPSDatabase {
  public:
-  // The length of time since last user interaction or site storage that a
-  // site's entry will not be subject to garbage collection due to expiration.
-  // However, even with interaction or storage within this period, if there are
-  // more than |max_entries_| entries, an entry can still be deleted by
-  // |GarbageCollectOldest()|.
-  static const base::TimeDelta kMaxAge;
-
   // The length of time that will be waited between emitting db health metrics.
   static const base::TimeDelta kMetricsInterval;
 
@@ -52,31 +47,38 @@
 
   absl::optional<StateValue> Read(const std::string& site);
 
-  std::vector<std::string> GetAllSitesForTesting() const;
+  // Note: this doesn't clear expired interactions from the database unlike the
+  // other database querying methods.
+  std::vector<std::string> GetAllSitesForTesting();
 
   // Returns all sites that did a bounce after |range_start| with their last
   // interaction happening before |last_interaction|.
-  //
-  // Note: |last_interaction| must be earlier than |range_start|.
-  std::vector<std::string> GetSitesThatBounced(
-      base::Time range_start,
-      base::Time last_interaction) const;
+  std::vector<std::string> GetSitesThatBounced(base::Time range_start,
+                                               base::Time last_interaction);
 
   // Returns all sites that did a stateful bounce after |range_start| with their
   // last interaction happening before |last_interaction|.
-  //
-  // Note: |last_interaction| must be earlier than |range_start|.
   std::vector<std::string> GetSitesThatBouncedWithState(
       base::Time range_start,
-      base::Time last_interaction) const;
+      base::Time last_interaction);
 
   // Returns all sites that wrote to storage after |range_start| with their last
   // interaction happening before |last_interaction|.
+  std::vector<std::string> GetSitesThatUsedStorage(base::Time range_start,
+                                                   base::Time last_interaction);
+
+  // Deletes all rows in the database whose interactions have expired out.
   //
-  // Note: |last_interaction| must be earlier than |range_start|.
-  std::vector<std::string> GetSitesThatUsedStorage(
-      base::Time range_start,
-      base::Time last_interaction) const;
+  // When an interaction happens before a DIPS-triggering action or during the
+  // following grace-period it protects that site from its data being cleared
+  // by DIPS. Further interactions will prolong that protection until the last
+  // one reaches the `interaction_ttl`.
+  //
+  // Clearing expired interactions effectively restarts the DIPS procedure for
+  // determining if a site is a tracker for sites that are cleared.
+  //
+  // Returns the number of rows that are removed.
+  size_t ClearRowsWithExpiredInteractions();
 
   // Delete the row from the bounces table for `site`. Returns true if query
   // executes successfully.
@@ -95,17 +97,13 @@
                           const DIPSEventRemovalType type);
 
   // Returns the number of entries present in the database.
-  size_t GetEntryCount() const;
+  size_t GetEntryCount();
 
   // If the number of entries in the database is greater than
   // |GetMaxEntries()|, garbage collect. Returns the number of entries deleted
   // (useful for debugging).
   size_t GarbageCollect();
 
-  // Removes entries for sites without user interaction within |kMaxAge|.
-  // Returns the number of entries deleted.
-  size_t GarbageCollectExpired();
-
   // Removes the |purge_goal| entries with the oldest
   // |MAX(last_user_interaction_time,last_site_storage_time)| value. Returns the
   // number of entries deleted.
@@ -117,13 +115,14 @@
   }
 
   // Checks that the internal SQLite database is initialized.
-  bool CheckDBInit() const;
+  bool CheckDBInit();
 
   size_t GetMaxEntries() const { return max_entries_; }
   size_t GetPurgeEntries() const { return purge_entries_; }
 
   void SetMaxEntriesForTesting(size_t entries) { max_entries_ = entries; }
   void SetPurgeEntriesForTesting(size_t entries) { purge_entries_ = entries; }
+  void SetClockForTesting(base::Clock* clock) { clock_ = clock; }
 
  protected:
   // Initialization functions --------------------------------------------------
@@ -148,7 +147,7 @@
                              const DIPSEventRemovalType type);
   bool RemoveEmptyRows();
 
-  void ComputeDatabaseMetrics() const;
+  void ComputeDatabaseMetrics();
 
  private:
   // Callback for database errors.
@@ -160,6 +159,7 @@
   size_t purge_entries_ = 300;
   const base::FilePath db_path_ GUARDED_BY_CONTEXT(sequence_checker_);
   std::unique_ptr<sql::Database> db_ GUARDED_BY_CONTEXT(sequence_checker_);
+  raw_ptr<base::Clock> clock_ = base::DefaultClock::GetInstance();
   mutable base::Time last_health_metrics_time_
       GUARDED_BY_CONTEXT(sequence_checker_) = base::Time::Min();
   SEQUENCE_CHECKER(sequence_checker_);
diff --git a/chrome/browser/dips/dips_database_unittest.cc b/chrome/browser/dips/dips_database_unittest.cc
index 1fccc5a..39bd8e2b 100644
--- a/chrome/browser/dips/dips_database_unittest.cc
+++ b/chrome/browser/dips/dips_database_unittest.cc
@@ -5,14 +5,17 @@
 #include <optional>
 #include <string>
 
+#include "base/strings/stringprintf.h"
 #include "base/time/time.h"
 #include "chrome/browser/dips/dips_database.h"
 
 #include "base/files/file_path.h"
 #include "base/files/scoped_temp_dir.h"
 #include "base/strings/strcat.h"
-#include "base/test/gtest_util.h"
 #include "base/test/metrics/histogram_tester.h"
+#include "base/test/scoped_feature_list.h"
+#include "base/test/simple_test_clock.h"
+#include "chrome/browser/dips/dips_features.h"
 #include "chrome/browser/dips/dips_utils.h"
 #include "sql/sqlite_result_code_values.h"
 #include "sql/test/scoped_error_expecter.h"
@@ -45,9 +48,11 @@
   explicit DIPSDatabaseTest(bool in_memory) : in_memory_(in_memory) {}
 
  protected:
+  base::SimpleTestClock clock_;
   std::unique_ptr<TestDatabase> db_;
   base::ScopedTempDir temp_dir_;
   base::FilePath db_path_;
+  base::test::ScopedFeatureList features_;
   // Test setup.
   void SetUp() override {
     if (in_memory_) {
@@ -59,6 +64,7 @@
     }
 
     ASSERT_TRUE(db_->CheckDBInit());
+    db_->SetClockForTesting(clock());
   }
 
   void TearDown() override {
@@ -69,6 +75,16 @@
       ASSERT_TRUE(temp_dir_.Delete());
   }
 
+  base::Time Now() { return clock_.Now(); }
+
+  void AdvanceTimeTo(base::Time now) {
+    ASSERT_GE(now, clock_.Now());
+    clock_.SetNow(now);
+  }
+  void AdvanceTimeBy(base::TimeDelta delta) { clock_.Advance(delta); }
+
+  base::Clock* clock() { return &clock_; }
+
  private:
   bool in_memory_;
 };
@@ -84,6 +100,13 @@
       : DIPSDatabaseTest(std::get<0>(GetParam())),
         column_(std::get<1>(GetParam())) {}
 
+  void SetUp() override {
+    DIPSDatabaseTest::SetUp();
+    // Use inf ttl to prevent interactions from expiring unintentionally.
+    features_.InitAndEnableFeatureWithParameters(dips::kFeature,
+                                                 {{"interaction_ttl", "inf"}});
+  }
+
  protected:
   // Uses `times` to write  to the first and last columns for `column_` in the
   // `site` row in `db`. This also writes the empty time stamps to all other
@@ -203,6 +226,99 @@
                                          ColumnType::kStatefulBounce,
                                          ColumnType::kStatelessBounce)));
 
+// A test class that verifies the behavior DIPSDatabase with respect to
+// interactions.
+//
+// Parameterized over whether the db is in memory.
+class DIPSDatabaseInteractionTest : public DIPSDatabaseTest,
+                                    public testing::WithParamInterface<bool> {
+ public:
+  DIPSDatabaseInteractionTest() : DIPSDatabaseTest(GetParam()) {}
+
+  void SetUp() override {
+    DIPSDatabaseTest::SetUp();
+    DCHECK(db_);
+    db_->Write("storage-only.test", storage_times,
+               {interaction_for_storage, interaction_for_storage},
+               /*stateful_bounce_times=*/{}, /*stateless_bounce_times=*/{});
+    db_->Write(
+        "stateful-bounce.test", stateful_bounce_times,
+        {interaction_for_stateful_bounce, interaction_for_stateful_bounce},
+        stateful_bounce_times,
+        /*stateless_bounce_times=*/{});
+    db_->Write(
+        "stateless-bounce.test",
+        /*storage_times=*/{},
+        {interaction_for_stateless_bounce, interaction_for_stateless_bounce},
+        /*stateful_bounce_times=*/{}, stateless_bounce_times);
+  }
+
+ protected:
+  // Used to simulate just before/after another timestamp.
+  base::TimeDelta tiny_delta = base::Milliseconds(1);
+
+  base::Time storage = Time::FromDoubleT(1);
+  base::Time interaction_for_storage = Time::FromDoubleT(2);
+
+  base::Time stateful_bounce = Time::FromDoubleT(3);
+  base::Time interaction_for_stateful_bounce = Time::FromDoubleT(5);
+
+  base::Time stateless_bounce = Time::FromDoubleT(6);
+  base::Time interaction_for_stateless_bounce = Time::FromDoubleT(9);
+
+  TimestampRange storage_times = {storage, storage};
+  TimestampRange stateful_bounce_times = {stateful_bounce, stateful_bounce};
+  TimestampRange stateless_bounce_times = {stateless_bounce, stateless_bounce};
+};
+
+TEST_P(DIPSDatabaseInteractionTest, ClearExpiredInteractions) {
+  base::test::ScopedFeatureList features;
+  features.InitAndEnableFeatureWithParameters(dips::kFeature,
+                                              {{"interaction_ttl", "3s"}});
+
+  base::TimeDelta interaction_ttl = dips::kInteractionTtl.Get();
+
+  ASSERT_EQ(interaction_ttl, base::Seconds(3));
+
+  EXPECT_THAT(
+      db_->GetAllSitesForTesting(),
+      testing::ElementsAre("stateful-bounce.test", "stateless-bounce.test",
+                           "storage-only.test"));
+  AdvanceTimeTo(interaction_for_storage + interaction_ttl + tiny_delta);
+  EXPECT_EQ(db_->ClearRowsWithExpiredInteractions(), 1u);
+  EXPECT_THAT(
+      db_->GetAllSitesForTesting(),
+      testing::ElementsAre("stateful-bounce.test", "stateless-bounce.test"));
+
+  AdvanceTimeTo(interaction_for_stateful_bounce + interaction_ttl + tiny_delta);
+  EXPECT_EQ(db_->ClearRowsWithExpiredInteractions(), 1u);
+  EXPECT_THAT(db_->GetAllSitesForTesting(),
+              testing::ElementsAre("stateless-bounce.test"));
+
+  AdvanceTimeTo(interaction_for_stateless_bounce + interaction_ttl +
+                tiny_delta);
+  EXPECT_EQ(db_->ClearRowsWithExpiredInteractions(), 1u);
+  EXPECT_THAT(db_->GetAllSitesForTesting(), testing::IsEmpty());
+}
+
+TEST_P(DIPSDatabaseInteractionTest, ReadWithExpiredInteractions) {
+  base::test::ScopedFeatureList features;
+  features.InitAndEnableFeatureWithParameters(dips::kFeature,
+                                              {{"interaction_ttl", "10s"}});
+
+  EXPECT_TRUE(db_->Read("storage-only.test").has_value());
+  EXPECT_TRUE(db_->Read("stateful-bounce.test").has_value());
+  EXPECT_TRUE(db_->Read("stateless-bounce.test").has_value());
+
+  // Time travel to a point by which all interactions should've expired.
+  AdvanceTimeTo(Time::FromDoubleT(100));
+  EXPECT_EQ(db_->Read("storage-only.test"), absl::nullopt);
+  EXPECT_EQ(db_->Read("stateful-bounce.test"), absl::nullopt);
+  EXPECT_EQ(db_->Read("stateless-bounce.test"), absl::nullopt);
+}
+
+INSTANTIATE_TEST_SUITE_P(All, DIPSDatabaseInteractionTest, ::testing::Bool());
+
 // A test class that verifies the behavior of the methods used to query the
 // DIPSDatabase for information more efficiently than using DIPSDatabase::Read.
 //
@@ -214,7 +330,9 @@
 
   void SetUp() override {
     DIPSDatabaseTest::SetUp();
-
+    // Use inf ttl to prevent interactions from expiring unintentionally.
+    features_.InitAndEnableFeatureWithParameters(dips::kFeature,
+                                                 {{"interaction_ttl", "inf"}});
     DCHECK(db_);
     // Add entries to the database filling in the columns we want to test.
     // These test entries correspond with the following cases:
@@ -222,12 +340,12 @@
     // - a site redirects the user while accessing storage
     // - a site redirects the user (without regard to storage access)
     //  All of these entries include a user interaction.
-    db_->Write("https://storage-only.test", storage_times, interaction_times,
+    db_->Write("storage-only.test", storage_times, interaction_times,
                /*stateful_bounce_times=*/{}, /*stateless_bounce_times=*/{});
-    db_->Write("https://stateful-bounce.test", storage_times, interaction_times,
+    db_->Write("stateful-bounce.test", storage_times, interaction_times,
                stateful_bounce_times,
                /*stateless_bounce_times=*/{});
-    db_->Write("https://stateless-bounce.test",
+    db_->Write("stateless-bounce.test",
                /*storage_times=*/{}, interaction_times,
                /*stateful_bounce_times=*/{}, stateless_bounce_times);
   }
@@ -235,13 +353,13 @@
  protected:
   // Rewrites the entries that were wrote in SetUp() to not have interactions.
   void ClearAllInteractions() {
-    db_->Write("https://storage-only.test", storage_times,
+    db_->Write("storage-only.test", storage_times,
                /*interaction_times=*/{},
                /*stateful_bounce_times=*/{}, /*stateless_bounce_times=*/{});
-    db_->Write("https://stateful-bounce.test", storage_times,
+    db_->Write("stateful-bounce.test", storage_times,
                /*interaction_times=*/{}, stateful_bounce_times,
                /*stateless_bounce_times=*/{});
-    db_->Write("https://stateless-bounce.test",
+    db_->Write("stateless-bounce.test",
                /*storage_times=*/{}, /*interaction_times=*/{},
                /*stateful_bounce_times=*/{}, stateless_bounce_times);
   }
@@ -278,28 +396,6 @@
               testing::IsEmpty());
 }
 
-TEST_P(DIPSDatabaseQueryTest, EnsureLastInteractionStrictlyBeforeRangeStart) {
-  // Verify that the |last_interaction| shouldn't be greater than |range_start|
-  // for each query method.
-  base::Time range_start = Time::FromDoubleT(0);
-  base::Time last_interaction = Time::FromDoubleT(1);
-
-  EXPECT_DCHECK_DEATH(db_->GetSitesThatBounced(range_start, last_interaction));
-  EXPECT_DCHECK_DEATH(
-      db_->GetSitesThatBouncedWithState(range_start, last_interaction));
-  EXPECT_DCHECK_DEATH(
-      db_->GetSitesThatUsedStorage(range_start, last_interaction));
-
-  // Verify that the |last_interaction| should be strictly less than
-  // |range_start| for each query method.
-  EXPECT_DCHECK_DEATH(
-      db_->GetSitesThatBounced(range_start, /*last_interaction=*/range_start));
-  EXPECT_DCHECK_DEATH(db_->GetSitesThatBouncedWithState(
-      range_start, /*last_interaction=*/range_start));
-  EXPECT_DCHECK_DEATH(db_->GetSitesThatUsedStorage(
-      range_start, /*last_interaction=*/range_start));
-}
-
 TEST_P(DIPSDatabaseQueryTest, GetSitesThatBounced_InteractionTest) {
   // All queries should be for the entire range (range_start > before_storage)
   // so that we can test the behavior of this method for different values of
@@ -318,20 +414,20 @@
 
   // When the last_interaction bound is `after_interaction`, both sites that
   // bounced are returned since they had user interaction before it.
-  EXPECT_THAT(db_->GetSitesThatBounced(earliest_range_start, after_interaction),
-              testing::ElementsAre("https://stateful-bounce.test",
-                                   "https://stateless-bounce.test"));
+  EXPECT_THAT(
+      db_->GetSitesThatBounced(earliest_range_start, after_interaction),
+      testing::ElementsAre("stateful-bounce.test", "stateless-bounce.test"));
 }
 
 TEST_P(DIPSDatabaseQueryTest, GetSitesThatBounced_RangeStartTest) {
-  EXPECT_THAT(db_->GetSitesThatBounced(before_storage, after_interaction),
-              testing::ElementsAre("https://stateful-bounce.test",
-                                   "https://stateless-bounce.test"));
+  EXPECT_THAT(
+      db_->GetSitesThatBounced(before_storage, after_interaction),
+      testing::ElementsAre("stateful-bounce.test", "stateless-bounce.test"));
   // When the range begins after the stateful bounce happened,
   // "stateless-bounce.test" is returned since it bounces later.
   EXPECT_THAT(
       db_->GetSitesThatBounced(after_stateful_bounce, after_interaction),
-      testing::ElementsAre("https://stateless-bounce.test"));
+      testing::ElementsAre("stateless-bounce.test"));
   // When the range begins after the stateless bounce happened, neither are
   // returned since both sites bounced before this.
   EXPECT_THAT(
@@ -360,13 +456,13 @@
   // did a stateful bounce is returned since it had user interaction before it.
   EXPECT_THAT(db_->GetSitesThatBouncedWithState(earliest_range_start,
                                                 after_interaction),
-              testing::ElementsAre("https://stateful-bounce.test"));
+              testing::ElementsAre("stateful-bounce.test"));
 }
 
 TEST_P(DIPSDatabaseQueryTest, GetSitesThatBouncedWithState_RangeStartTest) {
   EXPECT_THAT(
       db_->GetSitesThatBouncedWithState(before_storage, after_interaction),
-      testing::ElementsAre("https://stateful-bounce.test"));
+      testing::ElementsAre("stateful-bounce.test"));
   // When the range begins after the stateful bounce happened, no other sites
   // are returned (since no other site did a stateful bounce after this time).
   EXPECT_THAT(db_->GetSitesThatBouncedWithState(after_stateful_bounce,
@@ -394,20 +490,19 @@
   // used storage are returned since they had user interaction before it.
   EXPECT_THAT(
       db_->GetSitesThatUsedStorage(earliest_range_start, after_interaction),
-      testing::ElementsAre("https://stateful-bounce.test",
-                           "https://storage-only.test"));
+      testing::ElementsAre("stateful-bounce.test", "storage-only.test"));
 }
 
 TEST_P(DIPSDatabaseQueryTest, GetSitesThatUsedStorage_RangeStartTest) {
   // When the range begins at 0, both sites that used storage are returned since
   // they did so after t=0.
-  EXPECT_THAT(db_->GetSitesThatUsedStorage(before_storage, after_interaction),
-              testing::ElementsAre("https://stateful-bounce.test",
-                                   "https://storage-only.test"));
+  EXPECT_THAT(
+      db_->GetSitesThatUsedStorage(before_storage, after_interaction),
+      testing::ElementsAre("stateful-bounce.test", "storage-only.test"));
   // When the range begins after "storage-only.test" used storage, only
   // "stateful_bounce.test" is returned since it uses storage later.
   EXPECT_THAT(db_->GetSitesThatUsedStorage(after_storage, after_interaction),
-              testing::ElementsAre("https://stateful-bounce.test"));
+              testing::ElementsAre("stateful-bounce.test"));
   // When the range begins after the stateful bounce happened, no other sites
   // are returned (since no other site used storage after this time).
   EXPECT_THAT(
@@ -426,14 +521,19 @@
 
   void SetUp() override {
     DIPSDatabaseTest::SetUp();
+    features_.InitAndEnableFeatureWithParameters(
+        dips::kFeature,
+        {{"interaction_ttl",
+          base::StringPrintf("%dh", base::Days(180).InHours())}});
+    ASSERT_EQ(dips::kInteractionTtl.Get(), base::Days(180));
 
     DCHECK(db_);
-
     db_->SetMaxEntriesForTesting(200);
     db_->SetPurgeEntriesForTesting(20);
+    clock_.Advance(dips::kInteractionTtl.Get());
 
-    recent_interaction = Time::Now();
-    old_interaction = Time::Now() - DIPSDatabase::kMaxAge - base::Days(1);
+    recent_interaction = Now();
+    old_interaction = Now() - dips::kInteractionTtl.Get() - base::Days(1);
 
     recent_interaction_times = {recent_interaction, recent_interaction};
     old_interaction_times = {old_interaction, old_interaction};
@@ -443,20 +543,21 @@
     DCHECK(db_);
 
     for (int i = 0; i < num_recent_entries; i++) {
-      db_->Write(base::StrCat({"https://recent_interaction.test",
-                               base::NumberToString(i)}),
-                 storage_times, recent_interaction_times, stateful_bounce_times,
-                 stateless_bounce_times);
+      db_->Write(
+          base::StrCat({"recent_interaction.test", base::NumberToString(i)}),
+          storage_times, recent_interaction_times, stateful_bounce_times,
+          stateless_bounce_times);
     }
 
     for (int i = 0; i < num_old_entries; i++) {
-      db_->Write(base::StrCat(
-                     {"https://old_interaction.test", base::NumberToString(i)}),
-                 storage_times, old_interaction_times, stateful_bounce_times,
-                 stateless_bounce_times);
+      db_->Write(
+          base::StrCat({"old_interaction.test", base::NumberToString(i)}),
+          storage_times, old_interaction_times, stateful_bounce_times,
+          stateless_bounce_times);
     }
   }
 
+ protected:
   base::Time recent_interaction;
   base::Time old_interaction;
   base::Time storage = Time::FromDoubleT(2);
@@ -508,40 +609,28 @@
   EXPECT_EQ(db_->GetEntryCount(), db_->GetMaxEntries());
 }
 
-// More than |max_entries_| entries with recent user interaction and a few with
-// expired user interaction; only entries with expired user interaction should
-// be garbage collected by pure expiration.
-TEST_P(DIPSDatabaseGarbageCollectionTest, ExpirationPreservesRecent) {
-  BloatBouncesForGC(/*num_recent_entries=*/db_->GetMaxEntries() * 2,
-                    /*num_old_entries=*/db_->GetMaxEntries() / 2);
-
-  EXPECT_EQ(db_->GarbageCollectExpired(), db_->GetMaxEntries() / 2);
-
-  EXPECT_EQ(db_->GetEntryCount(), db_->GetMaxEntries() * 2);
-}
-
 // The entries with the oldest interaction and storage times should be deleted
 // first.
 TEST_P(DIPSDatabaseGarbageCollectionTest, OldestEntriesRemoved) {
-  db_->Write("https://old_interaction.test", {},
+  db_->Write("old_interaction.test", {},
              /*interaction_times=*/{Time::FromDoubleT(1), Time::FromDoubleT(1)},
              {}, {});
-  db_->Write("https://old_storage_old_interaction.test",
+  db_->Write("old_storage_old_interaction.test",
              /*storage_times=*/{Time::FromDoubleT(1), Time::FromDoubleT(1)},
              /*interaction_times=*/{Time::FromDoubleT(2), Time::FromDoubleT(2)},
              {}, {});
-  db_->Write("https://old_storage.test",
+  db_->Write("old_storage.test",
              /*storage_times=*/{Time::FromDoubleT(3), Time::FromDoubleT(3)}, {},
              {}, {});
-  db_->Write("https://old_storage_new_interaction.test",
+  db_->Write("old_storage_new_interaction.test",
              /*storage_times=*/{Time::FromDoubleT(1), Time::FromDoubleT(1)},
              /*interaction_times=*/{Time::FromDoubleT(4), Time::FromDoubleT(4)},
              {}, {});
-  db_->Write("https://new_storage_old_interaction.test",
+  db_->Write("new_storage_old_interaction.test",
              /*storage_times=*/{Time::FromDoubleT(5), Time::FromDoubleT(5)},
              /*interaction_times=*/{Time::FromDoubleT(2), Time::FromDoubleT(2)},
              {}, {});
-  db_->Write("https://new_storage_new_interaction.test",
+  db_->Write("new_storage_new_interaction.test",
              /*storage_times=*/{Time::FromDoubleT(6), Time::FromDoubleT(6)},
              /*interaction_times=*/{Time::FromDoubleT(7), Time::FromDoubleT(7)},
              {}, {});
@@ -550,9 +639,9 @@
   EXPECT_EQ(db_->GetEntryCount(), static_cast<size_t>(3));
 
   EXPECT_THAT(db_->GetAllSitesForTesting(),
-              testing::ElementsAre("https://new_storage_new_interaction.test",
-                                   "https://new_storage_old_interaction.test",
-                                   "https://old_storage_new_interaction.test"));
+              testing::ElementsAre("new_storage_new_interaction.test",
+                                   "new_storage_old_interaction.test",
+                                   "old_storage_new_interaction.test"));
 }
 
 INSTANTIATE_TEST_SUITE_P(All,
@@ -565,6 +654,12 @@
  public:
   DIPSDatabaseHistogramTest() : DIPSDatabaseTest(false) {}
 
+  void SetUp() override {
+    DIPSDatabaseTest::SetUp();
+    features_.InitAndEnableFeatureWithParameters(dips::kFeature,
+                                                 {{"interaction_ttl", "inf"}});
+  }
+
   const base::HistogramTester& histograms() const { return histogram_tester_; }
 
  protected:
@@ -586,7 +681,7 @@
   histograms().ExpectUniqueSample("Privacy.DIPS.DatabaseEntryCount", 0, 1);
 
   // Write an entry to the db.
-  db_->Write("https://url1.test", {},
+  db_->Write("url1.test", {},
              /*interaction_times=*/{Time::FromDoubleT(1), Time::FromDoubleT(1)},
              {}, {});
   db_->ComputeDatabaseMetricsForTesting();
@@ -612,7 +707,7 @@
   histograms().ExpectUniqueSample("Privacy.DIPS.DatabaseInit", 1, 1);
 
   // Write an entry to the db.
-  db_->Write("https://url1.test", {},
+  db_->Write("url1.test", {},
              /*interaction_times=*/{Time::FromDoubleT(1), Time::FromDoubleT(1)},
              {}, {});
   EXPECT_EQ(db_->GetEntryCount(), static_cast<size_t>(1));
diff --git a/chrome/browser/dips/dips_features.cc b/chrome/browser/dips/dips_features.cc
index 0fecc9b7..adce83a 100644
--- a/chrome/browser/dips/dips_features.cc
+++ b/chrome/browser/dips/dips_features.cc
@@ -9,7 +9,9 @@
 namespace dips {
 
 // Enables the DIPS (Detect Incidental Party State) feature.
-BASE_FEATURE(kFeature, "DIPS", base::FEATURE_DISABLED_BY_DEFAULT);
+// On by default to allow for collecting metrics. All potentially dangerous
+// behavior (database persistence, DIPS deletion) will be gated by params.
+BASE_FEATURE(kFeature, "DIPS", base::FEATURE_ENABLED_BY_DEFAULT);
 
 // Set whether DIPS persists its database to disk.
 const base::FeatureParam<bool> kPersistedDatabaseEnabled{
@@ -26,6 +28,15 @@
 const base::FeatureParam<base::TimeDelta> kTimerDelay{&kFeature, "timer_delay",
                                                       base::Hours(24)};
 
+// Set how long DIPS maintains an interaction for a site.
+//
+// If a site in the DIPS  database has an interaction within the grace period a
+// DIPS-triggering action, then that action and all ensuing actions are
+// protected from DIPS clearing until the interaction "expires" as set by this
+// param.
+const base::FeatureParam<base::TimeDelta> kInteractionTtl{
+    &kFeature, "interaction_ttl", base::Days(90)};
+
 // Sets the actions which will trigger DIPS clearing for a site. The default is
 // to set to kBounce, but can be overridden by Finch experiment groups or by
 // command-line flags.
diff --git a/chrome/browser/dips/dips_features.h b/chrome/browser/dips/dips_features.h
index 951ef8c..eb371296 100644
--- a/chrome/browser/dips/dips_features.h
+++ b/chrome/browser/dips/dips_features.h
@@ -16,6 +16,7 @@
 extern const base::FeatureParam<bool> kPersistedDatabaseEnabled;
 extern const base::FeatureParam<base::TimeDelta> kGracePeriod;
 extern const base::FeatureParam<base::TimeDelta> kTimerDelay;
+extern const base::FeatureParam<base::TimeDelta> kInteractionTtl;
 extern const base::FeatureParam<DIPSTriggeringAction> kTriggeringAction;
 
 }  // namespace dips
diff --git a/chrome/browser/dips/dips_storage.h b/chrome/browser/dips/dips_storage.h
index 859f5a26..eb8edb7b 100644
--- a/chrome/browser/dips/dips_storage.h
+++ b/chrome/browser/dips/dips_storage.h
@@ -49,6 +49,10 @@
   // Utility Methods -----------------------------------------------------------
 
   static size_t SetPrepopulateChunkSizeForTesting(size_t size);
+  void SetClockForTesting(base::Clock* clock) {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    db_->SetClockForTesting(clock);
+  }
 
   // For each site in |sites|, set the interaction and storage timestamps to
   // |time|. Note this may run asynchronously -- the DB is not guaranteed to be
diff --git a/chrome/browser/dips/dips_storage_unittest.cc b/chrome/browser/dips/dips_storage_unittest.cc
index 5cb50f9..c2e42aa7 100644
--- a/chrome/browser/dips/dips_storage_unittest.cc
+++ b/chrome/browser/dips/dips_storage_unittest.cc
@@ -5,10 +5,12 @@
 #include "chrome/browser/dips/dips_storage.h"
 
 #include "base/functional/bind.h"
+#include "base/metrics/field_trial_params.h"
 #include "base/task/thread_pool.h"
+#include "base/test/scoped_feature_list.h"
 #include "base/test/task_environment.h"
-#include "base/test/test_future.h"
 #include "base/threading/sequence_bound.h"
+#include "chrome/browser/dips/dips_features.h"
 #include "chrome/browser/dips/dips_state.h"
 #include "chrome/browser/dips/dips_utils.h"
 #include "content/public/browser/browsing_data_filter_builder.h"
@@ -39,6 +41,17 @@
                                     : absl::nullopt;
 }
 
+class ScopedDIPSFeatureEnabledWithParams {
+ public:
+  explicit ScopedDIPSFeatureEnabledWithParams(
+      const base::FieldTrialParams& params) {
+    features_.InitAndEnableFeatureWithParameters(dips::kFeature, params);
+  }
+
+ private:
+  base::test::ScopedFeatureList features_;
+};
+
 }  // namespace
 
 class DIPSStorageTest : public testing::Test {
@@ -46,10 +59,9 @@
   DIPSStorageTest() = default;
 
  protected:
-  TestStorage storage_;
-
- private:
   base::test::TaskEnvironment env_;
+  ScopedDIPSFeatureEnabledWithParams feature{{{"interaction_ttl", "inf"}}};
+  TestStorage storage_;
 };
 
 TEST(DirtyBit, Constructor) {
@@ -494,6 +506,7 @@
 
  protected:
   base::test::TaskEnvironment task_environment_;
+  ScopedDIPSFeatureEnabledWithParams feature{{{"interaction_ttl", "inf"}}};
   base::SequenceBound<DIPSStorage> storage_;
 };
 
diff --git a/chrome/browser/extensions/extension_browsertest.cc b/chrome/browser/extensions/extension_browsertest.cc
index d2b5bf59..9818f99f 100644
--- a/chrome/browser/extensions/extension_browsertest.cc
+++ b/chrome/browser/extensions/extension_browsertest.cc
@@ -663,14 +663,7 @@
     //                 and then always pack the extension here.
     base::FilePath crx_path = path;
     if (crx_path.Extension() != FILE_PATH_LITERAL(".crx")) {
-      int run_flags = ExtensionCreator::kNoRunFlags;
-      if (creation_flags & Extension::FROM_BOOKMARK) {
-        run_flags = ExtensionCreator::kBookmarkApp;
-        if (install_source == ManifestLocation::kExternalComponent)
-          run_flags |= ExtensionCreator::kSystemApp;
-      }
-
-      crx_path = PackExtension(path, run_flags);
+      crx_path = PackExtension(path, ExtensionCreator::kNoRunFlags);
     }
     if (crx_path.empty())
       return nullptr;
diff --git a/chrome/browser/extensions/extension_service_unittest.cc b/chrome/browser/extensions/extension_service_unittest.cc
index 296b14b..64051177 100644
--- a/chrome/browser/extensions/extension_service_unittest.cc
+++ b/chrome/browser/extensions/extension_service_unittest.cc
@@ -6041,19 +6041,6 @@
     EXPECT_EQ(0, visitor.Visit(json_data));
   }
 
-  // Test is_bookmark_app.
-  MockProviderVisitor from_bookmark_visitor(
-      base_path, Extension::FROM_BOOKMARK);
-  json_data =
-      "{"
-      "  \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\": {"
-      "    \"external_crx\": \"RandomExtension.crx\","
-      "    \"external_version\": \"1.0\","
-      "    \"is_bookmark_app\": true"
-      "  }"
-      "}";
-  EXPECT_EQ(1, from_bookmark_visitor.Visit(json_data));
-
   // Test is_from_webstore.
   MockProviderVisitor from_webstore_visitor(
       base_path, Extension::FROM_WEBSTORE);
diff --git a/chrome/browser/extensions/extension_sync_service.cc b/chrome/browser/extensions/extension_sync_service.cc
index f931dc05..5d4c2cc 100644
--- a/chrome/browser/extensions/extension_sync_service.cc
+++ b/chrome/browser/extensions/extension_sync_service.cc
@@ -180,24 +180,6 @@
     }
   }
 
-  ExtensionRegistry* registry = ExtensionRegistry::Get(profile_);
-  std::unique_ptr<ExtensionSet> all_extensions =
-      registry->GenerateInstalledExtensionsSet();
-  for (const auto& extension : *all_extensions) {
-    if (extension->from_deprecated_bookmark()) {
-      // Deleting deprecated bookmark apps.
-      const std::string& id = extension->id();
-      std::u16string error;
-      bool uninstalled = extension_service()->UninstallExtension(
-          id, extensions::UNINSTALL_REASON_SYNC, &error);
-      if (!uninstalled) {
-        LOG(WARNING) << "Failed to uninstall bookmark apps with id '" << id
-                     << "' : " << error;
-      }
-      base::UmaHistogramBoolean("Extensions.UninstallBookmarkApp", uninstalled);
-    }
-  }
-
   // Now push the local state to sync.
   // Note: We'd like to only send out changes for extensions which have
   // NeedsSync set. However, we can't tell if our changes ever made it to the
diff --git a/chrome/browser/extensions/external_provider_impl.cc b/chrome/browser/extensions/external_provider_impl.cc
index d1f471b..7d5d93f 100644
--- a/chrome/browser/extensions/external_provider_impl.cc
+++ b/chrome/browser/extensions/external_provider_impl.cc
@@ -119,7 +119,6 @@
 const char ExternalProviderImpl::kExternalCrx[] = "external_crx";
 const char ExternalProviderImpl::kExternalVersion[] = "external_version";
 const char ExternalProviderImpl::kExternalUpdateUrl[] = "external_update_url";
-const char ExternalProviderImpl::kIsBookmarkApp[] = "is_bookmark_app";
 const char ExternalProviderImpl::kIsFromWebstore[] = "is_from_webstore";
 const char ExternalProviderImpl::kKeepIfPresent[] = "keep_if_present";
 const char ExternalProviderImpl::kWasInstalledByOem[] = "was_installed_by_oem";
@@ -376,11 +375,6 @@
     }
 
     int creation_flags = creation_flags_;
-    absl::optional<bool> is_bookmark_app =
-        extension_dict.FindBool(kIsBookmarkApp);
-    if (is_bookmark_app.value_or(false)) {
-      creation_flags |= Extension::FROM_BOOKMARK;
-    }
     absl::optional<bool> is_from_webstore =
         extension_dict.FindBool(kIsFromWebstore);
     if (is_from_webstore.value_or(false)) {
diff --git a/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/FeedSwipeRefreshLayout.java b/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/FeedSwipeRefreshLayout.java
index 2262c50..b6c2794e 100644
--- a/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/FeedSwipeRefreshLayout.java
+++ b/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/FeedSwipeRefreshLayout.java
@@ -276,8 +276,7 @@
                 final float yDiff = y - mLastMotionY;
                 if (yDiff > mTouchSlop && !mIsBeingDragged) {
                     mIsBeingDragged = true;
-                    // TODO(1335416): Update this to |true| if experiment is successful
-                    start(false);
+                    start();
                 }
                 break;
             }
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 274c5e0..589aaa6 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -574,6 +574,11 @@
     "expiry_milestone": 107
   },
   {
+    "name": "autofill-remove-card-expiration-and-type-titles",
+    "owners": [ "qihuizhao@google.com", "jsaul@google.com" ],
+    "expiry_milestone": 120
+  },
+  {
     "name": "autofill-save-card-dismiss-on-navigation",
     "owners": [ "sczs", "bling-flags@google.com" ],
     "expiry_milestone": 82
@@ -1139,11 +1144,6 @@
     "expiry_milestone": 114
   },
   {
-    "name": "cros-labs-float-window",
-    "owners": [ "shidi", "afakhry" ],
-    "expiry_milestone": 110
-  },
-  {
     "name": "cros-labs-overview-desk-navigation",
     "owners": [ "richui", "janetmac" ],
     "expiry_milestone": 122
@@ -3801,6 +3801,11 @@
     "expiry_milestone": 111
   },
   {
+    "name": "float-window",
+    "owners": [ "shidi", "afakhry" ],
+    "expiry_milestone": 122
+  },
+  {
     "name": "focus-follows-cursor",
     "owners": [ "dandersson", "tclaiborne" ],
     "expiry_milestone": 120
@@ -5809,7 +5814,7 @@
   {
     "name": "quick-gesture-show-launcher",
     "owners": [ "anasalazar", "//ash/shelf/OWNERS" ],
-    "expiry_milestone": 110
+    "expiry_milestone": 115
   },
   {
     "name": "quick-intensive-throttling-after-loading",
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 002982c..8ddb429a 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -487,6 +487,13 @@
     "If enabled Autofill's popup becomes more prominent, i.e. its shadow "
     "becomes more emphasized, position is also updated";
 
+const char kAutofillRemoveCardExpirationAndTypeTitlesName[] =
+    "Remove expiration and type titles from Chrome Payment Settings page on "
+    "desktop";
+const char kAutofillRemoveCardExpirationAndTypeTitlesDescription[] =
+    "When enabled, expiration and type titles will be removed from the Chrome "
+    "Payment Settings page on desktop platforms.";
+
 const char kAutofillUseConsistentPopupSettingsIconsName[] =
     "Consistent Autofill settings icon";
 const char kAutofillUseConsistentPopupSettingsIconsDescription[] =
@@ -5443,10 +5450,11 @@
 const char kFilesTrashDescription[] =
     "Enable trash for My files volume in Files App.";
 
-const char kFloatWindow[] = "CrOS Labs: Float current active window";
+const char kFloatWindow[] = "Float current active window";
 const char kFloatWindowDescription[] =
     "Enables the accelerator (Command + Alt + F) to float current active "
-    "window.";
+    "window. You can also access this from Multitask Menu, by "
+    "hovering/long-pressing on the maximize button on the window frame.";
 
 const char kForceSpectreVariant2MitigationName[] =
     "Force Spectre variant 2 mitigagtion";
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index f9369c10..8e7b949e3 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -265,6 +265,9 @@
 extern const char kAutofillPreventOverridingPrefilledValuesName[];
 extern const char kAutofillPreventOverridingPrefilledValuesDescription[];
 
+extern const char kAutofillRemoveCardExpirationAndTypeTitlesName[];
+extern const char kAutofillRemoveCardExpirationAndTypeTitlesDescription[];
+
 extern const char kAutofillSaveAndFillVPAName[];
 extern const char kAutofillSaveAndFillVPADescription[];
 
diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browser/flags/android/chrome_feature_list.cc
index b3aedd7..6751305f 100644
--- a/chrome/browser/flags/android/chrome_feature_list.cc
+++ b/chrome/browser/flags/android/chrome_feature_list.cc
@@ -24,7 +24,6 @@
 #include "chrome/browser/push_messaging/push_messaging_features.h"
 #include "chrome/browser/share/share_features.h"
 #include "chrome/browser/signin/signin_features.h"
-#include "chrome/browser/supervised_user/supervised_user_features/supervised_user_features.h"
 #include "chrome/browser/ui/ui_features.h"
 #include "chrome/browser/video_tutorials/switches.h"
 #include "chrome/common/chrome_features.h"
@@ -64,6 +63,7 @@
 #include "components/shared_highlighting/core/common/shared_highlighting_features.h"
 #include "components/signin/public/base/signin_switches.h"
 #include "components/subresource_filter/core/browser/subresource_filter_features.h"
+#include "components/supervised_user/core/common/features.h"
 #include "components/sync/base/features.h"
 #include "components/webapps/browser/features.h"
 #include "content/public/common/content_features.h"
@@ -237,7 +237,6 @@
     &kContextualSearchThinWebViewImplementation,
     &kDeferKeepScreenOnDuringGesture,
     &kDirectActions,
-    &kDuetTabStripIntegrationAndroid,
     &kExperimentsForAgsa,
     &kExploreSites,
     &kFocusOmniboxInIncognitoTabIntents,
@@ -265,7 +264,6 @@
     &kOmniboxModernizeVisualUpdate,
     &kOpaqueOriginForIncomingIntents,
     &kOptimizeGeolocationHeaderGeneration,
-    &kOptimizeLayoutsForPullRefresh,
     &kPostTaskFocusTab,
     &kProbabilisticCryptidRenderer,
     &kReachedCodeProfiler,
@@ -319,7 +317,6 @@
     &kTrustedWebActivityQualityEnforcement,
     &kTrustedWebActivityQualityEnforcementForced,
     &kTrustedWebActivityQualityEnforcementWarning,
-    &kShowExtendedPreloadingSetting,
     &kStartSurfaceAndroid,
     &kStartSurfaceReturnTime,
     &kStartSurfaceRefactor,
@@ -736,10 +733,6 @@
              "DownloadHomeForExternalApp",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
-BASE_FEATURE(kDuetTabStripIntegrationAndroid,
-             "DuetTabStripIntegrationAndroid",
-             base::FEATURE_DISABLED_BY_DEFAULT);
-
 BASE_FEATURE(kExperimentsForAgsa,
              "ExperimentsForAgsa",
              base::FEATURE_ENABLED_BY_DEFAULT);
@@ -848,10 +841,6 @@
              "OptimizeGeolocationHeaderGeneration",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
-BASE_FEATURE(kOptimizeLayoutsForPullRefresh,
-             "OptimizeLayoutsForPullRefresh",
-             base::FEATURE_DISABLED_BY_DEFAULT);
-
 BASE_FEATURE(kPostTaskFocusTab,
              "PostTaskFocusTab",
              base::FEATURE_ENABLED_BY_DEFAULT);
@@ -1040,10 +1029,6 @@
              "TrustedWebActivityQualityEnforcementWarning",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
-BASE_FEATURE(kShowExtendedPreloadingSetting,
-             "ShowExtendedPreloadingSetting",
-             base::FEATURE_ENABLED_BY_DEFAULT);
-
 BASE_FEATURE(kStartSurfaceAndroid,
              "StartSurfaceAndroid",
              base::FEATURE_ENABLED_BY_DEFAULT);
diff --git a/chrome/browser/flags/android/chrome_feature_list.h b/chrome/browser/flags/android/chrome_feature_list.h
index f5ff5df..efb2bbc173 100644
--- a/chrome/browser/flags/android/chrome_feature_list.h
+++ b/chrome/browser/flags/android/chrome_feature_list.h
@@ -86,7 +86,6 @@
 BASE_DECLARE_FEATURE(kDontPrefetchLibraries);
 BASE_DECLARE_FEATURE(kDownloadAutoResumptionThrottling);
 BASE_DECLARE_FEATURE(kDownloadHomeForExternalApp);
-BASE_DECLARE_FEATURE(kDuetTabStripIntegrationAndroid);
 BASE_DECLARE_FEATURE(kExperimentsForAgsa);
 BASE_DECLARE_FEATURE(kExploreSites);
 BASE_DECLARE_FEATURE(kFocusOmniboxInIncognitoTabIntents);
@@ -117,7 +116,6 @@
 BASE_DECLARE_FEATURE(kBookmarksRefresh);
 BASE_DECLARE_FEATURE(kBackGestureRefactorAndroid);
 BASE_DECLARE_FEATURE(kOpaqueOriginForIncomingIntents);
-BASE_DECLARE_FEATURE(kOptimizeLayoutsForPullRefresh);
 BASE_DECLARE_FEATURE(kPostTaskFocusTab);
 BASE_DECLARE_FEATURE(kProbabilisticCryptidRenderer);
 BASE_DECLARE_FEATURE(kReachedCodeProfiler);
@@ -173,7 +171,6 @@
 BASE_DECLARE_FEATURE(kTrustedWebActivityQualityEnforcement);
 BASE_DECLARE_FEATURE(kTrustedWebActivityQualityEnforcementForced);
 BASE_DECLARE_FEATURE(kTrustedWebActivityQualityEnforcementWarning);
-BASE_DECLARE_FEATURE(kShowExtendedPreloadingSetting);
 BASE_DECLARE_FEATURE(kStartSurfaceAndroid);
 BASE_DECLARE_FEATURE(kStartSurfaceReturnTime);
 BASE_DECLARE_FEATURE(kStartSurfaceRefactor);
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 25be175..e93a506 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
@@ -418,7 +418,6 @@
             "OptimizationGuidePushNotifications";
     public static final String OPTIMIZE_GEOLOCATION_HEADER_GENERATION =
             "OptimizeGeolocationHeaderGeneration";
-    public static final String OPTIMIZE_LAYOUTS_FOR_PULL_REFRESH = "OptimizeLayoutsForPullRefresh";
     public static final String OSK_RESIZES_VISUAL_VIEWPORT = "OSKResizesVisualViewportByDefault";
     public static final String OVERLAY_NEW_LAYOUT = "OverlayNewLayout";
     public static final String PAGE_ANNOTATIONS_SERVICE = "PageAnnotationsService";
@@ -502,7 +501,6 @@
     public static final String SHOPPING_LIST = "ShoppingList";
     public static final String SHOPPING_LIST_ENABLE_DESYNC_RESOLUTION =
             "ShoppingListEnableDesyncResolution";
-    public static final String SHOW_EXTENDED_PRELOADING_SETTING = "ShowExtendedPreloadingSetting";
     public static final String SHOW_SCROLLABLE_MVT_ON_NTP_ANDROID = "ShowScrollableMVTOnNTPAndroid";
     public static final String SHOW_TRUSTED_PUBLISHER_URL = "ShowTrustedPublisherURL";
     public static final String SMART_SUGGESTION_FOR_LARGE_DOWNLOADS =
diff --git a/chrome/browser/guest_view/web_view/chrome_web_view_permission_helper_delegate.cc b/chrome/browser/guest_view/web_view/chrome_web_view_permission_helper_delegate.cc
index 9f1e485..ac94ef5 100644
--- a/chrome/browser/guest_view/web_view/chrome_web_view_permission_helper_delegate.cc
+++ b/chrome/browser/guest_view/web_view/chrome_web_view_permission_helper_delegate.cc
@@ -76,11 +76,11 @@
   const char kPluginName[] = "name";
   const char kPluginIdentifier[] = "identifier";
 
-  base::DictionaryValue info;
-  info.SetStringKey(kPluginName, name);
-  info.SetStringKey(kPluginIdentifier, identifier);
+  base::Value::Dict info;
+  info.Set(kPluginName, name);
+  info.Set(kPluginIdentifier, identifier);
   web_view_permission_helper()->RequestPermission(
-      WEB_VIEW_PERMISSION_TYPE_LOAD_PLUGIN, info,
+      WEB_VIEW_PERMISSION_TYPE_LOAD_PLUGIN, std::move(info),
       base::BindOnce(
           &ChromeWebViewPermissionHelperDelegate::OnPermissionResponse,
           weak_factory_.GetWeakPtr(), identifier),
@@ -106,10 +106,10 @@
     const GURL& url,
     const std::string& request_method,
     base::OnceCallback<void(bool)> callback) {
-  base::DictionaryValue request_info;
-  request_info.SetStringKey(guest_view::kUrl, url.spec());
+  base::Value::Dict request_info;
+  request_info.Set(guest_view::kUrl, url.spec());
   web_view_permission_helper()->RequestPermission(
-      WEB_VIEW_PERMISSION_TYPE_DOWNLOAD, request_info,
+      WEB_VIEW_PERMISSION_TYPE_DOWNLOAD, std::move(request_info),
       base::BindOnce(
           &ChromeWebViewPermissionHelperDelegate::OnDownloadPermissionResponse,
           weak_factory_.GetWeakPtr(), std::move(callback)),
@@ -127,18 +127,17 @@
     bool user_gesture,
     bool last_unlocked_by_target,
     base::OnceCallback<void(bool)> callback) {
-  base::DictionaryValue request_info;
-  request_info.SetBoolKey(guest_view::kUserGesture, user_gesture);
-  request_info.SetBoolKey(webview::kLastUnlockedBySelf,
-                          last_unlocked_by_target);
-  request_info.SetStringKey(guest_view::kUrl, web_view_permission_helper()
-                                                  ->web_view_guest()
-                                                  ->web_contents()
-                                                  ->GetLastCommittedURL()
-                                                  .spec());
+  base::Value::Dict request_info;
+  request_info.Set(guest_view::kUserGesture, user_gesture);
+  request_info.Set(webview::kLastUnlockedBySelf, last_unlocked_by_target);
+  request_info.Set(guest_view::kUrl, web_view_permission_helper()
+                                         ->web_view_guest()
+                                         ->web_contents()
+                                         ->GetLastCommittedURL()
+                                         .spec());
 
   web_view_permission_helper()->RequestPermission(
-      WEB_VIEW_PERMISSION_TYPE_POINTER_LOCK, request_info,
+      WEB_VIEW_PERMISSION_TYPE_POINTER_LOCK, std::move(request_info),
       base::BindOnce(&ChromeWebViewPermissionHelperDelegate::
                          OnPointerLockPermissionResponse,
                      weak_factory_.GetWeakPtr(), std::move(callback)),
@@ -156,9 +155,9 @@
     const GURL& requesting_frame,
     bool user_gesture,
     base::OnceCallback<void(bool)> callback) {
-  base::DictionaryValue request_info;
-  request_info.SetStringKey(guest_view::kUrl, requesting_frame.spec());
-  request_info.SetBoolKey(guest_view::kUserGesture, user_gesture);
+  base::Value::Dict request_info;
+  request_info.Set(guest_view::kUrl, requesting_frame.spec());
+  request_info.Set(guest_view::kUserGesture, user_gesture);
 
   // It is safe to hold an unretained pointer to
   // ChromeWebViewPermissionHelperDelegate because this callback is called from
@@ -169,7 +168,7 @@
                      weak_factory_.GetWeakPtr(), user_gesture,
                      base::BindOnce(&CallbackWrapper, std::move(callback)));
   web_view_permission_helper()->RequestPermission(
-      WEB_VIEW_PERMISSION_TYPE_GEOLOCATION, request_info,
+      WEB_VIEW_PERMISSION_TYPE_GEOLOCATION, std::move(request_info),
       std::move(permission_callback), false /* allowed_by_default */);
 }
 
@@ -198,10 +197,10 @@
     const GURL& url,
     bool allowed_by_default,
     base::OnceCallback<void(bool)> callback) {
-  base::DictionaryValue request_info;
-  request_info.SetStringKey(guest_view::kUrl, url.spec());
+  base::Value::Dict request_info;
+  request_info.Set(guest_view::kUrl, url.spec());
   web_view_permission_helper()->RequestPermission(
-      WEB_VIEW_PERMISSION_TYPE_FILESYSTEM, request_info,
+      WEB_VIEW_PERMISSION_TYPE_FILESYSTEM, std::move(request_info),
       base::BindOnce(&ChromeWebViewPermissionHelperDelegate::
                          OnFileSystemPermissionResponse,
                      weak_factory_.GetWeakPtr(), std::move(callback)),
diff --git a/chrome/browser/optimization_guide/page_content_annotations_service_factory.cc b/chrome/browser/optimization_guide/page_content_annotations_service_factory.cc
index 85a82edf..6555fdf 100644
--- a/chrome/browser/optimization_guide/page_content_annotations_service_factory.cc
+++ b/chrome/browser/optimization_guide/page_content_annotations_service_factory.cc
@@ -4,12 +4,16 @@
 
 #include "chrome/browser/optimization_guide/page_content_annotations_service_factory.h"
 
+#include <memory>
+
 #include "base/feature_list.h"
 #include "base/no_destructor.h"
 #include "base/task/sequenced_task_runner.h"
 #include "base/task/task_traits.h"
 #include "base/task/thread_pool.h"
 #include "chrome/browser/app_mode/app_mode_utils.h"
+#include "chrome/browser/autocomplete/chrome_autocomplete_provider_client.h"
+#include "chrome/browser/autocomplete/zero_suggest_cache_service_factory.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/history/history_service_factory.h"
 #include "chrome/browser/optimization_guide/optimization_guide_keyed_service.h"
@@ -22,6 +26,7 @@
 #include "chrome/browser/search_engines/template_url_service_factory.h"
 #include "components/history/core/browser/history_service.h"
 #include "components/leveldb_proto/public/proto_database_provider.h"
+#include "components/omnibox/browser/zero_suggest_cache_service.h"
 #include "components/optimization_guide/content/browser/page_content_annotations_service.h"
 #include "components/optimization_guide/core/optimization_guide_features.h"
 #include "components/search_engines/template_url_service.h"
@@ -93,6 +98,7 @@
   DependsOn(OptimizationGuideKeyedServiceFactory::GetInstance());
   DependsOn(HistoryServiceFactory::GetInstance());
   DependsOn(TemplateURLServiceFactory::GetInstance());
+  DependsOn(ZeroSuggestCacheServiceFactory::GetInstance());
 }
 
 PageContentAnnotationsServiceFactory::~PageContentAnnotationsServiceFactory() =
@@ -119,11 +125,14 @@
                                            ServiceAccessType::IMPLICIT_ACCESS);
   TemplateURLService* template_url_service =
       TemplateURLServiceFactory::GetForProfile(profile);
+  ZeroSuggestCacheService* zero_suggest_cache_service =
+      ZeroSuggestCacheServiceFactory::GetForProfile(profile);
   if (optimization_guide_keyed_service && history_service) {
     return new optimization_guide::PageContentAnnotationsService(
+        std::make_unique<ChromeAutocompleteProviderClient>(profile),
         g_browser_process->GetApplicationLocale(),
         optimization_guide_keyed_service, history_service, template_url_service,
-        proto_db_provider, profile_path,
+        zero_suggest_cache_service, proto_db_provider, profile_path,
         optimization_guide_keyed_service->GetOptimizationGuideLogger(),
         base::ThreadPool::CreateSequencedTaskRunner(
             {base::MayBlock(), base::TaskPriority::BEST_EFFORT}));
diff --git a/chrome/browser/policy/extension_policy_browsertest.cc b/chrome/browser/policy/extension_policy_browsertest.cc
index 8336ab0..3cc665a 100644
--- a/chrome/browser/policy/extension_policy_browsertest.cc
+++ b/chrome/browser/policy/extension_policy_browsertest.cc
@@ -30,9 +30,11 @@
 #include "chrome/browser/extensions/forced_extensions/install_stage_tracker.h"
 #include "chrome/browser/extensions/install_verifier.h"
 #include "chrome/browser/extensions/load_error_waiter.h"
+#include "chrome/browser/extensions/mixin_based_extension_apitest.h"
 #include "chrome/browser/extensions/shared_module_service.h"
 #include "chrome/browser/extensions/unpacked_installer.h"
 #include "chrome/browser/extensions/updater/extension_updater.h"
+#include "chrome/browser/policy/extension_force_install_mixin.h"
 #include "chrome/browser/policy/extension_policy_test_base.h"
 #include "chrome/browser/policy/policy_test_utils.h"
 #include "chrome/browser/policy/profile_policy_connector_builder.h"
@@ -47,6 +49,7 @@
 #include "chrome/browser/web_applications/web_app_provider.h"
 #include "chrome/browser/web_applications/web_app_registrar.h"
 #include "chrome/common/chrome_features.h"
+#include "chrome/common/chrome_paths.h"
 #include "chrome/common/extensions/extension_test_util.h"
 #include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
 #include "chrome/test/base/ui_test_utils.h"
@@ -80,6 +83,7 @@
 #include "extensions/common/manifest_handlers/shared_module_info.h"
 #include "extensions/common/permissions/permissions_data.h"
 #include "extensions/common/value_builder.h"
+#include "extensions/test/extension_test_message_listener.h"
 #include "net/test/embedded_test_server/http_request.h"
 #include "net/test/embedded_test_server/http_response.h"
 #include "third_party/blink/public/common/switches.h"
@@ -118,7 +122,15 @@
 // {DIR_TEST_DATA}/extensions/pinning/ are used in extension pinning tests.
 const char kPinnedExtensionCrxId[] = "fdlpamochgodkfemfnickdlkabcfmbln";
 
+// A proxy test extension which is setting proxy for chromium primary profile:
+const char kProxySettingExtensionId[] = "cmppkjmdihjjoenffkfoejbjdgnlgnbm";
+const char kProxySettingExtensionExtensionPath[] =
+    "extensions/browsertest/proxy_setting_extension/";
+const char kProxySettingExtensionPemPath[] =
+    "extensions/browsertest/proxy_setting_extension.pem";
+
 const char kGoodCrxVersion[] = "1.0.0.1";
+const char kProxyPreferencesName[] = "proxy";
 
 const base::FilePath::CharType kGoodV1CrxName[] =
     FILE_PATH_LITERAL("good_v1.crx");
@@ -2588,4 +2600,152 @@
   EXPECT_TRUE(app2->permissions_data()->CanAccessPage(test_url, tab_id, error));
 }
 
+class MixinBasedExtensionPolicyTest
+    : public extensions::MixinBasedExtensionApiTest {
+ public:
+  MixinBasedExtensionPolicyTest() = default;
+
+  std::string GetKeyFromProxyPrefs(const PrefService::Preference* prefs,
+                                   std::string key) {
+    return prefs->GetValue()->FindKey(key)->GetString();
+  }
+
+  const PrefService::Preference* GetOriginalProxyPrefs() {
+    PrefService* prefs = browser()->profile()->GetOriginalProfile()->GetPrefs();
+    return prefs->FindPreference(kProxyPreferencesName);
+  }
+
+  const PrefService::Preference* GetIncognitoProxyPrefs() {
+    PrefService* prefs =
+        browser()
+            ->profile()
+            ->GetOffTheRecordProfile(
+                Profile::OTRProfileID::CreateUniqueForTesting(),
+                /*create_if_needed=*/true)
+            ->GetPrefs();
+    return prefs->FindPreference(kProxyPreferencesName);
+  }
+
+  policy::MockConfigurationPolicyProvider* policy_provider() {
+    return &mock_policy_provider_;
+  }
+
+  ExtensionForceInstallMixin* force_mixin() {
+    return &extension_force_install_mixin_;
+  }
+
+  base::FilePath GetTestDataDir() {
+    return base::PathService::CheckedGet(chrome::DIR_TEST_DATA);
+  }
+
+ protected:
+  void SetUp() override {
+    extensions::ChromeContentVerifierDelegate::SetDefaultModeForTesting(
+        extensions::ChromeContentVerifierDelegate::VerifyInfo::Mode::
+            ENFORCE_STRICT);
+    extensions::MixinBasedExtensionApiTest::SetUp();
+  }
+
+  void SetUpInProcessBrowserTestFixture() override {
+    extensions::MixinBasedExtensionApiTest::SetUpInProcessBrowserTestFixture();
+
+    mock_policy_provider_.SetDefaultReturns(
+        true /* is_initialization_complete_return */,
+        true /* is_first_policy_load_complete_return */);
+    mock_policy_provider_.SetAutoRefresh();
+    BrowserPolicyConnector::SetPolicyProviderForTesting(&mock_policy_provider_);
+  }
+
+  void SetUpOnMainThread() override {
+    extensions::MixinBasedExtensionApiTest::SetUpOnMainThread();
+    extension_force_install_mixin_.InitWithMockPolicyProvider(
+        profile(), policy_provider());
+  }
+
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    extensions::MixinBasedExtensionApiTest::SetUpCommandLine(command_line);
+  }
+
+  ExtensionForceInstallMixin extension_force_install_mixin_{&mixin_host_};
+
+ private:
+  testing::NiceMock<MockConfigurationPolicyProvider> mock_policy_provider_;
+  web_app::OsIntegrationManager::ScopedSuppressForTesting os_hooks_suppress_;
+};
+
+// Verifies that proxy which is configured by force-installed extensions
+// will not affect incognito profile.
+IN_PROC_BROWSER_TEST_F(MixinBasedExtensionPolicyTest,
+                       ForcedProxyExtensionHasNoEffectInIncognitoMode) {
+#if BUILDFLAG(IS_WIN)
+  // Mark as enterprise managed.
+  base::win::ScopedDomainStateForTesting scoped_domain(true);
+#endif
+
+  {
+    // Check that extension is not loaded.
+    EXPECT_FALSE(
+        force_mixin()->GetInstalledExtension(kProxySettingExtensionId));
+    // Check that in main profile default proxy mode is system.
+    const PrefService::Preference* proxy_prefs = GetOriginalProxyPrefs();
+    ASSERT_TRUE(proxy_prefs);
+    EXPECT_FALSE(proxy_prefs->IsExtensionControlled());
+    std::string proxy_mode = GetKeyFromProxyPrefs(proxy_prefs, "mode");
+    EXPECT_EQ(proxy_mode, "system");
+
+    // Check that in incognito profile default proxy mode is system.
+    const PrefService::Preference* incognito_proxy_prefs =
+        GetIncognitoProxyPrefs();
+    ASSERT_TRUE(incognito_proxy_prefs);
+    EXPECT_FALSE(proxy_prefs->IsExtensionControlled());
+    std::string incognito_proxy_mode =
+        GetKeyFromProxyPrefs(incognito_proxy_prefs, "mode");
+    EXPECT_EQ(proxy_mode, "system");
+  }
+
+  // Force load extension from source dir and get back extension_id.
+  // As PEM file is provided, we are expecting same extension ID always.
+  ExtensionTestMessageListener ready_listener("ready");
+  ready_listener.set_extension_id(kProxySettingExtensionId);
+  EXPECT_TRUE(force_mixin()->ForceInstallFromSourceDir(
+      GetTestDataDir().AppendASCII(kProxySettingExtensionExtensionPath),
+      GetTestDataDir().AppendASCII(kProxySettingExtensionPemPath),
+      ExtensionForceInstallMixin::WaitMode::kLoad, nullptr));
+
+  // Waiting for JS execution after the extension is loaded.
+  ASSERT_TRUE(ready_listener.WaitUntilSatisfied());
+
+  // Verify extension is installed and enabled.
+  ASSERT_TRUE(force_mixin()->GetInstalledExtension(kProxySettingExtensionId));
+  EXPECT_TRUE(force_mixin()->GetEnabledExtension(kProxySettingExtensionId));
+  EXPECT_EQ(force_mixin()
+                ->GetInstalledExtension(kProxySettingExtensionId)
+                ->location(),
+            ManifestLocation::kExternalPolicyDownload);
+
+  {
+    // Verify extension has changed proxy setting for Main profile.
+    const PrefService::Preference* proxy_prefs = GetOriginalProxyPrefs();
+    ASSERT_TRUE(proxy_prefs);
+    EXPECT_TRUE(proxy_prefs->IsExtensionControlled());
+    std::string proxy_mode = GetKeyFromProxyPrefs(proxy_prefs, "mode");
+    std::string proxy_server = GetKeyFromProxyPrefs(proxy_prefs, "server");
+    std::string proxy_bypass_list =
+        GetKeyFromProxyPrefs(proxy_prefs, "bypass_list");
+
+    EXPECT_EQ(proxy_mode, "fixed_servers");
+    EXPECT_EQ(proxy_server, "https=google.com:5555");
+    EXPECT_EQ(proxy_bypass_list, "127.0.0.1");
+
+    // Verify extension has not changed proxy setting for Incognito profile.
+    const PrefService::Preference* incognito_proxy_prefs =
+        GetIncognitoProxyPrefs();
+    ASSERT_TRUE(proxy_prefs);
+    EXPECT_FALSE(incognito_proxy_prefs->IsExtensionControlled());
+    std::string incognito_proxy_mode =
+        GetKeyFromProxyPrefs(incognito_proxy_prefs, "mode");
+    EXPECT_EQ(incognito_proxy_mode, "system");
+  }
+}
+
 }  // namespace policy
diff --git a/chrome/browser/prefetch/android/java/src/org/chromium/chrome/browser/prefetch/settings/PreloadPagesSettingsFragment.java b/chrome/browser/prefetch/android/java/src/org/chromium/chrome/browser/prefetch/settings/PreloadPagesSettingsFragment.java
index 166d9b14a..b1890ec 100644
--- a/chrome/browser/prefetch/android/java/src/org/chromium/chrome/browser/prefetch/settings/PreloadPagesSettingsFragment.java
+++ b/chrome/browser/prefetch/android/java/src/org/chromium/chrome/browser/prefetch/settings/PreloadPagesSettingsFragment.java
@@ -10,7 +10,6 @@
 import androidx.annotation.VisibleForTesting;
 import androidx.preference.Preference;
 
-import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.settings.ChromeManagedPreferenceDelegate;
 import org.chromium.components.browser_ui.settings.FragmentSettingsLauncher;
 import org.chromium.components.browser_ui.settings.ManagedPreferenceDelegate;
@@ -42,13 +41,6 @@
     public static String getPreloadPagesSummaryString(Context context) {
         @PreloadPagesState
         int preloadPagesState = PreloadPagesSettingsBridge.getState();
-        if (!ChromeFeatureList.isEnabled(ChromeFeatureList.SHOW_EXTENDED_PRELOADING_SETTING)
-                && preloadPagesState == PreloadPagesState.EXTENDED_PRELOADING) {
-            // If the extended preloading UI setting is disabled, show "Standard
-            // Preloading" as a substitute.
-            preloadPagesState = PreloadPagesState.STANDARD_PRELOADING;
-        }
-        String preloadPagesStateString = "";
         if (preloadPagesState == PreloadPagesState.EXTENDED_PRELOADING) {
             return context.getString(R.string.preload_pages_extended_preloading_title);
         }
diff --git a/chrome/browser/prefetch/android/java/src/org/chromium/chrome/browser/prefetch/settings/PreloadPagesSettingsFragmentTest.java b/chrome/browser/prefetch/android/java/src/org/chromium/chrome/browser/prefetch/settings/PreloadPagesSettingsFragmentTest.java
index f5740cb..fe92a90 100644
--- a/chrome/browser/prefetch/android/java/src/org/chromium/chrome/browser/prefetch/settings/PreloadPagesSettingsFragmentTest.java
+++ b/chrome/browser/prefetch/android/java/src/org/chromium/chrome/browser/prefetch/settings/PreloadPagesSettingsFragmentTest.java
@@ -7,8 +7,6 @@
 import static androidx.test.espresso.action.ViewActions.click;
 import static androidx.test.espresso.matcher.ViewMatchers.withId;
 
-import android.view.View;
-
 import androidx.preference.Preference;
 import androidx.test.filters.SmallTest;
 
@@ -24,7 +22,6 @@
 import org.chromium.base.test.util.DoNotBatch;
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.browser.feedback.HelpAndFeedbackLauncher;
-import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.init.ChromeBrowserInitializer;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.settings.SettingsActivityTestRule;
@@ -50,8 +47,6 @@
             "Incorrect radio button checked state.";
     private static final String ASSERT_PRELOAD_PAGES_STATE_NATIVE =
             "Incorrect Preload Pages state from native.";
-    private static final String ASSERT_RADIO_BUTTON_VISIBILITY =
-            "Incorrect radio button visibility.";
 
     @Rule
     public SettingsActivityTestRule<PreloadPagesSettingsFragment> mTestRule =
@@ -93,17 +88,14 @@
     @Test
     @SmallTest
     @Feature({"PreloadPages"})
-    @Features.EnableFeatures({ChromeFeatureList.SHOW_EXTENDED_PRELOADING_SETTING,
-            SettingsFeatureList.HIGHLIGHT_MANAGED_PREF_DISCLAIMER_ANDROID})
-    public void
-    testOnStartup_EnableHighlight() {
+    @Features.EnableFeatures(SettingsFeatureList.HIGHLIGHT_MANAGED_PREF_DISCLAIMER_ANDROID)
+    public void testOnStartup_EnableHighlight() {
         testOnStartupImpl();
     }
 
     @Test
     @SmallTest
     @Feature({"PreloadPages"})
-    @Features.EnableFeatures(ChromeFeatureList.SHOW_EXTENDED_PRELOADING_SETTING)
     @Features.DisableFeatures(SettingsFeatureList.HIGHLIGHT_MANAGED_PREF_DISCLAIMER_ANDROID)
     public void testOnStartup_DisableHighlight() {
         testOnStartupImpl();
@@ -134,48 +126,13 @@
     @SmallTest
     @Feature({"PreloadPages"})
     @Features.EnableFeatures(SettingsFeatureList.HIGHLIGHT_MANAGED_PREF_DISCLAIMER_ANDROID)
-    @Features.DisableFeatures(ChromeFeatureList.SHOW_EXTENDED_PRELOADING_SETTING)
-    public void testOnStartupExtendedPreloadingStateButNotShownInUI_EnableHighlight() {
-        testOnStartupExtendedPreloadingStateButNotShownInUIImpl();
-    }
-
-    @Test
-    @SmallTest
-    @Feature({"PreloadPages"})
-    @Features.DisableFeatures({ChromeFeatureList.SHOW_EXTENDED_PRELOADING_SETTING,
-            SettingsFeatureList.HIGHLIGHT_MANAGED_PREF_DISCLAIMER_ANDROID})
-    public void
-    testOnStartupExtendedPreloadingStateButNotShownInUI_DisableHighlight() {
-        testOnStartupExtendedPreloadingStateButNotShownInUIImpl();
-    }
-
-    public void testOnStartupExtendedPreloadingStateButNotShownInUIImpl() {
-        launchSettingsActivity();
-        TestThreadUtils.runOnUiThreadBlocking(() -> {
-            PreloadPagesSettingsBridge.setState(PreloadPagesState.EXTENDED_PRELOADING);
-            Assert.assertEquals(ASSERT_RADIO_BUTTON_VISIBILITY,
-                    getExtendedPreloadingButton().getVisibility(), View.INVISIBLE);
-            Assert.assertTrue(getStandardPreloadingButton().isChecked());
-            Assert.assertFalse(getNoPreloadingButton().isChecked());
-            Assert.assertFalse(mManagedTextPreferenceLegacy.isVisible());
-            Assert.assertFalse(mManagedDisclaimerText.isVisible());
-        });
-    }
-
-    @Test
-    @SmallTest
-    @Feature({"PreloadPages"})
-    @Features.EnableFeatures({ChromeFeatureList.SHOW_EXTENDED_PRELOADING_SETTING,
-            SettingsFeatureList.HIGHLIGHT_MANAGED_PREF_DISCLAIMER_ANDROID})
-    public void
-    testCheckRadioButtons_EnableHighlight() {
+    public void testCheckRadioButtons_EnableHighlight() {
         testCheckRadioButtonsImpl();
     }
 
     @Test
     @SmallTest
     @Feature({"PreloadPages"})
-    @Features.EnableFeatures(ChromeFeatureList.SHOW_EXTENDED_PRELOADING_SETTING)
     @Features.DisableFeatures(SettingsFeatureList.HIGHLIGHT_MANAGED_PREF_DISCLAIMER_ANDROID)
     public void testCheckRadioButtons_DisableHighlight() {
         testCheckRadioButtonsImpl();
@@ -227,57 +184,6 @@
     @Test
     @SmallTest
     @Feature({"PreloadPages"})
-    @Features.EnableFeatures(SettingsFeatureList.HIGHLIGHT_MANAGED_PREF_DISCLAIMER_ANDROID)
-    @Features.DisableFeatures(ChromeFeatureList.SHOW_EXTENDED_PRELOADING_SETTING)
-    public void testCheckRadioButtonsExtendedPreloadingHidden_EnableHighlight() {
-        testCheckRadioButtonsExtendedPreloadingHiddenImpl();
-    }
-
-    @Test
-    @SmallTest
-    @Feature({"PreloadPages"})
-    @Features.DisableFeatures({ChromeFeatureList.SHOW_EXTENDED_PRELOADING_SETTING,
-            SettingsFeatureList.HIGHLIGHT_MANAGED_PREF_DISCLAIMER_ANDROID})
-    public void
-    testCheckRadioButtonsExtendedPreloadingHidden_DisableHighlight() {
-        testCheckRadioButtonsExtendedPreloadingHiddenImpl();
-    }
-
-    public void testCheckRadioButtonsExtendedPreloadingHiddenImpl() {
-        launchSettingsActivity();
-        TestThreadUtils.runOnUiThreadBlocking(() -> {
-            Assert.assertFalse(mManagedTextPreferenceLegacy.isVisible());
-            Assert.assertFalse(mManagedDisclaimerText.isVisible());
-            // Click the Standard Preloading button.
-            getStandardPreloadingButton().onClick(null);
-            Assert.assertEquals(ASSERT_PRELOAD_PAGES_STATE_RADIO_BUTTON_GROUP,
-                    PreloadPagesState.STANDARD_PRELOADING, getPreloadPagesState());
-            Assert.assertFalse(
-                    ASSERT_RADIO_BUTTON_CHECKED, getExtendedPreloadingButton().isChecked());
-            Assert.assertTrue(
-                    ASSERT_RADIO_BUTTON_CHECKED, getStandardPreloadingButton().isChecked());
-            Assert.assertFalse(ASSERT_RADIO_BUTTON_CHECKED, getNoPreloadingButton().isChecked());
-            Assert.assertEquals(ASSERT_PRELOAD_PAGES_STATE_NATIVE,
-                    PreloadPagesState.STANDARD_PRELOADING, PreloadPagesSettingsBridge.getState());
-
-            // Click the No Preloading button.
-            getNoPreloadingButton().onClick(null);
-            Assert.assertEquals(ASSERT_PRELOAD_PAGES_STATE_RADIO_BUTTON_GROUP,
-                    PreloadPagesState.NO_PRELOADING, getPreloadPagesState());
-            Assert.assertFalse(
-                    ASSERT_RADIO_BUTTON_CHECKED, getExtendedPreloadingButton().isChecked());
-            Assert.assertFalse(
-                    ASSERT_RADIO_BUTTON_CHECKED, getStandardPreloadingButton().isChecked());
-            Assert.assertTrue(ASSERT_RADIO_BUTTON_CHECKED, getNoPreloadingButton().isChecked());
-            Assert.assertEquals(ASSERT_PRELOAD_PAGES_STATE_NATIVE, PreloadPagesState.NO_PRELOADING,
-                    PreloadPagesSettingsBridge.getState());
-        });
-    }
-
-    @Test
-    @SmallTest
-    @Feature({"PreloadPages"})
-    @Features.EnableFeatures(ChromeFeatureList.SHOW_EXTENDED_PRELOADING_SETTING)
     public void testExtendedPreloadingAuxButtonClicked() {
         launchSettingsActivity();
         TestThreadUtils.runOnUiThreadBlocking(() -> {
@@ -306,8 +212,7 @@
     @Test
     @SmallTest
     @Feature({"PreloadPages"})
-    @Features.EnableFeatures({ChromeFeatureList.SHOW_EXTENDED_PRELOADING_SETTING,
-            SettingsFeatureList.HIGHLIGHT_MANAGED_PREF_DISCLAIMER_ANDROID})
+    @Features.EnableFeatures(SettingsFeatureList.HIGHLIGHT_MANAGED_PREF_DISCLAIMER_ANDROID)
     @Policies.Add({
         @Policies.Item(key = "NetworkPredictionOptions",
                 string = "2" /* NetworkPredictionOptions::kDisabled */)
@@ -320,7 +225,6 @@
     @Test
     @SmallTest
     @Feature({"PreloadPages"})
-    @Features.EnableFeatures(ChromeFeatureList.SHOW_EXTENDED_PRELOADING_SETTING)
     @Features.DisableFeatures(SettingsFeatureList.HIGHLIGHT_MANAGED_PREF_DISCLAIMER_ANDROID)
     @Policies.Add({
         @Policies.Item(key = "NetworkPredictionOptions",
@@ -370,38 +274,6 @@
         });
     }
 
-    @Test
-    @SmallTest
-    @Feature({"PreloadPages"})
-    @Features.DisableFeatures(ChromeFeatureList.SHOW_EXTENDED_PRELOADING_SETTING)
-    public void testExtendedPreloadingHiddenViaFinch() {
-        launchSettingsActivity();
-        TestThreadUtils.runOnUiThreadBlocking(() -> {
-            Assert.assertEquals(ASSERT_RADIO_BUTTON_VISIBILITY,
-                    getExtendedPreloadingButton().getVisibility(), View.INVISIBLE);
-            Assert.assertEquals(ASSERT_RADIO_BUTTON_VISIBILITY,
-                    getStandardPreloadingButton().getVisibility(), View.VISIBLE);
-            Assert.assertEquals(ASSERT_RADIO_BUTTON_VISIBILITY,
-                    getNoPreloadingButton().getVisibility(), View.VISIBLE);
-        });
-    }
-
-    @Test
-    @SmallTest
-    @Feature({"PreloadPages"})
-    @Features.EnableFeatures(ChromeFeatureList.SHOW_EXTENDED_PRELOADING_SETTING)
-    public void testExtendedPreloadingShownViaFinch() {
-        launchSettingsActivity();
-        TestThreadUtils.runOnUiThreadBlocking(() -> {
-            Assert.assertEquals(ASSERT_RADIO_BUTTON_VISIBILITY,
-                    getExtendedPreloadingButton().getVisibility(), View.VISIBLE);
-            Assert.assertEquals(ASSERT_RADIO_BUTTON_VISIBILITY,
-                    getStandardPreloadingButton().getVisibility(), View.VISIBLE);
-            Assert.assertEquals(ASSERT_RADIO_BUTTON_VISIBILITY,
-                    getNoPreloadingButton().getVisibility(), View.VISIBLE);
-        });
-    }
-
     private @PreloadPagesState int getPreloadPagesState() {
         return mPreloadPagesPreference.getPreloadPagesStateForTesting();
     }
diff --git a/chrome/browser/prefetch/android/java/src/org/chromium/chrome/browser/prefetch/settings/RadioButtonGroupPreloadPagesSettings.java b/chrome/browser/prefetch/android/java/src/org/chromium/chrome/browser/prefetch/settings/RadioButtonGroupPreloadPagesSettings.java
index ef31fa4..c26f287 100644
--- a/chrome/browser/prefetch/android/java/src/org/chromium/chrome/browser/prefetch/settings/RadioButtonGroupPreloadPagesSettings.java
+++ b/chrome/browser/prefetch/android/java/src/org/chromium/chrome/browser/prefetch/settings/RadioButtonGroupPreloadPagesSettings.java
@@ -6,14 +6,12 @@
 
 import android.content.Context;
 import android.util.AttributeSet;
-import android.view.View;
 import android.widget.RadioGroup;
 
 import androidx.annotation.VisibleForTesting;
 import androidx.preference.Preference;
 import androidx.preference.PreferenceViewHolder;
 
-import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.components.browser_ui.settings.ManagedPreferenceDelegate;
 import org.chromium.components.browser_ui.settings.ManagedPreferencesUtils;
 import org.chromium.components.browser_ui.widget.RadioButtonWithDescription;
@@ -76,11 +74,7 @@
         super.onBindViewHolder(holder);
         mExtendedPreloading = (RadioButtonWithDescriptionAndAuxButton) holder.findViewById(
                 R.id.extended_preloading);
-        if (ChromeFeatureList.isEnabled(ChromeFeatureList.SHOW_EXTENDED_PRELOADING_SETTING)) {
-            mExtendedPreloading.setAuxButtonClickedListener(this);
-        } else {
-            mExtendedPreloading.setVisibility(View.INVISIBLE);
-        }
+        mExtendedPreloading.setAuxButtonClickedListener(this);
         mStandardPreloading = (RadioButtonWithDescriptionAndAuxButton) holder.findViewById(
                 R.id.standard_preloading);
         mStandardPreloading.setAuxButtonClickedListener(this);
@@ -137,12 +131,6 @@
      *         buttons of other states to unchecked.
      */
     public void setCheckedState(@PreloadPagesState int checkedState) {
-        if (!ChromeFeatureList.isEnabled(ChromeFeatureList.SHOW_EXTENDED_PRELOADING_SETTING)
-                && checkedState == PreloadPagesState.EXTENDED_PRELOADING) {
-            // If the extended preloading UI setting is disabled, show "Standard
-            // Preloading" as a substitute.
-            checkedState = PreloadPagesState.STANDARD_PRELOADING;
-        }
         mPreloadPagesState = checkedState;
         mExtendedPreloading.setChecked(checkedState == PreloadPagesState.EXTENDED_PRELOADING);
         mStandardPreloading.setChecked(checkedState == PreloadPagesState.STANDARD_PRELOADING);
diff --git a/chrome/browser/profiles/profile_keyed_service_browsertest.cc b/chrome/browser/profiles/profile_keyed_service_browsertest.cc
index fa98de34..c36ba0d5 100644
--- a/chrome/browser/profiles/profile_keyed_service_browsertest.cc
+++ b/chrome/browser/profiles/profile_keyed_service_browsertest.cc
@@ -490,7 +490,8 @@
     "WebRtcEventLogManagerKeyedService",
     "WebrtcAudioPrivateEventService",
     "feedback::FeedbackUploaderChrome",
-    "sct_reporting::Factory"
+    "sct_reporting::Factory",
+    "ZeroSuggestCacheServiceFactory",
   };
   // clang-format on
 
diff --git a/chrome/browser/resources/chromeos/accessibility/switch_access/focus_ring_manager.js b/chrome/browser/resources/chromeos/accessibility/switch_access/focus_ring_manager.js
index 63b6e2b..18007fc 100644
--- a/chrome/browser/resources/chromeos/accessibility/switch_access/focus_ring_manager.js
+++ b/chrome/browser/resources/chromeos/accessibility/switch_access/focus_ring_manager.js
@@ -5,36 +5,29 @@
 import {RectUtil} from '../common/rect_util.js';
 
 import {MenuManager} from './menu_manager.js';
-import {SAChildNode, SARootNode} from './nodes/switch_access_node.js';
+import {SAChildNode, SANode, SARootNode} from './nodes/switch_access_node.js';
 import {SwitchAccess} from './switch_access.js';
 import {SAConstants} from './switch_access_constants.js';
 
-/**
- * Class to handle focus rings.
- */
+const FocusRingInfo = chrome.accessibilityPrivate.FocusRingInfo;
+const FocusType = chrome.accessibilityPrivate.FocusType;
+
+
+/** Class to handle focus rings. */
 export class FocusRingManager {
   /** @private */
   constructor() {
     /**
      * A map of all the focus rings.
-     * @private {!Map<SAConstants.Focus.ID,
-     *     chrome.accessibilityPrivate.FocusRingInfo>}
+     * @private {!Object<SAConstants.Focus.ID, FocusRingInfo>}
      */
-    this.rings_ = this.createMap_();
+    this.rings_ = this.createRings_();
 
-    /** @private {!Map<SAConstants.Focus.ID, SAChildNode>} */
-    this.ringNodesForTesting_ = new Map([
-      [SAConstants.Focus.ID.PRIMARY, null],
-      [SAConstants.Focus.ID.PREVIEW, null],
-    ]);
-
-    /**
-     * Regex pattern to verify valid colors. Checks that the first character
-     * is '#', followed by 3, 4, 6, or 8 valid hex characters, and no other
-     * characters (ignoring case).
-     * @private
-     */
-    this.colorPattern_ = /^#([0-9A-F]{3,4}|[0-9A-F]{6}|[0-9A-F]{8})$/i;
+    /** @private {!Object<SAConstants.Focus.ID, ?SANode>} */
+    this.ringNodesForTesting_ = {
+      [SAConstants.Focus.ID.PRIMARY]: null,
+      [SAConstants.Focus.ID.PREVIEW]: null,
+    };
   }
 
   static get instance() {
@@ -49,42 +42,23 @@
    * @param {!string} color
    */
   static setColor(color) {
-    const manager = FocusRingManager.instance;
-
-    if (manager.colorPattern_.test(color) !== true) {
+    if (!FocusRingManager.colorPattern_.test(color)) {
       console.error(SwitchAccess.error(
           SAConstants.ErrorType.INVALID_COLOR,
           'Problem setting focus ring color: ' + color + ' is not' +
               'a valid CSS color string.'));
       return;
     }
-    manager.rings_.forEach(ring => ring.color = color);
+    FocusRingManager.instance.setColorValidated_(color);
   }
 
   /**
-   * Sets the primary and preview focus rings based on the current primary and
-   *     group nodes used for navigation.
+   * Sets the primary and preview focus rings based on the provided node.
    * @param {!SAChildNode} node
    */
   static setFocusedNode(node) {
-    const manager = FocusRingManager.instance;
-
     if (node.ignoreWhenComputingUnionOfBoundingBoxes()) {
-      // Nodes of this type, e.g. the back button node, handles setting its own
-      // focus, as it has special requirements (a round focus ring that has no
-      // gap with the edges of the view).
-      manager.rings_.get(SAConstants.Focus.ID.PRIMARY).rects = [];
-      // Clear the dashed ring between transitions, as the animation is
-      // distracting.
-      manager.rings_.get(SAConstants.Focus.ID.PREVIEW).rects = [];
-      manager.updateFocusRings_(node, null);
-
-      // The dashed focus ring should not be shown around the menu when exiting.
-      if (!MenuManager.isMenuOpen()) {
-        manager.rings_.get(SAConstants.Focus.ID.PREVIEW).rects =
-            [node.group.location];
-        manager.updateFocusRings_(node, null);
-      }
+      FocusRingManager.instance.setFocusedNodeIgnorePrimary_(node);
       return;
     }
 
@@ -99,38 +73,16 @@
     // focus.
     if (node.isGroup()) {
       const firstChild = node.asRootNode().firstChild;
-
-      // Clear the dashed ring between transitions, as the animation is
-      // distracting.
-      manager.rings_.get(SAConstants.Focus.ID.PREVIEW).rects = [];
-
-      let focusRect = node.location;
-      const childRect = firstChild ? firstChild.location : null;
-      if (childRect) {
-        // If the current element is not specialized in location handling, e.g.
-        // the back button, the focus rect should expand to contain the child
-        // rect.
-        focusRect = RectUtil.expandToFitWithPadding(
-            SAConstants.Focus.GROUP_BUFFER, focusRect, childRect);
-        manager.rings_.get(SAConstants.Focus.ID.PREVIEW).rects = [childRect];
-      }
-      manager.rings_.get(SAConstants.Focus.ID.PRIMARY).rects = [focusRect];
-      manager.updateFocusRings_(node, firstChild);
+      FocusRingManager.instance.setFocusedNodeGroup_(node, firstChild);
       return;
     }
 
-    manager.rings_.get(SAConstants.Focus.ID.PRIMARY).rects = [node.location];
-    manager.rings_.get(SAConstants.Focus.ID.PREVIEW).rects = [];
-    manager.updateFocusRings_(node, null);
+    FocusRingManager.instance.setFocusedNodeLeaf_(node);
   }
 
   /** Clears all focus rings. */
   static clearAll() {
-    const manager = FocusRingManager.instance;
-    manager.rings_.forEach(ring => {
-      ring.rects = [];
-    });
-    manager.updateFocusRings_(null, null);
+    FocusRingManager.instance.clearAll_();
   }
 
   /**
@@ -138,23 +90,31 @@
    * are updated. It will be called with two arguments: the node for
    * the primary ring, and the node for the preview ring. Either may
    * be null.
-   * @param {function(SAChildNode, SAChildNode)} observer
+   * @param {function(?SANode, ?SANode)} observer
    */
   static setObserver(observer) {
     FocusRingManager.instance.observer_ = observer;
   }
 
+  // ======== Private methods ========
+
+  /** @private */
+  clearAll_() {
+    this.forEachRing_(ring => ring.rects = []);
+    this.updateNodesForTesting_(null, null);
+    this.updateFocusRings_();
+  }
+
   /**
    * Creates the map of focus rings.
-   * @return {!Map<SAConstants.Focus.ID,
-   * chrome.accessibilityPrivate.FocusRingInfo>}
+   * @return {!Object<SAConstants.Focus.ID, FocusRingInfo>}
    * @private
    */
-  createMap_() {
+  createRings_() {
     const primaryRing = {
       id: SAConstants.Focus.ID.PRIMARY,
       rects: [],
-      type: chrome.accessibilityPrivate.FocusType.SOLID,
+      type: FocusType.SOLID,
       color: SAConstants.Focus.PRIMARY_COLOR,
       secondaryColor: SAConstants.Focus.OUTER_COLOR,
     };
@@ -162,46 +122,138 @@
     const previewRing = {
       id: SAConstants.Focus.ID.PREVIEW,
       rects: [],
-      type: chrome.accessibilityPrivate.FocusType.DASHED,
+      type: FocusType.DASHED,
       color: SAConstants.Focus.PREVIEW_COLOR,
       secondaryColor: SAConstants.Focus.OUTER_COLOR,
     };
 
-    return new Map([
-      [SAConstants.Focus.ID.PRIMARY, primaryRing],
-      [SAConstants.Focus.ID.PREVIEW, previewRing],
-    ]);
+    return {
+      [SAConstants.Focus.ID.PRIMARY]: primaryRing,
+      [SAConstants.Focus.ID.PREVIEW]: previewRing,
+    };
   }
 
+  /**
+   * Calls a function for each focus ring.
+   * @param {!function(!FocusRingInfo)} callback
+   * @private
+   */
+  forEachRing_(callback) {
+    Object.values(this.rings_).forEach(ring => callback(ring));
+  }
+
+  /**
+   * Sets the focus ring color. Assumes the color has already been validated.
+   * @param {!string} color
+   * @private
+   */
+  setColorValidated_(color) {
+    this.forEachRing_(ring => ring.color = color);
+  }
+
+  /**
+   * Sets the primary focus ring to |node|, and the preview focus ring to
+   * |firstChild|.
+   * @param {!SAChildNode} group
+   * @param {!SAChildNode} firstChild
+   * @private
+   */
+  setFocusedNodeGroup_(group, firstChild) {
+    // Clear the dashed ring between transitions, as the animation is
+    // distracting.
+    this.rings_[SAConstants.Focus.ID.PREVIEW].rects = [];
+
+    let focusRect = group.location;
+    const childRect = firstChild ? firstChild.location : null;
+    if (childRect) {
+      // If the current element is not specialized in location handling, e.g.
+      // the back button, the focus rect should expand to contain the child
+      // rect.
+      focusRect = RectUtil.expandToFitWithPadding(
+          SAConstants.Focus.GROUP_BUFFER, focusRect, childRect);
+      this.rings_[SAConstants.Focus.ID.PREVIEW].rects = [childRect];
+    }
+    this.rings_[SAConstants.Focus.ID.PRIMARY].rects = [focusRect];
+    this.updateNodesForTesting_(group, firstChild);
+    this.updateFocusRings_();
+  }
+
+  /**
+   * Clears the primary focus ring and sets the preview focus ring based on the
+   *     provided node.
+   * @param {!SAChildNode} node
+   * @private
+   */
+  setFocusedNodeIgnorePrimary_(node) {
+    // Nodes of this type, e.g. the back button node, handles setting its own
+    // focus, as it has special requirements (a round focus ring that has no
+    // gap with the edges of the view).
+    this.rings_[SAConstants.Focus.ID.PRIMARY].rects = [];
+    // Clear the dashed ring between transitions, as the animation is
+    // distracting.
+    this.rings_[SAConstants.Focus.ID.PREVIEW].rects = [];
+    this.updateFocusRings_();
+
+    // Show the preview focus ring unless the menu is open (it has a custom exit
+    // button).
+    if (!MenuManager.isMenuOpen()) {
+      this.rings_[SAConstants.Focus.ID.PREVIEW].rects = [node.group.location];
+    }
+    this.updateNodesForTesting_(node, node.group);
+    this.updateFocusRings_();
+  }
+
+  /**
+   * Sets the primary focus to |node| and clears the secondary focus.
+   * @param {!SAChildNode} node
+   * @private
+   */
+  setFocusedNodeLeaf_(node) {
+    this.rings_[SAConstants.Focus.ID.PRIMARY].rects = [node.location];
+    this.rings_[SAConstants.Focus.ID.PREVIEW].rects = [];
+    this.updateNodesForTesting_(node, null);
+    this.updateFocusRings_();
+  }
 
   /**
    * Updates all focus rings to reflect new location, color, style, or other
    * changes. Enables observers to monitor what's focused.
-   * @param {SAChildNode} primaryRingNode
-   * @param {SAChildNode} previewRingNode
    * @private
    */
-  updateFocusRings_(primaryRingNode, previewRingNode) {
+  updateFocusRings_() {
     if (SwitchAccess.mode === SAConstants.Mode.POINT_SCAN &&
         !MenuManager.isMenuOpen()) {
       return;
     }
 
-    const focusRings = [];
-    this.rings_.forEach(ring => focusRings.push(ring));
+    const focusRings = Object.values(this.rings_);
     chrome.accessibilityPrivate.setFocusRings(focusRings);
+  }
 
+  /**
+   * Saves the primary/preview focus for testing.
+   * @param {?SANode} primary
+   * @param {?SANode} preview
+   * @private
+   */
+  updateNodesForTesting_(primary, preview) {
     // Keep track of the nodes associated with each focus ring for testing
     // purposes, since focus ring locations are not guaranteed to exactly match
     // node locations.
-    this.ringNodesForTesting_.set(
-        SAConstants.Focus.ID.PRIMARY, primaryRingNode);
-    this.ringNodesForTesting_.set(
-        SAConstants.Focus.ID.PREVIEW, previewRingNode);
+    this.ringNodesForTesting_[SAConstants.Focus.ID.PRIMARY] = primary;
+    this.ringNodesForTesting_[SAConstants.Focus.ID.PREVIEW] = preview;
 
     const observer = FocusRingManager.instance.observer_;
     if (observer) {
-      observer(primaryRingNode, previewRingNode);
+      observer(primary, preview);
     }
   }
 }
+
+/**
+ * Regex pattern to verify valid colors. Checks that the first character
+ * is '#', followed by 3, 4, 6, or 8 valid hex characters, and no other
+ * characters (ignoring case).
+ * @private {RegExp}
+ */
+FocusRingManager.colorPattern_ = /^#([0-9A-F]{3,4}|[0-9A-F]{6}|[0-9A-F]{8})$/i;
diff --git a/chrome/browser/resources/chromeos/accessibility/switch_access/focus_ring_manager_test.js b/chrome/browser/resources/chromeos/accessibility/switch_access/focus_ring_manager_test.js
index bd5d90f..5fcbb9bb 100644
--- a/chrome/browser/resources/chromeos/accessibility/switch_access/focus_ring_manager_test.js
+++ b/chrome/browser/resources/chromeos/accessibility/switch_access/focus_ring_manager_test.js
@@ -39,8 +39,8 @@
         'Third node should be a BackButtonNode');
 
     const rings = FocusRingManager.instance.rings_;
-    const primary = rings.get(SAConstants.Focus.ID.PRIMARY);
-    const preview = rings.get(SAConstants.Focus.ID.PREVIEW);
+    const primary = rings[SAConstants.Focus.ID.PRIMARY];
+    const preview = rings[SAConstants.Focus.ID.PREVIEW];
     assertEquals(SAConstants.Focus.ID.PRIMARY, primary.id);
     assertEquals(SAConstants.Focus.ID.PREVIEW, preview.id);
     assertEquals('solid', primary.type);
@@ -74,8 +74,8 @@
       }
 
       const rings = FocusRingManager.instance.rings_;
-      const primary = rings.get(SAConstants.Focus.ID.PRIMARY);
-      const preview = rings.get(SAConstants.Focus.ID.PREVIEW);
+      const primary = rings[SAConstants.Focus.ID.PRIMARY];
+      const preview = rings[SAConstants.Focus.ID.PREVIEW];
       // Primary and preview focus should be empty.
       assertEquals(0, primary.rects.length);
       assertEquals(0, preview.rects.length);
@@ -88,8 +88,8 @@
   Navigator.byItem.moveTo_(button);
 
   const rings = FocusRingManager.instance.rings_;
-  const primary = rings.get(SAConstants.Focus.ID.PRIMARY);
-  const preview = rings.get(SAConstants.Focus.ID.PREVIEW);
+  const primary = rings[SAConstants.Focus.ID.PRIMARY];
+  const preview = rings[SAConstants.Focus.ID.PREVIEW];
   assertEquals(1, primary.rects.length);
   assertEquals(0, preview.rects.length);
   // Primary focus should be on the button.
@@ -115,17 +115,15 @@
 
   // Verify the number of rings.
   const rings = FocusRingManager.instance.rings_;
-  const primary = rings.get(SAConstants.Focus.ID.PRIMARY);
-  const preview = rings.get(SAConstants.Focus.ID.PREVIEW);
+  const primary = rings[SAConstants.Focus.ID.PRIMARY];
+  const preview = rings[SAConstants.Focus.ID.PREVIEW];
   assertEquals(1, primary.rects.length);
   assertEquals(1, preview.rects.length);
 
   // Use ringNodesForTesting_ to verify the underlying nodes.
   const ringNodes = FocusRingManager.instance.ringNodesForTesting_;
-  const primaryNode =
-      ringNodes.get(SAConstants.Focus.ID.PRIMARY).automationNode;
-  const previewNode =
-      ringNodes.get(SAConstants.Focus.ID.PREVIEW).automationNode;
+  const primaryNode = ringNodes[SAConstants.Focus.ID.PRIMARY].automationNode;
+  const previewNode = ringNodes[SAConstants.Focus.ID.PREVIEW].automationNode;
 
   assertEquals(
       menu, primaryNode, 'primary focus should be around the group (the menu)');
diff --git a/chrome/browser/resources/chromeos/accessibility/switch_access/nodes/switch_access_node.js b/chrome/browser/resources/chromeos/accessibility/switch_access/nodes/switch_access_node.js
index 932a4c1..39656b9 100644
--- a/chrome/browser/resources/chromeos/accessibility/switch_access/nodes/switch_access_node.js
+++ b/chrome/browser/resources/chromeos/accessibility/switch_access/nodes/switch_access_node.js
@@ -7,7 +7,6 @@
 import {SwitchAccess} from '../switch_access.js';
 import {SAConstants, SwitchAccessMenuAction} from '../switch_access_constants.js';
 
-
 const AutomationNode = chrome.automation.AutomationNode;
 
 /**
@@ -479,3 +478,6 @@
     }
   }
 }
+
+/** @typedef {!SAChildNode|!SARootNode} */
+export let SANode;
diff --git a/chrome/browser/resources/chromeos/drive_internals.html b/chrome/browser/resources/chromeos/drive_internals.html
index 2597f04..abd997f 100644
--- a/chrome/browser/resources/chromeos/drive_internals.html
+++ b/chrome/browser/resources/chromeos/drive_internals.html
@@ -122,6 +122,10 @@
           <td id="bulk-pinning-setup-stage">Unknown</td>
         </tr>
         <tr>
+          <td>Setup Stage Error</td>
+          <td id="bulk-pinning-setup-stage-error">Unknown</td>
+        </tr>
+        <tr>
           <td>Available Disk Space:</td>
           <td id="bulk-pinning-available-disk-space">Unknown</td>
         </tr>
diff --git a/chrome/browser/resources/chromeos/drive_internals.js b/chrome/browser/resources/chromeos/drive_internals.js
index aa4a97c..1842470 100644
--- a/chrome/browser/resources/chromeos/drive_internals.js
+++ b/chrome/browser/resources/chromeos/drive_internals.js
@@ -106,6 +106,7 @@
     return;
   }
   $('bulk-pinning-setup-stage').innerText = 'Unknown';
+  $('bulk-pinning-setup-stage-error').innerText = 'Unknown';
   $('bulk-pinning-available-disk-space').innerText = 'Unknown';
   $('bulk-pinning-required-disk-space').innerText = 'Unknown';
   $('bulk-pinning-pinned-disk-space').innerText = 'Unknown';
@@ -119,6 +120,9 @@
     return;
   }
   $('bulk-pinning-setup-stage').innerText = progress.stage;
+  if (progress.setupError) {
+    $('bulk-pinning-setup-stage-error').innerText = progress.setupError;
+  }
   $('bulk-pinning-available-disk-space').innerText =
       progress.availableDiskSpace;
   $('bulk-pinning-required-disk-space').innerText = progress.requiredDiskSpace;
diff --git a/chrome/browser/resources/history/history_toolbar.ts b/chrome/browser/resources/history/history_toolbar.ts
index 4ba20eb..64875bf 100644
--- a/chrome/browser/resources/history/history_toolbar.ts
+++ b/chrome/browser/resources/history/history_toolbar.ts
@@ -5,7 +5,7 @@
 import './shared_style.css.js';
 import './strings.m.js';
 import 'chrome://resources/cr_elements/cr_toolbar/cr_toolbar.js';
-import 'chrome://resources/cr_elements/cr_toolbar/cr_toolbar_search_field.js';
+import 'chrome://resources/cr_elements/cr_toolbar/cr_toolbar_selection_overlay.js';
 
 import {CrToolbarElement} from 'chrome://resources/cr_elements/cr_toolbar/cr_toolbar.js';
 import {CrToolbarSearchFieldElement} from 'chrome://resources/cr_elements/cr_toolbar/cr_toolbar_search_field.js';
diff --git a/chrome/browser/resources/settings/chromeos/device_page/audio.html b/chrome/browser/resources/settings/chromeos/device_page/audio.html
index bede8fc..343e3d4 100644
--- a/chrome/browser/resources/settings/chromeos/device_page/audio.html
+++ b/chrome/browser/resources/settings/chromeos/device_page/audio.html
@@ -151,6 +151,10 @@
             </cr-slider>
           </div>
         </div>
+        <div id="audioInputNoiseCancellationSubsection">
+          <div id="audioInputNoiseCancellationLabel">Noise Cancellation</div>
+          <cr-toggle id="audioInputNoiseCancellationToggle"></cr-toggle>
+        </div>
       </div>
     </div>
   </div>
diff --git a/chrome/browser/resources/settings/chromeos/device_page/fake_cros_audio_config.ts b/chrome/browser/resources/settings/chromeos/device_page/fake_cros_audio_config.ts
index 22dba36..9a90da4d 100644
--- a/chrome/browser/resources/settings/chromeos/device_page/fake_cros_audio_config.ts
+++ b/chrome/browser/resources/settings/chromeos/device_page/fake_cros_audio_config.ts
@@ -58,6 +58,7 @@
 export interface AudioSystemProperties extends AudioSystemPropertiesMojom {
   inputDevices: AudioDevice[];
   inputMuteState: MuteState;
+  inputVolumePercent: number;
 }
 
 export interface FakePropertiesObserverInterface {
@@ -70,6 +71,7 @@
   outputMuteState: MuteState.kNotMuted,
   inputDevices: [fakeInternalFrontMic, fakeBluetoothMic],
   inputMuteState: MuteState.kNotMuted,
+  inputVolumePercent: 57,
 };
 
 /** Creates an audio device based on provided device and isActive override. */
@@ -83,6 +85,7 @@
   setActiveDevice(outputDevice: AudioDevice): void;
   setOutputMuted(muted: boolean): void;
   setInputMuted(muted: boolean): void;
+  setInputVolumePercent(percent: number): void;
 }
 
 export class FakeCrosAudioConfig implements FakeCrosAudioConfigInterface {
@@ -150,6 +153,16 @@
   }
 
   /**
+   * Sets the `inputVolumePercent` to the desired volume and notifies
+   * observers.
+   */
+  setInputVolumePercent(volume: number): void {
+    assert(volume >= 0 && volume <= 100);
+    this.audioSystemProperties.inputVolumePercent = volume;
+    this.notifyAudioSystemPropertiesUpdated();
+  }
+
+  /**
    * Sets the `outputVolumePercent` to the desired volume and notifies
    * observers.
    */
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/internet_page/BUILD.gn
index acec84f..8ad05f8 100644
--- a/chrome/browser/resources/settings/chromeos/internet_page/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/internet_page/BUILD.gn
@@ -21,10 +21,6 @@
     ":internet_page",
     ":internet_page_browser_proxy",
     ":internet_subpage",
-    ":network_always_on_vpn",
-    ":network_proxy_section",
-    ":network_summary",
-    ":network_summary_item",
   ]
 }
 
@@ -97,7 +93,6 @@
 js_library("internet_detail_page") {
   deps = [
     ":internet_page_browser_proxy",
-    ":network_proxy_section",
     "//ash/webui/common/resources:assert",
     "//ash/webui/common/resources:i18n_behavior",
     "//ash/webui/common/resources:web_ui_listener_behavior",
@@ -129,6 +124,7 @@
   ]
   externs_list = [
     "../settings_controls_types.js",
+    "$externs_path/settings_private.js",
     "//ui/webui/resources/cr_elements/cr_button/cr_button_externs.js",
     "//ui/webui/resources/cr_elements/cr_toggle/cr_toggle_externs.js",
     "//ui/webui/resources/cr_elements/policy/cr_policy_indicator_externs.js",
@@ -163,7 +159,6 @@
     ":internet_known_networks_page",
     ":internet_page_browser_proxy",
     ":internet_subpage",
-    ":network_summary",
     "//ash/webui/common/resources:i18n_behavior",
     "//ash/webui/common/resources:web_ui_listener_behavior",
     "//ash/webui/common/resources/cellular_setup:cellular_types",
@@ -183,6 +178,7 @@
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
   ]
   externs_list = [
+    "../settings_controls_types.js",
     "//ui/webui/resources/cr_elements/cr_toast/cr_toast_externs.js",
     "//ui/webui/resources/cr_elements/policy/cr_policy_indicator_externs.js",
   ]
@@ -194,7 +190,6 @@
 js_library("internet_subpage") {
   deps = [
     ":internet_page_browser_proxy",
-    ":network_always_on_vpn",
     "//ash/webui/common/resources:i18n_behavior",
     "//ash/webui/common/resources/cellular_setup:cellular_types",
     "//ash/webui/common/resources/network:cr_policy_network_behavior_mojo",
@@ -224,65 +219,6 @@
   externs_list = []
 }
 
-js_library("network_always_on_vpn") {
-  deps = [
-    "//ash/webui/common/resources:assert",
-    "//ash/webui/common/resources:i18n_behavior",
-    "//ash/webui/common/resources/network:onc_mojo",
-    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
-  ]
-}
-
-js_library("network_proxy_section") {
-  deps = [
-    "//ash/webui/common/resources:i18n_behavior",
-    "//ash/webui/common/resources/network:cr_policy_network_behavior_mojo",
-    "//ash/webui/common/resources/network:cr_policy_network_indicator_mojo",
-    "//ash/webui/common/resources/network:network_proxy",
-    "//chrome/browser/resources/settings/chromeos:os_route",
-    "//chrome/browser/resources/settings/chromeos:prefs_behavior",
-    "//chrome/browser/resources/settings/chromeos:route_observer_behavior",
-    "//chrome/browser/resources/settings/chromeos:router",
-    "//third_party/polymer/v3_0/components-chromium/iron-flex-layout:iron-flex-layout-classes",
-    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
-  ]
-  externs_list = [
-    "../settings_controls_types.js",
-    "//ui/webui/resources/cr_elements/cr_button/cr_button_externs.js",
-    "//ui/webui/resources/cr_elements/cr_dialog/cr_dialog_externs.js",
-  ]
-}
-
-js_library("network_summary") {
-  deps = [
-    ":hotspot_summary_item",
-    ":network_summary_item",
-    "//ash/webui/common/resources/network:mojo_interface_provider",
-    "//ash/webui/common/resources/network:network_listener_behavior",
-    "//ash/webui/common/resources/network:onc_mojo",
-    "//chromeos/ash/services/hotspot_config/public/mojom:mojom_webui_js",
-    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
-  ]
-}
-
-js_library("network_summary_item") {
-  deps = [
-    "//ash/webui/common/resources:assert",
-    "//ash/webui/common/resources:cr_policy_indicator_behavior",
-    "//ash/webui/common/resources:i18n_behavior",
-    "//ash/webui/common/resources/network:cellular_utils",
-    "//ash/webui/common/resources/network:cr_policy_network_behavior_mojo",
-    "//ash/webui/common/resources/network:network_icon",
-    "//ash/webui/common/resources/network:network_siminfo",
-    "//ash/webui/common/resources/network:onc_mojo",
-    "//ash/webui/common/resources/traffic_counters:traffic_counters",
-    "//third_party/polymer/v3_0/components-chromium/iron-flex-layout:iron-flex-layout-classes",
-    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
-  ]
-  externs_list =
-      [ "//ui/webui/resources/cr_elements/cr_toggle/cr_toggle_externs.js" ]
-}
-
 html_to_js("web_components") {
   js_files = [
     "apn_subpage.js",
@@ -294,9 +230,5 @@
     "internet_known_networks_page.js",
     "internet_page.js",
     "internet_subpage.js",
-    "network_always_on_vpn.js",
-    "network_proxy_section.js",
-    "network_summary_item.js",
-    "network_summary.js",
   ]
 }
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/internet_detail_page.js b/chrome/browser/resources/settings/chromeos/internet_page/internet_detail_page.js
index 93c2fcd..50542b9 100644
--- a/chrome/browser/resources/settings/chromeos/internet_page/internet_detail_page.js
+++ b/chrome/browser/resources/settings/chromeos/internet_page/internet_detail_page.js
@@ -59,14 +59,13 @@
 
 import {InternetPageBrowserProxy, InternetPageBrowserProxyImpl} from './internet_page_browser_proxy.js';
 
+// TODO(crbug/1315757) The following type definitions are only needed for
+// Closure compiler and can be removed when this file is converted to TS.
 /**
- * TODO(crbug/1315757) The following type definitions are only needed for
- * Closure compiler and can be removed when this file is converted to TS.
- *
  * @constructor
  * @extends {HTMLElement}
  */
-export function CellularRoamingToggleButtonElement() {}
+function CellularRoamingToggleButtonElement() {}
 /** @return {?CrToggleElement} */
 CellularRoamingToggleButtonElement.prototype.getCellularRoamingToggle =
     function() {};
@@ -75,12 +74,20 @@
  * @constructor
  * @extends {HTMLElement}
  */
-export function TetherConnectionDialogElement() {}
+function TetherConnectionDialogElement() {}
 TetherConnectionDialogElement.prototype.open = function() {};
 TetherConnectionDialogElement.prototype.close = function() {};
 
 /**
  * @constructor
+ * @extends {HTMLElement}
+ */
+function NetworkProxySectionElement() {}
+/** @return {?CrToggleElement} */
+NetworkProxySectionElement.prototype.getAllowSharedToggle = function() {};
+
+/**
+ * @constructor
  * @extends {PolymerElement}
  * @implements {NetworkListenerBehaviorInterface}
  * @implements {CrPolicyNetworkBehaviorMojoInterface}
@@ -571,7 +578,8 @@
       this.proxyExpanded_ = true;
       this.afterRenderShowDeepLink(
           settingId,
-          () => this.shadowRoot.querySelector('network-proxy-section')
+          () => /** @type {NetworkProxySectionElement} */ (
+                    this.shadowRoot.querySelector('network-proxy-section'))
                     .getAllowSharedToggle());
       return false;
     }
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/internet_page.js b/chrome/browser/resources/settings/chromeos/internet_page/internet_page.js
index f3616b2d..7f0b0c8a 100644
--- a/chrome/browser/resources/settings/chromeos/internet_page/internet_page.js
+++ b/chrome/browser/resources/settings/chromeos/internet_page/internet_page.js
@@ -56,6 +56,27 @@
 import {InternetConfigElement} from './internet_config.js';
 import {InternetPageBrowserProxy, InternetPageBrowserProxyImpl} from './internet_page_browser_proxy.js';
 
+// TODO(crbug/1315757) The following type definitions are only needed for
+// Closure compiler and can be removed when this file is converted to TS.
+/**
+ * @constructor
+ * @extends {HTMLElement}
+ */
+function NetworkSummaryItemElement() {}
+/** @return {?CrToggleElement} */
+NetworkSummaryItemElement.prototype.getDeviceEnabledToggle = function() {};
+
+/**
+ * @constructor
+ * @extends {HTMLElement}
+ */
+function NetworkSummaryElement() {}
+/**
+ * @param {?NetworkType} networkType
+ * @return {?NetworkSummaryItemElement}
+ */
+NetworkSummaryElement.prototype.getNetworkRow = function(networkType) {};
+
 /** @type {number} */
 const ESIM_PROFILE_LIMIT = 5;
 
@@ -426,11 +447,15 @@
     }
 
     afterNextRender(this, () => {
-      const networkRow = this.shadowRoot.querySelector('network-summary')
+      const networkRow = /** @type {NetworkSummaryElement} */ (
+                             this.shadowRoot.querySelector('network-summary'))
                              .getNetworkRow(networkType);
-      if (networkRow && networkRow.getDeviceEnabledToggle()) {
-        this.showDeepLinkElement(networkRow.getDeviceEnabledToggle());
-        return;
+      if (networkRow) {
+        const toggleEl = networkRow.getDeviceEnabledToggle();
+        if (toggleEl) {
+          this.showDeepLinkElement(toggleEl);
+          return;
+        }
       }
       console.warn(`Element with deep link id ${settingId} not focusable.`);
     });
@@ -506,8 +531,10 @@
         element = subPage.shadowRoot.querySelector('#networkList');
       }
     } else if (this.detailType_ !== undefined) {
-      const rowForDetailType = this.shadowRoot.querySelector('network-summary')
-                                   .getNetworkRow(this.detailType_);
+      const rowForDetailType =
+          /** @type {NetworkSummaryElement} */ (
+              this.shadowRoot.querySelector('network-summary'))
+              .getNetworkRow(this.detailType_);
 
       // Note: It is possible that the row is no longer present in the DOM
       // (e.g., when a Cellular dongle is unplugged or when Instant Tethering
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/network_always_on_vpn.js b/chrome/browser/resources/settings/chromeos/internet_page/network_always_on_vpn.ts
similarity index 63%
rename from chrome/browser/resources/settings/chromeos/internet_page/network_always_on_vpn.js
rename to chrome/browser/resources/settings/chromeos/internet_page/network_always_on_vpn.ts
index 282d8c973..705cdd5 100644
--- a/chrome/browser/resources/settings/chromeos/internet_page/network_always_on_vpn.js
+++ b/chrome/browser/resources/settings/chromeos/internet_page/network_always_on_vpn.ts
@@ -11,39 +11,41 @@
 import 'chrome://resources/ash/common/network/network_shared.css.js';
 
 import {OncMojo} from 'chrome://resources/ash/common/network/onc_mojo.js';
-import {I18nBehavior, I18nBehaviorInterface} from 'chrome://resources/ash/common/i18n_behavior.js';
+import {CrToggleElement} from 'chrome://resources/cr_elements/cr_toggle/cr_toggle.js';
+import {I18nMixin} from 'chrome://resources/cr_elements/i18n_mixin.js';
 import {AlwaysOnVpnMode} from 'chrome://resources/mojo/chromeos/services/network_config/public/mojom/cros_network_config.mojom-webui.js';
-import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
-/**
- * @constructor
- * @extends {PolymerElement}
- * @implements {I18nBehaviorInterface}
- */
-const NetworkAlwaysOnVpnElementBase =
-    mixinBehaviors([I18nBehavior], PolymerElement);
+import {cast} from '../assert_extras.js';
 
-/** @polymer */
+import {getTemplate} from './network_always_on_vpn.html.js';
+
+interface VpnServiceOption {
+  name: string;
+  value: string;
+  selected: boolean;
+}
+
+const NetworkAlwaysOnVpnElementBase = I18nMixin(PolymerElement);
+
 class NetworkAlwaysOnVpnElement extends NetworkAlwaysOnVpnElementBase {
   static get is() {
-    return 'network-always-on-vpn';
+    return 'network-always-on-vpn' as const;
   }
 
   static get template() {
-    return html`{__html_template__}`;
+    return getTemplate();
   }
 
   static get properties() {
     return {
       /**
        * List of all always-on VPN compatible network states.
-       * @type {!Array<!OncMojo.NetworkStateProperties>}
        */
       networks: Array,
 
       /**
        * Always-on VPN operating mode.
-       * @type {!AlwaysOnVpnMode|undefined}
        */
       mode: {
         type: Number,
@@ -52,7 +54,6 @@
 
       /**
        * Always-on VPN service automatically started on login.
-       * @type {string|undefined}
        */
       service: {
         type: String,
@@ -61,44 +62,41 @@
     };
   }
 
+  mode: AlwaysOnVpnMode|undefined;
+  networks: OncMojo.NetworkStateProperties[];
+  service: string|undefined;
+
   /**
    * Tells whether the always-on VPN main toggle is disabled or not. The toggle
    * is disabled when there's no compatible VPN networks available.
-   * @return {boolean}
-   * @private
    */
-  shouldDisableAlwaysOnVpn_() {
+  private shouldDisableAlwaysOnVpn_(): boolean {
     return this.networks.length === 0;
   }
 
   /**
    * Computes the visibility of always-on VPN networks list and lockdown toggle.
    * These settings are visible when always-on VPN is enabled.
-   * @return {boolean}
-   * @private
    */
-  shouldShowAlwaysOnVpnOptions_() {
+  private shouldShowAlwaysOnVpnOptions_(): boolean {
     return !this.shouldDisableAlwaysOnVpn_() &&
         this.mode !== AlwaysOnVpnMode.kOff;
   }
 
   /**
    * Computes the checked value for the always-on VPN enabled toggle.
-   * @returns {boolean}
-   * @private
    */
-  computeAlwaysOnVpnEnabled_() {
+  private computeAlwaysOnVpnEnabled_(): boolean {
     return !this.shouldDisableAlwaysOnVpn_() &&
         this.mode !== AlwaysOnVpnMode.kOff;
   }
 
   /**
    * Handles a state change on always-on VPN enable toggle.
-   * @param {!Event} event
-   * @private
    */
-  onAlwaysOnEnableChanged_(event) {
-    if (!event.target.checked) {
+  private onAlwaysOnEnableChanged_(event: Event): void {
+    const toggleEl = cast(event.target, CrToggleElement);
+    if (!toggleEl.checked) {
       this.mode = AlwaysOnVpnMode.kOff;
       return;
     }
@@ -107,27 +105,24 @@
 
   /**
    * Deduces the lockdown state from the always-on VPN mode.
-   * @return {boolean}
-   * @private
    */
-  computeAlwaysOnVpnLockdown_() {
+  private computeAlwaysOnVpnLockdown_(): boolean {
     return this.mode === AlwaysOnVpnMode.kStrict;
   }
 
   /**
    * Handles a lockdown toggle state change. It reflects the change on the
    * current always-on VPN mode.
-   * @param {!Event} event
-   * @private
    */
-  onAlwaysOnVpnLockdownChanged_(event) {
+  private onAlwaysOnVpnLockdownChanged_(event: Event): void {
     if (this.mode === AlwaysOnVpnMode.kOff) {
       // The event should not be fired when always-on VPN is disabled (the
       // enable toggle is disabled).
       return;
     }
-    this.mode = event.target.checked ? AlwaysOnVpnMode.kStrict :
-                                       AlwaysOnVpnMode.kBestEffort;
+    const toggleEl = cast(event.target, CrToggleElement);
+    this.mode = toggleEl.checked ? AlwaysOnVpnMode.kStrict :
+                                   AlwaysOnVpnMode.kBestEffort;
   }
 
   /**
@@ -136,12 +131,9 @@
    * @return {!Array<{name: string, value: string, selected: boolean}>}
    * @private
    */
-  getAlwaysOnVpnListOptions_() {
-    /** @type {!Array<{name: string, value: string, selected: boolean}>} */
-    const options = [];
-    /** @type {string} */
-    const currentService = /** @type {string} */ (this.service);
-    /** @type {boolean} */
+  private getAlwaysOnVpnListOptions_(): VpnServiceOption[] {
+    const options: VpnServiceOption[] = [];
+    const currentService = this.service;
     let serviceIsInList = false;
 
     if (!this.networks) {
@@ -155,10 +147,10 @@
         value: state.guid,
         selected: currentService === state.guid,
       });
-      serviceIsInList |= (currentService === state.guid);
+      serviceIsInList = serviceIsInList || (currentService === state.guid);
     });
 
-    // The current always-on VPN service is not in the VPN network list, it
+    // If the current always-on VPN service is not in the VPN network list, it
     // needs a placeholder.
     if (!serviceIsInList) {
       options.unshift({
@@ -171,12 +163,15 @@
     return options;
   }
 
-  /**
-   * @param {!Event} event
-   * @private
-   */
-  onAlwaysOnVpnServiceChanged_(event) {
-    this.service = /** @type {string} */ (event.target.value);
+  private onAlwaysOnVpnServiceChanged_(event: Event): void {
+    const selectEl = cast(event.target, HTMLSelectElement);
+    this.service = selectEl.value;
+  }
+}
+
+declare global {
+  interface HTMLElementTagNameMap {
+    [NetworkAlwaysOnVpnElement.is]: NetworkAlwaysOnVpnElement;
   }
 }
 
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/network_proxy_section.js b/chrome/browser/resources/settings/chromeos/internet_page/network_proxy_section.ts
similarity index 63%
rename from chrome/browser/resources/settings/chromeos/internet_page/network_proxy_section.js
rename to chrome/browser/resources/settings/chromeos/internet_page/network_proxy_section.ts
index a9cec8b..1d097a0 100644
--- a/chrome/browser/resources/settings/chromeos/internet_page/network_proxy_section.js
+++ b/chrome/browser/resources/settings/chromeos/internet_page/network_proxy_section.ts
@@ -13,56 +13,62 @@
 import 'chrome://resources/cr_elements/cr_button/cr_button.js';
 import 'chrome://resources/cr_elements/cr_dialog/cr_dialog.js';
 import 'chrome://resources/cr_elements/cr_hidden_style.css.js';
+import 'chrome://resources/cr_elements/cr_toggle/cr_toggle.js';
 import 'chrome://resources/polymer/v3_0/iron-flex-layout/iron-flex-layout-classes.js';
 import '../../controls/extension_controlled_indicator.js';
 import '../../settings_vars.css.js';
 import './internet_shared.css.js';
 import '../../controls/settings_toggle_button.js';
-import 'chrome://resources/cr_elements/cr_toggle/cr_toggle.js';
 
-import {I18nBehavior, I18nBehaviorInterface} from 'chrome://resources/ash/common/i18n_behavior.js';
 import {CrPolicyNetworkBehaviorMojo, CrPolicyNetworkBehaviorMojoInterface} from 'chrome://resources/ash/common/network/cr_policy_network_behavior_mojo.js';
+import {NetworkProxyElement} from 'chrome://resources/ash/common/network/network_proxy.js';
 import {OncMojo} from 'chrome://resources/ash/common/network/onc_mojo.js';
+import {CrDialogElement} from 'chrome://resources/cr_elements/cr_dialog/cr_dialog.js';
+import {CrToggleElement} from 'chrome://resources/cr_elements/cr_toggle/cr_toggle.js';
+import {I18nMixin, I18nMixinInterface} from 'chrome://resources/cr_elements/i18n_mixin.js';
 import {ManagedProperties, ManagedString} from 'chrome://resources/mojo/chromeos/services/network_config/public/mojom/cros_network_config.mojom-webui.js';
 import {OncSource} from 'chrome://resources/mojo/chromeos/services/network_config/public/mojom/network_types.mojom-webui.js';
-import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
+import {SettingsToggleButtonElement} from '../../controls/settings_toggle_button.js';
+import {PrefsMixin, PrefsMixinInterface} from '../../prefs/prefs_mixin.js';
 import {routes} from '../os_route.js';
-import {PrefsBehavior, PrefsBehaviorInterface} from '../prefs_behavior.js';
-import {RouteObserverBehavior, RouteObserverBehaviorInterface} from '../route_observer_behavior.js';
+import {RouteObserverMixin, RouteObserverMixinInterface} from '../route_observer_mixin.js';
+import {Route} from '../router.js';
 
-/**
- * @typedef {{name: (string|undefined),
- *            id: (string|undefined),
- *            canBeDisabled: (boolean|undefined)}}
- */
-let ExtensionInfo;
+import {getTemplate} from './network_proxy_section.html.js';
 
-/**
- * @constructor
- * @extends {PolymerElement}
- * @implements {CrPolicyNetworkBehaviorMojoInterface}
- * @implements {I18nBehaviorInterface}
- * @implements {PrefsBehaviorInterface}
- * @implements {RouteObserverBehaviorInterface}
- */
-const NetworkProxySectionElementBase = mixinBehaviors(
-    [
-      CrPolicyNetworkBehaviorMojo,
-      I18nBehavior,
-      PrefsBehavior,
-      RouteObserverBehavior,
-    ],
-    PolymerElement);
+interface ExtensionInfo {
+  name: string|undefined;
+  id: string|undefined;
+  canBeDisabled: boolean|undefined;
+}
 
-/** @polymer */
+interface NetworkProxySectionElement {
+  $: {
+    allowShared: SettingsToggleButtonElement,
+    confirmAllowSharedDialog: CrDialogElement,
+  };
+}
+
+const NetworkProxySectionElementBase =
+    mixinBehaviors(
+        [
+          CrPolicyNetworkBehaviorMojo,
+        ],
+        PrefsMixin(RouteObserverMixin(I18nMixin(PolymerElement)))) as {
+      new (): PolymerElement & I18nMixinInterface &
+          RouteObserverMixinInterface & PrefsMixinInterface &
+          CrPolicyNetworkBehaviorMojoInterface,
+    };
+
 class NetworkProxySectionElement extends NetworkProxySectionElementBase {
   static get is() {
-    return 'network-proxy-section';
+    return 'network-proxy-section' as const;
   }
 
   static get template() {
-    return html`{__html_template__}`;
+    return getTemplate();
   }
 
   static get properties() {
@@ -72,19 +78,16 @@
         value: false,
       },
 
-      /** @type {!ManagedProperties|undefined} */
       managedProperties: Object,
 
       /**
        * Reflects prefs.settings.use_shared_proxies for data binding.
-       * @private
        */
       useSharedProxies_: Boolean,
 
       /**
        * Indicates if the proxy if set by an extension in the Lacros primary
        * profile.
-       * @private
        */
       isProxySetByLacrosExtension_: Boolean,
 
@@ -92,8 +95,6 @@
        * Information about the extension in the Ash or Lacros browser which
        * controlling the proxy. Can be null is the proxy is not controlled by an
        * extension.
-       * @type {!ExtensionInfo|undefined}
-       * @private
        */
       extensionInfo_: Object,
     };
@@ -106,32 +107,32 @@
     ];
   }
 
+  disabled: boolean;
+  managedProperties: ManagedProperties|undefined;
+  private extensionInfo_: ExtensionInfo|undefined;
+  private isProxySetByLacrosExtension_: boolean;
+  private useSharedProxies_: boolean;
+
   /**
    * Returns the allow shared CrToggleElement.
-   * @return {?CrToggleElement}
    */
-  getAllowSharedToggle() {
-    return /** @type {?CrToggleElement} */ (
-        this.shadowRoot.querySelector('#allowShared'));
+  getAllowSharedToggle(): CrToggleElement|null {
+    return this.shadowRoot!.querySelector<CrToggleElement>('#allowShared');
   }
 
-  /** @protected RouteObserverBehavior */
-  currentRouteChanged(newRoute) {
+  override currentRouteChanged(newRoute: Route): void {
     if (newRoute === routes.NETWORK_DETAIL) {
-      /** @type {NetworkProxyElement} */ (
-          this.shadowRoot.querySelector('network-proxy'))
-          .reset();
+      this.shadowRoot!.querySelector<NetworkProxyElement>(
+                          'network-proxy')!.reset();
     }
   }
 
-  /** @private */
-  useSharedProxiesChanged_() {
+  private useSharedProxiesChanged_(): void {
     const pref = this.getPref('settings.use_shared_proxies');
     this.useSharedProxies_ = !!pref && !!pref.value;
   }
 
-  /** @private */
-  extensionProxyChanged_() {
+  private extensionProxyChanged_(): void {
     if (this.proxySetByAshExtension_()) {
       return;
     }
@@ -147,11 +148,7 @@
     }
   }
 
-  /**
-   * @return {boolean}
-   * @private
-   */
-  proxySetByAshExtension_() {
+  private proxySetByAshExtension_(): boolean {
     const property = this.getProxySettingsTypeProperty_();
     if (!property || !this.isExtensionControlled(property)) {
       return false;
@@ -167,27 +164,17 @@
   /**
    * Return true if the proxy is controlled by an extension in the Ash Browser
    * or in the Lacros Browser.
-   * @returns {boolean}
-   * @private
    */
-  isProxySetByExtension_() {
+  private isProxySetByExtension_(): boolean {
     return this.proxySetByAshExtension_() || this.isProxySetByLacrosExtension_;
   }
 
-  /**
-   * @return {boolean}
-   * @private
-   */
-  isShared_() {
-    return this.managedProperties.source === OncSource.kDevice ||
-        this.managedProperties.source === OncSource.kDevicePolicy;
+  private isShared_(): boolean {
+    return this.managedProperties!.source === OncSource.kDevice ||
+        this.managedProperties!.source === OncSource.kDevicePolicy;
   }
 
-  /**
-   * @return {!ManagedString|undefined}
-   * @private
-   */
-  getProxySettingsTypeProperty_() {
+  private getProxySettingsTypeProperty_(): ManagedString|undefined {
     if (!this.managedProperties) {
       return undefined;
     }
@@ -195,34 +182,20 @@
     return proxySettings ? proxySettings.type : undefined;
   }
 
-  /**
-   * @param {boolean} allowShared
-   * @return {string}
-   * @private
-   */
-  getAllowSharedDialogTitle_(allowShared) {
+  private getAllowSharedDialogTitle_(allowShared: boolean): string {
     if (allowShared) {
       return this.i18n('networkProxyAllowSharedDisableWarningTitle');
     }
     return this.i18n('networkProxyAllowSharedEnableWarningTitle');
   }
 
-  /**
-   * @return {boolean}
-   * @private
-   */
-  shouldShowNetworkPolicyIndicator_() {
+  private shouldShowNetworkPolicyIndicator_(): boolean {
     const property = this.getProxySettingsTypeProperty_();
     return !!property && !this.isProxySetByExtension_() &&
         this.isNetworkPolicyEnforced(property);
   }
 
-  /**
-   * @param {!OncMojo.ManagedProperty} property
-   * @return {boolean}
-   * @private
-   */
-  shouldShowAllowShared_(property) {
+  private shouldShowAllowShared_(_property: OncMojo.ManagedProperty): boolean {
     if (!this.isShared_()) {
       return false;
     }
@@ -235,39 +208,38 @@
   /**
    * Handles the change event for the shared proxy checkbox. Shows a
    * confirmation dialog.
-   * @param {!Event} event
-   * @private
    */
-  onAllowSharedProxiesChange_(event) {
+  private onAllowSharedProxiesChange_(): void {
     this.$.confirmAllowSharedDialog.showModal();
   }
 
   /**
    * Handles the shared proxy confirmation dialog 'Confirm' button.
-   * @private
    */
-  onAllowSharedDialogConfirm_() {
-    /** @type {!SettingsToggleButtonElement} */ (this.$.allowShared)
-        .sendPrefChange();
+  private onAllowSharedDialogConfirm_(): void {
+    this.$.allowShared.sendPrefChange();
     this.$.confirmAllowSharedDialog.close();
   }
 
   /**
    * Handles the shared proxy confirmation dialog 'Cancel' button or a cancel
    * event.
-   * @private
    */
-  onAllowSharedDialogCancel_() {
-    /** @type {!SettingsToggleButtonElement} */ (this.$.allowShared)
-        .resetToPrefValue();
+  private onAllowSharedDialogCancel_(): void {
+    this.$.allowShared.resetToPrefValue();
     this.$.confirmAllowSharedDialog.close();
   }
 
-  /** @private */
-  onAllowSharedDialogClose_() {
+  private onAllowSharedDialogClose_(): void {
     this.$.allowShared.focus();
   }
 }
 
+declare global {
+  interface HTMLElementTagNameMap {
+    [NetworkProxySectionElement.is]: NetworkProxySectionElement;
+  }
+}
+
 customElements.define(
     NetworkProxySectionElement.is, NetworkProxySectionElement);
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/network_summary.js b/chrome/browser/resources/settings/chromeos/internet_page/network_summary.ts
similarity index 68%
rename from chrome/browser/resources/settings/chromeos/internet_page/network_summary.js
rename to chrome/browser/resources/settings/chromeos/internet_page/network_summary.ts
index a097ef0..52a65ee 100644
--- a/chrome/browser/resources/settings/chromeos/internet_page/network_summary.js
+++ b/chrome/browser/resources/settings/chromeos/internet_page/network_summary.ts
@@ -11,33 +11,32 @@
 import './network_summary_item.js';
 
 import {getHotspotConfig} from 'chrome://resources/ash/common/hotspot/cros_hotspot_config.js';
-import {MojoInterfaceProvider, MojoInterfaceProviderImpl} from 'chrome://resources/ash/common/network/mojo_interface_provider.js';
+import {CrosHotspotConfigInterface, CrosHotspotConfigObserverReceiver, HotspotAllowStatus, HotspotInfo} from 'chrome://resources/ash/common/hotspot/cros_hotspot_config.mojom-webui.js';
+import {MojoInterfaceProviderImpl} from 'chrome://resources/ash/common/network/mojo_interface_provider.js';
 import {NetworkListenerBehavior, NetworkListenerBehaviorInterface} from 'chrome://resources/ash/common/network/network_listener_behavior.js';
 import {OncMojo} from 'chrome://resources/ash/common/network/onc_mojo.js';
-import {loadTimeData} from 'chrome://resources/ash/common/load_time_data.m.js';
-import {CrosHotspotConfigInterface, CrosHotspotConfigObserverInterface, CrosHotspotConfigObserverReceiver, HotspotAllowStatus, HotspotInfo, HotspotState} from 'chrome://resources/mojo/chromeos/ash/services/hotspot_config/public/mojom/cros_hotspot_config.mojom-webui.js';
+import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
 import {CrosNetworkConfigRemote, FilterType, GlobalPolicy, NO_LIMIT} from 'chrome://resources/mojo/chromeos/services/network_config/public/mojom/cros_network_config.mojom-webui.js';
 import {DeviceStateType, NetworkType, OncSource} from 'chrome://resources/mojo/chromeos/services/network_config/public/mojom/network_types.mojom-webui.js';
-import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
+import {castExists} from '../assert_extras.js';
+
+import {getTemplate} from './network_summary.html.js';
 import {NetworkSummaryItemElement} from './network_summary_item.js';
 
-/**
- * @constructor
- * @extends {PolymerElement}
- * @implements {NetworkListenerBehaviorInterface}
- */
 const NetworkSummaryElementBase =
-    mixinBehaviors([NetworkListenerBehavior], PolymerElement);
+    mixinBehaviors([NetworkListenerBehavior], PolymerElement) as {
+      new (): PolymerElement & NetworkListenerBehaviorInterface,
+    };
 
-/** @polymer */
 class NetworkSummaryElement extends NetworkSummaryElementBase {
   static get is() {
-    return 'network-summary';
+    return 'network-summary' as const;
   }
 
   static get template() {
-    return html`{__html_template__}`;
+    return getTemplate();
   }
 
   static get properties() {
@@ -45,7 +44,6 @@
       /**
        * Highest priority connected network or null. Set here to update
        * internet-page which updates internet-subpage and internet-detail-page.
-       * @type {?OncMojo.NetworkStateProperties}
        */
       defaultNetwork: {
         type: Object,
@@ -57,17 +55,16 @@
        * The device state for each network device type. We initialize this to
        * include a disabled WiFi type since WiFi is always present. This reduces
        * the amount of visual change on first load.
-       * @private {!Object<!OncMojo.DeviceStateProperties>}
        */
       deviceStates: {
         type: Object,
         value() {
-          const result = {};
-          result[NetworkType.kWiFi] = {
-            deviceState: DeviceStateType.kDisabled,
-            type: NetworkType.kWiFi,
+          return {
+            [NetworkType.kWiFi]: {
+              deviceState: DeviceStateType.kDisabled,
+              type: NetworkType.kWiFi,
+            },
           };
-          return result;
         },
         notify: true,
       },
@@ -75,7 +72,6 @@
       /**
        * Array of active network states, one per device type. Initialized to
        * include a default WiFi state (see deviceStates comment).
-       * @private {!Array<!OncMojo.NetworkStateProperties>}
        */
       activeNetworkStates_: {
         type: Array,
@@ -86,26 +82,22 @@
 
       /**
        * List of network state data for each network type.
-       * @private {!Object<!Array<!OncMojo.NetworkStateProperties>>}
        */
       networkStateLists_: {
         type: Object,
         value() {
-          const result = {};
-          result[NetworkType.kWiFi] = [];
-          return result;
+          return {
+            [NetworkType.kWiFi]: [],
+          };
         },
       },
 
-      /** @private {!GlobalPolicy|undefined} */
       globalPolicy_: Object,
 
-      /** @private {!HotspotInfo} */
       hotspotInfo_: Object,
 
       /**
        * Return true if hotspot feature flag is enabled.
-       * @private
        */
       isHotspotFeatureEnabled_: {
         type: Boolean,
@@ -117,47 +109,47 @@
     };
   }
 
-  /** @override */
+  defaultNetwork: OncMojo.NetworkStateProperties|null;
+  deviceStates: Record<NetworkType, OncMojo.DeviceStateProperties>;
+  private activeNetworkIds_: Set<string>|null;
+  private activeNetworkStates_: OncMojo.NetworkStateProperties[];
+  private crosHotspotConfig_: CrosHotspotConfigInterface;
+  private crosHotspotConfigObserverReceiver_: CrosHotspotConfigObserverReceiver;
+  private globalPolicy_: GlobalPolicy|undefined;
+  private hotspotInfo_: HotspotInfo;
+  private isHotspotFeatureEnabled_: boolean;
+  private networkConfig_: CrosNetworkConfigRemote;
+  private networkStateLists_:
+      Record<NetworkType, OncMojo.NetworkStateProperties[]>;
+
   constructor() {
     super();
 
     /**
      * Set of GUIDs identifying active networks, one for each type.
-     * @private {?Set<string>}
      */
     this.activeNetworkIds_ = null;
 
-    /** @private {!CrosNetworkConfigRemote} */
     this.networkConfig_ =
         MojoInterfaceProviderImpl.getInstance().getMojoServiceRemote();
 
     if (this.isHotspotFeatureEnabled_) {
-      /** @private {!CrosHotspotConfigInterface} */
       this.crosHotspotConfig_ = getHotspotConfig();
-
-      /**
-       * @private {!CrosHotspotConfigObserverReceiver}
-       */
       this.crosHotspotConfigObserverReceiver_ =
-          new CrosHotspotConfigObserverReceiver(
-              /**
-               * @type {!CrosHotspotConfigObserverInterface}
-               */
-              (this));
+          new CrosHotspotConfigObserverReceiver(this);
     }
   }
 
-  /** @override */
-  ready() {
+  override ready(): void {
     super.ready();
+
     if (this.isHotspotFeatureEnabled_) {
       this.crosHotspotConfig_.addObserver(
           this.crosHotspotConfigObserverReceiver_.$.bindNewPipeAndPassRemote());
     }
   }
 
-  /** @override */
-  connectedCallback() {
+  override connectedCallback(): void {
     super.connectedCallback();
 
     this.getNetworkLists_();
@@ -170,30 +162,26 @@
     }
   }
 
-  /** override */
-  onHotspotInfoChanged() {
-    this.crosHotspotConfig_.getHotspotInfo().then(response => {
-      this.hotspotInfo_ = response.hotspotInfo;
-    });
+  async onHotspotInfoChanged(): Promise<void> {
+    const response = await this.crosHotspotConfig_.getHotspotInfo();
+    this.hotspotInfo_ = response.hotspotInfo;
   }
 
   /**
    * CrosNetworkConfigObserver impl
-   * @param {!string} userhash
    */
-  onPoliciesApplied(userhash) {
-    this.networkConfig_.getGlobalPolicy().then(response => {
-      this.globalPolicy_ = response.result;
-    });
+  override async onPoliciesApplied(_userhash: string): Promise<void> {
+    const response = await this.networkConfig_.getGlobalPolicy();
+    this.globalPolicy_ = response.result;
   }
 
   /**
    * CrosNetworkConfigObserver impl
    * Updates any matching existing active networks. Note: newly active networks
    * will trigger onNetworkStateListChanged which triggers getNetworkLists_.
-   * @param {!Array<OncMojo.NetworkStateProperties>} networks
    */
-  onActiveNetworksChanged(networks) {
+  override onActiveNetworksChanged(networks: OncMojo.NetworkStateProperties[]):
+      void {
     if (!this.activeNetworkIds_) {
       // Initial list of networks not received yet.
       return;
@@ -208,68 +196,60 @@
   }
 
   /** CrosNetworkConfigObserver impl */
-  onNetworkStateListChanged() {
+  override onNetworkStateListChanged(): void {
     this.getNetworkLists_();
   }
 
   /** CrosNetworkConfigObserver impl */
-  onDeviceStateListChanged() {
+  override onDeviceStateListChanged(): void {
     this.getNetworkLists_();
   }
 
   /**
    * Returns the network-summary-item element corresponding to the
    * |networkType|.
-   * @param {!NetworkType} networkType
-   * @return {?NetworkSummaryItemElement}
    */
-  getNetworkRow(networkType) {
+  getNetworkRow(networkType: NetworkType): NetworkSummaryItemElement|null {
     const networkTypeString = OncMojo.getNetworkTypeString(networkType);
-    return /** @type {NetworkSummaryItemElement} */ (
-        this.shadowRoot.querySelector(`#${networkTypeString}`));
+    return this.shadowRoot!.querySelector<NetworkSummaryItemElement>(
+        `#${networkTypeString}`);
   }
 
   /**
    * Requests the list of device states and network states from Chrome.
    * Updates deviceStates, activeNetworkStates, and networkStateLists once the
    * results are returned from Chrome.
-   * @private
    */
-  getNetworkLists_() {
+  private async getNetworkLists_(): Promise<void> {
     // First get the device states.
-    this.networkConfig_.getDeviceStateList().then(response => {
-      // Second get the network states.
-      this.getNetworkStates_(response.result);
-    });
+    const response = await this.networkConfig_.getDeviceStateList();
+    // Second get the network states.
+    this.getNetworkStates_(response.result);
   }
 
   /**
    * Requests the list of network states from Chrome. Updates
    * activeNetworkStates and networkStateLists once the results are returned
    * from Chrome.
-   * @param {!Array<!OncMojo.DeviceStateProperties>} deviceStateList
-   * @private
    */
-  getNetworkStates_(deviceStateList) {
+  private async getNetworkStates_(
+      deviceStateList: OncMojo.DeviceStateProperties[]): Promise<void> {
     const filter = {
       filter: FilterType.kVisible,
       limit: NO_LIMIT,
       networkType: NetworkType.kAll,
     };
-    this.networkConfig_.getNetworkStateList(filter).then(response => {
-      this.updateNetworkStates_(response.result, deviceStateList);
-    });
+    const response = await this.networkConfig_.getNetworkStateList(filter);
+    this.updateNetworkStates_(response.result, deviceStateList);
   }
 
   /**
    * Called after network states are received from getNetworks.
-   * @param {!Array<!OncMojo.NetworkStateProperties>} networkStates The state
-   *     properties for all visible networks.
-   * @param {!Array<!OncMojo.DeviceStateProperties>} deviceStateList
-   * @private
    */
-  updateNetworkStates_(networkStates, deviceStateList) {
-    const newDeviceStates = {};
+  private updateNetworkStates_(
+      networkStates: OncMojo.NetworkStateProperties[],
+      deviceStateList: OncMojo.DeviceStateProperties[]): void {
+    const newDeviceStates: Record<string, OncMojo.DeviceStateProperties> = {};
     for (const device of deviceStateList) {
       newDeviceStates[device.type] = device;
     }
@@ -284,17 +264,17 @@
 
     // Clear any current networks.
     const activeNetworkStatesByType =
-        /** @type {!Map<NetworkType, !OncMojo.NetworkStateProperties>} */
-        (new Map());
+        new Map<NetworkType, OncMojo.NetworkStateProperties>();
 
     // Complete list of states by type.
-    const newNetworkStateLists = {};
+    const newNetworkStateLists:
+        Record<string, OncMojo.NetworkStateProperties[]> = {};
     for (const type of orderedNetworkTypes) {
       newNetworkStateLists[type] = [];
     }
 
-    let firstConnectedNetwork = null;
-    networkStates.forEach(function(networkState) {
+    let firstConnectedNetwork: OncMojo.NetworkStateProperties|null = null;
+    networkStates.forEach((networkState) => {
       const type = networkState.type;
       if (!activeNetworkStatesByType.has(type)) {
         activeNetworkStatesByType.set(type, networkState);
@@ -311,7 +291,7 @@
 
     // Push the active networks onto newActiveNetworkStates in order based on
     // device priority, creating an empty state for devices with no networks.
-    const newActiveNetworkStates = [];
+    const newActiveNetworkStates: OncMojo.NetworkStateProperties[] = [];
     this.activeNetworkIds_ = new Set();
     for (const type of orderedNetworkTypes) {
       const device = newDeviceStates[type];
@@ -340,8 +320,8 @@
 
       // Note: The active state for 'Cellular' may be a Tether network if both
       // types are enabled but no Cellular network exists (edge case).
-      const networkState =
-          this.getActiveStateForType_(activeNetworkStatesByType, type);
+      const networkState = castExists(
+          this.getActiveStateForType_(activeNetworkStatesByType, type));
       if (networkState.source === OncSource.kNone &&
           device.deviceState === DeviceStateType.kProhibited) {
         // Prohibited technologies are enforced by the device policy.
@@ -364,13 +344,10 @@
    * Returns the active network state for |type| or a default network state.
    * If there is no 'Cellular' network, return the active 'Tether' network if
    * any since the two types are represented by the same section / subpage.
-   * @param {!Map<NetworkType, !OncMojo.NetworkStateProperties>}
-   *     activeStatesByType
-   * @param {!NetworkType} type
-   * @return {!OncMojo.NetworkStateProperties|undefined}
-   * @private
    */
-  getActiveStateForType_(activeStatesByType, type) {
+  private getActiveStateForType_(
+      activeStatesByType: Map<NetworkType, OncMojo.NetworkStateProperties>,
+      type: NetworkType): OncMojo.NetworkStateProperties|undefined {
     let activeState = activeStatesByType.get(type);
     if (!activeState && type === NetworkType.kCellular) {
       activeState = activeStatesByType.get(NetworkType.kTether);
@@ -380,30 +357,21 @@
 
   /**
    * Provides an id string for summary items. Used in tests.
-   * @param {!OncMojo.NetworkStateProperties} network
-   * @return {string}
-   * @private
    */
-  getTypeString_(network) {
+  private getTypeString_(network: OncMojo.NetworkStateProperties): string {
     return OncMojo.getNetworkTypeString(network.type);
   }
 
-  /**
-   * @param {!Object<!OncMojo.DeviceStateProperties>} deviceStates
-   * @return {!OncMojo.DeviceStateProperties|undefined}
-   * @private
-   */
-  getTetherDeviceState_(deviceStates) {
-    return this.deviceStates[NetworkType.kTether];
+  private getTetherDeviceState_(
+      deviceStates: Record<NetworkType, OncMojo.DeviceStateProperties>):
+      OncMojo.DeviceStateProperties|undefined {
+    return deviceStates[NetworkType.kTether];
   }
 
   /**
    * Return whether hotspot row should be shown in network summary.
-   *
-   * @return {boolean}
-   * @private
    */
-  shouldShowHotspotSummary_() {
+  private shouldShowHotspotSummary_(): boolean {
     if (!this.isHotspotFeatureEnabled_ || !this.hotspotInfo_) {
       return false;
     }
@@ -415,4 +383,10 @@
   }
 }
 
+declare global {
+  interface HTMLElementTagNameMap {
+    [NetworkSummaryElement.is]: NetworkSummaryElement;
+  }
+}
+
 customElements.define(NetworkSummaryElement.is, NetworkSummaryElement);
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/network_summary_item.js b/chrome/browser/resources/settings/chromeos/internet_page/network_summary_item.ts
similarity index 70%
rename from chrome/browser/resources/settings/chromeos/internet_page/network_summary_item.js
rename to chrome/browser/resources/settings/chromeos/internet_page/network_summary_item.ts
index ea14c5c4..b0ad7d8e 100644
--- a/chrome/browser/resources/settings/chromeos/internet_page/network_summary_item.js
+++ b/chrome/browser/resources/settings/chromeos/internet_page/network_summary_item.ts
@@ -16,36 +16,35 @@
 import 'chrome://resources/cr_elements/cr_shared_vars.css.js';
 import 'chrome://resources/polymer/v3_0/iron-flex-layout/iron-flex-layout-classes.js';
 
+import {CrPolicyIndicatorType} from 'chrome://resources/ash/common/cr_policy_indicator_behavior.js';
 import {getSimSlotCount} from 'chrome://resources/ash/common/network/cellular_utils.js';
 import {CrPolicyNetworkBehaviorMojo, CrPolicyNetworkBehaviorMojoInterface} from 'chrome://resources/ash/common/network/cr_policy_network_behavior_mojo.js';
 import {OncMojo} from 'chrome://resources/ash/common/network/onc_mojo.js';
-import {I18nBehavior, I18nBehaviorInterface} from 'chrome://resources/ash/common/i18n_behavior.js';
-import {CrPolicyIndicatorType} from 'chrome://resources/ash/common/cr_policy_indicator_behavior.js';
-import {assert, assertNotReached} from 'chrome://resources/ash/common/assert.js';
-import {loadTimeData} from 'chrome://resources/ash/common/load_time_data.m.js';
+import {CrToggleElement} from 'chrome://resources/cr_elements/cr_toggle/cr_toggle.js';
+import {I18nMixin, I18nMixinInterface} from 'chrome://resources/cr_elements/i18n_mixin.js';
+import {assert, assertNotReached} from 'chrome://resources/js/assert_ts.js';
+import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
 import {GlobalPolicy, VpnType} from 'chrome://resources/mojo/chromeos/services/network_config/public/mojom/cros_network_config.mojom-webui.js';
 import {ConnectionStateType, DeviceStateType, NetworkType, OncSource, PortalState} from 'chrome://resources/mojo/chromeos/services/network_config/public/mojom/network_types.mojom-webui.js';
-import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {InternetPageBrowserProxy, InternetPageBrowserProxyImpl} from './internet_page_browser_proxy.js';
+import {getTemplate} from './network_summary_item.html.js';
 
-/**
- * @constructor
- * @extends {PolymerElement}
- * @implements {CrPolicyNetworkBehaviorMojoInterface}
- * @implements {I18nBehaviorInterface}
- */
 const NetworkSummaryItemElementBase =
-    mixinBehaviors([CrPolicyNetworkBehaviorMojo, I18nBehavior], PolymerElement);
+    mixinBehaviors([CrPolicyNetworkBehaviorMojo], I18nMixin(PolymerElement)) as
+    {
+      new (): PolymerElement & I18nMixinInterface &
+          CrPolicyNetworkBehaviorMojoInterface,
+    };
 
-/** @polymer */
 export class NetworkSummaryItemElement extends NetworkSummaryItemElementBase {
   static get is() {
-    return 'network-summary-item';
+    return 'network-summary-item' as const;
   }
 
   static get template() {
-    return html`{__html_template__}`;
+    return getTemplate();
   }
 
   static get properties() {
@@ -53,7 +52,6 @@
       /**
        * Device state for the network type. This might briefly be undefined if
        * a device becomes unavailable.
-       * @type {!OncMojo.DeviceStateProperties|undefined}
        */
       deviceState: {
         type: Object,
@@ -63,19 +61,16 @@
       /**
        * If both Cellular and Tether technologies exist, we combine the
        * sections and set this to the device state for Tether.
-       * @type {!OncMojo.DeviceStateProperties|undefined}
        */
       tetherDeviceState: Object,
 
       /**
        * Network state for the active network.
-       * @type {!OncMojo.NetworkStateProperties|undefined}
        */
       activeNetworkState: Object,
 
       /**
        * List of all network state data for the network type.
-       * @type {!Array<!OncMojo.NetworkStateProperties>}
        */
       networkStateList: {
         type: Array,
@@ -87,13 +82,11 @@
       /**
        * Title line describing the network type to appear in the row's top
        * line. If it is undefined, the title text is set to a default value.
-       * @type {string|undefined}
        */
       networkTitleText: String,
 
       /**
        * Whether to show technology badge on mobile network icon.
-       * @private
        */
       showTechnologyBadge_: {
         type: Boolean,
@@ -105,7 +98,6 @@
 
       /**
        * Return true if captivePortalUI2022 feature flag is enabled.
-       * @private
        */
       isCaptivePortalUI2022Enabled_: {
         type: Boolean,
@@ -115,31 +107,32 @@
         },
       },
 
-      /** @private {!GlobalPolicy|undefined} */
       globalPolicy: Object,
     };
   }
 
+  activeNetworkState: OncMojo.NetworkStateProperties|undefined;
+  deviceState: OncMojo.DeviceStateProperties|undefined;
+  globalPolicy: GlobalPolicy|undefined;
+  networkStateList: OncMojo.NetworkStateProperties[];
+  networkTitleText: string|undefined;
+  tetherDeviceState: OncMojo.DeviceStateProperties|undefined;
+  private browserProxy_: InternetPageBrowserProxy;
+  private isCaptivePortalUI2022Enabled_: boolean;
+  private showTechnologyBadge_: boolean;
+
   constructor() {
     super();
 
-    /** @private  {!InternetPageBrowserProxy} */
     this.browserProxy_ = InternetPageBrowserProxyImpl.getInstance();
   }
 
-  /*
-   * Returns the device enabled toggle element.
-   * @return {?CrToggleElement}
-   */
-  getDeviceEnabledToggle() {
-    return this.shadowRoot.querySelector('#deviceEnabledButton');
+  getDeviceEnabledToggle(): CrToggleElement|null {
+    return this.shadowRoot!.querySelector<CrToggleElement>(
+        '#deviceEnabledButton');
   }
 
-  /**
-   * @return {string}
-   * @private
-   */
-  getNetworkStateText_() {
+  private getNetworkStateText_(): string {
     // If SIM Locked, show warning message instead of connection state.
     if (this.shouldShowLockedWarningMessage_(this.deviceState)) {
       return this.i18n('networkSimLockedSubtitle');
@@ -149,7 +142,7 @@
     }
 
     if (this.isCaptivePortalUI2022Enabled_ &&
-        this.isPortalState_(this.activeNetworkState.portalState)) {
+        this.isPortalState_(this.activeNetworkState!.portalState)) {
       return this.i18n('networkListItemSignIn');
     }
 
@@ -181,12 +174,8 @@
     return this.i18n('deviceOff');
   }
 
-  /**
-   * @param {!OncMojo.NetworkStateProperties|undefined} networkState
-   * @return {string}
-   * @private
-   */
-  getConnectionStateText_(networkState) {
+  private getConnectionStateText_(networkState: OncMojo.NetworkStateProperties|
+                                  undefined): string {
     if (!networkState || !networkState.guid) {
       return '';
     }
@@ -207,12 +196,8 @@
     return this.i18n('networkListItemNotConnected');
   }
 
-  /**
-   * @param {!OncMojo.NetworkStateProperties} activeNetworkState
-   * @return {boolean}
-   * @private
-   */
-  showPolicyIndicator_(activeNetworkState) {
+  private showPolicyIndicator_(activeNetworkState:
+                                   OncMojo.NetworkStateProperties): boolean {
     return (activeNetworkState !== undefined &&
             OncMojo.connectionStateIsConnected(
                 activeNetworkState.connectionState)) ||
@@ -221,30 +206,26 @@
   }
 
   /**
-   * @param {!OncMojo.NetworkStateProperties} activeNetworkState
-   * @return {!CrPolicyIndicatorType} Device policy indicator for VPN when
+   * @return Device policy indicator for VPN when
    *     disabled by policy and an indicator corresponding to the source of the
    *     active network state otherwise.
-   * @private
    */
-  getPolicyIndicatorType_(activeNetworkState) {
+  private getPolicyIndicatorType_(activeNetworkState:
+                                      OncMojo.NetworkStateProperties):
+      CrPolicyIndicatorType {
     if (this.isProhibitedVpn_()) {
       return this.getIndicatorTypeForSource(OncSource.kDevicePolicy);
     }
     return this.getIndicatorTypeForSource(activeNetworkState.source);
   }
 
-  /**
-   * @param {!OncMojo.DeviceStateProperties|undefined} deviceState
-   * @return {boolean}
-   * @private
-   */
-  showSimInfo_(deviceState) {
+  private showSimInfo_(deviceState: OncMojo.DeviceStateProperties|
+                       undefined): boolean {
     if (!deviceState || deviceState.type !== NetworkType.kCellular) {
       return false;
     }
 
-    const {pSimSlots, eSimSlots} = getSimSlotCount(deviceState);
+    const {eSimSlots} = getSimSlotCount(deviceState);
     if (eSimSlots > 0) {
       // Do not show simInfo if we are using an eSIM enabled device.
       return false;
@@ -252,27 +233,20 @@
     return this.simLocked_(deviceState);
   }
 
-  /**
-   * @param {!OncMojo.NetworkStateProperties|undefined} activeNetworkState
-   * @param {!OncMojo.DeviceStateProperties|undefined} deviceState
-   * @return {string}
-   * @private
-   */
-  getNetworkStateClass_(activeNetworkState, deviceState) {
+  private getNetworkStateClass_(
+      activeNetworkState: OncMojo.NetworkStateProperties|undefined,
+      deviceState: OncMojo.DeviceStateProperties|undefined): string {
     if ((this.isCaptivePortalUI2022Enabled_ &&
-         this.isPortalState_(activeNetworkState.portalState)) ||
+         this.isPortalState_(activeNetworkState!.portalState)) ||
         this.shouldShowLockedWarningMessage_(deviceState)) {
       return 'warning-message';
     }
     return 'network-state';
   }
 
-  /**
-   * @param {!OncMojo.DeviceStateProperties|undefined} deviceState
-   * @return {boolean}
-   * @private
-   */
-  shouldShowLockedWarningMessage_(deviceState) {
+  private shouldShowLockedWarningMessage_(deviceState:
+                                              OncMojo.DeviceStateProperties|
+                                          undefined): boolean {
     if (!deviceState || deviceState.type !== NetworkType.kCellular ||
         !deviceState.simLockStatus) {
       return false;
@@ -287,12 +261,8 @@
     return !!deviceState.simLockStatus.lockType;
   }
 
-  /**
-   * @param {!OncMojo.DeviceStateProperties|undefined} deviceState
-   * @return {boolean}
-   * @private
-   */
-  simLocked_(deviceState) {
+  private simLocked_(deviceState: OncMojo.DeviceStateProperties|
+                     undefined): boolean {
     if (!deviceState) {
       return false;
     }
@@ -304,28 +274,23 @@
   }
 
   /**
-   * @param {!OncMojo.DeviceStateProperties|undefined} deviceState
-   * @return {boolean} True if the device is enabled or if it is a VPN or if
+   * @return True if the device is enabled or if it is a VPN or if
    *     we are in the state of inhibited. Note:
    *     This function will always return true for VPNs because VPNs can be
    *     disabled by policy only for built-in VPNs (OpenVPN & L2TP), but always
    *     enabled for other VPN providers. To know whether built-in VPNs are
    *     disabled, use builtInVpnProhibited_() instead.
-   * @private
    */
-  deviceIsEnabled_(deviceState) {
+  private deviceIsEnabled_(deviceState: OncMojo.DeviceStateProperties|
+                           undefined): boolean {
     return !!deviceState &&
         (deviceState.type === NetworkType.kVPN ||
          deviceState.deviceState === DeviceStateType.kEnabled ||
          OncMojo.deviceIsInhibited(deviceState));
   }
 
-  /**
-   * @param {!OncMojo.DeviceStateProperties|undefined} deviceState
-   * @return {boolean}
-   * @private
-   */
-  enableToggleIsVisible_(deviceState) {
+  private enableToggleIsVisible_(deviceState: OncMojo.DeviceStateProperties|
+                                 undefined): boolean {
     if (!deviceState) {
       return false;
     }
@@ -333,48 +298,35 @@
       case NetworkType.kEthernet:
       case NetworkType.kVPN:
         return false;
-
       case NetworkType.kTether:
         return true;
-
       case NetworkType.kWiFi:
         return deviceState.deviceState !== DeviceStateType.kUninitialized;
-
       case NetworkType.kCellular:
         if (deviceState.deviceState === DeviceStateType.kUninitialized) {
           return false;
         }
-
         // Toggle should be shown as long as we are not also showing the UI for
         // unlocking the SIM.
         return !this.showSimInfo_(deviceState);
     }
     assertNotReached();
-    return false;
   }
 
-  /**
-   * @param {!OncMojo.DeviceStateProperties|undefined} deviceState
-   * @return {boolean}
-   * @private
-   */
-  enableToggleIsEnabled_(deviceState) {
+  private enableToggleIsEnabled_(deviceState: OncMojo.DeviceStateProperties|
+                                 undefined): boolean {
     return this.enableToggleIsVisible_(deviceState) &&
-        deviceState.deviceState !== DeviceStateType.kProhibited &&
+        deviceState!.deviceState !== DeviceStateType.kProhibited &&
         !OncMojo.deviceIsInhibited(deviceState) &&
-        !OncMojo.deviceStateIsIntermediate(deviceState.deviceState);
+        !OncMojo.deviceStateIsIntermediate(deviceState!.deviceState);
   }
 
-  /**
-   * @param {!OncMojo.DeviceStateProperties|undefined} deviceState
-   * @return {string}
-   * @private
-   */
-  getToggleA11yString_(deviceState) {
+  private getToggleA11yString_(deviceState: OncMojo.DeviceStateProperties|
+                               undefined): string {
     if (!this.enableToggleIsVisible_(deviceState)) {
       return '';
     }
-    switch (deviceState.type) {
+    switch (deviceState!.type) {
       case NetworkType.kTether:
       case NetworkType.kCellular:
         return this.i18n('internetToggleMobileA11yLabel');
@@ -382,103 +334,80 @@
         return this.i18n('internetToggleWiFiA11yLabel');
     }
     assertNotReached();
-    return '';
   }
 
-  /**
-   * @param {!OncMojo.DeviceStateProperties|undefined} deviceState
-   * @return {string}
-   * @private
-   */
-  getToggleA11yDescribedBy_(deviceState) {
+  private getToggleA11yDescribedBy_(deviceState: OncMojo.DeviceStateProperties|
+                                    undefined): string {
     // Use network state text to describe toggle for uninitialized tether
     // device. This announces details about enabling bluetooth.
     if (this.enableToggleIsVisible_(deviceState) &&
-        deviceState.type === NetworkType.kTether &&
-        deviceState.deviceState === DeviceStateType.kUninitialized) {
+        deviceState!.type === NetworkType.kTether &&
+        deviceState!.deviceState === DeviceStateType.kUninitialized) {
       return 'networkState';
     }
     return '';
   }
 
   /**
-   * @return {boolean} True if VPNs are disabled by policy and the current
-   *     device is VPN.
-   * @private
+   * @return True if VPNs are disabled by policy and the current device is VPN.
    */
-  isProhibitedVpn_() {
+  private isProhibitedVpn_(): boolean {
     return !!this.deviceState && this.deviceState.type === NetworkType.kVPN &&
         this.builtInVpnProhibited_(this.deviceState);
   }
 
-  /**
-   * @param {!VpnType} vpnType
-   * @return {boolean}
-   * @private
-   */
-  isBuiltInVpnType_(vpnType) {
+  private isBuiltInVpnType_(vpnType: VpnType): boolean {
     return vpnType === VpnType.kL2TPIPsec || vpnType === VpnType.kOpenVPN;
   }
 
   /**
-   * @param {!Array<!OncMojo.NetworkStateProperties>} networkStateList
-   * @return {boolean} True if at least one non-native VPN is configured.
-   * @private
+   * @return True if at least one non-native VPN is configured.
    */
-  hasNonBuiltInVpn_(networkStateList) {
+  private hasNonBuiltInVpn_(networkStateList: OncMojo.NetworkStateProperties[]):
+      boolean {
     const nonBuiltInVpnIndex = networkStateList.findIndex((networkState) => {
-      return !this.isBuiltInVpnType_(networkState.typeState.vpn.type);
+      return !this.isBuiltInVpnType_(networkState.typeState.vpn!.type);
     });
     return nonBuiltInVpnIndex !== -1;
   }
 
   /**
-   * @param {!OncMojo.DeviceStateProperties|undefined} deviceState
-   * @return {boolean} True if the built-in VPNs are disabled by policy.
-   * @private
+   * @return True if the built-in VPNs are disabled by policy.
    */
-  builtInVpnProhibited_(deviceState) {
+  private builtInVpnProhibited_(deviceState: OncMojo.DeviceStateProperties|
+                                undefined): boolean {
     return !!deviceState &&
         deviceState.deviceState === DeviceStateType.kProhibited;
   }
 
   /**
-   * @param {!OncMojo.DeviceStateProperties|undefined} deviceState
-   * @param {!Array<!OncMojo.NetworkStateProperties>} networkStateList
-   * @return {boolean} True if there is any configured VPN for a non-disabled
+   * @return True if there is any configured VPN for a non-disabled
    *     VPN provider. Note: Only built-in VPN providers can be disabled by
    *     policy at the moment.
-   * @private
    */
-  anyVpnExists_(deviceState, networkStateList) {
+  private anyVpnExists_(
+      deviceState: OncMojo.DeviceStateProperties|undefined,
+      networkStateList: OncMojo.NetworkStateProperties[]): boolean {
     return this.hasNonBuiltInVpn_(networkStateList) ||
         (!this.builtInVpnProhibited_(deviceState) &&
          networkStateList.length > 0);
   }
 
-  /**
-   * @param {!OncMojo.NetworkStateProperties|undefined} activeNetworkState
-   * @param {!OncMojo.DeviceStateProperties|undefined} deviceState
-   * @param {!Array<!OncMojo.NetworkStateProperties>} networkStateList
-   * @return {boolean}
-   * @private
-   */
-  shouldShowDetails_(activeNetworkState, deviceState, networkStateList) {
+  private shouldShowDetails_(
+      activeNetworkState: OncMojo.NetworkStateProperties|undefined,
+      deviceState: OncMojo.DeviceStateProperties|undefined,
+      networkStateList: OncMojo.NetworkStateProperties[]): boolean {
     if (!!deviceState && deviceState.type === NetworkType.kVPN) {
       return this.anyVpnExists_(deviceState, networkStateList);
     }
 
     return this.deviceIsEnabled_(deviceState) &&
-        (!!activeNetworkState.guid || networkStateList.length > 0);
+        (!!activeNetworkState!.guid || networkStateList.length > 0);
   }
 
-  /**
-   * @param {!OncMojo.DeviceStateProperties|undefined} deviceState
-   * @param {!Array<!OncMojo.NetworkStateProperties>} networkStateList
-   * @return {boolean}
-   * @private
-   */
-  shouldShowSubpage_(deviceState, networkStateList) {
+  private shouldShowSubpage_(
+      deviceState: OncMojo.DeviceStateProperties|undefined,
+      networkStateList: OncMojo.NetworkStateProperties[]): boolean {
     if (!deviceState) {
       return false;
     }
@@ -509,7 +438,7 @@
       return this.anyVpnExists_(deviceState, networkStateList);
     }
 
-    let minlen;
+    let minlen: number;
     if (type === NetworkType.kWiFi) {
       // WiFi subpage includes 'Known Networks' so always show, even if the
       // technology is still enabling / scanning, or none are visible.
@@ -526,13 +455,11 @@
    * lead to toggling device enablement or showing the corresponding networks
    * list or showing details about a network or doing nothing based on the
    * device and networks states.
-   * @param {!Event} event The enable button event.
-   * @private
    */
-  onShowDetailsTap_(event) {
+  private onShowDetailsTap_(event: Event): void {
     if (!this.deviceIsEnabled_(this.deviceState)) {
       if (this.enableToggleIsEnabled_(this.deviceState)) {
-        const type = this.deviceState.type;
+        const type = this.deviceState!.type;
         const deviceEnabledToggledEvent =
             new CustomEvent('device-enabled-toggled', {
               bubbles: true,
@@ -543,20 +470,20 @@
       }
     } else if (
         this.isCaptivePortalUI2022Enabled_ &&
-        this.isPortalState_(this.activeNetworkState.portalState)) {
-      this.browserProxy_.showPortalSignin(this.activeNetworkState.guid);
+        this.isPortalState_(this.activeNetworkState!.portalState)) {
+      this.browserProxy_.showPortalSignin(this.activeNetworkState!.guid);
     } else if (this.shouldShowSubpage_(
                    this.deviceState, this.networkStateList)) {
       const showNetworksEvent = new CustomEvent('show-networks', {
         bubbles: true,
         composed: true,
-        detail: this.deviceState.type,
+        detail: this.deviceState!.type,
       });
       this.dispatchEvent(showNetworksEvent);
     } else if (this.shouldShowDetails_(
                    this.activeNetworkState, this.deviceState,
                    this.networkStateList)) {
-      if (this.activeNetworkState.guid) {
+      if (this.activeNetworkState!.guid) {
         const showDetailEvent = new CustomEvent('show-detail', {
           bubbles: true,
           composed: true,
@@ -581,21 +508,19 @@
    * a network or doing nothing based on the device and networks states.
    * TODO(b/253326370) Cleanup duplicate functionality between this
    * function and `onShowDetailsTap_`.
-   * @param {!Event} event The enable button event.
-   * @private
    */
-  onShowDetailsArrowTap_(event) {
+  private onShowDetailsArrowTap_(event: Event): void {
     if (this.shouldShowSubpage_(this.deviceState, this.networkStateList)) {
       const showNetworksEvent = new CustomEvent('show-networks', {
         bubbles: true,
         composed: true,
-        detail: this.deviceState.type,
+        detail: this.deviceState!.type,
       });
       this.dispatchEvent(showNetworksEvent);
     } else if (this.shouldShowDetails_(
                    this.activeNetworkState, this.deviceState,
                    this.networkStateList)) {
-      if (this.activeNetworkState.guid) {
+      if (this.activeNetworkState!.guid) {
         const showDetailEvent = new CustomEvent('show-detail', {
           bubbles: true,
           composed: true,
@@ -614,14 +539,10 @@
     event.stopPropagation();
   }
 
-  /**
-   * @param {!OncMojo.NetworkStateProperties} activeNetworkState
-   * @param {!OncMojo.DeviceStateProperties|undefined} deviceState
-   * @param {!Array<!OncMojo.NetworkStateProperties>} networkStateList
-   * @return {boolean}
-   * @private
-   */
-  isItemActionable_(activeNetworkState, deviceState, networkStateList) {
+  private isItemActionable_(
+      activeNetworkState: OncMojo.NetworkStateProperties,
+      deviceState: OncMojo.DeviceStateProperties|undefined,
+      networkStateList: OncMojo.NetworkStateProperties[]): boolean {
     // The boolean logic here matches onShowDetailsTap_ method that handles the
     // item click event.
 
@@ -633,7 +554,7 @@
 
     // Item is actionable if tapping should show the user to the portal signin.
     if (this.isCaptivePortalUI2022Enabled_ &&
-        this.isPortalState_(this.activeNetworkState.portalState)) {
+        this.isPortalState_(this.activeNetworkState!.portalState)) {
       return true;
     }
 
@@ -644,14 +565,10 @@
             activeNetworkState, deviceState, networkStateList);
   }
 
-  /**
-   * @param {!OncMojo.NetworkStateProperties} activeNetworkState
-   * @param {!OncMojo.DeviceStateProperties|undefined} deviceState
-   * @param {!Array<!OncMojo.NetworkStateProperties>} networkStateList
-   * @return {boolean}
-   * @private
-   */
-  showArrowButton_(activeNetworkState, deviceState, networkStateList) {
+  private showArrowButton_(
+      activeNetworkState: OncMojo.NetworkStateProperties,
+      deviceState: OncMojo.DeviceStateProperties|undefined,
+      networkStateList: OncMojo.NetworkStateProperties[]): boolean {
     // If SIM info is shown on the right side of the item, no arrow should be
     // shown.
     if (this.showSimInfo_(deviceState)) {
@@ -667,10 +584,8 @@
 
   /**
    * Event triggered when the enable button is toggled.
-   * @param {!Event} event
-   * @private
    */
-  onDeviceEnabledChange_(event) {
+  private onDeviceEnabledChange_(): void {
     assert(this.deviceState);
     const deviceIsEnabled = this.deviceIsEnabled_(this.deviceState);
     const deviceEnabledToggledEvent =
@@ -687,39 +602,28 @@
         DeviceStateType.kEnabling;
   }
 
-  /**
-   * @return {string}
-   * @private
-   */
-  getTitleText_() {
+  private getTitleText_(): string {
     if (this.networkTitleText) {
       return this.networkTitleText;
     }
     if (this.isCaptivePortalUI2022Enabled_ &&
-        this.isPortalState_(this.activeNetworkState.portalState)) {
+        this.isPortalState_(this.activeNetworkState!.portalState)) {
       const stateText = this.getConnectionStateText_(this.activeNetworkState);
       if (stateText) {
         return stateText;
       }
     }
-    return this.getNetworkTypeString_(this.activeNetworkState.type);
+    return this.getNetworkTypeString_(this.activeNetworkState!.type);
   }
 
   /**
    * Make sure events in embedded components do not propagate to onDetailsTap_.
-   * @param {!Event} event
-   * @private
    */
-  doNothing_(event) {
+  private doNothing_(event: Event): void {
     event.stopPropagation();
   }
 
-  /**
-   * @param {!NetworkType} type
-   * @return {string}
-   * @private
-   */
-  getNetworkTypeString_(type) {
+  private getNetworkTypeString_(type: NetworkType): string {
     // The shared Cellular/Tether subpage is referred to as "Mobile".
     // TODO(khorimoto): Remove once Cellular/Tether are split into their own
     // sections.
@@ -731,14 +635,17 @@
 
   /**
    * Return true if portalState is either kPortal or kProxyAuthRequired.
-   * @param {!PortalState} portalState
-   * @return {boolean}
-   * @private
    */
-  isPortalState_(portalState) {
+  private isPortalState_(portalState: PortalState): boolean {
     return portalState === PortalState.kPortal ||
         portalState === PortalState.kProxyAuthRequired;
   }
 }
 
+declare global {
+  interface HTMLElementTagNameMap {
+    [NetworkSummaryItemElement.is]: NetworkSummaryItemElement;
+  }
+}
+
 customElements.define(NetworkSummaryItemElement.is, NetworkSummaryItemElement);
diff --git a/chrome/browser/resources/settings/chromeos/os_settings.gni b/chrome/browser/resources/settings/chromeos/os_settings.gni
index 278f7bd..5b296ab7 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings.gni
+++ b/chrome/browser/resources/settings/chromeos/os_settings.gni
@@ -62,6 +62,10 @@
   "chromeos/internet_page/esim_install_error_dialog.ts",
   "chromeos/internet_page/esim_remove_profile_dialog.ts",
   "chromeos/internet_page/esim_rename_dialog.ts",
+  "chromeos/internet_page/network_always_on_vpn.ts",
+  "chromeos/internet_page/network_proxy_section.ts",
+  "chromeos/internet_page/network_summary_item.ts",
+  "chromeos/internet_page/network_summary.ts",
   "chromeos/internet_page/settings_traffic_counters.ts",
   "chromeos/internet_page/tether_connection_dialog.ts",
   "chromeos/kerberos_page/kerberos_accounts.ts",
@@ -355,10 +359,6 @@
   "chromeos/internet_page/internet_known_networks_page.js",
   "chromeos/internet_page/internet_page.js",
   "chromeos/internet_page/internet_subpage.js",
-  "chromeos/internet_page/network_always_on_vpn.js",
-  "chromeos/internet_page/network_proxy_section.js",
-  "chromeos/internet_page/network_summary.js",
-  "chromeos/internet_page/network_summary_item.js",
   "chromeos/multidevice_page/multidevice_combined_setup_item.js",
   "chromeos/multidevice_page/multidevice_feature_item.js",
   "chromeos/multidevice_page/multidevice_feature_toggle.js",
diff --git a/chrome/browser/resources/settings/settings.ts b/chrome/browser/resources/settings/settings.ts
index 9f33a751..3db81111 100644
--- a/chrome/browser/resources/settings/settings.ts
+++ b/chrome/browser/resources/settings/settings.ts
@@ -49,7 +49,7 @@
 export {SettingsStartupUrlsPageElement} from './on_startup_page/startup_urls_page.js';
 export {StartupUrlsPageBrowserProxy, StartupUrlsPageBrowserProxyImpl} from './on_startup_page/startup_urls_page_browser_proxy.js';
 export {OpenWindowProxy, OpenWindowProxyImpl} from './open_window_proxy.js';
-export {pageVisibility, setPageVisibilityForTesting} from './page_visibility.js';
+export {pageVisibility, PrivacyPageVisibility, setPageVisibilityForTesting} from './page_visibility.js';
 // <if expr="chromeos_ash">
 export {AccountManagerBrowserProxy, AccountManagerBrowserProxyImpl} from './people_page/account_manager_browser_proxy.js';
 // </if>
@@ -75,7 +75,7 @@
 export {ResetBrowserProxy, ResetBrowserProxyImpl} from './reset_page/reset_browser_proxy.js';
 export {SettingsResetProfileBannerElement} from './reset_page/reset_profile_banner.js';
 export {buildRouter, routes} from './route.js';
-export {SettingsRoutes, Route, Router} from './router.js';
+export {Route, Router, SettingsRoutes} from './router.js';
 export {SafetyCheckBrowserProxy, SafetyCheckBrowserProxyImpl, SafetyCheckCallbackConstants, SafetyCheckChromeCleanerStatus, SafetyCheckExtensionsStatus, SafetyCheckParentStatus, SafetyCheckPasswordsStatus, SafetyCheckSafeBrowsingStatus, SafetyCheckUpdatesStatus} from './safety_check_page/safety_check_browser_proxy.js';
 export {SafetyCheckIconStatus, SettingsSafetyCheckChildElement} from './safety_check_page/safety_check_child.js';
 // <if expr="_google_chrome and is_win">
diff --git a/chrome/browser/resources/side_panel/customize_chrome/categories.html b/chrome/browser/resources/side_panel/customize_chrome/categories.html
index 28f60e2..15a60a9 100644
--- a/chrome/browser/resources/side_panel/customize_chrome/categories.html
+++ b/chrome/browser/resources/side_panel/customize_chrome/categories.html
@@ -115,7 +115,8 @@
       <!-- TODO(crbug.com/1399596): use i18n for label -->
       <div class="label">Classic Chrome</div>
     </div>
-    <div class="tile" tabindex="0" role="button">
+    <div class="tile" tabindex="0" id="uploadImageTile"
+        role="button" on-click="onUploadImageClick_">
       <div class="image-container"></div>
       <!-- TODO(crbug.com/1399596): use i18n for label -->
       <div class="label">Upload Image</div>
diff --git a/chrome/browser/resources/side_panel/customize_chrome/categories.ts b/chrome/browser/resources/side_panel/customize_chrome/categories.ts
index 9b83f56..9a5270b4 100644
--- a/chrome/browser/resources/side_panel/customize_chrome/categories.ts
+++ b/chrome/browser/resources/side_panel/customize_chrome/categories.ts
@@ -17,6 +17,7 @@
   $: {
     backButton: HTMLElement,
     classicChromeTile: HTMLElement,
+    uploadImageTile: HTMLElement,
   };
 }
 
@@ -52,6 +53,13 @@
     this.dispatchEvent(new Event('theme-select'));
   }
 
+  private async onUploadImageClick_() {
+    const {success} = await this.pageHandler_.chooseLocalCustomBackground();
+    if (success) {
+      this.dispatchEvent(new Event('theme-select'));
+    }
+  }
+
   private onCollectionClick_(e: DomRepeatEvent<BackgroundCollection>) {
     this.dispatchEvent(new CustomEvent<BackgroundCollection>(
         'collection-select', {detail: e.model.item}));
diff --git a/chrome/browser/resources/supervised_user_error_page_resources.grdp b/chrome/browser/resources/supervised_user_error_page_resources.grdp
deleted file mode 100644
index da3164e0..0000000
--- a/chrome/browser/resources/supervised_user_error_page_resources.grdp
+++ /dev/null
@@ -1,5 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<grit-part>
-  <include name="IDR_SUPERVISED_USER_BLOCK_INTERSTITIAL_HTML" file="supervised_user/supervised_user_error_page/resources/supervised_user_block_interstitial.html" flattenhtml="true" type="BINDATA" compress="brotli" />
-  <include name="IDR_SUPERVISED_USER_BLOCK_INTERSTITIAL_V2_HTML" file="supervised_user/supervised_user_error_page/resources/supervised_user_block_interstitial_v2.html" flattenhtml="true" type="BINDATA" compress="brotli" />
-</grit-part>
diff --git a/chrome/browser/safe_browsing/extension_telemetry/potential_password_theft_signal_processor.cc b/chrome/browser/safe_browsing/extension_telemetry/potential_password_theft_signal_processor.cc
index db995406..f68dd26c 100644
--- a/chrome/browser/safe_browsing/extension_telemetry/potential_password_theft_signal_processor.cc
+++ b/chrome/browser/safe_browsing/extension_telemetry/potential_password_theft_signal_processor.cc
@@ -9,6 +9,7 @@
 #include "chrome/browser/safe_browsing/extension_telemetry/extension_telemetry_service.h"
 #include "chrome/browser/safe_browsing/extension_telemetry/password_reuse_signal.h"
 #include "chrome/browser/safe_browsing/extension_telemetry/remote_host_contacted_signal.h"
+#include "components/safe_browsing/core/common/features.h"
 #include "components/safe_browsing/core/common/proto/csd.pb.h"
 #include "crypto/sha2.h"
 
@@ -28,6 +29,10 @@
     const ExtensionSignal& signal) {
   DCHECK(signal.GetType() == ExtensionSignalType::kRemoteHostContacted ||
          signal.GetType() == ExtensionSignalType::kPasswordReuse);
+  if (!base::FeatureList::IsEnabled(
+          safe_browsing::kExtensionTelemetryPotentialPasswordTheft)) {
+    return;
+  }
   base::Time signal_creation_time = base::Time::NowFromSystemTime();
   extensions::ExtensionId extension_id;
   // Process remote host contacted signal.
diff --git a/chrome/browser/safe_browsing/extension_telemetry/potential_password_theft_signal_processor_unittest.cc b/chrome/browser/safe_browsing/extension_telemetry/potential_password_theft_signal_processor_unittest.cc
index bfec3ab6..c5c5da2 100644
--- a/chrome/browser/safe_browsing/extension_telemetry/potential_password_theft_signal_processor_unittest.cc
+++ b/chrome/browser/safe_browsing/extension_telemetry/potential_password_theft_signal_processor_unittest.cc
@@ -3,9 +3,11 @@
 // found in the LICENSE file.
 
 #include "chrome/browser/safe_browsing/extension_telemetry/potential_password_theft_signal_processor.h"
+#include "base/test/scoped_feature_list.h"
 #include "chrome/browser/safe_browsing/extension_telemetry/password_reuse_signal.h"
 #include "chrome/browser/safe_browsing/extension_telemetry/remote_host_contacted_signal.h"
 #include "components/safe_browsing/content/browser/password_protection/password_protection_service.h"
+#include "components/safe_browsing/core/common/features.h"
 #include "components/safe_browsing/core/common/proto/csd.pb.h"
 #include "content/public/test/browser_task_environment.h"
 #include "extensions/common/extension_id.h"
@@ -66,6 +68,7 @@
     return reused_password_account_type;
   }
 
+  base::test::ScopedFeatureList scoped_feature_list;
   PotentialPasswordTheftSignalProcessor processor_;
   content::BrowserTaskEnvironment task_environment_;
 
@@ -84,6 +87,8 @@
   auto pw_reuse_signal = PasswordReuseSignal(kExtensionId[0], pw_reuse_event_0);
   auto remote_host_signal = RemoteHostContactedSignal(
       kExtensionId[0], GURL(host_urls[0]), kProtocolType);
+  scoped_feature_list.InitAndEnableFeature(
+      kExtensionTelemetryPotentialPasswordTheft);
   processor_.ProcessSignal(pw_reuse_signal);
 
   EXPECT_FALSE(processor_.IsPasswordQueueEmptyForTest());
@@ -119,6 +124,8 @@
       kExtensionId[0], GURL(host_urls[0]), kProtocolType);
   auto remote_host_signal_1 = RemoteHostContactedSignal(
       kExtensionId[0], GURL(host_urls[1]), kProtocolType);
+  scoped_feature_list.InitAndEnableFeature(
+      kExtensionTelemetryPotentialPasswordTheft);
   processor_.ProcessSignal(remote_host_signal_0);
   task_environment_.FastForwardBy(base::Milliseconds(100));
   processor_.ProcessSignal(remote_host_signal_1);
@@ -156,6 +163,8 @@
   auto remote_host_signal_3 = RemoteHostContactedSignal(
       kExtensionId[0], GURL(host_urls[3]), kProtocolType);
 
+  scoped_feature_list.InitAndEnableFeature(
+      kExtensionTelemetryPotentialPasswordTheft);
   processor_.ProcessSignal(pw_reuse_signal_0);
   task_environment_.FastForwardBy(base::Milliseconds(50));
   processor_.ProcessSignal(pw_reuse_signal_1);
diff --git a/chrome/browser/search/background/ntp_custom_background_service.h b/chrome/browser/search/background/ntp_custom_background_service.h
index 39061396..25c73bae 100644
--- a/chrome/browser/search/background/ntp_custom_background_service.h
+++ b/chrome/browser/search/background/ntp_custom_background_service.h
@@ -65,7 +65,8 @@
                                const std::string& collection_id);
 
   // Invoked when a user selected the "Upload an image" option on the NTP.
-  void SelectLocalBackgroundImage(const base::FilePath& path);
+  // Virtual for testing.
+  virtual void SelectLocalBackgroundImage(const base::FilePath& path);
 
   // Virtual for testing.
   virtual void RefreshBackgroundIfNeeded();
diff --git a/chrome/browser/sessions/closed_tab_cache_browsertest.cc b/chrome/browser/sessions/closed_tab_cache_browsertest.cc
index 8279e8e..7f45b04 100644
--- a/chrome/browser/sessions/closed_tab_cache_browsertest.cc
+++ b/chrome/browser/sessions/closed_tab_cache_browsertest.cc
@@ -192,6 +192,9 @@
 
 // Restore an entry that is in the cache.
 IN_PROC_BROWSER_TEST_F(ClosedTabCacheBrowserTest, RestoreEntryWhenFound) {
+  base::HistogramTester histogram_tester;
+  const char kTabRestored[] = "Tab.RestoreClosedTab";
+
   ASSERT_TRUE(embedded_test_server()->Start());
 
   NavigateToURL(browser(), "a.com");
@@ -208,6 +211,12 @@
   EXPECT_EQ(closed_tab_cache().EntriesCount(), 0U);
   ASSERT_EQ(browser()->tab_strip_model()->count(), 2);
   EXPECT_EQ(browser()->tab_strip_model()->GetWebContentsAt(1), wc);
+
+  // We should store histogram kTabRestored when a tab is restored from
+  // ClosedTabCache with value 1.
+  EXPECT_EQ(histogram_tester.GetAllSamples(kTabRestored).size(), 1U);
+  EXPECT_THAT(histogram_tester.GetAllSamples(kTabRestored),
+              testing::ElementsAre(base::Bucket(1, 1)));
 }
 
 // Evict an entry after timeout.
diff --git a/chrome/browser/storage_access_api/storage_access_grant_permission_context_unittest.cc b/chrome/browser/storage_access_api/storage_access_grant_permission_context_unittest.cc
index a66d9ce..4e87cf75 100644
--- a/chrome/browser/storage_access_api/storage_access_grant_permission_context_unittest.cc
+++ b/chrome/browser/storage_access_api/storage_access_grant_permission_context_unittest.cc
@@ -4,8 +4,11 @@
 
 #include "chrome/browser/storage_access_api/storage_access_grant_permission_context.h"
 
+#include "base/barrier_callback.h"
+#include "base/test/bind.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
+#include "base/test/test_future.h"
 #include "chrome/test/base/chrome_render_view_host_test_harness.h"
 #include "components/content_settings/core/common/content_settings.h"
 #include "components/permissions/permission_request_id.h"
@@ -41,12 +44,6 @@
               base::NumberToString(dummy_id) + ".com");
 }
 
-void SaveResult(ContentSetting* content_setting_result,
-                ContentSetting content_setting) {
-  DCHECK(content_setting_result);
-  *content_setting_result = content_setting;
-}
-
 }  // namespace
 
 class StorageAccessGrantPermissionContextTest
@@ -100,17 +97,22 @@
     permissions::PermissionRequestManager* manager =
         permissions::PermissionRequestManager::FromWebContents(web_contents());
     DCHECK(manager);
-    for (int grant_id = 0;
-         grant_id < net::features::kStorageAccessAPIDefaultImplicitGrantLimit;
-         grant_id++) {
-      ContentSetting result = CONTENT_SETTING_DEFAULT;
+    const int implicit_grant_limit =
+        net::features::kStorageAccessAPIDefaultImplicitGrantLimit;
+    base::RunLoop run_loop;
+    auto barrier = base::BarrierCallback<ContentSetting>(
+        implicit_grant_limit,
+        base::BindLambdaForTesting(
+            [&](const std::vector<ContentSetting> results) {
+              run_loop.Quit();
+            }));
+    for (int grant_id = 0; grant_id < implicit_grant_limit; grant_id++) {
       permission_context.DecidePermissionForTesting(
           fake_id, requesting_origin, GetDummyEmbeddingUrl(grant_id),
-          /*user_gesture=*/true, base::BindOnce(&SaveResult, &result));
-      base::RunLoop().RunUntilIdle();
-
-      EXPECT_FALSE(manager->IsRequestInProgress());
+          /*user_gesture=*/true, barrier);
     }
+    run_loop.Run();
+    EXPECT_FALSE(manager->IsRequestInProgress());
   }
 
   permissions::PermissionRequestID CreateFakeID() {
@@ -150,11 +152,11 @@
   StorageAccessGrantPermissionContext permission_context(profile());
   permissions::PermissionRequestID fake_id = CreateFakeID();
 
-  ContentSetting result = CONTENT_SETTING_DEFAULT;
+  base::test::TestFuture<ContentSetting> future;
   permission_context.DecidePermissionForTesting(
       fake_id, GetRequesterURL(), GetTopLevelURL(),
-      /*user_gesture=*/true, base::BindOnce(&SaveResult, &result));
-  EXPECT_EQ(CONTENT_SETTING_BLOCK, result);
+      /*user_gesture=*/true, future.GetCallback());
+  EXPECT_EQ(CONTENT_SETTING_BLOCK, future.Get());
 }
 
 class StorageAccessGrantPermissionContextAPIEnabledTest
@@ -177,10 +179,12 @@
 
   ExhaustImplicitGrants(GetRequesterURL(), permission_context);
 
-  ContentSetting result = CONTENT_SETTING_DEFAULT;
+  base::test::TestFuture<ContentSetting> future;
   permission_context.DecidePermissionForTesting(
       fake_id, GetRequesterURL(), GetTopLevelURL(),
-      /*user_gesture=*/true, base::BindOnce(&SaveResult, &result));
+      /*user_gesture=*/true, future.GetCallback());
+
+  // Run until the prompt is ready.
   base::RunLoop().RunUntilIdle();
 
   permissions::PermissionRequestManager* manager =
@@ -196,8 +200,7 @@
   EXPECT_EQ(GetTopLevelURL(), manager->GetEmbeddingOrigin());
 
   manager->Dismiss();
-  base::RunLoop().RunUntilIdle();
-  EXPECT_EQ(CONTENT_SETTING_ASK, result);
+  EXPECT_EQ(CONTENT_SETTING_ASK, future.Get());
   EXPECT_EQ(histogram_tester().GetBucketCount(kRequestOutcomeHistogram,
                                               RequestOutcome::kDismissedByUser),
             1);
@@ -209,11 +212,11 @@
   StorageAccessGrantPermissionContext permission_context(profile());
   permissions::PermissionRequestID fake_id = CreateFakeID();
 
-  ContentSetting result = CONTENT_SETTING_DEFAULT;
+  base::test::TestFuture<ContentSetting> future;
   permission_context.DecidePermissionForTesting(
       fake_id, GetRequesterURL(), GetTopLevelURL(),
-      /*user_gesture=*/false, base::BindOnce(&SaveResult, &result));
-  EXPECT_EQ(CONTENT_SETTING_BLOCK, result);
+      /*user_gesture=*/false, future.GetCallback());
+  EXPECT_EQ(CONTENT_SETTING_BLOCK, future.Get());
   EXPECT_EQ(
       histogram_tester().GetBucketCount(kRequestOutcomeHistogram,
                                         RequestOutcome::kDeniedByPrerequisites),
@@ -258,23 +261,26 @@
   EXPECT_EQ(histogram_tester().GetBucketCount(
                 kRequestOutcomeHistogram, RequestOutcome::kGrantedByAllowance),
             5);
-
-  ContentSetting result = CONTENT_SETTING_DEFAULT;
-  permission_context.DecidePermissionForTesting(
-      fake_id, GetRequesterURL(), GetTopLevelURL(),
-      /*user_gesture=*/true, base::BindOnce(&SaveResult, &result));
-  base::RunLoop().RunUntilIdle();
-
   permissions::PermissionRequestManager* manager =
       permissions::PermissionRequestManager::FromWebContents(web_contents());
   ASSERT_TRUE(manager);
-  ASSERT_TRUE(manager->IsRequestInProgress());
 
-  // Close the prompt and validate we get the expected setting back in our
-  // callback.
-  manager->Dismiss();
-  base::RunLoop().RunUntilIdle();
-  EXPECT_EQ(CONTENT_SETTING_ASK, result);
+  {
+    base::test::TestFuture<ContentSetting> future;
+    permission_context.DecidePermissionForTesting(
+        fake_id, GetRequesterURL(), GetTopLevelURL(),
+        /*user_gesture=*/true, future.GetCallback());
+
+    // Run until the prompt is ready.
+    base::RunLoop().RunUntilIdle();
+
+    ASSERT_TRUE(manager->IsRequestInProgress());
+
+    // Close the prompt and validate we get the expected setting back in our
+    // callback.
+    manager->Dismiss();
+    EXPECT_EQ(CONTENT_SETTING_ASK, future.Get());
+  }
   EXPECT_EQ(histogram_tester().GetBucketCount(kRequestOutcomeHistogram,
                                               RequestOutcome::kDismissedByUser),
             1);
@@ -291,15 +297,14 @@
 
   // However now if a different requesting origin makes a request we should see
   // it gets auto-granted as the limit has not been reached for it yet.
-  result = CONTENT_SETTING_DEFAULT;
+  base::test::TestFuture<ContentSetting> future;
   permission_context.DecidePermissionForTesting(
       fake_id, alternate_requester_url, GetTopLevelURL(),
-      /*user_gesture=*/true, base::BindOnce(&SaveResult, &result));
-  base::RunLoop().RunUntilIdle();
+      /*user_gesture=*/true, future.GetCallback());
 
   // We should have no prompts still and our latest result should be an allow.
+  EXPECT_EQ(CONTENT_SETTING_ALLOW, future.Get());
   EXPECT_FALSE(manager->IsRequestInProgress());
-  EXPECT_EQ(CONTENT_SETTING_ALLOW, result);
   EXPECT_EQ(histogram_tester().GetBucketCount(
                 kRequestOutcomeHistogram, RequestOutcome::kGrantedByAllowance),
             6);
@@ -323,7 +328,6 @@
 
   permissions::PermissionRequestManager* manager =
       permissions::PermissionRequestManager::FromWebContents(web_contents());
-  ContentSetting result = CONTENT_SETTING_DEFAULT;
 
   content::WebContentsTester::For(web_contents())
       ->NavigateAndCommit(GetDummyEmbeddingUrlWithSubdomain());
@@ -332,16 +336,16 @@
   // that is same site with an existing grant should still be auto-granted. The
   // call is to `RequestPermission`, which checks for existing grants, while
   // `DecidePermission` does not.
+  base::test::TestFuture<ContentSetting> future;
   permission_context.RequestPermission(CreateFakeID(), GetRequesterURL(), true,
-                                       base::BindOnce(&SaveResult, &result));
-  base::RunLoop().RunUntilIdle();
+                                       future.GetCallback());
 
   int implicit_grant_limit =
       net::features::kStorageAccessAPIDefaultImplicitGrantLimit;
 
   // We should have no prompts still and our latest result should be an allow.
+  EXPECT_EQ(CONTENT_SETTING_ALLOW, future.Get());
   EXPECT_FALSE(manager->IsRequestInProgress());
-  EXPECT_EQ(CONTENT_SETTING_ALLOW, result);
   EXPECT_EQ(histogram_tester().GetBucketCount(
                 kRequestOutcomeHistogram, RequestOutcome::kGrantedByAllowance),
             implicit_grant_limit);
@@ -364,10 +368,12 @@
   histogram_tester().ExpectBucketCount(kGrantIsImplicitHistogram,
                                        /*sample=*/true, 5);
 
-  ContentSetting result = CONTENT_SETTING_DEFAULT;
+  base::test::TestFuture<ContentSetting> future;
   permission_context.DecidePermissionForTesting(
       fake_id, GetRequesterURL(), GetTopLevelURL(),
-      /*user_gesture=*/true, base::BindOnce(&SaveResult, &result));
+      /*user_gesture=*/true, future.GetCallback());
+
+  // Run until the prompt is ready.
   base::RunLoop().RunUntilIdle();
 
   permissions::PermissionRequestManager* manager =
@@ -378,8 +384,7 @@
   // Deny the prompt and validate we get the expected setting back in our
   // callback.
   manager->Deny();
-  base::RunLoop().RunUntilIdle();
-  EXPECT_EQ(CONTENT_SETTING_BLOCK, result);
+  EXPECT_EQ(CONTENT_SETTING_BLOCK, future.Get());
 
   histogram_tester().ExpectTotalCount(kGrantIsImplicitHistogram, 5);
   histogram_tester().ExpectBucketCount(kGrantIsImplicitHistogram,
@@ -405,10 +410,12 @@
   histogram_tester().ExpectBucketCount(kGrantIsImplicitHistogram,
                                        /*sample=*/true, 5);
 
-  ContentSetting result = CONTENT_SETTING_DEFAULT;
+  base::test::TestFuture<ContentSetting> future;
   permission_context.DecidePermissionForTesting(
       fake_id, GetRequesterURL(), GetTopLevelURL(),
-      /*user_gesture=*/true, base::BindOnce(&SaveResult, &result));
+      /*user_gesture=*/true, future.GetCallback());
+
+  // Run until the prompt is ready.
   base::RunLoop().RunUntilIdle();
 
   permissions::PermissionRequestManager* manager =
@@ -419,8 +426,7 @@
   // Accept the prompt and validate we get the expected setting back in our
   // callback.
   manager->Accept();
-  base::RunLoop().RunUntilIdle();
-  EXPECT_EQ(CONTENT_SETTING_ALLOW, result);
+  EXPECT_EQ(CONTENT_SETTING_ALLOW, future.Get());
 
   histogram_tester().ExpectTotalCount(kGrantIsImplicitHistogram, 6);
   histogram_tester().ExpectBucketCount(kGrantIsImplicitHistogram,
diff --git a/chrome/browser/supervised_user/OWNERS b/chrome/browser/supervised_user/OWNERS
index a8ae8ed..faa42f3a 100644
--- a/chrome/browser/supervised_user/OWNERS
+++ b/chrome/browser/supervised_user/OWNERS
@@ -1,19 +1 @@
-# Note: Unless you want a specific reviewer's expertise, please send CLs to
-# chrome-family-kids-reviews@google.com rather than to specific individuals.
-#
-# These CLs will be automatically reassigned to a reviewer within
-# about 5 minutes. This approach helps our team to load-balance incoming
-# reviews. Googlers can read more about this at go/gwsq-gerrit.
-
-# ChromeOS
-agawronska@chromium.org
-danan@chromium.org
-llin@chromium.org
-
-# Chrome Browser
-fernandex@chromium.org
-ljjlee@google.com
-tju@google.com
-
-# Previous owner, feel free to add for any questions or historical context:
-# treib@chromium.org
+file://components/supervised_user/OWNERS
diff --git a/chrome/browser/supervised_user/child_accounts/child_account_service_impl.cc b/chrome/browser/supervised_user/child_accounts/child_account_service_impl.cc
index 09ea57c..5a65bcd 100644
--- a/chrome/browser/supervised_user/child_accounts/child_account_service_impl.cc
+++ b/chrome/browser/supervised_user/child_accounts/child_account_service_impl.cc
@@ -24,7 +24,6 @@
 #include "chrome/browser/supervised_user/kids_chrome_management/kids_profile_manager.h"
 #include "chrome/browser/supervised_user/kids_chrome_management/kidschromemanagement_messages.pb.h"
 #include "chrome/browser/supervised_user/supervised_user_constants.h"
-#include "chrome/browser/supervised_user/supervised_user_features/supervised_user_features.h"
 #include "chrome/browser/supervised_user/supervised_user_service.h"
 #include "chrome/browser/supervised_user/supervised_user_service_factory.h"
 #include "chrome/browser/supervised_user/supervised_user_settings_service.h"
@@ -37,6 +36,7 @@
 #include "components/signin/public/identity_manager/account_info.h"
 #include "components/signin/public/identity_manager/accounts_in_cookie_jar_info.h"
 #include "components/signin/public/identity_manager/tribool.h"
+#include "components/supervised_user/core/common/features.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/storage_partition.h"
 
diff --git a/chrome/browser/supervised_user/supervised_user_error_page/BUILD.gn b/chrome/browser/supervised_user/supervised_user_error_page/BUILD.gn
deleted file mode 100644
index 0dd9626..0000000
--- a/chrome/browser/supervised_user/supervised_user_error_page/BUILD.gn
+++ /dev/null
@@ -1,40 +0,0 @@
-# Copyright 2014 The Chromium Authors
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-if (is_android) {
-  import("//build/config/android/rules.gni")
-}
-
-static_library("supervised_user_error_page") {
-  sources = [
-    "supervised_user_error_page.cc",
-    "supervised_user_error_page.h",
-  ]
-
-  deps = [
-    "//base",
-    "//chrome/app:generated_resources",
-    "//chrome/browser:resources_grit",
-    "//chrome/browser/supervised_user/supervised_user_features",
-    "//components/signin/public/base",
-    "//ui/base",
-    "//url",
-  ]
-}
-
-source_set("unit_tests") {
-  testonly = true
-  sources = [ "supervised_user_error_page_unittest.cc" ]
-  deps = [
-    ":supervised_user_error_page",
-    "//base",
-    "//base/test:test_support",
-    "//chrome/app:generated_resources",
-    "//chrome/browser:resources_grit",
-    "//chrome/browser/supervised_user/supervised_user_features",
-    "//testing/gmock",
-    "//testing/gtest",
-    "//ui/base",
-  ]
-}
diff --git a/chrome/browser/supervised_user/supervised_user_error_page/DEPS b/chrome/browser/supervised_user/supervised_user_error_page/DEPS
deleted file mode 100644
index 2b85ef17a..0000000
--- a/chrome/browser/supervised_user/supervised_user_error_page/DEPS
+++ /dev/null
@@ -1,8 +0,0 @@
-include_rules = [
-  "+chrome/app:generated_resources",
-  "+chrome/browser:resources_grit",
-  "+content/public/renderer",
-  "+third_party/blink",
-  "+url",
-  "+v8",
-]
diff --git a/chrome/browser/supervised_user/supervised_user_features/BUILD.gn b/chrome/browser/supervised_user/supervised_user_features/BUILD.gn
deleted file mode 100644
index 4c4f99a..0000000
--- a/chrome/browser/supervised_user/supervised_user_features/BUILD.gn
+++ /dev/null
@@ -1,23 +0,0 @@
-# Copyright 2021 The Chromium Authors
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-static_library("supervised_user_features") {
-  sources = [
-    "supervised_user_features.cc",
-    "supervised_user_features.h",
-  ]
-
-  deps = [ "//base" ]
-}
-
-source_set("unit_tests") {
-  testonly = true
-  sources = [ "supervised_user_features_unittest.cc" ]
-  deps = [
-    ":supervised_user_features",
-    "//base",
-    "//base/test:test_support",
-    "//testing/gtest",
-  ]
-}
diff --git a/chrome/browser/supervised_user/supervised_user_interstitial.cc b/chrome/browser/supervised_user/supervised_user_interstitial.cc
index abbd20c..e8b9a22 100644
--- a/chrome/browser/supervised_user/supervised_user_interstitial.cc
+++ b/chrome/browser/supervised_user/supervised_user_interstitial.cc
@@ -18,7 +18,6 @@
 #include "build/build_config.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/supervised_user/supervised_user_features/supervised_user_features.h"
 #include "chrome/browser/supervised_user/supervised_user_navigation_observer.h"
 #include "chrome/browser/supervised_user/supervised_user_service.h"
 #include "chrome/browser/supervised_user/supervised_user_service_factory.h"
@@ -29,6 +28,7 @@
 #include "components/infobars/core/infobar.h"
 #include "components/infobars/core/infobar_delegate.h"
 #include "components/prefs/pref_service.h"
+#include "components/supervised_user/core/common/features.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/navigation_controller.h"
diff --git a/chrome/browser/supervised_user/supervised_user_interstitial.h b/chrome/browser/supervised_user/supervised_user_interstitial.h
index 158d12be..d64d2d9 100644
--- a/chrome/browser/supervised_user/supervised_user_interstitial.h
+++ b/chrome/browser/supervised_user/supervised_user_interstitial.h
@@ -11,7 +11,7 @@
 #include "base/callback_forward.h"
 #include "base/memory/raw_ptr.h"
 #include "build/chromeos_buildflags.h"
-#include "chrome/browser/supervised_user/supervised_user_error_page/supervised_user_error_page.h"
+#include "components/supervised_user/core/browser/supervised_user_error_page.h"
 #include "url/gurl.h"
 
 namespace content {
diff --git a/chrome/browser/supervised_user/supervised_user_navigation_observer.h b/chrome/browser/supervised_user/supervised_user_navigation_observer.h
index d0dccaa1..ac4045a 100644
--- a/chrome/browser/supervised_user/supervised_user_navigation_observer.h
+++ b/chrome/browser/supervised_user/supervised_user_navigation_observer.h
@@ -11,13 +11,13 @@
 #include <vector>
 
 #include "base/memory/raw_ptr.h"
-#include "chrome/browser/supervised_user/supervised_user_error_page/supervised_user_error_page.h"
 #include "chrome/browser/supervised_user/supervised_user_navigation_throttle.h"
 #include "chrome/browser/supervised_user/supervised_user_service_observer.h"
 #include "chrome/browser/supervised_user/supervised_user_url_filter.h"
 #include "chrome/browser/supervised_user/supervised_users.h"
 #include "chrome/common/supervised_user_commands.mojom.h"
 #include "components/sessions/core/serialized_navigation_entry.h"
+#include "components/supervised_user/core/browser/supervised_user_error_page.h"
 #include "content/public/browser/render_frame_host_receiver_set.h"
 #include "content/public/browser/web_contents_observer.h"
 #include "content/public/browser/web_contents_user_data.h"
diff --git a/chrome/browser/supervised_user/supervised_user_navigation_throttle.h b/chrome/browser/supervised_user/supervised_user_navigation_throttle.h
index bfd722d..f85b819 100644
--- a/chrome/browser/supervised_user/supervised_user_navigation_throttle.h
+++ b/chrome/browser/supervised_user/supervised_user_navigation_throttle.h
@@ -9,9 +9,9 @@
 
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
-#include "chrome/browser/supervised_user/supervised_user_error_page/supervised_user_error_page.h"
 #include "chrome/browser/supervised_user/supervised_user_url_filter.h"
 #include "chrome/browser/supervised_user/supervised_users.h"
+#include "components/supervised_user/core/browser/supervised_user_error_page.h"
 #include "content/public/browser/navigation_throttle.h"
 
 class SupervisedUserNavigationThrottle : public content::NavigationThrottle {
diff --git a/chrome/browser/supervised_user/supervised_user_navigation_throttle_browsertest.cc b/chrome/browser/supervised_user/supervised_user_navigation_throttle_browsertest.cc
index 0375a28..5c4f013 100644
--- a/chrome/browser/supervised_user/supervised_user_navigation_throttle_browsertest.cc
+++ b/chrome/browser/supervised_user/supervised_user_navigation_throttle_browsertest.cc
@@ -24,7 +24,6 @@
 #include "chrome/browser/profiles/profile_key.h"
 #include "chrome/browser/supervised_user/permission_request_creator_mock.h"
 #include "chrome/browser/supervised_user/supervised_user_constants.h"
-#include "chrome/browser/supervised_user/supervised_user_features/supervised_user_features.h"
 #include "chrome/browser/supervised_user/supervised_user_interstitial.h"
 #include "chrome/browser/supervised_user/supervised_user_navigation_observer.h"
 #include "chrome/browser/supervised_user/supervised_user_service.h"
@@ -36,6 +35,7 @@
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/common/webui_url_constants.h"
 #include "chrome/test/base/ui_test_utils.h"
+#include "components/supervised_user/core/common/features.h"
 #include "content/public/browser/navigation_controller.h"
 #include "content/public/browser/navigation_entry.h"
 #include "content/public/browser/navigation_handle.h"
diff --git a/chrome/browser/supervised_user/supervised_user_pref_store.cc b/chrome/browser/supervised_user/supervised_user_pref_store.cc
index e586a84..f9d188e 100644
--- a/chrome/browser/supervised_user/supervised_user_pref_store.cc
+++ b/chrome/browser/supervised_user/supervised_user_pref_store.cc
@@ -19,7 +19,6 @@
 #include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/prefs/incognito_mode_prefs.h"
 #include "chrome/browser/supervised_user/supervised_user_constants.h"
-#include "chrome/browser/supervised_user/supervised_user_features/supervised_user_features.h"
 #include "chrome/browser/supervised_user/supervised_user_settings_service.h"
 #include "chrome/browser/supervised_user/supervised_user_url_filter.h"
 #include "chrome/common/chrome_switches.h"
@@ -29,6 +28,7 @@
 #include "components/prefs/pref_value_map.h"
 #include "components/signin/public/base/signin_pref_names.h"
 #include "components/signin/public/base/signin_switches.h"
+#include "components/supervised_user/core/common/features.h"
 #include "extensions/buildflags/buildflags.h"
 
 namespace {
diff --git a/chrome/browser/supervised_user/supervised_user_pref_store_unittest.cc b/chrome/browser/supervised_user/supervised_user_pref_store_unittest.cc
index e86e186..3ef30d4 100644
--- a/chrome/browser/supervised_user/supervised_user_pref_store_unittest.cc
+++ b/chrome/browser/supervised_user/supervised_user_pref_store_unittest.cc
@@ -11,12 +11,12 @@
 #include "base/values.h"
 #include "chrome/browser/prefs/incognito_mode_prefs.h"
 #include "chrome/browser/supervised_user/supervised_user_constants.h"
-#include "chrome/browser/supervised_user/supervised_user_features/supervised_user_features.h"
 #include "chrome/browser/supervised_user/supervised_user_pref_store.h"
 #include "chrome/browser/supervised_user/supervised_user_settings_service.h"
 #include "chrome/common/net/safe_search_util.h"
 #include "chrome/common/pref_names.h"
 #include "components/prefs/testing_pref_store.h"
+#include "components/supervised_user/core/common/features.h"
 #include "extensions/buildflags/buildflags.h"
 #include "testing/gmock/include/gmock/gmock-matchers.h"
 #include "testing/gtest/include/gtest/gtest.h"
diff --git a/chrome/browser/supervised_user/supervised_user_service.cc b/chrome/browser/supervised_user/supervised_user_service.cc
index 3cb99ba..182c23d 100644
--- a/chrome/browser/supervised_user/supervised_user_service.cc
+++ b/chrome/browser/supervised_user/supervised_user_service.cc
@@ -31,7 +31,6 @@
 #include "chrome/browser/profiles/profile_key.h"
 #include "chrome/browser/supervised_user/permission_request_creator.h"
 #include "chrome/browser/supervised_user/supervised_user_constants.h"
-#include "chrome/browser/supervised_user/supervised_user_features/supervised_user_features.h"
 #include "chrome/browser/supervised_user/supervised_user_service_factory.h"
 #include "chrome/browser/supervised_user/supervised_user_service_observer.h"
 #include "chrome/browser/supervised_user/supervised_user_settings_service.h"
@@ -43,6 +42,7 @@
 #include "chrome/grit/generated_resources.h"
 #include "components/pref_registry/pref_registry_syncable.h"
 #include "components/prefs/pref_service.h"
+#include "components/supervised_user/core/common/features.h"
 #include "components/sync/driver/sync_service.h"
 #include "components/sync/driver/sync_user_settings.h"
 #include "content/public/browser/browser_context.h"
diff --git a/chrome/browser/supervised_user/supervised_user_unscaled_resources.grd b/chrome/browser/supervised_user/supervised_user_unscaled_resources.grd
index ac16af79..6fa6fb6 100644
--- a/chrome/browser/supervised_user/supervised_user_unscaled_resources.grd
+++ b/chrome/browser/supervised_user/supervised_user_unscaled_resources.grd
@@ -1,4 +1,9 @@
 <?xml version="1.0" encoding="UTF-8"?>
+<!--
+This file is deprecated following the move of supervised user resources to //components.
+TODO(b/262703607): Remove the file once the resource ID conflicts from Grit settings
+have been resolved.
+-->
 <grit latest_public_release="0" current_release="1" output_all_resource_defines="false">
   <outputs>
     <output filename="grit/supervised_user_unscaled_resources.h" type="rc_header" >
@@ -6,9 +11,4 @@
     </output>
     <output filename="supervised_user_unscaled_resources.pak" type="data_package" />
   </outputs>
-  <release seq="1">
-    <includes>
-        <include name="IDR_SUPERVISED_USER_ICON" file="resources/supervised_user_icon.png" type="BINDATA" />
-    </includes>
-  </release>
 </grit>
diff --git a/chrome/browser/supervised_user/supervised_user_url_filter.h b/chrome/browser/supervised_user/supervised_user_url_filter.h
index 749b2be..1cbf4ae 100644
--- a/chrome/browser/supervised_user/supervised_user_url_filter.h
+++ b/chrome/browser/supervised_user/supervised_user_url_filter.h
@@ -15,9 +15,9 @@
 #include "base/observer_list.h"
 #include "base/sequence_checker.h"
 #include "build/chromeos_buildflags.h"
-#include "chrome/browser/supervised_user/supervised_user_error_page/supervised_user_error_page.h"
 #include "chrome/browser/supervised_user/supervised_users.h"
 #include "components/safe_search_api/url_checker.h"
+#include "components/supervised_user/core/browser/supervised_user_error_page.h"
 
 class GURL;
 class SupervisedUserDenylist;
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index 932388c..52066213 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -1925,8 +1925,8 @@
       "webui/family_link_user_internals/family_link_user_internals_ui.h",
     ]
     deps += [
-      "//chrome/browser/supervised_user/supervised_user_error_page",
-      "//chrome/browser/supervised_user/supervised_user_features",
+      "//components/supervised_user/core/browser",
+      "//components/supervised_user/core/common",
     ]
   }
 
diff --git a/chrome/browser/ui/browser_live_tab_context.cc b/chrome/browser/ui/browser_live_tab_context.cc
index 70cf647..116b6eb 100644
--- a/chrome/browser/ui/browser_live_tab_context.cc
+++ b/chrome/browser/ui/browser_live_tab_context.cc
@@ -8,6 +8,7 @@
 #include <utility>
 
 #include "base/feature_list.h"
+#include "base/metrics/histogram_macros.h"
 #include "base/token.h"
 #include "base/values.h"
 #include "chrome/browser/apps/app_service/web_contents_app_id_utils.h"
@@ -219,6 +220,10 @@
         user_agent_override, extra_data, false /* from_session_restore */);
   }
 
+  // Record the metrics for restoring closed tabs. Set to true when the tab is
+  // restored from closed tab cache and false otherwise.
+  UMA_HISTOGRAM_BOOLEAN("Tab.RestoreClosedTab", restored_from_closed_tab_cache);
+
   // Only update the metadata if the group doesn't already exist since the
   // existing group has the latest metadata, which may have changed from the
   // time the tab was closed.
diff --git a/chrome/browser/ui/extensions/hosted_app_browsertest.cc b/chrome/browser/ui/extensions/hosted_app_browsertest.cc
index 70a53ba..6fdfeebf 100644
--- a/chrome/browser/ui/extensions/hosted_app_browsertest.cc
+++ b/chrome/browser/ui/extensions/hosted_app_browsertest.cc
@@ -237,9 +237,7 @@
     DCHECK_EQ(GetParam(), AppType::HOSTED_APP);
     const Extension* app = InstallExtensionWithSourceAndFlags(
         app_folder, 1, extensions::mojom::ManifestLocation::kInternal,
-        app_type_ == AppType::HOSTED_APP
-            ? extensions::Extension::NO_FLAGS
-            : extensions::Extension::FROM_BOOKMARK);
+        extensions::Extension::NO_FLAGS);
     ASSERT_TRUE(app);
     app_id_ = app->id();
 
diff --git a/chrome/browser/ui/views/intent_picker_bubble_view_browsertest.cc b/chrome/browser/ui/views/intent_picker_bubble_view_browsertest.cc
index bbeeba0..34664c46 100644
--- a/chrome/browser/ui/views/intent_picker_bubble_view_browsertest.cc
+++ b/chrome/browser/ui/views/intent_picker_bubble_view_browsertest.cc
@@ -269,10 +269,18 @@
   EXPECT_TRUE(intent_picker_icon->GetVisible());
 }
 
+// TODO(crbug.com/1399441): This test is flaky. Re-enable this test.
 // Tests that the intent picker icon is not visible if the navigation redirects
 // to a URL that doesn't have an installed PWA.
+#if BUILDFLAG(IS_MAC)
+#define MAYBE_DoesNotShowIntentPickerWhenRedirectedOutOfScope \
+  DISABLED_DoesNotShowIntentPickerWhenRedirectedOutOfScope
+#else
+#define MAYBE_DoesNotShowIntentPickerWhenRedirectedOutOfScope \
+  DoesNotShowIntentPickerWhenRedirectedOutOfScope
+#endif
 IN_PROC_BROWSER_TEST_P(IntentPickerBubbleViewBrowserTest,
-                       DoesNotShowIntentPickerWhenRedirectedOutOfScope) {
+                       MAYBE_DoesNotShowIntentPickerWhenRedirectedOutOfScope) {
   InstallTestWebApp(GetOtherAppUrlHost(), /*app_scope=*/"/");
 
   const GURL out_of_scope_url =
diff --git a/chrome/browser/ui/views/tabs/tab_strip_layout_helper.h b/chrome/browser/ui/views/tabs/tab_strip_layout_helper.h
index 57017498..d996b86 100644
--- a/chrome/browser/ui/views/tabs/tab_strip_layout_helper.h
+++ b/chrome/browser/ui/views/tabs/tab_strip_layout_helper.h
@@ -138,11 +138,6 @@
   // in |slots_|.
   int GetSlotIndexForGroupHeader(tab_groups::TabGroupId group) const;
 
-  // Compares |cached_slots_| to the TabAnimations in |animator_| and DCHECKs if
-  // the TabAnimation::ViewType do not match. Prevents bugs that could cause the
-  // wrong callback being run when a tab or group is deleted.
-  void VerifyAnimationsMatchTabSlots() const;
-
   // Updates the value of either |active_tab_width_| or |inactive_tab_width_|,
   // as appropriate.
   void UpdateCachedTabWidth(int tab_index, int tab_width, bool active);
diff --git a/chrome/browser/ui/webui/app_home/app_home_page_handler.cc b/chrome/browser/ui/webui/app_home/app_home_page_handler.cc
index d8cdb0c..b086d85 100644
--- a/chrome/browser/ui/webui/app_home/app_home_page_handler.cc
+++ b/chrome/browser/ui/webui/app_home/app_home_page_handler.cc
@@ -93,7 +93,8 @@
       web_app_provider_(web_app::WebAppProvider::GetForWebApps(profile)),
       extension_service_(
           extensions::ExtensionSystem::Get(profile)->extension_service()) {
-  web_app_registrar_observation_.Observe(&web_app_provider_->registrar());
+  web_app_registrar_observation_.Observe(
+      &web_app_provider_->registrar_unsafe());
   install_manager_observation_.Observe(&web_app_provider_->install_manager());
   ExtensionRegistry::Get(profile)->AddObserver(this);
 }
@@ -200,7 +201,7 @@
 
 app_home::mojom::AppInfoPtr AppHomePageHandler::CreateAppInfoPtrFromWebApp(
     const web_app::AppId& app_id) {
-  auto& registrar = web_app_provider_->registrar();
+  auto& registrar = web_app_provider_->registrar_unsafe();
 
   auto app_info = app_home::mojom::AppInfo::New();
 
@@ -251,7 +252,7 @@
 
 void AppHomePageHandler::FillWebAppInfoList(
     std::vector<app_home::mojom::AppInfoPtr>* result) {
-  web_app::WebAppRegistrar& registrar = web_app_provider_->registrar();
+  web_app::WebAppRegistrar& registrar = web_app_provider_->registrar_unsafe();
 
   for (const web_app::AppId& web_app_id : registrar.GetAppIds()) {
     if (IsYoutubeExtension(web_app_id))
@@ -454,7 +455,7 @@
   if (extension_dialog_prompting_)
     return;
 
-  if (web_app_provider_->registrar().IsInstalled(app_id) &&
+  if (web_app_provider_->registrar_unsafe().IsInstalled(app_id) &&
       !IsYoutubeExtension(app_id)) {
     UninstallWebApp(app_id);
     return;
@@ -469,7 +470,7 @@
 }
 
 void AppHomePageHandler::ShowAppSettings(const std::string& app_id) {
-  if (web_app_provider_->registrar().IsInstalled(app_id) &&
+  if (web_app_provider_->registrar_unsafe().IsInstalled(app_id) &&
       !IsYoutubeExtension(app_id)) {
     ShowWebAppSettings(app_id);
     return;
@@ -488,7 +489,7 @@
 
 void AppHomePageHandler::CreateAppShortcut(const std::string& app_id,
                                            CreateAppShortcutCallback callback) {
-  if (web_app_provider_->registrar().IsInstalled(app_id) &&
+  if (web_app_provider_->registrar_unsafe().IsInstalled(app_id) &&
       !IsYoutubeExtension(app_id)) {
     CreateWebAppShortcut(app_id, std::move(callback));
     return;
@@ -514,7 +515,7 @@
   GURL full_launch_url;
   apps::LaunchContainer launch_container;
 
-  web_app::WebAppRegistrar& registrar = web_app_provider_->registrar();
+  web_app::WebAppRegistrar& registrar = web_app_provider_->registrar_unsafe();
   if (registrar.IsInstalled(app_id) && !IsYoutubeExtension(app_id)) {
     type = extensions::Manifest::Type::TYPE_HOSTED_APP;
     full_launch_url = registrar.GetAppStartUrl(app_id);
diff --git a/chrome/browser/ui/webui/app_home/app_home_page_handler_browsertest.cc b/chrome/browser/ui/webui/app_home/app_home_page_handler_browsertest.cc
index 01283fb9..d2f138f 100644
--- a/chrome/browser/ui/webui/app_home/app_home_page_handler_browsertest.cc
+++ b/chrome/browser/ui/webui/app_home/app_home_page_handler_browsertest.cc
@@ -466,7 +466,7 @@
   loop.Run();
   EXPECT_EQ(web_app::RunOnOsLoginMode::kWindowed,
             web_app::WebAppProvider::GetForWebApps(profile())
-                ->registrar()
+                ->registrar_unsafe()
                 .GetAppRunOnOsLoginMode(installed_app_id)
                 .value);
 }
diff --git a/chrome/browser/ui/webui/ash/drive_internals_ui.cc b/chrome/browser/ui/webui/ash/drive_internals_ui.cc
index 072df69..68b6ed5 100644
--- a/chrome/browser/ui/webui/ash/drive_internals_ui.cc
+++ b/chrome/browser/ui/webui/ash/drive_internals_ui.cc
@@ -93,6 +93,56 @@
   }
 }
 
+std::string BulkPinSetupStageToString(drivefs::pinning::SetupStage stage) {
+  using drivefs::pinning::SetupStage;
+  switch (stage) {
+    case SetupStage::kFinishedSetup:
+      return "kFinishedSetup";
+    case SetupStage::kFinishedSetupWithError:
+      return "kFinishedSetupWithError";
+    case SetupStage::kCalculatedFreeLocalDiskSpace:
+      return "kCalculatedFreeLocalDiskSpace";
+    case SetupStage::kCalculatedRequiredDiskSpace:
+      return "kCalculatedRequiredDiskSpace";
+    case SetupStage::kNotStarted:
+      return "kNotStarted";
+    case SetupStage::kStarted:
+      return "kStarted";
+    default:
+      return "SetupStage(Unknown)";
+  }
+}
+
+std::string BulkPinSetupErrorToString(drivefs::pinning::SetupError error) {
+  using drivefs::pinning::SetupError;
+  switch (error) {
+    case SetupError::kSuccess:
+      return "kSuccess";
+    case SetupError::kManagerDisabled:
+      return "kManagerDisabled";
+    case SetupError::kErrorCalculatingFreeDiskSpace:
+      return "kErrorCalculatingFreeDiskSpace";
+    case SetupError::kErrorRetrievingSearchResults:
+      return "kErrorRetrievingSearchResults";
+    case SetupError::kErrorResultsReturnedInvalid:
+      return "kErrorResultsReturnedInvalid";
+    case SetupError::kErrorNotEnoughFreeSpace:
+      return "kErrorNotEnoughFreeSpace";
+    case SetupError::kErrorRetrievingSearchResultsForPinning:
+      return "kErrorRetrievingSearchResultsForPinning";
+    case SetupError::kErrorResultsReturnedInvalidForPinning:
+      return "kErrorResultsReturnedInvalidForPinning";
+    case SetupError::kErrorFailedToPinItem:
+      return "kErrorFailedToPinItem";
+    case SetupError::kErrorSearchQueryNotBound:
+      return "kErrorSearchQueryNotBound";
+    case SetupError::kErrorManagerStopped:
+      return "kErrorManagerStopped";
+    default:
+      return "SetupError(Unknown)";
+  }
+}
+
 // Gets metadata of all files and directories in |root_path|
 // recursively. Stores the result as a list of dictionaries like:
 //
@@ -608,8 +658,12 @@
   void OnSetupProgress(
       const drivefs::pinning::SetupProgress& progress) override {
     base::Value::Dict setup_progress;
-    setup_progress.Set("stage",
-                       base::NumberToString(static_cast<int>(progress.stage)));
+    setup_progress.Set("stage", BulkPinSetupStageToString(progress.stage));
+    if (progress.stage ==
+        drivefs::pinning::SetupStage::kFinishedSetupWithError) {
+      setup_progress.Set("setupError",
+                         BulkPinSetupErrorToString(progress.error));
+    }
     setup_progress.Set("availableDiskSpace",
                        base::NumberToString(progress.available_disk_space));
     setup_progress.Set("requiredDiskSpace",
diff --git a/chrome/browser/ui/webui/extensions/extensions_internals_source.cc b/chrome/browser/ui/webui/extensions/extensions_internals_source.cc
index 05816ec0b..55885da 100644
--- a/chrome/browser/ui/webui/extensions/extensions_internals_source.cc
+++ b/chrome/browser/ui/webui/extensions/extensions_internals_source.cc
@@ -105,8 +105,6 @@
     flags_value.Append("ALLOW_FILE_ACCESS");
   if (creation_flags & extensions::Extension::FROM_WEBSTORE)
     flags_value.Append("FROM_WEBSTORE");
-  if (creation_flags & extensions::Extension::FROM_BOOKMARK)
-    flags_value.Append("FROM_BOOKMARK");
   if (creation_flags & extensions::Extension::FOLLOW_SYMLINKS_ANYWHERE)
     flags_value.Append("FOLLOW_SYMLINKS_ANYWHERE");
   if (creation_flags & extensions::Extension::ERROR_ON_PRIVATE_KEY)
@@ -127,29 +125,47 @@
 }
 
 base::Value::List DisableReasonsToList(int disable_reasons) {
+  static_assert(extensions::disable_reason::DISABLE_REASON_LAST == 1 << 22,
+                "Please add your new disable reason here.");
+
   base::Value::List disable_reasons_value;
+  if (disable_reasons & extensions::disable_reason::DISABLE_USER_ACTION) {
+    disable_reasons_value.Append("DISABLE_USER_ACTION");
+  }
   if (disable_reasons &
       extensions::disable_reason::DISABLE_PERMISSIONS_INCREASE) {
     disable_reasons_value.Append("DISABLE_PERMISSIONS_INCREASE");
   }
-  if (disable_reasons & extensions::disable_reason::DISABLE_RELOAD)
+  if (disable_reasons & extensions::disable_reason::DISABLE_RELOAD) {
     disable_reasons_value.Append("DISABLE_RELOAD");
+  }
   if (disable_reasons &
       extensions::disable_reason::DISABLE_UNSUPPORTED_REQUIREMENT) {
     disable_reasons_value.Append("DISABLE_UNSUPPORTED_REQUIREMENT");
   }
-  if (disable_reasons & extensions::disable_reason::DISABLE_SIDELOAD_WIPEOUT)
+  if (disable_reasons & extensions::disable_reason::DISABLE_SIDELOAD_WIPEOUT) {
     disable_reasons_value.Append("DISABLE_SIDELOAD_WIPEOUT");
-  if (disable_reasons & extensions::disable_reason::DISABLE_NOT_VERIFIED)
+  }
+  if (disable_reasons &
+      extensions::disable_reason::DEPRECATED_DISABLE_UNKNOWN_FROM_SYNC) {
+    disable_reasons_value.Append("DEPRECATED_DISABLE_UNKNOWN_FROM_SYNC");
+  }
+  if (disable_reasons & extensions::disable_reason::DISABLE_NOT_VERIFIED) {
     disable_reasons_value.Append("DISABLE_NOT_VERIFIED");
-  if (disable_reasons & extensions::disable_reason::DISABLE_GREYLIST)
+  }
+  if (disable_reasons & extensions::disable_reason::DISABLE_GREYLIST) {
     disable_reasons_value.Append("DISABLE_GREYLIST");
-  if (disable_reasons & extensions::disable_reason::DISABLE_CORRUPTED)
+  }
+  if (disable_reasons & extensions::disable_reason::DISABLE_CORRUPTED) {
     disable_reasons_value.Append("DISABLE_CORRUPTED");
-  if (disable_reasons & extensions::disable_reason::DISABLE_REMOTE_INSTALL)
+  }
+  if (disable_reasons & extensions::disable_reason::DISABLE_REMOTE_INSTALL) {
     disable_reasons_value.Append("DISABLE_REMOTE_INSTALL");
-  if (disable_reasons & extensions::disable_reason::DISABLE_EXTERNAL_EXTENSION)
+  }
+  if (disable_reasons &
+      extensions::disable_reason::DISABLE_EXTERNAL_EXTENSION) {
     disable_reasons_value.Append("DISABLE_EXTERNAL_EXTENSION");
+  }
   if (disable_reasons &
       extensions::disable_reason::DISABLE_UPDATE_REQUIRED_BY_POLICY) {
     disable_reasons_value.Append("DISABLE_UPDATE_REQUIRED_BY_POLICY");
@@ -158,10 +174,23 @@
       extensions::disable_reason::DISABLE_CUSTODIAN_APPROVAL_REQUIRED) {
     disable_reasons_value.Append("DISABLE_CUSTODIAN_APPROVAL_REQUIRED");
   }
-  if (disable_reasons & extensions::disable_reason::DISABLE_BLOCKED_BY_POLICY)
+  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_REINSTALL) {
+    disable_reasons_value.Append("DISABLE_REINSTALL");
+  }
+  if (disable_reasons & extensions::disable_reason::DISABLE_NOT_ALLOWLISTED) {
+    disable_reasons_value.Append("DISABLE_NOT_ALLOWLISTED");
+  }
+  if (disable_reasons &
+      extensions::disable_reason::DISABLE_NOT_ASH_KEEPLISTED) {
+    disable_reasons_value.Append("DISABLE_NOT_ASH_KEEPLISTED");
+  }
+
   return disable_reasons_value;
 }
+
 // The JSON we generate looks like this:
 // Note:
 // - tab_specific permissions can have 0 or more DICT entries with each tab id
diff --git a/chrome/browser/ui/webui/family_link_user_internals/family_link_user_internals_message_handler.cc b/chrome/browser/ui/webui/family_link_user_internals/family_link_user_internals_message_handler.cc
index 7903f5d..a5d13f8 100644
--- a/chrome/browser/ui/webui/family_link_user_internals/family_link_user_internals_message_handler.cc
+++ b/chrome/browser/ui/webui/family_link_user_internals/family_link_user_internals_message_handler.cc
@@ -15,7 +15,6 @@
 #include "chrome/browser/profiles/profile_key.h"
 #include "chrome/browser/signin/identity_manager_factory.h"
 #include "chrome/browser/supervised_user/child_accounts/child_account_service.h"
-#include "chrome/browser/supervised_user/supervised_user_error_page/supervised_user_error_page.h"
 #include "chrome/browser/supervised_user/supervised_user_service_factory.h"
 #include "chrome/browser/supervised_user/supervised_user_settings_service.h"
 #include "chrome/browser/supervised_user/supervised_user_settings_service_factory.h"
@@ -23,6 +22,7 @@
 #include "chrome/common/channel_info.h"
 #include "components/signin/public/identity_manager/identity_manager.h"
 #include "components/signin/public/identity_manager/tribool.h"
+#include "components/supervised_user/core/browser/supervised_user_error_page.h"
 #include "components/url_formatter/url_fixer.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/storage_partition.h"
diff --git a/chrome/browser/ui/webui/family_link_user_internals/family_link_user_internals_message_handler.h b/chrome/browser/ui/webui/family_link_user_internals/family_link_user_internals_message_handler.h
index df72d37a..073be2b 100644
--- a/chrome/browser/ui/webui/family_link_user_internals/family_link_user_internals_message_handler.h
+++ b/chrome/browser/ui/webui/family_link_user_internals/family_link_user_internals_message_handler.h
@@ -8,10 +8,10 @@
 #include "base/callback_list.h"
 #include "base/memory/weak_ptr.h"
 #include "base/scoped_observation.h"
-#include "chrome/browser/supervised_user/supervised_user_error_page/supervised_user_error_page.h"
 #include "chrome/browser/supervised_user/supervised_user_service.h"
 #include "chrome/browser/supervised_user/supervised_user_service_observer.h"
 #include "chrome/browser/supervised_user/supervised_user_url_filter.h"
+#include "components/supervised_user/core/browser/supervised_user_error_page.h"
 #include "content/public/browser/web_ui_message_handler.h"
 
 // The implementation for the chrome://family-link-user-internals page.
diff --git a/chrome/browser/ui/webui/interstitials/interstitial_ui.cc b/chrome/browser/ui/webui/interstitials/interstitial_ui.cc
index 8a25a75..010554f 100644
--- a/chrome/browser/ui/webui/interstitials/interstitial_ui.cc
+++ b/chrome/browser/ui/webui/interstitials/interstitial_ui.cc
@@ -67,8 +67,8 @@
 #endif
 
 #if BUILDFLAG(ENABLE_SUPERVISED_USERS)
-#include "chrome/browser/supervised_user/supervised_user_error_page/supervised_user_error_page.h"  // nogncheck
 #include "chrome/browser/supervised_user/supervised_user_interstitial.h"
+#include "components/supervised_user/core/browser/supervised_user_error_page.h"  // nogncheck
 #endif
 
 using security_interstitials::TestSafeBrowsingBlockingPageQuiet;
diff --git a/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome.mojom b/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome.mojom
index 183df02..bb5ca13f 100644
--- a/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome.mojom
+++ b/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome.mojom
@@ -83,6 +83,9 @@
 
   // Sets theme to default classic chrome.
   SetClassicChromeDefaultTheme();
+
+  // Choose custom background from local file system.
+  ChooseLocalCustomBackground() => (bool success);
 };
 
 // WebUI-side handler for requests from the browser.
diff --git a/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler.cc b/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler.cc
index 9a9b5107..0bc2111 100644
--- a/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler.cc
+++ b/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler.cc
@@ -14,6 +14,7 @@
 #include "chrome/browser/themes/theme_service.h"
 #include "chrome/browser/themes/theme_service_factory.h"
 #include "chrome/browser/ui/browser_window.h"
+#include "chrome/browser/ui/chrome_select_file_policy.h"
 #include "chrome/browser/ui/color/chrome_color_id.h"
 #include "chrome/browser/ui/webui/new_tab_page/ntp_pref_names.h"
 #include "chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_colors.h"
@@ -49,6 +50,9 @@
 
 CustomizeChromePageHandler::~CustomizeChromePageHandler() {
   ntp_background_service_->RemoveObserver(this);
+  if (select_file_dialog_) {
+    select_file_dialog_->ListenerDestroyed();
+  }
 }
 
 void CustomizeChromePageHandler::SetMostVisitedSettings(
@@ -138,6 +142,34 @@
   theme_service_->UseDefaultTheme();
 }
 
+void CustomizeChromePageHandler::ChooseLocalCustomBackground(
+    ChooseLocalCustomBackgroundCallback callback) {
+  // Early return if the select file dialog is already active.
+  if (select_file_dialog_) {
+    std::move(callback).Run(false);
+    return;
+  }
+
+  select_file_dialog_ = ui::SelectFileDialog::Create(
+      this, std::make_unique<ChromeSelectFilePolicy>(web_contents_));
+  ui::SelectFileDialog::FileTypeInfo file_types;
+  file_types.allowed_paths = ui::SelectFileDialog::FileTypeInfo::NATIVE_PATH;
+  file_types.extensions.resize(1);
+  file_types.extensions[0].push_back(FILE_PATH_LITERAL("jpg"));
+  file_types.extensions[0].push_back(FILE_PATH_LITERAL("jpeg"));
+  file_types.extensions[0].push_back(FILE_PATH_LITERAL("png"));
+  file_types.extensions[0].push_back(FILE_PATH_LITERAL("gif"));
+  file_types.extension_description_overrides.push_back(
+      l10n_util::GetStringUTF16(IDS_UPLOAD_IMAGE_FORMAT));
+  DCHECK(!choose_local_custom_background_callback_);
+  choose_local_custom_background_callback_ = std::move(callback);
+  select_file_dialog_->SelectFile(
+      ui::SelectFileDialog::SELECT_OPEN_FILE, std::u16string(),
+      profile_->last_selected_directory(), &file_types, 0,
+      base::FilePath::StringType(), web_contents_->GetTopLevelNativeWindow(),
+      nullptr);
+}
+
 void CustomizeChromePageHandler::OnNativeThemeUpdated(
     ui::NativeTheme* observed_theme) {
   UpdateTheme();
@@ -194,3 +226,23 @@
   ntp_background_service_->RemoveObserver(this);
   ntp_background_service_ = nullptr;
 }
+
+void CustomizeChromePageHandler::FileSelected(const base::FilePath& path,
+                                              int index,
+                                              void* params) {
+  DCHECK(choose_local_custom_background_callback_);
+  if (ntp_custom_background_service_) {
+    profile_->set_last_selected_directory(path.DirName());
+    ntp_custom_background_service_->SelectLocalBackgroundImage(path);
+  }
+
+  select_file_dialog_ = nullptr;
+  std::move(choose_local_custom_background_callback_).Run(true);
+}
+
+void CustomizeChromePageHandler::FileSelectionCanceled(void* params) {
+  DCHECK(choose_local_custom_background_callback_);
+  select_file_dialog_ = nullptr;
+
+  std::move(choose_local_custom_background_callback_).Run(false);
+}
diff --git a/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler.h b/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler.h
index 1a5b1d78..9fea65eb6 100644
--- a/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler.h
+++ b/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler.h
@@ -18,6 +18,7 @@
 #include "mojo/public/cpp/bindings/remote.h"
 #include "ui/native_theme/native_theme.h"
 #include "ui/native_theme/native_theme_observer.h"
+#include "ui/shell_dialogs/select_file_dialog.h"
 
 namespace content {
 class WebContents;
@@ -29,7 +30,8 @@
     : public side_panel::mojom::CustomizeChromePageHandler,
       public NtpBackgroundServiceObserver,
       public ui::NativeThemeObserver,
-      public ThemeServiceObserver {
+      public ThemeServiceObserver,
+      public ui::SelectFileDialog::Listener {
  public:
   CustomizeChromePageHandler(
       mojo::PendingReceiver<side_panel::mojom::CustomizeChromePageHandler>
@@ -54,6 +56,8 @@
   void SetDefaultColor() override;
   void SetForegroundColor(SkColor foreground_color) override;
   void SetClassicChromeDefaultTheme() override;
+  void ChooseLocalCustomBackground(
+      ChooseLocalCustomBackgroundCallback callback) override;
 
  private:
   // ui::NativeThemeObserver:
@@ -71,8 +75,16 @@
   void OnNextCollectionImageAvailable() override;
   void OnNtpBackgroundServiceShuttingDown() override;
 
+  // SelectFileDialog::Listener:
+  void FileSelected(const base::FilePath& path,
+                    int index,
+                    void* params) override;
+  void FileSelectionCanceled(void* params) override;
+
+  ChooseLocalCustomBackgroundCallback choose_local_custom_background_callback_;
   raw_ptr<NtpCustomBackgroundService> ntp_custom_background_service_;
   raw_ptr<Profile> profile_;
+  scoped_refptr<ui::SelectFileDialog> select_file_dialog_;
   raw_ptr<content::WebContents> web_contents_;
   raw_ptr<NtpBackgroundService> ntp_background_service_;
   GetBackgroundCollectionsCallback background_collections_callback_;
diff --git a/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler_unittest.cc b/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler_unittest.cc
index df9063e..1a06c9e02 100644
--- a/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler_unittest.cc
+++ b/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler_unittest.cc
@@ -17,6 +17,7 @@
 #include "chrome/browser/search_engines/template_url_service_factory.h"
 #include "chrome/browser/themes/theme_service.h"
 #include "chrome/browser/themes/theme_service_factory.h"
+#include "chrome/browser/ui/chrome_select_file_policy.h"
 #include "chrome/browser/ui/color/chrome_color_id.h"
 #include "chrome/browser/ui/webui/new_tab_page/ntp_pref_names.h"
 #include "chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome.mojom.h"
@@ -38,6 +39,7 @@
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/color/color_provider.h"
 #include "ui/native_theme/native_theme.h"
+#include "ui/shell_dialogs/select_file_dialog_factory.h"
 
 namespace content {
 class BrowserContext;
@@ -47,6 +49,77 @@
 
 using testing::_;
 
+// A test SelectFileDialog to go straight to calling the listener.
+class TestSelectFileDialog : public ui::SelectFileDialog {
+ public:
+  TestSelectFileDialog(Listener* listener,
+                       std::unique_ptr<ui::SelectFilePolicy> policy,
+                       const bool auto_cancel)
+      : ui::SelectFileDialog(listener, std::move(policy)),
+        auto_cancel_(auto_cancel) {}
+
+  TestSelectFileDialog(const TestSelectFileDialog&) = delete;
+  TestSelectFileDialog& operator=(const TestSelectFileDialog&) = delete;
+
+ protected:
+  ~TestSelectFileDialog() override = default;
+
+  void SelectFileImpl(Type type,
+                      const std::u16string& title,
+                      const base::FilePath& default_path,
+                      const FileTypeInfo* file_types,
+                      int file_type_index,
+                      const base::FilePath::StringType& default_extension,
+                      gfx::NativeWindow owning_window,
+                      void* params,
+                      const GURL* caller) override {
+    if (auto_cancel_) {
+      listener_->FileSelectionCanceled(params);
+    } else {
+      listener_->FileSelected(base::FilePath(FILE_PATH_LITERAL("/test/path")),
+                              file_type_index, params);
+    }
+  }
+  // Pure virtual methods that need to be implemented.
+  bool IsRunning(gfx::NativeWindow owning_window) const override {
+    return false;
+  }
+  void ListenerDestroyed() override {}
+  bool HasMultipleFileTypeChoicesImpl() override { return false; }
+
+ private:
+  bool auto_cancel_;
+};
+
+class TestSelectFilePolicy : public ui::SelectFilePolicy {
+ public:
+  TestSelectFilePolicy& operator=(const TestSelectFilePolicy&) = delete;
+
+  // Pure virtual methods that need to be implemented.
+  bool CanOpenSelectFileDialog() override { return true; }
+  void SelectFileDenied() override {}
+};
+
+// A test SelectFileDialogFactory so that the TestSelectFileDialog is used.
+class TestSelectFileDialogFactory : public ui::SelectFileDialogFactory {
+ public:
+  explicit TestSelectFileDialogFactory(bool auto_cancel)
+      : auto_cancel_(auto_cancel) {}
+
+  TestSelectFileDialogFactory& operator=(const TestSelectFileDialogFactory&) =
+      delete;
+
+  ui::SelectFileDialog* Create(
+      ui::SelectFileDialog::Listener* listener,
+      std::unique_ptr<ui::SelectFilePolicy> policy) override {
+    return new TestSelectFileDialog(
+        listener, std::make_unique<TestSelectFilePolicy>(), auto_cancel_);
+  }
+
+ private:
+  bool auto_cancel_;
+};
+
 class MockPage : public side_panel::mojom::CustomizeChromePage {
  public:
   MockPage() = default;
@@ -71,6 +144,7 @@
       : NtpCustomBackgroundService(profile) {}
   MOCK_METHOD0(GetCustomBackground, absl::optional<CustomBackground>());
   MOCK_METHOD0(ResetCustomBackgroundInfo, void());
+  MOCK_METHOD1(SelectLocalBackgroundImage, void(const base::FilePath&));
 };
 
 class MockNtpBackgroundService : public NtpBackgroundService {
@@ -385,3 +459,33 @@
 
   handler().SetClassicChromeDefaultTheme();
 }
+
+TEST_F(CustomizeChromePageHandlerTest, ChooseLocalCustomBackgroundSuccess) {
+  bool success;
+  base::MockCallback<
+      CustomizeChromePageHandler::ChooseLocalCustomBackgroundCallback>
+      callback;
+  ui::SelectFileDialog::SetFactory(new TestSelectFileDialogFactory(false));
+  EXPECT_CALL(callback, Run(testing::_))
+      .Times(1)
+      .WillOnce(testing::Invoke(
+          [&success](bool success_arg) { success = std::move(success_arg); }));
+  EXPECT_CALL(mock_ntp_custom_background_service_, SelectLocalBackgroundImage)
+      .Times(1);
+  handler().ChooseLocalCustomBackground(callback.Get());
+  EXPECT_TRUE(success);
+}
+
+TEST_F(CustomizeChromePageHandlerTest, ChooseLocalCustomBackgroundCancel) {
+  bool success;
+  base::MockCallback<
+      CustomizeChromePageHandler::ChooseLocalCustomBackgroundCallback>
+      callback;
+  ui::SelectFileDialog::SetFactory(new TestSelectFileDialogFactory(true));
+  EXPECT_CALL(callback, Run(testing::_))
+      .Times(1)
+      .WillOnce(testing::Invoke(
+          [&success](bool success_arg) { success = std::move(success_arg); }));
+  handler().ChooseLocalCustomBackground(callback.Get());
+  EXPECT_TRUE(!success);
+}
diff --git a/chrome/browser/web_applications/extensions/web_app_policy_manager_unittest.cc b/chrome/browser/web_applications/extensions/web_app_policy_manager_unittest.cc
index 54e98d5..359dfd6a 100644
--- a/chrome/browser/web_applications/extensions/web_app_policy_manager_unittest.cc
+++ b/chrome/browser/web_applications/extensions/web_app_policy_manager_unittest.cc
@@ -16,6 +16,7 @@
 #include "base/test/bind.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
+#include "base/test/test_future.h"
 #include "base/values.h"
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
@@ -519,6 +520,13 @@
         unhashed_app_id);
   }
 
+  void WaitForAppsToSynchronize() {
+    base::RunLoop loop;
+    policy_manager().SetOnAppsSynchronizedCompletedCallbackForTesting(
+        loop.QuitClosure());
+    loop.Run();
+  }
+
  private:
   webapps::InstallResultCode install_result_code_ =
       webapps::InstallResultCode::kSuccessNewInstall;
@@ -537,8 +545,6 @@
   if (ShouldSkipPWASpecificTest())
     return;
 
-  base::RunLoop().RunUntilIdle();
-
   const auto& install_requests =
       externally_managed_app_manager().install_requests();
   EXPECT_TRUE(install_requests.empty());
@@ -551,7 +557,7 @@
   profile()->GetPrefs()->SetList(prefs::kWebAppInstallForceList,
                                  base::Value::List());
 
-  base::RunLoop().RunUntilIdle();
+  WaitForAppsToSynchronize();
 
   const auto& install_requests =
       externally_managed_app_manager().install_requests();
@@ -686,7 +692,7 @@
   profile()->GetPrefs()->SetList(prefs::kWebAppInstallForceList,
                                  std::move(list));
 
-  base::RunLoop().RunUntilIdle();
+  WaitForAppsToSynchronize();
 
   const auto& install_requests =
       externally_managed_app_manager().install_requests();
@@ -706,7 +712,7 @@
   profile()->GetPrefs()->SetList(prefs::kWebAppInstallForceList,
                                  std::move(list));
 
-  base::RunLoop().RunUntilIdle();
+  WaitForAppsToSynchronize();
 
   const auto& install_requests =
       externally_managed_app_manager().install_requests();
@@ -726,7 +732,7 @@
   profile()->GetPrefs()->SetList(prefs::kWebAppInstallForceList,
                                  std::move(list));
 
-  base::RunLoop().RunUntilIdle();
+  WaitForAppsToSynchronize();
 
   const auto& install_requests =
       externally_managed_app_manager().install_requests();
@@ -747,7 +753,7 @@
   profile()->GetPrefs()->SetList(prefs::kWebAppInstallForceList,
                                  std::move(list));
 
-  base::RunLoop().RunUntilIdle();
+  WaitForAppsToSynchronize();
 
   const auto& install_requests =
       externally_managed_app_manager().install_requests();
@@ -769,7 +775,7 @@
   profile()->GetPrefs()->SetList(prefs::kWebAppInstallForceList,
                                  std::move(list));
 
-  base::RunLoop().RunUntilIdle();
+  WaitForAppsToSynchronize();
 
   const auto& install_requests =
       externally_managed_app_manager().install_requests();
@@ -789,7 +795,7 @@
   profile()->GetPrefs()->SetList(prefs::kWebAppInstallForceList,
                                  std::move(list));
 
-  base::RunLoop().RunUntilIdle();
+  WaitForAppsToSynchronize();
 
   const auto& install_requests =
       externally_managed_app_manager().install_requests();
@@ -809,7 +815,7 @@
   profile()->GetPrefs()->SetList(prefs::kWebAppInstallForceList,
                                  std::move(list));
 
-  base::RunLoop().RunUntilIdle();
+  WaitForAppsToSynchronize();
 
   const auto& install_requests =
       externally_managed_app_manager().install_requests();
@@ -829,7 +835,7 @@
   profile()->GetPrefs()->SetList(prefs::kWebAppInstallForceList,
                                  std::move(list));
 
-  base::RunLoop().RunUntilIdle();
+  WaitForAppsToSynchronize();
 
   const auto& install_requests =
       externally_managed_app_manager().install_requests();
@@ -854,7 +860,7 @@
     profile()->GetPrefs()->SetList(prefs::kWebAppInstallForceList,
                                    std::move(list));
   }
-  base::RunLoop().RunUntilIdle();
+  WaitForAppsToSynchronize();
   // Change custom name
   {
     base::Value::List list;
@@ -862,7 +868,7 @@
     profile()->GetPrefs()->SetList(prefs::kWebAppInstallForceList,
                                    std::move(list));
   }
-  base::RunLoop().RunUntilIdle();
+  WaitForAppsToSynchronize();
 
   const auto& install_requests =
       externally_managed_app_manager().install_requests();
@@ -895,7 +901,7 @@
   profile()->GetPrefs()->SetList(prefs::kWebAppInstallForceList,
                                  std::move(first_list));
 
-  base::RunLoop().RunUntilIdle();
+  WaitForAppsToSynchronize();
 
   const auto& install_requests =
       externally_managed_app_manager().install_requests();
@@ -910,7 +916,7 @@
   profile()->GetPrefs()->SetList(prefs::kWebAppInstallForceList,
                                  std::move(second_list));
 
-  base::RunLoop().RunUntilIdle();
+  WaitForAppsToSynchronize();
 
   expected_install_options_list.push_back(GetTabbedInstallOptions());
 
@@ -935,7 +941,7 @@
   profile()->GetPrefs()->SetList(prefs::kWebAppInstallForceList,
                                  std::move(first_list));
 
-  base::RunLoop().RunUntilIdle();
+  WaitForAppsToSynchronize();
 
   // We should only try to install the app in the policy.
   std::vector<ExternalInstallOptions> expected_install_options_list;
@@ -953,7 +959,6 @@
 TEST_P(WebAppPolicyManagerTest, UninstallAppInstalledInCurrentSession) {
   if (ShouldSkipPWASpecificTest())
     return;
-  base::RunLoop().RunUntilIdle();
 
   // Add two sites, one that opens in a window and one that opens in a tab.
   base::Value::List first_list;
@@ -961,7 +966,7 @@
   first_list.Append(GetTabbedItem());
   profile()->GetPrefs()->SetList(prefs::kWebAppInstallForceList,
                                  std::move(first_list));
-  base::RunLoop().RunUntilIdle();
+  WaitForAppsToSynchronize();
 
   const auto& install_requests =
       externally_managed_app_manager().install_requests();
@@ -977,7 +982,7 @@
   second_list.Append(GetWindowedItem());
   profile()->GetPrefs()->SetList(prefs::kWebAppInstallForceList,
                                  std::move(second_list));
-  base::RunLoop().RunUntilIdle();
+  WaitForAppsToSynchronize();
 
   // We'll try to install the app again but ExternallyManagedAppManager will
   // handle not re-installing the app.
@@ -998,7 +1003,7 @@
   profile()->GetPrefs()->SetList(prefs::kWebAppInstallForceList,
                                  std::move(list));
 
-  base::RunLoop().RunUntilIdle();
+  WaitForAppsToSynchronize();
 
   std::vector<ExternalInstallOptions> expected_options_list;
   expected_options_list.push_back(GetWindowedInstallOptions());
@@ -1008,8 +1013,13 @@
   EXPECT_EQ(expected_options_list, install_options_list);
 
   MakeInstalledAppPlaceholder(GURL(kWindowedUrl));
-  policy_manager().ReinstallPlaceholderAppIfNecessary(GURL(kWindowedUrl));
-  base::RunLoop().RunUntilIdle();
+  base::test::TestFuture<const GURL&,
+                         ExternallyManagedAppManager::InstallResult>
+      future;
+  policy_manager().ReinstallPlaceholderAppIfNecessary(GURL(kWindowedUrl),
+                                                      future.GetCallback());
+  EXPECT_EQ(future.Get<1>().code,
+            webapps::InstallResultCode::kSuccessNewInstall);
 
   auto reinstall_options = GetWindowedInstallOptions();
   reinstall_options.install_placeholder = false;
@@ -1028,7 +1038,7 @@
   profile()->GetPrefs()->SetList(prefs::kWebAppInstallForceList,
                                  std::move(list));
 
-  base::RunLoop().RunUntilIdle();
+  WaitForAppsToSynchronize();
 
   std::vector<ExternalInstallOptions> expected_options_list;
   expected_options_list.push_back(GetWindowedInstallOptions());
@@ -1038,8 +1048,13 @@
   EXPECT_EQ(expected_options_list, install_options_list);
 
   // By default, the app being installed is not a placeholder app.
-  policy_manager().ReinstallPlaceholderAppIfNecessary(GURL(kWindowedUrl));
-  base::RunLoop().RunUntilIdle();
+  base::test::TestFuture<const GURL&,
+                         ExternallyManagedAppManager::InstallResult>
+      future;
+  policy_manager().ReinstallPlaceholderAppIfNecessary(GURL(kWindowedUrl),
+                                                      future.GetCallback());
+  EXPECT_EQ(future.Get<1>().code,
+            webapps::InstallResultCode::kFailedPlaceholderUninstall);
 
   // No other options are added to list as the app is currently not
   // installed as a placeholder app.
@@ -1056,7 +1071,7 @@
   profile()->GetPrefs()->SetList(prefs::kWebAppInstallForceList,
                                  std::move(list));
 
-  base::RunLoop().RunUntilIdle();
+  WaitForAppsToSynchronize();
 
   std::vector<ExternalInstallOptions> expected_options_list;
   expected_options_list.push_back(GetFallbackAppNameInstallOptions());
@@ -1066,8 +1081,13 @@
   EXPECT_EQ(expected_options_list, install_options_list);
 
   MakeInstalledAppPlaceholder(GURL(kWindowedUrl));
-  policy_manager().ReinstallPlaceholderAppIfNecessary(GURL(kWindowedUrl));
-  base::RunLoop().RunUntilIdle();
+  base::test::TestFuture<const GURL&,
+                         ExternallyManagedAppManager::InstallResult>
+      future;
+  policy_manager().ReinstallPlaceholderAppIfNecessary(GURL(kWindowedUrl),
+                                                      future.GetCallback());
+  EXPECT_EQ(future.Get<1>().code,
+            webapps::InstallResultCode::kSuccessNewInstall);
 
   auto reinstall_options = GetFallbackAppNameInstallOptions();
   reinstall_options.install_placeholder = false;
@@ -1086,7 +1106,7 @@
   profile()->GetPrefs()->SetList(prefs::kWebAppInstallForceList,
                                  std::move(list));
 
-  base::RunLoop().RunUntilIdle();
+  WaitForAppsToSynchronize();
 
   std::vector<ExternalInstallOptions> expected_options_list;
   expected_options_list.push_back(GetWindowedInstallOptions());
@@ -1095,9 +1115,14 @@
       externally_managed_app_manager().install_requests();
   EXPECT_EQ(expected_options_list, install_options_list);
 
+  base::test::TestFuture<const GURL&,
+                         ExternallyManagedAppManager::InstallResult>
+      future;
   // Try to reinstall for app not installed by policy.
-  policy_manager().ReinstallPlaceholderAppIfNecessary(GURL(kTabbedUrl));
-  base::RunLoop().RunUntilIdle();
+  policy_manager().ReinstallPlaceholderAppIfNecessary(GURL(kTabbedUrl),
+                                                      future.GetCallback());
+  EXPECT_EQ(future.Get<1>().code,
+            webapps::InstallResultCode::kFailedPlaceholderUninstall);
 
   EXPECT_EQ(expected_options_list, install_options_list);
 }
@@ -1105,7 +1130,6 @@
 TEST_P(WebAppPolicyManagerTest, SayRefreshTwoTimesQuickly) {
   if (ShouldSkipPWASpecificTest())
     return;
-  base::RunLoop().RunUntilIdle();
   // Add an app.
   {
     base::Value::List list;
@@ -1120,7 +1144,10 @@
     profile()->GetPrefs()->SetList(prefs::kWebAppInstallForceList,
                                    std::move(list));
   }
-  base::RunLoop().RunUntilIdle();
+
+  // `OnAppsSynchronized` should be triggered twice.
+  WaitForAppsToSynchronize();
+  WaitForAppsToSynchronize();
 
   // Both apps should have been installed.
   std::vector<ExternalInstallOptions> expected_options_list;
@@ -1156,7 +1183,7 @@
     histograms.ExpectTotalCount(
         WebAppPolicyManager::kInstallResultHistogramName, 0);
 
-    base::RunLoop().RunUntilIdle();
+    WaitForAppsToSynchronize();
 
     histograms.ExpectTotalCount(
         WebAppPolicyManager::kInstallResultHistogramName, 1);
@@ -1174,7 +1201,8 @@
     profile()->GetPrefs()->SetList(prefs::kWebAppInstallForceList,
                                    std::move(list));
 
-    base::RunLoop().RunUntilIdle();
+    WaitForAppsToSynchronize();
+
     histograms.ExpectTotalCount(
         WebAppPolicyManager::kInstallResultHistogramName, 3);
     histograms.ExpectBucketCount(
@@ -1185,8 +1213,6 @@
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 TEST_P(WebAppPolicyManagerTest, DisableSystemWebApps) {
-  base::RunLoop().RunUntilIdle();
-
   auto disabled_apps = policy_manager().GetDisabledSystemWebApps();
   EXPECT_TRUE(disabled_apps.empty());
 
@@ -1262,12 +1288,9 @@
   list.Append(GetWindowedItem());
   list.Append(GetTabbedItem());
 
-  base::RunLoop loop;
-  policy_manager().SetOnAppsSynchronizedCompletedCallbackForTesting(
-      loop.QuitClosure());
   profile()->GetPrefs()->SetList(prefs::kWebAppInstallForceList,
                                  std::move(list));
-  loop.Run();
+  WaitForAppsToSynchronize();
 
   const auto& install_requests =
       externally_managed_app_manager().install_requests();
@@ -1322,12 +1345,9 @@
   list.Append(GetWindowedItem());
   list.Append(GetTabbedItem());
 
-  base::RunLoop force_install_loop;
-  policy_manager().SetOnAppsSynchronizedCompletedCallbackForTesting(
-      force_install_loop.QuitClosure());
   profile()->GetPrefs()->SetList(prefs::kWebAppInstallForceList,
                                  std::move(list));
-  force_install_loop.Run();
+  WaitForAppsToSynchronize();
 
   provider()->command_manager().AwaitAllCommandsCompleteForTesting();
 
diff --git a/chrome/browser/web_applications/policy/web_app_policy_manager.cc b/chrome/browser/web_applications/policy/web_app_policy_manager.cc
index 83ccc9a..4ac2bff 100644
--- a/chrome/browser/web_applications/policy/web_app_policy_manager.cc
+++ b/chrome/browser/web_applications/policy/web_app_policy_manager.cc
@@ -118,7 +118,10 @@
   system_web_apps_delegate_map_ = system_web_apps_delegate_map;
 }
 
-void WebAppPolicyManager::Start(base::OnceClosure on_done) {
+void WebAppPolicyManager::Start(base::OnceClosure initialization_complete) {
+  DCHECK(initialization_complete_.is_null());
+
+  initialization_complete_ = std::move(initialization_complete);
   // When Lacros is enabled, don't run PWA-specific logic in Ash.
   // TODO(crbug.com/1251491): Consider factoring out logic that should only run
   // in Ash into a separate class. This way, when running in Ash, we won't need
@@ -131,11 +134,12 @@
       ->PostTask(FROM_HERE,
                  base::BindOnce(
                      &WebAppPolicyManager::InitChangeRegistrarAndRefreshPolicy,
-                     weak_ptr_factory_.GetWeakPtr(), enable_pwa_support)
-                     .Then(std::move(on_done)));
+                     weak_ptr_factory_.GetWeakPtr(), enable_pwa_support));
 }
 
-void WebAppPolicyManager::ReinstallPlaceholderAppIfNecessary(const GURL& url) {
+void WebAppPolicyManager::ReinstallPlaceholderAppIfNecessary(
+    const GURL& url,
+    ExternallyManagedAppManager::OnceInstallCallback on_complete) {
   const base::Value::List& web_apps =
       pref_service_->GetList(prefs::kWebAppInstallForceList);
   const auto& web_apps_list = web_apps;
@@ -149,13 +153,21 @@
       app_registrar_->LookupPlaceholderAppId(url, WebAppManagement::kPolicy)
           .has_value();
 
-  if (it == web_apps_list.end() || !is_placeholder_url)
+  if (it == web_apps_list.end() || !is_placeholder_url) {
+    std::move(on_complete)
+        .Run(url, ExternallyManagedAppManager::InstallResult(
+                      webapps::InstallResultCode::kFailedPlaceholderUninstall));
     return;
+  }
 
   ExternalInstallOptions install_options = ParseInstallPolicyEntry(*it);
 
-  if (!install_options.install_url.is_valid())
+  if (!install_options.install_url.is_valid()) {
+    std::move(on_complete)
+        .Run(url, ExternallyManagedAppManager::InstallResult(
+                      webapps::InstallResultCode::kInstallURLInvalid));
     return;
+  }
 
   // No need to install a placeholder because there should be one already.
   install_options.wait_for_windows_closed = true;
@@ -164,7 +176,7 @@
   // If the app is not a placeholder app, ExternallyManagedAppManager will
   // ignore the request.
   externally_managed_app_manager_->InstallNow(std::move(install_options),
-                                              base::DoNothing());
+                                              std::move(on_complete));
 }
 
 // static
@@ -204,6 +216,10 @@
             weak_ptr_factory_.GetWeakPtr()));
     RefreshPolicyInstalledIsolatedWebApps();
 #endif
+  } else {
+    if (initialization_complete_) {
+      std::move(initialization_complete_).Run();
+    }
   }
   ObserveDisabledSystemFeaturesPolicy();
 }
@@ -538,7 +554,7 @@
 
 void WebAppPolicyManager::SetOnAppsSynchronizedCompletedCallbackForTesting(
     base::OnceClosure callback) {
-  on_apps_synchronized_ = std::move(callback);
+  on_apps_synchronized_for_testing_ = std::move(callback);
 }
 
 void WebAppPolicyManager::SetRefreshPolicySettingsCompletedCallbackForTesting(
@@ -631,8 +647,13 @@
                                   url_and_result.second.code);
   }
 
-  if (on_apps_synchronized_)
-    std::move(on_apps_synchronized_).Run();
+  if (on_apps_synchronized_for_testing_) {
+    std::move(on_apps_synchronized_for_testing_).Run();
+  }
+
+  if (initialization_complete_) {
+    std::move(initialization_complete_).Run();
+  }
 }
 
 WebAppPolicyManager::WebAppSetting::WebAppSetting() {
diff --git a/chrome/browser/web_applications/policy/web_app_policy_manager.h b/chrome/browser/web_applications/policy/web_app_policy_manager.h
index 6ea29f7..f37be5a 100644
--- a/chrome/browser/web_applications/policy/web_app_policy_manager.h
+++ b/chrome/browser/web_applications/policy/web_app_policy_manager.h
@@ -65,9 +65,13 @@
   void SetSystemWebAppDelegateMap(
       const ash::SystemWebAppDelegateMap* system_web_apps_delegate_map);
 
+  // `initialization_complete` waits for the first `SynchronizeInstalledApps` to
+  // finish if it's triggered on `Start`.
   void Start(base::OnceClosure initialization_complete);
 
-  void ReinstallPlaceholderAppIfNecessary(const GURL& url);
+  void ReinstallPlaceholderAppIfNecessary(
+      const GURL& url,
+      ExternallyManagedAppManager::OnceInstallCallback on_complete);
 
   static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry);
 
@@ -190,7 +194,7 @@
 
   // Testing callbacks
   base::OnceClosure refresh_policy_settings_completed_;
-  base::OnceClosure on_apps_synchronized_;
+  base::OnceClosure on_apps_synchronized_for_testing_;
 
   bool is_refreshing_ = false;
   bool needs_refresh_ = false;
@@ -200,6 +204,9 @@
   std::unique_ptr<WebAppSetting> default_settings_;
 
   ExternallyInstalledWebAppPrefs externally_installed_app_prefs_;
+
+  base::OnceClosure initialization_complete_;
+
 #if BUILDFLAG(IS_CHROMEOS)
   std::unique_ptr<IsolatedWebAppPolicyManager> iwa_policy_manager_;
 #endif
diff --git a/chrome/browser/web_applications/web_app_tab_helper.cc b/chrome/browser/web_applications/web_app_tab_helper.cc
index 3a2e469..2160a8b3 100644
--- a/chrome/browser/web_applications/web_app_tab_helper.cc
+++ b/chrome/browser/web_applications/web_app_tab_helper.cc
@@ -178,7 +178,8 @@
 }
 
 void WebAppTabHelper::ReinstallPlaceholderAppIfNecessary(const GURL& url) {
-  provider_->policy_manager().ReinstallPlaceholderAppIfNecessary(url);
+  provider_->policy_manager().ReinstallPlaceholderAppIfNecessary(
+      url, base::DoNothing());
 }
 
 absl::optional<AppId> WebAppTabHelper::FindAppWithUrlInScope(
diff --git a/chrome/build/mac.pgo.txt b/chrome/build/mac.pgo.txt
index db2d98f7..e51cd9c 100644
--- a/chrome/build/mac.pgo.txt
+++ b/chrome/build/mac.pgo.txt
@@ -1 +1 @@
-chrome-mac-main-1671105478-9542d88baf5f95c811a54a0ffc94796550dcf945.profdata
+chrome-mac-main-1671126672-5755c38cfe13133153d6cf92cbf51e9ce23f7e3c.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index 641e4359c..162402e 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-main-1671105478-28a3831c720731c59eb6652e09a8362932ca8c4e.profdata
+chrome-win32-main-1671116308-c3a4e361c490d4de9eb7abe31d561d478d8d8ae7.profdata
diff --git a/chrome/chrome_paks.gni b/chrome/chrome_paks.gni
index 6b478cb..35fe22a6 100644
--- a/chrome/chrome_paks.gni
+++ b/chrome/chrome_paks.gni
@@ -262,7 +262,6 @@
         "$root_gen_dir/chrome/assistant_optin_resources.pak",
         "$root_gen_dir/chrome/audio_resources.pak",
         "$root_gen_dir/chrome/bluetooth_pairing_dialog_resources.pak",
-        "$root_gen_dir/chrome/browser/supervised_user/supervised_user_unscaled_resources.pak",
         "$root_gen_dir/chrome/cloud_upload_resources.pak",
         "$root_gen_dir/chrome/desk_api_resources.pak",
         "$root_gen_dir/chrome/emoji_picker_resources.pak",
@@ -354,7 +353,6 @@
         "//chrome/browser/resources/nearby_internals:resources",
         "//chrome/browser/resources/nearby_share:resources",
         "//chrome/browser/resources/settings/chromeos:resources",
-        "//chrome/browser/supervised_user:supervised_user_unscaled_resources",
         "//chromeos/ash/resources",
         "//chromeos/resources",
         "//ui/file_manager:file_manager_gen_resources",
diff --git a/chrome/common/features.gni b/chrome/common/features.gni
index 7aa0949..d2036b2 100644
--- a/chrome/common/features.gni
+++ b/chrome/common/features.gni
@@ -11,6 +11,7 @@
 import("//components/nacl/features.gni")
 import("//components/safe_browsing/buildflags.gni")
 import("//components/signin/features.gni")
+import("//components/supervised_user/buildflags.gni")
 import("//crypto/features.gni")
 import("//device/vr/buildflags/buildflags.gni")
 import("//extensions/buildflags/buildflags.gni")
@@ -85,10 +86,6 @@
 assert(!chrome_enable_logging_by_default || !is_official_build,
        "Logging must be disabled by default in Official builds")
 
-# Enables supervision for Family Link users.
-# Supervision is only supported on Chrome OS and Android.
-enable_supervised_users = is_chromeos || is_android
-
 # Use brlapi from brltty for braille display support.
 use_brlapi = is_chromeos_ash
 
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index c4ce03f..f3ce22c 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -3618,9 +3618,9 @@
         ]
       }
       deps += [
+        "//components/supervised_user/core/common",
         "//chrome/browser/supervised_user:test_support",
         "//chrome/browser/supervised_user/kids_chrome_management:proto",
-        "//chrome/browser/supervised_user/supervised_user_features",
       ]
     }
 
@@ -9073,9 +9073,7 @@
     deps += [
       "//chrome/browser/supervised_user:test_support",
       "//chrome/browser/supervised_user/kids_chrome_management:proto",
-      "//chrome/browser/supervised_user/supervised_user_error_page:unit_tests",
-      "//chrome/browser/supervised_user/supervised_user_features",
-      "//chrome/browser/supervised_user/supervised_user_features:unit_tests",
+      "//components/supervised_user/core/common",
     ]
   }
   if (safe_browsing_mode == 1 && enable_extensions) {
diff --git a/chrome/test/data/extensions/browsertest/proxy_setting_extension.pem b/chrome/test/data/extensions/browsertest/proxy_setting_extension.pem
new file mode 100644
index 0000000..b63f6a1
--- /dev/null
+++ b/chrome/test/data/extensions/browsertest/proxy_setting_extension.pem
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCmyYDVyB24tYXx
+SmwI8G6InnE1vT6LBPOLGNcyBX8E/tBWgYZwr/gRMco9S40djNEAiGvpBLMyezWS
+FnYHW11DYmzlW7VZ6E+MYAYY/G1P+xDL2AA1jVxjqC68JVdU91Eub11anQDlLu8c
+gwSGwy0Sg/nfMUFV7gylFahZ9EbfZ8vMsPlS8RFEf7TG7vZb4aQrh8d7npPrpJI3
+llkVQooQ0V/C56L5tmv2XdyWA0KjFiw5eE/wU0ZkQQjcJTtQHtH8DPqItmG57xXr
+XYgnU5WNrA9m4FouhOL3JCN+6W0VwEkQ+WMJGlisF4wJJgQLznfDxCcwBvRk33FT
+roAV9nmLAgMBAAECggEAMtLTZhGpcOgrrSfa/OxbGlmBsPj+bnlvR1Ml/DGtj5Wg
+YcPtdjhBFOhBsuTrVvZal9l6XJLYkkj2PZKHm91M8aQz/74u6HtAhkuagBGns8gT
+SbKD+c2eOiX9O8r7LkKA4+/+mAagx7XCGkOYZQJjoTFDOq51NNyp0MJlPygPUrL9
+9cmQ4SS9Tz6GOxjR+SJcHeJGrt6Sy0cSIuL3Ve1xNVpId+t8xxSQ1qoow36VkjK8
+lB302fgvelY75AYGAfoa8kIoEFlNq8zCtQ6rnwGOHkS67Jp/1lS/sroinxmEkcrL
+lx914c5nJ8gf2xRJFdBJ3860LQ0Qlzqa9ztJCbcEAQKBgQDR/MR7eYzz4knTcO0U
+R0A2ATfntkDianxqnVddd7SC0gU43LzsHjxMoYEMWqE3zpyBahNiUo38BuDBWGkw
+zwJQPUsr+mAw4ue63+zAqXCjMHyoYF9xO7duBQQCsyN86A832fv/fFiOCYklWjh0
+yxV9u+IDwProRKcYAoO04/5tiwKBgQDLVWto0tA+LLM3qC0H2iI2V5vm9eOAXx4z
+sPj+xS/y1xlZhlb0yoAae6Vsf7dTXln7U9uk/eFEt72CI3jYCu6xafv7OTH4f+FY
+9SF8UcSaUC0I7ir3Ynx3ch/avNZer7JEbw15/gi2b7nHTZ/N7FZaHRqjUX8Tdf7N
+ZPTSmsGkAQKBgFChm5zCDsKzOJ/tM8velCRokVvzGrUWKJITurFUPscg+ApekFim
+P4c5WMEyp8czGduDrM/LXWPSDEmRtkqcoonHD29io78B0fNq+pD8PxkajDNnVh63
+zHZ9jI2w1canoPhURrRSvfpEZRonq5gFR0pRc7b3SiEYA4Vvqb0y8saDAoGAPAtQ
+eZbvabzXe5MNISl4OuXqIuzvrqPoDkmvbcSbITIFyVsTaJw38hobQowAffz5w28D
+CJj0Ic/EhZ6l+95WmEt83rYIHKLez98rcUPQM+WDNwTl6UHLEZbA814aS8HEedL1
+ftXWitE5YjJ5A11MLrYzfJD/XqbUhKDQbzTGBAECgYAHPnCl6tB21INnlDEw2E4+
+/l6BsgkVX4hfN9ohWToYPfQtY4mzSSAGEx09xODAMDOkWM5n6Kjifhh8VxDnCljl
+0YktosZzYBwtnLFOYGfT1QCTOuSuesemdOb8ySMARJhve8IOe5Y2oVJG6pnGzu6R
+LtrfJylDDN9kzzIOO6onbg==
+-----END PRIVATE KEY-----
diff --git a/chrome/test/data/extensions/browsertest/proxy_setting_extension/background.js b/chrome/test/data/extensions/browsertest/proxy_setting_extension/background.js
new file mode 100644
index 0000000..5ed2567
--- /dev/null
+++ b/chrome/test/data/extensions/browsertest/proxy_setting_extension/background.js
@@ -0,0 +1,20 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+chrome.runtime.onInstalled.addListener(() => {
+    var config = {
+      mode: "fixed_servers",
+      rules: {
+        proxyForHttps: {scheme: "http", host: "google.com", port: 5555},
+        bypassList: ["127.0.0.1"]
+      }
+    };
+    chrome.proxy.settings.set(
+      { 'value': config, 'scope': 'regular' },
+      () => {
+        chrome.test.sendMessage('ready');
+      }
+    );
+  }
+)
diff --git a/chrome/test/data/extensions/browsertest/proxy_setting_extension/manifest.json b/chrome/test/data/extensions/browsertest/proxy_setting_extension/manifest.json
new file mode 100644
index 0000000..95aebbf
--- /dev/null
+++ b/chrome/test/data/extensions/browsertest/proxy_setting_extension/manifest.json
@@ -0,0 +1,12 @@
+{
+    "manifest_version": 3,
+    "name": "Proxy extension for testing",
+    "version": "1.0.2",
+    "background": {
+      "service_worker": "background.js"
+    },
+    "permissions": [
+        "proxy", "tabs"
+    ],
+    "incognito": "spanning"
+}
diff --git a/chrome/test/data/webui/bookmarks/BUILD.gn b/chrome/test/data/webui/bookmarks/BUILD.gn
index b295b8f9..1dfbe6a 100644
--- a/chrome/test/data/webui/bookmarks/BUILD.gn
+++ b/chrome/test/data/webui/bookmarks/BUILD.gn
@@ -10,9 +10,9 @@
   out_dir = "$target_gen_dir/tsc"
   tsconfig_base = "tsconfig_base.json"
   path_mappings = [
-    "chrome://bookmarks/*|" +
-        rebase_path("$root_gen_dir/chrome/browser/resources/bookmarks/tsc/*",
-                    target_gen_dir),
+    "chrome://bookmarks/bookmarks.js|" + rebase_path(
+            "$root_gen_dir/chrome/browser/resources/bookmarks/tsc/bookmarks.d.ts",
+            target_gen_dir),
     "chrome://webui-test/*|" +
         rebase_path("$root_gen_dir/chrome/test/data/webui/tsc/*",
                     target_gen_dir),
diff --git a/chrome/test/data/webui/downloads/BUILD.gn b/chrome/test/data/webui/downloads/BUILD.gn
index 9981c28..0133a76 100644
--- a/chrome/test/data/webui/downloads/BUILD.gn
+++ b/chrome/test/data/webui/downloads/BUILD.gn
@@ -15,9 +15,8 @@
     "toolbar_tests.ts",
   ]
 
-  ts_path_mappings =
-      [ "chrome://downloads/*|" +
-        rebase_path("$root_gen_dir/chrome/browser/resources/downloads/tsc/*",
-                    target_gen_dir) ]
+  ts_path_mappings = [ "chrome://downloads/downloads.js|" + rebase_path(
+                           "$root_gen_dir/chrome/browser/resources/downloads/tsc/downloads.d.ts",
+                           target_gen_dir) ]
   ts_deps = [ "//chrome/browser/resources/downloads:build_ts" ]
 }
diff --git a/chrome/test/data/webui/extensions/BUILD.gn b/chrome/test/data/webui/extensions/BUILD.gn
index 5837083..b15e4fd1 100644
--- a/chrome/test/data/webui/extensions/BUILD.gn
+++ b/chrome/test/data/webui/extensions/BUILD.gn
@@ -60,10 +60,9 @@
     ]
   }
 
-  ts_path_mappings =
-      [ "chrome://extensions/*|" +
-        rebase_path("$root_gen_dir/chrome/browser/resources/extensions/tsc/*",
-                    target_gen_dir) ]
+  ts_path_mappings = [ "chrome://extensions/extensions.js|" + rebase_path(
+                           "$root_gen_dir/chrome/browser/resources/extensions/tsc/extensions.d.ts",
+                           target_gen_dir) ]
 
   ts_deps = [ "//chrome/browser/resources/extensions:build_ts" ]
   ts_definitions = [
diff --git a/chrome/test/data/webui/history/BUILD.gn b/chrome/test/data/webui/history/BUILD.gn
index 2575c77..4b25236 100644
--- a/chrome/test/data/webui/history/BUILD.gn
+++ b/chrome/test/data/webui/history/BUILD.gn
@@ -29,9 +29,15 @@
     "test_util.ts",
   ]
 
-  ts_path_mappings =
-      [ "chrome://history/*|" +
-        rebase_path("$root_gen_dir/chrome/browser/resources/history/tsc/*",
-                    target_gen_dir) ]
+  ts_path_mappings = [
+    "chrome://history/history.js|" +
+        rebase_path(
+            "$root_gen_dir/chrome/browser/resources/history/tsc/history.d.ts",
+            target_gen_dir),
+    "chrome://history/lazy_load.ts|" +
+        rebase_path(
+            "$root_gen_dir/chrome/browser/resources/history/tsc/lazy_load.d.ts",
+            target_gen_dir),
+  ]
   ts_deps = [ "//chrome/browser/resources/history:build_ts" ]
 }
diff --git a/chrome/test/data/webui/print_preview/BUILD.gn b/chrome/test/data/webui/print_preview/BUILD.gn
index e607512..685e00c 100644
--- a/chrome/test/data/webui/print_preview/BUILD.gn
+++ b/chrome/test/data/webui/print_preview/BUILD.gn
@@ -81,8 +81,8 @@
   }
 
   ts_path_mappings = [
-    "chrome://print/*|" + rebase_path(
-            "$root_gen_dir/chrome/browser/resources/print_preview/tsc/*",
+    "chrome://print/print_preview.js|" + rebase_path(
+            "$root_gen_dir/chrome/browser/resources/print_preview/tsc/print_preview.d.ts",
             target_gen_dir),
     "chrome://print/pdf/*|" +
         rebase_path("$root_gen_dir/chrome/browser/resources/pdf/tsc/*",
diff --git a/chrome/test/data/webui/settings/BUILD.gn b/chrome/test/data/webui/settings/BUILD.gn
index a26e155..008781ec 100644
--- a/chrome/test/data/webui/settings/BUILD.gn
+++ b/chrome/test/data/webui/settings/BUILD.gn
@@ -188,10 +188,20 @@
     ]
   }
 
-  ts_path_mappings =
-      [ "chrome://settings/*|" +
-        rebase_path("$root_gen_dir/chrome/browser/resources/settings/tsc/*",
-                    target_gen_dir) ]
+  ts_path_mappings = [
+    # Settings tests should only be importing from one of the URLs below, so
+    # that tests work both in optimize_webui=true/false modes.
+    "chrome://settings/privacy_sandbox/*|" + rebase_path(
+            "$root_gen_dir/chrome/browser/resources/settings/tsc/privacy_sandbox/*",
+            target_gen_dir),
+    "chrome://settings/settings.js|" +
+        rebase_path(
+            "$root_gen_dir/chrome/browser/resources/settings/tsc/settings.d.ts",
+            target_gen_dir),
+    "chrome://settings/lazy_load.js|" + rebase_path(
+            "$root_gen_dir/chrome/browser/resources/settings/tsc/lazy_load.d.ts",
+            target_gen_dir),
+  ]
 
   ts_definitions = [
     "//tools/typescript/definitions/autofill_private.d.ts",
diff --git a/chrome/test/data/webui/settings/chromeos/device_page_tests.js b/chrome/test/data/webui/settings/chromeos/device_page_tests.js
index 30d57f2a..6a5bbf4 100644
--- a/chrome/test/data/webui/settings/chromeos/device_page_tests.js
+++ b/chrome/test/data/webui/settings/chromeos/device_page_tests.js
@@ -587,6 +587,15 @@
       const inputVolumeSlider =
           audioPage.shadowRoot.querySelector('#audioInputGainVolumeSlider');
       assertTrue(isVisible(inputVolumeSlider), 'audioInputGainVolumeSlider');
+      const noiseCancellationSubsectionHeader =
+          audioPage.shadowRoot.querySelector(
+              '#audioInputNoiseCancellationLabel');
+      assertTrue(isVisible(noiseCancellationSubsectionHeader));
+      assertEquals(
+          'Noise Cancellation', noiseCancellationSubsectionHeader.textContent);
+      const noiseCancellationToggle = audioPage.shadowRoot.querySelector(
+          '#audioInputNoiseCancellationToggle');
+      assertTrue(isVisible(noiseCancellationToggle));
     });
   });
 
diff --git a/chrome/test/data/webui/settings/chromeos/fake_cros_audio_config_test.js b/chrome/test/data/webui/settings/chromeos/fake_cros_audio_config_test.js
index 7177b4d4..cc96e0da 100644
--- a/chrome/test/data/webui/settings/chromeos/fake_cros_audio_config_test.js
+++ b/chrome/test/data/webui/settings/chromeos/fake_cros_audio_config_test.js
@@ -166,4 +166,17 @@
         crosAudioConfigMojomWebui.MuteState.kNotMuted,
         onPropertiesUpdated.calls_[2][0].inputMuteState);
   });
+
+  test('VerifySetInputVolumeTriggersMatchingPropertyUpdate', () => {
+    const expectedVolumePercent = 32;
+    /** @type {AudioSystemProperties} */
+    const updatedProperties = {
+      ...defaultProperties,
+      inputVolumePercent: expectedVolumePercent,
+    };
+    onPropertiesUpdated.addExpectation(updatedProperties);
+    crosAudioConfig.setInputVolumePercent(expectedVolumePercent);
+
+    mockController.verifyMocks();
+  });
 });
diff --git a/chrome/test/data/webui/settings/personalization_options_test.ts b/chrome/test/data/webui/settings/personalization_options_test.ts
index 2b82f2c..0a74e17 100644
--- a/chrome/test/data/webui/settings/personalization_options_test.ts
+++ b/chrome/test/data/webui/settings/personalization_options_test.ts
@@ -7,8 +7,7 @@
 
 import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 import {SettingsPersonalizationOptionsElement} from 'chrome://settings/lazy_load.js';
-import {PrivacyPageVisibility} from 'chrome://settings/page_visibility.js';
-import {loadTimeData, PrivacyPageBrowserProxyImpl, StatusAction, SyncBrowserProxyImpl} from 'chrome://settings/settings.js';
+import {loadTimeData, PrivacyPageVisibility, PrivacyPageBrowserProxyImpl, StatusAction, SyncBrowserProxyImpl} from 'chrome://settings/settings.js';
 import {assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
 // <if expr="not is_chromeos">
 import {eventToPromise, isVisible} from 'chrome://webui-test/test_util.js';
diff --git a/chrome/test/data/webui/side_panel/customize_chrome/categories_test.ts b/chrome/test/data/webui/side_panel/customize_chrome/categories_test.ts
index b70fbecd..2da7b20 100644
--- a/chrome/test/data/webui/side_panel/customize_chrome/categories_test.ts
+++ b/chrome/test/data/webui/side_panel/customize_chrome/categories_test.ts
@@ -97,4 +97,17 @@
     assertTrue(!!event);
     assertEquals(1, handler.getCallCount('setClassicChromeDefaultTheme'));
   });
+
+  test('clicking upload image creates dialog and sends event', async () => {
+    await setInitialSettings(0);
+    handler.setResultFor('chooseLocalCustomBackground', Promise.resolve({
+      success: true,
+    }));
+
+    const eventPromise = eventToPromise('theme-select', categoriesElement);
+    categoriesElement.$.uploadImageTile.click();
+    const event = await eventPromise;
+    assertTrue(!!event);
+    assertEquals(1, handler.getCallCount('chooseLocalCustomBackground'));
+  });
 });
diff --git a/chrome/test/data/webui/side_panel/side_panel_browsertest.js b/chrome/test/data/webui/side_panel/side_panel_browsertest.js
index 8fde73b..a21341d 100644
--- a/chrome/test/data/webui/side_panel/side_panel_browsertest.js
+++ b/chrome/test/data/webui/side_panel/side_panel_browsertest.js
@@ -55,6 +55,12 @@
   mocha.run();
 });
 
+// TODO(crbug.com/1396268): Flaky on Mac. Re-enable this test.
+GEN('#if BUILDFLAG(IS_MAC)');
+GEN('#define MAYBE_All DISABLED_All');
+GEN('#else');
+GEN('#define MAYBE_All All');
+GEN('#endif');
 
 var ShoppingListTest = class extends SidePanelBrowserTest {
   /** @override */
@@ -63,7 +69,7 @@
   }
 };
 
-TEST_F('ShoppingListTest', 'All', function() {
+TEST_F('ShoppingListTest', 'MAYBE_All', function() {
   mocha.run();
 });
 
diff --git a/chrome/test/data/webui/signin/BUILD.gn b/chrome/test/data/webui/signin/BUILD.gn
index b462206..2d98a3e 100644
--- a/chrome/test/data/webui/signin/BUILD.gn
+++ b/chrome/test/data/webui/signin/BUILD.gn
@@ -64,8 +64,11 @@
   out_dir = "$target_gen_dir/tsc"
   tsconfig_base = "tsconfig_base.json"
   path_mappings = [
-    "chrome://profile-picker/*|" + rebase_path(
-            "$root_gen_dir/chrome/browser/resources/signin/profile_picker/tsc/*",
+    "chrome://profile-picker/profile_picker.js|" + rebase_path(
+            "$root_gen_dir/chrome/browser/resources/signin/profile_picker/tsc/profile_picker.d.ts",
+            target_gen_dir),
+    "chrome://profile-picker/lazy_load.js|" + rebase_path(
+            "$root_gen_dir/chrome/browser/resources/signin/profile_picker/tsc/lazy_load.d.ts",
             target_gen_dir),
     "chrome://signin-dice-web-intercept/*|" + rebase_path(
             "$root_gen_dir/chrome/browser/resources/signin/tsc/dice_web_signin_intercept/*",
diff --git a/chrome/test/data/webui/tab_search/BUILD.gn b/chrome/test/data/webui/tab_search/BUILD.gn
index 0dd30984..e8e2327 100644
--- a/chrome/test/data/webui/tab_search/BUILD.gn
+++ b/chrome/test/data/webui/tab_search/BUILD.gn
@@ -21,9 +21,9 @@
   ]
 
   ts_path_mappings = [
-    "chrome://tab-search.top-chrome/*|" +
-        rebase_path("$root_gen_dir/chrome/browser/resources/tab_search/tsc/*",
-                    target_gen_dir),
+    "chrome://tab-search.top-chrome/tab_search.js|" + rebase_path(
+            "$root_gen_dir/chrome/browser/resources/tab_search/tsc/tab_search.d.ts",
+            target_gen_dir),
     "chrome://webui-test/metrics_reporter/*|" +
         rebase_path(
             "$root_gen_dir/chrome/test/data/webui/metrics_reporter/tsc/*",
diff --git a/chrome/updater/test/integration_test_commands.h b/chrome/updater/test/integration_test_commands.h
index 6071841..7a81612a 100644
--- a/chrome/updater/test/integration_test_commands.h
+++ b/chrome/updater/test/integration_test_commands.h
@@ -46,6 +46,11 @@
                                     const std::string& install_data_index,
                                     const base::Version& from_version,
                                     const base::Version& to_version) const = 0;
+  virtual void ExpectInstallSequence(ScopedServer* test_server,
+                                     const std::string& app_id,
+                                     const std::string& install_data_index,
+                                     const base::Version& from_version,
+                                     const base::Version& to_version) const = 0;
   virtual void ExpectVersionActive(const std::string& version) const = 0;
   virtual void ExpectVersionNotActive(const std::string& version) const = 0;
   virtual void Uninstall() const = 0;
@@ -90,6 +95,7 @@
   virtual void RunUninstallCmdLine() const = 0;
   virtual void SetUpTestService() const = 0;
   virtual void TearDownTestService() const = 0;
+  virtual void RunHandoff(const std::string& app_id) const = 0;
 #endif  // BUILDFLAG(IS_WIN)
   virtual void StressUpdateService() const = 0;
   virtual void CallServiceUpdate(const std::string& app_id,
diff --git a/chrome/updater/test/integration_test_commands_system.cc b/chrome/updater/test/integration_test_commands_system.cc
index a62cb61..27d95ef 100644
--- a/chrome/updater/test/integration_test_commands_system.cc
+++ b/chrome/updater/test/integration_test_commands_system.cc
@@ -102,6 +102,16 @@
                                         to_version);
   }
 
+  void ExpectInstallSequence(ScopedServer* test_server,
+                             const std::string& app_id,
+                             const std::string& install_data_index,
+                             const base::Version& from_version,
+                             const base::Version& to_version) const override {
+    updater::test::ExpectInstallSequence(updater_scope_, test_server, app_id,
+                                         install_data_index, from_version,
+                                         to_version);
+  }
+
   void ExpectVersionActive(const std::string& version) const override {
     RunCommand("expect_version_active", {Param("version", version)});
   }
@@ -251,6 +261,10 @@
   void TearDownTestService() const override {
     updater::test::RunTestServiceCommand("teardown");
   }
+
+  void RunHandoff(const std::string& app_id) const override {
+    RunCommand("run_handoff", {Param("app_id", app_id)});
+  }
 #endif  // BUILDFLAG(IS_WIN)
 
   base::FilePath GetDifferentUserPath() const override {
diff --git a/chrome/updater/test/integration_test_commands_user.cc b/chrome/updater/test/integration_test_commands_user.cc
index 453e1dd..0db12bda 100644
--- a/chrome/updater/test/integration_test_commands_user.cc
+++ b/chrome/updater/test/integration_test_commands_user.cc
@@ -83,6 +83,16 @@
                                         to_version);
   }
 
+  void ExpectInstallSequence(ScopedServer* test_server,
+                             const std::string& app_id,
+                             const std::string& install_data_index,
+                             const base::Version& from_version,
+                             const base::Version& to_version) const override {
+    updater::test::ExpectInstallSequence(updater_scope_, test_server, app_id,
+                                         install_data_index, from_version,
+                                         to_version);
+  }
+
   void ExpectVersionActive(const std::string& version) const override {
     updater::test::ExpectVersionActive(updater_scope_, version);
   }
@@ -218,6 +228,10 @@
   void SetUpTestService() const override {}
 
   void TearDownTestService() const override {}
+
+  void RunHandoff(const std::string& app_id) const override {
+    updater::test::RunHandoff(updater_scope_, app_id);
+  }
 #endif  // BUILDFLAG(IS_WIN)
 
   base::FilePath GetDifferentUserPath() const override {
diff --git a/chrome/updater/test/integration_tests.cc b/chrome/updater/test/integration_tests.cc
index 58f2a80..5fb89f9 100644
--- a/chrome/updater/test/integration_tests.cc
+++ b/chrome/updater/test/integration_tests.cc
@@ -194,6 +194,10 @@
   }
 
   void RunUninstallCmdLine() { test_commands_->RunUninstallCmdLine(); }
+
+  void RunHandoff(const std::string& app_id) {
+    test_commands_->RunHandoff(app_id);
+  }
 #endif  // BUILDFLAG(IS_WIN)
 
   void SetupFakeUpdaterHigherVersion() {
@@ -308,14 +312,13 @@
     test_commands_->ExpectSelfUpdateSequence(test_server);
   }
 
-  void ExpectInstallEvent(ScopedServer* test_server,
-                          const std::string& app_id) {
-    test_server->ExpectOnce(
-        {base::BindRepeating(
-            RequestMatcherRegex,
-            base::StrCat({R"(.*"appid":")", app_id, R"(","enabled":true,")",
-                          R"(event":\[{"eventresult":1,"eventtype":2,.*)"}))},
-        "");
+  void ExpectInstallSequence(ScopedServer* test_server,
+                             const std::string& app_id,
+                             const std::string& install_data_index,
+                             const base::Version& from_version,
+                             const base::Version& to_version) {
+    test_commands_->ExpectInstallSequence(
+        test_server, app_id, install_data_index, from_version, to_version);
   }
 
   void StressUpdateService() { test_commands_->StressUpdateService(); }
@@ -369,7 +372,7 @@
 TEST_F(IntegrationTest, DoNothing) {}
 
 TEST_F(IntegrationTest, Install) {
-  Install();
+  ASSERT_NO_FATAL_FAILURE(Install());
   EXPECT_TRUE(WaitForUpdaterExit());
   ExpectInstalled();
   ExpectVersionActive(kUpdaterVersion);
@@ -393,7 +396,7 @@
 
   // A new version hands off installation to the old version, and doesn't
   // change the active version of the updater.
-  Install();
+  ASSERT_NO_FATAL_FAILURE(Install());
   EXPECT_TRUE(WaitForUpdaterExit());
   ExpectVersionNotActive(kUpdaterVersion);
 
@@ -407,7 +410,7 @@
 
   // Since the old version is not working, the new version should install and
   // become active.
-  Install();
+  ASSERT_NO_FATAL_FAILURE(Install());
   EXPECT_TRUE(WaitForUpdaterExit());
   ExpectVersionActive(kUpdaterVersion);
 
@@ -416,7 +419,7 @@
 #endif  // !BUILDFLAG(IS_LINUX)
 
 TEST_F(IntegrationTest, SelfUninstallOutdatedUpdater) {
-  Install();
+  ASSERT_NO_FATAL_FAILURE(Install());
   ExpectInstalled();
   EXPECT_TRUE(WaitForUpdaterExit());
   SetupFakeUpdaterHigherVersion();
@@ -443,7 +446,7 @@
 
 TEST_F(IntegrationTest, QualifyUpdater) {
   ScopedServer test_server(test_commands_);
-  Install();
+  ASSERT_NO_FATAL_FAILURE(Install());
   ExpectInstalled();
   EXPECT_TRUE(WaitForUpdaterExit());
   SetupFakeUpdaterLowerVersion();
@@ -470,7 +473,7 @@
 
 TEST_F(IntegrationTest, SelfUpdate) {
   ScopedServer test_server(test_commands_);
-  Install();
+  ASSERT_NO_FATAL_FAILURE(Install());
 
   base::Version next_version(base::StringPrintf("%s1", kUpdaterVersion));
   ExpectUpdateSequence(&test_server, kUpdaterAppId, "",
@@ -485,7 +488,7 @@
 
 TEST_F(IntegrationTest, SelfUpdateWithWakeAll) {
   ScopedServer test_server(test_commands_);
-  Install();
+  ASSERT_NO_FATAL_FAILURE(Install());
 
   base::Version next_version(base::StringPrintf("%s1", kUpdaterVersion));
   ExpectUpdateSequence(&test_server, kUpdaterAppId, "",
@@ -508,7 +511,7 @@
                                            TestTimeouts::action_timeout());
 
   ScopedServer test_server(test_commands_);
-  Install();
+  ASSERT_NO_FATAL_FAILURE(Install());
   ExpectInstalled();
 
   // Register apps test1 and test2. Expect pings for each.
@@ -540,7 +543,7 @@
 
 TEST_F(IntegrationTest, UpdateApp) {
   ScopedServer test_server(test_commands_);
-  Install();
+  ASSERT_NO_FATAL_FAILURE(Install());
 
   const std::string kAppId("test");
   InstallApp(kAppId);
@@ -561,9 +564,24 @@
 }
 
 #if BUILDFLAG(IS_WIN)
+TEST_F(IntegrationTest, Handoff) {
+  ScopedServer test_server(test_commands_);
+  ASSERT_NO_FATAL_FAILURE(Install());
+
+  const std::string kAppId("test");
+  const base::Version v1("1");
+  ExpectInstallSequence(&test_server, kAppId, "", base::Version({0, 0, 0, 0}),
+                        v1);
+  RunHandoff(kAppId);
+  EXPECT_TRUE(WaitForUpdaterExit());
+  ExpectAppVersion(kAppId, v1);
+
+  Uninstall();
+}
+
 TEST_F(IntegrationTest, ForceInstallApp) {
   ScopedServer test_server(test_commands_);
-  Install();
+  ASSERT_NO_FATAL_FAILURE(Install());
 
   base::Value::Dict group_policies;
   group_policies.Set("Installtest1", IsSystemInstall(GetTestScope())
@@ -588,7 +606,7 @@
 
 TEST_F(IntegrationTest, MultipleWakesOneNetRequest) {
   ScopedServer test_server(test_commands_);
-  Install();
+  ASSERT_NO_FATAL_FAILURE(Install());
 
   // Only one sequence visible to the server despite multiple wakes.
   ExpectNoUpdateSequence(&test_server, kUpdaterAppId);
@@ -600,7 +618,7 @@
 
 TEST_F(IntegrationTest, MultipleUpdateAllsMultipleNetRequests) {
   ScopedServer test_server(test_commands_);
-  Install();
+  ASSERT_NO_FATAL_FAILURE(Install());
 
   ExpectNoUpdateSequence(&test_server, kUpdaterAppId);
   UpdateAll();
@@ -612,14 +630,14 @@
 
 #if BUILDFLAG(IS_WIN)
 TEST_F(IntegrationTest, MarshalInterface) {
-  Install();
+  ASSERT_NO_FATAL_FAILURE(Install());
   ExpectMarshalInterfaceSucceeds();
   Uninstall();
 }
 
 TEST_F(IntegrationTest, LegacyUpdate3Web) {
   ScopedServer test_server(test_commands_);
-  Install();
+  ASSERT_NO_FATAL_FAILURE(Install());
 
   const char kAppId[] = "test1";
   InstallApp(kAppId);
@@ -648,13 +666,13 @@
 }
 
 TEST_F(IntegrationTest, LegacyProcessLauncher) {
-  Install();
+  ASSERT_NO_FATAL_FAILURE(Install());
   ExpectLegacyProcessLauncherSucceeds();
   Uninstall();
 }
 
 TEST_F(IntegrationTest, LegacyAppCommandWeb) {
-  Install();
+  ASSERT_NO_FATAL_FAILURE(Install());
 
   const char kAppId[] = "test1";
   InstallApp(kAppId);
@@ -668,7 +686,7 @@
 
 TEST_F(IntegrationTest, LegacyPolicyStatus) {
   ScopedServer test_server(test_commands_);
-  Install();
+  ASSERT_NO_FATAL_FAILURE(Install());
 
   const std::string kAppId("test");
   InstallApp(kAppId);
@@ -683,7 +701,7 @@
 }
 
 TEST_F(IntegrationTest, UninstallCmdLine) {
-  Install();
+  ASSERT_NO_FATAL_FAILURE(Install());
   ExpectInstalled();
   ExpectVersionActive(kUpdaterVersion);
   ExpectActiveUpdater();
@@ -703,7 +721,7 @@
 #endif  // BUILDFLAG(IS_WIN)
 
 TEST_F(IntegrationTest, UnregisterUninstalledApp) {
-  Install();
+  ASSERT_NO_FATAL_FAILURE(Install());
   ExpectInstalled();
   InstallApp("test1");
   InstallApp("test2");
@@ -724,7 +742,7 @@
 }
 
 TEST_F(IntegrationTest, UninstallIfMaxServerWakesBeforeRegistrationExceeded) {
-  Install();
+  ASSERT_NO_FATAL_FAILURE(Install());
   EXPECT_TRUE(WaitForUpdaterExit());
   ExpectInstalled();
   SetServerStarts(24);
@@ -733,7 +751,7 @@
 }
 
 TEST_F(IntegrationTest, UninstallUpdaterWhenAllAppsUninstalled) {
-  Install();
+  ASSERT_NO_FATAL_FAILURE(Install());
   InstallApp("test1");
   ExpectInstalled();
   EXPECT_TRUE(WaitForUpdaterExit());
@@ -750,7 +768,7 @@
 }
 
 TEST_F(IntegrationTest, RotateLog) {
-  Install();
+  ASSERT_NO_FATAL_FAILURE(Install());
   EXPECT_TRUE(WaitForUpdaterExit());
   FillLog();
   RunWake(0);
@@ -763,7 +781,7 @@
 // test need not run on Windows.
 #if BUILDFLAG(IS_MAC)
 TEST_F(IntegrationTest, UnregisterUnownedApp) {
-  Install();
+  ASSERT_NO_FATAL_FAILURE(Install());
   ExpectInstalled();
   ExpectVersionActive(kUpdaterVersion);
   ExpectActiveUpdater();
@@ -837,7 +855,7 @@
 #endif
 
 TEST_F(IntegrationTest, UpdateServiceStress) {
-  Install();
+  ASSERT_NO_FATAL_FAILURE(Install());
   ExpectInstalled();
   StressUpdateService();
   Uninstall();
@@ -845,7 +863,7 @@
 
 TEST_F(IntegrationTest, SameVersionUpdate) {
   ScopedServer test_server(test_commands_);
-  Install();
+  ASSERT_NO_FATAL_FAILURE(Install());
   ExpectInstalled();
 
   const std::string app_id = "test-appid";
@@ -885,7 +903,7 @@
 
 TEST_F(IntegrationTest, InstallDataIndex) {
   ScopedServer test_server(test_commands_);
-  Install();
+  ASSERT_NO_FATAL_FAILURE(Install());
   ExpectInstalled();
 
   const std::string app_id = "test-appid";
@@ -925,7 +943,7 @@
 
 TEST_F(IntegrationTest, MigrateLegacyUpdater) {
   SetupFakeLegacyUpdaterData();
-  Install();
+  ASSERT_NO_FATAL_FAILURE(Install());
   ExpectInstalled();
   ExpectLegacyUpdaterDataMigrated();
   Uninstall();
@@ -943,21 +961,21 @@
 }
 
 TEST_F(IntegrationTest, OfflineInstall) {
-  Install();
+  ASSERT_NO_FATAL_FAILURE(Install());
   ExpectInstalled();
   RunOfflineInstall(/*is_legacy_install=*/false, /*is_silent_install=*/false);
   Uninstall();
 }
 
 TEST_F(IntegrationTest, SilentOfflineInstall) {
-  Install();
+  ASSERT_NO_FATAL_FAILURE(Install());
   ExpectInstalled();
   RunOfflineInstall(/*is_legacy_install=*/false, /*is_silent_install=*/true);
   Uninstall();
 }
 
 TEST_F(IntegrationTest, LegacySilentOfflineInstall) {
-  Install();
+  ASSERT_NO_FATAL_FAILURE(Install());
   ExpectInstalled();
   RunOfflineInstall(/*is_legacy_install=*/true, /*is_silent_install=*/true);
   Uninstall();
diff --git a/chrome/updater/test/integration_tests_helper.cc b/chrome/updater/test/integration_tests_helper.cc
index 7ca630d..c887368 100644
--- a/chrome/updater/test/integration_tests_helper.cc
+++ b/chrome/updater/test/integration_tests_helper.cc
@@ -273,6 +273,7 @@
     {"expect_legacy_policy_status_succeeds",
      WithSystemScope(Wrap(&ExpectLegacyPolicyStatusSucceeds))},
     {"run_uninstall_cmd_line", WithSystemScope(Wrap(&RunUninstallCmdLine))},
+    {"run_handoff", WithSwitch("app_id", WithSystemScope(Wrap(&RunHandoff)))},
 #endif  // BUILDFLAG(IS_WIN)
     {"expect_version_active",
      WithSwitch("version", WithSystemScope(Wrap(&ExpectVersionActive)))},
diff --git a/chrome/updater/test/integration_tests_impl.cc b/chrome/updater/test/integration_tests_impl.cc
index 0cf5453..c407f92 100644
--- a/chrome/updater/test/integration_tests_impl.cc
+++ b/chrome/updater/test/integration_tests_impl.cc
@@ -194,6 +194,57 @@
   EXPECT_EQ(exit_code, expected_exit_code);
 }
 
+void ExpectSequence(UpdaterScope scope,
+                    ScopedServer* test_server,
+                    const std::string& app_id,
+                    const std::string& install_data_index,
+                    int event_type,
+                    const base::Version& from_version,
+                    const base::Version& to_version) {
+  base::FilePath test_data_path;
+  ASSERT_TRUE(base::PathService::Get(chrome::DIR_TEST_DATA, &test_data_path));
+  base::FilePath crx_path = test_data_path.Append(FILE_PATH_LITERAL("updater"))
+                                .AppendASCII(kDoNothingCRXName);
+  ASSERT_TRUE(base::PathExists(crx_path));
+
+  // First request: update check.
+  test_server->ExpectOnce(
+      {base::BindRepeating(
+           RequestMatcherRegex,
+           base::StringPrintf(R"(.*"appid":"%s".*)", app_id.c_str())),
+       base::BindRepeating(
+           RequestMatcherRegex,
+           base::StringPrintf(
+               R"(.*%s)",
+               !install_data_index.empty()
+                   ? base::StringPrintf(
+                         R"("data":\[{"index":"%s","name":"install"}],.*)",
+                         install_data_index.c_str())
+                         .c_str()
+                   : "")),
+       GetScopePredicate(scope)},
+      GetUpdateResponse(app_id, install_data_index,
+                        test_server->base_url().spec(), to_version, crx_path,
+                        kDoNothingCRXRun, {}));
+
+  // Second request: update download.
+  std::string crx_bytes;
+  base::ReadFileToString(crx_path, &crx_bytes);
+  test_server->ExpectOnce({base::BindRepeating(RequestMatcherRegex, "")},
+                          crx_bytes);
+
+  // Third request: event ping.
+  test_server->ExpectOnce(
+      {base::BindRepeating(
+           RequestMatcherRegex,
+           base::StringPrintf(R"(.*"eventresult":1,"eventtype":%d,)"
+                              R"("nextversion":"%s","previousversion":"%s".*)",
+                              event_type, to_version.GetString().c_str(),
+                              from_version.GetString().c_str())),
+       GetScopePredicate(scope)},
+      ")]}'\n");
+}
+
 }  // namespace
 
 void ExitTestMode(UpdaterScope scope) {
@@ -544,48 +595,18 @@
                           const std::string& install_data_index,
                           const base::Version& from_version,
                           const base::Version& to_version) {
-  base::FilePath test_data_path;
-  ASSERT_TRUE(base::PathService::Get(chrome::DIR_TEST_DATA, &test_data_path));
-  base::FilePath crx_path = test_data_path.Append(FILE_PATH_LITERAL("updater"))
-                                .AppendASCII(kDoNothingCRXName);
-  ASSERT_TRUE(base::PathExists(crx_path));
+  ExpectSequence(scope, test_server, app_id, install_data_index, 3,
+                 from_version, to_version);
+}
 
-  // First request: update check.
-  test_server->ExpectOnce(
-      {base::BindRepeating(
-           RequestMatcherRegex,
-           base::StringPrintf(R"(.*"appid":"%s".*)", app_id.c_str())),
-       base::BindRepeating(
-           RequestMatcherRegex,
-           base::StringPrintf(
-               R"(.*%s)",
-               !install_data_index.empty()
-                   ? base::StringPrintf(
-                         R"("data":\[{"index":"%s","name":"install"}],.*)",
-                         install_data_index.c_str())
-                         .c_str()
-                   : "")),
-       GetScopePredicate(scope)},
-      GetUpdateResponse(app_id, install_data_index,
-                        test_server->base_url().spec(), to_version, crx_path,
-                        kDoNothingCRXRun, {}));
-
-  // Second request: update download.
-  std::string crx_bytes;
-  base::ReadFileToString(crx_path, &crx_bytes);
-  test_server->ExpectOnce({base::BindRepeating(RequestMatcherRegex, "")},
-                          crx_bytes);
-
-  // Third request: event ping.
-  test_server->ExpectOnce(
-      {base::BindRepeating(
-           RequestMatcherRegex,
-           base::StringPrintf(R"(.*"eventresult":1,"eventtype":3,)"
-                              R"("nextversion":"%s","previousversion":"%s".*)",
-                              to_version.GetString().c_str(),
-                              from_version.GetString().c_str())),
-       GetScopePredicate(scope)},
-      ")]}'\n");
+void ExpectInstallSequence(UpdaterScope scope,
+                           ScopedServer* test_server,
+                           const std::string& app_id,
+                           const std::string& install_data_index,
+                           const base::Version& from_version,
+                           const base::Version& to_version) {
+  ExpectSequence(scope, test_server, app_id, install_data_index, 2,
+                 from_version, to_version);
 }
 
 // Runs multiple cycles of instantiating the update service, calling
diff --git a/chrome/updater/test/integration_tests_impl.h b/chrome/updater/test/integration_tests_impl.h
index 061a92abc..e6d3c68 100644
--- a/chrome/updater/test/integration_tests_impl.h
+++ b/chrome/updater/test/integration_tests_impl.h
@@ -215,6 +215,7 @@
                                const base::Value::Dict& arguments);
 
 void RunUninstallCmdLine(UpdaterScope scope);
+void RunHandoff(UpdaterScope scope, const std::string& app_id);
 #endif  // BUILDFLAG(IS_WIN)
 
 // Returns the number of files in the directory, not including directories,
@@ -234,6 +235,13 @@
                           const base::Version& from_version,
                           const base::Version& to_version);
 
+void ExpectInstallSequence(UpdaterScope scope,
+                           ScopedServer* test_server,
+                           const std::string& app_id,
+                           const std::string& install_data_index,
+                           const base::Version& from_version,
+                           const base::Version& to_version);
+
 void StressUpdateService(UpdaterScope scope);
 
 void CallServiceUpdate(UpdaterScope updater_scope,
@@ -259,7 +267,6 @@
 void RunOfflineInstall(UpdaterScope scope,
                        bool is_legacy_install,
                        bool is_silent_install);
-
 }  // namespace updater::test
 
 #endif  // CHROME_UPDATER_TEST_INTEGRATION_TESTS_IMPL_H_
diff --git a/chrome/updater/test/integration_tests_win.cc b/chrome/updater/test/integration_tests_win.cc
index ce9df3e..65ea452 100644
--- a/chrome/updater/test/integration_tests_win.cc
+++ b/chrome/updater/test/integration_tests_win.cc
@@ -1410,6 +1410,27 @@
   EXPECT_EQ(0, exit_code);
 }
 
+void RunHandoff(UpdaterScope scope, const std::string& app_id) {
+  const absl::optional<base::FilePath> installed_executable_path =
+      GetInstalledExecutablePath(scope);
+  ASSERT_TRUE(installed_executable_path);
+  ASSERT_TRUE(base::PathExists(*installed_executable_path));
+
+  base::ScopedAllowBaseSyncPrimitivesForTesting allow_wait_process;
+  const std::wstring command_line(base::StrCat(
+      {installed_executable_path->value(), L" /handoff \"appguid=",
+       base::ASCIIToWide(app_id), L"&needsadmin=",
+       IsSystemInstall(scope) ? L"Prefers" : L"False", L"\" /silent"}));
+  VLOG(0) << " RunHandoff: " << command_line;
+  const base::Process process = base::LaunchProcess(command_line, {});
+  ASSERT_TRUE(process.IsValid());
+
+  int exit_code = 0;
+  ASSERT_TRUE(process.WaitForExitWithTimeout(TestTimeouts::action_max_timeout(),
+                                             &exit_code));
+  ASSERT_EQ(exit_code, 0);
+}
+
 void SetupFakeLegacyUpdaterData(UpdaterScope scope) {
   const HKEY root = UpdaterScopeToHKeyRoot(scope);
 
diff --git a/chrome/updater/updater_scope.cc b/chrome/updater/updater_scope.cc
index 1d1314b..0d02a500 100644
--- a/chrome/updater/updater_scope.cc
+++ b/chrome/updater/updater_scope.cc
@@ -8,11 +8,11 @@
 #include "base/command_line.h"
 #include "build/build_config.h"
 #include "chrome/updater/constants.h"
+#include "chrome/updater/util/util.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
 #if BUILDFLAG(IS_WIN)
 #include "chrome/updater/tag.h"
-#include "chrome/updater/util/util.h"
 #include "chrome/updater/util/win_util.h"
 #endif
 
@@ -73,7 +73,7 @@
 }
 
 UpdaterScope GetUpdaterScope() {
-  return GetUpdaterScopeForCommandLine(*base::CommandLine::ForCurrentProcess());
+  return GetUpdaterScopeForCommandLine(GetCommandLineLegacyCompatible());
 }
 
 bool IsSystemInstall() {
diff --git a/chromeos/ash/components/audio/cros_audio_config_impl.cc b/chromeos/ash/components/audio/cros_audio_config_impl.cc
index 1fd8962..278d5e8 100644
--- a/chromeos/ash/components/audio/cros_audio_config_impl.cc
+++ b/chromeos/ash/components/audio/cros_audio_config_impl.cc
@@ -56,34 +56,42 @@
   return mojo_device;
 }
 
-CrosAudioConfigImpl::CrosAudioConfigImpl() {
-  CrasAudioHandler::Get()->AddAudioObserver(this);
+CrosAudioConfigImpl::CrosAudioConfigImpl()
+    : audio_handler_{CrasAudioHandler::Get()} {
+  DCHECK(audio_handler_);
+  audio_handler_->AddAudioObserver(this);
 }
 
 CrosAudioConfigImpl::~CrosAudioConfigImpl() {
-  if (CrasAudioHandler::Get())
-    CrasAudioHandler::Get()->RemoveAudioObserver(this);
+  if (audio_handler_) {
+    audio_handler_->RemoveAudioObserver(this);
+  }
 }
 
 uint8_t CrosAudioConfigImpl::GetOutputVolumePercent() const {
-  return CrasAudioHandler::Get()->GetOutputVolumePercent();
+  DCHECK(audio_handler_);
+  return audio_handler_->GetOutputVolumePercent();
 }
 
 mojom::MuteState CrosAudioConfigImpl::GetOutputMuteState() const {
+  DCHECK(audio_handler_);
   // TODO(crbug.com/1092970): Add kMutedExternally.
-  if (CrasAudioHandler::Get()->IsOutputMutedByPolicy())
+  if (audio_handler_->IsOutputMutedByPolicy()) {
     return mojom::MuteState::kMutedByPolicy;
+  }
 
-  if (CrasAudioHandler::Get()->IsOutputMuted())
+  if (audio_handler_->IsOutputMuted()) {
     return mojom::MuteState::kMutedByUser;
+  }
 
   return mojom::MuteState::kNotMuted;
 }
 
 void CrosAudioConfigImpl::GetAudioDevices(
     std::vector<mojom::AudioDevicePtr>* output_devices_out) const {
+  DCHECK(audio_handler_);
   AudioDeviceList audio_devices_list;
-  CrasAudioHandler::Get()->GetAudioDevices(&audio_devices_list);
+  audio_handler_->GetAudioDevices(&audio_devices_list);
   for (const auto& device : audio_devices_list) {
     if (!device.is_for_simple_usage()) {
       continue;
@@ -97,13 +105,13 @@
 }
 
 void CrosAudioConfigImpl::SetOutputVolumePercent(int8_t volume) {
-  CrasAudioHandler* audio_handler = CrasAudioHandler::Get();
-  audio_handler->SetOutputVolumePercent(volume);
+  DCHECK(audio_handler_);
+  audio_handler_->SetOutputVolumePercent(volume);
 
   // If the volume is above certain level and it's muted, it should be unmuted.
-  if (audio_handler->IsOutputMuted() &&
-      volume > audio_handler->GetOutputDefaultVolumeMuteThreshold()) {
-    audio_handler->SetOutputMute(false);
+  if (audio_handler_->IsOutputMuted() &&
+      volume > audio_handler_->GetOutputDefaultVolumeMuteThreshold()) {
+    audio_handler_->SetOutputMute(false);
   }
 }
 
diff --git a/chromeos/ash/components/audio/cros_audio_config_impl.h b/chromeos/ash/components/audio/cros_audio_config_impl.h
index a67a734..1583d73 100644
--- a/chromeos/ash/components/audio/cros_audio_config_impl.h
+++ b/chromeos/ash/components/audio/cros_audio_config_impl.h
@@ -6,6 +6,7 @@
 #define CHROMEOS_ASH_COMPONENTS_AUDIO_CROS_AUDIO_CONFIG_IMPL_H_
 
 #include "base/component_export.h"
+#include "base/memory/raw_ptr.h"
 #include "chromeos/ash/components/audio/cras_audio_handler.h"
 #include "chromeos/ash/components/audio/cros_audio_config.h"
 
@@ -30,6 +31,8 @@
   void OnOutputNodeVolumeChanged(uint64_t node_id, int volume) override;
   void OnOutputMuteChanged(bool mute_on) override;
   void OnAudioNodesChanged() override;
+
+  base::raw_ptr<CrasAudioHandler> audio_handler_;
 };
 
 }  // namespace ash::audio_config
diff --git a/chromeos/ash/components/network/network_state.cc b/chromeos/ash/components/network/network_state.cc
index b69f724..d2da45ed 100644
--- a/chromeos/ash/components/network/network_state.cc
+++ b/chromeos/ash/components/network/network_state.cc
@@ -650,6 +650,13 @@
 }
 
 void NetworkState::UpdateCaptivePortalState(const base::Value& properties) {
+  if (!IsConnectedState()) {
+    // Unconnected networks are in an unknown portal state and should not
+    // update histograms.
+    shill_portal_state_ = PortalState::kUnknown;
+    return;
+  }
+
   int status_code =
       properties.FindIntKey(shill::kPortalDetectionFailedStatusCodeProperty)
           .value_or(0);
diff --git a/chromeos/ash/components/network/network_state.h b/chromeos/ash/components/network/network_state.h
index 4d29fc3..8f64ae17 100644
--- a/chromeos/ash/components/network/network_state.h
+++ b/chromeos/ash/components/network/network_state.h
@@ -27,6 +27,7 @@
 
 class DeviceState;
 class NetworkStateHandler;
+class NetworkStateTest;
 
 // Simple class to provide network state information about a network service.
 // This class should always be passed as a const* and should never be held
@@ -292,6 +293,7 @@
  private:
   friend class MobileActivatorTest;
   friend class NetworkStateHandler;
+  friend class NetworkStateTest;
 
   // Updates |name_| from the 'WiFi.HexSSID' entry in |properties|, which must
   // be of type DICTIONARY, if the key exists, and validates |name_|. Returns
diff --git a/chromeos/ash/components/network/network_state_handler.cc b/chromeos/ash/components/network/network_state_handler.cc
index 336dbd93..8a80c0ed 100644
--- a/chromeos/ash/components/network/network_state_handler.cc
+++ b/chromeos/ash/components/network/network_state_handler.cc
@@ -2152,10 +2152,12 @@
 void NetworkStateHandler::SendPortalHistogramTimes(base::TimeDelta elapsed) {
   switch (default_network_portal_state_) {
     case NetworkState::PortalState::kPortal:
-      base::UmaHistogramTimes("Network.RedirectFoundToOnlineTime", elapsed);
+      base::UmaHistogramMediumTimes("Network.RedirectFoundToOnlineTime",
+                                    elapsed);
       break;
     case NetworkState::PortalState::kPortalSuspected:
-      base::UmaHistogramTimes("Network.PortalSuspectedToOnlineTime", elapsed);
+      base::UmaHistogramMediumTimes("Network.PortalSuspectedToOnlineTime",
+                                    elapsed);
       break;
     default:
       // Previous state was not portalled, no times to report.
diff --git a/chromeos/ash/components/network/network_state_unittest.cc b/chromeos/ash/components/network/network_state_unittest.cc
index 0ef0468..3a2af70 100644
--- a/chromeos/ash/components/network/network_state_unittest.cc
+++ b/chromeos/ash/components/network/network_state_unittest.cc
@@ -17,6 +17,7 @@
 #include "chromeos/ash/components/network/network_state_handler.h"
 #include "chromeos/ash/components/network/network_state_test_helper.h"
 #include "chromeos/ash/components/network/tether_constants.h"
+#include "net/http/http_status_code.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/cros_system_api/dbus/service_constants.h"
@@ -29,15 +30,22 @@
 const char kTestCellularDevicePath[] = "cellular_path";
 const char kTestCellularDeviceName[] = "cellular_name";
 
+}  // namespace
+
 class NetworkStateTest : public testing::Test {
  public:
-  NetworkStateTest() : network_state_("test_path") {}
+  NetworkStateTest() = default;
 
   NetworkStateTest(const NetworkStateTest&) = delete;
   NetworkStateTest& operator=(const NetworkStateTest&) = delete;
 
   // testing::Test:
-  void SetUp() override { AddCellularDevice(); }
+  void SetUp() override {
+    network_state_ = std::make_unique<NetworkState>("test_path");
+    AddCellularDevice();
+  }
+
+  void TearDown() override { network_state_.reset(); }
 
  protected:
   const DeviceState* GetCellularDevice() {
@@ -46,7 +54,7 @@
   }
 
   bool SetProperty(const std::string& key, base::Value value) {
-    const bool result = network_state_.PropertyChanged(key, value);
+    const bool result = network_state_->PropertyChanged(key, value);
     properties_.SetKey(key, std::move(value));
     return result;
   }
@@ -56,10 +64,22 @@
   }
 
   bool SignalInitialPropertiesReceived() {
-    return network_state_.InitialPropertiesReceived(properties_);
+    return network_state_->InitialPropertiesReceived(properties_);
   }
 
-  NetworkState network_state_;
+  void SetConnectionState(const std::string& connection_state) {
+    network_state_->SetConnectionState(connection_state);
+  }
+
+  void UpdateCaptivePortalState(const base::Value& properties) {
+    network_state_->UpdateCaptivePortalState(properties);
+  }
+
+  NetworkState::PortalState GetShillPortalState() {
+    return network_state_->shill_portal_state_;
+  }
+
+  std::unique_ptr<NetworkState> network_state_;
 
  private:
   void AddCellularDevice() {
@@ -74,8 +94,6 @@
   base::Value properties_{base::Value::Type::DICTIONARY};
 };
 
-}  // namespace
-
 // Setting kNameProperty should set network name after call to
 // InitialPropertiesReceived().
 TEST_F(NetworkStateTest, NameAscii) {
@@ -84,7 +102,7 @@
   std::string network_setname = "Name TEST";
   EXPECT_TRUE(SetStringProperty(shill::kNameProperty, network_setname));
   EXPECT_FALSE(SignalInitialPropertiesReceived());
-  EXPECT_EQ(network_state_.name(), network_setname);
+  EXPECT_EQ(network_state_->name(), network_setname);
 }
 
 TEST_F(NetworkStateTest, NameAsciiWithNull) {
@@ -94,7 +112,7 @@
   std::string network_setname_result = "Name TEST";
   EXPECT_TRUE(SetStringProperty(shill::kNameProperty, network_setname));
   EXPECT_FALSE(SignalInitialPropertiesReceived());
-  EXPECT_EQ(network_state_.name(), network_setname_result);
+  EXPECT_EQ(network_state_->name(), network_setname_result);
 }
 
 // Truncates invalid UTF-8. base::Value has a DCHECK against invalid UTF-8
@@ -107,7 +125,7 @@
   std::string network_setname_result = "SSID TEST \xEF\xBF\xBD\xEF\xBF\xBD!";
   EXPECT_TRUE(SetStringProperty(shill::kNameProperty, network_setname));
   EXPECT_TRUE(SignalInitialPropertiesReceived());
-  EXPECT_EQ(network_state_.name(), network_setname_result);
+  EXPECT_EQ(network_state_->name(), network_setname_result);
 }
 #endif
 
@@ -119,7 +137,7 @@
   std::string wifi_utf8_result = "UTF-8 \xE3\x81\x82\xE3\x81\x84\xE3\x81\x86";
   EXPECT_TRUE(SetStringProperty(shill::kNameProperty, wifi_utf8));
   EXPECT_FALSE(SignalInitialPropertiesReceived());
-  EXPECT_EQ(network_state_.name(), wifi_utf8_result);
+  EXPECT_EQ(network_state_->name(), wifi_utf8_result);
 }
 
 // latin1 SSID -> UTF8 SSID (Hex)
@@ -132,7 +150,7 @@
   std::string wifi_latin1_result = "latin-1 T\xc3\xa9\x6c\xc3\xa9\x63om";
   EXPECT_TRUE(SetStringProperty(shill::kWifiHexSsid, wifi_latin1_hex));
   EXPECT_TRUE(SignalInitialPropertiesReceived());
-  EXPECT_EQ(network_state_.name(), wifi_latin1_result);
+  EXPECT_EQ(network_state_->name(), wifi_latin1_result);
 }
 
 // Hex SSID
@@ -144,11 +162,11 @@
       base::HexEncode(wifi_hex_result.c_str(), wifi_hex_result.length());
   EXPECT_TRUE(SetStringProperty(shill::kWifiHexSsid, wifi_hex));
   EXPECT_TRUE(SignalInitialPropertiesReceived());
-  EXPECT_EQ(wifi_hex_result, network_state_.name());
+  EXPECT_EQ(wifi_hex_result, network_state_->name());
 
   // Check HexSSID via network state dictionary.
   base::Value dictionary(base::Value::Type::DICTIONARY);
-  network_state_.GetStateProperties(&dictionary);
+  network_state_->GetStateProperties(&dictionary);
   std::string* value = dictionary.FindStringKey(shill::kWifiHexSsid);
   EXPECT_NE(nullptr, value);
   EXPECT_EQ(wifi_hex, *value);
@@ -168,7 +186,7 @@
       base::HexEncode(non_utf8_ssid.data(), non_utf8_ssid.size());
   EXPECT_TRUE(SetStringProperty(shill::kWifiHexSsid, wifi_hex));
   EXPECT_TRUE(SignalInitialPropertiesReceived());
-  EXPECT_EQ(network_state_.raw_ssid(), non_utf8_ssid_bytes);
+  EXPECT_EQ(network_state_->raw_ssid(), non_utf8_ssid_bytes);
 }
 
 // Multiple updates for Hex SSID should work fine.
@@ -184,6 +202,8 @@
 
 TEST_F(NetworkStateTest, CaptivePortalState) {
   std::string network_name = "test";
+  network_state_->set_visible(true);
+
   EXPECT_TRUE(SetStringProperty(shill::kTypeProperty, shill::kTypeWifi));
   EXPECT_TRUE(SetStringProperty(shill::kNameProperty, network_name));
   std::string hex_ssid =
@@ -193,34 +213,34 @@
   // State != portal or online -> portal_state() == kUnknown
   EXPECT_TRUE(SetStringProperty(shill::kStateProperty, shill::kStateReady));
   SignalInitialPropertiesReceived();
-  EXPECT_EQ(network_state_.GetPortalState(),
+  EXPECT_EQ(network_state_->GetPortalState(),
             NetworkState::PortalState::kUnknown);
 
   // State == online -> portal_state() == kOnline
   EXPECT_TRUE(SetStringProperty(shill::kStateProperty, shill::kStateOnline));
   SignalInitialPropertiesReceived();
-  EXPECT_EQ(network_state_.GetPortalState(),
+  EXPECT_EQ(network_state_->GetPortalState(),
             NetworkState::PortalState::kOnline);
 
   // State == redirect-found -> portal_state() == kPortal
   EXPECT_TRUE(
       SetStringProperty(shill::kStateProperty, shill::kStateRedirectFound));
   SignalInitialPropertiesReceived();
-  EXPECT_EQ(network_state_.GetPortalState(),
+  EXPECT_EQ(network_state_->GetPortalState(),
             NetworkState::PortalState::kPortal);
 
   // State == portal-suspected -> portal_state() == kPortalSuspected
   EXPECT_TRUE(
       SetStringProperty(shill::kStateProperty, shill::kStatePortalSuspected));
   SignalInitialPropertiesReceived();
-  EXPECT_EQ(network_state_.GetPortalState(),
+  EXPECT_EQ(network_state_->GetPortalState(),
             NetworkState::PortalState::kPortalSuspected);
 
   // State == no-connectivity -> portal_state() == kOffline
   EXPECT_TRUE(
       SetStringProperty(shill::kStateProperty, shill::kStateNoConnectivity));
   SignalInitialPropertiesReceived();
-  EXPECT_EQ(network_state_.GetPortalState(),
+  EXPECT_EQ(network_state_->GetPortalState(),
             NetworkState::PortalState::kNoInternet);
 }
 
@@ -236,9 +256,10 @@
                   base::Value("third-party-vpn-provider-extension-id"));
   EXPECT_TRUE(SetProperty(shill::kProviderProperty, std::move(provider)));
   SignalInitialPropertiesReceived();
-  ASSERT_TRUE(network_state_.vpn_provider());
-  EXPECT_EQ(network_state_.vpn_provider()->type, shill::kProviderThirdPartyVpn);
-  EXPECT_EQ(network_state_.vpn_provider()->id,
+  ASSERT_TRUE(network_state_->vpn_provider());
+  EXPECT_EQ(network_state_->vpn_provider()->type,
+            shill::kProviderThirdPartyVpn);
+  EXPECT_EQ(network_state_->vpn_provider()->id,
             "third-party-vpn-provider-extension-id");
 }
 
@@ -252,114 +273,114 @@
   provider.SetKey(shill::kHostProperty, base::Value("package.name.foo"));
   EXPECT_TRUE(SetProperty(shill::kProviderProperty, std::move(provider)));
   SignalInitialPropertiesReceived();
-  ASSERT_TRUE(network_state_.vpn_provider());
-  EXPECT_EQ(network_state_.vpn_provider()->type, shill::kProviderArcVpn);
-  EXPECT_EQ(network_state_.vpn_provider()->id, "package.name.foo");
+  ASSERT_TRUE(network_state_->vpn_provider());
+  EXPECT_EQ(network_state_->vpn_provider()->type, shill::kProviderArcVpn);
+  EXPECT_EQ(network_state_->vpn_provider()->id, "package.name.foo");
 }
 
 TEST_F(NetworkStateTest, AllowRoaming) {
-  EXPECT_FALSE(network_state_.allow_roaming());
+  EXPECT_FALSE(network_state_->allow_roaming());
   EXPECT_TRUE(
       SetProperty(shill::kCellularAllowRoamingProperty, base::Value(true)));
-  EXPECT_TRUE(network_state_.allow_roaming());
+  EXPECT_TRUE(network_state_->allow_roaming());
 }
 
 TEST_F(NetworkStateTest, Visible) {
-  EXPECT_FALSE(network_state_.visible());
+  EXPECT_FALSE(network_state_->visible());
 
-  network_state_.set_visible(true);
-  EXPECT_TRUE(network_state_.visible());
+  network_state_->set_visible(true);
+  EXPECT_TRUE(network_state_->visible());
 
-  network_state_.set_visible(false);
-  EXPECT_FALSE(network_state_.visible());
+  network_state_->set_visible(false);
+  EXPECT_FALSE(network_state_->visible());
 }
 
 TEST_F(NetworkStateTest, ConnectionState) {
-  network_state_.set_visible(true);
+  network_state_->set_visible(true);
 
-  network_state_.SetConnectionState(shill::kStateConfiguration);
-  EXPECT_EQ(network_state_.connection_state(), shill::kStateConfiguration);
-  EXPECT_TRUE(network_state_.IsConnectingState());
-  EXPECT_TRUE(network_state_.IsConnectingOrConnected());
-  EXPECT_TRUE(network_state_.IsActive());
+  network_state_->SetConnectionState(shill::kStateConfiguration);
+  EXPECT_EQ(network_state_->connection_state(), shill::kStateConfiguration);
+  EXPECT_TRUE(network_state_->IsConnectingState());
+  EXPECT_TRUE(network_state_->IsConnectingOrConnected());
+  EXPECT_TRUE(network_state_->IsActive());
   // State change to configuration from idle should not set connect_requested
   // unless explicitly set by the UI.
-  EXPECT_FALSE(network_state_.connect_requested());
+  EXPECT_FALSE(network_state_->connect_requested());
 
-  network_state_.SetConnectionState(shill::kStateOnline);
-  EXPECT_EQ(network_state_.connection_state(), shill::kStateOnline);
-  EXPECT_TRUE(network_state_.IsConnectedState());
-  EXPECT_TRUE(network_state_.IsConnectingOrConnected());
-  EXPECT_TRUE(network_state_.IsActive());
+  network_state_->SetConnectionState(shill::kStateOnline);
+  EXPECT_EQ(network_state_->connection_state(), shill::kStateOnline);
+  EXPECT_TRUE(network_state_->IsConnectedState());
+  EXPECT_TRUE(network_state_->IsConnectingOrConnected());
+  EXPECT_TRUE(network_state_->IsActive());
 
-  network_state_.SetConnectionState(shill::kStateConfiguration);
-  EXPECT_EQ(network_state_.connection_state(), shill::kStateConfiguration);
-  EXPECT_TRUE(network_state_.IsConnectingState());
+  network_state_->SetConnectionState(shill::kStateConfiguration);
+  EXPECT_EQ(network_state_->connection_state(), shill::kStateConfiguration);
+  EXPECT_TRUE(network_state_->IsConnectingState());
   // State change to configuration from a connected state should set
   // connect_requested.
-  EXPECT_TRUE(network_state_.connect_requested());
+  EXPECT_TRUE(network_state_->connect_requested());
 
-  network_state_.SetConnectionState(shill::kStateOnline);
-  EXPECT_TRUE(network_state_.IsConnectedState());
+  network_state_->SetConnectionState(shill::kStateOnline);
+  EXPECT_TRUE(network_state_->IsConnectedState());
   // State change to connected should clear connect_requested.
-  EXPECT_FALSE(network_state_.connect_requested());
+  EXPECT_FALSE(network_state_->connect_requested());
 
-  network_state_.SetConnectionState(shill::kStateIdle);
-  EXPECT_EQ(network_state_.connection_state(), shill::kStateIdle);
-  EXPECT_FALSE(network_state_.IsConnectedState());
-  EXPECT_FALSE(network_state_.IsConnectingState());
-  EXPECT_FALSE(network_state_.IsConnectingOrConnected());
-  EXPECT_FALSE(network_state_.IsActive());
+  network_state_->SetConnectionState(shill::kStateIdle);
+  EXPECT_EQ(network_state_->connection_state(), shill::kStateIdle);
+  EXPECT_FALSE(network_state_->IsConnectedState());
+  EXPECT_FALSE(network_state_->IsConnectingState());
+  EXPECT_FALSE(network_state_->IsConnectingOrConnected());
+  EXPECT_FALSE(network_state_->IsActive());
 
   EXPECT_TRUE(SetStringProperty(shill::kActivationStateProperty,
                                 shill::kActivationStateActivating));
-  EXPECT_FALSE(network_state_.IsConnectedState());
-  EXPECT_FALSE(network_state_.IsConnectingState());
-  EXPECT_FALSE(network_state_.IsConnectingOrConnected());
-  EXPECT_TRUE(network_state_.IsActive());
+  EXPECT_FALSE(network_state_->IsConnectedState());
+  EXPECT_FALSE(network_state_->IsConnectingState());
+  EXPECT_FALSE(network_state_->IsConnectingOrConnected());
+  EXPECT_TRUE(network_state_->IsActive());
 }
 
 TEST_F(NetworkStateTest, ConnectRequested) {
-  network_state_.set_visible(true);
+  network_state_->set_visible(true);
 
-  network_state_.SetConnectionState(shill::kStateIdle);
+  network_state_->SetConnectionState(shill::kStateIdle);
 
-  network_state_.set_connect_requested_for_testing(true);
-  EXPECT_EQ(network_state_.connection_state(), shill::kStateIdle);
-  EXPECT_FALSE(network_state_.IsConnectedState());
-  EXPECT_TRUE(network_state_.IsConnectingState());
-  EXPECT_TRUE(network_state_.IsConnectingOrConnected());
+  network_state_->set_connect_requested_for_testing(true);
+  EXPECT_EQ(network_state_->connection_state(), shill::kStateIdle);
+  EXPECT_FALSE(network_state_->IsConnectedState());
+  EXPECT_TRUE(network_state_->IsConnectingState());
+  EXPECT_TRUE(network_state_->IsConnectingOrConnected());
 
-  network_state_.SetConnectionState(shill::kStateOnline);
-  EXPECT_TRUE(network_state_.IsConnectedState());
-  EXPECT_FALSE(network_state_.IsConnectingState());
+  network_state_->SetConnectionState(shill::kStateOnline);
+  EXPECT_TRUE(network_state_->IsConnectedState());
+  EXPECT_FALSE(network_state_->IsConnectingState());
 }
 
 TEST_F(NetworkStateTest, ConnectionStateNotVisible) {
-  network_state_.set_visible(false);
+  network_state_->set_visible(false);
 
-  network_state_.SetConnectionState(shill::kStateConfiguration);
-  EXPECT_EQ(network_state_.connection_state(), shill::kStateIdle);
-  EXPECT_FALSE(network_state_.IsConnectingState());
+  network_state_->SetConnectionState(shill::kStateConfiguration);
+  EXPECT_EQ(network_state_->connection_state(), shill::kStateIdle);
+  EXPECT_FALSE(network_state_->IsConnectingState());
 
-  network_state_.SetConnectionState(shill::kStateOnline);
-  EXPECT_EQ(network_state_.connection_state(), shill::kStateIdle);
-  EXPECT_FALSE(network_state_.IsConnectedState());
+  network_state_->SetConnectionState(shill::kStateOnline);
+  EXPECT_EQ(network_state_->connection_state(), shill::kStateIdle);
+  EXPECT_FALSE(network_state_->IsConnectedState());
 
-  network_state_.SetConnectionState(shill::kStateConfiguration);
-  EXPECT_EQ(network_state_.connection_state(), shill::kStateIdle);
-  EXPECT_FALSE(network_state_.IsConnectingState());
+  network_state_->SetConnectionState(shill::kStateConfiguration);
+  EXPECT_EQ(network_state_->connection_state(), shill::kStateIdle);
+  EXPECT_FALSE(network_state_->IsConnectingState());
 }
 
 TEST_F(NetworkStateTest, TetherProperties) {
-  network_state_.set_type_for_testing(kTypeTether);
-  network_state_.set_tether_carrier("Project Fi");
-  network_state_.set_battery_percentage(85);
-  network_state_.set_tether_has_connected_to_host(true);
-  network_state_.set_signal_strength(75);
+  network_state_->set_type_for_testing(kTypeTether);
+  network_state_->set_tether_carrier("Project Fi");
+  network_state_->set_battery_percentage(85);
+  network_state_->set_tether_has_connected_to_host(true);
+  network_state_->set_signal_strength(75);
 
   base::Value dictionary(base::Value::Type::DICTIONARY);
-  network_state_.GetStateProperties(&dictionary);
+  network_state_->GetStateProperties(&dictionary);
 
   absl::optional<int> signal_strength =
       dictionary.FindIntKey(kTetherSignalStrength);
@@ -402,14 +423,14 @@
       SetProperty(shill::kPaymentPortalProperty, std::move(payment_portal)));
 
   SignalInitialPropertiesReceived();
-  EXPECT_EQ("Test Cellular", network_state_.name());
+  EXPECT_EQ("Test Cellular", network_state_->name());
   EXPECT_EQ(shill::kNetworkTechnologyLteAdvanced,
-            network_state_.network_technology());
-  EXPECT_EQ(shill::kActivationTypeOTA, network_state_.activation_type());
+            network_state_->network_technology());
+  EXPECT_EQ(shill::kActivationTypeOTA, network_state_->activation_type());
   EXPECT_EQ(shill::kActivationStateActivated,
-            network_state_.activation_state());
-  EXPECT_EQ("http://test-portal.com", network_state_.payment_url());
-  EXPECT_EQ("fake_data", network_state_.payment_post_data());
+            network_state_->activation_state());
+  EXPECT_EQ("http://test-portal.com", network_state_->payment_url());
+  EXPECT_EQ("fake_data", network_state_->payment_post_data());
 }
 
 TEST_F(NetworkStateTest, CelularPaymentPortalGet) {
@@ -433,14 +454,14 @@
 
   SignalInitialPropertiesReceived();
 
-  EXPECT_EQ("Test Cellular", network_state_.name());
+  EXPECT_EQ("Test Cellular", network_state_->name());
   EXPECT_EQ(shill::kNetworkTechnologyLteAdvanced,
-            network_state_.network_technology());
-  EXPECT_EQ(shill::kActivationTypeOTA, network_state_.activation_type());
+            network_state_->network_technology());
+  EXPECT_EQ(shill::kActivationTypeOTA, network_state_->activation_type());
   EXPECT_EQ(shill::kActivationStateActivated,
-            network_state_.activation_state());
-  EXPECT_EQ("http://test-portal.com", network_state_.payment_url());
-  EXPECT_EQ("", network_state_.payment_post_data());
+            network_state_->activation_state());
+  EXPECT_EQ("http://test-portal.com", network_state_->payment_url());
+  EXPECT_EQ("", network_state_->payment_post_data());
 }
 
 TEST_F(NetworkStateTest, CellularSpecifier) {
@@ -450,14 +471,14 @@
   EXPECT_TRUE(SetStringProperty(shill::kTypeProperty, shill::kTypeCellular));
   EXPECT_TRUE(
       SetStringProperty(shill::kNameProperty, kTestCellularNetworkName));
-  network_state_.set_update_received();
+  network_state_->set_update_received();
 
   // Verify that cellular network state with same name but different iccid
   // produce different specifier values.
   EXPECT_TRUE(SetStringProperty(shill::kIccidProperty, kTestIccid1));
-  std::string specifier1 = network_state_.GetSpecifier();
+  std::string specifier1 = network_state_->GetSpecifier();
   EXPECT_TRUE(SetStringProperty(shill::kIccidProperty, kTestIccid2));
-  std::string specifier2 = network_state_.GetSpecifier();
+  std::string specifier2 = network_state_->GetSpecifier();
   EXPECT_NE(specifier1, specifier2);
 }
 
@@ -495,4 +516,38 @@
   EXPECT_EQ(kTestGuid, *dictionary.FindStringKey(shill::kGuidProperty));
 }
 
+TEST_F(NetworkStateTest, UpdateCaptivePortalState) {
+  base::Value shill_properties(base::Value::Type::DICT);
+
+  network_state_->set_visible(true);
+  EXPECT_EQ(GetShillPortalState(), NetworkState::PortalState::kUnknown);
+
+  SetConnectionState(shill::kStateIdle);
+  UpdateCaptivePortalState(shill_properties);
+  EXPECT_EQ(GetShillPortalState(), NetworkState::PortalState::kUnknown);
+
+  SetConnectionState(shill::kStateNoConnectivity);
+  UpdateCaptivePortalState(shill_properties);
+  EXPECT_EQ(GetShillPortalState(), NetworkState::PortalState::kNoInternet);
+
+  SetConnectionState(shill::kStateRedirectFound);
+  UpdateCaptivePortalState(shill_properties);
+  EXPECT_EQ(GetShillPortalState(), NetworkState::PortalState::kPortal);
+
+  SetConnectionState(shill::kStatePortalSuspected);
+  UpdateCaptivePortalState(shill_properties);
+  EXPECT_EQ(GetShillPortalState(), NetworkState::PortalState::kPortalSuspected);
+
+  shill_properties.GetDict().Set(
+      shill::kPortalDetectionFailedStatusCodeProperty,
+      net::HTTP_PROXY_AUTHENTICATION_REQUIRED);
+  UpdateCaptivePortalState(shill_properties);
+  EXPECT_EQ(GetShillPortalState(),
+            NetworkState::PortalState::kProxyAuthRequired);
+
+  SetConnectionState(shill::kStateOnline);
+  UpdateCaptivePortalState(shill_properties);
+  EXPECT_EQ(GetShillPortalState(), NetworkState::PortalState::kOnline);
+}
+
 }  // namespace ash
diff --git a/chromeos/ash/components/phonehub/notification_manager_impl.h b/chromeos/ash/components/phonehub/notification_manager_impl.h
index 427e3d7..f74bfbf 100644
--- a/chromeos/ash/components/phonehub/notification_manager_impl.h
+++ b/chromeos/ash/components/phonehub/notification_manager_impl.h
@@ -9,6 +9,7 @@
 #include "chromeos/ash/components/phonehub/notification_manager.h"
 #include "chromeos/ash/services/multidevice_setup/public/cpp/multidevice_setup_client.h"
 #include "chromeos/ash/services/multidevice_setup/public/mojom/multidevice_setup.mojom.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace ash {
 namespace phonehub {
@@ -40,7 +41,8 @@
   MessageSender* message_sender_;
   UserActionRecorder* user_action_recorder_;
   multidevice_setup::MultiDeviceSetupClient* multidevice_setup_client_;
-  multidevice_setup::mojom::FeatureState notifications_feature_status_;
+  absl::optional<multidevice_setup::mojom::FeatureState>
+      notifications_feature_status_;
 };
 
 }  // namespace phonehub
diff --git a/chromeos/ash/services/assistant/assistant_manager_service.h b/chromeos/ash/services/assistant/assistant_manager_service.h
index 9e75eb1..6f75b66 100644
--- a/chromeos/ash/services/assistant/assistant_manager_service.h
+++ b/chromeos/ash/services/assistant/assistant_manager_service.h
@@ -34,6 +34,8 @@
 
   // These values are persisted to logs. Entries should not be renumbered and
   // numeric values should never be reused.
+  // If any value is added, please update enums.xml
+  // `AssistantServiceState`.
   // Enumeration of possible assistant manager service states.
   enum State {
     // Initial state, the service is created but not started yet.
diff --git a/chromeos/ash/services/device_sync/device_sync_impl.cc b/chromeos/ash/services/device_sync/device_sync_impl.cc
index afbde9b..b47a0b43 100644
--- a/chromeos/ash/services/device_sync/device_sync_impl.cc
+++ b/chromeos/ash/services/device_sync/device_sync_impl.cc
@@ -35,6 +35,7 @@
 #include "chromeos/ash/services/device_sync/cryptauth_v2_device_manager_impl.h"
 #include "chromeos/ash/services/device_sync/cryptauth_v2_enrollment_manager_impl.h"
 #include "chromeos/ash/services/device_sync/device_sync_type_converters.h"
+#include "chromeos/ash/services/device_sync/group_private_key_and_better_together_metadata_status.h"
 #include "chromeos/ash/services/device_sync/proto/cryptauth_api.pb.h"
 #include "chromeos/ash/services/device_sync/proto/device_classifier_util.h"
 #include "chromeos/ash/services/device_sync/public/cpp/gcm_device_info_provider.h"
@@ -488,6 +489,43 @@
       ForceCryptAuthOperationResult::kSuccess /* result */);
 }
 
+void DeviceSyncImpl::GetGroupPrivateKeyStatus(
+    GetGroupPrivateKeyStatusCallback callback) {
+  DCHECK(features::ShouldUseV2DeviceSync);
+
+  if (status_ != InitializationStatus::kReady) {
+    PA_LOG(WARNING) << "DeviceSyncImpl::GetGroupPrivateKeyStatus() invoked "
+                       "before initialization was complete. Cannot return "
+                       "group private key status.";
+    std::move(callback).Run(
+        GroupPrivateKeyStatus::
+            kStatusUnavailableBecauseDeviceSyncIsNotInitialized);
+    return;
+  }
+
+  std::move(callback).Run(
+      cryptauth_v2_device_manager_->GetDeviceSyncerGroupPrivateKeyStatus());
+}
+
+void DeviceSyncImpl::GetBetterTogetherMetadataStatus(
+    GetBetterTogetherMetadataStatusCallback callback) {
+  DCHECK(features::ShouldUseV2DeviceSync);
+
+  if (status_ != InitializationStatus::kReady) {
+    PA_LOG(WARNING)
+        << "DeviceSyncImpl::GetBetterTogetherMetadataStatus() invoked "
+           "before initialization was complete. Cannot return "
+           "better together metadata status.";
+    std::move(callback).Run(
+        BetterTogetherMetadataStatus::
+            kStatusUnavailableBecauseDeviceSyncIsNotInitialized);
+    return;
+  }
+
+  std::move(callback).Run(cryptauth_v2_device_manager_
+                              ->GetDeviceSyncerBetterTogetherMetadataStatus());
+}
+
 void DeviceSyncImpl::GetLocalDeviceMetadata(
     GetLocalDeviceMetadataCallback callback) {
   if (status_ != InitializationStatus::kReady) {
diff --git a/chromeos/ash/services/device_sync/device_sync_impl.h b/chromeos/ash/services/device_sync/device_sync_impl.h
index 2dbd3c2..893ccd5e4 100644
--- a/chromeos/ash/services/device_sync/device_sync_impl.h
+++ b/chromeos/ash/services/device_sync/device_sync_impl.h
@@ -116,6 +116,10 @@
   // device_sync::mojom::DeviceSync:
   void ForceEnrollmentNow(ForceEnrollmentNowCallback callback) override;
   void ForceSyncNow(ForceSyncNowCallback callback) override;
+  void GetGroupPrivateKeyStatus(
+      GetGroupPrivateKeyStatusCallback callback) override;
+  void GetBetterTogetherMetadataStatus(
+      GetBetterTogetherMetadataStatusCallback callback) override;
   void GetLocalDeviceMetadata(GetLocalDeviceMetadataCallback callback) override;
   void GetSyncedDevices(GetSyncedDevicesCallback callback) override;
   void SetSoftwareFeatureState(
diff --git a/chromeos/ash/services/device_sync/device_sync_service_unittest.cc b/chromeos/ash/services/device_sync/device_sync_service_unittest.cc
index 4db30a87..7ee01a89 100644
--- a/chromeos/ash/services/device_sync/device_sync_service_unittest.cc
+++ b/chromeos/ash/services/device_sync/device_sync_service_unittest.cc
@@ -46,6 +46,7 @@
 #include "chromeos/ash/services/device_sync/fake_device_sync_observer.h"
 #include "chromeos/ash/services/device_sync/fake_remote_device_provider.h"
 #include "chromeos/ash/services/device_sync/fake_software_feature_manager.h"
+#include "chromeos/ash/services/device_sync/group_private_key_and_better_together_metadata_status.h"
 #include "chromeos/ash/services/device_sync/proto/cryptauth_client_app_metadata.pb.h"
 #include "chromeos/ash/services/device_sync/proto/cryptauth_common.pb.h"
 #include "chromeos/ash/services/device_sync/proto/cryptauth_v2_test_util.h"
@@ -1178,6 +1179,15 @@
     // initialization.
     EXPECT_FALSE(CallGetLocalDeviceMetadata());
 
+    // GetGroupPrivateKeyStatus() and GetBetterTogetherMetadataStatus() both
+    // should return kStatusUnavailableBecauseDeviceSyncIsNotInitialized.
+    EXPECT_EQ(GroupPrivateKeyStatus::
+                  kStatusUnavailableBecauseDeviceSyncIsNotInitialized,
+              CallGetGroupPrivateKeyStatus());
+    EXPECT_EQ(BetterTogetherMetadataStatus::
+                  kStatusUnavailableBecauseDeviceSyncIsNotInitialized,
+              CallGetBetterTogetherMetadataStatus());
+
     if (features::ShouldUseV1DeviceSync()) {
       // SetSoftwareFeatureState() should return a struct with the special
       // kErrorNotInitialized error code.
@@ -1286,6 +1296,24 @@
     return last_force_sync_now_result_;
   }
 
+  GroupPrivateKeyStatus CallGetGroupPrivateKeyStatus() {
+    base::RunLoop run_loop;
+    device_sync_->GetGroupPrivateKeyStatus(base::BindOnce(
+        &DeviceSyncServiceTest::OnGetGroupPrivateKeyStatusCompleted,
+        base::Unretained(this), run_loop.QuitClosure()));
+    run_loop.Run();
+    return last_group_private_key_status_result_;
+  }
+
+  BetterTogetherMetadataStatus CallGetBetterTogetherMetadataStatus() {
+    base::RunLoop run_loop;
+    device_sync_->GetBetterTogetherMetadataStatus(base::BindOnce(
+        &DeviceSyncServiceTest::OnGetBetterTogetherMetadataStatusCompleted,
+        base::Unretained(this), run_loop.QuitClosure()));
+    run_loop.Run();
+    return last_better_together_metadata_status_result_;
+  }
+
   const absl::optional<multidevice::RemoteDevice>&
   CallGetLocalDeviceMetadata() {
     base::RunLoop run_loop;
@@ -1503,6 +1531,19 @@
     std::move(quit_closure).Run();
   }
 
+  void OnGetGroupPrivateKeyStatusCompleted(base::OnceClosure quit_closure,
+                                           GroupPrivateKeyStatus status) {
+    last_group_private_key_status_result_ = status;
+    std::move(quit_closure).Run();
+  }
+
+  void OnGetBetterTogetherMetadataStatusCompleted(
+      base::OnceClosure quit_closure,
+      BetterTogetherMetadataStatus status) {
+    last_better_together_metadata_status_result_ = status;
+    std::move(quit_closure).Run();
+  }
+
   void OnGetLocalDeviceMetadataCompleted(
       base::OnceClosure quit_closure,
       const absl::optional<multidevice::RemoteDevice>& local_device_metadata) {
@@ -1625,6 +1666,8 @@
   bool device_already_enrolled_in_cryptauth_;
   bool last_force_enrollment_now_result_;
   bool last_force_sync_now_result_;
+  GroupPrivateKeyStatus last_group_private_key_status_result_;
+  BetterTogetherMetadataStatus last_better_together_metadata_status_result_;
   absl::optional<multidevice::RemoteDeviceList> last_synced_devices_result_;
   absl::optional<multidevice::RemoteDevice> last_local_device_metadata_result_;
   std::unique_ptr<mojom::NetworkRequestResult>
@@ -1835,6 +1878,24 @@
   EXPECT_EQ(1u, fake_device_sync_observer()->num_enrollment_events());
 }
 
+TEST_P(DeviceSyncServiceTest, GetGroupPrivateKeyStatus) {
+  if (!features::ShouldUseV2DeviceSync())
+    return;
+
+  InitializeServiceSuccessfully();
+  EXPECT_EQ(GroupPrivateKeyStatus::kWaitingForGroupPrivateKey,
+            CallGetGroupPrivateKeyStatus());
+}
+
+TEST_P(DeviceSyncServiceTest, GetBetterTogetherMetadataStatus) {
+  if (!features::ShouldUseV2DeviceSync())
+    return;
+
+  InitializeServiceSuccessfully();
+  EXPECT_EQ(BetterTogetherMetadataStatus::kWaitingToProcessDeviceMetadata,
+            CallGetBetterTogetherMetadataStatus());
+}
+
 TEST_P(DeviceSyncServiceTest, GetLocalDeviceMetadata) {
   InitializeServiceSuccessfully();
 
diff --git a/chromeos/ash/services/device_sync/fake_device_sync.cc b/chromeos/ash/services/device_sync/fake_device_sync.cc
index 3b9e840..7889aec 100644
--- a/chromeos/ash/services/device_sync/fake_device_sync.cc
+++ b/chromeos/ash/services/device_sync/fake_device_sync.cc
@@ -18,6 +18,19 @@
 
 FakeDeviceSync::~FakeDeviceSync() = default;
 
+void FakeDeviceSync::InvokePendingGetGroupPrivateKeyStatusCallback(
+    GroupPrivateKeyStatus status) {
+  std::move(get_group_private_key_status_callback_queue_.front()).Run(status);
+  get_group_private_key_status_callback_queue_.pop();
+}
+
+void FakeDeviceSync::InvokePendingGetBetterTogetherMetadataStatusCallback(
+    BetterTogetherMetadataStatus status) {
+  std::move(get_better_together_metadata_status_callback_queue_.front())
+      .Run(status);
+  get_better_together_metadata_status_callback_queue_.pop();
+}
+
 void FakeDeviceSync::InvokePendingGetLocalDeviceMetadataCallback(
     const absl::optional<multidevice::RemoteDevice>& local_device_metadata) {
   std::move(get_local_device_metadata_callback_queue_.front())
@@ -83,6 +96,16 @@
   std::move(callback).Run(force_sync_now_completed_success_);
 }
 
+void FakeDeviceSync::GetGroupPrivateKeyStatus(
+    GetGroupPrivateKeyStatusCallback callback) {
+  get_group_private_key_status_callback_queue_.push(std::move(callback));
+}
+
+void FakeDeviceSync::GetBetterTogetherMetadataStatus(
+    GetBetterTogetherMetadataStatusCallback callback) {
+  get_better_together_metadata_status_callback_queue_.push(std::move(callback));
+}
+
 void FakeDeviceSync::GetLocalDeviceMetadata(
     GetLocalDeviceMetadataCallback callback) {
   get_local_device_metadata_callback_queue_.push(std::move(callback));
diff --git a/chromeos/ash/services/device_sync/fake_device_sync.h b/chromeos/ash/services/device_sync/fake_device_sync.h
index 338dc63..76bbcf9 100644
--- a/chromeos/ash/services/device_sync/fake_device_sync.h
+++ b/chromeos/ash/services/device_sync/fake_device_sync.h
@@ -37,6 +37,10 @@
     force_sync_now_completed_success_ = force_sync_now_completed_success;
   }
 
+  void InvokePendingGetGroupPrivateKeyStatusCallback(
+      GroupPrivateKeyStatus status);
+  void InvokePendingGetBetterTogetherMetadataStatusCallback(
+      BetterTogetherMetadataStatus status);
   void InvokePendingGetLocalDeviceMetadataCallback(
       const absl::optional<multidevice::RemoteDevice>& local_device_metadata);
   void InvokePendingGetSyncedDevicesCallback(
@@ -61,6 +65,10 @@
   // device_sync::mojom::DeviceSync:
   void ForceEnrollmentNow(ForceEnrollmentNowCallback callback) override;
   void ForceSyncNow(ForceSyncNowCallback callback) override;
+  void GetGroupPrivateKeyStatus(
+      GetGroupPrivateKeyStatusCallback callback) override;
+  void GetBetterTogetherMetadataStatus(
+      GetBetterTogetherMetadataStatusCallback callback) override;
   void GetLocalDeviceMetadata(GetLocalDeviceMetadataCallback callback) override;
   void GetSyncedDevices(GetSyncedDevicesCallback callback) override;
   void SetSoftwareFeatureState(
@@ -87,6 +95,10 @@
   bool force_enrollment_now_completed_success_ = true;
   bool force_sync_now_completed_success_ = true;
 
+  std::queue<GetGroupPrivateKeyStatusCallback>
+      get_group_private_key_status_callback_queue_;
+  std::queue<GetBetterTogetherMetadataStatusCallback>
+      get_better_together_metadata_status_callback_queue_;
   std::queue<GetLocalDeviceMetadataCallback>
       get_local_device_metadata_callback_queue_;
   std::queue<GetSyncedDevicesCallback> get_synced_devices_callback_queue_;
diff --git a/chromeos/ash/services/device_sync/group_private_key_and_better_together_metadata_status.h b/chromeos/ash/services/device_sync/group_private_key_and_better_together_metadata_status.h
index c05abcc..0f0f838a 100644
--- a/chromeos/ash/services/device_sync/group_private_key_and_better_together_metadata_status.h
+++ b/chromeos/ash/services/device_sync/group_private_key_and_better_together_metadata_status.h
@@ -11,6 +11,10 @@
 // CryptAuthDeviceSyncer. These enums are declared in their own module so they
 // can be consumed by mojom::DeviceSync and avoid a dependency cycle.
 enum class GroupPrivateKeyStatus {
+  // When Device Sync is not initialized, it cannot access the group private key
+  // status and will return this value.
+  kStatusUnavailableBecauseDeviceSyncIsNotInitialized,
+
   // The CryptAuth SyncMetadata response that includes the encrypted group
   // private key hasn't been received yet.
   kWaitingForGroupPrivateKey,
@@ -33,10 +37,14 @@
 
   // The group private key was successfully decrypted. This is the expected
   // final state of this flow.
-  kGroupPrivateKeySuccessfullyDecrypted
+  kGroupPrivateKeySuccessfullyDecrypted,
 };
 
 enum class BetterTogetherMetadataStatus {
+  // When Device Sync is not initialized, it cannot access the better together
+  // metadata status and will return this value.
+  kStatusUnavailableBecauseDeviceSyncIsNotInitialized,
+
   // The attempt to process the encrypted device metadata hasn't started yet.
   // If the device sync attempt finishes and this is still the metadata
   // status, clients can inspect GroupPrivateKeyStatus to understand why.
diff --git a/chromeos/ash/services/device_sync/public/mojom/BUILD.gn b/chromeos/ash/services/device_sync/public/mojom/BUILD.gn
index 5b00270..e9501a98 100644
--- a/chromeos/ash/services/device_sync/public/mojom/BUILD.gn
+++ b/chromeos/ash/services/device_sync/public/mojom/BUILD.gn
@@ -33,11 +33,20 @@
           mojom = "ash.device_sync.mojom.CryptAuthService"
           cpp = "cryptauthv2::TargetService"
         },
+        {
+          mojom = "ash.device_sync.mojom.GroupPrivateKeyStatus"
+          cpp = "ash::device_sync::GroupPrivateKeyStatus"
+        },
+        {
+          mojom = "ash.device_sync.mojom.BetterTogetherMetadataStatus"
+          cpp = "ash::device_sync::BetterTogetherMetadataStatus"
+        },
       ]
       traits_headers = [ "device_sync_mojom_traits.h" ]
       traits_sources = [ "device_sync_mojom_traits.cc" ]
       traits_public_deps = [
         "//chromeos/ash/services/device_sync:feature_status_change",
+        "//chromeos/ash/services/device_sync:group_private_key_and_better_together_metadata_status",
         "//chromeos/ash/services/device_sync/proto",
       ]
     },
diff --git a/chromeos/ash/services/device_sync/public/mojom/device_sync.mojom b/chromeos/ash/services/device_sync/public/mojom/device_sync.mojom
index 2398b2de..af4316b 100644
--- a/chromeos/ash/services/device_sync/public/mojom/device_sync.mojom
+++ b/chromeos/ash/services/device_sync/public/mojom/device_sync.mojom
@@ -73,6 +73,62 @@
   kUnknownConnectivity
 };
 
+// Describes the status of the encrypted group private key received in the
+// SyncMetadataResponse.
+enum GroupPrivateKeyStatus {
+  // When Device Sync is not initialized, it cannot access the group private key
+  // status and will return this value.
+  kStatusUnavailableBecauseDeviceSyncIsNotInitialized,
+
+  // The CryptAuth SyncMetadata response that includes the encrypted group
+  // private key hasn't been received yet.
+  kWaitingForGroupPrivateKey,
+
+  // The SyncMetadata response was been received, but doesn't include any
+  // encrypted group private key. This is expected when no other user device
+  // uploaded the key or if we already own the key.
+  kNoEncryptedGroupPrivateKeyReceived,
+
+  // The SyncMetadata response was received, but the included encrypted group
+  // private key is empty.
+  kEncryptedGroupPrivateKeyEmpty,
+
+  // This device's CryptAuthKeyBundle::Name::kDeviceSyncBetterTogether key is
+  // missing, so the encrypted group private key cannot be decrypted.
+  kLocalDeviceSyncBetterTogetherKeyMissing,
+
+  // An error occurred when decrypting the group private key.
+  kGroupPrivateKeyDecryptionFailed,
+
+  // The group private key was successfully decrypted. This is the expected
+  // final state of this flow.
+  kGroupPrivateKeySuccessfullyDecrypted
+};
+
+// Describes the status of the better together remote device metadata.
+enum BetterTogetherMetadataStatus {
+  // When Device Sync is not initialized, it cannot access the better together
+  // metadata status and will return this value.
+  kStatusUnavailableBecauseDeviceSyncIsNotInitialized,
+
+  // The attempt to process the encrypted device metadata hasn't started yet.
+  // If the device sync attempt finishes and this is still the metadata
+  // status, clients can inspect GroupPrivateKeyStatus to understand why.
+  kWaitingToProcessDeviceMetadata,
+
+  // The group private key required to decrypt the metadata is missing.
+  // Clients can inspect GroupPrivateKeyStatus to understand why the group
+  // private key is missing.
+  kGroupPrivateKeyMissing,
+
+  // CryptAuth didn't send any encrypted metadata.
+  kEncryptedMetadataEmpty,
+
+  // Device metadata was decrypted. This is the expected final state of this
+  // flow.
+  kMetadataDecrypted
+};
+
 struct FindEligibleDevicesResponse {
   array<ash.multidevice.mojom.RemoteDevice> eligible_devices;
   array<ash.multidevice.mojom.RemoteDevice> ineligible_devices;
@@ -136,6 +192,13 @@
   [Sync]
   ForceSyncNow() => (bool success);
 
+  // Returns the status of the encrypted group private key received in the
+  // SyncMetadataResponse.
+  GetGroupPrivateKeyStatus() => (GroupPrivateKeyStatus status);
+
+  // Returns the status of the better together remote device metadata.
+  GetBetterTogetherMetadataStatus() => (BetterTogetherMetadataStatus status);
+
   // Returns all synced devices associated with the primary account. If this
   // device has not yet registered with the back-end, no list is provided.
   GetSyncedDevices() =>
diff --git a/chromeos/ash/services/device_sync/public/mojom/device_sync_mojom_traits.cc b/chromeos/ash/services/device_sync/public/mojom/device_sync_mojom_traits.cc
index d57709f..856bac2 100644
--- a/chromeos/ash/services/device_sync/public/mojom/device_sync_mojom_traits.cc
+++ b/chromeos/ash/services/device_sync/public/mojom/device_sync_mojom_traits.cc
@@ -46,6 +46,157 @@
   return false;
 }
 
+ash::device_sync::mojom::GroupPrivateKeyStatus
+EnumTraits<ash::device_sync::mojom::GroupPrivateKeyStatus,
+           ash::device_sync::GroupPrivateKeyStatus>::
+    ToMojom(ash::device_sync::GroupPrivateKeyStatus input) {
+  switch (input) {
+    case ash::device_sync::GroupPrivateKeyStatus::
+        kStatusUnavailableBecauseDeviceSyncIsNotInitialized:
+      return ash::device_sync::mojom::GroupPrivateKeyStatus::
+          kStatusUnavailableBecauseDeviceSyncIsNotInitialized;
+    case ash::device_sync::GroupPrivateKeyStatus::kWaitingForGroupPrivateKey:
+      return ash::device_sync::mojom::GroupPrivateKeyStatus::
+          kWaitingForGroupPrivateKey;
+    case ash::device_sync::GroupPrivateKeyStatus::
+        kNoEncryptedGroupPrivateKeyReceived:
+      return ash::device_sync::mojom::GroupPrivateKeyStatus::
+          kNoEncryptedGroupPrivateKeyReceived;
+    case ash::device_sync::GroupPrivateKeyStatus::
+        kEncryptedGroupPrivateKeyEmpty:
+      return ash::device_sync::mojom::GroupPrivateKeyStatus::
+          kEncryptedGroupPrivateKeyEmpty;
+    case ash::device_sync::GroupPrivateKeyStatus::
+        kLocalDeviceSyncBetterTogetherKeyMissing:
+      return ash::device_sync::mojom::GroupPrivateKeyStatus::
+          kLocalDeviceSyncBetterTogetherKeyMissing;
+    case ash::device_sync::GroupPrivateKeyStatus::
+        kGroupPrivateKeyDecryptionFailed:
+      return ash::device_sync::mojom::GroupPrivateKeyStatus::
+          kGroupPrivateKeyDecryptionFailed;
+    case ash::device_sync::GroupPrivateKeyStatus::
+        kGroupPrivateKeySuccessfullyDecrypted:
+      return ash::device_sync::mojom::GroupPrivateKeyStatus::
+          kGroupPrivateKeySuccessfullyDecrypted;
+  }
+
+  NOTREACHED();
+  return ash::device_sync::mojom::GroupPrivateKeyStatus::
+      kStatusUnavailableBecauseDeviceSyncIsNotInitialized;
+}
+
+bool EnumTraits<ash::device_sync::mojom::GroupPrivateKeyStatus,
+                ash::device_sync::GroupPrivateKeyStatus>::
+    FromMojom(ash::device_sync::mojom::GroupPrivateKeyStatus input,
+              ash::device_sync::GroupPrivateKeyStatus* out) {
+  switch (input) {
+    case ash::device_sync::mojom::GroupPrivateKeyStatus::
+        kStatusUnavailableBecauseDeviceSyncIsNotInitialized:
+      *out = ash::device_sync::GroupPrivateKeyStatus::
+          kStatusUnavailableBecauseDeviceSyncIsNotInitialized;
+      return true;
+    case ash::device_sync::mojom::GroupPrivateKeyStatus::
+        kWaitingForGroupPrivateKey:
+      *out =
+          ash::device_sync::GroupPrivateKeyStatus::kWaitingForGroupPrivateKey;
+      return true;
+    case ash::device_sync::mojom::GroupPrivateKeyStatus::
+        kNoEncryptedGroupPrivateKeyReceived:
+      *out = ash::device_sync::GroupPrivateKeyStatus::
+          kNoEncryptedGroupPrivateKeyReceived;
+      return true;
+    case ash::device_sync::mojom::GroupPrivateKeyStatus::
+        kEncryptedGroupPrivateKeyEmpty:
+      *out = ash::device_sync::GroupPrivateKeyStatus::
+          kEncryptedGroupPrivateKeyEmpty;
+      return true;
+    case ash::device_sync::mojom::GroupPrivateKeyStatus::
+        kLocalDeviceSyncBetterTogetherKeyMissing:
+      *out = ash::device_sync::GroupPrivateKeyStatus::
+          kLocalDeviceSyncBetterTogetherKeyMissing;
+      return true;
+    case ash::device_sync::mojom::GroupPrivateKeyStatus::
+        kGroupPrivateKeyDecryptionFailed:
+      *out = ash::device_sync::GroupPrivateKeyStatus::
+          kGroupPrivateKeyDecryptionFailed;
+      return true;
+    case ash::device_sync::mojom::GroupPrivateKeyStatus::
+        kGroupPrivateKeySuccessfullyDecrypted:
+      *out = ash::device_sync::GroupPrivateKeyStatus::
+          kGroupPrivateKeySuccessfullyDecrypted;
+      return true;
+  }
+
+  NOTREACHED();
+  return false;
+}
+
+ash::device_sync::mojom::BetterTogetherMetadataStatus
+EnumTraits<ash::device_sync::mojom::BetterTogetherMetadataStatus,
+           ash::device_sync::BetterTogetherMetadataStatus>::
+    ToMojom(ash::device_sync::BetterTogetherMetadataStatus input) {
+  switch (input) {
+    case ash::device_sync::BetterTogetherMetadataStatus::
+        kStatusUnavailableBecauseDeviceSyncIsNotInitialized:
+      return ash::device_sync::mojom::BetterTogetherMetadataStatus::
+          kStatusUnavailableBecauseDeviceSyncIsNotInitialized;
+    case ash::device_sync::BetterTogetherMetadataStatus::
+        kWaitingToProcessDeviceMetadata:
+      return ash::device_sync::mojom::BetterTogetherMetadataStatus::
+          kWaitingToProcessDeviceMetadata;
+    case ash::device_sync::BetterTogetherMetadataStatus::
+        kGroupPrivateKeyMissing:
+      return ash::device_sync::mojom::BetterTogetherMetadataStatus::
+          kGroupPrivateKeyMissing;
+    case ash::device_sync::BetterTogetherMetadataStatus::
+        kEncryptedMetadataEmpty:
+      return ash::device_sync::mojom::BetterTogetherMetadataStatus::
+          kEncryptedMetadataEmpty;
+    case ash::device_sync::BetterTogetherMetadataStatus::kMetadataDecrypted:
+      return ash::device_sync::mojom::BetterTogetherMetadataStatus::
+          kMetadataDecrypted;
+  }
+
+  NOTREACHED();
+  return ash::device_sync::mojom::BetterTogetherMetadataStatus::
+      kStatusUnavailableBecauseDeviceSyncIsNotInitialized;
+}
+
+bool EnumTraits<ash::device_sync::mojom::BetterTogetherMetadataStatus,
+                ash::device_sync::BetterTogetherMetadataStatus>::
+    FromMojom(ash::device_sync::mojom::BetterTogetherMetadataStatus input,
+              ash::device_sync::BetterTogetherMetadataStatus* out) {
+  switch (input) {
+    case ash::device_sync::mojom::BetterTogetherMetadataStatus::
+        kStatusUnavailableBecauseDeviceSyncIsNotInitialized:
+      *out = ash::device_sync::BetterTogetherMetadataStatus::
+          kStatusUnavailableBecauseDeviceSyncIsNotInitialized;
+      return true;
+    case ash::device_sync::mojom::BetterTogetherMetadataStatus::
+        kWaitingToProcessDeviceMetadata:
+      *out = ash::device_sync::BetterTogetherMetadataStatus::
+          kWaitingToProcessDeviceMetadata;
+      return true;
+    case ash::device_sync::mojom::BetterTogetherMetadataStatus::
+        kGroupPrivateKeyMissing:
+      *out = ash::device_sync::BetterTogetherMetadataStatus::
+          kGroupPrivateKeyMissing;
+      return true;
+    case ash::device_sync::mojom::BetterTogetherMetadataStatus::
+        kEncryptedMetadataEmpty:
+      *out = ash::device_sync::BetterTogetherMetadataStatus::
+          kEncryptedMetadataEmpty;
+      return true;
+    case ash::device_sync::mojom::BetterTogetherMetadataStatus::
+        kMetadataDecrypted:
+      *out = ash::device_sync::BetterTogetherMetadataStatus::kMetadataDecrypted;
+      return true;
+  }
+
+  NOTREACHED();
+  return false;
+}
+
 ash::device_sync::mojom::FeatureStatusChange
 EnumTraits<ash::device_sync::mojom::FeatureStatusChange,
            ash::device_sync::FeatureStatusChange>::
diff --git a/chromeos/ash/services/device_sync/public/mojom/device_sync_mojom_traits.h b/chromeos/ash/services/device_sync/public/mojom/device_sync_mojom_traits.h
index 82589ae..b604f25 100644
--- a/chromeos/ash/services/device_sync/public/mojom/device_sync_mojom_traits.h
+++ b/chromeos/ash/services/device_sync/public/mojom/device_sync_mojom_traits.h
@@ -6,6 +6,7 @@
 #define CHROMEOS_ASH_SERVICES_DEVICE_SYNC_PUBLIC_MOJOM_DEVICE_SYNC_MOJOM_TRAITS_H_
 
 #include "chromeos/ash/services/device_sync/feature_status_change.h"
+#include "chromeos/ash/services/device_sync/group_private_key_and_better_together_metadata_status.h"
 #include "chromeos/ash/services/device_sync/proto/cryptauth_common.pb.h"
 #include "chromeos/ash/services/device_sync/proto/cryptauth_devicesync.pb.h"
 #include "chromeos/ash/services/device_sync/public/mojom/device_sync.mojom-shared.h"
@@ -24,6 +25,27 @@
 };
 
 template <>
+class EnumTraits<ash::device_sync::mojom::GroupPrivateKeyStatus,
+                 ash::device_sync::GroupPrivateKeyStatus> {
+ public:
+  static ash::device_sync::mojom::GroupPrivateKeyStatus ToMojom(
+      ash::device_sync::GroupPrivateKeyStatus input);
+  static bool FromMojom(ash::device_sync::mojom::GroupPrivateKeyStatus input,
+                        ash::device_sync::GroupPrivateKeyStatus* out);
+};
+
+template <>
+class EnumTraits<ash::device_sync::mojom::BetterTogetherMetadataStatus,
+                 ash::device_sync::BetterTogetherMetadataStatus> {
+ public:
+  static ash::device_sync::mojom::BetterTogetherMetadataStatus ToMojom(
+      ash::device_sync::BetterTogetherMetadataStatus input);
+  static bool FromMojom(
+      ash::device_sync::mojom::BetterTogetherMetadataStatus input,
+      ash::device_sync::BetterTogetherMetadataStatus* out);
+};
+
+template <>
 class EnumTraits<ash::device_sync::mojom::FeatureStatusChange,
                  ash::device_sync::FeatureStatusChange> {
  public:
diff --git a/chromeos/ash/services/device_sync/public/mojom/device_sync_mojom_traits_unittest.cc b/chromeos/ash/services/device_sync/public/mojom/device_sync_mojom_traits_unittest.cc
index df9a3d2..552874ed 100644
--- a/chromeos/ash/services/device_sync/public/mojom/device_sync_mojom_traits_unittest.cc
+++ b/chromeos/ash/services/device_sync/public/mojom/device_sync_mojom_traits_unittest.cc
@@ -5,6 +5,7 @@
 #include "chromeos/ash/services/device_sync/public/mojom/device_sync_mojom_traits.h"
 
 #include "chromeos/ash/services/device_sync/feature_status_change.h"
+#include "chromeos/ash/services/device_sync/group_private_key_and_better_together_metadata_status.h"
 #include "chromeos/ash/services/device_sync/proto/cryptauth_devicesync.pb.h"
 #include "chromeos/ash/services/device_sync/public/mojom/device_sync.mojom.h"
 #include "mojo/public/cpp/test_support/test_utils.h"
@@ -29,6 +30,66 @@
   }
 }
 
+TEST(DeviceSyncMojomTraitsTest, GroupPrivateKeyStatus) {
+  static constexpr ash::device_sync::GroupPrivateKeyStatus
+      kTestGroupPrivateKeyStatuses[] = {
+          ash::device_sync::GroupPrivateKeyStatus::
+              kStatusUnavailableBecauseDeviceSyncIsNotInitialized,
+          ash::device_sync::GroupPrivateKeyStatus::kWaitingForGroupPrivateKey,
+          ash::device_sync::GroupPrivateKeyStatus::
+              kNoEncryptedGroupPrivateKeyReceived,
+          ash::device_sync::GroupPrivateKeyStatus::
+              kEncryptedGroupPrivateKeyEmpty,
+          ash::device_sync::GroupPrivateKeyStatus::
+              kLocalDeviceSyncBetterTogetherKeyMissing,
+          ash::device_sync::GroupPrivateKeyStatus::
+              kGroupPrivateKeyDecryptionFailed,
+          ash::device_sync::GroupPrivateKeyStatus::
+              kGroupPrivateKeySuccessfullyDecrypted};
+
+  for (auto status_in : kTestGroupPrivateKeyStatuses) {
+    ash::device_sync::GroupPrivateKeyStatus status_out;
+
+    ash::device_sync::mojom::GroupPrivateKeyStatus serialized_status =
+        mojo::EnumTraits<
+            ash::device_sync::mojom::GroupPrivateKeyStatus,
+            ash::device_sync::GroupPrivateKeyStatus>::ToMojom(status_in);
+    ASSERT_TRUE(
+        (mojo::EnumTraits<ash::device_sync::mojom::GroupPrivateKeyStatus,
+                          ash::device_sync::GroupPrivateKeyStatus>::
+             FromMojom(serialized_status, &status_out)));
+    EXPECT_EQ(status_in, status_out);
+  }
+}
+
+TEST(DeviceSyncMojomTraitsTest, BetterTogetherMetadataStatus) {
+  static constexpr ash::device_sync::BetterTogetherMetadataStatus
+      kTestBetterTogetherMetadataStatuses[] = {
+          ash::device_sync::BetterTogetherMetadataStatus::
+              kStatusUnavailableBecauseDeviceSyncIsNotInitialized,
+          ash::device_sync::BetterTogetherMetadataStatus::
+              kWaitingToProcessDeviceMetadata,
+          ash::device_sync::BetterTogetherMetadataStatus::
+              kGroupPrivateKeyMissing,
+          ash::device_sync::BetterTogetherMetadataStatus::
+              kEncryptedMetadataEmpty,
+          ash::device_sync::BetterTogetherMetadataStatus::kMetadataDecrypted};
+
+  for (auto status_in : kTestBetterTogetherMetadataStatuses) {
+    ash::device_sync::BetterTogetherMetadataStatus status_out;
+
+    ash::device_sync::mojom::BetterTogetherMetadataStatus serialized_status =
+        mojo::EnumTraits<
+            ash::device_sync::mojom::BetterTogetherMetadataStatus,
+            ash::device_sync::BetterTogetherMetadataStatus>::ToMojom(status_in);
+    ASSERT_TRUE(
+        (mojo::EnumTraits<ash::device_sync::mojom::BetterTogetherMetadataStatus,
+                          ash::device_sync::BetterTogetherMetadataStatus>::
+             FromMojom(serialized_status, &status_out)));
+    EXPECT_EQ(status_in, status_out);
+  }
+}
+
 TEST(DeviceSyncMojomTraitsTest, FeatureStatusChange) {
   static constexpr ash::device_sync::FeatureStatusChange
       kTestFeatureStatusChanges[] = {
diff --git a/chromeos/ash/services/device_sync/stub_device_sync.cc b/chromeos/ash/services/device_sync/stub_device_sync.cc
index a6e8797..5b93ffb 100644
--- a/chromeos/ash/services/device_sync/stub_device_sync.cc
+++ b/chromeos/ash/services/device_sync/stub_device_sync.cc
@@ -15,6 +15,7 @@
 #include "chromeos/ash/components/multidevice/stub_multidevice_util.h"
 #include "chromeos/ash/services/device_sync/device_sync_base.h"
 #include "chromeos/ash/services/device_sync/device_sync_impl.h"
+#include "chromeos/ash/services/device_sync/group_private_key_and_better_together_metadata_status.h"
 #include "chromeos/ash/services/device_sync/public/mojom/device_sync.mojom.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
@@ -82,6 +83,18 @@
     std::move(callback).Run(/*success=*/true);
   }
 
+  void GetBetterTogetherMetadataStatus(
+      GetBetterTogetherMetadataStatusCallback callback) override {
+    std::move(callback).Run(/*result=*/BetterTogetherMetadataStatus::
+                                kWaitingToProcessDeviceMetadata);
+  }
+
+  void GetGroupPrivateKeyStatus(
+      GetGroupPrivateKeyStatusCallback callback) override {
+    std::move(callback).Run(
+        /*result=*/GroupPrivateKeyStatus::kWaitingForGroupPrivateKey);
+  }
+
   void GetLocalDeviceMetadata(
       GetLocalDeviceMetadataCallback callback) override {
     std::move(callback).Run(local_device_metadata_);
diff --git a/chromeos/chromeos_strings.grd b/chromeos/chromeos_strings.grd
index 996ce6c1..a8bc9e6f 100644
--- a/chromeos/chromeos_strings.grd
+++ b/chromeos/chromeos_strings.grd
@@ -3673,6 +3673,11 @@
         <message name="IDS_SHORTCUT_CUSTOMIZATION_SUBCATEGORY_GENERAL" desc="Subcategory named 'General' shown within main shortcuts section" translateable="false">General</message>
         <message name="IDS_SHORTCUT_CUSTOMIZATION_SUBCATEGORY_SYSTEM_APPS" desc="Subcategory named 'System Apps' shown within main shortcuts section" translateable="false">System Apps</message>
         <message name="IDS_SHORTCUT_CUSTOMIZATION_SUBCATEGORY_SYSTEM_CONTROLS" desc="Subcategory named 'System Controls' shown within main shortcuts section" translateable="false">System Controls</message>
+        <message name="IDS_SHORTCUT_CUSTOMIZATION_KEY_SPACE" desc="Lowercase name of the keyboard key 'Space'" translateable="false">space</message>
+        <message name="IDS_SHORTCUT_CUSTOMIZATION_KEY_TAB" desc="Lowercase name of the keyboard key 'Tab'" translateable="false">tab</message>
+        <message name="IDS_SHORTCUT_CUSTOMIZATION_KEY_ESCAPE" desc="Lowercase name of the keyboard key 'Escape', abbreviated if possible" translateable="false">esc</message>
+        <message name="IDS_SHORTCUT_CUSTOMIZATION_KEY_RETURN" desc="Lowercase name of the keyboard key 'Enter'" translateable="false">enter</message>
+        <message name="IDS_SHORTCUT_CUSTOMIZATION_KEY_BACKSPACE" desc="Lowercase name of the keyboard key 'Backspace'" translateable="false">backspace</message>
         <!-- End of Shortcut Customization -->
 
         <!-- APN -->
diff --git a/chromeos/crosapi/mojom/web_app_types.mojom b/chromeos/crosapi/mojom/web_app_types.mojom
index 2103c606..a54194a 100644
--- a/chromeos/crosapi/mojom/web_app_types.mojom
+++ b/chromeos/crosapi/mojom/web_app_types.mojom
@@ -37,6 +37,7 @@
   kUpdateTaskFailed,
   [MinVersion=1] kAppNotInRegistrarAfterCommit,
   [MinVersion=2] kHaltedBySyncUninstall,
+  [MinVersion=3] kInstallURLInvalid,
 };
 
 // See |webapps::UninstallResultCode|.
diff --git a/chromeos/crosapi/mojom/web_app_types_mojom_traits.cc b/chromeos/crosapi/mojom/web_app_types_mojom_traits.cc
index a2a05edc..2f6a19c 100644
--- a/chromeos/crosapi/mojom/web_app_types_mojom_traits.cc
+++ b/chromeos/crosapi/mojom/web_app_types_mojom_traits.cc
@@ -71,6 +71,8 @@
           kAppNotInRegistrarAfterCommit;
     case webapps::InstallResultCode::kHaltedBySyncUninstall:
       return crosapi::mojom::WebAppInstallResultCode::kHaltedBySyncUninstall;
+    case webapps::InstallResultCode::kInstallURLInvalid:
+      return crosapi::mojom::WebAppInstallResultCode::kInstallURLInvalid;
   };
 }
 
@@ -157,6 +159,9 @@
     case crosapi::mojom::WebAppInstallResultCode::kHaltedBySyncUninstall:
       *output = webapps::InstallResultCode::kHaltedBySyncUninstall;
       return true;
+    case crosapi::mojom::WebAppInstallResultCode::kInstallURLInvalid:
+      *output = webapps::InstallResultCode::kInstallURLInvalid;
+      return true;
   };
 
   NOTREACHED();
diff --git a/chromeos/ui/wm/features.cc b/chromeos/ui/wm/features.cc
index dae87381..9d3ef8e 100644
--- a/chromeos/ui/wm/features.cc
+++ b/chromeos/ui/wm/features.cc
@@ -12,9 +12,7 @@
 
 // Enables a window to float.
 // https://crbug.com/1240411
-BASE_FEATURE(kFloatWindow,
-             "CrOSLabsFloatWindow",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+BASE_FEATURE(kFloatWindow, "FloatWindow", base::FEATURE_DISABLED_BY_DEFAULT);
 
 BASE_FEATURE(kPartialSplit, "PartialSplit", base::FEATURE_DISABLED_BY_DEFAULT);
 
diff --git a/components/BUILD.gn b/components/BUILD.gn
index 5bddc33..c0838c7 100644
--- a/components/BUILD.gn
+++ b/components/BUILD.gn
@@ -10,6 +10,7 @@
 import("//components/optimization_guide/features.gni")
 import("//components/safe_browsing/buildflags.gni")
 import("//components/services/screen_ai/buildflags/features.gni")
+import("//components/supervised_user/buildflags.gni")
 import("//extensions/buildflags/buildflags.gni")
 import("//media/media_options.gni")
 import("//pdf/features.gni")
@@ -230,6 +231,7 @@
     "//components/subresource_filter/core/browser:unit_tests",
     "//components/subresource_filter/core/common:unit_tests",
     "//components/subresource_filter/tools:unit_tests",
+    "//components/supervised_user/core/common:unit_tests",
     "//components/sync:unit_tests",
     "//components/sync_bookmarks:unit_tests",
     "//components/sync_device_info:unit_tests",
@@ -258,6 +260,10 @@
     "//components/webdata_services:unit_tests",
   ]
 
+  if (enable_supervised_users) {
+    deps += [ "//components/supervised_user/core/browser:unit_tests" ]
+  }
+
   if (!is_ios) {
     deps += [ "//components/file_access:unit_tests" ]
   }
diff --git a/components/autofill/core/common/autofill_payments_features.cc b/components/autofill/core/common/autofill_payments_features.cc
index 39dcdc7a..8f117ce 100644
--- a/components/autofill/core/common/autofill_payments_features.cc
+++ b/components/autofill/core/common/autofill_payments_features.cc
@@ -188,6 +188,12 @@
              "AutofillParseVcnCardOnFileStandaloneCvcFields",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
+// When enabled, Expiration and Type titles will be removed from Chrome
+// payment settings page.
+BASE_FEATURE(kAutofillRemoveCardExpirationAndTypeTitles,
+             "AutofillRemoveCardExpirationAndTypeTitles",
+             base::FEATURE_ENABLED_BY_DEFAULT);
+
 // When enabled, the Save Card infobar will be dismissed by a user initiated
 // navigation other than one caused by submitted form.
 BASE_FEATURE(kAutofillSaveCardDismissOnNavigation,
diff --git a/components/autofill/core/common/autofill_payments_features.h b/components/autofill/core/common/autofill_payments_features.h
index 159798b0..fd39956 100644
--- a/components/autofill/core/common/autofill_payments_features.h
+++ b/components/autofill/core/common/autofill_payments_features.h
@@ -38,6 +38,7 @@
 BASE_DECLARE_FEATURE(kAutofillFillMerchantPromoCodeFields);
 BASE_DECLARE_FEATURE(kAutofillParseIBANFields);
 BASE_DECLARE_FEATURE(kAutofillParseVcnCardOnFileStandaloneCvcFields);
+BASE_DECLARE_FEATURE(kAutofillRemoveCardExpirationAndTypeTitles);
 BASE_DECLARE_FEATURE(kAutofillSaveCardDismissOnNavigation);
 BASE_DECLARE_FEATURE(kAutofillSaveCardInfobarEditSupport);
 BASE_DECLARE_FEATURE(kAutofillSaveCardUiExperiment);
diff --git a/components/breadcrumbs/core/breadcrumb_persistent_storage_manager.cc b/components/breadcrumbs/core/breadcrumb_persistent_storage_manager.cc
index 4a9416c9..ecf68e2 100644
--- a/components/breadcrumbs/core/breadcrumb_persistent_storage_manager.cc
+++ b/components/breadcrumbs/core/breadcrumb_persistent_storage_manager.cc
@@ -211,12 +211,9 @@
   if (!CheckForFileConsent() || pending_breadcrumbs_.empty())
     return;
 
-  // Make a copy of |pending_breadcrumbs_| to pass to the DoWriteEventsToFile()
-  // callback, since |pending_breadcrumbs_| is about to be cleared.
-  const std::string pending_breadcrumbs = pending_breadcrumbs_;
   task_runner_->PostTask(
       FROM_HERE, base::BindOnce(&DoWriteEventsToFile, breadcrumbs_file_path_,
-                                file_position_.value(), pending_breadcrumbs,
+                                file_position_.value(), pending_breadcrumbs_,
                                 /*append=*/true, write_counter_,
                                 write_counter_at_last_full_rewrite_));
 
diff --git a/components/browsing_topics/browsing_topics_calculator_unittest.cc b/components/browsing_topics/browsing_topics_calculator_unittest.cc
index eef2d1c..b15d81f 100644
--- a/components/browsing_topics/browsing_topics_calculator_unittest.cc
+++ b/components/browsing_topics/browsing_topics_calculator_unittest.cc
@@ -87,9 +87,9 @@
         optimization_guide::TestOptimizationGuideModelProvider>();
     page_content_annotations_service_ =
         std::make_unique<optimization_guide::PageContentAnnotationsService>(
-            "en-US", optimization_guide_model_provider_.get(),
-            history_service_.get(), nullptr, nullptr, base::FilePath(), nullptr,
-            nullptr);
+            nullptr, "en-US", optimization_guide_model_provider_.get(),
+            history_service_.get(), nullptr, nullptr, nullptr, base::FilePath(),
+            nullptr, nullptr);
 
     page_content_annotations_service_->OverridePageContentAnnotatorForTesting(
         &test_page_content_annotator_);
diff --git a/components/browsing_topics/browsing_topics_service_impl_unittest.cc b/components/browsing_topics/browsing_topics_service_impl_unittest.cc
index 40a2f0fb..2293057 100644
--- a/components/browsing_topics/browsing_topics_service_impl_unittest.cc
+++ b/components/browsing_topics/browsing_topics_service_impl_unittest.cc
@@ -211,9 +211,9 @@
         optimization_guide::TestOptimizationGuideModelProvider>();
     page_content_annotations_service_ =
         std::make_unique<optimization_guide::PageContentAnnotationsService>(
-            "en-US", optimization_guide_model_provider_.get(),
-            history_service_.get(), nullptr, nullptr, base::FilePath(), nullptr,
-            nullptr);
+            nullptr, "en-US", optimization_guide_model_provider_.get(),
+            history_service_.get(), nullptr, nullptr, nullptr, base::FilePath(),
+            nullptr, nullptr);
 
     page_content_annotations_service_->OverridePageContentAnnotatorForTesting(
         &test_page_content_annotator_);
diff --git a/components/commerce/core/android/BUILD.gn b/components/commerce/core/android/BUILD.gn
index 3f7251e..c9a9621 100644
--- a/components/commerce/core/android/BUILD.gn
+++ b/components/commerce/core/android/BUILD.gn
@@ -12,7 +12,6 @@
   deps = [
     "//base:jni_java",
     "//build/android:build_java",
-    "//third_party/android_deps:guava_android_java",
     "//third_party/androidx:androidx_annotation_annotation_java",
     "//url:gurl_java",
   ]
diff --git a/components/commerce/core/android/java/src/org/chromium/components/commerce/core/ShoppingService.java b/components/commerce/core/android/java/src/org/chromium/components/commerce/core/ShoppingService.java
index 89ce899..6df1dc12 100644
--- a/components/commerce/core/android/java/src/org/chromium/components/commerce/core/ShoppingService.java
+++ b/components/commerce/core/android/java/src/org/chromium/components/commerce/core/ShoppingService.java
@@ -6,14 +6,14 @@
 
 import androidx.annotation.VisibleForTesting;
 
-import com.google.common.base.Optional;
-
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.base.annotations.JNINamespace;
 import org.chromium.base.annotations.NativeMethods;
 import org.chromium.url.GURL;
 
-/** A central hub for accessing shopping and product infomration. */
+import java.util.Optional;
+
+/** A central hub for accessing shopping and product information. */
 @JNINamespace("commerce")
 public class ShoppingService {
     /** A data container for product info provided by the shopping service. */
@@ -167,7 +167,7 @@
             boolean hasPreviousPrice, long previousAmountMicros) {
         Optional<Long> previousPrice;
         if (hasPreviousPrice) {
-            previousPrice = Optional.absent();
+            previousPrice = Optional.empty();
         } else {
             previousPrice = Optional.of(previousAmountMicros);
         }
diff --git a/components/components_strings.grd b/components/components_strings.grd
index e3e64ee..21a34cf 100644
--- a/components/components_strings.grd
+++ b/components/components_strings.grd
@@ -329,6 +329,7 @@
       <part file="soda_strings.grdp" />
       <part file="ssl_errors_strings.grdp" />
       <part file="subresource_filter_strings.grdp" />
+      <part file="supervised_user_strings.grdp" />
       <part file="sync_ui_strings.grdp" />
       <part file="translate_strings.grdp" />
       <part file="tab_groups_strings.grdp" />
diff --git a/components/history_clusters/core/config.cc b/components/history_clusters/core/config.cc
index c7c61df..ab66bc3e 100644
--- a/components/history_clusters/core/config.cc
+++ b/components/history_clusters/core/config.cc
@@ -176,6 +176,18 @@
             "omnibox_history_cluster_provider_score",
             omnibox_history_cluster_provider_score);
 
+    omnibox_history_cluster_provider_inherit_search_match_score =
+        base::GetFieldTrialParamByFeatureAsBool(
+            internal::kOmniboxHistoryClusterProvider,
+            "omnibox_history_cluster_provider_inherit_search_match_score",
+            omnibox_history_cluster_provider_inherit_search_match_score);
+
+    omnibox_history_cluster_provider_rank_above_searches =
+        base::GetFieldTrialParamByFeatureAsBool(
+            internal::kOmniboxHistoryClusterProvider,
+            "omnibox_history_cluster_provider_rank_above_searches",
+            omnibox_history_cluster_provider_rank_above_searches);
+
     omnibox_history_cluster_provider_shortcuts =
         base::GetFieldTrialParamByFeatureAsBool(
             internal::kOmniboxHistoryClusterProvider,
@@ -200,12 +212,6 @@
             internal::kOmniboxHistoryClusterProvider,
             "omnibox_history_cluster_provider_on_navigation_intents",
             omnibox_history_cluster_provider_on_navigation_intents);
-
-    omnibox_history_cluster_provider_free_ranking =
-        base::GetFieldTrialParamByFeatureAsBool(
-            internal::kOmniboxHistoryClusterProvider,
-            "omnibox_history_cluster_provider_free_ranking",
-            omnibox_history_cluster_provider_free_ranking);
   }
 
   // The `kOnDeviceClusteringKeywordFiltering` feature and child params.
diff --git a/components/history_clusters/core/config.h b/components/history_clusters/core/config.h
index 69edc4e..57d9d481 100644
--- a/components/history_clusters/core/config.h
+++ b/components/history_clusters/core/config.h
@@ -190,6 +190,20 @@
   // aren't too many strong navigation matches.
   int omnibox_history_cluster_provider_score = 900;
 
+  // If enabled, will inherit the score from the matched search suggestion. This
+  // tries to emulate the ranking of chips, though remains slightly more
+  // conservative in that chips will be shown if the match query is at least the
+  // 8th top scored suggestion, while rows will be shown if the matched query is
+  // at least the 7th top scored suggestion. If enabled,
+  // `omnibox_history_cluster_provider_score` becomes a no-op.
+  bool omnibox_history_cluster_provider_inherit_search_match_score = false;
+
+  // If enabled, ranks the suggestion row below the default suggestion, but
+  // above the searches. Though whether it appears or not will depend on scores.
+  // Otherwise, ranks the suggestion among the search group; the exact position
+  // will depend on scores.
+  bool omnibox_history_cluster_provider_rank_above_searches = false;
+
   // Whether Journey suggestions from the `HistoryClusterProvider` can be
   // surfaced from the shortcuts' provider. They will be scored according to the
   // shortcuts' provider's scoring, which is more aggressive than the default
@@ -219,10 +233,6 @@
   // `omnibox_history_cluster_provider` is disabled.
   bool omnibox_history_cluster_provider_on_navigation_intents = false;
 
-  // If enabled, allows the suggestion row to be ranked in any position;
-  // otherwise, always ranked last.
-  bool omnibox_history_cluster_provider_free_ranking = false;
-
   // The `kOnDeviceClusteringKeywordFiltering` feature and child params.
 
   // If enabled, adds the keywords of aliases for detected entity names to a
diff --git a/components/navigation_metrics/navigation_metrics.cc b/components/navigation_metrics/navigation_metrics.cc
index 2e65b1c..3934eb3b 100644
--- a/components/navigation_metrics/navigation_metrics.cc
+++ b/components/navigation_metrics/navigation_metrics.cc
@@ -20,6 +20,7 @@
 #include "net/base/url_util.h"
 #include "url/gurl.h"
 #include "url/url_canon.h"
+#include "url/url_features.h"
 
 namespace navigation_metrics {
 
@@ -36,11 +37,6 @@
 
 namespace {
 
-// Kill switch for crbug.com/1362507.
-BASE_FEATURE(kStopRecordingIDNA2008Metrics,
-             "StopRecordingIDNA2008Metrics",
-             base::FEATURE_DISABLED_BY_DEFAULT);
-
 const char* const kSchemeNames[] = {
     "unknown",
     url::kHttpScheme,
@@ -176,7 +172,7 @@
 
 IDNA2008DeviationCharacter RecordIDNA2008Metrics(
     const std::u16string& hostname16) {
-  if (base::FeatureList::IsEnabled(kStopRecordingIDNA2008Metrics)) {
+  if (!url::IsRecordingIDNA2008Metrics()) {
     return IDNA2008DeviationCharacter::kNone;
   }
   if (hostname16.empty()) {
diff --git a/components/omnibox/browser/autocomplete_result.cc b/components/omnibox/browser/autocomplete_result.cc
index b42a1b57..9fac07da 100644
--- a/components/omnibox/browser/autocomplete_result.cc
+++ b/components/omnibox/browser/autocomplete_result.cc
@@ -25,6 +25,7 @@
 #include "base/trace_event/memory_usage_estimator.h"
 #include "base/trace_event/typed_macros.h"
 #include "build/build_config.h"
+#include "components/history_clusters/core/config.h"
 #include "components/omnibox/browser/actions/omnibox_pedal.h"
 #include "components/omnibox/browser/actions/omnibox_pedal_provider.h"
 #include "components/omnibox/browser/autocomplete_input.h"
@@ -1278,12 +1279,15 @@
   base::ranges::stable_sort(begin, end, {}, [](const auto& m) {
     if (AutocompleteMatch::IsStarterPackType(m.type))
       return 0;
-    // Group history cluster suggestions with searches. If the
-    // `omnibox_history_cluster_provider_free_ranking` feature is disabled,
-    // they'll be pushed back to last position, and grouping here will have
-    // no effect.
-    if (m.type == AutocompleteMatchType::HISTORY_CLUSTER)
-      return 0;
+#if !BUILDFLAG(IS_IOS)
+    // Group history cluster suggestions above or with searches.
+    if (m.type == AutocompleteMatchType::HISTORY_CLUSTER) {
+      return history_clusters::GetConfig()
+                     .omnibox_history_cluster_provider_rank_above_searches
+                 ? 0
+                 : 1;
+    }
+#endif  // !BUILDFLAG(IS_IOS)
     if (AutocompleteMatch::IsSearchType(m.type))
       return 1;
     return 2;
diff --git a/components/omnibox/browser/history_cluster_provider.cc b/components/omnibox/browser/history_cluster_provider.cc
index 8268f96..d3cfbe7 100644
--- a/components/omnibox/browser/history_cluster_provider.cc
+++ b/components/omnibox/browser/history_cluster_provider.cc
@@ -17,7 +17,6 @@
 #include "components/omnibox/browser/autocomplete_provider_client.h"
 #include "components/omnibox/browser/autocomplete_provider_listener.h"
 #include "components/strings/grit/components_strings.h"
-#include "third_party/omnibox_proto/groups.pb.h"
 #include "ui/base/l10n/l10n_util.h"
 
 HistoryClusterProvider::HistoryClusterProvider(
@@ -44,18 +43,8 @@
 void HistoryClusterProvider::CompleteHistoryClustersMatch(
     const std::string& matching_text,
     history::ClusterKeywordData matched_keyword_data,
-    AutocompleteMatch* match,
-    omnibox::GroupConfigMap* provider_suggestion_group_maps) {
+    AutocompleteMatch* match) {
   DCHECK(match);
-  DCHECK(provider_suggestion_group_maps);
-
-  if (!history_clusters::GetConfig()
-           .omnibox_history_cluster_provider_free_ranking) {
-    match->suggestion_group_id = omnibox::GROUP_HISTORY_CLUSTER;
-    // Insert a corresponding omnibox::GroupConfig with default values in the
-    // suggestion groups map; otherwise the group ID will get dropped.
-    (*provider_suggestion_group_maps)[omnibox::GROUP_HISTORY_CLUSTER];
-  }
 
   // It's fine to unconditionally attach this takeover action, as the action
   // itself checks the flag to redirect the user to either the Side Panel or
@@ -145,8 +134,8 @@
           OmniboxTriggeredFeatureService::Feature::kHistoryClusterSuggestion);
       if (!history_clusters::GetConfig()
                .omnibox_history_cluster_provider_counterfactual) {
-        matches_.push_back(CreateMatch(
-            search_match.contents, std::move(matched_keyword_data.value())));
+        matches_.push_back(
+            CreateMatch(search_match, std::move(matched_keyword_data.value())));
       }
       return true;
     }
@@ -155,7 +144,7 @@
 }
 
 AutocompleteMatch HistoryClusterProvider::CreateMatch(
-    std::u16string text,
+    const AutocompleteMatch& search_match,
     history::ClusterKeywordData matched_keyword_data) {
   AutocompleteMatch match;
   match.provider = this;
@@ -167,7 +156,13 @@
   //  Ideally, relevance would depend on how many keywords matched, how
   //  significant the keywords were, how significant their clusters were etc.
   match.relevance =
-      history_clusters::GetConfig().omnibox_history_cluster_provider_score;
+      history_clusters::GetConfig()
+              .omnibox_history_cluster_provider_inherit_search_match_score
+          ? search_match.relevance - 1
+          : history_clusters::GetConfig()
+                .omnibox_history_cluster_provider_score;
+
+  const auto& text = search_match.contents;
 
   match.destination_url = GURL(base::UTF8ToUTF16(base::StringPrintf(
       "chrome://history/journeys?q=%s",
@@ -186,8 +181,7 @@
   match.contents_class = {{0, ACMatchClassification::DIM}};
 
   CompleteHistoryClustersMatch(base::UTF16ToUTF8(text),
-                               std::move(matched_keyword_data), &match,
-                               &suggestion_groups_map_);
+                               std::move(matched_keyword_data), &match);
 
   return match;
 }
diff --git a/components/omnibox/browser/history_cluster_provider.h b/components/omnibox/browser/history_cluster_provider.h
index 29a536e1..17fc5305 100644
--- a/components/omnibox/browser/history_cluster_provider.h
+++ b/components/omnibox/browser/history_cluster_provider.h
@@ -35,8 +35,7 @@
   static void CompleteHistoryClustersMatch(
       const std::string& matching_text,
       history::ClusterKeywordData matched_keyword_data,
-      AutocompleteMatch* match,
-      omnibox::GroupConfigMap* provider_suggestion_groups_map);
+      AutocompleteMatch* match);
 
   // AutocompleteProvider:
   void Start(const AutocompleteInput& input, bool minimal_changes) override;
@@ -59,7 +58,7 @@
 
   // Creates a `AutocompleteMatch`.
   AutocompleteMatch CreateMatch(
-      std::u16string text,
+      const AutocompleteMatch& search_match,
       history::ClusterKeywordData matched_keyword_data);
 
   // The `AutocompleteInput` passed to `Start()`.
diff --git a/components/omnibox/browser/history_cluster_provider_unittest.cc b/components/omnibox/browser/history_cluster_provider_unittest.cc
index 951748e..ca878e0 100644
--- a/components/omnibox/browser/history_cluster_provider_unittest.cc
+++ b/components/omnibox/browser/history_cluster_provider_unittest.cc
@@ -446,25 +446,8 @@
   VerifyFeatureTriggered(true);
 }
 
-TEST_F(HistoryClustersProviderTest, Grouping) {
-  // By default, should have groups.
-  AutocompleteInput input;
-  input.set_omit_asynchronous_matches(false);
-  search_provider_->matches_ = {CreateMatch(u"keyword")};
-  search_provider_->done_ = true;
-
-  provider_->Start(input, false);
-  ASSERT_EQ(provider_->matches().size(), 1u);
-  EXPECT_EQ(provider_->matches()[0].suggestion_group_id,
-            omnibox::GROUP_HISTORY_CLUSTER);
-}
-
-TEST_F(HistoryClustersProviderTest, Grouping_FreeRanking) {
-  // When `omnibox_history_cluster_provider_free_ranking` is enabled, should not
-  // have groups.
-  config_.omnibox_history_cluster_provider_free_ranking = true;
-  history_clusters::SetConfigForTesting(config_);
-
+TEST_F(HistoryClustersProviderTest, Grouping_Ranking) {
+  // Should not have groups.
   AutocompleteInput input;
   input.set_omit_asynchronous_matches(false);
   search_provider_->matches_ = {CreateMatch(u"keyword")};
diff --git a/components/omnibox/browser/shortcuts_provider.cc b/components/omnibox/browser/shortcuts_provider.cc
index a9887240..566b50a0 100644
--- a/components/omnibox/browser/shortcuts_provider.cc
+++ b/components/omnibox/browser/shortcuts_provider.cc
@@ -364,8 +364,7 @@
         // Shortcut-generated HC matches have empty `ClusterKeywordData()`s,
         // because it wasn't generated via an entity match in the first place.
         HistoryClusterProvider::CompleteHistoryClustersMatch(
-            matching_string, history::ClusterKeywordData(), &match,
-            &suggestion_groups_map_);
+            matching_string, history::ClusterKeywordData(), &match);
 #endif  // !BUILDFLAG(IS_IOS)
 
         return match;
diff --git a/components/omnibox/browser/shortcuts_provider_unittest.cc b/components/omnibox/browser/shortcuts_provider_unittest.cc
index 4b45d38f..112f27f 100644
--- a/components/omnibox/browser/shortcuts_provider_unittest.cc
+++ b/components/omnibox/browser/shortcuts_provider_unittest.cc
@@ -901,26 +901,14 @@
   EXPECT_EQ(matches[5].relevance, matches[0].relevance);
   EXPECT_EQ(matches[6].relevance, matches[0].relevance);
 
-  // Expect cluster matches to have grouping.
+  // Expect cluster matches to not have grouping.
   EXPECT_EQ(matches[0].suggestion_group_id, absl::nullopt);
   EXPECT_EQ(matches[1].suggestion_group_id, absl::nullopt);
   EXPECT_EQ(matches[2].suggestion_group_id, absl::nullopt);
-  EXPECT_EQ(matches[3].suggestion_group_id, omnibox::GROUP_HISTORY_CLUSTER);
-  EXPECT_EQ(matches[4].suggestion_group_id, omnibox::GROUP_HISTORY_CLUSTER);
-  EXPECT_EQ(matches[5].suggestion_group_id, omnibox::GROUP_HISTORY_CLUSTER);
-  EXPECT_EQ(matches[6].suggestion_group_id, omnibox::GROUP_HISTORY_CLUSTER);
-
-  // With `omnibox_history_cluster_provider_free_ranking`, should not have
-  // groups.
-  config.omnibox_history_cluster_provider_free_ranking = true;
-  history_clusters::SetConfigForTesting(config);
-  provider_->Start(input, false);
-  const auto matches_with_free_ranking = provider_->matches();
-  ASSERT_EQ(matches_with_free_ranking.size(), matches.size());
-  for (size_t i = 0; i < matches.size(); ++i) {
-    EXPECT_EQ(matches_with_free_ranking[i].contents, matches[i].contents);
-    EXPECT_EQ(matches_with_free_ranking[i].suggestion_group_id, absl::nullopt);
-  }
+  EXPECT_EQ(matches[3].suggestion_group_id, absl::nullopt);
+  EXPECT_EQ(matches[4].suggestion_group_id, absl::nullopt);
+  EXPECT_EQ(matches[5].suggestion_group_id, absl::nullopt);
+  EXPECT_EQ(matches[6].suggestion_group_id, absl::nullopt);
 
   // With `omnibox_history_cluster_provider_allow_default`, should be allowed
   // default.
diff --git a/components/omnibox/browser/zero_suggest_cache_service.cc b/components/omnibox/browser/zero_suggest_cache_service.cc
index 4835e3f..7d556de 100644
--- a/components/omnibox/browser/zero_suggest_cache_service.cc
+++ b/components/omnibox/browser/zero_suggest_cache_service.cc
@@ -6,6 +6,9 @@
 
 #include "base/metrics/histogram_functions.h"
 #include "base/trace_event/memory_usage_estimator.h"
+#include "components/omnibox/browser/autocomplete_input.h"
+#include "components/omnibox/browser/autocomplete_provider_client.h"
+#include "components/omnibox/browser/search_suggestion_parser.h"
 
 using CacheEntry = ZeroSuggestCacheService::CacheEntry;
 
@@ -57,6 +60,27 @@
 
 CacheEntry::~CacheEntry() = default;
 
+SearchSuggestionParser::SuggestResults CacheEntry::GetSuggestResults(
+    const AutocompleteInput& input,
+    const AutocompleteProviderClient& client) const {
+  SearchSuggestionParser::Results results;
+
+  auto response_data =
+      SearchSuggestionParser::DeserializeJsonData(response_json);
+  if (!response_data) {
+    return results.suggest_results;
+  }
+
+  if (!SearchSuggestionParser::ParseSuggestResults(
+          *response_data, input, client.GetSchemeClassifier(),
+          /*default_result_relevance=*/100, /*is_keyword_result=*/false,
+          &results)) {
+    return results.suggest_results;
+  }
+
+  return results.suggest_results;
+}
+
 size_t CacheEntry::EstimateMemoryUsage() const {
   return base::trace_event::EstimateMemoryUsage(response_json);
 }
diff --git a/components/omnibox/browser/zero_suggest_cache_service.h b/components/omnibox/browser/zero_suggest_cache_service.h
index a8af960..812346c 100644
--- a/components/omnibox/browser/zero_suggest_cache_service.h
+++ b/components/omnibox/browser/zero_suggest_cache_service.h
@@ -12,6 +12,9 @@
 #include "base/observer_list_types.h"
 #include "build/build_config.h"
 #include "components/keyed_service/core/keyed_service.h"
+#include "components/omnibox/browser/autocomplete_input.h"
+#include "components/omnibox/browser/autocomplete_provider_client.h"
+#include "components/omnibox/browser/search_suggestion_parser.h"
 
 class ZeroSuggestCacheService : public KeyedService {
  public:
@@ -27,6 +30,14 @@
     // JSON response received from the remote Suggest service.
     std::string response_json;
 
+    // Parses the stored JSON response in order to extract the list of
+    // suggestions received from the remote Suggest service.
+    // For memory efficiency reasons, CacheEntry does not store the
+    // deserialized SuggestResults object as a data member.
+    SearchSuggestionParser::SuggestResults GetSuggestResults(
+        const AutocompleteInput& input,
+        const AutocompleteProviderClient& client) const;
+
     // Estimates dynamic memory usage.
     // See base/trace_event/memory_usage_estimator.h for more info.
     size_t EstimateMemoryUsage() const;
diff --git a/components/optimization_guide/content/browser/BUILD.gn b/components/optimization_guide/content/browser/BUILD.gn
index 3ea5ddf..82c0a51 100644
--- a/components/optimization_guide/content/browser/BUILD.gn
+++ b/components/optimization_guide/content/browser/BUILD.gn
@@ -39,6 +39,7 @@
     "//components/history/core/browser",
     "//components/keyed_service/core",
     "//components/no_state_prefetch/browser",
+    "//components/omnibox/browser",
     "//components/optimization_guide:machine_learning_tflite_buildflags",
     "//components/optimization_guide:optimization_guide_buildflags",
     "//components/optimization_guide/content/mojom:mojo_interfaces",
@@ -50,6 +51,7 @@
     "//services/metrics/public/cpp:metrics_cpp",
     "//services/metrics/public/cpp:ukm_builders",
     "//services/network/public/cpp",
+    "//third_party/omnibox_proto",
   ]
   if (build_with_tflite_lib) {
     public_deps += [
diff --git a/components/optimization_guide/content/browser/DEPS b/components/optimization_guide/content/browser/DEPS
index e4c9a2d..9da827c4 100644
--- a/components/optimization_guide/content/browser/DEPS
+++ b/components/optimization_guide/content/browser/DEPS
@@ -5,6 +5,7 @@
   "+components/history/core/browser",
   "+components/keyed_service/core",
   "+components/no_state_prefetch/browser",
+  "+components/omnibox/browser",
   "+components/optimization_guide/core",
   "+components/optimization_guide/proto",
   "+components/search_engines",
@@ -15,4 +16,5 @@
   "+services/metrics/public",
   "+services/service_manager/public",
   "+third_party/blink/public",
+  "+third_party/omnibox_proto",
 ]
diff --git a/components/optimization_guide/content/browser/page_content_annotations_service.cc b/components/optimization_guide/content/browser/page_content_annotations_service.cc
index dcb6c609..40e200b 100644
--- a/components/optimization_guide/content/browser/page_content_annotations_service.cc
+++ b/components/optimization_guide/content/browser/page_content_annotations_service.cc
@@ -4,6 +4,9 @@
 
 #include "components/optimization_guide/content/browser/page_content_annotations_service.h"
 
+#include <algorithm>
+#include <utility>
+
 #include "base/barrier_closure.h"
 #include "base/containers/adapters.h"
 #include "base/metrics/histogram_functions.h"
@@ -14,6 +17,8 @@
 #include "components/history/core/browser/history_service.h"
 #include "components/history/core/browser/history_types.h"
 #include "components/leveldb_proto/public/proto_database_provider.h"
+#include "components/omnibox/browser/autocomplete_input.h"
+#include "components/omnibox/browser/search_suggestion_parser.h"
 #include "components/optimization_guide/content/browser/page_content_annotations_validator.h"
 #include "components/optimization_guide/core/entity_metadata.h"
 #include "components/optimization_guide/core/local_page_entities_metadata_provider.h"
@@ -30,6 +35,7 @@
 #include "services/metrics/public/cpp/ukm_recorder.h"
 #include "services/metrics/public/cpp/ukm_source.h"
 #include "services/metrics/public/cpp/ukm_source_id.h"
+#include "third_party/omnibox_proto/types.pb.h"
 
 #if BUILDFLAG(BUILD_WITH_TFLITE_LIB)
 #include "components/optimization_guide/content/browser/page_content_annotations_model_manager.h"
@@ -111,18 +117,22 @@
 }  // namespace
 
 PageContentAnnotationsService::PageContentAnnotationsService(
+    std::unique_ptr<AutocompleteProviderClient> autocomplete_provider_client,
     const std::string& application_locale,
     OptimizationGuideModelProvider* optimization_guide_model_provider,
     history::HistoryService* history_service,
     TemplateURLService* template_url_service,
+    ZeroSuggestCacheService* zero_suggest_cache_service,
     leveldb_proto::ProtoDatabaseProvider* database_provider,
     const base::FilePath& database_dir,
     OptimizationGuideLogger* optimization_guide_logger,
     scoped_refptr<base::SequencedTaskRunner> background_task_runner)
-    : min_page_category_score_to_persist_(
+    : autocomplete_provider_client_(std::move(autocomplete_provider_client)),
+      min_page_category_score_to_persist_(
           features::GetMinimumPageCategoryScoreToPersist()),
       history_service_(history_service),
       template_url_service_(template_url_service),
+      zero_suggest_cache_service_(zero_suggest_cache_service),
       last_annotated_history_visits_(
           features::MaxContentAnnotationRequestsCached()),
       annotated_text_cache_(features::MaxVisitAnnotationCacheSize()),
@@ -130,6 +140,10 @@
   DCHECK(optimization_guide_model_provider);
   DCHECK(history_service_);
   history_service_observation_.Observe(history_service_);
+  if (zero_suggest_cache_service_) {
+    zero_suggest_cache_service_observation_.Observe(
+        zero_suggest_cache_service_);
+  }
 #if BUILDFLAG(BUILD_WITH_TFLITE_LIB)
   model_manager_ = std::make_unique<PageContentAnnotationsModelManager>(
       optimization_guide_model_provider);
@@ -377,6 +391,10 @@
 void PageContentAnnotationsService::ExtractRelatedSearches(
     const HistoryVisit& visit,
     content::WebContents* web_contents) {
+  if (ShouldExtractRelatedSearchesFromZPSCache()) {
+    return;
+  }
+
   search_result_extractor_client_.RequestData(
       web_contents, {continuous_search::mojom::ResultType::kRelatedSearches},
       base::BindOnce(&PageContentAnnotationsService::OnRelatedSearchesExtracted,
@@ -428,6 +446,64 @@
 }
 #endif
 
+bool PageContentAnnotationsService::ShouldExtractRelatedSearchesFromZPSCache() {
+  return base::FeatureList::IsEnabled(
+             features::kExtractRelatedSearchesFromPrefetchedZPSResponse) &&
+         autocomplete_provider_client_ && zero_suggest_cache_service_;
+}
+
+void PageContentAnnotationsService::OnZeroSuggestResponseUpdated(
+    const std::string& page_url,
+    const ZeroSuggestCacheService::CacheEntry& response) {
+  if (!ShouldExtractRelatedSearchesFromZPSCache()) {
+    return;
+  }
+
+  if (page_url.empty() || !google_util::IsGoogleSearchUrl(GURL(page_url))) {
+    return;
+  }
+
+  history_service_->QueryURL(
+      GURL(page_url), /*want_visits=*/true,
+      base::BindOnce(&PageContentAnnotationsService::
+                         ExtractRelatedSearchesFromZeroSuggestResponse,
+                     weak_ptr_factory_.GetWeakPtr(), response),
+      &history_service_task_tracker_);
+}
+
+void PageContentAnnotationsService::
+    ExtractRelatedSearchesFromZeroSuggestResponse(
+        const ZeroSuggestCacheService::CacheEntry& response,
+        history::QueryURLResult url_result) {
+  if (url_result.visits.empty()) {
+    return;
+  }
+
+  AutocompleteInput input(u"", metrics::OmniboxEventProto::JOURNEYS,
+                          autocomplete_provider_client_->GetSchemeClassifier());
+  auto suggest_results =
+      response.GetSuggestResults(input, *autocomplete_provider_client_);
+
+  std::vector<std::string> related_searches;
+  for (const auto& result : suggest_results) {
+    const auto subtypes = result.subtypes();
+    // Suggestions with HIVEMIND subtype are considered "related searches".
+    auto it = std::find(subtypes.begin(), subtypes.end(),
+                        omnibox::SuggestSubtype::SUBTYPE_HIVEMIND);
+    if (it != subtypes.end()) {
+      related_searches.push_back(base::UTF16ToUTF8(
+          base::CollapseWhitespace(result.suggestion(), true)));
+    }
+  }
+
+  if (related_searches.empty()) {
+    return;
+  }
+
+  auto visit_id = url_result.visits.front().visit_id;
+  history_service_->AddRelatedSearchesForVisit(related_searches, visit_id);
+}
+
 void PageContentAnnotationsService::OnRelatedSearchesExtracted(
     const HistoryVisit& visit,
     continuous_search::SearchResultExtractorClientStatus status,
diff --git a/components/optimization_guide/content/browser/page_content_annotations_service.h b/components/optimization_guide/content/browser/page_content_annotations_service.h
index c4e97c37..b25fb62e 100644
--- a/components/optimization_guide/content/browser/page_content_annotations_service.h
+++ b/components/optimization_guide/content/browser/page_content_annotations_service.h
@@ -5,6 +5,7 @@
 #ifndef COMPONENTS_OPTIMIZATION_GUIDE_CONTENT_BROWSER_PAGE_CONTENT_ANNOTATIONS_SERVICE_H_
 #define COMPONENTS_OPTIMIZATION_GUIDE_CONTENT_BROWSER_PAGE_CONTENT_ANNOTATIONS_SERVICE_H_
 
+#include <memory>
 #include <string>
 #include <vector>
 
@@ -25,6 +26,8 @@
 #include "components/history/core/browser/history_types.h"
 #include "components/history/core/browser/url_row.h"
 #include "components/keyed_service/core/keyed_service.h"
+#include "components/omnibox/browser/autocomplete_provider_client.h"
+#include "components/omnibox/browser/zero_suggest_cache_service.h"
 #include "components/optimization_guide/content/browser/page_content_annotator.h"
 #include "components/optimization_guide/core/entity_metadata_provider.h"
 #include "components/optimization_guide/core/model_info.h"
@@ -96,13 +99,16 @@
 // A KeyedService that annotates page content.
 class PageContentAnnotationsService : public KeyedService,
                                       public EntityMetadataProvider,
-                                      public history::HistoryServiceObserver {
+                                      public history::HistoryServiceObserver,
+                                      public ZeroSuggestCacheService::Observer {
  public:
   PageContentAnnotationsService(
+      std::unique_ptr<AutocompleteProviderClient> autocomplete_provider_client,
       const std::string& application_locale,
       OptimizationGuideModelProvider* optimization_guide_model_provider,
       history::HistoryService* history_service,
       TemplateURLService* template_url_service,
+      ZeroSuggestCacheService* zero_suggest_cache_service,
       leveldb_proto::ProtoDatabaseProvider* database_provider,
       const base::FilePath& database_dir,
       OptimizationGuideLogger* optimization_guide_logger,
@@ -146,6 +152,20 @@
   // test_page_content_annotator.h for an implementation designed for testing.
   void OverridePageContentAnnotatorForTesting(PageContentAnnotator* annotator);
 
+  // Specifies whether PageContentAnnotationsService should extract "related
+  // searches" data from the ZPS response cache.
+  bool ShouldExtractRelatedSearchesFromZPSCache();
+
+  // ZeroSuggestCacheService::Observer:
+  void OnZeroSuggestResponseUpdated(
+      const std::string& page_url,
+      const ZeroSuggestCacheService::CacheEntry& response) override;
+
+  // Callback used to extract "related searches" data from cached ZPS responses.
+  void ExtractRelatedSearchesFromZeroSuggestResponse(
+      const ZeroSuggestCacheService::CacheEntry& response,
+      history::QueryURLResult url_result);
+
   OptimizationGuideLogger* optimization_guide_logger() const {
     return optimization_guide_logger_;
   }
@@ -258,6 +278,9 @@
                     PageContentAnnotationsType annotation_type,
                     history::QueryURLResult url_result);
 
+  // Provider client instance used when parsing cached ZPS response data.
+  std::unique_ptr<AutocompleteProviderClient> autocomplete_provider_client_;
+
   // The minimum score that an allowlisted page category must have for it to be
   // persisted.
   const int min_page_category_score_to_persist_;
@@ -280,6 +303,12 @@
       history_service_observation_{this};
   // The task tracker to keep track of tasks to query |history_service|.
   base::CancelableTaskTracker history_service_task_tracker_;
+  // The zero suggest cache service used to fetch cached ZPS response data.
+  const raw_ptr<ZeroSuggestCacheService> zero_suggest_cache_service_;
+  // The scoped observation to the ZeroSuggestCacheService.
+  base::ScopedObservation<ZeroSuggestCacheService,
+                          PageContentAnnotationsService>
+      zero_suggest_cache_service_observation_{this};
   // The client of continuous_search::mojom::SearchResultExtractor interface
   // used for extracting data from the main frame of Google SRP |web_contents|.
   continuous_search::SearchResultExtractorClient
diff --git a/components/optimization_guide/content/browser/page_content_annotations_web_contents_observer_unittest.cc b/components/optimization_guide/content/browser/page_content_annotations_web_contents_observer_unittest.cc
index 2cc0ae5..1af63838 100644
--- a/components/optimization_guide/content/browser/page_content_annotations_web_contents_observer_unittest.cc
+++ b/components/optimization_guide/content/browser/page_content_annotations_web_contents_observer_unittest.cc
@@ -83,11 +83,13 @@
   explicit FakePageContentAnnotationsService(
       OptimizationGuideModelProvider* optimization_guide_model_provider,
       history::HistoryService* history_service)
-      : PageContentAnnotationsService("en-US",
+      : PageContentAnnotationsService(nullptr,
+                                      "en-US",
                                       optimization_guide_model_provider,
                                       history_service,
                                       nullptr,
                                       nullptr,
+                                      nullptr,
                                       base::FilePath(),
                                       nullptr,
                                       nullptr) {}
diff --git a/components/optimization_guide/core/prediction_model_store.cc b/components/optimization_guide/core/prediction_model_store.cc
index 69f548a..57506cd 100644
--- a/components/optimization_guide/core/prediction_model_store.cc
+++ b/components/optimization_guide/core/prediction_model_store.cc
@@ -4,9 +4,11 @@
 
 #include "components/optimization_guide/core/prediction_model_store.h"
 
+#include "base/files/file_enumerator.h"
 #include "base/files/file_util.h"
 #include "base/guid.h"
 #include "base/memory/ptr_util.h"
+#include "base/metrics/histogram_functions.h"
 #include "base/rand_util.h"
 #include "base/task/thread_pool.h"
 #include "components/optimization_guide/core/model_store_metadata_entry.h"
@@ -19,6 +21,8 @@
 
 namespace {
 
+constexpr size_t kBytesPerMegabyte = 1024 * 1024;
+
 // Returns the model info parsed from |model_info_path|.
 absl::optional<proto::ModelInfo> ParseModelInfoFromFile(
     const base::FilePath& model_info_path) {
@@ -55,6 +59,50 @@
   return model_file_paths;
 }
 
+// Parses the OptimizationTarget from the string.
+proto::OptimizationTarget ParseOptimizationTargetFromString(
+    const std::string& optimization_target_str) {
+  int optimization_target;
+  if (!base::StringToInt(optimization_target_str, &optimization_target)) {
+    return proto::OPTIMIZATION_TARGET_UNKNOWN;
+  }
+  if (!proto::OptimizationTarget_IsValid(optimization_target)) {
+    return proto::OPTIMIZATION_TARGET_UNKNOWN;
+  }
+  return static_cast<proto::OptimizationTarget>(optimization_target);
+}
+
+void RecordModelStorageMetrics(const base::FilePath& base_store_dir) {
+  base::FileEnumerator enumerator(base_store_dir, false,
+                                  base::FileEnumerator::DIRECTORIES);
+  for (base::FilePath optimization_target_dir = enumerator.Next();
+       !optimization_target_dir.empty();
+       optimization_target_dir = enumerator.Next()) {
+    proto::OptimizationTarget optimization_target =
+        ParseOptimizationTargetFromString(
+            optimization_target_dir.BaseName().AsUTF8Unsafe());
+    if (optimization_target == proto::OPTIMIZATION_TARGET_UNKNOWN) {
+      continue;
+    }
+    size_t total_models = 0;
+    base::FileEnumerator models_enumerator(optimization_target_dir, false,
+                                           base::FileEnumerator::DIRECTORIES);
+    for (base::FilePath model_dir = models_enumerator.Next();
+         !model_dir.empty(); model_dir = models_enumerator.Next()) {
+      total_models++;
+    }
+    base::UmaHistogramCounts100(
+        "OptimizationGuide.PredictionModelStore.ModelCount." +
+            GetStringNameForOptimizationTarget(optimization_target),
+        total_models);
+    base::UmaHistogramMemoryMB(
+        "OptimizationGuide.PredictionModelStore.TotalDirectorySize." +
+            GetStringNameForOptimizationTarget(optimization_target),
+        base::ComputeDirectorySize(optimization_target_dir) /
+            kBytesPerMegabyte);
+  }
+}
+
 }  // namespace
 
 // static
@@ -83,6 +131,9 @@
 
   local_state_ = local_state;
   base_store_dir_ = base_store_dir;
+
+  background_task_runner_->PostTask(
+      FROM_HERE, base::BindOnce(&RecordModelStorageMetrics, base_store_dir_));
 }
 
 // static
diff --git a/components/optimization_guide/core/prediction_model_store_unittest.cc b/components/optimization_guide/core/prediction_model_store_unittest.cc
index 227847fe..b6df191d 100644
--- a/components/optimization_guide/core/prediction_model_store_unittest.cc
+++ b/components/optimization_guide/core/prediction_model_store_unittest.cc
@@ -9,6 +9,7 @@
 #include "base/guid.h"
 #include "base/rand_util.h"
 #include "base/task/thread_pool.h"
+#include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/test/task_environment.h"
 #include "components/optimization_guide/core/model_store_metadata_entry.h"
@@ -244,4 +245,52 @@
   EXPECT_TRUE(metadata_entry->GetKeepBeyondValidDuration());
 }
 
+TEST_F(PredictionModelStoreTest, ModelStorageMetrics) {
+  RunUntilIdle();
+  base::HistogramTester histogram_tester;
+
+  auto model_cache_key = CreateModelCacheKey(kTestLocaleFoo);
+  auto model_detail =
+      CreateTestModelFiles(kTestOptimizationTargetFoo, model_cache_key, {});
+  prediction_model_store_->UpdateModel(
+      kTestOptimizationTargetFoo, model_cache_key, model_detail.model_info,
+      model_detail.base_model_dir, base::DoNothing());
+  RunUntilIdle();
+  model_detail =
+      CreateTestModelFiles(kTestOptimizationTargetBar, model_cache_key, {});
+  prediction_model_store_->UpdateModel(
+      kTestOptimizationTargetBar, model_cache_key, model_detail.model_info,
+      model_detail.base_model_dir, base::DoNothing());
+  RunUntilIdle();
+
+  // Recreate the model store, and that should record model storage metrics.
+  prediction_model_store_ =
+      PredictionModelStore::CreatePredictionModelStoreForTesting(
+          local_state_prefs_.get(), temp_models_dir_.GetPath());
+  RunUntilIdle();
+  histogram_tester.ExpectUniqueSample(
+      "OptimizationGuide.PredictionModelStore.ModelCount.PainfulPageLoad", 1,
+      1);
+  histogram_tester.ExpectTotalCount(
+      "OptimizationGuide.PredictionModelStore.TotalDirectorySize."
+      "PainfulPageLoad",
+      1);
+  histogram_tester.ExpectUniqueSample(
+      "OptimizationGuide.PredictionModelStore.ModelCount.ModelValidation", 1,
+      1);
+  histogram_tester.ExpectTotalCount(
+      "OptimizationGuide.PredictionModelStore.TotalDirectorySize."
+      "ModelValidation",
+      1);
+  EXPECT_EQ(2U, histogram_tester
+                    .GetTotalCountsForPrefix(
+                        "OptimizationGuide.PredictionModelStore.ModelCount.")
+                    .size());
+  EXPECT_EQ(
+      2U, histogram_tester
+              .GetTotalCountsForPrefix(
+                  "OptimizationGuide.PredictionModelStore.TotalDirectorySize.")
+              .size());
+}
+
 }  // namespace optimization_guide
diff --git a/components/paint_preview/player/android/java/src/org/chromium/components/paintpreview/player/PlayerSwipeRefreshHandler.java b/components/paint_preview/player/android/java/src/org/chromium/components/paintpreview/player/PlayerSwipeRefreshHandler.java
index b2279f9..ff079046 100644
--- a/components/paint_preview/player/android/java/src/org/chromium/components/paintpreview/player/PlayerSwipeRefreshHandler.java
+++ b/components/paint_preview/player/android/java/src/org/chromium/components/paintpreview/player/PlayerSwipeRefreshHandler.java
@@ -67,8 +67,7 @@
 
     @Override
     public boolean start() {
-        // TODO(1335416): Update this to |true| if experiment is successful
-        return mSwipeRefreshLayout.start(false);
+        return mSwipeRefreshLayout.start();
     }
 
     @Override
diff --git a/components/resources/BUILD.gn b/components/resources/BUILD.gn
index 2a80cbf..a5e43ac 100644
--- a/components/resources/BUILD.gn
+++ b/components/resources/BUILD.gn
@@ -4,6 +4,7 @@
 
 import("//build/config/android/config.gni")
 import("//components/safe_browsing/buildflags.gni")
+import("//components/supervised_user/buildflags.gni")
 import("//printing/buildflags/buildflags.gni")
 import("//tools/grit/grit_rule.gni")
 
@@ -42,6 +43,7 @@
   defines = [
     "enable_printing=$enable_printing",
     "enable_print_preview=$enable_print_preview",
+    "enable_supervised_users=$enable_supervised_users",
     "safe_browsing_mode=$safe_browsing_mode",
     "is_fuchsia=$is_fuchsia",
   ]
diff --git a/components/resources/components_resources.grd b/components/resources/components_resources.grd
index 03e7665..0e9f7458 100644
--- a/components/resources/components_resources.grd
+++ b/components/resources/components_resources.grd
@@ -30,6 +30,9 @@
       <if expr="is_android">
         <part file="android_system_error_page_resources.grdp" />
       </if>
+      <if expr="enable_supervised_users">
+        <part file="supervised_user_resources.grdp" />
+      </if>
     </includes>
   </release>
 </grit>
diff --git a/components/resources/supervised_user_resources.grdp b/components/resources/supervised_user_resources.grdp
new file mode 100644
index 0000000..cc8c8aa
--- /dev/null
+++ b/components/resources/supervised_user_resources.grdp
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<grit-part>
+  <include name="IDR_SUPERVISED_USER_BLOCK_INTERSTITIAL_HTML" file="../supervised_user/core/browser/resources/supervised_user_block_interstitial.html" flattenhtml="true" type="BINDATA" compress="brotli" />
+  <include name="IDR_SUPERVISED_USER_BLOCK_INTERSTITIAL_V2_HTML" file="../supervised_user/core/browser/resources/supervised_user_block_interstitial_v2.html" flattenhtml="true" type="BINDATA" compress="brotli" />
+  <include name="IDR_SUPERVISED_USER_ICON" file="../supervised_user/core/browser/resources/supervised_user_icon.png" type="BINDATA" />
+</grit-part>
diff --git a/components/segmentation_platform/internal/execution/model_executor_impl.cc b/components/segmentation_platform/internal/execution/model_executor_impl.cc
index d8ee45e..1a4e8d9 100644
--- a/components/segmentation_platform/internal/execution/model_executor_impl.cc
+++ b/components/segmentation_platform/internal/execution/model_executor_impl.cc
@@ -30,8 +30,8 @@
                            const ModelExecutorImpl::ExecutionState& state);
   ~ModelExecutionTraceEvent();
 
-  const raw_ref<const ModelExecutorImpl::ExecutionState, DanglingUntriaged>
-      state;
+  // Which track to add this trace event to.
+  perfetto::Track track;
 };
 
 struct ModelExecutorImpl::ExecutionState {
@@ -71,14 +71,13 @@
 ModelExecutorImpl::ModelExecutionTraceEvent::ModelExecutionTraceEvent(
     const char* event_name,
     const ModelExecutorImpl::ExecutionState& state)
-    : state(state) {
+    : track(perfetto::Track::FromPointer(&state)) {
   TRACE_EVENT_BEGIN("segmentation_platform", perfetto::StaticString(event_name),
-                    perfetto::Track::FromPointer(&state));
+                    track);
 }
 
 ModelExecutorImpl::ModelExecutionTraceEvent::~ModelExecutionTraceEvent() {
-  TRACE_EVENT_END("segmentation_platform",
-                  perfetto::Track::FromPointer(&*state));
+  TRACE_EVENT_END("segmentation_platform", track);
 }
 
 ModelExecutorImpl::ModelExecutorImpl(
@@ -109,7 +108,7 @@
                                        *state);
 
   if (!state->model_provider || !state->model_provider->ModelAvailable()) {
-    RunModelExecutionCallback(std::move(state),
+    RunModelExecutionCallback(*state, std::move(state->callback),
                               std::make_unique<ModelExecutionResult>(
                                   ModelExecutionStatus::kSkippedModelNotReady));
     return;
@@ -119,8 +118,9 @@
   if (metadata_utils::ValidateSegmentInfo(segment_info) !=
       metadata_utils::ValidationResult::kValidationSuccess) {
     RunModelExecutionCallback(
-        std::move(state), std::make_unique<ModelExecutionResult>(
-                              ModelExecutionStatus::kSkippedInvalidMetadata));
+        *state, std::move(state->callback),
+        std::make_unique<ModelExecutionResult>(
+            ModelExecutionStatus::kSkippedInvalidMetadata));
     return;
   }
 
@@ -141,8 +141,9 @@
   if (error) {
     // Validation error occurred on model's metadata.
     RunModelExecutionCallback(
-        std::move(state), std::make_unique<ModelExecutionResult>(
-                              ModelExecutionStatus::kSkippedInvalidMetadata));
+        *state, std::move(state->callback),
+        std::make_unique<ModelExecutionResult>(
+            ModelExecutionStatus::kSkippedInvalidMetadata));
     return;
   }
   state->input_tensor.insert(state->input_tensor.end(), input_tensor.begin(),
@@ -205,28 +206,29 @@
       }
     }
     ModelProvider::Request input_tensor = state->input_tensor;
-    RunModelExecutionCallback(std::move(state),
+    RunModelExecutionCallback(*state, std::move(state->callback),
                               std::make_unique<ModelExecutionResult>(
                                   std::move(input_tensor), *result));
   } else {
     VLOG(1) << "Segmentation model returned no result for segment "
             << proto::SegmentId_Name(state->segment_info.segment_id());
-    RunModelExecutionCallback(std::move(state),
+    RunModelExecutionCallback(*state, std::move(state->callback),
                               std::make_unique<ModelExecutionResult>(
                                   ModelExecutionStatus::kExecutionError));
   }
 }
 
 void ModelExecutorImpl::RunModelExecutionCallback(
-    std::unique_ptr<ExecutionState> state,
+    const ExecutionState& state,
+    ModelExecutionCallback callback,
     std::unique_ptr<ModelExecutionResult> result) {
   stats::RecordModelExecutionDurationTotal(
-      state->segment_info.segment_id(), result->status,
-      clock_->Now() - state->total_execution_start_time);
-  stats::RecordModelExecutionStatus(state->segment_info.segment_id(),
-                                    state->record_metrics_for_default,
+      state.segment_info.segment_id(), result->status,
+      clock_->Now() - state.total_execution_start_time);
+  stats::RecordModelExecutionStatus(state.segment_info.segment_id(),
+                                    state.record_metrics_for_default,
                                     result->status);
-  std::move(state->callback).Run(std::move(result));
+  std::move(callback).Run(std::move(result));
 }
 
 }  // namespace segmentation_platform
diff --git a/components/segmentation_platform/internal/execution/model_executor_impl.h b/components/segmentation_platform/internal/execution/model_executor_impl.h
index 8417f59f..069b778a 100644
--- a/components/segmentation_platform/internal/execution/model_executor_impl.h
+++ b/components/segmentation_platform/internal/execution/model_executor_impl.h
@@ -67,8 +67,11 @@
       const absl::optional<ModelProvider::Response>& result);
 
   // Helper function for synchronously invoking the callback with the given
-  // result and status.
-  void RunModelExecutionCallback(std::unique_ptr<ExecutionState> state,
+  // result and status. Before invoking this, it is required to move the
+  // ExecutionState::callback out as a separate parameter, e.g.:
+  // `RunModelExecutionCallback(*state, std::move(state->callback), ...)`.
+  void RunModelExecutionCallback(const ExecutionState& state,
+                                 ModelExecutionCallback callback,
                                  std::unique_ptr<ModelExecutionResult> result);
 
   const raw_ptr<base::Clock> clock_;
diff --git a/components/segmentation_platform/internal/execution/model_executor_impl_unittest.cc b/components/segmentation_platform/internal/execution/model_executor_impl_unittest.cc
index 609c2ee7..6633e6b 100644
--- a/components/segmentation_platform/internal/execution/model_executor_impl_unittest.cc
+++ b/components/segmentation_platform/internal/execution/model_executor_impl_unittest.cc
@@ -34,6 +34,7 @@
 #include "components/segmentation_platform/public/proto/types.pb.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/perfetto/include/perfetto/tracing/track.h"
 
 using ::base::test::RunOnceCallback;
 using segmentation_platform::processing::FeatureListQueryProcessor;
@@ -54,6 +55,10 @@
   ~ModelExecutorTest() override = default;
 
   void SetUp() override {
+    // TODO(khokhlov): Replace with a test environment for tracing after the
+    // SDK migration
+    perfetto::internal::TrackRegistry::InitializeInstance();
+
     signal_database_ = std::make_unique<MockSignalDatabase>();
     clock_.SetNow(base::Time::Now());
   }
diff --git a/components/segmentation_platform/internal/segmentation_platform_service_impl_unittest.cc b/components/segmentation_platform/internal/segmentation_platform_service_impl_unittest.cc
index 70f40ad2..ac5d814 100644
--- a/components/segmentation_platform/internal/segmentation_platform_service_impl_unittest.cc
+++ b/components/segmentation_platform/internal/segmentation_platform_service_impl_unittest.cc
@@ -30,6 +30,7 @@
 #include "components/ukm/test_ukm_recorder.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/perfetto/include/perfetto/tracing/track.h"
 
 using base::test::RunOnceCallback;
 using ::testing::_;
@@ -83,6 +84,10 @@
     base::SetRecordActionTaskRunner(
         task_environment_.GetMainThreadTaskRunner());
 
+    // TODO(khokhlov): Replace with a test environment for tracing after the
+    // SDK migration
+    perfetto::internal::TrackRegistry::InitializeInstance();
+
     // Setup model provider data for default model for supporting on demand
     // execution.
     model_provider_data_.segments_supporting_default_model = {
diff --git a/components/segmentation_platform/internal/selection/segment_result_provider_unittest.cc b/components/segmentation_platform/internal/selection/segment_result_provider_unittest.cc
index f790814b..1ffd645 100644
--- a/components/segmentation_platform/internal/selection/segment_result_provider_unittest.cc
+++ b/components/segmentation_platform/internal/selection/segment_result_provider_unittest.cc
@@ -24,6 +24,7 @@
 #include "components/segmentation_platform/public/segmentation_platform_service.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/perfetto/include/perfetto/tracing/track.h"
 
 namespace segmentation_platform {
 namespace {
@@ -81,6 +82,10 @@
   ~SegmentResultProviderTest() override = default;
 
   void SetUp() override {
+    // TODO(khokhlov): Replace with a test environment for tracing after the
+    // SDK migration
+    perfetto::internal::TrackRegistry::InitializeInstance();
+
     default_manager_ = std::make_unique<DefaultModelManager>(
         &provider_factory_,
         std::vector<SegmentId>({kTestSegment, kTestSegment2}));
diff --git a/components/supervised_user/COMMON_METADATA b/components/supervised_user/COMMON_METADATA
new file mode 100644
index 0000000..b2bd8d8
--- /dev/null
+++ b/components/supervised_user/COMMON_METADATA
@@ -0,0 +1,5 @@
+# Chrome Kids team
+monorail {
+  component: "FamilyExperiences > Browser"
+}
+team_email: "chrome-kids-eng@google.com"
diff --git a/components/supervised_user/DEPS b/components/supervised_user/DEPS
new file mode 100644
index 0000000..d3410bb
--- /dev/null
+++ b/components/supervised_user/DEPS
@@ -0,0 +1,6 @@
+include_rules = [
+  "+components/grit",
+  "+components/strings/grit",
+  "+components/signin",
+  "+ui/base",
+]
diff --git a/components/supervised_user/OWNERS b/components/supervised_user/OWNERS
new file mode 100644
index 0000000..a8ae8ed
--- /dev/null
+++ b/components/supervised_user/OWNERS
@@ -0,0 +1,19 @@
+# Note: Unless you want a specific reviewer's expertise, please send CLs to
+# chrome-family-kids-reviews@google.com rather than to specific individuals.
+#
+# These CLs will be automatically reassigned to a reviewer within
+# about 5 minutes. This approach helps our team to load-balance incoming
+# reviews. Googlers can read more about this at go/gwsq-gerrit.
+
+# ChromeOS
+agawronska@chromium.org
+danan@chromium.org
+llin@chromium.org
+
+# Chrome Browser
+fernandex@chromium.org
+ljjlee@google.com
+tju@google.com
+
+# Previous owner, feel free to add for any questions or historical context:
+# treib@chromium.org
diff --git a/components/supervised_user/README.md b/components/supervised_user/README.md
new file mode 100644
index 0000000..1fe7bcb8
--- /dev/null
+++ b/components/supervised_user/README.md
@@ -0,0 +1,10 @@
+Supervised User is a multiprocess [layered
+component](https://sites.google.com/a/chromium.org/dev/developers/design-documents/layered-components-design) that supports cross-platform access to services and
+features that target supervised users in Chrome, including metrics
+collection, application of parental controls, and the enforcement of browser
+protections.
+
+It has the following structure:
+- [`core/`](https://source.chromium.org/chromium/chromium/src/+/main:components/supervised_user/core): Code shared by `content/` and `ios/`.
+  - [`browser/`](https://source.chromium.org/chromium/chromium/src/+/main:components/supervised_user/core/browser): Browser process code.
+  - [`common/`](https://source.chromium.org/chromium/chromium/src/+/main:components/supervised_user/core/common): Code shared by the browser and the renderer.
diff --git a/components/supervised_user/buildflags.gni b/components/supervised_user/buildflags.gni
new file mode 100644
index 0000000..d672775
--- /dev/null
+++ b/components/supervised_user/buildflags.gni
@@ -0,0 +1,9 @@
+# Copyright 2022 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+declare_args() {
+  # Enables supervision for Family Link users.
+  # Supervision is only supported on Chrome OS and Android.
+  enable_supervised_users = is_chromeos || is_android
+}
diff --git a/components/supervised_user/core/browser/BUILD.gn b/components/supervised_user/core/browser/BUILD.gn
new file mode 100644
index 0000000..1fbd454
--- /dev/null
+++ b/components/supervised_user/core/browser/BUILD.gn
@@ -0,0 +1,36 @@
+# Copyright 2022 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+static_library("browser") {
+  sources = [
+    "supervised_user_error_page.cc",
+    "supervised_user_error_page.h",
+  ]
+
+  deps = [
+    "//base",
+    "//components/resources:components_resources",
+    "//components/signin/public/base",
+    "//components/strings:components_strings_grit",
+    "//components/supervised_user/core/common",
+    "//ui/base",
+    "//url",
+  ]
+}
+
+source_set("unit_tests") {
+  testonly = true
+  sources = [ "supervised_user_error_page_unittest.cc" ]
+  deps = [
+    ":browser",
+    "//base",
+    "//base/test:test_support",
+    "//components/resources:components_resources",
+    "//components/strings:components_strings_grit",
+    "//components/supervised_user/core/common",
+    "//testing/gmock",
+    "//testing/gtest",
+    "//ui/base",
+  ]
+}
diff --git a/chrome/browser/supervised_user/supervised_user_error_page/resources/OWNERS b/components/supervised_user/core/browser/resources/OWNERS
similarity index 100%
rename from chrome/browser/supervised_user/supervised_user_error_page/resources/OWNERS
rename to components/supervised_user/core/browser/resources/OWNERS
diff --git a/chrome/browser/supervised_user/supervised_user_error_page/resources/default_100_percent/logo_avatar_circle_blue_color.png b/components/supervised_user/core/browser/resources/default_100_percent/logo_avatar_circle_blue_color.png
similarity index 100%
rename from chrome/browser/supervised_user/supervised_user_error_page/resources/default_100_percent/logo_avatar_circle_blue_color.png
rename to components/supervised_user/core/browser/resources/default_100_percent/logo_avatar_circle_blue_color.png
Binary files differ
diff --git a/chrome/browser/supervised_user/supervised_user_error_page/resources/default_200_percent/logo_avatar_circle_blue_color.png b/components/supervised_user/core/browser/resources/default_200_percent/logo_avatar_circle_blue_color.png
similarity index 100%
rename from chrome/browser/supervised_user/supervised_user_error_page/resources/default_200_percent/logo_avatar_circle_blue_color.png
rename to components/supervised_user/core/browser/resources/default_200_percent/logo_avatar_circle_blue_color.png
Binary files differ
diff --git a/chrome/browser/supervised_user/supervised_user_error_page/resources/error_page_illustration_dark_theme.svg b/components/supervised_user/core/browser/resources/error_page_illustration_dark_theme.svg
similarity index 100%
rename from chrome/browser/supervised_user/supervised_user_error_page/resources/error_page_illustration_dark_theme.svg
rename to components/supervised_user/core/browser/resources/error_page_illustration_dark_theme.svg
diff --git a/chrome/browser/supervised_user/supervised_user_error_page/resources/error_page_illustration_light_theme.svg b/components/supervised_user/core/browser/resources/error_page_illustration_light_theme.svg
similarity index 100%
rename from chrome/browser/supervised_user/supervised_user_error_page/resources/error_page_illustration_light_theme.svg
rename to components/supervised_user/core/browser/resources/error_page_illustration_light_theme.svg
diff --git a/chrome/browser/supervised_user/supervised_user_error_page/resources/supervised_user_block_interstitial.css b/components/supervised_user/core/browser/resources/supervised_user_block_interstitial.css
similarity index 97%
rename from chrome/browser/supervised_user/supervised_user_error_page/resources/supervised_user_block_interstitial.css
rename to components/supervised_user/core/browser/resources/supervised_user_block_interstitial.css
index ae76a42..4c308b9a 100644
--- a/chrome/browser/supervised_user/supervised_user_error_page/resources/supervised_user_block_interstitial.css
+++ b/components/supervised_user/core/browser/resources/supervised_user_block_interstitial.css
@@ -106,7 +106,7 @@
 }
 
 #family-link-kite {
-  content: -webkit-image-set(url(../../resources/supervised_user_icon.png) 1x);
+  content: -webkit-image-set(url(../resources/supervised_user_icon.png) 1x);
   height: 72px;
   width: 72px;
 }
diff --git a/chrome/browser/supervised_user/supervised_user_error_page/resources/supervised_user_block_interstitial.html b/components/supervised_user/core/browser/resources/supervised_user_block_interstitial.html
similarity index 100%
rename from chrome/browser/supervised_user/supervised_user_error_page/resources/supervised_user_block_interstitial.html
rename to components/supervised_user/core/browser/resources/supervised_user_block_interstitial.html
diff --git a/chrome/browser/supervised_user/supervised_user_error_page/resources/supervised_user_block_interstitial.js b/components/supervised_user/core/browser/resources/supervised_user_block_interstitial.js
similarity index 98%
rename from chrome/browser/supervised_user/supervised_user_error_page/resources/supervised_user_block_interstitial.js
rename to components/supervised_user/core/browser/resources/supervised_user_block_interstitial.js
index 64bcdf9..13b92ee 100644
--- a/chrome/browser/supervised_user/supervised_user_error_page/resources/supervised_user_block_interstitial.js
+++ b/components/supervised_user/core/browser/resources/supervised_user_block_interstitial.js
@@ -124,7 +124,6 @@
  *     frame.
  */
 function setRequestStatus(isSuccessful, isMainFrame) {
-  console.log('setRequestStatus(' + isSuccessful + ')');
   requestCreated(isSuccessful, isMainFrame);
 }
 
diff --git a/chrome/browser/supervised_user/supervised_user_error_page/resources/supervised_user_block_interstitial_v2.css b/components/supervised_user/core/browser/resources/supervised_user_block_interstitial_v2.css
similarity index 100%
rename from chrome/browser/supervised_user/supervised_user_error_page/resources/supervised_user_block_interstitial_v2.css
rename to components/supervised_user/core/browser/resources/supervised_user_block_interstitial_v2.css
diff --git a/chrome/browser/supervised_user/supervised_user_error_page/resources/supervised_user_block_interstitial_v2.html b/components/supervised_user/core/browser/resources/supervised_user_block_interstitial_v2.html
similarity index 100%
rename from chrome/browser/supervised_user/supervised_user_error_page/resources/supervised_user_block_interstitial_v2.html
rename to components/supervised_user/core/browser/resources/supervised_user_block_interstitial_v2.html
diff --git a/chrome/browser/supervised_user/supervised_user_error_page/resources/supervised_user_block_interstitial_v2.js b/components/supervised_user/core/browser/resources/supervised_user_block_interstitial_v2.js
similarity index 98%
rename from chrome/browser/supervised_user/supervised_user_error_page/resources/supervised_user_block_interstitial_v2.js
rename to components/supervised_user/core/browser/resources/supervised_user_block_interstitial_v2.js
index 2d3cdfb..5d8c416 100644
--- a/chrome/browser/supervised_user/supervised_user_error_page/resources/supervised_user_block_interstitial_v2.js
+++ b/components/supervised_user/core/browser/resources/supervised_user_block_interstitial_v2.js
@@ -140,7 +140,6 @@
  *     frame.
  */
 function setRequestStatus(isSuccessful, isMainFrame) {
-  console.log('setRequestStatus(' + isSuccessful + ')');
   requestCreated(isSuccessful, isMainFrame);
 }
 
diff --git a/chrome/browser/supervised_user/resources/supervised_user_icon.png b/components/supervised_user/core/browser/resources/supervised_user_icon.png
similarity index 100%
rename from chrome/browser/supervised_user/resources/supervised_user_icon.png
rename to components/supervised_user/core/browser/resources/supervised_user_icon.png
Binary files differ
diff --git a/chrome/browser/supervised_user/supervised_user_error_page/resources/waiting_for_approval_dark_theme.svg b/components/supervised_user/core/browser/resources/waiting_for_approval_dark_theme.svg
similarity index 100%
rename from chrome/browser/supervised_user/supervised_user_error_page/resources/waiting_for_approval_dark_theme.svg
rename to components/supervised_user/core/browser/resources/waiting_for_approval_dark_theme.svg
diff --git a/chrome/browser/supervised_user/supervised_user_error_page/resources/waiting_for_approval_light_theme.svg b/components/supervised_user/core/browser/resources/waiting_for_approval_light_theme.svg
similarity index 100%
rename from chrome/browser/supervised_user/supervised_user_error_page/resources/waiting_for_approval_light_theme.svg
rename to components/supervised_user/core/browser/resources/waiting_for_approval_light_theme.svg
diff --git a/chrome/browser/supervised_user/supervised_user_error_page/supervised_user_error_page.cc b/components/supervised_user/core/browser/supervised_user_error_page.cc
similarity index 96%
rename from chrome/browser/supervised_user/supervised_user_error_page/supervised_user_error_page.cc
rename to components/supervised_user/core/browser/supervised_user_error_page.cc
index 68b6ab9..54482757 100644
--- a/chrome/browser/supervised_user/supervised_user_error_page/supervised_user_error_page.cc
+++ b/components/supervised_user/core/browser/supervised_user_error_page.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/supervised_user/supervised_user_error_page/supervised_user_error_page.h"
+#include "components/supervised_user/core/browser/supervised_user_error_page.h"
 
 #include <string>
 
@@ -11,10 +11,10 @@
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/values.h"
-#include "chrome/browser/supervised_user/supervised_user_features/supervised_user_features.h"
-#include "chrome/grit/browser_resources.h"
-#include "chrome/grit/generated_resources.h"
+#include "components/grit/components_resources.h"
 #include "components/signin/public/base/avatar_icon_util.h"
+#include "components/strings/grit/components_strings.h"
+#include "components/supervised_user/core/common/features.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/resource/resource_bundle.h"
 #include "ui/base/webui/jstemplate_builder.h"
diff --git a/chrome/browser/supervised_user/supervised_user_error_page/supervised_user_error_page.h b/components/supervised_user/core/browser/supervised_user_error_page.h
similarity index 78%
rename from chrome/browser/supervised_user/supervised_user_error_page/supervised_user_error_page.h
rename to components/supervised_user/core/browser/supervised_user_error_page.h
index d236aa1..88b8128 100644
--- a/chrome/browser/supervised_user/supervised_user_error_page/supervised_user_error_page.h
+++ b/components/supervised_user/core/browser/supervised_user_error_page.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_SUPERVISED_USER_SUPERVISED_USER_ERROR_PAGE_SUPERVISED_USER_ERROR_PAGE_H_
-#define CHROME_BROWSER_SUPERVISED_USER_SUPERVISED_USER_ERROR_PAGE_SUPERVISED_USER_ERROR_PAGE_H_
+#ifndef COMPONENTS_SUPERVISED_USER_CORE_BROWSER_SUPERVISED_USER_ERROR_PAGE_H_
+#define COMPONENTS_SUPERVISED_USER_CORE_BROWSER_SUPERVISED_USER_ERROR_PAGE_H_
 
 #include <string>
 
@@ -36,4 +36,4 @@
 
 }  //  namespace supervised_user_error_page
 
-#endif  // CHROME_BROWSER_SUPERVISED_USER_SUPERVISED_USER_ERROR_PAGE_SUPERVISED_USER_ERROR_PAGE_H_
+#endif  // COMPONENTS_SUPERVISED_USER_CORE_BROWSER_SUPERVISED_USER_ERROR_PAGE_H_
diff --git a/chrome/browser/supervised_user/supervised_user_error_page/supervised_user_error_page_unittest.cc b/components/supervised_user/core/browser/supervised_user_error_page_unittest.cc
similarity index 97%
rename from chrome/browser/supervised_user/supervised_user_error_page/supervised_user_error_page_unittest.cc
rename to components/supervised_user/core/browser/supervised_user_error_page_unittest.cc
index a534019..43655ed 100644
--- a/chrome/browser/supervised_user/supervised_user_error_page/supervised_user_error_page_unittest.cc
+++ b/components/supervised_user/core/browser/supervised_user_error_page_unittest.cc
@@ -2,13 +2,13 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/supervised_user/supervised_user_error_page/supervised_user_error_page.h"
+#include "components/supervised_user/core/browser/supervised_user_error_page.h"
 
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/scoped_feature_list.h"
-#include "chrome/browser/supervised_user/supervised_user_features/supervised_user_features.h"
-#include "chrome/grit/browser_resources.h"
-#include "chrome/grit/generated_resources.h"
+#include "components/grit/components_resources.h"
+#include "components/strings/grit/components_strings.h"
+#include "components/supervised_user/core/common/features.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest-param-test.h"
 #include "testing/gtest/include/gtest/gtest.h"
diff --git a/components/supervised_user/core/common/BUILD.gn b/components/supervised_user/core/common/BUILD.gn
new file mode 100644
index 0000000..eefeda45
--- /dev/null
+++ b/components/supervised_user/core/common/BUILD.gn
@@ -0,0 +1,22 @@
+# Copyright 2022 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+static_library("common") {
+  sources = [
+    "features.cc",
+    "features.h",
+  ]
+  deps = [ "//base" ]
+}
+
+source_set("unit_tests") {
+  testonly = true
+  sources = [ "features_unittest.cc" ]
+  deps = [
+    ":common",
+    "//base",
+    "//base/test:test_support",
+    "//testing/gtest",
+  ]
+}
diff --git a/chrome/browser/supervised_user/supervised_user_features/supervised_user_features.cc b/components/supervised_user/core/common/features.cc
similarity index 96%
rename from chrome/browser/supervised_user/supervised_user_features/supervised_user_features.cc
rename to components/supervised_user/core/common/features.cc
index 1c165a8..27b4247 100644
--- a/chrome/browser/supervised_user/supervised_user_features/supervised_user_features.cc
+++ b/components/supervised_user/core/common/features.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/supervised_user/supervised_user_features/supervised_user_features.h"
+#include "components/supervised_user/core/common/features.h"
 #include <string>
 
 #include "base/check.h"
diff --git a/chrome/browser/supervised_user/supervised_user_features/supervised_user_features.h b/components/supervised_user/core/common/features.h
similarity index 80%
rename from chrome/browser/supervised_user/supervised_user_features/supervised_user_features.h
rename to components/supervised_user/core/common/features.h
index f4ccdce..b3b11a1 100644
--- a/chrome/browser/supervised_user/supervised_user_features/supervised_user_features.h
+++ b/components/supervised_user/core/common/features.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_SUPERVISED_USER_SUPERVISED_USER_FEATURES_SUPERVISED_USER_FEATURES_H_
-#define CHROME_BROWSER_SUPERVISED_USER_SUPERVISED_USER_FEATURES_SUPERVISED_USER_FEATURES_H_
+#ifndef COMPONENTS_SUPERVISED_USER_CORE_COMMON_FEATURES_H_
+#define COMPONENTS_SUPERVISED_USER_CORE_COMMON_FEATURES_H_
 
 #include "base/feature_list.h"
 
@@ -37,4 +37,4 @@
 
 }  // namespace supervised_users
 
-#endif  // CHROME_BROWSER_SUPERVISED_USER_SUPERVISED_USER_FEATURES_SUPERVISED_USER_FEATURES_H_
+#endif  // COMPONENTS_SUPERVISED_USER_CORE_COMMON_FEATURES_H_
diff --git a/chrome/browser/supervised_user/supervised_user_features/supervised_user_features_unittest.cc b/components/supervised_user/core/common/features_unittest.cc
similarity index 82%
rename from chrome/browser/supervised_user/supervised_user_features/supervised_user_features_unittest.cc
rename to components/supervised_user/core/common/features_unittest.cc
index 699b2b6..3777b187 100644
--- a/chrome/browser/supervised_user/supervised_user_features/supervised_user_features_unittest.cc
+++ b/components/supervised_user/core/common/features_unittest.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/supervised_user/supervised_user_features/supervised_user_features.h"
+#include "components/supervised_user/core/common/features.h"
 
 #include "base/dcheck_is_on.h"
 #include "base/feature_list.h"
@@ -12,20 +12,19 @@
 namespace supervised_users {
 
 // Tests supervised user features configurations.
-class SupervisedUserFeaturesTest : public testing::Test {
+class FeaturesTest : public testing::Test {
  protected:
-  SupervisedUserFeaturesTest() = default;
-  SupervisedUserFeaturesTest(const SupervisedUserFeaturesTest&) = delete;
-  SupervisedUserFeaturesTest& operator=(const SupervisedUserFeaturesTest&) =
-      delete;
-  ~SupervisedUserFeaturesTest() override = default;
+  FeaturesTest() = default;
+  FeaturesTest(const FeaturesTest&) = delete;
+  FeaturesTest& operator=(const FeaturesTest&) = delete;
+  ~FeaturesTest() override = default;
 
   base::test::ScopedFeatureList scoped_feature_list_;
 };
 
 // Tests `kWebFilterInterstitialRefresh` and `kLocalWebApproval`features
 // configuration.
-using LocalWebApprovalsFeatureTest = SupervisedUserFeaturesTest;
+using LocalWebApprovalsFeatureTest = FeaturesTest;
 
 TEST_F(LocalWebApprovalsFeatureTest,
        InterstitialRefreshDisabledAndLocalApprovalsDisabled) {
diff --git a/chrome/app/supervised_user_error_page_strings.grdp b/components/supervised_user_strings.grdp
similarity index 100%
rename from chrome/app/supervised_user_error_page_strings.grdp
rename to components/supervised_user_strings.grdp
diff --git a/chrome/app/supervised_user_error_page_strings_grdp/IDS_BACK_BUTTON.png.sha1 b/components/supervised_user_strings_grdp/IDS_BACK_BUTTON.png.sha1
similarity index 100%
rename from chrome/app/supervised_user_error_page_strings_grdp/IDS_BACK_BUTTON.png.sha1
rename to components/supervised_user_strings_grdp/IDS_BACK_BUTTON.png.sha1
diff --git a/chrome/app/supervised_user_error_page_strings_grdp/IDS_BLOCK_INTERSTITIAL_ASK_IN_PERSON_BUTTON.png.sha1 b/components/supervised_user_strings_grdp/IDS_BLOCK_INTERSTITIAL_ASK_IN_PERSON_BUTTON.png.sha1
similarity index 100%
rename from chrome/app/supervised_user_error_page_strings_grdp/IDS_BLOCK_INTERSTITIAL_ASK_IN_PERSON_BUTTON.png.sha1
rename to components/supervised_user_strings_grdp/IDS_BLOCK_INTERSTITIAL_ASK_IN_PERSON_BUTTON.png.sha1
diff --git a/chrome/app/supervised_user_error_page_strings_grdp/IDS_BLOCK_INTERSTITIAL_ASK_IN_PERSON_INSTEAD_BUTTON.png.sha1 b/components/supervised_user_strings_grdp/IDS_BLOCK_INTERSTITIAL_ASK_IN_PERSON_INSTEAD_BUTTON.png.sha1
similarity index 100%
rename from chrome/app/supervised_user_error_page_strings_grdp/IDS_BLOCK_INTERSTITIAL_ASK_IN_PERSON_INSTEAD_BUTTON.png.sha1
rename to components/supervised_user_strings_grdp/IDS_BLOCK_INTERSTITIAL_ASK_IN_PERSON_INSTEAD_BUTTON.png.sha1
diff --git a/chrome/app/supervised_user_error_page_strings_grdp/IDS_BLOCK_INTERSTITIAL_HEADER_ACCESS_REQUESTS_DISABLED.png.sha1 b/components/supervised_user_strings_grdp/IDS_BLOCK_INTERSTITIAL_HEADER_ACCESS_REQUESTS_DISABLED.png.sha1
similarity index 100%
rename from chrome/app/supervised_user_error_page_strings_grdp/IDS_BLOCK_INTERSTITIAL_HEADER_ACCESS_REQUESTS_DISABLED.png.sha1
rename to components/supervised_user_strings_grdp/IDS_BLOCK_INTERSTITIAL_HEADER_ACCESS_REQUESTS_DISABLED.png.sha1
diff --git a/chrome/app/supervised_user_error_page_strings_grdp/IDS_BLOCK_INTERSTITIAL_HEADER_NOT_SIGNED_IN.png.sha1 b/components/supervised_user_strings_grdp/IDS_BLOCK_INTERSTITIAL_HEADER_NOT_SIGNED_IN.png.sha1
similarity index 100%
rename from chrome/app/supervised_user_error_page_strings_grdp/IDS_BLOCK_INTERSTITIAL_HEADER_NOT_SIGNED_IN.png.sha1
rename to components/supervised_user_strings_grdp/IDS_BLOCK_INTERSTITIAL_HEADER_NOT_SIGNED_IN.png.sha1
diff --git a/chrome/app/supervised_user_error_page_strings_grdp/IDS_BLOCK_INTERSTITIAL_HIDE_DETAILS.png.sha1 b/components/supervised_user_strings_grdp/IDS_BLOCK_INTERSTITIAL_HIDE_DETAILS.png.sha1
similarity index 100%
rename from chrome/app/supervised_user_error_page_strings_grdp/IDS_BLOCK_INTERSTITIAL_HIDE_DETAILS.png.sha1
rename to components/supervised_user_strings_grdp/IDS_BLOCK_INTERSTITIAL_HIDE_DETAILS.png.sha1
diff --git a/chrome/app/supervised_user_error_page_strings_grdp/IDS_BLOCK_INTERSTITIAL_REQUEST_ACCESS_BUTTON.png.sha1 b/components/supervised_user_strings_grdp/IDS_BLOCK_INTERSTITIAL_REQUEST_ACCESS_BUTTON.png.sha1
similarity index 100%
rename from chrome/app/supervised_user_error_page_strings_grdp/IDS_BLOCK_INTERSTITIAL_REQUEST_ACCESS_BUTTON.png.sha1
rename to components/supervised_user_strings_grdp/IDS_BLOCK_INTERSTITIAL_REQUEST_ACCESS_BUTTON.png.sha1
diff --git a/chrome/app/supervised_user_error_page_strings_grdp/IDS_BLOCK_INTERSTITIAL_SEND_FEEDBACK.png.sha1 b/components/supervised_user_strings_grdp/IDS_BLOCK_INTERSTITIAL_SEND_FEEDBACK.png.sha1
similarity index 100%
rename from chrome/app/supervised_user_error_page_strings_grdp/IDS_BLOCK_INTERSTITIAL_SEND_FEEDBACK.png.sha1
rename to components/supervised_user_strings_grdp/IDS_BLOCK_INTERSTITIAL_SEND_FEEDBACK.png.sha1
diff --git a/chrome/app/supervised_user_error_page_strings_grdp/IDS_BLOCK_INTERSTITIAL_SEND_MESSAGE_BUTTON.png.sha1 b/components/supervised_user_strings_grdp/IDS_BLOCK_INTERSTITIAL_SEND_MESSAGE_BUTTON.png.sha1
similarity index 100%
rename from chrome/app/supervised_user_error_page_strings_grdp/IDS_BLOCK_INTERSTITIAL_SEND_MESSAGE_BUTTON.png.sha1
rename to components/supervised_user_strings_grdp/IDS_BLOCK_INTERSTITIAL_SEND_MESSAGE_BUTTON.png.sha1
diff --git a/chrome/app/supervised_user_error_page_strings_grdp/IDS_BLOCK_INTERSTITIAL_SHOW_DETAILS.png.sha1 b/components/supervised_user_strings_grdp/IDS_BLOCK_INTERSTITIAL_SHOW_DETAILS.png.sha1
similarity index 100%
rename from chrome/app/supervised_user_error_page_strings_grdp/IDS_BLOCK_INTERSTITIAL_SHOW_DETAILS.png.sha1
rename to components/supervised_user_strings_grdp/IDS_BLOCK_INTERSTITIAL_SHOW_DETAILS.png.sha1
diff --git a/chrome/app/supervised_user_error_page_strings_grdp/IDS_BLOCK_INTERSTITIAL_TITLE.png.sha1 b/components/supervised_user_strings_grdp/IDS_BLOCK_INTERSTITIAL_TITLE.png.sha1
similarity index 100%
rename from chrome/app/supervised_user_error_page_strings_grdp/IDS_BLOCK_INTERSTITIAL_TITLE.png.sha1
rename to components/supervised_user_strings_grdp/IDS_BLOCK_INTERSTITIAL_TITLE.png.sha1
diff --git a/chrome/app/supervised_user_error_page_strings_grdp/IDS_CHILD_BLOCK_INTERSTITIAL_HEADER.png.sha1 b/components/supervised_user_strings_grdp/IDS_CHILD_BLOCK_INTERSTITIAL_HEADER.png.sha1
similarity index 100%
rename from chrome/app/supervised_user_error_page_strings_grdp/IDS_CHILD_BLOCK_INTERSTITIAL_HEADER.png.sha1
rename to components/supervised_user_strings_grdp/IDS_CHILD_BLOCK_INTERSTITIAL_HEADER.png.sha1
diff --git a/chrome/app/supervised_user_error_page_strings_grdp/IDS_CHILD_BLOCK_INTERSTITIAL_MESSAGE.png.sha1 b/components/supervised_user_strings_grdp/IDS_CHILD_BLOCK_INTERSTITIAL_MESSAGE.png.sha1
similarity index 100%
rename from chrome/app/supervised_user_error_page_strings_grdp/IDS_CHILD_BLOCK_INTERSTITIAL_MESSAGE.png.sha1
rename to components/supervised_user_strings_grdp/IDS_CHILD_BLOCK_INTERSTITIAL_MESSAGE.png.sha1
diff --git a/chrome/app/supervised_user_error_page_strings_grdp/IDS_CHILD_BLOCK_INTERSTITIAL_MESSAGE_V2.png.sha1 b/components/supervised_user_strings_grdp/IDS_CHILD_BLOCK_INTERSTITIAL_MESSAGE_V2.png.sha1
similarity index 100%
rename from chrome/app/supervised_user_error_page_strings_grdp/IDS_CHILD_BLOCK_INTERSTITIAL_MESSAGE_V2.png.sha1
rename to components/supervised_user_strings_grdp/IDS_CHILD_BLOCK_INTERSTITIAL_MESSAGE_V2.png.sha1
diff --git a/chrome/app/supervised_user_error_page_strings_grdp/IDS_CHILD_BLOCK_INTERSTITIAL_REQUEST_FAILED_MESSAGE_MULTI_PARENT.png.sha1 b/components/supervised_user_strings_grdp/IDS_CHILD_BLOCK_INTERSTITIAL_REQUEST_FAILED_MESSAGE_MULTI_PARENT.png.sha1
similarity index 100%
rename from chrome/app/supervised_user_error_page_strings_grdp/IDS_CHILD_BLOCK_INTERSTITIAL_REQUEST_FAILED_MESSAGE_MULTI_PARENT.png.sha1
rename to components/supervised_user_strings_grdp/IDS_CHILD_BLOCK_INTERSTITIAL_REQUEST_FAILED_MESSAGE_MULTI_PARENT.png.sha1
diff --git a/chrome/app/supervised_user_error_page_strings_grdp/IDS_CHILD_BLOCK_INTERSTITIAL_REQUEST_FAILED_MESSAGE_SINGLE_PARENT.png.sha1 b/components/supervised_user_strings_grdp/IDS_CHILD_BLOCK_INTERSTITIAL_REQUEST_FAILED_MESSAGE_SINGLE_PARENT.png.sha1
similarity index 100%
rename from chrome/app/supervised_user_error_page_strings_grdp/IDS_CHILD_BLOCK_INTERSTITIAL_REQUEST_FAILED_MESSAGE_SINGLE_PARENT.png.sha1
rename to components/supervised_user_strings_grdp/IDS_CHILD_BLOCK_INTERSTITIAL_REQUEST_FAILED_MESSAGE_SINGLE_PARENT.png.sha1
diff --git a/chrome/app/supervised_user_error_page_strings_grdp/IDS_CHILD_BLOCK_INTERSTITIAL_REQUEST_SENT_MESSAGE_MULTI_PARENT.png.sha1 b/components/supervised_user_strings_grdp/IDS_CHILD_BLOCK_INTERSTITIAL_REQUEST_SENT_MESSAGE_MULTI_PARENT.png.sha1
similarity index 100%
rename from chrome/app/supervised_user_error_page_strings_grdp/IDS_CHILD_BLOCK_INTERSTITIAL_REQUEST_SENT_MESSAGE_MULTI_PARENT.png.sha1
rename to components/supervised_user_strings_grdp/IDS_CHILD_BLOCK_INTERSTITIAL_REQUEST_SENT_MESSAGE_MULTI_PARENT.png.sha1
diff --git a/chrome/app/supervised_user_error_page_strings_grdp/IDS_CHILD_BLOCK_INTERSTITIAL_REQUEST_SENT_MESSAGE_SINGLE_PARENT.png.sha1 b/components/supervised_user_strings_grdp/IDS_CHILD_BLOCK_INTERSTITIAL_REQUEST_SENT_MESSAGE_SINGLE_PARENT.png.sha1
similarity index 100%
rename from chrome/app/supervised_user_error_page_strings_grdp/IDS_CHILD_BLOCK_INTERSTITIAL_REQUEST_SENT_MESSAGE_SINGLE_PARENT.png.sha1
rename to components/supervised_user_strings_grdp/IDS_CHILD_BLOCK_INTERSTITIAL_REQUEST_SENT_MESSAGE_SINGLE_PARENT.png.sha1
diff --git a/chrome/app/supervised_user_error_page_strings_grdp/IDS_CHILD_BLOCK_INTERSTITIAL_WAITING_APPROVAL_DESCRIPTION_MULTI_PARENT.png.sha1 b/components/supervised_user_strings_grdp/IDS_CHILD_BLOCK_INTERSTITIAL_WAITING_APPROVAL_DESCRIPTION_MULTI_PARENT.png.sha1
similarity index 100%
rename from chrome/app/supervised_user_error_page_strings_grdp/IDS_CHILD_BLOCK_INTERSTITIAL_WAITING_APPROVAL_DESCRIPTION_MULTI_PARENT.png.sha1
rename to components/supervised_user_strings_grdp/IDS_CHILD_BLOCK_INTERSTITIAL_WAITING_APPROVAL_DESCRIPTION_MULTI_PARENT.png.sha1
diff --git a/chrome/app/supervised_user_error_page_strings_grdp/IDS_CHILD_BLOCK_INTERSTITIAL_WAITING_APPROVAL_DESCRIPTION_SINGLE_PARENT.png.sha1 b/components/supervised_user_strings_grdp/IDS_CHILD_BLOCK_INTERSTITIAL_WAITING_APPROVAL_DESCRIPTION_SINGLE_PARENT.png.sha1
similarity index 100%
rename from chrome/app/supervised_user_error_page_strings_grdp/IDS_CHILD_BLOCK_INTERSTITIAL_WAITING_APPROVAL_DESCRIPTION_SINGLE_PARENT.png.sha1
rename to components/supervised_user_strings_grdp/IDS_CHILD_BLOCK_INTERSTITIAL_WAITING_APPROVAL_DESCRIPTION_SINGLE_PARENT.png.sha1
diff --git a/chrome/app/supervised_user_error_page_strings_grdp/IDS_CHILD_BLOCK_INTERSTITIAL_WAITING_APPROVAL_MESSAGE.png.sha1 b/components/supervised_user_strings_grdp/IDS_CHILD_BLOCK_INTERSTITIAL_WAITING_APPROVAL_MESSAGE.png.sha1
similarity index 100%
rename from chrome/app/supervised_user_error_page_strings_grdp/IDS_CHILD_BLOCK_INTERSTITIAL_WAITING_APPROVAL_MESSAGE.png.sha1
rename to components/supervised_user_strings_grdp/IDS_CHILD_BLOCK_INTERSTITIAL_WAITING_APPROVAL_MESSAGE.png.sha1
diff --git a/chrome/app/supervised_user_error_page_strings_grdp/IDS_CHILD_BLOCK_MESSAGE_DEFAULT_MULTI_PARENT.png.sha1 b/components/supervised_user_strings_grdp/IDS_CHILD_BLOCK_MESSAGE_DEFAULT_MULTI_PARENT.png.sha1
similarity index 100%
rename from chrome/app/supervised_user_error_page_strings_grdp/IDS_CHILD_BLOCK_MESSAGE_DEFAULT_MULTI_PARENT.png.sha1
rename to components/supervised_user_strings_grdp/IDS_CHILD_BLOCK_MESSAGE_DEFAULT_MULTI_PARENT.png.sha1
diff --git a/chrome/app/supervised_user_error_page_strings_grdp/IDS_CHILD_BLOCK_MESSAGE_DEFAULT_SINGLE_PARENT.png.sha1 b/components/supervised_user_strings_grdp/IDS_CHILD_BLOCK_MESSAGE_DEFAULT_SINGLE_PARENT.png.sha1
similarity index 100%
rename from chrome/app/supervised_user_error_page_strings_grdp/IDS_CHILD_BLOCK_MESSAGE_DEFAULT_SINGLE_PARENT.png.sha1
rename to components/supervised_user_strings_grdp/IDS_CHILD_BLOCK_MESSAGE_DEFAULT_SINGLE_PARENT.png.sha1
diff --git a/chrome/app/supervised_user_error_page_strings_grdp/IDS_CHILD_BLOCK_MESSAGE_MANUAL_MULTI_PARENT.png.sha1 b/components/supervised_user_strings_grdp/IDS_CHILD_BLOCK_MESSAGE_MANUAL_MULTI_PARENT.png.sha1
similarity index 100%
rename from chrome/app/supervised_user_error_page_strings_grdp/IDS_CHILD_BLOCK_MESSAGE_MANUAL_MULTI_PARENT.png.sha1
rename to components/supervised_user_strings_grdp/IDS_CHILD_BLOCK_MESSAGE_MANUAL_MULTI_PARENT.png.sha1
diff --git a/chrome/app/supervised_user_error_page_strings_grdp/IDS_CHILD_BLOCK_MESSAGE_MANUAL_SINGLE_PARENT.png.sha1 b/components/supervised_user_strings_grdp/IDS_CHILD_BLOCK_MESSAGE_MANUAL_SINGLE_PARENT.png.sha1
similarity index 100%
rename from chrome/app/supervised_user_error_page_strings_grdp/IDS_CHILD_BLOCK_MESSAGE_MANUAL_SINGLE_PARENT.png.sha1
rename to components/supervised_user_strings_grdp/IDS_CHILD_BLOCK_MESSAGE_MANUAL_SINGLE_PARENT.png.sha1
diff --git a/chrome/app/supervised_user_error_page_strings_grdp/IDS_GENERIC_SITE_BLOCK_HEADER.png.sha1 b/components/supervised_user_strings_grdp/IDS_GENERIC_SITE_BLOCK_HEADER.png.sha1
similarity index 100%
rename from chrome/app/supervised_user_error_page_strings_grdp/IDS_GENERIC_SITE_BLOCK_HEADER.png.sha1
rename to components/supervised_user_strings_grdp/IDS_GENERIC_SITE_BLOCK_HEADER.png.sha1
diff --git a/chrome/app/supervised_user_error_page_strings_grdp/IDS_REQUEST_SENT_OK.png.sha1 b/components/supervised_user_strings_grdp/IDS_REQUEST_SENT_OK.png.sha1
similarity index 100%
rename from chrome/app/supervised_user_error_page_strings_grdp/IDS_REQUEST_SENT_OK.png.sha1
rename to components/supervised_user_strings_grdp/IDS_REQUEST_SENT_OK.png.sha1
diff --git a/chrome/app/supervised_user_error_page_strings_grdp/IDS_SUPERVISED_USER_BLOCK_HEADER.png.sha1 b/components/supervised_user_strings_grdp/IDS_SUPERVISED_USER_BLOCK_HEADER.png.sha1
similarity index 100%
rename from chrome/app/supervised_user_error_page_strings_grdp/IDS_SUPERVISED_USER_BLOCK_HEADER.png.sha1
rename to components/supervised_user_strings_grdp/IDS_SUPERVISED_USER_BLOCK_HEADER.png.sha1
diff --git a/chrome/app/supervised_user_error_page_strings_grdp/IDS_SUPERVISED_USER_BLOCK_MESSAGE_SAFE_SITES.png.sha1 b/components/supervised_user_strings_grdp/IDS_SUPERVISED_USER_BLOCK_MESSAGE_SAFE_SITES.png.sha1
similarity index 100%
rename from chrome/app/supervised_user_error_page_strings_grdp/IDS_SUPERVISED_USER_BLOCK_MESSAGE_SAFE_SITES.png.sha1
rename to components/supervised_user_strings_grdp/IDS_SUPERVISED_USER_BLOCK_MESSAGE_SAFE_SITES.png.sha1
diff --git a/chrome/app/supervised_user_error_page_strings_grdp/IDS_SUPERVISED_USER_NOT_SIGNED_IN.png.sha1 b/components/supervised_user_strings_grdp/IDS_SUPERVISED_USER_NOT_SIGNED_IN.png.sha1
similarity index 100%
rename from chrome/app/supervised_user_error_page_strings_grdp/IDS_SUPERVISED_USER_NOT_SIGNED_IN.png.sha1
rename to components/supervised_user_strings_grdp/IDS_SUPERVISED_USER_NOT_SIGNED_IN.png.sha1
diff --git a/chrome/app/supervised_user_error_page_strings_grdp/OWNERS b/components/supervised_user_strings_grdp/OWNERS
similarity index 100%
rename from chrome/app/supervised_user_error_page_strings_grdp/OWNERS
rename to components/supervised_user_strings_grdp/OWNERS
diff --git a/chrome/app/supervised_user_error_page_strings_grdp/README.md b/components/supervised_user_strings_grdp/README.md
similarity index 100%
rename from chrome/app/supervised_user_error_page_strings_grdp/README.md
rename to components/supervised_user_strings_grdp/README.md
diff --git a/components/variations/service/variations_field_trial_creator.cc b/components/variations/service/variations_field_trial_creator.cc
index 758daae0..63b4f52 100644
--- a/components/variations/service/variations_field_trial_creator.cc
+++ b/components/variations/service/variations_field_trial_creator.cc
@@ -324,7 +324,7 @@
   if (base::FeatureList::IsEnabled(kForceFieldTrialSetupCrashForTesting)) {
     // We log a recognizable token for the crash condition, to allow tests to
     // recognize the crash location in the test output. See:
-    // TEST_P(FieldTrialTest, ExtendedSafeModeEndToEnd)
+    // VariationsSafeModeEndToEndBrowserTest.ExtendedSafeSeedEndToEnd
     LOG(ERROR) << "crash_for_testing";
     abort();
   }
diff --git a/components/variations/variations_crash_keys.cc b/components/variations/variations_crash_keys.cc
index d6e4c00..1b6dbdd 100644
--- a/components/variations/variations_crash_keys.cc
+++ b/components/variations/variations_crash_keys.cc
@@ -7,6 +7,7 @@
 #include <string>
 
 #include "base/debug/leak_annotations.h"
+#include "base/metrics/histogram_macros.h"
 #include "base/sequence_checker.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_piece.h"
@@ -28,14 +29,19 @@
 
 namespace {
 
-// Size of the "num-experiments" crash key in bytes. 4096 bytes should be able
-// to hold about 227 entries, given each entry is 18 bytes long (due to being
+// Size of the "num-experiments" crash key in bytes. 1024*6 bytes should be able
+// to hold about 341 entries, given each entry is 18 bytes long (due to being
 // of the form "8e7abfb0-c16397b7,").
 #if BUILDFLAG(LARGE_VARIATION_KEY_SIZE)
-constexpr size_t kVariationsKeySize = 8192;
+constexpr size_t kVariationsKeySize = 1024 * 8;
+constexpr char kVariationKeySizeHistogram[] =
+    "Variations.Limits.VariationKeySize.Large";
 #else
-constexpr size_t kVariationsKeySize = 6144;
+constexpr size_t kVariationsKeySize = 1024 * 6;
+constexpr char kVariationKeySizeHistogram[] =
+    "Variations.Limits.VariationKeySize.Default";
 #endif
+constexpr size_t kVariationsKeySizeNumBuckets = 16;
 
 // Crash key reporting the number of experiments. 8 is the size of the crash key
 // in bytes, which is used to hold an int as a string.
@@ -180,13 +186,14 @@
   ExperimentListInfo info = GetExperimentListInfo();
   g_num_variations_crash_key.Set(base::NumberToString(info.num_experiments));
 
+  const size_t count_of_kbs = info.experiment_list.size() / 1024;
+  UMA_HISTOGRAM_EXACT_LINEAR(kVariationKeySizeHistogram, count_of_kbs,
+                             kVariationsKeySizeNumBuckets);
   if (info.experiment_list.size() > kVariationsKeySize) {
     // If size exceeded, truncate to the last full entry.
     int comma_index =
         info.experiment_list.substr(0, kVariationsKeySize).rfind(',');
     info.experiment_list.resize(comma_index + 1);
-    // NOTREACHED() will let us know of the problem and adjust the limit.
-    NOTREACHED();
   }
 
   g_variations_crash_key.Set(info.experiment_list);
diff --git a/components/viz/test/test_in_process_context_provider.cc b/components/viz/test/test_in_process_context_provider.cc
index b54d3580..f0825d1 100644
--- a/components/viz/test/test_in_process_context_provider.cc
+++ b/components/viz/test/test_in_process_context_provider.cc
@@ -9,45 +9,47 @@
 #include <memory>
 #include <utility>
 
-#include "base/lazy_instance.h"
 #include "base/task/single_thread_task_runner.h"
 #include "base/types/optional_util.h"
 #include "components/viz/common/gpu/context_cache_controller.h"
-#include "components/viz/common/resources/platform_color.h"
-#include "components/viz/service/display/display_compositor_memory_and_task_controller.h"
 #include "components/viz/service/gl/gpu_service_impl.h"
 #include "components/viz/test/test_gpu_service_holder.h"
-#include "gpu/GLES2/gl2extchromium.h"
 #include "gpu/command_buffer/client/gles2_implementation.h"
 #include "gpu/command_buffer/client/raster_implementation_gles.h"
 #include "gpu/command_buffer/client/shared_memory_limits.h"
 #include "gpu/command_buffer/common/context_creation_attribs.h"
-#include "gpu/command_buffer/service/gpu_task_scheduler_helper.h"
 #include "gpu/config/skia_limits.h"
 #include "gpu/ipc/gl_in_process_context.h"
 #include "gpu/ipc/raster_in_process_context.h"
-#include "gpu/ipc/test_gpu_thread_holder.h"
 #include "gpu/skia_bindings/grcontext_for_gles2_interface.h"
-#include "third_party/khronos/GLES2/gl2.h"
-#include "third_party/khronos/GLES2/gl2ext.h"
 #include "third_party/skia/include/gpu/GrDirectContext.h"
-#include "third_party/skia/include/gpu/gl/GrGLInterface.h"
-#include "ui/gfx/native_widget_types.h"
 
 namespace viz {
 
 namespace {
 
-std::unique_ptr<gpu::GLInProcessContext> CreateGLInProcessContext() {
+gpu::ContextCreationAttribs GetAttributes(bool enable_gles2,
+                                          bool enable_raster,
+                                          bool enable_oopr) {
   gpu::ContextCreationAttribs attribs;
-  attribs.alpha_size = -1;
-  attribs.depth_size = 24;
+  attribs.alpha_size = 8;
+  attribs.blue_size = 8;
+  attribs.green_size = 8;
+  attribs.red_size = 8;
+  attribs.depth_size = 0;
   attribs.stencil_size = 8;
   attribs.samples = 0;
   attribs.sample_buffers = 0;
-  attribs.fail_if_major_perf_caveat = false;
   attribs.bind_generates_resource = false;
-  attribs.enable_oop_rasterization = false;
+  attribs.enable_gles2_interface = enable_gles2;
+  attribs.enable_raster_interface = enable_raster;
+  attribs.enable_oop_rasterization = enable_oopr;
+  return attribs;
+}
+
+std::unique_ptr<gpu::GLInProcessContext> CreateGLInProcessContext() {
+  gpu::ContextCreationAttribs attribs = GetAttributes(
+      /*enable_gles2=*/true, /*enable_raster=*/false, /*enable_oopr=*/false);
 
   auto context = std::make_unique<gpu::GLInProcessContext>();
   auto result =
@@ -70,11 +72,18 @@
     gpu::raster::GrShaderCache* gr_shader_cache,
     gpu::GpuProcessActivityFlags* activity_flags)
     : type_(type), activity_flags_(activity_flags) {
-  if (support_locking)
+  DCHECK(main_thread_checker_.CalledOnValidThread());
+  context_thread_checker_.DetachFromThread();
+
+  if (support_locking) {
     context_lock_.emplace();
+  }
 }
 
-TestInProcessContextProvider::~TestInProcessContextProvider() = default;
+TestInProcessContextProvider::~TestInProcessContextProvider() {
+  DCHECK(main_thread_checker_.CalledOnValidThread() ||
+         context_thread_checker_.CalledOnValidThread());
+}
 
 void TestInProcessContextProvider::AddRef() const {
   base::RefCountedThreadSafe<TestInProcessContextProvider>::AddRef();
@@ -85,6 +94,8 @@
 }
 
 gpu::ContextResult TestInProcessContextProvider::BindToCurrentSequence() {
+  DCHECK(context_thread_checker_.CalledOnValidThread());
+
   auto* holder = TestGpuServiceHolder::GetInstance();
 
   if (type_ == TestContextType::kGLES2) {
@@ -94,11 +105,8 @@
   } else {
     bool is_gpu_raster = type_ == TestContextType::kGpuRaster;
 
-    gpu::ContextCreationAttribs attribs;
-    attribs.bind_generates_resource = false;
-    attribs.enable_oop_rasterization = is_gpu_raster;
-    attribs.enable_raster_interface = true;
-    attribs.enable_gles2_interface = false;
+    gpu::ContextCreationAttribs attribs = GetAttributes(
+        /*enable_gles2=*/false, /*enable_raster=*/true, is_gpu_raster);
 
     raster_context_ = std::make_unique<gpu::RasterInProcessContext>();
     auto result = raster_context_->Initialize(
@@ -122,11 +130,13 @@
 }
 
 gpu::gles2::GLES2Interface* TestInProcessContextProvider::ContextGL() {
+  CheckValidThreadOrLockAcquired();
   DCHECK_EQ(type_, TestContextType::kGLES2);
   return gles2_context_->GetImplementation();
 }
 
 gpu::raster::RasterInterface* TestInProcessContextProvider::RasterInterface() {
+  CheckValidThreadOrLockAcquired();
   DCHECK_NE(type_, TestContextType::kGLES2);
   return raster_context_->GetImplementation();
 }
@@ -140,6 +150,7 @@
 }
 
 class GrDirectContext* TestInProcessContextProvider::GrContext() {
+  CheckValidThreadOrLockAcquired();
   if (gr_context_)
     return gr_context_->get();
 
@@ -168,6 +179,7 @@
 }
 
 ContextCacheController* TestInProcessContextProvider::CacheController() {
+  CheckValidThreadOrLockAcquired();
   return cache_controller_.get();
 }
 
@@ -177,14 +189,30 @@
 
 const gpu::Capabilities& TestInProcessContextProvider::ContextCapabilities()
     const {
+  CheckValidThreadOrLockAcquired();
   return caps_;
 }
 
 const gpu::GpuFeatureInfo& TestInProcessContextProvider::GetGpuFeatureInfo()
     const {
+  CheckValidThreadOrLockAcquired();
   return gpu_feature_info_;
 }
 
+void TestInProcessContextProvider::AddObserver(ContextLostObserver* obs) {
+  observers_.AddObserver(obs);
+}
+
+void TestInProcessContextProvider::RemoveObserver(ContextLostObserver* obs) {
+  observers_.RemoveObserver(obs);
+}
+
+void TestInProcessContextProvider::SendOnContextLost() {
+  for (auto& observer : observers_) {
+    observer.OnContextLost();
+  }
+}
+
 void TestInProcessContextProvider::ExecuteOnGpuThread(base::OnceClosure task) {
   DCHECK(raster_context_);
   raster_context_->GetCommandBufferForTest()
@@ -192,4 +220,14 @@
       ->ScheduleOutOfOrderTask(std::move(task));
 }
 
+void TestInProcessContextProvider::CheckValidThreadOrLockAcquired() const {
+#if DCHECK_IS_ON()
+  if (context_lock_) {
+    context_lock_->AssertAcquired();
+  } else {
+    DCHECK(context_thread_checker_.CalledOnValidThread());
+  }
+#endif
+}
+
 }  // namespace viz
diff --git a/components/viz/test/test_in_process_context_provider.h b/components/viz/test/test_in_process_context_provider.h
index afc94df..ebf8d64 100644
--- a/components/viz/test/test_in_process_context_provider.h
+++ b/components/viz/test/test_in_process_context_provider.h
@@ -10,11 +10,11 @@
 #include <memory>
 
 #include "base/memory/raw_ptr.h"
+#include "base/observer_list.h"
 #include "base/synchronization/lock.h"
-#include "base/task/single_thread_task_runner.h"
+#include "base/threading/thread_checker.h"
 #include "components/viz/common/gpu/context_provider.h"
 #include "components/viz/common/gpu/raster_context_provider.h"
-#include "components/viz/test/test_gpu_memory_buffer_manager.h"
 #include "gpu/config/gpu_feature_info.h"
 
 class GrDirectContext;
@@ -67,8 +67,11 @@
   base::Lock* GetLock() override;
   const gpu::Capabilities& ContextCapabilities() const override;
   const gpu::GpuFeatureInfo& GetGpuFeatureInfo() const override;
-  void AddObserver(ContextLostObserver* obs) override {}
-  void RemoveObserver(ContextLostObserver* obs) override {}
+  void AddObserver(ContextLostObserver* obs) override;
+  void RemoveObserver(ContextLostObserver* obs) override;
+
+  // Calls OnContextLost() on all observers. This doesn't modify the context.
+  void SendOnContextLost();
 
   void ExecuteOnGpuThread(base::OnceClosure task);
 
@@ -77,10 +80,15 @@
   ~TestInProcessContextProvider() override;
 
  private:
+  void CheckValidThreadOrLockAcquired() const;
+
   const TestContextType type_;
   raw_ptr<gpu::raster::GrShaderCache> gr_shader_cache_ = nullptr;
   raw_ptr<gpu::GpuProcessActivityFlags> activity_flags_ = nullptr;
 
+  base::ThreadChecker main_thread_checker_;
+  base::ThreadChecker context_thread_checker_;
+
   gpu::Capabilities caps_;
 
   // Used for GLES2 contexts only.
@@ -93,6 +101,8 @@
   std::unique_ptr<ContextCacheController> cache_controller_;
   absl::optional<base::Lock> context_lock_;
   gpu::GpuFeatureInfo gpu_feature_info_;
+
+  base::ObserverList<ContextLostObserver>::Unchecked observers_;
 };
 
 }  // namespace viz
diff --git a/components/webapps/browser/install_result_code.cc b/components/webapps/browser/install_result_code.cc
index eed3793b..9fce22a 100644
--- a/components/webapps/browser/install_result_code.cc
+++ b/components/webapps/browser/install_result_code.cc
@@ -79,6 +79,8 @@
       return os << "kAppNotInRegistrarAfterCommit";
     case InstallResultCode::kHaltedBySyncUninstall:
       return os << "kHaltedBySyncUninstall";
+    case InstallResultCode::kInstallURLInvalid:
+      return os << "kInstallURLInvalid";
   }
 }
 
diff --git a/components/webapps/browser/install_result_code.h b/components/webapps/browser/install_result_code.h
index b791da5..4c1f38db 100644
--- a/components/webapps/browser/install_result_code.h
+++ b/components/webapps/browser/install_result_code.h
@@ -83,7 +83,10 @@
   // The installation stopped due to an uninstall from sync being scheduled.
   kHaltedBySyncUninstall = 28,
 
-  kMaxValue = kHaltedBySyncUninstall,
+  // Invalid install URL for externally managed apps.
+  kInstallURLInvalid = 29,
+
+  kMaxValue = kInstallURLInvalid,
 };
 
 // Checks if InstallResultCode is not a failure.
diff --git a/content/app_shim_remote_cocoa/render_widget_host_view_cocoa.mm b/content/app_shim_remote_cocoa/render_widget_host_view_cocoa.mm
index d89c11d6..fd4c01dd 100644
--- a/content/app_shim_remote_cocoa/render_widget_host_view_cocoa.mm
+++ b/content/app_shim_remote_cocoa/render_widget_host_view_cocoa.mm
@@ -26,6 +26,7 @@
 #import "content/browser/renderer_host/render_widget_host_view_mac_editcommand_helper.h"
 #import "content/public/browser/render_widget_host_view_mac_delegate.h"
 #include "content/public/common/content_features.h"
+#include "skia/ext/skia_utils_mac.h"
 #include "third_party/blink/public/mojom/input/input_handler.mojom.h"
 #include "third_party/blink/public/platform/web_text_input_type.h"
 #include "ui/accessibility/platform/ax_platform_node.h"
@@ -75,7 +76,7 @@
 // functions.
 class DummyHostHelper : public RenderWidgetHostNSViewHostHelper {
  public:
-  explicit DummyHostHelper() {}
+  explicit DummyHostHelper() = default;
 
   DummyHostHelper(const DummyHostHelper&) = delete;
   DummyHostHelper& operator=(const DummyHostHelper&) = delete;
@@ -117,37 +118,32 @@
   return content::GetSystemHotkeyMap()->IsEventReserved(event);
 }
 
-// TODO(suzhe): Upstream this function.
-SkColor SkColorFromNSColor(NSColor* color) {
-  CGFloat r, g, b, a;
-  [color getRed:&r green:&g blue:&b alpha:&a];
-
-  return base::clamp(static_cast<int>(lroundf(255.0f * a)), 0, 255) << 24 |
-         base::clamp(static_cast<int>(lroundf(255.0f * r)), 0, 255) << 16 |
-         base::clamp(static_cast<int>(lroundf(255.0f * g)), 0, 255) << 8 |
-         base::clamp(static_cast<int>(lroundf(255.0f * b)), 0, 255);
-}
-
-// Extract underline information from an attributed string. Mostly copied from
-// third_party/WebKit/Source/WebKit/mac/WebView/WebHTMLView.mm
+// Extract underline information from an attributed string. Inspired by
+// `extractUnderlines` in
+// https://github.com/WebKit/WebKit/blob/main/Source/WebKitLegacy/mac/WebView/WebHTMLView.mm
 void ExtractUnderlines(NSAttributedString* string,
                        std::vector<ui::ImeTextSpan>* ime_text_spans) {
-  int length = [[string string] length];
-  int i = 0;
+  NSUInteger length = string.length;
+  NSUInteger i = 0;
   while (i < length) {
     NSRange range;
     NSDictionary* attrs = [string attributesAtIndex:i
                               longestEffectiveRange:&range
                                             inRange:NSMakeRange(i, length - i)];
-    if (NSNumber* style = attrs[NSUnderlineStyleAttributeName]) {
+    if (NSNumber* style_attr = attrs[NSUnderlineStyleAttributeName]) {
       SkColor color = SK_ColorBLACK;
-      if (NSColor* colorAttr = attrs[NSUnderlineColorAttributeName]) {
-        color = SkColorFromNSColor(
-            [colorAttr colorUsingColorSpaceName:NSDeviceRGBColorSpace]);
+      if (NSColor* color_attr = attrs[NSUnderlineColorAttributeName]) {
+        color = skia::NSDeviceColorToSkColor(
+            [color_attr colorUsingColorSpace:NSColorSpace.deviceRGBColorSpace]);
       }
+
+      // `NSUnderlineStyle` is the combination of a type enum with a pattern
+      // style in the higher bits. Fold anything more complicated than a single
+      // unstyled underline down to "thick" rather than "thin".
       ui::ImeTextSpan::Thickness thickness =
-          [style intValue] > 1 ? ui::ImeTextSpan::Thickness::kThick
-                               : ui::ImeTextSpan::Thickness::kThin;
+          style_attr.intValue > NSUnderlineStyleSingle
+              ? ui::ImeTextSpan::Thickness::kThick
+              : ui::ImeTextSpan::Thickness::kThin;
       ui::ImeTextSpan ui_ime_text_span = ui::ImeTextSpan(
           ui::ImeTextSpan::Type::kComposition, range.location,
           NSMaxRange(range), thickness, ui::ImeTextSpan::UnderlineStyle::kSolid,
@@ -155,7 +151,7 @@
       ui_ime_text_span.underline_color = color;
       ime_text_spans->push_back(ui_ime_text_span);
     }
-    i = range.location + range.length;
+    i = NSMaxRange(range);
   }
 }
 
@@ -2056,10 +2052,10 @@
     ExtractUnderlines(string, &_ime_text_spans);
   } else {
     // Use a thin black underline by default.
-    _ime_text_spans.push_back(ui::ImeTextSpan(
-        ui::ImeTextSpan::Type::kComposition, 0, length,
-        ui::ImeTextSpan::Thickness::kThin,
-        ui::ImeTextSpan::UnderlineStyle::kSolid, SK_ColorTRANSPARENT));
+    _ime_text_spans.emplace_back(ui::ImeTextSpan::Type::kComposition, 0, length,
+                                 ui::ImeTextSpan::Thickness::kThin,
+                                 ui::ImeTextSpan::UnderlineStyle::kSolid,
+                                 SK_ColorTRANSPARENT);
   }
 
   // If we are handling a key down event and the reconversion is not triggered,
diff --git a/content/browser/attribution_reporting/attribution_debug_report.cc b/content/browser/attribution_reporting/attribution_debug_report.cc
index 27fbbb63..b7e7e9a 100644
--- a/content/browser/attribution_reporting/attribution_debug_report.cc
+++ b/content/browser/attribution_reporting/attribution_debug_report.cc
@@ -29,6 +29,8 @@
 using EventLevelResult = ::content::AttributionTrigger::EventLevelResult;
 using AggregatableResult = ::content::AttributionTrigger::AggregatableResult;
 
+constexpr char kAttributionDestination[] = "attribution_destination";
+
 absl::optional<DebugDataType> DataTypeIfCookieSet(DebugDataType data_type,
                                                   bool is_debug_cookie_set) {
   return is_debug_cookie_set ? absl::make_optional(data_type) : absl::nullopt;
@@ -205,11 +207,6 @@
   }
 }
 
-void SetAttributionDestination(base::Value::Dict& data_body,
-                               const net::SchemefulSite& destination) {
-  data_body.Set("attribution_destination", destination.Serialize());
-}
-
 template <typename T>
 void SetLimit(base::Value::Dict& data_body, absl::optional<T> limit) {
   DCHECK(limit.has_value());
@@ -224,7 +221,8 @@
 
   const CommonSourceInfo& common_info = source.common_info();
   base::Value::Dict data_body;
-  SetAttributionDestination(data_body, common_info.DestinationSite());
+  data_body.Set(kAttributionDestination,
+                common_info.SerializeDestinationSites());
   SetSourceData(data_body, common_info);
 
   switch (data_type) {
@@ -266,8 +264,8 @@
                                     const AttributionTrigger& trigger,
                                     const CreateReportResult& result) {
   base::Value::Dict data_body;
-  SetAttributionDestination(data_body,
-                            net::SchemefulSite(trigger.destination_origin()));
+  data_body.Set(kAttributionDestination,
+                net::SchemefulSite(trigger.destination_origin()).Serialize());
   if (absl::optional<uint64_t> debug_key = trigger.registration().debug_key)
     data_body.Set("trigger_debug_key", base::NumberToString(*debug_key));
 
diff --git a/content/browser/attribution_reporting/attribution_debug_report_unittest.cc b/content/browser/attribution_reporting/attribution_debug_report_unittest.cc
index 811b210..10e3a11 100644
--- a/content/browser/attribution_reporting/attribution_debug_report_unittest.cc
+++ b/content/browser/attribution_reporting/attribution_debug_report_unittest.cc
@@ -7,6 +7,7 @@
 #include <stdint.h>
 
 #include "base/test/values_test_util.h"
+#include "components/attribution_reporting/suitable_origin.h"
 #include "content/browser/attribution_reporting/attribution_observer_types.h"
 #include "content/browser/attribution_reporting/attribution_storage.h"
 #include "content/browser/attribution_reporting/attribution_test_utils.h"
@@ -19,6 +20,8 @@
 namespace content {
 namespace {
 
+using ::attribution_reporting::SuitableOrigin;
+
 using EventLevelResult = ::content::AttributionTrigger::EventLevelResult;
 using AggregatableResult = ::content::AttributionTrigger::AggregatableResult;
 
@@ -223,6 +226,39 @@
       }
     }
   }
+
+  // Multiple destinations
+  {
+    absl::optional<AttributionDebugReport> report =
+        AttributionDebugReport::Create(
+            SourceBuilder()
+                .SetDebugReporting(true)
+                .SetDestinationOrigins({
+                    *SuitableOrigin::Create(GURL("https://a.c.test")),
+                    *SuitableOrigin::Create(GURL("https://b.c.test")),
+                    *SuitableOrigin::Create(GURL("https://d.test")),
+                })
+                .Build(),
+            /*is_debug_cookie_set=*/true,
+            AttributionStorage::StoreSourceResult(
+                StorableSource::Result::kSuccessNoised,
+                /*min_fake_report_time=*/absl::nullopt,
+                /*max_destinations_per_source_site_reporting_origin=*/
+                absl::nullopt,
+                /*max_sources_per_origin=*/absl::nullopt));
+
+    EXPECT_EQ(report->ReportBody(), base::test::ParseJson(R"json([{
+         "body": {
+           "attribution_destination": [
+             "https://c.test",
+             "https://d.test"
+           ],
+           "source_event_id": "123",
+           "source_site": "https://impression.test"
+         },
+         "type": "source-noised"
+      }])json"));
+  }
 }
 
 TEST(AttributionDebugReportTest, TriggerDebugging) {
diff --git a/content/browser/attribution_reporting/attribution_input_event_tracker_android.cc b/content/browser/attribution_reporting/attribution_input_event_tracker_android.cc
index cbb0e9d4..b09d5ea 100644
--- a/content/browser/attribution_reporting/attribution_input_event_tracker_android.cc
+++ b/content/browser/attribution_reporting/attribution_input_event_tracker_android.cc
@@ -9,8 +9,6 @@
 #include <tuple>
 
 #include "base/android/scoped_java_ref.h"
-#include "base/bind.h"
-#include "base/callback.h"
 #include "base/check.h"
 #include "base/time/time.h"
 #include "content/browser/web_contents/web_contents_android.h"
@@ -21,18 +19,8 @@
 
 namespace content {
 
-namespace {
-
-bool IsEventValid(const ui::MotionEventAndroid& event) {
-  // TODO(crbug.com/1378617): Apply Android's event policy.
-  return true;
-}
-
-}  // namespace
-
 AttributionInputEventTrackerAndroid::AttributionInputEventTrackerAndroid(
-    WebContents* web_contents)
-    : event_filter_(base::BindRepeating(&IsEventValid)) {
+    WebContents* web_contents) {
   DCHECK(web_contents);
 
   // Lazy initialization
@@ -52,14 +40,11 @@
 
 void AttributionInputEventTrackerAndroid::OnTouchEvent(
     const ui::MotionEventAndroid& event) {
-  PushEventIfValid(event);
+  PushEvent(event);
 }
 
-void AttributionInputEventTrackerAndroid::PushEventIfValid(
+void AttributionInputEventTrackerAndroid::PushEvent(
     const ui::MotionEventAndroid& event) {
-  if (!event_filter_.Run(event))
-    return;
-
   most_recent_event_ =
       base::android::ScopedJavaGlobalRef<jobject>(event.GetJavaObject());
   most_recent_event_cache_time_ = base::TimeTicks::Now();
diff --git a/content/browser/attribution_reporting/attribution_input_event_tracker_android.h b/content/browser/attribution_reporting/attribution_input_event_tracker_android.h
index 36ee114..776d6a3 100644
--- a/content/browser/attribution_reporting/attribution_input_event_tracker_android.h
+++ b/content/browser/attribution_reporting/attribution_input_event_tracker_android.h
@@ -7,10 +7,6 @@
 
 #include <jni.h>
 
-#include <utility>
-
-#include "base/callback.h"
-#include "base/check.h"
 #include "base/time/time.h"
 #include "content/common/content_export.h"
 #include "ui/android/event_forwarder.h"
@@ -59,20 +55,10 @@
  private:
   friend class AttributionInputEventTrackerAndroidTest;
 
-  using EventFilterFunction =
-      base::RepeatingCallback<bool(const ui::MotionEventAndroid&)>;
-
   // ui::EventForwarder::Observer:
   void OnTouchEvent(const ui::MotionEventAndroid& event) override;
 
-  void PushEventIfValid(const ui::MotionEventAndroid& event);
-
-  void set_event_filter_for_testing(EventFilterFunction event_filter) {
-    DCHECK(!event_filter.is_null());
-    event_filter_ = std::move(event_filter);
-  }
-
-  EventFilterFunction event_filter_;
+  void PushEvent(const ui::MotionEventAndroid& event);
 
   base::android::ScopedJavaGlobalRef<jobject> most_recent_event_;
 
diff --git a/content/browser/attribution_reporting/attribution_input_event_tracker_android_unittest.cc b/content/browser/attribution_reporting/attribution_input_event_tracker_android_unittest.cc
index 58956ea..f0a021c 100644
--- a/content/browser/attribution_reporting/attribution_input_event_tracker_android_unittest.cc
+++ b/content/browser/attribution_reporting/attribution_input_event_tracker_android_unittest.cc
@@ -7,15 +7,12 @@
 #include <jni.h>
 
 #include <memory>
-#include <utility>
 
 #include "base/android/jni_android.h"
 #include "base/android/jni_string.h"
 #include "base/android/scoped_java_ref.h"
-#include "base/bind.h"
 #include "base/memory/raw_ptr.h"
 #include "base/strings/string_piece.h"
-#include "base/test/bind.h"
 #include "base/time/time.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/test/browser_task_environment.h"
@@ -56,19 +53,12 @@
 
     input_event_tracker_ =
         std::make_unique<AttributionInputEventTrackerAndroid>(web_contents());
-    SetEventFilter(base::BindRepeating(
-        [](const ui::MotionEventAndroid&) { return true; }));
   }
 
   void OnTouchEvent(const ui::MotionEventAndroid& event) {
     input_event_tracker_->OnTouchEvent(event);
   }
 
-  void SetEventFilter(
-      AttributionInputEventTrackerAndroid::EventFilterFunction event_filter) {
-    input_event_tracker_->set_event_filter_for_testing(std::move(event_filter));
-  }
-
  protected:
   // The Java strings are used as standins for the input events.
   base::android::ScopedJavaLocalRef<jstring> GetJavaString(
@@ -86,22 +76,6 @@
   std::unique_ptr<AttributionInputEventTrackerAndroid> input_event_tracker_;
 };
 
-TEST_F(AttributionInputEventTrackerAndroidTest, EventFilterApplied) {
-  base::android::ScopedJavaLocalRef<jstring> str1 = GetJavaString("str1");
-  base::android::ScopedJavaLocalRef<jstring> str2 = GetJavaString("str2");
-
-  SetEventFilter(
-      base::BindLambdaForTesting([&](const ui::MotionEventAndroid& event) {
-        return IsSameObject(event.GetJavaObject(), str2);
-      }));
-
-  OnTouchEvent(CreateTouchEventAt(100.f, 100.f, str1.obj()));
-  EXPECT_TRUE(input_event_tracker_->GetMostRecentEvent().is_null());
-
-  OnTouchEvent(CreateTouchEventAt(100.f, 100.f, str2.obj()));
-  EXPECT_TRUE(IsSameObject(input_event_tracker_->GetMostRecentEvent(), str2));
-}
-
 TEST_F(AttributionInputEventTrackerAndroidTest, EventExpiryApplied) {
   EXPECT_TRUE(input_event_tracker_->GetMostRecentEvent().is_null());
 
diff --git a/content/browser/attribution_reporting/attribution_report.cc b/content/browser/attribution_reporting/attribution_report.cc
index 3756faed..c3c20f2 100644
--- a/content/browser/attribution_reporting/attribution_report.cc
+++ b/content/browser/attribution_reporting/attribution_report.cc
@@ -10,7 +10,6 @@
 
 #include "base/check.h"
 #include "base/check_op.h"
-#include "base/containers/flat_set.h"
 #include "base/functional/overloaded.h"
 #include "base/memory/raw_ptr.h"
 #include "base/strings/strcat.h"
@@ -19,7 +18,6 @@
 #include "components/attribution_reporting/suitable_origin.h"
 #include "content/browser/attribution_reporting/attribution_source_type.h"
 #include "content/browser/attribution_reporting/common_source_info.h"
-#include "net/base/schemeful_site.h"
 #include "third_party/abseil-cpp/absl/types/variant.h"
 #include "url/gurl.h"
 #include "url/origin.h"
@@ -27,32 +25,6 @@
 
 namespace content {
 
-namespace {
-
-base::Value SerializeDestinations(
-    const base::flat_set<attribution_reporting::SuitableOrigin>& destinations) {
-  DCHECK(!destinations.empty());
-
-  base::flat_set<net::SchemefulSite> sites;
-  for (const auto& destination : destinations) {
-    sites.insert(net::SchemefulSite(destination));
-  }
-
-  if (sites.size() == 1)
-    return base::Value(sites.begin()->Serialize());
-
-  base::Value::List list;
-  list.reserve(sites.size());
-
-  for (const auto& site : sites) {
-    list.Append(site.Serialize());
-  }
-
-  return base::Value(std::move(list));
-}
-
-}  // namespace
-
 AttributionReport::EventLevelData::EventLevelData(
     uint64_t trigger_data,
     int64_t priority,
@@ -177,8 +149,7 @@
                 this->attribution_info().source.common_info();
 
             dict.Set("attribution_destination",
-                     SerializeDestinations(
-                         common_source_info.destination_origins()));
+                     common_source_info.SerializeDestinationSites());
 
             // The API denotes these values as strings; a `uint64_t` cannot be
             // put in a dict as an integer in order to be opaque to various API
diff --git a/content/browser/attribution_reporting/common_source_info.cc b/content/browser/attribution_reporting/common_source_info.cc
index 6f862f0..1dbe130 100644
--- a/content/browser/attribution_reporting/common_source_info.cc
+++ b/content/browser/attribution_reporting/common_source_info.cc
@@ -10,6 +10,7 @@
 #include "base/check_op.h"
 #include "base/containers/flat_set.h"
 #include "base/cxx17_backports.h"
+#include "base/values.h"
 #include "components/attribution_reporting/suitable_origin.h"
 #include "net/base/schemeful_site.h"
 
@@ -149,4 +150,24 @@
   return net::SchemefulSite(source_origin_);
 }
 
+base::Value CommonSourceInfo::SerializeDestinationSites() const {
+  base::flat_set<net::SchemefulSite> sites;
+  for (const auto& destination : destination_origins_) {
+    sites.insert(net::SchemefulSite(destination));
+  }
+
+  if (sites.size() == 1) {
+    return base::Value(sites.begin()->Serialize());
+  }
+
+  base::Value::List list;
+  list.reserve(sites.size());
+
+  for (const auto& site : sites) {
+    list.Append(site.Serialize());
+  }
+
+  return base::Value(std::move(list));
+}
+
 }  // namespace content
diff --git a/content/browser/attribution_reporting/common_source_info.h b/content/browser/attribution_reporting/common_source_info.h
index 3350469..77925ff 100644
--- a/content/browser/attribution_reporting/common_source_info.h
+++ b/content/browser/attribution_reporting/common_source_info.h
@@ -17,6 +17,10 @@
 #include "content/common/content_export.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
+namespace base {
+class Value;
+}  // namespace base
+
 namespace net {
 class SchemefulSite;
 }  // namespace net
@@ -132,6 +136,11 @@
   // that we avoid unnecessary copies of |source_origin_|.
   net::SchemefulSite SourceSite() const;
 
+  // Serializes the source's destination origins as a set of sites. If the set
+  // has a single element, returns the string directly. Otherwise, returns a
+  // list of strings.
+  base::Value SerializeDestinationSites() const;
+
  private:
   uint64_t source_event_id_;
   attribution_reporting::SuitableOrigin source_origin_;
diff --git a/content/browser/cache_storage/cache_storage_cache.cc b/content/browser/cache_storage/cache_storage_cache.cc
index 6cbdb791..2a180c7a 100644
--- a/content/browser/cache_storage/cache_storage_cache.cc
+++ b/content/browser/cache_storage/cache_storage_cache.cc
@@ -214,11 +214,14 @@
               "ConnectionInfo enum is stable");
 static_assert(net::HttpResponseInfo::CONNECTION_INFO_QUIC_RFC_V1 == 40,
               "ConnectionInfo enum is stable");
-static_assert(net::HttpResponseInfo::CONNECTION_INFO_QUIC_2_DRAFT_1 == 41,
+static_assert(
+    net::HttpResponseInfo::CONNECTION_INFO_DEPRECATED_QUIC_2_DRAFT_1 == 41,
+    "ConnectionInfo enum is stable");
+static_assert(net::HttpResponseInfo::CONNECTION_INFO_QUIC_2_DRAFT_8 == 42,
               "ConnectionInfo enum is stable");
 // The following assert needs to be changed every time a new value is added.
 // It exists to prevent us from forgetting to add new values above.
-static_assert(net::HttpResponseInfo::NUM_OF_CONNECTION_INFOS == 42,
+static_assert(net::HttpResponseInfo::NUM_OF_CONNECTION_INFOS == 43,
               "Please add new values above and update this assert");
 
 // Copy headers out of a cache entry and into a protobuf. The callback is
diff --git a/content/browser/child_process_security_policy_unittest.cc b/content/browser/child_process_security_policy_unittest.cc
index 12805d2..909604f 100644
--- a/content/browser/child_process_security_policy_unittest.cc
+++ b/content/browser/child_process_security_policy_unittest.cc
@@ -130,6 +130,14 @@
   }
 
   void TearDown() override {
+    auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
+    {
+      base::AutoLock lock(policy->lock_);
+      EXPECT_EQ(0u, policy->security_state_.size())
+          << "ChildProcessSecurityPolicy should not be tracking any processes "
+          << "at test shutdown.  Did you forget to call Remove() at the end of "
+          << "a test?";
+    }
     test_browser_client_.ClearSchemes();
     SetBrowserClientForTesting(old_browser_client_);
   }
diff --git a/content/browser/devtools/protocol/network_handler.cc b/content/browser/devtools/protocol/network_handler.cc
index 70d70bc..dcfa80f 100644
--- a/content/browser/devtools/protocol/network_handler.cc
+++ b/content/browser/devtools/protocol/network_handler.cc
@@ -3160,6 +3160,9 @@
     case network::mojom::TrustTokenOperationStatus::kUnavailable:
       return protocol::Network::TrustTokenOperationDone::StatusEnum::
           Unavailable;
+    case network::mojom::TrustTokenOperationStatus::kUnauthorized:
+      return protocol::Network::TrustTokenOperationDone::StatusEnum::
+          Unauthorized;
     case network::mojom::TrustTokenOperationStatus::kBadResponse:
       return protocol::Network::TrustTokenOperationDone::StatusEnum::
           BadResponse;
diff --git a/content/browser/loader/navigation_url_loader_impl_unittest.cc b/content/browser/loader/navigation_url_loader_impl_unittest.cc
index 1f2540ff..6e083c9 100644
--- a/content/browser/loader/navigation_url_loader_impl_unittest.cc
+++ b/content/browser/loader/navigation_url_loader_impl_unittest.cc
@@ -117,6 +117,7 @@
         nullptr /* keepalive_statistics_recorder */,
         nullptr /* trust_token_helper */,
         mojo::NullRemote() /* cookie_observer */,
+        mojo::NullRemote() /* trust_token_observer */,
         mojo::NullRemote() /* url_loader_network_observer */,
         /*devtools_observer=*/mojo::NullRemote(),
         /*accept_ch_frame_observer=*/mojo::NullRemote(),
diff --git a/content/browser/renderer_host/clipboard_host_impl.cc b/content/browser/renderer_host/clipboard_host_impl.cc
index 5228eca..4436627 100644
--- a/content/browser/renderer_host/clipboard_host_impl.cc
+++ b/content/browser/renderer_host/clipboard_host_impl.cc
@@ -49,22 +49,11 @@
 
 namespace content {
 
-namespace {
-bool IsRendererPasteAllowed(RenderFrameHost& render_frame_host) {
-  ContentBrowserClient* browser_client = GetContentClient()->browser();
-  return browser_client->IsClipboardPasteAllowed(&render_frame_host);
-}
-}  // namespace
-
-// 5 mins is based on the timeout in BinaryUploadService. This scanning timeout
-// of 5 mins means no paste will be held back longer before being allowed or
-// blocked, so matching this timeout with the threshold for a paste being too
-// old ensures scans that:
-//  - Scans that timeout can be retried without waiting
-//  - Scans that succeed will apply their verdicts without the risk that their
-//    associated IsPasteContentAllowedRequest is already too old.
-const base::TimeDelta ClipboardHostImpl::kIsPasteContentAllowedRequestTooOld =
-    base::Minutes(5);
+// The amount of time that the result of a content allow request is cached
+// and reused for the same clipboard `seqno`.
+constexpr base::TimeDelta
+    ClipboardHostImpl::kIsPasteContentAllowedRequestTooOld =
+        base::Milliseconds(500);
 
 ClipboardHostImpl::IsPasteContentAllowedRequest::
     IsPasteContentAllowedRequest() = default;
@@ -88,16 +77,19 @@
 
 void ClipboardHostImpl::IsPasteContentAllowedRequest::Complete(
     IsClipboardPasteContentAllowedCallbackArgType data) {
+  completed_time_ = base::Time::Now();
   data_ = std::move(data);
   InvokeCallbacks();
 }
 
 bool ClipboardHostImpl::IsPasteContentAllowedRequest::IsObsolete(
     base::Time now) {
-  // If the request is old and no longer has any registered callbacks it is
-  // obsolete.
-  return (now - time_) > kIsPasteContentAllowedRequestTooOld &&
-         callbacks_.empty();
+  return (now - completed_time_) > kIsPasteContentAllowedRequestTooOld;
+}
+
+base::Time ClipboardHostImpl::IsPasteContentAllowedRequest::completed_time() {
+  DCHECK(is_complete());
+  return completed_time_;
 }
 
 void ClipboardHostImpl::IsPasteContentAllowedRequest::InvokeCallbacks() {
@@ -206,7 +198,7 @@
 
 void ClipboardHostImpl::ReadText(ui::ClipboardBuffer clipboard_buffer,
                                  ReadTextCallback callback) {
-  if (!IsRendererPasteAllowed(render_frame_host())) {
+  if (!IsRendererPasteAllowed(clipboard_buffer, render_frame_host())) {
     std::move(callback).Run(std::u16string());
     return;
   }
@@ -243,7 +235,7 @@
 
 void ClipboardHostImpl::ReadHtml(ui::ClipboardBuffer clipboard_buffer,
                                  ReadHtmlCallback callback) {
-  if (!IsRendererPasteAllowed(render_frame_host())) {
+  if (!IsRendererPasteAllowed(clipboard_buffer, render_frame_host())) {
     std::move(callback).Run(std::u16string(), GURL(), 0, 0);
     return;
   }
@@ -275,7 +267,7 @@
 
 void ClipboardHostImpl::ReadSvg(ui::ClipboardBuffer clipboard_buffer,
                                 ReadSvgCallback callback) {
-  if (!IsRendererPasteAllowed(render_frame_host())) {
+  if (!IsRendererPasteAllowed(clipboard_buffer, render_frame_host())) {
     std::move(callback).Run(std::u16string());
     return;
   }
@@ -298,7 +290,7 @@
 
 void ClipboardHostImpl::ReadRtf(ui::ClipboardBuffer clipboard_buffer,
                                 ReadRtfCallback callback) {
-  if (!IsRendererPasteAllowed(render_frame_host())) {
+  if (!IsRendererPasteAllowed(clipboard_buffer, render_frame_host())) {
     std::move(callback).Run(std::string());
     return;
   }
@@ -322,7 +314,7 @@
 
 void ClipboardHostImpl::ReadPng(ui::ClipboardBuffer clipboard_buffer,
                                 ReadPngCallback callback) {
-  if (!IsRendererPasteAllowed(render_frame_host())) {
+  if (!IsRendererPasteAllowed(clipboard_buffer, render_frame_host())) {
     std::move(callback).Run(mojo_base::BigBuffer());
     return;
   }
@@ -356,7 +348,7 @@
 void ClipboardHostImpl::ReadFiles(ui::ClipboardBuffer clipboard_buffer,
                                   ReadFilesCallback callback) {
   blink::mojom::ClipboardFilesPtr result = blink::mojom::ClipboardFiles::New();
-  if (!IsRendererPasteAllowed(render_frame_host())) {
+  if (!IsRendererPasteAllowed(clipboard_buffer, render_frame_host())) {
     std::move(callback).Run(std::move(result));
     return;
   }
@@ -435,7 +427,7 @@
 void ClipboardHostImpl::ReadCustomData(ui::ClipboardBuffer clipboard_buffer,
                                        const std::u16string& type,
                                        ReadCustomDataCallback callback) {
-  if (!IsRendererPasteAllowed(render_frame_host())) {
+  if (!IsRendererPasteAllowed(clipboard_buffer, render_frame_host())) {
     std::move(callback).Run(std::u16string());
     return;
   }
@@ -503,6 +495,21 @@
       ui::ClipboardBuffer::kCopyPaste, CreateDataEndpoint());
 }
 
+bool ClipboardHostImpl::IsRendererPasteAllowed(
+    ui::ClipboardBuffer clipboard_buffer,
+    RenderFrameHost& render_frame_host) {
+  auto it = is_allowed_requests_.find(
+      ui::Clipboard::GetForCurrentThread()->GetSequenceNumber(
+          clipboard_buffer));
+  if (it != is_allowed_requests_.end() && it->second.is_complete() &&
+      !it->second.IsObsolete(base::Time::Now())) {
+    return true;
+  }
+
+  return GetContentClient()->browser()->IsClipboardPasteAllowed(
+      &render_frame_host);
+}
+
 bool ClipboardHostImpl::IsUnsanitizedCustomFormatContentAllowed() {
   if (!base::FeatureList::IsEnabled(blink::features::kClipboardCustomFormats)) {
     mojo::ReportBadMessage("Custom format read/write is not enabled.");
diff --git a/content/browser/renderer_host/clipboard_host_impl.h b/content/browser/renderer_host/clipboard_host_impl.h
index 363b90fb..8205fe2 100644
--- a/content/browser/renderer_host/clipboard_host_impl.h
+++ b/content/browser/renderer_host/clipboard_host_impl.h
@@ -75,6 +75,9 @@
     // Invoke all callbacks now.
     void Complete(IsClipboardPasteContentAllowedCallbackArgType data);
 
+    // Returns true if the request has completed.
+    bool is_complete() const { return data_.has_value(); }
+
     // Returns true if this request is obsolete.  An obsolete request
     // is one that is completed, all registered callbacks have been
     // called, and is considered old.
@@ -82,15 +85,18 @@
     // |now| represents the current time.  It is an argument to ease testing.
     bool IsObsolete(base::Time now);
 
-    // Returns the time at which this request was created.
-    base::Time time() { return time_; }
+    // Returns the time at which this request was completed.  If called
+    // before the request is completed the return value is undefined.
+    base::Time completed_time();
 
    private:
     // Calls all the callbacks in |callbacks_| with the current value of
     // |allowed_|.  |allowed_| must not be empty.
     void InvokeCallbacks();
 
-    base::Time time_{base::Time::Now()};
+    // The time at which the request was completed.  Before completion this
+    // value is undefined.
+    base::Time completed_time_;
 
     // The data argument to pass to the IsClipboardPasteContentAllowedCallback.
     // This member is null until Complete() is called.
@@ -201,6 +207,11 @@
   void WriteStringToFindPboard(const std::u16string& text) override;
 #endif
 
+  // Checks if the renderer allows pasting.  This check is skipped if called
+  // soon after a successful content allowed request.
+  bool IsRendererPasteAllowed(ui::ClipboardBuffer clipboard_buffer,
+                              RenderFrameHost& render_frame_host);
+
   // Returns true if custom format is allowed to be read/written from/to the
   // clipboard, else, fails.
   bool IsUnsanitizedCustomFormatContentAllowed();
diff --git a/content/browser/renderer_host/clipboard_host_impl_unittest.cc b/content/browser/renderer_host/clipboard_host_impl_unittest.cc
index 6865a7ea67..e3d747a3 100644
--- a/content/browser/renderer_host/clipboard_host_impl_unittest.cc
+++ b/content/browser/renderer_host/clipboard_host_impl_unittest.cc
@@ -221,23 +221,12 @@
 TEST_F(ClipboardHostImplTest, IsPasteContentAllowedRequest_IsObsolete) {
   ClipboardHostImpl::IsPasteContentAllowedRequest request;
 
-  // A request that is not too old is not obsolete, even if it has no callbacks.
-  EXPECT_FALSE(request.IsObsolete(
-      request.time() +
-      ClipboardHostImpl::kIsPasteContentAllowedRequestTooOld / 2));
-
-  // A request that still has callbacks is not obsolete, even if older than
-  // "too old".
-  request.AddCallback(base::DoNothing());
-  EXPECT_FALSE(request.IsObsolete(
-      request.time() + ClipboardHostImpl::kIsPasteContentAllowedRequestTooOld +
-      base::Microseconds(1)));
-
-  // A request is obsolete once it is too old and has no callbacks.
+  // A request is obsolete once it is too old and completed.
   // Whether paste is allowed or not is not important.
   request.Complete("data");
   EXPECT_TRUE(request.IsObsolete(
-      request.time() + ClipboardHostImpl::kIsPasteContentAllowedRequestTooOld +
+      request.completed_time() +
+      ClipboardHostImpl::kIsPasteContentAllowedRequestTooOld +
       base::Microseconds(1)));
 }
 
diff --git a/content/browser/renderer_host/navigation_request.cc b/content/browser/renderer_host/navigation_request.cc
index 8dcc8499..5ba4614 100644
--- a/content/browser/renderer_host/navigation_request.cc
+++ b/content/browser/renderer_host/navigation_request.cc
@@ -2456,9 +2456,20 @@
           policy_container_builder_->FinalPolicies().cross_origin_opener_policy,
           origin, net::NetworkAnonymizationKey(site, site));
 
-      // Select an appropriate RenderFrameHost.
-      render_frame_host_ =
-          frame_tree_node_->render_manager()->GetFrameHostForNavigation(this);
+      if (auto result =
+              frame_tree_node_->render_manager()->GetFrameHostForNavigation(
+                  this);
+          result.has_value()) {
+        render_frame_host_ = result.value();
+      } else {
+        switch (result.error()) {
+          case GetFrameHostForNavigationFailed::kCouldNotReinitializeMainFrame:
+            // TODO(https://crbug.com/1400535): This was unhandled before and
+            // remains explicitly unhandled. This branch may be removed in the
+            // future.
+            break;
+        }
+      }
 
       CHECK(Navigator::CheckWebUIRendererDoesNotDisplayNormalURL(
           render_frame_host_, GetUrlInfo(),
@@ -3913,8 +3924,19 @@
     // TODO(https://crbug.com/1181712): Handle the cases when the prerender is
     // cancelled and RFH is destroyed while NavigationRequest is alive.
   } else if (response_should_be_rendered_) {
-    render_frame_host_ =
-        frame_tree_node_->render_manager()->GetFrameHostForNavigation(this);
+    if (auto result =
+            frame_tree_node_->render_manager()->GetFrameHostForNavigation(this);
+        result.has_value()) {
+      render_frame_host_ = result.value();
+    } else {
+      switch (result.error()) {
+        case GetFrameHostForNavigationFailed::kCouldNotReinitializeMainFrame:
+          // TODO(https://crbug.com/1400535): This was unhandled before and
+          // remains explicitly unhandled. This branch may be removed in the
+          // future.
+          break;
+      }
+    }
 
     // Update the associated RenderFrameHost type, which could have changed
     // due to redirects during navigation.
@@ -4150,9 +4172,16 @@
   // The CrossOriginRedirectAfterEarlyHints variant of
   // Navigation.MainFrame.TimeToReadyToCommit2 histogram tracks the performance
   // impacts.
-  RenderProcessHost* process = frame_tree_node_->render_manager()
-                                   ->GetFrameHostForNavigation(this)
-                                   ->GetProcess();
+  auto result =
+      frame_tree_node_->render_manager()->GetFrameHostForNavigation(this);
+
+  // Early hints is an optimization; if it is not possible to get a suitable
+  // RenderFrameHost for any reason, just bail out.
+  if (!result.has_value()) {
+    return absl::nullopt;
+  }
+
+  RenderProcessHost* process = result.value()->GetProcess();
 
   // The process is shutting down.
   if (!process->GetBrowserContext())
@@ -4267,8 +4296,20 @@
       // https://crbug.com/1125106.
       common_params_->navigation_type =
           ConvertToCrossDocumentType(common_params_->navigation_type);
-      render_frame_host =
-          frame_tree_node_->render_manager()->GetFrameHostForNavigation(this);
+      if (auto result =
+              frame_tree_node_->render_manager()->GetFrameHostForNavigation(
+                  this);
+          result.has_value()) {
+        render_frame_host = result.value();
+      } else {
+        switch (result.error()) {
+          case GetFrameHostForNavigationFailed::kCouldNotReinitializeMainFrame:
+            // TODO(https://crbug.com/1400535): This was unhandled
+            // before and remains explicitly unhandled. This branch may be
+            // removed in the future.
+            break;
+        }
+      }
       break;
   }
   // Sanity check that we haven't changed the RenderFrameHost picked for the
diff --git a/content/browser/renderer_host/render_frame_host_manager.cc b/content/browser/renderer_host/render_frame_host_manager.cc
index 0ee4d43..3aa703c 100644
--- a/content/browser/renderer_host/render_frame_host_manager.cc
+++ b/content/browser/renderer_host/render_frame_host_manager.cc
@@ -25,6 +25,7 @@
 #include "base/trace_event/base_tracing.h"
 #include "base/trace_event/trace_event.h"
 #include "base/trace_event/typed_macros.h"
+#include "base/types/expected.h"
 #include "build/build_config.h"
 #include "content/browser/child_process_security_policy_impl.h"
 #include "content/browser/network/cross_origin_opener_policy_reporter.h"
@@ -1114,18 +1115,20 @@
     request->SetAssociatedRFHType(
         NavigationRequest::AssociatedRenderFrameHostType::CURRENT);
   } else {
-    RenderFrameHostImpl* dest_rfh = GetFrameHostForNavigation(request);
-    DCHECK(dest_rfh);
-    request->SetAssociatedRFHType(
-        dest_rfh == render_frame_host_.get()
-            ? NavigationRequest::AssociatedRenderFrameHostType::CURRENT
-            : NavigationRequest::AssociatedRenderFrameHostType::SPECULATIVE);
+    auto result = GetFrameHostForNavigation(request);
+    if (result.has_value()) {
+      DCHECK(result.value());
+      request->SetAssociatedRFHType(
+          result.value() == render_frame_host_.get()
+              ? NavigationRequest::AssociatedRenderFrameHostType::CURRENT
+              : NavigationRequest::AssociatedRenderFrameHostType::SPECULATIVE);
+    }
   }
 }
 
-RenderFrameHostImpl* RenderFrameHostManager::GetFrameHostForNavigation(
-    NavigationRequest* request,
-    std::string* reason) {
+base::expected<RenderFrameHostImpl*, GetFrameHostForNavigationFailed>
+RenderFrameHostManager::GetFrameHostForNavigation(NavigationRequest* request,
+                                                  std::string* reason) {
   TRACE_EVENT("navigation", "RenderFrameHostManager::GetFrameHostForNavigation",
               ChromeTrackEvent::kFrameTreeNodeInfo, *frame_tree_node_);
 
@@ -1385,8 +1388,10 @@
   // before.
   if (!navigation_rfh->IsRenderFrameLive()) {
     DCHECK(!frame_tree_node_->parent());
-    if (!ReinitializeMainRenderFrame(navigation_rfh))
-      return nullptr;
+    if (!ReinitializeMainRenderFrame(navigation_rfh)) {
+      return base::unexpected(
+          GetFrameHostForNavigationFailed::kCouldNotReinitializeMainFrame);
+    }
 
     notify_webui_of_rf_creation = true;
 
diff --git a/content/browser/renderer_host/render_frame_host_manager.h b/content/browser/renderer_host/render_frame_host_manager.h
index e79044e..77001a65 100644
--- a/content/browser/renderer_host/render_frame_host_manager.h
+++ b/content/browser/renderer_host/render_frame_host_manager.h
@@ -16,6 +16,7 @@
 #include "base/containers/unique_ptr_adapters.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
+#include "base/types/expected.h"
 #include "content/browser/renderer_host/browsing_context_state.h"
 #include "content/browser/renderer_host/navigation_discard_reason.h"
 #include "content/browser/renderer_host/render_frame_host_impl.h"
@@ -62,6 +63,14 @@
 using RemoteFramesBroadcastMethodCallback =
     base::RepeatingCallback<void(RenderFrameProxyHost*)>;
 
+// Reasons that `GetFrameHostForNavigation()` might fail.
+enum class GetFrameHostForNavigationFailed {
+  // Failed to reinitialize the main frame, for whatever reason.
+  // TODO(https://crbug.com/1400535): This adds a tremendous amount of failure
+  // plumbing *everywhere* and might be unnecessary.
+  kCouldNotReinitializeMainFrame,
+};
+
 // Manages RenderFrameHosts for a FrameTreeNode. It maintains a
 // current_frame_host() which is the content currently visible to the user. When
 // a frame is told to navigate to a different web site (as determined by
@@ -388,16 +397,22 @@
   void DidCreateNavigationRequest(NavigationRequest* request);
 
   // Called (possibly several times) during a navigation to select or create an
-  // appropriate RenderFrameHost for the provided URL. The returned pointer will
-  // be for the current or the speculative RenderFrameHost and the instance is
-  // owned by this manager.
+  // appropriate RenderFrameHost for the provided URL.
   //
-  // |reason| is an optional out-parameter that will be populated with
+  // On success, returns a non-null pointer to a RenderFrameHost to use for the
+  // navigation. The returned pointer always refers to either the current or the
+  // speculative RenderFrameHost owned by `this`.
+  //
+  // Otherwise, on failure, returns an enum value denoting the reason for
+  // failure.
+  //
+  // `reason` is an optional out-parameter that will be populated with
   // engineer-readable information describing the reason for the method
-  // behavior.  The returned |reason| should fit into
+  // behavior.  The returned `reason` should fit into
   // base::debug::CrashKeySize::Size256.
-  RenderFrameHostImpl* GetFrameHostForNavigation(NavigationRequest* request,
-                                                 std::string* reason = nullptr);
+  base::expected<RenderFrameHostImpl*, GetFrameHostForNavigationFailed>
+  GetFrameHostForNavigation(NavigationRequest* request,
+                            std::string* reason = nullptr);
 
   // Discards `speculative_render_frame_host_` if it exists, even if there are
   // NavigationRequests associated with it, including pending commit
diff --git a/content/browser/renderer_host/render_frame_host_manager_unittest.cc b/content/browser/renderer_host/render_frame_host_manager_unittest.cc
index be3e2e4..022929d6 100644
--- a/content/browser/renderer_host/render_frame_host_manager_unittest.cc
+++ b/content/browser/renderer_host/render_frame_host_manager_unittest.cc
@@ -446,7 +446,7 @@
     // And also simulates the 2nd and final call to GetFrameHostForNavigation
     // that determines the final frame that will commit the navigation.
     TestRenderFrameHost* frame_host = static_cast<TestRenderFrameHost*>(
-        manager->GetFrameHostForNavigation(navigation_request.get()));
+        manager->GetFrameHostForNavigation(navigation_request.get()).value());
     CHECK(frame_host);
 
     frame_host->SetPolicyContainerHost(
diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc
index 3d43d94..0b462411 100644
--- a/content/renderer/render_frame_impl.cc
+++ b/content/renderer/render_frame_impl.cc
@@ -1017,6 +1017,11 @@
   if (commit_params.http_response_code != -1)
     navigation_params->http_status_code = commit_params.http_response_code;
 
+  // Copy the modified runtime features from `commit_params` to send to the
+  // Blink renderer class WebLocalFrameImpl.
+  navigation_params->modified_runtime_features =
+      commit_params.modified_runtime_features;
+
   // Populate the arrays of non-current entries for the window.navigation API.
   auto& entry_arrays = commit_params.navigation_api_history_entry_arrays;
   navigation_params->navigation_api_back_entries.reserve(
diff --git a/content/test/data/gpu/pixel_offscreenCanvas_transferToImageBitmap_main.html b/content/test/data/gpu/pixel_offscreenCanvas_transferToImageBitmap_main.html
index a8d34b23..9ab1ec97 100644
--- a/content/test/data/gpu/pixel_offscreenCanvas_transferToImageBitmap_main.html
+++ b/content/test/data/gpu/pixel_offscreenCanvas_transferToImageBitmap_main.html
@@ -8,7 +8,7 @@
 
 <html>
 <head>
-<title>OffscreenCanvas transferToImageBitmap on main thread: green and blue squares on white background.</title>
+<title>OffscreenCanvas transferToImageBitmap on main thread: RGB squares and on large blue square on white background.</title>
 <style type="text/css">
 .nomargin {
   margin: 0px auto;
@@ -29,8 +29,18 @@
   var height = 100;
   var aCanvas = new OffscreenCanvas(width, height);
   var gl = aCanvas.getContext('webgl');
-  gl.clearColor(0.0, 1.0, 0.0, 1.0);
+  gl.enable(gl.SCISSOR_TEST);
+  gl.scissor(0, 50, 50, 50);
+  gl.clearColor(1, 0, 0, 1);
   gl.clear(gl.COLOR_BUFFER_BIT);
+  gl.scissor(50, 50, 100, 50);
+  gl.clearColor(0, 1, 0, 1);
+  gl.clear(gl.COLOR_BUFFER_BIT);
+  gl.scissor(0, 0, 50, 50);
+  gl.clearColor(0, 0, 1, 1);
+  gl.clear(gl.COLOR_BUFFER_BIT);
+  gl.disable(gl.SCISSOR_TEST);
+
   var image1 = aCanvas.transferToImageBitmap();
   gl.clearColor(0.0, 0.0, 1.0, 1.0);
   gl.clear(gl.COLOR_BUFFER_BIT);
diff --git a/content/test/gpu/gpu_tests/pixel_test_pages.py b/content/test/gpu/gpu_tests/pixel_test_pages.py
index 56a02b47..c1b37d5 100644
--- a/content/test/gpu/gpu_tests/pixel_test_pages.py
+++ b/content/test/gpu/gpu_tests/pixel_test_pages.py
@@ -633,6 +633,12 @@
                       base_name + '_OffscreenCanvasTransferToImageBitmap',
                       test_rect=[0, 0, 300, 300],
                       browser_args=browser_args),
+        PixelTestPage(
+            'pixel_offscreenCanvas_transferToImageBitmap_main.html',
+            base_name +
+            '_OffscreenCanvasTransferToImageBitmapSoftwareCompositing',
+            test_rect=[0, 0, 300, 300],
+            browser_args=browser_args + unaccelerated_args),
         PixelTestPage('pixel_offscreenCanvas_transferToImageBitmap_worker.html',
                       base_name + '_OffscreenCanvasTransferToImageBitmapWorker',
                       test_rect=[0, 0, 300, 300],
diff --git a/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt
index b60b641b..8799d9d 100644
--- a/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt
@@ -83,6 +83,7 @@
 [ android ] Pixel_RepeatedWebGLTo2D_SoftwareCompositing [ Skip ]
 [ android ] Pixel_Canvas2DTabSwitch_SoftwareCompositing [ Skip ]
 [ android ] Pixel_WebGLReadPixelsTabSwitch_SoftwareCompositing [ Skip ]
+[ android ] Pixel_OffscreenCanvasTransferToImageBitmapSoftwareCompositing [ Skip ]
 [ chromeos ] Pixel_OffscreenCanvasUnaccelerated2D [ Skip ]
 [ chromeos ] Pixel_OffscreenCanvasUnaccelerated2DWorker [ Skip ]
 [ chromeos ] Pixel_OffscreenCanvasUnaccelerated2DGPUCompositing [ Skip ]
@@ -95,6 +96,7 @@
 [ chromeos ] Pixel_RepeatedWebGLTo2D_SoftwareCompositing [ Skip ]
 [ chromeos ] Pixel_Canvas2DTabSwitch_SoftwareCompositing [ Skip ]
 [ chromeos ] Pixel_WebGLReadPixelsTabSwitch_SoftwareCompositing [ Skip ]
+[ chromeos ] Pixel_OffscreenCanvasTransferToImageBitmapSoftwareCompositing [ Skip ]
 [ fuchsia ] Pixel_OffscreenCanvasUnaccelerated2D [ Skip ]
 [ fuchsia ] Pixel_OffscreenCanvasUnaccelerated2DWorker [ Skip ]
 [ fuchsia ] Pixel_OffscreenCanvasUnaccelerated2DGPUCompositing [ Skip ]
@@ -107,6 +109,7 @@
 [ fuchsia ] Pixel_RepeatedWebGLTo2D_SoftwareCompositing [ Skip ]
 [ fuchsia ] Pixel_Canvas2DTabSwitch_SoftwareCompositing [ Skip ]
 [ fuchsia ] Pixel_WebGLReadPixelsTabSwitch_SoftwareCompositing [ Skip ]
+[ fuchsia ] Pixel_OffscreenCanvasTransferToImageBitmapSoftwareCompositing [ Skip ]
 
 # Skip tab switching tests on Android webview and Fuchsia since they don't have tabs
 [ android android-webview-instrumentation ] Pixel_Canvas2DTabSwitch [ Skip ]
diff --git a/device/bluetooth/test/bluetooth_test_cast.cc b/device/bluetooth/test/bluetooth_test_cast.cc
index 50f7b63..d67254e34 100644
--- a/device/bluetooth/test/bluetooth_test_cast.cc
+++ b/device/bluetooth/test/bluetooth_test_cast.cc
@@ -130,16 +130,16 @@
   }
 
   // Add service_uuids.
-  std::vector<uint8_t> data;
+  std::vector<uint8_t> parsed_uuids;
   for (const auto& uuid_str : service_uuids) {
     chromecast::bluetooth_v2_shlib::Uuid uuid;
     ASSERT_TRUE(chromecast::bluetooth::util::ParseUuid(uuid_str, &uuid));
-    data.insert(data.end(), uuid.rbegin(), uuid.rend());
+    parsed_uuids.insert(parsed_uuids.end(), uuid.rbegin(), uuid.rend());
   }
   result
       .type_to_data
           [chromecast::bluetooth::LeScanResult::kGapComplete128BitServiceUuids]
-      .push_back(std::move(data));
+      .push_back(std::move(parsed_uuids));
 
   // Add service data.
   for (const auto& it : service_data) {
@@ -155,7 +155,8 @@
 
   // Add manufacturer data.
   for (const auto& it : manufacturer_data) {
-    std::vector<uint8_t> data({(it.first & 0xFF), ((it.first >> 8) & 0xFF)});
+    std::vector<uint8_t> data{{static_cast<uint8_t>(it.first & 0xFF),
+                               static_cast<uint8_t>((it.first >> 8) & 0xFF)}};
     data.insert(data.end(), it.second.begin(), it.second.end());
     result
         .type_to_data[chromecast::bluetooth::LeScanResult::kGapManufacturerData]
diff --git a/docs/fuchsia/build_instructions.md b/docs/fuchsia/build_instructions.md
index 4b823ca..abb2ed3 100644
--- a/docs/fuchsia/build_instructions.md
+++ b/docs/fuchsia/build_instructions.md
@@ -259,6 +259,9 @@
 
 ### Running test suites
 
+A description of how our testing scripts work can be found
+[here](test_scripts.md).
+
 There are three types of tests available to run on Fuchsia:
 
 1.  [Gtests](gtests.md)
@@ -276,7 +279,7 @@
   directory in Fuchsia. For instance. `/path/to/src/fuchsia/out/qemu-x64`. This
   will automatically add the `--fuchsia-out-dir` flag to wrapper scripts.
 * `default_fuchsia_device_node_name`. Set this to a Fuchsia device node name.
-  This will automatically add the `--node-name` flag to most wrapper scripts.
+  This will automatically add the `--target-id` flag to most wrapper scripts.
 * Finally, use the `-d` flag when running the <test_target_name> wrappers to
   execute them on an already running device or emulator, rather than starting an
   ephemeral emulator instance. This speeds up subsequent runs since the runner
diff --git a/docs/fuchsia/gtests.md b/docs/fuchsia/gtests.md
index 67c56da..267b6e4 100644
--- a/docs/fuchsia/gtests.md
+++ b/docs/fuchsia/gtests.md
@@ -3,13 +3,9 @@
 [TOC]
 
 Fuchsia gtest binaries are deployed and executed via scripts that are
-automatically generated by the `test()` GN target. For each test, three wrapper
-scripts are created:
-
-1. deploy_<test_target_name>, for deploying the Fuchsia package onto a device
-2. run_<test_target_name>, for running the Fuchsia package on the device
-
-These executables are found in `${OUTPUT_DIR}/bin`.
+automatically generated by the `test()` GN target. For each test, a wrapper
+scripts is created named `run_<test_target_name>` for running the Fuchsia
+package on the device. These executables are found in `${OUTPUT_DIR}/bin`.
 
 The aforementioned devices can be either emulators started by the scripts
 themselves, an existing emulator instance, or a physical device. To build a
@@ -31,12 +27,13 @@
 `--custom-image=workstation.qemu-x64-release` would run the test on a
 workstation image.
 
-## Run on a persistent emulated device from the Chromium three
+## Run on a persistent device
 
-You can start a persistent emulator from the Chromium tree by running this command:
+You can also run tests on physical devices or start a persistent emulator
+from the Chromium tree. To start a persistent emulator, run:
 
 ```bash
-$ ./build/fuchsia/start_emulator.py
+$ ./build/fuchsia/test/start_emulator.py
 ```
 
 Part of the output should be:
@@ -44,19 +41,18 @@
 ```
 2022-05-31 22:55:13,011:INFO:root:Emulator successfully started.\
  You can now run Chrome Fuchsia tests with\
- "-d --node-name fuchsia-5254-0063-5e7a" to target this emulator.
+ "--target-id fuchsia-emulator-198" to target this emulator.
 ```
 
-Once the emulator is running, you can run tests on this emulator instance by
-adding the command line arguments indicated above:
+You can run tests on persistent devices by adding the command line
+arguments indicated above. For example, to run `base_unittests` you
+would run the following command:
 
 ```bash
-$ out/fuchsia/bin/run_base_unittests -d --node-name fuchsia-5254-0063-5e7a
+$ out/fuchsia/bin/run_base_unittests --target-id fuchsia-emulator-198
 ```
 
-## Run on an physical device
-
-Note the `-d` flag, which is an alias for `--device`.
+If only one Fuchsia device is connected, you can also simply use 
 
 ```bash
 $ out/fuchsia/bin/run_base_unittests -d
@@ -68,47 +64,17 @@
 the architecture of the Fuchsia output directory (x64==x64, arm64==arm64, etc.).
 
 ```bash
-$ out/fuchsia/bin/run_base_unittests -d
---fuchsia-out-dir=/path/to/fuchsia/outdir
+$ out/fuchsia/bin/run_base_unittests -d --repo [FUCHSIA_OUT_DIR]/amber-files \
+--no-repo-init
 ```
 
-If you are frequently deploying to Fuchsia built from source, you might want to
-add the following entry to your `args.gn`:
-
-```
-default_fuchsia_build_dir_for_installation = "/path/to/fuchsia/outdir"
-```
-
-With this flag in place, the `--fuchsia-out-dir` flag will automatically be
-used whenever you use the wrapper scripts to run or deploy Fuchsia packages,
-making your command lines much shorter:
-
-```bash
-$ out/fuchsia/bin/run_base_unittests -d
-```
-
-## Install on a device running Fuchsia built from source
-
-```bash
-$ out/fuchsia/bin/deploy_base_unittests
---fuchsia-out-dir=/path/to/fuchsia/outdir
-```
-
-You will need to run the package manually on your device. In this case, run the
-following from your Fuchsia directory:
-
-```
-$ fx shell run fuchsia-pkg://fuchsia.com/base_unittests#meta/base_unittests.cmx
-```
+Note that you should not have `fx serve` running as Chromium will handle serving
+the repository.
 
 ## Run on a device the host is connected to remotely via ssh
 
-Note the `--ssh-config` flag, which should point to the config file used to set
-up the connection between the host and the remote device.
-
 ```bash
-$ out/fuchsia/bin/run_base_unittests -d \
-  --host=::1 --port=8022 --ssh-config=/path/to/ssh/config
+$ out/fuchsia/bin/run_base_unittests --target-id=[::1]:8022
 ```
 
 ## Troubleshooting a test
diff --git a/docs/fuchsia/test_scripts.md b/docs/fuchsia/test_scripts.md
index 0c2985fa..aed02ef 100644
--- a/docs/fuchsia/test_scripts.md
+++ b/docs/fuchsia/test_scripts.md
@@ -11,33 +11,6 @@
 To build Fuchsia gtest binaries follow
 [build instructions](build_instructions.md).
 
-### Run on a transient emulated device
-
-The test script brings up an emulator, runs the tests on it, and
-shuts the emulator down when finished.
-```bash
-$ ./build/fuchsia/test/run_test.py [TEST_BINARY] -C [OUTPUT_DIR]
-```
-
-### Run on a persistent emulated device from the Chromium tree
-
-You can start a persistent emulator from the Chromium tree by running this
-command:
-
-```bash
-$ ./build/fuchsia/test/start_emulator.py
-```
-
-Note that running this command for the first time will download a Fuchsia
-product bundle and will take around a minute. Part of the output should be:
-
-```
-Logging to \
- "$HOME/.local/share/Fuchsia/ffx/emu/instances/[EMULATOR_NAME]/emulator.log"
-```
-
-Record the [EMULATOR_NAME] value as it will be used in subsequent commands.
-
 #### E2E Testing Script
 
 Once the emulator is running, you can run tests on this emulator instance by
diff --git a/docs/updater/test_plan.md b/docs/updater/test_plan.md
index 0edd5b4..25fc243 100644
--- a/docs/updater/test_plan.md
+++ b/docs/updater/test_plan.md
@@ -70,6 +70,10 @@
 that it is immediately active after installation, and then can be cleanly
 uninstalled.
 
+IntegrationTest.Handoff tests that the updater can be installed on a clean OS,
+that it can install an app via a "/handoff" command line, and then can be
+cleanly uninstalled.
+
 Overinstall cases are tested by IntegrationTest.OverinstallWorking and
 IntegrationTest.OverinstallBroken, to ensure that the updater can be installed
 on an unclean OS, and as a post-condition of installation, the system has a
diff --git a/docs/webui_explainer.md b/docs/webui_explainer.md
index 7c02b13..a783f93 100644
--- a/docs/webui_explainer.md
+++ b/docs/webui_explainer.md
@@ -408,9 +408,310 @@
 * Setting the resource to load for the empty path.
 * Adding all resources from a GritResourceMap.
 
-## Browser (C++) &rarr; Renderer (JS)
+## Browser (C++) and Renderer (JS) communication
 
-### WebUIMessageHandler::AllowJavascript()
+### Mojo
+
+[Mojo](https://chromium.googlesource.com/chromium/src/+/master/mojo/README.md)
+is used for IPC throughout Chromium, and should generally be used for new
+WebUIs to communicate between the browser (C++) and the renderer (JS/TS). To
+use Mojo, you will need to:
+
+* Write an interface definition for the JS/C++ interface in a mojom file
+* Add a build target in the BUILD.gn file to autogenerate C++ and TypeScript
+  code ("bindings").
+* Bind the interface on the C++ side and implement any methods to send or
+  receive information from TypeScript.
+* Add the TypeScript bindings file to your WebUI's <code>ts_library()</code>
+  and use them in your TypeScript code.
+
+#### Mojo Interface Definition
+Mojo interfaces are declared in mojom files. For WebUIs, these normally live
+alongside the C++ code in chrome/browser/ui/webui. For example:
+
+**chrome/browser/ui/webui/donuts/donuts.mojom**
+```
+module donuts.mojom;
+
+interface PageHandlerFactory {
+  CreatePageHandler(pending_remote<Page> page,
+                    pending_receiver<PageHandler> handler);
+};
+
+// Called from TS side of chrome://donuts (Renderer -> Browser)
+interface PageHandler {
+  StartPilotLight();
+
+  BakeDonuts(uint32 num_donuts);
+
+  // Expects a response from the browser.
+  GetNumberOfDonuts() => (uint32 num_donuts);
+}
+
+// Called from C++ side of chrome://donuts. (Browser -> Renderer)
+interface Page {
+  DonutsBaked(uint32 num_donuts);
+}
+```
+
+#### BUILD.gn mojo target
+mojom() is the build rule used to generate mojo bindings. It can be set up as
+follows:
+
+**chrome/browser/ui/webui/donuts/BUILD.gn**
+```
+import("//mojo/public/tools/bindings/mojom.gni")
+
+mojom("mojo_bindings") {
+  sources = [ "donuts.mojom" ]
+  webui_module_path = "/"
+  use_typescript_sources = true
+}
+```
+
+#### Setting up C++ bindings
+The WebUIController class should inherit from ui::MojoWebUIController and
+from the PageHandlerFactory class defined in the mojom file.
+
+**chrome/browser/ui/webui/donuts/donuts_ui.h**
+```c++
+class DonutsPageHandler;
+
+class DonutsUI : public ui::MojoWebUIController,
+                 public donuts::mojom::PageHandlerFactory {
+ public:
+  explicit DonutsUI(content::WebUI* web_ui);
+
+  DonutsUI(const DonutsUI&) = delete;
+  DonutsUI& operator=(const DonutsUI&) = delete;
+
+  ~DonutsUI() override;
+
+  // Instantiates the implementor of the mojom::PageHandlerFactory mojo
+  // interface passing the pending receiver that will be internally bound.
+  void BindInterface(
+      mojo::PendingReceiver<donuts::mojom::PageHandlerFactory> receiver);
+
+ private:
+  // donuts::mojom::PageHandlerFactory:
+  void CreatePageHandler(
+      mojo::PendingRemote<donuts::mojom::Page> page,
+      mojo::PendingReceiver<donuts::mojom::PageHandler> receiver) override;
+
+  std::unique_ptr<DonutsPageHandler> page_handler_;
+
+  mojo::Receiver<donuts::mojom::PageHandlerFactory> page_factory_receiver_{
+      this};
+
+  WEB_UI_CONTROLLER_TYPE_DECL();
+};
+```
+
+**chrome/browser/ui/webui/donuts/donuts_ui.cc**
+```c++
+DonutsUI::DonutsUI(content::WebUI* web_ui)
+    : ui::MojoWebUIController(web_ui, true) {
+  // Normal constructor steps (e.g. setting up data source) go here.
+}
+
+WEB_UI_CONTROLLER_TYPE_IMPL(DonutsUI)
+
+DonutsUI::~DonutsUI() = default;
+
+void DonutsUI::BindInterface(
+    mojo::PendingReceiver<donuts::mojom::PageHandlerFactory> receiver) {
+  page_factory_receiver_.reset();
+  page_factory_receiver_.Bind(std::move(receiver));
+}
+
+void DonutsUI::CreatePageHandler(
+    mojo::PendingRemote<donuts::mojom::Page> page,
+    mojo::PendingReceiver<donuts::mojom::PageHandler> receiver) {
+  DCHECK(page);
+  page_handler_ = std::make_unique<DonutsPageHandler>(
+      std::move(receiver), std::move(page));
+}
+```
+
+You also need to register the PageHandlerFactory to your controller in
+**chrome/browser/chrome_browser_interface_binders.cc**:
+```c++
+RegisterWebUIControllerInterfaceBinder<donuts::mojom::PageHandlerFactory,
+                                       DonutsUI>(map);
+```
+
+#### Using C++ bindings for communication
+The WebUI message handler should inherit from the Mojo PageHandler class.
+
+**chrome/browser/ui/webui/donuts/donuts_page_handler.h**
+```c++
+#include "chrome/browser/ui/webui/donuts/donuts.mojom.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/remote.h"
+
+class DonutsPageHandler : public donuts::mojom::PageHandler {
+ public:
+  DonutsPageHandler(
+      mojo::PendingReceiver<donuts::mojom::PageHandler> receiver,
+      mojo::PendingRemote<donuts::mojom::Page> page);
+
+  DonutsPageHandler(const DonutsPageHandler&) = delete;
+  DonutsPageHandler& operator=(const DonutsPageHandler&) = delete;
+
+  ~DonutsPageHandler() override;
+
+  // Triggered by some outside event
+  void DonutsPageHandler::OnBakingDonutsFinished(uint32_t num_donuts);
+
+  // donuts::mojom::PageHandler:
+  void StartPilotLight() override;
+  void BakeDonuts(uint32_t num_donuts) override;
+  void GetNumberOfDonuts(GetNumberOfDonutsCallback callback) override;
+}
+```
+
+The message handler needs to implement all the methods on the PageHandler
+interface.
+
+**chrome/browser/ui/webui/donuts/donuts_page_handler.cc**
+```c++
+DonutsPageHandler::DonutsPageHandler(
+    mojo::PendingReceiver<donuts::mojom::PageHandler> receiver,
+    mojo::PendingRemote<donuts::mojom::Page> page)
+    : receiver_(this, std::move(receiver)),
+      page_(std::move(page)) {
+}
+
+DonutsPageHandler::~DonutsPageHandler() {
+  GetOven()->TurnOffGas();
+}
+
+// Triggered by outside asynchronous event; sends information to the renderer.
+void DonutsPageHandler::OnBakingDonutsFinished(uint32_t num_donuts) {
+  page_->DonutsBaked(num_donuts);
+}
+
+// Triggered by startPilotLight() call in TS.
+void DonutsPageHandler::StartPilotLight() {
+  GetOven()->StartPilotLight();
+}
+
+// Triggered by bakeDonuts() call in TS.
+void DonutsPageHandler::BakeDonuts(int32_t num_donuts) {
+  GetOven()->BakeDonuts();
+}
+
+// Triggered by getNumberOfDonuts() call in TS; sends a response back to the
+// renderer.
+void DonutsPageHandler::GetNumberOfDonuts(GetNumberOfDonutsCallback callback) {
+  uint32_t result = GetOven()->GetNumberOfDonuts();
+  std::move(callback).Run(result);
+}
+```
+
+#### Setting Up TypeScript bindings
+
+For WebUIs using the `build_webui()` rule, the TypeScript mojo bindings can be
+added to the build and served from the root (e.g.
+`chrome://donuts/donuts.mojom-webui.js`) by adding the following arguments to
+`build_webui()`:
+
+**chrome/browser/resources/donuts/BUILD.gn**
+```
+build_webui("build") {
+  # ... Other arguments go here
+  mojo_files_deps =
+      [ "//chrome/browser/ui/webui/donuts:mojo_bindings_ts__generator" ]
+  mojo_files = [
+    "$root_gen_dir/chrome/browser/ui/webui/donuts/donuts.mojom-webui.ts",
+  ]
+  # ... Other arguments can go here
+}
+```
+
+It is often helpful to wrap the TypeScript side of Mojo setup in a BrowserProxy
+class:
+
+**chrome/browser/resources/donuts/browser_proxy.ts**
+```js
+import {PageCallbackRouter, PageHandlerFactory, PageHandlerInterface, PageHandlerRemote} from './donuts.mojom-webui.js';
+
+class BrowserProxy {
+  callbackRouter: PageCallbackRouter;
+  handler: PageHandlerInterface;
+
+  constructor() {
+    this.callbackRouter = new PageCallbackRouter();
+
+    this.handler = new PageHandlerRemote();
+
+    const factory = PageHandlerFactory.getRemote();
+    factory.createPageHandler(
+        this.callbackRouter.$.bindNewPipeAndPassRemote(),
+        (this.handler as PageHandlerRemote).$.bindNewPipeAndPassReceiver());
+  }
+
+  static getInstance(): BrowserProxy {
+    return instance || (instance = new BrowserProxy());
+  }
+
+  static setInstance(obj: BrowserProxy) {
+    instance = obj;
+  }
+}
+
+let instance: BrowserProxy|null = null;
+```
+
+#### Using TypeScript bindings for communication
+The `callbackRouter` (`PageCallbackRouter`) can be used to add listeners for
+asynchronous events sent from the browser.
+
+The `handler` (`PageHandlerRemote`) can be used to send messages from the
+renderer to the browser. For interface methods that require a browser response,
+calling the method returns a promise. The promise will be resolved with the
+response from the browser.
+
+**chrome/browser/resources/donuts/donuts.ts**
+```js
+import {BrowserProxy} from './browser_proxy.js';
+
+let numDonutsBaked: number = 0;
+
+window.onload = function() {
+  // Other page initialization steps go here
+  const proxy = BrowserProxy.getInstance();
+  // Tells the browser to start the pilot light.
+  proxy.handler.startPilotLight();
+  // Adds a listener for the asynchronous "donutsBaked" event.
+  proxy.callbackRouter.donutsBaked.addListener(
+    (numDonuts: number) => {
+      numDonutsBaked += numDonuts;
+    });
+};
+
+function CheckNumberOfDonuts() {
+  // Requests the number of donuts from the browser, and alerts with the
+  // response.
+  BrowserProxy.getInstance().handler.getNumberOfDonuts().then(
+      (numDonuts: number) => {
+        alert('Yay, there are ' + numDonuts + ' delicious donuts left!');
+      });
+}
+
+function BakeDonuts(numDonuts: number) {
+  // Tells the browser to bake |numDonuts| donuts.
+  BrowserProxy.getInstance().handler.bakeDonuts(numDonuts);
+}
+```
+
+### Pre-Mojo alternative: chrome.send()/WebUIMessageHandler
+Most Chrome WebUIs were added before the introduction of Mojo, and use the
+older style WebUIMessageHandler + chrome.send() pattern. The following sections
+detail the methods in WebUIMessageHandler and the corresponding communication
+methods in TypeScript/JavaScript and how to use them.
+
+#### WebUIMessageHandler::AllowJavascript()
 
 A tab that has been used for settings UI may be reloaded, or may navigate to an
 external origin. In both cases, one does not want callbacks from C++ to
@@ -474,7 +775,7 @@
 custom <code>'initialized'</code> message is often necessary.
 </div>
 
-### WebUIMessageHandler::CallJavascriptFunction()
+#### WebUIMessageHandler::CallJavascriptFunction()
 
 When the browser process needs to tell the renderer/JS of an event or otherwise
 execute code, it can use `CallJavascriptFunction()`.
@@ -519,7 +820,7 @@
   when Javascript requires a response to an inquiry about C++-canonical state
   (i.e. "Is Autofill enabled?", "Is the user incognito?")
 
-### WebUIMessageHandler::FireWebUIListener()
+#### WebUIMessageHandler::FireWebUIListener()
 
 `FireWebUIListener()` is used to notify a registered set of listeners that an
 event has occurred. This is generally used for events that are not guaranteed to
@@ -547,7 +848,7 @@
 [`sendWithPromise()`](#sendWithPromise) and
 [`ResolveJavascriptCallback`](#ResolveJavascriptCallback).
 
-### WebUIMessageHandler::OnJavascriptAllowed()
+#### WebUIMessageHandler::OnJavascriptAllowed()
 
 `OnJavascriptDisallowed()` is a lifecycle method called in response to
 [`AllowJavascript()`](#AllowJavascript). It is a good place to register
@@ -589,7 +890,7 @@
 document has loaded enough to signal to the C++ that it's ready to respond to
 messages.
 
-### WebUIMessageHandler::OnJavascriptDisallowed()
+#### WebUIMessageHandler::OnJavascriptDisallowed()
 
 `OnJavascriptDisallowed` is a lifecycle method called when it's unclear whether
 it's safe to send JavaScript messsages to the renderer.
@@ -623,7 +924,7 @@
 scoped observer that automatically unsubscribes on destruction but can also
 imperatively unsubscribe in `OnJavascriptDisallowed()`.
 
-### WebUIMessageHandler::RejectJavascriptCallback()
+#### WebUIMessageHandler::RejectJavascriptCallback()
 
 This method is called in response to
 [`sendWithPromise()`](#sendWithPromise) to reject the issued Promise. This
@@ -654,7 +955,7 @@
 
 See also: [`ResolveJavascriptCallback`](#ResolveJavascriptCallback)
 
-### WebUIMessageHandler::ResolveJavascriptCallback()
+#### WebUIMessageHandler::ResolveJavascriptCallback()
 
 This method is called in response to
 [`sendWithPromise()`](#sendWithPromise) to fulfill an issued Promise,
@@ -666,7 +967,7 @@
 So, given this TypeScript code:
 
 ```js
-sendWithPromise('bakeDonuts').then(function(numDonutsBaked: number) {
+sendWithPromise('bakeDonuts', [5]).then(function(numDonutsBaked: number) {
   shop.donuts += numDonutsBaked;
 });
 ```
@@ -681,9 +982,7 @@
 }
 ```
 
-## Renderer (JS) &rarr; Browser (C++)
-
-### chrome.send()
+#### chrome.send()
 
 When the JavaScript `window` object is created, a renderer is checked for [WebUI
 bindings](#bindings).
@@ -734,7 +1033,7 @@
 message_callbacks_.find(message)->second.Run(&args);
 ```
 
-### addWebUiListener()
+#### addWebUiListener()
 
 WebUI listeners are a convenient way for C++ to inform JavaScript of events.
 
@@ -747,7 +1046,7 @@
 Adding WebUI listeners creates and inserts a unique ID into a map in JavaScript,
 just like [sendWithPromise()](#sendWithPromise).
 
-addWebUiListener can be imported from 'chrome://resources/js/cr.m.js'.
+addWebUiListener can be imported from 'chrome://resources/js/cr.js'.
 
 ```js
 // addWebUiListener():
@@ -785,7 +1084,7 @@
 });
 ```
 
-### sendWithPromise()
+#### sendWithPromise()
 
 `sendWithPromise()` is a wrapper around `chrome.send()`. It's used when
 triggering a message requires a response:
diff --git a/extensions/browser/event_router_unittest.cc b/extensions/browser/event_router_unittest.cc
index 7fed31b..81b12c6ae 100644
--- a/extensions/browser/event_router_unittest.cc
+++ b/extensions/browser/event_router_unittest.cc
@@ -27,7 +27,6 @@
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/mojom/service_worker/service_worker_database.mojom-blink-forward.h"
 
-using base::DictionaryValue;
 using base::Value;
 
 namespace extensions {
@@ -118,13 +117,12 @@
 scoped_refptr<const Extension> CreateExtension(bool component,
                                                bool persistent) {
   ExtensionBuilder builder;
-  std::unique_ptr<base::DictionaryValue> manifest =
-      std::make_unique<base::DictionaryValue>();
-  manifest->SetStringKey("name", "foo");
-  manifest->SetStringKey("version", "1.0.0");
-  manifest->SetIntKey("manifest_version", 2);
-  manifest->SetStringPath("background.page", "background.html");
-  manifest->SetBoolPath("background.persistent", persistent);
+  base::Value::Dict manifest;
+  manifest.Set("name", "foo");
+  manifest.Set("version", "1.0.0");
+  manifest.Set("manifest_version", 2);
+  manifest.SetByDottedPath("background.page", "background.html");
+  manifest.SetByDottedPath("background.persistent", persistent);
   builder.SetManifest(std::move(manifest));
   if (component)
     builder.SetLocation(mojom::ManifestLocation::kComponent);
@@ -134,11 +132,11 @@
 
 scoped_refptr<const Extension> CreateServiceWorkerExtension() {
   ExtensionBuilder builder;
-  auto manifest = std::make_unique<base::DictionaryValue>();
-  manifest->SetStringKey("name", "foo");
-  manifest->SetStringKey("version", "1.0.0");
-  manifest->SetIntKey("manifest_version", 2);
-  manifest->SetStringPath("background.service_worker", "worker.js");
+  base::Value::Dict manifest;
+  manifest.Set("name", "foo");
+  manifest.Set("version", "1.0.0");
+  manifest.Set("manifest_version", 2);
+  manifest.SetByDottedPath("background.service_worker", "worker.js");
   builder.SetManifest(std::move(manifest));
   return builder.Build();
 }
@@ -607,7 +605,7 @@
                              .Set("name", "Test app")
                              .Set("version", "1.0")
                              .Set("manifest_version", 2)
-                             .Build())
+                             .BuildDict())
             .Build();
     ExtensionRegistry::Get(browser_context())->AddEnabled(extension);
   };
diff --git a/extensions/browser/events/lazy_event_dispatch_util.cc b/extensions/browser/events/lazy_event_dispatch_util.cc
index d97f2fd..6e289f4f 100644
--- a/extensions/browser/events/lazy_event_dispatch_util.cc
+++ b/extensions/browser/events/lazy_event_dispatch_util.cc
@@ -107,16 +107,16 @@
   // |pending_on_install_info| currently only contains a version string. Instead
   // of making the pref hold a plain string, we store it as a dictionary value
   // so that we can add more stuff to it in the future if necessary.
-  auto pending_on_install_info = std::make_unique<base::DictionaryValue>();
+  base::Value::Dict pending_on_install_info;
   base::Version previous_version = ExtensionRegistry::Get(browser_context_)
                                        ->GetStoredVersion(extension->id());
-  pending_on_install_info->SetStringKey(kPrefPreviousVersion,
-                                        previous_version.IsValid()
-                                            ? previous_version.GetString()
-                                            : std::string());
-  prefs->UpdateExtensionPref(extension->id(),
-                             kPrefPendingOnInstalledEventDispatchInfo,
-                             std::move(pending_on_install_info));
+  pending_on_install_info.Set(kPrefPreviousVersion,
+                              previous_version.IsValid()
+                                  ? previous_version.GetString()
+                                  : std::string());
+  prefs->UpdateExtensionPref(
+      extension->id(), kPrefPendingOnInstalledEventDispatchInfo,
+      std::make_unique<base::Value>(std::move(pending_on_install_info)));
 }
 
 }  // namespace extensions
diff --git a/extensions/browser/extension_action_manager_unittest.cc b/extensions/browser/extension_action_manager_unittest.cc
index d4b96ba..bd64547 100644
--- a/extensions/browser/extension_action_manager_unittest.cc
+++ b/extensions/browser/extension_action_manager_unittest.cc
@@ -62,9 +62,9 @@
           .SetManifestKey("icons", DictionaryBuilder()
                                        .Set("48", "icon48.png")
                                        .Set("128", "icon128.png")
-                                       .Build())
+                                       .BuildDict())
           .SetManifestKey(ActionInfo::GetManifestKeyForActionType(GetParam()),
-                          std::make_unique<base::DictionaryValue>())
+                          base::Value::Dict())
           .Build();
 
   ASSERT_TRUE(extension);
@@ -84,7 +84,7 @@
   scoped_refptr<const Extension> extension =
       ExtensionBuilder("Test Extension")
           .SetManifestKey(ActionInfo::GetManifestKeyForActionType(GetParam()),
-                          std::make_unique<base::DictionaryValue>())
+                          base::Value::Dict())
           .Build();
 
   ASSERT_TRUE(extension);
@@ -102,15 +102,15 @@
 TEST_P(ExtensionActionManagerTest, TestDontOverrideIfDefaultsProvided) {
   scoped_refptr<const Extension> extension =
       ExtensionBuilder("Test Extension")
-          .SetManifestKey("icons",
-                          DictionaryBuilder().Set("24", "icon24.png").Build())
+          .SetManifestKey(
+              "icons", DictionaryBuilder().Set("24", "icon24.png").BuildDict())
           .SetManifestKey(
               ActionInfo::GetManifestKeyForActionType(GetParam()),
               DictionaryBuilder()
                   .Set("default_icon",
-                       DictionaryBuilder().Set("19", "icon19.png").Build())
+                       DictionaryBuilder().Set("19", "icon19.png").BuildDict())
                   .Set("default_title", "Action!")
-                  .Build())
+                  .BuildDict())
           .Build();
 
   ASSERT_TRUE(extension);
diff --git a/extensions/browser/extension_creator.cc b/extensions/browser/extension_creator.cc
index de7b816..7cfe286 100644
--- a/extensions/browser/extension_creator.cc
+++ b/extensions/browser/extension_creator.cc
@@ -107,9 +107,6 @@
   if (run_flags & kRequireModernManifestVersion)
     create_flags |= Extension::REQUIRE_MODERN_MANIFEST_VERSION;
 
-  if (run_flags & kBookmarkApp)
-    create_flags |= Extension::FROM_BOOKMARK;
-
   scoped_refptr<Extension> extension(file_util::LoadExtension(
       extension_dir, extension_id,
       run_flags & kSystemApp ? mojom::ManifestLocation::kExternalComponent
diff --git a/extensions/browser/extension_creator.h b/extensions/browser/extension_creator.h
index afc95f4..234a2392 100644
--- a/extensions/browser/extension_creator.h
+++ b/extensions/browser/extension_creator.h
@@ -40,7 +40,8 @@
     kNoRunFlags = 0,
     kOverwriteCRX = 1 << 0,
     kRequireModernManifestVersion = 1 << 1,
-    kBookmarkApp = 1 << 2,
+    // DEPRECATED.
+    // kBookmarkApp = 1 << 2,
     kSystemApp = 1 << 3,
   };
 
diff --git a/extensions/browser/extension_prefs.cc b/extensions/browser/extension_prefs.cc
index 9bdacd5..a151352 100644
--- a/extensions/browser/extension_prefs.cc
+++ b/extensions/browser/extension_prefs.cc
@@ -2180,8 +2180,6 @@
 
   MigrateToNewExternalUninstallPref();
 
-  MigrateYoutubeOffBookmarkApps();
-
   MigrateDeprecatedDisableReasons();
 }
 
@@ -2518,23 +2516,6 @@
   }
 }
 
-void ExtensionPrefs::MigrateYoutubeOffBookmarkApps() {
-  const base::Value::Dict& extensions_dictionary =
-      prefs_->GetDict(pref_names::kExtensions);
-  const base::Value::Dict* youtube_dictionary =
-      extensions_dictionary.FindDict(extension_misc::kYoutubeAppId);
-  if (!youtube_dictionary) {
-    return;
-  }
-  int creation_flags =
-      youtube_dictionary->FindInt(kPrefCreationFlags).value_or(0);
-  if ((creation_flags & Extension::FROM_BOOKMARK) == 0)
-    return;
-  ScopedExtensionPrefUpdate update(prefs_, extension_misc::kYoutubeAppId);
-  creation_flags &= ~Extension::FROM_BOOKMARK;
-  update->SetInteger(kPrefCreationFlags, creation_flags);
-}
-
 void ExtensionPrefs::MigrateObsoleteExtensionPrefs() {
   const base::Value::Dict& extensions_dictionary =
       prefs_->GetDict(pref_names::kExtensions);
diff --git a/extensions/browser/extension_prefs.h b/extensions/browser/extension_prefs.h
index 77059027..dfca56a 100644
--- a/extensions/browser/extension_prefs.h
+++ b/extensions/browser/extension_prefs.h
@@ -752,11 +752,6 @@
   // TODO(archanasimha): Remove this around M89.
   void MigrateDeprecatedDisableReasons();
 
-  // Looks to see if the Youtube extension is installed, and removes the
-  // FROM_BOOKMARK flag from it's creation flags.
-  // TODO(dmurph): Remove this in m90.
-  void MigrateYoutubeOffBookmarkApps();
-
   // Iterates over the extension pref entries and removes any obsolete keys. We
   // need to do this here specially (rather than in
   // MigrateObsoleteProfilePrefs()) because these entries are subkeys of the
diff --git a/extensions/browser/file_highlighter.cc b/extensions/browser/file_highlighter.cc
index c4dc097..d31e6621 100644
--- a/extensions/browser/file_highlighter.cc
+++ b/extensions/browser/file_highlighter.cc
@@ -6,17 +6,11 @@
 
 #include "base/check_op.h"
 #include "base/containers/stack.h"
-#include "base/values.h"
 
 namespace extensions {
 
 namespace {
 
-// Keys for a highlighted dictionary.
-const char kBeforeHighlightKey[] = "beforeHighlight";
-const char kHighlightKey[] = "highlight";
-const char kAfterHighlightKey[] = "afterHighlight";
-
 // Increment |index| to the position of the next quote ('"') in |str|, skipping
 // over any escaped quotes. If no next quote is found, |index| is set to
 // std::string::npos. Assumes |index| currently points to a quote.
@@ -113,20 +107,6 @@
   return contents_.substr(end_);
 }
 
-void FileHighlighter::SetHighlightedRegions(base::DictionaryValue* dict) const {
-  std::string before_feature = GetBeforeFeature();
-  if (!before_feature.empty())
-    dict->SetStringKey(kBeforeHighlightKey, before_feature);
-
-  std::string feature = GetFeature();
-  if (!feature.empty())
-    dict->SetStringKey(kHighlightKey, feature);
-
-  std::string after_feature = GetAfterFeature();
-  if (!after_feature.empty())
-    dict->SetStringKey(kAfterHighlightKey, after_feature);
-}
-
 ManifestHighlighter::ManifestHighlighter(const std::string& manifest,
                                          const std::string& key,
                                          const std::string& specific)
diff --git a/extensions/browser/file_highlighter.h b/extensions/browser/file_highlighter.h
index b7c39c8..bfd2e4baf 100644
--- a/extensions/browser/file_highlighter.h
+++ b/extensions/browser/file_highlighter.h
@@ -9,10 +9,6 @@
 
 #include <string>
 
-namespace base {
-class DictionaryValue;
-}
-
 namespace extensions {
 
 // The FileHighlighter class is used in order to isolate and highlight a portion
@@ -39,10 +35,6 @@
   // after the feature.
   std::string GetAfterFeature() const;
 
-  // Populate a DictionaryValue with the highlighted portions (in UTF16) of the
-  // source file.
-  void SetHighlightedRegions(base::DictionaryValue* dict) const;
-
  protected:
   explicit FileHighlighter(const std::string& contents);
 
diff --git a/extensions/browser/file_highlighter_fuzzer.cc b/extensions/browser/file_highlighter_fuzzer.cc
index 10d2223..a95b316c 100644
--- a/extensions/browser/file_highlighter_fuzzer.cc
+++ b/extensions/browser/file_highlighter_fuzzer.cc
@@ -34,11 +34,6 @@
   CHECK_EQ(highlighter->GetBeforeFeature() + highlighter->GetFeature() +
                highlighter->GetAfterFeature(),
            contents);
-  if (base::IsStringUTF8(contents)) {
-    // `base::Value` only supports valid UTF-8 strings.
-    base::DictionaryValue dict;
-    highlighter->SetHighlightedRegions(&dict);
-  }
 
   return 0;
 }
diff --git a/extensions/browser/guest_view/web_view/javascript_dialog_helper.cc b/extensions/browser/guest_view/web_view/javascript_dialog_helper.cc
index f72bdeb..ae215773 100644
--- a/extensions/browser/guest_view/web_view/javascript_dialog_helper.cc
+++ b/extensions/browser/guest_view/web_view/javascript_dialog_helper.cc
@@ -51,18 +51,18 @@
     const std::u16string& default_prompt_text,
     DialogClosedCallback callback,
     bool* did_suppress_message) {
-  base::DictionaryValue request_info;
-  request_info.SetStringKey(webview::kDefaultPromptText, default_prompt_text);
-  request_info.SetStringKey(webview::kMessageText, message_text);
-  request_info.SetStringKey(webview::kMessageType,
-                            JavaScriptDialogTypeToString(dialog_type));
-  request_info.SetStringKey(guest_view::kUrl,
-                            render_frame_host->GetLastCommittedURL().spec());
+  base::Value::Dict request_info;
+  request_info.Set(webview::kDefaultPromptText, default_prompt_text);
+  request_info.Set(webview::kMessageText, message_text);
+  request_info.Set(webview::kMessageType,
+                   JavaScriptDialogTypeToString(dialog_type));
+  request_info.Set(guest_view::kUrl,
+                   render_frame_host->GetLastCommittedURL().spec());
 
   WebViewPermissionHelper* web_view_permission_helper =
       web_view_guest_->web_view_permission_helper();
   web_view_permission_helper->RequestPermission(
-      WEB_VIEW_PERMISSION_TYPE_JAVASCRIPT_DIALOG, request_info,
+      WEB_VIEW_PERMISSION_TYPE_JAVASCRIPT_DIALOG, std::move(request_info),
       base::BindOnce(&JavaScriptDialogHelper::OnPermissionResponse,
                      weak_factory_.GetWeakPtr(), std::move(callback)),
       false /* allowed_by_default */);
diff --git a/extensions/browser/guest_view/web_view/web_view_guest.cc b/extensions/browser/guest_view/web_view/web_view_guest.cc
index 2ab1953e..91d5e3cb 100644
--- a/extensions/browser/guest_view/web_view/web_view_guest.cc
+++ b/extensions/browser/guest_view/web_view/web_view_guest.cc
@@ -1431,12 +1431,12 @@
     content::RenderFrameHost* requesting_frame,
     const blink::mojom::FullscreenOptions& options) {
   // Ask the embedder for permission.
-  base::DictionaryValue request_info;
+  base::Value::Dict request_info;
   const GURL& origin =
       requesting_frame->GetLastCommittedURL().DeprecatedGetOriginAsURL();
-  request_info.SetStringKey(webview::kOrigin, origin.spec());
+  request_info.Set(webview::kOrigin, origin.spec());
   web_view_permission_helper_->RequestPermission(
-      WEB_VIEW_PERMISSION_TYPE_FULLSCREEN, request_info,
+      WEB_VIEW_PERMISSION_TYPE_FULLSCREEN, std::move(request_info),
       base::BindOnce(&WebViewGuest::OnFullscreenPermissionDecided,
                      weak_ptr_factory_.GetWeakPtr()),
       false /* allowed_by_default */);
@@ -1540,23 +1540,23 @@
 
   const int guest_instance_id = new_guest->guest_instance_id();
 
-  base::DictionaryValue request_info;
-  request_info.SetIntKey(webview::kInitialHeight, initial_bounds.height());
-  request_info.SetIntKey(webview::kInitialWidth, initial_bounds.width());
-  request_info.SetStringKey(webview::kTargetURL, new_window_info.url.spec());
-  request_info.SetStringKey(webview::kName, new_window_info.name);
-  request_info.SetIntKey(webview::kWindowID, guest_instance_id);
+  base::Value::Dict request_info;
+  request_info.Set(webview::kInitialHeight, initial_bounds.height());
+  request_info.Set(webview::kInitialWidth, initial_bounds.width());
+  request_info.Set(webview::kTargetURL, new_window_info.url.spec());
+  request_info.Set(webview::kName, new_window_info.name);
+  request_info.Set(webview::kWindowID, guest_instance_id);
   // We pass in partition info so that window-s created through newwindow
   // API can use it to set their partition attribute.
-  request_info.SetStringKey(webview::kStoragePartitionId, storage_partition_id);
-  request_info.SetStringKey(webview::kWindowOpenDisposition,
-                            WindowOpenDispositionToString(disposition));
+  request_info.Set(webview::kStoragePartitionId, storage_partition_id);
+  request_info.Set(webview::kWindowOpenDisposition,
+                   WindowOpenDispositionToString(disposition));
 
   GuestViewManager::FromBrowserContext(browser_context())
       ->ManageOwnership(std::move(new_guest));
 
   web_view_permission_helper_->RequestPermission(
-      WEB_VIEW_PERMISSION_TYPE_NEW_WINDOW, request_info,
+      WEB_VIEW_PERMISSION_TYPE_NEW_WINDOW, std::move(request_info),
       base::BindOnce(&WebViewGuest::OnWebViewNewWindowResponse,
                      weak_ptr_factory_.GetWeakPtr(), guest_instance_id),
       false /* allowed_by_default */);
diff --git a/extensions/browser/guest_view/web_view/web_view_permission_helper.cc b/extensions/browser/guest_view/web_view/web_view_permission_helper.cc
index 8fd08e8..55b5d03 100644
--- a/extensions/browser/guest_view/web_view/web_view_permission_helper.cc
+++ b/extensions/browser/guest_view/web_view/web_view_permission_helper.cc
@@ -179,10 +179,10 @@
     content::WebContents* source,
     const content::MediaStreamRequest& request,
     content::MediaResponseCallback callback) {
-  base::DictionaryValue request_info;
-  request_info.SetStringKey(guest_view::kUrl, request.security_origin.spec());
+  base::Value::Dict request_info;
+  request_info.Set(guest_view::kUrl, request.security_origin.spec());
   RequestPermission(
-      WEB_VIEW_PERMISSION_TYPE_MEDIA, request_info,
+      WEB_VIEW_PERMISSION_TYPE_MEDIA, std::move(request_info),
       base::BindOnce(&WebViewPermissionHelper::OnMediaPermissionResponse,
                      weak_factory_.GetWeakPtr(), request, std::move(callback)),
       default_media_access_permission_);
@@ -266,7 +266,7 @@
 
 int WebViewPermissionHelper::RequestPermission(
     WebViewPermissionType permission_type,
-    const base::DictionaryValue& request_info,
+    base::Value::Dict request_info,
     PermissionResponseCallback callback,
     bool allowed_by_default) {
   // If there are too many pending permission requests then reject this request.
@@ -286,7 +286,7 @@
   pending_permission_requests_[request_id] = PermissionResponseInfo(
       std::move(callback), permission_type, allowed_by_default);
   base::Value::Dict args;
-  args.Set(webview::kRequestInfo, request_info.Clone());
+  args.Set(webview::kRequestInfo, std::move(request_info));
   args.Set(webview::kRequestId, request_id);
   switch (permission_type) {
     case WEB_VIEW_PERMISSION_TYPE_NEW_WINDOW: {
diff --git a/extensions/browser/guest_view/web_view/web_view_permission_helper.h b/extensions/browser/guest_view/web_view/web_view_permission_helper.h
index 98d2e40..2c08f89 100644
--- a/extensions/browser/guest_view/web_view/web_view_permission_helper.h
+++ b/extensions/browser/guest_view/web_view/web_view_permission_helper.h
@@ -10,6 +10,7 @@
 
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
+#include "base/values.h"
 #include "components/guest_view/common/guest_view_constants.h"
 #include "content/public/browser/media_stream_request.h"
 #include "content/public/browser/web_contents.h"
@@ -17,10 +18,6 @@
 #include "ppapi/buildflags/buildflags.h"
 #include "third_party/blink/public/common/mediastream/media_stream_request.h"
 
-namespace base {
-class DictionaryValue;
-}
-
 namespace extensions {
 
 class WebViewGuest;
@@ -58,7 +55,7 @@
   using RequestMap = std::map<int, PermissionResponseInfo>;
 
   int RequestPermission(WebViewPermissionType permission_type,
-                        const base::DictionaryValue& request_info,
+                        base::Value::Dict request_info,
                         PermissionResponseCallback callback,
                         bool allowed_by_default);
 
diff --git a/extensions/browser/renderer_startup_helper_unittest.cc b/extensions/browser/renderer_startup_helper_unittest.cc
index 76b629c..9c9a036 100644
--- a/extensions/browser/renderer_startup_helper_unittest.cc
+++ b/extensions/browser/renderer_startup_helper_unittest.cc
@@ -206,44 +206,43 @@
   }
 
   scoped_refptr<const Extension> CreateExtension(const std::string& id_input) {
-    std::unique_ptr<base::DictionaryValue> manifest =
-        DictionaryBuilder()
-            .Set("name", "extension")
-            .Set("description", "an extension")
-            .Set("manifest_version", 2)
-            .Set("version", "0.1")
-            .Build();
+    base::Value::Dict manifest = DictionaryBuilder()
+                                     .Set("name", "extension")
+                                     .Set("description", "an extension")
+                                     .Set("manifest_version", 2)
+                                     .Set("version", "0.1")
+                                     .BuildDict();
     return CreateExtension(id_input, std::move(manifest));
   }
 
   scoped_refptr<const Extension> CreateTheme(const std::string& id_input) {
-    std::unique_ptr<base::DictionaryValue> manifest =
+    base::Value::Dict manifest =
         DictionaryBuilder()
             .Set("name", "theme")
             .Set("description", "a theme")
-            .Set("theme", DictionaryBuilder().Build())
+            .Set("theme", DictionaryBuilder().BuildDict())
             .Set("manifest_version", 2)
             .Set("version", "0.1")
-            .Build();
+            .BuildDict();
     return CreateExtension(id_input, std::move(manifest));
   }
 
   scoped_refptr<const Extension> CreatePlatformApp(
       const std::string& id_input) {
-    std::unique_ptr<base::Value> background =
+    base::Value::Dict background =
         DictionaryBuilder()
-            .Set("scripts", ListBuilder().Append("background.js").Build())
-            .Build();
-    std::unique_ptr<base::DictionaryValue> manifest =
+            .Set("scripts", ListBuilder().Append("background.js").BuildList())
+            .BuildDict();
+    base::Value::Dict manifest =
         DictionaryBuilder()
             .Set("name", "platform_app")
             .Set("description", "a platform app")
             .Set("app", DictionaryBuilder()
                             .Set("background", std::move(background))
-                            .Build())
+                            .BuildDict())
             .Set("manifest_version", 2)
             .Set("version", "0.1")
-            .Build();
+            .BuildDict();
     return CreateExtension(id_input, std::move(manifest));
   }
 
@@ -284,9 +283,8 @@
   scoped_refptr<const Extension> extension_;
 
  private:
-  scoped_refptr<const Extension> CreateExtension(
-      const std::string& id_input,
-      std::unique_ptr<base::DictionaryValue> manifest) {
+  scoped_refptr<const Extension> CreateExtension(const std::string& id_input,
+                                                 base::Value::Dict manifest) {
     return ExtensionBuilder()
         .SetManifest(std::move(manifest))
         .SetID(crx_file::id_util::GenerateId(id_input))
diff --git a/extensions/browser/service_worker_task_queue.cc b/extensions/browser/service_worker_task_queue.cc
index dc7c77b..32ee2d7 100644
--- a/extensions/browser/service_worker_task_queue.cc
+++ b/extensions/browser/service_worker_task_queue.cc
@@ -602,11 +602,11 @@
   if (browser_context_->IsOffTheRecord()) {
     off_the_record_registrations_[extension_id] = version;
   } else {
-    auto info = std::make_unique<base::DictionaryValue>();
-    info->SetStringKey(kServiceWorkerVersion, version.GetString());
+    base::Value::Dict info;
+    info.Set(kServiceWorkerVersion, version.GetString());
     ExtensionPrefs::Get(browser_context_)
         ->UpdateExtensionPref(extension_id, kPrefServiceWorkerRegistrationInfo,
-                              std::move(info));
+                              std::make_unique<base::Value>(std::move(info)));
   }
 }
 
diff --git a/extensions/common/api/bluetooth/bluetooth_manifest_data.cc b/extensions/common/api/bluetooth/bluetooth_manifest_data.cc
index e88b9f1..fc76abf 100644
--- a/extensions/common/api/bluetooth/bluetooth_manifest_data.cc
+++ b/extensions/common/api/bluetooth/bluetooth_manifest_data.cc
@@ -18,7 +18,7 @@
   DCHECK(permission_);
 }
 
-BluetoothManifestData::~BluetoothManifestData() {}
+BluetoothManifestData::~BluetoothManifestData() = default;
 
 // static
 BluetoothManifestData* BluetoothManifestData::Get(const Extension* extension) {
@@ -72,6 +72,6 @@
     const std::string& uuid)
     : uuid(uuid) {}
 
-BluetoothPermissionRequest::~BluetoothPermissionRequest() {}
+BluetoothPermissionRequest::~BluetoothPermissionRequest() = default;
 
 }  // namespace extensions
diff --git a/extensions/common/api/extension_action/action_info.cc b/extensions/common/api/extension_action/action_info.cc
index 1c629a72a..dfdf0656 100644
--- a/extensions/common/api/extension_action/action_info.cc
+++ b/extensions/common/api/extension_action/action_info.cc
@@ -37,7 +37,7 @@
 ActionInfoData::ActionInfoData(std::unique_ptr<ActionInfo> info)
     : action_info(std::move(info)) {}
 
-ActionInfoData::~ActionInfoData() {}
+ActionInfoData::~ActionInfoData() = default;
 
 }  // namespace
 
diff --git a/extensions/common/api/printer_provider/usb_printer_manifest_unittest.cc b/extensions/common/api/printer_provider/usb_printer_manifest_unittest.cc
index c4c2110c..648fe24 100644
--- a/extensions/common/api/printer_provider/usb_printer_manifest_unittest.cc
+++ b/extensions/common/api/printer_provider/usb_printer_manifest_unittest.cc
@@ -4,15 +4,14 @@
 
 #include "extensions/common/api/printer_provider/usb_printer_manifest_data.h"
 #include "extensions/common/manifest_test.h"
-#include "extensions/common/value_builder.h"
 #include "services/device/public/mojom/usb_enumeration_options.mojom.h"
 
 namespace extensions {
 
 class UsbPrinterManifestTest : public ManifestTest {
  public:
-  UsbPrinterManifestTest() {}
-  ~UsbPrinterManifestTest() override {}
+  UsbPrinterManifestTest() = default;
+  ~UsbPrinterManifestTest() override = default;
 };
 
 TEST_F(UsbPrinterManifestTest, Filters) {
diff --git a/extensions/common/api/sockets/sockets_manifest_handler.cc b/extensions/common/api/sockets/sockets_manifest_handler.cc
index 4a57436d..fa18aa4 100644
--- a/extensions/common/api/sockets/sockets_manifest_handler.cc
+++ b/extensions/common/api/sockets/sockets_manifest_handler.cc
@@ -11,9 +11,9 @@
 
 namespace extensions {
 
-SocketsManifestHandler::SocketsManifestHandler() {}
+SocketsManifestHandler::SocketsManifestHandler() = default;
 
-SocketsManifestHandler::~SocketsManifestHandler() {}
+SocketsManifestHandler::~SocketsManifestHandler() = default;
 
 bool SocketsManifestHandler::Parse(Extension* extension,
                                    std::u16string* error) {
diff --git a/extensions/common/command.cc b/extensions/common/command.cc
index 08dcb30..df15399 100644
--- a/extensions/common/command.cc
+++ b/extensions/common/command.cc
@@ -270,7 +270,7 @@
 
 Command::Command(const Command& other) = default;
 
-Command::~Command() {}
+Command::~Command() = default;
 
 // static
 std::string Command::CommandPlatform() {
diff --git a/extensions/common/core_extensions_api_provider.cc b/extensions/common/core_extensions_api_provider.cc
index 3f58613..ccead91 100644
--- a/extensions/common/core_extensions_api_provider.cc
+++ b/extensions/common/core_extensions_api_provider.cc
@@ -17,7 +17,7 @@
 
 namespace extensions {
 
-CoreExtensionsAPIProvider::CoreExtensionsAPIProvider() {}
+CoreExtensionsAPIProvider::CoreExtensionsAPIProvider() = default;
 CoreExtensionsAPIProvider::~CoreExtensionsAPIProvider() = default;
 
 void CoreExtensionsAPIProvider::AddAPIFeatures(FeatureProvider* provider) {
diff --git a/extensions/common/extension.cc b/extensions/common/extension.cc
index df8bc5b..48f57d8 100644
--- a/extensions/common/extension.cc
+++ b/extensions/common/extension.cc
@@ -254,12 +254,6 @@
     return nullptr;
   }
 
-  if ((flags & FROM_BOOKMARK) != 0) {
-    // Extension-based bookmark apps are no longer supported.
-    // They have been replaced by web apps.
-    return nullptr;
-  }
-
   std::unique_ptr<extensions::Manifest> manifest;
   if (flags & FOR_LOGIN_SCREEN) {
     manifest = Manifest::CreateManifestForLoginScreen(location, value.Clone(),
@@ -864,6 +858,6 @@
     extension_manifest = std::make_unique<base::Value::Dict>(manifest->Clone());
 }
 
-ExtensionInfo::~ExtensionInfo() {}
+ExtensionInfo::~ExtensionInfo() = default;
 
 }   // namespace extensions
diff --git a/extensions/common/extension.h b/extensions/common/extension.h
index 91698320..e4d1e08 100644
--- a/extensions/common/extension.h
+++ b/extensions/common/extension.h
@@ -95,10 +95,11 @@
     // Chrome Web Store.
     FROM_WEBSTORE = 1 << 3,
 
-    // |FROM_BOOKMARK| indicates the extension is a bookmark app which has been
-    // generated from a web page. Bookmark apps have no permissions or extent
-    // and launch the web page they are created from when run.
-    FROM_BOOKMARK = 1 << 4,
+    // DEPRECATED - |FROM_BOOKMARK| indicates the extension is a bookmark app
+    // which has been generated from a web page. Bookmark apps have no
+    // permissions or extent and launch the web page they are created from when
+    // run.
+    // FROM_BOOKMARK = 1 << 4,
 
     // |FOLLOW_SYMLINKS_ANYWHERE| means that resources can be symlinks to
     // anywhere in the filesystem, rather than being restricted to the
@@ -311,11 +312,6 @@
   }
   int creation_flags() const { return creation_flags_; }
   bool from_webstore() const { return (creation_flags_ & FROM_WEBSTORE) != 0; }
-  // TODO(crbug.com/1065748): Retire this function when there are no old
-  // entries.
-  bool from_deprecated_bookmark() const {
-    return (creation_flags_ & FROM_BOOKMARK) != 0;
-  }
   bool may_be_untrusted() const {
     return (creation_flags_ & MAY_BE_UNTRUSTED) != 0;
   }
diff --git a/extensions/common/extension_builder.cc b/extensions/common/extension_builder.cc
index bc9b698..3ef1ddf 100644
--- a/extensions/common/extension_builder.cc
+++ b/extensions/common/extension_builder.cc
@@ -131,7 +131,7 @@
   manifest_data_->type = type;
 }
 
-ExtensionBuilder::~ExtensionBuilder() {}
+ExtensionBuilder::~ExtensionBuilder() = default;
 
 ExtensionBuilder::ExtensionBuilder(ExtensionBuilder&& other) = default;
 ExtensionBuilder& ExtensionBuilder::operator=(ExtensionBuilder&& other) =
diff --git a/extensions/common/extension_icon_set.cc b/extensions/common/extension_icon_set.cc
index e5dcaef..5bf4ccd 100644
--- a/extensions/common/extension_icon_set.cc
+++ b/extensions/common/extension_icon_set.cc
@@ -10,11 +10,11 @@
 #include "base/files/file_path.h"
 #include "base/strings/string_util.h"
 
-ExtensionIconSet::ExtensionIconSet() {}
+ExtensionIconSet::ExtensionIconSet() = default;
 
 ExtensionIconSet::ExtensionIconSet(const ExtensionIconSet& other) = default;
 
-ExtensionIconSet::~ExtensionIconSet() {}
+ExtensionIconSet::~ExtensionIconSet() = default;
 
 void ExtensionIconSet::Clear() {
   map_.clear();
diff --git a/extensions/common/features/feature.cc b/extensions/common/features/feature.cc
index 47b67bd..0a5be7d 100644
--- a/extensions/common/features/feature.cc
+++ b/extensions/common/features/feature.cc
@@ -48,7 +48,7 @@
 
 Feature::Feature() : no_parent_(false) {}
 
-Feature::~Feature() {}
+Feature::~Feature() = default;
 
 void Feature::set_name(base::StringPiece name) {
   name_ = std::string(name);
diff --git a/extensions/common/manifest_handlers/extension_action_handler.cc b/extensions/common/manifest_handlers/extension_action_handler.cc
index aa0dee4..7f052a8 100644
--- a/extensions/common/manifest_handlers/extension_action_handler.cc
+++ b/extensions/common/manifest_handlers/extension_action_handler.cc
@@ -45,7 +45,7 @@
 
 ExtensionActionHandler::ExtensionActionHandler() = default;
 
-ExtensionActionHandler::~ExtensionActionHandler() {}
+ExtensionActionHandler::~ExtensionActionHandler() = default;
 
 bool ExtensionActionHandler::Parse(Extension* extension,
                                    std::u16string* error) {
diff --git a/extensions/common/manifest_handlers/extension_action_handler_unittest.cc b/extensions/common/manifest_handlers/extension_action_handler_unittest.cc
index f0b7d45b..c710e49 100644
--- a/extensions/common/manifest_handlers/extension_action_handler_unittest.cc
+++ b/extensions/common/manifest_handlers/extension_action_handler_unittest.cc
@@ -140,13 +140,13 @@
     : public ManifestTest,
       public testing::WithParamInterface<ActionInfo::Type> {
  public:
-  ExtensionActionManifestTest() {}
+  ExtensionActionManifestTest() = default;
 
   ExtensionActionManifestTest(const ExtensionActionManifestTest&) = delete;
   ExtensionActionManifestTest& operator=(const ExtensionActionManifestTest&) =
       delete;
 
-  ~ExtensionActionManifestTest() override {}
+  ~ExtensionActionManifestTest() override = default;
 
   // Constructs and returns a ManifestData object with the provided
   // |action_spec|.
diff --git a/extensions/common/manifest_handlers/externally_connectable_unittest.cc b/extensions/common/manifest_handlers/externally_connectable_unittest.cc
index 2312964..c45559a 100644
--- a/extensions/common/manifest_handlers/externally_connectable_unittest.cc
+++ b/extensions/common/manifest_handlers/externally_connectable_unittest.cc
@@ -23,8 +23,8 @@
 
 class ExternallyConnectableTest : public ManifestTest {
  public:
-  ExternallyConnectableTest() {}
-  ~ExternallyConnectableTest() override {}
+  ExternallyConnectableTest() = default;
+  ~ExternallyConnectableTest() override = default;
 
  protected:
   ExternallyConnectableInfo* GetExternallyConnectableInfo(
diff --git a/extensions/common/manifest_test.cc b/extensions/common/manifest_test.cc
index 74a8527..b0ea918 100644
--- a/extensions/common/manifest_test.cc
+++ b/extensions/common/manifest_test.cc
@@ -69,8 +69,7 @@
     : enable_apps_(true) {
 }
 
-ManifestTest::~ManifestTest() {
-}
+ManifestTest::~ManifestTest() = default;
 
 // Helper class that simplifies creating methods that take either a filename
 // to a manifest or the manifest itself.
@@ -82,6 +81,14 @@
     : name_(name), manifest_(std::move(manifest)) {
   CHECK(manifest_.is_dict()) << "Manifest must be a dictionary. " << name_;
 }
+
+ManifestTest::ManifestData::ManifestData(base::Value::Dict manifest,
+                                         base::StringPiece name)
+    : ManifestData(base::Value(std::move(manifest)), std::move(name)) {}
+
+ManifestTest::ManifestData::ManifestData(base::Value::Dict manifest)
+    : ManifestData(base::Value(std::move(manifest))) {}
+
 ManifestTest::ManifestData::ManifestData(base::Value manifest)
     : name_(GetNameFromManifest(manifest)), manifest_(std::move(manifest)) {
   CHECK(manifest_.is_dict()) << "Manifest must be a dictionary.";
diff --git a/extensions/common/manifest_test.h b/extensions/common/manifest_test.h
index eff2c07..02c8c6cb5 100644
--- a/extensions/common/manifest_test.h
+++ b/extensions/common/manifest_test.h
@@ -39,8 +39,12 @@
   class ManifestData {
    public:
     explicit ManifestData(base::StringPiece name);
+    // This is deprecated. Use the constructor that accepts base::Value::Dict.
     explicit ManifestData(base::Value manifest);
+    explicit ManifestData(base::Value::Dict manifest);
+    // This is deprecated. Use the constructor that accepts base::Value::Dict.
     ManifestData(base::Value manifest, base::StringPiece name);
+    ManifestData(base::Value::Dict manifest, base::StringPiece name);
     ManifestData(ManifestData&& other);
     ~ManifestData();
 
diff --git a/extensions/common/mojom/permission_set_mojom_traits_unittest.cc b/extensions/common/mojom/permission_set_mojom_traits_unittest.cc
index a75a2e4..dbc240d 100644
--- a/extensions/common/mojom/permission_set_mojom_traits_unittest.cc
+++ b/extensions/common/mojom/permission_set_mojom_traits_unittest.cc
@@ -19,10 +19,10 @@
 
 class MockManifestHandler : public ManifestHandler {
  public:
-  MockManifestHandler() {}
+  MockManifestHandler() = default;
   MockManifestHandler(const MockManifestHandler&) = delete;
   MockManifestHandler& operator=(const MockManifestHandler&) = delete;
-  ~MockManifestHandler() override {}
+  ~MockManifestHandler() override = default;
 
   bool Parse(Extension* extension, std::u16string* error) override {
     return true;
diff --git a/extensions/common/permissions/manifest_permission.cc b/extensions/common/permissions/manifest_permission.cc
index 0e69e351..e54e7ba 100644
--- a/extensions/common/permissions/manifest_permission.cc
+++ b/extensions/common/permissions/manifest_permission.cc
@@ -6,9 +6,9 @@
 
 namespace extensions {
 
-ManifestPermission::ManifestPermission() {}
+ManifestPermission::ManifestPermission() = default;
 
-ManifestPermission::~ManifestPermission() { }
+ManifestPermission::~ManifestPermission() = default;
 
 std::unique_ptr<ManifestPermission> ManifestPermission::Clone() const {
   return Union(this);
diff --git a/extensions/common/permissions/permission_set.cc b/extensions/common/permissions/permission_set.cc
index 9deb51b..5ecd79e 100644
--- a/extensions/common/permissions/permission_set.cc
+++ b/extensions/common/permissions/permission_set.cc
@@ -17,7 +17,7 @@
 
 namespace extensions {
 
-PermissionSet::PermissionSet() {}
+PermissionSet::PermissionSet() = default;
 PermissionSet::PermissionSet(APIPermissionSet apis,
                              ManifestPermissionSet manifest_permissions,
                              URLPatternSet explicit_hosts,
diff --git a/extensions/common/permissions/usb_device_permission_data.cc b/extensions/common/permissions/usb_device_permission_data.cc
index 8e7a669cf..337b2f8 100644
--- a/extensions/common/permissions/usb_device_permission_data.cc
+++ b/extensions/common/permissions/usb_device_permission_data.cc
@@ -46,7 +46,7 @@
 
 }  // namespace
 
-UsbDevicePermissionData::UsbDevicePermissionData() {}
+UsbDevicePermissionData::UsbDevicePermissionData() = default;
 
 UsbDevicePermissionData::UsbDevicePermissionData(int vendor_id,
                                                  int product_id,
diff --git a/extensions/common/user_script.cc b/extensions/common/user_script.cc
index c7d8d91..2b54cba 100644
--- a/extensions/common/user_script.cc
+++ b/extensions/common/user_script.cc
@@ -26,9 +26,10 @@
 
 bool UrlMatchesGlobs(const std::vector<std::string>* globs,
                      const GURL& url) {
-  for (auto glob = globs->cbegin(); glob != globs->cend(); ++glob) {
-    if (base::MatchPattern(url.spec(), *glob))
+  for (const auto& glob : *globs) {
+    if (base::MatchPattern(url.spec(), glob)) {
       return true;
+    }
   }
 
   return false;
@@ -91,7 +92,7 @@
       url_(url) {
 }
 
-UserScript::File::File() {}
+UserScript::File::File() = default;
 
 // File content is not copied.
 UserScript::File::File(const File& other)
diff --git a/fuchsia_web/webengine/BUILD.gn b/fuchsia_web/webengine/BUILD.gn
index 82203c4..85adb8d9 100644
--- a/fuchsia_web/webengine/BUILD.gn
+++ b/fuchsia_web/webengine/BUILD.gn
@@ -317,6 +317,7 @@
     "browser/navigation_policy_throttle.h",
     "browser/theme_manager.cc",
     "browser/theme_manager.h",
+    "browser/trace_event.h",
     "browser/url_request_rewrite_type_converters.cc",
     "browser/url_request_rewrite_type_converters.h",
     "browser/web_engine_browser_context.cc",
diff --git a/fuchsia_web/webengine/browser/context_impl.cc b/fuchsia_web/webengine/browser/context_impl.cc
index cd63412..921a47f 100644
--- a/fuchsia_web/webengine/browser/context_impl.cc
+++ b/fuchsia_web/webengine/browser/context_impl.cc
@@ -15,15 +15,19 @@
 #include "base/fuchsia/koid.h"
 #include "base/fuchsia/mem_buffer_util.h"
 #include "base/strings/stringprintf.h"
+#include "base/trace_event/trace_event.h"
+#include "base/trace_event/typed_macros.h"
 #include "build/chromecast_buildflags.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/storage_partition.h"
 #include "content/public/browser/web_contents.h"
 #include "fuchsia_web/webengine/browser/frame_impl.h"
+#include "fuchsia_web/webengine/browser/trace_event.h"
 #include "fuchsia_web/webengine/browser/web_engine_devtools_controller.h"
 #include "third_party/blink/public/common/web_preferences/web_preferences.h"
 #include "third_party/blink/public/mojom/webpreferences/web_preferences.mojom.h"
+#include "third_party/perfetto/include/perfetto/tracing/track_event_args.h"
 
 #if BUILDFLAG(ENABLE_CAST_RECEIVER)
 #include "components/cast_streaming/browser/public/network_context_getter.h"  // nogncheck
@@ -40,9 +44,15 @@
                                           base::Unretained(this))) {
   DCHECK(browser_context_);
   DCHECK(devtools_controller_);
+
+  TRACE_EVENT(kWebEngineFidlCategory, "fuchsia.web/Context created",
+              perfetto::Flow::FromPointer(this));
 }
 
-ContextImpl::~ContextImpl() = default;
+ContextImpl::~ContextImpl() {
+  TRACE_EVENT(kWebEngineFidlCategory, "fuchsia.web/Context destroyed",
+              perfetto::TerminatingFlow::FromPointer(this));
+}
 
 void ContextImpl::DestroyFrame(FrameImpl* frame) {
   auto iter = frames_.find(frame);
@@ -64,12 +74,21 @@
 
 void ContextImpl::CreateFrame(
     fidl::InterfaceRequest<fuchsia::web::Frame> frame) {
+  TRACE_EVENT(kWebEngineFidlCategory, "fuchsia.web/Context.CreateFrame",
+              perfetto::Flow::FromPointer(this));
+
   CreateFrameWithParams(fuchsia::web::CreateFrameParams(), std::move(frame));
 }
 
 void ContextImpl::CreateFrameWithParams(
     fuchsia::web::CreateFrameParams params,
     fidl::InterfaceRequest<fuchsia::web::Frame> frame) {
+  if (!params.IsEmpty()) {
+    TRACE_EVENT(kWebEngineFidlCategory,
+                "fuchsia.web/Context.CreateFrameWithParams",
+                perfetto::Flow::FromPointer(this));
+  }
+
   // FrameImpl clones the params used to create it when creating popup Frames.
   // Ensure the params can be cloned to avoid problems when handling popups.
   // TODO(fxbug.dev/65750): Consider removing this restriction if clients
@@ -155,11 +174,18 @@
 
 void ContextImpl::GetCookieManager(
     fidl::InterfaceRequest<fuchsia::web::CookieManager> request) {
+  TRACE_EVENT(kWebEngineFidlCategory, "fuchsia.web/Context.GetCookieManager",
+              perfetto::Flow::FromPointer(this));
+
   cookie_manager_bindings_.AddBinding(&cookie_manager_, std::move(request));
 }
 
 void ContextImpl::GetRemoteDebuggingPort(
     GetRemoteDebuggingPortCallback callback) {
+  TRACE_EVENT(kWebEngineFidlCategory,
+              "fuchsia.web/Context.GetRemoteDebuggingPort",
+              perfetto::Flow::FromPointer(this));
+
   devtools_controller_->GetDevToolsPort(base::BindOnce(
       [](GetRemoteDebuggingPortCallback callback, uint16_t port) {
         if (port == 0) {
diff --git a/fuchsia_web/webengine/browser/frame_impl.cc b/fuchsia_web/webengine/browser/frame_impl.cc
index 0cb90a5..1d5e4398 100644
--- a/fuchsia_web/webengine/browser/frame_impl.cc
+++ b/fuchsia_web/webengine/browser/frame_impl.cc
@@ -26,6 +26,7 @@
 #include "base/task/single_thread_task_runner.h"
 #include "base/thread_annotations.h"
 #include "base/time/time.h"
+#include "base/trace_event/trace_event.h"
 #include "build/chromecast_buildflags.h"
 #include "content/public/browser/audio_stream_broker.h"
 #include "content/public/browser/browser_accessibility_state.h"
@@ -51,6 +52,7 @@
 #include "fuchsia_web/webengine/browser/media_player_impl.h"
 #include "fuchsia_web/webengine/browser/message_port.h"
 #include "fuchsia_web/webengine/browser/navigation_policy_handler.h"
+#include "fuchsia_web/webengine/browser/trace_event.h"
 #include "fuchsia_web/webengine/browser/url_request_rewrite_type_converters.h"
 #include "fuchsia_web/webengine/browser/web_engine_devtools_controller.h"
 #include "media/mojo/mojom/audio_processing.mojom.h"
@@ -66,6 +68,7 @@
 #include "third_party/blink/public/mojom/loader/resource_load_info.mojom.h"
 #include "third_party/blink/public/mojom/mediastream/media_stream.mojom.h"
 #include "third_party/blink/public/mojom/navigation/was_activated_option.mojom.h"
+#include "third_party/perfetto/include/perfetto/tracing/track_event_args.h"
 #include "ui/aura/window.h"
 #include "ui/compositor/compositor.h"
 #include "ui/gfx/switches.h"
@@ -418,7 +421,7 @@
       console_log_tag_(params.has_debug_name() ? params.debug_name()
                                                : std::string()),
       params_for_popups_(std::move(params)),
-      navigation_controller_(web_contents_.get()),
+      navigation_controller_(web_contents_.get(), this),
       permission_controller_(web_contents_.get()),
       binding_(this, std::move(frame_request)),
       media_blocker_(web_contents_.get()),
@@ -432,6 +435,10 @@
                                            params_for_popups_.debug_name())
               : inspect::StringProperty()) {
   DCHECK(!WebContentsToFrameImplMap()[web_contents_.get()]);
+  TRACE_EVENT(kWebEngineFidlCategory, "fuchsia.web/Frame created",
+              perfetto::Flow::FromPointer(context_),
+              perfetto::Flow::FromPointer(this));
+
   WebContentsToFrameImplMap()[web_contents_.get()] = this;
 
   web_contents_->SetDelegate(this);
@@ -451,6 +458,9 @@
 }
 
 FrameImpl::~FrameImpl() {
+  TRACE_EVENT(kWebEngineFidlCategory, "fuchsia.web/Frame destroyed",
+              perfetto::TerminatingFlow::FromPointer(this));
+
   DestroyWindowTreeHost();
   context_->devtools_controller()->OnFrameDestroyed(web_contents_.get());
 
@@ -788,16 +798,28 @@
 }
 
 void FrameImpl::CreateView(fuchsia::ui::views::ViewToken view_token) {
+  TRACE_EVENT(kWebEngineFidlCategory, "fuchsia.web/Frame.CreateView",
+              perfetto::Flow::FromPointer(this));
+
   auto view_ref_pair = scenic::ViewRefPair::New();
-  CreateViewWithViewRef(std::move(view_token),
-                        std::move(view_ref_pair.control_ref),
-                        std::move(view_ref_pair.view_ref));
+  CreateViewImpl(std::move(view_token), std::move(view_ref_pair.control_ref),
+                 std::move(view_ref_pair.view_ref));
 }
 
 void FrameImpl::CreateViewWithViewRef(
     fuchsia::ui::views::ViewToken view_token,
     fuchsia::ui::views::ViewRefControl control_ref,
     fuchsia::ui::views::ViewRef view_ref) {
+  TRACE_EVENT(kWebEngineFidlCategory, "fuchsia.web/Frame.CreateViewWithViewRef",
+              perfetto::Flow::FromPointer(this));
+
+  CreateViewImpl(std::move(view_token), std::move(control_ref),
+                 std::move(view_ref));
+}
+
+void FrameImpl::CreateViewImpl(fuchsia::ui::views::ViewToken view_token,
+                               fuchsia::ui::views::ViewRefControl control_ref,
+                               fuchsia::ui::views::ViewRef view_ref) {
   if (IsHeadless()) {
     LOG(WARNING) << "CreateView() called on a HEADLESS Context.";
     CloseAndDestroyFrame(ZX_ERR_INVALID_ARGS);
@@ -822,6 +844,9 @@
 }
 
 void FrameImpl::CreateView2(fuchsia::web::CreateView2Args view_args) {
+  TRACE_EVENT(kWebEngineFidlCategory, "fuchsia.web/Frame.CreateView2",
+              perfetto::Flow::FromPointer(this));
+
   if (IsHeadless()) {
     LOG(WARNING) << "CreateView2() called on a HEADLESS Context.";
     CloseAndDestroyFrame(ZX_ERR_INVALID_ARGS);
@@ -847,6 +872,9 @@
 
 void FrameImpl::GetMediaPlayer(
     fidl::InterfaceRequest<fuchsia::media::sessions2::Player> player) {
+  TRACE_EVENT(kWebEngineFidlCategory, "fuchsia.web/Frame.GetMediaPlayer",
+              perfetto::Flow::FromPointer(this));
+
   media_player_ = std::make_unique<MediaPlayerImpl>(
       content::MediaSession::Get(web_contents_.get()), std::move(player),
       base::BindOnce(&FrameImpl::OnMediaPlayerDisconnect,
@@ -855,12 +883,19 @@
 
 void FrameImpl::GetNavigationController(
     fidl::InterfaceRequest<fuchsia::web::NavigationController> controller) {
+  TRACE_EVENT(kWebEngineFidlCategory,
+              "fuchsia.web/Frame.GetNavigationController",
+              perfetto::Flow::FromPointer(this));
+
   navigation_controller_.AddBinding(std::move(controller));
 }
 
 void FrameImpl::ExecuteJavaScript(std::vector<std::string> origins,
                                   fuchsia::mem::Buffer script,
                                   ExecuteJavaScriptCallback callback) {
+  TRACE_EVENT(kWebEngineFidlCategory, "fuchsia.web/Frame.ExecuteJavaScript",
+              perfetto::Flow::FromPointer(this));
+
   ExecuteJavaScriptInternal(std::move(origins), std::move(script),
                             std::move(callback), true);
 }
@@ -869,6 +904,10 @@
     std::vector<std::string> origins,
     fuchsia::mem::Buffer script,
     ExecuteJavaScriptNoResultCallback callback) {
+  TRACE_EVENT(kWebEngineFidlCategory,
+              "fuchsia.web/Frame.ExecuteJavaScriptNoResult",
+              perfetto::Flow::FromPointer(this));
+
   ExecuteJavaScriptInternal(
       std::move(origins), std::move(script),
       [callback = std::move(callback)](
@@ -887,6 +926,10 @@
     std::vector<std::string> origins,
     fuchsia::mem::Buffer script,
     AddBeforeLoadJavaScriptCallback callback) {
+  TRACE_EVENT(kWebEngineFidlCategory,
+              "fuchsia.web/Frame.AddBeforeLoadJavaScript",
+              perfetto::Flow::FromPointer(this));
+
   if (!context_->IsJavaScriptInjectionAllowed()) {
     callback(fpromise::error(fuchsia::web::FrameError::INTERNAL_ERROR));
     return;
@@ -921,12 +964,19 @@
 }
 
 void FrameImpl::RemoveBeforeLoadJavaScript(uint64_t id) {
+  TRACE_EVENT(kWebEngineFidlCategory,
+              "fuchsia.web/Frame.RemoveBeforeLoadJavaScript",
+              perfetto::Flow::FromPointer(this));
+
   script_injector_.RemoveScript(id);
 }
 
 void FrameImpl::PostMessage(std::string origin,
                             fuchsia::web::WebMessage message,
                             PostMessageCallback callback) {
+  TRACE_EVENT(kWebEngineFidlCategory, "fuchsia.web/Frame.PostMessage",
+              perfetto::Flow::FromPointer(this));
+
 #if BUILDFLAG(ENABLE_CAST_RECEIVER)
   if (MaybeHandleCastStreamingMessage(&origin, &message, &callback))
     return;
@@ -991,14 +1041,24 @@
 void FrameImpl::SetNavigationEventListener2(
     fidl::InterfaceHandle<fuchsia::web::NavigationEventListener> listener,
     fuchsia::web::NavigationEventListenerFlags flags) {
+  TRACE_EVENT(kWebEngineFidlCategory,
+              "fuchsia.web/Frame.SetNavigationEventListener",
+              perfetto::Flow::FromPointer(this));
+
   navigation_controller_.SetEventListener(std::move(listener), flags);
 }
 
 void FrameImpl::SetJavaScriptLogLevel(fuchsia::web::ConsoleLogLevel level) {
+  TRACE_EVENT(kWebEngineFidlCategory, "fuchsia.web/Frame.SetJavaScriptLogLevel",
+              perfetto::Flow::FromPointer(this));
+
   log_level_ = FuchsiaWebConsoleLogLevelToFxLogSeverity(level);
 }
 
 void FrameImpl::SetConsoleLogSink(fuchsia::logger::LogSinkHandle sink) {
+  TRACE_EVENT(kWebEngineFidlCategory, "fuchsia.web/Frame.SetConsoleLogSink",
+              perfetto::Flow::FromPointer(this));
+
   if (sink) {
     console_logger_ = base::ScopedFxLogger::CreateFromLogSink(
         std::move(sink), {console_log_tag_});
@@ -1009,11 +1069,18 @@
 
 void FrameImpl::ConfigureInputTypes(fuchsia::web::InputTypes types,
                                     fuchsia::web::AllowInputState allow) {
+  TRACE_EVENT(kWebEngineFidlCategory, "fuchsia.web/Frame.ConfigureInputTypes",
+              perfetto::Flow::FromPointer(this));
+
   event_filter_.ConfigureInputTypes(types, allow);
 }
 
 void FrameImpl::SetPopupFrameCreationListener(
     fidl::InterfaceHandle<fuchsia::web::PopupFrameCreationListener> listener) {
+  TRACE_EVENT(kWebEngineFidlCategory,
+              "fuchsia.web/Frame.SetPopupFrameCreationListener",
+              perfetto::Flow::FromPointer(this));
+
   popup_listener_ = listener.Bind();
   popup_listener_.set_error_handler(
       fit::bind_member(this, &FrameImpl::OnPopupListenerDisconnected));
@@ -1022,6 +1089,10 @@
 void FrameImpl::SetUrlRequestRewriteRules(
     std::vector<fuchsia::web::UrlRequestRewriteRule> rules,
     SetUrlRequestRewriteRulesCallback callback) {
+  TRACE_EVENT(kWebEngineFidlCategory,
+              "fuchsia.web/Frame.SetUrlRequestRewriteRules",
+              perfetto::Flow::FromPointer(this));
+
   auto mojom_rules =
       mojo::ConvertTo<url_rewrite::mojom::UrlRequestRewriteRulesPtr>(
           std::move(rules));
@@ -1034,6 +1105,10 @@
 }
 
 void FrameImpl::EnableHeadlessRendering() {
+  TRACE_EVENT(kWebEngineFidlCategory,
+              "fuchsia.web/Frame.EnableHeadlessRendering",
+              perfetto::Flow::FromPointer(this));
+
   if (!IsHeadless()) {
     LOG(ERROR) << "EnableHeadlessRendering() on non-HEADLESS Context.";
     CloseAndDestroyFrame(ZX_ERR_INVALID_ARGS);
@@ -1059,6 +1134,10 @@
 }
 
 void FrameImpl::DisableHeadlessRendering() {
+  TRACE_EVENT(kWebEngineFidlCategory,
+              "fuchsia.web/Frame.DisableHeadlessRendering",
+              perfetto::Flow::FromPointer(this));
+
   if (!IsHeadless()) {
     LOG(ERROR)
         << "Attempted to disable headless rendering on non-HEADLESS Context.";
@@ -1124,6 +1203,9 @@
 
 void FrameImpl::SetMediaSettings(
     fuchsia::web::FrameMediaSettings media_settings) {
+  TRACE_EVENT(kWebEngineFidlCategory, "fuchsia.web/Frame.SetMediaSettings",
+              perfetto::Flow::FromPointer(this));
+
   media_settings_ = std::move(media_settings);
   if (media_settings.has_renderer_usage() && set_audio_output_usage_callback_)
     set_audio_output_usage_callback_.Run(media_settings.renderer_usage());
@@ -1131,6 +1213,10 @@
 
 void FrameImpl::ForceContentDimensions(
     std::unique_ptr<fuchsia::ui::gfx::vec2> web_dips) {
+  TRACE_EVENT(kWebEngineFidlCategory,
+              "fuchsia.web/Frame.ForceContentDimensions",
+              perfetto::Flow::FromPointer(this));
+
   if (!web_dips) {
     render_size_override_ = {};
     if (layout_manager_)
@@ -1154,6 +1240,9 @@
     fuchsia::web::PermissionDescriptor fidl_permission,
     std::string web_origin_string,
     fuchsia::web::PermissionState fidl_state) {
+  TRACE_EVENT(kWebEngineFidlCategory, "fuchsia.web/Frame.SetPermissionState",
+              perfetto::Flow::FromPointer(this));
+
   if (!fidl_permission.has_type()) {
     LOG(ERROR) << "PermissionDescriptor.type is not specified in "
                   "SetPermissionState().";
@@ -1190,6 +1279,9 @@
 }
 
 void FrameImpl::GetPrivateMemorySize(GetPrivateMemorySizeCallback callback) {
+  TRACE_EVENT(kWebEngineFidlCategory, "fuchsia.web/Frame.GetPrivateMemorySize",
+              perfetto::Flow::FromPointer(this));
+
   if (!web_contents_->GetPrimaryMainFrame()->GetProcess()->IsReady()) {
     // Renderer process is not yet started.
     callback(0);
@@ -1214,12 +1306,20 @@
 void FrameImpl::SetNavigationPolicyProvider(
     fuchsia::web::NavigationPolicyProviderParams params,
     fidl::InterfaceHandle<fuchsia::web::NavigationPolicyProvider> provider) {
+  TRACE_EVENT(kWebEngineFidlCategory,
+              "fuchsia.web/Frame.SetNavigationPolicyProvider",
+              perfetto::Flow::FromPointer(this));
+
   navigation_policy_handler_ = std::make_unique<NavigationPolicyHandler>(
       std::move(params), std::move(provider));
 }
 
 void FrameImpl::SetContentAreaSettings(
     fuchsia::web::ContentAreaSettings settings) {
+  TRACE_EVENT(kWebEngineFidlCategory,
+              "fuchsia.web/Frame.SetContentAreaSettings",
+              perfetto::Flow::FromPointer(this));
+
   if (settings.has_hide_scrollbars())
     content_area_settings_.set_hide_scrollbars(settings.hide_scrollbars());
   if (settings.has_autoplay_policy())
@@ -1245,6 +1345,10 @@
 }
 
 void FrameImpl::ResetContentAreaSettings() {
+  TRACE_EVENT(kWebEngineFidlCategory,
+              "fuchsia.web/Frame.ResetContentAreaSettings",
+              perfetto::Flow::FromPointer(this));
+
   content_area_settings_ = fuchsia::web::ContentAreaSettings();
   web_contents_->OnWebPreferencesChanged();
   UpdateRenderFrameZoomLevel(web_contents_->GetPrimaryMainFrame());
@@ -1291,6 +1395,9 @@
 }
 
 void FrameImpl::SetBlockMediaLoading(bool blocked) {
+  TRACE_EVENT(kWebEngineFidlCategory, "fuchsia.web/Frame.SetBlockMediaLoading",
+              perfetto::Flow::FromPointer(this));
+
   media_blocker_.BlockMediaLoading(blocked);
 }
 
diff --git a/fuchsia_web/webengine/browser/frame_impl.h b/fuchsia_web/webengine/browser/frame_impl.h
index 8bd7e95..69e45b7 100644
--- a/fuchsia_web/webengine/browser/frame_impl.h
+++ b/fuchsia_web/webengine/browser/frame_impl.h
@@ -221,6 +221,11 @@
   // |accessibility_bridge_|.
   void ConnectToAccessibilityBridge();
 
+  // Shared implementation of CreateView and CreateViewWithViewRef.
+  void CreateViewImpl(fuchsia::ui::views::ViewToken view_token,
+                      fuchsia::ui::views::ViewRefControl control_ref,
+                      fuchsia::ui::views::ViewRef view_ref);
+
   // fuchsia::web::Frame implementation.
   void CreateView(fuchsia::ui::views::ViewToken view_token) override;
   void CreateViewWithViewRef(fuchsia::ui::views::ViewToken view_token,
diff --git a/fuchsia_web/webengine/browser/navigation_controller_impl.cc b/fuchsia_web/webengine/browser/navigation_controller_impl.cc
index b495690..d83978a 100644
--- a/fuchsia_web/webengine/browser/navigation_controller_impl.cc
+++ b/fuchsia_web/webengine/browser/navigation_controller_impl.cc
@@ -13,15 +13,19 @@
 #include "base/strings/strcat.h"
 #include "base/strings/string_piece.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/trace_event/trace_event.h"
+#include "base/trace_event/typed_macros.h"
 #include "components/favicon/content/content_favicon_driver.h"
 #include "content/public/browser/favicon_status.h"
 #include "content/public/browser/navigation_entry.h"
 #include "content/public/browser/navigation_handle.h"
 #include "content/public/browser/web_contents.h"
 #include "fuchsia_web/common/string_util.h"
+#include "fuchsia_web/webengine/browser/trace_event.h"
 #include "net/base/net_errors.h"
 #include "net/http/http_util.h"
 #include "third_party/blink/public/mojom/navigation/was_activated_option.mojom.h"
+#include "third_party/perfetto/include/perfetto/tracing/track_event_args.h"
 #include "third_party/skia/include/core/SkBitmap.h"
 #include "ui/base/page_transition_types.h"
 #include "ui/gfx/image/image.h"
@@ -132,8 +136,13 @@
 }  // namespace
 
 NavigationControllerImpl::NavigationControllerImpl(
-    content::WebContents* web_contents)
-    : web_contents_(web_contents), weak_factory_(this) {
+    content::WebContents* web_contents,
+    void* parent_for_trace_flow)
+    : parent_for_trace_flow_(parent_for_trace_flow),
+      web_contents_(web_contents),
+      weak_factory_(this) {
+  DCHECK(parent_for_trace_flow_);
+
   Observe(web_contents_);
 }
 
@@ -253,6 +262,14 @@
 
   waiting_for_navigation_event_ack_ = true;
 
+  // Note that the events is logged to the parent Frame's flow.
+  TRACE_EVENT(kWebEngineFidlCategory,
+              "fuchsia.web/NavigationEventListener.OnNavigationStateChanged",
+              perfetto::Flow::FromPointer(parent_for_trace_flow_), "url",
+              previous_navigation_state_.url(), "title",
+              previous_navigation_state_.title().data(), "is_loaded",
+              is_main_document_loaded_);
+
   // Send the event to the observer and, upon acknowledgement, revisit this
   // function to send another update.
   navigation_listener_->OnNavigationStateChanged(
@@ -267,6 +284,11 @@
 void NavigationControllerImpl::LoadUrl(std::string url,
                                        fuchsia::web::LoadUrlParams params,
                                        LoadUrlCallback callback) {
+  // Note that the event is logged to the parent Frame's flow.
+  TRACE_EVENT(kWebEngineFidlCategory,
+              "fuchsia.web/NavigationController.LoadUrl",
+              perfetto::Flow::FromPointer(parent_for_trace_flow_), "url", url);
+
   GURL validated_url(url);
   if (!validated_url.is_valid()) {
     callback(
@@ -310,20 +332,33 @@
 }
 
 void NavigationControllerImpl::GoBack() {
+  TRACE_EVENT(kWebEngineFidlCategory, "fuchsia.web/NavigationController.GoBack",
+              perfetto::Flow::FromPointer(parent_for_trace_flow_));
+
   if (web_contents_->GetController().CanGoBack())
     web_contents_->GetController().GoBack();
 }
 
 void NavigationControllerImpl::GoForward() {
+  TRACE_EVENT(kWebEngineFidlCategory,
+              "fuchsia.web/NavigationController.GoForward",
+              perfetto::Flow::FromPointer(parent_for_trace_flow_));
+
   if (web_contents_->GetController().CanGoForward())
     web_contents_->GetController().GoForward();
 }
 
 void NavigationControllerImpl::Stop() {
+  TRACE_EVENT(kWebEngineFidlCategory, "fuchsia.web/NavigationController.Stop",
+              perfetto::Flow::FromPointer(parent_for_trace_flow_));
+
   web_contents_->Stop();
 }
 
 void NavigationControllerImpl::Reload(fuchsia::web::ReloadType type) {
+  TRACE_EVENT(kWebEngineFidlCategory, "fuchsia.web/NavigationController.Reload",
+              perfetto::Flow::FromPointer(parent_for_trace_flow_));
+
   content::ReloadType internal_reload_type;
   switch (type) {
     case fuchsia::web::ReloadType::PARTIAL_CACHE:
diff --git a/fuchsia_web/webengine/browser/navigation_controller_impl.h b/fuchsia_web/webengine/browser/navigation_controller_impl.h
index 996c96f..6c1d99d1 100644
--- a/fuchsia_web/webengine/browser/navigation_controller_impl.h
+++ b/fuchsia_web/webengine/browser/navigation_controller_impl.h
@@ -25,7 +25,8 @@
       public content::WebContentsObserver,
       public favicon::FaviconDriverObserver {
  public:
-  explicit NavigationControllerImpl(content::WebContents* web_contents);
+  NavigationControllerImpl(content::WebContents* web_contents,
+                           void* parent_for_trace_flow);
 
   NavigationControllerImpl(const NavigationControllerImpl&) = delete;
   NavigationControllerImpl& operator=(const NavigationControllerImpl&) = delete;
@@ -81,6 +82,7 @@
                         bool icon_url_changed,
                         const gfx::Image& image) override;
 
+  const raw_ptr<void> parent_for_trace_flow_;
   content::WebContents* const web_contents_;
 
   // NavigationController client bindings.
diff --git a/fuchsia_web/webengine/browser/trace_event.h b/fuchsia_web/webengine/browser/trace_event.h
new file mode 100644
index 0000000..b1785cb
--- /dev/null
+++ b/fuchsia_web/webengine/browser/trace_event.h
@@ -0,0 +1,13 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef FUCHSIA_WEB_WEBENGINE_BROWSER_TRACE_EVENT_H_
+#define FUCHSIA_WEB_WEBENGINE_BROWSER_TRACE_EVENT_H_
+
+// The category to use for WebEngine FIDL events.
+// The event names should use the convention `namespace`/`interface`.`method`,
+// e.g. "fuchsia.web/Frame.CreateView".
+inline constexpr char kWebEngineFidlCategory[] = "webengine.fidl";
+
+#endif  // FUCHSIA_WEB_WEBENGINE_BROWSER_TRACE_EVENT_H_
diff --git a/fuchsia_web/webinstance_host/web_instance_host_internal.cc b/fuchsia_web/webinstance_host/web_instance_host_internal.cc
index 94c76ff..58e556b8 100644
--- a/fuchsia_web/webinstance_host/web_instance_host_internal.cc
+++ b/fuchsia_web/webinstance_host/web_instance_host_internal.cc
@@ -333,6 +333,10 @@
   }
 
 #if BUILDFLAG(ENABLE_CAST_RECEIVER)
+  // Use a constexpr instead of the media::IsClearKey() helper, because of the
+  // additional dependencies required.
+  static constexpr char kClearKeyKeySystem[] = "org.w3.clearkey";
+
   if (enable_playready) {
     const std::string& key_system = params.playready_key_system();
     if (key_system == kWidevineKeySystem || key_system == kClearKeyKeySystem) {
diff --git a/gin/gin_features.cc b/gin/gin_features.cc
index 6a90e38..035dfdb 100644
--- a/gin/gin_features.cc
+++ b/gin/gin_features.cc
@@ -150,6 +150,10 @@
 const base::FeatureParam<base::TimeDelta> kV8MemoryReducerStartDelay{
     &kV8DelayMemoryReducer, "delay", base::Seconds(30)};
 
+BASE_FEATURE(kV8UseLibmTrigFunctions,
+             "V8UseLibmTrigFunctions",
+             base::FEATURE_ENABLED_BY_DEFAULT);
+
 // JavaScript language features.
 
 // Enables the Symbols-as-WeakMap-keys proposal.
diff --git a/gin/gin_features.h b/gin/gin_features.h
index f3b1b52..45f01f7 100644
--- a/gin/gin_features.h
+++ b/gin/gin_features.h
@@ -46,6 +46,7 @@
 GIN_EXPORT BASE_DECLARE_FEATURE(kV8SparkplugNeedsShortBuiltinCalls);
 GIN_EXPORT BASE_DECLARE_FEATURE(kV8TurboFastApiCalls);
 GIN_EXPORT BASE_DECLARE_FEATURE(kV8DelayMemoryReducer);
+GIN_EXPORT BASE_DECLARE_FEATURE(kV8UseLibmTrigFunctions);
 GIN_EXPORT extern const base::FeatureParam<base::TimeDelta>
     kV8MemoryReducerStartDelay;
 GIN_EXPORT BASE_DECLARE_FEATURE(kJavaScriptSymbolAsWeakMapKey);
diff --git a/gin/v8_initializer.cc b/gin/v8_initializer.cc
index 10b8bfa..8820680 100644
--- a/gin/v8_initializer.cc
+++ b/gin/v8_initializer.cc
@@ -356,6 +356,10 @@
     SetV8Flags("--use_strict");
   }
 
+  SetV8FlagsIfOverridden(features::kV8UseLibmTrigFunctions,
+                         "--use-libm-trig-functions",
+                         "--no-use-libm-trig-functions");
+
   if (js_command_line_flags.empty())
     return;
 
diff --git a/gpu/command_buffer/service/scheduler_dfs.cc b/gpu/command_buffer/service/scheduler_dfs.cc
index d21e69ee..d181581 100644
--- a/gpu/command_buffer/service/scheduler_dfs.cc
+++ b/gpu/command_buffer/service/scheduler_dfs.cc
@@ -576,11 +576,13 @@
               << " (order_num: " << fence_iter->first.order_num << ").";
     Sequence* release_sequence =
         GetSequence(fence_iter->first.release_sequence_id);
-    // Sanity check to make sure we're not processing a sequence on another
-    // thread that's past
-    DCHECK(!(release_sequence && release_sequence->HasTasks() &&
-             release_sequence->tasks_.front().order_num >=
-                 fence_iter->first.order_num));
+    // ShouldYield might be calling this function, and a dependency might depend
+    // on the calling sequence, which might have not released its fences yet.
+    if (release_sequence && release_sequence->HasTasks() &&
+        release_sequence->tasks_.front().order_num >=
+            fence_iter->first.order_num) {
+      continue;
+    }
     if (Sequence* result = FindNextTaskFromRoot(release_sequence);
         result != nullptr) {
       return result;
@@ -617,7 +619,7 @@
   // dependency tied to another thread.
   for (const SchedulingState& state : sorted_sequences) {
     Sequence* root_sequence = GetSequence(state.sequence_id);
-    DVLOG(10) << "RunNextTask: Calling FindNextTask on sequence "
+    DVLOG(10) << "FindNextTask: Calling FindNextTaskFromRoot on sequence "
               << root_sequence->sequence_id().value();
     if (Sequence* sequence = FindNextTaskFromRoot(root_sequence);
         sequence != nullptr) {
@@ -767,9 +769,7 @@
   total_blocked_time_ += blocked_time;
 
   // Reset pointers after reaquiring the lock.
-  thread_state = &per_thread_state_map_[task_runner];
   sequence = GetSequence(sequence_id);
-
   if (sequence) {
     sequence->FinishTask();
   }
diff --git a/gpu/command_buffer/service/scheduler_dfs_unittest.cc b/gpu/command_buffer/service/scheduler_dfs_unittest.cc
index ec9eda6..1bb1828 100644
--- a/gpu/command_buffer/service/scheduler_dfs_unittest.cc
+++ b/gpu/command_buffer/service/scheduler_dfs_unittest.cc
@@ -15,7 +15,6 @@
 #include "base/functional/bind.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/test/task_environment.h"
-#include "base/test/test_simple_task_runner.h"
 #include "base/time/time.h"
 #include "gpu/command_buffer/service/scheduler.h"
 #include "gpu/command_buffer/service/sync_point_manager.h"
@@ -171,9 +170,9 @@
         sync_point_manager()->CreateSyncPointClientState(
             kNamespaceId, command_buffer_id, sequence_id);
 
-    sequence_info_.emplace(std::make_pair(
+    sequence_info_.emplace(
         sequence_key,
-        SequenceInfo(sequence_id, command_buffer_id, release_state)));
+        SequenceInfo(sequence_id, command_buffer_id, release_state));
   }
 
   void CreateExternalSequence(int sequence_key) {
@@ -182,9 +181,9 @@
     auto release_state = sync_point_manager()->CreateSyncPointClientState(
         kNamespaceId, command_buffer_id, order_data->sequence_id());
 
-    sequence_info_.emplace(std::make_pair(
+    sequence_info_.emplace(
         sequence_key,
-        SequenceInfo(std::move(order_data), command_buffer_id, release_state)));
+        SequenceInfo(std::move(order_data), command_buffer_id, release_state));
   }
 
   void DestroySequence(int sequence_key) {
@@ -205,9 +204,9 @@
     ASSERT_TRUE(info_it != sequence_info_.end());
 
     uint64_t release = release_sync + 1;
-    sync_tokens_.emplace(std::make_pair(
+    sync_tokens_.emplace(
         release_sync,
-        SyncToken(kNamespaceId, info_it->second.command_buffer_id, release)));
+        SyncToken(kNamespaceId, info_it->second.command_buffer_id, release));
   }
 
   static void RunExternalTask(base::OnceClosure task,
@@ -605,6 +604,56 @@
   scheduler()->DestroySequence(sequence_id2);
 }
 
+// Tests a situation where a sequence's WaitFence has an order number less than
+// the sequence's first order number, because the sequence is currently running,
+// and called ShouldYield before release the WaitFence.
+TEST_F(SchedulerDfsTest, ShouldYieldIsValidWhenSequenceReleaseIsPending) {
+  SequenceId sequence_id1 =
+      scheduler()->CreateSequenceForTesting(SchedulingPriority::kHigh);
+  CommandBufferNamespace namespace_id = CommandBufferNamespace::GPU_IO;
+  CommandBufferId command_buffer_id1 = CommandBufferId::FromUnsafeValue(1);
+  scoped_refptr<SyncPointClientState> release_state1 =
+      sync_point_manager()->CreateSyncPointClientState(
+          namespace_id, command_buffer_id1, sequence_id1);
+
+  SequenceId sequence_id2 =
+      scheduler()->CreateSequenceForTesting(SchedulingPriority::kNormal);
+  CommandBufferId command_buffer_id2 = CommandBufferId::FromUnsafeValue(2);
+  scoped_refptr<SyncPointClientState> release_state2 =
+      sync_point_manager()->CreateSyncPointClientState(
+          namespace_id, command_buffer_id2, sequence_id2);
+
+  SyncToken sync_token1(namespace_id, command_buffer_id1, 1);
+  SyncToken sync_token2(namespace_id, command_buffer_id2, 2);
+
+  // Job 1.1 doesn't depend on anything.
+  scheduler()->ScheduleTask(
+      Scheduler::Task(sequence_id1, GetClosure([&] {
+                        EXPECT_FALSE(scheduler()->ShouldYield(sequence_id1));
+                        release_state1->ReleaseFenceSync(1);
+                      }),
+                      {}));
+
+  // Job 2.1 depends on Job 1.1.
+  scheduler()->ScheduleTask(Scheduler::Task(sequence_id2, GetClosure([&] {
+                                              release_state2->ReleaseFenceSync(
+                                                  sync_token2.release_count());
+                                            }),
+                                            {sync_token1}));
+
+  // Job 1.2 depends on Job 2.1.
+  scheduler()->ScheduleTask(
+      Scheduler::Task(sequence_id1, GetClosure([&] {}), {sync_token2}));
+
+  RunAllPendingTasks();
+
+  release_state1->Destroy();
+  release_state2->Destroy();
+
+  scheduler()->DestroySequence(sequence_id1);
+  scheduler()->DestroySequence(sequence_id2);
+}
+
 TEST_F(SchedulerDfsTest, ReentrantEnableSequenceShouldNotDeadlock) {
   SequenceId sequence_id1 =
       scheduler()->CreateSequenceForTesting(SchedulingPriority::kHigh);
diff --git a/gpu/vulkan/init/gr_vk_memory_allocator_impl.cc b/gpu/vulkan/init/gr_vk_memory_allocator_impl.cc
index 3d14b16..1948267b 100644
--- a/gpu/vulkan/init/gr_vk_memory_allocator_impl.cc
+++ b/gpu/vulkan/init/gr_vk_memory_allocator_impl.cc
@@ -195,16 +195,11 @@
     return vma::InvalidateAllocation(allocator_, allocation, offset, size);
   }
 
-  uint64_t totalUsedMemory() const override {
+  std::pair<uint64_t, uint64_t> totalAllocatedAndUsedMemory() const override {
     VmaStats stats;
     vma::CalculateStats(allocator_, &stats);
-    return stats.total.usedBytes;
-  }
-
-  uint64_t totalAllocatedMemory() const override {
-    VmaStats stats;
-    vma::CalculateStats(allocator_, &stats);
-    return stats.total.usedBytes + stats.total.unusedBytes;
+    return {stats.total.usedBytes + stats.total.unusedBytes,
+            stats.total.usedBytes};
   }
 
   const VmaAllocator allocator_;
diff --git a/infra/config/generated/builders/ci/linux-wpt-content-shell-asan-fyi-rel/properties.json b/infra/config/generated/builders/ci/linux-wpt-content-shell-asan-fyi-rel/properties.json
new file mode 100644
index 0000000..b8f8bb3
--- /dev/null
+++ b/infra/config/generated/builders/ci/linux-wpt-content-shell-asan-fyi-rel/properties.json
@@ -0,0 +1,54 @@
+{
+  "$build/chromium_tests_builder_config": {
+    "builder_config": {
+      "builder_db": {
+        "entries": [
+          {
+            "builder_id": {
+              "bucket": "ci",
+              "builder": "linux-wpt-content-shell-asan-fyi-rel",
+              "project": "chromium"
+            },
+            "builder_spec": {
+              "build_gs_bucket": "chromium-fyi-archive",
+              "builder_group": "chromium.fyi",
+              "execution_mode": "COMPILE_AND_TEST",
+              "legacy_chromium_config": {
+                "apply_configs": [
+                  "mb"
+                ],
+                "build_config": "Release",
+                "config": "chromium",
+                "target_bits": 64
+              },
+              "legacy_gclient_config": {
+                "config": "chromium"
+              }
+            }
+          }
+        ]
+      },
+      "builder_ids": [
+        {
+          "bucket": "ci",
+          "builder": "linux-wpt-content-shell-asan-fyi-rel",
+          "project": "chromium"
+        }
+      ]
+    }
+  },
+  "$build/reclient": {
+    "instance": "rbe-chromium-trusted",
+    "jobs": 250,
+    "metrics_project": "chromium-reclient-metrics"
+  },
+  "$recipe_engine/resultdb/test_presentation": {
+    "column_keys": [],
+    "grouping_keys": [
+      "status",
+      "v.test_suite"
+    ]
+  },
+  "builder_group": "chromium.fyi",
+  "recipe": "chromium"
+}
\ No newline at end of file
diff --git a/infra/config/generated/luci/cr-buildbucket.cfg b/infra/config/generated/luci/cr-buildbucket.cfg
index 8e0cd11..5f3b01e 100644
--- a/infra/config/generated/luci/cr-buildbucket.cfg
+++ b/infra/config/generated/luci/cr-buildbucket.cfg
@@ -41204,6 +41204,96 @@
       }
     }
     builders {
+      name: "linux-wpt-content-shell-asan-fyi-rel"
+      swarming_host: "chromium-swarm.appspot.com"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "free_space:standard"
+      dimensions: "os:Ubuntu-18.04"
+      dimensions: "pool:luci.chromium.ci"
+      dimensions: "ssd:0"
+      exe {
+        cipd_package: "infra/chromium/bootstrapper/${platform}"
+        cipd_version: "latest"
+        cmd: "bootstrapper"
+      }
+      properties:
+        '{'
+        '  "$bootstrap/exe": {'
+        '    "exe": {'
+        '      "cipd_package": "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build",'
+        '      "cipd_version": "refs/heads/main",'
+        '      "cmd": ['
+        '        "luciexe"'
+        '      ]'
+        '    }'
+        '  },'
+        '  "$bootstrap/properties": {'
+        '    "properties_file": "infra/config/generated/builders/ci/linux-wpt-content-shell-asan-fyi-rel/properties.json",'
+        '    "top_level_project": {'
+        '      "ref": "refs/heads/main",'
+        '      "repo": {'
+        '        "host": "chromium.googlesource.com",'
+        '        "project": "chromium/src"'
+        '      }'
+        '    }'
+        '  },'
+        '  "builder_group": "chromium.fyi",'
+        '  "led_builder_is_bootstrapped": true,'
+        '  "recipe": "chromium"'
+        '}'
+      priority: 35
+      execution_timeout_secs: 36000
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+      experimental: YES
+      experiments {
+        key: "chromium_swarming.expose_merge_script_failures"
+        value: 100
+      }
+      experiments {
+        key: "luci.buildbucket.omit_python2"
+        value: 100
+      }
+      experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+      resultdb {
+        enable: true
+        bq_exports {
+          project: "chrome-luci-data"
+          dataset: "chromium"
+          table: "ci_test_results"
+          test_results {}
+        }
+        bq_exports {
+          project: "chrome-luci-data"
+          dataset: "chromium"
+          table: "gpu_ci_test_results"
+          test_results {
+            predicate {
+              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+            }
+          }
+        }
+        bq_exports {
+          project: "chrome-luci-data"
+          dataset: "chromium"
+          table: "blink_web_tests_ci_test_results"
+          test_results {
+            predicate {
+              test_id_regexp: "(ninja://[^/]*blink_web_tests/.+)|(ninja://[^/]*blink_wpt_tests/.+)"
+            }
+          }
+        }
+        history_options {
+          use_invocation_timestamp: true
+        }
+      }
+    }
+    builders {
       name: "linux-wpt-content-shell-fyi-rel"
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builderless:1"
diff --git a/infra/config/generated/luci/luci-milo.cfg b/infra/config/generated/luci/luci-milo.cfg
index d4f42827..ac609cc7 100644
--- a/infra/config/generated/luci/luci-milo.cfg
+++ b/infra/config/generated/luci/luci-milo.cfg
@@ -8862,6 +8862,10 @@
     category: "linux"
   }
   builders {
+    name: "buildbucket/luci.chromium.ci/linux-wpt-content-shell-asan-fyi-rel"
+    category: "linux"
+  }
+  builders {
     name: "buildbucket/luci.chromium.ci/linux-wpt-content-shell-fyi-rel"
     category: "linux"
   }
diff --git a/infra/config/generated/luci/luci-scheduler.cfg b/infra/config/generated/luci/luci-scheduler.cfg
index 9fb6e243..b97b6cd0 100644
--- a/infra/config/generated/luci/luci-scheduler.cfg
+++ b/infra/config/generated/luci/luci-scheduler.cfg
@@ -5099,6 +5099,15 @@
   }
 }
 job {
+  id: "linux-wpt-content-shell-asan-fyi-rel"
+  realm: "ci"
+  buildbucket {
+    server: "cr-buildbucket.appspot.com"
+    bucket: "ci"
+    builder: "linux-wpt-content-shell-asan-fyi-rel"
+  }
+}
+job {
   id: "linux-wpt-content-shell-fyi-rel"
   realm: "ci"
   buildbucket {
@@ -6276,6 +6285,7 @@
   triggers: "linux-ubsan-vptr"
   triggers: "linux-upload-perfetto"
   triggers: "linux-win_cross-rel"
+  triggers: "linux-wpt-content-shell-asan-fyi-rel"
   triggers: "linux-wpt-content-shell-fyi-rel"
   triggers: "linux-wpt-fyi-rel"
   triggers: "linux-wpt-identity-fyi-rel"
diff --git a/infra/config/subprojects/chromium/ci/chromium.fyi.star b/infra/config/subprojects/chromium/ci/chromium.fyi.star
index 6a696067..1794c46 100644
--- a/infra/config/subprojects/chromium/ci/chromium.fyi.star
+++ b/infra/config/subprojects/chromium/ci/chromium.fyi.star
@@ -806,6 +806,27 @@
 )
 
 ci.builder(
+    name = "linux-wpt-content-shell-asan-fyi-rel",
+    builder_spec = builder_config.builder_spec(
+        gclient_config = builder_config.gclient_config(
+            config = "chromium",
+        ),
+        chromium_config = builder_config.chromium_config(
+            config = "chromium",
+            apply_configs = ["mb"],
+            build_config = builder_config.build_config.RELEASE,
+            target_bits = 64,
+        ),
+        build_gs_bucket = "chromium-fyi-archive",
+    ),
+    os = os.LINUX_DEFAULT,
+    console_view_entry = consoles.console_view_entry(
+        category = "linux",
+    ),
+    experimental = True,
+)
+
+ci.builder(
     name = "linux-wpt-identity-fyi-rel",
     builder_spec = builder_config.builder_spec(
         gclient_config = builder_config.gclient_config(config = "chromium"),
diff --git a/ios/chrome/browser/ui/content_suggestions/ntp_home_egtest.mm b/ios/chrome/browser/ui/content_suggestions/ntp_home_egtest.mm
index 9e0789e..76f4a2d 100644
--- a/ios/chrome/browser/ui/content_suggestions/ntp_home_egtest.mm
+++ b/ios/chrome/browser/ui/content_suggestions/ntp_home_egtest.mm
@@ -446,14 +446,17 @@
 // Tests that the tap gesture recognizer that dismisses the keyboard and
 // defocuses the omnibox works.
 - (void)testDefocusOmniboxTapWorks {
-  // TODO(crbug.com/1394749): Test fails on iPad.
-  if ([ChromeEarlGrey isIPadIdiom]) {
-    EARL_GREY_TEST_DISABLED(@"Fails on iPad.");
-  }
-
   [self focusFakebox];
-  [[EarlGrey selectElementWithMatcher:chrome_test_util::NTPCollectionView()]
-      performAction:grey_tap()];
+  if ([ChromeEarlGrey isIPadIdiom]) {
+    // Tap on a space in the collectionView that is not a Feed card.
+    [[EarlGrey selectElementWithMatcher:
+                   grey_accessibilityID(
+                       ntp_home::DiscoverHeaderTitleAccessibilityID())]
+        performAction:grey_tap()];
+  } else {
+    [[EarlGrey selectElementWithMatcher:chrome_test_util::NTPCollectionView()]
+        performAction:grey_tap()];
+  }
 
   [ChromeEarlGreyUI waitForAppToIdle];
   // Check the fake omnibox is displayed again at the same position.
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 42b1555..88d3f5b 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
@@ -576,8 +576,13 @@
   }
 }
 
+// TODO(crbug.com/1359392): Remove this override when kPasswordsGrouping flag is
+// removed.
 - (BOOL)shouldHideToolbar {
-  return !self.editing;
+  // When credentials are grouped, each credential section has its own Delete
+  // button displayed on editing mode, hence showing the toolbar with the Delete
+  // button is not necessary.
+  return IsPasswordGroupingEnabled() || !self.editing;
 }
 
 #pragma mark - Private
diff --git a/ios/chrome/browser/variations/variations_safe_mode_end_to_end_egtest.mm b/ios/chrome/browser/variations/variations_safe_mode_end_to_end_egtest.mm
index 141a6d9..21588f6 100644
--- a/ios/chrome/browser/variations/variations_safe_mode_end_to_end_egtest.mm
+++ b/ios/chrome/browser/variations/variations_safe_mode_end_to_end_egtest.mm
@@ -129,7 +129,7 @@
 
 // Tests that three seed-driven crashes trigger variations safe mode.
 //
-// Corresponds to VariationsSafeModeEndToEndBrowserTest.ExtendedSafeModeEndToEnd
+// Corresponds to VariationsSafeModeEndToEndBrowserTest.ExtendedSafeSeedEndToEnd
 // in variations_safe_mode_browsertest.cc.
 - (void)testVariationsSafeModeEndToEnd {
 #if !TARGET_OS_SIMULATOR
diff --git a/media/base/video_util.cc b/media/base/video_util.cc
index 1a8af95..835b0ef 100644
--- a/media/base/video_util.cc
+++ b/media/base/video_util.cc
@@ -890,13 +890,14 @@
       (src_frame.format() == PIXEL_FORMAT_I420 ||
        src_frame.format() == PIXEL_FORMAT_I420A)) {
     if (dst_frame.format() == PIXEL_FORMAT_I420A) {
-      libyuv::ScalePlane(
-          src_frame.visible_data(VideoFrame::kAPlane),
-          src_frame.stride(VideoFrame::kAPlane),
-          src_frame.visible_rect().width(), src_frame.visible_rect().height(),
-          dst_frame.writable_data(VideoFrame::kAPlane),  // TODO: Is this right?
-          dst_frame.stride(VideoFrame::kAPlane), dst_frame.coded_size().width(),
-          dst_frame.coded_size().height(), kDefaultFiltering);
+      libyuv::ScalePlane(src_frame.visible_data(VideoFrame::kAPlane),
+                         src_frame.stride(VideoFrame::kAPlane),
+                         src_frame.visible_rect().width(),
+                         src_frame.visible_rect().height(),
+                         dst_frame.GetWritableVisibleData(VideoFrame::kAPlane),
+                         dst_frame.stride(VideoFrame::kAPlane),
+                         dst_frame.visible_rect().width(),
+                         dst_frame.visible_rect().height(), kDefaultFiltering);
     }
     int error = libyuv::I420Scale(
         src_frame.visible_data(VideoFrame::kYPlane),
@@ -924,14 +925,14 @@
       (src_frame.format() == PIXEL_FORMAT_NV12 ||
        src_frame.format() == PIXEL_FORMAT_NV12A)) {
     if (dst_frame.format() == PIXEL_FORMAT_NV12A) {
-      libyuv::ScalePlane(src_frame.visible_data(VideoFrame::kAPlaneTriPlanar),
-                         src_frame.stride(VideoFrame::kAPlaneTriPlanar),
-                         src_frame.visible_rect().width(),
-                         src_frame.visible_rect().height(),
-                         dst_frame.writable_data(VideoFrame::kAPlaneTriPlanar),
-                         dst_frame.stride(VideoFrame::kAPlaneTriPlanar),
-                         dst_frame.coded_size().width(),
-                         dst_frame.coded_size().height(), kDefaultFiltering);
+      libyuv::ScalePlane(
+          src_frame.visible_data(VideoFrame::kAPlaneTriPlanar),
+          src_frame.stride(VideoFrame::kAPlaneTriPlanar),
+          src_frame.visible_rect().width(), src_frame.visible_rect().height(),
+          dst_frame.GetWritableVisibleData(VideoFrame::kAPlaneTriPlanar),
+          dst_frame.stride(VideoFrame::kAPlaneTriPlanar),
+          dst_frame.visible_rect().width(), dst_frame.visible_rect().height(),
+          kDefaultFiltering);
     }
     int error = libyuv::NV12Scale(
         src_frame.visible_data(VideoFrame::kYPlane),
@@ -1031,13 +1032,14 @@
       if (error)
         return EncoderStatus::Codes::kFormatConversionError;
       // Convert alpha channel separately.
-      libyuv::ScalePlane(
-          src_frame.visible_data(VideoFrame::kAPlaneTriPlanar),
-          src_frame.stride(VideoFrame::kAPlaneTriPlanar),
-          src_frame.visible_rect().width(), src_frame.visible_rect().height(),
-          dst_frame.writable_data(VideoFrame::kAPlane),
-          dst_frame.stride(VideoFrame::kAPlane), dst_frame.coded_size().width(),
-          dst_frame.coded_size().height(), kDefaultFiltering);
+      libyuv::ScalePlane(src_frame.visible_data(VideoFrame::kAPlaneTriPlanar),
+                         src_frame.stride(VideoFrame::kAPlaneTriPlanar),
+                         src_frame.visible_rect().width(),
+                         src_frame.visible_rect().height(),
+                         dst_frame.GetWritableVisibleData(VideoFrame::kAPlane),
+                         dst_frame.stride(VideoFrame::kAPlane),
+                         dst_frame.visible_rect().width(),
+                         dst_frame.visible_rect().height(), kDefaultFiltering);
       return OkStatus();
     } else {
       // Both resize and NV12-to-I420 conversion are required.
@@ -1077,13 +1079,14 @@
       if (error)
         return EncoderStatus::Codes::kScalingError;
       // Convert alpha channel separately.
-      libyuv::ScalePlane(
-          src_frame.visible_data(VideoFrame::kAPlaneTriPlanar),
-          src_frame.stride(VideoFrame::kAPlaneTriPlanar),
-          src_frame.visible_rect().width(), src_frame.visible_rect().height(),
-          dst_frame.writable_data(VideoFrame::kAPlane),
-          dst_frame.stride(VideoFrame::kAPlane), dst_frame.coded_size().width(),
-          dst_frame.coded_size().height(), kDefaultFiltering);
+      libyuv::ScalePlane(src_frame.visible_data(VideoFrame::kAPlaneTriPlanar),
+                         src_frame.stride(VideoFrame::kAPlaneTriPlanar),
+                         src_frame.visible_rect().width(),
+                         src_frame.visible_rect().height(),
+                         dst_frame.GetWritableVisibleData(VideoFrame::kAPlane),
+                         dst_frame.stride(VideoFrame::kAPlane),
+                         dst_frame.visible_rect().width(),
+                         dst_frame.visible_rect().height(), kDefaultFiltering);
       return OkStatus();
     }
   }
@@ -1166,14 +1169,14 @@
       if (error)
         return EncoderStatus::Codes::kFormatConversionError;
       // Convert alpha channel separately.
-      libyuv::ScalePlane(src_frame.visible_data(VideoFrame::kAPlane),
-                         src_frame.stride(VideoFrame::kAPlane),
-                         src_frame.visible_rect().width(),
-                         src_frame.visible_rect().height(),
-                         dst_frame.writable_data(VideoFrame::kAPlaneTriPlanar),
-                         dst_frame.stride(VideoFrame::kAPlaneTriPlanar),
-                         dst_frame.coded_size().width(),
-                         dst_frame.coded_size().height(), kDefaultFiltering);
+      libyuv::ScalePlane(
+          src_frame.visible_data(VideoFrame::kAPlane),
+          src_frame.stride(VideoFrame::kAPlane),
+          src_frame.visible_rect().width(), src_frame.visible_rect().height(),
+          dst_frame.GetWritableVisibleData(VideoFrame::kAPlaneTriPlanar),
+          dst_frame.stride(VideoFrame::kAPlaneTriPlanar),
+          dst_frame.visible_rect().width(), dst_frame.visible_rect().height(),
+          kDefaultFiltering);
       return OkStatus();
     } else {
       // Both resize and I420-to-NV12 conversion are required.
@@ -1210,14 +1213,14 @@
       if (error)
         return EncoderStatus::Codes::kScalingError;
       // Convert alpha channel separately.
-      libyuv::ScalePlane(src_frame.visible_data(VideoFrame::kAPlane),
-                         src_frame.stride(VideoFrame::kAPlane),
-                         src_frame.visible_rect().width(),
-                         src_frame.visible_rect().height(),
-                         dst_frame.writable_data(VideoFrame::kAPlaneTriPlanar),
-                         dst_frame.stride(VideoFrame::kAPlaneTriPlanar),
-                         dst_frame.coded_size().width(),
-                         dst_frame.coded_size().height(), kDefaultFiltering);
+      libyuv::ScalePlane(
+          src_frame.visible_data(VideoFrame::kAPlane),
+          src_frame.stride(VideoFrame::kAPlane),
+          src_frame.visible_rect().width(), src_frame.visible_rect().height(),
+          dst_frame.GetWritableVisibleData(VideoFrame::kAPlaneTriPlanar),
+          dst_frame.stride(VideoFrame::kAPlaneTriPlanar),
+          dst_frame.visible_rect().width(), dst_frame.visible_rect().height(),
+          kDefaultFiltering);
       return OkStatus();
     }
   }
diff --git a/media/gpu/v4l2/test/v4l2_ioctl_shim.cc b/media/gpu/v4l2/test/v4l2_ioctl_shim.cc
index b785b78c..d0dd1bbcb 100644
--- a/media/gpu/v4l2/test/v4l2_ioctl_shim.cc
+++ b/media/gpu/v4l2/test/v4l2_ioctl_shim.cc
@@ -52,6 +52,7 @@
         V4L2_REQUEST_CODE_AND_STRING(VIDIOC_QBUF),
         V4L2_REQUEST_CODE_AND_STRING(VIDIOC_DQBUF),
         V4L2_REQUEST_CODE_AND_STRING(VIDIOC_STREAMON),
+        V4L2_REQUEST_CODE_AND_STRING(VIDIOC_STREAMOFF),
         V4L2_REQUEST_CODE_AND_STRING(VIDIOC_S_EXT_CTRLS),
         V4L2_REQUEST_CODE_AND_STRING(MEDIA_IOC_REQUEST_ALLOC),
         V4L2_REQUEST_CODE_AND_STRING(MEDIA_REQUEST_IOC_QUEUE),
@@ -267,6 +268,7 @@
 template <>
 bool V4L2IoctlShim::Ioctl(int request_code, int* arg) const {
   DCHECK(request_code == static_cast<int>(VIDIOC_STREAMON) ||
+         request_code == static_cast<int>(VIDIOC_STREAMOFF) ||
          request_code == static_cast<int>(MEDIA_IOC_REQUEST_ALLOC));
   LOG_ASSERT(arg != nullptr) << "|arg| check failed.";
 
@@ -401,6 +403,30 @@
   return ret;
 }
 
+bool V4L2IoctlShim::ReqBufsWithCount(std::unique_ptr<V4L2Queue>& queue,
+                                     uint32_t count) const {
+  struct v4l2_requestbuffers reqbuf;
+
+  memset(&reqbuf, 0, sizeof(reqbuf));
+  reqbuf.count = count;
+  reqbuf.type = queue->type();
+  reqbuf.memory = queue->memory();
+
+  const bool ret = Ioctl(VIDIOC_REQBUFS, &reqbuf);
+
+  queue->set_num_buffers(reqbuf.count);
+
+  if (count == 0) {
+    LOG(INFO) << "Requested to free all buffers in " << queue->type()
+              << "with a buffer count of 0.";
+  } else {
+    LOG(INFO) << queue->num_buffers() << " buffers requested, " << reqbuf.count
+              << " buffers returned for " << queue->type() << ".";
+  }
+
+  return ret;
+}
+
 bool V4L2IoctlShim::QBuf(const std::unique_ptr<V4L2Queue>& queue,
                          const uint32_t index) const {
   LOG_ASSERT(queue->memory() == V4L2_MEMORY_MMAP)
@@ -501,6 +527,12 @@
   return Ioctl(VIDIOC_STREAMON, &arg);
 }
 
+bool V4L2IoctlShim::StreamOff(const enum v4l2_buf_type type) const {
+  int arg = static_cast<int>(type);
+
+  return Ioctl(VIDIOC_STREAMOFF, &arg);
+}
+
 bool V4L2IoctlShim::SetExtCtrls(const std::unique_ptr<V4L2Queue>& queue,
                                 v4l2_ext_controls* ext_ctrls) const {
   // TODO(b/230021497): add compressed header probability related change
@@ -626,4 +658,4 @@
 }
 
 }  // namespace v4l2_test
-}  // namespace media
\ No newline at end of file
+}  // namespace media
diff --git a/media/gpu/v4l2/test/v4l2_ioctl_shim.h b/media/gpu/v4l2/test/v4l2_ioctl_shim.h
index 331ba675..25f244fa 100644
--- a/media/gpu/v4l2/test/v4l2_ioctl_shim.h
+++ b/media/gpu/v4l2/test/v4l2_ioctl_shim.h
@@ -92,7 +92,12 @@
 
   enum v4l2_buf_type type() const { return type_; }
   uint32_t fourcc() const { return fourcc_; }
+
   gfx::Size display_size() const { return display_size_; }
+  void set_display_size(gfx::Size display_size) {
+    display_size_ = display_size;
+  }
+
   enum v4l2_memory memory() const { return memory_; }
 
   void set_buffers(MmapedBuffers& buffers) { buffers_ = buffers; }
@@ -132,13 +137,15 @@
     queued_buffer_indexes_.erase(index);
   }
 
+  void DequeueAllBufferIndexes() { queued_buffer_indexes_.clear(); }
+
  private:
   const enum v4l2_buf_type type_;
   const uint32_t fourcc_;
   MmapedBuffers buffers_;
   uint32_t num_buffers_;
   // The size of the image on the screen.
-  const gfx::Size display_size_;
+  gfx::Size display_size_;
   // The size of the encoded frame. Usually has an alignment of 16, 32
   // depending on codec.
   gfx::Size coded_size_;
@@ -189,6 +196,10 @@
   // Allocates buffers via VIDIOC_REQBUFS for |queue|.
   [[nodiscard]] bool ReqBufs(std::unique_ptr<V4L2Queue>& queue) const;
 
+  // Allocates buffers via VIDIOC_REQBUFS for |queue| with a buffer count.
+  [[nodiscard]] bool ReqBufsWithCount(std::unique_ptr<V4L2Queue>& queue,
+                                      uint32_t count) const;
+
   // Enqueues an empty (capturing) or filled (output) buffer
   // in the driver's incoming |queue|.
   [[nodiscard]] bool QBuf(const std::unique_ptr<V4L2Queue>& queue,
@@ -202,6 +213,9 @@
   // Starts streaming |queue| (via VIDIOC_STREAMON).
   [[nodiscard]] bool StreamOn(const enum v4l2_buf_type type) const;
 
+  // Stops streaming |queue| (via VIDIOC_STREAMOFF).
+  [[nodiscard]] bool StreamOff(const enum v4l2_buf_type type) const;
+
   // Sets the value of controls which specify decoding parameters
   // for each frame.
   [[nodiscard]] bool SetExtCtrls(const std::unique_ptr<V4L2Queue>& queue,
diff --git a/media/gpu/v4l2/test/video_decoder.cc b/media/gpu/v4l2/test/video_decoder.cc
index 5d8ee81..c9f7640 100644
--- a/media/gpu/v4l2/test/video_decoder.cc
+++ b/media/gpu/v4l2/test/video_decoder.cc
@@ -50,10 +50,10 @@
 // 4:2:0 subsampled, but UV are interlaced). This function converts a single
 // MM21 plane into its equivalent NV12 plane.
 void DetilePlane(std::vector<char>& dest,
-                 gfx::Size dest_size,
+                 const gfx::Size& dest_size,
                  char* src,
-                 gfx::Size src_size,
-                 gfx::Size tile_size) {
+                 const gfx::Size& src_size,
+                 const gfx::Size& tile_size) {
   // Tile size in bytes.
   const int tile_len = tile_size.GetArea();
   // |width| rounded down to the nearest multiple of |tile_width|.
@@ -166,11 +166,15 @@
   if (!v4l2_ioctl_->SetFmt(CAPTURE_queue_))
     LOG(FATAL) << "SetFmt for CAPTURE queue failed.";
 
-  if (!v4l2_ioctl_->ReqBufs(OUTPUT_queue_))
-    LOG(FATAL) << "ReqBufs for OUTPUT queue failed.";
+  // If there is a dynamic resolution change, the Initialization sequence will
+  // be performed again, minus the allocation of OUTPUT queue buffers.
+  if (!IsResolutionChanged()) {
+    if (!v4l2_ioctl_->ReqBufs(OUTPUT_queue_))
+      LOG(FATAL) << "ReqBufs for OUTPUT queue failed.";
 
-  if (!v4l2_ioctl_->QueryAndMmapQueueBuffers(OUTPUT_queue_))
-    LOG(FATAL) << "QueryAndMmapQueueBuffers for OUTPUT queue failed";
+    if (!v4l2_ioctl_->QueryAndMmapQueueBuffers(OUTPUT_queue_))
+      LOG(FATAL) << "QueryAndMmapQueueBuffers for OUTPUT queue failed";
+  }
 
   if (!v4l2_ioctl_->ReqBufs(CAPTURE_queue_))
     LOG(FATAL) << "ReqBufs for CAPTURE queue failed.";
@@ -196,15 +200,52 @@
     LOG(FATAL) << "StreamOn for CAPTURE queue failed.";
 }
 
+// Follows the dynamic resolution change sequence described in
+// https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/dev-stateless-decoder.html#dynamic-resolution-change
+VideoDecoder::Result VideoDecoder::HandleDynamicResolutionChange(
+    const gfx::Size& new_resolution) {
+  // Call VIDIOC_STREAMOFF() on both the OUTPUT and CAPTURE queues.
+  if (!v4l2_ioctl_->StreamOff(OUTPUT_queue_->type()))
+    LOG(FATAL) << "StreamOff for OUTPUT queue failed.";
+
+  if (!v4l2_ioctl_->StreamOff(CAPTURE_queue_->type()))
+    LOG(FATAL) << "StreamOff for CAPTURE queue failed.";
+
+  // Free all CAPTURE buffers from the driver side by calling VIDIOC_REQBUFS()
+  // on the CAPTURE queue with a buffer count of zero.
+  if (!v4l2_ioctl_->ReqBufsWithCount(CAPTURE_queue_, 0))
+    LOG(FATAL) << "Failed to free all buffers for CAPTURE queue";
+
+  // Free queued CAPTURE buffer indexes that are tracked by the client side.
+  CAPTURE_queue_->DequeueAllBufferIndexes();
+
+  // Set the new resolution on OUTPUT queue. The driver will then pick up
+  // the new resolution to be set on the coded size for CAPTURE queue.
+  OUTPUT_queue_->set_display_size(new_resolution);
+  OUTPUT_queue_->set_coded_size(new_resolution);
+
+  if (!v4l2_ioctl_->ReqBufsWithCount(CAPTURE_queue_,
+                                     number_of_buffers_in_capture_queue_)) {
+    LOG(FATAL) << "ReqBufs for CAPTURE queue failed.";
+  }
+  CAPTURE_queue_->set_display_size(new_resolution);
+
+  // Perform the initialization sequence again
+  Initialize();
+  is_resolution_changed_ = false;
+
+  return VideoDecoder::kOk;
+}
+
 // Unpacks NV12 to I420 and optionally trims padding from source.
 // This expects a contiguous NV12 buffer, as specified by
 // V4L2_PIX_FMT_NV12.
 void VideoDecoder::ConvertNV12ToYUV(std::vector<char>& dest_y,
                                     std::vector<char>& dest_u,
                                     std::vector<char>& dest_v,
-                                    gfx::Size dest_size,
+                                    const gfx::Size& dest_size,
                                     const char* src,
-                                    gfx::Size src_size) {
+                                    const gfx::Size& src_size) {
   CHECK(dest_size.width() <= src_size.width());
   CHECK(dest_size.height() <= src_size.height());
 
@@ -246,10 +287,10 @@
 void VideoDecoder::ConvertMM21ToYUV(std::vector<char>& dest_y,
                                     std::vector<char>& dest_u,
                                     std::vector<char>& dest_v,
-                                    gfx::Size dest_size,
+                                    const gfx::Size& dest_size,
                                     char* src_y,
                                     char* src_uv,
-                                    gfx::Size src_size) {
+                                    const gfx::Size& src_size) {
   constexpr int kMM21TileWidth = 16;
   constexpr int kMM21TileHeight = 32;
 
diff --git a/media/gpu/v4l2/test/video_decoder.h b/media/gpu/v4l2/test/video_decoder.h
index 3e27fc4..3afadd7 100644
--- a/media/gpu/v4l2/test/video_decoder.h
+++ b/media/gpu/v4l2/test/video_decoder.h
@@ -46,26 +46,34 @@
                                  gfx::Size& size,
                                  const int frame_number) = 0;
 
+  // Handles dynamic resolution change with new resolution parsed from frame
+  // header.
+  VideoDecoder::Result HandleDynamicResolutionChange(
+      const gfx::Size& new_resolution);
+
   // Returns whether the last decoded frame was visible.
   bool LastDecodedFrameVisible() const { return last_decoded_frame_visible_; }
 
+  // Return whether there is a dynamic resolytion change.
+  bool IsResolutionChanged() const { return is_resolution_changed_; }
+
  protected:
   // Helper method for converting NV12 frames to I420.
   static void ConvertNV12ToYUV(std::vector<char>& dest_y,
                                std::vector<char>& dest_u,
                                std::vector<char>& dest_v,
-                               gfx::Size dest_size,
+                               const gfx::Size& dest_size,
                                const char* src,
-                               gfx::Size src_size);
+                               const gfx::Size& src_size);
 
   // Helper method for converting MM21 frames to I420.
   static void ConvertMM21ToYUV(std::vector<char>& dest_y,
                                std::vector<char>& dest_u,
                                std::vector<char>& dest_v,
-                               gfx::Size dest_size,
+                               const gfx::Size& dest_size,
                                char* src_y,
                                char* src_uv,
-                               gfx::Size src_size);
+                               const gfx::Size& src_size);
 
   // Wrapper for V4L2 ioctl requests.
   const std::unique_ptr<V4L2IoctlShim> v4l2_ioctl_;
@@ -78,6 +86,12 @@
 
   // Whether the last decoded frame was visible.
   bool last_decoded_frame_visible_ = false;
+
+  // Whether there is a dynamic support change.
+  bool is_resolution_changed_ = false;
+
+  // Number of buffers in CAPTURE queue varied by different codecs.
+  uint32_t number_of_buffers_in_capture_queue_;
 };
 
 }  // namespace v4l2_test
diff --git a/media/gpu/v4l2/test/vp8_decoder.cc b/media/gpu/v4l2/test/vp8_decoder.cc
index 144209d..64bbcccc 100644
--- a/media/gpu/v4l2/test/vp8_decoder.cc
+++ b/media/gpu/v4l2/test/vp8_decoder.cc
@@ -38,6 +38,37 @@
                   base::strict_cast<TypeOfVp8RefType>(media::VP8_FRAME_ALTREF),
               "Invalid index value for Altref reference frame");
 
+// The resolution encoded in the bitstream is required for queue creation. Note
+// that parsing ivf file and parsing the first frame VP8 parser happen
+// again later in the code. This is intentionally duplicated.
+const gfx::Size GetResolutionFromBitstream(
+    const base::MemoryMappedFile& stream) {
+  media::IvfParser ivf_parser{};
+  media::IvfFileHeader ivf_file_header{};
+
+  if (!ivf_parser.Initialize(stream.data(), stream.length(),
+                             &ivf_file_header)) {
+    LOG(FATAL) << "Couldn't initialize IVF parser.";
+  }
+
+  media::IvfFrameHeader ivf_frame_header{};
+  const uint8_t* ivf_frame_data;
+
+  if (!ivf_parser.ParseNextFrame(&ivf_frame_header, &ivf_frame_data)) {
+    LOG(FATAL) << "Failed to parse the first frame with IVF parser.";
+  }
+
+  VLOG(2) << "Ivf file header: " << ivf_file_header.width << " x "
+          << ivf_file_header.height;
+
+  media::Vp8Parser vp8_parser;
+  media::Vp8FrameHeader vp8_frame_header;
+  vp8_parser.ParseFrame(ivf_frame_data, ivf_frame_header.frame_size,
+                        &vp8_frame_header);
+
+  return gfx::Size(vp8_frame_header.width, vp8_frame_header.height);
+}
+
 // Section 9.4. Loop filter type and levels syntax in VP8 specs.
 // https://datatracker.ietf.org/doc/rfc6386/
 struct v4l2_vp8_loop_filter FillV4L2VP8LoopFilterHeader(
@@ -227,6 +258,7 @@
   DCHECK(v4l2_ioctl_->QueryCtrl(V4L2_CID_STATELESS_VP8_FRAME));
 
   std::fill(ref_frames_.begin(), ref_frames_.end(), nullptr);
+  number_of_buffers_in_capture_queue_ = kNumberOfBuffersInCaptureQueue;
 }
 
 Vp8Decoder::~Vp8Decoder() = default;
@@ -278,20 +310,22 @@
   LOG(INFO) << "Ivf file header: " << file_header.width << " x "
             << file_header.height;
 
+  const gfx::Size bitstream_coded_size = GetResolutionFromBitstream(stream);
+
   // TODO(b/256251694): might need to consider using more than 1 file descriptor
   // (fd) & buffer with the output queue for 4K60 requirement.
   // https://buganizer.corp.google.com/issues/202214561#comment31
   auto OUTPUT_queue = std::make_unique<V4L2Queue>(
       V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, kDriverCodecFourcc,
-      gfx::Size(file_header.width, file_header.height), /*num_planes=*/1,
-      V4L2_MEMORY_MMAP, /*num_buffers=*/kNumberOfBuffersInOutputQueue);
+      bitstream_coded_size, /*num_planes=*/1, V4L2_MEMORY_MMAP,
+      /*num_buffers=*/kNumberOfBuffersInOutputQueue);
 
   // TODO(b/256543928): enable V4L2_MEMORY_DMABUF memory for CAPTURE queue.
   // |num_planes| represents separate memory buffers, not planes for Y, U, V.
   // https://www.kernel.org/doc/html/v5.10/userspace-api/media/v4l/pixfmt-v4l2-mplane.html#c.V4L.v4l2_plane_pix_format
   auto CAPTURE_queue = std::make_unique<V4L2Queue>(
       V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE, uncompressed_fourcc,
-      gfx::Size(file_header.width, file_header.height),
+      bitstream_coded_size,
       /*num_planes=*/num_planes, V4L2_MEMORY_MMAP,
       /*num_buffers=*/kNumberOfBuffersInCaptureQueue);
 
@@ -534,6 +568,23 @@
       break;
   }
 
+  if (frame_hdr.IsKeyframe()) {
+    is_resolution_changed_ =
+        frame_hdr.width != OUTPUT_queue_->coded_size().width() ||
+        frame_hdr.height != OUTPUT_queue_->coded_size().height();
+  } else {
+    frame_hdr.width = OUTPUT_queue_->coded_size().width();
+    frame_hdr.height = OUTPUT_queue_->coded_size().height();
+  }
+
+  if (IsResolutionChanged()) {
+    const gfx::Size new_resolution(frame_hdr.width, frame_hdr.height);
+    LOG_ASSERT(!new_resolution.IsEmpty())
+        << "New key frame resolution is empty.";
+
+    HandleDynamicResolutionChange(new_resolution);
+  }
+
   VLOG_IF(2, !frame_hdr.show_frame) << "Not displaying frame";
   last_decoded_frame_visible_ = frame_hdr.show_frame;
 
diff --git a/media/video/video_encode_accelerator_adapter.cc b/media/video/video_encode_accelerator_adapter.cc
index 61cf5e4..6a050c9a 100644
--- a/media/video/video_encode_accelerator_adapter.cc
+++ b/media/video/video_encode_accelerator_adapter.cc
@@ -160,23 +160,23 @@
     : public base::RefCountedThreadSafe<GpuMemoryBufferVideoFramePool> {
  public:
   GpuMemoryBufferVideoFramePool(GpuVideoAcceleratorFactories* gpu_factories,
-                                const gfx::Size& size)
-      : gpu_factories_(gpu_factories), size_(size) {}
+                                const gfx::Size& coded_size)
+      : gpu_factories_(gpu_factories), coded_size_(coded_size) {}
   GpuMemoryBufferVideoFramePool(const GpuMemoryBufferVideoFramePool&) = delete;
   GpuMemoryBufferVideoFramePool& operator=(
       const GpuMemoryBufferVideoFramePool&) = delete;
 
-  scoped_refptr<VideoFrame> MaybeCreateVideoFrame(const gfx::Size& size) {
+  scoped_refptr<VideoFrame> MaybeCreateVideoFrame(
+      const gfx::Size& visible_size) {
     DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-    if (size_ != size)
-      return nullptr;
+    DCHECK(gfx::Rect(coded_size_).Contains(gfx::Rect(visible_size)));
 
     if (available_gmbs_.empty()) {
       constexpr auto kBufferFormat = gfx::BufferFormat::YUV_420_BIPLANAR;
       constexpr auto kBufferUsage =
           gfx::BufferUsage::VEA_READ_CAMERA_AND_CPU_READ_WRITE;
-      auto gmb = gpu_factories_->CreateGpuMemoryBuffer(size_, kBufferFormat,
-                                                       kBufferUsage);
+      auto gmb = gpu_factories_->CreateGpuMemoryBuffer(
+          coded_size_, kBufferFormat, kBufferUsage);
       if (!gmb)
         return nullptr;
 
@@ -191,7 +191,7 @@
     const gpu::MailboxHolder kEmptyMailBoxes[media::VideoFrame::kMaxPlanes] =
         {};
     return VideoFrame::WrapExternalGpuMemoryBuffer(
-        gfx::Rect(size_), size_, std::move(gmb), kEmptyMailBoxes,
+        gfx::Rect(visible_size), visible_size, std::move(gmb), kEmptyMailBoxes,
         std::move(reuse_cb), base::TimeDelta());
   }
 
@@ -208,7 +208,7 @@
   }
 
   const raw_ptr<GpuVideoAcceleratorFactories> gpu_factories_;
-  const gfx::Size size_;
+  const gfx::Size coded_size_;
 
   std::vector<std::unique_ptr<gfx::GpuMemoryBuffer>> available_gmbs_;
 
@@ -522,7 +522,8 @@
   }
 
   const bool frame_needs_resizing =
-      frame->visible_rect().size() != options_.frame_size;
+      frame->visible_rect().size() != options_.frame_size ||
+      frame->coded_size() != input_coded_size_;
 
   // Try using a frame with GPU buffer both are true:
   // 1. the frame already has GPU buffer
@@ -539,9 +540,9 @@
 
   EncoderStatus::Or<scoped_refptr<VideoFrame>> result(nullptr);
   if (use_gpu_buffer)
-    result = PrepareGpuFrame(input_coded_size_, frame);
+    result = PrepareGpuFrame(frame);
   else
-    result = PrepareCpuFrame(input_coded_size_, frame);
+    result = PrepareCpuFrame(frame);
 
   if (!result.has_value()) {
     std::move(done_cb).Run(
@@ -946,25 +947,27 @@
 // frames can I420, NV12, or RGB -- they'll be converted to I420 if needed.
 EncoderStatus::Or<scoped_refptr<VideoFrame>>
 VideoEncodeAcceleratorAdapter::PrepareCpuFrame(
-    const gfx::Size& size,
     scoped_refptr<VideoFrame> src_frame) {
   TRACE_EVENT0("media", "VideoEncodeAcceleratorAdapter::PrepareCpuFrame");
 
+  const auto dest_coded_size = input_coded_size_;
+  const auto dest_visible_rect = gfx::Rect(options_.frame_size);
+
   // The frame whose storage type is STORAGE_OWNED_MEMORY and
   // STORAGE_UNOWNED_MEMORY is copied here, not in mojo_video_frame_traits.
   // It is because VEAAdapter recycles the SharedMemoryRegion, but
   // mojo_video_frame_traits doesn't.
   if (src_frame->storage_type() == VideoFrame::STORAGE_SHMEM &&
       src_frame->format() == PIXEL_FORMAT_I420 &&
-      src_frame->visible_rect().size() == size &&
-      src_frame->visible_rect().origin().IsOrigin()) {
+      src_frame->visible_rect() == dest_visible_rect &&
+      src_frame->coded_size() == dest_coded_size) {
     // Nothing to do here, the input frame is already what we need.
     return src_frame;
   }
 
   if (!input_pool_) {
     const size_t input_buffer_size =
-        VideoFrame::AllocationSize(PIXEL_FORMAT_I420, size);
+        VideoFrame::AllocationSize(PIXEL_FORMAT_I420, dest_coded_size);
     input_pool_ = base::MakeRefCounted<ReadOnlyRegionPool>(input_buffer_size);
   }
 
@@ -978,9 +981,9 @@
                               ? ConvertToMemoryMappedFrame(src_frame)
                               : src_frame;
   auto shared_frame = VideoFrame::WrapExternalData(
-      PIXEL_FORMAT_I420, size, gfx::Rect(size), size,
-      static_cast<uint8_t*>(mapping->memory()), mapping->size(),
-      src_frame->timestamp());
+      PIXEL_FORMAT_I420, dest_coded_size, dest_visible_rect,
+      dest_visible_rect.size(), static_cast<uint8_t*>(mapping->memory()),
+      mapping->size(), src_frame->timestamp());
 
   if (!shared_frame || !mapped_src_frame)
     return EncoderStatus(EncoderStatus::Codes::kEncoderFailedEncode);
@@ -1001,24 +1004,28 @@
 // can I420, NV12, or RGB -- they'll be converted to NV12 if needed.
 EncoderStatus::Or<scoped_refptr<VideoFrame>>
 VideoEncodeAcceleratorAdapter::PrepareGpuFrame(
-    const gfx::Size& size,
     scoped_refptr<VideoFrame> src_frame) {
   TRACE_EVENT0("media", "VideoEncodeAcceleratorAdapter::PrepareGpuFrame");
   DCHECK_CALLED_ON_VALID_SEQUENCE(accelerator_sequence_checker_);
   DCHECK(src_frame);
+
+  const auto dest_coded_size = input_coded_size_;
+  const auto dest_visible_rect = gfx::Rect(options_.frame_size);
+
   if (src_frame->HasGpuMemoryBuffer() &&
       src_frame->format() == PIXEL_FORMAT_NV12 &&
-      (gpu_resize_supported_ || src_frame->visible_rect().size() == size)) {
+      (gpu_resize_supported_ || src_frame->coded_size() == dest_coded_size)) {
     // Nothing to do here, the input frame is already what we need
     return src_frame;
   }
 
   if (!gmb_frame_pool_) {
     gmb_frame_pool_ = base::MakeRefCounted<GpuMemoryBufferVideoFramePool>(
-        gpu_factories_, size);
+        gpu_factories_, dest_coded_size);
   }
 
-  auto gpu_frame = gmb_frame_pool_->MaybeCreateVideoFrame(size);
+  auto gpu_frame =
+      gmb_frame_pool_->MaybeCreateVideoFrame(dest_visible_rect.size());
   if (!gpu_frame)
     return EncoderStatus(EncoderStatus::Codes::kEncoderFailedEncode);
 
diff --git a/media/video/video_encode_accelerator_adapter.h b/media/video/video_encode_accelerator_adapter.h
index ead17024..4cf209fb0 100644
--- a/media/video/video_encode_accelerator_adapter.h
+++ b/media/video/video_encode_accelerator_adapter.h
@@ -123,10 +123,8 @@
   template <class T>
   T WrapCallback(T cb);
   EncoderStatus::Or<scoped_refptr<VideoFrame>> PrepareGpuFrame(
-      const gfx::Size& size,
       scoped_refptr<VideoFrame> src_frame);
   EncoderStatus::Or<scoped_refptr<VideoFrame>> PrepareCpuFrame(
-      const gfx::Size& size,
       scoped_refptr<VideoFrame> src_frame);
 
   scoped_refptr<ReadOnlyRegionPool> input_pool_;
diff --git a/net/http/http_response_info.cc b/net/http/http_response_info.cc
index 72b007a..4c637b4 100644
--- a/net/http/http_response_info.cc
+++ b/net/http/http_response_info.cc
@@ -180,7 +180,8 @@
     case CONNECTION_INFO_QUIC_DRAFT_29:
     case CONNECTION_INFO_QUIC_T051:
     case CONNECTION_INFO_QUIC_RFC_V1:
-    case CONNECTION_INFO_QUIC_2_DRAFT_1:
+    case CONNECTION_INFO_DEPRECATED_QUIC_2_DRAFT_1:
+    case CONNECTION_INFO_QUIC_2_DRAFT_8:
       return CONNECTION_INFO_COARSE_QUIC;
 
     case CONNECTION_INFO_UNKNOWN:
@@ -553,7 +554,8 @@
     case CONNECTION_INFO_QUIC_DRAFT_29:
     case CONNECTION_INFO_QUIC_T051:
     case CONNECTION_INFO_QUIC_RFC_V1:
-    case CONNECTION_INFO_QUIC_2_DRAFT_1:
+    case CONNECTION_INFO_DEPRECATED_QUIC_2_DRAFT_1:
+    case CONNECTION_INFO_QUIC_2_DRAFT_8:
       return true;
     case NUM_OF_CONNECTION_INFOS:
       NOTREACHED();
@@ -652,8 +654,10 @@
       return "h3-T051";
     case CONNECTION_INFO_QUIC_RFC_V1:
       return "h3";
-    case CONNECTION_INFO_QUIC_2_DRAFT_1:
+    case CONNECTION_INFO_DEPRECATED_QUIC_2_DRAFT_1:
       return "h3/quic2draft01";
+    case CONNECTION_INFO_QUIC_2_DRAFT_8:
+      return "h3/quic2draft08";
     case NUM_OF_CONNECTION_INFOS:
       break;
   }
diff --git a/net/http/http_response_info.h b/net/http/http_response_info.h
index f8b39a7..1455d37 100644
--- a/net/http/http_response_info.h
+++ b/net/http/http_response_info.h
@@ -78,7 +78,8 @@
     CONNECTION_INFO_QUIC_DRAFT_29 = 38,
     CONNECTION_INFO_QUIC_T051 = 39,
     CONNECTION_INFO_QUIC_RFC_V1 = 40,
-    CONNECTION_INFO_QUIC_2_DRAFT_1 = 41,
+    CONNECTION_INFO_DEPRECATED_QUIC_2_DRAFT_1 = 41,
+    CONNECTION_INFO_QUIC_2_DRAFT_8 = 42,
     NUM_OF_CONNECTION_INFOS,
   };
 
diff --git a/net/quic/quic_http_stream.cc b/net/quic/quic_http_stream.cc
index 790c7b9..eb7a639 100644
--- a/net/quic/quic_http_stream.cc
+++ b/net/quic/quic_http_stream.cc
@@ -90,9 +90,9 @@
       return HttpResponseInfo::CONNECTION_INFO_QUIC_RFC_V1;
     case quic::QUIC_VERSION_RESERVED_FOR_NEGOTIATION:
       return HttpResponseInfo::CONNECTION_INFO_QUIC_999;
-    case quic::QUIC_VERSION_IETF_2_DRAFT_01:
+    case quic::QUIC_VERSION_IETF_2_DRAFT_08:
       DCHECK(quic_version.UsesTls());
-      return HttpResponseInfo::CONNECTION_INFO_QUIC_2_DRAFT_1;
+      return HttpResponseInfo::CONNECTION_INFO_QUIC_2_DRAFT_8;
   }
   NOTREACHED();
   return HttpResponseInfo::CONNECTION_INFO_QUIC_UNKNOWN_VERSION;
diff --git a/net/quic/quic_network_transaction_unittest.cc b/net/quic/quic_network_transaction_unittest.cc
index aa63e952..d2873b2c 100644
--- a/net/quic/quic_network_transaction_unittest.cc
+++ b/net/quic/quic_network_transaction_unittest.cc
@@ -690,11 +690,11 @@
     // QUIC v1 and QUIC v2 are considered a match, because they have the same
     // ALPN token.
     if ((connection_info == HttpResponseInfo::CONNECTION_INFO_QUIC_RFC_V1 ||
-         connection_info == HttpResponseInfo::CONNECTION_INFO_QUIC_2_DRAFT_1) &&
+         connection_info == HttpResponseInfo::CONNECTION_INFO_QUIC_2_DRAFT_8) &&
         (response->connection_info ==
              HttpResponseInfo::CONNECTION_INFO_QUIC_RFC_V1 ||
          response->connection_info ==
-             HttpResponseInfo::CONNECTION_INFO_QUIC_2_DRAFT_1)) {
+             HttpResponseInfo::CONNECTION_INFO_QUIC_2_DRAFT_8)) {
       return;
     }
 
diff --git a/sandbox/linux/integration_tests/seccomp_broker_process_unittest.cc b/sandbox/linux/integration_tests/seccomp_broker_process_unittest.cc
index b8ec997c..d85f940 100644
--- a/sandbox/linux/integration_tests/seccomp_broker_process_unittest.cc
+++ b/sandbox/linux/integration_tests/seccomp_broker_process_unittest.cc
@@ -4,6 +4,7 @@
 
 #include <errno.h>
 #include <fcntl.h>
+#include <sys/inotify.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <unistd.h>
@@ -15,13 +16,19 @@
 
 #include "base/bind.h"
 #include "base/callback.h"
+#include "base/files/file.h"
 #include "base/files/file_path.h"
+#include "base/files/file_path_watcher.h"
+#include "base/files/file_path_watcher_inotify.h"
+#include "base/files/file_util.h"
 #include "base/files/scoped_file.h"
 #include "base/files/scoped_temp_dir.h"
 #include "base/logging.h"
 #include "base/memory/raw_ptr.h"
 #include "base/posix/eintr_wrapper.h"
+#include "base/run_loop.h"
 #include "base/test/bind.h"
+#include "base/test/task_environment.h"
 #include "build/build_config.h"
 #include "sandbox/linux/bpf_dsl/bpf_dsl.h"
 #include "sandbox/linux/bpf_dsl/policy.h"
@@ -261,6 +268,7 @@
   virtual int Mkdir(const char* pathname, mode_t mode) = 0;
   virtual int Rmdir(const char* path) = 0;
   virtual int Unlink(const char* path) = 0;
+  virtual int InotifyAddWatch(int fd, const char* path, uint32_t mask) = 0;
 };
 
 class IPCSyscaller : public Syscaller {
@@ -307,6 +315,11 @@
     return broker_->GetBrokerClientSignalBased()->Unlink(path);
   }
 
+  int InotifyAddWatch(int fd, const char* path, uint32_t mask) override {
+    return broker_->GetBrokerClientSignalBased()->InotifyAddWatch(fd, path,
+                                                                  mask);
+  }
+
  private:
   raw_ptr<BrokerProcess> broker_;
 };
@@ -385,6 +398,13 @@
       return -errno;
     return ret;
   }
+
+  int InotifyAddWatch(int fd, const char* path, uint32_t mask) override {
+    int ret = syscall(__NR_inotify_add_watch, fd, path, mask);
+    if (ret < 0)
+      return -errno;
+    return ret;
+  }
 };
 #endif  // defined(DIRECT_SYSCALLER_ENABLED)
 
@@ -448,9 +468,21 @@
       return -errno;
     return ret;
   }
+
+  int InotifyAddWatch(int fd, const char* path, uint32_t mask) override {
+    int ret = inotify_add_watch(fd, path, mask);
+    if (ret < 0)
+      return -errno;
+    return ret;
+  }
 };
 
-enum class SyscallerType { IPCSyscaller = 0, DirectSyscaller, LibcSyscaller };
+enum class SyscallerType {
+  IPCSyscaller = 0,
+  DirectSyscaller,
+  LibcSyscaller,
+  NoSyscaller
+};
 
 // The testing infrastructure for the broker integration tests is built on the
 // same infrastructure that BPF_TEST or SANDBOX_TEST uses. Each individual test
@@ -599,6 +631,9 @@
       case SyscallerType::LibcSyscaller:
         syscaller_ = std::make_unique<LibcSyscaller>();
         break;
+      case SyscallerType::NoSyscaller:
+        syscaller_ = nullptr;
+        break;
     }
   }
 
@@ -1125,7 +1160,7 @@
     // Create a conflict for the temp filename.
     base::ScopedFD fd(
         open(existing_temp_file_str_.c_str(), O_RDWR | O_CREAT, 0600));
-    BPF_ASSERT_GE(fd.get(), 0);
+    ASSERT_GE(fd.get(), 0);
   }
 
   BrokerParams ChildSetUpPreSandbox() override {
@@ -2535,5 +2570,276 @@
   RunAllBrokerTests<UnlinkFileRWCPermissionsDelegate>();
 }
 
+// Parent class for the inotify_add_watch() tests.
+class InotifyAddWatchDelegate : public BrokerTestDelegate {
+ public:
+  const uint32_t kBadMask =
+      IN_CREATE | IN_DELETE | IN_CLOSE_WRITE | IN_MOVE | IN_ONLYDIR;
+  const uint32_t kGoodMask = kBadMask | IN_ATTRIB;
+
+  static constexpr char kNestedTempDirName[] = "nested_temp_dir";
+  static constexpr char kBadPrefixName[] = "nested_t";
+
+  void ParentSetUp() override {
+    // Create two nested temp dirs.
+    ASSERT_TRUE(temp_dir_.CreateUniqueTempDirUnderPath(
+        base::FilePath(kTempDirForTests)));
+    temp_dir_str_ = temp_dir_.GetPath().MaybeAsASCII();
+    ASSERT_FALSE(temp_dir_str_.empty());
+
+    ASSERT_TRUE(nested_temp_dir_.Set(
+        temp_dir_.GetPath().AppendASCII(kNestedTempDirName)));
+    nested_temp_dir_str_ = nested_temp_dir_.GetPath().MaybeAsASCII();
+    ASSERT_FALSE(nested_temp_dir_str_.empty());
+
+    temp_file_ = base::CreateAndOpenTemporaryFileInDir(
+        nested_temp_dir_.GetPath(), &temp_file_path_);
+    temp_file_path_str_ = temp_file_path_.MaybeAsASCII();
+    ASSERT_FALSE(temp_file_path_str_.empty());
+  }
+
+ protected:
+  // Parent temp directory.
+  base::ScopedTempDir temp_dir_;
+  std::string temp_dir_str_;
+
+  // A directory nested under |temp_dir_|.
+  base::ScopedTempDir nested_temp_dir_;
+  std::string nested_temp_dir_str_;
+
+  // In |nested_temp_dir_|.
+  base::FilePath temp_file_path_;
+  std::string temp_file_path_str_;
+  base::File temp_file_;
+};
+
+// Try inotify_add_watch() without the relevant broker command.
+class InotifyAddWatchNoCommandDelegate final : public InotifyAddWatchDelegate {
+ public:
+  BrokerParams ChildSetUpPreSandbox() override {
+    BrokerParams params;
+    params.allowed_command_set = syscall_broker::MakeBrokerCommandSet({});
+    params.permissions = {
+        BrokerFilePermission::InotifyAddWatchWithIntermediateDirs(
+            temp_file_path_str_),
+        BrokerFilePermission::ReadWriteCreateRecursive(nested_temp_dir_str_ +
+                                                       "/")};
+    return params;
+  }
+
+  void RunTestInSandboxedChild(Syscaller* syscaller) override {
+    base::ScopedFD inotify_instance(inotify_init());
+    BPF_ASSERT(inotify_instance.is_valid());
+    BPF_ASSERT_EQ(
+        -kFakeErrnoSentinel,
+        syscaller->InotifyAddWatch(inotify_instance.get(),
+                                   nested_temp_dir_str_.c_str(), kGoodMask));
+  }
+};
+
+TEST(BrokerProcessIntegrationTest, InotifyAddWatchNoCommand) {
+  RunAllBrokerTests<InotifyAddWatchNoCommandDelegate>();
+}
+
+// Try inotify_add_watch() without the relevant permissions.
+class InotifyAddWatchNoPermissionsDelegate final
+    : public InotifyAddWatchDelegate {
+ public:
+  BrokerParams ChildSetUpPreSandbox() override {
+    BrokerParams params;
+    params.allowed_command_set = syscall_broker::MakeBrokerCommandSet(
+        {syscall_broker::COMMAND_INOTIFY_ADD_WATCH});
+    params.permissions = {};
+    return params;
+  }
+
+  void RunTestInSandboxedChild(Syscaller* syscaller) override {
+    base::ScopedFD inotify_instance(inotify_init());
+    BPF_ASSERT(inotify_instance.is_valid());
+    BPF_ASSERT_EQ(
+        -kFakeErrnoSentinel,
+        syscaller->InotifyAddWatch(inotify_instance.get(),
+                                   nested_temp_dir_str_.c_str(), kGoodMask));
+  }
+};
+
+TEST(BrokerProcessIntegrationTest, InotifyAddWatchNoPermissions) {
+  RunAllBrokerTests<InotifyAddWatchNoPermissionsDelegate>();
+}
+
+// Try inotify_add_watch() with a variety of bad arguments.
+class InotifyAddWatchBadArgumentsDelegate final
+    : public InotifyAddWatchDelegate {
+ public:
+  void ParentSetUp() override {
+    InotifyAddWatchDelegate::ParentSetUp();
+
+    ASSERT_TRUE(
+        other_temp_dir_.Set(temp_dir_.GetPath().AppendASCII("other_temp_dir")));
+    other_temp_dir_str_ = other_temp_dir_.GetPath().MaybeAsASCII();
+    ASSERT_FALSE(other_temp_dir_str_.empty());
+
+    base::FilePath bad_prefix = temp_dir_.GetPath().AppendASCII(kBadPrefixName);
+    bad_prefix_str_ = bad_prefix.MaybeAsASCII();
+    ASSERT_FALSE(bad_prefix_str_.empty());
+  }
+
+  BrokerParams ChildSetUpPreSandbox() override {
+    BrokerParams params;
+    params.allowed_command_set = syscall_broker::MakeBrokerCommandSet(
+        {syscall_broker::COMMAND_INOTIFY_ADD_WATCH});
+    params.permissions = {
+        BrokerFilePermission::InotifyAddWatchWithIntermediateDirs(
+            temp_file_path_str_)};
+    return params;
+  }
+
+  void RunTestInSandboxedChild(Syscaller* syscaller) override {
+    base::ScopedFD inotify_instance(inotify_init());
+    BPF_ASSERT(inotify_instance.is_valid());
+    // Watch the correct directory with bad flags.
+    BPF_ASSERT_EQ(
+        -kFakeErrnoSentinel,
+        syscaller->InotifyAddWatch(inotify_instance.get(),
+                                   nested_temp_dir_str_.c_str(), kBadMask));
+
+    // Try to watch an unintended directory, should fail.
+    BPF_ASSERT_EQ(
+        -kFakeErrnoSentinel,
+        syscaller->InotifyAddWatch(inotify_instance.get(),
+                                   other_temp_dir_str_.c_str(), kGoodMask));
+
+    // Try to access a prefix that isn't a full directory.
+    BPF_ASSERT_EQ(-kFakeErrnoSentinel, syscaller->InotifyAddWatch(
+                                           inotify_instance.get(),
+                                           bad_prefix_str_.c_str(), kGoodMask));
+  }
+
+ protected:
+  // Another directory nested under |temp_dir_| with no sandbox permissions.
+  base::ScopedTempDir other_temp_dir_;
+  std::string other_temp_dir_str_;
+
+  // A prefix of |nested_temp_dir_| that doesn't match a valid directory.
+  std::string bad_prefix_str_;
+};
+
+TEST(BrokerProcessIntegrationTest, InotifyAddWatchBadArguments) {
+  RunAllBrokerTests<InotifyAddWatchBadArgumentsDelegate>();
+}
+
+// Use inottify_add_watch() successfully and verify it actually works.
+class InotifyAddWatchSuccessDelegate final : public InotifyAddWatchDelegate {
+ public:
+  BrokerParams ChildSetUpPreSandbox() override {
+    BrokerParams params;
+    params.allowed_command_set = syscall_broker::MakeBrokerCommandSet(
+        {syscall_broker::COMMAND_INOTIFY_ADD_WATCH,
+         syscall_broker::COMMAND_UNLINK});
+    params.permissions = {
+        BrokerFilePermission::InotifyAddWatchWithIntermediateDirs(
+            temp_file_path_str_),
+        BrokerFilePermission::ReadWriteCreateRecursive(nested_temp_dir_str_ +
+                                                       "/")};
+    return params;
+  }
+
+  void RunTestInSandboxedChild(Syscaller* syscaller) override {
+    base::ScopedFD inotify_instance(inotify_init());
+    BPF_ASSERT(inotify_instance.is_valid());
+    // This inotify_add_watch() call should succeed.
+    int wd = syscaller->InotifyAddWatch(
+        inotify_instance.get(), nested_temp_dir_str_.c_str(), kGoodMask);
+    BPF_ASSERT_GE(wd, 0);
+
+    // Unlinking the file generates an inotify notification.
+    BPF_ASSERT_GE(unlink(temp_file_path_str_.c_str()), 0);
+
+    // Read one inotify message and verify it names the correct watch descriptor
+    // |wd|. The test will timeout if no inotify notifications are ever
+    // generated.
+    std::vector<char> buf(4096);
+    BPF_ASSERT_GE(read(inotify_instance.get(), buf.data(), buf.size()), 0);
+    struct inotify_event* event =
+        reinterpret_cast<struct inotify_event*>(buf.data());
+    BPF_ASSERT_EQ(event->wd, wd);
+
+    // Removing the watch should succeed.
+    BPF_ASSERT_GE(inotify_rm_watch(inotify_instance.get(), wd), 0);
+  }
+};
+
+TEST(BrokerProcessIntegrationTest, InotifyAddWatchSuccess) {
+  RunAllBrokerTests<InotifyAddWatchSuccessDelegate>();
+}
+
+// Tests base::FilePathWatcher which uses inotify on Linux.
+// This is used in the network service sandbox.
+class BaseFilePathWatcherDelegate final : public InotifyAddWatchDelegate {
+ public:
+  BrokerParams ChildSetUpPreSandbox() override {
+    // Prewarm file accesses.
+    base::GetMaxNumberOfInotifyWatches();
+
+    BrokerParams params;
+    params.allowed_command_set = syscall_broker::MakeBrokerCommandSet(
+        {syscall_broker::COMMAND_INOTIFY_ADD_WATCH,
+         syscall_broker::COMMAND_OPEN});
+    params.permissions = {
+        BrokerFilePermission::InotifyAddWatchWithIntermediateDirs(
+            temp_file_path_str_),
+        BrokerFilePermission::ReadWriteCreateRecursive(nested_temp_dir_str_ +
+                                                       "/")};
+    return params;
+  }
+
+  void RunTestInSandboxedChild(Syscaller* syscaller) override {
+    base::test::SingleThreadTaskEnvironment task_environment(
+        base::test::TaskEnvironment::MainThreadType::IO);
+
+    // Watch the file and wait for a notification about that file from
+    // FilePathWatcher.
+    base::RunLoop run_loop;
+    base::FilePathWatcher file_watcher_;
+    BPF_ASSERT(file_watcher_.Watch(
+        temp_file_path_, base::FilePathWatcher::Type::kNonRecursive,
+        base::BindLambdaForTesting([&](const base::FilePath& path, bool error) {
+          BPF_ASSERT_EQ(temp_file_path_, path);
+          run_loop.Quit();
+        })));
+
+    // Our inotify file path watcher requires a file to be opened for writing
+    // *after* adding the watch, and then closed, in order to generate a
+    // notification. The actual call to Write() isn't even strictly necessary.
+    // Another way to generate a notification is
+    // base::DeleteFile(temp_file_path_).
+    base::File temp_file_again(temp_file_path_, base::File::FLAG_OPEN |
+                                                    base::File::FLAG_READ |
+                                                    base::File::FLAG_WRITE);
+    char buf2[] = "a";
+    BPF_ASSERT_EQ(temp_file_again.Write(0, buf2, sizeof(buf2)), sizeof(buf2));
+    temp_file_again.Flush();
+    temp_file_again.Close();
+    // Wait until we receive a notification about the file modification.
+    // Failure results in a test timeout.
+    run_loop.Run();
+  }
+};
+
+TEST(BrokerProcessIntegrationTest, BaseFilePathWatcherInotifyTest) {
+  const std::vector<BrokerTestConfiguration> inotify_test_configs = {
+      {"FastCheckInClient_NoSyscaller", true, SyscallerType::NoSyscaller,
+       BrokerType::SIGNAL_BASED},
+      {"NoFastCheckInClient_NoSyscaller", false, SyscallerType::NoSyscaller,
+       BrokerType::SIGNAL_BASED},
+  };
+
+  for (const BrokerTestConfiguration& test_config : inotify_test_configs) {
+    SCOPED_TRACE(test_config.test_name);
+    auto test_delegate = std::make_unique<BaseFilePathWatcherDelegate>();
+    RunSingleBrokerTest(test_delegate.get(), test_config);
+  }
+}
+
 #endif  // !defined(THREAD_SANITIZER)
 }  // namespace sandbox
diff --git a/sandbox/linux/syscall_broker/broker_client.cc b/sandbox/linux/syscall_broker/broker_client.cc
index 05d99ba5..0aace79 100644
--- a/sandbox/linux/syscall_broker/broker_client.cc
+++ b/sandbox/linux/syscall_broker/broker_client.cc
@@ -207,6 +207,38 @@
   return PathOnlySyscall(COMMAND_UNLINK, path);
 }
 
+int BrokerClient::InotifyAddWatch(int fd,
+                                  const char* pathname,
+                                  uint32_t mask) const {
+  if (!pathname)
+    return -EFAULT;
+
+  if (fast_check_in_client_ &&
+      !CommandInotifyAddWatchIsSafe(policy_->allowed_command_set,
+                                    *policy_->file_permissions, pathname, mask,
+                                    nullptr)) {
+    return -policy_->file_permissions->denied_errno();
+  }
+
+  BrokerSimpleMessage message;
+  RAW_CHECK(message.AddIntToMessage(COMMAND_INOTIFY_ADD_WATCH));
+  RAW_CHECK(message.AddStringToMessage(pathname));
+  RAW_CHECK(message.AddIntToMessage(mask));
+
+  BrokerSimpleMessage reply;
+  ssize_t msg_len = message.SendRecvMsgWithFlagsMultipleFds(
+      ipc_channel_.get(), 0, base::span<const int>(&fd, 1), {}, &reply);
+
+  if (msg_len < 0)
+    return msg_len;
+
+  int return_value = -1;
+  if (!reply.ReadInt(&return_value))
+    return -ENOMEM;
+
+  return return_value;
+}
+
 int BrokerClient::PathOnlySyscall(BrokerCommand syscall_type,
                                   const char* pathname) const {
   BrokerSimpleMessage message;
diff --git a/sandbox/linux/syscall_broker/broker_client.h b/sandbox/linux/syscall_broker/broker_client.h
index f96e77e..9d0fdee 100644
--- a/sandbox/linux/syscall_broker/broker_client.h
+++ b/sandbox/linux/syscall_broker/broker_client.h
@@ -68,6 +68,9 @@
              bool follow_links,
              struct kernel_stat64* sb) const override;
   int Unlink(const char* unlink) const override;
+  int InotifyAddWatch(int fd,
+                      const char* pathname,
+                      uint32_t mask) const override;
 
   const BrokerSandboxConfig& policy() const { return *policy_; }
 
diff --git a/sandbox/linux/syscall_broker/broker_command.cc b/sandbox/linux/syscall_broker/broker_command.cc
index 340305d..79d27b0d 100644
--- a/sandbox/linux/syscall_broker/broker_command.cc
+++ b/sandbox/linux/syscall_broker/broker_command.cc
@@ -95,5 +95,15 @@
                                            filename_to_use, nullptr);
 }
 
+bool CommandInotifyAddWatchIsSafe(const BrokerCommandSet& command_set,
+                                  const BrokerPermissionList& policy,
+                                  const char* requested_filename,
+                                  uint32_t mask,
+                                  const char** filename_to_use) {
+  return command_set.test(COMMAND_INOTIFY_ADD_WATCH) &&
+         policy.GetFileNameIfAllowedToInotifyAddWatch(requested_filename, mask,
+                                                      filename_to_use);
+}
+
 }  // namespace syscall_broker
 }  // namespace sandbox
diff --git a/sandbox/linux/syscall_broker/broker_command.h b/sandbox/linux/syscall_broker/broker_command.h
index 514c5f6..468d9056 100644
--- a/sandbox/linux/syscall_broker/broker_command.h
+++ b/sandbox/linux/syscall_broker/broker_command.h
@@ -42,9 +42,10 @@
   COMMAND_STAT,
   COMMAND_STAT64,
   COMMAND_UNLINK,
+  COMMAND_INOTIFY_ADD_WATCH,
 
   // NOTE: update when adding new commands.
-  COMMAND_MAX = COMMAND_UNLINK
+  COMMAND_MAX = COMMAND_INOTIFY_ADD_WATCH
 };
 
 using BrokerCommandSet = std::bitset<COMMAND_MAX + 1>;
@@ -106,6 +107,12 @@
                          const char* requested_filename,
                          const char** filename_to_use);
 
+bool CommandInotifyAddWatchIsSafe(const BrokerCommandSet& command_set,
+                                  const BrokerPermissionList& policy,
+                                  const char* requested_filename,
+                                  uint32_t mask,
+                                  const char** filename_to_use);
+
 }  // namespace syscall_broker
 }  // namespace sandbox
 
diff --git a/sandbox/linux/syscall_broker/broker_file_permission.cc b/sandbox/linux/syscall_broker/broker_file_permission.cc
index b399455..02fbf8b6 100644
--- a/sandbox/linux/syscall_broker/broker_file_permission.cc
+++ b/sandbox/linux/syscall_broker/broker_file_permission.cc
@@ -7,6 +7,7 @@
 #include <fcntl.h>
 #include <stddef.h>
 #include <string.h>
+#include <sys/inotify.h>
 #include <unistd.h>
 
 #include <ostream>
@@ -206,8 +207,9 @@
   return true;
 }
 
-bool BrokerFilePermission::CheckStat(const char* requested_filename,
-                                     const char** file_to_access) const {
+bool BrokerFilePermission::CheckStatWithIntermediates(
+    const char* requested_filename,
+    const char** file_to_access) const {
   if (!ValidatePath(requested_filename))
     return false;
 
@@ -219,26 +221,64 @@
   if (!(allow_create() || allow_stat_with_intermediates()))
     return false;
 
-  // NOTE: ValidatePath proved requested_length != 0;
+  // |allow_stat_with_intermediates()| can match on the full path, and
+  // |allow_create()| only matches a leading directory.
+  if (!CheckIntermediates(
+          requested_filename,
+          /*can_match_full_path=*/allow_stat_with_intermediates()))
+    return false;
+
+  if (file_to_access)
+    *file_to_access = requested_filename;
+
+  return true;
+}
+
+bool BrokerFilePermission::CheckInotifyAddWatchWithIntermediates(
+    const char* requested_filename,
+    uint32_t mask,
+    const char** file_to_inotify_add_watch) const {
+  if (!allow_inotify_add_watch_with_intermediates())
+    return false;
+
+  if (!ValidatePath(requested_filename))
+    return false;
+
+  // Allow only this exact mask as it is used by
+  // base/files/file_path_watcher_inotify.cc.
+  if (mask != (IN_ATTRIB | IN_CREATE | IN_DELETE | IN_CLOSE_WRITE | IN_MOVE |
+               IN_ONLYDIR))
+    return false;
+
+  if (!CheckIntermediates(requested_filename,
+                          /*can_match_full_path=*/true))
+    return false;
+
+  if (file_to_inotify_add_watch)
+    *file_to_inotify_add_watch = requested_filename;
+
+  return true;
+}
+
+bool BrokerFilePermission::CheckIntermediates(const char* requested_filename,
+                                              bool can_match_full_path) const {
+  // NOTE: ValidatePath proves requested_length != 0 and |requested_filename| is
+  // absolute.
   size_t requested_length = strlen(requested_filename);
   CHECK(requested_length);
+  CHECK(requested_filename[0] == '/');
 
   // Special case for root: only one slash, otherwise must have a second
   // slash in the right spot to avoid substring matches.
-  // |allow_stat_with_intermediates()| can match on the full path, and
-  // |allow_create()| only matches a leading directory.
-  if ((requested_length == 1 && requested_filename[0] == '/') ||
-      (allow_stat_with_intermediates() && path_ == requested_filename) ||
-      (requested_length < path_.length() &&
-       memcmp(path_.c_str(), requested_filename, requested_length) == 0 &&
-       path_.c_str()[requested_length] == '/')) {
-    if (file_to_access)
-      *file_to_access = requested_filename;
-
-    return true;
-  }
-
-  return false;
+  return (requested_length == 1 && requested_filename[0] == '/') ||
+         // If this permission can match the full path, compare directly to the
+         // requested filename.
+         (can_match_full_path && path_ == requested_filename) ||
+         // Check whether |requested_filename| matches a leading directory of
+         // |path_|.
+         (requested_length < path_.length() &&
+          memcmp(path_.c_str(), requested_filename, requested_length) == 0 &&
+          path_.c_str()[requested_length] == '/');
 }
 
 const char* BrokerFilePermission::GetErrorMessageForTests() {
@@ -256,9 +296,10 @@
   if (temporary_only())
     CHECK(allow_create()) << GetErrorMessageForTests();
 
-  // Recursive paths must have a trailing slash, absolutes must not.
-  const char last_char = *(path_.rbegin());
-  if (recursive())
+  // Recursive paths must have a trailing slash, absolutes must not (except
+  // root).
+  const char last_char = path_.back();
+  if (recursive() || path_.length() == 1)
     CHECK(last_char == '/') << GetErrorMessageForTests();
   else
     CHECK(last_char != '/') << GetErrorMessageForTests();
@@ -276,7 +317,8 @@
     ReadPermission read_perm,
     WritePermission write_perm,
     CreatePermission create_perm,
-    StatWithIntermediatesPermission stat_perm)
+    StatWithIntermediatesPermission stat_perm,
+    InotifyAddWatchWithIntermediatesPermission inotify_perm)
     : path_(std::move(path)) {
   flags_[kRecursiveBitPos] = recurse_opt == RecursionOption::kRecursive;
   flags_[kTemporaryOnlyBitPos] =
@@ -286,6 +328,9 @@
   flags_[kAllowCreateBitPos] = create_perm == CreatePermission::kAllowCreate;
   flags_[kAllowStatWithIntermediatesBitPos] =
       stat_perm == StatWithIntermediatesPermission::kAllowStatWithIntermediates;
+  flags_[kAllowInotifyAddWatchWithIntermediates] =
+      inotify_perm == InotifyAddWatchWithIntermediatesPermission::
+                          kAllowInotifyAddWatchWithIntermediates;
 
   DieOnInvalidPermission();
 }
diff --git a/sandbox/linux/syscall_broker/broker_file_permission.h b/sandbox/linux/syscall_broker/broker_file_permission.h
index b167ffb0..a9577adf 100644
--- a/sandbox/linux/syscall_broker/broker_file_permission.h
+++ b/sandbox/linux/syscall_broker/broker_file_permission.h
@@ -25,6 +25,10 @@
   kBlockStatWithIntermediates = 0,
   kAllowStatWithIntermediates
 };
+enum class InotifyAddWatchWithIntermediatesPermission {
+  kBlockInotifyAddWatchWithIntermediates = 0,
+  kAllowInotifyAddWatchWithIntermediates
+};
 
 // BrokerFilePermission defines a path for allowlisting.
 // Pick the correct static factory method to create a permission.
@@ -46,7 +50,9 @@
         path, RecursionOption::kNonRecursive, PersistenceOption::kPermanent,
         ReadPermission::kAllowRead, WritePermission::kBlockWrite,
         CreatePermission::kBlockCreate,
-        StatWithIntermediatesPermission::kBlockStatWithIntermediates);
+        StatWithIntermediatesPermission::kBlockStatWithIntermediates,
+        InotifyAddWatchWithIntermediatesPermission::
+            kBlockInotifyAddWatchWithIntermediates);
   }
 
   static BrokerFilePermission ReadOnlyRecursive(const std::string& path) {
@@ -54,7 +60,9 @@
         path, RecursionOption::kRecursive, PersistenceOption::kPermanent,
         ReadPermission::kAllowRead, WritePermission::kBlockWrite,
         CreatePermission::kBlockCreate,
-        StatWithIntermediatesPermission::kBlockStatWithIntermediates);
+        StatWithIntermediatesPermission::kBlockStatWithIntermediates,
+        InotifyAddWatchWithIntermediatesPermission::
+            kBlockInotifyAddWatchWithIntermediates);
   }
 
   static BrokerFilePermission WriteOnly(const std::string& path) {
@@ -62,7 +70,9 @@
         path, RecursionOption::kNonRecursive, PersistenceOption::kPermanent,
         ReadPermission::kBlockRead, WritePermission::kAllowWrite,
         CreatePermission::kBlockCreate,
-        StatWithIntermediatesPermission::kBlockStatWithIntermediates);
+        StatWithIntermediatesPermission::kBlockStatWithIntermediates,
+        InotifyAddWatchWithIntermediatesPermission::
+            kBlockInotifyAddWatchWithIntermediates);
   }
 
   static BrokerFilePermission ReadWrite(const std::string& path) {
@@ -70,7 +80,9 @@
         path, RecursionOption::kNonRecursive, PersistenceOption::kPermanent,
         ReadPermission::kAllowRead, WritePermission::kAllowWrite,
         CreatePermission::kBlockCreate,
-        StatWithIntermediatesPermission::kBlockStatWithIntermediates);
+        StatWithIntermediatesPermission::kBlockStatWithIntermediates,
+        InotifyAddWatchWithIntermediatesPermission::
+            kBlockInotifyAddWatchWithIntermediates);
   }
 
   static BrokerFilePermission ReadWriteCreate(const std::string& path) {
@@ -78,7 +90,9 @@
         path, RecursionOption::kNonRecursive, PersistenceOption::kPermanent,
         ReadPermission::kAllowRead, WritePermission::kAllowWrite,
         CreatePermission::kAllowCreate,
-        StatWithIntermediatesPermission::kBlockStatWithIntermediates);
+        StatWithIntermediatesPermission::kBlockStatWithIntermediates,
+        InotifyAddWatchWithIntermediatesPermission::
+            kBlockInotifyAddWatchWithIntermediates);
   }
 
   static BrokerFilePermission ReadWriteCreateRecursive(
@@ -87,7 +101,9 @@
         path, RecursionOption::kRecursive, PersistenceOption::kPermanent,
         ReadPermission::kAllowRead, WritePermission::kAllowWrite,
         CreatePermission::kAllowCreate,
-        StatWithIntermediatesPermission::kBlockStatWithIntermediates);
+        StatWithIntermediatesPermission::kBlockStatWithIntermediates,
+        InotifyAddWatchWithIntermediatesPermission::
+            kBlockInotifyAddWatchWithIntermediates);
   }
 
   // Temporary files must always be newly created and do not confer rights to
@@ -98,7 +114,9 @@
         path, RecursionOption::kNonRecursive, PersistenceOption::kTemporaryOnly,
         ReadPermission::kAllowRead, WritePermission::kAllowWrite,
         CreatePermission::kAllowCreate,
-        StatWithIntermediatesPermission::kBlockStatWithIntermediates);
+        StatWithIntermediatesPermission::kBlockStatWithIntermediates,
+        InotifyAddWatchWithIntermediatesPermission::
+            kBlockInotifyAddWatchWithIntermediates);
   }
 
   static BrokerFilePermission ReadWriteCreateTemporaryRecursive(
@@ -107,7 +125,9 @@
         path, RecursionOption::kRecursive, PersistenceOption::kTemporaryOnly,
         ReadPermission::kAllowRead, WritePermission::kAllowWrite,
         CreatePermission::kAllowCreate,
-        StatWithIntermediatesPermission::kBlockStatWithIntermediates);
+        StatWithIntermediatesPermission::kBlockStatWithIntermediates,
+        InotifyAddWatchWithIntermediatesPermission::
+            kBlockInotifyAddWatchWithIntermediates);
   }
 
   static BrokerFilePermission StatOnlyWithIntermediateDirs(
@@ -116,7 +136,20 @@
         path, RecursionOption::kNonRecursive, PersistenceOption::kPermanent,
         ReadPermission::kBlockRead, WritePermission::kBlockWrite,
         CreatePermission::kBlockCreate,
-        StatWithIntermediatesPermission::kAllowStatWithIntermediates);
+        StatWithIntermediatesPermission::kAllowStatWithIntermediates,
+        InotifyAddWatchWithIntermediatesPermission::
+            kBlockInotifyAddWatchWithIntermediates);
+  }
+
+  static BrokerFilePermission InotifyAddWatchWithIntermediateDirs(
+      const std::string& path) {
+    return BrokerFilePermission(
+        path, RecursionOption::kNonRecursive, PersistenceOption::kPermanent,
+        ReadPermission::kBlockRead, WritePermission::kBlockWrite,
+        CreatePermission::kBlockCreate,
+        StatWithIntermediatesPermission::kBlockStatWithIntermediates,
+        InotifyAddWatchWithIntermediatesPermission::
+            kAllowInotifyAddWatchWithIntermediates);
   }
 
   // Returns true if |requested_filename| is allowed to be accessed
@@ -149,13 +182,29 @@
   // by this permission as per stat(2). Differs from CheckAccess()
   // in that if create permission is granted to a file, we permit
   // stat() on all of its leading components.
-  // If |file_to_open| is not NULL, it is set to point to either
+  // If |file_to_access| is not NULL, it is set to point to either
   // the |requested_filename| in the case of a recursive match,
   // or a pointer to the matched path in the allowlist if an absolute
   // match.
   // Async signal safe if |file_to_access| is NULL
-  bool CheckStat(const char* requested_filename,
-                 const char** file_to_access) const;
+  bool CheckStatWithIntermediates(const char* requested_filename,
+                                  const char** file_to_access) const;
+
+  // Returns true if |requested_filename| is allowed by this permission to be
+  // added to an inotify instance's watch list by inotify_add_watch(2), with the
+  // specific |mask|. Differs from CheckAccess() in that if inotify_add_watch
+  // permission is granted to a file, we permit inotify_add_watch() on all of
+  // its leading components.
+  //
+  // If |file_to_inotify_add_watch| is not NULL, it is set to point to either
+  // the |requested_filename| in the case of a recursive match, or a pointer to
+  // the matched path in the allowlist if an absolute match.
+  //
+  // Async signal safe if |file_to_inotify_add_watch| is NULL
+  bool CheckInotifyAddWatchWithIntermediates(
+      const char* requested_filename,
+      uint32_t mask,
+      const char** file_to_inotify_add_watch) const;
 
  private:
   friend class BrokerFilePermissionTester;
@@ -167,8 +216,9 @@
     kAllowWriteBitPos,
     kAllowCreateBitPos,
     kAllowStatWithIntermediatesBitPos,
+    kAllowInotifyAddWatchWithIntermediates,
 
-    kMaxValueBitPos = kAllowStatWithIntermediatesBitPos
+    kMaxValueBitPos = kAllowInotifyAddWatchWithIntermediates
   };
 
   // NOTE: Validates the permission and dies if invalid!
@@ -178,7 +228,8 @@
                        ReadPermission read_perm,
                        WritePermission write_perm,
                        CreatePermission create_perm,
-                       StatWithIntermediatesPermission stat_perm);
+                       StatWithIntermediatesPermission stat_perm,
+                       InotifyAddWatchWithIntermediatesPermission inotify_perm);
 
   // Allows construction from the raw bitset.
   BrokerFilePermission(std::string path, uint64_t flags);
@@ -202,6 +253,10 @@
     return flags_.test(kAllowStatWithIntermediatesBitPos);
   }
 
+  bool allow_inotify_add_watch_with_intermediates() const {
+    return flags_.test(kAllowInotifyAddWatchWithIntermediates);
+  }
+
   // ValidatePath checks |path| and returns true if these conditions are met
   // * Greater than 0 length
   // * Is an absolute path
@@ -218,6 +273,14 @@
                            int mode,
                            const char** file_to_access) const;
 
+  // Helper routine for CheckStatWithIntermediates() and
+  // CheckInotifyAddWatchWithIntermediates() to return true if one of the
+  // following is true:
+  // 1. |requested_filename| matches a leading directory of |path_|.
+  // 2. |can_match_full_path| is true and |path_| == |requested_filename|.
+  bool CheckIntermediates(const char* requested_filename,
+                          bool can_match_full_path) const;
+
   // Used in by BrokerFilePermissionTester for tests.
   static const char* GetErrorMessageForTests();
 
diff --git a/sandbox/linux/syscall_broker/broker_file_permission_unittest.cc b/sandbox/linux/syscall_broker/broker_file_permission_unittest.cc
index dd3df5a5..65d3d640 100644
--- a/sandbox/linux/syscall_broker/broker_file_permission_unittest.cc
+++ b/sandbox/linux/syscall_broker/broker_file_permission_unittest.cc
@@ -6,6 +6,7 @@
 
 #include <fcntl.h>
 #include <string.h>
+#include <sys/inotify.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <unistd.h>
@@ -265,23 +266,65 @@
   const char kLeading1[] = "/";
   const char kLeading2[] = "/tmp";
   const char kLeading3[] = "/tmp/good/path";
+  const char kBadPrefix[] = "/tmp/good/pa";
   const char kTrailing[] = "/tmp/good/path/bad";
 
   BrokerFilePermission perm =
       BrokerFilePermission::StatOnlyWithIntermediateDirs(kPath);
   // No open or access permission.
-  ASSERT_FALSE(perm.CheckOpen(kPath, O_RDONLY, NULL, NULL));
-  ASSERT_FALSE(perm.CheckOpen(kPath, O_WRONLY, NULL, NULL));
-  ASSERT_FALSE(perm.CheckOpen(kPath, O_RDWR, NULL, NULL));
-  ASSERT_FALSE(perm.CheckAccess(kPath, R_OK, NULL));
-  ASSERT_FALSE(perm.CheckAccess(kPath, W_OK, NULL));
+  ASSERT_FALSE(perm.CheckOpen(kPath, O_RDONLY, nullptr, nullptr));
+  ASSERT_FALSE(perm.CheckOpen(kPath, O_WRONLY, nullptr, nullptr));
+  ASSERT_FALSE(perm.CheckOpen(kPath, O_RDWR, nullptr, nullptr));
+  ASSERT_FALSE(perm.CheckAccess(kPath, R_OK, nullptr));
+  ASSERT_FALSE(perm.CheckAccess(kPath, W_OK, nullptr));
 
   // Stat for all leading paths, but not trailing paths.
-  ASSERT_TRUE(perm.CheckStat(kPath, NULL));
-  ASSERT_TRUE(perm.CheckStat(kLeading1, NULL));
-  ASSERT_TRUE(perm.CheckStat(kLeading2, NULL));
-  ASSERT_TRUE(perm.CheckStat(kLeading3, NULL));
-  ASSERT_FALSE(perm.CheckStat(kTrailing, NULL));
+  ASSERT_TRUE(perm.CheckStatWithIntermediates(kPath, nullptr));
+  ASSERT_TRUE(perm.CheckStatWithIntermediates(kLeading1, nullptr));
+  ASSERT_TRUE(perm.CheckStatWithIntermediates(kLeading2, nullptr));
+  ASSERT_TRUE(perm.CheckStatWithIntermediates(kLeading3, nullptr));
+  ASSERT_FALSE(perm.CheckStatWithIntermediates(kBadPrefix, nullptr));
+  ASSERT_FALSE(perm.CheckStatWithIntermediates(kTrailing, nullptr));
+}
+
+TEST(BrokerFilePermission, InotifyAddWatchWithIntermediateDirs) {
+  const char kPath[] = "/tmp/good/path";
+  const char kLeading1[] = "/";
+  const char kLeading2[] = "/tmp";
+  const char kLeading3[] = "/tmp/good/path";
+  const char kBadPrefix[] = "/tmp/good/pa";
+  const char kTrailing[] = "/tmp/good/path/bad";
+
+  const uint32_t kBadMask =
+      IN_CREATE | IN_DELETE | IN_CLOSE_WRITE | IN_MOVE | IN_ONLYDIR;
+  const uint32_t kGoodMask = kBadMask | IN_ATTRIB;
+
+  BrokerFilePermission perm =
+      BrokerFilePermission::InotifyAddWatchWithIntermediateDirs(kPath);
+  // No open or access permission.
+  ASSERT_FALSE(perm.CheckOpen(kPath, O_RDONLY, nullptr, nullptr));
+  ASSERT_FALSE(perm.CheckOpen(kPath, O_WRONLY, nullptr, nullptr));
+  ASSERT_FALSE(perm.CheckOpen(kPath, O_RDWR, nullptr, nullptr));
+  ASSERT_FALSE(perm.CheckAccess(kPath, R_OK, nullptr));
+  ASSERT_FALSE(perm.CheckAccess(kPath, W_OK, nullptr));
+
+  // Inotify_add_watch for all leading paths, but not trailing paths.
+  ASSERT_TRUE(
+      perm.CheckInotifyAddWatchWithIntermediates(kPath, kGoodMask, nullptr));
+  ASSERT_TRUE(perm.CheckInotifyAddWatchWithIntermediates(kLeading1, kGoodMask,
+                                                         nullptr));
+  ASSERT_TRUE(perm.CheckInotifyAddWatchWithIntermediates(kLeading2, kGoodMask,
+                                                         nullptr));
+  ASSERT_TRUE(perm.CheckInotifyAddWatchWithIntermediates(kLeading3, kGoodMask,
+                                                         nullptr));
+  ASSERT_FALSE(perm.CheckInotifyAddWatchWithIntermediates(kBadPrefix, kGoodMask,
+                                                          nullptr));
+  ASSERT_FALSE(perm.CheckInotifyAddWatchWithIntermediates(kTrailing, kGoodMask,
+                                                          nullptr));
+
+  // Fails without correct mask.
+  ASSERT_FALSE(
+      perm.CheckInotifyAddWatchWithIntermediates(kPath, kBadMask, nullptr));
 }
 
 TEST(BrokerFilePermission, ValidatePath) {
diff --git a/sandbox/linux/syscall_broker/broker_host.cc b/sandbox/linux/syscall_broker/broker_host.cc
index de86b08..99b5fa7 100644
--- a/sandbox/linux/syscall_broker/broker_host.cc
+++ b/sandbox/linux/syscall_broker/broker_host.cc
@@ -8,12 +8,14 @@
 #include <fcntl.h>
 #include <limits.h>
 #include <stddef.h>
+#include <sys/inotify.h>
 #include <sys/socket.h>
 #include <sys/stat.h>
 #include <sys/syscall.h>
 #include <sys/types.h>
 #include <unistd.h>
 
+#include <array>
 #include <string>
 #include <utility>
 
@@ -51,12 +53,12 @@
 // permission_list. Write the syscall return value (-errno) to |reply|.
 void AccessFileForIPC(const BrokerCommandSet& allowed_command_set,
                       const BrokerPermissionList& permission_list,
-                      const std::string& requested_filename,
+                      const char* requested_filename,
                       int mode,
                       BrokerSimpleMessage* reply) {
   const char* file_to_access = NULL;
   if (!CommandAccessIsSafe(allowed_command_set, permission_list,
-                           requested_filename.c_str(), mode, &file_to_access)) {
+                           requested_filename, mode, &file_to_access)) {
     RAW_CHECK(reply->AddIntToMessage(-permission_list.denied_errno()));
     return;
   }
@@ -70,16 +72,16 @@
   RAW_CHECK(reply->AddIntToMessage(0));
 }
 
-// Performs mkdir(2) on |filename| with mode |mode| if allowed by our
+// Performs mkdir(2) on |requested_filename| with mode |mode| if allowed by our
 // permission_list. Write the syscall return value (-errno) to |reply|.
 void MkdirFileForIPC(const BrokerCommandSet& allowed_command_set,
                      const BrokerPermissionList& permission_list,
-                     const std::string& filename,
+                     const char* requested_filename,
                      int mode,
                      BrokerSimpleMessage* reply) {
   const char* file_to_access = nullptr;
   if (!CommandMkdirIsSafe(allowed_command_set, permission_list,
-                          filename.c_str(), &file_to_access)) {
+                          requested_filename, &file_to_access)) {
     RAW_CHECK(reply->AddIntToMessage(-permission_list.denied_errno()));
     return;
   }
@@ -95,14 +97,14 @@
 // file descriptor in the |opened_file| if relevant.
 void OpenFileForIPC(const BrokerCommandSet& allowed_command_set,
                     const BrokerPermissionList& permission_list,
-                    const std::string& requested_filename,
+                    const char* requested_filename,
                     int flags,
                     BrokerSimpleMessage* reply,
                     base::ScopedFD* opened_file) {
-  const char* file_to_open = NULL;
+  const char* file_to_open = nullptr;
   bool unlink_after_open = false;
   if (!CommandOpenIsSafe(allowed_command_set, permission_list,
-                         requested_filename.c_str(), flags, &file_to_open,
+                         requested_filename, flags, &file_to_open,
                          &unlink_after_open)) {
     RAW_CHECK(reply->AddIntToMessage(-permission_list.denied_errno()));
     return;
@@ -125,14 +127,14 @@
 // result to |return_val|.
 void RenameFileForIPC(const BrokerCommandSet& allowed_command_set,
                       const BrokerPermissionList& permission_list,
-                      const std::string& old_filename,
-                      const std::string& new_filename,
+                      const char* old_filename,
+                      const char* new_filename,
                       BrokerSimpleMessage* reply) {
   const char* old_file_to_access = nullptr;
   const char* new_file_to_access = nullptr;
-  if (!CommandRenameIsSafe(allowed_command_set, permission_list,
-                           old_filename.c_str(), new_filename.c_str(),
-                           &old_file_to_access, &new_file_to_access)) {
+  if (!CommandRenameIsSafe(allowed_command_set, permission_list, old_filename,
+                           new_filename, &old_file_to_access,
+                           &new_file_to_access)) {
     RAW_CHECK(reply->AddIntToMessage(-permission_list.denied_errno()));
     return;
   }
@@ -146,11 +148,11 @@
 // Perform readlink(2) on |filename| using a buffer of MAX_PATH bytes.
 void ReadlinkFileForIPC(const BrokerCommandSet& allowed_command_set,
                         const BrokerPermissionList& permission_list,
-                        const std::string& filename,
+                        const char* requested_filename,
                         BrokerSimpleMessage* reply) {
   const char* file_to_access = nullptr;
   if (!CommandReadlinkIsSafe(allowed_command_set, permission_list,
-                             filename.c_str(), &file_to_access)) {
+                             requested_filename, &file_to_access)) {
     RAW_CHECK(reply->AddIntToMessage(-permission_list.denied_errno()));
     return;
   }
@@ -166,11 +168,11 @@
 
 void RmdirFileForIPC(const BrokerCommandSet& allowed_command_set,
                      const BrokerPermissionList& permission_list,
-                     const std::string& requested_filename,
+                     const char* requested_filename,
                      BrokerSimpleMessage* reply) {
   const char* file_to_access = nullptr;
   if (!CommandRmdirIsSafe(allowed_command_set, permission_list,
-                          requested_filename.c_str(), &file_to_access)) {
+                          requested_filename, &file_to_access)) {
     RAW_CHECK(reply->AddIntToMessage(-permission_list.denied_errno()));
     return;
   }
@@ -186,12 +188,12 @@
 void StatFileForIPC(const BrokerCommandSet& allowed_command_set,
                     const BrokerPermissionList& permission_list,
                     BrokerCommand command_type,
-                    const std::string& requested_filename,
+                    const char* requested_filename,
                     bool follow_links,
                     BrokerSimpleMessage* reply) {
   const char* file_to_access = nullptr;
   if (!CommandStatIsSafe(allowed_command_set, permission_list,
-                         requested_filename.c_str(), &file_to_access)) {
+                         requested_filename, &file_to_access)) {
     RAW_CHECK(reply->AddIntToMessage(-permission_list.denied_errno()));
     return;
   }
@@ -232,11 +234,11 @@
 
 void UnlinkFileForIPC(const BrokerCommandSet& allowed_command_set,
                       const BrokerPermissionList& permission_list,
-                      const std::string& requested_filename,
+                      const char* requested_filename,
                       BrokerSimpleMessage* message) {
   const char* file_to_access = nullptr;
   if (!CommandUnlinkIsSafe(allowed_command_set, permission_list,
-                           requested_filename.c_str(), &file_to_access)) {
+                           requested_filename, &file_to_access)) {
     RAW_CHECK(message->AddIntToMessage(-permission_list.denied_errno()));
     return;
   }
@@ -247,11 +249,34 @@
   RAW_CHECK(message->AddIntToMessage(0));
 }
 
+void InotifyAddWatchForIPC(const BrokerCommandSet& allowed_command_set,
+                           const BrokerPermissionList& permission_list,
+                           base::ScopedFD inotify_fd,
+                           const char* requested_filename,
+                           uint32_t mask,
+                           BrokerSimpleMessage* message) {
+  const char* file_to_access = nullptr;
+  if (!CommandInotifyAddWatchIsSafe(allowed_command_set, permission_list,
+                                    requested_filename, mask,
+                                    &file_to_access)) {
+    RAW_CHECK(message->AddIntToMessage(-permission_list.denied_errno()));
+    return;
+  }
+
+  int wd = inotify_add_watch(inotify_fd.get(), file_to_access, mask);
+  if (wd < 0) {
+    RAW_CHECK(message->AddIntToMessage(-errno));
+    return;
+  }
+  RAW_CHECK(message->AddIntToMessage(wd));
+}
+
 // Handle a |command_type| request contained in |iter| and write the reply
 // to |reply|.
 bool HandleRemoteCommand(const BrokerCommandSet& allowed_command_set,
                          const BrokerPermissionList& permission_list,
                          BrokerSimpleMessage* message,
+                         base::span<base::ScopedFD> recv_fds,
                          BrokerSimpleMessage* reply,
                          base::ScopedFD* opened_file) {
   // Message structure:
@@ -340,6 +365,20 @@
                        reply);
       break;
     }
+    case COMMAND_INOTIFY_ADD_WATCH: {
+      const char* requested_filename;
+      if (!message->ReadString(&requested_filename))
+        return false;
+      int mask;
+      if (!message->ReadInt(&mask))
+        return false;
+      if (!recv_fds[0].is_valid())
+        return false;
+      InotifyAddWatchForIPC(allowed_command_set, permission_list,
+                            std::move(recv_fds[0]), requested_filename, mask,
+                            reply);
+      break;
+    }
     default:
       LOG(ERROR) << "Invalid IPC command";
       return false;
@@ -364,26 +403,33 @@
     BrokerSimpleMessage message;
     errno = 0;
     base::ScopedFD temporary_ipc;
+    std::array<base::ScopedFD, 2> recv_fds_arr;
+    base::span<base::ScopedFD> recv_fds(recv_fds_arr);
     const ssize_t msg_len =
-        message.RecvMsgWithFlags(ipc_channel_.get(), 0, &temporary_ipc);
+        message.RecvMsgWithFlagsMultipleFds(ipc_channel_.get(), 0, recv_fds);
 
     if (msg_len == 0 || (msg_len == -1 && errno == ECONNRESET)) {
       // EOF from the client, or the client died, we should finish looping.
       return;
     }
 
-    // The client sends exactly one file descriptor, on which we
-    // will write the reply.
-    if (msg_len < 0) {
+    // This indicates an error occurred in IPC. For example, too many fds were
+    // sent along with the message.
+    if (msg_len < 0 || !recv_fds[0].is_valid()) {
+      if (!recv_fds[0].is_valid()) {
+        errno = EBADF;
+      }
       PLOG(ERROR) << "Error reading message from the client";
       continue;
     }
 
+    temporary_ipc = std::move(recv_fds[0]);
+
     BrokerSimpleMessage reply;
     base::ScopedFD opened_file;
     if (!HandleRemoteCommand(policy_->allowed_command_set,
-                             *policy_->file_permissions, &message, &reply,
-                             &opened_file)) {
+                             *policy_->file_permissions, &message,
+                             recv_fds.subspan(1), &reply, &opened_file)) {
       // Does not exit if we received a malformed message.
       LOG(ERROR) << "Received malformed message from the client";
       continue;
diff --git a/sandbox/linux/syscall_broker/broker_permission_list.cc b/sandbox/linux/syscall_broker/broker_permission_list.cc
index f949797..3d28d7f 100644
--- a/sandbox/linux/syscall_broker/broker_permission_list.cc
+++ b/sandbox/linux/syscall_broker/broker_permission_list.cc
@@ -111,7 +111,23 @@
     return false;
 
   for (size_t i = 0; i < num_of_permissions_; i++) {
-    if (permissions_array_[i].CheckStat(requested_filename, file_to_stat))
+    if (permissions_array_[i].CheckStatWithIntermediates(requested_filename,
+                                                         file_to_stat))
+      return true;
+  }
+  return false;
+}
+
+bool BrokerPermissionList::GetFileNameIfAllowedToInotifyAddWatch(
+    const char* requested_filename,
+    uint32_t mask,
+    const char** file_to_inotify_add_watch) const {
+  if (!CheckCallerArgs(file_to_inotify_add_watch))
+    return false;
+
+  for (size_t i = 0; i < num_of_permissions_; i++) {
+    if (permissions_array_[i].CheckInotifyAddWatchWithIntermediates(
+            requested_filename, mask, file_to_inotify_add_watch))
       return true;
   }
   return false;
diff --git a/sandbox/linux/syscall_broker/broker_permission_list.h b/sandbox/linux/syscall_broker/broker_permission_list.h
index 98580ed..8524df5 100644
--- a/sandbox/linux/syscall_broker/broker_permission_list.h
+++ b/sandbox/linux/syscall_broker/broker_permission_list.h
@@ -46,11 +46,10 @@
   // If |file_to_open| is not NULL, a pointer to the path will be returned.
   // In the case of a recursive match, this will be the requested_filename,
   // otherwise it will return the matching pointer from the
-  // allowlist. For paranoia a caller should then use |file_to_access|. See
-  // GetFileNameIfAllowedToOpen() for more explanation.
-  // return true if calling access() on this file should be allowed, false
-  // otherwise.
-  // Async signal safe if and only if |file_to_access| is NULL.
+  // allowlist. A caller should then use |file_to_access|. See
+  // GetFileNameIfAllowedToOpen() for more explanation. return true if calling
+  // access() on this file should be allowed, false otherwise. Async signal safe
+  // if and only if |file_to_access| is NULL.
   bool GetFileNameIfAllowedToAccess(const char* requested_filename,
                                     int requested_mode,
                                     const char** file_to_access) const;
@@ -59,8 +58,8 @@
   // If |file_to_open| is not NULL, a pointer to the path will be returned.
   // In the case of a recursive match, this will be the requested_filename,
   // otherwise it will return the matching pointer from the
-  // allowlist. For paranoia, a caller should then use |file_to_open| rather
-  // than |requested_filename|, so that it never attempts to open an
+  // allowlist. A caller should then use |file_to_open| rather than
+  // |requested_filename|, so that it never attempts to open an
   // attacker-controlled file name, even if an attacker managed to fool the
   // string comparison mechanism.
   // |unlink_after_open| if not NULL will be set to point to true if the
@@ -77,11 +76,25 @@
   // similar to GetFileNameIfAllowedToAccess(), except that if we have
   // create permission on file, we permit stat() on all its leading
   // components, otherwise checking for missing intermediate directories
-  // can't happen proplery during a base::CreateDirectory() call.
-  // Async signal safe if and only if |file_to_open| is NULL.
+  // can't happen properly during a base::CreateDirectory() call.
+  // Async signal safe if and only if |file_to_access| is NULL.
   bool GetFileNameIfAllowedToStat(const char* requested_filename,
                                   const char** file_to_access) const;
 
+  // Check if |requested_filename| can be watched with mask |mask|.
+  // If |file_to_inotify_add_watch| is not NULL, a pointer to the validated path
+  // will be returned. In the case of a recursive match, this will be the
+  // requested_filename, otherwise it will return the matching pointer from the
+  // allowlist. A caller should then use |file_to_inotify_add_watch| rather than
+  // |requested_filename|, so that it never attempts to open an
+  // attacker-controlled file name, even if an attacker managed to fool the
+  // string comparison mechanism. Async signal safe if and only if
+  // |file_to_inotify_add_watch| is NULL.
+  bool GetFileNameIfAllowedToInotifyAddWatch(
+      const char* requested_filename,
+      uint32_t mask,
+      const char** file_to_inotify_add_watch) const;
+
   int denied_errno() const { return denied_errno_; }
 
  private:
diff --git a/sandbox/linux/syscall_broker/broker_process.cc b/sandbox/linux/syscall_broker/broker_process.cc
index ed5b6f2..ab66dfed 100644
--- a/sandbox/linux/syscall_broker/broker_process.cc
+++ b/sandbox/linux/syscall_broker/broker_process.cc
@@ -26,8 +26,10 @@
 #include "build/build_config.h"
 #include "sandbox/linux/syscall_broker/broker_channel.h"
 #include "sandbox/linux/syscall_broker/broker_client.h"
+#include "sandbox/linux/syscall_broker/broker_command.h"
 #include "sandbox/linux/syscall_broker/broker_host.h"
 #include "sandbox/linux/syscall_broker/broker_permission_list.h"
+#include "sandbox/linux/system_headers/linux_syscalls.h"
 
 namespace sandbox {
 
@@ -187,7 +189,9 @@
       // If rmdir() doesn't exist, unlinkat is used with AT_REMOVEDIR.
       return !fast_check || policy_->allowed_command_set.test(COMMAND_RMDIR) ||
              policy_->allowed_command_set.test(COMMAND_UNLINK);
-
+    case __NR_inotify_add_watch:
+      return !fast_check ||
+             policy_->allowed_command_set.test(COMMAND_INOTIFY_ADD_WATCH);
     default:
       return false;
   }
diff --git a/sandbox/linux/syscall_broker/broker_process_unittest.cc b/sandbox/linux/syscall_broker/broker_process_unittest.cc
index 526574ec..fbe4ea31 100644
--- a/sandbox/linux/syscall_broker/broker_process_unittest.cc
+++ b/sandbox/linux/syscall_broker/broker_process_unittest.cc
@@ -8,6 +8,7 @@
 #include <fcntl.h>
 #include <poll.h>
 #include <stddef.h>
+#include <sys/inotify.h>
 #include <sys/resource.h>
 #include <sys/stat.h>
 #include <sys/types.h>
@@ -25,6 +26,7 @@
 #include "base/containers/flat_set.h"
 #include "base/files/file_util.h"
 #include "base/files/scoped_file.h"
+#include "base/files/scoped_temp_dir.h"
 #include "base/logging.h"
 #include "base/posix/eintr_wrapper.h"
 #include "base/posix/unix_domain_socket.h"
@@ -1748,6 +1750,169 @@
   TestUnlinkHelper(false);
 }
 
+void TestInotifyAddWatchHelper(bool fast_check_in_client) {
+  const uint32_t kBadMask =
+      IN_CREATE | IN_DELETE | IN_CLOSE_WRITE | IN_MOVE | IN_ONLYDIR;
+  const uint32_t kGoodMask = kBadMask | IN_ATTRIB;
+
+  // Create two nested temp dirs.
+  base::ScopedTempDir temp_dir;
+  ASSERT_TRUE(
+      temp_dir.CreateUniqueTempDirUnderPath(base::FilePath(kTempDirForTests)));
+  std::string temp_dir_str = temp_dir.GetPath().MaybeAsASCII();
+  ASSERT_FALSE(temp_dir_str.empty());
+
+  base::ScopedTempDir nested_temp_dir;
+  ASSERT_TRUE(
+      nested_temp_dir.Set(temp_dir.GetPath().AppendASCII("nested_temp_dir")));
+  std::string nested_temp_dir_str = nested_temp_dir.GetPath().MaybeAsASCII();
+  ASSERT_FALSE(nested_temp_dir_str.empty());
+
+  base::FilePath bad_prefix = temp_dir.GetPath().AppendASCII("nested_t");
+  std::string bad_prefix_str = bad_prefix.MaybeAsASCII();
+  ASSERT_FALSE(bad_prefix_str.empty());
+
+  {
+    // Try to watch a directory without COMMAND_INOTIFY_ADD_WATCH
+    std::vector<BrokerFilePermission> permissions = {
+        BrokerFilePermission::InotifyAddWatchWithIntermediateDirs(
+            nested_temp_dir_str)};
+    auto policy = absl::make_optional<BrokerSandboxConfig>(
+        BrokerCommandSet(), permissions, kFakeErrnoSentinel);
+    BrokerProcess open_broker(std::move(policy), BrokerType::SIGNAL_BASED,
+                              fast_check_in_client);
+
+    ASSERT_TRUE(open_broker.Fork(base::BindOnce(&NoOpCallback)));
+
+    base::ScopedFD inotify_instance(inotify_init());
+    ASSERT_TRUE(inotify_instance.is_valid());
+    EXPECT_EQ(
+        -kFakeErrnoSentinel,
+        open_broker.GetBrokerClientSignalBased()->InotifyAddWatch(
+            inotify_instance.get(), nested_temp_dir_str.c_str(), kGoodMask));
+  }
+
+  BrokerCommandSet command_set;
+  command_set.set(COMMAND_INOTIFY_ADD_WATCH);
+
+  {
+    // Try to watch a directory with no permission.
+    std::vector<BrokerFilePermission> permissions;
+    auto policy = absl::make_optional<BrokerSandboxConfig>(
+        command_set, permissions, kFakeErrnoSentinel);
+    BrokerProcess open_broker(std::move(policy), BrokerType::SIGNAL_BASED,
+                              fast_check_in_client);
+
+    ASSERT_TRUE(open_broker.Fork(base::BindOnce(&NoOpCallback)));
+
+    base::ScopedFD inotify_instance(inotify_init());
+    ASSERT_TRUE(inotify_instance.is_valid());
+    EXPECT_EQ(
+        -kFakeErrnoSentinel,
+        open_broker.GetBrokerClientSignalBased()->InotifyAddWatch(
+            inotify_instance.get(), nested_temp_dir_str.c_str(), kGoodMask));
+  }
+  {
+    // Try to watch a directory with permission, but bad flags.
+    std::vector<BrokerFilePermission> permissions = {
+        BrokerFilePermission::InotifyAddWatchWithIntermediateDirs(
+            nested_temp_dir_str)};
+    auto policy = absl::make_optional<BrokerSandboxConfig>(
+        command_set, permissions, kFakeErrnoSentinel);
+    BrokerProcess open_broker(std::move(policy), BrokerType::SIGNAL_BASED,
+                              fast_check_in_client);
+
+    ASSERT_TRUE(open_broker.Fork(base::BindOnce(&NoOpCallback)));
+
+    base::ScopedFD inotify_instance(inotify_init());
+    ASSERT_TRUE(inotify_instance.is_valid());
+    EXPECT_EQ(
+        -kFakeErrnoSentinel,
+        open_broker.GetBrokerClientSignalBased()->InotifyAddWatch(
+            inotify_instance.get(), nested_temp_dir_str.c_str(), kBadMask));
+  }
+  {
+    // Add a directory with permissions and make sure it does not give watch
+    // permission to uintended directories or files.
+    std::vector<BrokerFilePermission> permissions = {
+        BrokerFilePermission::InotifyAddWatchWithIntermediateDirs(
+            nested_temp_dir_str)};
+    auto policy = absl::make_optional<BrokerSandboxConfig>(
+        command_set, permissions, kFakeErrnoSentinel);
+    BrokerProcess open_broker(std::move(policy), BrokerType::SIGNAL_BASED,
+                              fast_check_in_client);
+
+    ASSERT_TRUE(open_broker.Fork(base::BindOnce(&NoOpCallback)));
+
+    base::ScopedTempDir other_directory;
+    ASSERT_TRUE(
+        other_directory.CreateUniqueTempDirUnderPath(temp_dir.GetPath()));
+    std::string other_directory_str = other_directory.GetPath().MaybeAsASCII();
+    ASSERT_FALSE(other_directory_str.empty());
+
+    base::ScopedFD inotify_instance(inotify_init());
+    ASSERT_TRUE(inotify_instance.is_valid());
+
+    // Try to watch an unintended directory, should fail.
+    ASSERT_EQ(
+        -kFakeErrnoSentinel,
+        open_broker.GetBrokerClientSignalBased()->InotifyAddWatch(
+            inotify_instance.get(), other_directory_str.c_str(), kGoodMask));
+
+    // Try to access a prefix that isn't a full directory.
+    ASSERT_EQ(-kFakeErrnoSentinel,
+              open_broker.GetBrokerClientSignalBased()->InotifyAddWatch(
+                  inotify_instance.get(), bad_prefix_str.c_str(), kGoodMask));
+  }
+  {
+    // Try to watch a directory with permission, and good flags.
+    std::vector<BrokerFilePermission> permissions = {
+        BrokerFilePermission::InotifyAddWatchWithIntermediateDirs(
+            nested_temp_dir_str)};
+    auto policy = absl::make_optional<BrokerSandboxConfig>(
+        command_set, permissions, kFakeErrnoSentinel);
+    BrokerProcess open_broker(std::move(policy), BrokerType::SIGNAL_BASED,
+                              fast_check_in_client);
+
+    ASSERT_TRUE(open_broker.Fork(base::BindOnce(&NoOpCallback)));
+
+    base::ScopedFD inotify_instance(inotify_init());
+    ASSERT_TRUE(inotify_instance.is_valid());
+
+    // Try to watch the directory, which should succeed.
+    int wd = open_broker.GetBrokerClientSignalBased()->InotifyAddWatch(
+        inotify_instance.get(), nested_temp_dir_str.c_str(), kGoodMask);
+    // The returned watch descriptor should be valid.
+    ASSERT_LE(0, wd);
+    // Removing the watch should succeed.
+    ASSERT_EQ(0, inotify_rm_watch(inotify_instance.get(), wd));
+
+    // Now try watching a leading directory, which should succeed.
+    wd = open_broker.GetBrokerClientSignalBased()->InotifyAddWatch(
+        inotify_instance.get(), temp_dir_str.c_str(), kGoodMask);
+    // The returned watch descriptor should be valid.
+    ASSERT_LE(0, wd);
+    // Removing the watch should succeed.
+    ASSERT_EQ(0, inotify_rm_watch(inotify_instance.get(), wd));
+
+    // Watching root should succeed with any valid permission.
+    wd = open_broker.GetBrokerClientSignalBased()->InotifyAddWatch(
+        inotify_instance.get(), "/", kGoodMask);
+    // The returned watch descriptor should be valid.
+    ASSERT_LE(0, wd);
+    // Removing the watch should succeed.
+    ASSERT_EQ(0, inotify_rm_watch(inotify_instance.get(), wd));
+  }
+}
+
+TEST(BrokerProcess, InotifyAddWatchClient) {
+  TestInotifyAddWatchHelper(true);
+}
+
+TEST(BrokerProcess, InotifyAddWatchHost) {
+  TestInotifyAddWatchHelper(false);
+}
+
 TEST(BrokerProcess, IsSyscallAllowed) {
   const base::flat_map<BrokerCommand, base::flat_set<int>> kSysnosForCommand = {
       {COMMAND_ACCESS,
@@ -1815,6 +1980,12 @@
 #if defined(__NR_lstat64)
            __NR_lstat64,
 #endif
+       }},
+      {COMMAND_INOTIFY_ADD_WATCH,
+       {
+#if defined(__NR_inotify_add_watch)
+           __NR_inotify_add_watch
+#endif
        }}};
 
   // First gather up all the syscalls numbers we want to test.
diff --git a/sandbox/linux/syscall_broker/broker_simple_message.h b/sandbox/linux/syscall_broker/broker_simple_message.h
index ea0a467..91bc057e 100644
--- a/sandbox/linux/syscall_broker/broker_simple_message.h
+++ b/sandbox/linux/syscall_broker/broker_simple_message.h
@@ -83,16 +83,16 @@
   // This returns a pointer to the next available data buffer in |data|. The
   // pointer is owned by |this| class. The resulting buffer is a string and
   // terminated with a '\0' character.
-  bool ReadString(const char** string);
+  [[nodiscard]] bool ReadString(const char** string);
 
   // This returns a pointer to the next available data buffer in the message
   // in |data|, and the length of the buffer in |length|. The buffer is owned
   // by |this| class.
-  bool ReadData(const char** data, size_t* length);
+  [[nodiscard]] bool ReadData(const char** data, size_t* length);
 
   // This reads the next available int from the message and stores it in
   // |result|.
-  bool ReadInt(int* result);
+  [[nodiscard]] bool ReadInt(int* result);
 
   // The maximum length of a message in the fixed size buffer.
   static constexpr size_t kMaxMessageLength = 4096;
diff --git a/sandbox/linux/syscall_broker/syscall_dispatcher.cc b/sandbox/linux/syscall_broker/syscall_dispatcher.cc
index 6d9782cf..1f20d42 100644
--- a/sandbox/linux/syscall_broker/syscall_dispatcher.cc
+++ b/sandbox/linux/syscall_broker/syscall_dispatcher.cc
@@ -195,6 +195,12 @@
     case __NR_unlinkat:
       return PerformUnlinkat(args);
 #endif  // defined(__NR_unlinkat)
+#if defined(__NR_inotify_add_watch)
+    case __NR_inotify_add_watch:
+      return InotifyAddWatch(static_cast<int>(args.args[0]),
+                             reinterpret_cast<const char*>(args.args[1]),
+                             static_cast<uint32_t>(args.args[2]));
+#endif
     default:
       RAW_CHECK(false);
       return -ENOSYS;
diff --git a/sandbox/linux/syscall_broker/syscall_dispatcher.h b/sandbox/linux/syscall_broker/syscall_dispatcher.h
index 3c5b26034..906c37d 100644
--- a/sandbox/linux/syscall_broker/syscall_dispatcher.h
+++ b/sandbox/linux/syscall_broker/syscall_dispatcher.h
@@ -53,6 +53,11 @@
   // Emulates unlink()/unlinkat().
   virtual int Unlink(const char* unlink) const = 0;
 
+  // Emulates inotify_add_watch().
+  virtual int InotifyAddWatch(int fd,
+                              const char* pathname,
+                              uint32_t mask) const = 0;
+
   // Different architectures use a different syscall from the stat family by
   // default in glibc. E.g. 32-bit systems use *stat*64() and fill out struct
   // kernel_stat64, whereas 64-bit systems use *stat*() and fill out struct
diff --git a/sandbox/linux/tests/scoped_temporary_file.cc b/sandbox/linux/tests/scoped_temporary_file.cc
index 35f53eb..4527a30d 100644
--- a/sandbox/linux/tests/scoped_temporary_file.cc
+++ b/sandbox/linux/tests/scoped_temporary_file.cc
@@ -9,26 +9,21 @@
 #include <unistd.h>
 
 #include "base/check_op.h"
+#include "base/files/file_util.h"
 #include "base/posix/eintr_wrapper.h"
 #include "build/build_config.h"
 
 namespace sandbox {
 
-ScopedTemporaryFile::ScopedTemporaryFile() : fd_(-1) {
-#if BUILDFLAG(IS_ANDROID)
-  static const char file_template[] = "/data/local/tmp/ScopedTempFileXXXXXX";
-#else
-  static const char file_template[] = "/tmp/ScopedTempFileXXXXXX";
-#endif  // BUILDFLAG(IS_ANDROID)
-  static_assert(sizeof(full_file_name_) >= sizeof(file_template),
-                "full_file_name is not large enough");
-  memcpy(full_file_name_, file_template, sizeof(file_template));
-  fd_ = mkstemp(full_file_name_);
+ScopedTemporaryFile::ScopedTemporaryFile() {
+  static const char kFileNameTemplate[] = "ScopedTempFileXXXXXX";
+  full_file_name_ = std::string(kTempDirForTests) + kFileNameTemplate;
+  fd_ = mkstemp(full_file_name_.data());
   CHECK_LE(0, fd_);
 }
 
 ScopedTemporaryFile::~ScopedTemporaryFile() {
-  CHECK_EQ(0, unlink(full_file_name_));
+  CHECK_EQ(0, unlink(full_file_name_.c_str()));
   CHECK_EQ(0, IGNORE_EINTR(close(fd_)));
 }
 
diff --git a/sandbox/linux/tests/scoped_temporary_file.h b/sandbox/linux/tests/scoped_temporary_file.h
index 69903a1..10787b9 100644
--- a/sandbox/linux/tests/scoped_temporary_file.h
+++ b/sandbox/linux/tests/scoped_temporary_file.h
@@ -5,7 +5,18 @@
 #ifndef SANDBOX_LINUX_TESTS_SCOPED_TEMPORARY_FILE_H_
 #define SANDBOX_LINUX_TESTS_SCOPED_TEMPORARY_FILE_H_
 
+#include <string>
+
+#include "build/build_config.h"
+
 namespace sandbox {
+
+#if BUILDFLAG(IS_ANDROID)
+static const char kTempDirForTests[] = "/data/local/tmp/";
+#else
+static const char kTempDirForTests[] = "/tmp/";
+#endif  // BUILDFLAG(IS_ANDROID)
+
 // Creates and open a temporary file on creation and closes
 // and removes it on destruction.
 // Unlike base/ helpers, this does not require JNI on Android.
@@ -19,11 +30,11 @@
   ~ScopedTemporaryFile();
 
   int fd() const { return fd_; }
-  const char* full_file_name() const { return full_file_name_; }
+  const char* full_file_name() const { return full_file_name_.c_str(); }
 
  private:
-  int fd_;
-  char full_file_name_[128];
+  int fd_ = -1;
+  std::string full_file_name_;
 };
 
 }  // namespace sandbox
diff --git a/sandbox/policy/linux/bpf_broker_policy_linux.cc b/sandbox/policy/linux/bpf_broker_policy_linux.cc
index 0dc454a..87a0cc1fa 100644
--- a/sandbox/policy/linux/bpf_broker_policy_linux.cc
+++ b/sandbox/policy/linux/bpf_broker_policy_linux.cc
@@ -5,6 +5,7 @@
 #include "sandbox/policy/linux/bpf_broker_policy_linux.h"
 
 #include "sandbox/linux/bpf_dsl/bpf_dsl.h"
+#include "sandbox/linux/syscall_broker/broker_command.h"
 #include "sandbox/linux/system_headers/linux_syscalls.h"
 
 using sandbox::bpf_dsl::Allow;
@@ -146,6 +147,14 @@
       }
       break;
 #endif
+#if defined(__NR_inotify_add_watch)
+    case __NR_inotify_add_watch:
+      if (allowed_command_set_.test(
+              syscall_broker::COMMAND_INOTIFY_ADD_WATCH)) {
+        return Allow();
+      }
+      break;
+#endif
     default:
       break;
   }
diff --git a/services/network/network_context_unittest.cc b/services/network/network_context_unittest.cc
index 1a76284..61a74a1 100644
--- a/services/network/network_context_unittest.cc
+++ b/services/network/network_context_unittest.cc
@@ -7168,7 +7168,7 @@
   EXPECT_EQ(client->completion_status().error_code,
             net::ERR_TRUST_TOKEN_OPERATION_FAILED);
   EXPECT_EQ(client->completion_status().trust_token_operation_status,
-            mojom::TrustTokenOperationStatus::kUnavailable);
+            mojom::TrustTokenOperationStatus::kUnauthorized);
 }
 
 TEST_F(NetworkContextTest, NoAvailableTrustTokensWhenTrustTokensAreDisabled) {
diff --git a/services/network/public/mojom/BUILD.gn b/services/network/public/mojom/BUILD.gn
index fa107688..f06533a9 100644
--- a/services/network/public/mojom/BUILD.gn
+++ b/services/network/public/mojom/BUILD.gn
@@ -359,6 +359,7 @@
     "source_location.mojom",
     "supports_loading_mode.mojom",
     "timing_allow_origin.mojom",
+    "trust_token_access_observer.mojom",
     "trust_tokens.mojom",
     "url_loader.mojom",
     "url_loader_completion_status.mojom",
@@ -510,6 +511,7 @@
       traits_private_headers = [
         "//services/network/public/mojom/devtools_observer.mojom.h",
         "//services/network/public/mojom/cookie_access_observer.mojom.h",
+        "//services/network/public/mojom/trust_token_access_observer.mojom.h",
       ]
       traits_public_deps = [
         "//base",
diff --git a/services/network/public/mojom/trust_token_access_observer.mojom b/services/network/public/mojom/trust_token_access_observer.mojom
new file mode 100644
index 0000000..ca3710f
--- /dev/null
+++ b/services/network/public/mojom/trust_token_access_observer.mojom
@@ -0,0 +1,36 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+module network.mojom;
+
+import "sandbox/policy/mojom/context.mojom";
+import "url/mojom/origin.mojom";
+
+struct TrustTokenAccessDetails {
+  // This is the origin that is triggering a Trust Token access. If the access
+  // is triggered from a resource request, `origin` is the origin of the
+  // request; if they're being triggered by a script, it's the origin of the
+  // window or service worker.
+  url.mojom.Origin origin;
+
+  // Whether the Trust Token operation was blocked.
+  bool blocked = false;
+};
+
+// Cloneable interface to observe trust token operations being performed in
+// the NetworkService from other services (primarily the browser process).
+// Users of TrustTokenAccessObserver should create a dedicated observer for
+// each network request context (URLLoaderFactory) they are interested in
+// observing.
+[RequireContext=sandbox.mojom.Context.kBrowser]
+interface TrustTokenAccessObserver {
+  // Called when an attempt has been made to access trust tokens by the
+  // NetworkService with the details about the trust token access.
+  OnTrustTokensAccessed(TrustTokenAccessDetails details);
+
+  // Called to create a copy of this observer. (e.g. when cloning observers
+  // from ResourceRequest).
+  [AllowedContext=sandbox.mojom.Context.kBrowser]
+  Clone(pending_receiver<TrustTokenAccessObserver> listener);
+};
diff --git a/services/network/public/mojom/trust_tokens.mojom b/services/network/public/mojom/trust_tokens.mojom
index c41e116..3778b34 100644
--- a/services/network/public/mojom/trust_tokens.mojom
+++ b/services/network/public/mojom/trust_tokens.mojom
@@ -50,6 +50,9 @@
   // some other general, probably transient, manner.
   kUnavailable,
 
+  // The operation was unauthorized due to some sort of policy.
+  kUnauthorized,
+
   // The server response was malformed or otherwise invalid.
   kBadResponse,
 
diff --git a/services/network/test/url_loader_context_for_tests.cc b/services/network/test/url_loader_context_for_tests.cc
index c03a02c..c4969e1 100644
--- a/services/network/test/url_loader_context_for_tests.cc
+++ b/services/network/test/url_loader_context_for_tests.cc
@@ -28,6 +28,11 @@
   return nullptr;
 }
 
+mojom::TrustTokenAccessObserver*
+URLLoaderContextForTests::GetTrustTokenAccessObserver() const {
+  return nullptr;
+}
+
 mojom::CrossOriginEmbedderPolicyReporter*
 URLLoaderContextForTests::GetCoepReporter() const {
   return nullptr;
diff --git a/services/network/test/url_loader_context_for_tests.h b/services/network/test/url_loader_context_for_tests.h
index def81636..d25cc252 100644
--- a/services/network/test/url_loader_context_for_tests.h
+++ b/services/network/test/url_loader_context_for_tests.h
@@ -42,6 +42,7 @@
   const cors::OriginAccessList& GetOriginAccessList() const override;
   const mojom::URLLoaderFactoryParams& GetFactoryParams() const override;
   mojom::CookieAccessObserver* GetCookieAccessObserver() const override;
+  mojom::TrustTokenAccessObserver* GetTrustTokenAccessObserver() const override;
   mojom::CrossOriginEmbedderPolicyReporter* GetCoepReporter() const override;
   mojom::DevToolsObserver* GetDevToolsObserver() const override;
   mojom::NetworkContextClient* GetNetworkContextClient() const override;
diff --git a/services/network/trust_tokens/trust_token_request_helper_factory.cc b/services/network/trust_tokens/trust_token_request_helper_factory.cc
index e4e59d7..eb2389c3 100644
--- a/services/network/trust_tokens/trust_token_request_helper_factory.cc
+++ b/services/network/trust_tokens/trust_token_request_helper_factory.cc
@@ -98,7 +98,7 @@
 
   if (!authorizer_.Run()) {
     LogOutcome(net_log, params.type, Outcome::kRejectedByAuthorizer);
-    std::move(done).Run(mojom::TrustTokenOperationStatus::kUnavailable);
+    std::move(done).Run(mojom::TrustTokenOperationStatus::kUnauthorized);
     return;
   }
 
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 9a410843..4eb6faf9 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
@@ -291,7 +291,7 @@
   run_loop.Run();
 
   EXPECT_EQ(obtained_result.status(),
-            mojom::TrustTokenOperationStatus::kUnavailable);
+            mojom::TrustTokenOperationStatus::kUnauthorized);
   histogram_tester.ExpectUniqueSample(
       "Net.TrustTokens.RequestHelperFactoryOutcome.Signing",
       Outcome::kRejectedByAuthorizer, 1);
diff --git a/services/network/url_loader.cc b/services/network/url_loader.cc
index 0e03522..633e750 100644
--- a/services/network/url_loader.cc
+++ b/services/network/url_loader.cc
@@ -484,6 +484,7 @@
     base::WeakPtr<KeepaliveStatisticsRecorder> keepalive_statistics_recorder,
     std::unique_ptr<TrustTokenRequestHelperFactory> trust_token_helper_factory,
     mojo::PendingRemote<mojom::CookieAccessObserver> cookie_observer,
+    mojo::PendingRemote<mojom::TrustTokenAccessObserver> trust_token_observer,
     mojo::PendingRemote<mojom::URLLoaderNetworkServiceObserver>
         url_loader_network_observer,
     mojo::PendingRemote<mojom::DevToolsObserver> devtools_observer,
@@ -532,6 +533,10 @@
       cookie_observer_remote_(std::move(cookie_observer)),
       cookie_observer_(PtrOrFallback(cookie_observer_remote_,
                                      context.GetCookieAccessObserver())),
+      trust_token_observer_remote_(std::move(trust_token_observer)),
+      trust_token_observer_(
+          PtrOrFallback(trust_token_observer_remote_,
+                        context.GetTrustTokenAccessObserver())),
       url_loader_network_observer_remote_(
           std::move(url_loader_network_observer)),
       url_loader_network_observer_(
@@ -937,6 +942,21 @@
 void URLLoader::OnDoneConstructingTrustTokenHelper(
     mojom::TrustTokenOperationType type,
     TrustTokenStatusOrRequestHelper status_or_helper) {
+  if (trust_token_observer_) {
+    const net::IsolationInfo& isolation_info = url_request_->isolation_info();
+    url::Origin top_frame_origin;
+    if (isolation_info.top_frame_origin()) {
+      top_frame_origin = *isolation_info.top_frame_origin();
+    }
+
+    bool token_operation_unauthorized =
+        status_or_helper.status() ==
+        mojom::TrustTokenOperationStatus::kUnauthorized;
+    trust_token_observer_->OnTrustTokensAccessed(
+        mojom::TrustTokenAccessDetails::New(top_frame_origin,
+                                            token_operation_unauthorized));
+  }
+
   if (!status_or_helper.ok()) {
     trust_token_status_ = status_or_helper.status();
 
diff --git a/services/network/url_loader.h b/services/network/url_loader.h
index f3debce..ccc98aa5 100644
--- a/services/network/url_loader.h
+++ b/services/network/url_loader.h
@@ -45,6 +45,7 @@
 #include "services/network/public/mojom/ip_address_space.mojom-forward.h"
 #include "services/network/public/mojom/ip_address_space.mojom-shared.h"
 #include "services/network/public/mojom/network_service.mojom.h"
+#include "services/network/public/mojom/trust_token_access_observer.mojom.h"
 #include "services/network/public/mojom/trust_tokens.mojom-shared.h"
 #include "services/network/public/mojom/url_loader.mojom.h"
 #include "services/network/resource_scheduler/resource_scheduler.h"
@@ -156,6 +157,7 @@
       std::unique_ptr<TrustTokenRequestHelperFactory>
           trust_token_helper_factory,
       mojo::PendingRemote<mojom::CookieAccessObserver> cookie_observer,
+      mojo::PendingRemote<mojom::TrustTokenAccessObserver> trust_token_observer,
       mojo::PendingRemote<mojom::URLLoaderNetworkServiceObserver>
           url_loader_network_observer,
       mojo::PendingRemote<mojom::DevToolsObserver> devtools_observer,
@@ -597,6 +599,10 @@
   // URLLoaderFactory).
   const mojo::Remote<mojom::CookieAccessObserver> cookie_observer_remote_;
   const raw_ptr<mojom::CookieAccessObserver> cookie_observer_ = nullptr;
+  const mojo::Remote<mojom::TrustTokenAccessObserver>
+      trust_token_observer_remote_;
+  const raw_ptr<mojom::TrustTokenAccessObserver> trust_token_observer_ =
+      nullptr;
   const mojo::Remote<mojom::URLLoaderNetworkServiceObserver>
       url_loader_network_observer_remote_;
   const raw_ptr<mojom::URLLoaderNetworkServiceObserver>
diff --git a/services/network/url_loader_context.h b/services/network/url_loader_context.h
index 775fe41..4b61d5aa 100644
--- a/services/network/url_loader_context.h
+++ b/services/network/url_loader_context.h
@@ -27,6 +27,7 @@
 class DevToolsObserver;
 class NetworkContextClient;
 class TrustedURLLoaderHeaderClient;
+class TrustTokenAccessObserver;
 class URLLoaderFactoryParams;
 class URLLoaderNetworkServiceObserver;
 }  // namespace mojom
@@ -39,6 +40,8 @@
   virtual const cors::OriginAccessList& GetOriginAccessList() const = 0;
   virtual const mojom::URLLoaderFactoryParams& GetFactoryParams() const = 0;
   virtual mojom::CookieAccessObserver* GetCookieAccessObserver() const = 0;
+  virtual mojom::TrustTokenAccessObserver* GetTrustTokenAccessObserver()
+      const = 0;
   virtual mojom::CrossOriginEmbedderPolicyReporter* GetCoepReporter() const = 0;
   virtual mojom::DevToolsObserver* GetDevToolsObserver() const = 0;
   virtual mojom::NetworkContextClient* GetNetworkContextClient() const = 0;
diff --git a/services/network/url_loader_factory.cc b/services/network/url_loader_factory.cc
index 1eb0584..f0f256bf 100644
--- a/services/network/url_loader_factory.cc
+++ b/services/network/url_loader_factory.cc
@@ -296,6 +296,9 @@
         std::move(const_cast<mojo::PendingRemote<mojom::CookieAccessObserver>&>(
             resource_request.trusted_params->cookie_observer));
   }
+  // TODO(https://crbug.com/1378264): Currently Trust Token Access observer
+  // isn't hooked up through URLLoaderFactory.
+  mojo::PendingRemote<mojom::TrustTokenAccessObserver> trust_token_observer;
   mojo::PendingRemote<mojom::URLLoaderNetworkServiceObserver>
       url_loader_network_observer;
   if (resource_request.trusted_params &&
@@ -338,9 +341,10 @@
       static_cast<net::NetworkTrafficAnnotationTag>(traffic_annotation),
       request_id, keepalive_request_size,
       std::move(keepalive_statistics_recorder), std::move(trust_token_factory),
-      std::move(cookie_observer), std::move(url_loader_network_observer),
-      std::move(devtools_observer), std::move(accept_ch_frame_observer),
-      third_party_cookies_enabled, context_->cache_transparency_settings());
+      std::move(cookie_observer), std::move(trust_token_observer),
+      std::move(url_loader_network_observer), std::move(devtools_observer),
+      std::move(accept_ch_frame_observer), third_party_cookies_enabled,
+      context_->cache_transparency_settings());
 
   if (context_->GetMemoryCache())
     loader->SetMemoryCache(context_->GetMemoryCache()->GetWeakPtr());
@@ -360,6 +364,13 @@
   return nullptr;
 }
 
+mojom::TrustTokenAccessObserver* URLLoaderFactory::GetTrustTokenAccessObserver()
+    const {
+  // TODO(https://crbug.com/1378264): URLLoaderFactory support for the Trust
+  // Token Access Observer is currently unimplemented.
+  return nullptr;
+}
+
 mojom::URLLoaderNetworkServiceObserver*
 URLLoaderFactory::GetURLLoaderNetworkServiceObserver() const {
   if (url_loader_network_service_observer_)
diff --git a/services/network/url_loader_factory.h b/services/network/url_loader_factory.h
index 35df8f9..fd5a0483 100644
--- a/services/network/url_loader_factory.h
+++ b/services/network/url_loader_factory.h
@@ -15,6 +15,7 @@
 #include "services/network/public/mojom/cookie_access_observer.mojom.h"
 #include "services/network/public/mojom/devtools_observer.mojom.h"
 #include "services/network/public/mojom/network_context.mojom.h"
+#include "services/network/public/mojom/trust_token_access_observer.mojom.h"
 #include "services/network/public/mojom/url_loader_factory.mojom.h"
 #include "services/network/public/mojom/url_loader_network_service_observer.mojom.h"
 #include "services/network/url_loader_context.h"
@@ -71,6 +72,7 @@
   const cors::OriginAccessList& GetOriginAccessList() const override;
   const mojom::URLLoaderFactoryParams& GetFactoryParams() const override;
   mojom::CookieAccessObserver* GetCookieAccessObserver() const override;
+  mojom::TrustTokenAccessObserver* GetTrustTokenAccessObserver() const override;
   mojom::CrossOriginEmbedderPolicyReporter* GetCoepReporter() const override;
   mojom::DevToolsObserver* GetDevToolsObserver() const override;
   mojom::NetworkContextClient* GetNetworkContextClient() const override;
diff --git a/services/network/url_loader_unittest.cc b/services/network/url_loader_unittest.cc
index 5266d26..4d35671 100644
--- a/services/network/url_loader_unittest.cc
+++ b/services/network/url_loader_unittest.cc
@@ -643,9 +643,9 @@
         std::move(sync_url_loader_client), traffic_annotation, request_id,
         keepalive_request_size, std::move(keepalive_statistics_recorder),
         std::move(trust_token_helper_factory), std::move(cookie_observer),
-        std::move(url_loader_network_observer), std::move(devtools_observer),
-        std::move(accept_ch_frame_observer), third_party_cookies_enabled,
-        cache_transparency_settings);
+        std::move(trust_token_observer), std::move(url_loader_network_observer),
+        std::move(devtools_observer), std::move(accept_ch_frame_observer),
+        third_party_cookies_enabled, cache_transparency_settings);
   }
 
   int32_t options = mojom::kURLLoadOptionNone;
@@ -658,6 +658,8 @@
   std::unique_ptr<TrustTokenRequestHelperFactory> trust_token_helper_factory;
   mojo::PendingRemote<mojom::CookieAccessObserver> cookie_observer =
       mojo::NullRemote();
+  mojo::PendingRemote<mojom::TrustTokenAccessObserver> trust_token_observer =
+      mojo::NullRemote();
   mojo::PendingRemote<mojom::URLLoaderNetworkServiceObserver>
       url_loader_network_observer = mojo::NullRemote();
   mojo::PendingRemote<mojom::DevToolsObserver> devtools_observer =
@@ -3886,6 +3888,70 @@
       arg, result_listener);
 }
 
+class MockTrustTokenObserver : public network::mojom::TrustTokenAccessObserver {
+ public:
+  MockTrustTokenObserver() = default;
+  ~MockTrustTokenObserver() override = default;
+
+  struct TrustTokenDetails {
+    explicit TrustTokenDetails(const mojom::TrustTokenAccessDetailsPtr& details)
+        : origin(details->origin), blocked(details->blocked) {}
+
+    url::Origin origin;
+    bool blocked;
+  };
+
+  mojo::PendingRemote<mojom::TrustTokenAccessObserver> GetRemote() {
+    mojo::PendingRemote<mojom::TrustTokenAccessObserver> remote;
+    receivers_.Add(this, remote.InitWithNewPipeAndPassReceiver());
+    return remote;
+  }
+
+  void OnTrustTokensAccessed(
+      mojom::TrustTokenAccessDetailsPtr details) override {
+    observed_tokens_.emplace_back(details);
+    if (wait_for_token_count_ &&
+        observed_tokens().size() >= wait_for_token_count_) {
+      std::move(wait_for_tokens_quit_closure_).Run();
+    }
+  }
+
+  void WaitForTrustTokens(size_t token_count) {
+    if (observed_tokens_.size() < token_count) {
+      wait_for_token_count_ = token_count;
+      base::RunLoop run_loop;
+      wait_for_tokens_quit_closure_ = run_loop.QuitClosure();
+      run_loop.Run();
+    }
+    EXPECT_EQ(observed_tokens_.size(), token_count);
+  }
+
+  void Clone(mojo::PendingReceiver<mojom::TrustTokenAccessObserver> observer)
+      override {
+    receivers_.Add(this, std::move(observer));
+  }
+
+  const std::vector<TrustTokenDetails>& observed_tokens() {
+    return observed_tokens_;
+  }
+
+ private:
+  size_t wait_for_token_count_ = 0;
+  base::OnceClosure wait_for_tokens_quit_closure_;
+  std::vector<TrustTokenDetails> observed_tokens_;
+  mojo::ReceiverSet<mojom::TrustTokenAccessObserver> receivers_;
+};
+
+MATCHER_P2(MatchesTrustTokenDetails, origin, blocked, "") {
+  return testing::ExplainMatchResult(
+      testing::AllOf(
+          testing::Field(&MockTrustTokenObserver::TrustTokenDetails::origin,
+                         origin),
+          testing::Field(&MockTrustTokenObserver::TrustTokenDetails::blocked,
+                         blocked)),
+      arg, result_listener);
+}
+
 // Responds certificate request with previously set responses.
 class ClientCertAuthObserver : public TestURLLoaderNetworkObserver {
  public:
@@ -5839,6 +5905,8 @@
   MockTrustTokenDevToolsObserver devtools_observer;
 
   URLLoaderOptions url_loader_options;
+  MockTrustTokenObserver trust_token_observer;
+  url_loader_options.trust_token_observer = trust_token_observer.GetRemote();
   url_loader_options.trust_token_helper_factory =
       std::make_unique<MockTrustTokenRequestHelperFactory>(
           mojom::TrustTokenOperationStatus::kOk /* on_begin */,
@@ -5869,6 +5937,11 @@
   EXPECT_EQ(ReadBody(), expected);
 
   EXPECT_FALSE(client()->response_head()->headers->raw_headers().empty());
+
+  trust_token_observer.WaitForTrustTokens(1u);
+  EXPECT_THAT(trust_token_observer.observed_tokens(),
+              testing::ElementsAre(
+                  MatchesTrustTokenDetails(test_server()->GetOrigin(), false)));
 }
 
 // A request with an associated Trust Tokens operation whose Begin step returns
@@ -5890,6 +5963,8 @@
   MockTrustTokenDevToolsObserver devtools_observer;
 
   URLLoaderOptions url_loader_options;
+  MockTrustTokenObserver trust_token_observer;
+  url_loader_options.trust_token_observer = trust_token_observer.GetRemote();
   url_loader_options.trust_token_helper_factory =
       std::make_unique<MockTrustTokenRequestHelperFactory>(
           mojom::TrustTokenOperationStatus::kAlreadyExists /* on_begin */,
@@ -5914,6 +5989,11 @@
 
   EXPECT_FALSE(client()->response_head());
   EXPECT_FALSE(client()->response_body().is_valid());
+
+  trust_token_observer.WaitForTrustTokens(1u);
+  EXPECT_THAT(trust_token_observer.observed_tokens(),
+              testing::ElementsAre(
+                  MatchesTrustTokenDetails(test_server()->GetOrigin(), false)));
 }
 
 // When a request's associated Trust Tokens operation's Begin step fails, the
@@ -5929,6 +6009,8 @@
   MockTrustTokenDevToolsObserver devtools_observer;
 
   URLLoaderOptions url_loader_options;
+  MockTrustTokenObserver trust_token_observer;
+  url_loader_options.trust_token_observer = trust_token_observer.GetRemote();
   url_loader_options.trust_token_helper_factory =
       std::make_unique<MockTrustTokenRequestHelperFactory>(
           mojom::TrustTokenOperationStatus::kFailedPrecondition /* on_begin */,
@@ -5953,6 +6035,11 @@
 
   EXPECT_FALSE(client()->response_head());
   EXPECT_FALSE(client()->response_body().is_valid());
+
+  trust_token_observer.WaitForTrustTokens(1u);
+  EXPECT_THAT(trust_token_observer.observed_tokens(),
+              testing::ElementsAre(
+                  MatchesTrustTokenDetails(test_server()->GetOrigin(), false)));
 }
 
 // When a request's associated Trust Tokens operation's Begin step succeeds but
@@ -5968,6 +6055,8 @@
   MockTrustTokenDevToolsObserver devtools_observer;
 
   URLLoaderOptions url_loader_options;
+  MockTrustTokenObserver trust_token_observer;
+  url_loader_options.trust_token_observer = trust_token_observer.GetRemote();
   url_loader_options.trust_token_helper_factory =
       std::make_unique<MockTrustTokenRequestHelperFactory>(
           mojom::TrustTokenOperationStatus::kOk /* on_begin */,
@@ -5990,6 +6079,11 @@
   // Verify the DevTools event was fired and it has the right status.
   EXPECT_EQ(devtools_observer.trust_token_operation_status(),
             mojom::TrustTokenOperationStatus::kBadResponse);
+
+  trust_token_observer.WaitForTrustTokens(1u);
+  EXPECT_THAT(trust_token_observer.observed_tokens(),
+              testing::ElementsAre(
+                  MatchesTrustTokenDetails(test_server()->GetOrigin(), false)));
 }
 
 // When URLLoader receives a  request parameterized to perform a Trust Tokens
@@ -6007,6 +6101,8 @@
   MockTrustTokenDevToolsObserver devtools_observer;
 
   URLLoaderOptions url_loader_options;
+  MockTrustTokenObserver trust_token_observer;
+  url_loader_options.trust_token_observer = trust_token_observer.GetRemote();
   url_loader_options.trust_token_helper_factory =
       std::make_unique<MockTrustTokenRequestHelperFactory>(
           mojom::TrustTokenOperationStatus::
@@ -6028,6 +6124,54 @@
   // Verify the DevTools event was fired and it has the right status.
   EXPECT_EQ(devtools_observer.trust_token_operation_status(),
             mojom::TrustTokenOperationStatus::kInternalError);
+
+  trust_token_observer.WaitForTrustTokens(1u);
+  EXPECT_THAT(trust_token_observer.observed_tokens(),
+              testing::ElementsAre(
+                  MatchesTrustTokenDetails(test_server()->GetOrigin(), false)));
+}
+
+// When URLLoader receives a request that is blocked by policy, the request
+// should fail entirely and report a blocked event to the observer.
+TEST_P(URLLoaderSyncOrAsyncTrustTokenOperationTest,
+       HandlesTrustTokenRequestHelperCreationBlocked) {
+  ResourceRequest request = CreateTrustTokenResourceRequest();
+
+  base::RunLoop delete_run_loop;
+  mojo::PendingRemote<mojom::URLLoader> loader_remote;
+  std::unique_ptr<URLLoader> url_loader;
+  context().mutable_factory_params().process_id = mojom::kBrowserProcessId;
+  MockTrustTokenDevToolsObserver devtools_observer;
+
+  URLLoaderOptions url_loader_options;
+  MockTrustTokenObserver trust_token_observer;
+  url_loader_options.trust_token_observer = trust_token_observer.GetRemote();
+  url_loader_options.trust_token_helper_factory =
+      std::make_unique<MockTrustTokenRequestHelperFactory>(
+          mojom::TrustTokenOperationStatus::
+              kUnauthorized /* helper_creation_error */,
+          GetParam());
+  url_loader_options.devtools_observer = devtools_observer.Bind();
+  url_loader = url_loader_options.MakeURLLoader(
+      context(), DeleteLoaderCallback(&delete_run_loop, &url_loader),
+      loader_remote.InitWithNewPipeAndPassReceiver(), request,
+      client()->CreateRemote());
+
+  client()->RunUntilComplete();
+  delete_run_loop.Run();
+
+  EXPECT_EQ(client()->completion_status().error_code,
+            net::ERR_TRUST_TOKEN_OPERATION_FAILED);
+  EXPECT_EQ(client()->completion_status().trust_token_operation_status,
+            mojom::TrustTokenOperationStatus::kUnauthorized);
+  // Verify the DevTools event was fired and it has the right status.
+  EXPECT_EQ(devtools_observer.trust_token_operation_status(),
+            mojom::TrustTokenOperationStatus::kUnauthorized);
+
+  trust_token_observer.WaitForTrustTokens(1u);
+  EXPECT_THAT(trust_token_observer.observed_tokens(),
+              testing::ElementsAre(
+                  MatchesTrustTokenDetails(test_server()->GetOrigin(), true)));
 }
 
 TEST_F(URLLoaderTest, OnRawRequestClientSecurityStateFactory) {
@@ -6049,6 +6193,8 @@
   base::RunLoop delete_run_loop;
   mojo::PendingRemote<mojom::URLLoader> loader;
   URLLoaderOptions url_loader_options;
+  MockTrustTokenObserver trust_token_observer;
+  url_loader_options.trust_token_observer = trust_token_observer.GetRemote();
   url_loader_options.devtools_observer = devtools_observer.Bind();
   std::unique_ptr<URLLoader> url_loader = url_loader_options.MakeURLLoader(
       context(), DeleteLoaderCallback(&delete_run_loop, &url_loader),
diff --git a/skia/config/SkUserConfig.h b/skia/config/SkUserConfig.h
index 50670b75..4793800 100644
--- a/skia/config/SkUserConfig.h
+++ b/skia/config/SkUserConfig.h
@@ -216,10 +216,6 @@
 
 #define SK_USE_LEGACY_MIPMAP_BUILDER
 
-// To ensure chrome only has access to legacy vma memory query apis until all
-// skia changes to implement new api is completed.
-#define SK_USE_LEGACY_VMA_MEMORY_QUERY
-
 // Temporary until web tests can be rebaselined (skbug.com/13752)
 #define SK_DISABLE_RASTER_PIPELINE_SAMPLING_FIXES
 
diff --git a/storage/browser/quota/quota_database.cc b/storage/browser/quota/quota_database.cc
index 08cd682..e252dd6 100644
--- a/storage/browser/quota/quota_database.cc
+++ b/storage/browser/quota/quota_database.cc
@@ -860,12 +860,19 @@
 
   db_->set_histogram_tag("Quota");
 
-  // UMA logging and don't crash on database errors in DCHECK builds.
-  db_->set_error_callback(
-      base::BindRepeating([](int sqlite_error_code, sql::Statement* statement) {
+  db_->set_error_callback(base::BindRepeating(
+      [](base::RepeatingClosure full_disk_error_callback, int sqlite_error_code,
+         sql::Statement* statement) {
         sql::UmaHistogramSqliteResult("Quota.QuotaDatabaseError",
                                       sqlite_error_code);
-      }));
+
+        if (!full_disk_error_callback.is_null() &&
+            static_cast<sql::SqliteErrorCode>(sqlite_error_code) ==
+                sql::SqliteErrorCode::kFullDisk) {
+          full_disk_error_callback.Run();
+        }
+      },
+      full_disk_error_callback_));
 
   // Migrate an existing database from the old path.
   if (!db_file_path_.empty() && !MoveLegacyDatabase()) {
@@ -1134,4 +1141,9 @@
   return result;
 }
 
+void QuotaDatabase::SetOnFullDiskErrorCallback(
+    const base::RepeatingClosure& callback) {
+  full_disk_error_callback_ = callback;
+}
+
 }  // namespace storage
diff --git a/storage/browser/quota/quota_database.h b/storage/browser/quota/quota_database.h
index a0cca26..b89f307d 100644
--- a/storage/browser/quota/quota_database.h
+++ b/storage/browser/quota/quota_database.h
@@ -209,6 +209,10 @@
   // Flushes previously scheduled commits.
   void CommitNow();
 
+  // The given callback will be invoked whenever the database encounters a full
+  // disk error.
+  void SetOnFullDiskErrorCallback(const base::RepeatingClosure& callback);
+
   // Testing support for database corruption handling.
   //
   // Runs `corrupter` on the same sequence used to do database I/O,
@@ -293,6 +297,8 @@
   static const size_t kTableCount;
   static const IndexSchema kIndexes[];
   static const size_t kIndexCount;
+
+  base::RepeatingClosure full_disk_error_callback_;
 };
 
 }  // namespace storage
diff --git a/storage/browser/quota/quota_manager_impl.cc b/storage/browser/quota/quota_manager_impl.cc
index 1b401886..cc540a0 100644
--- a/storage/browser/quota/quota_manager_impl.cc
+++ b/storage/browser/quota/quota_manager_impl.cc
@@ -1727,6 +1727,12 @@
   database_ = std::make_unique<QuotaDatabase>(is_incognito_ ? base::FilePath()
                                                             : profile_path_);
 
+  // Start the storage eviction routine on a full disk error.
+  database_->SetOnFullDiskErrorCallback(
+      base::BindPostTask(base::SequencedTaskRunner::GetCurrentDefault(),
+                         base::BindRepeating(&QuotaManagerImpl::StartEviction,
+                                             weak_factory_.GetWeakPtr())));
+
   temporary_usage_tracker_ = std::make_unique<UsageTracker>(
       this, client_types_[StorageType::kTemporary], StorageType::kTemporary,
       special_storage_policy_.get());
@@ -1993,12 +1999,13 @@
 
 void QuotaManagerImpl::StartEviction() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  DCHECK(!temporary_storage_evictor_.get());
 
   if (eviction_disabled_)
     return;
-  temporary_storage_evictor_ = std::make_unique<QuotaTemporaryStorageEvictor>(
-      this, kEvictionIntervalInMilliSeconds);
+  if (!temporary_storage_evictor_) {
+    temporary_storage_evictor_ = std::make_unique<QuotaTemporaryStorageEvictor>(
+        this, kEvictionIntervalInMilliSeconds);
+  }
   temporary_storage_evictor_->Start();
 }
 
diff --git a/storage/browser/quota/quota_temporary_storage_evictor.cc b/storage/browser/quota/quota_temporary_storage_evictor.cc
index 11f7d53..3486b2d 100644
--- a/storage/browser/quota/quota_temporary_storage_evictor.cc
+++ b/storage/browser/quota/quota_temporary_storage_evictor.cc
@@ -27,7 +27,7 @@
   base::UmaHistogramCustomCounts(name, sample / kMBytes, 1,
                                  10 * 1024 * 1024 /* 10 TB */, 100);
 }
-}
+}  // namespace
 
 namespace storage {
 
@@ -35,8 +35,7 @@
     QuotaEvictionHandler* quota_eviction_handler,
     int64_t interval_ms)
     : quota_eviction_handler_(quota_eviction_handler),
-      interval_ms_(interval_ms),
-      timer_disabled_for_testing_(false) {
+      interval_ms_(interval_ms) {
   DCHECK(quota_eviction_handler);
 }
 
@@ -52,8 +51,7 @@
   (*statistics)["errors-on-getting-usage-and-quota"] =
       statistics_.num_errors_on_getting_usage_and_quota;
   (*statistics)["evicted-buckets"] = statistics_.num_evicted_buckets;
-  (*statistics)["eviction-rounds"] =
-      statistics_.num_eviction_rounds;
+  (*statistics)["eviction-rounds"] = statistics_.num_eviction_rounds;
   (*statistics)["skipped-eviction-rounds"] =
       statistics_.num_skipped_eviction_rounds;
 }
@@ -115,6 +113,11 @@
   } else {
     ++statistics_.num_skipped_eviction_rounds;
   }
+
+  if (!on_round_finished_for_testing_.is_null()) {
+    on_round_finished_for_testing_.Run();
+  }
+
   // Reset stats for next round.
   round_statistics_ = EvictionRoundStatistics();
 }
@@ -122,6 +125,17 @@
 void QuotaTemporaryStorageEvictor::Start() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
+  // Don't start while we're in a round.
+  if (round_statistics_.in_round) {
+    return;
+  }
+
+  // If we already have a round scheduled, just run it now.
+  if (eviction_timer_.IsRunning()) {
+    eviction_timer_.FireNow();
+    return;
+  }
+
   base::AutoReset<bool> auto_reset(&timer_disabled_for_testing_, false);
   StartEvictionTimerWithDelay(0);
 
@@ -183,8 +197,7 @@
       current_usage - static_cast<int64_t>(settings.pool_size *
                                            kUsageRatioToStartEviction));
   int64_t diskspace_shortage =
-      std::max(INT64_C(0),
-               settings.should_remain_available - available_space);
+      std::max(INT64_C(0), settings.should_remain_available - available_space);
   DCHECK(current_usage_is_complete || diskspace_shortage == 0);
 
   // If we're using so little that freeing all of it wouldn't help,
diff --git a/storage/browser/quota/quota_temporary_storage_evictor.h b/storage/browser/quota/quota_temporary_storage_evictor.h
index 1b666f4..c7d9481 100644
--- a/storage/browser/quota/quota_temporary_storage_evictor.h
+++ b/storage/browser/quota/quota_temporary_storage_evictor.h
@@ -97,7 +97,8 @@
   base::Time time_of_end_of_last_round_;
 
   int64_t interval_ms_;
-  bool timer_disabled_for_testing_;
+  bool timer_disabled_for_testing_ = false;
+  base::RepeatingClosure on_round_finished_for_testing_;
 
   base::OneShotTimer eviction_timer_;
   base::RepeatingTimer histogram_timer_;
diff --git a/storage/browser/quota/quota_temporary_storage_evictor_unittest.cc b/storage/browser/quota/quota_temporary_storage_evictor_unittest.cc
index 8f72496..febc778 100644
--- a/storage/browser/quota/quota_temporary_storage_evictor_unittest.cc
+++ b/storage/browser/quota/quota_temporary_storage_evictor_unittest.cc
@@ -29,6 +29,11 @@
 
 namespace {
 
+struct EvictionBucket {
+  BucketLocator locator;
+  int64_t usage;
+};
+
 class MockQuotaEvictionHandler : public QuotaEvictionHandler {
  public:
   void EvictExpiredBuckets(StatusCallback done) override {
@@ -57,9 +62,11 @@
     }
     if (!task_for_get_usage_and_quota_.is_null())
       task_for_get_usage_and_quota_.Run();
-    std::move(callback).Run(blink::mojom::QuotaStatusCode::kOk, settings_,
-                            available_space_, available_space_ * 2, GetUsage(),
-                            true);
+    base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
+        FROM_HERE,
+        base::BindOnce(std::move(callback), blink::mojom::QuotaStatusCode::kOk,
+                       settings_, available_space_, available_space_ * 2,
+                       GetUsage(), true));
   }
 
   void GetEvictionBucket(StorageType type,
@@ -119,6 +126,10 @@
     buckets_[bucket.id] = usage;
   }
 
+  bool HasBucket(const EvictionBucket& bucket) {
+    return base::Contains(buckets_, bucket.locator.id);
+  }
+
  private:
   int64_t EnsureBucketRemoved(const BucketLocator& bucket) {
     int64_t bucket_usage;
@@ -202,6 +213,62 @@
                          blink::mojom::StorageType::kTemporary, is_default);
   }
 
+  EvictionBucket CreateEvictionBucket(const std::string& url,
+                                      int64_t usage,
+                                      bool is_default = true) {
+    return EvictionBucket{CreateBucket(url, is_default), usage};
+  }
+
+  EvictionBucket CreateAndAddBucket(const std::string& url,
+                                    int64_t usage,
+                                    bool is_default = true) {
+    EvictionBucket bucket = CreateEvictionBucket(url, usage, is_default);
+    quota_eviction_handler()->AddBucket(bucket.locator, bucket.usage);
+    return bucket;
+  }
+
+  bool EvictorHasBuckets(const std::vector<EvictionBucket>& buckets) {
+    int64_t total_usage = 0;
+    for (const auto& bucket : buckets) {
+      if (!quota_eviction_handler_->HasBucket(bucket)) {
+        return false;
+      }
+      total_usage += bucket.usage;
+    }
+    return total_usage == quota_eviction_handler_->GetUsage();
+  }
+
+  void RunAnEvictionRound(bool schedule_next_round) {
+    temporary_storage_evictor_->timer_disabled_for_testing_ =
+        !schedule_next_round;
+    base::RunLoop run_loop;
+    quota_eviction_handler_->set_task_for_get_usage_and_quota(
+        base::BindRepeating(
+            [](int* num_get_usage_and_quota_for_eviction) {
+              (*num_get_usage_and_quota_for_eviction)++;
+            },
+            base::Unretained(&num_get_usage_and_quota_for_eviction_)));
+    set_on_round_finished_callback(run_loop.QuitClosure());
+    temporary_storage_evictor()->Start();
+    run_loop.Run();
+  }
+
+  bool RunAnEvictionPass() {
+    base::RunLoop run_loop;
+    quota_eviction_handler_->set_task_for_get_usage_and_quota(
+        base::BindRepeating(
+            [](base::RepeatingClosure quit_closure,
+               int* num_get_usage_and_quota_for_eviction) {
+              (*num_get_usage_and_quota_for_eviction)++;
+              quit_closure.Run();
+            },
+            run_loop.QuitClosure(),
+            base::Unretained(&num_get_usage_and_quota_for_eviction_)));
+    set_on_round_finished_callback(run_loop.QuitClosure());
+    run_loop.Run();
+    return temporary_storage_evictor_->round_statistics_.in_round;
+  }
+
  protected:
   MockQuotaEvictionHandler* quota_eviction_handler() const {
     return static_cast<MockQuotaEvictionHandler*>(
@@ -220,6 +287,10 @@
     temporary_storage_evictor_->timer_disabled_for_testing_ = true;
   }
 
+  void set_on_round_finished_callback(base::RepeatingClosure callback) {
+    temporary_storage_evictor_->on_round_finished_for_testing_ = callback;
+  }
+
   int num_get_usage_and_quota_for_eviction() const {
     return num_get_usage_and_quota_for_eviction_;
   }
@@ -446,4 +517,87 @@
   EXPECT_EQ(0, statistics().num_skipped_eviction_rounds);  // FIXME?
 }
 
+TEST_F(QuotaTemporaryStorageEvictorTest, CallingStartAfterEvictionScheduled) {
+  // After running an eviction round and letting it schedule the next one, it
+  // should immediately run the next one if you call Start instead of waiting
+  // out the timer.
+  quota_eviction_handler()->SetPoolSize(4000);
+  quota_eviction_handler()->set_available_space(1000000000);
+
+  EvictionBucket bucket_z = CreateAndAddBucket("http://www.z.com", 20);
+  EvictionBucket bucket_y = CreateAndAddBucket("http://www.y.com", 2900);
+  EvictionBucket bucket_x = CreateAndAddBucket("http://www.x.com", 450);
+  EvictionBucket bucket_w = CreateAndAddBucket("http://www.w.com", 400);
+  EXPECT_TRUE(EvictorHasBuckets({bucket_z, bucket_y, bucket_x, bucket_w}));
+
+  RunAnEvictionRound(/*schedule_next_round=*/true);
+  EXPECT_TRUE(EvictorHasBuckets({bucket_x, bucket_w}));
+
+  EvictionBucket bucket_v = CreateAndAddBucket("http://www.v.com", 2000);
+  EvictionBucket bucket_u = CreateAndAddBucket("http://www.u.com", 400);
+  EXPECT_TRUE(EvictorHasBuckets({bucket_x, bucket_w, bucket_v, bucket_u}));
+
+  RunAnEvictionRound(/*schedule_next_round=*/false);
+  EXPECT_TRUE(EvictorHasBuckets({bucket_w, bucket_v, bucket_u}));
+
+  EXPECT_EQ(0, statistics().num_errors_on_getting_usage_and_quota);
+  EXPECT_EQ(3, statistics().num_evicted_buckets);
+  EXPECT_EQ(2, statistics().num_eviction_rounds);
+  EXPECT_EQ(0, statistics().num_skipped_eviction_rounds);
+  EXPECT_EQ(2U, quota_eviction_handler()->get_evict_expired_buckets_count());
+}
+
+TEST_F(QuotaTemporaryStorageEvictorTest,
+       CallingStartImmediatelyAfterEvictionScheduled) {
+  // After calling start to schedule an eviction round, calling start multiple
+  // times before the eviction round begins shouldn't cause it to run multiple
+  // times.
+  quota_eviction_handler()->SetPoolSize(4000);
+  quota_eviction_handler()->set_available_space(1000000000);
+
+  EvictionBucket bucket_z = CreateAndAddBucket("http://www.z.com", 20);
+  EvictionBucket bucket_y = CreateAndAddBucket("http://www.y.com", 2900);
+  EvictionBucket bucket_x = CreateAndAddBucket("http://www.x.com", 450);
+  EvictionBucket bucket_w = CreateAndAddBucket("http://www.w.com", 400);
+  EXPECT_TRUE(EvictorHasBuckets({bucket_z, bucket_y, bucket_x, bucket_w}));
+
+  temporary_storage_evictor()->Start();
+  temporary_storage_evictor()->Start();
+  RunAnEvictionRound(/*schedule_next_round=*/false);
+  EXPECT_TRUE(EvictorHasBuckets({bucket_x, bucket_w}));
+
+  EXPECT_EQ(0, statistics().num_errors_on_getting_usage_and_quota);
+  EXPECT_EQ(2, statistics().num_evicted_buckets);
+  EXPECT_EQ(1, statistics().num_eviction_rounds);
+  EXPECT_EQ(0, statistics().num_skipped_eviction_rounds);
+  EXPECT_EQ(1U, quota_eviction_handler()->get_evict_expired_buckets_count());
+}
+
+TEST_F(QuotaTemporaryStorageEvictorTest, CallingStartDuringEvictionRoutine) {
+  // If we're in the middle of an eviction round, calling Start should do
+  // nothing.
+  quota_eviction_handler()->SetPoolSize(4000);
+  quota_eviction_handler()->set_available_space(1000000000);
+
+  EvictionBucket bucket_z = CreateAndAddBucket("http://www.z.com", 20);
+  EvictionBucket bucket_y = CreateAndAddBucket("http://www.y.com", 2900);
+  EvictionBucket bucket_x = CreateAndAddBucket("http://www.x.com", 450);
+  EvictionBucket bucket_w = CreateAndAddBucket("http://www.w.com", 400);
+  EXPECT_TRUE(EvictorHasBuckets({bucket_z, bucket_y, bucket_x, bucket_w}));
+
+  disable_timer_for_testing();
+  temporary_storage_evictor()->Start();
+  while (RunAnEvictionPass()) {
+    temporary_storage_evictor_->Start();
+  }
+  EXPECT_TRUE(EvictorHasBuckets({bucket_x, bucket_w}));
+
+  EXPECT_EQ(3, num_get_usage_and_quota_for_eviction());
+  EXPECT_EQ(0, statistics().num_errors_on_getting_usage_and_quota);
+  EXPECT_EQ(2, statistics().num_evicted_buckets);
+  EXPECT_EQ(1, statistics().num_eviction_rounds);
+  EXPECT_EQ(0, statistics().num_skipped_eviction_rounds);
+  EXPECT_EQ(1U, quota_eviction_handler()->get_evict_expired_buckets_count());
+}
+
 }  // namespace storage
diff --git a/testing/buildbot/chromium.fyi.json b/testing/buildbot/chromium.fyi.json
index dffa27d..fd9af986 100644
--- a/testing/buildbot/chromium.fyi.json
+++ b/testing/buildbot/chromium.fyi.json
@@ -23731,7 +23731,7 @@
       {
         "args": [
           "--platform",
-          "iPad Pro (12.9-inch) (2nd generation)",
+          "iPhone 7",
           "--version",
           "14.5",
           "--out-dir",
@@ -23748,7 +23748,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_chrome_signin_eg2tests_module iPad Pro (12.9-inch) (2nd generation) 14.5",
+        "name": "ios_chrome_signin_eg2tests_module iPhone 7 14.5",
         "resultdb": {
           "enable": true,
           "has_native_resultdb_integration": true
@@ -23782,7 +23782,7 @@
           "shards": 6
         },
         "test_id_prefix": "ninja://ios/chrome/test/earl_grey2:ios_chrome_signin_eg2tests_module/",
-        "variant_id": "iPad Pro (12.9-inch) (2nd generation) 14.5"
+        "variant_id": "iPhone 7 14.5"
       },
       {
         "args": [
@@ -24064,7 +24064,7 @@
       {
         "args": [
           "--platform",
-          "iPad Pro (12.9-inch) (2nd generation)",
+          "iPhone 7",
           "--version",
           "14.5",
           "--out-dir",
@@ -24081,7 +24081,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_chrome_ui_eg2tests_module iPad Pro (12.9-inch) (2nd generation) 14.5",
+        "name": "ios_chrome_ui_eg2tests_module iPhone 7 14.5",
         "resultdb": {
           "enable": true,
           "has_native_resultdb_integration": true
@@ -24115,7 +24115,7 @@
           "shards": 12
         },
         "test_id_prefix": "ninja://ios/chrome/test/earl_grey2:ios_chrome_ui_eg2tests_module/",
-        "variant_id": "iPad Pro (12.9-inch) (2nd generation) 14.5"
+        "variant_id": "iPhone 7 14.5"
       },
       {
         "args": [
@@ -30432,62 +30432,6 @@
       {
         "args": [
           "--platform",
-          "iPhone 6s",
-          "--version",
-          "15.5",
-          "--output-disabled-tests",
-          "--out-dir",
-          "${ISOLATED_OUTDIR}",
-          "--xcode-build-version",
-          "14b47b",
-          "--xctest",
-          "--xcode-parallelization"
-        ],
-        "isolate_name": "ios_chrome_signin_eg2tests_module",
-        "isolate_profile_data": true,
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
-        },
-        "name": "ios_chrome_signin_eg2tests_module iPhone 6s 15.5",
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/mac_toolchain/${platform}",
-              "location": ".",
-              "revision": "git_revision:723fc1a6c8cdf2631a57851f5610e598db0c1de1"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Mac-12"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "xcode_ios_14b47b",
-              "path": "Xcode.app"
-            },
-            {
-              "name": "runtime_ios_15_5",
-              "path": "Runtime-ios-15.5"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
-          "shards": 6
-        },
-        "test_id_prefix": "ninja://ios/chrome/test/earl_grey2:ios_chrome_signin_eg2tests_module/",
-        "variant_id": "iPhone 6s 15.5"
-      },
-      {
-        "args": [
-          "--platform",
           "iPhone 7",
           "--version",
           "15.5",
@@ -30602,6 +30546,62 @@
           "--platform",
           "iPhone X",
           "--version",
+          "15.5",
+          "--output-disabled-tests",
+          "--out-dir",
+          "${ISOLATED_OUTDIR}",
+          "--xcode-build-version",
+          "14b47b",
+          "--xctest",
+          "--xcode-parallelization"
+        ],
+        "isolate_name": "ios_chrome_signin_eg2tests_module",
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "ios_chrome_signin_eg2tests_module iPhone X 15.5",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/mac_toolchain/${platform}",
+              "location": ".",
+              "revision": "git_revision:723fc1a6c8cdf2631a57851f5610e598db0c1de1"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Mac-12"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "xcode_ios_14b47b",
+              "path": "Xcode.app"
+            },
+            {
+              "name": "runtime_ios_15_5",
+              "path": "Runtime-ios-15.5"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 6
+        },
+        "test_id_prefix": "ninja://ios/chrome/test/earl_grey2:ios_chrome_signin_eg2tests_module/",
+        "variant_id": "iPhone X 15.5"
+      },
+      {
+        "args": [
+          "--platform",
+          "iPhone X",
+          "--version",
           "16.1",
           "--output-disabled-tests",
           "--out-dir",
@@ -31320,62 +31320,6 @@
       {
         "args": [
           "--platform",
-          "iPhone 6s",
-          "--version",
-          "15.5",
-          "--output-disabled-tests",
-          "--out-dir",
-          "${ISOLATED_OUTDIR}",
-          "--xcode-build-version",
-          "14b47b",
-          "--xctest",
-          "--xcode-parallelization"
-        ],
-        "isolate_name": "ios_chrome_ui_eg2tests_module",
-        "isolate_profile_data": true,
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
-        },
-        "name": "ios_chrome_ui_eg2tests_module iPhone 6s 15.5",
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/mac_toolchain/${platform}",
-              "location": ".",
-              "revision": "git_revision:723fc1a6c8cdf2631a57851f5610e598db0c1de1"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Mac-12"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "xcode_ios_14b47b",
-              "path": "Xcode.app"
-            },
-            {
-              "name": "runtime_ios_15_5",
-              "path": "Runtime-ios-15.5"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
-          "shards": 12
-        },
-        "test_id_prefix": "ninja://ios/chrome/test/earl_grey2:ios_chrome_ui_eg2tests_module/",
-        "variant_id": "iPhone 6s 15.5"
-      },
-      {
-        "args": [
-          "--platform",
           "iPhone 7",
           "--version",
           "15.5",
@@ -31490,6 +31434,62 @@
           "--platform",
           "iPhone X",
           "--version",
+          "15.5",
+          "--output-disabled-tests",
+          "--out-dir",
+          "${ISOLATED_OUTDIR}",
+          "--xcode-build-version",
+          "14b47b",
+          "--xctest",
+          "--xcode-parallelization"
+        ],
+        "isolate_name": "ios_chrome_ui_eg2tests_module",
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "ios_chrome_ui_eg2tests_module iPhone X 15.5",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/mac_toolchain/${platform}",
+              "location": ".",
+              "revision": "git_revision:723fc1a6c8cdf2631a57851f5610e598db0c1de1"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Mac-12"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "xcode_ios_14b47b",
+              "path": "Xcode.app"
+            },
+            {
+              "name": "runtime_ios_15_5",
+              "path": "Runtime-ios-15.5"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 12
+        },
+        "test_id_prefix": "ninja://ios/chrome/test/earl_grey2:ios_chrome_ui_eg2tests_module/",
+        "variant_id": "iPhone X 15.5"
+      },
+      {
+        "args": [
+          "--platform",
+          "iPhone X",
+          "--version",
           "16.1",
           "--output-disabled-tests",
           "--out-dir",
@@ -61855,6 +61855,62 @@
       {
         "args": [
           "--platform",
+          "iPad Air (3rd generation)",
+          "--version",
+          "16.1",
+          "--out-dir",
+          "${ISOLATED_OUTDIR}",
+          "--xcode-build-version",
+          "14b47b",
+          "--xctest",
+          "--xcode-parallelization",
+          "--record-video",
+          "failed_only"
+        ],
+        "isolate_name": "ios_chrome_signin_eg2tests_module",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "ios_chrome_signin_eg2tests_module iPad Air (3rd generation) 16.1",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/mac_toolchain/${platform}",
+              "location": ".",
+              "revision": "git_revision:723fc1a6c8cdf2631a57851f5610e598db0c1de1"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Mac-12"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "xcode_ios_14b47b",
+              "path": "Xcode.app"
+            },
+            {
+              "name": "runtime_ios_16_1",
+              "path": "Runtime-ios-16.1"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 6
+        },
+        "test_id_prefix": "ninja://ios/chrome/test/earl_grey2:ios_chrome_signin_eg2tests_module/",
+        "variant_id": "iPad Air (3rd generation) 16.1"
+      },
+      {
+        "args": [
+          "--platform",
           "iPad Pro (12.9-inch) (3rd generation)",
           "--version",
           "16.1",
@@ -62354,6 +62410,62 @@
       {
         "args": [
           "--platform",
+          "iPad Air (3rd generation)",
+          "--version",
+          "16.1",
+          "--out-dir",
+          "${ISOLATED_OUTDIR}",
+          "--xcode-build-version",
+          "14b47b",
+          "--xctest",
+          "--xcode-parallelization",
+          "--record-video",
+          "failed_only"
+        ],
+        "isolate_name": "ios_chrome_ui_eg2tests_module",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "ios_chrome_ui_eg2tests_module iPad Air (3rd generation) 16.1",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/mac_toolchain/${platform}",
+              "location": ".",
+              "revision": "git_revision:723fc1a6c8cdf2631a57851f5610e598db0c1de1"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Mac-12"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "xcode_ios_14b47b",
+              "path": "Xcode.app"
+            },
+            {
+              "name": "runtime_ios_16_1",
+              "path": "Runtime-ios-16.1"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 12
+        },
+        "test_id_prefix": "ninja://ios/chrome/test/earl_grey2:ios_chrome_ui_eg2tests_module/",
+        "variant_id": "iPad Air (3rd generation) 16.1"
+      },
+      {
+        "args": [
+          "--platform",
           "iPad Pro (12.9-inch) (3rd generation)",
           "--version",
           "16.1",
@@ -92546,6 +92658,42 @@
       }
     ]
   },
+  "linux-wpt-content-shell-asan-fyi-rel": {
+    "isolated_scripts": [
+      {
+        "args": [
+          "--log-wptreport",
+          "--xvfb",
+          "--enable-sanitizer"
+        ],
+        "experiment_percentage": 100,
+        "isolate_name": "wpt_tests_isolate_content_shell",
+        "merge": {
+          "args": [
+            "--verbose"
+          ],
+          "script": "//third_party/blink/tools/merge_web_test_results.py"
+        },
+        "name": "wpt_tests_suite",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "results_handler": "layout tests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 8
+        },
+        "test_id_prefix": "ninja://:wpt_tests_isolate_content_shell/"
+      }
+    ]
+  },
   "linux-wpt-content-shell-fyi-rel": {
     "isolated_scripts": [
       {
diff --git a/testing/buildbot/chromium.mac.json b/testing/buildbot/chromium.mac.json
index 96eb101d..9a424bd 100644
--- a/testing/buildbot/chromium.mac.json
+++ b/testing/buildbot/chromium.mac.json
@@ -8599,6 +8599,9 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
+        "precommit_args": [
+          "--gtest_filter=-*"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
@@ -12123,118 +12126,6 @@
       {
         "args": [
           "--platform",
-          "iPhone 8",
-          "--version",
-          "16.1",
-          "--out-dir",
-          "${ISOLATED_OUTDIR}",
-          "--xcode-build-version",
-          "14b47b",
-          "--xctest",
-          "--xcode-parallelization"
-        ],
-        "isolate_name": "ios_chrome_signin_eg2tests_module",
-        "isolate_profile_data": true,
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
-        },
-        "name": "ios_chrome_signin_eg2tests_module iPhone 8 16.1",
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/mac_toolchain/${platform}",
-              "location": ".",
-              "revision": "git_revision:723fc1a6c8cdf2631a57851f5610e598db0c1de1"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "mac_model": "Macmini8,1",
-              "os": "Mac-12"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "xcode_ios_14b47b",
-              "path": "Xcode.app"
-            },
-            {
-              "name": "runtime_ios_16_1",
-              "path": "Runtime-ios-16.1"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
-          "shards": 6
-        },
-        "test_id_prefix": "ninja://ios/chrome/test/earl_grey2:ios_chrome_signin_eg2tests_module/",
-        "variant_id": "iPhone 8 16.1"
-      },
-      {
-        "args": [
-          "--platform",
-          "iPhone 8",
-          "--version",
-          "16.1",
-          "--out-dir",
-          "${ISOLATED_OUTDIR}",
-          "--xcode-build-version",
-          "14b47b",
-          "--xctest",
-          "--xcode-parallelization"
-        ],
-        "isolate_name": "ios_chrome_ui_eg2tests_module",
-        "isolate_profile_data": true,
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
-        },
-        "name": "ios_chrome_ui_eg2tests_module iPhone 8 16.1",
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/mac_toolchain/${platform}",
-              "location": ".",
-              "revision": "git_revision:723fc1a6c8cdf2631a57851f5610e598db0c1de1"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "mac_model": "Macmini8,1",
-              "os": "Mac-12"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "xcode_ios_14b47b",
-              "path": "Xcode.app"
-            },
-            {
-              "name": "runtime_ios_16_1",
-              "path": "Runtime-ios-16.1"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
-          "shards": 12
-        },
-        "test_id_prefix": "ninja://ios/chrome/test/earl_grey2:ios_chrome_ui_eg2tests_module/",
-        "variant_id": "iPhone 8 16.1"
-      },
-      {
-        "args": [
-          "--platform",
           "iPad Air (3rd generation)",
           "--version",
           "16.1",
@@ -17258,62 +17149,6 @@
       {
         "args": [
           "--platform",
-          "iPhone 6s",
-          "--version",
-          "15.5",
-          "--out-dir",
-          "${ISOLATED_OUTDIR}",
-          "--xcode-build-version",
-          "14b47b",
-          "--xctest",
-          "--xcode-parallelization"
-        ],
-        "isolate_name": "ios_chrome_signin_eg2tests_module",
-        "isolate_profile_data": true,
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
-        },
-        "name": "ios_chrome_signin_eg2tests_module iPhone 6s 15.5",
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/mac_toolchain/${platform}",
-              "location": ".",
-              "revision": "git_revision:723fc1a6c8cdf2631a57851f5610e598db0c1de1"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "mac_model": "Macmini8,1",
-              "os": "Mac-12"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "xcode_ios_14b47b",
-              "path": "Xcode.app"
-            },
-            {
-              "name": "runtime_ios_15_5",
-              "path": "Runtime-ios-15.5"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
-          "shards": 6
-        },
-        "test_id_prefix": "ninja://ios/chrome/test/earl_grey2:ios_chrome_signin_eg2tests_module/",
-        "variant_id": "iPhone 6s 15.5"
-      },
-      {
-        "args": [
-          "--platform",
           "iPhone 7",
           "--version",
           "15.5",
@@ -17426,6 +17261,118 @@
       {
         "args": [
           "--platform",
+          "iPhone X",
+          "--version",
+          "15.5",
+          "--out-dir",
+          "${ISOLATED_OUTDIR}",
+          "--xcode-build-version",
+          "14b47b",
+          "--xctest",
+          "--xcode-parallelization"
+        ],
+        "isolate_name": "ios_chrome_signin_eg2tests_module",
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "ios_chrome_signin_eg2tests_module iPhone X 15.5",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/mac_toolchain/${platform}",
+              "location": ".",
+              "revision": "git_revision:723fc1a6c8cdf2631a57851f5610e598db0c1de1"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "mac_model": "Macmini8,1",
+              "os": "Mac-12"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "xcode_ios_14b47b",
+              "path": "Xcode.app"
+            },
+            {
+              "name": "runtime_ios_15_5",
+              "path": "Runtime-ios-15.5"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 6
+        },
+        "test_id_prefix": "ninja://ios/chrome/test/earl_grey2:ios_chrome_signin_eg2tests_module/",
+        "variant_id": "iPhone X 15.5"
+      },
+      {
+        "args": [
+          "--platform",
+          "iPhone X",
+          "--version",
+          "16.1",
+          "--out-dir",
+          "${ISOLATED_OUTDIR}",
+          "--xcode-build-version",
+          "14b47b",
+          "--xctest",
+          "--xcode-parallelization"
+        ],
+        "isolate_name": "ios_chrome_signin_eg2tests_module",
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "ios_chrome_signin_eg2tests_module iPhone X 16.1",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/mac_toolchain/${platform}",
+              "location": ".",
+              "revision": "git_revision:723fc1a6c8cdf2631a57851f5610e598db0c1de1"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "mac_model": "Macmini8,1",
+              "os": "Mac-12"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "xcode_ios_14b47b",
+              "path": "Xcode.app"
+            },
+            {
+              "name": "runtime_ios_16_1",
+              "path": "Runtime-ios-16.1"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 6
+        },
+        "test_id_prefix": "ninja://ios/chrome/test/earl_grey2:ios_chrome_signin_eg2tests_module/",
+        "variant_id": "iPhone X 16.1"
+      },
+      {
+        "args": [
+          "--platform",
           "iPad Air (3rd generation)",
           "--version",
           "16.1",
@@ -18090,62 +18037,6 @@
       {
         "args": [
           "--platform",
-          "iPhone 6s",
-          "--version",
-          "15.5",
-          "--out-dir",
-          "${ISOLATED_OUTDIR}",
-          "--xcode-build-version",
-          "14b47b",
-          "--xctest",
-          "--xcode-parallelization"
-        ],
-        "isolate_name": "ios_chrome_ui_eg2tests_module",
-        "isolate_profile_data": true,
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
-        },
-        "name": "ios_chrome_ui_eg2tests_module iPhone 6s 15.5",
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/mac_toolchain/${platform}",
-              "location": ".",
-              "revision": "git_revision:723fc1a6c8cdf2631a57851f5610e598db0c1de1"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "mac_model": "Macmini8,1",
-              "os": "Mac-12"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "xcode_ios_14b47b",
-              "path": "Xcode.app"
-            },
-            {
-              "name": "runtime_ios_15_5",
-              "path": "Runtime-ios-15.5"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
-          "shards": 12
-        },
-        "test_id_prefix": "ninja://ios/chrome/test/earl_grey2:ios_chrome_ui_eg2tests_module/",
-        "variant_id": "iPhone 6s 15.5"
-      },
-      {
-        "args": [
-          "--platform",
           "iPhone 7",
           "--version",
           "15.5",
@@ -18258,6 +18149,118 @@
       {
         "args": [
           "--platform",
+          "iPhone X",
+          "--version",
+          "15.5",
+          "--out-dir",
+          "${ISOLATED_OUTDIR}",
+          "--xcode-build-version",
+          "14b47b",
+          "--xctest",
+          "--xcode-parallelization"
+        ],
+        "isolate_name": "ios_chrome_ui_eg2tests_module",
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "ios_chrome_ui_eg2tests_module iPhone X 15.5",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/mac_toolchain/${platform}",
+              "location": ".",
+              "revision": "git_revision:723fc1a6c8cdf2631a57851f5610e598db0c1de1"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "mac_model": "Macmini8,1",
+              "os": "Mac-12"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "xcode_ios_14b47b",
+              "path": "Xcode.app"
+            },
+            {
+              "name": "runtime_ios_15_5",
+              "path": "Runtime-ios-15.5"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 12
+        },
+        "test_id_prefix": "ninja://ios/chrome/test/earl_grey2:ios_chrome_ui_eg2tests_module/",
+        "variant_id": "iPhone X 15.5"
+      },
+      {
+        "args": [
+          "--platform",
+          "iPhone X",
+          "--version",
+          "16.1",
+          "--out-dir",
+          "${ISOLATED_OUTDIR}",
+          "--xcode-build-version",
+          "14b47b",
+          "--xctest",
+          "--xcode-parallelization"
+        ],
+        "isolate_name": "ios_chrome_ui_eg2tests_module",
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "ios_chrome_ui_eg2tests_module iPhone X 16.1",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/mac_toolchain/${platform}",
+              "location": ".",
+              "revision": "git_revision:723fc1a6c8cdf2631a57851f5610e598db0c1de1"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "mac_model": "Macmini8,1",
+              "os": "Mac-12"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "xcode_ios_14b47b",
+              "path": "Xcode.app"
+            },
+            {
+              "name": "runtime_ios_16_1",
+              "path": "Runtime-ios-16.1"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 12
+        },
+        "test_id_prefix": "ninja://ios/chrome/test/earl_grey2:ios_chrome_ui_eg2tests_module/",
+        "variant_id": "iPhone X 16.1"
+      },
+      {
+        "args": [
+          "--platform",
           "iPad Air 2",
           "--version",
           "15.5",
@@ -23625,62 +23628,6 @@
       {
         "args": [
           "--platform",
-          "iPhone 6s Plus",
-          "--version",
-          "14.5",
-          "--out-dir",
-          "${ISOLATED_OUTDIR}",
-          "--xcode-build-version",
-          "14b47b",
-          "--xctest",
-          "--xcode-parallelization",
-          "--record-video",
-          "failed_only"
-        ],
-        "isolate_name": "ios_chrome_signin_eg2tests_module",
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
-        },
-        "name": "ios_chrome_signin_eg2tests_module iPhone 6s Plus 14.5",
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/mac_toolchain/${platform}",
-              "location": ".",
-              "revision": "git_revision:723fc1a6c8cdf2631a57851f5610e598db0c1de1"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Mac-12"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "xcode_ios_14b47b",
-              "path": "Xcode.app"
-            },
-            {
-              "name": "runtime_ios_14_5",
-              "path": "Runtime-ios-14.5"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
-          "shards": 6
-        },
-        "test_id_prefix": "ninja://ios/chrome/test/earl_grey2:ios_chrome_signin_eg2tests_module/",
-        "variant_id": "iPhone 6s Plus 14.5"
-      },
-      {
-        "args": [
-          "--platform",
           "iPhone 7",
           "--version",
           "14.5",
@@ -23737,7 +23684,7 @@
       {
         "args": [
           "--platform",
-          "iPhone SE (1st generation)",
+          "iPhone X",
           "--version",
           "14.5",
           "--out-dir",
@@ -23754,7 +23701,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_chrome_signin_eg2tests_module iPhone SE (1st generation) 14.5",
+        "name": "ios_chrome_signin_eg2tests_module iPhone X 14.5",
         "resultdb": {
           "enable": true,
           "has_native_resultdb_integration": true
@@ -23788,7 +23735,7 @@
           "shards": 6
         },
         "test_id_prefix": "ninja://ios/chrome/test/earl_grey2:ios_chrome_signin_eg2tests_module/",
-        "variant_id": "iPhone SE (1st generation) 14.5"
+        "variant_id": "iPhone X 14.5"
       },
       {
         "args": [
@@ -24125,62 +24072,6 @@
       {
         "args": [
           "--platform",
-          "iPhone 6s Plus",
-          "--version",
-          "14.5",
-          "--out-dir",
-          "${ISOLATED_OUTDIR}",
-          "--xcode-build-version",
-          "14b47b",
-          "--xctest",
-          "--xcode-parallelization",
-          "--record-video",
-          "failed_only"
-        ],
-        "isolate_name": "ios_chrome_ui_eg2tests_module",
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
-        },
-        "name": "ios_chrome_ui_eg2tests_module iPhone 6s Plus 14.5",
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/mac_toolchain/${platform}",
-              "location": ".",
-              "revision": "git_revision:723fc1a6c8cdf2631a57851f5610e598db0c1de1"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Mac-12"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "xcode_ios_14b47b",
-              "path": "Xcode.app"
-            },
-            {
-              "name": "runtime_ios_14_5",
-              "path": "Runtime-ios-14.5"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
-          "shards": 12
-        },
-        "test_id_prefix": "ninja://ios/chrome/test/earl_grey2:ios_chrome_ui_eg2tests_module/",
-        "variant_id": "iPhone 6s Plus 14.5"
-      },
-      {
-        "args": [
-          "--platform",
           "iPhone 7",
           "--version",
           "14.5",
@@ -24237,7 +24128,7 @@
       {
         "args": [
           "--platform",
-          "iPhone SE (1st generation)",
+          "iPhone X",
           "--version",
           "14.5",
           "--out-dir",
@@ -24254,7 +24145,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "ios_chrome_ui_eg2tests_module iPhone SE (1st generation) 14.5",
+        "name": "ios_chrome_ui_eg2tests_module iPhone X 14.5",
         "resultdb": {
           "enable": true,
           "has_native_resultdb_integration": true
@@ -24288,7 +24179,7 @@
           "shards": 12
         },
         "test_id_prefix": "ninja://ios/chrome/test/earl_grey2:ios_chrome_ui_eg2tests_module/",
-        "variant_id": "iPhone SE (1st generation) 14.5"
+        "variant_id": "iPhone X 14.5"
       },
       {
         "args": [
diff --git a/testing/buildbot/chromium.perf.fyi.json b/testing/buildbot/chromium.perf.fyi.json
index 9a2cbec..f0ab13e 100644
--- a/testing/buildbot/chromium.perf.fyi.json
+++ b/testing/buildbot/chromium.perf.fyi.json
@@ -189,7 +189,8 @@
   "fuchsia-builder-perf-x64": {
     "additional_compile_targets": [
       "chrome_pkg",
-      "base_perftests"
+      "base_perftests",
+      "sync_performance_tests"
     ]
   },
   "fuchsia-perf-ast": {
diff --git a/testing/buildbot/gn_isolate_map.pyl b/testing/buildbot/gn_isolate_map.pyl
index 539eedf77..4334b9a 100644
--- a/testing/buildbot/gn_isolate_map.pyl
+++ b/testing/buildbot/gn_isolate_map.pyl
@@ -1672,6 +1672,19 @@
     "label": "//chrome/test:sync_integration_tests",
     "type": "windowed_test_launcher",
   },
+  "sync_performance_tests": {
+      "args": [
+          "sync_performance_tests",
+          "--non-telemetry=true",
+          "--test-launcher-print-test-stdio=always",
+          "--test-launcher-jobs=1",
+          "--test-launcher-retry-limit=0",
+      ],
+      "label": "//chrome/test:sync_performance_tests",
+      "skip_usage_check": True,  # Used by Pinpoint: crbug.com/1042778
+      "script": "//testing/scripts/run_performance_tests.py",
+      "type": "script",
+  },
   "system_webview_apk": {
     "label": "//android_webview:system_webview_apk",
     "type": "additional_compile_target",
diff --git a/testing/buildbot/test_suite_exceptions.pyl b/testing/buildbot/test_suite_exceptions.pyl
index 8a4ae8b..633419bb 100644
--- a/testing/buildbot/test_suite_exceptions.pyl
+++ b/testing/buildbot/test_suite_exceptions.pyl
@@ -3291,6 +3291,9 @@
         },
       },
       'Mac12 Tests': {
+        'precommit_args': [
+            '--gtest_filter=-*',
+        ],
         'swarming': {
           'shards': 4,
         },
@@ -4068,6 +4071,14 @@
   },
   'wpt_tests_suite': {
     'modifications': {
+      'linux-wpt-content-shell-asan-fyi-rel': {
+        'args': [
+          '--enable-sanitizer',
+        ],
+        'swarming': {
+          'shards': 8,
+        },
+      },
       'win10-wpt-content-shell-fyi-rel': {
         'args': [
           '--target',
diff --git a/testing/buildbot/test_suites.pyl b/testing/buildbot/test_suites.pyl
index d828c17..897813a 100644
--- a/testing/buildbot/test_suites.pyl
+++ b/testing/buildbot/test_suites.pyl
@@ -3839,16 +3839,6 @@
           'shards': 8,
         },
       },
-      'ios_chrome_signin_eg2tests_module': {
-      'swarming': {
-          'shards': 6,
-        },
-      },
-      'ios_chrome_ui_eg2tests_module': {
-        'swarming': {
-          'shards': 12,
-        },
-      },
       'ios_web_shell_eg2tests_module': {},
     },
 
@@ -3859,7 +3849,17 @@
           'shards': 3,
         },
       },
+      'ios_chrome_signin_eg2tests_module': {
+      'swarming': {
+          'shards': 6,
+        },
+      },
       'ios_chrome_smoke_eg2tests_module': {},
+      'ios_chrome_ui_eg2tests_module': {
+        'swarming': {
+          'shards': 12,
+        },
+      },
       'ios_chrome_web_eg2tests_module': {},
       'ios_showcase_eg2tests_module': {},
     },
diff --git a/testing/buildbot/waterfalls.pyl b/testing/buildbot/waterfalls.pyl
index a95547b..2c2323a10 100644
--- a/testing/buildbot/waterfalls.pyl
+++ b/testing/buildbot/waterfalls.pyl
@@ -3532,6 +3532,15 @@
         },
         'use_swarming': False,
       },
+      'linux-wpt-content-shell-asan-fyi-rel': {
+        'mixins': [
+          'has_native_resultdb_integration',
+          'linux-bionic',
+        ],
+        'test_suites': {
+          'isolated_scripts': 'wpt_web_tests_content_shell',
+        },
+      },
       'linux-wpt-content-shell-fyi-rel': {
         'mixins': [
           'has_native_resultdb_integration',
diff --git a/testing/scripts/run_performance_tests.py b/testing/scripts/run_performance_tests.py
index 2b09d2f..82af9d41 100755
--- a/testing/scripts/run_performance_tests.py
+++ b/testing/scripts/run_performance_tests.py
@@ -503,7 +503,8 @@
 
 
 def execute_telemetry_benchmark(
-    command_generator, output_paths, use_xvfb=False):
+    command_generator, output_paths, use_xvfb=False,
+    return_exit_code_zero=False):
   start = time.time()
 
   env = os.environ.copy()
@@ -567,6 +568,10 @@
           'this as a success.' % return_code)
     return 0
   if return_code:
+    if return_exit_code_zero:
+      print ('run_benchmark returned exit code ' + str(return_code)
+             + ' which indicates there were test failures in the run.')
+      return 0
     return return_code
   return 0
 
@@ -633,6 +638,12 @@
                       'replace the static shardmap file.',
                       type=str,
                       required=False)
+  parser.add_argument('--ignore-benchmark-exit-code',
+                      help='If set, return an exit code 0 even if there'
+                            + ' are benchmark failures',
+                      action='store_true',
+                      required=False
+                      )
   options, leftover_args = parser.parse_known_args(args)
   options.passthrough_args.extend(leftover_args)
   return options
@@ -703,7 +714,8 @@
             benchmark, options)
         print('\n### {folder} ###'.format(folder=benchmark))
         return_code = execute_telemetry_benchmark(
-            command_generator, output_paths, options.xvfb)
+            command_generator, output_paths, options.xvfb,
+            options.ignore_benchmark_exit_code)
         overall_return_code = return_code or overall_return_code
         test_results_files.append(output_paths.test_results)
       if options.run_ref_build:
@@ -773,7 +785,8 @@
           story_selection_config=story_selection_config)
       print('\n### {folder} ###'.format(folder=benchmark))
       return_code = execute_telemetry_benchmark(
-          command_generator, output_paths, options.xvfb)
+          command_generator, output_paths, options.xvfb,
+          options.ignore_benchmark_exit_code)
       overall_return_code = return_code or overall_return_code
       test_results_files.append(output_paths.test_results)
       if options.run_ref_build:
@@ -790,7 +803,7 @@
         # reference build.
         execute_telemetry_benchmark(
             reference_command_generator, reference_output_paths,
-            options.xvfb)
+            options.xvfb, options.ignore_benchmark_exit_code)
   if 'executables' in shard_configuration:
     names_and_configs = shard_configuration['executables']
     for (name, configuration) in names_and_configs.items():
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 45683764..0482393 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -7782,21 +7782,6 @@
             ]
         }
     ],
-    "OptimizeLayoutsForPullRefresh": [
-        {
-            "platforms": [
-                "android"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled",
-                    "enable_features": [
-                        "OptimizeLayoutsForPullRefresh"
-                    ]
-                }
-            ]
-        }
-    ],
     "OptimizeNetworkBuffers": [
         {
             "platforms": [
@@ -8373,21 +8358,6 @@
             ]
         }
     ],
-    "PasswordsGrouping": [
-        {
-            "platforms": [
-                "ios"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled",
-                    "enable_features": [
-                        "PasswordsGrouping"
-                    ]
-                }
-            ]
-        }
-    ],
     "Path2DPaintCache": [
         {
             "platforms": [
diff --git a/third_party/android_swipe_refresh/README.chromium b/third_party/android_swipe_refresh/README.chromium
index 1744524..e199415 100644
--- a/third_party/android_swipe_refresh/README.chromium
+++ b/third_party/android_swipe_refresh/README.chromium
@@ -28,5 +28,4 @@
   * All ViewCompat and MotionEventCompat dependencies removed.
   * Added OnResetListener interface to notify SwipeRefreshHandler to detach
     this view.
-  * Add a flag to minimze the number of z-order changes, thereby minimizing
-    relayouts of the UI.
+  * Optimization to minimize number of relayouts of the UI.
diff --git a/third_party/android_swipe_refresh/java/src/org/chromium/third_party/android/swiperefresh/SwipeRefreshLayout.java b/third_party/android_swipe_refresh/java/src/org/chromium/third_party/android/swiperefresh/SwipeRefreshLayout.java
index 09d92a7..02545ad 100644
--- a/third_party/android_swipe_refresh/java/src/org/chromium/third_party/android/swiperefresh/SwipeRefreshLayout.java
+++ b/third_party/android_swipe_refresh/java/src/org/chromium/third_party/android/swiperefresh/SwipeRefreshLayout.java
@@ -21,16 +21,12 @@
 import android.content.res.TypedArray;
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
-import android.util.Log;
-import android.view.MotionEvent;
 import android.view.View;
-import android.view.ViewConfiguration;
 import android.view.ViewGroup;
 import android.view.animation.Animation;
 import android.view.animation.Animation.AnimationListener;
 import android.view.animation.DecelerateInterpolator;
 import android.view.animation.Transformation;
-import android.widget.AbsListView;
 
 /**
  * The SwipeRefreshLayout should be used whenever the user can refresh the
@@ -136,12 +132,6 @@
 
     private boolean mNotify;
 
-    /**
-     * Flag used during duration of pull-refresh animation to reduce the number of calls to
-     * |bringToFront|, and therefore requested layouts. crbug/1335416
-     */
-    private boolean mOptimizeLayouts;
-
     private int mCircleWidth;
 
     private int mCircleHeight;
@@ -570,11 +560,10 @@
      * is currently active, the request will be ignored.
      * @return whether a new pull sequence has started.
      */
-    public boolean start(boolean optimizeLayouts) {
+    public boolean start() {
         if (!isEnabled()) return false;
         if (mRefreshing) return false;
         mCircleView.clearAnimation();
-        mOptimizeLayouts = optimizeLayouts;
         mProgress.stop();
         // See ACTION_DOWN handling in {@link #onTouchEvent(...)}.
         setTargetOffsetTopAndBottom(mOriginalOffsetTop - mCircleView.getTop(), true);
@@ -645,7 +634,7 @@
 
     @Override
     public void bringChildToFront(View child) {
-        if (mOptimizeLayouts && indexOfChild(child) == getChildCount() - 1) return;
+        if (indexOfChild(child) == getChildCount() - 1) return;
         super.bringChildToFront(child);
     }
 
diff --git a/third_party/blink/public/devtools_protocol/browser_protocol.pdl b/third_party/blink/public/devtools_protocol/browser_protocol.pdl
index 32c02cdd..feea463b 100644
--- a/third_party/blink/public/devtools_protocol/browser_protocol.pdl
+++ b/third_party/blink/public/devtools_protocol/browser_protocol.pdl
@@ -6408,6 +6408,7 @@
         ResourceExhausted
         AlreadyExists
         Unavailable
+        Unauthorized
         BadResponse
         InternalError
         UnknownError
diff --git a/third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom b/third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom
index b89eabe..57866250 100644
--- a/third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom
+++ b/third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom
@@ -3765,6 +3765,8 @@
   kDialogCloseWatcherCloseSignalClosedMultiple = 4424,
   kNoVarySearch = 4425,
   kFedCmUserInfo = 4426,
+  kIDNA2008DeviationCharacterInHostnameOfSubresource = 4427,
+  kIDNA2008DeviationCharacterInHostnameOfIFrame = 4428,
 
   // Add new features immediately above this line. Don't change assigned
   // numbers of any item, and don't reuse removed slots.
diff --git a/third_party/blink/public/web/DEPS b/third_party/blink/public/web/DEPS
index c17f725..2a47463 100644
--- a/third_party/blink/public/web/DEPS
+++ b/third_party/blink/public/web/DEPS
@@ -1,5 +1,6 @@
 include_rules = [
     "+base/containers/span.h",
+    "+base/containers/flat_map.h",
     "+base/files/file_path.h",
     "+base/i18n/rtl.h",
     "+base/observer_list_types.h",
diff --git a/third_party/blink/public/web/web_navigation_params.h b/third_party/blink/public/web/web_navigation_params.h
index 086a171..b124fa6 100644
--- a/third_party/blink/public/web/web_navigation_params.h
+++ b/third_party/blink/public/web/web_navigation_params.h
@@ -7,6 +7,7 @@
 
 #include <memory>
 
+#include "base/containers/flat_map.h"
 #include "base/containers/span.h"
 #include "base/time/time.h"
 #include "base/unguessable_token.h"
@@ -27,6 +28,7 @@
 #include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom-shared.h"
 #include "third_party/blink/public/mojom/frame/policy_container.mojom-forward.h"
 #include "third_party/blink/public/mojom/frame/triggering_event_info.mojom-shared.h"
+#include "third_party/blink/public/mojom/runtime_feature_state/runtime_feature_state.mojom-shared.h"
 #include "third_party/blink/public/platform/cross_variant_mojo_util.h"
 #include "third_party/blink/public/platform/web_common.h"
 #include "third_party/blink/public/platform/web_content_security_policy_struct.h"
@@ -514,6 +516,12 @@
   // components associated with the winning bid in an auction.
   absl::optional<FencedFrame::RedactedFencedFrameProperties>
       fenced_frame_properties;
+
+  // Maps the blink runtime-enabled features modified in the browser process to
+  // their new enabled/disabled status:
+  // <enum_representing_runtime_enabled_feature, enabled/disabled>
+  base::flat_map<::blink::mojom::RuntimeFeatureState, bool>
+      modified_runtime_features;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/build/scripts/templates/runtime_feature_state_override_context.cc.tmpl b/third_party/blink/renderer/build/scripts/templates/runtime_feature_state_override_context.cc.tmpl
index 8257d0e..1fc1ee15 100644
--- a/third_party/blink/renderer/build/scripts/templates/runtime_feature_state_override_context.cc.tmpl
+++ b/third_party/blink/renderer/build/scripts/templates/runtime_feature_state_override_context.cc.tmpl
@@ -6,10 +6,21 @@
 #include "third_party/blink/public/mojom/runtime_feature_state/runtime_feature_state.mojom-shared.h"
 #include "third_party/blink/renderer/platform/runtime_feature_state/runtime_feature_state_override_context.h"
 
+#include "base/process/process.h"
+
 namespace blink {
+
+void RuntimeFeatureStateOverrideContext::ApplyOverrideValuesFromParams(
+    const base::flat_map<mojom::RuntimeFeatureState, bool>&
+        values_from_params) {
+  for(const auto& override_value : values_from_params) {
+    override_values_[override_value.first] = override_value.second;
+  }
+}
+
 {% for feature in overridable_features %}
 bool RuntimeFeatureStateOverrideContext::
-    Is{{feature.name}}ForceDisabled() {
+    Is{{feature.name}}ForceDisabled() const {
   auto it = override_values_.find(
       blink::mojom::RuntimeFeatureState::k{{feature.name}});
   if (it != override_values_.end() && it->second == false)
@@ -19,7 +30,7 @@
 }
 
 bool RuntimeFeatureStateOverrideContext::
-    Is{{feature.name}}ForceEnabled() {
+    Is{{feature.name}}ForceEnabled() const {
   auto it = override_values_.find(
       blink::mojom::RuntimeFeatureState::k{{feature.name}});
   if(it != override_values_.end() && it->second == true)
diff --git a/third_party/blink/renderer/build/scripts/templates/runtime_feature_state_override_context.h.tmpl b/third_party/blink/renderer/build/scripts/templates/runtime_feature_state_override_context.h.tmpl
index 67a087a5..08089d3 100644
--- a/third_party/blink/renderer/build/scripts/templates/runtime_feature_state_override_context.h.tmpl
+++ b/third_party/blink/renderer/build/scripts/templates/runtime_feature_state_override_context.h.tmpl
@@ -24,16 +24,25 @@
     ApplyEnterprisePolicyOverrides();
   }
 
-  {% for feature in overridable_features %}
-  bool Is{{feature.name}}ForceDisabled();
+  void ApplyOverrideValuesFromParams(
+      const base::flat_map<mojom::RuntimeFeatureState, bool>&
+          values_from_params);
 
-  bool Is{{feature.name}}ForceEnabled();
+  {% for feature in overridable_features %}
+  bool Is{{feature.name}}ForceDisabled() const;
+
+  bool Is{{feature.name}}ForceEnabled() const;
 
   void Set{{feature.name}}ForceDisabled();
 
   void Set{{feature.name}}ForceEnabled();
 
   {% endfor %}
+
+  const base::flat_map<blink::mojom::RuntimeFeatureState, bool>& GetOverrideValuesForTesting() const {
+    return override_values_;
+  }
+
   void Trace(Visitor*) const {}
 
  private:
diff --git a/third_party/blink/renderer/core/display_lock/display_lock_context.cc b/third_party/blink/renderer/core/display_lock/display_lock_context.cc
index ccdcd3c..9d3fae9 100644
--- a/third_party/blink/renderer/core/display_lock/display_lock_context.cc
+++ b/third_party/blink/renderer/core/display_lock/display_lock_context.cc
@@ -556,11 +556,26 @@
 
 void DisplayLockContext::ScheduleStateChangeEventIfNeeded() {
   if (state_ == EContentVisibility::kAuto &&
-      RuntimeEnabledFeatures::ContentVisibilityAutoStateChangeEventEnabled()) {
-    element_->EnqueueEvent(
-        *ContentVisibilityAutoStateChangeEvent::Create(
-            event_type_names::kContentvisibilityautostatechange, is_locked_),
-        TaskType::kMiscPlatformAPI);
+      RuntimeEnabledFeatures::ContentVisibilityAutoStateChangeEventEnabled() &&
+      !state_change_task_pending_) {
+    document_->GetExecutionContext()
+        ->GetTaskRunner(TaskType::kMiscPlatformAPI)
+        ->PostTask(
+            FROM_HERE,
+            WTF::BindOnce(&DisplayLockContext::DispatchStateChangeEventIfNeeded,
+                          WrapPersistent(this)));
+    state_change_task_pending_ = true;
+  }
+}
+
+void DisplayLockContext::DispatchStateChangeEventIfNeeded() {
+  DCHECK(state_change_task_pending_);
+  state_change_task_pending_ = false;
+  if (!last_notified_skipped_state_ ||
+      *last_notified_skipped_state_ != is_locked_) {
+    last_notified_skipped_state_ = is_locked_;
+    element_->DispatchEvent(*ContentVisibilityAutoStateChangeEvent::Create(
+        event_type_names::kContentvisibilityautostatechange, is_locked_));
   }
 }
 
diff --git a/third_party/blink/renderer/core/display_lock/display_lock_context.h b/third_party/blink/renderer/core/display_lock/display_lock_context.h
index d9d4a3a..352b5d1 100644
--- a/third_party/blink/renderer/core/display_lock/display_lock_context.h
+++ b/third_party/blink/renderer/core/display_lock/display_lock_context.h
@@ -402,6 +402,7 @@
   bool SubtreeHasTopLayerElement() const;
 
   void ScheduleStateChangeEventIfNeeded();
+  void DispatchStateChangeEventIfNeeded();
 
   WeakMember<Element> element_;
   WeakMember<Document> document_;
@@ -558,9 +559,17 @@
   // the next frame.
   bool has_pending_clear_has_top_layer_ = false;
 
-  // If ture, we need to check if this subtree has any top layer elements at the
+  // If true, we need to check if this subtree has any top layer elements at the
   // start of the next frame.
   bool has_pending_top_layer_check_ = false;
+
+  // This is set to the last value for which ContentVisibilityAutoStateChange
+  // event has been dispatched (if any).
+  absl::optional<bool> last_notified_skipped_state_;
+
+  // If true, there is a pending task that will dispatch a state change event if
+  // needed.
+  bool state_change_task_pending_ = false;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/dom/document.cc b/third_party/blink/renderer/core/dom/document.cc
index 725ad63e..e1e32d4 100644
--- a/third_party/blink/renderer/core/dom/document.cc
+++ b/third_party/blink/renderer/core/dom/document.cc
@@ -198,6 +198,7 @@
 #include "third_party/blink/renderer/core/html/canvas/canvas_font_cache.h"
 #include "third_party/blink/renderer/core/html/canvas/canvas_rendering_context.h"
 #include "third_party/blink/renderer/core/html/canvas/html_canvas_element.h"
+#include "third_party/blink/renderer/core/html/collection_type.h"
 #include "third_party/blink/renderer/core/html/custom/custom_element.h"
 #include "third_party/blink/renderer/core/html/custom/custom_element_definition.h"
 #include "third_party/blink/renderer/core/html/custom/custom_element_descriptor.h"
@@ -7102,6 +7103,10 @@
       kDocumentAllNamedItems, name);
 }
 
+HTMLCollection* Document::PopoverInvokers() {
+  return EnsureCachedCollection<HTMLCollection>(kPopoverInvokers);
+}
+
 void Document::IncrementLazyAdsFrameCount() {
   data_->lazy_ads_frame_count_++;
 }
diff --git a/third_party/blink/renderer/core/dom/document.h b/third_party/blink/renderer/core/dom/document.h
index a020c70..88821f69 100644
--- a/third_party/blink/renderer/core/dom/document.h
+++ b/third_party/blink/renderer/core/dom/document.h
@@ -264,6 +264,7 @@
   kInvalidateForFormControls,
   kInvalidateOnHRefAttrChange,
   kInvalidateOnAnyAttrChange,
+  kInvalidateOnPopoverInvokerAttrChange,
 };
 const int kNumNodeListInvalidationTypes = kInvalidateOnAnyAttrChange + 1;
 
@@ -544,6 +545,7 @@
   HTMLCollection* WindowNamedItems(const AtomicString& name);
   DocumentNameCollection* DocumentNamedItems(const AtomicString& name);
   HTMLCollection* DocumentAllNamedItems(const AtomicString& name);
+  HTMLCollection* PopoverInvokers();
 
   // The unassociated listed elements are listed elements that are not
   // associated to a <form> element.
diff --git a/third_party/blink/renderer/core/dom/live_node_list_base.h b/third_party/blink/renderer/core/dom/live_node_list_base.h
index 8fdd370..5620418 100644
--- a/third_party/blink/renderer/core/dom/live_node_list_base.h
+++ b/third_party/blink/renderer/core/dom/live_node_list_base.h
@@ -130,6 +130,11 @@
              attr_name == html_names::kTypeAttr;
     case kInvalidateOnHRefAttrChange:
       return attr_name == html_names::kHrefAttr;
+    case kInvalidateOnPopoverInvokerAttrChange:
+      return attr_name == html_names::kPopoverAttr ||
+             attr_name == html_names::kPopovertoggletargetAttr ||
+             attr_name == html_names::kPopoverhidetargetAttr ||
+             attr_name == html_names::kPopovershowtargetAttr;
     case kDoNotInvalidateOnAttributeChanges:
       return false;
     case kInvalidateOnAnyAttrChange:
diff --git a/third_party/blink/renderer/core/frame/web_frame_test.cc b/third_party/blink/renderer/core/frame/web_frame_test.cc
index 912a9721..5c47e804 100644
--- a/third_party/blink/renderer/core/frame/web_frame_test.cc
+++ b/third_party/blink/renderer/core/frame/web_frame_test.cc
@@ -200,6 +200,7 @@
 #include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher_properties.h"
 #include "third_party/blink/renderer/platform/loader/fetch/resource_timing_info.h"
 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
+#include "third_party/blink/renderer/platform/runtime_feature_state/runtime_feature_state_override_context.h"
 #include "third_party/blink/renderer/platform/scheduler/public/event_loop.h"
 #include "third_party/blink/renderer/platform/scheduler/public/thread.h"
 #include "third_party/blink/renderer/platform/testing/find_cc_layer.h"
@@ -14436,4 +14437,44 @@
   script_request.Complete();
 }
 
+// Verify that modified_runtime_features is correctly set in the
+// RuntimeFeatureStateOverrideContext when a navigation is committed.
+TEST_F(WebFrameSimTest, SetModifiedFeaturesInOverrideContext) {
+  frame_test_helpers::WebViewHelper web_view_helper;
+  web_view_helper.Initialize();
+
+  WebLocalFrameImpl* frame = web_view_helper.LocalMainFrame();
+
+  auto params = std::make_unique<WebNavigationParams>();
+  // The url isn't important, just pick something.
+  params->url = url_test_helpers::ToKURL("http://www.example.com");
+
+  // Create a modified features value map and give it a value that we can check.
+  auto modified_features =
+      base::flat_map<::blink::mojom::RuntimeFeatureState, bool>();
+  modified_features[blink::mojom::RuntimeFeatureState::kTestFeature] = true;
+  params->modified_runtime_features = modified_features;
+
+  // Commit the navigation
+  frame->CommitNavigation(std::move(params), nullptr);
+
+  // Get the override context and compare the override values map with the
+  // modified features map.
+  RuntimeFeatureStateOverrideContext* override_context =
+      frame->GetFrame()->DomWindow()->GetRuntimeFeatureStateOverrideContext();
+  EXPECT_EQ(override_context->GetOverrideValuesForTesting(), modified_features);
+
+  // Do the same thing for a value of "false"
+  params = std::make_unique<WebNavigationParams>();
+  params->url = url_test_helpers::ToKURL("http://www.example2.com");
+  modified_features =
+      base::flat_map<::blink::mojom::RuntimeFeatureState, bool>();
+  modified_features[blink::mojom::RuntimeFeatureState::kTestFeature] = false;
+  params->modified_runtime_features = modified_features;
+  frame->CommitNavigation(std::move(params), nullptr);
+  override_context =
+      frame->GetFrame()->DomWindow()->GetRuntimeFeatureStateOverrideContext();
+  EXPECT_EQ(override_context->GetOverrideValuesForTesting(), modified_features);
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/html/collection_type.h b/third_party/blink/renderer/core/html/collection_type.h
index 97468e4e..3ed2818d 100644
--- a/third_party/blink/renderer/core/html/collection_type.h
+++ b/third_party/blink/renderer/core/html/collection_type.h
@@ -48,6 +48,7 @@
   kDataListOptions,
   kMapAreas,
   kFormControls,
+  kPopoverInvokers,
 
   // Named HTMLCollection types cached in the document.
   kWindowNamedItems,
diff --git a/third_party/blink/renderer/core/html/forms/html_form_control_element.cc b/third_party/blink/renderer/core/html/forms/html_form_control_element.cc
index cee7313..f47f8d6 100644
--- a/third_party/blink/renderer/core/html/forms/html_form_control_element.cc
+++ b/third_party/blink/renderer/core/html/forms/html_form_control_element.cc
@@ -332,7 +332,7 @@
 //     values match, the behavior is to toggle.
 HTMLFormControlElement::PopoverTargetElement
 HTMLFormControlElement::popoverTargetElement() const {
-  const PopoverTargetElement no_element{.element = nullptr,
+  const PopoverTargetElement no_element{.popover = nullptr,
                                         .action = PopoverTriggerAction::kNone,
                                         .attribute_name = g_null_name};
   if (!RuntimeEnabledFeatures::HTMLPopoverAttributeEnabled(
@@ -368,7 +368,7 @@
       DynamicTo<HTMLElement>(GetTreeScope().getElementById(idref));
   if (!popover_element || !popover_element->HasPopoverAttribute())
     return no_element;
-  return PopoverTargetElement{.element = popover_element,
+  return PopoverTargetElement{.popover = popover_element,
                               .action = action,
                               .attribute_name = attribute_name};
 }
@@ -376,7 +376,7 @@
 void HTMLFormControlElement::DefaultEventHandler(Event& event) {
   if (!IsDisabledFormControl()) {
     auto popover = popoverTargetElement();
-    if (popover.element) {
+    if (popover.popover) {
       auto trigger_support = SupportsPopoverTriggering();
       DCHECK_NE(popover.action, PopoverTriggerAction::kNone);
       DCHECK_NE(trigger_support, PopoverTriggerSupport::kNone);
@@ -392,21 +392,21 @@
       // popover and set focus to the previously focused element, then the
       // normal focus management code will reset focus to the clicked control.
       bool can_show =
-          popover.element->IsPopoverReady(PopoverTriggerAction::kShow) &&
+          popover.popover->IsPopoverReady(PopoverTriggerAction::kShow) &&
           (popover.action == PopoverTriggerAction::kToggle ||
            popover.action == PopoverTriggerAction::kShow);
       bool can_hide =
-          popover.element->IsPopoverReady(PopoverTriggerAction::kHide) &&
+          popover.popover->IsPopoverReady(PopoverTriggerAction::kHide) &&
           (popover.action == PopoverTriggerAction::kToggle ||
            popover.action == PopoverTriggerAction::kHide);
       if (event.type() == event_type_names::kDOMActivate &&
           (!Form() || !IsSuccessfulSubmitButton())) {
         if (can_hide) {
-          popover.element->HidePopoverInternal(
+          popover.popover->HidePopoverInternal(
               HidePopoverFocusBehavior::kFocusPreviousElement,
               HidePopoverForcingLevel::kHideAfterAnimations);
         } else if (can_show) {
-          popover.element->InvokePopover(this);
+          popover.popover->InvokePopover(this);
         }
       }
     }
diff --git a/third_party/blink/renderer/core/html/forms/html_form_control_element.h b/third_party/blink/renderer/core/html/forms/html_form_control_element.h
index c2243b1..021d9836 100644
--- a/third_party/blink/renderer/core/html/forms/html_form_control_element.h
+++ b/third_party/blink/renderer/core/html/forms/html_form_control_element.h
@@ -91,10 +91,10 @@
   struct PopoverTargetElement final {
    public:
     DISALLOW_NEW();
-    WeakMember<HTMLElement> element;
+    WeakMember<HTMLElement> popover;
     PopoverTriggerAction action;
     QualifiedName attribute_name;
-    void Trace(Visitor* visitor) const { visitor->Trace(element); }
+    void Trace(Visitor* visitor) const { visitor->Trace(popover); }
   };
 
   enum class PopoverTriggerSupport {
diff --git a/third_party/blink/renderer/core/html/html_collection.cc b/third_party/blink/renderer/core/html/html_collection.cc
index edfdfe69..31852f5f 100644
--- a/third_party/blink/renderer/core/html/html_collection.cc
+++ b/third_party/blink/renderer/core/html/html_collection.cc
@@ -26,6 +26,7 @@
 #include "third_party/blink/renderer/core/dom/class_collection.h"
 #include "third_party/blink/renderer/core/dom/element_traversal.h"
 #include "third_party/blink/renderer/core/dom/node_rare_data.h"
+#include "third_party/blink/renderer/core/html/collection_type.h"
 #include "third_party/blink/renderer/core/html/document_all_name_collection.h"
 #include "third_party/blink/renderer/core/html/document_name_collection.h"
 #include "third_party/blink/renderer/core/html/forms/html_data_list_options_collection.h"
@@ -37,6 +38,7 @@
 #include "third_party/blink/renderer/core/html/html_tag_collection.h"
 #include "third_party/blink/renderer/core/html/window_name_collection.h"
 #include "third_party/blink/renderer/core/html_names.h"
+#include "third_party/blink/renderer/platform/wtf/casting.h"
 #include "third_party/blink/renderer/platform/wtf/hash_set.h"
 
 namespace blink {
@@ -64,6 +66,7 @@
     case kDataListOptions:
     case kWindowNamedItems:
     case kFormControls:
+    case kPopoverInvokers:
       return false;
     case kNodeChildren:
     case kTRCells:
@@ -108,6 +111,7 @@
     case kSelectedOptions:
     case kDataListOptions:
     case kMapAreas:
+    case kPopoverInvokers:
       return NodeListSearchRoot::kOwnerNode;
     case kFormControls:
       if (IsA<HTMLFieldSetElement>(owner))
@@ -162,6 +166,8 @@
       return kInvalidateForFormControls;
     case kClassCollectionType:
       return kInvalidateOnClassAttrChange;
+    case kPopoverInvokers:
+      return kInvalidateOnPopoverInvokerAttrChange;
     case kNameNodeListType:
     case kRadioNodeListType:
     case kRadioImgNodeListType:
@@ -254,6 +260,11 @@
       return IsA<HTMLObjectElement>(element) ||
              IsA<HTMLFormControlElement>(element) ||
              element.IsFormAssociatedCustomElement();
+    case kPopoverInvokers:
+      if (auto* invoker = DynamicTo<HTMLFormControlElement>(element)) {
+        return invoker->popoverTargetElement().popover;
+      }
+      return false;
     case kClassCollectionType:
     case kTagCollectionType:
     case kTagCollectionNSType:
diff --git a/third_party/blink/renderer/core/html/html_element.cc b/third_party/blink/renderer/core/html/html_element.cc
index 0d4a310..30364041 100644
--- a/third_party/blink/renderer/core/html/html_element.cc
+++ b/third_party/blink/renderer/core/html/html_element.cc
@@ -69,6 +69,7 @@
 #include "third_party/blink/renderer/core/html/custom/custom_element.h"
 #include "third_party/blink/renderer/core/html/custom/custom_element_registry.h"
 #include "third_party/blink/renderer/core/html/custom/element_internals.h"
+#include "third_party/blink/renderer/core/html/forms/html_form_control_element.h"
 #include "third_party/blink/renderer/core/html/forms/html_form_element.h"
 #include "third_party/blink/renderer/core/html/forms/html_input_element.h"
 #include "third_party/blink/renderer/core/html/forms/labels_node_list.h"
@@ -1350,10 +1351,8 @@
   auto original_type = PopoverType();
   if (original_type == PopoverValueType::kAuto) {
     // If the new popover is a popover=auto, hide any popover above this in the
-    // stack. Because this popover isn't yet in the stack, we call
-    // NearestOpenAncestralPopover to find this popover's ancestor, if any.
-    const auto* auto_ancestor =
-        NearestOpenAncestralPopover(*this, PopoverAncestorType::kNewPopover);
+    // stack, if any.
+    const auto* auto_ancestor = FindTopmostPopoverAncestor(*this);
     HideAllPopoversUntil(auto_ancestor, document,
                          HidePopoverFocusBehavior::kNone,
                          HidePopoverForcingLevel::kHideAfterAnimations);
@@ -1628,145 +1627,140 @@
   doc.TopDocument().FinalizeAutofocus();
 }
 
-using PopoverPositionMap = HeapHashMap<Member<const Element>, int>;
-using PopoverAnchorMap =
-    HeapHashMap<Member<const Element>, Member<const Element>>;
-using PopoverSeenSet = HashSet<Member<const Node>>;
-
 namespace {
-const HTMLElement* NearestOpenAncestralPopoverRecursive(
-    const Node* node,
-    const PopoverPositionMap& popover_positions,
-    const PopoverAnchorMap& anchors_to_popovers,
-    int upper_bound,
-    PopoverSeenSet& seen) {
-  if (!node || seen.Contains(node))
-    return nullptr;
-  seen.insert(node);
 
-  const HTMLElement* ancestor = nullptr;
-  int position = -1;
-  auto update = [&ancestor, &position, &popover_positions,
-                 upper_bound](const HTMLElement* popover) {
-    DCHECK(popover);
-    if (popover->popoverOpen() &&
-        popover->PopoverType() != PopoverValueType::kManual) {
-      DCHECK(popover_positions.Contains(popover));
-      int new_position = popover_positions.at(popover);
-      if (new_position > position && new_position < upper_bound) {
-        ancestor = popover;
-        position = new_position;
-      }
-    }
-  };
-  auto recurse_and_update = [&update, &popover_positions, upper_bound,
-                             &anchors_to_popovers, &seen](const Node* node) {
-    if (auto* popover = NearestOpenAncestralPopoverRecursive(
-            node, popover_positions, anchors_to_popovers, upper_bound, seen))
-      update(popover);
-  };
-
-  if (auto* element = DynamicTo<HTMLElement>(node)) {
-    // Update for this element.
-    update(element);
-    // Recursively look up the tree from this element's anchors and invokers.
-    if (popover_positions.Contains(element)) {
-      recurse_and_update(element->anchorElement());
-      recurse_and_update(element->GetPopoverData()->invoker());
-    }
-    // Include invokers that weren't used to invoke the popover. This is
-    // necessary to catch invoking elements that should not light dismiss a
-    // popover, even if they weren't used to show it.
-    if (auto* form_control = DynamicTo<HTMLFormControlElement>(element)) {
-      recurse_and_update(form_control->popoverTargetElement().element);
-    }
-    // Include the anchor elements for all showing popovers.
-    if (anchors_to_popovers.Contains(element)) {
-      recurse_and_update(anchors_to_popovers.at(element));
-    }
+template <typename UnaryPredicate>
+const HTMLElement* NearestInclusiveMatchingAncestor(const Node* node,
+                                                    UnaryPredicate predicate) {
+  for (; node; node = FlatTreeTraversal::Parent(*node)) {
+    if (auto* value = predicate(node))
+      return value;
   }
-  // Also walk up the flat tree from this node.
-  recurse_and_update(FlatTreeTraversal::Parent(*node));
-
-  return ancestor;
+  return nullptr;
 }
+
+const HTMLElement* NearestInclusiveOpenPopover(const Node* node) {
+  return NearestInclusiveMatchingAncestor(node, [](const Node* test_node) {
+    auto* popover = DynamicTo<HTMLElement>(test_node);
+    return (popover && popover->popoverOpen() &&
+            popover->PopoverType() != PopoverValueType::kManual)
+               ? popover
+               : nullptr;
+  });
+}
+
+const HTMLElement* NearestInclusiveTargetPopoverForInvoker(const Node* node) {
+  return NearestInclusiveMatchingAncestor(node, [](const Node* test_node) {
+    auto* form_element = DynamicTo<HTMLFormControlElement>(test_node);
+    auto target_popover =
+        form_element ? form_element->popoverTargetElement().popover : nullptr;
+    return (target_popover && target_popover->popoverOpen() &&
+            target_popover->PopoverType() != PopoverValueType::kManual)
+               ? target_popover.Get()
+               : nullptr;
+  });
+}
+
 }  // namespace
 
 // static
-// This function will return the popover that is highest in the popover stack
-// that is an ancestral popover of the provided node. Popover ancestors are
-// created by DOM flat tree parents, or through either anchor or invoker
-// relationships. Anchor relationships are formed by the anchor attribute on a
-// popover, pointing to another node in the tree. Invoker relationships are
-// formed by invoking elements, which are HTMLFormControlElements having
-// popovertoggletarget, popovershowtarget, or popoverhidetarget attributes
-// pointing to a popover element. There can be multiple popovers that point to a
-// single anchor element, and there can be multiple invoking elements for a
-// single popover. Additionally, an anchor for one popover can be an invoker for
-// a different popover. For these reasons, this function needs to do a recursive
-// tree walk up from the provided node, plus all associated anchors and
-// invokers, returning the highest (on the stack) popover that is found. If the
-// inclusive parameter is true, the highest popover found during the tree-walk
-// is included in the search. If it is false, the |node| parameter must be a
-// popover, and the highest popover *below* that starting pop- up will be
-// returned.
-const HTMLElement* HTMLElement::NearestOpenAncestralPopover(
-    const Node& node,
-    PopoverAncestorType ancestor_type) {
+// This function will return the topmost (highest in the popover stack)
+// ancestral popover for the provided popover. Popovers can be related to each
+// other in several ways, creating a tree of popovers. There are three paths
+// through which one popover (call it the "child" popover) can have an ancestor
+// popover (call it the "parent" popover):
+//  1. the popovers are nested within each other in the DOM tree. In this case,
+//     the descendant popover is the "child" and its ancestor popover is the
+//     "parent".
+//  2. a popover has an `anchor` attribute pointing to another element in the
+//     DOM. In this case, the popover is the "child", and the DOM-contained
+//     popover of its anchor element is the "parent". If the anchor doesn't
+//     point to an element, or that element isn't contained within a popover, no
+//     such relationship exists.
+//  3. an invoking element (e.g. a <button>) has one of the invoking attributes
+//     (e.g. popovertoggletarget) pointing to a popover. In this case, the
+//     popover is the "child", and the DOM-contained popover of the invoking
+//     element is the "parent". As with anchor, the invoker must be in a popover
+//     and reference an open popover.
+// In each of the relationships formed above, the parent popover must be
+// strictly lower in the popover stack than the child popover, or it does not
+// form a valid ancestral relationship. This eliminates non-showing popovers and
+// self-pointers (e.g. a popover with an anchor attribute that points back to
+// the same popover), and it allows for the construction of a well-formed tree
+// from the (possibly cyclic) graph of connections. For example, if two popovers
+// have anchors pointing to each other, the only valid relationship is that the
+// first one to open is the "parent" and the second is the "child". Only
+// popover=auto popovers are considered.
+const HTMLElement* HTMLElement::FindTopmostPopoverAncestor(
+    const HTMLElement& new_popover) {
+  DCHECK(new_popover.HasPopoverAttribute() && !new_popover.popoverOpen());
+  auto& document = new_popover.GetDocument();
   DCHECK(RuntimeEnabledFeatures::HTMLPopoverAttributeEnabled(
-      node.GetDocument().GetExecutionContext()));
-  // popover_positions is a map from all showing (or about-to-show) popovers to
-  // their position in the popover stack.
-  PopoverPositionMap popover_positions;
-  // anchors_to_popovers is a map from the anchor elements of all showing
-  // popovers back to the popover itself.
-  PopoverAnchorMap anchors_to_popovers;
+      document.GetExecutionContext()));
+
+  // Build a map from each open popover to its position in the stack.
+  HeapHashMap<Member<const HTMLElement>, int> popover_positions;
   int indx = 0;
-  for (auto popover : node.GetDocument().PopoverStack()) {
+  for (auto popover : document.PopoverStack()) {
     popover_positions.Set(popover, indx++);
-    if (popover->anchorElement())
-      anchors_to_popovers.Set(popover->anchorElement(), popover);
   }
-  auto* element = DynamicTo<HTMLElement>(node);
-  if (ancestor_type == PopoverAncestorType::kNewPopover) {
-    DCHECK(element && element->HasPopoverAttribute() &&
-           !element->popoverOpen());
-    popover_positions.Set(element, indx++);
-  }
-  // upper_bound is one above the maximum popover stack height to accept. It is
-  // typically the position of the provided element.
-  int upper_bound = popover_positions.Contains(element)
-                        ? popover_positions.at(element)
-                        : INT_MAX;
-  if (ancestor_type == PopoverAncestorType::kInclusive) {
-    // For inclusive mode, we need to walk up the tree until we find an open
-    // popover, or an invoker for an open popover, and then modify the upper
-    // bound to include the highest such popover found, if any.
-    for (const Node* current_node = &node; current_node;
-         current_node = FlatTreeTraversal::Parent(*current_node)) {
-      if (auto* current_element = DynamicTo<HTMLElement>(current_node);
-          current_element && current_element->HasPopoverAttribute() &&
-          current_element->popoverOpen() &&
-          current_element->PopoverType() != PopoverValueType::kManual) {
-        upper_bound =
-            std::max(upper_bound, popover_positions.at(current_element) + 1);
-      }
-      if (auto* form_control =
-              DynamicTo<HTMLFormControlElement>(current_node)) {
-        if (auto target_popover = form_control->popoverTargetElement().element;
-            target_popover && target_popover->popoverOpen() &&
-            target_popover->PopoverType() != PopoverValueType::kManual) {
-          upper_bound =
-              std::max(upper_bound, popover_positions.at(target_popover) + 1);
-        }
-      }
+  popover_positions.Set(&new_popover, indx++);
+
+  const HTMLElement* topmost_popover_ancestor = nullptr;
+  auto check_ancestor = [&topmost_popover_ancestor,
+                         &popover_positions](const Element* candidate) {
+    if (!candidate)
+      return;
+    auto* candidate_ancestor = NearestInclusiveOpenPopover(candidate);
+    if (!candidate_ancestor)
+      return;
+    int candidate_position = popover_positions.at(candidate_ancestor);
+    if (!topmost_popover_ancestor ||
+        popover_positions.at(topmost_popover_ancestor) < candidate_position) {
+      topmost_popover_ancestor = candidate_ancestor;
     }
+  };
+  // Add the three types of ancestor relationships to the map:
+  // 1. DOM tree ancestor.
+  check_ancestor(NearestInclusiveOpenPopover(
+      FlatTreeTraversal::ParentElement(new_popover)));
+  // 2. Anchor attribute.
+  check_ancestor(new_popover.anchorElement());
+  // 3. Invoker to popover (need to consider all of them).
+  for (auto* invoker : *document.PopoverInvokers()) {
+    DCHECK(IsA<HTMLFormControlElement>(invoker));
+    auto* popover = To<HTMLFormControlElement>(invoker)
+                        ->popoverTargetElement()
+                        .popover.Get();
+    if (popover == &new_popover)
+      check_ancestor(invoker);
   }
-  PopoverSeenSet seen;
-  return NearestOpenAncestralPopoverRecursive(
-      &node, popover_positions, anchors_to_popovers, upper_bound, seen);
+  return topmost_popover_ancestor;
 }
 
+namespace {
+// For light dismiss, we need to find the closest popover that the user has
+// clicked. That is the nearest DOM ancestor that is either a popover or the
+// invoking element for a popover. It is possible both exist, in which case
+// the topmost one (highest on the popover stack) is returned.
+const HTMLElement* FindTopmostClickedPopover(const Node& node) {
+  auto& document = node.GetDocument();
+  DCHECK(RuntimeEnabledFeatures::HTMLPopoverAttributeEnabled(
+      document.GetExecutionContext()));
+  // Check if we're in an invoking element or a popover, and choose
+  // the higher popover on the stack.
+  auto* clicked_popover = NearestInclusiveOpenPopover(&node);
+  auto* invoker_popover = NearestInclusiveTargetPopoverForInvoker(&node);
+  auto get_stack_position = [&document](const HTMLElement* popover) {
+    auto pos = document.PopoverStack().Find(popover);
+    return pos == kNotFound ? 0 : (pos + 1);
+  };
+  if (get_stack_position(clicked_popover) > get_stack_position(invoker_popover))
+    return clicked_popover;
+  return invoker_popover;
+}
+}  // namespace
+
 // static
 void HTMLElement::HandlePopoverLightDismiss(const Event& event,
                                             const Node& target_node) {
@@ -1786,8 +1780,8 @@
     DCHECK_EQ(Event::PhaseType::kNone, event.eventPhase());
 
     if (event_type == event_type_names::kPointerdown) {
-      document.SetPopoverPointerdownTarget(NearestOpenAncestralPopover(
-          target_node, PopoverAncestorType::kInclusive));
+      document.SetPopoverPointerdownTarget(
+          FindTopmostClickedPopover(target_node));
     } else if (event_type == event_type_names::kPointerup) {
       // Hide everything up to the clicked element. We do this on pointerup,
       // rather than pointerdown or click, primarily for accessibility concerns.
@@ -1799,8 +1793,7 @@
       // a pointer-drag on a popover, and finishes off the popover (to highlight
       // text), the ancestral popover is stored in pointerdown and compared
       // here.
-      auto* ancestor_popover = NearestOpenAncestralPopover(
-          target_node, PopoverAncestorType::kInclusive);
+      auto* ancestor_popover = FindTopmostClickedPopover(target_node);
       bool same_target =
           ancestor_popover == document.PopoverPointerdownTarget();
       document.SetPopoverPointerdownTarget(nullptr);
diff --git a/third_party/blink/renderer/core/html/html_element.h b/third_party/blink/renderer/core/html/html_element.h
index 7593fa0..9f6781e8 100644
--- a/third_party/blink/renderer/core/html/html_element.h
+++ b/third_party/blink/renderer/core/html/html_element.h
@@ -84,12 +84,6 @@
   kHideImmediately,
 };
 
-enum class PopoverAncestorType {
-  kDefault,
-  kNewPopover,
-  kInclusive,
-};
-
 class CORE_EXPORT HTMLElement : public Element {
   DEFINE_WRAPPERTYPEINFO();
 
@@ -229,8 +223,7 @@
   void HidePopoverInternal(HidePopoverFocusBehavior focus_behavior,
                            HidePopoverForcingLevel forcing_level);
   void PopoverHideFinishIfNeeded();
-  static const HTMLElement* NearestOpenAncestralPopover(const Node&,
-                                                        PopoverAncestorType);
+  static const HTMLElement* FindTopmostPopoverAncestor(const HTMLElement&);
 
   // Retrieves the element pointed to by this element's 'anchor' content
   // attribute, if that element exists, and if this element is a popover.
diff --git a/third_party/blink/renderer/core/layout/layout_shift_tracker.cc b/third_party/blink/renderer/core/layout/layout_shift_tracker.cc
index 40e5526..ce984a6 100644
--- a/third_party/blink/renderer/core/layout/layout_shift_tracker.cc
+++ b/third_party/blink/renderer/core/layout/layout_shift_tracker.cc
@@ -592,12 +592,12 @@
   DCHECK(performance);
 
   double input_timestamp = LastInputTimestamp();
-  LayoutShift* entry = LayoutShift::Create(
-      performance->now(), score_delta, had_recent_input, input_timestamp,
-      CreateAttributionList(),
-      PerformanceEntry::GetNavigationId(window));  // Add WPT for
-                                                   //  LayoutShift. See
-                                                   //  crbug.com/1320878.
+  LayoutShift* entry =
+      LayoutShift::Create(performance->now(), score_delta, had_recent_input,
+                          input_timestamp, CreateAttributionList(),
+                          PerformanceEntry::GetNavigationId(window), window);
+
+  // Add WPT for LayoutShift. See crbug.com/1320878.
 
   performance->AddLayoutShiftEntry(entry);
 }
diff --git a/third_party/blink/renderer/core/layout/ng/ng_absolute_utils.cc b/third_party/blink/renderer/core/layout/ng/ng_absolute_utils.cc
index cf115695..c9154c81 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_absolute_utils.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_absolute_utils.cc
@@ -325,15 +325,24 @@
     const NGConstraintSpace& space,
     const NGLogicalOutOfFlowInsets& insets,
     const NGLogicalStaticPosition& static_position) {
+  return ComputeOutOfFlowAvailableRect(node, space.AvailableSize(), insets,
+                                       static_position);
+}
+
+LogicalRect ComputeOutOfFlowAvailableRect(
+    const NGBlockNode& node,
+    const LogicalSize& available_size,
+    const NGLogicalOutOfFlowInsets& insets,
+    const NGLogicalStaticPosition& static_position) {
   const bool is_table = node.IsTable();
   LayoutUnit inline_offset, inline_size;
   std::tie(inline_offset, inline_size) = ComputeAvailableSpaceInOneAxis(
-      space.AvailableSize().inline_size, insets.inline_start, insets.inline_end,
+      available_size.inline_size, insets.inline_start, insets.inline_end,
       static_position.offset.inline_offset,
       GetStaticPositionEdge(static_position.inline_edge), is_table);
   LayoutUnit block_offset, block_size;
   std::tie(block_offset, block_size) = ComputeAvailableSpaceInOneAxis(
-      space.AvailableSize().block_size, insets.block_start, insets.block_end,
+      available_size.block_size, insets.block_start, insets.block_end,
       static_position.offset.block_offset,
       GetStaticPositionEdge(static_position.block_edge), is_table);
   return LogicalRect(inline_offset, block_offset, inline_size, block_size);
diff --git a/third_party/blink/renderer/core/layout/ng/ng_absolute_utils.h b/third_party/blink/renderer/core/layout/ng/ng_absolute_utils.h
index b6ab2fb..4d479cc 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_absolute_utils.h
+++ b/third_party/blink/renderer/core/layout/ng/ng_absolute_utils.h
@@ -64,6 +64,11 @@
                               const NGConstraintSpace&,
                               const NGLogicalOutOfFlowInsets&,
                               const NGLogicalStaticPosition&);
+CORE_EXPORT LogicalRect
+ComputeOutOfFlowAvailableRect(const NGBlockNode&,
+                              const LogicalSize& available_size,
+                              const NGLogicalOutOfFlowInsets&,
+                              const NGLogicalStaticPosition&);
 
 // The following routines implement the absolute size resolution algorithm.
 // https://www.w3.org/TR/css-position-3/#abs-non-replaced-width
diff --git a/third_party/blink/renderer/core/loader/DEPS b/third_party/blink/renderer/core/loader/DEPS
index 904d7124..d9556285 100644
--- a/third_party/blink/renderer/core/loader/DEPS
+++ b/third_party/blink/renderer/core/loader/DEPS
@@ -20,5 +20,7 @@
   "+base/containers/flat_map.h",
   "+base/features.h",
   "+base/no_destructor.h",
+  "+base/strings/utf_string_conversions.h",
   "+services/network/public/cpp/client_hints.h",
+  "+url/url_features.h",
 ]
diff --git a/third_party/blink/renderer/core/loader/base_fetch_context.cc b/third_party/blink/renderer/core/loader/base_fetch_context.cc
index 17e1dcf..b62b4e1 100644
--- a/third_party/blink/renderer/core/loader/base_fetch_context.cc
+++ b/third_party/blink/renderer/core/loader/base_fetch_context.cc
@@ -20,6 +20,7 @@
 #include "third_party/blink/renderer/core/inspector/console_message.h"
 #include "third_party/blink/renderer/core/inspector/identifiers_factory.h"
 #include "third_party/blink/renderer/core/loader/frame_client_hints_preferences_context.h"
+#include "third_party/blink/renderer/core/loader/idna_util.h"
 #include "third_party/blink/renderer/core/loader/subresource_filter.h"
 #include "third_party/blink/renderer/platform/exported/wrapped_resource_request.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
@@ -690,6 +691,24 @@
     }
   }
 
+  // Warn if the resource URL's hostname contains IDNA deviation characters.
+  // Only warn if the resource URL's origin is different than its requestor
+  // (we don't want to warn for <img src="faß.de/image.img"> on faß.de).
+  // TODO(crbug.com/1396475): Remove once Non-Transitional mode is shipped.
+  if (!resource_request.RequestorOrigin()->IsSameOriginWith(
+          SecurityOrigin::Create(url).get()) &&
+      url.HasIDNA2008DeviationCharacter()) {
+    String message = GetConsoleWarningForIDNADeviationCharacters(url);
+    if (!message.empty()) {
+      AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(
+          mojom::ConsoleMessageSource::kSecurity,
+          mojom::ConsoleMessageLevel::kWarning, message));
+      UseCounter::Count(
+          GetExecutionContext(),
+          WebFeature::kIDNA2008DeviationCharacterInHostnameOfSubresource);
+    }
+  }
+
   return absl::nullopt;
 }
 
diff --git a/third_party/blink/renderer/core/loader/build.gni b/third_party/blink/renderer/core/loader/build.gni
index 04d9959..23600d7 100644
--- a/third_party/blink/renderer/core/loader/build.gni
+++ b/third_party/blink/renderer/core/loader/build.gni
@@ -11,6 +11,8 @@
   "back_forward_cache_loader_helper_impl.h",
   "base_fetch_context.cc",
   "base_fetch_context.h",
+  "idna_util.h",
+  "idna_util.cc",
   "beacon_data.cc",
   "beacon_data.h",
   "cookie_jar.cc",
diff --git a/third_party/blink/renderer/core/loader/frame_loader.cc b/third_party/blink/renderer/core/loader/frame_loader.cc
index 30d84870..edcb94a2 100644
--- a/third_party/blink/renderer/core/loader/frame_loader.cc
+++ b/third_party/blink/renderer/core/loader/frame_loader.cc
@@ -54,6 +54,7 @@
 #include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom-blink.h"
 #include "third_party/blink/public/mojom/frame/frame.mojom-blink.h"
 #include "third_party/blink/public/mojom/loader/request_context_frame_type.mojom-blink.h"
+#include "third_party/blink/public/mojom/runtime_feature_state/runtime_feature_state.mojom-blink.h"
 #include "third_party/blink/public/platform/modules/service_worker/web_service_worker_network_provider.h"
 #include "third_party/blink/public/platform/platform.h"
 #include "third_party/blink/public/platform/task_type.h"
@@ -92,6 +93,7 @@
 #include "third_party/blink/renderer/core/loader/form_submission.h"
 #include "third_party/blink/renderer/core/loader/frame_load_request.h"
 #include "third_party/blink/renderer/core/loader/frame_loader_types.h"
+#include "third_party/blink/renderer/core/loader/idna_util.h"
 #include "third_party/blink/renderer/core/loader/mixed_content_checker.h"
 #include "third_party/blink/renderer/core/loader/progress_tracker.h"
 #include "third_party/blink/renderer/core/navigation_api/navigation_api.h"
@@ -123,6 +125,7 @@
 #include "third_party/blink/renderer/platform/network/http_parsers.h"
 #include "third_party/blink/renderer/platform/network/mime/mime_type_registry.h"
 #include "third_party/blink/renderer/platform/network/network_utils.h"
+#include "third_party/blink/renderer/platform/runtime_feature_state/runtime_feature_state_override_context.h"
 #include "third_party/blink/renderer/platform/scheduler/public/frame_scheduler.h"
 #include "third_party/blink/renderer/platform/weborigin/security_origin.h"
 #include "third_party/blink/renderer/platform/weborigin/security_policy.h"
@@ -818,6 +821,25 @@
     }
   }
 
+  // Warn if the resource URL's hostname contains IDNA deviation characters.
+  // Only warn if the resource URL's origin is different than its requestor
+  // (we don't want to warn for <img src="faß.de/image.img"> on faß.de).
+  // TODO(crbug.com/1396475): Remove once Non-Transitional mode is shipped.
+  if (resource_request.RequestorOrigin() &&
+      !resource_request.RequestorOrigin()->IsSameOriginWith(
+          SecurityOrigin::Create(url).get()) &&
+      url.HasIDNA2008DeviationCharacter()) {
+    String message = GetConsoleWarningForIDNADeviationCharacters(url);
+    if (!message.empty()) {
+      request.GetOriginWindow()->AddConsoleMessage(
+          MakeGarbageCollected<ConsoleMessage>(
+              mojom::ConsoleMessageSource::kSecurity,
+              mojom::ConsoleMessageLevel::kWarning, message));
+      origin_window->CountUse(
+          WebFeature::kIDNA2008DeviationCharacterInHostnameOfIFrame);
+    }
+  }
+
   Client()->BeginNavigation(
       resource_request, request.GetFrameType(), origin_window,
       nullptr /* document_loader */, navigation_type,
@@ -1164,6 +1186,10 @@
     policy_container = PolicyContainer::CreateFromWebPolicyContainer(
         std::move(navigation_params->policy_container));
   }
+
+  base::flat_map<mojom::blink::RuntimeFeatureState, bool> override_values =
+      navigation_params->modified_runtime_features;
+
   // TODO(dgozman): get rid of provisional document loader and most of the code
   // below. We should probably call DocumentLoader::CommitNavigation directly.
   DocumentLoader* new_document_loader = MakeGarbageCollected<DocumentLoader>(
@@ -1175,6 +1201,14 @@
       ScopedOldDocumentInfoForCommitCapturer::CurrentInfo()->history_item,
       commit_reason);
 
+  // Now that the RuntimeFeatureStateOverrideContext has been created, set the
+  // override values.
+  // TODO(crbug.com/1377000): Move this inside CommitNavigation() and put it
+  // alongside the other state initialization.
+  frame_->DomWindow()
+      ->GetRuntimeFeatureStateOverrideContext()
+      ->ApplyOverrideValuesFromParams(override_values);
+
   RestoreScrollPositionAndViewState();
 
   TakeObjectSnapshot();
diff --git a/third_party/blink/renderer/core/loader/idna_util.cc b/third_party/blink/renderer/core/loader/idna_util.cc
new file mode 100644
index 0000000..87cbb505
--- /dev/null
+++ b/third_party/blink/renderer/core/loader/idna_util.cc
@@ -0,0 +1,94 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/blink/renderer/core/loader/idna_util.h"
+
+#include <unicode/idna.h>
+
+#include "base/strings/utf_string_conversions.h"
+#include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
+#include "third_party/blink/renderer/platform/wtf/text/string_utf8_adaptor.h"
+#include "url/url_features.h"
+
+namespace {
+
+// RFC5321 says the maximum total length of a domain name is 255 octets.
+constexpr int32_t kMaximumDomainNameLengthForIDNADecoding = 255;
+
+// Unsafely decodes a punycode hostname to unicode (e.g. xn--fa-hia.de to
+// faß.de). Only used for logging. Doesn't do any spoof checks on the output,
+// so the output MUST NOT be used for anything else.
+String UnsafeASCIIToIDNA(String hostname_ascii) {
+  static UIDNA* uidna = [] {
+    UErrorCode err = U_ZERO_ERROR;
+    UIDNA* value =
+        uidna_openUTS46(UIDNA_CHECK_BIDI | UIDNA_NONTRANSITIONAL_TO_ASCII |
+                            UIDNA_NONTRANSITIONAL_TO_UNICODE,
+                        &err);
+    if (U_FAILURE(err)) {
+      value = nullptr;
+    }
+    return value;
+  }();
+
+  if (!uidna) {
+    return String();
+  }
+  DCHECK(hostname_ascii.ContainsOnlyASCIIOrEmpty());
+
+  UErrorCode status = U_ZERO_ERROR;
+  UIDNAInfo info = UIDNA_INFO_INITIALIZER;
+  Vector<char> output_utf8(
+      static_cast<wtf_size_t>(kMaximumDomainNameLengthForIDNADecoding), '\0');
+  StringUTF8Adaptor hostname(hostname_ascii);
+
+  // This returns the actual length required. If processing fails, info.errors
+  // will be nonzero. `status` indicates an error only in exceptional cases,
+  // such as a U_MEMORY_ALLOCATION_ERROR.
+  int32_t output_utf8_length = uidna_nameToUnicodeUTF8(
+      uidna, hostname.data(), static_cast<int32_t>(hostname.size()),
+      output_utf8.data(), output_utf8.size(), &info, &status);
+  if (U_FAILURE(status) || info.errors != 0 ||
+      output_utf8_length > kMaximumDomainNameLengthForIDNADecoding) {
+    return String();
+  }
+  return String::FromUTF8(output_utf8.data(),
+                          static_cast<wtf_size_t>(output_utf8_length));
+}
+
+}  // namespace
+
+namespace blink {
+
+String GetConsoleWarningForIDNADeviationCharacters(const KURL& url) {
+  if (!url::IsRecordingIDNA2008Metrics()) {
+    return String();
+  }
+  // `url` is canonicalized to ASCII (i.e. punycode). First decode it to unicode
+  // then check for deviation characters.
+  String host = UnsafeASCIIToIDNA(url.Host());
+
+  if (!host.Contains(u"\u00DF") &&  // Sharp-s
+      !host.Contains(u"\u03C2") &&  // Greek final sigma
+      !host.Contains(u"\u200D") &&  // Zero width joiner
+      !host.Contains(u"\u200C")) {  // Zero width non-joiner
+    return String();
+  }
+
+  String elided = url.ElidedString().replace(
+      url.HostStart(), url.HostEnd() - url.HostStart(), host);
+  StringBuilder message;
+  message.Append("The resource at ");
+  message.Append(elided);
+  message.Append(
+      " contains IDNA Deviation Characters. The hostname for this URL (");
+  message.Append(host);
+  message.Append(
+      ") might point to a different IP address after "
+      "https://chromestatus.com/feature/5105856067141632. Make sure you are "
+      "using the correct host name.");
+  return message.ToString();
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/core/loader/idna_util.h b/third_party/blink/renderer/core/loader/idna_util.h
new file mode 100644
index 0000000..2f9c7bae
--- /dev/null
+++ b/third_party/blink/renderer/core/loader/idna_util.h
@@ -0,0 +1,19 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_IDNA_UTIL_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_IDNA_UTIL_H_
+
+#include "third_party/blink/renderer/platform/weborigin/kurl.h"
+#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
+
+namespace blink {
+
+// Returns a console message if the hostname of `url` contains IDNA 2008
+// deviation characters. Returns empty string otherwise.
+String GetConsoleWarningForIDNADeviationCharacters(const KURL& url);
+
+}  // namespace blink
+
+#endif
diff --git a/third_party/blink/renderer/core/page/focus_controller.cc b/third_party/blink/renderer/core/page/focus_controller.cc
index 5c942f07..2f2e89c 100644
--- a/third_party/blink/renderer/core/page/focus_controller.cc
+++ b/third_party/blink/renderer/core/page/focus_controller.cc
@@ -91,7 +91,7 @@
   auto* invoker = DynamicTo<HTMLFormControlElement>(node);
   if (!invoker)
     return false;
-  HTMLElement* popover = invoker->popoverTargetElement().element;
+  HTMLElement* popover = invoker->popoverTargetElement().popover;
   if (!popover)
     return false;
   return popover->popoverOpen();
@@ -368,7 +368,7 @@
   DCHECK(IsA<HTMLFormControlElement>(invoker));
   HTMLElement* popover = DynamicTo<HTMLFormControlElement>(invoker)
                              ->popoverTargetElement()
-                             .element;
+                             .popover;
   DCHECK(IsOpenPopoverWithInvoker(popover));
   return ScopedFocusNavigation(*popover, nullptr, owner_map);
 }
diff --git a/third_party/blink/renderer/core/timing/back_forward_cache_restoration.cc b/third_party/blink/renderer/core/timing/back_forward_cache_restoration.cc
index caf882e4..f3bdd66b 100644
--- a/third_party/blink/renderer/core/timing/back_forward_cache_restoration.cc
+++ b/third_party/blink/renderer/core/timing/back_forward_cache_restoration.cc
@@ -12,11 +12,13 @@
     DOMHighResTimeStamp start_time,
     DOMHighResTimeStamp pageshow_event_start,
     DOMHighResTimeStamp pageshow_event_end,
-    uint32_t navigation_id)
+    uint32_t navigation_id,
+    DOMWindow* source)
     : PerformanceEntry(g_empty_atom,
                        start_time,
                        pageshow_event_start,
-                       navigation_id),
+                       navigation_id,
+                       source),
       pageshow_event_start_(pageshow_event_start),
       pageshow_event_end_(pageshow_event_end) {}
 BackForwardCacheRestoration::~BackForwardCacheRestoration() = default;
diff --git a/third_party/blink/renderer/core/timing/back_forward_cache_restoration.h b/third_party/blink/renderer/core/timing/back_forward_cache_restoration.h
index 762514f..f20da0b2 100644
--- a/third_party/blink/renderer/core/timing/back_forward_cache_restoration.h
+++ b/third_party/blink/renderer/core/timing/back_forward_cache_restoration.h
@@ -18,7 +18,8 @@
   BackForwardCacheRestoration(DOMHighResTimeStamp start_time,
                               DOMHighResTimeStamp pageshow_event_start,
                               DOMHighResTimeStamp pageshow_event_end,
-                              uint32_t navigation_id);
+                              uint32_t navigation_id,
+                              DOMWindow* source);
   ~BackForwardCacheRestoration() override;
   const AtomicString& entryType() const override;
   PerformanceEntryType EntryTypeEnum() const override;
diff --git a/third_party/blink/renderer/core/timing/largest_contentful_paint.cc b/third_party/blink/renderer/core/timing/largest_contentful_paint.cc
index 32f67e1..40b4a21 100644
--- a/third_party/blink/renderer/core/timing/largest_contentful_paint.cc
+++ b/third_party/blink/renderer/core/timing/largest_contentful_paint.cc
@@ -20,8 +20,13 @@
     const AtomicString& id,
     const String& url,
     Element* element,
-    uint32_t navigation_id)
-    : PerformanceEntry(g_empty_atom, start_time, start_time, navigation_id),
+    uint32_t navigation_id,
+    DOMWindow* source)
+    : PerformanceEntry(g_empty_atom,
+                       start_time,
+                       start_time,
+                       navigation_id,
+                       source),
       size_(size),
       render_time_(render_time),
       load_time_(load_time),
diff --git a/third_party/blink/renderer/core/timing/largest_contentful_paint.h b/third_party/blink/renderer/core/timing/largest_contentful_paint.h
index 852141ef..c20ced0 100644
--- a/third_party/blink/renderer/core/timing/largest_contentful_paint.h
+++ b/third_party/blink/renderer/core/timing/largest_contentful_paint.h
@@ -27,7 +27,8 @@
                          const AtomicString& id,
                          const String& url,
                          Element* element,
-                         uint32_t navigation_id);
+                         uint32_t navigation_id,
+                         DOMWindow* source);
   ~LargestContentfulPaint() override;
 
   const AtomicString& entryType() const override;
diff --git a/third_party/blink/renderer/core/timing/layout_shift.cc b/third_party/blink/renderer/core/timing/layout_shift.cc
index 8c9527f..847789d 100644
--- a/third_party/blink/renderer/core/timing/layout_shift.cc
+++ b/third_party/blink/renderer/core/timing/layout_shift.cc
@@ -16,10 +16,11 @@
                                  bool input_detected,
                                  double input_timestamp,
                                  AttributionList sources,
-                                 uint32_t navigation_id) {
+                                 uint32_t navigation_id,
+                                 DOMWindow* source) {
   return MakeGarbageCollected<LayoutShift>(start_time, value, input_detected,
                                            input_timestamp, sources,
-                                           navigation_id);
+                                           navigation_id, source);
 }
 
 LayoutShift::LayoutShift(double start_time,
@@ -27,8 +28,13 @@
                          bool input_detected,
                          double input_timestamp,
                          AttributionList sources,
-                         uint32_t navigation_id)
-    : PerformanceEntry(g_empty_atom, start_time, start_time, navigation_id),
+                         uint32_t navigation_id,
+                         DOMWindow* source)
+    : PerformanceEntry(g_empty_atom,
+                       start_time,
+                       start_time,
+                       navigation_id,
+                       source),
       value_(value),
       had_recent_input_(input_detected),
       most_recent_input_timestamp_(input_timestamp),
diff --git a/third_party/blink/renderer/core/timing/layout_shift.h b/third_party/blink/renderer/core/timing/layout_shift.h
index 42fd190..6476499 100644
--- a/third_party/blink/renderer/core/timing/layout_shift.h
+++ b/third_party/blink/renderer/core/timing/layout_shift.h
@@ -31,14 +31,16 @@
                              bool input_detected,
                              double input_timestamp,
                              AttributionList sources,
-                             uint32_t navigation_id);
+                             uint32_t navigation_id,
+                             DOMWindow* source);
 
   explicit LayoutShift(double start_time,
                        double value,
                        bool input_detected,
                        double input_timestamp,
                        AttributionList sources,
-                       uint32_t navigation_id);
+                       uint32_t navigation_id,
+                       DOMWindow* source);
 
   ~LayoutShift() override;
 
diff --git a/third_party/blink/renderer/core/timing/performance.cc b/third_party/blink/renderer/core/timing/performance.cc
index 8a4990c..d6ea3167 100644
--- a/third_party/blink/renderer/core/timing/performance.cc
+++ b/third_party/blink/renderer/core/timing/performance.cc
@@ -676,7 +676,7 @@
                                     ExecutionContext* context) {
   auto* entry = MakeGarbageCollected<PerformanceResourceTiming>(
       *info, time_origin_, cross_origin_isolated_capability_, initiator_type,
-      context);
+      context, DynamicTo<LocalDOMWindow>(context));
   NotifyObserversOfEntry(*entry);
   // https://w3c.github.io/resource-timing/#dfn-add-a-performanceresourcetiming-entry
   if (CanAddResourceTimingEntry() &&
@@ -818,7 +818,8 @@
                                  base::TimeTicks start_time) {
   PerformanceEntry* entry = MakeGarbageCollected<PerformancePaintTiming>(
       type, MonotonicTimeToDOMHighResTimeStamp(start_time),
-      PerformanceEntry::GetNavigationId(GetExecutionContext()));
+      PerformanceEntry::GetNavigationId(GetExecutionContext()),
+      DynamicTo<LocalDOMWindow>(GetExecutionContext()));
   DCHECK((type == PerformancePaintTiming::PaintType::kFirstPaint) ||
          (type == PerformancePaintTiming::PaintType::kFirstContentfulPaint));
   if (paint_entries_timing_.size() < kDefaultPaintEntriesBufferSize) {
@@ -852,7 +853,8 @@
       static_cast<int>(MonotonicTimeToDOMHighResTimeStamp(end_time) -
                        dom_high_res_start_time),
       name, container_type, container_src, container_id, container_name,
-      PerformanceEntry::GetNavigationId(execution_context));
+      PerformanceEntry::GetNavigationId(execution_context),
+      DynamicTo<LocalDOMWindow>(execution_context));
   if (longtask_buffer_.size() < kDefaultLongTaskBufferSize) {
     InsertEntryIntoSortedBuffer(longtask_buffer_, *entry, kRecordSwaps);
   } else {
@@ -875,7 +877,8 @@
       MonotonicTimeToDOMHighResTimeStamp(start_time),
       MonotonicTimeToDOMHighResTimeStamp(pageshow_start_time),
       MonotonicTimeToDOMHighResTimeStamp(pageshow_end_time),
-      PerformanceEntry::GetNavigationId(GetExecutionContext()));
+      PerformanceEntry::GetNavigationId(GetExecutionContext()),
+      DynamicTo<LocalDOMWindow>(GetExecutionContext()));
   if (back_forward_cache_restoration_buffer_.size() <
       back_forward_cache_restoration_buffer_size_limit_) {
     InsertEntryIntoSortedBuffer(back_forward_cache_restoration_buffer_, *entry,
@@ -1069,9 +1072,9 @@
     const V8UnionDoubleOrString* end,
     const ScriptValue& detail,
     ExceptionState& exception_state) {
-  PerformanceMeasure* performance_measure =
-      GetUserTiming().Measure(script_state, measure_name, start, duration, end,
-                              detail, exception_state);
+  PerformanceMeasure* performance_measure = GetUserTiming().Measure(
+      script_state, measure_name, start, duration, end, detail, exception_state,
+      LocalDOMWindow::From(script_state));
   if (performance_measure)
     NotifyObserversOfEntry(*performance_measure);
   return performance_measure;
diff --git a/third_party/blink/renderer/core/timing/performance_element_timing.cc b/third_party/blink/renderer/core/timing/performance_element_timing.cc
index 991e076..6e527f2 100644
--- a/third_party/blink/renderer/core/timing/performance_element_timing.cc
+++ b/third_party/blink/renderer/core/timing/performance_element_timing.cc
@@ -23,7 +23,8 @@
     int naturalHeight,
     const AtomicString& id,
     Element* element,
-    uint32_t navigation_id) {
+    uint32_t navigation_id,
+    DOMWindow* source) {
   // It is possible to 'paint' images which have naturalWidth or naturalHeight
   // equal to 0.
   DCHECK_GE(naturalWidth, 0);
@@ -32,7 +33,8 @@
   double start_time = render_time != 0.0 ? render_time : load_time;
   return MakeGarbageCollected<PerformanceElementTiming>(
       name, start_time, url, intersection_rect, render_time, load_time,
-      identifier, naturalWidth, naturalHeight, id, element, navigation_id);
+      identifier, naturalWidth, naturalHeight, id, element, navigation_id,
+      source);
 }
 
 PerformanceElementTiming::PerformanceElementTiming(
@@ -47,8 +49,9 @@
     int naturalHeight,
     const AtomicString& id,
     Element* element,
-    uint32_t navigation_id)
-    : PerformanceEntry(name, start_time, start_time, navigation_id),
+    uint32_t navigation_id,
+    DOMWindow* source)
+    : PerformanceEntry(name, start_time, start_time, navigation_id, source),
       element_(element),
       intersection_rect_(DOMRectReadOnly::FromRectF(intersection_rect)),
       render_time_(render_time),
diff --git a/third_party/blink/renderer/core/timing/performance_element_timing.h b/third_party/blink/renderer/core/timing/performance_element_timing.h
index 6e40732..3be35a42 100644
--- a/third_party/blink/renderer/core/timing/performance_element_timing.h
+++ b/third_party/blink/renderer/core/timing/performance_element_timing.h
@@ -32,7 +32,8 @@
                                           int naturalHeight,
                                           const AtomicString& id,
                                           Element*,
-                                          uint32_t navigation_id);
+                                          uint32_t navigation_id,
+                                          DOMWindow* source);
   PerformanceElementTiming(const AtomicString& name,
                            DOMHighResTimeStamp start_time,
                            const String& url,
@@ -44,7 +45,8 @@
                            int naturalHeight,
                            const AtomicString& id,
                            Element*,
-                           uint32_t navigation_id);
+                           uint32_t navigation_id,
+                           DOMWindow* source);
 
   ~PerformanceElementTiming() override;
 
diff --git a/third_party/blink/renderer/core/timing/performance_entry.cc b/third_party/blink/renderer/core/timing/performance_entry.cc
index 1571f7327..52a9592 100644
--- a/third_party/blink/renderer/core/timing/performance_entry.cc
+++ b/third_party/blink/renderer/core/timing/performance_entry.cc
@@ -48,22 +48,26 @@
 PerformanceEntry::PerformanceEntry(const AtomicString& name,
                                    double start_time,
                                    double finish_time,
-                                   uint32_t navigation_id)
+                                   uint32_t navigation_id,
+                                   DOMWindow* source)
     : duration_(finish_time - start_time),
       name_(name),
       start_time_(start_time),
       index_(index_seq.GetNext()),
-      navigation_id_(navigation_id) {}
+      navigation_id_(navigation_id),
+      source_(source) {}
 
 PerformanceEntry::PerformanceEntry(double duration,
                                    const AtomicString& name,
                                    double start_time,
-                                   uint32_t navigation_id)
+                                   uint32_t navigation_id,
+                                   DOMWindow* source)
     : duration_(duration),
       name_(name),
       start_time_(start_time),
       index_(index_seq.GetNext()),
-      navigation_id_(navigation_id) {
+      navigation_id_(navigation_id),
+      source_(source) {
   DCHECK_GE(duration_, 0.0);
 }
 
@@ -81,6 +85,10 @@
   return navigation_id_;
 }
 
+DOMWindow* PerformanceEntry::source() const {
+  return source_;
+}
+
 mojom::blink::PerformanceMarkOrMeasurePtr
 PerformanceEntry::ToMojoPerformanceMarkOrMeasure() {
   DCHECK(EntryTypeEnum() == kMark || EntryTypeEnum() == kMeasure);
@@ -153,6 +161,11 @@
   return local_dom_window->GetNavigationId();
 }
 
+void PerformanceEntry::Trace(Visitor* visitor) const {
+  visitor->Trace(source_);
+  ScriptWrappable::Trace(visitor);
+}
+
 ScriptValue PerformanceEntry::toJSONForBinding(
     ScriptState* script_state) const {
   V8ObjectBuilder result(script_state);
diff --git a/third_party/blink/renderer/core/timing/performance_entry.h b/third_party/blink/renderer/core/timing/performance_entry.h
index 95916303..93695e7 100644
--- a/third_party/blink/renderer/core/timing/performance_entry.h
+++ b/third_party/blink/renderer/core/timing/performance_entry.h
@@ -35,6 +35,7 @@
 #include "third_party/blink/public/mojom/timing/performance_mark_or_measure.mojom-blink-forward.h"
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/dom/dom_high_res_time_stamp.h"
+#include "third_party/blink/renderer/core/frame/dom_window.h"
 #include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
 #include "third_party/blink/renderer/platform/wtf/hash_set.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
@@ -79,6 +80,9 @@
   const AtomicString& name() const { return name_; }
   DOMHighResTimeStamp startTime() const;
   uint32_t navigationId() const;
+  // source() will return null if the PerformanceEntry did not originate from a
+  // Window context.
+  DOMWindow* source() const;
   virtual const AtomicString& entryType() const = 0;
   virtual PerformanceEntryType EntryTypeEnum() const = 0;
   // PerformanceNavigationTiming will override this due to
@@ -129,15 +133,19 @@
   virtual mojom::blink::PerformanceMarkOrMeasurePtr
   ToMojoPerformanceMarkOrMeasure();
 
+  void Trace(Visitor*) const override;
+
  protected:
   PerformanceEntry(const AtomicString& name,
                    double start_time,
                    double finish_time,
-                   uint32_t navigation_id);
+                   uint32_t navigation_id,
+                   DOMWindow* source);
   PerformanceEntry(double duration,
                    const AtomicString& name,
                    double start_time,
-                   uint32_t navigation_id);
+                   uint32_t navigation_id,
+                   DOMWindow* source);
 
   virtual void BuildJSONValue(V8ObjectBuilder&) const;
 
@@ -149,6 +157,9 @@
   const double start_time_;
   const int index_;
   const uint32_t navigation_id_;
+  // source_ will be null if the PerformanceEntry did not originate from a
+  // Window context.
+  const Member<DOMWindow> source_;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/timing/performance_entry.idl b/third_party/blink/renderer/core/timing/performance_entry.idl
index 8c8d8bd8..a7e9ef3 100644
--- a/third_party/blink/renderer/core/timing/performance_entry.idl
+++ b/third_party/blink/renderer/core/timing/performance_entry.idl
@@ -37,5 +37,6 @@
     readonly attribute DOMHighResTimeStamp startTime;
     readonly attribute DOMHighResTimeStamp duration;
     [RuntimeEnabled=NavigationId] readonly attribute unsigned long navigationId;
+    [RuntimeEnabled=CrossFramePerformanceTimeline, Exposed=Window] readonly attribute EventTarget source;
     [CallWith=ScriptState, ImplementedAs=toJSONForBinding] object toJSON();
 };
diff --git a/third_party/blink/renderer/core/timing/performance_event_timing.cc b/third_party/blink/renderer/core/timing/performance_event_timing.cc
index ab6bb840..24259bb 100644
--- a/third_party/blink/renderer/core/timing/performance_event_timing.cc
+++ b/third_party/blink/renderer/core/timing/performance_event_timing.cc
@@ -22,13 +22,14 @@
     DOMHighResTimeStamp processing_end,
     bool cancelable,
     Node* target,
-    uint32_t navigation_id) {
+    uint32_t navigation_id,
+    DOMWindow* source) {
   // TODO(npm): enable this DCHECK once https://crbug.com/852846 is fixed.
   // DCHECK_LE(start_time, processing_start);
   DCHECK_LE(processing_start, processing_end);
   return MakeGarbageCollected<PerformanceEventTiming>(
       event_type, performance_entry_names::kEvent, start_time, processing_start,
-      processing_end, cancelable, target, navigation_id);
+      processing_end, cancelable, target, navigation_id, source);
 }
 
 // static
@@ -38,7 +39,8 @@
       MakeGarbageCollected<PerformanceEventTiming>(
           entry->name(), performance_entry_names::kFirstInput,
           entry->startTime(), entry->processingStart(), entry->processingEnd(),
-          entry->cancelable(), entry->target(), entry->navigationId());
+          entry->cancelable(), entry->target(), entry->navigationId(),
+          entry->source());
   first_input->SetDuration(entry->duration());
   return first_input;
 }
@@ -51,8 +53,9 @@
     DOMHighResTimeStamp processing_end,
     bool cancelable,
     Node* target,
-    uint32_t navigation_id)
-    : PerformanceEntry(event_type, start_time, 0.0, navigation_id),
+    uint32_t navigation_id,
+    DOMWindow* source)
+    : PerformanceEntry(event_type, start_time, 0.0, navigation_id, source),
       entry_type_(entry_type),
       processing_start_(processing_start),
       processing_end_(processing_end),
diff --git a/third_party/blink/renderer/core/timing/performance_event_timing.h b/third_party/blink/renderer/core/timing/performance_event_timing.h
index 4f10adcc..84b44e0e 100644
--- a/third_party/blink/renderer/core/timing/performance_event_timing.h
+++ b/third_party/blink/renderer/core/timing/performance_event_timing.h
@@ -24,7 +24,8 @@
                                         DOMHighResTimeStamp processing_end,
                                         bool cancelable,
                                         Node* target,
-                                        uint32_t navigation_id);
+                                        uint32_t navigation_id,
+                                        DOMWindow* source);
 
   static PerformanceEventTiming* CreateFirstInputTiming(
       PerformanceEventTiming* entry);
@@ -36,7 +37,8 @@
                          DOMHighResTimeStamp processing_end,
                          bool cancelable,
                          Node* target,
-                         uint32_t navigation_id);
+                         uint32_t navigation_id,
+                         DOMWindow* source);
   ~PerformanceEventTiming() override;
 
   const AtomicString& entryType() const override { return entry_type_; }
diff --git a/third_party/blink/renderer/core/timing/performance_long_task_timing.cc b/third_party/blink/renderer/core/timing/performance_long_task_timing.cc
index effe654..929be42f 100644
--- a/third_party/blink/renderer/core/timing/performance_long_task_timing.cc
+++ b/third_party/blink/renderer/core/timing/performance_long_task_timing.cc
@@ -20,11 +20,12 @@
     const AtomicString& culprit_src,
     const AtomicString& culprit_id,
     const AtomicString& culprit_name,
-    const uint32_t navigation_id)
-    : PerformanceEntry(duration, name, start_time, navigation_id) {
+    const uint32_t navigation_id,
+    DOMWindow* source)
+    : PerformanceEntry(duration, name, start_time, navigation_id, source) {
   auto* attribution_entry = MakeGarbageCollected<TaskAttributionTiming>(
       "unknown", culprit_type, culprit_src, culprit_id, culprit_name,
-      navigation_id);
+      navigation_id, source);
   attribution_.push_back(*attribution_entry);
 }
 
diff --git a/third_party/blink/renderer/core/timing/performance_long_task_timing.h b/third_party/blink/renderer/core/timing/performance_long_task_timing.h
index a5292fc..85318575 100644
--- a/third_party/blink/renderer/core/timing/performance_long_task_timing.h
+++ b/third_party/blink/renderer/core/timing/performance_long_task_timing.h
@@ -31,7 +31,8 @@
                             const AtomicString& culprit_src,
                             const AtomicString& culprit_id,
                             const AtomicString& culprit_name,
-                            const uint32_t navigation_id);
+                            const uint32_t navigation_id,
+                            DOMWindow* source);
   ~PerformanceLongTaskTiming() override;
 
   const AtomicString& entryType() const override;
diff --git a/third_party/blink/renderer/core/timing/performance_mark.cc b/third_party/blink/renderer/core/timing/performance_mark.cc
index d3f1553d..7e1372a 100644
--- a/third_party/blink/renderer/core/timing/performance_mark.cc
+++ b/third_party/blink/renderer/core/timing/performance_mark.cc
@@ -23,8 +23,9 @@
     base::TimeTicks unsafe_time_for_traces,
     scoped_refptr<SerializedScriptValue> serialized_detail,
     ExceptionState& exception_state,
-    uint32_t navigation_id)
-    : PerformanceEntry(name, start_time, start_time, navigation_id),
+    uint32_t navigation_id,
+    DOMWindow* source)
+    : PerformanceEntry(name, start_time, start_time, navigation_id, source),
       serialized_detail_(std::move(serialized_detail)),
       unsafe_time_for_traces_(unsafe_time_for_traces) {}
 
@@ -93,7 +94,7 @@
   uint32_t navigation_id = PerformanceEntry::GetNavigationId(script_state);
   return MakeGarbageCollected<PerformanceMark>(
       mark_name, start, unsafe_start_for_traces, std::move(serialized_detail),
-      exception_state, navigation_id);
+      exception_state, navigation_id, LocalDOMWindow::From(script_state));
 }
 
 const AtomicString& PerformanceMark::entryType() const {
diff --git a/third_party/blink/renderer/core/timing/performance_mark.h b/third_party/blink/renderer/core/timing/performance_mark.h
index 009fd8b0..d8dd8be 100644
--- a/third_party/blink/renderer/core/timing/performance_mark.h
+++ b/third_party/blink/renderer/core/timing/performance_mark.h
@@ -57,14 +57,13 @@
                                  ExceptionState&);
 
   // This constructor is only public so that MakeGarbageCollected can call it.
-  PerformanceMark(
-      const AtomicString& name,
-      double start_time,
-      base::TimeTicks unsafe_time_for_traces,
-      scoped_refptr<SerializedScriptValue>,
-      ExceptionState& exception_state,
-      uint32_t navigation_count = 0); /* TODO(1273925): Remove the default value
-                                      when all callers have been updated. */
+  PerformanceMark(const AtomicString& name,
+                  double start_time,
+                  base::TimeTicks unsafe_time_for_traces,
+                  scoped_refptr<SerializedScriptValue>,
+                  ExceptionState& exception_state,
+                  uint32_t navigation_count,
+                  DOMWindow* source);
 
   ~PerformanceMark() override = default;
 
diff --git a/third_party/blink/renderer/core/timing/performance_mark_test.cc b/third_party/blink/renderer/core/timing/performance_mark_test.cc
index 73412ec..ddbd2352 100644
--- a/third_party/blink/renderer/core/timing/performance_mark_test.cc
+++ b/third_party/blink/renderer/core/timing/performance_mark_test.cc
@@ -9,6 +9,7 @@
 #include "third_party/blink/renderer/bindings/core/v8/script_value.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_testing.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_performance_mark_options.h"
+#include "third_party/blink/renderer/core/frame/local_dom_window.h"
 #include "third_party/blink/renderer/core/performance_entry_names.h"
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
 #include "third_party/blink/renderer/platform/bindings/script_state.h"
@@ -46,7 +47,7 @@
 
   PerformanceMark* pm = MakeGarbageCollected<PerformanceMark>(
       "mark-name", 0, base::TimeTicks(), SerializedScriptValue::NullValue(),
-      exception_state, 1);
+      exception_state, 1, LocalDOMWindow::From(script_state));
   ASSERT_EQ(pm->entryType(), performance_entry_names::kMark);
   ASSERT_EQ(pm->EntryTypeEnum(), PerformanceEntry::EntryType::kMark);
 
@@ -65,7 +66,8 @@
       SerializedScriptValue::Create(String("some-payload"));
 
   PerformanceMark* pm = MakeGarbageCollected<PerformanceMark>(
-      "mark-name", 0, base::TimeTicks(), payload_string, exception_state);
+      "mark-name", 0, base::TimeTicks(), payload_string, exception_state, 0,
+      LocalDOMWindow::From(script_state));
   ASSERT_EQ(pm->entryType(), performance_entry_names::kMark);
   ASSERT_EQ(pm->EntryTypeEnum(), PerformanceEntry::EntryType::kMark);
 
@@ -86,7 +88,8 @@
   const AtomicString expected_entry_type = "mark";
   PerformanceMark pm(expected_name, expected_start_time, base::TimeTicks(),
                      SerializedScriptValue::NullValue(), exception_state,
-                     expected_navigation_count);
+                     expected_navigation_count,
+                     LocalDOMWindow::From(script_state));
 
   ScriptValue json_object = pm.toJSONForBinding(script_state);
   EXPECT_TRUE(json_object.IsObject());
diff --git a/third_party/blink/renderer/core/timing/performance_measure.cc b/third_party/blink/renderer/core/timing/performance_measure.cc
index c8035df..d98397a4 100644
--- a/third_party/blink/renderer/core/timing/performance_measure.cc
+++ b/third_party/blink/renderer/core/timing/performance_measure.cc
@@ -19,21 +19,23 @@
     double start_time,
     double end_time,
     scoped_refptr<SerializedScriptValue> serialized_detail,
-    ExceptionState& exception_state)
+    ExceptionState& exception_state,
+    DOMWindow* source)
     : PerformanceEntry(name,
                        start_time,
                        end_time,
-                       PerformanceEntry::GetNavigationId(script_state)),
+                       PerformanceEntry::GetNavigationId(script_state),
+                       source),
       serialized_detail_(serialized_detail) {}
 
 // static
-PerformanceMeasure* PerformanceMeasure::Create(
-    ScriptState* script_state,
-    const AtomicString& name,
-    double start_time,
-    double end_time,
-    const ScriptValue& detail,
-    ExceptionState& exception_state) {
+PerformanceMeasure* PerformanceMeasure::Create(ScriptState* script_state,
+                                               const AtomicString& name,
+                                               double start_time,
+                                               double end_time,
+                                               const ScriptValue& detail,
+                                               ExceptionState& exception_state,
+                                               DOMWindow* source) {
   scoped_refptr<SerializedScriptValue> serialized_detail;
   if (detail.IsEmpty()) {
     serialized_detail = nullptr;
@@ -46,7 +48,7 @@
   }
   return MakeGarbageCollected<PerformanceMeasure>(
       script_state, name, start_time, end_time, serialized_detail,
-      exception_state);
+      exception_state, source);
 }
 
 ScriptValue PerformanceMeasure::detail(ScriptState* script_state) {
diff --git a/third_party/blink/renderer/core/timing/performance_measure.h b/third_party/blink/renderer/core/timing/performance_measure.h
index d74343e8..4209bbd1 100644
--- a/third_party/blink/renderer/core/timing/performance_measure.h
+++ b/third_party/blink/renderer/core/timing/performance_measure.h
@@ -49,7 +49,8 @@
                      double start_time,
                      double end_time,
                      scoped_refptr<SerializedScriptValue>,
-                     ExceptionState&);
+                     ExceptionState&,
+                     DOMWindow* source);
   ~PerformanceMeasure() override = default;
 
   static PerformanceMeasure* Create(ScriptState*,
@@ -57,7 +58,8 @@
                                     double start_time,
                                     double end_time,
                                     const ScriptValue& detail,
-                                    ExceptionState&);
+                                    ExceptionState&,
+                                    DOMWindow* source);
 
   ScriptValue detail(ScriptState*);
 
diff --git a/third_party/blink/renderer/core/timing/performance_navigation_timing.cc b/third_party/blink/renderer/core/timing/performance_navigation_timing.cc
index e67d2c7..456afb0 100644
--- a/third_party/blink/renderer/core/timing/performance_navigation_timing.cc
+++ b/third_party/blink/renderer/core/timing/performance_navigation_timing.cc
@@ -68,7 +68,8 @@
                          window->Url().Protocol().Ascii()),
           std::move(server_timing),
           window,
-          navigation_delivery_type),
+          navigation_delivery_type,
+          window),
       ExecutionContextClient(window),
       resource_timing_info_(info) {
   DCHECK(window);
diff --git a/third_party/blink/renderer/core/timing/performance_observer_test.cc b/third_party/blink/renderer/core/timing/performance_observer_test.cc
index e30b482..6b1b866b 100644
--- a/third_party/blink/renderer/core/timing/performance_observer_test.cc
+++ b/third_party/blink/renderer/core/timing/performance_observer_test.cc
@@ -77,9 +77,9 @@
   EXPECT_EQ(0, NumPerformanceEntries());
 
   // add a layout-shift to performance so getEntries() returns it
-  auto* entry =
-      LayoutShift::Create(0.0, 1234, true, 5678, LayoutShift::AttributionList(),
-                          /*navigation_id=*/1);
+  auto* entry = LayoutShift::Create(
+      0.0, 1234, true, 5678, LayoutShift::AttributionList(),
+      /*navigation_id=*/1, LocalDOMWindow::From(scope.GetScriptState()));
   base_->AddLayoutShiftBuffer(*entry);
 
   // call observe with the buffered flag
diff --git a/third_party/blink/renderer/core/timing/performance_paint_timing.cc b/third_party/blink/renderer/core/timing/performance_paint_timing.cc
index b6c3e66f..3c75040 100644
--- a/third_party/blink/renderer/core/timing/performance_paint_timing.cc
+++ b/third_party/blink/renderer/core/timing/performance_paint_timing.cc
@@ -32,11 +32,13 @@
 
 PerformancePaintTiming::PerformancePaintTiming(PaintType type,
                                                double start_time,
-                                               uint32_t navigation_id)
+                                               uint32_t navigation_id,
+                                               DOMWindow* source)
     : PerformanceEntry(FromPaintTypeToString(type),
                        start_time,
                        start_time,
-                       navigation_id) {}
+                       navigation_id,
+                       source) {}
 
 PerformancePaintTiming::~PerformancePaintTiming() = default;
 
diff --git a/third_party/blink/renderer/core/timing/performance_paint_timing.h b/third_party/blink/renderer/core/timing/performance_paint_timing.h
index a1bfbd741..c640163 100644
--- a/third_party/blink/renderer/core/timing/performance_paint_timing.h
+++ b/third_party/blink/renderer/core/timing/performance_paint_timing.h
@@ -16,7 +16,10 @@
  public:
   enum class PaintType { kFirstPaint, kFirstContentfulPaint };
 
-  PerformancePaintTiming(PaintType, double start_time, uint32_t navigation_id);
+  PerformancePaintTiming(PaintType,
+                         double start_time,
+                         uint32_t navigation_id,
+                         DOMWindow* source);
   ~PerformancePaintTiming() override;
 
   const AtomicString& entryType() const override;
diff --git a/third_party/blink/renderer/core/timing/performance_resource_timing.cc b/third_party/blink/renderer/core/timing/performance_resource_timing.cc
index 0ff35dff..45f7680e 100644
--- a/third_party/blink/renderer/core/timing/performance_resource_timing.cc
+++ b/third_party/blink/renderer/core/timing/performance_resource_timing.cc
@@ -80,7 +80,8 @@
     base::TimeTicks time_origin,
     bool cross_origin_isolated_capability,
     const AtomicString& initiator_type,
-    ExecutionContext* context)
+    ExecutionContext* context,
+    DOMWindow* source)
     : PerformanceEntry(AtomicString(info.name),
                        Performance::MonotonicTimeToDOMHighResTimeStamp(
                            time_origin,
@@ -92,7 +93,8 @@
                            info.response_end,
                            info.allow_negative_values,
                            cross_origin_isolated_capability),
-                       PerformanceEntry::GetNavigationId(context)),
+                       PerformanceEntry::GetNavigationId(context),
+                       source),
       initiator_type_(initiator_type.empty()
                           ? fetch_initiator_type_names::kOther
                           : initiator_type),
@@ -136,8 +138,9 @@
     bool is_secure_transport,
     HeapVector<Member<PerformanceServerTiming>> server_timing,
     ExecutionContext* context,
-    NavigationDeliveryType navigation_delivery_type)
-    : PerformanceEntry(name, 0.0, 0.0, kNavigationIdDefaultValue),
+    NavigationDeliveryType navigation_delivery_type,
+    DOMWindow* source)
+    : PerformanceEntry(name, 0.0, 0.0, kNavigationIdDefaultValue, source),
       delivery_type_(GetDeliveryType(navigation_delivery_type, cache_state)),
       time_origin_(time_origin),
       cross_origin_isolated_capability_(cross_origin_isolated_capability),
diff --git a/third_party/blink/renderer/core/timing/performance_resource_timing.h b/third_party/blink/renderer/core/timing/performance_resource_timing.h
index 450eb6a2..8826f1fb 100644
--- a/third_party/blink/renderer/core/timing/performance_resource_timing.h
+++ b/third_party/blink/renderer/core/timing/performance_resource_timing.h
@@ -66,12 +66,14 @@
       bool is_secure_transport,
       HeapVector<Member<PerformanceServerTiming>> server_timing,
       ExecutionContext* context,
-      network::mojom::NavigationDeliveryType delivery_type);
+      network::mojom::NavigationDeliveryType delivery_type,
+      DOMWindow* source);
   PerformanceResourceTiming(const mojom::blink::ResourceTimingInfo&,
                             base::TimeTicks time_origin,
                             bool cross_origin_isolated_capability,
                             const AtomicString& initiator_type,
-                            ExecutionContext* context);
+                            ExecutionContext* context,
+                            DOMWindow* source);
   ~PerformanceResourceTiming() override;
 
   const AtomicString& entryType() const override;
diff --git a/third_party/blink/renderer/core/timing/performance_resource_timing_test.cc b/third_party/blink/renderer/core/timing/performance_resource_timing_test.cc
index ebf03b4..bab768d 100644
--- a/third_party/blink/renderer/core/timing/performance_resource_timing_test.cc
+++ b/third_party/blink/renderer/core/timing/performance_resource_timing_test.cc
@@ -5,7 +5,9 @@
 #include "third_party/blink/renderer/core/timing/performance_resource_timing.h"
 
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_testing.h"
 #include "third_party/blink/renderer/core/dom/document.h"
+#include "third_party/blink/renderer/core/frame/local_dom_window.h"
 #include "third_party/blink/renderer/core/testing/dummy_page_holder.h"
 
 namespace blink {
@@ -31,6 +33,10 @@
                                       connection_info);
   }
 
+  void Initialize(ScriptState* script_state) { script_state_ = script_state; }
+
+  ScriptState* GetScriptState() { return script_state_; }
+
  private:
   PerformanceResourceTiming* MakePerformanceResourceTiming(
       const mojom::blink::ResourceTimingInfo& info) {
@@ -42,12 +48,18 @@
             .GetExecutionContext()
             ->CrossOriginIsolatedCapability(),
         /*initiator_type=*/"",
-        dummy_page_holder->GetDocument().GetExecutionContext());
+        dummy_page_holder->GetDocument().GetExecutionContext(),
+        LocalDOMWindow::From(GetScriptState()));
   }
+
+  Persistent<ScriptState> script_state_;
 };
 
 TEST_F(PerformanceResourceTimingTest,
        TestFallbackToConnectionInfoWhenALPNUnknown) {
+  V8TestingScope scope;
+  Initialize(scope.GetScriptState());
+
   AtomicString connection_info = "http/1.1";
   AtomicString alpn_negotiated_protocol = "unknown";
   EXPECT_EQ(GetNextHopProtocol(alpn_negotiated_protocol, connection_info),
@@ -56,12 +68,18 @@
 
 TEST_F(PerformanceResourceTimingTest,
        TestFallbackToHTTPInfoWhenALPNAndConnectionInfoUnknown) {
+  V8TestingScope scope;
+  Initialize(scope.GetScriptState());
+
   AtomicString connection_info = "unknown";
   AtomicString alpn_negotiated_protocol = "unknown";
   EXPECT_EQ(GetNextHopProtocol(alpn_negotiated_protocol, connection_info), "");
 }
 
 TEST_F(PerformanceResourceTimingTest, TestNoChangeWhenContainsQuic) {
+  V8TestingScope scope;
+  Initialize(scope.GetScriptState());
+
   AtomicString connection_info = "http/1.1";
   AtomicString alpn_negotiated_protocol = "http/2+quic/39";
   EXPECT_EQ(GetNextHopProtocol(alpn_negotiated_protocol, connection_info),
@@ -69,6 +87,9 @@
 }
 
 TEST_F(PerformanceResourceTimingTest, TestNoChangeWhenOtherwise) {
+  V8TestingScope scope;
+  Initialize(scope.GetScriptState());
+
   AtomicString connection_info = "http/1.1";
   AtomicString alpn_negotiated_protocol = "RandomProtocol";
   EXPECT_EQ(GetNextHopProtocol(alpn_negotiated_protocol, connection_info),
@@ -76,6 +97,9 @@
 }
 
 TEST_F(PerformanceResourceTimingTest, TestNextHopProtocolIsGuardedByTao) {
+  V8TestingScope scope;
+  Initialize(scope.GetScriptState());
+
   AtomicString connection_info = "http/1.1";
   AtomicString alpn_negotiated_protocol = "RandomProtocol";
   EXPECT_EQ(
diff --git a/third_party/blink/renderer/core/timing/performance_test.cc b/third_party/blink/renderer/core/timing/performance_test.cc
index b3292ef..bd11c511 100644
--- a/third_party/blink/renderer/core/timing/performance_test.cc
+++ b/third_party/blink/renderer/core/timing/performance_test.cc
@@ -14,6 +14,7 @@
 #include "third_party/blink/renderer/bindings/core/v8/v8_performance_observer_callback.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_performance_observer_init.h"
 #include "third_party/blink/renderer/core/execution_context/execution_context.h"
+#include "third_party/blink/renderer/core/frame/local_dom_window.h"
 #include "third_party/blink/renderer/core/testing/null_execution_context.h"
 #include "third_party/blink/renderer/core/testing/page_test_base.h"
 #include "third_party/blink/renderer/core/timing/back_forward_cache_restoration.h"
@@ -33,6 +34,8 @@
 constexpr int kEvent2PageshowEnd = 987;
 }  // namespace
 
+class LocalDOMWindow;
+
 class TestPerformance : public Performance {
  public:
   explicit TestPerformance(ScriptState* script_state)
@@ -231,8 +234,9 @@
 
   PerformanceEntryVector test_buffer_;
 
-  PerformanceEventTiming* test_entry =
-      PerformanceEventTiming::Create("event", 0.0, 0.0, 0.0, false, nullptr, 0);
+  PerformanceEventTiming* test_entry = PerformanceEventTiming::Create(
+      "event", 0.0, 0.0, 0.0, false, nullptr, 0,
+      LocalDOMWindow::From(scope.GetScriptState()));
 
   base_->InsertEntryIntoSortedBuffer(test_buffer_, *test_entry,
                                      Performance::kDoNotRecordSwaps);
@@ -254,12 +258,14 @@
   for (int i = 0; i < 3; i++) {
     double tmp = 1.0;
     PerformanceEventTiming* entry = PerformanceEventTiming::Create(
-        "event", tmp * i, 0.0, 0.0, false, nullptr, 0);
+        "event", tmp * i, 0.0, 0.0, false, nullptr, 0,
+        LocalDOMWindow::From(scope.GetScriptState()));
     test_buffer_.push_back(*entry);
   }
 
-  PerformanceEventTiming* test_entry =
-      PerformanceEventTiming::Create("event", 1.0, 0.0, 0.0, false, nullptr, 0);
+  PerformanceEventTiming* test_entry = PerformanceEventTiming::Create(
+      "event", 1.0, 0.0, 0.0, false, nullptr, 0,
+      LocalDOMWindow::From(scope.GetScriptState()));
 
   // Create copy of the test_buffer_.
   PerformanceEntryVector sorted_buffer_ = test_buffer_;
@@ -285,12 +291,14 @@
   for (int i = 0; i < 3; i++) {
     double tmp = 1.0;
     PerformanceEventTiming* entry = PerformanceEventTiming::Create(
-        "event", tmp * i, 0.0, 0.0, false, nullptr, 0);
+        "event", tmp * i, 0.0, 0.0, false, nullptr, 0,
+        LocalDOMWindow::From(scope.GetScriptState()));
     test_buffer_.push_back(*entry);
   }
 
-  PerformanceEventTiming* test_entry =
-      PerformanceEventTiming::Create("event", 0.0, 0.0, 0.0, false, nullptr, 0);
+  PerformanceEventTiming* test_entry = PerformanceEventTiming::Create(
+      "event", 0.0, 0.0, 0.0, false, nullptr, 0,
+      LocalDOMWindow::From(scope.GetScriptState()));
 
   // Create copy of the test_buffer_.
   PerformanceEntryVector sorted_buffer_ = test_buffer_;
@@ -306,6 +314,9 @@
 }
 
 TEST_F(PerformanceTest, MergePerformanceEntryVectorsTest) {
+  V8TestingScope scope;
+  Initialize(scope.GetScriptState());
+
   PerformanceEntryVector first_vector;
   PerformanceEntryVector second_vector;
 
@@ -314,7 +325,8 @@
   for (int i = 0; i < 6; i += 2) {
     double tmp = 1.0;
     PerformanceEventTiming* entry = PerformanceEventTiming::Create(
-        "event", tmp * i, 0.0, 0.0, false, nullptr, 0);
+        "event", tmp * i, 0.0, 0.0, false, nullptr, 0,
+        LocalDOMWindow::From(scope.GetScriptState()));
     first_vector.push_back(*entry);
     test_vector.push_back(*entry);
   }
@@ -322,7 +334,8 @@
   for (int i = 1; i < 6; i += 2) {
     double tmp = 1.0;
     PerformanceEventTiming* entry = PerformanceEventTiming::Create(
-        "event", tmp * i, 0.0, 0.0, false, nullptr, 0);
+        "event", tmp * i, 0.0, 0.0, false, nullptr, 0,
+        LocalDOMWindow::From(scope.GetScriptState()));
     second_vector.push_back(*entry);
     test_vector.push_back(*entry);
   }
diff --git a/third_party/blink/renderer/core/timing/performance_user_timing.cc b/third_party/blink/renderer/core/timing/performance_user_timing.cc
index de67bfbb..6a2025d 100644
--- a/third_party/blink/renderer/core/timing/performance_user_timing.cc
+++ b/third_party/blink/renderer/core/timing/performance_user_timing.cc
@@ -175,7 +175,8 @@
                                         const absl::optional<double>& duration,
                                         const V8UnionDoubleOrString* end,
                                         const ScriptValue& detail,
-                                        ExceptionState& exception_state) {
+                                        ExceptionState& exception_state,
+                                        DOMWindow* source) {
   double start_time =
       start ? GetTimeOrFindMarkTime(measure_name, start, exception_state) : 0;
   if (exception_state.HadException())
@@ -219,7 +220,7 @@
 
   PerformanceMeasure* measure =
       PerformanceMeasure::Create(script_state, measure_name, start_time,
-                                 end_time, detail, exception_state);
+                                 end_time, detail, exception_state, source);
   if (!measure)
     return nullptr;
   InsertPerformanceEntry(measures_map_, measures_buffer_, *measure);
diff --git a/third_party/blink/renderer/core/timing/performance_user_timing.h b/third_party/blink/renderer/core/timing/performance_user_timing.h
index fc283ce..4293c98 100644
--- a/third_party/blink/renderer/core/timing/performance_user_timing.h
+++ b/third_party/blink/renderer/core/timing/performance_user_timing.h
@@ -53,7 +53,8 @@
                               const absl::optional<double>& duration,
                               const V8UnionDoubleOrString* end,
                               const ScriptValue& detail,
-                              ExceptionState&);
+                              ExceptionState&,
+                              DOMWindow* source);
   void ClearMeasures(const AtomicString& measure_name);
 
   PerformanceEntryVector GetMarks() const;
diff --git a/third_party/blink/renderer/core/timing/soft_navigation_entry.cc b/third_party/blink/renderer/core/timing/soft_navigation_entry.cc
index ab59820..06c47c65 100644
--- a/third_party/blink/renderer/core/timing/soft_navigation_entry.cc
+++ b/third_party/blink/renderer/core/timing/soft_navigation_entry.cc
@@ -11,8 +11,9 @@
 
 SoftNavigationEntry::SoftNavigationEntry(AtomicString name,
                                          double start_time,
-                                         uint32_t navigation_id)
-    : PerformanceEntry(name, start_time, start_time, navigation_id) {}
+                                         uint32_t navigation_id,
+                                         DOMWindow* source)
+    : PerformanceEntry(name, start_time, start_time, navigation_id, source) {}
 
 SoftNavigationEntry::~SoftNavigationEntry() = default;
 
diff --git a/third_party/blink/renderer/core/timing/soft_navigation_entry.h b/third_party/blink/renderer/core/timing/soft_navigation_entry.h
index 379d3809..4472ba0 100644
--- a/third_party/blink/renderer/core/timing/soft_navigation_entry.h
+++ b/third_party/blink/renderer/core/timing/soft_navigation_entry.h
@@ -16,7 +16,8 @@
  public:
   SoftNavigationEntry(AtomicString name,
                       double start_time,
-                      uint32_t navigation_id);
+                      uint32_t navigation_id,
+                      DOMWindow* source);
   ~SoftNavigationEntry() override;
 
   const AtomicString& entryType() const override;
diff --git a/third_party/blink/renderer/core/timing/task_attribution_timing.cc b/third_party/blink/renderer/core/timing/task_attribution_timing.cc
index 3df4f44..cf46dc7 100644
--- a/third_party/blink/renderer/core/timing/task_attribution_timing.cc
+++ b/third_party/blink/renderer/core/timing/task_attribution_timing.cc
@@ -15,8 +15,9 @@
                                              const AtomicString& container_src,
                                              const AtomicString& container_id,
                                              const AtomicString& container_name,
-                                             const uint32_t navigation_id)
-    : PerformanceEntry(name, 0.0, 0.0, navigation_id),
+                                             const uint32_t navigation_id,
+                                             DOMWindow* source)
+    : PerformanceEntry(name, 0.0, 0.0, navigation_id, source),
       container_type_(container_type),
       container_src_(container_src),
       container_id_(container_id),
diff --git a/third_party/blink/renderer/core/timing/task_attribution_timing.h b/third_party/blink/renderer/core/timing/task_attribution_timing.h
index 5795ab9..c1a07ad 100644
--- a/third_party/blink/renderer/core/timing/task_attribution_timing.h
+++ b/third_party/blink/renderer/core/timing/task_attribution_timing.h
@@ -31,7 +31,8 @@
                         const AtomicString& container_src,
                         const AtomicString& container_id,
                         const AtomicString& container_name,
-                        const uint32_t navigation_id);
+                        const uint32_t navigation_id,
+                        DOMWindow* source);
   ~TaskAttributionTiming() override;
 
  private:
diff --git a/third_party/blink/renderer/core/timing/visibility_state_entry.cc b/third_party/blink/renderer/core/timing/visibility_state_entry.cc
index 0003027..6722d6d 100644
--- a/third_party/blink/renderer/core/timing/visibility_state_entry.cc
+++ b/third_party/blink/renderer/core/timing/visibility_state_entry.cc
@@ -11,8 +11,9 @@
 
 VisibilityStateEntry::VisibilityStateEntry(AtomicString name,
                                            double start_time,
-                                           uint32_t navigation_id)
-    : PerformanceEntry(name, start_time, start_time, navigation_id) {}
+                                           uint32_t navigation_id,
+                                           DOMWindow* source)
+    : PerformanceEntry(name, start_time, start_time, navigation_id, source) {}
 
 VisibilityStateEntry::~VisibilityStateEntry() = default;
 
diff --git a/third_party/blink/renderer/core/timing/visibility_state_entry.h b/third_party/blink/renderer/core/timing/visibility_state_entry.h
index c02cd59..ead3b39 100644
--- a/third_party/blink/renderer/core/timing/visibility_state_entry.h
+++ b/third_party/blink/renderer/core/timing/visibility_state_entry.h
@@ -16,7 +16,8 @@
  public:
   VisibilityStateEntry(AtomicString name,
                        double start_time,
-                       uint32_t navigation_id);
+                       uint32_t navigation_id,
+                       DOMWindow* source);
   ~VisibilityStateEntry() override;
 
   const AtomicString& entryType() const override;
diff --git a/third_party/blink/renderer/core/timing/window_performance.cc b/third_party/blink/renderer/core/timing/window_performance.cc
index 678a098..30be2b7 100644
--- a/third_party/blink/renderer/core/timing/window_performance.cc
+++ b/third_party/blink/renderer/core/timing/window_performance.cc
@@ -422,9 +422,9 @@
       MonotonicTimeToDOMHighResTimeStamp(processing_start),
       MonotonicTimeToDOMHighResTimeStamp(processing_end), event.cancelable(),
       event.target() ? event.target()->ToNode() : nullptr,
-      PerformanceEntry::GetNavigationId(
-          GetExecutionContext()));  // TODO(haoliuk): Add WPT for Event Timing.
-                                    // See crbug.com/1320878.
+      PerformanceEntry::GetNavigationId(GetExecutionContext()),
+      DomWindow());  // TODO(haoliuk): Add WPT for Event Timing.
+                     // See crbug.com/1320878.
   absl::optional<int> key_code;
   if (event.IsKeyboardEvent())
     key_code = DynamicTo<KeyboardEvent>(event)->keyCode();
@@ -618,7 +618,7 @@
       name, url, rect, MonotonicTimeToDOMHighResTimeStamp(start_time),
       MonotonicTimeToDOMHighResTimeStamp(load_time), identifier,
       intrinsic_size.width(), intrinsic_size.height(), id, element,
-      PerformanceEntry::GetNavigationId(GetExecutionContext()));
+      PerformanceEntry::GetNavigationId(GetExecutionContext()), DomWindow());
   TRACE_EVENT2("loading", "PerformanceElementTiming", "data",
                entry->ToTracedValue(), "frame",
                ToTraceValue(DomWindow()->GetFrame()));
@@ -657,10 +657,10 @@
   VisibilityStateEntry* entry = MakeGarbageCollected<VisibilityStateEntry>(
       PageHiddenStateString(!is_visible),
       MonotonicTimeToDOMHighResTimeStamp(timestamp),
-      PerformanceEntry::GetNavigationId(
-          GetExecutionContext()));  // Todo(haoliuk): Add WPT for
-                                    // VisibilityStateEntry. See
-                                    // crbug.com/1320878.
+      PerformanceEntry::GetNavigationId(GetExecutionContext()),
+      DomWindow());  // Todo(haoliuk): Add WPT for
+                     // VisibilityStateEntry. See
+                     // crbug.com/1320878.
   if (HasObserverFor(PerformanceEntry::kVisibilityState))
     NotifyObserversOfEntry(*entry);
 
@@ -676,7 +676,7 @@
   }
   SoftNavigationEntry* entry = MakeGarbageCollected<SoftNavigationEntry>(
       name, MonotonicTimeToDOMHighResTimeStamp(timestamp),
-      PerformanceEntry::GetNavigationId(GetExecutionContext()));
+      PerformanceEntry::GetNavigationId(GetExecutionContext()), DomWindow());
 
   if (HasObserverFor(PerformanceEntry::kSoftNavigation)) {
     UseCounter::Count(GetExecutionContext(),
@@ -721,7 +721,7 @@
   auto* entry = MakeGarbageCollected<LargestContentfulPaint>(
       start_timestamp, render_timestamp, paint_size, load_timestamp,
       first_animated_frame_timestamp, id, url, element,
-      PerformanceEntry::GetNavigationId(GetExecutionContext()));
+      PerformanceEntry::GetNavigationId(GetExecutionContext()), DomWindow());
   if (HasObserverFor(PerformanceEntry::kLargestContentfulPaint))
     NotifyObserversOfEntry(*entry);
   AddLargestContentfulPaint(entry);
diff --git a/third_party/blink/renderer/core/timing/window_performance_test.cc b/third_party/blink/renderer/core/timing/window_performance_test.cc
index e8dd5813..daafb98 100644
--- a/third_party/blink/renderer/core/timing/window_performance_test.cc
+++ b/third_party/blink/renderer/core/timing/window_performance_test.cc
@@ -133,8 +133,9 @@
 
   PerformanceEventTiming* CreatePerformanceEventTiming(
       const AtomicString& name) {
-    return PerformanceEventTiming::Create(name, 0.0, 0.0, 0.0, false, nullptr,
-                                          1);
+    return PerformanceEventTiming::Create(
+        name, 0.0, 0.0, 0.0, false, nullptr, 1,
+        LocalDOMWindow::From(GetScriptState()));
   }
 
   LocalFrame* GetFrame() const { return &page_holder_->GetFrame(); }
diff --git a/third_party/blink/renderer/modules/accessibility/ax_node_object.cc b/third_party/blink/renderer/modules/accessibility/ax_node_object.cc
index 52cdecd..4b50ec0 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_node_object.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_node_object.cc
@@ -1880,7 +1880,7 @@
   // kAuto, then set aria-expanded=false when the popover is hidden, and
   // aria-expanded=true when it is showing.
   if (auto* form_control = DynamicTo<HTMLFormControlElement>(element)) {
-    if (auto popover = form_control->popoverTargetElement().element;
+    if (auto popover = form_control->popoverTargetElement().popover;
         popover && popover->PopoverType() == PopoverValueType::kAuto) {
       return popover->popoverOpen() ? kExpandedExpanded : kExpandedCollapsed;
     }
diff --git a/third_party/blink/renderer/platform/graphics/gpu/image_layer_bridge.cc b/third_party/blink/renderer/platform/graphics/gpu/image_layer_bridge.cc
index 444739e..9b9f292e 100644
--- a/third_party/blink/renderer/platform/graphics/gpu/image_layer_bridge.cc
+++ b/third_party/blink/renderer/platform/graphics/gpu/image_layer_bridge.cc
@@ -151,6 +151,18 @@
 
   const bool gpu_compositing = SharedGpuContext::IsGpuCompositingEnabled();
 
+  if (!gpu_compositing) {
+    // Readback if needed and retain the readback in image_ to prevent future
+    // readbacks.
+    // Note: Switching to unaccelerated may change the value of
+    // image_->IsOriginTopLeft(), so it is important to make the switch before
+    // calling IsOriginTopLeft().
+    image_ = image_->MakeUnaccelerated();
+    if (!image_) {
+      return false;
+    }
+  }
+
   const ImageOrientation origin = image_->IsOriginTopLeft()
                                       ? ImageOrientationEnum::kOriginTopLeft
                                       : ImageOrientationEnum::kOriginBottomLeft;
@@ -204,12 +216,6 @@
                               std::move(image_for_compositor));
     *out_release_callback = std::move(func);
   } else {
-    // Readback if needed and retain the readback in image_ to prevent future
-    // readbacks
-    image_ = image_->MakeUnaccelerated();
-    if (!image_)
-      return false;
-
     sk_sp<SkImage> sk_image =
         image_->PaintImageForCurrentFrame().GetSwSkImage();
     if (!sk_image)
diff --git a/third_party/blink/renderer/platform/loader/fetch/resource_error.cc b/third_party/blink/renderer/platform/loader/fetch/resource_error.cc
index 0ce1919..6b6aa02 100644
--- a/third_party/blink/renderer/platform/loader/fetch/resource_error.cc
+++ b/third_party/blink/renderer/platform/loader/fetch/resource_error.cc
@@ -200,8 +200,10 @@
 bool ResourceError::IsUnactionableTrustTokensStatus() const {
   return IsTrustTokenCacheHit() ||
          (error_code_ == net::ERR_TRUST_TOKEN_OPERATION_FAILED &&
-          trust_token_operation_error_ ==
-              network::mojom::TrustTokenOperationStatus::kUnavailable);
+          (trust_token_operation_error_ ==
+               network::mojom::TrustTokenOperationStatus::kUnavailable ||
+           trust_token_operation_error_ ==
+               network::mojom::TrustTokenOperationStatus::kUnauthorized));
 }
 
 bool ResourceError::IsCacheMiss() const {
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index 0f3495e..31c27f94 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -594,7 +594,7 @@
     },
     {
       name: "CrossFramePerformanceTimeline",
-      status: "test",
+      status: "experimental",
       base_feature: "CrossFramePerformanceTimeline",
     },
     {
diff --git a/third_party/blink/renderer/platform/weborigin/kurl.cc b/third_party/blink/renderer/platform/weborigin/kurl.cc
index 5f26de2..df63fbe 100644
--- a/third_party/blink/renderer/platform/weborigin/kurl.cc
+++ b/third_party/blink/renderer/platform/weborigin/kurl.cc
@@ -223,7 +223,10 @@
   return GetString().Left(511) + "..." + GetString().Right(510);
 }
 
-KURL::KURL() : is_valid_(false), protocol_is_in_http_family_(false) {}
+KURL::KURL()
+    : is_valid_(false),
+      protocol_is_in_http_family_(false),
+      has_idna2008_deviation_character_(false) {}
 
 // Initializes with a string representing an absolute URL. No encoding
 // information is specified. This generally happens when a KURL is converted
@@ -239,6 +242,7 @@
     // empty string, which is what Init() will create.
     is_valid_ = false;
     protocol_is_in_http_family_ = false;
+    has_idna2008_deviation_character_ = false;
   }
 }
 
@@ -267,15 +271,21 @@
            bool is_valid)
     : is_valid_(is_valid),
       protocol_is_in_http_family_(false),
+      has_idna2008_deviation_character_(false),
       parsed_(parsed),
       string_(canonical_string) {
   InitProtocolMetadata();
   InitInnerURL();
+  // For URLs with non-ASCII hostnames canonical_string will be in punycode.
+  // We can't check has_idna2008_deviation_character_ without decoding punycode.
+  // here.
 }
 
 KURL::KURL(const KURL& other)
     : is_valid_(other.is_valid_),
       protocol_is_in_http_family_(other.protocol_is_in_http_family_),
+      has_idna2008_deviation_character_(
+          other.has_idna2008_deviation_character_),
       protocol_(other.protocol_),
       parsed_(other.parsed_),
       string_(other.string_) {
@@ -288,6 +298,7 @@
 KURL& KURL::operator=(const KURL& other) {
   is_valid_ = other.is_valid_;
   protocol_is_in_http_family_ = other.protocol_is_in_http_family_;
+  has_idna2008_deviation_character_ = other.has_idna2008_deviation_character_;
   protocol_ = other.protocol_;
   parsed_ = other.parsed_;
   string_ = other.string_;
@@ -322,6 +333,10 @@
   return protocol_is_in_http_family_;
 }
 
+bool KURL::HasIDNA2008DeviationCharacter() const {
+  return has_idna2008_deviation_character_;
+}
+
 bool KURL::HasPath() const {
   // Note that http://www.google.com/" has a path, the path is "/". This can
   // return false only for invalid or nonstandard URLs.
@@ -912,6 +927,15 @@
   InitProtocolMetadata();
   InitInnerURL();
   DCHECK(!::blink::ProtocolIsJavaScript(string_) || ProtocolIsJavaScript());
+
+  // Check for deviation characters in the string. See
+  // https://unicode.org/reports/tr46/#Table_Deviation_Characters
+  has_idna2008_deviation_character_ =
+      base.has_idna2008_deviation_character_ ||
+      relative.Contains(u"\u00DF") ||  // Sharp-s
+      relative.Contains(u"\u03C2") ||  // Greek final sigma
+      relative.Contains(u"\u200D") ||  // Zero width joiner
+      relative.Contains(u"\u200C");    // Zero width non-joiner
 }
 
 void KURL::InitInnerURL() {
diff --git a/third_party/blink/renderer/platform/weborigin/kurl.h b/third_party/blink/renderer/platform/weborigin/kurl.h
index d33bb3f..479b3b5 100644
--- a/third_party/blink/renderer/platform/weborigin/kurl.h
+++ b/third_party/blink/renderer/platform/weborigin/kurl.h
@@ -229,6 +229,8 @@
 
   void WriteIntoTrace(perfetto::TracedValue context) const;
 
+  bool HasIDNA2008DeviationCharacter() const;
+
  private:
   friend struct WTF::HashTraits<blink::KURL>;
 
@@ -253,6 +255,11 @@
 
   bool is_valid_;
   bool protocol_is_in_http_family_;
+  // Set to true if any part of the URL string contains an IDNA 2008 deviation
+  // character. Only used for logging. The hostname is decoded to IDN and
+  // checked for deviation characters again before logging.
+  // TODO(crbug.com/1396475): Remove once Non-Transitional mode is shipped.
+  bool has_idna2008_deviation_character_;
 
   // Keep a separate string for the protocol to avoid copious copies for
   // protocol().
diff --git a/third_party/blink/renderer/platform/weborigin/kurl_test.cc b/third_party/blink/renderer/platform/weborigin/kurl_test.cc
index 283f81d..1bcca14 100644
--- a/third_party/blink/renderer/platform/weborigin/kurl_test.cc
+++ b/third_party/blink/renderer/platform/weborigin/kurl_test.cc
@@ -1080,6 +1080,30 @@
   EXPECT_EQ(gurl.host_piece(), "%25t%EF%BF%BD");
 }
 
+TEST(KURLTest, HasIDNA2008DeviationCharacters) {
+  // èxample.com:
+  EXPECT_FALSE(
+      KURL("http://\xE8xample.com/path").HasIDNA2008DeviationCharacter());
+  // faß.de (contains Sharp-S):
+  EXPECT_TRUE(KURL(u"http://fa\u00df.de/path").HasIDNA2008DeviationCharacter());
+  // βόλος.com (contains Greek Final Sigma):
+  EXPECT_TRUE(KURL(u"http://\u03b2\u03cc\u03bb\u03bf\u03c2.com/path")
+                  .HasIDNA2008DeviationCharacter());
+  // ශ්‍රී.com (contains Zero Width Joiner):
+  EXPECT_TRUE(KURL(u"http://\u0DC1\u0DCA\u200D\u0DBB\u0DD3.com")
+                  .HasIDNA2008DeviationCharacter());
+  // http://نامه\u200cای.com (contains Zero Width Non-Joiner):
+  EXPECT_TRUE(KURL(u"http://\u0646\u0627\u0645\u0647\u200C\u0627\u06CC.com")
+                  .HasIDNA2008DeviationCharacter());
+
+  // Copying the URL from a canonical string presently doesn't copy the boolean.
+  KURL url1(u"http://\u03b2\u03cc\u03bb\u03bf\u03c2.com/path");
+  std::string url_string = url1.GetString().Utf8();
+  KURL url2(AtomicString::FromUTF8(url_string.data(), url_string.length()),
+            url1.GetParsed(), url1.IsValid());
+  EXPECT_FALSE(url2.HasIDNA2008DeviationCharacter());
+}
+
 enum class PortIsValid {
   // The constructor does strict checking. Ports which are considered valid by
   // the constructor are kAlways valid.
diff --git a/third_party/blink/tools/blinkpy/presubmit/audit_non_blink_usage.py b/third_party/blink/tools/blinkpy/presubmit/audit_non_blink_usage.py
index 8d05024..f2454aa 100755
--- a/third_party/blink/tools/blinkpy/presubmit/audit_non_blink_usage.py
+++ b/third_party/blink/tools/blinkpy/presubmit/audit_non_blink_usage.py
@@ -1764,6 +1764,14 @@
     },
     {
         'paths': [
+            'third_party/blink/renderer/core/loader/frame_loader.cc',
+        ],
+        'allowed': [
+            'base::flat_map',
+        ]
+    },
+    {
+        'paths': [
             'third_party/blink/renderer/modules/webdatabase/dom_window_web_database.cc',
         ],
         'allowed': [
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index ee96ab99..731b115 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -2337,9 +2337,27 @@
 crbug.com/876485 fast/performance/performance-measure-null-exception.html [ Failure ]
 
 
-# Unprefixed image-set() not supported
-crbug.com/630597 external/wpt/css/css-images/image-set/image-set-rendering.html [ Failure ]
-crbug.com/630597 external/wpt/css/css-images/image-set/image-set-content-rendering.html [ Failure ]
+# image-set() does not yet support default resolution
+crbug.com/1400898 external/wpt/css/css-images/image-set/image-set-no-res-rendering.html [ Failure ]
+
+# image-set() does not yet support URLs without the url function notation
+crbug.com/1400901 external/wpt/css/css-images/image-set/image-set-no-url-rendering.html [ Failure ]
+
+# image-set() does not yet support gradient images
+crbug.com/1400902 external/wpt/css/css-images/image-set/image-set-linear-gradient-rendering.html [ Failure ]
+crbug.com/1400902 external/wpt/css/css-images/image-set/image-set-radial-gradient-rendering.html [ Failure ]
+
+# image-set() does not yet support all resolution units
+crbug.com/1400903 external/wpt/css/css-images/image-set/image-set-dppx-rendering.html [ Failure ]
+crbug.com/1400903 external/wpt/css/css-images/image-set/image-set-dpi-rendering.html [ Failure ]
+crbug.com/1400903 external/wpt/css/css-images/image-set/image-set-dpi-rendering-2.html [ Failure ]
+
+# image-set() type function not yet supported
+crbug.com/1399341 external/wpt/css/css-images/image-set/image-set-type-rendering.html [ Failure ]
+crbug.com/1399341 external/wpt/css/css-images/image-set/image-set-type-rendering-2.html [ Failure ]
+crbug.com/1399341 external/wpt/css/css-images/image-set/image-set-type-rendering-3.html [ Failure ]
+crbug.com/1399341 external/wpt/css/css-images/image-set/image-set-type-first-match-rendering.html [ Failure ]
+crbug.com/1399341 external/wpt/css/css-images/image-set/image-set-type-skip-unsupported-rendering.html [ Failure ]
 
 # CSS Color 4 interpolations
 crbug.com/1362022 external/wpt/css/css-images/gradient/gradient-eval-004.html [ Failure ]
diff --git a/third_party/blink/web_tests/external/wpt/css/css-contain/content-visibility/content-visibility-auto-state-changed-first-observation.html b/third_party/blink/web_tests/external/wpt/css/css-contain/content-visibility/content-visibility-auto-state-changed-first-observation.html
new file mode 100644
index 0000000..1c51851
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-contain/content-visibility/content-visibility-auto-state-changed-first-observation.html
@@ -0,0 +1,65 @@
+<!doctype HTML>
+<html>
+<meta charset="utf8">
+<title>Content Visibility: ContentVisibilityAutoStateChange event.</title>
+<link rel="author" title="Vladimir Levin" href="mailto:vmpstr@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-contain/#content-visibility">
+<meta name="assert" content="ContentVisibilityAutoStateChange fires once when element is inserted">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<style>
+.spacer {
+  height: 10000px;
+}
+</style>
+
+<div id=topdiv></div>
+<div class=spacer></div>
+<div id=bottomdiv></div>
+
+<script>
+promise_test(t => new Promise(async (resolve, reject) => {
+  await new Promise((waited, _) => {
+    requestAnimationFrame(() => requestAnimationFrame(waited));
+  });
+
+  let observed = false;
+  let div = document.createElement("div");
+  div.addEventListener("contentvisibilityautostatechange", (e) => {
+    if (observed)
+      reject("already observed");
+    if (e.skipped)
+      reject("unexpected skipped");
+    observed = true;
+    // Wait a couple of frames to ensure no other signal comes in
+    requestAnimationFrame(() => requestAnimationFrame(resolve));
+  });
+
+  div.style.contentVisibility = "auto";
+  topdiv.appendChild(div);
+}), "ContentVisibilityAutoStateChange fires once when added (not skipped)");
+
+promise_test(t => new Promise(async (resolve, reject) => {
+  await new Promise((waited, _) => {
+    requestAnimationFrame(() => requestAnimationFrame(waited));
+  });
+
+  let observed = false;
+  let div = document.createElement("div");
+  div.addEventListener("contentvisibilityautostatechange", (e) => {
+    if (observed)
+      reject("already observed");
+    if (!e.skipped)
+      reject("unexpected not skipped");
+    observed = true;
+    // Wait a couple of frames to ensure no other signal comes in
+    requestAnimationFrame(() => requestAnimationFrame(resolve));
+  });
+
+  div.style.contentVisibility = "auto";
+  bottomdiv.appendChild(div);
+}), "ContentVisibilityAutoStateChange fires once when added (skipped)");
+</script>
+
diff --git a/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-content-rendering-ref.html b/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-content-rendering-ref.html
deleted file mode 100644
index 29316bc1..0000000
--- a/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-content-rendering-ref.html
+++ /dev/null
@@ -1,11 +0,0 @@
-<!doctype html>
-<title>CSS Test Reference</title>
-<style>
-  .test {
-    display: inline-block;
-    width: 100px;
-    height: 100px;
-    background: red;
-  }
-</style>
-<div class="test" style="content: url('/images/green.png')" ></div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-content-rendering.html b/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-content-rendering.html
index 9110837..f0622a89 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-content-rendering.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-content-rendering.html
@@ -1,16 +1,15 @@
-<!doctype html>
+<!DOCTYPE html>
 <title>Image set is supported in the content property</title>
 <link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
 <link rel="author" title="Noam Rosenthal" href="mailto:noam@webkit.org">
+<link rel="author" title="Traian Captan" href="mailto:tcaptan@chromium.org">
 <link rel="help" href="https://drafts.csswg.org/css-images-4/#image-set-notation">
 <link rel="help" href="https://drafts.csswg.org/css-content/#content-property">
-<link rel="match"  href="image-set-content-rendering-ref.html">
+<link rel="match"  href="reference/image-set-content-rendering-ref.html">
+<meta name="assert" content="image-set content rendering">
+<script src="resources/image-set-rendering-helper.js"></script>
 <style>
-  .test {
-    display: inline-block;
-    width: 100px;
-    height: 100px;
-    background: red;
+  #test {
+    content: image-set(url("/images/green.png") 1x);
   }
 </style>
-<div class="test" style="content: image-set(url() 2x, url('/images/green.png') 100dpi)" ></div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-content-rendering.html.ini b/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-content-rendering.html.ini
deleted file mode 100644
index f223f93..0000000
--- a/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-content-rendering.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[image-set-content-rendering.html]
-  expected: FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-dpi-rendering-2.html b/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-dpi-rendering-2.html
new file mode 100644
index 0000000..d7bd02e
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-dpi-rendering-2.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<title>Image set dpi rendering</title>
+<link rel="author" title="Noam Rosenthal" href="mailto:noam@webkit.org">
+<link rel="author" title="Traian Captan" href="mailto:tcaptan@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-images-4/#image-set-notation">
+<link rel="match" href="reference/image-set-rendering-ref.html">
+<meta name="assert" content="image-set dpi rendering">
+<script src="resources/image-set-rendering-helper.js"></script>
+<style>
+  #test {
+    background-image: image-set(
+      url("/images/green.png") 96dpi,
+      url("/images/red.png") 100dpi
+    );
+  }
+</style>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-dpi-rendering.html b/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-dpi-rendering.html
new file mode 100644
index 0000000..be18e85f
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-dpi-rendering.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<title>Image set dpi rendering</title>
+<link rel="author" title="Noam Rosenthal" href="mailto:noam@webkit.org">
+<link rel="author" title="Traian Captan" href="mailto:tcaptan@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-images-4/#image-set-notation">
+<link rel="match" href="reference/image-set-rendering-ref.html">
+<meta name="assert" content="image-set dpi rendering">
+<script src="resources/image-set-rendering-helper.js"></script>
+<style>
+  #test {
+    background-image: image-set(url("/images/green.png") 96dpi);
+  }
+</style>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-dppx-rendering.html b/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-dppx-rendering.html
new file mode 100644
index 0000000..1fc2f8c
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-dppx-rendering.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<title>Image set dppx rendering</title>
+<link rel="author" title="Noam Rosenthal" href="mailto:noam@webkit.org">
+<link rel="author" title="Traian Captan" href="mailto:tcaptan@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-images-4/#image-set-notation">
+<link rel="match" href="reference/image-set-rendering-ref.html">
+<meta name="assert" content="image-set dppx rendering">
+<script src="resources/image-set-rendering-helper.js"></script>
+<style>
+  #test {
+    background-image: image-set(url("/images/green.png") 1dppx);
+  }
+</style>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-empty-url-rendering.html b/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-empty-url-rendering.html
new file mode 100644
index 0000000..0286d737
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-empty-url-rendering.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<title>Image set empty url rendering</title>
+<link rel="author" title="Noam Rosenthal" href="mailto:noam@webkit.org">
+<link rel="author" title="Traian Captan" href="mailto:tcaptan@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-images-4/#image-set-notation">
+<link rel="match" href="reference/image-set-rendering-ref.html">
+<meta name="assert" content="image-set rendering when 2x url is empty">
+<script src="resources/image-set-rendering-helper.js"></script>
+<style>
+  #test {
+    background-image: image-set(
+      url("/images/green.png") 1x,
+      url("") 2x
+    );
+  }
+</style>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-first-match-rendering.html b/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-first-match-rendering.html
new file mode 100644
index 0000000..4d5b9d7d
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-first-match-rendering.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<title>Image set type first match rendering</title>
+<link rel="author" title="Noam Rosenthal" href="mailto:noam@webkit.org">
+<link rel="author" title="Traian Captan" href="mailto:tcaptan@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-images-4/#image-set-notation">
+<link rel="match" href="reference/image-set-rendering-ref.html">
+<meta name="assert" content="image-set rendering picks first valid match">
+<script src="resources/image-set-rendering-helper.js"></script>
+<style>
+  #test {
+    background-image: image-set(
+      url("/images/green.png") 1x,
+      url("/images/red.png") 1x
+    );
+  }
+</style>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-invalid-resolution-rendering-2.html b/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-invalid-resolution-rendering-2.html
new file mode 100644
index 0000000..88917e1
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-invalid-resolution-rendering-2.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<title>Image set invalid resolution rendering</title>
+<link rel="author" title="Noam Rosenthal" href="mailto:noam@webkit.org">
+<link rel="author" title="Traian Captan" href="mailto:tcaptan@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-images-4/#image-set-notation">
+<link rel="match" href="reference/image-set-rendering-ref.html">
+<meta name="assert" content="image-set rendering when resolution is invalid">
+<script src="resources/image-set-rendering-helper.js"></script>
+<style>
+  #test {
+    background-image: url("/images/green.png");
+    background-image: image-set(
+      url("/images/red.png") 0x,
+      url("/images/red.png") 2x
+    );
+  }
+</style>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-invalid-resolution-rendering.html b/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-invalid-resolution-rendering.html
new file mode 100644
index 0000000..0cdcf601
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-invalid-resolution-rendering.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<title>Image set invalid resolution rendering</title>
+<link rel="author" title="Noam Rosenthal" href="mailto:noam@webkit.org">
+<link rel="author" title="Traian Captan" href="mailto:tcaptan@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-images-4/#image-set-notation">
+<link rel="match" href="reference/image-set-rendering-ref.html">
+<meta name="assert" content="image-set rendering when resolution is invalid">
+<script src="resources/image-set-rendering-helper.js"></script>
+<style>
+  #test {
+    background-image: url("/images/green.png");
+    background-image: image-set(url("/images/red.png") 0x);
+  }
+</style>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-linear-gradient-rendering.html b/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-linear-gradient-rendering.html
new file mode 100644
index 0000000..62f41a74
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-linear-gradient-rendering.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<title>Image set linear-gradient rendering</title>
+<link rel="author" title="Noam Rosenthal" href="mailto:noam@webkit.org">
+<link rel="author" title="Traian Captan" href="mailto:tcaptan@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-images-4/#image-set-notation">
+<link rel="match" href="reference/image-set-linear-gradient-rendering-ref.html">
+<meta name="assert" content="image-set linear-gradient rendering">
+<script src="resources/image-set-rendering-helper.js"></script>
+<style>
+  #test {
+    background-image: image-set(linear-gradient(green, lightgreen) 1x);
+  }
+</style>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-no-res-rendering.html b/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-no-res-rendering.html
new file mode 100644
index 0000000..927d4c67
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-no-res-rendering.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<title>Image set no resolution rendering</title>
+<link rel="author" title="Noam Rosenthal" href="mailto:noam@webkit.org">
+<link rel="author" title="Traian Captan" href="mailto:tcaptan@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-images-4/#image-set-notation">
+<link rel="match" href="reference/image-set-rendering-ref.html">
+<meta name="assert" content="image-set rendering with no resolution defined">
+<script src="resources/image-set-rendering-helper.js"></script>
+<style>
+  #test {
+    background-image: image-set(url("/images/green.png"));
+  }
+</style>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-no-url-rendering.html b/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-no-url-rendering.html
new file mode 100644
index 0000000..7af722d
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-no-url-rendering.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<title>Image set no url rendering</title>
+<link rel="author" title="Noam Rosenthal" href="mailto:noam@webkit.org">
+<link rel="author" title="Traian Captan" href="mailto:tcaptan@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-images-4/#image-set-notation">
+<link rel="match" href="reference/image-set-rendering-ref.html">
+<meta name="assert" content="image-set without url functional notation rendering">
+<script src="resources/image-set-rendering-helper.js"></script>
+<style>
+  #test {
+    background-image: image-set("/images/green.png" 1x);
+  }
+</style>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-radial-gradient-rendering.html b/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-radial-gradient-rendering.html
new file mode 100644
index 0000000..7bf950a
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-radial-gradient-rendering.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<title>Image set radial-gradient rendering</title>
+<link rel="author" title="Noam Rosenthal" href="mailto:noam@webkit.org">
+<link rel="author" title="Traian Captan" href="mailto:tcaptan@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-images-4/#image-set-notation">
+<link rel="match" href="reference/image-set-radial-gradient-rendering-ref.html">
+<meta name="assert" content="image-set radial-gradient rendering">
+<script src="resources/image-set-rendering-helper.js"></script>
+<style>
+  #test {
+    background-image: image-set(radial-gradient(green, lightgreen) 1x);
+  }
+</style>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-rendering-2.html b/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-rendering-2.html
new file mode 100644
index 0000000..a672455
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-rendering-2.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<title>Image set rendering</title>
+<link rel="author" title="Noam Rosenthal" href="mailto:noam@webkit.org">
+<link rel="author" title="Traian Captan" href="mailto:tcaptan@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-images-4/#image-set-notation">
+<link rel="match" href="reference/image-set-rendering-ref.html">
+<meta name="assert" content="image-set rendering">
+<script src="resources/image-set-rendering-helper.js"></script>
+<style>
+  #test {
+    background-image: image-set(
+      url("/images/green.png") 1x,
+      url("/images/red.png") 2x
+    );
+  }
+</style>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-rendering-ref.html b/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-rendering-ref.html
deleted file mode 100644
index 081766c..0000000
--- a/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-rendering-ref.html
+++ /dev/null
@@ -1,44 +0,0 @@
-<!DOCTYPE html>
-<html>
-    <head>
-        <title>Image set rendering</title>
-        <link rel="author" title="Noam Rosenthal" href="mailto:noam@webkit.org">
-        <link rel="help" href="https://drafts.csswg.org/css-images-4/#image-set-notation">
-        <meta name="assert" content="image-set rendering">
-        <style>
-            .test {
-                display: inline-block;
-                width: 100px;
-                height: 100px;
-            }
-
-            main {
-                width: 500px;
-            }
-        </style>
-    </head>
-    <body>
-        <p>All images below should be lime or a lime-green gradient when devicePixelRatio is 1 - no red!.</p>
-        <main>
-            <div class="test" style="background-image: url(/images/green.png)"></div>
-            <div class="test" style="background-image: linear-gradient(green, lightgreen)"></div>
-            <div class="test" style="background-image: url('/images/green.png')" ></div>
-            <div class="test" style="background-image: radial-gradient(green, lightgreen)" ></div>
-            <div class="test" style="background-image: linear-gradient(green, lightgreen)" ></div>
-            <div class="test" style="background-image: url('/images/green.png')" ></div>
-            <div class="test" style="background-image: url('/images/green.png')" ></div>
-            <div class="test" style="background-image: url('/images/green.png')" ></div>
-            <div class="test" style="background-image: url('/images/green.png')"></div>
-            <div class="test" style="background-color: lime"></div>
-            <div class="test" style="background-color: lime"></div>
-            <div class="test" style="background-color: lime"></div>
-            <div class="test" style="background-image: url(/images/green.png)"></div>
-            <div class="test" style="background-image: url(/images/green.png)"></div>
-            <div class="test" style="background-image: url(/images/green.png)"></div>
-            <div class="test" style="background-image: url(/images/red.png)"></div>
-            <div class="test" style="background-image: url(/images/green.png)"></div>
-            <div class="test" style="background-image: url(/images/red.png)"></div>
-            <div class="test" style="background-image: url(/images/green.png)"></div>
-        </main>
-    </body>
-</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-rendering.html b/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-rendering.html
index eb3ece96..ce4a900 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-rendering.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-rendering.html
@@ -1,46 +1,13 @@
 <!DOCTYPE html>
-<html>
-    <head>
-        <title>Image set rendering</title>
-        <link rel="author" title="Noam Rosenthal" href="mailto:noam@webkit.org">
-        <link rel="help" href="https://drafts.csswg.org/css-images-4/#image-set-notation">
-        <meta name="assert" content="image-set rendering">
-        <link rel="match"  href="image-set-rendering-ref.html">
-        <style>
-            .test {
-                display: inline-block;
-                width: 100px;
-                height: 100px;
-                background: red;
-            }
-
-            main {
-                width: 500px;
-            }
-        </style>
-    </head>
-    <body>
-        <p>All images below should be lime or a lime-green gradient when devicePixelRatio is 1 - no red!.</p>
-        <main>
-            <div class="test" style="background-image: image-set('/images/green.png' 1x)"></div>
-            <div class="test" style="background-image: image-set('/images/red.png' 20dpi, linear-gradient(green, lightgreen) 2x)"></div>
-            <div class="test" style="background-image: image-set(url('/images/green.png') 1x, url('/images/red.png') 2x)" ></div>
-            <div class="test" style="background-image: image-set(radial-gradient(green, lightgreen) 1x, linear-gradient(yellow, red) 2x)" ></div>
-            <div class="test" style="background-image: image-set(linear-gradient(green, lightgreen) 96dpi, '/images/red.png' 100dpi)" ></div>
-            <div class="test" style="background-image: image-set(radial-gradient(red, yellow) 1.3x, url('/images/green.png') 100dpi, linear-gradient(red, yellow) 2.5x)" ></div>
-            <div class="test" style="background-image: image-set('/images/red.png' 2x, url('/images/green.png') 1x, url('/images/yellow.png') 300dpi)" ></div>
-            <div class="test" style="background-color: red; background-image: image-set('' 2x, url('/images/green.png') 1x)" ></div>
-            <div class="test" style="background-image: image-set('/images/green.png' 1x)"></div>
-            <div class="test" style="background-color: red; background-image: image-set('/images/green.png')" ></div>
-            <div class="test" style="background-color: lime; background-image: image-set('/images/red.png' 0x)" ></div>
-            <div class="test" style="background-color: lime; background-image: image-set('/images/red.png' 0x, url('/images/red.png') 2x)" ></div>
-            <div class="test" style="background-image: image-set('/images/green.png' type('image/png') 1x)"></div>
-            <div class="test" style="background-image: image-set('/images/green.png' 1x type('image/png'))"></div>
-            <div class="test" style="background-image: image-set('/images/green.png' type('image/png'))"></div>
-            <div class="test" style="background-image: image-set('/images/red.png' type('image/png'), '/images/green.png' type('image/png'))"></div>
-            <div class="test" style="background-image: image-set('/images/red.png' type('image/unsupported'), '/images/green.png' type('image/png'))"></div>
-            <div class="test" style="background-image: image-set('/images/red.png' type('image/unsupported'), '/images/green.png' type('image/unsupported'))"></div>
-            <div class="test" style="background-image: image-set('/images/red.png' 2x type('image/png'), '/images/green.png' 1x type('image/png'))"></div>
-        </main>
-    </body>
-</html>
+<title>Image set rendering</title>
+<link rel="author" title="Noam Rosenthal" href="mailto:noam@webkit.org">
+<link rel="author" title="Traian Captan" href="mailto:tcaptan@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-images-4/#image-set-notation">
+<link rel="match" href="reference/image-set-rendering-ref.html">
+<meta name="assert" content="image-set rendering">
+<script src="resources/image-set-rendering-helper.js"></script>
+<style>
+  #test {
+    background-image: image-set(url("/images/green.png") 1x);
+  }
+</style>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-rendering.html.ini b/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-rendering.html.ini
deleted file mode 100644
index cac31096..0000000
--- a/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-rendering.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[image-set-rendering.html]
-  expected: FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-type-first-match-rendering.html b/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-type-first-match-rendering.html
new file mode 100644
index 0000000..1283d22
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-type-first-match-rendering.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<title>Image set type first match rendering</title>
+<link rel="author" title="Noam Rosenthal" href="mailto:noam@webkit.org">
+<link rel="author" title="Traian Captan" href="mailto:tcaptan@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-images-4/#image-set-notation">
+<link rel="match" href="reference/image-set-rendering-ref.html">
+<meta name="assert" content="image-set rendering with type picks first valid match">
+<script src="resources/image-set-rendering-helper.js"></script>
+<style>
+  #test {
+    background-image: image-set(
+      url("/images/green.png") 1x type('image/png'),
+      url("/images/red.png") 1x type('image/png')
+    );
+  }
+</style>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-type-rendering-2.html b/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-type-rendering-2.html
new file mode 100644
index 0000000..897118ac
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-type-rendering-2.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<title>Image set type rendering</title>
+<link rel="author" title="Noam Rosenthal" href="mailto:noam@webkit.org">
+<link rel="author" title="Traian Captan" href="mailto:tcaptan@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-images-4/#image-set-notation">
+<link rel="match" href="reference/image-set-rendering-ref.html">
+<meta name="assert" content="image-set rendering for images with same type but different resolutions">
+<script src="resources/image-set-rendering-helper.js"></script>
+<style>
+  #test {
+    background-image: image-set(
+      url("/images/green.png") type('image/png') 1x,
+      url("/images/red.png") type('image/png') 2x
+    );
+  }
+</style>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-type-rendering-3.html b/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-type-rendering-3.html
new file mode 100644
index 0000000..51dbe2c
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-type-rendering-3.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<title>Image set type rendering</title>
+<link rel="author" title="Noam Rosenthal" href="mailto:noam@webkit.org">
+<link rel="author" title="Traian Captan" href="mailto:tcaptan@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-images-4/#image-set-notation">
+<link rel="match" href="reference/image-set-rendering-ref.html">
+<meta name="assert" content="image-set rendering with type before resolution">
+<script src="resources/image-set-rendering-helper.js"></script>
+<style>
+  #test {
+    background-image: image-set(url("/images/green.png") type('image/png') 1x);
+  }
+</style>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-type-rendering.html b/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-type-rendering.html
new file mode 100644
index 0000000..16055db
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-type-rendering.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<title>Image set type rendering</title>
+<link rel="author" title="Noam Rosenthal" href="mailto:noam@webkit.org">
+<link rel="author" title="Traian Captan" href="mailto:tcaptan@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-images-4/#image-set-notation">
+<link rel="match" href="reference/image-set-rendering-ref.html">
+<meta name="assert" content="image-set rendering with type">
+<script src="resources/image-set-rendering-helper.js"></script>
+<style>
+  #test {
+    background-image: image-set(url("/images/green.png") 1x type('image/png'));
+  }
+</style>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-type-skip-unsupported-rendering.html b/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-type-skip-unsupported-rendering.html
new file mode 100644
index 0000000..e555633c
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-type-skip-unsupported-rendering.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<title>Image set type skip unsupported rendering</title>
+<link rel="author" title="Noam Rosenthal" href="mailto:noam@webkit.org">
+<link rel="author" title="Traian Captan" href="mailto:tcaptan@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-images-4/#image-set-notation">
+<link rel="match" href="reference/image-set-rendering-ref.html">
+<meta name="assert" content="image-set rendering with type skips unsupported type">
+<script src="resources/image-set-rendering-helper.js"></script>
+<style>
+  #test {
+    background-image: image-set(
+      url("/images/red.png") 1x type('image/unsupported'),
+      url("/images/green.png") 1x type('image/png')
+    );
+  }
+</style>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-type-unsupported-rendering-2.html b/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-type-unsupported-rendering-2.html
new file mode 100644
index 0000000..3f9fcd2
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-type-unsupported-rendering-2.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<title>Image set type rendering</title>
+<link rel="author" title="Noam Rosenthal" href="mailto:noam@webkit.org">
+<link rel="author" title="Traian Captan" href="mailto:tcaptan@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-images-4/#image-set-notation">
+<link rel="match" href="reference/image-set-rendering-ref.html">
+<meta name="assert" content="image-set rendering with all unsupported types">
+<!--
+Spec definition:
+"An image-set() function contains a list of one or more <image-set-option>s,
+  and must select only one of them to determine what image it will represent:
+  First, remove any <image-set-option>s from the list that specify
+  an unknown or unsupported MIME type in their type() value."
+
+If all the values in the image set are of an unsupported type,
+the set should be empty.
+-->
+<script src="resources/image-set-rendering-helper.js"></script>
+<style>
+  #test {
+    background-image: url("/images/green.png");
+    background-image: image-set(
+      url("/images/red.png") 1x type('image/unsupported'),
+      url("/images/red.png") 1x type('image/unsupported')
+    );
+  }
+</style>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-type-unsupported-rendering.html b/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-type-unsupported-rendering.html
new file mode 100644
index 0000000..6e9e56a
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-type-unsupported-rendering.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<title>Image set type rendering</title>
+<link rel="author" title="Noam Rosenthal" href="mailto:noam@webkit.org">
+<link rel="author" title="Traian Captan" href="mailto:tcaptan@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-images-4/#image-set-notation">
+<link rel="match" href="reference/image-set-rendering-ref.html">
+<meta name="assert" content="image-set rendering with unsupported type">
+<!--
+Spec definition:
+"An image-set() function contains a list of one or more <image-set-option>s,
+  and must select only one of them to determine what image it will represent:
+  First, remove any <image-set-option>s from the list that specify
+  an unknown or unsupported MIME type in their type() value."
+
+If all the values in the image set are of an unsupported type,
+the set should be empty.
+-->
+<script src="resources/image-set-rendering-helper.js"></script>
+<style>
+  #test {
+    background-image: url("/images/green.png");
+    background-image: image-set(url("/images/red.png") 1x type('image/unsupported'));
+  }
+</style>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-unordered-res-rendering.html b/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-unordered-res-rendering.html
new file mode 100644
index 0000000..c1063b7
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-images/image-set/image-set-unordered-res-rendering.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<title>Image set type unordered resolutions rendering</title>
+<link rel="author" title="Noam Rosenthal" href="mailto:noam@webkit.org">
+<link rel="author" title="Traian Captan" href="mailto:tcaptan@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-images-4/#image-set-notation">
+<link rel="match" href="reference/image-set-rendering-ref.html">
+<meta name="assert" content="image-set rendering when resolutions are unordered">
+<script src="resources/image-set-rendering-helper.js"></script>
+<style>
+  #test {
+    background-image: image-set(
+      url("/images/red.png") 2x,
+      url("/images/green.png") 1x
+    );
+  }
+</style>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-images/image-set/reference/image-set-content-rendering-ref.html b/third_party/blink/web_tests/external/wpt/css/css-images/image-set/reference/image-set-content-rendering-ref.html
new file mode 100644
index 0000000..c1ef6d4
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-images/image-set/reference/image-set-content-rendering-ref.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<title>Image set is supported in the content property</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="author" title="Noam Rosenthal" href="mailto:noam@webkit.org">
+<link rel="author" title="Traian Captan" href="mailto:tcaptan@chromium.org">
+<style>
+  #test {
+    content: url("/images/green.png");
+  }
+</style>
+<script src="../resources/image-set-rendering-helper.js"></script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-images/image-set/reference/image-set-linear-gradient-rendering-ref.html b/third_party/blink/web_tests/external/wpt/css/css-images/image-set/reference/image-set-linear-gradient-rendering-ref.html
new file mode 100644
index 0000000..272bab9
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-images/image-set/reference/image-set-linear-gradient-rendering-ref.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<title>Image set linear-gradient rendering</title>
+<link rel="author" title="Noam Rosenthal" href="mailto:noam@webkit.org">
+<link rel="author" title="Traian Captan" href="mailto:tcaptan@chromium.org">
+<style>
+  #test {
+    background-image: linear-gradient(green, lightgreen);
+  }
+</style>
+<script src="../resources/image-set-rendering-helper.js"></script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-images/image-set/reference/image-set-radial-gradient-rendering-ref.html b/third_party/blink/web_tests/external/wpt/css/css-images/image-set/reference/image-set-radial-gradient-rendering-ref.html
new file mode 100644
index 0000000..f5545af
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-images/image-set/reference/image-set-radial-gradient-rendering-ref.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<title>Image set radial-gradient rendering</title>
+<link rel="author" title="Noam Rosenthal" href="mailto:noam@webkit.org">
+<link rel="author" title="Traian Captan" href="mailto:tcaptan@chromium.org">
+<style>
+  #test {
+    background-image: radial-gradient(green, lightgreen);
+  }
+</style>
+<script src="../resources/image-set-rendering-helper.js"></script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-images/image-set/reference/image-set-rendering-ref.html b/third_party/blink/web_tests/external/wpt/css/css-images/image-set/reference/image-set-rendering-ref.html
new file mode 100644
index 0000000..2ca6b33
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-images/image-set/reference/image-set-rendering-ref.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<title>Image set rendering</title>
+<link rel="author" title="Noam Rosenthal" href="mailto:noam@webkit.org">
+<link rel="author" title="Traian Captan" href="mailto:tcaptan@chromium.org">
+<style>
+  #test {
+    background-image: url("/images/green.png");
+  }
+</style>
+<script src="../resources/image-set-rendering-helper.js"></script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-images/image-set/resources/image-set-rendering-helper.js b/third_party/blink/web_tests/external/wpt/css/css-images/image-set/resources/image-set-rendering-helper.js
new file mode 100644
index 0000000..7dbd50a
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-images/image-set/resources/image-set-rendering-helper.js
@@ -0,0 +1,27 @@
+function setupTest() {
+  createPassingNotice();
+  createTestDiv();
+}
+
+function createPassingNotice() {
+  const notice = document.createElement('p');
+
+  notice.textContent =
+      'Test passes if the image below is green when devicePixelRatio is 1, not red.';
+
+  document.body.appendChild(notice);
+}
+
+function createTestDiv() {
+  const testDiv = document.createElement('div');
+
+  testDiv.id = 'test';
+
+  testDiv.style.width = '100px';
+  testDiv.style.height = '100px';
+  testDiv.style.backgroundColor = 'red';
+
+  document.body.appendChild(testDiv);
+}
+
+window.onload = setupTest;
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/popovers/popover-light-dismiss.tentative.html b/third_party/blink/web_tests/external/wpt/html/semantics/popovers/popover-light-dismiss.tentative.html
index bdd0d12..83573c86 100644
--- a/third_party/blink/web_tests/external/wpt/html/semantics/popovers/popover-light-dismiss.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/popovers/popover-light-dismiss.tentative.html
@@ -242,13 +242,9 @@
     popover1.showPopover();
     assert_true(popover1.matches(':open'));
     await waitForRender();
-    p1HideCount = popover1HideCount;
     await clickOn(popover1anchor);
-    assert_true(popover1.matches(':open'),'popover1 not open');
-    assert_equals(popover1HideCount,p1HideCount);
-    popover1.hidePopover(); // Cleanup
-    assert_false(popover1.matches(':open'));
-  },'Clicking on anchor element (that isn\'t an invoking element) shouldn\'t close its popover');
+    assert_false(popover1.matches(':open'),'popover1 should close');
+  },'Clicking on anchor element (that isn\'t an invoking element) shouldn\'t prevent its popover from being closed');
 
   promise_test(async () => {
     popover1.showPopover();
@@ -273,9 +269,9 @@
   <div popover id=p4>Inside popover 4</div>
 </button>
 <div popover id=p3>Inside popover 3</div>
-<button id=b4 popovertoggletarget=p3>Popover 3 - button 4
-  <div popover id=p5>Inside popover 5</div>
-</button>
+<div popover id=p5>Inside popover 5
+  <button popovertoggletarget=p3>Popover 3 - button 4 - unused</button>
+</div>
 <style>
   #p3 {top:100px;}
   #p4 {top:200px;}
@@ -291,24 +287,20 @@
     assert_true(popover3.matches(':open'),'invoking element should open popover');
     popover4.showPopover();
     assert_true(popover4.matches(':open'));
-    assert_true(popover3.matches(':open'));
-    popover3.hidePopover(); // Cleanup
-    assert_false(popover3.matches(':open'));
+    assert_false(popover3.matches(':open'),'popover3 is unrelated to popover4');
+    popover4.hidePopover(); // Cleanup
     assert_false(popover4.matches(':open'));
-  },'An invoking element should be part of the ancestor chain');
+  },'A popover inside an invoking element doesn\'t participate in that invoker\'s ancestor chain');
 
   promise_test(async () => {
-    await clickOn(button3);
-    assert_true(popover3.matches(':open'));
-    assert_false(popover4.matches(':open'));
-    assert_false(popover5.matches(':open'));
     popover5.showPopover();
-    assert_true(popover3.matches(':open'));
-    assert_false(popover4.matches(':open'));
     assert_true(popover5.matches(':open'));
-    popover3.hidePopover();
     assert_false(popover3.matches(':open'));
-    assert_false(popover4.matches(':open'));
+    popover3.showPopover();
+    assert_true(popover3.matches(':open'));
+    assert_true(popover5.matches(':open'));
+    popover5.hidePopover();
+    assert_false(popover3.matches(':open'));
     assert_false(popover5.matches(':open'));
   },'An invoking element that was not used to invoke the popover can still be part of the ancestor chain');
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/performance-timeline/tentative/performance-entry-source.html b/third_party/blink/web_tests/external/wpt/performance-timeline/tentative/performance-entry-source.html
new file mode 100644
index 0000000..5e51ddb
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/performance-timeline/tentative/performance-entry-source.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<head>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+</body>
+<script>
+promise_test(() => {
+    return new Promise(resolve => {
+        const navigationEntries = performance.getEntriesByType("navigation")
+        const parentEntry = navigationEntries[0]
+
+        // Parent NavigationTiming source is current window.
+        assert_equals(parentEntry.source, window)
+
+        // Create child iframe.
+        const childFrame = document.createElement('iframe')
+        childFrame.src = "../resources/child-frame.html"
+        document.body.appendChild(childFrame)
+
+        childFrame.addEventListener('load', () => {
+            const markedEntries = performance.getEntriesByName("entry-name", undefined, true)
+            const childEntry = markedEntries[0]
+
+            // Child PerformanceMark source is the child's Window.
+            assert_equals(childEntry.source, childFrame.contentWindow)
+
+            resolve()
+        })
+    })
+}, "PerformanceEntry source is equal to its respective Window")
+</script>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/fast/loader/resource-with-idna-deviation-char-in-hostname-expected.txt b/third_party/blink/web_tests/fast/loader/resource-with-idna-deviation-char-in-hostname-expected.txt
new file mode 100644
index 0000000..b96e97a
--- /dev/null
+++ b/third_party/blink/web_tests/fast/loader/resource-with-idna-deviation-char-in-hostname-expected.txt
@@ -0,0 +1,9 @@
+CONSOLE WARNING: The resource at http://faß.test/image.jpg contains IDNA Deviation Characters. The hostname for this URL (faß.test) might point to a different IP address after https://chromestatus.com/feature/5105856067141632. Make sure you are using the correct host name.
+CONSOLE WARNING: The resource at http://βόλος.test/image.jpg contains IDNA Deviation Characters. The hostname for this URL (βόλος.test) might point to a different IP address after https://chromestatus.com/feature/5105856067141632. Make sure you are using the correct host name.
+CONSOLE WARNING: The resource at http://ශ්‍රී.test/image.jpg contains IDNA Deviation Characters. The hostname for this URL (ශ්‍රී.test) might point to a different IP address after https://chromestatus.com/feature/5105856067141632. Make sure you are using the correct host name.
+CONSOLE WARNING: The resource at http://نامه‌ای.test/image.jpg contains IDNA Deviation Characters. The hostname for this URL (نامه‌ای.test) might point to a different IP address after https://chromestatus.com/feature/5105856067141632. Make sure you are using the correct host name.
+CONSOLE WARNING: The resource at http://faß.test/image.jpg contains IDNA Deviation Characters. The hostname for this URL (faß.test) might point to a different IP address after https://chromestatus.com/feature/5105856067141632. Make sure you are using the correct host name.
+CONSOLE WARNING: The resource at http://βόλος.test/image.jpg contains IDNA Deviation Characters. The hostname for this URL (βόλος.test) might point to a different IP address after https://chromestatus.com/feature/5105856067141632. Make sure you are using the correct host name.
+CONSOLE WARNING: The resource at http://ශ්‍රී.test/image.jpg contains IDNA Deviation Characters. The hostname for this URL (ශ්‍රී.test) might point to a different IP address after https://chromestatus.com/feature/5105856067141632. Make sure you are using the correct host name.
+CONSOLE WARNING: The resource at http://نامه‌ای.test/image.jpg contains IDNA Deviation Characters. The hostname for this URL (نامه‌ای.test) might point to a different IP address after https://chromestatus.com/feature/5105856067141632. Make sure you are using the correct host name.
+     t
diff --git a/third_party/blink/web_tests/fast/loader/resource-with-idna-deviation-char-in-hostname-iframe-expected.txt b/third_party/blink/web_tests/fast/loader/resource-with-idna-deviation-char-in-hostname-iframe-expected.txt
new file mode 100644
index 0000000..85e1ec21
--- /dev/null
+++ b/third_party/blink/web_tests/fast/loader/resource-with-idna-deviation-char-in-hostname-iframe-expected.txt
@@ -0,0 +1,5 @@
+CONSOLE WARNING: The resource at http://faß.test/ contains IDNA Deviation Characters. The hostname for this URL (faß.test) might point to a different IP address after https://chromestatus.com/feature/5105856067141632. Make sure you are using the correct host name.
+CONSOLE WARNING: The resource at http://βόλος.test/ contains IDNA Deviation Characters. The hostname for this URL (βόλος.test) might point to a different IP address after https://chromestatus.com/feature/5105856067141632. Make sure you are using the correct host name.
+CONSOLE WARNING: The resource at http://ශ්‍රී.test/ contains IDNA Deviation Characters. The hostname for this URL (ශ්‍රී.test) might point to a different IP address after https://chromestatus.com/feature/5105856067141632. Make sure you are using the correct host name.
+CONSOLE WARNING: The resource at http://نامه‌ای.test/ contains IDNA Deviation Characters. The hostname for this URL (نامه‌ای.test) might point to a different IP address after https://chromestatus.com/feature/5105856067141632. Make sure you are using the correct host name.
+    
diff --git a/third_party/blink/web_tests/fast/loader/resource-with-idna-deviation-char-in-hostname-iframe.html b/third_party/blink/web_tests/fast/loader/resource-with-idna-deviation-char-in-hostname-iframe.html
new file mode 100644
index 0000000..57216ae
--- /dev/null
+++ b/third_party/blink/web_tests/fast/loader/resource-with-idna-deviation-char-in-hostname-iframe.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+if (window.testRunner)
+    testRunner.dumpAsText();
+</script>
+</head>
+<body>
+<!-- Frame with a non-ASCII hostname -->
+<iframe src="http://èxample.test/"></iframe>
+
+<!-- Frame with a hostname containing IDNA 2008 deviation character (Sharp-s) -->
+<iframe src="http://faß.test"></iframe>
+
+<!-- Frame with a hostname containing IDNA 2008 deviation character (Greek Final Sigma). -->
+<iframe src="http://βόλος.test"></iframe>
+
+<!-- Frame with a hostname containing IDNA 2008 deviation character (Zero Width Joiner) -->
+<iframe src="http://ශ්‍රී.test"></iframe>
+
+<!-- Frame with a hostname containing IDNA 2008 deviation character (Zero Width Non-Joiner) -->
+<iframe src="http://نامه‌ای.test"></iframe>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/fast/loader/resource-with-idna-deviation-char-in-hostname.html b/third_party/blink/web_tests/fast/loader/resource-with-idna-deviation-char-in-hostname.html
new file mode 100644
index 0000000..771cb48
--- /dev/null
+++ b/third_party/blink/web_tests/fast/loader/resource-with-idna-deviation-char-in-hostname.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+if (window.testRunner)
+    testRunner.dumpAsText();
+</script>
+</head>
+<body>
+<!-- Image with a non-ASCII hostname -->
+<img src="http://èxample.test/image.jpg">
+
+<!-- Image with a hostname containing IDNA 2008 deviation character (Sharp-s) -->
+<img src="http://faß.test/image.jpg">
+
+<!-- Image with a hostname containing IDNA 2008 deviation character (Greek Final Sigma). -->
+<img src="http://βόλος.test/image.jpg">
+
+<!-- Image with a hostname containing IDNA 2008 deviation character (Zero Width Joiner) -->
+<img src="http://ශ්‍රී.test/image.jpg">
+
+<!-- Image with a hostname containing IDNA 2008 deviation character (Zero Width Non-Joiner) -->
+<img src="http://نامه‌ای.test/image.jpg">
+
+</body>
+</html>t
\ No newline at end of file
diff --git a/third_party/blink/web_tests/fast/loader/resource-with-idna-deviation-char-in-path-expected.txt b/third_party/blink/web_tests/fast/loader/resource-with-idna-deviation-char-in-path-expected.txt
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/third_party/blink/web_tests/fast/loader/resource-with-idna-deviation-char-in-path-expected.txt
@@ -0,0 +1 @@
+
diff --git a/third_party/blink/web_tests/fast/loader/resource-with-idna-deviation-char-in-path.html b/third_party/blink/web_tests/fast/loader/resource-with-idna-deviation-char-in-path.html
new file mode 100644
index 0000000..25467e39
--- /dev/null
+++ b/third_party/blink/web_tests/fast/loader/resource-with-idna-deviation-char-in-path.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+if (window.testRunner)
+    testRunner.dumpAsText();
+</script>
+</head>
+<body>
+<!-- An image containing IDNA 2008 deviation character in the path. -->
+<img src="http://example.test/faß.jpg">
+</body>
+</html>
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 7858e2ca..3cbd744 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
@@ -6616,6 +6616,7 @@
     getter entryType
     getter name
     getter navigationId
+    getter source
     getter startTime
     method constructor
     method toJSON
diff --git a/tools/mb/mb_config.pyl b/tools/mb/mb_config.pyl
index 0e3c9d21..3f9cb80 100644
--- a/tools/mb/mb_config.pyl
+++ b/tools/mb/mb_config.pyl
@@ -443,6 +443,7 @@
       'linux-perfetto-rel': 'perfetto_release_bot_reclient',
       'linux-rel-no-external-ip': 'gpu_tests_release_bot_do_typecheck_reclient',
       'linux-upload-perfetto': 'release_bot_perfetto_zlib_reclient',
+      'linux-wpt-content-shell-asan-fyi-rel': 'asan_lsan_release_trybot_reclient',
       'linux-wpt-content-shell-fyi-rel': 'release_trybot_minimal_symbols_reclient',
       'linux-wpt-fyi-rel': 'release_trybot_minimal_symbols_reclient',
       'linux-wpt-identity-fyi-rel': 'release_bot_minimal_symbols_reclient',
diff --git a/tools/mb/mb_config_expectations/chromium.fyi.json b/tools/mb/mb_config_expectations/chromium.fyi.json
index 50b79bee..6b4bc5e 100644
--- a/tools/mb/mb_config_expectations/chromium.fyi.json
+++ b/tools/mb/mb_config_expectations/chromium.fyi.json
@@ -1298,6 +1298,17 @@
       "use_remoteexec": true
     }
   },
+  "linux-wpt-content-shell-asan-fyi-rel": {
+    "gn_args": {
+      "dcheck_always_on": true,
+      "is_asan": true,
+      "is_component_build": false,
+      "is_debug": false,
+      "is_lsan": true,
+      "symbol_level": 1,
+      "use_remoteexec": true
+    }
+  },
   "linux-wpt-content-shell-fyi-rel": {
     "gn_args": {
       "dcheck_always_on": true,
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 133531b..750aab7 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -5178,6 +5178,15 @@
   <int value="14" label="kBloom"/>
 </enum>
 
+<enum name="AssistantServiceState">
+  <int value="0" label="kStopped"/>
+  <int value="1" label="kStarting"/>
+  <int value="2" label="kStarted"/>
+  <int value="3" label="kRunning"/>
+  <int value="4" label="kStopping"/>
+  <int value="5" label="kDisconnected"/>
+</enum>
+
 <enum name="AsyncDNSConfigParsePosix">
   <obsolete>
     Removed 2021-01.
@@ -18026,7 +18035,8 @@
   <int value="38" label="HTTP/3 (draft-29)"/>
   <int value="39" label="HTTP/3 (T051)"/>
   <int value="40" label="QUIC (RFC)"/>
-  <int value="41" label="QUICv2 (draft-01)"/>
+  <int value="41" label="QUICv2 draft-01 (deprecated)"/>
+  <int value="42" label="QUICv2 draft-08"/>
 </enum>
 
 <enum name="ConnectionResult">
@@ -41655,6 +41665,8 @@
   <int value="4424" label="DialogCloseWatcherCloseSignalClosedMultiple"/>
   <int value="4425" label="NoVarySearch"/>
   <int value="4426" label="FedCmUserInfo"/>
+  <int value="4427" label="IDNA2008DeviationCharacterInHostnameOfSubresource"/>
+  <int value="4428" label="IDNA2008DeviationCharacterInHostnameOfIFrame"/>
 </enum>
 
 <enum name="FeaturePolicyAllowlistType">
@@ -57264,6 +57276,8 @@
   <int value="-2008272679" label="disable-webrtc-hw-encoding"/>
   <int value="-2007947679" label="OmniboxMostVisitedTilesOnSrp:disabled"/>
   <int value="-2007377632" label="DiscoverFeedMultiColumn:enabled"/>
+  <int value="-2006505465"
+      label="AutofillRemoveCardExpirationAndTypeTitles:disabled"/>
   <int value="-2005089558" label="BackgroundVideoTrackOptimization:disabled"/>
   <int value="-2004882388" label="AutofillPruneSuggestions:enabled"/>
   <int value="-2004862295" label="FedCmUserInfo:disabled"/>
@@ -57646,6 +57660,7 @@
   <int value="-1802559277" label="RunTasksByBatches:disabled"/>
   <int value="-1802502753" label="enable-manual-password-generation:enabled"/>
   <int value="-1801515363" label="ThreadedPreloadScanner:enabled"/>
+  <int value="-1801349476" label="FloatWindow:enabled"/>
   <int value="-1799801575" label="CookieDeprecationMessages:enabled"/>
   <int value="-1798337879" label="enable-md-downloads"/>
   <int value="-1798258607" label="AdaptiveCharging:disabled"/>
@@ -58325,7 +58340,6 @@
   <int value="-1408288176" label="enable-account-consistency"/>
   <int value="-1407904527"
       label="OmniboxRemoveSuggestionHeaderCapitalization:disabled"/>
-  <int value="-1407568904" label="CrOSLabsFloatWindow:enabled"/>
   <int value="-1405349891" label="PictureInPictureAPI:enabled"/>
   <int value="-1405048637" label="OfflinePagesResourceBasedSnapshot:enabled"/>
   <int value="-1404469375" label="RemoteCopyProgressNotification:enabled"/>
@@ -62240,6 +62254,7 @@
   <int value="879992337" label="disable-pull-to-refresh-effect"/>
   <int value="880201245" label="NewTabPageTilesTitleWrapAround:disabled"/>
   <int value="880510010" label="enable-permissions-bubbles"/>
+  <int value="882103442" label="FloatWindow:disabled"/>
   <int value="882893584" label="UseOfHashAffiliationFetcher:disabled"/>
   <int value="883190338" label="PrintWithReducedRasterization:disabled"/>
   <int value="884106779" label="supervised-user-safesites"/>
@@ -63860,6 +63875,8 @@
   <int value="1841051176" label="WindowsScrollingPersonality:disabled"/>
   <int value="1841793150" label="TwoPanesStartSurfaceAndroid:enabled"/>
   <int value="1841976850" label="FeedLoadingPlaceholder:enabled"/>
+  <int value="1841993231"
+      label="AutofillRemoveCardExpirationAndTypeTitles:enabled"/>
   <int value="1842219851" label="enable-incognito-window-counter"/>
   <int value="1843088575" label="OutOfBlinkCors:enabled"/>
   <int value="1843847665" label="SyncPromoAfterSigninIntercept:disabled"/>
@@ -64161,7 +64178,6 @@
       label="ContextualSuggestionsAlternateCardLayout:enabled"/>
   <int value="2006856618" label="BiometricTouchToFill:enabled"/>
   <int value="2007421572" label="EapGtcWifiAuthentication:disabled"/>
-  <int value="2007638959" label="CrOSLabsFloatWindow:disabled"/>
   <int value="2008599705" label="EnableFeedbackPanel:enabled"/>
   <int value="2008878342" label="TouchToFillAndroid:disabled"/>
   <int value="2009097351" label="memlog-sampling-rate"/>
@@ -89988,6 +90004,18 @@
   <int value="2" label="Media app opened"/>
 </enum>
 
+<enum name="ScanFailureProgress">
+  <summary>
+    Shows which function caused a failure while scanning. These functions
+    correspond to function found in chromos's lorgnette.c
+  </summary>
+  <int value="1" label="StartScan"/>
+  <int value="2" label="GetScanCapabilities"/>
+  <int value="3" label="ConnectToDevice"/>
+  <int value="4" label="ScanLoop"/>
+  <int value="5" label="ListScanners"/>
+</enum>
+
 <enum name="ScanJobFailureReason">
   <int value="0" label="Unknown scanner error"/>
   <int value="1" label="Scanner not found"/>
diff --git a/tools/metrics/histograms/metadata/METRIC_REVIEWER_OWNERS b/tools/metrics/histograms/metadata/METRIC_REVIEWER_OWNERS
index 1f33dca..3745273 100644
--- a/tools/metrics/histograms/metadata/METRIC_REVIEWER_OWNERS
+++ b/tools/metrics/histograms/metadata/METRIC_REVIEWER_OWNERS
@@ -9,6 +9,7 @@
 ckitagawa@chromium.org
 sinansahin@google.com
 mvanouwerkerk@chromium.org
+jinsukkim@chromium.org
 # apps
 nancylingwang@chromium.org
 tby@chromium.org
diff --git a/tools/metrics/histograms/metadata/arc/histograms.xml b/tools/metrics/histograms/metadata/arc/histograms.xml
index faab4f5..6f33a81 100644
--- a/tools/metrics/histograms/metadata/arc/histograms.xml
+++ b/tools/metrics/histograms/metadata/arc/histograms.xml
@@ -2014,7 +2014,7 @@
 </histogram>
 
 <histogram name="Arc.Session.HasWebViewUsage" enum="Boolean"
-    expires_after="2022-12-20">
+    expires_after="2023-02-01">
   <owner>hungmn@google.com</owner>
   <owner>khmel@google.com</owner>
   <owner>arc-performance@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/assistant/histograms.xml b/tools/metrics/histograms/metadata/assistant/histograms.xml
index 88cc8d6..b3a8746 100644
--- a/tools/metrics/histograms/metadata/assistant/histograms.xml
+++ b/tools/metrics/histograms/metadata/assistant/histograms.xml
@@ -218,7 +218,7 @@
   <summary>Amount of time spent in starting Assistant service.</summary>
 </histogram>
 
-<histogram name="Assistant.ServiceState" enum="State"
+<histogram name="Assistant.ServiceState" enum="AssistantServiceState"
     expires_after="2023-10-31">
   <owner>wutao@chromium.org</owner>
   <owner>assistive-eng@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/extensions/histograms.xml b/tools/metrics/histograms/metadata/extensions/histograms.xml
index d6eed612..d1cbd14 100644
--- a/tools/metrics/histograms/metadata/extensions/histograms.xml
+++ b/tools/metrics/histograms/metadata/extensions/histograms.xml
@@ -3078,6 +3078,9 @@
 
 <histogram name="Extensions.UninstallBookmarkApp" enum="Boolean"
     expires_after="2022-12-01">
+  <obsolete>
+    Obsolete in M110.
+  </obsolete>
   <owner>phillis@chromium.org</owner>
   <owner>extensions-core@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/network/histograms.xml b/tools/metrics/histograms/metadata/network/histograms.xml
index 73444793..10a25036 100644
--- a/tools/metrics/histograms/metadata/network/histograms.xml
+++ b/tools/metrics/histograms/metadata/network/histograms.xml
@@ -1478,7 +1478,8 @@
   <owner>cros-network-health@google.com</owner>
   <summary>
     Captive portal time from a portal-suspected state to an online state on
-    ChromeOS.
+    ChromeOS. NOTE: Prior to M110 this used a range that was too small so those
+    results are invalid.
   </summary>
 </histogram>
 
@@ -1573,7 +1574,8 @@
   <owner>cros-network-health@google.com</owner>
   <summary>
     Captive portal time from a redirect-found state to an online state on
-    ChromeOS.
+    ChromeOS. NOTE: Prior to M110 this used a range that was too small so those
+    results are invalid.
   </summary>
 </histogram>
 
diff --git a/tools/metrics/histograms/metadata/optimization/histograms.xml b/tools/metrics/histograms/metadata/optimization/histograms.xml
index 8101c5f..9adffd9 100644
--- a/tools/metrics/histograms/metadata/optimization/histograms.xml
+++ b/tools/metrics/histograms/metadata/optimization/histograms.xml
@@ -1275,6 +1275,30 @@
 </histogram>
 
 <histogram
+    name="OptimizationGuide.PredictionModelStore.ModelCount.{OptimizationTarget}"
+    units="counts" expires_after="M112">
+  <owner>sophiechang@chromium.org</owner>
+  <owner>rajendrant@chromium.org</owner>
+  <summary>
+    The total number of model directories in the prediction model store for
+    {OptimizationTarget}. Recorded once at startup.
+  </summary>
+  <token key="OptimizationTarget" variants="OptimizationTarget"/>
+</histogram>
+
+<histogram
+    name="OptimizationGuide.PredictionModelStore.TotalDirectorySize.{OptimizationTarget}"
+    units="MB" expires_after="M112">
+  <owner>sophiechang@chromium.org</owner>
+  <owner>rajendrant@chromium.org</owner>
+  <summary>
+    The total disk usage of the prediction model store attributed to
+    {OptimizationTarget}. Recorded once at startup.
+  </summary>
+  <token key="OptimizationTarget" variants="OptimizationTarget"/>
+</histogram>
+
+<histogram
     name="OptimizationGuide.PredictionModelUpdateVersion.{OptimizationTarget}"
     units="version number" expires_after="M112">
   <owner>mcrouse@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/others/histograms.xml b/tools/metrics/histograms/metadata/others/histograms.xml
index e865f30..4bc61419 100644
--- a/tools/metrics/histograms/metadata/others/histograms.xml
+++ b/tools/metrics/histograms/metadata/others/histograms.xml
@@ -5091,6 +5091,16 @@
   </summary>
 </histogram>
 
+<histogram name="DocumentScan.ScanFailureProgress" enum="ScanFailureProgress"
+    expires_after="2023-12-14">
+  <owner>rishabhagr@google.com</owner>
+  <owner>project-bolton@google.com</owner>
+  <summary>
+    Chrome OS document scan metric that tracks the stage at which the scan
+    failed.
+  </summary>
+</histogram>
+
 <histogram name="DocumentScan.ScanFailureReason" enum="ScanJobFailureReason"
     expires_after="2023-11-30">
   <owner>rishabhagr@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/sync/histograms.xml b/tools/metrics/histograms/metadata/sync/histograms.xml
index d9400b6..ffb4e4c 100644
--- a/tools/metrics/histograms/metadata/sync/histograms.xml
+++ b/tools/metrics/histograms/metadata/sync/histograms.xml
@@ -1369,9 +1369,10 @@
   <owner>mmoskvitin@google.com</owner>
   <component>Services&gt;Sync</component>
   <summary>
-    Records the known degraded recoverability value when the primary account is
-    set. Note that the value reflects state restored from the file and recorded
-    before sending new request to the server.
+    Records the known degraded recoverability value when the degraded
+    recoverability handler is started. Note that the value reflects state
+    restored from the file and recorded before sending new request to the
+    server.
   </summary>
 </histogram>
 
diff --git a/tools/metrics/histograms/metadata/tab/histograms.xml b/tools/metrics/histograms/metadata/tab/histograms.xml
index 65db5f3..735849e 100644
--- a/tools/metrics/histograms/metadata/tab/histograms.xml
+++ b/tools/metrics/histograms/metadata/tab/histograms.xml
@@ -590,6 +590,18 @@
   </summary>
 </histogram>
 
+<histogram name="Tab.RestoreClosedTab" enum="Boolean"
+    expires_after="2023-11-30">
+  <owner>sreejakshetty@chromium.org</owner>
+  <owner>chrome-brapp-loading@google.com</owner>
+  <summary>
+    [Desktop] This metric is recorded whenever a tab is restored by the user.
+    Set to true if the tab is restored from ClosedTabCache and false indicates
+    the tab is restored normally. This accounts for all types of tab restores
+    i.e., in a closed window, closed group also on startup tab restores.
+  </summary>
+</histogram>
+
 <histogram name="Tab.RestoreResult" enum="TabRestoreResult" expires_after="M88">
   <owner>dtrainor@chromium.org</owner>
   <owner>marq@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/variations/histograms.xml b/tools/metrics/histograms/metadata/variations/histograms.xml
index 18ac0fa..8d9d826b 100644
--- a/tools/metrics/histograms/metadata/variations/histograms.xml
+++ b/tools/metrics/histograms/metadata/variations/histograms.xml
@@ -255,6 +255,26 @@
   </summary>
 </histogram>
 
+<histogram name="Variations.Limits.VariationKeySize.{Size}" units="KiB"
+    expires_after="2023-09-22">
+  <owner>dalerogerson@google.com</owner>
+  <owner>src/base/metrics/OWNERS</owner>
+  <summary>
+    The number of kilobytes of the variations crash keys, recorded every time
+    crash keys are updated. Crash keys larger than kVariationKeySize will be
+    truncated. The size of kVariationKeySize is controlled by the preprocessor
+    macro LARGE_VARIATION_KEY_SIZE. {Size};
+  </summary>
+  <token key="Size">
+    <variant name="Default"
+        summary="&quot;Default&quot; is recorded when
+                 LARGE_VARIATION_KEY_SIZE is NOT defined."/>
+    <variant name="Large"
+        summary="&quot;Large&quot; is used when LARGE_VARIATION_KEY_SIZE is
+                 defined."/>
+  </token>
+</histogram>
+
 <histogram name="Variations.LoadPermanentConsistencyCountryResult"
     enum="VariationsPermanentConsistencyCountryResult"
     expires_after="2023-11-01">
diff --git a/tools/perf/benchmark.csv b/tools/perf/benchmark.csv
index 595089ddb..e9a1cbb 100644
--- a/tools/perf/benchmark.csv
+++ b/tools/perf/benchmark.csv
@@ -73,6 +73,7 @@
 speedometer2-minormc,omerkatz@chromium.org,Blink>JavaScript>GarbageCollection,,all
 speedometer2-pcscan,tmrts@chromium.org,Blink>JavaScript,,all
 startup.mobile,"pasko@chromium.org, lizeb@chromium.org",Speed>Metrics>SystemHealthRegressions,,
+sync_performance_tests,mastiz@chromium.org,Services>Sync,https://chromium.googlesource.com/chromium/src/+/HEAD/components/sync/README.md,
 system_health.common_desktop,"charliea@chromium.org, sullivan@chromium.org, tdresser@chromium.org, chrome-speed-metrics-dev@chromium.org",Speed>Metrics>SystemHealthRegressions,https://bit.ly/system-health-benchmarks,"2016,2018,2019,2020,2021,accessibility,emerging_market,health_check,images,infinite_scroll,international,javascript_heavy,keyboard_input,scroll,tabs_switching,wasm,webgl"
 system_health.common_mobile,"charliea@chromium.org, sullivan@chromium.org, tdresser@chromium.org, chrome-speed-metrics-dev@chromium.org",Speed>Metrics>SystemHealthRegressions,https://bit.ly/system-health-benchmarks,"2016,2018,2019,2020,2021,emerging_market,health_check,images,infinite_scroll,international,javascript_heavy"
 system_health.memory_desktop,"pasko@chromium.org, lizeb@chromium.org",,https://bit.ly/system-health-benchmarks,"2016,2018,2019,2020,2021,accessibility,emerging_market,health_check,images,infinite_scroll,international,javascript_heavy,keyboard_input,scroll,tabs_switching,wasm,webgl"
diff --git a/tools/perf/core/bot_platforms.py b/tools/perf/core/bot_platforms.py
index 3e75430..c1b805d 100644
--- a/tools/perf/core/bot_platforms.py
+++ b/tools/perf/core/bot_platforms.py
@@ -248,6 +248,19 @@
         ['blink_perf.display_locking', 'jetstream2'])
 
 
+def _sync_performance_tests(estimated_runtime=110,
+                            path=None,
+                            additional_flags=None):
+  if not additional_flags:
+    additional_flags = []
+  flags = ['--test-launcher-jobs=1', '--test-launcher-retry-limit=0']
+  flags.extend(additional_flags)
+  return ExecutableConfig('sync_performance_tests',
+                          path=path,
+                          flags=flags,
+                          estimated_runtime=estimated_runtime)
+
+
 def _base_perftests(estimated_runtime=270, path=None, additional_flags=None):
   if not additional_flags:
     additional_flags = []
@@ -357,7 +370,10 @@
   FUCHSIA_EXEC_CONFIGS[board] = frozenset([
       _base_perftests(900,
                       path='bin/run_base_perftests',
-                      additional_flags=FUCHSIA_EXEC_ARGS[board])
+                      additional_flags=FUCHSIA_EXEC_ARGS[board]),
+      _sync_performance_tests(900,
+                              path='bin/run_sync_performance_tests',
+                              additional_flags=FUCHSIA_EXEC_ARGS[board]),
   ])
 
 _LINUX_BENCHMARK_CONFIGS = PerfSuite(OFFICIAL_BENCHMARK_CONFIGS).Remove([
diff --git a/tools/perf/core/perf_data_generator.py b/tools/perf/core/perf_data_generator.py
index 79f585a..b3e3499 100755
--- a/tools/perf/core/perf_data_generator.py
+++ b/tools/perf/core/perf_data_generator.py
@@ -415,7 +415,8 @@
         ],
     },
     'fuchsia-builder-perf-x64': {
-        'additional_compile_targets': ['chrome_pkg', 'base_perftests'],
+        'additional_compile_targets':
+        ['chrome_pkg', 'base_perftests', 'sync_performance_tests'],
     },
 }
 
@@ -1560,6 +1561,11 @@
         'skyostil@chromium.org, gab@chromium.org', 'Internals>SequenceManager',
         ('https://chromium.googlesource.com/chromium/src/+/HEAD/base/' +
          'README.md#performance-testing')),
+    'sync_performance_tests':
+    BenchmarkMetadata(
+        'mastiz@chromium.org', 'Services>Sync',
+        'https://chromium.googlesource.com/chromium/src/+/HEAD/components/sync/README.md'
+    ),
     'tracing_perftests':
     BenchmarkMetadata('eseckler@chromium.org, oysteine@chromium.org',
                       'Speed>Tracing'),
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json
index 5f322b0c..b44f31e 100644
--- a/tools/perf/core/perfetto_binary_roller/binary_deps.json
+++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -5,8 +5,8 @@
             "full_remote_path": "perfetto-luci-artifacts/v31.0/linux-arm64/trace_processor_shell"
         },
         "win": {
-            "hash": "1745dfffbb9a5fa93b72ac8ea7da6ac1bfefc8bc",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/f3ce1d9b9ce6a1401be9e0cebbaa0596f1373a10/trace_processor_shell.exe"
+            "hash": "46be90dba011723aa14314617f8b86a10bbc984b",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/42498077f54f5e3093b865b22cf6822f14c73222/trace_processor_shell.exe"
         },
         "linux_arm": {
             "hash": "6373f26144aad58f230d11d6a91efda5a09c9873",
@@ -22,7 +22,7 @@
         },
         "linux": {
             "hash": "16ec80773ab59dfaf991857341533653a091a918",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/42498077f54f5e3093b865b22cf6822f14c73222/trace_processor_shell"
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/39f3c505498164c3ad9ca31a4f5924de5d6aa8d0/trace_processor_shell"
         }
     },
     "power_profile.sql": {
diff --git a/tools/perf/core/shard_maps/fuchsia-perf-atlas-fyi_map.json b/tools/perf/core/shard_maps/fuchsia-perf-atlas-fyi_map.json
index 76cb5507..3906bec 100644
--- a/tools/perf/core/shard_maps/fuchsia-perf-atlas-fyi_map.json
+++ b/tools/perf/core/shard_maps/fuchsia-perf-atlas-fyi_map.json
@@ -27,18 +27,25 @@
             },
             "speedometer2": {
                 "abridged": false
-            },
-            "system_health.common_desktop": {
-                "end": 24,
-                "abridged": false
+            }
+        },
+        "executables": {
+            "sync_performance_tests": {
+                "arguments": [
+                    "--test-launcher-jobs=1",
+                    "--test-launcher-retry-limit=0",
+                    "-d",
+                    "--os-check=check",
+                    "--system-image-dir=workstation_eng.chromebook-x64"
+                ],
+                "path": "bin/run_sync_performance_tests"
             }
         }
     },
     "2": {
         "benchmarks": {
             "system_health.common_desktop": {
-                "begin": 24,
-                "end": 53,
+                "end": 41,
                 "abridged": false
             }
         }
@@ -46,20 +53,20 @@
     "3": {
         "benchmarks": {
             "system_health.common_desktop": {
-                "begin": 53,
+                "begin": 41,
                 "abridged": false
             }
         }
     },
     "extra_infos": {
-        "num_stories": 86,
-        "predicted_min_shard_time": 280,
-        "predicted_min_shard_index": 1,
-        "predicted_max_shard_time": 900.0,
-        "predicted_max_shard_index": 0,
+        "num_stories": 87,
+        "predicted_min_shard_time": 400,
+        "predicted_min_shard_index": 3,
+        "predicted_max_shard_time": 940.0,
+        "predicted_max_shard_index": 1,
         "shard #0": 900.0,
-        "shard #1": 280,
-        "shard #2": 290,
-        "shard #3": 280
+        "shard #1": 940.0,
+        "shard #2": 410,
+        "shard #3": 400
     }
 }
\ No newline at end of file
diff --git a/tools/perf/core/shard_maps/fuchsia-perf-nuc-fyi_map.json b/tools/perf/core/shard_maps/fuchsia-perf-nuc-fyi_map.json
index 9134264..51dae71d 100644
--- a/tools/perf/core/shard_maps/fuchsia-perf-nuc-fyi_map.json
+++ b/tools/perf/core/shard_maps/fuchsia-perf-nuc-fyi_map.json
@@ -27,18 +27,25 @@
             },
             "speedometer2": {
                 "abridged": false
-            },
-            "system_health.common_desktop": {
-                "end": 24,
-                "abridged": false
+            }
+        },
+        "executables": {
+            "sync_performance_tests": {
+                "arguments": [
+                    "--test-launcher-jobs=1",
+                    "--test-launcher-retry-limit=0",
+                    "-d",
+                    "--os-check=check",
+                    "--system-image-dir=workstation_eng.x64"
+                ],
+                "path": "bin/run_sync_performance_tests"
             }
         }
     },
     "2": {
         "benchmarks": {
             "system_health.common_desktop": {
-                "begin": 24,
-                "end": 53,
+                "end": 41,
                 "abridged": false
             }
         }
@@ -46,20 +53,20 @@
     "3": {
         "benchmarks": {
             "system_health.common_desktop": {
-                "begin": 53,
+                "begin": 41,
                 "abridged": false
             }
         }
     },
     "extra_infos": {
-        "num_stories": 86,
-        "predicted_min_shard_time": 280,
-        "predicted_min_shard_index": 1,
-        "predicted_max_shard_time": 900.0,
-        "predicted_max_shard_index": 0,
+        "num_stories": 87,
+        "predicted_min_shard_time": 400,
+        "predicted_min_shard_index": 3,
+        "predicted_max_shard_time": 940.0,
+        "predicted_max_shard_index": 1,
         "shard #0": 900.0,
-        "shard #1": 280,
-        "shard #2": 290,
-        "shard #3": 280
+        "shard #1": 940.0,
+        "shard #2": 410,
+        "shard #3": 400
     }
 }
\ No newline at end of file
diff --git a/tools/perf/core/shard_maps/timing_data/fuchsia-perf-atlas-fyi_timing.json b/tools/perf/core/shard_maps/timing_data/fuchsia-perf-atlas-fyi_timing.json
index 4ecf637..b7976a1 100644
--- a/tools/perf/core/shard_maps/timing_data/fuchsia-perf-atlas-fyi_timing.json
+++ b/tools/perf/core/shard_maps/timing_data/fuchsia-perf-atlas-fyi_timing.json
@@ -2,5 +2,9 @@
     {
         "duration": "900.0",
         "name": "base_perftests/_gtest_"
+    },
+    {
+        "duration": "900.0",
+        "name": "sync_performance_tests/_gtest_"
     }
 ]
\ No newline at end of file
diff --git a/tools/perf/core/shard_maps/timing_data/fuchsia-perf-nuc-fyi_timing.json b/tools/perf/core/shard_maps/timing_data/fuchsia-perf-nuc-fyi_timing.json
index 4ecf637..b7976a1 100644
--- a/tools/perf/core/shard_maps/timing_data/fuchsia-perf-nuc-fyi_timing.json
+++ b/tools/perf/core/shard_maps/timing_data/fuchsia-perf-nuc-fyi_timing.json
@@ -2,5 +2,9 @@
     {
         "duration": "900.0",
         "name": "base_perftests/_gtest_"
+    },
+    {
+        "duration": "900.0",
+        "name": "sync_performance_tests/_gtest_"
     }
 ]
\ No newline at end of file
diff --git a/tools/rust/build_rust.py b/tools/rust/build_rust.py
index 0a424cf..ae9de42 100755
--- a/tools/rust/build_rust.py
+++ b/tools/rust/build_rust.py
@@ -191,9 +191,8 @@
     # A `-Clink-arg=<foo>` arg passes `foo`` to the linker invovation.
 
     RUSTENV['RUSTFLAGS_BOOTSTRAP'] = ''
-    if gcc_toolchain_flag:
-        RUSTENV['RUSTFLAGS_BOOTSTRAP'] += f' -Clink-arg={gcc_toolchain_flag} '
     if gcc_toolchain_path:
+        RUSTENV['RUSTFLAGS_BOOTSTRAP'] += f' -Clink-arg={gcc_toolchain_flag} '
         RUSTENV['RUSTFLAGS_BOOTSTRAP'] += (
             f' -L native={gcc_toolchain_path}/lib64')
     RUSTENV['RUSTFLAGS_NOT_BOOTSTRAP'] = RUSTENV['RUSTFLAGS_BOOTSTRAP']
diff --git a/tools/rust/package_rust.py b/tools/rust/package_rust.py
index ace96ef..d67f348 100755
--- a/tools/rust/package_rust.py
+++ b/tools/rust/package_rust.py
@@ -5,6 +5,7 @@
 
 import argparse
 import os
+import platform
 import shutil
 import sys
 import tarfile
@@ -56,11 +57,6 @@
     # Share this argument with other build scripts that we execute here.
     BUILD_MAC_ARM = args.build_mac_arm
 
-    # Only build on Linux. Other platforms are currently unsupported.
-    if not (sys.platform.startswith('linux') or sys.platform == 'win32'):
-        print('Only Linux and Windows is supported!')
-        return 1
-
     # The gcs_platform logic copied from `//tools/clang/scripts/upload.sh`.
     if sys.platform == 'darwin':
         # The --build-mac-intel switch can be used to force the Mac build to
diff --git a/ui/accelerated_widget_mac/ca_layer_tree_unittest_mac.mm b/ui/accelerated_widget_mac/ca_layer_tree_unittest_mac.mm
index 6c01505..9356415 100644
--- a/ui/accelerated_widget_mac/ca_layer_tree_unittest_mac.mm
+++ b/ui/accelerated_widget_mac/ca_layer_tree_unittest_mac.mm
@@ -15,7 +15,6 @@
 #include "ui/gfx/geometry/rect_conversions.h"
 #include "ui/gfx/mac/io_surface.h"
 #include "ui/gl/ca_renderer_layer_params.h"
-#include "ui/gl/gl_image_io_surface.h"
 
 @interface CALayer (Private)
 @property BOOL wantsExtendedDynamicRangeContent;
@@ -41,39 +40,34 @@
   float opacity = 1.0f;
   float scale_factor = 1.0f;
   unsigned filter = GL_LINEAR;
-  scoped_refptr<gl::GLImageIOSurface> gl_image;
+  gfx::ScopedIOSurface io_surface;
+  gfx::ColorSpace color_space;
+  base::ScopedCFTypeRef<CVPixelBufferRef> cv_pixel_buffer;
 
   bool allow_av_layers = true;
   bool allow_solid_color_layers = true;
 };
 
-scoped_refptr<gl::GLImageIOSurface> CreateGLImage(const gfx::Size& size,
-                                                  gfx::BufferFormat format,
-                                                  bool video) {
-  scoped_refptr<gl::GLImageIOSurface> gl_image(
-      gl::GLImageIOSurface::Create(size));
-  base::ScopedCFTypeRef<IOSurfaceRef> io_surface(
-      gfx::CreateIOSurface(size, format));
-  if (video) {
-    base::ScopedCFTypeRef<CVPixelBufferRef> cv_pixel_buffer;
-    CVPixelBufferCreateWithIOSurface(nullptr, io_surface, nullptr,
-                                     cv_pixel_buffer.InitializeInto());
-    gl_image->InitializeWithCVPixelBuffer(cv_pixel_buffer, 0,
-                                          gfx::GenericSharedMemoryId(), format,
-                                          gfx::ColorSpace::CreateREC709());
-  } else {
-    gl_image->Initialize(io_surface, 0, gfx::GenericSharedMemoryId(), format);
-  }
-  return gl_image;
+gfx::ScopedIOSurface CreateScopedIOSurface(const gfx::Size& size,
+                                           gfx::BufferFormat format) {
+  return gfx::ScopedIOSurface(gfx::CreateIOSurface(size, format));
+}
+
+base::ScopedCFTypeRef<CVPixelBufferRef> CreateCVPixelBuffer(
+    gfx::ScopedIOSurface io_surface) {
+  base::ScopedCFTypeRef<CVPixelBufferRef> cv_pixel_buffer;
+  CVPixelBufferCreateWithIOSurface(nullptr, io_surface, nullptr,
+                                   cv_pixel_buffer.InitializeInto());
+  return cv_pixel_buffer;
 }
 
 bool ScheduleCALayer(ui::CARendererLayerTree* tree,
                      CALayerProperties* properties) {
   gfx::ScopedIOSurface io_surface;
   gfx::ColorSpace io_surface_color_space;
-  if (properties->gl_image) {
-    io_surface = properties->gl_image->io_surface();
-    io_surface_color_space = properties->gl_image->color_space();
+  if (properties->io_surface) {
+    io_surface = properties->io_surface;
+    io_surface_color_space = properties->color_space;
   }
   return tree->ScheduleCALayer(ui::CARendererLayerParams(
       properties->is_clipped, properties->clip_rect,
@@ -136,8 +130,8 @@
     properties.background_color = SkColors::kRed;
     properties.edge_aa_mask = ui::CALayerEdge::kLayerEdgeLeft;
     properties.opacity = 0.5f;
-    properties.gl_image =
-        CreateGLImage(gfx::Size(256, 256), gfx::BufferFormat::BGRA_8888, false);
+    properties.io_surface = CreateScopedIOSurface(gfx::Size(256, 256),
+                                                  gfx::BufferFormat::BGRA_8888);
 
     std::unique_ptr<ui::CARendererLayerTree> ca_layer_tree;
     CALayer* root_layer = nil;
@@ -190,7 +184,7 @@
                 [transform_layer sublayerTransform].m42);
 
       // Validate the content layer.
-      EXPECT_EQ(static_cast<id>(properties.gl_image->io_surface().get()),
+      EXPECT_EQ(static_cast<id>(properties.io_surface.get()),
                 [content_layer contents]);
       EXPECT_EQ(properties.contents_rect,
                 gfx::RectF([content_layer contentsRect]));
@@ -299,7 +293,7 @@
 
     // Change the contents and commit.
     {
-      properties.gl_image = nullptr;
+      properties.io_surface = gfx::ScopedIOSurface();
       UpdateCALayerTree(ca_layer_tree, &properties, superlayer_);
 
       // Validate the tree structure.
@@ -414,8 +408,8 @@
     // Add the clipping and IOSurface contents back.
     {
       properties.is_clipped = true;
-      properties.gl_image = CreateGLImage(gfx::Size(256, 256),
-                                          gfx::BufferFormat::BGRA_8888, false);
+      properties.io_surface = CreateScopedIOSurface(
+          gfx::Size(256, 256), gfx::BufferFormat::BGRA_8888);
       UpdateCALayerTree(ca_layer_tree, &properties, superlayer_);
 
       // Validate the tree structure.
@@ -430,7 +424,7 @@
       EXPECT_EQ(content_layer, [transform_layer sublayers][0]);
 
       // Validate the content layer.
-      EXPECT_EQ(static_cast<id>(properties.gl_image->io_surface().get()),
+      EXPECT_EQ(static_cast<id>(properties.io_surface.get()),
                 [content_layer contents]);
       EXPECT_EQ(kCALayerBottomEdge, [content_layer edgeAntialiasingMask]);
     }
@@ -484,7 +478,7 @@
                 [transform_layer sublayerTransform].m42);
 
       // Validate the content layer.
-      EXPECT_EQ(static_cast<id>(properties.gl_image->io_surface().get()),
+      EXPECT_EQ(static_cast<id>(properties.io_surface.get()),
                 [content_layer contents]);
       EXPECT_EQ(properties.contents_rect,
                 gfx::RectF([content_layer contentsRect]));
@@ -577,10 +571,10 @@
   properties.rect = gfx::Rect(0, 0, 256, 256);
 
   // We'll use the IOSurface contents to identify the content layers.
-  scoped_refptr<gl::GLImageIOSurface> gl_images[5];
+  gfx::ScopedIOSurface io_surfaces[5];
   for (size_t i = 0; i < 5; ++i) {
-    gl_images[i] =
-        CreateGLImage(gfx::Size(256, 256), gfx::BufferFormat::BGRA_8888, false);
+    io_surfaces[i] = CreateScopedIOSurface(gfx::Size(256, 256),
+                                           gfx::BufferFormat::BGRA_8888);
   }
 
   // Have 5 transforms:
@@ -598,7 +592,7 @@
   std::unique_ptr<ui::CARendererLayerTree> ca_layer_tree(
       new ui::CARendererLayerTree(true, true));
   for (size_t i = 0; i < 5; ++i) {
-    properties.gl_image = gl_images[i];
+    properties.io_surface = io_surfaces[i];
     properties.transform = transforms[i];
     bool result = ScheduleCALayer(ca_layer_tree.get(), &properties);
     EXPECT_TRUE(result);
@@ -645,16 +639,11 @@
   CALayer* content_layer_4 = [transform_layer_2_0 sublayers][1];
 
   // Validate that the layers come out in order.
-  EXPECT_EQ(static_cast<id>(gl_images[0]->io_surface().get()),
-            [content_layer_0 contents]);
-  EXPECT_EQ(static_cast<id>(gl_images[1]->io_surface().get()),
-            [content_layer_1 contents]);
-  EXPECT_EQ(static_cast<id>(gl_images[2]->io_surface().get()),
-            [content_layer_2 contents]);
-  EXPECT_EQ(static_cast<id>(gl_images[3]->io_surface().get()),
-            [content_layer_3 contents]);
-  EXPECT_EQ(static_cast<id>(gl_images[4]->io_surface().get()),
-            [content_layer_4 contents]);
+  EXPECT_EQ(static_cast<id>(io_surfaces[0].get()), [content_layer_0 contents]);
+  EXPECT_EQ(static_cast<id>(io_surfaces[1].get()), [content_layer_1 contents]);
+  EXPECT_EQ(static_cast<id>(io_surfaces[2].get()), [content_layer_2 contents]);
+  EXPECT_EQ(static_cast<id>(io_surfaces[3].get()), [content_layer_3 contents]);
+  EXPECT_EQ(static_cast<id>(io_surfaces[4].get()), [content_layer_4 contents]);
 }
 
 // Verify that sorting contexts are allocated appropriately.
@@ -665,10 +654,10 @@
   properties.rect = gfx::Rect(0, 0, 256, 256);
 
   // We'll use the IOSurface contents to identify the content layers.
-  scoped_refptr<gl::GLImageIOSurface> gl_images[3];
+  gfx::ScopedIOSurface io_surfaces[3];
   for (size_t i = 0; i < 3; ++i) {
-    gl_images[i] =
-        CreateGLImage(gfx::Size(256, 256), gfx::BufferFormat::BGRA_8888, false);
+    io_surfaces[i] = CreateScopedIOSurface(gfx::Size(256, 256),
+                                           gfx::BufferFormat::BGRA_8888);
   }
 
   int sorting_context_ids[3] = {3, -1, 0};
@@ -678,7 +667,7 @@
       new ui::CARendererLayerTree(true, true));
   for (size_t i = 0; i < 3; ++i) {
     properties.sorting_context_id = sorting_context_ids[i];
-    properties.gl_image = gl_images[i];
+    properties.io_surface = io_surfaces[i];
     bool result = ScheduleCALayer(ca_layer_tree.get(), &properties);
     EXPECT_TRUE(result);
   }
@@ -715,12 +704,9 @@
   CALayer* content_layer_2 = [transform_layer_2 sublayers][0];
 
   // Validate that the layers come out in order.
-  EXPECT_EQ(static_cast<id>(gl_images[0]->io_surface().get()),
-            [content_layer_0 contents]);
-  EXPECT_EQ(static_cast<id>(gl_images[1]->io_surface().get()),
-            [content_layer_1 contents]);
-  EXPECT_EQ(static_cast<id>(gl_images[2]->io_surface().get()),
-            [content_layer_2 contents]);
+  EXPECT_EQ(static_cast<id>(io_surfaces[0].get()), [content_layer_0 contents]);
+  EXPECT_EQ(static_cast<id>(io_surfaces[1].get()), [content_layer_1 contents]);
+  EXPECT_EQ(static_cast<id>(io_surfaces[2].get()), [content_layer_2 contents]);
 }
 
 // Verify that sorting contexts must all have the same clipping properties.
@@ -773,8 +759,8 @@
 // Test updating each layer's properties.
 TEST_F(CALayerTreeTest, AVLayer) {
   CALayerProperties properties;
-  properties.gl_image =
-      CreateGLImage(gfx::Size(256, 256), gfx::BufferFormat::BGRA_8888, false);
+  properties.io_surface =
+      CreateScopedIOSurface(gfx::Size(256, 256), gfx::BufferFormat::BGRA_8888);
 
   std::unique_ptr<ui::CARendererLayerTree> ca_layer_tree;
   CALayer* content_layer_old = nil;
@@ -791,8 +777,8 @@
 
   // Pass a YUV 420 frame. This will become an AVSampleBufferDisplayLayer
   // because it is in fullscreen low power mode.
-  properties.gl_image = CreateGLImage(
-      gfx::Size(256, 256), gfx::BufferFormat::YUV_420_BIPLANAR, false);
+  properties.io_surface = CreateScopedIOSurface(
+      gfx::Size(256, 256), gfx::BufferFormat::YUV_420_BIPLANAR);
   {
     UpdateCALayerTree(ca_layer_tree, &properties, superlayer_);
     content_layer_new = GetOnlyContentLayer();
@@ -803,8 +789,8 @@
   content_layer_old = content_layer_new;
 
   // Pass a similar frame. Nothing should change.
-  properties.gl_image = CreateGLImage(
-      gfx::Size(256, 128), gfx::BufferFormat::YUV_420_BIPLANAR, false);
+  properties.io_surface = CreateScopedIOSurface(
+      gfx::Size(256, 128), gfx::BufferFormat::YUV_420_BIPLANAR);
   {
     UpdateCALayerTree(ca_layer_tree, &properties, superlayer_);
     content_layer_new = GetOnlyContentLayer();
@@ -828,8 +814,8 @@
 
   // Now try a P010 frame. Because this may be HDR, we should jump back to
   // having an AVSampleBufferDisplayLayer.
-  properties.gl_image =
-      CreateGLImage(gfx::Size(128, 256), gfx::BufferFormat::P010, false);
+  properties.io_surface =
+      CreateScopedIOSurface(gfx::Size(128, 256), gfx::BufferFormat::P010);
   {
     UpdateCALayerTree(ca_layer_tree, &properties, superlayer_);
     content_layer_new = GetOnlyContentLayer();
@@ -844,8 +830,10 @@
 
   // Pass a frame with a CVPixelBuffer which, when scaled down, will have a
   // fractional dimension.
-  properties.gl_image = CreateGLImage(
-      gfx::Size(513, 512), gfx::BufferFormat::YUV_420_BIPLANAR, true);
+  properties.io_surface = CreateScopedIOSurface(
+      gfx::Size(513, 512), gfx::BufferFormat::YUV_420_BIPLANAR);
+  properties.cv_pixel_buffer = CreateCVPixelBuffer(properties.io_surface);
+  properties.color_space = gfx::ColorSpace::CreateREC709();
   {
     UpdateCALayerTree(ca_layer_tree, &properties, superlayer_);
     content_layer_new = GetOnlyContentLayer();
@@ -860,8 +848,8 @@
 
   // Pass a frame that is clipped.
   properties.contents_rect = gfx::RectF(0, 0, 1, 0.9);
-  properties.gl_image = CreateGLImage(
-      gfx::Size(256, 256), gfx::BufferFormat::YUV_420_BIPLANAR, false);
+  properties.io_surface = CreateScopedIOSurface(
+      gfx::Size(256, 256), gfx::BufferFormat::YUV_420_BIPLANAR);
   {
     UpdateCALayerTree(ca_layer_tree, &properties, superlayer_);
     content_layer_new = GetOnlyContentLayer();
@@ -875,8 +863,8 @@
 // Ensure that blocklisting AVSampleBufferDisplayLayer works.
 TEST_F(CALayerTreeTest, AVLayerBlocklist) {
   CALayerProperties properties;
-  properties.gl_image = CreateGLImage(
-      gfx::Size(256, 256), gfx::BufferFormat::YUV_420_BIPLANAR, false);
+  properties.io_surface = CreateScopedIOSurface(
+      gfx::Size(256, 256), gfx::BufferFormat::YUV_420_BIPLANAR);
 
   std::unique_ptr<ui::CARendererLayerTree> ca_layer_tree;
   CALayer* root_layer = nil;
@@ -932,8 +920,10 @@
 // Test fullscreen low power detection.
 TEST_F(CALayerTreeTest, FullscreenLowPower) {
   CALayerProperties properties;
-  properties.gl_image = CreateGLImage(
-      gfx::Size(256, 256), gfx::BufferFormat::YUV_420_BIPLANAR, true);
+  properties.io_surface = CreateScopedIOSurface(
+      gfx::Size(256, 256), gfx::BufferFormat::YUV_420_BIPLANAR);
+  properties.cv_pixel_buffer = CreateCVPixelBuffer(properties.io_surface);
+  properties.color_space = gfx::ColorSpace::CreateREC709();
   properties.is_clipped = false;
 
   CALayerProperties properties_black;
@@ -1075,18 +1065,16 @@
   bool result = false;
 
   // We only copy images that have both high-bit-depth and an HDR color space.
-  scoped_refptr<gl::GLImageIOSurface> sdr_image =
-      CreateGLImage(gfx::Size(256, 256), gfx::BufferFormat::BGRA_8888, false);
-  scoped_refptr<gl::GLImageIOSurface> tricky_sdr_image =
-      CreateGLImage(gfx::Size(256, 256), gfx::BufferFormat::BGRA_8888, false);
-  scoped_refptr<gl::GLImageIOSurface> hdr_image =
-      CreateGLImage(gfx::Size(256, 256), gfx::BufferFormat::RGBA_F16, false);
-  sdr_image->SetColorSpace(gfx::ColorSpace::CreateSRGB());
-  tricky_sdr_image->SetColorSpace(gfx::ColorSpace::CreateExtendedSRGB());
-  hdr_image->SetColorSpace(gfx::ColorSpace::CreateExtendedSRGB());
+  auto sdr_image =
+      CreateScopedIOSurface(gfx::Size(256, 256), gfx::BufferFormat::BGRA_8888);
+  auto tricky_sdr_image =
+      CreateScopedIOSurface(gfx::Size(256, 256), gfx::BufferFormat::BGRA_8888);
+  auto hdr_image =
+      CreateScopedIOSurface(gfx::Size(256, 256), gfx::BufferFormat::RGBA_F16);
 
   // Schedule and commit the HDR layer.
-  properties.gl_image = hdr_image;
+  properties.io_surface = hdr_image;
+  properties.color_space = gfx::ColorSpace::CreateExtendedSRGB();
   result = ScheduleCALayer(ca_layer_trees[0].get(), &properties);
   EXPECT_TRUE(result);
   ca_layer_trees[0]->CommitScheduledCALayers(
@@ -1098,7 +1086,8 @@
     EXPECT_TRUE([content_layer wantsExtendedDynamicRangeContent]);
 
   // Commit the SDR layer.
-  properties.gl_image = sdr_image;
+  properties.io_surface = sdr_image;
+  properties.color_space = gfx::ColorSpace::CreateSRGB();
   result = ScheduleCALayer(ca_layer_trees[1].get(), &properties);
   EXPECT_TRUE(result);
   ca_layer_trees[1]->CommitScheduledCALayers(
@@ -1113,7 +1102,8 @@
     EXPECT_FALSE([content_layer wantsExtendedDynamicRangeContent]);
 
   // Commit the tricky SDR layer.
-  properties.gl_image = tricky_sdr_image;
+  properties.io_surface = tricky_sdr_image;
+  properties.color_space = gfx::ColorSpace::CreateExtendedSRGB();
   result = ScheduleCALayer(ca_layer_trees[2].get(), &properties);
   EXPECT_TRUE(result);
   ca_layer_trees[2]->CommitScheduledCALayers(
@@ -1126,7 +1116,8 @@
     EXPECT_FALSE([content_layer wantsExtendedDynamicRangeContent]);
 
   // Commit the HDR layer.
-  properties.gl_image = hdr_image;
+  properties.io_surface = hdr_image;
+  properties.color_space = gfx::ColorSpace::CreateExtendedSRGB();
   result = ScheduleCALayer(ca_layer_trees[3].get(), &properties);
   EXPECT_TRUE(result);
   ca_layer_trees[3]->CommitScheduledCALayers(
diff --git a/ui/chromeos/styles/cros_sys_colors.json5 b/ui/chromeos/styles/cros_sys_colors.json5
index 1ce9aab..c99aed4 100644
--- a/ui/chromeos/styles/cros_sys_colors.json5
+++ b/ui/chromeos/styles/cros_sys_colors.json5
@@ -197,6 +197,7 @@
       light: '$cros.ref.primary10',
       dark: '$cros.ref.primary10',
     },
+    'system-on-primary-container-disabled': 'rgba($cros.sys.system-on-primary-container.rgb, 0.38)',
 
     /* Harmonized colors */
     'on-positive-container': {
diff --git a/ui/compositor/test/in_process_context_provider.cc b/ui/compositor/test/in_process_context_provider.cc
index 1828eee..3aef16e7 100644
--- a/ui/compositor/test/in_process_context_provider.cc
+++ b/ui/compositor/test/in_process_context_provider.cc
@@ -193,15 +193,6 @@
   observers_.RemoveObserver(obs);
 }
 
-uint32_t InProcessContextProvider::GetCopyTextureInternalFormat() {
-  if (attribs_.alpha_size > 0)
-    return GL_RGBA;
-  DCHECK_NE(attribs_.red_size, 0);
-  DCHECK_NE(attribs_.green_size, 0);
-  DCHECK_NE(attribs_.blue_size, 0);
-  return GL_RGB;
-}
-
 void InProcessContextProvider::SendOnContextLost() {
   for (auto& observer : observers_)
     observer.OnContextLost();
diff --git a/ui/compositor/test/in_process_context_provider.h b/ui/compositor/test/in_process_context_provider.h
index 9ec54fb..3f0a766d 100644
--- a/ui/compositor/test/in_process_context_provider.h
+++ b/ui/compositor/test/in_process_context_provider.h
@@ -67,10 +67,6 @@
   void AddObserver(viz::ContextLostObserver* obs) override;
   void RemoveObserver(viz::ContextLostObserver* obs) override;
 
-  // Gives the GL internal format that should be used for calling CopyTexImage2D
-  // on the default framebuffer.
-  uint32_t GetCopyTextureInternalFormat();
-
   // Calls OnContextLost() on all observers. This doesn't modify the context.
   void SendOnContextLost();
 
diff --git a/ui/gl/gl_image_io_surface.h b/ui/gl/gl_image_io_surface.h
index bcad4544..6376ad4 100644
--- a/ui/gl/gl_image_io_surface.h
+++ b/ui/gl/gl_image_io_surface.h
@@ -66,7 +66,6 @@
                     uint64_t process_tracing_id,
                     const std::string& dump_name) override;
 
-  const gfx::ColorSpace& color_space() const { return color_space_; }
   gfx::BufferFormat format() const { return format_; }
   gfx::GenericSharedMemoryId io_surface_id() const { return io_surface_id_; }
   base::ScopedCFTypeRef<IOSurfaceRef> io_surface() { return io_surface_; }
diff --git a/url/url_features.cc b/url/url_features.cc
index 39000c8..25132d1 100644
--- a/url/url_features.cc
+++ b/url/url_features.cc
@@ -10,7 +10,17 @@
              "UseIDNA2008NonTransitional",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
+// Kill switch for crbug.com/1362507.
+BASE_FEATURE(kRecordIDNA2008Metrics,
+             "RecordIDNA2008Metrics",
+             base::FEATURE_ENABLED_BY_DEFAULT);
+
 bool IsUsingIDNA2008NonTransitional() {
   return base::FeatureList::IsEnabled(kUseIDNA2008NonTransitional);
 }
+
+bool IsRecordingIDNA2008Metrics() {
+  return base::FeatureList::IsEnabled(kRecordIDNA2008Metrics);
+}
+
 }  // namespace url
diff --git a/url/url_features.h b/url/url_features.h
index 3bd283f3..9924f3d 100644
--- a/url/url_features.h
+++ b/url/url_features.h
@@ -14,6 +14,10 @@
 
 // Returns true if Chrome is using IDNA 2008 in Non-Transitional mode.
 COMPONENT_EXPORT(URL) bool IsUsingIDNA2008NonTransitional();
+
+// Returns true if Chrome is recording IDNA 2008 related metrics.
+COMPONENT_EXPORT(URL) bool IsRecordingIDNA2008Metrics();
+
 }  // namespace url
 
 #endif  // URL_URL_FEATURES_H_