diff --git a/.eslintrc.js b/.eslintrc.js
index 5b2eafc..9b682b6 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -93,11 +93,22 @@
         {
           selector: 'classProperty',
           format: ['UPPER_CASE'],
-          modifiers: ['static', 'readonly'],
+          modifiers: ['private', 'static', 'readonly'],
+        },
+        {
+          selector: 'classProperty',
+          format: ['UPPER_CASE'],
+          modifiers: ['public', 'static', 'readonly'],
         },
         {
           selector: 'classProperty',
           format: ['camelCase'],
+          modifiers: ['public'],
+        },
+        {
+          selector: 'classProperty',
+          format: ['camelCase'],
+          modifiers: ['private'],
           trailingUnderscore: 'allow',
         },
         {
diff --git a/DEPS b/DEPS
index b0343bd..a85acaed 100644
--- a/DEPS
+++ b/DEPS
@@ -280,11 +280,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': '78c1845e6655428ab6f9b35fecf3d3d1032b5348',
+  'skia_revision': '1f6b4de4014784409325b051604e2f9b5b9537e7',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
-  'v8_revision': '9a3e0afbcd224b74d03f2e6b1dbcc2110c98c28e',
+  'v8_revision': 'dc48e6496209f29b87b58bbcd6a6ebfcc64eb0df',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ANGLE
   # and whatever else without interference from each other.
@@ -359,7 +359,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling devtools-frontend
   # and whatever else without interference from each other.
-  'devtools_frontend_revision': '45cde6c2f5bfb8e12c81528092edfd7b1429e492',
+  'devtools_frontend_revision': '5780a775c90505c63824d50d414fd82b9c68b514',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libprotobuf-mutator
   # and whatever else without interference from each other.
@@ -395,7 +395,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'dawn_revision': '6d48f573af6004ce4243c97745562a270de9c0e4',
+  'dawn_revision': '6d200d53eebe92cc23d358d9cd8b378cabc5ce15',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -439,7 +439,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'libcxxabi_revision':    'bb4dcb7164a67f828225714c7ed099cfe418d3d7',
+  'libcxxabi_revision':    '2dba7d2cc46a25cd67fb990826179e0c159c5b5c',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -780,7 +780,7 @@
   },
 
   'src/ios/third_party/material_components_ios/src': {
-      'url': Var('chromium_git') + '/external/github.com/material-components/material-components-ios.git' + '@' + '4ca68941b8b4b7a02ebf2e3902168196f4122135',
+      'url': Var('chromium_git') + '/external/github.com/material-components/material-components-ios.git' + '@' + 'c6d4682d323ed098f4a5265a1f264531f4a251f6',
       'condition': 'checkout_ios',
   },
 
@@ -929,7 +929,7 @@
     'packages': [
       {
           'package': 'chromium/third_party/androidx',
-          'version': 'oRZBRiYR93sr5l0V68UgLgP_K7BSeq12h0dvFXdyLkEC',
+          'version': 'ffRu7ou3A-tWC0cp9INlqplJN090p9v3saKCut6KJZ8C',
       },
     ],
     'condition': 'checkout_android',
@@ -1542,7 +1542,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + '29c99a813c0b03800cc2b06f521d9dfbf590194b',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + 'ddc88694c41b71562428bd08b168666c8d42eaf3',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1712,7 +1712,7 @@
     Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + '49e659bea564367588238bd5e88a554014956064',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + '932d0799374774d397bf28be9deabac31f7260b6',
+    Var('webrtc_git') + '/src.git' + '@' + '67d2d35443c32e84880f52e7af41cc209be63833',
 
   'src/third_party/libgifcodec':
      Var('skia_git') + '/libgifcodec' + '@'+  Var('libgifcodec_revision'),
diff --git a/android_webview/BUILD.gn b/android_webview/BUILD.gn
index 8fa1491..054b38c 100644
--- a/android_webview/BUILD.gn
+++ b/android_webview/BUILD.gn
@@ -807,7 +807,6 @@
     "//services/network/public:features_java",
     "//third_party/androidx:androidx_annotation_annotation_java",
     "//third_party/blink/public/common:common_java",
-    "//third_party/webrtc_overrides:webrtc_overrides_java",
   ]
   srcjar_deps = [
     ":common_java_features_srcjar",
diff --git a/android_webview/browser/gfx/output_surface_provider_webview.cc b/android_webview/browser/gfx/output_surface_provider_webview.cc
index 4e2c33f..12555ab 100644
--- a/android_webview/browser/gfx/output_surface_provider_webview.cc
+++ b/android_webview/browser/gfx/output_surface_provider_webview.cc
@@ -25,7 +25,6 @@
 #include "gpu/ipc/single_task_sequence.h"
 #include "ui/base/ui_base_switches.h"
 #include "ui/gfx/geometry/size.h"
-#include "ui/gl/gl_bindings.h"
 #include "ui/gl/gl_context.h"
 #include "ui/gl/gl_share_group.h"
 #include "ui/gl/gl_surface_egl.h"
@@ -113,7 +112,7 @@
   // If EGL supports EGL_ANGLE_external_context_and_surface, then we will create
   // an ANGLE context for the current native GL context.
   const bool is_angle =
-      !enable_vulkan_ && display->ext->b_EGL_ANGLE_external_context_and_surface;
+      !enable_vulkan_ && display->IsANGLEExternalContextAndSurfaceSupported();
 
   GLSurfaceContextPair real_context;
   if (enable_vulkan_) {
diff --git a/android_webview/browser/gfx/scoped_app_gl_state_restore.cc b/android_webview/browser/gfx/scoped_app_gl_state_restore.cc
index 35305c1..1289f30 100644
--- a/android_webview/browser/gfx/scoped_app_gl_state_restore.cc
+++ b/android_webview/browser/gfx/scoped_app_gl_state_restore.cc
@@ -32,7 +32,7 @@
 
   TRACE_EVENT0("android_webview", "AppGLStateSave");
   if (gl::GLSurfaceEGL::GetGLDisplayEGL()
-          ->ext->b_EGL_ANGLE_external_context_and_surface) {
+          ->IsANGLEExternalContextAndSurfaceSupported()) {
     impl_ = std::make_unique<internal::ScopedAppGLStateRestoreImplAngle>(
         mode, save_restore);
   } else {
diff --git a/android_webview/java/src/org/chromium/android_webview/common/ProductionSupportedFlagList.java b/android_webview/java/src/org/chromium/android_webview/common/ProductionSupportedFlagList.java
index 9fe24a0..9c2a946 100644
--- a/android_webview/java/src/org/chromium/android_webview/common/ProductionSupportedFlagList.java
+++ b/android_webview/java/src/org/chromium/android_webview/common/ProductionSupportedFlagList.java
@@ -19,7 +19,6 @@
 import org.chromium.gpu.config.GpuFeatures;
 import org.chromium.gpu.config.GpuSwitches;
 import org.chromium.services.network.NetworkServiceFeatures;
-import org.chromium.webrtc_overrides.WebRtcOverridesFeatures;
 
 /**
  * List of experimental features/flags supported for user devices. Add features/flags to this list
@@ -267,8 +266,6 @@
                     "Enables prefetching Android fonts on renderer startup."),
             Flag.baseFeature(AwFeatures.WEBVIEW_LEGACY_TLS_SUPPORT,
                     "Whether legacy TLS versions (TLS 1.0/1.1) conections are allowed."),
-            Flag.baseFeature(WebRtcOverridesFeatures.WEB_RTC_METRONOME_TASK_QUEUE,
-                    "Enables more efficient scheduling of work in WebRTC."),
             Flag.baseFeature(BlinkFeatures.INITIAL_NAVIGATION_ENTRY,
                     "Enables creation of initial NavigationEntries on WebContents creation."),
             Flag.baseFeature(BlinkFeatures.CANVAS2D_STAYS_GPU_ON_READBACK,
@@ -297,8 +294,6 @@
                     AwFeatures.WEBVIEW_SYNTHESIZE_PAGE_LOAD_ONLY_ON_INITIAL_MAIN_DOCUMENT_ACCESS,
                     "Only synthesize page load for URL spoof prevention at most once,"
                             + " on initial main document access."),
-            Flag.baseFeature(WebRtcOverridesFeatures.WEB_RTC_TIMER_USES_METRONOME,
-                    "Makes WebRtcTimer coalesce delayed tasks on metronome ticks."),
             Flag.baseFeature(BlinkFeatures.VIEWPORT_HEIGHT_CLIENT_HINT_HEADER,
                     "Enables the use of sec-ch-viewport-height client hint."),
             Flag.baseFeature(BlinkFeatures.USER_AGENT_OVERRIDE_EXPERIMENT,
diff --git a/android_webview/tools/cts_config/README.md b/android_webview/tools/cts_config/README.md
index c7130472..57bf2b4 100644
--- a/android_webview/tools/cts_config/README.md
+++ b/android_webview/tools/cts_config/README.md
@@ -36,6 +36,14 @@
               // be queryable by other APKs
               // Only usable from Android 11+
               "forced_queryable": true
+            },
+            {
+              "apk": "location of the additional apk in the cts zip file",
+              // An optional boolean flag to indicate an additional APK should always
+              // be installed in full mode, even when the tests are being run in
+              // instant mode
+              // This flag is only available for additional APKs
+              "force_full_mode": true
             }
           ]
         },
diff --git a/android_webview/tools/cts_config/webview_cts_gcs_path.json b/android_webview/tools/cts_config/webview_cts_gcs_path.json
index e6ddcc2..36b6ca2 100644
--- a/android_webview/tools/cts_config/webview_cts_gcs_path.json
+++ b/android_webview/tools/cts_config/webview_cts_gcs_path.json
@@ -170,7 +170,8 @@
             "apk": "android-cts/testcases/CtsAssistApp.apk"
           },
           {
-            "apk": "android-cts/testcases/CtsAssistService.apk"
+            "apk": "android-cts/testcases/CtsAssistService.apk",
+            "force_full_mode": true
           }
         ],
         "includes": [
@@ -220,7 +221,8 @@
             "apk": "android-cts/testcases/CtsAssistApp.apk"
           },
           {
-            "apk": "android-cts/testcases/CtsAssistService.apk"
+            "apk": "android-cts/testcases/CtsAssistService.apk",
+            "force_full_mode": true
           }
         ],
         "includes": [
@@ -289,7 +291,8 @@
             "apk": "android-cts/testcases/CtsAssistApp.apk"
           },
           {
-            "apk": "android-cts/testcases/CtsAssistService.apk"
+            "apk": "android-cts/testcases/CtsAssistService.apk",
+            "force_full_mode": true
           }
         ],
         "includes": [
@@ -389,7 +392,8 @@
             "apk": "android-cts/testcases/CtsAssistApp.apk"
           },
           {
-            "apk": "android-cts/testcases/CtsAssistService.apk"
+            "apk": "android-cts/testcases/CtsAssistService.apk",
+            "force_full_mode": true
           }
         ],
         "includes": [
@@ -480,7 +484,8 @@
             "apk": "android-cts/testcases/CtsAssistApp.apk"
           },
           {
-            "apk": "android-cts/testcases/CtsAssistService.apk"
+            "apk": "android-cts/testcases/CtsAssistService.apk",
+            "force_full_mode": true
           }
         ],
         "includes": [
@@ -494,7 +499,8 @@
         "additional_apks": [
           {
             "apk": "android-cts/testcases/CtsMockInputMethod.apk",
-            "forced_queryable": true
+            "forced_queryable": true,
+            "force_full_mode": true
           }
         ],
         "includes": [
@@ -605,7 +611,8 @@
             "apk": "android-cts/testcases/CtsAssistApp.apk"
           },
           {
-            "apk": "android-cts/testcases/CtsAssistService.apk"
+            "apk": "android-cts/testcases/CtsAssistService.apk",
+            "force_full_mode": true
           }
         ],
         "includes": [
@@ -619,7 +626,8 @@
         "additional_apks": [
           {
             "apk": "android-cts/testcases/CtsMockInputMethod.apk",
-            "forced_queryable": true
+            "forced_queryable": true,
+            "force_full_mode": true
           }
         ],
         "includes": [
diff --git a/android_webview/tools/run_cts.py b/android_webview/tools/run_cts.py
index ea6b15e..9729334 100755
--- a/android_webview/tools/run_cts.py
+++ b/android_webview/tools/run_cts.py
@@ -32,6 +32,9 @@
 # contents need to be updated if there is an important fix to any of
 # the tests
 
+_APP_MODE_FULL = 'full'
+_APP_MODE_INSTANT = 'instant'
+
 _TEST_RUNNER_PATH = os.path.join(
     os.path.dirname(__file__), os.pardir, os.pardir,
     'build', 'android', 'test_runner.py')
@@ -101,16 +104,16 @@
   return [os.path.basename(r['apk']) for r in test_runs]
 
 
-def GetTestRunFilterArg(args, test_run, arch=None):
+def GetTestRunFilterArg(args, test_run, test_app_mode=None, arch=None):
   """ Merges json file filters with cmdline filters using
       test_filter.InitializeFilterFromArgs
   """
 
+  test_app_mode = test_app_mode or _APP_MODE_FULL
+
   # Convert cmdline filters to test-filter style
   filter_string = test_filter.InitializeFilterFromArgs(args)
 
-  test_app_mode = 'instant' if args.test_apk_as_instant else 'full'
-
   # Get all the filters for either include or exclude patterns
   # and filter where an architecture is provided and does not match
   # The architecture is used when tests only fail on one architecture
@@ -144,6 +147,7 @@
            apk,
            voice_service=None,
            additional_apks=None,
+           test_app_mode=None,
            json_results_file=None):
   """Run tests in apk using test_runner script at _TEST_RUNNER_PATH.
 
@@ -151,6 +155,8 @@
   the json_results_file file if specified.
   """
 
+  test_app_mode = test_app_mode or _APP_MODE_FULL
+
   local_test_runner_args = test_runner_args + ['--test-apk',
                                                os.path.join(local_cts_dir, apk)]
 
@@ -167,6 +173,12 @@
             '--forced-queryable-additional-apk', additional_apk_tmp
         ]
 
+      if test_app_mode == _APP_MODE_INSTANT and not additional_apk.get(
+          'force_full_mode', False):
+        local_test_runner_args += [
+            '--instant-additional-apk', additional_apk_tmp
+        ]
+
   if json_results_file:
     local_test_runner_args += ['--json-results-file=%s' %
                                json_results_file]
@@ -253,25 +265,29 @@
       # services to run
       additional_apks = cts_test_run.get('additional_apks')
 
+      test_app_mode = (_APP_MODE_INSTANT
+                       if args.test_apk_as_instant else _APP_MODE_FULL)
+
       # If --module-apk is specified then skip tests in all other modules
       if args.module_apk and os.path.basename(test_apk) != args.module_apk:
         continue
 
       iter_test_runner_args = test_runner_args + GetTestRunFilterArg(
-          args, cts_test_run, arch)
+          args, cts_test_run, test_app_mode, arch)
 
       if json_results_file:
         with tempfile.NamedTemporaryFile() as iteration_json_file:
           iteration_cts_result = RunCTS(iter_test_runner_args, local_cts_dir,
                                         test_apk, voice_service,
-                                        additional_apks,
+                                        additional_apks, test_app_mode,
                                         iteration_json_file.name)
           with open(iteration_json_file.name) as f:
             additional_results_json = json.load(f)
             MergeTestResults(cts_results_json, additional_results_json)
       else:
         iteration_cts_result = RunCTS(iter_test_runner_args, local_cts_dir,
-                                      test_apk, voice_service, additional_apks)
+                                      test_apk, voice_service, test_app_mode,
+                                      additional_apks)
       if iteration_cts_result:
         cts_result = iteration_cts_result
     if json_results_file:
diff --git a/android_webview/tools/run_cts_test.py b/android_webview/tools/run_cts_test.py
index 5f59503..e3f329b8 100755
--- a/android_webview/tools/run_cts_test.py
+++ b/android_webview/tools/run_cts_test.py
@@ -140,7 +140,8 @@
     }
 
     self.assertEqual([run_cts.TEST_FILTER_OPT + '=good.test1:good.test2'],
-                     run_cts.GetTestRunFilterArg(mock_args, cts_run, 'x86'))
+                     run_cts.GetTestRunFilterArg(mock_args, cts_run,
+                                                 arch='x86'))
 
   def testFilter_ExcludesForArchitecture(self):
     mock_args = self._getArgsMock(skip_expected_failures=True)
@@ -160,10 +161,11 @@
     }
 
     self.assertEqual([run_cts.TEST_FILTER_OPT + '=-good.test1:good.test2'],
-                     run_cts.GetTestRunFilterArg(mock_args, cts_run, 'x86'))
+                     run_cts.GetTestRunFilterArg(mock_args, cts_run,
+                                                 arch='x86'))
 
   def testFilter_IncludesForMode(self):
-    mock_args = self._getArgsMock(test_apk_as_instant=True)
+    mock_args = self._getArgsMock()
 
     cts_run = {
         'apk':
@@ -180,11 +182,12 @@
     }
 
     self.assertEqual([run_cts.TEST_FILTER_OPT + '=good.test1:good.test2'],
-                     run_cts.GetTestRunFilterArg(mock_args, cts_run))
+                     run_cts.GetTestRunFilterArg(mock_args,
+                                                 cts_run,
+                                                 test_app_mode='instant'))
 
   def testFilter_ExcludesForMode(self):
-    mock_args = self._getArgsMock(test_apk_as_instant=True,
-                                  skip_expected_failures=True)
+    mock_args = self._getArgsMock(skip_expected_failures=True)
 
     cts_run = {
         'apk':
@@ -201,7 +204,9 @@
     }
 
     self.assertEqual([run_cts.TEST_FILTER_OPT + '=-good.test1:good.test2'],
-                     run_cts.GetTestRunFilterArg(mock_args, cts_run))
+                     run_cts.GetTestRunFilterArg(mock_args,
+                                                 cts_run,
+                                                 test_app_mode='instant'))
 
   def testIsolatedFilter_CombinesExcludedMatches(self):
     mock_args = self._getArgsMock(isolated_script_test_filter='good#test',
diff --git a/ash/components/phonehub/phone_status_processor_unittest.cc b/ash/components/phonehub/phone_status_processor_unittest.cc
index 83efe78..dff4943 100644
--- a/ash/components/phonehub/phone_status_processor_unittest.cc
+++ b/ash/components/phonehub/phone_status_processor_unittest.cc
@@ -152,6 +152,111 @@
   std::unique_ptr<PhoneStatusProcessor> phone_status_processor_;
 };
 
+TEST_F(PhoneStatusProcessorTest, PhoneStatusSnapshotUpdate_EcheDisabled) {
+  scoped_feature_list_.Reset();
+  scoped_feature_list_.InitWithFeatures(
+      /*enabled_features=*/{},
+      /*disabled_features=*/{features::kEcheSWA,
+                             features::kPhoneHubCameraRoll});
+
+  fake_multidevice_setup_client_->SetHostStatusWithDevice(
+      std::make_pair(HostStatus::kHostVerified, test_remote_device_));
+  CreatePhoneStatusProcessor();
+
+  auto expected_phone_properties = std::make_unique<proto::PhoneProperties>();
+  expected_phone_properties->set_notification_mode(
+      proto::NotificationMode::DO_NOT_DISTURB_ON);
+  expected_phone_properties->set_profile_type(
+      proto::ProfileType::DEFAULT_PROFILE);
+  expected_phone_properties->set_notification_access_state(
+      proto::NotificationAccessState::ACCESS_NOT_GRANTED);
+  expected_phone_properties->set_ring_status(
+      proto::FindMyDeviceRingStatus::RINGING);
+  expected_phone_properties->set_battery_percentage(24u);
+  expected_phone_properties->set_charging_state(
+      proto::ChargingState::CHARGING_AC);
+  expected_phone_properties->set_signal_strength(
+      proto::SignalStrength::FOUR_BARS);
+  expected_phone_properties->set_mobile_provider("google");
+  expected_phone_properties->set_connection_state(
+      proto::MobileConnectionState::SIM_WITH_RECEPTION);
+  expected_phone_properties->set_screen_lock_state(
+      proto::ScreenLockState::SCREEN_LOCK_UNKNOWN);
+  proto::CameraRollAccessState* access_state =
+      expected_phone_properties->mutable_camera_roll_access_state();
+  access_state->set_feature_enabled(true);
+  proto::FeatureSetupConfig* feature_setup_config =
+      expected_phone_properties->mutable_feature_setup_config();
+  feature_setup_config->set_feature_setup_request_supported(true);
+
+  expected_phone_properties->add_user_states();
+  proto::UserState* mutable_user_state =
+      expected_phone_properties->mutable_user_states(0);
+  mutable_user_state->set_user_id(1u);
+  mutable_user_state->set_is_quiet_mode_enabled(false);
+
+  proto::PhoneStatusSnapshot expected_snapshot;
+  expected_snapshot.set_allocated_properties(
+      expected_phone_properties.release());
+  expected_snapshot.add_notifications();
+  InitializeNotificationProto(expected_snapshot.mutable_notifications(0),
+                              /*id=*/0u);
+  auto* app = expected_snapshot.mutable_streamable_apps()->add_apps();
+  app->set_package_name("pkg1");
+  app->set_visible_name("vis");
+
+  // Simulate feature set to enabled and connected.
+  fake_feature_status_provider_->SetStatus(FeatureStatus::kEnabledAndConnected);
+  fake_multidevice_setup_client_->SetFeatureState(
+      Feature::kPhoneHubNotifications, FeatureState::kEnabledByUser);
+
+  // Simulate receiving a proto message.
+  fake_message_receiver_->NotifyPhoneStatusSnapshotReceived(expected_snapshot);
+
+  EXPECT_EQ(1u, fake_notification_manager_->num_notifications());
+  EXPECT_EQ(base::UTF8ToUTF16(test_remote_device_.name()),
+            *mutable_phone_model_->phone_name());
+  EXPECT_TRUE(fake_do_not_disturb_controller_->IsDndEnabled());
+  EXPECT_TRUE(fake_do_not_disturb_controller_->CanRequestNewDndState());
+  EXPECT_EQ(FindMyDeviceController::Status::kRingingOn,
+            fake_find_my_device_controller_->GetPhoneRingingStatus());
+  EXPECT_EQ(
+      MultideviceFeatureAccessManager::AccessStatus::kAvailableButNotGranted,
+      fake_multidevice_feature_access_manager_->GetNotificationAccessStatus());
+  EXPECT_EQ(
+      MultideviceFeatureAccessManager::AccessStatus::kAvailableButNotGranted,
+      fake_multidevice_feature_access_manager_->GetCameraRollAccessStatus());
+  EXPECT_TRUE(fake_multidevice_feature_access_manager_
+                  ->GetFeatureSetupRequestSupported());
+  EXPECT_EQ(ScreenLockManager::LockStatus::kUnknown,
+            fake_screen_lock_manager_->GetLockStatus());
+
+  absl::optional<PhoneStatusModel> phone_status_model =
+      mutable_phone_model_->phone_status_model();
+  EXPECT_EQ(PhoneStatusModel::ChargingState::kChargingAc,
+            phone_status_model->charging_state());
+  EXPECT_EQ(24u, phone_status_model->battery_percentage());
+  EXPECT_EQ(u"google",
+            phone_status_model->mobile_connection_metadata()->mobile_provider);
+  EXPECT_EQ(PhoneStatusModel::SignalStrength::kFourBars,
+            phone_status_model->mobile_connection_metadata()->signal_strength);
+  EXPECT_EQ(PhoneStatusModel::MobileStatus::kSimWithReception,
+            phone_status_model->mobile_status());
+
+  // Change feature status to disconnected.
+  fake_feature_status_provider_->SetStatus(
+      FeatureStatus::kEnabledButDisconnected);
+
+  EXPECT_EQ(0u, fake_notification_manager_->num_notifications());
+  EXPECT_EQ(base::UTF8ToUTF16(test_remote_device_.name()),
+            *mutable_phone_model_->phone_name());
+  EXPECT_FALSE(mutable_phone_model_->phone_status_model().has_value());
+
+  std::vector<RecentAppsInteractionHandler::UserState> user_states =
+      fake_recent_apps_interaction_handler_->user_states();
+  EXPECT_TRUE(user_states.empty());
+}
+
 TEST_F(PhoneStatusProcessorTest, PhoneStatusSnapshotUpdate) {
   fake_multidevice_setup_client_->SetHostStatusWithDevice(
       std::make_pair(HostStatus::kHostVerified, test_remote_device_));
@@ -195,6 +300,9 @@
   expected_snapshot.add_notifications();
   InitializeNotificationProto(expected_snapshot.mutable_notifications(0),
                               /*id=*/0u);
+  auto* app = expected_snapshot.mutable_streamable_apps()->add_apps();
+  app->set_package_name("pkg1");
+  app->set_visible_name("vis");
 
   // Simulate feature set to enabled and connected.
   fake_feature_status_provider_->SetStatus(FeatureStatus::kEnabledAndConnected);
diff --git a/ash/constants/ash_features.cc b/ash/constants/ash_features.cc
index 6da86ba4..66ee474 100644
--- a/ash/constants/ash_features.cc
+++ b/ash/constants/ash_features.cc
@@ -156,11 +156,6 @@
 const base::Feature kAssistMultiWordExpanded{"AssistMultiWordExpanded",
                                              base::FEATURE_DISABLED_BY_DEFAULT};
 
-// Controls whether to enable lacros support for the assistive multi word
-// suggestions feature.
-const base::Feature kAssistMultiWordLacrosSupport{
-    "AssistMultiWordLacrosSupport", base::FEATURE_DISABLED_BY_DEFAULT};
-
 // Controls whether to enable assistive personal information.
 const base::Feature kAssistPersonalInfo{"AssistPersonalInfo",
                                         base::FEATURE_DISABLED_BY_DEFAULT};
diff --git a/ash/constants/ash_features.h b/ash/constants/ash_features.h
index 568b28b..cbae848 100644
--- a/ash/constants/ash_features.h
+++ b/ash/constants/ash_features.h
@@ -76,8 +76,6 @@
 COMPONENT_EXPORT(ASH_CONSTANTS) extern const base::Feature kAssistMultiWord;
 COMPONENT_EXPORT(ASH_CONSTANTS)
 extern const base::Feature kAssistMultiWordExpanded;
-COMPONENT_EXPORT(ASH_CONSTANTS)
-extern const base::Feature kAssistMultiWordLacrosSupport;
 COMPONENT_EXPORT(ASH_CONSTANTS) extern const base::Feature kAssistPersonalInfo;
 COMPONENT_EXPORT(ASH_CONSTANTS)
 extern const base::Feature kAssistPersonalInfoAddress;
diff --git a/ash/services/ime/ime_service.cc b/ash/services/ime/ime_service.cc
index 711ea002..b07328f 100644
--- a/ash/services/ime/ime_service.cc
+++ b/ash/services/ime/ime_service.cc
@@ -181,9 +181,7 @@
     return chromeos::features::IsAssistiveMultiWordEnabled();
   }
   if (strcmp(feature_name, "AssistiveMultiWordLacrosSupport") == 0) {
-    return base::FeatureList::IsEnabled(
-               chromeos::features::kAssistMultiWordLacrosSupport) &&
-           chromeos::features::IsAssistiveMultiWordEnabled();
+    return true;
   }
   if (strcmp(feature_name, chromeos::features::kAutocorrectParamsTuning.name) ==
       0) {
diff --git a/ash/webui/camera_app_ui/resources/css/review.css b/ash/webui/camera_app_ui/resources/css/review.css
index 2fb9f1d..c705fa9d7 100644
--- a/ash/webui/camera_app_ui/resources/css/review.css
+++ b/ash/webui/camera_app_ui/resources/css/review.css
@@ -33,8 +33,8 @@
   align-items: center;
   bottom: 0;
   display: flex;
-  flex-direction: row-reverse;
   flex-wrap: wrap;
+  gap: 16px;
   position: absolute;
   top: calc(100% - calc(var(--bottom-line) * 2));
 }
@@ -47,18 +47,6 @@
   left: calc(var(--left-line) * 2);
 }
 
-.review-views .button-group button {
-  margin: 0 8px;
-}
-
-.review-views .button-group button:first-child {
-  margin-inline-end: 0;
-}
-
-.review-views .button-group button:last-child {
-  margin-inline-start: 0;
-}
-
 .review-views .button-group button[i18n-text=label_share]::before {
   content: url(/images/review_share.svg);
   display: inline-block;
diff --git a/ash/webui/camera_app_ui/resources/js/views/camera.ts b/ash/webui/camera_app_ui/resources/js/views/camera.ts
index 04e221b9..c60eb283 100644
--- a/ash/webui/camera_app_ui/resources/js/views/camera.ts
+++ b/ash/webui/camera_app_ui/resources/js/views/camera.ts
@@ -749,21 +749,25 @@
           nav.close(ViewName.FLASH);
         }
 
+        const negative = new review.OptionGroup({
+          template: review.ButtonGroupTemplate.NEGATIVE,
+          options: [
+            new review.Option({text: I18nString.LABEL_RETAKE}, {
+              callback: () => {
+                sendEvent(metrics.DocResultType.CANCELED);
+              },
+              exitValue: null,
+            }),
+            new review.Option({text: I18nString.LABEL_FIX_DOCUMENT}, {
+              callback: doRecrop,
+              hasPopup: true,
+            }),
+          ],
+        });
+
         const positive = new review.OptionGroup({
           template: review.ButtonGroupTemplate.POSITIVE,
           options: [
-            new review.Option({text: I18nString.LABEL_SAVE_PDF_DOCUMENT}, {
-              callback: () => {
-                sendEvent(metrics.DocResultType.SAVE_AS_PDF);
-              },
-              exitValue: MimeType.PDF,
-            }),
-            new review.Option({text: I18nString.LABEL_SAVE_PHOTO_DOCUMENT}, {
-              callback: () => {
-                sendEvent(metrics.DocResultType.SAVE_AS_PHOTO);
-              },
-              exitValue: MimeType.JPEG,
-            }),
             new review.Option({text: I18nString.LABEL_SHARE}, {
               callback: async () => {
                 sendEvent(metrics.DocResultType.SHARE);
@@ -772,26 +776,22 @@
                 await util.share(new File([docBlob], name, {type}));
               },
             }),
-          ],
-        });
-
-        const negative = new review.OptionGroup({
-          template: review.ButtonGroupTemplate.NEGATIVE,
-          options: [
-            new review.Option({text: I18nString.LABEL_FIX_DOCUMENT}, {
-              callback: doRecrop,
-              hasPopup: true,
-            }),
-            new review.Option({text: I18nString.LABEL_RETAKE}, {
+            new review.Option({text: I18nString.LABEL_SAVE_PHOTO_DOCUMENT}, {
               callback: () => {
-                sendEvent(metrics.DocResultType.CANCELED);
+                sendEvent(metrics.DocResultType.SAVE_AS_PHOTO);
               },
-              exitValue: null,
+              exitValue: MimeType.JPEG,
             }),
+            new review.Option(
+                {text: I18nString.LABEL_SAVE_PDF_DOCUMENT, primary: true}, {
+                  callback: () => {
+                    sendEvent(metrics.DocResultType.SAVE_AS_PDF);
+                  },
+                  exitValue: MimeType.PDF,
+                }),
           ],
         });
-
-        const mimeType = await this.review.startReview(positive, negative);
+        const mimeType = await this.review.startReview(negative, positive);
         assert(mimeType !== undefined);
         if (mimeType !== null) {
           result = {docBlob, mimeType};
@@ -838,25 +838,26 @@
     let result: boolean|null = false;
     await this.prepareReview(async () => {
       await this.review.setReviewPhoto(blob);
+      const negative = new review.OptionGroup({
+        template: review.ButtonGroupTemplate.NEGATIVE,
+        options: [new review.Option(
+            {text: I18nString.LABEL_RETAKE}, {exitValue: null})],
+      });
       const positive = new review.OptionGroup({
         template: review.ButtonGroupTemplate.POSITIVE,
         options: [
-          new review.Option({text: I18nString.LABEL_SAVE}, {exitValue: true}),
           new review.Option({text: I18nString.LABEL_SHARE}, {
             callback: async () => {
               sendEvent(metrics.GifResultType.SHARE);
               await util.share(new File([blob], name, {type: MimeType.GIF}));
             },
           }),
+          new review.Option(
+              {text: I18nString.LABEL_SAVE, primary: true}, {exitValue: true}),
         ],
       });
-      const negative = new review.OptionGroup({
-        template: review.ButtonGroupTemplate.NEGATIVE,
-        options: [new review.Option(
-            {text: I18nString.LABEL_RETAKE}, {exitValue: null})],
-      });
       nav.close(ViewName.FLASH);
-      result = (await this.review.startReview(positive, negative)) as boolean;
+      result = (await this.review.startReview(negative, positive)) as boolean;
     });
     if (result) {
       sendEvent(metrics.GifResultType.SAVE);
diff --git a/ash/webui/camera_app_ui/resources/js/views/camera_intent.ts b/ash/webui/camera_app_ui/resources/js/views/camera_intent.ts
index eadc1af7..70a0e96a 100644
--- a/ash/webui/camera_app_ui/resources/js/views/camera_intent.ts
+++ b/ash/webui/camera_app_ui/resources/js/views/camera_intent.ts
@@ -76,6 +76,7 @@
               {
                 label: I18nString.CONFIRM_REVIEW_BUTTON,
                 templateId: 'review-intent-button-template',
+                primary: true,
               },
               {exitValue: true}),
           new review.Option(
diff --git a/ash/webui/camera_app_ui/resources/js/views/crop_document.ts b/ash/webui/camera_app_ui/resources/js/views/crop_document.ts
index 1577391..0a43b9f4 100644
--- a/ash/webui/camera_app_ui/resources/js/views/crop_document.ts
+++ b/ash/webui/camera_app_ui/resources/js/views/crop_document.ts
@@ -349,8 +349,9 @@
     this.cornerSpaceSize = null;
     await super.startReview(new OptionGroup({
       template: ButtonGroupTemplate.POSITIVE,
-      options:
-          [new Option({text: I18nString.LABEL_CROP_DONE}, {exitValue: true})],
+      options: [new Option(
+          {text: I18nString.LABEL_CROP_DONE, primary: true},
+          {exitValue: true})],
     }));
     const newCorners = this.corners.map(({pt: {x, y}}) => {
       assert(this.cornerSpaceSize !== null);
diff --git a/ash/webui/camera_app_ui/resources/js/views/option_panel.ts b/ash/webui/camera_app_ui/resources/js/views/option_panel.ts
index f31d981..3ca1945 100644
--- a/ash/webui/camera_app_ui/resources/js/views/option_panel.ts
+++ b/ash/webui/camera_app_ui/resources/js/views/option_panel.ts
@@ -49,6 +49,7 @@
       span.setAttribute('i18n-aria', ariaLabel);
 
       const input = dom.getFrom(item, 'input', HTMLInputElement);
+      input.setAttribute('name', titleLabel);
       const stateEnabled = state.get(targetState);
       const checked = isDisableOption ? !stateEnabled : stateEnabled;
       input.checked = checked;
diff --git a/ash/webui/camera_app_ui/resources/js/views/review.ts b/ash/webui/camera_app_ui/resources/js/views/review.ts
index 4907388..0b10f93 100644
--- a/ash/webui/camera_app_ui/resources/js/views/review.ts
+++ b/ash/webui/camera_app_ui/resources/js/views/review.ts
@@ -18,6 +18,7 @@
   text?: I18nString;
   label?: I18nString;
   templateId?: string;
+  primary?: boolean;
 }
 
 /**
@@ -158,8 +159,12 @@
     }
     for (const btnGroup of this.btnGroups) {
       const addButton =
-          ({uiArgs: {text, label, templateId}, exitValue, callback, hasPopup}:
-               Option<T|null>) => {
+          ({
+            uiArgs: {text, label, templateId, primary},
+            exitValue,
+            callback,
+            hasPopup,
+          }: Option<T|null>) => {
             const templ = instantiateTemplate(
                 templateId !== undefined ? `#${templateId}` :
                                            '#text-button-template');
@@ -170,7 +175,7 @@
             if (label !== undefined) {
               btn.setAttribute('i18n-label', label);
             }
-            if (this.primaryBtn === null) {
+            if (this.primaryBtn === null && primary === true) {
               btn.classList.add('primary');
               this.primaryBtn = btn;
             } else {
diff --git a/ash/webui/camera_app_ui/resources/views/main.html b/ash/webui/camera_app_ui/resources/views/main.html
index ce418d4..f1816e2b 100644
--- a/ash/webui/camera_app_ui/resources/views/main.html
+++ b/ash/webui/camera_app_ui/resources/views/main.html
@@ -294,15 +294,15 @@
         <div id="ptz-panel-container" class="panel">
           <button id="tilt-up" class="inkdrop" i18n-label="tilt_up_button"
             tabindex="0"></button>
-          <button id="pan-right" class="inkdrop" i18n-label="pan_right_button"
-            tabindex="0"></button>
-          <button id="tilt-down" class="inkdrop" i18n-label="tilt_down_button"
-            tabindex="0"></button>
           <button id="pan-left" class="inkdrop" i18n-label="pan_left_button"
             tabindex="0"></button>
+          <button id="zoom-out" class="inkdrop" i18n-label="zoom_out_button"
+            tabindex="0"></button>
           <button id="zoom-in" class="inkdrop" i18n-label="zoom_in_button"
             tabindex="0"></button>
-          <button id="zoom-out" class="inkdrop" i18n-label="zoom_out_button"
+          <button id="pan-right" class="inkdrop" i18n-label="pan_right_button"
+            tabindex="0"></button>
+          <button id="tilt-down" class="inkdrop" i18n-label="tilt_down_button"
             tabindex="0"></button>
           <div id="ptz-divider1" class="ptz-divider"></div>
           <div id="ptz-divider2" class="ptz-divider"></div>
diff --git a/ash/webui/guest_os_installer/resources/app.ts b/ash/webui/guest_os_installer/resources/app.ts
index 3f7f036..d9a84f1 100644
--- a/ash/webui/guest_os_installer/resources/app.ts
+++ b/ash/webui/guest_os_installer/resources/app.ts
@@ -15,7 +15,7 @@
     return html`{__html_template__}`;
   }
 
-  listenerIds_: number[] = [];
+  private listenerIds_: number[] = [];
 
   override connectedCallback() {
     this.listenerIds_ = [];
diff --git a/ash/webui/os_feedback_ui/os_feedback_ui.cc b/ash/webui/os_feedback_ui/os_feedback_ui.cc
index 5a05e3a7..46f4d083 100644
--- a/ash/webui/os_feedback_ui/os_feedback_ui.cc
+++ b/ash/webui/os_feedback_ui/os_feedback_ui.cc
@@ -52,11 +52,14 @@
       {"popularHelpContent", IDS_FEEDBACK_TOOL_POPULAR_HELP_CONTENT},
       {"noMatchedResults", IDS_FEEDBACK_TOOL_NO_MATCHED_RESULTS},
       {"attachFilesLabel", IDS_FEEDBACK_TOOL_ATTACH_FILES_LABEL},
+      {"attachScreenshotLabel", IDS_FEEDBACK_TOOL_SCREENSHOT_LABEL},
       {"replaceFileLabel", IDS_FEEDBACK_TOOL_REPLACE_FILE_LABEL},
       {"userEmailLabel", IDS_FEEDBACK_TOOL_USER_EMAIL_LABEL},
       {"shareDiagnosticDataLabel",
        IDS_FEEDBACK_TOOL_SHARE_DIAGNOSTIC_DATA_LABEL},
       {"confirmationTitleOnline", IDS_FEEDBACK_TOOL_PAGE_TITLE_AFTER_SENT},
+      {"diagnosticsAppLabel",
+       IDS_FEEDBACK_TOOL_RESOURCES_DIAGNOSTICS_APP_LABEL},
   };
 
   source->AddLocalizedStrings(kLocalizedStrings);
diff --git a/ash/webui/os_feedback_ui/resources/confirmation_page.html b/ash/webui/os_feedback_ui/resources/confirmation_page.html
index 157a7b7..53ac7c6 100644
--- a/ash/webui/os_feedback_ui/resources/confirmation_page.html
+++ b/ash/webui/os_feedback_ui/resources/confirmation_page.html
@@ -26,7 +26,7 @@
           on-click="handleLinkClicked_">
       </cr-link-row>
       <cr-link-row id="diagnostics" start-icon="help-resources:diagnostics"
-          label="Diagnostics app" external
+          label="[[i18n('diagnosticsAppLabel')]]" external
           sub-label="Run tests and troubleshooting for hardware issues"
           on-click="handleLinkClicked_">
       </cr-link-row>
diff --git a/ash/webui/os_feedback_ui/resources/share_data_page.html b/ash/webui/os_feedback_ui/resources/share_data_page.html
index 10b8e02..584806d 100644
--- a/ash/webui/os_feedback_ui/resources/share_data_page.html
+++ b/ash/webui/os_feedback_ui/resources/share_data_page.html
@@ -82,7 +82,7 @@
           <input id="screenshotCheckbox" type="checkbox"
               aria-labelledby="screenshotCheckLabel"
               disabled="[[!hasScreenshot_(screenshotUrl)]]">
-          <label id="screenshotCheckLabel">Screenshot</label>
+          <label id="screenshotCheckLabel">[[i18n('attachScreenshotLabel')]]</label>
           <img id="screenshotImage" aria-label="$i18n{screenshotA11y}"
               src="[[screenshotUrl]]">
         </div>
diff --git a/ash/webui/personalization_app/resources/trusted/user/user_subpage_element.ts b/ash/webui/personalization_app/resources/trusted/user/user_subpage_element.ts
index e319e1d..34afd1bc 100644
--- a/ash/webui/personalization_app/resources/trusted/user/user_subpage_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/user/user_subpage_element.ts
@@ -34,7 +34,7 @@
   }
 
   path: string;
-  isUserImageEnterpriseManaged_: boolean|null;
+  private isUserImageEnterpriseManaged_: boolean|null;
 
   override connectedCallback() {
     super.connectedCallback();
diff --git a/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_subpage_element.ts b/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_subpage_element.ts
index c74a58d..8305d1d7 100644
--- a/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_subpage_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_subpage_element.ts
@@ -38,7 +38,7 @@
 
   path: string;
   queryParams: Record<string, string>;
-  currentSelected_: CurrentWallpaper|null;
+  private currentSelected_: CurrentWallpaper|null;
 
   override connectedCallback(): void {
     super.connectedCallback();
diff --git a/base/allocator/allocator.gni b/base/allocator/allocator.gni
index 97a57df..c3c62f8 100644
--- a/base/allocator/allocator.gni
+++ b/base/allocator/allocator.gni
@@ -112,7 +112,10 @@
   # Finch.
   use_fake_binary_experiment = false
 
-  use_asan_backup_ref_ptr = false
+  # The supported platforms are supposed to match `_is_brp_supported`, but we
+  # enable the feature on Linux early because it's most widely used for security
+  # research
+  use_asan_backup_ref_ptr = is_asan && (is_win || is_android || is_linux)
 }
 
 # Prevent using BackupRefPtr when PartitionAlloc-Everywhere isn't used.
diff --git a/base/allocator/partition_alloc_features.cc b/base/allocator/partition_alloc_features.cc
index 621f767b..d37b2500 100644
--- a/base/allocator/partition_alloc_features.cc
+++ b/base/allocator/partition_alloc_features.cc
@@ -55,7 +55,8 @@
 
 const Feature kPartitionAllocBackupRefPtr {
   "PartitionAllocBackupRefPtr",
-#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_WIN)
+#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_WIN) || \
+    (BUILDFLAG(USE_ASAN_BACKUP_REF_PTR) && BUILDFLAG(IS_LINUX))
       FEATURE_ENABLED_BY_DEFAULT
 #else
       FEATURE_DISABLED_BY_DEFAULT
diff --git a/base/memory/raw_ptr.cc b/base/memory/raw_ptr.cc
index 56b70fe..9bfe23b 100644
--- a/base/memory/raw_ptr.cc
+++ b/base/memory/raw_ptr.cc
@@ -128,6 +128,7 @@
 }
 }  // namespace
 
+NO_SANITIZE("address")
 void AsanBackupRefPtrImpl::AsanCheckIfValidDereference(
     void const volatile* ptr) {
   if (RawPtrAsanService::GetInstance().is_dereference_check_enabled() &&
@@ -138,6 +139,7 @@
   }
 }
 
+NO_SANITIZE("address")
 void AsanBackupRefPtrImpl::AsanCheckIfValidExtraction(
     void const volatile* ptr) {
   auto& service = RawPtrAsanService::GetInstance();
@@ -172,6 +174,7 @@
   }
 }
 
+NO_SANITIZE("address")
 void AsanBackupRefPtrImpl::AsanCheckIfValidInstantiation(
     void const volatile* ptr) {
   if (RawPtrAsanService::GetInstance().is_instantiation_check_enabled() &&
diff --git a/base/memory/raw_ptr_asan_service.cc b/base/memory/raw_ptr_asan_service.cc
index 844d9179..78d30ae9f 100644
--- a/base/memory/raw_ptr_asan_service.cc
+++ b/base/memory/raw_ptr_asan_service.cc
@@ -11,7 +11,6 @@
 #include <string.h>
 
 #include "base/check_op.h"
-#include "base/compiler_specific.h"
 #include "base/logging.h"
 #include "base/strings/stringprintf.h"
 
diff --git a/base/memory/raw_ptr_asan_service.h b/base/memory/raw_ptr_asan_service.h
index 8566634..7028e7a6 100644
--- a/base/memory/raw_ptr_asan_service.h
+++ b/base/memory/raw_ptr_asan_service.h
@@ -12,6 +12,7 @@
 #include <cstdint>
 
 #include "base/base_export.h"
+#include "base/compiler_specific.h"
 #include "base/types/strong_alias.h"
 
 namespace base {
@@ -44,17 +45,24 @@
 
   bool IsSupportedAllocation(void*) const;
 
-  bool is_dereference_check_enabled() const {
+  NO_SANITIZE("address")
+  ALWAYS_INLINE bool is_dereference_check_enabled() const {
     return is_dereference_check_enabled_;
   }
-  bool is_extraction_check_enabled() const {
+
+  NO_SANITIZE("address")
+  ALWAYS_INLINE bool is_extraction_check_enabled() const {
     return is_extraction_check_enabled_;
   }
-  bool is_instantiation_check_enabled() const {
+
+  NO_SANITIZE("address")
+  ALWAYS_INLINE bool is_instantiation_check_enabled() const {
     return is_instantiation_check_enabled_;
   }
 
-  static RawPtrAsanService& GetInstance() { return instance_; }
+  NO_SANITIZE("address") ALWAYS_INLINE static RawPtrAsanService& GetInstance() {
+    return instance_;
+  }
 
   static void SetPendingReport(ReportType type, const volatile void* ptr);
   static void Log(const char* format, ...);
diff --git a/build/android/pylib/instrumentation/instrumentation_test_instance.py b/build/android/pylib/instrumentation/instrumentation_test_instance.py
index b98c9310..7a1340b 100644
--- a/build/android/pylib/instrumentation/instrumentation_test_instance.py
+++ b/build/android/pylib/instrumentation/instrumentation_test_instance.py
@@ -630,6 +630,7 @@
 
     self._additional_apks = []
     self._forced_queryable_additional_apks = []
+    self._instant_additional_apks = []
     self._apk_under_test = None
     self._apk_under_test_incremental_install_json = None
     self._modules = None
@@ -808,7 +809,8 @@
           '(This may just mean that the test package is '
           'currently being installed.)', self._test_package)
 
-    for x in set(args.additional_apks + args.forced_queryable_additional_apks):
+    for x in set(args.additional_apks + args.forced_queryable_additional_apks +
+                 args.instant_additional_apks):
       if not os.path.exists(x):
         error_func('Unable to find additional APK: %s' % x)
 
@@ -818,6 +820,9 @@
       if x in args.forced_queryable_additional_apks:
         self._forced_queryable_additional_apks.append(apk)
 
+      if x in args.instant_additional_apks:
+        self._instant_additional_apks.append(apk)
+
   def _initializeDataDependencyAttributes(self, args, data_deps_delegate):
     self._data_deps = []
     self._data_deps_delegate = data_deps_delegate
@@ -1137,6 +1142,9 @@
   def IsApkForceQueryable(self, apk):
     return apk in self._forced_queryable_additional_apks
 
+  def IsApkInstant(self, apk):
+    return apk in self._instant_additional_apks
+
   # pylint: disable=no-self-use
   def _InflateTests(self, tests):
     inflated_tests = []
diff --git a/build/android/pylib/local/device/local_device_instrumentation_test_run.py b/build/android/pylib/local/device/local_device_instrumentation_test_run.py
index 6b8231f..510684c 100644
--- a/build/android/pylib/local/device/local_device_instrumentation_test_run.py
+++ b/build/android/pylib/local/device/local_device_instrumentation_test_run.py
@@ -288,7 +288,8 @@
                            instant_app=self._test_instance.test_apk_as_instant))
 
       steps.extend(
-          install_helper(apk) for apk in self._test_instance.additional_apks)
+          install_helper(apk, instant_app=self._test_instance.IsApkInstant(apk))
+          for apk in self._test_instance.additional_apks)
 
       # We'll potentially need the package names later for setting app
       # compatibility workarounds.
diff --git a/build/android/test_runner.py b/build/android/test_runner.py
index b8bdaee..4aea6f5 100755
--- a/build/android/test_runner.py
+++ b/build/android/test_runner.py
@@ -437,6 +437,12 @@
                       type=_RealPath,
                       help='Configures an additional-apk to be forced '
                       'to be queryable by other APKs.')
+  parser.add_argument('--instant-additional-apk',
+                      action='append',
+                      dest='instant_additional_apks',
+                      default=[],
+                      type=_RealPath,
+                      help='Configures an additional-apk to be an instant APK')
   parser.add_argument(
       '-A', '--annotation',
       dest='annotation_str',
diff --git a/chrome/BUILD.gn b/chrome/BUILD.gn
index abbbfc5db..06e3385 100644
--- a/chrome/BUILD.gn
+++ b/chrome/BUILD.gn
@@ -1569,12 +1569,14 @@
 }
 
 repack("unit_tests_pak") {
+  testonly = true
   sources = [ "$root_gen_dir/chrome/chrome_test_resources.pak" ]
   output = "$root_out_dir/unit_tests.pak"
   deps = [ "//chrome/test/data:chrome_test_resources" ]
 }
 
 repack("browser_tests_pak") {
+  testonly = true
   sources = [ "$root_gen_dir/chrome/webui_test_resources.pak" ]
   output = "$root_out_dir/browser_tests.pak"
   deps = [ "//chrome/test/data:webui_test_resources" ]
diff --git a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogTest.java b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogTest.java
index 69d7e918..9e7dfdf 100644
--- a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogTest.java
+++ b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogTest.java
@@ -812,8 +812,7 @@
     @MediumTest
     @DisabledTest(message = "TODO(crbug.com/1128345): Fix flakiness.")
     // clang-format off
-    @Features.EnableFeatures({ChromeFeatureList.TAB_GROUPS_CONTINUATION_ANDROID,
-            ChromeFeatureList.TAB_GRID_LAYOUT_ANDROID + "<Study"})
+    @Features.EnableFeatures({ChromeFeatureList.TAB_GROUPS_CONTINUATION_ANDROID + "<Study"})
     @CommandLineFlags.Add({"force-fieldtrials=Study/Group", TAB_GROUP_LAUNCH_POLISH_PARAMS})
     public void testAccessibilityString() throws ExecutionException {
         // clang-format on
diff --git a/chrome/android/java/res/layout/share_sheet_content.xml b/chrome/android/java/res/layout/share_sheet_content.xml
index f272986..ac498439 100644
--- a/chrome/android/java/res/layout/share_sheet_content.xml
+++ b/chrome/android/java/res/layout/share_sheet_content.xml
@@ -56,7 +56,7 @@
 
       <TextView
         android:id="@+id/subtitle_preview"
-        android:ellipsize="end"
+        android:ellipsize="start"
         android:layout_below="@id/title_preview"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityTest.java
index aba87c53..b2b94fe 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityTest.java
@@ -701,7 +701,6 @@
 
     @Test
     @SmallTest
-    @DisabledTest(message = "https://crbug.com/1148544")
     public void testCreateNewTab() throws Exception {
         final String testUrl = mTestServer.getURL(
                 "/chrome/test/data/android/customtabs/test_window_open.html");
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/site_settings/ManageSpaceActivityTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/site_settings/ManageSpaceActivityTest.java
index 5383798..265f4cb 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/site_settings/ManageSpaceActivityTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/site_settings/ManageSpaceActivityTest.java
@@ -28,7 +28,6 @@
 
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.CriteriaHelper;
-import org.chromium.base.test.util.DisabledTest;
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.browsing_data.BrowsingDataBridge;
@@ -106,7 +105,6 @@
     @Test
     @MediumTest
     @Feature({"SiteEngagement"})
-    @DisabledTest(message = "https://crbug.com/1060975")
     public void testClearUnimportantOnly() throws Exception {
         final String cookiesUrl =
                 mTestServer.getURL("/chrome/test/data/android/storage_persistance.html");
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/site_settings/SiteSettingsTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/site_settings/SiteSettingsTest.java
index d269f54e..c228463 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/site_settings/SiteSettingsTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/site_settings/SiteSettingsTest.java
@@ -821,7 +821,6 @@
     @Test
     @SmallTest
     @Feature({"Preferences"})
-    @DisabledTest(message = "Flaky - https://crbug.com/1313206")
     public void testPopupsNotBlocked() throws TimeoutException {
         new TwoStatePermissionTestCase(
                 "Popups", SiteSettingsCategory.Type.POPUPS, ContentSettingsType.POPUPS, true)
@@ -1504,7 +1503,6 @@
     @Test
     @SmallTest
     @Feature({"Preferences"})
-    @DisabledTest(message = "https://crbug.com/1094934")
     public void testEmbargoedNotificationCategorySiteSettings() throws Exception {
         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return;
         final String urlToEmbargo = mPermissionRule.getURLWithHostName(
diff --git a/chrome/browser/OWNERS b/chrome/browser/OWNERS
index 24c9190..647fce6 100644
--- a/chrome/browser/OWNERS
+++ b/chrome/browser/OWNERS
@@ -85,9 +85,7 @@
 per-file chrome_content_browser_client_binder_policies.*=file://ipc/SECURITY_OWNERS
 
 # BackForwardCache
-per-file chrome_back_forward_cache_browsertest.cc=arthursonzogni@chromium.org
-per-file chrome_back_forward_cache_browsertest.cc=altimin@chromium.org
-per-file chrome_back_forward_cache_browsertest.cc=rakina@chromium.org
+per-file chrome_back_forward_cache_browsertest.cc=file://content/browser/BACK_FORWARD_CACHE_OWNERS
 per-file chrome_back_forward_cache_browsertest.cc=file://content/OWNERS
 
 # Web Platform security metrics tests:
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 774928d5..ee27ac4 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -4698,10 +4698,6 @@
      flag_descriptions::kImeAssistMultiWordExpandedName,
      flag_descriptions::kImeAssistMultiWordExpandedDescription, kOsCrOS,
      FEATURE_VALUE_TYPE(chromeos::features::kAssistMultiWordExpanded)},
-    {"enable-cros-ime-assist-multi-word-lacros",
-     flag_descriptions::kImeAssistMultiWordLacrosSupportName,
-     flag_descriptions::kImeAssistMultiWordLacrosSupportDescription, kOsCrOS,
-     FEATURE_VALUE_TYPE(chromeos::features::kAssistMultiWordLacrosSupport)},
     {"enable-cros-ime-assist-personal-info",
      flag_descriptions::kImeAssistPersonalInfoName,
      flag_descriptions::kImeAssistPersonalInfoDescription, kOsCrOS,
@@ -4728,10 +4724,6 @@
      flag_descriptions::kImeStylusHandwritingName,
      flag_descriptions::kImeStylusHandwritingDescription, kOsCrOS,
      FEATURE_VALUE_TYPE(chromeos::features::kImeStylusHandwriting)},
-    {"enable-cros-language-settings-update-2",
-     flag_descriptions::kCrosLanguageSettingsUpdate2Name,
-     flag_descriptions::kCrosLanguageSettingsUpdate2Description, kOsCrOS,
-     FEATURE_VALUE_TYPE(chromeos::features::kLanguageSettingsUpdate2)},
     {"enable-cros-language-settings-update-japanese",
      flag_descriptions::kCrosLanguageSettingsUpdateJapaneseName,
      flag_descriptions::kCrosLanguageSettingsUpdateJapaneseDescription, kOsCrOS,
diff --git a/chrome/browser/apps/app_service/app_service_proxy_base.cc b/chrome/browser/apps/app_service/app_service_proxy_base.cc
index acb8c19..e38bc26 100644
--- a/chrome/browser/apps/app_service/app_service_proxy_base.cc
+++ b/chrome/browser/apps/app_service/app_service_proxy_base.cc
@@ -104,7 +104,7 @@
     : inner_icon_loader_(this),
       icon_coalescer_(&inner_icon_loader_),
       outer_icon_loader_(&icon_coalescer_,
-                         apps::IconCache::GarbageCollectionPolicy::kEager),
+                         IconCache::GarbageCollectionPolicy::kEager),
       profile_(profile) {
   if (base::FeatureList::IsEnabled(kAppServicePreferredAppsWithoutMojom)) {
     preferred_apps_impl_ = std::make_unique<apps::PreferredAppsImpl>(
@@ -114,7 +114,10 @@
 
 AppServiceProxyBase::~AppServiceProxyBase() = default;
 
-void AppServiceProxyBase::ReInitializeForTesting(Profile* profile) {
+void AppServiceProxyBase::ReInitializeForTesting(
+    Profile* profile,
+    base::OnceClosure read_completed_for_testing,
+    base::OnceClosure write_completed_for_testing) {
   // Some test code creates a profile and profile-linked services, like the App
   // Service, before the profile is fully initialized. Such tests can call this
   // after full profile initialization to ensure the App Service implementation
@@ -122,6 +125,13 @@
   app_service_.reset();
   profile_ = profile;
   is_using_testing_profile_ = true;
+  if (base::FeatureList::IsEnabled(kAppServicePreferredAppsWithoutMojom)) {
+    preferred_apps_impl_ = std::make_unique<apps::PreferredAppsImpl>(
+        this, profile ? profile->GetPath() : base::FilePath(),
+        std::move(read_completed_for_testing),
+        std::move(write_completed_for_testing));
+  }
+  publishers_.clear();
   Initialize();
 }
 
@@ -218,7 +228,8 @@
     ReplacedAppPreferences replaced_app_preferences) {
   for (const auto& iter : publishers_) {
     iter.second->OnPreferredAppSet(
-        app_id, intent_filter->Clone(), intent->Clone(),
+        app_id, intent_filter ? intent_filter->Clone() : nullptr,
+        intent ? intent->Clone() : nullptr,
         CloneIntentFiltersMap(replaced_app_preferences));
   }
 }
@@ -750,7 +761,7 @@
 void AppServiceProxyBase::OnApps(std::vector<AppPtr> deltas,
                                  AppType app_type,
                                  bool should_notify_initialized) {
-  if (app_service_.is_connected()) {
+  if (preferred_apps_impl_ || app_service_.is_connected()) {
     for (const auto& delta : deltas) {
       if (delta->readiness != Readiness::kUnknown &&
           !apps_util::IsInstalled(delta->readiness)) {
diff --git a/chrome/browser/apps/app_service/app_service_proxy_base.h b/chrome/browser/apps/app_service/app_service_proxy_base.h
index 2879e59..0433e90c 100644
--- a/chrome/browser/apps/app_service/app_service_proxy_base.h
+++ b/chrome/browser/apps/app_service/app_service_proxy_base.h
@@ -81,7 +81,10 @@
   AppServiceProxyBase& operator=(const AppServiceProxyBase&) = delete;
   ~AppServiceProxyBase() override;
 
-  void ReInitializeForTesting(Profile* profile);
+  void ReInitializeForTesting(
+      Profile* profile,
+      base::OnceClosure read_completed_for_testing = base::OnceClosure(),
+      base::OnceClosure write_completed_for_testing = base::OnceClosure());
 
   Profile* profile() const { return profile_; }
 
diff --git a/chrome/browser/apps/app_service/app_service_proxy_factory.cc b/chrome/browser/apps/app_service/app_service_proxy_factory.cc
index 31adec0..49c31ff 100644
--- a/chrome/browser/apps/app_service/app_service_proxy_factory.cc
+++ b/chrome/browser/apps/app_service/app_service_proxy_factory.cc
@@ -8,6 +8,7 @@
 #include "base/debug/dump_without_crashing.h"
 #include "build/chromeos_buildflags.h"
 #include "chrome/browser/apps/app_service/app_service_proxy.h"
+#include "chrome/browser/ash/system_web_apps/system_web_app_manager_factory.h"
 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
 #include "chrome/browser/profiles/incognito_helpers.h"
 #include "chrome/browser/profiles/profile.h"
@@ -93,6 +94,7 @@
   DependsOn(extensions::ExtensionRegistryFactory::GetInstance());
   DependsOn(HostContentSettingsMapFactory::GetInstance());
   DependsOn(web_app::WebAppProviderFactory::GetInstance());
+  DependsOn(ash::SystemWebAppManagerFactory::GetInstance());
 #if BUILDFLAG(IS_CHROMEOS_ASH)
   DependsOn(guest_os::GuestOsRegistryServiceFactory::GetInstance());
   DependsOn(NotificationDisplayServiceFactory::GetInstance());
diff --git a/chrome/browser/apps/app_service/app_service_proxy_unittest.cc b/chrome/browser/apps/app_service/app_service_proxy_unittest.cc
index fd1044f..dedc4dd 100644
--- a/chrome/browser/apps/app_service/app_service_proxy_unittest.cc
+++ b/chrome/browser/apps/app_service/app_service_proxy_unittest.cc
@@ -24,6 +24,7 @@
 #include "components/services/app_service/public/cpp/intent_filter_util.h"
 #include "components/services/app_service/public/cpp/intent_test_util.h"
 #include "components/services/app_service/public/cpp/intent_util.h"
+#include "components/services/app_service/public/cpp/preferred_app.h"
 #include "components/services/app_service/public/cpp/publisher_base.h"
 #include "components/services/app_service/public/mojom/types.mojom-shared.h"
 #include "components/services/app_service/public/mojom/types.mojom.h"
@@ -49,7 +50,7 @@
         app_type_(app_type),
         known_app_ids_(std::move(initial_app_ids)) {
     RegisterPublisher(app_type_);
-    CallOnApps();
+    CallOnApps(known_app_ids_, /*uninstall=*/false);
   }
 
   void LaunchAppWithParams(AppLaunchParams&& params,
@@ -62,15 +63,27 @@
                 bool allow_placeholder_icon,
                 LoadIconCallback callback) override {}
 
+  void UninstallApps(std::vector<std::string> app_ids) {
+    CallOnApps(app_ids, /*uninstall=*/true);
+
+    for (const auto& app_id : app_ids) {
+      known_app_ids_.push_back(app_id);
+    }
+  }
+
   bool AppHasSupportedLinksPreference(const std::string& app_id) {
     return supported_link_apps_.find(app_id) != supported_link_apps_.end();
   }
 
  private:
-  void CallOnApps() {
+  void CallOnApps(std::vector<std::string>& app_ids, bool uninstall) {
     std::vector<AppPtr> apps;
-    for (const auto& app_id : known_app_ids_) {
-      apps.push_back(std::make_unique<App>(app_type_, app_id));
+    for (const auto& app_id : app_ids) {
+      auto app = std::make_unique<App>(app_type_, app_id);
+      if (uninstall) {
+        app->readiness = Readiness::kUninstalledByUser;
+      }
+      apps.push_back(std::move(app));
     }
     AppPublisher::Publish(std::move(apps), app_type_,
                           /*should_notify_initialized=*/true);
@@ -106,6 +119,10 @@
     preferred_apps_list_.ApplyBulkUpdate(std::move(changes));
   }
 
+  void InitializePreferredApps(apps::PreferredApps preferred_apps) override {
+    preferred_apps_list_.Init(std::move(preferred_apps));
+  }
+
  private:
   apps::PreferredAppsList preferred_apps_list_;
 };
@@ -562,6 +579,128 @@
             proxy()->PreferredAppsList().FindPreferredAppForUrl(kTestUrl3));
 }
 
+TEST_F(AppServiceProxyPreferredAppsTest, PreferredApps) {
+  // Test Initialize.
+  GetPreferredAppsList().Init();
+
+  const char kAppId1[] = "abcdefg";
+  const char kAppId2[] = "aaaaaaa";
+  GURL filter_url = GURL("https://www.google.com/abc");
+  auto intent_filter = apps_util::MakeIntentFilterForUrlScope(filter_url);
+
+  GetPreferredAppsList().AddPreferredApp(kAppId1, intent_filter);
+
+  FakePublisherForProxyTest pub(proxy(), AppType::kArc,
+                                std::vector<std::string>{kAppId1, kAppId2});
+
+  // Test sync preferred app to all subscribers.
+  filter_url = GURL("https://www.abc.com/");
+  GURL another_filter_url = GURL("https://www.test.com/");
+  intent_filter = apps_util::MakeIntentFilterForUrlScope(filter_url);
+  auto another_intent_filter =
+      apps_util::MakeIntentFilterForUrlScope(another_filter_url);
+
+  EXPECT_EQ(absl::nullopt,
+            GetPreferredAppsList().FindPreferredAppForUrl(filter_url));
+  EXPECT_EQ(absl::nullopt,
+            GetPreferredAppsList().FindPreferredAppForUrl(another_filter_url));
+
+  proxy()->PreferredAppsImpl()->AddPreferredApp(
+      AppType::kUnknown, kAppId2, intent_filter->Clone(),
+      std::make_unique<Intent>(apps_util::kIntentActionView, filter_url),
+      /*from_publisher=*/true);
+  proxy()->PreferredAppsImpl()->AddPreferredApp(
+      AppType::kUnknown, kAppId2, another_intent_filter->Clone(),
+      std::make_unique<Intent>(apps_util::kIntentActionView,
+                               another_filter_url),
+      /*from_publisher=*/true);
+  EXPECT_EQ(kAppId2, GetPreferredAppsList().FindPreferredAppForUrl(filter_url));
+  EXPECT_EQ(kAppId2,
+            GetPreferredAppsList().FindPreferredAppForUrl(another_filter_url));
+
+  // Test that uninstall removes all the settings for the app.
+  pub.UninstallApps(std::vector<std::string>{kAppId2});
+  EXPECT_EQ(absl::nullopt,
+            GetPreferredAppsList().FindPreferredAppForUrl(filter_url));
+  EXPECT_EQ(absl::nullopt,
+            GetPreferredAppsList().FindPreferredAppForUrl(another_filter_url));
+
+  proxy()->PreferredAppsImpl()->AddPreferredApp(
+      AppType::kUnknown, kAppId2, intent_filter->Clone(),
+      std::make_unique<Intent>(apps_util::kIntentActionView, filter_url),
+      /*from_publisher=*/true);
+  proxy()->PreferredAppsImpl()->AddPreferredApp(
+      AppType::kUnknown, kAppId2, another_intent_filter->Clone(),
+      std::make_unique<Intent>(apps_util::kIntentActionView,
+                               another_filter_url),
+      /*from_publisher=*/true);
+
+  EXPECT_EQ(kAppId2, GetPreferredAppsList().FindPreferredAppForUrl(filter_url));
+  EXPECT_EQ(kAppId2,
+            GetPreferredAppsList().FindPreferredAppForUrl(another_filter_url));
+}
+
+// Tests that writing a preferred app value before the PreferredAppsList is
+// initialized queues the write for after initialization.
+TEST_F(AppServiceProxyPreferredAppsTest, PreferredAppsWriteBeforeInit) {
+  base::RunLoop run_loop_read;
+  proxy()->ReInitializeForTesting(proxy()->profile(),
+                                  run_loop_read.QuitClosure());
+  GURL filter_url("https://www.abc.com/");
+
+  std::string kAppId1 = "aaa";
+  std::string kAppId2 = "bbb";
+
+  proxy()->PreferredAppsImpl()->AddPreferredApp(
+      AppType::kArc, kAppId1,
+      apps_util::MakeIntentFilterForMimeType("image/png"), nullptr,
+      /*from_publisher=*/false);
+
+  IntentFilters filters;
+  filters.push_back(apps_util::MakeIntentFilterForUrlScope(filter_url));
+  proxy()->SetSupportedLinksPreference(kAppId2, std::move(filters));
+
+  // Wait for the preferred apps list initialization to read from disk.
+  run_loop_read.Run();
+
+  // Both changes to the PreferredAppsList should have been applied.
+  std::vector<GURL> filesystem_urls(
+      {GURL("filesystem:chrome://foo/image.png")});
+  std::vector<std::string> mime_types({"image/png"});
+  ASSERT_EQ(kAppId1,
+            GetPreferredAppsList().FindPreferredAppForIntent(
+                apps_util::MakeShareIntent(filesystem_urls, mime_types)));
+  ASSERT_EQ(kAppId2, GetPreferredAppsList().FindPreferredAppForUrl(filter_url));
+}
+
+TEST_F(AppServiceProxyPreferredAppsTest, PreferredAppsPersistency) {
+  const char kAppId1[] = "abcdefg";
+  GURL filter_url = GURL("https://www.google.com/abc");
+  auto intent_filter = apps_util::MakeIntentFilterForUrlScope(filter_url);
+  {
+    base::RunLoop run_loop_read;
+    base::RunLoop run_loop_write;
+    proxy()->ReInitializeForTesting(proxy()->profile(),
+                                    run_loop_read.QuitClosure(),
+                                    run_loop_write.QuitClosure());
+    run_loop_read.Run();
+    proxy()->PreferredAppsImpl()->AddPreferredApp(
+        AppType::kUnknown, kAppId1, intent_filter->Clone(),
+        std::make_unique<Intent>(apps_util::kIntentActionView, filter_url),
+        /*from_publisher=*/false);
+    run_loop_write.Run();
+  }
+  // Create a new impl to initialize preferred apps from the disk.
+  {
+    base::RunLoop run_loop_read;
+    proxy()->ReInitializeForTesting(proxy()->profile(),
+                                    run_loop_read.QuitClosure());
+    run_loop_read.Run();
+    EXPECT_EQ(kAppId1,
+              GetPreferredAppsList().FindPreferredAppForUrl(filter_url));
+  }
+}
+
 TEST_F(AppServiceProxyPreferredAppsTest,
        PreferredAppsSetSupportedLinksPublisher) {
   GetPreferredAppsList().Init();
@@ -632,6 +771,68 @@
   EXPECT_FALSE(pub.AppHasSupportedLinksPreference(kAppId3));
 }
 
+// Test that app with overlapped works properly.
+TEST_F(AppServiceProxyPreferredAppsTest, PreferredAppsOverlap) {
+  // Test Initialize.
+  GetPreferredAppsList().Init();
+
+  const char kAppId1[] = "abcdefg";
+  const char kAppId2[] = "hijklmn";
+
+  GURL filter_url_1 = GURL("https://www.google.com/abc");
+  GURL filter_url_2 = GURL("http://www.google.com.au/abc");
+  GURL filter_url_3 = GURL("https://www.abc.com/abc");
+
+  auto intent_filter_1 = apps_util::MakeIntentFilterForUrlScope(filter_url_1);
+  apps_util::AddConditionValue(ConditionType::kScheme, filter_url_2.scheme(),
+                               PatternMatchType::kNone, intent_filter_1);
+  apps_util::AddConditionValue(ConditionType::kHost, filter_url_2.host(),
+                               PatternMatchType::kNone, intent_filter_1);
+
+  auto intent_filter_2 = apps_util::MakeIntentFilterForUrlScope(filter_url_3);
+  apps_util::AddConditionValue(ConditionType::kScheme, filter_url_2.scheme(),
+                               PatternMatchType::kNone, intent_filter_2);
+  apps_util::AddConditionValue(ConditionType::kHost, filter_url_2.host(),
+                               PatternMatchType::kNone, intent_filter_2);
+
+  auto intent_filter_3 = apps_util::MakeIntentFilterForUrlScope(filter_url_1);
+
+  EXPECT_EQ(absl::nullopt,
+            GetPreferredAppsList().FindPreferredAppForUrl(filter_url_1));
+  EXPECT_EQ(absl::nullopt,
+            GetPreferredAppsList().FindPreferredAppForUrl(filter_url_2));
+  EXPECT_EQ(absl::nullopt,
+            GetPreferredAppsList().FindPreferredAppForUrl(filter_url_3));
+  EXPECT_EQ(0U, GetPreferredAppsList().GetEntrySize());
+  EXPECT_EQ(0U, GetPreferredAppsList().GetEntrySize());
+
+  proxy()->PreferredAppsImpl()->AddPreferredApp(
+      AppType::kArc, kAppId1, intent_filter_1->Clone(),
+      std::make_unique<Intent>(apps_util::kIntentActionView, filter_url_1),
+      /*from_publisher=*/true);
+  EXPECT_EQ(kAppId1,
+            GetPreferredAppsList().FindPreferredAppForUrl(filter_url_1));
+  EXPECT_EQ(kAppId1,
+            GetPreferredAppsList().FindPreferredAppForUrl(filter_url_2));
+  EXPECT_EQ(absl::nullopt,
+            GetPreferredAppsList().FindPreferredAppForUrl(filter_url_3));
+  EXPECT_EQ(1U, GetPreferredAppsList().GetEntrySize());
+
+  // Add preferred app with intent filter overlap with existing entry for
+  // another app will reset the preferred app setting for the other app.
+  proxy()->PreferredAppsImpl()->AddPreferredApp(
+      AppType::kArc, kAppId2, intent_filter_2->Clone(),
+      std::make_unique<Intent>(apps_util::kIntentActionView, filter_url_1),
+      /*from_publisher=*/true);
+  EXPECT_EQ(absl::nullopt,
+            GetPreferredAppsList().FindPreferredAppForUrl(filter_url_1));
+  EXPECT_EQ(kAppId2,
+            GetPreferredAppsList().FindPreferredAppForUrl(filter_url_2));
+  EXPECT_EQ(kAppId2,
+            GetPreferredAppsList().FindPreferredAppForUrl(filter_url_3));
+  EXPECT_EQ(1U, GetPreferredAppsList().GetEntrySize());
+}
+
 // Test that app with overlapped supported links works properly.
 TEST_F(AppServiceProxyPreferredAppsTest, PreferredAppsOverlapSupportedLink) {
   // Test Initialize.
@@ -720,6 +921,37 @@
   EXPECT_EQ(2U, GetPreferredAppsList().GetEntrySize());
 }
 
+// Test that duplicated entry will not be added.
+TEST_F(AppServiceProxyPreferredAppsTest, PreferredAppsDuplicated) {
+  // Test Initialize.
+  GetPreferredAppsList().Init();
+
+  const char kAppId1[] = "abcdefg";
+
+  GURL filter_url = GURL("https://www.google.com/abc");
+
+  auto intent_filter = apps_util::MakeIntentFilterForUrlScope(filter_url);
+
+  EXPECT_EQ(absl::nullopt,
+            GetPreferredAppsList().FindPreferredAppForUrl(filter_url));
+  EXPECT_EQ(0U, GetPreferredAppsList().GetEntrySize());
+
+  proxy()->PreferredAppsImpl()->AddPreferredApp(
+      AppType::kArc, kAppId1, intent_filter->Clone(),
+      std::make_unique<Intent>(apps_util::kIntentActionView, filter_url),
+      /*from_publisher=*/true);
+  EXPECT_EQ(kAppId1, GetPreferredAppsList().FindPreferredAppForUrl(filter_url));
+  EXPECT_EQ(1U, GetPreferredAppsList().GetEntrySize());
+  EXPECT_EQ(1U, GetPreferredAppsList().GetEntrySize());
+
+  proxy()->PreferredAppsImpl()->AddPreferredApp(
+      AppType::kArc, kAppId1, intent_filter->Clone(),
+      std::make_unique<Intent>(apps_util::kIntentActionView, filter_url),
+      /*from_publisher=*/true);
+  EXPECT_EQ(kAppId1, GetPreferredAppsList().FindPreferredAppForUrl(filter_url));
+  EXPECT_EQ(1U, GetPreferredAppsList().GetEntrySize());
+}
+
 // Test that duplicated entry will not be added for supported links.
 TEST_F(AppServiceProxyPreferredAppsTest, PreferredAppsDuplicatedSupportedLink) {
   // Test Initialize.
diff --git a/chrome/browser/apps/app_service/subscriber_crosapi.cc b/chrome/browser/apps/app_service/subscriber_crosapi.cc
index d90dd72..d1d2962 100644
--- a/chrome/browser/apps/app_service/subscriber_crosapi.cc
+++ b/chrome/browser/apps/app_service/subscriber_crosapi.cc
@@ -18,6 +18,7 @@
 #include "chrome/browser/ui/webui/settings/ash/app_management/app_management_uma.h"
 #include "components/services/app_service/public/cpp/app_registry_cache.h"
 #include "components/services/app_service/public/cpp/app_types.h"
+#include "components/services/app_service/public/cpp/features.h"
 #include "components/services/app_service/public/mojom/types.mojom.h"
 
 namespace {
@@ -191,8 +192,13 @@
 
 void SubscriberCrosapi::AddPreferredApp(const std::string& app_id,
                                         crosapi::mojom::IntentPtr intent) {
-  proxy_->AddPreferredApp(
-      app_id, apps_util::ConvertCrosapiToAppServiceIntent(intent, profile_));
+  if (base::FeatureList::IsEnabled(kAppServicePreferredAppsWithoutMojom)) {
+    proxy_->AddPreferredApp(
+        app_id, apps_util::CreateAppServiceIntentFromCrosapi(intent, profile_));
+  } else {
+    proxy_->AddPreferredApp(
+        app_id, apps_util::ConvertCrosapiToAppServiceIntent(intent, profile_));
+  }
 }
 
 void SubscriberCrosapi::ShowAppManagementPage(const std::string& app_id) {
diff --git a/chrome/browser/apps/app_service/subscriber_crosapi.h b/chrome/browser/apps/app_service/subscriber_crosapi.h
index c8dfb9f..ffbea681 100644
--- a/chrome/browser/apps/app_service/subscriber_crosapi.h
+++ b/chrome/browser/apps/app_service/subscriber_crosapi.h
@@ -46,7 +46,7 @@
 
   void OnApps(const std::vector<AppPtr>& deltas);
 
-  void InitializePreferredApps(PreferredApps preferred_apps);
+  virtual void InitializePreferredApps(PreferredApps preferred_apps);
   virtual void OnPreferredAppsChanged(PreferredAppChangesPtr changes);
 
  protected:
diff --git a/chrome/browser/apps/platform_apps/platform_app_navigation_redirector.cc b/chrome/browser/apps/platform_apps/platform_app_navigation_redirector.cc
index 45ad314..b9f5a2315 100644
--- a/chrome/browser/apps/platform_apps/platform_app_navigation_redirector.cc
+++ b/chrome/browser/apps/platform_apps/platform_app_navigation_redirector.cc
@@ -32,6 +32,7 @@
                       const std::string& handler_id,
                       content::NavigationHandle* navigation_handle) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  DCHECK(navigation_handle->IsInMainFrame());
 
   // Redirect top-level navigations only. This excludes iframes and webviews
   // in particular.
@@ -40,6 +41,11 @@
     return false;
   }
 
+  if (navigation_handle->IsInPrerenderedMainFrame()) {
+    // If it's from prerendering, don't launch the app but abort the navigation.
+    return true;
+  }
+
   // If no-state prefetching, don't launch the app but abort the navigation.
   prerender::NoStatePrefetchContents* no_state_prefetch_contents =
       prerender::ChromeNoStatePrefetchContentsDelegate::FromWebContents(
diff --git a/chrome/browser/apps/platform_apps/platform_app_navigation_redirector_browsertest.cc b/chrome/browser/apps/platform_apps/platform_app_navigation_redirector_browsertest.cc
index 91377d0..3d41585 100644
--- a/chrome/browser/apps/platform_apps/platform_app_navigation_redirector_browsertest.cc
+++ b/chrome/browser/apps/platform_apps/platform_app_navigation_redirector_browsertest.cc
@@ -13,6 +13,7 @@
 #include "content/public/test/browser_test.h"
 #include "content/public/test/browser_test_base.h"
 #include "content/public/test/browser_test_utils.h"
+#include "content/public/test/prerender_test_util.h"
 #include "extensions/test/extension_test_message_listener.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
 #include "ui/base/page_transition_types.h"
@@ -425,4 +426,53 @@
                        "XHR failed", "url_handlers/handlers/steal_xhr_target");
 }
 
+class PlatformAppNavigationRedirectorPrerenderingBrowserTest
+    : public PlatformAppNavigationRedirectorBrowserTest {
+ public:
+  PlatformAppNavigationRedirectorPrerenderingBrowserTest()
+      : prerender_helper_(base::BindRepeating(
+            &PlatformAppNavigationRedirectorPrerenderingBrowserTest::
+                GetWebContents,
+            base::Unretained(this))) {}
+  ~PlatformAppNavigationRedirectorPrerenderingBrowserTest() override = default;
+
+  content::WebContents* GetWebContents() {
+    return browser()->tab_strip_model()->GetActiveWebContents();
+  }
+
+  content::test::PrerenderTestHelper& prerender_helper() {
+    return prerender_helper_;
+  }
+
+ private:
+  content::test::PrerenderTestHelper prerender_helper_;
+};
+
+// Test that prerendering doesn't launch an app but aborts the navigation.
+IN_PROC_BROWSER_TEST_F(PlatformAppNavigationRedirectorPrerenderingBrowserTest,
+                       DoNotLaunchAppInPrerendering) {
+  ASSERT_TRUE(StartEmbeddedTestServer());
+  const char* handler = "url_handlers/handlers/simple";
+  InstallPlatformApp(handler);
+
+  const auto initial_url = embedded_test_server()->GetURL("/empty.html");
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url));
+  EXPECT_EQ(initial_url, GetWebContents()->GetLastCommittedURL());
+
+  const auto prerender_url = embedded_test_server()->GetURL(
+      "/extensions/platform_apps/url_handlers/common/target.html");
+
+  // Loading an app URL in prerendering cancels prerendering.
+  prerender_helper().AddPrerenderAsync(prerender_url);
+  content::test::PrerenderHostObserver host_observer(*GetWebContents(),
+                                                     prerender_url);
+  // Wait until PrerenderHost is destroyed by canceling prerendering.
+  host_observer.WaitForDestroyed();
+
+  // The primary page doesn't have any change.
+  EXPECT_EQ(initial_url, GetWebContents()->GetLastCommittedURL());
+  EXPECT_EQ(1, browser()->tab_strip_model()->count());
+  EXPECT_EQ(0U, GetAppWindowCount());
+}
+
 }  // namespace extensions
diff --git a/chrome/browser/ash/BUILD.gn b/chrome/browser/ash/BUILD.gn
index 9d9b08d..12a9e90 100644
--- a/chrome/browser/ash/BUILD.gn
+++ b/chrome/browser/ash/BUILD.gn
@@ -1116,6 +1116,176 @@
     "floating_workspace/floating_workspace_util.h",
     "fusebox/fusebox_util.cc",
     "fusebox/fusebox_util.h",
+    "guest_os/guest_os_capabilities.cc",
+    "guest_os/guest_os_capabilities.h",
+    "guest_os/guest_os_diagnostics_builder.cc",
+    "guest_os/guest_os_diagnostics_builder.h",
+    "guest_os/guest_os_external_protocol_handler.cc",
+    "guest_os/guest_os_external_protocol_handler.h",
+    "guest_os/guest_os_launcher.cc",
+    "guest_os/guest_os_launcher.h",
+    "guest_os/guest_os_mime_types_service.cc",
+    "guest_os/guest_os_mime_types_service.h",
+    "guest_os/guest_os_mime_types_service_factory.cc",
+    "guest_os/guest_os_mime_types_service_factory.h",
+    "guest_os/guest_os_pref_names.cc",
+    "guest_os/guest_os_pref_names.h",
+    "guest_os/guest_os_registry_service.cc",
+    "guest_os/guest_os_registry_service.h",
+    "guest_os/guest_os_registry_service_factory.cc",
+    "guest_os/guest_os_registry_service_factory.h",
+    "guest_os/guest_os_share_path.cc",
+    "guest_os/guest_os_share_path.h",
+    "guest_os/guest_os_share_path_factory.cc",
+    "guest_os/guest_os_share_path_factory.h",
+    "guest_os/guest_os_stability_monitor.cc",
+    "guest_os/guest_os_stability_monitor.h",
+    "guest_os/infra/cached_callback.h",
+    "guest_os/public/guest_os_mount_provider.cc",
+    "guest_os/public/guest_os_mount_provider.h",
+    "guest_os/public/guest_os_mount_provider_registry.cc",
+    "guest_os/public/guest_os_mount_provider_registry.h",
+    "guest_os/public/guest_os_service.cc",
+    "guest_os/public/guest_os_service.h",
+    "guest_os/public/guest_os_service_factory.cc",
+    "guest_os/public/guest_os_service_factory.h",
+    "guest_os/public/guest_os_terminal_provider.cc",
+    "guest_os/public/guest_os_terminal_provider.h",
+    "guest_os/public/guest_os_terminal_provider_registry.cc",
+    "guest_os/public/guest_os_terminal_provider_registry.h",
+    "guest_os/public/guest_os_wayland_server.cc",
+    "guest_os/public/guest_os_wayland_server.h",
+    "guest_os/public/installer_delegate_factory.cc",
+    "guest_os/public/installer_delegate_factory.h",
+    "guest_os/public/types.h",
+    "guest_os/virtual_machines/virtual_machines_util.cc",
+    "guest_os/virtual_machines/virtual_machines_util.h",
+    "guest_os/vm_sk_forwarding_native_message_host.cc",
+    "guest_os/vm_sk_forwarding_native_message_host.h",
+    "hats/hats_config.cc",
+    "hats/hats_config.h",
+    "hats/hats_dialog.cc",
+    "hats/hats_dialog.h",
+    "hats/hats_finch_helper.cc",
+    "hats/hats_finch_helper.h",
+    "hats/hats_notification_controller.cc",
+    "hats/hats_notification_controller.h",
+    "idle_detector.cc",
+    "idle_detector.h",
+    "input_method/accessibility.cc",
+    "input_method/accessibility.h",
+    "input_method/assistive_suggester.cc",
+    "input_method/assistive_suggester.h",
+    "input_method/assistive_suggester_client_filter.cc",
+    "input_method/assistive_suggester_client_filter.h",
+    "input_method/assistive_suggester_prefs.cc",
+    "input_method/assistive_suggester_prefs.h",
+    "input_method/assistive_suggester_switch.h",
+    "input_method/assistive_window_controller.cc",
+    "input_method/assistive_window_controller.h",
+    "input_method/assistive_window_controller_delegate.h",
+    "input_method/assistive_window_properties.cc",
+    "input_method/assistive_window_properties.h",
+    "input_method/autocorrect_manager.cc",
+    "input_method/autocorrect_manager.h",
+    "input_method/candidate_window_controller.cc",
+    "input_method/candidate_window_controller.h",
+    "input_method/candidate_window_controller_impl.cc",
+    "input_method/candidate_window_controller_impl.h",
+    "input_method/component_extension_ime_manager_delegate_impl.cc",
+    "input_method/component_extension_ime_manager_delegate_impl.h",
+    "input_method/diacritics_checker.cc",
+    "input_method/diacritics_checker.h",
+    "input_method/diacritics_insensitive_string_comparator.cc",
+    "input_method/diacritics_insensitive_string_comparator.h",
+    "input_method/emoji_suggester.cc",
+    "input_method/emoji_suggester.h",
+    "input_method/get_browser_url.cc",
+    "input_method/get_browser_url.h",
+    "input_method/grammar_manager.cc",
+    "input_method/grammar_manager.h",
+    "input_method/grammar_service_client.cc",
+    "input_method/grammar_service_client.h",
+    "input_method/ime_rules_config.cc",
+    "input_method/ime_rules_config.h",
+    "input_method/ime_service_connector.cc",
+    "input_method/ime_service_connector.h",
+    "input_method/input_method_configuration.cc",
+    "input_method/input_method_configuration.h",
+    "input_method/input_method_delegate_impl.cc",
+    "input_method/input_method_delegate_impl.h",
+    "input_method/input_method_engine.cc",
+    "input_method/input_method_engine.h",
+    "input_method/input_method_engine_observer.h",
+    "input_method/input_method_manager_impl.cc",
+    "input_method/input_method_manager_impl.h",
+    "input_method/input_method_persistence.cc",
+    "input_method/input_method_persistence.h",
+    "input_method/input_method_quick_settings_helpers.cc",
+    "input_method/input_method_quick_settings_helpers.h",
+    "input_method/input_method_settings.cc",
+    "input_method/input_method_settings.h",
+    "input_method/input_method_syncer.cc",
+    "input_method/input_method_syncer.h",
+    "input_method/longpress_diacritics_suggester.cc",
+    "input_method/longpress_diacritics_suggester.h",
+    "input_method/multi_word_suggester.cc",
+    "input_method/multi_word_suggester.h",
+    "input_method/native_input_method_engine.cc",
+    "input_method/native_input_method_engine.h",
+    "input_method/native_input_method_engine_observer.cc",
+    "input_method/native_input_method_engine_observer.h",
+    "input_method/personal_info_suggester.cc",
+    "input_method/personal_info_suggester.h",
+    "input_method/suggester.h",
+    "input_method/suggestion_enums.h",
+    "input_method/suggestion_handler_interface.h",
+    "input_method/suggestions_collector.cc",
+    "input_method/suggestions_collector.h",
+    "input_method/suggestions_service_client.cc",
+    "input_method/suggestions_service_client.h",
+    "input_method/suggestions_source.h",
+    "input_method/text_field_contextual_info_fetcher.cc",
+    "input_method/text_field_contextual_info_fetcher.h",
+    "input_method/text_utils.cc",
+    "input_method/text_utils.h",
+    "input_method/ui/assistive_accessibility_view.cc",
+    "input_method/ui/assistive_accessibility_view.h",
+    "input_method/ui/assistive_delegate.h",
+    "input_method/ui/border_factory.cc",
+    "input_method/ui/border_factory.h",
+    "input_method/ui/candidate_view.cc",
+    "input_method/ui/candidate_view.h",
+    "input_method/ui/candidate_window_constants.h",
+    "input_method/ui/candidate_window_view.cc",
+    "input_method/ui/candidate_window_view.h",
+    "input_method/ui/colors.cc",
+    "input_method/ui/colors.h",
+    "input_method/ui/completion_suggestion_label_view.cc",
+    "input_method/ui/completion_suggestion_label_view.h",
+    "input_method/ui/completion_suggestion_view.cc",
+    "input_method/ui/completion_suggestion_view.h",
+    "input_method/ui/grammar_suggestion_window.cc",
+    "input_method/ui/grammar_suggestion_window.h",
+    "input_method/ui/infolist_window.cc",
+    "input_method/ui/infolist_window.h",
+    "input_method/ui/input_method_menu_item.cc",
+    "input_method/ui/input_method_menu_item.h",
+    "input_method/ui/input_method_menu_manager.cc",
+    "input_method/ui/input_method_menu_manager.h",
+    "input_method/ui/suggestion_accessibility_label.cc",
+    "input_method/ui/suggestion_accessibility_label.h",
+    "input_method/ui/suggestion_details.h",
+    "input_method/ui/suggestion_window_view.cc",
+    "input_method/ui/suggestion_window_view.h",
+    "input_method/ui/undo_window.cc",
+    "input_method/ui/undo_window.h",
+    "kerberos/kerberos_credentials_manager.cc",
+    "kerberos/kerberos_credentials_manager.h",
+    "kerberos/kerberos_credentials_manager_factory.cc",
+    "kerberos/kerberos_credentials_manager_factory.h",
+    "kerberos/kerberos_ticket_expiry_notification.cc",
+    "kerberos/kerberos_ticket_expiry_notification.h",
   ]
 
   allow_circular_includes_from = [
@@ -1146,18 +1316,24 @@
     "//ash/components/login/auth",
     "//ash/components/settings",
     "//ash/components/tpm",
+    "//ash/constants",
     "//ash/public/cpp",
     "//ash/public/cpp/external_arc",
     "//ash/services/device_sync/proto",
     "//ash/services/device_sync/public/cpp",
+    "//ash/services/ime/public/cpp:structs",
+    "//ash/services/ime/public/mojom",
     "//ash/services/multidevice_setup/public/cpp",
     "//ash/services/multidevice_setup/public/cpp:android_sms_app_helper_delegate",
     "//ash/services/multidevice_setup/public/cpp:android_sms_pairing_state_tracker",
     "//ash/webui/eche_app_ui",
+    "//ash/webui/guest_os_installer/mojom",
     "//base",
+    "//build:chromeos_buildflags",
     "//chrome/browser/ash/arc/input_overlay/db/proto",
     "//chrome/browser/ash/crosapi",
     "//chrome/browser/ash/crostini:crostini_installer_types_mojom",
+    "//chrome/browser/ash/guest_os:guest_os_diagnostics_mojom",
     "//chrome/browser/chromeos",
     "//chrome/browser/extensions",
     "//chrome/browser/image_decoder",
@@ -1167,6 +1343,7 @@
     "//chrome/browser/web_applications",
     "//chrome/common",
     "//chrome/common:buildflags",
+    "//chrome/common:chrome_features",
     "//chrome/common:constants",
     "//chrome/common/extensions/api",
     "//chrome/services/file_util/public/cpp",
@@ -1178,16 +1355,24 @@
     "//chromeos/ash/components/dbus/cicerone:cicerone_proto",
     "//chromeos/ash/components/dbus/concierge",
     "//chromeos/ash/components/dbus/concierge:concierge_proto",
+    "//chromeos/ash/components/dbus/kerberos:kerberos_proto",
+    "//chromeos/ash/components/dbus/seneschal",
+    "//chromeos/ash/components/dbus/seneschal:seneschal_proto",
     "//chromeos/ash/components/dbus/services",
     "//chromeos/ash/components/dbus/system_clock",
     "//chromeos/ash/components/memory",
+    "//chromeos/ash/components/network/portal_detector",
+    "//chromeos/components/onc",
     "//chromeos/components/sharesheet:constants",
     "//chromeos/crosapi/mojom",
     "//chromeos/dbus:metrics_event_proto",
+    "//chromeos/dbus:vm_applications_apps_proto",
+    "//chromeos/dbus:vm_launch_proto",
     "//chromeos/dbus/anomaly_detector",
     "//chromeos/dbus/anomaly_detector:proto",
     "//chromeos/dbus/attestation",
     "//chromeos/dbus/attestation:attestation_proto",
+    "//chromeos/dbus/chunneld",
     "//chromeos/dbus/common",
     "//chromeos/dbus/constants",
     "//chromeos/dbus/cros_disks",
@@ -1202,12 +1387,14 @@
     "//chromeos/metrics",
     "//chromeos/network",
     "//chromeos/services/cros_healthd/public/mojom",
+    "//chromeos/services/machine_learning/public/mojom",
     "//chromeos/ui/base",
     "//components/account_id",
     "//components/account_manager_core",
     "//components/app_restore",
     "//components/arc",
     "//components/arc/common",
+    "//components/autofill/core/browser",
     "//components/content_settings/core/browser",
     "//components/download/content/public",
     "//components/drive",
@@ -1271,6 +1458,7 @@
     "//third_party/abseil-cpp:absl",
     "//third_party/blink/public/mojom:mojom_platform",
     "//third_party/boringssl",
+    "//third_party/icu",
     "//ui/accessibility",
     "//ui/accessibility:ax_base",
     "//ui/accessibility:ax_enums_mojo_headers",
@@ -1278,9 +1466,11 @@
     "//ui/base",
     "//ui/base:ui_data_pack",
     "//ui/base/ime",
+    "//ui/base/ime:ime_types",
     "//ui/base/ime/ash",
     "//ui/base/metadata",
     "//ui/chromeos/events",
+    "//ui/chromeos/styles:cros_styles_views",
     "//ui/compositor",
     "//ui/display",
     "//ui/display/types",
@@ -1290,9 +1480,11 @@
     "//ui/gfx",
     "//ui/gfx:native_widget_types",
     "//ui/gfx/geometry",
+    "//ui/gfx/range",
     "//ui/message_center/public/cpp",
     "//ui/shell_dialogs",
     "//ui/views",
+    "//ui/web_dialogs",
     "//ui/wm/public",
     "//url",
   ]
@@ -1315,12 +1507,12 @@
     "//ash/components/peripheral_notification",
     "//ash/components/phonehub",
     "//ash/components/power",
-    "//ash/constants",
     "//ash/keyboard/ui",
     "//ash/public/mojom",
     "//ash/resources/vector_icons",
     "//ash/services/device_sync",
     "//ash/services/device_sync:stub_device_sync",
+    "//ash/services/ime:constants",
     "//ash/services/multidevice_setup/public/cpp:prefs",
     "//ash/services/multidevice_setup/public/mojom",
     "//ash/services/secure_channel",
@@ -1330,6 +1522,7 @@
     "//ash/webui/file_manager:constants",
     "//ash/webui/file_manager:file_manager_ui",
     "//ash/webui/file_manager:file_manager_untrusted_ui",
+    "//ash/webui/guest_os_installer",
     "//ash/webui/shimless_rma",
     "//base:i18n",
     "//build:branding_buildflags",
@@ -1347,11 +1540,11 @@
     "//chrome/browser/metrics/structured",
     "//chrome/browser/profiles",
     "//chrome/browser/resources:component_extension_resources",
+    "//chrome/browser/resources/chromeos:app_icon_resources",
     "//chrome/browser/ui/webui/chromeos/crostini_upgrader:mojo_bindings",
     "//chrome/browser/ui/webui/settings/chromeos/constants:mojom",
     "//chrome/browser/webshare:storage",
     "//chrome/common:channel_info",
-    "//chrome/common:chrome_features",
     "//chrome/common:non_code_constants",
     "//chrome/common/net",
     "//chromeos/ash/components/dbus/biod",
@@ -1366,26 +1559,21 @@
     "//chromeos/ash/components/dbus/pciguard",
     "//chromeos/ash/components/dbus/rgbkbd",
     "//chromeos/ash/components/dbus/rmad",
-    "//chromeos/ash/components/dbus/seneschal",
     "//chromeos/ash/components/dbus/spaced",
     "//chromeos/ash/components/dbus/system_proxy",
     "//chromeos/ash/components/dbus/typecd",
     "//chromeos/ash/components/dbus/upstart",
     "//chromeos/ash/components/hibernate:buildflags",
-    "//chromeos/ash/components/network/portal_detector",
     "//chromeos/components/cdm_factory_daemon:cdm_factory_daemon_browser",
     "//chromeos/components/chromebox_for_meetings/buildflags",
     "//chromeos/components/disks:prefs",
     "//chromeos/components/local_search_service/public/cpp",
     "//chromeos/components/mojo_bootstrap",
-    "//chromeos/components/onc",
     "//chromeos/components/sensors",
     "//chromeos/constants",
     "//chromeos/dbus",
     "//chromeos/dbus:plugin_vm_service_proto",
-    "//chromeos/dbus:vm_applications_apps_proto",
     "//chromeos/dbus:vm_disk_management_proto",
-    "//chromeos/dbus:vm_launch_proto",
     "//chromeos/dbus:vm_permission_service_proto",
     "//chromeos/dbus:vm_sk_forwarding_proto",
     "//chromeos/dbus/arc",
@@ -1401,6 +1589,7 @@
     "//chromeos/dbus/hermes",
     "//chromeos/dbus/human_presence",
     "//chromeos/dbus/init",
+    "//chromeos/dbus/lorgnette_manager",
     "//chromeos/dbus/machine_learning",
     "//chromeos/dbus/permission_broker",
     "//chromeos/dbus/tpm_manager",
@@ -1408,6 +1597,7 @@
     "//chromeos/dbus/userdataauth",
     "//chromeos/dbus/util",
     "//chromeos/dbus/virtual_file_provider",
+    "//chromeos/ime:gencode",
     "//chromeos/login/login_state",
     "//chromeos/services/assistant/public/cpp",
     "//chromeos/services/cros_healthd/private/cpp",
@@ -1426,6 +1616,7 @@
     "//components/device_event_log",
     "//components/download/public/common:public",
     "//components/embedder_support:browser_util",
+    "//components/exo/server",
     "//components/gcm_driver",
     "//components/google/core/common",
     "//components/guest_os:prefs",
@@ -1454,12 +1645,14 @@
     "//components/services/unzip/content",
     "//components/services/unzip/public/mojom",
     "//components/signin/public/base",
+    "//components/spellcheck/browser",
     "//components/strings:components_strings",
     "//components/sync/base",
     "//components/sync/driver",
     "//components/sync_sessions",
     "//components/translate/core/browser",
     "//components/url_matcher",
+    "//components/user_prefs",
     "//components/vector_icons",
     "//components/version_info",
     "//components/version_info:channel",
@@ -1492,7 +1685,6 @@
     "//services/network/public/mojom:url_loader_base",
     "//services/tracing/public/mojom",
     "//third_party/blink/public/common:headers",
-    "//third_party/icu",
     "//third_party/re2",
     "//third_party/securemessage/proto",
     "//third_party/zlib/google:zip",
@@ -1502,10 +1694,10 @@
     "//ui/base/clipboard:clipboard_types",
     "//ui/base/clipboard:file_info",
     "//ui/base/data_transfer_policy",
+    "//ui/base/ime:text_input_types",
     "//ui/chromeos",
     "//ui/chromeos/resources",
     "//ui/chromeos/strings",
-    "//ui/chromeos/styles:cros_styles_views",
     "//ui/color",
     "//ui/color:color_headers",
     "//ui/color:mixers",
@@ -1519,6 +1711,7 @@
     "//ui/gfx/codec",
     "//ui/message_center",
     "//ui/native_theme",
+    "//ui/ozone",
     "//ui/strings:ui_strings",
     "//ui/views/controls/webview",
     "//ui/wm",
diff --git a/chrome/browser/ash/arc/input_overlay/ui/input_menu_view.cc b/chrome/browser/ash/arc/input_overlay/ui/input_menu_view.cc
index 9461293..4460a838 100644
--- a/chrome/browser/ash/arc/input_overlay/ui/input_menu_view.cc
+++ b/chrome/browser/ash/arc/input_overlay/ui/input_menu_view.cc
@@ -324,7 +324,7 @@
 
 std::unique_ptr<views::View> InputMenuView::BuildSeparator() {
   auto separator = std::make_unique<views::Separator>();
-  separator->SetColorId(ui::kColorAshArcInputMenuSeparator);
+  separator->SetColorId(ui::kColorAshSystemUIMenuSeparator);
 
   return std::move(separator);
 }
diff --git a/chrome/browser/ash/crosapi/audio_service_ash.cc b/chrome/browser/ash/crosapi/audio_service_ash.cc
index 0534482f..2220c48 100644
--- a/chrome/browser/ash/crosapi/audio_service_ash.cc
+++ b/chrome/browser/ash/crosapi/audio_service_ash.cc
@@ -14,7 +14,7 @@
 
 namespace crosapi {
 
-// TODO: Add unit tests for AudioServiceAsh (b/235565865)
+// TODO: Add unit tests for AudioServiceAsh (b/235565865).
 
 AudioServiceAsh::Observer::Observer() = default;
 AudioServiceAsh::Observer::~Observer() = default;
@@ -24,11 +24,6 @@
   audio_service_observation_.Observe(service);
 }
 
-void AudioServiceAsh::Observer::OnDeviceChanged() {
-  // Not implemented, because this event is deprecated in chrome.audio API.
-  // TODO: Should have been removed in M60, see (http://crbug.com/697279).
-}
-
 void AudioServiceAsh::Observer::OnLevelChanged(const std::string& id,
                                                int level) {
   for (auto& observer : observers_) {
@@ -64,7 +59,7 @@
 void AudioServiceAsh::Initialize(Profile* profile) {
   DCHECK(profile);
   if (stable_id_calculator_) {
-    // TODO: investigate why crosapi ash object inits are called more than once
+    // TODO: investigate why crosapi ash object inits are called more than once.
     // (b/235203815)
     LOG(WARNING)
         << "AudioServiceAsh was already initialized. Not initializing again.";
@@ -154,9 +149,7 @@
 
   bool success = false;
   if (properties) {
-    // reusing existing volume/gain method, thus same param passed twice
-    success =
-        service_->SetDeviceSoundLevel(id, properties->level, properties->level);
+    success = service_->SetDeviceSoundLevel(id, properties->level);
   }
   std::move(callback).Run(success);
 }
diff --git a/chrome/browser/ash/crosapi/audio_service_ash.h b/chrome/browser/ash/crosapi/audio_service_ash.h
index 65132f0b..51bc899 100644
--- a/chrome/browser/ash/crosapi/audio_service_ash.h
+++ b/chrome/browser/ash/crosapi/audio_service_ash.h
@@ -19,7 +19,7 @@
 
 namespace crosapi {
 
-// Implements the crosapi interface for audio service API
+// Implements the crosapi interface for audio service API.
 class AudioServiceAsh : public mojom::AudioService {
  public:
   AudioServiceAsh();
@@ -56,7 +56,6 @@
     void Initialize(extensions::AudioService* service);
 
     // extensions::AudioService::Observer implementation:
-    void OnDeviceChanged() override;
     void OnLevelChanged(const std::string& id, int level) override;
     void OnMuteChanged(bool is_input, bool is_muted) override;
     void OnDevicesChanged(const extensions::DeviceInfoList& devices) override;
@@ -79,7 +78,7 @@
   std::unique_ptr<extensions::AudioDeviceIdCalculator> stable_id_calculator_;
   std::unique_ptr<extensions::AudioService> service_;
 
-  // Observer must be defined after AudioService for a correct destruction order
+  // Observer must be defined after service for a correct destruction order.
   Observer observer_;
 };
 
diff --git a/chrome/browser/ash/crosapi/browser_manager.cc b/chrome/browser/ash/crosapi/browser_manager.cc
index 7ac97e0f..7120f6cc 100644
--- a/chrome/browser/ash/crosapi/browser_manager.cc
+++ b/chrome/browser/ash/crosapi/browser_manager.cc
@@ -86,6 +86,7 @@
 #include "components/session_manager/core/session_manager.h"
 #include "components/user_manager/user_type.h"
 #include "components/version_info/version_info.h"
+#include "media/capture/capture_switches.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/platform/platform_channel.h"
 #include "ui/base/l10n/l10n_util.h"
@@ -1093,6 +1094,12 @@
   for (const auto& flag : delimited_flags)
     argv.emplace_back(flag);
 
+  // Forward flag for zero copy video capture to Lacros if it is enabled.
+  if (switches::IsVideoCaptureUseGpuMemoryBufferEnabled()) {
+    argv.emplace_back(
+        base::StringPrintf("--%s", switches::kVideoCaptureUseGpuMemoryBuffer));
+  }
+
   // If logfd is valid, enable logging and redirect stdout/stderr to logfd.
   if (params.logfd.is_valid()) {
     // The next flag will make chrome log only via stderr. See
diff --git a/chrome/browser/ash/crosapi/networking_private_ash.cc b/chrome/browser/ash/crosapi/networking_private_ash.cc
index b7a5bfe5..312dc52f 100644
--- a/chrome/browser/ash/crosapi/networking_private_ash.cc
+++ b/chrome/browser/ash/crosapi/networking_private_ash.cc
@@ -425,8 +425,10 @@
       std::move(observer)));
 
   if (!is_listening_network_state) {
-    network_state_observation_.Observe(
-        NetworkHandler::Get()->network_state_handler());
+    auto* net_handler = NetworkHandler::Get();
+    network_state_observation_.Observe(net_handler->network_state_handler());
+    network_certificate_observation_.Observe(
+        net_handler->network_certificate_handler());
   }
 }
 
@@ -490,9 +492,16 @@
   }
 }
 
+void NetworkingPrivateAsh::OnCertificatesChanged() {
+  for (auto& observer : observers_) {
+    observer->OnCertificateListsChanged();
+  }
+}
+
 void NetworkingPrivateAsh::OnObserverDisconnected(mojo::RemoteSetElementId id) {
   if (observers_.empty()) {
     network_state_observation_.Reset();
+    network_certificate_observation_.Reset();
   }
 }
 
diff --git a/chrome/browser/ash/crosapi/networking_private_ash.h b/chrome/browser/ash/crosapi/networking_private_ash.h
index 0a247d8..d6d745d 100644
--- a/chrome/browser/ash/crosapi/networking_private_ash.h
+++ b/chrome/browser/ash/crosapi/networking_private_ash.h
@@ -7,6 +7,7 @@
 
 #include "base/scoped_observation.h"
 #include "chromeos/crosapi/mojom/networking_private.mojom.h"
+#include "chromeos/network/network_certificate_handler.h"
 #include "chromeos/network/network_state_handler.h"
 #include "chromeos/network/network_state_handler_observer.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
@@ -16,8 +17,10 @@
 namespace crosapi {
 
 // The ash-chrome implementation of the NetworkingPrivate crosapi interface.
-class NetworkingPrivateAsh : public mojom::NetworkingPrivate,
-                             public chromeos::NetworkStateHandlerObserver {
+class NetworkingPrivateAsh
+    : public mojom::NetworkingPrivate,
+      public chromeos::NetworkStateHandlerObserver,
+      public chromeos::NetworkCertificateHandler::Observer {
  public:
   NetworkingPrivateAsh();
   NetworkingPrivateAsh(const NetworkingPrivateAsh&) = delete;
@@ -98,6 +101,9 @@
       const chromeos::NetworkState* default_network,
       chromeos::NetworkState::PortalState portal_state) override;
 
+  // NetworkCertificateHandler::Observer overrides:
+  void OnCertificatesChanged() override;
+
  private:
   void OnObserverDisconnected(mojo::RemoteSetElementId id);
 
@@ -107,6 +113,9 @@
   base::ScopedObservation<chromeos::NetworkStateHandler,
                           chromeos::NetworkStateHandlerObserver>
       network_state_observation_{this};
+  base::ScopedObservation<chromeos::NetworkCertificateHandler,
+                          chromeos::NetworkCertificateHandler::Observer>
+      network_certificate_observation_{this};
   // This class supports any number of connections.
   mojo::ReceiverSet<mojom::NetworkingPrivate> receivers_;
 };
diff --git a/chrome/browser/ash/file_manager/file_manager_jstest.cc b/chrome/browser/ash/file_manager/file_manager_jstest.cc
index 2394711..a733e95 100644
--- a/chrome/browser/ash/file_manager/file_manager_jstest.cc
+++ b/chrome/browser/ash/file_manager/file_manager_jstest.cc
@@ -7,8 +7,9 @@
 
 class FileManagerJsTest : public FileManagerJsTestBase {
  protected:
-  FileManagerJsTest() : FileManagerJsTestBase(
-      base::FilePath(FILE_PATH_LITERAL("ui/file_manager/file_manager"))) {}
+  FileManagerJsTest()
+      : FileManagerJsTestBase(
+            base::FilePath(FILE_PATH_LITERAL("file_manager"))) {}
 };
 
 // Tests that draw to canvases and test pixels need pixel output turned on.
@@ -293,3 +294,7 @@
 IN_PROC_BROWSER_TEST_F(FileManagerJsTest, VolumeManagerTypesTest) {
   RunTestURL("common/js/volume_manager_types_unittest.m_gen.html");
 }
+
+IN_PROC_BROWSER_TEST_F(FileManagerJsTest, RecentDateBucketTest) {
+  RunTestURL("common/js/recent_date_bucket_unittest.m_gen.html");
+}
diff --git a/chrome/browser/ash/file_manager/file_manager_jstest_base.cc b/chrome/browser/ash/file_manager/file_manager_jstest_base.cc
index 56d23d6..5c430466 100644
--- a/chrome/browser/ash/file_manager/file_manager_jstest_base.cc
+++ b/chrome/browser/ash/file_manager/file_manager_jstest_base.cc
@@ -25,6 +25,7 @@
 #include "content/public/test/scoped_web_ui_controller_factory_registration.h"
 #include "net/base/filename_util.h"
 #include "services/network/public/mojom/content_security_policy.mojom.h"
+#include "ui/base/resource/resource_bundle.h"
 
 namespace {
 
@@ -34,133 +35,6 @@
   return executable_path.AppendASCII("gen");
 }
 
-// URLDataSource for the test URL chrome://file_manager_test/. It reads files
-// directly from repository source.
-class TestFilesDataSource : public content::URLDataSource {
- public:
-  TestFilesDataSource() {}
-
-  TestFilesDataSource(const TestFilesDataSource&) = delete;
-  TestFilesDataSource& operator=(const TestFilesDataSource&) = delete;
-
-  ~TestFilesDataSource() override {}
-
- private:
-  // This has to match TestResourceUrl()
-  std::string GetSource() override { return "file_manager_test"; }
-
-  void StartDataRequest(
-      const GURL& url,
-      const content::WebContents::Getter& wc_getter,
-      content::URLDataSource::GotDataCallback callback) override {
-    const std::string path = content::URLDataSource::URLToRequestPath(url);
-    base::ThreadPool::PostTask(
-        FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_BLOCKING},
-        base::BindOnce(&TestFilesDataSource::ReadFile, base::Unretained(this),
-                       path, std::move(callback)));
-  }
-
-  void ReadFile(const std::string& path,
-                content::URLDataSource::GotDataCallback callback) {
-    if (source_root_.empty()) {
-      CHECK(base::PathService::Get(base::DIR_SOURCE_ROOT, &source_root_));
-    }
-    if (gen_root_.empty()) {
-      CHECK(base::PathService::Get(base::DIR_EXE, &gen_root_));
-      gen_root_ = GetGenRoot();
-    }
-
-    std::string content;
-
-    base::FilePath src_file_path =
-        source_root_.Append(base::FilePath::FromUTF8Unsafe(path));
-    base::FilePath gen_file_path =
-        gen_root_.Append(base::FilePath::FromUTF8Unsafe(path));
-
-    // File manager sets up the embedded test server with a specific base path,
-    // and the server assumes all paths are relative to this path without
-    // checking for absolute URLs. Hence, absolute URLS are transformed to
-    // requests for <some_base_path>/chrome://resources/<path_to_resource>.
-    // Strip off the assumed base path and replace chrome://resources with
-    // ui/webui/resources in this case.
-    const char kResourcesUrl[] = "chrome://resources";
-    size_t url_pos = path.find(kResourcesUrl);
-    if (url_pos != std::string::npos) {
-      std::string new_path =
-          "ui/webui/resources" +
-          path.substr(url_pos + std::size(kResourcesUrl) - 1);
-      src_file_path =
-          source_root_.Append(base::FilePath::FromUTF8Unsafe(new_path));
-      gen_file_path =
-          gen_root_.Append(base::FilePath::FromUTF8Unsafe(new_path));
-    }
-
-    // Do some basic validation of the file extension.
-    CHECK(src_file_path.Extension() == ".html" ||
-          src_file_path.Extension() == ".js" ||
-          src_file_path.Extension() == ".css" ||
-          src_file_path.Extension() == ".svg")
-        << "chrome://file_manager_test/ only supports .html/.js/.css/.svg "
-           "extension files";
-
-    CHECK(base::PathExists(src_file_path) || base::PathExists(gen_file_path))
-        << src_file_path << " or: " << gen_file_path << " input path: " << path;
-    CHECK(base::ReadFileToString(gen_file_path, &content) ||
-          base::ReadFileToString(src_file_path, &content))
-        << src_file_path << " or: " << gen_file_path;
-
-    scoped_refptr<base::RefCountedString> response =
-        base::RefCountedString::TakeString(&content);
-    std::move(callback).Run(response.get());
-  }
-
-  bool ShouldServeMimeTypeAsContentTypeHeader() override { return true; }
-
-  // It currently only serves HTML/JS/CSS/SVG.
-  std::string GetMimeType(const std::string& path) override {
-    if (base::EndsWith(path, ".html", base::CompareCase::INSENSITIVE_ASCII)) {
-      return "text/html";
-    }
-
-    if (base::EndsWith(path, ".css", base::CompareCase::INSENSITIVE_ASCII)) {
-      return "text/css";
-    }
-
-    if (base::EndsWith(path, ".js", base::CompareCase::INSENSITIVE_ASCII)) {
-      return "application/javascript";
-    }
-
-    if (base::EndsWith(path, ".svg", base::CompareCase::INSENSITIVE_ASCII)) {
-      return "image/svg+xml";
-    }
-
-    LOG(FATAL) << "unsupported file type: " << path;
-    return {};
-  }
-
-  std::string GetContentSecurityPolicy(
-      const network::mojom::CSPDirectiveName directive) override {
-    if (directive == network::mojom::CSPDirectiveName::ScriptSrc) {
-      // Add 'unsafe-inline' to CSP to allow the inline <script> in the
-      // generated HTML to run see js_test_gen_html.py.
-      return "script-src chrome://resources chrome://test 'self'  "
-             "chrome-extension://hhaomjibdihmijegdhdafkllkbggdgoj "
-             "chrome-extension://pmfjbimdmchhbnneeidfognadeopoehp "
-             "'unsafe-inline'; ";
-    } else if (directive ==
-                   network::mojom::CSPDirectiveName::RequireTrustedTypesFor ||
-               directive == network::mojom::CSPDirectiveName::TrustedTypes) {
-      return std::string();
-    }
-
-    return content::URLDataSource::GetContentSecurityPolicy(directive);
-  }
-
-  // Root of repository source, where files are served directly from.
-  base::FilePath source_root_;
-  base::FilePath gen_root_;
-};
-
 // WebUIProvider to attach the URLDataSource for the test URL during tests.
 // Used to start the unittest from a chrome:// URL which allows unittest files
 // (HTML/JS/CSS) to load other resources from WebUI URLs chrome://*.
@@ -176,21 +50,36 @@
 
   std::unique_ptr<content::WebUIController> NewWebUI(content::WebUI* web_ui,
                                                      const GURL& url) override {
-    auto* profile = Profile::FromWebUI(web_ui);
-    content::URLDataSource::Add(profile,
-                                std::make_unique<TestFilesDataSource>());
-    content::URLDataSource::Add(profile,
-                                std::make_unique<TestDataSource>("webui"));
-
     return std::make_unique<content::WebUIController>(web_ui);
   }
+
+  void DataSourceOverrides(content::WebUIDataSource* source) override {
+    // Add 'unsafe-inline' to CSP to allow the inline <script> in the
+    // generated HTML to run see js_test_gen_html.py.
+    source->OverrideContentSecurityPolicy(
+        network::mojom::CSPDirectiveName::ScriptSrc,
+        "script-src chrome://resources chrome://webui-test chrome://test "
+        "'self' chrome-extension://hhaomjibdihmijegdhdafkllkbggdgoj "
+        "chrome-extension://pmfjbimdmchhbnneeidfognadeopoehp "
+        "'unsafe-inline'; ");
+
+    source->OverrideContentSecurityPolicy(
+        network::mojom::CSPDirectiveName::ScriptSrcElem,
+        "script-src chrome://resources chrome://webui-test chrome://test "
+        "'self' chrome-extension://hhaomjibdihmijegdhdafkllkbggdgoj "
+        "chrome-extension://pmfjbimdmchhbnneeidfognadeopoehp "
+        "'unsafe-inline'; ");
+
+    // TODO(crbug.com/1098685): Trusted Type remaining WebUI.
+    source->DisableTrustedTypesCSP();
+  }
 };
 
 base::LazyInstance<TestWebUIProvider>::DestructorAtExit test_webui_provider_ =
     LAZY_INSTANCE_INITIALIZER;
 
 static const GURL TestResourceUrl() {
-  static GURL url(content::GetWebUIURLString("file_manager_test"));
+  static GURL url(content::GetWebUIURLString("webui-test"));
   return url;
 }
 
@@ -227,8 +116,7 @@
 }
 
 void FileManagerJsTestBase::RunTestURL(const std::string& file) {
-  RunTestImpl(
-      GURL("chrome://file_manager_test/" + base_path_.Append(file).value()));
+  RunTestImpl(GURL("chrome://webui-test/" + base_path_.Append(file).value()));
 }
 
 void FileManagerJsTestBase::RunTestImpl(const GURL& url) {
@@ -242,6 +130,12 @@
 void FileManagerJsTestBase::SetUpOnMainThread() {
   InProcessBrowserTest::SetUpOnMainThread();
 
+  base::FilePath pak_path;
+  ASSERT_TRUE(base::PathService::Get(base::DIR_MODULE, &pak_path));
+  pak_path = pak_path.AppendASCII("browser_tests.pak");
+  ui::ResourceBundle::GetSharedInstance().AddDataPackFromPath(
+      pak_path, ui::kScaleFactorNone);
+
   webui_controller_factory_ =
       std::make_unique<TestChromeWebUIControllerFactory>();
   webui_controller_factory_registration_ =
diff --git a/chrome/browser/ash/file_manager/image_loader_jstest.cc b/chrome/browser/ash/file_manager/image_loader_jstest.cc
index 219df76..c3b0c08 100644
--- a/chrome/browser/ash/file_manager/image_loader_jstest.cc
+++ b/chrome/browser/ash/file_manager/image_loader_jstest.cc
@@ -9,8 +9,9 @@
 
 class ImageLoaderJsTest : public FileManagerJsTestBase {
  protected:
-  ImageLoaderJsTest() : FileManagerJsTestBase(
-      base::FilePath(FILE_PATH_LITERAL("ui/file_manager/image_loader"))) {}
+  ImageLoaderJsTest()
+      : FileManagerJsTestBase(
+            base::FilePath(FILE_PATH_LITERAL("image_loader"))) {}
 
   void SetUpCommandLine(base::CommandLine* command_lin) override {
     // Until Files SWA is fully launched Image Loader imports using
diff --git a/chrome/browser/ash/file_manager/trash_io_task.cc b/chrome/browser/ash/file_manager/trash_io_task.cc
index 1479cbe3f..a25cf76 100644
--- a/chrome/browser/ash/file_manager/trash_io_task.cc
+++ b/chrome/browser/ash/file_manager/trash_io_task.cc
@@ -177,8 +177,8 @@
   TrashLocation& trash_location = trash_parent_path_it->second;
   const base::FilePath trash_parent_path = trash_parent_path_it->first;
   TrashEntry& entry = trash_entries_[source_idx];
-  entry.trash_path =
-      trash_parent_path.Append(trash_location.relative_folder_path);
+  entry.trash_mount_path = trash_parent_path;
+  entry.relative_trash_path = trash_location.relative_folder_path;
 
   if (!UpdateTrashInfoContents(source_path, trash_parent_path,
                                trash_location.prefix_restore_path, entry)) {
@@ -363,8 +363,10 @@
   DCHECK(source_idx < progress_.sources.size());
   DCHECK(source_idx < trash_entries_.size());
 
+  const TrashEntry& entry = trash_entries_[source_idx];
   const auto trash_path = MakeRelativeFromBasePath(
-      trash_entries_[source_idx].trash_path.Append(kFilesFolderName));
+      entry.trash_mount_path.Append(entry.relative_trash_path)
+          .Append(kFilesFolderName));
 
   const storage::FileSystemURL files_location =
       CreateFileSystemURL(progress_.sources[source_idx].url, trash_path);
@@ -386,9 +388,14 @@
     TrashComplete(source_idx, output_idx, destination_result.error());
     return;
   }
+  const base::FilePath absolute_trash_path =
+      trash_entries_[source_idx].trash_mount_path.Append(
+          trash_entries_[source_idx].relative_trash_path);
+  const std::string file_name =
+      destination_result.value().path().BaseName().value();
+
   const base::FilePath destination_path =
-      GenerateTrashPath(trash_entries_[source_idx].trash_path, kInfoFolderName,
-                        destination_result.value().path().BaseName().value());
+      GenerateTrashPath(absolute_trash_path, kInfoFolderName, file_name);
   progress_.outputs.emplace_back(
       CreateFileSystemURL(progress_.sources[source_idx].url, destination_path),
       absl::nullopt);
diff --git a/chrome/browser/ash/file_manager/trash_io_task.h b/chrome/browser/ash/file_manager/trash_io_task.h
index 1c4b57e..126588b0 100644
--- a/chrome/browser/ash/file_manager/trash_io_task.h
+++ b/chrome/browser/ash/file_manager/trash_io_task.h
@@ -36,9 +36,13 @@
   TrashEntry(TrashEntry&& other);
   TrashEntry& operator=(TrashEntry&& other);
 
-  // The final location for the trashed file, this may not be the same as the
-  // `url` in the case of naming conflicts.
-  base::FilePath trash_path;
+  // The relative path (to `trash_mount_path`) where the final location of the
+  // trashed file.
+  base::FilePath relative_trash_path;
+
+  // An absolute location which contains the `relative_trash_path` and combined
+  // represents the final location of the trashed file.
+  base::FilePath trash_mount_path;
 
   // The date of deletion, stored in the metadata file to help scheduled
   // cleanup.
diff --git a/chrome/browser/ash/input_method/assistive_window_controller_unittest.cc b/chrome/browser/ash/input_method/assistive_window_controller_unittest.cc
index f2a9f73..d4a91e2 100644
--- a/chrome/browser/ash/input_method/assistive_window_controller_unittest.cc
+++ b/chrome/browser/ash/input_method/assistive_window_controller_unittest.cc
@@ -255,7 +255,9 @@
   ASSERT_TRUE(controller_->GetSuggestionWindowViewForTesting() != nullptr);
   views::BoxLayout::Orientation layout_orientation =
       static_cast<views::BoxLayout*>(
-          controller_->GetSuggestionWindowViewForTesting()->GetLayoutManager())
+          controller_->GetSuggestionWindowViewForTesting()
+              ->multiple_candidate_area_for_testing()
+              ->GetLayoutManager())
           ->GetOrientation();
   EXPECT_EQ(layout_orientation, views::BoxLayout::Orientation::kVertical);
 }
@@ -274,7 +276,9 @@
   ASSERT_TRUE(controller_->GetSuggestionWindowViewForTesting() != nullptr);
   views::BoxLayout::Orientation layout_orientation =
       static_cast<views::BoxLayout*>(
-          controller_->GetSuggestionWindowViewForTesting()->GetLayoutManager())
+          controller_->GetSuggestionWindowViewForTesting()
+              ->multiple_candidate_area_for_testing()
+              ->GetLayoutManager())
           ->GetOrientation();
   EXPECT_EQ(layout_orientation, views::BoxLayout::Orientation::kVertical);
 }
@@ -292,7 +296,9 @@
   ASSERT_TRUE(controller_->GetSuggestionWindowViewForTesting() != nullptr);
   views::BoxLayout::Orientation layout_orientation =
       static_cast<views::BoxLayout*>(
-          controller_->GetSuggestionWindowViewForTesting()->GetLayoutManager())
+          controller_->GetSuggestionWindowViewForTesting()
+              ->multiple_candidate_area_for_testing()
+              ->GetLayoutManager())
           ->GetOrientation();
   EXPECT_EQ(layout_orientation, views::BoxLayout::Orientation::kVertical);
 }
@@ -312,7 +318,9 @@
   ASSERT_TRUE(controller_->GetSuggestionWindowViewForTesting() != nullptr);
   views::BoxLayout::Orientation layout_orientation =
       static_cast<views::BoxLayout*>(
-          controller_->GetSuggestionWindowViewForTesting()->GetLayoutManager())
+          controller_->GetSuggestionWindowViewForTesting()
+              ->multiple_candidate_area_for_testing()
+              ->GetLayoutManager())
           ->GetOrientation();
   EXPECT_EQ(layout_orientation, views::BoxLayout::Orientation::kHorizontal);
 }
diff --git a/chrome/browser/ash/input_method/input_method_settings.cc b/chrome/browser/ash/input_method/input_method_settings.cc
index f7e4e04..8c796b4 100644
--- a/chrome/browser/ash/input_method/input_method_settings.cc
+++ b/chrome/browser/ash/input_method/input_method_settings.cc
@@ -107,7 +107,6 @@
               .value_or(0) > 0;
   settings->predictive_writing =
       features::IsAssistiveMultiWordEnabled() &&
-      !base::FeatureList::IsEnabled(chromeos::features::kLacrosSupport) &&
       prefs.GetBoolean(prefs::kAssistPredictiveWritingEnabled) &&
       IsUsEnglishEngine(engine_id);
   return settings;
diff --git a/chrome/browser/ash/input_method/input_method_settings_unittest.cc b/chrome/browser/ash/input_method/input_method_settings_unittest.cc
index c7015ddf..cec16363 100644
--- a/chrome/browser/ash/input_method/input_method_settings_unittest.cc
+++ b/chrome/browser/ash/input_method/input_method_settings_unittest.cc
@@ -67,7 +67,7 @@
 }
 
 TEST(CreateSettingsFromPrefsTest,
-     PredictiveWritingEnabledWhenMultiWordAllowedAndEnabledAndLacrosDisabled) {
+     PredictiveWritingEnabledWhenMultiWordAllowedAndEnabled) {
   base::test::ScopedFeatureList features;
   features.InitWithFeatures({features::kAssistMultiWord}, {});
   TestingPrefServiceSimple prefs;
@@ -83,23 +83,6 @@
   EXPECT_TRUE(latin_settings.predictive_writing);
 }
 
-TEST(CreateSettingsFromPrefsTest, PredictiveWritingDisabledWhenLacrosEnabled) {
-  base::test::ScopedFeatureList features;
-  features.InitWithFeatures(
-      {features::kAssistMultiWord, features::kLacrosSupport}, {});
-  TestingPrefServiceSimple prefs;
-  base::DictionaryValue dict;
-  RegisterTestingPrefs(prefs, dict);
-  prefs.registry()->RegisterBooleanPref(prefs::kAssistPredictiveWritingEnabled,
-                                        true);
-
-  const auto settings = CreateSettingsFromPrefs(prefs, kUsEnglishEngineId);
-
-  ASSERT_TRUE(settings->is_latin_settings());
-  const auto& latin_settings = *settings->get_latin_settings();
-  EXPECT_FALSE(latin_settings.predictive_writing);
-}
-
 TEST(CreateSettingsFromPrefsTest,
      PredictiveWritingDisabledWhenMultiwordDisabled) {
   base::test::ScopedFeatureList features;
diff --git a/chrome/browser/ash/input_method/native_input_method_engine_observer.cc b/chrome/browser/ash/input_method/native_input_method_engine_observer.cc
index 93ad9ff..231705e 100644
--- a/chrome/browser/ash/input_method/native_input_method_engine_observer.cc
+++ b/chrome/browser/ash/input_method/native_input_method_engine_observer.cc
@@ -51,7 +51,6 @@
 namespace mojom = ::ash::ime::mojom;
 
 struct InputFieldContext {
-  bool lacros_enabled = false;
   bool multiword_enabled = false;
   bool multiword_allowed = false;
 };
@@ -123,13 +122,9 @@
   return autocorrect_setting && autocorrect_setting->GetIfInt().value_or(0) > 0;
 }
 
-bool IsLacrosEnabled() {
-  return base::FeatureList::IsEnabled(chromeos::features::kLacrosSupport);
-}
-
 bool IsPredictiveWritingEnabled(PrefService* pref_service,
                                 const std::string& engine_id) {
-  return (!IsLacrosEnabled() && features::IsAssistiveMultiWordEnabled() &&
+  return (features::IsAssistiveMultiWordEnabled() &&
           IsPredictiveWritingPrefEnabled(pref_service, engine_id) &&
           IsUsEnglishEngine(engine_id));
 }
@@ -455,7 +450,6 @@
 InputFieldContext CreateInputFieldContext(
     const AssistiveSuggesterSwitch::EnabledSuggestions& enabled_suggestions) {
   return InputFieldContext{
-      .lacros_enabled = IsLacrosEnabled(),
       .multiword_enabled = features::IsAssistiveMultiWordEnabled(),
       .multiword_allowed = enabled_suggestions.multi_word_suggestions};
 }
@@ -464,9 +458,7 @@
     const std::string& engine_id,
     const InputFieldContext& context,
     const PrefService& prefs) {
-  // TODO(crbug.com/1263335): Enable text prediction for Lacros.
   return context.multiword_enabled && context.multiword_allowed &&
-                 !context.lacros_enabled &&
                  prefs.GetBoolean(prefs::kAssistPredictiveWritingEnabled) &&
                  IsUsEnglishEngine(engine_id)
              ? mojom::TextPredictionMode::kEnabled
diff --git a/chrome/browser/ash/input_method/native_input_method_engine_unittest.cc b/chrome/browser/ash/input_method/native_input_method_engine_unittest.cc
index 02da8001..34e8c8a 100644
--- a/chrome/browser/ash/input_method/native_input_method_engine_unittest.cc
+++ b/chrome/browser/ash/input_method/native_input_method_engine_unittest.cc
@@ -264,16 +264,6 @@
     });
   }
 
-  void EnableDefaultFeatureListWithMultiWordAndLacros() {
-    EnableFeatureList({
-        features::kAssistPersonalInfo,
-        features::kAssistPersonalInfoEmail,
-        features::kAssistPersonalInfoName,
-        features::kAssistMultiWord,
-        features::kLacrosSupport,
-    });
-  }
-
  private:
   content::BrowserTaskEnvironment task_environment_;
   base::test::ScopedFeatureList feature_list_;
@@ -364,27 +354,6 @@
 }
 
 TEST_F(NativeInputMethodEngineTest,
-       PredictiveWritingDoesNotLaunchImeServiceWithLacrosEnabled) {
-  TestingProfile testing_profile;
-  EnableDefaultFeatureListWithMultiWordAndLacros();
-  SetInputMethodOptions(testing_profile, /*autocorrect_enabled=*/false,
-                        /*predictive_writing_enabled=*/true);
-
-  testing::StrictMock<MockInputMethod> mock_input_method;
-  InputMethodManager::Initialize(
-      new TestInputMethodManager(&mock_input_method));
-  NativeInputMethodEngine engine;
-  engine.Initialize(std::make_unique<StubInputMethodEngineObserver>(),
-                    /*extension_id=*/"", &testing_profile);
-
-  engine.Enable(kEngineIdUs);
-  engine.FlushForTesting();  // ensure input_method is connected.
-  EXPECT_FALSE(engine.IsConnectedForTesting());
-
-  InputMethodManager::Shutdown();
-}
-
-TEST_F(NativeInputMethodEngineTest,
        PredictiveWritingLaunchesImeServiceWithEnglishEngineId) {
   TestingProfile testing_profile;
   EnableDefaultFeatureListWithMultiWord();
diff --git a/chrome/browser/ash/input_method/ui/suggestion_view.cc b/chrome/browser/ash/input_method/ui/completion_suggestion_view.cc
similarity index 76%
rename from chrome/browser/ash/input_method/ui/suggestion_view.cc
rename to chrome/browser/ash/input_method/ui/completion_suggestion_view.cc
index 7d14b15..a1babec1 100644
--- a/chrome/browser/ash/input_method/ui/suggestion_view.cc
+++ b/chrome/browser/ash/input_method/ui/completion_suggestion_view.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/ash/input_method/ui/suggestion_view.h"
+#include "chrome/browser/ash/input_method/ui/completion_suggestion_view.h"
 
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/app/vector_icons/vector_icons.h"
@@ -35,21 +35,6 @@
 const int kDownIconSize = 16;
 const int kEnterKeyHorizontalPadding = 2;
 
-// Creates the index label, and returns it (never returns nullptr).
-// The label text is not set in this function.
-std::unique_ptr<views::Label> CreateIndexLabel() {
-  auto index_label = std::make_unique<views::Label>();
-  index_label->SetFontList(gfx::FontList({kFontStyle}, gfx::Font::NORMAL,
-                                         kIndexFontSize,
-                                         gfx::Font::Weight::MEDIUM));
-  index_label->SetEnabledColor(
-      ResolveSemanticColor(cros_styles::ColorName::kTextColorSecondary));
-  index_label->SetHorizontalAlignment(gfx::ALIGN_CENTER);
-  index_label->SetBorder(
-      views::CreateEmptyBorder(gfx::Insets::VH(kPadding / 2, 0)));
-  return index_label;
-}
-
 std::unique_ptr<views::ImageView> CreateDownIcon() {
   auto icon = std::make_unique<views::ImageView>();
   icon->SetBorder(views::CreateEmptyBorder(gfx::Insets::TLBR(
@@ -97,10 +82,8 @@
 
 }  // namespace
 
-SuggestionView::SuggestionView(PressedCallback callback)
+CompletionSuggestionView::CompletionSuggestionView(PressedCallback callback)
     : views::Button(std::move(callback)) {
-  index_label_ = AddChildView(CreateIndexLabel());
-  index_label_->SetVisible(false);
   suggestion_label_ =
       AddChildView(std::make_unique<CompletionSuggestionLabelView>());
   suggestion_label_->SetBorder(
@@ -123,9 +106,10 @@
   SetProperty(views::kSkipAccessibilityPaintChecks, true);
 }
 
-SuggestionView::~SuggestionView() = default;
+CompletionSuggestionView::~CompletionSuggestionView() = default;
 
-std::unique_ptr<views::View> SuggestionView::CreateAnnotationContainer() {
+std::unique_ptr<views::View>
+CompletionSuggestionView::CreateAnnotationContainer() {
   auto label = std::make_unique<views::View>();
   label->SetLayoutManager(std::make_unique<views::BoxLayout>(
       views::BoxLayout::Orientation::kHorizontal));
@@ -137,7 +121,7 @@
 }
 
 std::unique_ptr<views::View>
-SuggestionView::CreateDownAndEnterAnnotationLabel() {
+CompletionSuggestionView::CreateDownAndEnterAnnotationLabel() {
   auto label = std::make_unique<views::View>();
   label->SetBorder(views::CreateEmptyBorder(
       gfx::Insets::TLBR(0, kAnnotationPaddingLeft, 0, 0)));
@@ -152,7 +136,8 @@
   return label;
 }
 
-std::unique_ptr<views::View> SuggestionView::CreateTabAnnotationLabel() {
+std::unique_ptr<views::View>
+CompletionSuggestionView::CreateTabAnnotationLabel() {
   auto label = std::make_unique<views::View>();
   label->SetBorder(views::CreateEmptyBorder(
       gfx::Insets::TLBR(0, kAnnotationPaddingLeft, 0, 0)));
@@ -162,7 +147,7 @@
   return label;
 }
 
-void SuggestionView::SetView(const SuggestionDetails& details) {
+void CompletionSuggestionView::SetView(const SuggestionDetails& details) {
   SetSuggestionText(details.text, details.confirmed_length);
   suggestion_width_ = suggestion_label_->GetPreferredSize().width();
   down_and_enter_annotation_label_->SetVisible(details.show_accept_annotation);
@@ -171,22 +156,14 @@
                                     details.show_quick_accept_annotation);
 }
 
-void SuggestionView::SetViewWithIndex(const std::u16string& index,
-                                      const std::u16string& text) {
-  index_label_->SetText(index);
-  index_label_->SetVisible(true);
-  index_width_ = index_label_->GetPreferredSize().width();
-  suggestion_label_->SetPrefixAndPrediction(u"", text);
-  suggestion_width_ = suggestion_label_->GetPreferredSize().width();
-}
-
-void SuggestionView::SetSuggestionText(const std::u16string& text,
-                                       const size_t confirmed_length) {
+void CompletionSuggestionView::SetSuggestionText(
+    const std::u16string& text,
+    const size_t confirmed_length) {
   suggestion_label_->SetPrefixAndPrediction(text.substr(0, confirmed_length),
                                             text.substr(confirmed_length));
 }
 
-void SuggestionView::SetHighlighted(bool highlighted) {
+void CompletionSuggestionView::SetHighlighted(bool highlighted) {
   if (highlighted_ == highlighted)
     return;
 
@@ -202,7 +179,7 @@
   SchedulePaint();
 }
 
-void SuggestionView::OnThemeChanged() {
+void CompletionSuggestionView::OnThemeChanged() {
   const auto* color_provider = GetColorProvider();
   down_icon_->SetImage(
       gfx::CreateVectorIcon(kKeyboardArrowDownIcon, kDownIconSize,
@@ -213,12 +190,8 @@
   views::View::OnThemeChanged();
 }
 
-void SuggestionView::Layout() {
+void CompletionSuggestionView::Layout() {
   int left = kPadding;
-  if (index_label_->GetVisible()) {
-    index_label_->SetBounds(left, 0, index_width_, height());
-    left += index_width_ + kPadding;
-  }
 
   suggestion_label_->SetBounds(left, 0, suggestion_width_, height());
 
@@ -232,13 +205,8 @@
   }
 }
 
-gfx::Size SuggestionView::CalculatePreferredSize() const {
+gfx::Size CompletionSuggestionView::CalculatePreferredSize() const {
   gfx::Size size;
-  if (index_label_->GetVisible()) {
-    size = index_label_->GetPreferredSize();
-    size.SetToMax(gfx::Size(index_width_, 0));
-    size.Enlarge(kPadding, 0);
-  }
   gfx::Size suggestion_size = suggestion_label_->GetPreferredSize();
   suggestion_size.SetToMax(gfx::Size(suggestion_width_, 0));
   size.Enlarge(suggestion_size.width() + 2 * kPadding, 0);
@@ -252,24 +220,24 @@
   return size;
 }
 
-void SuggestionView::SetMinWidth(int min_width) {
+void CompletionSuggestionView::SetMinWidth(int min_width) {
   min_width_ = min_width;
 }
 
-gfx::Point SuggestionView::GetAnchorOrigin() const {
+gfx::Point CompletionSuggestionView::GetAnchorOrigin() const {
   return gfx::Point(suggestion_label_->GetPrefixWidthPx() + kPadding, 0);
 }
 
-std::u16string SuggestionView::GetSuggestionForTesting() {
+std::u16string CompletionSuggestionView::GetSuggestionForTesting() {
   return suggestion_label_->GetText();
 }
 
-CompletionSuggestionLabelView* SuggestionView::suggestion_label_for_testing()
-    const {
+CompletionSuggestionLabelView*
+CompletionSuggestionView::suggestion_label_for_testing() const {
   return suggestion_label_;
 }
 
-BEGIN_METADATA(SuggestionView, views::Button)
+BEGIN_METADATA(CompletionSuggestionView, views::Button)
 END_METADATA
 
 }  // namespace ime
diff --git a/chrome/browser/ash/input_method/ui/suggestion_view.h b/chrome/browser/ash/input_method/ui/completion_suggestion_view.h
similarity index 79%
rename from chrome/browser/ash/input_method/ui/suggestion_view.h
rename to chrome/browser/ash/input_method/ui/completion_suggestion_view.h
index 235876cf..b61e860c 100644
--- a/chrome/browser/ash/input_method/ui/suggestion_view.h
+++ b/chrome/browser/ash/input_method/ui/completion_suggestion_view.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_ASH_INPUT_METHOD_UI_SUGGESTION_VIEW_H_
-#define CHROME_BROWSER_ASH_INPUT_METHOD_UI_SUGGESTION_VIEW_H_
+#ifndef CHROME_BROWSER_ASH_INPUT_METHOD_UI_COMPLETION_SUGGESTION_VIEW_H_
+#define CHROME_BROWSER_ASH_INPUT_METHOD_UI_COMPLETION_SUGGESTION_VIEW_H_
 
 #include "base/gtest_prod_util.h"
 #include "ui/base/metadata/metadata_header_macros.h"
@@ -41,20 +41,17 @@
 constexpr cros_styles::ColorName kButtonHighlightColor =
     cros_styles::ColorName::kRippleColor;
 
-// SuggestionView renders a suggestion.
-class UI_CHROMEOS_EXPORT SuggestionView : public views::Button {
+// CompletionSuggestionView renders a suggestion.
+class UI_CHROMEOS_EXPORT CompletionSuggestionView : public views::Button {
  public:
-  METADATA_HEADER(SuggestionView);
-  explicit SuggestionView(PressedCallback callback);
-  SuggestionView(const SuggestionView&) = delete;
-  SuggestionView& operator=(const SuggestionView&) = delete;
-  ~SuggestionView() override;
+  METADATA_HEADER(CompletionSuggestionView);
+  explicit CompletionSuggestionView(PressedCallback callback);
+  CompletionSuggestionView(const CompletionSuggestionView&) = delete;
+  CompletionSuggestionView& operator=(const CompletionSuggestionView&) = delete;
+  ~CompletionSuggestionView() override;
 
   void SetView(const SuggestionDetails& details);
 
-  void SetViewWithIndex(const std::u16string& index,
-                        const std::u16string& text);
-
   void SetHighlighted(bool highlighted);
   void SetMinWidth(int width);
 
@@ -86,7 +83,6 @@
   void SetSuggestionText(const std::u16string& text,
                          const size_t confirmed_length);
 
-  views::Label* index_label_ = nullptr;
   // The suggestion label renders the suggestion text.
   CompletionSuggestionLabelView* suggestion_label_ = nullptr;
   // The annotation view renders annotations.
@@ -97,12 +93,11 @@
   views::ImageView* arrow_icon_ = nullptr;
 
   int suggestion_width_ = 0;
-  int index_width_ = 0;
   int min_width_ = 0;
   bool highlighted_ = false;
 };
 
-BEGIN_VIEW_BUILDER(UI_CHROMEOS_EXPORT, SuggestionView, views::Button)
+BEGIN_VIEW_BUILDER(UI_CHROMEOS_EXPORT, CompletionSuggestionView, views::Button)
 VIEW_BUILDER_PROPERTY(const SuggestionDetails&, View)
 VIEW_BUILDER_PROPERTY(bool, Highlighted)
 VIEW_BUILDER_PROPERTY(int, MinWidth)
@@ -111,6 +106,6 @@
 }  // namespace ime
 }  // namespace ui
 
-DEFINE_VIEW_BUILDER(UI_CHROMEOS_EXPORT, ui::ime::SuggestionView)
+DEFINE_VIEW_BUILDER(UI_CHROMEOS_EXPORT, ui::ime::CompletionSuggestionView)
 
-#endif  // CHROME_BROWSER_ASH_INPUT_METHOD_UI_SUGGESTION_VIEW_H_
+#endif  // CHROME_BROWSER_ASH_INPUT_METHOD_UI_COMPLETION_SUGGESTION_VIEW_H_
diff --git a/chrome/browser/ash/input_method/ui/suggestion_view_unittest.cc b/chrome/browser/ash/input_method/ui/completion_suggestion_view_unittest.cc
similarity index 73%
rename from chrome/browser/ash/input_method/ui/suggestion_view_unittest.cc
rename to chrome/browser/ash/input_method/ui/completion_suggestion_view_unittest.cc
index 2a0c592c..524badb 100644
--- a/chrome/browser/ash/input_method/ui/suggestion_view_unittest.cc
+++ b/chrome/browser/ash/input_method/ui/completion_suggestion_view_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/ash/input_method/ui/suggestion_view.h"
+#include "chrome/browser/ash/input_method/ui/completion_suggestion_view.h"
 
 #include <stddef.h>
 
@@ -17,13 +17,14 @@
 namespace ime {
 namespace {
 
-class SuggestionViewTest : public views::ViewsTestBase {
+class CompletionSuggestionViewTest : public views::ViewsTestBase {
  public:
-  SuggestionViewTest() = default;
+  CompletionSuggestionViewTest() = default;
 };
 
-TEST_F(SuggestionViewTest, AnchorOriginIsPaddingWhenConfirmedLengthIsZero) {
-  SuggestionView suggestion({});
+TEST_F(CompletionSuggestionViewTest,
+       AnchorOriginIsPaddingWhenConfirmedLengthIsZero) {
+  CompletionSuggestionView suggestion({});
   suggestion.SetView({
       .text = u"good",
       .confirmed_length = 0,
@@ -32,9 +33,9 @@
   EXPECT_EQ(suggestion.GetAnchorOrigin(), gfx::Point(kPadding, 0));
 }
 
-TEST_F(SuggestionViewTest,
+TEST_F(CompletionSuggestionViewTest,
        AnchorOriginIsPaddingAndPrefixWidthWhenConfirmedLengthIsNonZero) {
-  SuggestionView suggestion({});
+  CompletionSuggestionView suggestion({});
   // "how a" is confirmed
   suggestion.SetView({
       .text = u"how are you",
diff --git a/chrome/browser/ash/input_method/ui/grammar_suggestion_window.cc b/chrome/browser/ash/input_method/ui/grammar_suggestion_window.cc
index cc3aa12..972c406 100644
--- a/chrome/browser/ash/input_method/ui/grammar_suggestion_window.cc
+++ b/chrome/browser/ash/input_method/ui/grammar_suggestion_window.cc
@@ -50,8 +50,8 @@
   SetLayoutManager(std::make_unique<views::BoxLayout>(
       views::BoxLayout::Orientation::kHorizontal));
 
-  suggestion_button_ =
-      AddChildView(std::make_unique<SuggestionView>(base::BindRepeating(
+  suggestion_button_ = AddChildView(
+      std::make_unique<CompletionSuggestionView>(base::BindRepeating(
           &AssistiveDelegate::AssistiveWindowButtonClicked,
           base::Unretained(delegate_),
           AssistiveWindowButton{
@@ -164,7 +164,8 @@
   SetAnchorRect(bounds);
 }
 
-SuggestionView* GrammarSuggestionWindow::GetSuggestionButtonForTesting() {
+CompletionSuggestionView*
+GrammarSuggestionWindow::GetSuggestionButtonForTesting() {
   return suggestion_button_;
 }
 
diff --git a/chrome/browser/ash/input_method/ui/grammar_suggestion_window.h b/chrome/browser/ash/input_method/ui/grammar_suggestion_window.h
index 2d73404..dc204dc 100644
--- a/chrome/browser/ash/input_method/ui/grammar_suggestion_window.h
+++ b/chrome/browser/ash/input_method/ui/grammar_suggestion_window.h
@@ -6,7 +6,7 @@
 #define CHROME_BROWSER_ASH_INPUT_METHOD_UI_GRAMMAR_SUGGESTION_WINDOW_H_
 
 #include "chrome/browser/ash/input_method/ui/assistive_delegate.h"
-#include "chrome/browser/ash/input_method/ui/suggestion_view.h"
+#include "chrome/browser/ash/input_method/ui/completion_suggestion_view.h"
 #include "ui/base/metadata/metadata_header_macros.h"
 #include "ui/chromeos/ui_chromeos_export.h"
 #include "ui/views/bubble/bubble_dialog_delegate_view.h"
@@ -39,7 +39,7 @@
 
   void SetBounds(gfx::Rect bounds);
 
-  SuggestionView* GetSuggestionButtonForTesting();
+  CompletionSuggestionView* GetSuggestionButtonForTesting();
   views::Button* GetIgnoreButtonForTesting();
 
  protected:
@@ -47,7 +47,7 @@
 
  private:
   AssistiveDelegate* delegate_;
-  SuggestionView* suggestion_button_;
+  CompletionSuggestionView* suggestion_button_;
   views::ImageButton* ignore_button_;
 
   ButtonId current_highlighted_button_id_ = ButtonId::kNone;
diff --git a/chrome/browser/ash/input_method/ui/suggestion_window_view.cc b/chrome/browser/ash/input_method/ui/suggestion_window_view.cc
index 939ba73..301c9bb 100644
--- a/chrome/browser/ash/input_method/ui/suggestion_window_view.cc
+++ b/chrome/browser/ash/input_method/ui/suggestion_window_view.cc
@@ -14,8 +14,8 @@
 #include "chrome/browser/ash/input_method/ui/assistive_delegate.h"
 #include "chrome/browser/ash/input_method/ui/border_factory.h"
 #include "chrome/browser/ash/input_method/ui/colors.h"
+#include "chrome/browser/ash/input_method/ui/completion_suggestion_view.h"
 #include "chrome/browser/ash/input_method/ui/suggestion_details.h"
-#include "chrome/browser/ash/input_method/ui/suggestion_view.h"
 #include "chrome/grit/generated_resources.h"
 #include "components/strings/grit/components_strings.h"
 #include "components/vector_icons/vector_icons.h"
@@ -35,6 +35,7 @@
 #include "ui/views/bubble/bubble_border.h"
 #include "ui/views/bubble/bubble_frame_view.h"
 #include "ui/views/controls/button/image_button.h"
+#include "ui/views/controls/button/label_button.h"
 #include "ui/views/controls/link.h"
 #include "ui/views/layout/box_layout.h"
 #include "ui/views/layout/layout_provider.h"
@@ -87,12 +88,12 @@
 }
 
 void SuggestionWindowView::Show(const SuggestionDetails& details) {
-  ResizeCandidateArea(1);
-  auto* const candidate =
-      static_cast<SuggestionView*>(candidate_area_->children().front());
-  candidate->SetView(details);
+  ResizeCandidateArea(0);
+
+  completion_view_->SetVisible(true);
+  completion_view_->SetView(details);
   if (details.show_setting_link)
-    candidate->SetMinWidth(setting_link_->GetPreferredSize().width());
+    completion_view_->SetMinWidth(setting_link_->GetPreferredSize().width());
 
   setting_link_->SetVisible(details.show_setting_link);
 
@@ -102,14 +103,12 @@
 void SuggestionWindowView::ShowMultipleCandidates(
     const ash::input_method::AssistiveWindowProperties& properties) {
   const std::vector<std::u16string>& candidates = properties.candidates;
+  completion_view_->SetVisible(false);
   ResizeCandidateArea(candidates.size());
   for (size_t i = 0; i < candidates.size(); ++i) {
-    auto* const candidate =
-        static_cast<SuggestionView*>(candidate_area_->children()[i]);
-    if (properties.show_indices)
-      candidate->SetViewWithIndex(base::FormatNumber(i + 1), candidates[i]);
-    else
-      candidate->SetView({.text = candidates[i]});
+    auto* const candidate = static_cast<views::LabelButton*>(
+        multiple_candidate_area_->children()[i]);
+    candidate->SetText(candidates[i]);
   }
 
   learn_more_button_->SetVisible(properties.show_setting_link);
@@ -121,10 +120,16 @@
     const AssistiveWindowButton& button,
     bool highlighted) {
   if (button.id == ButtonId::kSuggestion) {
-    const views::View::Views& candidates = candidate_area_->children();
-    if (button.index < candidates.size()) {
-      SetCandidateHighlighted(
-          static_cast<SuggestionView*>(candidates[button.index]), highlighted);
+    if (completion_view_->GetVisible()) {
+      completion_view_->SetHighlighted(highlighted);
+    } else {
+      const views::View::Views& candidates =
+          multiple_candidate_area_->children();
+      if (button.index < candidates.size()) {
+        SetCandidateHighlighted(
+            static_cast<views::LabelButton*>(candidates[button.index]),
+            highlighted);
+      }
     }
   } else if (button.id == ButtonId::kSmartInputsSettingLink) {
     SetHighlighted(*setting_link_, highlighted);
@@ -134,15 +139,11 @@
 }
 
 gfx::Rect SuggestionWindowView::GetBubbleBounds() {
-  // The bubble bounds must be shifted to align with the anchor.
-  // If there is more than one suggestion, use the anchor origin of the first
-  // (topmost) suggestion. This allows the alignment to work correctly for both
-  // vertical and horizontal orientations.
-  const views::View::Views& candidates = candidate_area_->children();
-  const gfx::Point anchor_origin =
-      !candidates.empty()
-          ? static_cast<SuggestionView*>(candidates[0])->GetAnchorOrigin()
-          : gfx::Point(0, 0);
+  // The bubble bounds must be shifted to align with the anchor if there is a
+  // completion view.
+  const gfx::Point anchor_origin = completion_view_->GetVisible()
+                                       ? completion_view_->GetAnchorOrigin()
+                                       : gfx::Point(0, 0);
   return BubbleDialogDelegateView::GetBubbleBounds() -
          anchor_origin.OffsetFromOrigin();
 }
@@ -193,11 +194,19 @@
     }
   }
 
-  SetLayoutManager(std::make_unique<views::BoxLayout>(layout_orientation));
-
-  candidate_area_ = AddChildView(std::make_unique<views::View>());
-  candidate_area_->SetLayoutManager(
+  SetLayoutManager(std::make_unique<views::BoxLayout>(
+      views::BoxLayout::Orientation::kHorizontal));
+  completion_view_ = AddChildView(
+      std::make_unique<CompletionSuggestionView>(base::BindRepeating(
+          &AssistiveDelegate::AssistiveWindowButtonClicked,
+          base::Unretained(delegate_),
+          AssistiveWindowButton{.id = ui::ime::ButtonId::kSuggestion,
+                                .index = 0})));
+  completion_view_->SetVisible(false);
+  multiple_candidate_area_ = AddChildView(std::make_unique<views::View>());
+  multiple_candidate_area_->SetLayoutManager(
       std::make_unique<views::BoxLayout>(layout_orientation));
+  multiple_candidate_area_->SetVisible(false);
 
   setting_link_ = AddChildView(std::make_unique<views::Link>(
       l10n_util::GetStringUTF16(IDS_SUGGESTION_LEARN_MORE)));
@@ -242,24 +251,27 @@
 SuggestionWindowView::~SuggestionWindowView() = default;
 
 void SuggestionWindowView::ResizeCandidateArea(size_t size) {
-  if (highlighted_candidate_)
-    SetCandidateHighlighted(highlighted_candidate_, false);
-
-  const views::View::Views& candidates = candidate_area_->children();
+  const views::View::Views& candidates = multiple_candidate_area_->children();
   while (candidates.size() > size) {
     subscriptions_.erase(
-        candidate_area_->RemoveChildViewT(candidates.back()).get());
+        multiple_candidate_area_->RemoveChildViewT(candidates.back()).get());
   }
 
   for (size_t index = candidates.size(); index < size; ++index) {
-    auto* const candidate = candidate_area_->AddChildView(
-        std::make_unique<SuggestionView>(base::BindRepeating(
-            &AssistiveDelegate::AssistiveWindowButtonClicked,
-            base::Unretained(delegate_),
-            AssistiveWindowButton{.id = ui::ime::ButtonId::kSuggestion,
-                                  .index = index})));
+    // TODO(b/217560706): Separate this into a CandidateView that will follow
+    // specs and contain an index.
+    auto* const candidate = multiple_candidate_area_->AddChildView(
+        std::make_unique<views::LabelButton>(
+            base::BindRepeating(
+                &AssistiveDelegate::AssistiveWindowButtonClicked,
+                base::Unretained(delegate_),
+                AssistiveWindowButton{.id = ui::ime::ButtonId::kSuggestion,
+                                      .index = index}),
+            u""));
+    candidate->SetBorder(views::CreateEmptyBorder(gfx::Insets::VH(6, 10)));
+
     auto subscription = candidate->AddStateChangedCallback(base::BindRepeating(
-        [](SuggestionWindowView* window, SuggestionView* button) {
+        [](SuggestionWindowView* window, views::LabelButton* button) {
           window->SetCandidateHighlighted(button, ShouldHighlight(*button));
         },
         base::Unretained(this), base::Unretained(candidate)));
@@ -268,24 +280,19 @@
 }
 
 void SuggestionWindowView::MakeVisible() {
-  candidate_area_->SetVisible(true);
+  multiple_candidate_area_->SetVisible(true);
   SizeToContents();
 }
 
-void SuggestionWindowView::SetCandidateHighlighted(SuggestionView* candidate,
+void SuggestionWindowView::SetCandidateHighlighted(views::LabelButton* view,
                                                    bool highlighted) {
-  DCHECK(candidate);
-  DCHECK_EQ(candidate_area_, candidate->parent());
+  // Clear all highlights if any exists.
+  for (auto* candidate : multiple_candidate_area_->children()) {
+    SetHighlighted(*candidate, false);
+  }
 
-  // Can't highlight a highlighted candidate, or unhighlight an unhighlighted
-  // one.
-  if (highlighted == (candidate == highlighted_candidate_))
-    return;
-
-  if (highlighted && highlighted_candidate_)
-    highlighted_candidate_->SetHighlighted(false);
-  candidate->SetHighlighted(highlighted);
-  highlighted_candidate_ = highlighted ? candidate : nullptr;
+  if (highlighted)
+    SetHighlighted(*view, highlighted);
 }
 
 BEGIN_METADATA(SuggestionWindowView, views::BubbleDialogDelegateView)
diff --git a/chrome/browser/ash/input_method/ui/suggestion_window_view.h b/chrome/browser/ash/input_method/ui/suggestion_window_view.h
index 6ec619b..6696296 100644
--- a/chrome/browser/ash/input_method/ui/suggestion_window_view.h
+++ b/chrome/browser/ash/input_method/ui/suggestion_window_view.h
@@ -34,7 +34,7 @@
 class AssistiveDelegate;
 struct AssistiveWindowButton;
 struct SuggestionDetails;
-class SuggestionView;
+class CompletionSuggestionView;
 
 // SuggestionWindowView is the main container of the suggestion window UI.
 class UI_CHROMEOS_EXPORT SuggestionWindowView
@@ -69,7 +69,9 @@
   void SetButtonHighlighted(const AssistiveWindowButton& button,
                             bool highlighted);
 
-  views::View* candidate_area_for_testing() { return candidate_area_; }
+  views::View* multiple_candidate_area_for_testing() {
+    return multiple_candidate_area_;
+  }
   views::Link* setting_link_for_testing() { return setting_link_; }
   views::ImageButton* learn_more_button_for_testing() {
     return learn_more_button_;
@@ -96,13 +98,18 @@
 
   // Sets |candidate|'s highlight state to |highlighted|. At most one candidate
   // will be highlighted at any given time.
-  void SetCandidateHighlighted(SuggestionView* candidate, bool highlighted);
+  void SetCandidateHighlighted(views::LabelButton* candidate, bool highlighted);
 
   // The delegate to handle events from this class.
   AssistiveDelegate* const delegate_;
 
-  // The view containing all the suggestions.
-  views::View* candidate_area_;
+  // The view containing all the suggestions if multiple candidates are
+  // visible.
+  views::View* multiple_candidate_area_;
+
+  // The view containing the completion view. If this is visible then there is
+  // only one suggestion to show.
+  CompletionSuggestionView* completion_view_;
 
   // The setting link, positioned below candidate_area_.
   // TODO(crbug/1102175): Rename setting to settings since there can be multiple
@@ -111,9 +118,6 @@
 
   views::ImageButton* learn_more_button_;
 
-  // The currently-highlighted candidate, if any.
-  SuggestionView* highlighted_candidate_ = nullptr;
-
   // TODO(crbug/1099062): Add tests for mouse hovered and pressed.
   base::flat_map<views::View*, base::CallbackListSubscription> subscriptions_;
 
diff --git a/chrome/browser/ash/input_method/ui/suggestion_window_view_unittest.cc b/chrome/browser/ash/input_method/ui/suggestion_window_view_unittest.cc
index 1707199..6b5dc87 100644
--- a/chrome/browser/ash/input_method/ui/suggestion_window_view_unittest.cc
+++ b/chrome/browser/ash/input_method/ui/suggestion_window_view_unittest.cc
@@ -10,8 +10,8 @@
 #include "chrome/browser/ash/input_method/assistive_window_properties.h"
 #include "chrome/browser/ash/input_method/ui/assistive_delegate.h"
 #include "chrome/browser/ash/input_method/ui/completion_suggestion_label_view.h"
+#include "chrome/browser/ash/input_method/ui/completion_suggestion_view.h"
 #include "chrome/browser/ash/input_method/ui/suggestion_details.h"
-#include "chrome/browser/ash/input_method/ui/suggestion_view.h"
 #include "chrome/test/views/chrome_views_test_base.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
@@ -68,7 +68,8 @@
 
   size_t GetHighlightedCount() const {
     const auto& children =
-        suggestion_window_view_->candidate_area_for_testing()->children();
+        suggestion_window_view_->multiple_candidate_area_for_testing()
+            ->children();
     return std::count_if(
         children.cbegin(), children.cend(),
         [](const views::View* v) { return !!v->background(); });
@@ -76,7 +77,8 @@
 
   absl::optional<int> GetHighlightedIndex() const {
     const auto& children =
-        suggestion_window_view_->candidate_area_for_testing()->children();
+        suggestion_window_view_->multiple_candidate_area_for_testing()
+            ->children();
     const auto it =
         std::find_if(children.cbegin(), children.cend(),
                      [](const views::View* v) { return !!v->background(); });
@@ -310,7 +312,8 @@
   suggestion_window_view_->ShowMultipleCandidates(window_);
   views::BoxLayout::Orientation layout_orientation =
       static_cast<views::BoxLayout*>(
-          suggestion_window_view_->GetLayoutManager())
+          suggestion_window_view_->multiple_candidate_area_for_testing()
+              ->GetLayoutManager())
           ->GetOrientation();
   EXPECT_EQ(layout_orientation, expected_orientation);
 }
diff --git a/chrome/browser/ash/policy/dlp/dlp_files_controller.cc b/chrome/browser/ash/policy/dlp/dlp_files_controller.cc
index 07956516..ffc03bc1 100644
--- a/chrome/browser/ash/policy/dlp/dlp_files_controller.cc
+++ b/chrome/browser/ash/policy/dlp/dlp_files_controller.cc
@@ -4,11 +4,13 @@
 
 #include "chrome/browser/ash/policy/dlp/dlp_files_controller.h"
 
+#include <sys/types.h>
 #include <string>
 
 #include "base/bind.h"
 #include "base/check.h"
 #include "base/containers/contains.h"
+#include "base/containers/flat_map.h"
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
 #include "base/logging.h"
@@ -30,6 +32,22 @@
 
 namespace {
 
+absl::optional<ino_t> GetInodeValue(const base::FilePath& path) {
+  struct stat file_stats;
+  if (stat(path.value().c_str(), &file_stats) != 0)
+    return absl::nullopt;
+  return file_stats.st_ino;
+}
+
+std::vector<absl::optional<ino_t>> GetFilesInodes(
+    const std::vector<storage::FileSystemURL>& files) {
+  std::vector<absl::optional<ino_t>> inodes;
+  for (const auto& file : files) {
+    inodes.push_back(GetInodeValue(file.path()));
+  }
+  return inodes;
+}
+
 // Maps |file_path| to DlpRulesManager::Component if possible.
 absl::optional<DlpRulesManager::Component> MapFilePathtoPolicyComponent(
     Profile* profile,
@@ -62,6 +80,11 @@
 
 }  // namespace
 
+DlpFilesController::DlpFileMetadata::DlpFileMetadata(
+    const std::string& source_url,
+    bool is_dlp_restricted)
+    : source_url(source_url), is_dlp_restricted(is_dlp_restricted) {}
+
 DlpFilesController::DlpFilesController() = default;
 
 DlpFilesController::~DlpFilesController() = default;
@@ -100,16 +123,25 @@
                      std::move(result_callback)));
 }
 
-void DlpFilesController::GetFilesRestrictedByAnyRule(
+void DlpFilesController::GetDlpMetadata(
     std::vector<storage::FileSystemURL> files,
-    GetFilesRestrictedByAnyRuleCallback result_callback) {
+    GetDlpMetadataCallback result_callback) {
   if (!chromeos::DlpClient::Get() || !chromeos::DlpClient::Get()->IsAlive()) {
-    std::move(result_callback).Run(std::vector<storage::FileSystemURL>());
+    std::move(result_callback).Run(std::vector<DlpFileMetadata>());
     return;
   }
-  // TODO(aidazolic): Implement getting the restricted files by calling DLP
-  // daemon to check restrictions.
-  NOTIMPLEMENTED();
+
+  std::vector<absl::optional<ino_t>> inodes = GetFilesInodes(files);
+  dlp::GetFilesSourcesRequest request;
+  for (const auto& inode : inodes) {
+    if (inode.has_value()) {
+      request.add_files_inodes(inode.value());
+    }
+  }
+  chromeos::DlpClient::Get()->GetFilesSources(
+      request, base::BindOnce(&DlpFilesController::ReturnDlpMetadata,
+                              weak_ptr_factory_.GetWeakPtr(), std::move(inodes),
+                              std::move(result_callback)));
 }
 
 void DlpFilesController::FilterDisallowedUploads(
@@ -218,4 +250,48 @@
   std::move(result_callback).Run(std::move(filtered_files));
 }
 
+void DlpFilesController::ReturnDlpMetadata(
+    std::vector<absl::optional<ino_t>> inodes,
+    GetDlpMetadataCallback result_callback,
+    const dlp::GetFilesSourcesResponse response) {
+  if (response.has_error_message()) {
+    LOG(ERROR) << "Failed to get files sources, error: "
+               << response.error_message();
+  }
+
+  policy::DlpRulesManager* dlp_rules_manager =
+      policy::DlpRulesManagerFactory::GetForPrimaryProfile();
+  if (!dlp_rules_manager) {
+    std::move(result_callback).Run(std::vector<DlpFileMetadata>());
+    return;
+  }
+
+  base::flat_map<ino_t, DlpFileMetadata> metadata_map;
+  for (const auto& metadata : response.files_metadata()) {
+    DlpRulesManager::Level level = dlp_rules_manager->IsRestrictedByAnyRule(
+        GURL(metadata.source_url()), DlpRulesManager::Restriction::kFiles);
+    bool is_dlp_restricted = level != DlpRulesManager::Level::kNotSet &&
+                             level != DlpRulesManager::Level::kAllow;
+    metadata_map.emplace(
+        metadata.inode(),
+        DlpFileMetadata(metadata.source_url(), is_dlp_restricted));
+  }
+
+  std::vector<DlpFileMetadata> result;
+  for (const auto& inode : inodes) {
+    if (!inode.has_value()) {
+      result.emplace_back("", false);
+      continue;
+    }
+    auto metadata_itr = metadata_map.find(inode.value());
+    if (metadata_itr == metadata_map.end()) {
+      result.emplace_back("", false);
+    } else {
+      result.emplace_back(metadata_itr->second);
+    }
+  }
+
+  std::move(result_callback).Run(std::move(result));
+}
+
 }  // namespace policy
diff --git a/chrome/browser/ash/policy/dlp/dlp_files_controller.h b/chrome/browser/ash/policy/dlp/dlp_files_controller.h
index 9fadd84..b5ecd0f 100644
--- a/chrome/browser/ash/policy/dlp/dlp_files_controller.h
+++ b/chrome/browser/ash/policy/dlp/dlp_files_controller.h
@@ -11,6 +11,7 @@
 #include "base/containers/flat_map.h"
 #include "base/memory/weak_ptr.h"
 #include "chromeos/dbus/dlp/dlp_service.pb.h"
+#include "storage/browser/file_system/file_system_url.h"
 #include "third_party/blink/public/mojom/choosers/file_chooser.mojom-forward.h"
 #include "url/gurl.h"
 
@@ -27,14 +28,35 @@
 // of the Data leak prevention policy set by the admin.
 class DlpFilesController {
  public:
+  // DlpFileMetadata keeps metadata about a file, such as whether it's managed
+  // or not and the source URL, if it exists.
+  struct DlpFileMetadata {
+    DlpFileMetadata() = delete;
+    DlpFileMetadata(const std::string& source_url, bool is_dlp_restricted);
+
+    friend bool operator==(const DlpFileMetadata& a, const DlpFileMetadata& b) {
+      return a.is_dlp_restricted == b.is_dlp_restricted &&
+             a.source_url == b.source_url;
+    }
+    friend bool operator!=(const DlpFileMetadata& a, const DlpFileMetadata& b) {
+      return !(a == b);
+    }
+
+    // Source URL from which the file was downloaded.
+    std::string source_url;
+    // Whether the file is under any DLP rule or not.
+    bool is_dlp_restricted;
+  };
+
   using GetDisallowedTransfersCallback =
       base::OnceCallback<void(std::vector<storage::FileSystemURL>)>;
   using GetFilesRestrictedByAnyRuleCallback = GetDisallowedTransfersCallback;
   using FilterDisallowedUploadsCallback = base::OnceCallback<void(
       std::vector<blink::mojom::FileChooserFileInfoPtr>)>;
+  using GetDlpMetadataCallback =
+      base::OnceCallback<void(std::vector<DlpFileMetadata>)>;
 
   DlpFilesController();
-
   DlpFilesController(const DlpFilesController& other) = delete;
   DlpFilesController& operator=(const DlpFilesController& other) = delete;
 
@@ -46,10 +68,10 @@
       storage::FileSystemURL destination,
       GetDisallowedTransfersCallback result_callback);
 
-  // Returns a list of files restricted by any DLP rule in |result_callback|.
-  void GetFilesRestrictedByAnyRule(
-      std::vector<storage::FileSystemURL> files,
-      GetFilesRestrictedByAnyRuleCallback result_callback);
+  // Retrieves metadata for each entry in |files| and returns it as a list in
+  // |result_callback|.
+  void GetDlpMetadata(std::vector<storage::FileSystemURL> files,
+                      GetDlpMetadataCallback result_callback);
 
   // Filters files disallowed to be uploaded to `destination`.
   void FilterDisallowedUploads(
@@ -74,6 +96,9 @@
       std::vector<blink::mojom::FileChooserFileInfoPtr> uploaded_files,
       FilterDisallowedUploadsCallback result_callback,
       dlp::CheckFilesTransferResponse response);
+  void ReturnDlpMetadata(std::vector<absl::optional<ino_t>> inodes,
+                         GetDlpMetadataCallback result_callback,
+                         const dlp::GetFilesSourcesResponse response);
 
   base::WeakPtrFactory<DlpFilesController> weak_ptr_factory_{this};
 };
diff --git a/chrome/browser/ash/policy/dlp/dlp_files_controller_unittest.cc b/chrome/browser/ash/policy/dlp/dlp_files_controller_unittest.cc
index 19239177..50e1394 100644
--- a/chrome/browser/ash/policy/dlp/dlp_files_controller_unittest.cc
+++ b/chrome/browser/ash/policy/dlp/dlp_files_controller_unittest.cc
@@ -70,6 +70,10 @@
 }  // namespace
 
 class DlpFilesControllerTest : public testing::Test {
+ public:
+  DlpFilesControllerTest(const DlpFilesControllerTest&) = delete;
+  DlpFilesControllerTest& operator=(const DlpFilesControllerTest&) = delete;
+
  protected:
   DlpFilesControllerTest()
       : profile_(std::make_unique<TestingProfile>()),
@@ -77,10 +81,7 @@
         scoped_user_manager_(std::make_unique<user_manager::ScopedUserManager>(
             base::WrapUnique(user_manager_))) {}
 
-  DlpFilesControllerTest(const DlpFilesControllerTest&) = delete;
-  DlpFilesControllerTest& operator=(const DlpFilesControllerTest&) = delete;
-
-  ~DlpFilesControllerTest() override {}
+  ~DlpFilesControllerTest() override = default;
 
   void SetUp() override {
     AccountId account_id = AccountId::FromUserEmailGaiaId(kEmailId, kGaiaId);
@@ -327,19 +328,58 @@
   EXPECT_EQ(filtered_uploads, future.Take());
 }
 
+TEST_F(DlpFilesControllerTest, GetDlpMetadata) {
+  AddFilesToDlpClient();
+
+  std::vector<storage::FileSystemURL> files_to_check(
+      {file_url1_, file_url2_, file_url3_});
+  std::vector<DlpFilesController::DlpFileMetadata> dlp_metadata(
+      {DlpFilesController::DlpFileMetadata(kExample1, true),
+       DlpFilesController::DlpFileMetadata(kExample2, false),
+       DlpFilesController::DlpFileMetadata(kExample3, true)});
+
+  EXPECT_CALL(*rules_manager_, IsRestrictedByAnyRule)
+      .WillOnce(testing::Return(DlpRulesManager::Level::kBlock))
+      .WillOnce(testing::Return(DlpRulesManager::Level::kAllow))
+      .WillOnce(testing::Return(DlpRulesManager::Level::kWarn));
+
+  base::test::TestFuture<std::vector<DlpFilesController::DlpFileMetadata>>
+      future;
+  files_controller_.GetDlpMetadata(files_to_check, future.GetCallback());
+  EXPECT_TRUE(future.Wait());
+  EXPECT_EQ(dlp_metadata, future.Take());
+}
+
+TEST_F(DlpFilesControllerTest, GetDlpMetadata_FileNotAvailable) {
+  ASSERT_TRUE(chromeos::DlpClient::Get()->IsAlive());
+
+  std::vector<storage::FileSystemURL> files_to_check({file_url1_});
+  std::vector<DlpFilesController::DlpFileMetadata> dlp_metadata(
+      {DlpFilesController::DlpFileMetadata("", false)});
+
+  EXPECT_CALL(*rules_manager_, IsRestrictedByAnyRule).Times(0);
+
+  base::test::TestFuture<std::vector<DlpFilesController::DlpFileMetadata>>
+      future;
+  files_controller_.GetDlpMetadata(files_to_check, future.GetCallback());
+  EXPECT_TRUE(future.Wait());
+  EXPECT_EQ(dlp_metadata, future.Take());
+}
+
 class DlpFilesExternalDestinationTest
     : public DlpFilesControllerTest,
       public ::testing::WithParamInterface<
           std::tuple<std::string, std::string, DlpRulesManager::Component>> {
- protected:
-  DlpFilesExternalDestinationTest() = default;
-
+ public:
   DlpFilesExternalDestinationTest(const DlpFilesExternalDestinationTest&) =
       delete;
   DlpFilesExternalDestinationTest& operator=(
       const DlpFilesExternalDestinationTest&) = delete;
 
-  ~DlpFilesExternalDestinationTest() = default;
+ protected:
+  DlpFilesExternalDestinationTest() = default;
+
+  ~DlpFilesExternalDestinationTest() override = default;
 
   void SetUp() override {
     DlpFilesControllerTest::SetUp();
diff --git a/chrome/browser/ash/policy/status_collector/app_info_generator_unittest.cc b/chrome/browser/ash/policy/status_collector/app_info_generator_unittest.cc
index 0a558680..63d4fb4 100644
--- a/chrome/browser/ash/policy/status_collector/app_info_generator_unittest.cc
+++ b/chrome/browser/ash/policy/status_collector/app_info_generator_unittest.cc
@@ -17,7 +17,6 @@
 #include "chrome/browser/ash/login/users/chrome_user_manager.h"
 #include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
 #include "chrome/browser/ash/login/users/mock_user_manager.h"
-#include "chrome/browser/web_applications/system_web_apps/test/test_system_web_app_manager.h"
 #include "chrome/browser/web_applications/test/fake_install_finalizer.h"
 #include "chrome/browser/web_applications/test/fake_web_app_provider.h"
 #include "chrome/browser/web_applications/test/fake_web_app_registry_controller.h"
@@ -210,24 +209,11 @@
     profile_ = CreateProfile(account_id_);
     test_clock().SetNow(MakeLocalTime("25-MAR-2020 1:30am"));
 
-    web_app::WebAppProviderFactory::GetInstance()->SetTestingFactoryAndUse(
-        profile_.get(),
-        base::BindLambdaForTesting([this](content::BrowserContext* context)
-                                       -> std::unique_ptr<KeyedService> {
-          Profile* profile = Profile::FromBrowserContext(context);
-          auto provider =
-              std::make_unique<web_app::FakeWebAppProvider>(profile);
-          auto app_registrar =
-              std::make_unique<web_app::WebAppRegistrarMutable>(profile);
-          auto system_web_app_manager =
-              std::make_unique<web_app::TestSystemWebAppManager>(profile);
+    auto* provider = web_app::FakeWebAppProvider::Get(profile_.get());
+    provider->SetRunSubsystemStartupTasks(true);
+    provider->Start();
 
-          app_registrar_ = app_registrar.get();
-          provider->SetRegistrar(std::move(app_registrar));
-          provider->SetSystemWebAppManager(std::move(system_web_app_manager));
-          provider->Start();
-          return provider;
-        }));
+    app_registrar_ = &provider->GetRegistrarMutable();
   }
 
   apps::AppRegistryCache& GetCache() {
diff --git a/chrome/browser/ash/system_web_apps/system_web_app_manager.cc b/chrome/browser/ash/system_web_apps/system_web_app_manager.cc
index 385ba4e2..4da38809 100644
--- a/chrome/browser/ash/system_web_apps/system_web_app_manager.cc
+++ b/chrome/browser/ash/system_web_apps/system_web_app_manager.cc
@@ -22,10 +22,12 @@
 #include "base/version.h"
 #include "build/chromeos_buildflags.h"
 #include "chrome/browser/ash/system_web_apps/system_web_app_background_task.h"
+#include "chrome/browser/ash/system_web_apps/system_web_app_manager_factory.h"
 #include "chrome/browser/ash/system_web_apps/types/system_web_app_delegate.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/web_applications/external_install_options.h"
+#include "chrome/browser/web_applications/manifest_update_manager.h"
 #include "chrome/browser/web_applications/policy/web_app_policy_manager.h"
 #include "chrome/browser/web_applications/user_display_mode.h"
 #include "chrome/browser/web_applications/web_app.h"
@@ -241,20 +243,20 @@
   system_app_delegates_ = CreateSystemWebApps(profile_);
 }
 
-SystemWebAppManager::~SystemWebAppManager() = default;
+SystemWebAppManager::~SystemWebAppManager() {
+  // SystemWebAppManager lifetime matches WebAppProvider lifetime (see
+  // BrowserContextDependencyManager) but we reset pointers to
+  // system_app_delegates_ for integrity with DCHECKs.
+  if (provider_)
+    ConnectProviderToSystemWebAppDelegateMap(nullptr);
+}
 
 // static
 SystemWebAppManager* SystemWebAppManager::Get(Profile* profile) {
   if (!web_app::AreSystemWebAppsSupported())
     return nullptr;
 
-  web_app::WebAppProvider* provider =
-      web_app::WebAppProvider::GetForLocalAppsUnchecked(profile);
-  if (!provider)
-    return nullptr;
-
-  provider->CheckIsConnected();
-  return provider->system_web_app_manager_.get();
+  return GetForLocalAppsUnchecked(profile);
 }
 
 // static
@@ -269,24 +271,33 @@
 // static
 SystemWebAppManager* SystemWebAppManager::GetForLocalAppsUnchecked(
     Profile* profile) {
-  web_app::WebAppProvider* provider =
-      web_app::WebAppProvider::GetForLocalAppsUnchecked(profile);
-  if (!provider)
+  SystemWebAppManager* swa_manager =
+      SystemWebAppManagerFactory::GetForProfile(profile);
+  if (!swa_manager)
     return nullptr;
 
-  provider->CheckIsConnected();
-  return provider->system_web_app_manager_.get();
+  swa_manager->CheckIsConnected();
+  return swa_manager;
 }
 
 // static
 SystemWebAppManager* SystemWebAppManager::GetForTest(Profile* profile) {
   web_app::WebAppProvider* provider =
-      web_app::WebAppProvider::GetForTest(profile);
+      SystemWebAppManager::GetWebAppProvider(profile);
   if (!provider)
     return nullptr;
 
-  provider->CheckIsConnected();
-  return provider->system_web_app_manager_.get();
+  SystemWebAppManager* swa_manager = GetForLocalAppsUnchecked(profile);
+  DCHECK(swa_manager);
+  swa_manager->CheckIsConnected();
+
+  if (provider->on_registry_ready().is_signaled())
+    return swa_manager;
+
+  base::RunLoop run_loop;
+  provider->on_registry_ready().Post(FROM_HERE, run_loop.QuitClosure());
+  run_loop.Run();
+  return swa_manager;
 }
 
 void SystemWebAppManager::StopBackgroundTasks() {
@@ -299,11 +310,6 @@
   return IsSystemWebAppEnabled(system_app_delegates_, type);
 }
 
-void SystemWebAppManager::Shutdown() {
-  shutting_down_ = true;
-  StopBackgroundTasks();
-}
-
 void SystemWebAppManager::SetSubsystems(
     web_app::ExternallyManagedAppManager* externally_managed_app_manager,
     web_app::WebAppRegistrar* registrar,
@@ -320,6 +326,25 @@
   ui_manager_observation_.Observe(ui_manager);
 }
 
+void SystemWebAppManager::ConnectSubsystems(web_app::WebAppProvider* provider) {
+  DCHECK(provider);
+  DCHECK(!provider_);
+  provider_ = provider;
+
+  SetSubsystems(&provider->externally_managed_app_manager(),
+                &provider->registrar(), &provider->sync_bridge(),
+                &provider->ui_manager(), &provider->policy_manager());
+
+  ConnectProviderToSystemWebAppDelegateMap(&system_app_delegates_);
+}
+
+void SystemWebAppManager::ScheduleStart() {
+  CheckIsConnected();
+
+  provider_->on_registry_ready().Post(
+      FROM_HERE, base::BindOnce(&SystemWebAppManager::Start, GetWeakPtr()));
+}
+
 void SystemWebAppManager::Start() {
   const base::TimeTicks install_start_time = base::TimeTicks::Now();
 
@@ -370,6 +395,11 @@
                      should_force_install_apps, install_start_time));
 }
 
+void SystemWebAppManager::Shutdown() {
+  shutting_down_ = true;
+  StopBackgroundTasks();
+}
+
 void SystemWebAppManager::InstallSystemAppsForTesting() {
   on_apps_synchronized_ = std::make_unique<base::OneShotEvent>();
   on_tasks_started_ = std::make_unique<base::OneShotEvent>();
@@ -717,4 +747,19 @@
   return true;
 }
 
+void SystemWebAppManager::CheckIsConnected() const {
+  DCHECK(provider_) << "Attempted to access SystemWebAppManager while "
+                       "it is is not connected to WebAppProvider.";
+}
+
+void SystemWebAppManager::ConnectProviderToSystemWebAppDelegateMap(
+    const SystemWebAppDelegateMap* system_web_apps_delegate_map) const {
+  DCHECK(provider_);
+
+  provider_->manifest_update_manager().SetSystemWebAppDelegateMap(
+      system_web_apps_delegate_map);
+  provider_->policy_manager().SetSystemWebAppDelegateMap(
+      system_web_apps_delegate_map);
+}
+
 }  // namespace ash
diff --git a/chrome/browser/ash/system_web_apps/system_web_app_manager.h b/chrome/browser/ash/system_web_apps/system_web_app_manager.h
index b3849880..f83bdd8 100644
--- a/chrome/browser/ash/system_web_apps/system_web_app_manager.h
+++ b/chrome/browser/ash/system_web_apps/system_web_app_manager.h
@@ -25,6 +25,7 @@
 #include "chrome/browser/web_applications/web_app_install_info.h"
 #include "chrome/browser/web_applications/web_app_ui_manager.h"
 #include "chrome/browser/web_applications/web_app_url_loader.h"
+#include "components/keyed_service/core/keyed_service.h"
 #include "components/prefs/pref_change_registrar.h"
 #include "content/public/browser/web_contents.h"
 #include "ui/gfx/geometry/rect.h"
@@ -58,8 +59,10 @@
 
 // Installs, uninstalls, and updates System Web Apps.
 // System Web Apps are built-in, highly-privileged Web Apps for Chrome OS. They
-// have access to more APIs and are part of the Chrome OS image.
-class SystemWebAppManager : private web_app::WebAppUiManagerObserver {
+// have access to more APIs and are part of the Chrome OS image. All clients
+// should await `on_apps_synchronized()` event to start working with SWAs.
+class SystemWebAppManager : public KeyedService,
+                            public web_app::WebAppUiManagerObserver {
  public:
   // Policy for when the SystemWebAppManager will update apps/install new apps.
   enum class UpdatePolicy {
@@ -109,10 +112,15 @@
       web_app::WebAppSyncBridge* sync_bridge,
       web_app::WebAppUiManager* ui_manager,
       web_app::WebAppPolicyManager* web_app_policy_manager);
+  void ConnectSubsystems(web_app::WebAppProvider* provider);
+  void ScheduleStart();
 
   // Gets called when `WebAppProvider` is ready.
   void Start();
 
+  // KeyedService:
+  void Shutdown() override;
+
   // The SystemWebAppManager is disabled in browser tests by default because it
   // pollutes the startup state (several tests expect the Extensions state to be
   // clean).
@@ -176,8 +184,6 @@
 
   void ResetOnAppsSynchronizedForTesting();
 
-  void Shutdown();
-
   // Get the timers. Only use this for testing.
   const std::vector<std::unique_ptr<SystemWebAppBackgroundTask>>&
   GetBackgroundTasksForTesting();
@@ -225,7 +231,14 @@
       content::NavigationHandle* navigation_handle) override;
   void OnWebAppUiManagerDestroyed() override;
 
+  void CheckIsConnected() const;
+  void ConnectProviderToSystemWebAppDelegateMap(
+      const SystemWebAppDelegateMap* system_web_apps_delegate_map) const;
+
   raw_ptr<Profile> profile_;
+  // SystemWebAppManager KeyedService depends on WebAppProvider KeyedService,
+  // therefore this pointer is always valid once connected.
+  raw_ptr<web_app::WebAppProvider> provider_ = nullptr;
 
   std::unique_ptr<base::OneShotEvent> on_apps_synchronized_;
   std::unique_ptr<base::OneShotEvent> on_tasks_started_;
diff --git a/chrome/browser/ash/system_web_apps/system_web_app_manager_factory.cc b/chrome/browser/ash/system_web_apps/system_web_app_manager_factory.cc
new file mode 100644
index 0000000..278dcdb6
--- /dev/null
+++ b/chrome/browser/ash/system_web_apps/system_web_app_manager_factory.cc
@@ -0,0 +1,69 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ash/system_web_apps/system_web_app_manager_factory.h"
+
+#include "chrome/browser/ash/system_web_apps/system_web_app_manager.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/web_applications/web_app_provider.h"
+#include "chrome/browser/web_applications/web_app_provider_factory.h"
+#include "chrome/browser/web_applications/web_app_utils.h"
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+
+namespace ash {
+
+// static
+SystemWebAppManager* SystemWebAppManagerFactory::GetForProfile(
+    Profile* profile) {
+  return static_cast<SystemWebAppManager*>(
+      SystemWebAppManagerFactory::GetInstance()->GetServiceForBrowserContext(
+          profile, true /* create */));
+}
+
+// static
+SystemWebAppManagerFactory* SystemWebAppManagerFactory::GetInstance() {
+  return base::Singleton<SystemWebAppManagerFactory>::get();
+}
+
+// static
+bool SystemWebAppManagerFactory::IsServiceCreatedForProfile(Profile* profile) {
+  return SystemWebAppManagerFactory::GetInstance()->GetServiceForBrowserContext(
+             profile, /*create=*/false) != nullptr;
+}
+
+SystemWebAppManagerFactory::SystemWebAppManagerFactory()
+    : BrowserContextKeyedServiceFactory(
+          "SystemWebAppManager",
+          BrowserContextDependencyManager::GetInstance()) {
+  DependsOn(web_app::WebAppProviderFactory::GetInstance());
+}
+
+SystemWebAppManagerFactory::~SystemWebAppManagerFactory() = default;
+
+KeyedService* SystemWebAppManagerFactory::BuildServiceInstanceFor(
+    content::BrowserContext* context) const {
+  Profile* profile = Profile::FromBrowserContext(context);
+  DCHECK(web_app::WebAppProviderFactory::IsServiceCreatedForProfile(profile));
+
+  web_app::WebAppProvider* provider =
+      web_app::WebAppProvider::GetForLocalAppsUnchecked(profile);
+  DCHECK(provider);
+
+  SystemWebAppManager* swa_manager = new SystemWebAppManager(profile);
+  swa_manager->ConnectSubsystems(provider);
+  swa_manager->ScheduleStart();
+
+  return swa_manager;
+}
+
+bool SystemWebAppManagerFactory::ServiceIsCreatedWithBrowserContext() const {
+  return true;
+}
+
+content::BrowserContext* SystemWebAppManagerFactory::GetBrowserContextToUse(
+    content::BrowserContext* context) const {
+  return web_app::GetBrowserContextForWebApps(context);
+}
+
+}  //  namespace ash
diff --git a/chrome/browser/ash/system_web_apps/system_web_app_manager_factory.h b/chrome/browser/ash/system_web_apps/system_web_app_manager_factory.h
new file mode 100644
index 0000000..ea1660de
--- /dev/null
+++ b/chrome/browser/ash/system_web_apps/system_web_app_manager_factory.h
@@ -0,0 +1,55 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_ASH_SYSTEM_WEB_APPS_SYSTEM_WEB_APP_MANAGER_FACTORY_H_
+#define CHROME_BROWSER_ASH_SYSTEM_WEB_APPS_SYSTEM_WEB_APP_MANAGER_FACTORY_H_
+
+#include "base/memory/singleton.h"
+#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+
+namespace content {
+class BrowserContext;
+}
+
+class Profile;
+
+namespace ash {
+
+class SystemWebAppManager;
+
+// Singleton factory that creates all SystemWebAppManagers and associates them
+// with Profile. Clients of SystemWebAppManager shouldn't use this class to
+// obtain SystemWebAppManager instances, instead they should call
+// SystemWebAppManager static methods.
+class SystemWebAppManagerFactory : public BrowserContextKeyedServiceFactory {
+ public:
+  SystemWebAppManagerFactory(const SystemWebAppManagerFactory&) = delete;
+  SystemWebAppManagerFactory& operator=(const SystemWebAppManagerFactory&) =
+      delete;
+
+  static SystemWebAppManagerFactory* GetInstance();
+
+  static bool IsServiceCreatedForProfile(Profile* profile);
+
+ private:
+  friend struct base::DefaultSingletonTraits<SystemWebAppManagerFactory>;
+  friend class SystemWebAppManager;
+
+  SystemWebAppManagerFactory();
+  ~SystemWebAppManagerFactory() override;
+
+  // Called by SystemWebAppManager static methods.
+  static SystemWebAppManager* GetForProfile(Profile* profile);
+
+  // BrowserContextKeyedServiceFactory
+  KeyedService* BuildServiceInstanceFor(
+      content::BrowserContext* context) const override;
+  bool ServiceIsCreatedWithBrowserContext() const override;
+  content::BrowserContext* GetBrowserContextToUse(
+      content::BrowserContext* context) const override;
+};
+
+}  // namespace ash
+
+#endif  // CHROME_BROWSER_ASH_SYSTEM_WEB_APPS_SYSTEM_WEB_APP_MANAGER_FACTORY_H_
diff --git a/chrome/browser/chrome_back_forward_cache_browsertest.cc b/chrome/browser/chrome_back_forward_cache_browsertest.cc
index 96b82acb..598c0662 100644
--- a/chrome/browser/chrome_back_forward_cache_browsertest.cc
+++ b/chrome/browser/chrome_back_forward_cache_browsertest.cc
@@ -234,9 +234,8 @@
             content::RenderFrameHost::LifecycleState::kInBackForwardCache);
 }
 
-// TODO(crbug.com/1324437): Disabled for being flaky.
 IN_PROC_BROWSER_TEST_F(ChromeBackForwardCacheBrowserTest,
-                       DISABLED_PermissionContextBase) {
+                       PermissionContextBase) {
   // HTTPS needed for GEOLOCATION permission
   net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
   https_server.AddDefaultHandlers(GetChromeTestDataDir());
diff --git a/chrome/browser/chrome_content_browser_client_unittest.cc b/chrome/browser/chrome_content_browser_client_unittest.cc
index 077d33b..6ecb5f99 100644
--- a/chrome/browser/chrome_content_browser_client_unittest.cc
+++ b/chrome/browser/chrome_content_browser_client_unittest.cc
@@ -79,6 +79,8 @@
 #include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
 #include "chrome/browser/policy/networking/policy_cert_service.h"
 #include "chrome/browser/policy/networking/policy_cert_service_factory.h"
+#include "chrome/browser/web_applications/system_web_apps/test/test_system_web_app_manager.h"
+#include "chrome/browser/web_applications/web_app_provider.h"
 #include "components/user_manager/scoped_user_manager.h"
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
@@ -97,8 +99,35 @@
 using content::BrowsingDataFilterBuilder;
 using testing::_;
 using testing::NotNull;
+
 class ChromeContentBrowserClientTest : public testing::Test {
+ public:
+  ChromeContentBrowserClientTest()
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+      : test_system_web_app_manager_creator_(base::BindRepeating(
+            &ChromeContentBrowserClientTest::CreateSystemWebAppManager,
+            base::Unretained(this)))
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+  {
+  }
+
  protected:
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  std::unique_ptr<KeyedService> CreateSystemWebAppManager(Profile* profile) {
+    auto* provider = web_app::WebAppProvider::GetForLocalAppsUnchecked(profile);
+    DCHECK(provider);
+
+    // Unit tests need SWAs from production. Creates real SystemWebAppManager
+    // instead of `TestSystemWebAppManager::BuildDefault()` for
+    // `TestingProfile`.
+    auto swa_manager = std::make_unique<ash::SystemWebAppManager>(profile);
+    swa_manager->ConnectSubsystems(provider);
+    return swa_manager;
+  }
+  // The custom manager creator should be constructed before `TestingProfile`.
+  web_app::TestSystemWebAppManagerCreator test_system_web_app_manager_creator_;
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+
   content::BrowserTaskEnvironment task_environment_;
   TestingProfile profile_;
 };
@@ -573,6 +602,8 @@
 }
 
 TEST_F(ChromeContentSettingsRedirectTest, RedirectCameraAppURL) {
+  // This test needs `SystemWebAppType::CAMERA` (`CameraSystemAppDelegate`)
+  // registered in `SystemWebAppManager`.
   TestChromeContentBrowserClient test_content_browser_client;
   const GURL camera_app_url(ash::kChromeUICameraAppMainURL);
   GURL dest_url = camera_app_url;
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index 78c4f5302..a2d377d 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -588,176 +588,6 @@
   ]
 
   sources = [
-    "../ash/guest_os/guest_os_capabilities.cc",
-    "../ash/guest_os/guest_os_capabilities.h",
-    "../ash/guest_os/guest_os_diagnostics_builder.cc",
-    "../ash/guest_os/guest_os_diagnostics_builder.h",
-    "../ash/guest_os/guest_os_external_protocol_handler.cc",
-    "../ash/guest_os/guest_os_external_protocol_handler.h",
-    "../ash/guest_os/guest_os_launcher.cc",
-    "../ash/guest_os/guest_os_launcher.h",
-    "../ash/guest_os/guest_os_mime_types_service.cc",
-    "../ash/guest_os/guest_os_mime_types_service.h",
-    "../ash/guest_os/guest_os_mime_types_service_factory.cc",
-    "../ash/guest_os/guest_os_mime_types_service_factory.h",
-    "../ash/guest_os/guest_os_pref_names.cc",
-    "../ash/guest_os/guest_os_pref_names.h",
-    "../ash/guest_os/guest_os_registry_service.cc",
-    "../ash/guest_os/guest_os_registry_service.h",
-    "../ash/guest_os/guest_os_registry_service_factory.cc",
-    "../ash/guest_os/guest_os_registry_service_factory.h",
-    "../ash/guest_os/guest_os_share_path.cc",
-    "../ash/guest_os/guest_os_share_path.h",
-    "../ash/guest_os/guest_os_share_path_factory.cc",
-    "../ash/guest_os/guest_os_share_path_factory.h",
-    "../ash/guest_os/guest_os_stability_monitor.cc",
-    "../ash/guest_os/guest_os_stability_monitor.h",
-    "../ash/guest_os/infra/cached_callback.h",
-    "../ash/guest_os/public/guest_os_mount_provider.cc",
-    "../ash/guest_os/public/guest_os_mount_provider.h",
-    "../ash/guest_os/public/guest_os_mount_provider_registry.cc",
-    "../ash/guest_os/public/guest_os_mount_provider_registry.h",
-    "../ash/guest_os/public/guest_os_service.cc",
-    "../ash/guest_os/public/guest_os_service.h",
-    "../ash/guest_os/public/guest_os_service_factory.cc",
-    "../ash/guest_os/public/guest_os_service_factory.h",
-    "../ash/guest_os/public/guest_os_terminal_provider.cc",
-    "../ash/guest_os/public/guest_os_terminal_provider.h",
-    "../ash/guest_os/public/guest_os_terminal_provider_registry.cc",
-    "../ash/guest_os/public/guest_os_terminal_provider_registry.h",
-    "../ash/guest_os/public/guest_os_wayland_server.cc",
-    "../ash/guest_os/public/guest_os_wayland_server.h",
-    "../ash/guest_os/public/installer_delegate_factory.cc",
-    "../ash/guest_os/public/installer_delegate_factory.h",
-    "../ash/guest_os/public/types.h",
-    "../ash/guest_os/virtual_machines/virtual_machines_util.cc",
-    "../ash/guest_os/virtual_machines/virtual_machines_util.h",
-    "../ash/guest_os/vm_sk_forwarding_native_message_host.cc",
-    "../ash/guest_os/vm_sk_forwarding_native_message_host.h",
-    "../ash/hats/hats_config.cc",
-    "../ash/hats/hats_config.h",
-    "../ash/hats/hats_dialog.cc",
-    "../ash/hats/hats_dialog.h",
-    "../ash/hats/hats_finch_helper.cc",
-    "../ash/hats/hats_finch_helper.h",
-    "../ash/hats/hats_notification_controller.cc",
-    "../ash/hats/hats_notification_controller.h",
-    "../ash/idle_detector.cc",
-    "../ash/idle_detector.h",
-    "../ash/input_method/accessibility.cc",
-    "../ash/input_method/accessibility.h",
-    "../ash/input_method/assistive_suggester.cc",
-    "../ash/input_method/assistive_suggester.h",
-    "../ash/input_method/assistive_suggester_client_filter.cc",
-    "../ash/input_method/assistive_suggester_client_filter.h",
-    "../ash/input_method/assistive_suggester_prefs.cc",
-    "../ash/input_method/assistive_suggester_prefs.h",
-    "../ash/input_method/assistive_suggester_switch.h",
-    "../ash/input_method/assistive_window_controller.cc",
-    "../ash/input_method/assistive_window_controller.h",
-    "../ash/input_method/assistive_window_controller_delegate.h",
-    "../ash/input_method/assistive_window_properties.cc",
-    "../ash/input_method/assistive_window_properties.h",
-    "../ash/input_method/autocorrect_manager.cc",
-    "../ash/input_method/autocorrect_manager.h",
-    "../ash/input_method/candidate_window_controller.cc",
-    "../ash/input_method/candidate_window_controller.h",
-    "../ash/input_method/candidate_window_controller_impl.cc",
-    "../ash/input_method/candidate_window_controller_impl.h",
-    "../ash/input_method/component_extension_ime_manager_delegate_impl.cc",
-    "../ash/input_method/component_extension_ime_manager_delegate_impl.h",
-    "../ash/input_method/diacritics_checker.cc",
-    "../ash/input_method/diacritics_checker.h",
-    "../ash/input_method/diacritics_insensitive_string_comparator.cc",
-    "../ash/input_method/diacritics_insensitive_string_comparator.h",
-    "../ash/input_method/emoji_suggester.cc",
-    "../ash/input_method/emoji_suggester.h",
-    "../ash/input_method/get_browser_url.cc",
-    "../ash/input_method/get_browser_url.h",
-    "../ash/input_method/grammar_manager.cc",
-    "../ash/input_method/grammar_manager.h",
-    "../ash/input_method/grammar_service_client.cc",
-    "../ash/input_method/grammar_service_client.h",
-    "../ash/input_method/ime_rules_config.cc",
-    "../ash/input_method/ime_rules_config.h",
-    "../ash/input_method/ime_service_connector.cc",
-    "../ash/input_method/ime_service_connector.h",
-    "../ash/input_method/input_method_configuration.cc",
-    "../ash/input_method/input_method_configuration.h",
-    "../ash/input_method/input_method_delegate_impl.cc",
-    "../ash/input_method/input_method_delegate_impl.h",
-    "../ash/input_method/input_method_engine.cc",
-    "../ash/input_method/input_method_engine.h",
-    "../ash/input_method/input_method_engine_observer.h",
-    "../ash/input_method/input_method_manager_impl.cc",
-    "../ash/input_method/input_method_manager_impl.h",
-    "../ash/input_method/input_method_persistence.cc",
-    "../ash/input_method/input_method_persistence.h",
-    "../ash/input_method/input_method_quick_settings_helpers.cc",
-    "../ash/input_method/input_method_quick_settings_helpers.h",
-    "../ash/input_method/input_method_settings.cc",
-    "../ash/input_method/input_method_settings.h",
-    "../ash/input_method/input_method_syncer.cc",
-    "../ash/input_method/input_method_syncer.h",
-    "../ash/input_method/longpress_diacritics_suggester.cc",
-    "../ash/input_method/longpress_diacritics_suggester.h",
-    "../ash/input_method/multi_word_suggester.cc",
-    "../ash/input_method/multi_word_suggester.h",
-    "../ash/input_method/native_input_method_engine.cc",
-    "../ash/input_method/native_input_method_engine.h",
-    "../ash/input_method/native_input_method_engine_observer.cc",
-    "../ash/input_method/native_input_method_engine_observer.h",
-    "../ash/input_method/personal_info_suggester.cc",
-    "../ash/input_method/personal_info_suggester.h",
-    "../ash/input_method/suggester.h",
-    "../ash/input_method/suggestion_enums.h",
-    "../ash/input_method/suggestion_handler_interface.h",
-    "../ash/input_method/suggestions_collector.cc",
-    "../ash/input_method/suggestions_collector.h",
-    "../ash/input_method/suggestions_service_client.cc",
-    "../ash/input_method/suggestions_service_client.h",
-    "../ash/input_method/suggestions_source.h",
-    "../ash/input_method/text_field_contextual_info_fetcher.cc",
-    "../ash/input_method/text_field_contextual_info_fetcher.h",
-    "../ash/input_method/text_utils.cc",
-    "../ash/input_method/text_utils.h",
-    "../ash/input_method/ui/assistive_accessibility_view.cc",
-    "../ash/input_method/ui/assistive_accessibility_view.h",
-    "../ash/input_method/ui/assistive_delegate.h",
-    "../ash/input_method/ui/border_factory.cc",
-    "../ash/input_method/ui/border_factory.h",
-    "../ash/input_method/ui/candidate_view.cc",
-    "../ash/input_method/ui/candidate_view.h",
-    "../ash/input_method/ui/candidate_window_constants.h",
-    "../ash/input_method/ui/candidate_window_view.cc",
-    "../ash/input_method/ui/candidate_window_view.h",
-    "../ash/input_method/ui/colors.cc",
-    "../ash/input_method/ui/colors.h",
-    "../ash/input_method/ui/completion_suggestion_label_view.cc",
-    "../ash/input_method/ui/completion_suggestion_label_view.h",
-    "../ash/input_method/ui/grammar_suggestion_window.cc",
-    "../ash/input_method/ui/grammar_suggestion_window.h",
-    "../ash/input_method/ui/infolist_window.cc",
-    "../ash/input_method/ui/infolist_window.h",
-    "../ash/input_method/ui/input_method_menu_item.cc",
-    "../ash/input_method/ui/input_method_menu_item.h",
-    "../ash/input_method/ui/input_method_menu_manager.cc",
-    "../ash/input_method/ui/input_method_menu_manager.h",
-    "../ash/input_method/ui/suggestion_accessibility_label.cc",
-    "../ash/input_method/ui/suggestion_accessibility_label.h",
-    "../ash/input_method/ui/suggestion_details.h",
-    "../ash/input_method/ui/suggestion_view.cc",
-    "../ash/input_method/ui/suggestion_view.h",
-    "../ash/input_method/ui/suggestion_window_view.cc",
-    "../ash/input_method/ui/suggestion_window_view.h",
-    "../ash/input_method/ui/undo_window.cc",
-    "../ash/input_method/ui/undo_window.h",
-    "../ash/kerberos/kerberos_credentials_manager.cc",
-    "../ash/kerberos/kerberos_credentials_manager.h",
-    "../ash/kerberos/kerberos_credentials_manager_factory.cc",
-    "../ash/kerberos/kerberos_credentials_manager_factory.h",
-    "../ash/kerberos/kerberos_ticket_expiry_notification.cc",
-    "../ash/kerberos/kerberos_ticket_expiry_notification.h",
     "../ash/language_preferences.cc",
     "../ash/language_preferences.h",
     "../ash/locale_change_guard.cc",
@@ -3355,10 +3185,10 @@
     "../ash/input_method/ui/candidate_view_unittest.cc",
     "../ash/input_method/ui/candidate_window_view_unittest.cc",
     "../ash/input_method/ui/completion_suggestion_label_view_unittest.cc",
+    "../ash/input_method/ui/completion_suggestion_view_unittest.cc",
     "../ash/input_method/ui/grammar_suggestion_window_unittest.cc",
     "../ash/input_method/ui/input_method_menu_item_unittest.cc",
     "../ash/input_method/ui/input_method_menu_manager_unittest.cc",
-    "../ash/input_method/ui/suggestion_view_unittest.cc",
     "../ash/input_method/ui/suggestion_window_view_unittest.cc",
     "../ash/input_method/ui/undo_window_unittest.cc",
     "../ash/kerberos/kerberos_credentials_manager_test.cc",
diff --git a/chrome/browser/chromeos/extensions/file_manager/event_router.cc b/chrome/browser/chromeos/extensions/file_manager/event_router.cc
index f0fb5c56..319f0b3 100644
--- a/chrome/browser/chromeos/extensions/file_manager/event_router.cc
+++ b/chrome/browser/chromeos/extensions/file_manager/event_router.cc
@@ -1238,94 +1238,173 @@
 }
 
 void EventRouter::OnIOTaskStatus(const io_task::ProgressStatus& status) {
-  // If any Files app window exists we send the progress to all of them.
-  if (DoFilesSwaWindowsExist(profile_)) {
-    file_manager_private::ProgressStatus event_status;
-    event_status.task_id = status.task_id;
-    event_status.type = GetIOTaskType(status.type);
-    event_status.state = GetIOTaskState(status.state);
-
-    // Speedometer can produce infinite result which can't be serialized to JSON
-    // when sending the status via private API.
-    if (std::isfinite(status.remaining_seconds)) {
-      event_status.remaining_seconds = status.remaining_seconds;
-    }
-
-    if (status.destination_folder.is_valid()) {
-      event_status.destination_name =
-          util::GetDisplayablePath(profile_, status.destination_folder)
-              .value_or(base::FilePath())
-              .BaseName()
-              .value();
-    }
-
-    size_t processed = 0;
-    for (const auto& file_status : status.outputs) {
-      if (file_status.error)
-        processed++;
-    }
-    event_status.num_remaining_items = status.sources.size() - processed;
-    event_status.item_count = status.sources.size();
-
-    // Get the last error occurrence in the `sources`.
-    for (const io_task::EntryStatus& source : base::Reversed(status.sources)) {
-      if (source.error && source.error.value() != base::File::FILE_OK) {
-        event_status.error_name = FileErrorToErrorName(source.error.value());
-      }
-    }
-    // If we have no error on 'sources', check if an error came from 'outputs'.
-    if (status.state == io_task::State::kError &&
-        event_status.error_name.empty()) {
-      for (const io_task::EntryStatus& dest : base::Reversed(status.outputs)) {
-        if (dest.error && dest.error.value() != base::File::FILE_OK) {
-          event_status.error_name = FileErrorToErrorName(dest.error.value());
-        }
-      }
-    }
-
-    if (status.sources.size() > 0) {
-      event_status.source_name =
-          util::GetDisplayablePath(profile_, status.sources.front().url)
-              .value_or(base::FilePath())
-              .BaseName()
-              .value();
-    }
-    event_status.bytes_transferred = status.bytes_transferred;
-    event_status.total_bytes = status.total_bytes;
-
-    BroadcastEvent(
-        profile_,
-        extensions::events::FILE_MANAGER_PRIVATE_ON_IO_TASK_PROGRESS_STATUS,
-        file_manager_private::OnIOTaskProgressStatus::kEventName,
-        file_manager_private::OnIOTaskProgressStatus::Create(event_status));
-
-    // Send file watch notifications on I/O task completion. inotify is flaky on
-    // some filesystems, so send these notifications so that at least operations
-    // made from Files App are always reflected in the UI.
-    if (status.IsCompleted()) {
-      std::set<base::FilePath> updated_paths;
-      if (status.destination_folder.is_valid()) {
-        updated_paths.insert(status.destination_folder.path());
-      }
-      for (const auto& source : status.sources) {
-        updated_paths.insert(source.url.path().DirName());
-      }
-      for (const auto& output : status.outputs) {
-        updated_paths.insert(output.url.path().DirName());
-      }
-
-      for (const auto& path : updated_paths) {
-        HandleFileWatchNotification(path, false);
-      }
-    }
-
-    // Send the progress report to the system notification regardless of whether
-    // Files app window exists as we may need to remove an existing
-    // notification.
+  // Send the progress report to the system notification regardless of whether
+  // Files app window exists as we may need to remove an existing
+  // notification.
+  notification_manager_->HandleIOTaskProgress(status);
+  if (!DoFilesSwaWindowsExist(profile_) && !force_broadcasting_for_testing_) {
+    return;
   }
 
-  notification_manager_->HandleIOTaskProgress(status);
+  // Send file watch notifications on I/O task completion. inotify is flaky on
+  // some filesystems, so send these notifications so that at least operations
+  // made from Files App are always reflected in the UI.
+  if (status.IsCompleted()) {
+    std::set<base::FilePath> updated_paths;
+    if (status.destination_folder.is_valid()) {
+      updated_paths.insert(status.destination_folder.path());
+    }
+    for (const auto& source : status.sources) {
+      updated_paths.insert(source.url.path().DirName());
+    }
+    for (const auto& output : status.outputs) {
+      updated_paths.insert(output.url.path().DirName());
+    }
+
+    for (const auto& path : updated_paths) {
+      HandleFileWatchNotification(path, false);
+    }
+  }
+
+  // If any Files app window exists we send the progress to all of them.
+  file_manager_private::ProgressStatus event_status;
+  event_status.task_id = status.task_id;
+  event_status.type = GetIOTaskType(status.type);
+  event_status.state = GetIOTaskState(status.state);
+
+  // Speedometer can produce infinite result which can't be serialized to JSON
+  // when sending the status via private API.
+  if (std::isfinite(status.remaining_seconds)) {
+    event_status.remaining_seconds = status.remaining_seconds;
+  }
+
+  if (status.destination_folder.is_valid()) {
+    event_status.destination_name =
+        util::GetDisplayablePath(profile_, status.destination_folder)
+            .value_or(base::FilePath())
+            .BaseName()
+            .value();
+  }
+
+  size_t processed = 0;
+  std::vector<storage::FileSystemURL> outputs;
+  for (const auto& file_status : status.outputs) {
+    if (file_status.error) {
+      if (status.type == file_manager::io_task::OperationType::kTrash &&
+          file_status.error.value() == base::File::FILE_OK) {
+        // These entries are currently used to undo a TrashIOTask so only
+        // consider the successfully trashed files.
+        outputs.push_back(file_status.url);
+      }
+      processed++;
+    }
+  }
+
+  event_status.num_remaining_items = status.sources.size() - processed;
+  event_status.item_count = status.sources.size();
+
+  // Get the last error occurrence in the `sources`.
+  for (const io_task::EntryStatus& source : base::Reversed(status.sources)) {
+    if (source.error && source.error.value() != base::File::FILE_OK) {
+      event_status.error_name = FileErrorToErrorName(source.error.value());
+    }
+  }
+  // If we have no error on 'sources', check if an error came from 'outputs'.
+  if (status.state == io_task::State::kError &&
+      event_status.error_name.empty()) {
+    for (const io_task::EntryStatus& dest : base::Reversed(status.outputs)) {
+      if (dest.error && dest.error.value() != base::File::FILE_OK) {
+        event_status.error_name = FileErrorToErrorName(dest.error.value());
+      }
+    }
+  }
+
+  if (status.sources.size() > 0) {
+    event_status.source_name =
+        util::GetDisplayablePath(profile_, status.sources.front().url)
+            .value_or(base::FilePath())
+            .BaseName()
+            .value();
+  }
+  event_status.bytes_transferred = status.bytes_transferred;
+  event_status.total_bytes = status.total_bytes;
+
+  // The TrashIOTask is the only IOTask that uses the output Entry's, so don't
+  // try to resolve the outputs for all other IOTasks.
+  if (GetIOTaskType(status.type) != file_manager_private::IO_TASK_TYPE_TRASH ||
+      outputs.size() == 0) {
+    BroadcastIOTask(std::move(event_status));
+    return;
+  }
+
+  // All FileSystemURLs in the output come from the same FileSystemContext, so
+  // use the first URL to obtain the context.
+  auto* file_system_context = util::GetFileSystemContextForSourceURL(
+      profile_, outputs[0].origin().GetURL());
+  if (file_system_context == nullptr) {
+    LOG(ERROR) << "Could not find file system context";
+    BroadcastIOTask(std::move(event_status));
+    return;
+  }
+
+  file_manager::util::FileDefinitionList file_definition_list;
+  for (const auto& url : outputs) {
+    file_manager::util::FileDefinition file_definition;
+    if (file_manager::util::ConvertAbsoluteFilePathToRelativeFileSystemPath(
+            profile_, url.origin().GetURL(), url.path(),
+            &file_definition.virtual_path)) {
+      file_definition_list.push_back(std::move(file_definition));
+    }
+  }
+
+  file_manager::util::ConvertFileDefinitionListToEntryDefinitionList(
+      file_system_context, outputs[0].origin(), std::move(file_definition_list),
+      base::BindOnce(
+          &EventRouter::OnConvertFileDefinitionListToEntryDefinitionList,
+          weak_factory_.GetWeakPtr(), std::move(event_status)));
 }
+
+void EventRouter::OnConvertFileDefinitionListToEntryDefinitionList(
+    file_manager_private::ProgressStatus event_status,
+    std::unique_ptr<file_manager::util::EntryDefinitionList>
+        entry_definition_list) {
+  if (entry_definition_list == nullptr) {
+    BroadcastIOTask(std::move(event_status));
+    return;
+  }
+  std::vector<OutputsType> outputs;
+  for (const auto& def : *entry_definition_list) {
+    if (def.error != base::File::FILE_OK) {
+      LOG(WARNING) << "File entry ignored: " << static_cast<int>(def.error);
+      continue;
+    }
+    OutputsType output_entry;
+    output_entry.additional_properties.SetStringKey("fileSystemName",
+                                                    def.file_system_name);
+    output_entry.additional_properties.SetStringKey("fileSystemRoot",
+                                                    def.file_system_root_url);
+    // The `full_path` comes back as relative to the file system root, but the
+    // UI requires it as an absolute path.
+    output_entry.additional_properties.SetStringKey(
+        "fileFullPath", base::FilePath("/").Append(def.full_path).value());
+    output_entry.additional_properties.SetBoolKey("fileIsDirectory",
+                                                  def.is_directory);
+    outputs.push_back(std::move(output_entry));
+  }
+  event_status.outputs =
+      std::make_unique<std::vector<OutputsType>>(std::move(outputs));
+  BroadcastIOTask(std::move(event_status));
+}
+
+void EventRouter::BroadcastIOTask(
+    const file_manager_private::ProgressStatus& event_status) {
+  BroadcastEvent(
+      profile_,
+      extensions::events::FILE_MANAGER_PRIVATE_ON_IO_TASK_PROGRESS_STATUS,
+      file_manager_private::OnIOTaskProgressStatus::kEventName,
+      file_manager_private::OnIOTaskProgressStatus::Create(event_status));
+}
+
 void EventRouter::OnRegistered(guest_os::GuestOsMountProviderRegistry::Id id,
                                guest_os::GuestOsMountProvider* provider) {
   OnMountableGuestsChanged();
diff --git a/chrome/browser/chromeos/extensions/file_manager/event_router.h b/chrome/browser/chromeos/extensions/file_manager/event_router.h
index ecf43666..5bc8dfd 100644
--- a/chrome/browser/chromeos/extensions/file_manager/event_router.h
+++ b/chrome/browser/chromeos/extensions/file_manager/event_router.h
@@ -45,6 +45,8 @@
 class PrefChangeRegistrar;
 class Profile;
 
+using OutputsType =
+    extensions::api::file_manager_private::ProgressStatus::OutputsType;
 using file_manager::util::EntryDefinition;
 
 namespace file_manager {
@@ -206,6 +208,12 @@
                     guest_os::GuestOsMountProvider* provider) override;
   void OnUnregistered(guest_os::GuestOsMountProviderRegistry::Id id) override;
 
+  // Use this method for unit tests to bypass checking if there are any SWA
+  // windows.
+  void ForceBroadcastingForTesting(bool enabled) {
+    force_broadcasting_for_testing_ = enabled;
+  }
+
  private:
   FRIEND_TEST_ALL_PREFIXES(EventRouterTest, PopulateCrostiniEvent);
 
@@ -276,6 +284,17 @@
   // Called to refresh the list of guests and broadcast it.
   void OnMountableGuestsChanged();
 
+  // After resolving all file definitions, ensure they are available on the
+  // `event_status`.
+  void OnConvertFileDefinitionListToEntryDefinitionList(
+      file_manager_private::ProgressStatus event_status,
+      std::unique_ptr<file_manager::util::EntryDefinitionList>
+          entry_definition_list);
+
+  // Broadcast the `event_status` to all open SWA windows.
+  void BroadcastIOTask(
+      const file_manager_private::ProgressStatus& event_status);
+
   base::Time last_copy_progress_event_;
 
   std::map<base::FilePath, std::unique_ptr<FileWatcher>> file_watchers_;
@@ -291,6 +310,9 @@
   DispatchDirectoryChangeEventImplCallback
       dispatch_directory_change_event_impl_;
 
+  // Set this to true to ignore the DoFilesSwaWindowsExist check for testing.
+  bool force_broadcasting_for_testing_ = false;
+
   // Note: This should remain the last member so it'll be destroyed and
   // invalidate the weak pointers before any other members are destroyed.
   base::WeakPtrFactory<EventRouter> weak_factory_{this};
diff --git a/chrome/browser/chromeos/extensions/file_manager/event_router_unittest.cc b/chrome/browser/chromeos/extensions/file_manager/event_router_unittest.cc
index 83aa0587..83105c8 100644
--- a/chrome/browser/chromeos/extensions/file_manager/event_router_unittest.cc
+++ b/chrome/browser/chromeos/extensions/file_manager/event_router_unittest.cc
@@ -3,9 +3,32 @@
 // found in the LICENSE file.
 
 #include "chrome/browser/chromeos/extensions/file_manager/event_router.h"
+
+#include <memory>
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/rand_util.h"
+#include "base/run_loop.h"
+#include "base/test/bind.h"
+#include "base/test/gmock_callback_support.h"
 #include "base/values.h"
+#include "chrome/browser/ash/file_manager/fake_disk_mount_manager.h"
+#include "chrome/browser/ash/file_manager/io_task.h"
+#include "chrome/browser/ash/file_manager/path_util.h"
+#include "chrome/browser/ash/file_manager/volume_manager_factory.h"
+#include "chrome/browser/chromeos/extensions/file_manager/event_router_factory.h"
+#include "chrome/test/base/testing_profile.h"
+#include "content/public/test/browser_task_environment.h"
+#include "extensions/browser/event_router.h"
+#include "extensions/browser/test_event_router.h"
 #include "extensions/common/extension.h"
+#include "storage/browser/file_system/external_mount_points.h"
+#include "storage/browser/test/test_file_system_context.h"
+#include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/common/storage_key/storage_key.h"
 #include "url/gurl.h"
 #include "url/origin.h"
 
@@ -54,4 +77,157 @@
   EXPECT_EQ(swa_event.entries[0].additional_properties, swa_props);
 }
 
+namespace {
+
+using ::base::test::RunClosure;
+using ::testing::_;
+using ::testing::AllOf;
+using ::testing::Field;
+
+// Observes the `BroadcastEvent` operation that is emitted by the event router.
+// The mock methods are used to assert expectations on the return results.
+class TestEventRouterObserver
+    : public extensions::TestEventRouter::EventObserver {
+ public:
+  explicit TestEventRouterObserver(extensions::TestEventRouter* event_router)
+      : event_router_(event_router) {
+    event_router_->AddEventObserver(this);
+  }
+  ~TestEventRouterObserver() override {
+    event_router_->RemoveEventObserver(this);
+  }
+  TestEventRouterObserver(const TestEventRouterObserver&) = delete;
+  TestEventRouterObserver& operator=(const TestEventRouterObserver&) = delete;
+
+  // TestEventRouter::EventObserver:
+  MOCK_METHOD(void, OnBroadcastEvent, (const extensions::Event&), (override));
+  MOCK_METHOD(void,
+              OnDispatchEventToExtension,
+              (const std::string&, const extensions::Event&),
+              (override));
+
+ private:
+  raw_ptr<extensions::TestEventRouter> event_router_;
+};
+
+class FileManagerEventRouterTest : public testing::Test {
+ public:
+  FileManagerEventRouterTest() = default;
+  FileManagerEventRouterTest(const FileManagerEventRouterTest&) = delete;
+  FileManagerEventRouterTest& operator=(const FileManagerEventRouterTest&) =
+      delete;
+
+  void SetUp() override {
+    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+
+    profile_ =
+        std::make_unique<TestingProfile>(base::FilePath(temp_dir_.GetPath()));
+    file_system_context_ = storage::CreateFileSystemContextForTesting(
+        nullptr, temp_dir_.GetPath());
+
+    VolumeManagerFactory::GetInstance()->SetTestingFactory(
+        profile_.get(),
+        base::BindLambdaForTesting([this](content::BrowserContext* context) {
+          return std::unique_ptr<KeyedService>(std::make_unique<VolumeManager>(
+              Profile::FromBrowserContext(context), nullptr, nullptr,
+              &disk_mount_manager_, nullptr,
+              VolumeManager::GetMtpStorageInfoCallback()));
+        }));
+
+    storage::ExternalMountPoints::GetSystemInstance()->RegisterFileSystem(
+        file_manager::util::GetDownloadsMountPointName(profile_.get()),
+        storage::kFileSystemTypeLocal, storage::FileSystemMountOption(),
+        temp_dir_.GetPath());
+
+    file_manager::util::GetFileSystemContextForSourceURL(
+        profile_.get(), GURL("chrome-extension://abc"))
+        ->external_backend()
+        ->GrantFileAccessToOrigin(
+            url::Origin::Create(GURL("chrome-extension://abc")),
+            base::FilePath(file_manager::util::GetDownloadsMountPointName(
+                profile_.get())));
+  }
+
+  const io_task::EntryStatus CreateSuccessfulEntryStatusForFileName(
+      const std::string& file_name) {
+    const base::FilePath file_path = temp_dir_.GetPath().Append(file_name);
+    EXPECT_TRUE(base::WriteFile(file_path, base::RandBytesAsString(32)));
+
+    storage::FileSystemURL url =
+        file_system_context_->CreateCrackedFileSystemURL(
+            kTestStorageKey, storage::kFileSystemTypeTest, file_path);
+
+    return io_task::EntryStatus(std::move(url), base::File::FILE_OK);
+  }
+
+  content::BrowserTaskEnvironment task_environment_;
+  base::ScopedTempDir temp_dir_;
+  std::unique_ptr<TestingProfile> profile_;
+  const blink::StorageKey kTestStorageKey =
+      blink::StorageKey::CreateFromStringForTesting("chrome-extension://abc");
+  scoped_refptr<storage::FileSystemContext> file_system_context_;
+  file_manager::FakeDiskMountManager disk_mount_manager_;
+};
+
+// A matcher that matches an `extensions::Event::event_args` and attempts to
+// extract the "outputs" key. It then looks at the output at `index` and matches
+// the `field` against the `expected_value`.
+MATCHER_P3(ExpectEventArgString, index, field, expected_value, "") {
+  EXPECT_EQ((*arg).type(), base::Value::Type::LIST)
+      << "Supplied value must be of type LIST";
+  EXPECT_GE((*arg).GetList().size(), 1);
+  base::Value* outputs =
+      (*arg).GetList()[0].FindKeyOfType("outputs", base::Value::Type::LIST);
+  EXPECT_TRUE(outputs) << "The outputs field is not available on the event";
+  EXPECT_GT((*outputs).GetList().size(), index)
+      << "The supplied index on outputs is not available, size: "
+      << (*outputs).GetList().size() << ", index: " << index;
+  std::string* actual_value = (*outputs).GetList()[index].FindStringKey(field);
+  EXPECT_TRUE(actual_value) << "Could not find the string with key: " << field;
+  return testing::ExplainMatchResult(expected_value, *actual_value,
+                                     result_listener);
+}
+
+TEST_F(FileManagerEventRouterTest, OnIOTaskStatusForTrash) {
+  // Setup event routers.
+  extensions::TestEventRouter* test_event_router =
+      extensions::CreateAndUseTestEventRouter(profile_.get());
+  TestEventRouterObserver observer(test_event_router);
+  auto event_router = std::make_unique<EventRouter>(profile_.get());
+  event_router->ForceBroadcastingForTesting(true);
+
+  io_task::EntryStatus source_entry =
+      CreateSuccessfulEntryStatusForFileName("foo.txt");
+  io_task::EntryStatus output_entry =
+      CreateSuccessfulEntryStatusForFileName("bar.txt");
+
+  std::vector<io_task::EntryStatus> source_entries;
+  source_entries.push_back(std::move(source_entry));
+  std::vector<io_task::EntryStatus> output_entries;
+  output_entries.push_back(std::move(output_entry));
+
+  // Setup the ProgressStatus event that expects
+  file_manager::io_task::ProgressStatus status;
+  status.type = file_manager::io_task::OperationType::kTrash;
+  status.state = file_manager::io_task::State::kSuccess;
+  status.sources = std::move(source_entries);
+  status.outputs = std::move(output_entries);
+
+  base::RunLoop run_loop;
+  EXPECT_CALL(
+      observer,
+      OnBroadcastEvent(Field(
+          &extensions::Event::event_args,
+          AllOf(ExpectEventArgString(0, "fileFullPath", "/bar.txt"),
+                ExpectEventArgString(0, "fileSystemName", "Downloads"),
+                ExpectEventArgString(
+                    0, "fileSystemRoot",
+                    "filesystem:chrome-extension://abc/external/Downloads/")))))
+      .WillOnce(RunClosure(run_loop.QuitClosure()));
+
+  event_router->OnIOTaskStatus(status);
+  run_loop.Run();
+}
+
+}  // namespace
 }  // namespace file_manager
diff --git a/chrome/browser/chromeos/extensions/file_manager/private_api_file_system.cc b/chrome/browser/chromeos/extensions/file_manager/private_api_file_system.cc
index c707d57..2197db6 100644
--- a/chrome/browser/chromeos/extensions/file_manager/private_api_file_system.cc
+++ b/chrome/browser/chromeos/extensions/file_manager/private_api_file_system.cc
@@ -41,6 +41,7 @@
 #include "chrome/browser/ash/file_manager/fileapi_util.h"
 #include "chrome/browser/ash/file_manager/io_task.h"
 #include "chrome/browser/ash/file_manager/path_util.h"
+#include "chrome/browser/ash/file_manager/trash_io_task.h"
 #include "chrome/browser/ash/file_manager/volume_manager.h"
 #include "chrome/browser/ash/file_manager/zip_io_task.h"
 #include "chrome/browser/ash/policy/dlp/dlp_files_controller.h"
@@ -995,14 +996,14 @@
           *entry_definition_list))));
 }
 
-FileManagerPrivateInternalGetFilesRestrictedByDlpFunction::
-    FileManagerPrivateInternalGetFilesRestrictedByDlpFunction() = default;
+FileManagerPrivateInternalGetDlpMetadataFunction::
+    FileManagerPrivateInternalGetDlpMetadataFunction() = default;
 
-FileManagerPrivateInternalGetFilesRestrictedByDlpFunction::
-    ~FileManagerPrivateInternalGetFilesRestrictedByDlpFunction() = default;
+FileManagerPrivateInternalGetDlpMetadataFunction::
+    ~FileManagerPrivateInternalGetDlpMetadataFunction() = default;
 
 ExtensionFunction::ResponseAction
-FileManagerPrivateInternalGetFilesRestrictedByDlpFunction::Run() {
+FileManagerPrivateInternalGetDlpMetadataFunction::Run() {
   if (!base::FeatureList::IsEnabled(
           features::kDataLeakPreventionFilesRestriction)) {
     return RespondNow(OneArgument(base::Value(base::Value::Type::LIST)));
@@ -1014,8 +1015,7 @@
     return RespondNow(OneArgument(base::Value(base::Value::Type::LIST)));
   }
 
-  using extensions::api::file_manager_private_internal::
-      GetFilesRestrictedByDlp::Params;
+  using extensions::api::file_manager_private_internal::GetDlpMetadata::Params;
   const std::unique_ptr<Params> params(Params::Create(args()));
   EXTENSION_FUNCTION_VALIDATE(params);
 
@@ -1033,48 +1033,29 @@
   }
 
   files_controller_ = std::make_unique<policy::DlpFilesController>();
-  files_controller_->GetFilesRestrictedByAnyRule(
+  files_controller_->GetDlpMetadata(
       source_urls_,
       base::BindOnce(
-          &FileManagerPrivateInternalGetFilesRestrictedByDlpFunction::
-              OnGetFilesRestrictedByDlp,
+          &FileManagerPrivateInternalGetDlpMetadataFunction::OnGetDlpMetadata,
           this));
 
   return RespondLater();
 }
 
-void FileManagerPrivateInternalGetFilesRestrictedByDlpFunction::
-    OnGetFilesRestrictedByDlp(
-        std::vector<storage::FileSystemURL> restricted_files) {
-  file_manager::util::FileDefinitionList file_definition_list;
-  for (const auto& file : restricted_files) {
-    file_manager::util::FileDefinition file_definition;
-    file_definition.is_directory = false;
-    file_definition.virtual_path = file.virtual_path();
-    file_definition.absolute_path = file.path();
-    file_definition_list.emplace_back(std::move(file_definition));
+void FileManagerPrivateInternalGetDlpMetadataFunction::OnGetDlpMetadata(
+    std::vector<policy::DlpFilesController::DlpFileMetadata>
+        dlp_metadata_list) {
+  using extensions::api::file_manager_private::DlpMetadata;
+  std::vector<DlpMetadata> converted_list;
+  for (const auto& md : dlp_metadata_list) {
+    DlpMetadata metadata;
+    metadata.is_dlp_restricted = md.is_dlp_restricted;
+    metadata.source_url = md.source_url;
+    converted_list.emplace_back(std::move(metadata));
   }
-
-  file_manager::util::ConvertFileDefinitionListToEntryDefinitionList(
-      file_manager::util::GetFileSystemContextForSourceURL(
-          Profile::FromBrowserContext(browser_context()), source_url()),
-      url::Origin::Create(source_url().DeprecatedGetOriginAsURL()),
-      file_definition_list,  // Safe, since copied internally.
-      base::BindOnce(
-          &FileManagerPrivateInternalGetFilesRestrictedByDlpFunction::
-              OnConvertFileDefinitionListToEntryDefinitionList,
-          this));
-}
-
-void FileManagerPrivateInternalGetFilesRestrictedByDlpFunction::
-    OnConvertFileDefinitionListToEntryDefinitionList(
-        std::unique_ptr<file_manager::util::EntryDefinitionList>
-            entry_definition_list) {
-  DCHECK(entry_definition_list);
-
-  Respond(OneArgument(base::Value::FromUniquePtrValue(
-      file_manager::util::ConvertEntryDefinitionListToListValue(
-          *entry_definition_list))));
+  Respond(ArgumentList(
+      api::file_manager_private_internal::GetDlpMetadata::Results::Create(
+          converted_list)));
 }
 
 FileManagerPrivateInternalStartCopyFunction::
@@ -1655,6 +1636,15 @@
       task = std::make_unique<file_manager::io_task::DeleteIOTask>(
           std::move(source_urls), file_system_context);
       break;
+    case file_manager::io_task::OperationType::kTrash:
+      if (base::FeatureList::IsEnabled(chromeos::features::kFilesTrash)) {
+        task = std::make_unique<file_manager::io_task::TrashIOTask>(
+            std::move(source_urls), profile, file_system_context,
+            /*base_path=*/base::FilePath());
+        break;
+      } else {
+        return RespondNow(Error("Invalid operation type"));
+      }
     case file_manager::io_task::OperationType::kExtract:
       if (base::FeatureList::IsEnabled(
               chromeos::features::kFilesExtractArchive)) {
diff --git a/chrome/browser/chromeos/extensions/file_manager/private_api_file_system.h b/chrome/browser/chromeos/extensions/file_manager/private_api_file_system.h
index 8a0a4d5..4149d20 100644
--- a/chrome/browser/chromeos/extensions/file_manager/private_api_file_system.h
+++ b/chrome/browser/chromeos/extensions/file_manager/private_api_file_system.h
@@ -299,28 +299,24 @@
   storage::FileSystemURL destination_url_;
 };
 
-// Implements the chrome.fileManagerPrivate.getFilesRestrictedByDlp method.
-class FileManagerPrivateInternalGetFilesRestrictedByDlpFunction
+// Implements the chrome.fileManagerPrivateInternal.getDlpMetadata method.
+class FileManagerPrivateInternalGetDlpMetadataFunction
     : public LoggedExtensionFunction {
  public:
-  FileManagerPrivateInternalGetFilesRestrictedByDlpFunction();
+  FileManagerPrivateInternalGetDlpMetadataFunction();
 
-  DECLARE_EXTENSION_FUNCTION(
-      "fileManagerPrivateInternal.getFilesRestrictedByDlp",
-      FILEMANAGERPRIVATEINTERNAL_GETFILESRESTRICTEDBYDLP)
+  DECLARE_EXTENSION_FUNCTION("fileManagerPrivateInternal.getDlpMetadata",
+                             FILEMANAGERPRIVATEINTERNAL_GETDLPMETADATA)
 
  protected:
-  ~FileManagerPrivateInternalGetFilesRestrictedByDlpFunction() override;
+  ~FileManagerPrivateInternalGetDlpMetadataFunction() override;
 
   // ExtensionFunction overrides.
   ResponseAction Run() override;
 
  private:
-  void OnGetFilesRestrictedByDlp(
-      std::vector<storage::FileSystemURL> restricted_files);
-  void OnConvertFileDefinitionListToEntryDefinitionList(
-      std::unique_ptr<file_manager::util::EntryDefinitionList>
-          entry_definition_list);
+  void OnGetDlpMetadata(
+      std::vector<policy::DlpFilesController::DlpFileMetadata> dlp_metadata);
 
   std::unique_ptr<policy::DlpFilesController> files_controller_;
   std::vector<storage::FileSystemURL> source_urls_;
diff --git a/chrome/browser/chromeos/extensions/install_limiter.h b/chrome/browser/chromeos/extensions/install_limiter.h
index 2328951e..457140a 100644
--- a/chrome/browser/chromeos/extensions/install_limiter.h
+++ b/chrome/browser/chromeos/extensions/install_limiter.h
@@ -20,11 +20,6 @@
 
 namespace extensions {
 
-// TODO(hendrich, https://crbug.com/1046302)
-// Add a test for the InstallLimiter, which checks that small extensions are
-// installed before large extensions and that we don't have to wait the entire
-// 5s when the OnAllExternalProvidersReady() signal was called.
-
 // InstallLimiter defers big app installs after all small app installs and then
 // runs big app installs one by one. This improves first-time login experience.
 // See http://crbug.com/166296
diff --git a/chrome/browser/chromeos/extensions/install_limiter_unittest.cc b/chrome/browser/chromeos/extensions/install_limiter_unittest.cc
index 6929dfdc..421be2de 100644
--- a/chrome/browser/chromeos/extensions/install_limiter_unittest.cc
+++ b/chrome/browser/chromeos/extensions/install_limiter_unittest.cc
@@ -5,34 +5,59 @@
 #include "chrome/browser/chromeos/extensions/install_limiter.h"
 
 #include "ash/components/tpm/stub_install_attributes.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/test/task_environment.h"
+#include "base/time/time.h"
 #include "chrome/browser/ash/login/demo_mode/demo_mode_test_helper.h"
 #include "chrome/browser/ash/login/demo_mode/demo_session.h"
 #include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
+#include "chrome/browser/chrome_notification_types.h"
+#include "chrome/browser/extensions/crx_installer.h"
+#include "chrome/browser/extensions/extension_service.h"
+#include "chrome/browser/extensions/extension_service_test_base.h"
 #include "components/user_manager/scoped_user_manager.h"
+#include "content/public/browser/notification_service.h"
 #include "content/public/test/browser_task_environment.h"
+#include "extensions/browser/crx_file_info.h"
 #include "extensions/common/constants.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/verifier_formats.h"
+#include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 using extensions::InstallLimiter;
+using testing::Field;
+using testing::Invoke;
+using testing::Mock;
 
 namespace {
 
 constexpr char kRandomExtensionId[] = "abacabadabacabaeabacabadabacabaf";
+
 constexpr int kLargeExtensionSize = 2000000;
 constexpr int kSmallExtensionSize = 200000;
 
+constexpr char kLargeExtensionCrx[] = "large.crx";
+constexpr char kSmallExtensionCrx[] = "small.crx";
+
+constexpr base::TimeDelta kLessThanExpectedWaitTime = base::Seconds(4);
+constexpr base::TimeDelta kTimeDeltaUntilExpectedWaitTime = base::Seconds(1);
+
 }  // namespace
 
-class InstallLimiterTest
+class InstallLimiterShouldDeferInstallTest
     : public testing::TestWithParam<ash::DemoSession::DemoModeConfig> {
  public:
-  InstallLimiterTest()
+  InstallLimiterShouldDeferInstallTest()
       : scoped_user_manager_(std::make_unique<ash::FakeChromeUserManager>()) {}
 
-  InstallLimiterTest(const InstallLimiterTest&) = delete;
-  InstallLimiterTest& operator=(const InstallLimiterTest&) = delete;
+  InstallLimiterShouldDeferInstallTest(
+      const InstallLimiterShouldDeferInstallTest&) = delete;
+  InstallLimiterShouldDeferInstallTest& operator=(
+      const InstallLimiterShouldDeferInstallTest&) = delete;
 
-  ~InstallLimiterTest() override = default;
+  ~InstallLimiterShouldDeferInstallTest() override = default;
 
  private:
   content::BrowserTaskEnvironment task_environment_;
@@ -40,7 +65,7 @@
   user_manager::ScopedUserManager scoped_user_manager_;
 };
 
-TEST_P(InstallLimiterTest, ShouldDeferInstall) {
+TEST_P(InstallLimiterShouldDeferInstallTest, ShouldDeferInstall) {
   const std::vector<std::string> screensaver_ids = {
       extension_misc::kScreensaverAppId, extension_misc::kScreensaverAtlasAppId,
       extension_misc::kScreensaverKraneZdksAppId};
@@ -66,6 +91,179 @@
 
 INSTANTIATE_TEST_SUITE_P(
     DemoModeConfig,
-    InstallLimiterTest,
+    InstallLimiterShouldDeferInstallTest,
     ::testing::Values(ash::DemoSession::DemoModeConfig::kNone,
                       ash::DemoSession::DemoModeConfig::kOnline));
+
+namespace extensions {
+
+// A mock around CrxInstaller to track extension installations.
+class MockCrxInstaller : public CrxInstaller {
+ public:
+  explicit MockCrxInstaller(ExtensionService* frontend)
+      : CrxInstaller(frontend->AsWeakPtr(), nullptr, nullptr) {}
+
+  MOCK_METHOD(void, InstallCrxFile, (const CRXFileInfo& source_file));
+
+ private:
+  ~MockCrxInstaller() override = default;
+};
+
+}  // namespace extensions
+
+class InstallLimiterTest : public extensions::ExtensionServiceTestBase {
+ public:
+  InstallLimiterTest()
+      : extensions::ExtensionServiceTestBase(
+            std::make_unique<content::BrowserTaskEnvironment>(
+                base::test::TaskEnvironment::MainThreadType::IO,
+                content::BrowserTaskEnvironment::TimeSource::MOCK_TIME)) {}
+
+  InstallLimiterTest(const InstallLimiterTest&) = delete;
+  InstallLimiterTest& operator=(const InstallLimiterTest&) = delete;
+
+  ~InstallLimiterTest() override = default;
+
+  void NotifyCrxInstallerDone() {
+    content::NotificationService::current()->Notify(
+        extensions::NOTIFICATION_CRX_INSTALLER_DONE,
+        content::Source<extensions::MockCrxInstaller>(mock_installer_.get()),
+        content::Details<const extensions::Extension>(NULL));
+  }
+
+ protected:
+  void SetUp() override {
+    extensions::ExtensionServiceTestBase::SetUp();
+
+    extensions::ExtensionServiceTestBase::ExtensionServiceInitParams params =
+        CreateDefaultInitParams();
+    params.enable_install_limiter = true;
+    InitializeExtensionService(params);
+
+    install_limiter_ = InstallLimiter::Get(profile());
+
+    mock_installer_ =
+        base::MakeRefCounted<extensions::MockCrxInstaller>(service());
+  }
+
+  extensions::CRXFileInfo CreateTestExtensionCrx(const base::FilePath& path,
+                                                 int extension_size) {
+    const std::string data(extension_size, 0);
+    EXPECT_TRUE(base::WriteFile(path, data));
+    extensions::CRXFileInfo crx_info(path, extensions::GetTestVerifierFormat());
+    crx_info.extension_id = kRandomExtensionId;
+    return crx_info;
+  }
+
+  InstallLimiter* install_limiter_;
+  scoped_refptr<extensions::MockCrxInstaller> mock_installer_;
+};
+
+// Test that small extensions are installed immediately.
+TEST_F(InstallLimiterTest, DontDeferSmallExtensionInstallation) {
+  const base::FilePath path =
+      extensions_install_dir().AppendASCII(kSmallExtensionCrx);
+  extensions::CRXFileInfo crx_info_small =
+      CreateTestExtensionCrx(path, kSmallExtensionSize);
+
+  EXPECT_CALL(*mock_installer_,
+              InstallCrxFile(Field(&extensions::CRXFileInfo::path, path)));
+  install_limiter_->Add(mock_installer_, crx_info_small);
+  task_environment()->RunUntilIdle();
+  Mock::VerifyAndClearExpectations(mock_installer_.get());
+}
+
+// Test that large extension installations are deferred.
+TEST_F(InstallLimiterTest, DeferLargeExtensionInstallation) {
+  const base::FilePath path =
+      extensions_install_dir().AppendASCII(kLargeExtensionCrx);
+  extensions::CRXFileInfo crx_info_large =
+      CreateTestExtensionCrx(path, kLargeExtensionSize);
+
+  // Check that the large extension will not be installed immediately.
+  EXPECT_CALL(*mock_installer_,
+              InstallCrxFile(Field(&extensions::CRXFileInfo::path, path)))
+      .Times(0);
+  install_limiter_->Add(mock_installer_, crx_info_large);
+  task_environment()->FastForwardBy(kLessThanExpectedWaitTime);
+  task_environment()->RunUntilIdle();
+  Mock::VerifyAndClearExpectations(mock_installer_.get());
+
+  // The installation starts only after the wait time is elapsed.
+  EXPECT_CALL(*mock_installer_,
+              InstallCrxFile(Field(&extensions::CRXFileInfo::path, path)));
+  task_environment()->FastForwardBy(kTimeDeltaUntilExpectedWaitTime);
+  task_environment()->RunUntilIdle();
+  Mock::VerifyAndClearExpectations(mock_installer_.get());
+}
+
+// Test that deferred installations are run before the wait time expires if the
+// OnAllExternalProvidersReady() signal was called.
+TEST_F(InstallLimiterTest, RunDeferredInstallsWhenAllExternalProvidersReady) {
+  const base::FilePath path =
+      extensions_install_dir().AppendASCII(kLargeExtensionCrx);
+  extensions::CRXFileInfo crx_info_large =
+      CreateTestExtensionCrx(path, kLargeExtensionSize);
+
+  // Check that the large extension will not be installed immediately.
+  EXPECT_CALL(*mock_installer_,
+              InstallCrxFile(Field(&extensions::CRXFileInfo::path, path)))
+      .Times(0);
+  install_limiter_->Add(mock_installer_, crx_info_large);
+  task_environment()->FastForwardBy(kLessThanExpectedWaitTime);
+  task_environment()->RunUntilIdle();
+  Mock::VerifyAndClearExpectations(mock_installer_.get());
+
+  // The installation starts before the wait time is elapsed if
+  // OnAllExternalProvidersReady() is called.
+  EXPECT_CALL(*mock_installer_,
+              InstallCrxFile(Field(&extensions::CRXFileInfo::path, path)));
+  install_limiter_->OnAllExternalProvidersReady();
+  task_environment()->RunUntilIdle();
+  Mock::VerifyAndClearExpectations(mock_installer_.get());
+}
+
+// Test that small extensions are installed before large extensions.
+TEST_F(InstallLimiterTest, InstallSmallBeforeLargeExtensions) {
+  // Create a large test extension crx file.
+  const base::FilePath crx_path_large =
+      extensions_install_dir().AppendASCII(kLargeExtensionCrx);
+  extensions::CRXFileInfo crx_info_large =
+      CreateTestExtensionCrx(crx_path_large, kLargeExtensionSize);
+
+  // Create a small test extension crx file.
+  const base::FilePath crx_path_small =
+      extensions_install_dir().AppendASCII(kSmallExtensionCrx);
+  extensions::CRXFileInfo crx_info_small =
+      CreateTestExtensionCrx(crx_path_small, kSmallExtensionSize);
+
+  base::RunLoop run_loop;
+
+  // When adding a large extension and then a small extension, the small
+  // extension will be installed first. The mock function call will trigger a
+  // CRX_INSTALLER_DONE notification which will notify the install limiter to
+  // continue with any deferred installations. This will then start the
+  // installation of the large extension.
+  {
+    testing::InSequence s;
+
+    EXPECT_CALL(
+        *mock_installer_,
+        InstallCrxFile(Field(&extensions::CRXFileInfo::path, crx_path_small)))
+        .WillOnce(Invoke(this, &InstallLimiterTest::NotifyCrxInstallerDone));
+    EXPECT_CALL(
+        *mock_installer_,
+        InstallCrxFile(Field(&extensions::CRXFileInfo::path, crx_path_large)))
+        .WillOnce(Invoke(&run_loop, &base::RunLoop::Quit));
+  }
+
+  install_limiter_->Add(mock_installer_, crx_info_large);
+  // Ensure that AddWithSize() is called for the large extension before also
+  // adding the small extension.
+  task_environment()->RunUntilIdle();
+  install_limiter_->Add(mock_installer_, crx_info_small);
+
+  run_loop.Run();
+
+  Mock::VerifyAndClearExpectations(mock_installer_.get());
+}
diff --git a/chrome/browser/extensions/crx_installer.h b/chrome/browser/extensions/crx_installer.h
index 608e0d9..5a50106 100644
--- a/chrome/browser/extensions/crx_installer.h
+++ b/chrome/browser/extensions/crx_installer.h
@@ -44,6 +44,7 @@
 class ExtensionService;
 class ExtensionUpdaterTest;
 enum class InstallationStage;
+class MockCrxInstaller;
 class PreloadCheckGroup;
 
 // This class installs a crx file into a profile.
@@ -119,7 +120,7 @@
   void InstallCrx(const base::FilePath& source_file);
 
   // Install the crx in |source_file|.
-  void InstallCrxFile(const CRXFileInfo& source_file);
+  virtual void InstallCrxFile(const CRXFileInfo& source_file);
 
   // Install the unpacked crx in |unpacked_dir|.
   // If |delete_source_| is true, |unpacked_dir| will be removed at the end of
@@ -262,6 +263,7 @@
   friend class ::ExtensionServiceTest;
   friend class BookmarkAppInstallFinalizerTest;
   friend class ExtensionUpdaterTest;
+  friend class MockCrxInstaller;
 
   CrxInstaller(base::WeakPtr<ExtensionService> service_weak,
                std::unique_ptr<ExtensionInstallPrompt> client,
diff --git a/chrome/browser/extensions/extension_service_test_base.cc b/chrome/browser/extensions/extension_service_test_base.cc
index c10561b..2276c5d 100644
--- a/chrome/browser/extensions/extension_service_test_base.cc
+++ b/chrome/browser/extensions/extension_service_test_base.cc
@@ -412,7 +412,8 @@
                                 service_->shared_module_service());
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
-  InstallLimiter::Get(profile())->DisableForTest();
+  if (!params.enable_install_limiter)
+    InstallLimiter::Get(profile())->DisableForTest();
 #endif
 }
 
diff --git a/chrome/browser/extensions/extension_service_test_base.h b/chrome/browser/extensions/extension_service_test_base.h
index ff00127..0e29bec 100644
--- a/chrome/browser/extensions/extension_service_test_base.h
+++ b/chrome/browser/extensions/extension_service_test_base.h
@@ -70,6 +70,7 @@
     bool profile_is_supervised = false;
     bool profile_is_guest = false;
     bool enable_bookmark_model = false;
+    bool enable_install_limiter = false;
 
     raw_ptr<policy::PolicyService> policy_service = nullptr;
 
diff --git a/chrome/browser/first_run/first_run.cc b/chrome/browser/first_run/first_run.cc
index ac23c38..8f73f74e 100644
--- a/chrome/browser/first_run/first_run.cc
+++ b/chrome/browser/first_run/first_run.cc
@@ -68,7 +68,6 @@
 
 // Flags for functions of similar name.
 bool g_should_show_welcome_page = false;
-bool g_should_do_autofill_personal_data_manager_first_run = false;
 
 // Indicates whether this is first run. Populated when IsChromeFirstRun
 // is invoked, then used as a cache on subsequent calls.
@@ -378,16 +377,6 @@
          GURL(chrome::kChromeUIWelcomeURL);
 }
 
-void SetShouldDoPersonalDataManagerFirstRun() {
-  g_should_do_autofill_personal_data_manager_first_run = true;
-}
-
-bool ShouldDoPersonalDataManagerFirstRun() {
-  bool retval = g_should_do_autofill_personal_data_manager_first_run;
-  g_should_do_autofill_personal_data_manager_first_run = false;
-  return retval;
-}
-
 void SetInitialPrefsPathForTesting(const base::FilePath& initial_prefs) {
   GetInitialPrefsPathForTesting() = initial_prefs;
 }
@@ -504,7 +493,6 @@
   ProcessDefaultBrowserPolicy(make_chrome_default_for_user);
 
   SetShouldShowWelcomePage();
-  SetShouldDoPersonalDataManagerFirstRun();
 
   internal::DoPostImportPlatformSpecificTasks(profile);
 }
diff --git a/chrome/browser/first_run/first_run.h b/chrome/browser/first_run/first_run.h
index 97800b7..8984ee5 100644
--- a/chrome/browser/first_run/first_run.h
+++ b/chrome/browser/first_run/first_run.h
@@ -122,18 +122,6 @@
 // Returns true if |contents| hosts one of the welcome pages.
 bool IsOnWelcomePage(content::WebContents* contents);
 
-// Sets a flag that will cause ShouldDoPersonalDataManagerFirstRun()
-// to return true exactly once, so that the browser loads
-// PersonalDataManager once the main message loop gets going.
-void SetShouldDoPersonalDataManagerFirstRun();
-
-// Returns true if the autofill personal data manager first-run action
-// should be taken.
-//
-// This will return true only once, the first time it is called after
-// SetShouldDoPersonalDataManagerFirstRun() is called.
-bool ShouldDoPersonalDataManagerFirstRun();
-
 // Automatically imports items requested by |profile|'s configuration (sum of
 // policies and initial prefs). Also imports bookmarks from file if
 // |import_bookmarks_path| is not empty.
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index ce8e511c..aac03935 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -1035,8 +1035,8 @@
   },
   {
     "name": "crostini-ime-support",
-    "owners": [ "timloh", "davidmunro@google.com" ],
-    "expiry_milestone": 104
+    "owners": [ "timloh", "sophialin@google.com" ],
+    "expiry_milestone": 111
   },
   {
     "name": "crostini-multi-container",
@@ -1055,8 +1055,8 @@
   },
   {
     "name": "crostini-virtual-keyboard-support",
-    "owners": [ "timloh", "davidmunro@google.com" ],
-    "expiry_milestone": 104
+    "owners": [ "timloh", "sophialin@google.com" ],
+    "expiry_milestone": 111
   },
   {
     "name": "cryptauth-v2-dedup-device-last-activity-time",
@@ -1750,11 +1750,6 @@
     "expiry_milestone": 97
   },
   {
-    "name": "enable-cros-language-settings-update-2",
-    "owners": [ "cros-borders@google.com" ],
-    "expiry_milestone": 100
-  },
-  {
     "name": "enable-cros-language-settings-update-japanese",
     "owners": [ "keithlee", "essential-inputs-team@google.com" ],
     "expiry_milestone": 110
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 67dad774..3d079ca 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -4474,11 +4474,6 @@
     "When enabled, crosh (ChromeOS Shell) will run as a tabbed System Web App "
     "rather than a normal browser tab.";
 
-const char kCrosLanguageSettingsUpdate2Name[] = "Language Settings Update 2";
-const char kCrosLanguageSettingsUpdate2Description[] =
-    "Enables the second language settings update. Requires "
-    "#enable-cros-language-settings-update to be enabled.";
-
 const char kCrosLanguageSettingsUpdateJapaneseName[] =
     "Language Settings Update Japanese";
 const char kCrosLanguageSettingsUpdateJapaneseDescription[] =
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 4cf9dcbeb..c9947d8 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -2560,9 +2560,6 @@
 extern const char kCroshSWAName[];
 extern const char kCroshSWADescription[];
 
-extern const char kCrosLanguageSettingsUpdate2Name[];
-extern const char kCrosLanguageSettingsUpdate2Description[];
-
 extern const char kCrosLanguageSettingsUpdateJapaneseName[];
 extern const char kCrosLanguageSettingsUpdateJapaneseDescription[];
 
diff --git a/chrome/browser/lacros/browser_service_lacros.cc b/chrome/browser/lacros/browser_service_lacros.cc
index 79e90f0..a10501a 100644
--- a/chrome/browser/lacros/browser_service_lacros.cc
+++ b/chrome/browser/lacros/browser_service_lacros.cc
@@ -258,6 +258,15 @@
 
 void BrowserServiceLacros::NewTab(bool should_trigger_session_restore,
                                   NewTabCallback callback) {
+  if (ProfilePicker::ShouldShowAtLaunch() &&
+      chrome::GetTotalBrowserCount() == 0) {
+    // The first browser window will trigger session restore if needed.
+    ProfilePicker::Show(ProfilePicker::Params::FromEntryPoint(
+        ProfilePicker::EntryPoint::kNewSessionOnExistingProcess));
+    std::move(callback).Run();
+    return;
+  }
+
   LoadMainProfile(
       base::BindOnce(&BrowserServiceLacros::NewTabWithProfile,
                      weak_ptr_factory_.GetWeakPtr(),
diff --git a/chrome/browser/lacros/browser_service_lacros_browsertest.cc b/chrome/browser/lacros/browser_service_lacros_browsertest.cc
index f763c8e..25286dd6 100644
--- a/chrome/browser/lacros/browser_service_lacros_browsertest.cc
+++ b/chrome/browser/lacros/browser_service_lacros_browsertest.cc
@@ -9,6 +9,7 @@
 #include "chrome/browser/chromeos/app_mode/app_session.h"
 #include "chrome/browser/lacros/app_mode/kiosk_session_service_lacros.h"
 #include "chrome/browser/lacros/browser_service_lacros.h"
+#include "chrome/browser/lifetime/application_lifetime.h"
 #include "chrome/browser/prefs/incognito_mode_prefs.h"
 #include "chrome/browser/prefs/session_startup_pref.h"
 #include "chrome/browser/profiles/keep_alive/profile_keep_alive_types.h"
@@ -111,6 +112,20 @@
     EXPECT_EQ(web_content->GetVisibleURL(), kNavigationUrl);
   }
 
+  void NewWindowSync(bool incognito, bool should_trigger_session_restore) {
+    base::RunLoop run_loop;
+    browser_service()->NewWindow(incognito, should_trigger_session_restore,
+                                 run_loop.QuitClosure());
+    run_loop.Run();
+  }
+
+  void NewTabSync(bool should_trigger_session_restore) {
+    base::RunLoop run_loop;
+    browser_service()->NewTab(should_trigger_session_restore,
+                              run_loop.QuitClosure());
+    run_loop.Run();
+  }
+
   BrowserServiceLacros* browser_service() const {
     return browser_service_.get();
   }
@@ -156,37 +171,87 @@
   base::FilePath path_profile2 =
       profile_manager->user_data_dir().Append(FILE_PATH_LITERAL("Profile 2"));
 
-  base::RunLoop run_loop;
-  Profile* profile2;
-  profile_manager->CreateProfileAsync(
-      path_profile2, base::BindLambdaForTesting(
-                         [&](Profile* profile, Profile::CreateStatus status) {
-                           if (status == Profile::CREATE_STATUS_INITIALIZED) {
-                             profile2 = profile;
-                             run_loop.Quit();
-                           }
-                         }));
-  run_loop.Run();
+  Profile* profile2 =
+      profiles::testing::CreateProfileSync(profile_manager, path_profile2);
   // Open a browser window to make it the last used profile.
   chrome::NewEmptyWindow(profile2);
   ui_test_utils::WaitForBrowserToOpen();
 
   // Profile picker does _not_ open for incognito windows. Instead, the
   // incognito window for the last used profile is directly opened.
-  base::RunLoop run_loop2;
-  browser_service()->NewWindow(
-      /*incognito=*/true, /*should_trigger_session_restore=*/false,
-      /*callback=*/base::BindLambdaForTesting([&]() { run_loop2.Quit(); }));
-  run_loop2.Run();
+  NewWindowSync(/*incognito=*/true, /*should_trigger_session_restore=*/false);
   EXPECT_FALSE(ProfilePicker::IsOpen());
   Profile* profile = BrowserList::GetInstance()->GetLastActive()->profile();
   // Main profile should be always used.
   EXPECT_EQ(profile->GetPath(), main_profile->GetPath());
   EXPECT_TRUE(profile->IsOffTheRecord());
 
-  browser_service()->NewWindow(
-      /*incognito=*/false, /*should_trigger_session_restore=*/false,
-      /*callback=*/base::BindLambdaForTesting([]() {}));
+  NewWindowSync(/*incognito=*/false, /*should_trigger_session_restore=*/false);
+  EXPECT_TRUE(ProfilePicker::IsOpen());
+}
+
+IN_PROC_BROWSER_TEST_F(BrowserServiceLacrosBrowserTest,
+                       NewTab_OpensProfilePicker_SingleProfile) {
+  // Keep the browser process running during the test while the browser is
+  // closed.
+  ScopedKeepAlive keep_alive(KeepAliveOrigin::BROWSER,
+                             KeepAliveRestartOption::DISABLED);
+  // Start in a state with no browser windows opened.
+  CloseBrowserSynchronously(browser());
+  EXPECT_EQ(0u, chrome::GetTotalBrowserCount());
+
+  // `NewTab()` should create a new window if the system has only one
+  // profile.
+  NewTabSync(/*should_trigger_session_restore=*/true);
+  ui_test_utils::WaitForBrowserToOpen();
+  EXPECT_EQ(1u, chrome::GetTotalBrowserCount());
+  EXPECT_FALSE(ProfilePicker::IsOpen());
+  ProfileManager* profile_manager = g_browser_process->profile_manager();
+  auto* main_profile = profile_manager->GetProfileByPath(
+      profile_manager->GetPrimaryUserProfilePath());
+  auto* browser = chrome::FindBrowserWithProfile(main_profile);
+  auto* tab_strip = browser->tab_strip_model();
+  EXPECT_EQ(1, tab_strip->count());
+
+  // Consequent `NewTab()` should add a new tab to an existing browser.
+  NewTabSync(/*should_trigger_session_restore=*/true);
+  EXPECT_EQ(2, tab_strip->count());
+  EXPECT_FALSE(ProfilePicker::IsOpen());
+}
+
+IN_PROC_BROWSER_TEST_F(BrowserServiceLacrosBrowserTest,
+                       NewTab_OpensProfilePicker_MultiProfile) {
+  // Keep the browser process running during the test while the browser is
+  // closed.
+  ScopedKeepAlive keep_alive(KeepAliveOrigin::BROWSER,
+                             KeepAliveRestartOption::DISABLED);
+
+  // Create and open an additional profile to move Chrome to the multi-profile
+  // mode.
+  ProfileManager* profile_manager = g_browser_process->profile_manager();
+  base::FilePath profile2_path =
+      profile_manager->user_data_dir().Append(FILE_PATH_LITERAL("Profile 2"));
+  Profile* profile2 =
+      profiles::testing::CreateProfileSync(profile_manager, profile2_path);
+  chrome::NewEmptyWindow(profile2);
+  ui_test_utils::WaitForBrowserToOpen();
+  EXPECT_EQ(2u, chrome::GetTotalBrowserCount());
+  auto* tab_strip = browser()->tab_strip_model();
+  EXPECT_EQ(1, tab_strip->count());
+
+  // `NewTab()` should add a tab to the main profile window;
+  NewTabSync(/*should_trigger_session_restore=*/true);
+  EXPECT_EQ(2, tab_strip->count());
+
+  chrome::CloseAllBrowsers();
+  // Wait for two browsers to be closed.
+  ui_test_utils::WaitForBrowserToClose();
+  ui_test_utils::WaitForBrowserToClose();
+  EXPECT_EQ(0u, chrome::GetTotalBrowserCount());
+
+  // `NewTab()` should open the profile picker.
+  NewTabSync(/*should_trigger_session_restore=*/true);
+  EXPECT_EQ(0u, chrome::GetTotalBrowserCount());
   EXPECT_TRUE(ProfilePicker::IsOpen());
 }
 
@@ -232,11 +297,7 @@
 
   // Opening a new window should suppress the profile picker and the crash
   // restore bubble should be showing.
-  base::RunLoop run_loop;
-  browser_service()->NewWindow(
-      /*incognito=*/false, /*should_trigger_session_restore=*/true,
-      /*callback=*/base::BindLambdaForTesting([&]() { run_loop.Quit(); }));
-  run_loop.Run();
+  NewWindowSync(/*incognito=*/false, /*should_trigger_session_restore=*/true);
 
   EXPECT_FALSE(ProfilePicker::IsOpen());
   views::BubbleDialogDelegate* crash_bubble_delegate =
@@ -431,9 +492,7 @@
   // Trigger a new tab with session restore.
   base::RunLoop run_loop;
   testing::SessionsRestoredWaiter restore_waiter(run_loop.QuitClosure(), 1);
-  browser_service()->NewTab(
-      /*should_trigger_session_restore=*/true,
-      /*callback=*/base::DoNothing());
+  NewTabSync(/*should_trigger_session_restore=*/true);
   run_loop.Run();
 
   EXPECT_EQ(1u, BrowserList::GetInstance()->size());
@@ -449,11 +508,7 @@
 
   // A second call to NewTab() ignores session restore and adds a new tab to
   // the existing browser.
-  base::RunLoop run_loop2;
-  browser_service()->NewTab(
-      /*should_trigger_session_restore=*/true,
-      /*callback=*/run_loop2.QuitClosure());
-  run_loop2.Run();
+  NewTabSync(/*should_trigger_session_restore=*/true);
 
   EXPECT_EQ(1u, BrowserList::GetInstance()->size());
   ASSERT_EQ(3, new_tab_strip->count());
@@ -471,11 +526,7 @@
   IncognitoModePrefs::SetAvailability(
       main_profile->GetPrefs(), IncognitoModePrefs::Availability::kDisabled);
   // Request a new incognito window.
-  base::RunLoop run_loop;
-  browser_service()->NewWindow(
-      /*incognito=*/true, /*should_trigger_session_restore=*/false,
-      /*callback=*/base::BindLambdaForTesting([&]() { run_loop.Quit(); }));
-  run_loop.Run();
+  NewWindowSync(/*incognito=*/true, /*should_trigger_session_restore=*/false);
   // A regular window opens instead.
   EXPECT_FALSE(ProfilePicker::IsOpen());
   Profile* profile = BrowserList::GetInstance()->GetLastActive()->profile();
diff --git a/chrome/browser/net/network_context_configuration_browsertest.cc b/chrome/browser/net/network_context_configuration_browsertest.cc
index dbcccc4..7b2f03b 100644
--- a/chrome/browser/net/network_context_configuration_browsertest.cc
+++ b/chrome/browser/net/network_context_configuration_browsertest.cc
@@ -1451,7 +1451,9 @@
   EXPECT_FALSE(GetCookies(embedded_test_server()->base_url()).empty());
 }
 
-IN_PROC_BROWSER_TEST_P(NetworkContextConfigurationBrowserTest, CookiesEnabled) {
+// Disabled due to flakiness. See https://crbug.com/1273903.
+IN_PROC_BROWSER_TEST_P(NetworkContextConfigurationBrowserTest,
+                       DISABLED_CookiesEnabled) {
   if (IsRestartStateWithInProcessNetworkService())
     return;
   // Check that the cookie from the first stage of the test was / was not
diff --git a/chrome/browser/notifications/BUILD.gn b/chrome/browser/notifications/BUILD.gn
index dc8457ee..1af211e 100644
--- a/chrome/browser/notifications/BUILD.gn
+++ b/chrome/browser/notifications/BUILD.gn
@@ -103,9 +103,12 @@
     testonly = true
 
     sources = [
+      "android/java/src/org/chromium/chrome/browser/notifications/NotificationBuilderBaseTest.java",
       "android/java/src/org/chromium/chrome/browser/notifications/NotificationIntentInterceptorTest.java",
       "android/java/src/org/chromium/chrome/browser/notifications/NotificationSystemStatusUtilUnitTest.java",
+      "android/java/src/org/chromium/chrome/browser/notifications/NotificationTestUtil.java",
       "android/java/src/org/chromium/chrome/browser/notifications/PendingIntentProviderTest.java",
+      "android/java/src/org/chromium/chrome/browser/notifications/StandardNotificationBuilderTest.java",
       "android/java/src/org/chromium/chrome/browser/notifications/ThrottlingNotificationSchedulerTest.java",
       "android/java/src/org/chromium/chrome/browser/notifications/channels/ChromeChannelDefinitionsTest.java",
       "android/java/src/org/chromium/chrome/browser/notifications/permissions/NotificationPermissionChangeReceiverTest.java",
@@ -123,8 +126,10 @@
       "//chrome/browser/preferences:java",
       "//chrome/test/android:chrome_java_unit_test_support",
       "//components/browser_ui/notifications/android:java",
+      "//components/embedder_support/android:junit_test_support",
       "//third_party/android_deps:espresso_java",
       "//third_party/android_deps:robolectric_all_java",
+      "//third_party/androidx:androidx_annotation_annotation_java",
       "//third_party/androidx:androidx_test_core_java",
       "//third_party/androidx:androidx_test_core_java",
       "//third_party/androidx:androidx_test_ext_junit_java",
@@ -142,10 +147,8 @@
 
     sources = [
       "android/java/src/org/chromium/chrome/browser/notifications/CustomNotificationBuilderTest.java",
-      "android/java/src/org/chromium/chrome/browser/notifications/NotificationBuilderBaseTest.java",
       "android/java/src/org/chromium/chrome/browser/notifications/NotificationTestUtil.java",
       "android/java/src/org/chromium/chrome/browser/notifications/NotificationWrapperBuilderFactoryTest.java",
-      "android/java/src/org/chromium/chrome/browser/notifications/StandardNotificationBuilderTest.java",
       "android/java/src/org/chromium/chrome/browser/notifications/channels/ChannelsInitializerTest.java",
       "android/java/src/org/chromium/chrome/browser/notifications/channels/ChannelsUpdaterTest.java",
     ]
diff --git a/chrome/browser/notifications/android/java/src/org/chromium/chrome/browser/notifications/NotificationBuilderBaseTest.java b/chrome/browser/notifications/android/java/src/org/chromium/chrome/browser/notifications/NotificationBuilderBaseTest.java
index 7c1751f9..2cd2385 100644
--- a/chrome/browser/notifications/android/java/src/org/chromium/chrome/browser/notifications/NotificationBuilderBaseTest.java
+++ b/chrome/browser/notifications/android/java/src/org/chromium/chrome/browser/notifications/NotificationBuilderBaseTest.java
@@ -7,40 +7,46 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
-import android.support.test.InstrumentationRegistry;
 
-import androidx.test.filters.MediumTest;
-
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
 
-import org.chromium.base.test.BaseJUnit4ClassRunner;
-import org.chromium.base.test.util.Batch;
+import org.chromium.base.test.BaseRobolectricTestRunner;
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.browser.notifications.channels.ChromeChannelDefinitions;
 import org.chromium.components.browser_ui.notifications.NotificationMetadata;
 import org.chromium.components.browser_ui.notifications.NotificationWrapper;
 import org.chromium.components.browser_ui.widget.RoundedIconGenerator;
+import org.chromium.components.embedder_support.util.ShadowUrlUtilities;
 import org.chromium.components.embedder_support.util.UrlUtilities;
-import org.chromium.content_public.browser.test.NativeLibraryTestUtils;
 
 /**
- * Instrumentation unit tests for NotificationBuilderBase.
+ * Unit tests for NotificationBuilderBase.
  *
- * Extends NativeLibraryTestBase so that {@link UrlUtilities#getDomainAndRegistry} can access
- * native GetDomainAndRegistry, when called by {@link RoundedIconGenerator#getIconTextForUrl}
- * during testEnsureNormalizedIconBehavior().
+ * Uses ShadowUrlUtilities so that we can mock out {@link UrlUtilities#getDomainAndRegistry} called
+ * by {@link RoundedIconGenerator#getIconTextForUrl} during testEnsureNormalizedIconBehavior().
  */
-@RunWith(BaseJUnit4ClassRunner.class)
-@Batch(Batch.UNIT_TESTS)
+@RunWith(BaseRobolectricTestRunner.class)
+@Config(manifest = Config.NONE, shadows = {ShadowUrlUtilities.class})
 public class NotificationBuilderBaseTest {
     @Before
     public void setUp() {
-        // Not initializing the browser process is safe because GetDomainAndRegistry() is
-        // stand-alone.
-        NativeLibraryTestUtils.loadNativeLibraryNoBrowserProcess();
+        ShadowUrlUtilities.setTestImpl(new ShadowUrlUtilities.TestImpl() {
+            @Override
+            public String getDomainAndRegistry(String uri, boolean includePrivateRegistries) {
+                return uri;
+            }
+        });
+    }
+
+    @After
+    public void tearDown() {
+        ShadowUrlUtilities.reset();
     }
 
     /**
@@ -50,13 +56,10 @@
      *     (3) Smaller bitmaps should be left alone.
      */
     @Test
-    @MediumTest
     @Feature({"Browser", "Notifications"})
     public void testEnsureNormalizedIconBehavior() {
         // Get the dimensions of the notification icon that will be presented to the user.
-        Context appContext = InstrumentationRegistry.getInstrumentation()
-                                     .getTargetContext()
-                                     .getApplicationContext();
+        Context appContext = RuntimeEnvironment.getApplication();
         Resources resources = appContext.getResources();
 
         int largeIconWidthPx =
diff --git a/chrome/browser/notifications/android/java/src/org/chromium/chrome/browser/notifications/StandardNotificationBuilderTest.java b/chrome/browser/notifications/android/java/src/org/chromium/chrome/browser/notifications/StandardNotificationBuilderTest.java
index 1c9abe0..0b84088e 100644
--- a/chrome/browser/notifications/android/java/src/org/chromium/chrome/browser/notifications/StandardNotificationBuilderTest.java
+++ b/chrome/browser/notifications/android/java/src/org/chromium/chrome/browser/notifications/StandardNotificationBuilderTest.java
@@ -15,46 +15,47 @@
 import android.graphics.BitmapFactory;
 import android.graphics.Color;
 import android.os.Build;
-import android.support.test.InstrumentationRegistry;
 import android.text.SpannableStringBuilder;
 
-import androidx.test.filters.SmallTest;
-
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
 
-import org.chromium.base.test.BaseJUnit4ClassRunner;
-import org.chromium.base.test.util.Batch;
+import org.chromium.base.test.BaseRobolectricTestRunner;
 import org.chromium.base.test.util.Feature;
 import org.chromium.base.test.util.MinAndroidSdkLevel;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.notifications.channels.ChromeChannelDefinitions;
 import org.chromium.components.browser_ui.notifications.NotificationMetadata;
 import org.chromium.components.browser_ui.notifications.PendingIntentProvider;
-import org.chromium.components.browser_ui.widget.RoundedIconGenerator;
-import org.chromium.components.embedder_support.util.UrlUtilities;
-import org.chromium.content_public.browser.test.NativeLibraryTestUtils;
+import org.chromium.components.embedder_support.util.ShadowUrlUtilities;
 
 /**
- * Instrumentation unit tests for StandardNotificationBuilder.
- *
- * Extends NativeLibraryTestBase so that {@link UrlUtilities#getDomainAndRegistry} can access
- * native GetDomainAndRegistry, when called by {@link RoundedIconGenerator#getIconTextForUrl}
- * during notification construction.
+ * Robolectric unit tests for StandardNotificationBuilder.
  */
-@RunWith(BaseJUnit4ClassRunner.class)
-@Batch(Batch.UNIT_TESTS)
+@RunWith(BaseRobolectricTestRunner.class)
+@Config(manifest = Config.NONE, shadows = {ShadowUrlUtilities.class})
 public class StandardNotificationBuilderTest {
     private static final String NOTIFICATION_TAG = "TestNotificationTag";
     private static final int NOTIFICATION_ID = 99;
 
     @Before
     public void setUp() {
-        // Not initializing the browser process is safe because GetDomainAndRegistry() is
-        // stand-alone.
-        NativeLibraryTestUtils.loadNativeLibraryNoBrowserProcess();
+        ShadowUrlUtilities.setTestImpl(new ShadowUrlUtilities.TestImpl() {
+            @Override
+            public String getDomainAndRegistry(String uri, boolean includePrivateRegistries) {
+                return uri;
+            }
+        });
+    }
+
+    @After
+    public void tearDown() {
+        ShadowUrlUtilities.reset();
     }
 
     private NotificationBuilderBase createAllOptionsBuilder(
@@ -63,7 +64,7 @@
             throw new IllegalArgumentException();
         }
 
-        Context context = InstrumentationRegistry.getTargetContext();
+        Context context = RuntimeEnvironment.getApplication();
         outContentAndDeleteIntents[0] = createIntent(context, "content");
         outContentAndDeleteIntents[1] = createIntent(context, "delete");
 
@@ -105,7 +106,6 @@
     }
 
     @Test
-    @SmallTest
     @Feature({"Browser", "Notifications"})
     public void testSetAll() {
         PendingIntentProvider[] contentAndDeleteIntents = new PendingIntentProvider[2];
@@ -124,7 +124,7 @@
         Assert.assertNotNull(picture);
         Assert.assertTrue(picture.getWidth() > 0 && picture.getHeight() > 0);
 
-        Context context = InstrumentationRegistry.getTargetContext();
+        Context context = RuntimeEnvironment.getApplication();
         Bitmap smallIcon =
                 BitmapFactory.decodeResource(context.getResources(), R.drawable.ic_chrome);
         Assert.assertTrue(smallIcon.sameAs(
@@ -161,7 +161,6 @@
     }
 
     @Test
-    @SmallTest
     @Feature({"Browser", "Notifications"})
     public void testBigTextStyle() {
         PendingIntentProvider[] contentAndDeleteIntents = new PendingIntentProvider[2];
@@ -173,10 +172,9 @@
     }
 
     @Test
-    @SmallTest
     @Feature({"Browser", "Notifications"})
     public void testSetSmallIcon() {
-        Context context = InstrumentationRegistry.getTargetContext();
+        Context context = RuntimeEnvironment.getApplication();
         NotificationBuilderBase notificationBuilder = new StandardNotificationBuilder(context);
 
         Bitmap bitmap =
@@ -217,10 +215,9 @@
      */
     @Test
     @MinAndroidSdkLevel(Build.VERSION_CODES.M)
-    @SmallTest
     @Feature({"Browser", "Notifications"})
     public void testRenotifyWithCustomBadgeDoesNotCrash() {
-        Context context = InstrumentationRegistry.getTargetContext();
+        Context context = RuntimeEnvironment.getApplication();
 
         NotificationBuilderBase builder =
                 new StandardNotificationBuilder(context)
@@ -245,10 +242,9 @@
     }
 
     @Test
-    @SmallTest
     @Feature({"Browser", "Notifications"})
     public void testAddTextActionSetsRemoteInput() {
-        Context context = InstrumentationRegistry.getTargetContext();
+        Context context = RuntimeEnvironment.getApplication();
         NotificationBuilderBase notificationBuilder =
                 new StandardNotificationBuilder(context)
                         .setChannelId(ChromeChannelDefinitions.ChannelId.SITES)
diff --git a/chrome/browser/prefetch/search_prefetch/base_search_prefetch_request.cc b/chrome/browser/prefetch/search_prefetch/base_search_prefetch_request.cc
index 16d8ca9..10b14eb 100644
--- a/chrome/browser/prefetch/search_prefetch/base_search_prefetch_request.cc
+++ b/chrome/browser/prefetch/search_prefetch/base_search_prefetch_request.cc
@@ -6,10 +6,12 @@
 
 #include <vector>
 
+#include "base/metrics/histogram_functions.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/trace_event/base_tracing.h"
 #include "build/build_config.h"
 #include "chrome/browser/prefetch/prefetch_headers.h"
+#include "chrome/browser/prerender/prerender_manager.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/search_engines/template_url_service_factory.h"
 #include "chrome/common/pref_names.h"
@@ -106,10 +108,12 @@
 }  // namespace
 
 BaseSearchPrefetchRequest::BaseSearchPrefetchRequest(
+    const std::u16string& prefetch_search_terms,
     const GURL& prefetch_url,
     bool navigation_prefetch,
     base::OnceCallback<void(bool)> report_error_callback)
-    : prefetch_url_(prefetch_url),
+    : prefetch_search_terms_(prefetch_search_terms),
+      prefetch_url_(prefetch_url),
       navigation_prefetch_(navigation_prefetch),
       report_error_callback_(std::move(report_error_callback)) {}
 
@@ -234,12 +238,6 @@
   auto* default_search = template_url_service->GetDefaultSearchProvider();
   DCHECK(default_search);
 
-  std::u16string prefetch_url_search_terms;
-
-  default_search->ExtractSearchTermsFromURL(
-      prefetch_url_, template_url_service->search_terms_data(),
-      &prefetch_url_search_terms);
-
   bool should_defer = false;
   {
     TRACE_EVENT0(
@@ -269,7 +267,7 @@
           resource_request->url, template_url_service->search_terms_data(),
           &new_url_search_terms);
 
-      if (should_defer || new_url_search_terms != prefetch_url_search_terms ||
+      if (should_defer || new_url_search_terms != prefetch_search_terms_ ||
           cancel_or_pause_delegate.cancelled_or_paused()) {
         return false;
       }
@@ -291,6 +289,50 @@
          current_status_ == SearchPrefetchStatus::kCanBeServed);
   current_status_ = SearchPrefetchStatus::kRequestCancelled;
   StopPrefetch();
+  StopPrerender();
+}
+
+void BaseSearchPrefetchRequest::MaybeStartPrerenderSearchResult(
+    PrerenderManager& prerender_manager) {
+  // Prerendering is supposed to be requested after prefetch received a servable
+  // response and take over the prefetched main resource response. When
+  // prerendering is requested while prefetching is still running, it has to
+  // wait until the completion of that. This procedure depends on the progress
+  // of prefetching as follows:
+  //    *1  |         *2     |    *3         | *4
+  //  prefetch started     received      prerender started
+
+  switch (current_status_) {
+    case SearchPrefetchStatus::kNotStarted:
+      // Case1: This request has been canceled before it starts sending network
+      // requests (see `StartPrefetchRequest`), so prerender should not be
+      // triggered.
+      return;
+    case SearchPrefetchStatus::kInFlight:
+    case SearchPrefetchStatus::kCanBeServed:
+    case SearchPrefetchStatus::kCanBeServedAndUserClicked:
+    case SearchPrefetchStatus::kComplete:
+      break;
+    case SearchPrefetchStatus::kRequestCancelled:
+    case SearchPrefetchStatus::kRequestFailed:
+      // Case N: The prefetch request failed, or has failed. Prerender cannot
+      // reuse the response and will fail for sure, so this does not start
+      // prerendering.
+      return;
+    case SearchPrefetchStatus::kServed:
+      NOTREACHED();
+  }
+
+  if (servable_response_code_received_) {
+    // Case 3, 4: This can start prerendering because it has received a
+    // response.
+    prerender_manager.StartPrerenderSearchResult(prefetch_search_terms_,
+                                                 prefetch_url_);
+  } else {
+    // Case 2: this will start prerendering after it receives a
+    // servable response.
+    prerender_manager_ = prerender_manager.GetWeakPtr();
+  }
 }
 
 void BaseSearchPrefetchRequest::ErrorEncountered() {
@@ -301,11 +343,23 @@
   StopPrefetch();
 }
 
+void BaseSearchPrefetchRequest::OnServableResponseCodeReceived() {
+  servable_response_code_received_ = true;
+  if (prerender_manager_) {
+    prerender_manager_->StartPrerenderSearchResult(prefetch_search_terms_,
+                                                   prefetch_url_);
+  }
+}
+
 void BaseSearchPrefetchRequest::MarkPrefetchAsServable() {
   DCHECK(current_status_ == SearchPrefetchStatus::kInFlight);
   current_status_ = SearchPrefetchStatus::kCanBeServed;
 }
 
+void BaseSearchPrefetchRequest::ResetPrerenderUpgrader() {
+  prerender_manager_ = nullptr;
+}
+
 void BaseSearchPrefetchRequest::MarkPrefetchAsComplete() {
   DCHECK(current_status_ == SearchPrefetchStatus::kInFlight ||
          current_status_ == SearchPrefetchStatus::kCanBeServed ||
@@ -329,3 +383,10 @@
 void BaseSearchPrefetchRequest::RecordClickTime() {
   time_clicked_ = base::TimeTicks::Now();
 }
+
+void BaseSearchPrefetchRequest::StopPrerender() {
+  if (prerender_manager_) {
+    prerender_manager_->StopPrerenderSearchResult(prefetch_search_terms_);
+    prerender_manager_ = nullptr;
+  }
+}
diff --git a/chrome/browser/prefetch/search_prefetch/base_search_prefetch_request.h b/chrome/browser/prefetch/search_prefetch/base_search_prefetch_request.h
index f281a70..c38f13c 100644
--- a/chrome/browser/prefetch/search_prefetch/base_search_prefetch_request.h
+++ b/chrome/browser/prefetch/search_prefetch/base_search_prefetch_request.h
@@ -12,6 +12,7 @@
 #include "services/network/public/cpp/resource_request.h"
 #include "url/gurl.h"
 
+class PrerenderManager;
 class Profile;
 class SearchPrefetchURLLoader;
 
@@ -46,11 +47,17 @@
 };
 
 // A class representing a prefetch used by the Search Prefetch Service.
+// It plays the following roles to support search preloading.
+// - Preparing a resource request to prefetch a search terms.
+// - Starting prerendering upon the request succeeding to upgrade prefetch to
+//   prerender after the Search Prefetch Service tells it that the prefetched
+//   term is prerenderable.
 // Implementors should provide the fetch and storage functionality as well as
 // updating |current_status_|.
 class BaseSearchPrefetchRequest {
  public:
   BaseSearchPrefetchRequest(
+      const std::u16string& prefetch_search_terms,
       const GURL& prefetch_url,
       bool navigation_prefetch,
       base::OnceCallback<void(bool)> report_error_callback);
@@ -72,12 +79,19 @@
   // Marks a prefetch as canceled and stops any ongoing fetch.
   void CancelPrefetch();
 
+  // Called when SearchPrefetchService receives the hint that this prefetch
+  // request can be upgraded to a prerender attempt.
+  void MaybeStartPrerenderSearchResult(PrerenderManager& prerender_manager);
+
   // Called when the prefetch encounters an error.
   void ErrorEncountered();
 
   // Called when the prefetch encounters an error.
   void ErrorEncounteredUsingFallback();
 
+  // Called on the URL loader receives servable response.
+  void OnServableResponseCodeReceived();
+
   // Update the status when the request is serveable.
   void MarkPrefetchAsServable();
 
@@ -91,6 +105,11 @@
   // stack.
   void MarkPrefetchAsServed();
 
+  // Called when AutocompleteMatches changes. It resets PrerenderUpgrader.
+  // And if the AutocompleteMatches suggests to prerender a search result,
+  // `MaybeStartPrerenderSearchResult` will be called soon.
+  void ResetPrerenderUpgrader();
+
   // Record the time at which the user clicked a suggestion matching this
   // prefetch.
   void RecordClickTime();
@@ -119,8 +138,16 @@
   TakeSearchPrefetchURLLoader() = 0;
 
  protected:
+  // Whether the request has received a servable response. See
+  // `CanServePrefetchRequest` in ./streaming_search_prefetch_url_loader.cc for
+  // the definition of servable response.
+  bool servable_response_code_received_ = false;
+
   SearchPrefetchStatus current_status_ = SearchPrefetchStatus::kNotStarted;
 
+  // The search terms that this request is prefetching.
+  const std::u16string prefetch_search_terms_;
+
   // The URL to prefetch the search terms from.
   GURL prefetch_url_;
 
@@ -132,6 +159,14 @@
 
   // The time at which the prefetched URL was clicked in the Omnibox.
   base::TimeTicks time_clicked_;
+
+  // Once set, this request will trigger search prerender upon receiving success
+  // response.
+  base::WeakPtr<PrerenderManager> prerender_manager_;
+
+ private:
+  // Cancels ongoing and pending prerender.
+  void StopPrerender();
 };
 
 #endif  // CHROME_BROWSER_PREFETCH_SEARCH_PREFETCH_BASE_SEARCH_PREFETCH_REQUEST_H_
diff --git a/chrome/browser/prefetch/search_prefetch/field_trial_settings.cc b/chrome/browser/prefetch/search_prefetch/field_trial_settings.cc
index fcc8edc..af69c0c 100644
--- a/chrome/browser/prefetch/search_prefetch/field_trial_settings.cc
+++ b/chrome/browser/prefetch/search_prefetch/field_trial_settings.cc
@@ -8,6 +8,7 @@
 
 #include "base/metrics/field_trial_params.h"
 #include "base/system/sys_info.h"
+#include "chrome/browser/prerender/prerender_utils.h"
 
 const base::Feature kSearchPrefetchServicePrefetching{
     "SearchPrefetchServicePrefetching", base::FEATURE_ENABLED_BY_DEFAULT};
@@ -15,6 +16,9 @@
 const base::Feature kSearchPrefetchBlockBeforeHeaders{
     "SearchPrefetchBlockBeforeHeaders", base::FEATURE_DISABLED_BY_DEFAULT};
 
+const base::Feature kSearchPrefetchUpgradeToPrerender{
+    "SearchPrefetchUpgradeToPrerender", base::FEATURE_DISABLED_BY_DEFAULT};
+
 bool SearchPrefetchBlockBeforeHeadersIsEnabled() {
   return base::FeatureList::IsEnabled(kSearchPrefetchBlockBeforeHeaders);
 }
@@ -30,6 +34,12 @@
              3000);
 }
 
+bool SearchPrefetchUpgradeToPrerenderIsEnabled() {
+  if (!prerender_utils::IsSearchSuggestionPrerenderEnabled())
+    return false;
+  return base::FeatureList::IsEnabled(kSearchPrefetchUpgradeToPrerender);
+}
+
 const base::Feature kSearchPrefetchUsesNetworkCache{
     "SearchPrefetchUsesNetworkCache", base::FEATURE_DISABLED_BY_DEFAULT};
 
diff --git a/chrome/browser/prefetch/search_prefetch/field_trial_settings.h b/chrome/browser/prefetch/search_prefetch/field_trial_settings.h
index 5c603dd4..890f9cb 100644
--- a/chrome/browser/prefetch/search_prefetch/field_trial_settings.h
+++ b/chrome/browser/prefetch/search_prefetch/field_trial_settings.h
@@ -12,6 +12,8 @@
 
 extern const base::Feature kSearchPrefetchBlockBeforeHeaders;
 
+extern const base::Feature kSearchPrefetchUpgradeToPrerender;
+
 extern const base::Feature kSearchPrefetchUsesNetworkCache;
 
 // Whether matching prefetches can block navigation until they are determined to
@@ -21,6 +23,9 @@
 // Whether the search prefetch service actually initiates prefetches.
 bool SearchPrefetchServicePrefetchingIsEnabled();
 
+// Whether supporting upgrading a prefetch response to prerender page.
+bool SearchPrefetchUpgradeToPrerenderIsEnabled();
+
 // Whether the search prefetch service uses the network cache instead of the in
 // memory cache.
 bool SearchPrefetchUsesNetworkCache();
diff --git a/chrome/browser/prefetch/search_prefetch/search_prefetch_service.cc b/chrome/browser/prefetch/search_prefetch/search_prefetch_service.cc
index c56c03bc..6926206e0 100644
--- a/chrome/browser/prefetch/search_prefetch/search_prefetch_service.cc
+++ b/chrome/browser/prefetch/search_prefetch/search_prefetch_service.cc
@@ -6,6 +6,7 @@
 
 #include "base/bind.h"
 #include "base/callback.h"
+#include "base/containers/contains.h"
 #include "base/json/values_util.h"
 #include "base/location.h"
 #include "base/metrics/histogram_macros.h"
@@ -25,6 +26,7 @@
 #include "chrome/common/pref_names.h"
 #include "components/content_settings/core/browser/host_content_settings_map.h"
 #include "components/content_settings/core/common/content_settings.h"
+#include "components/omnibox/browser/autocomplete_match.h"
 #include "components/omnibox/browser/autocomplete_result.h"
 #include "components/omnibox/browser/base_search_provider.h"
 #include "components/omnibox/browser/omnibox_event_global_tracker.h"
@@ -98,6 +100,13 @@
   }
 }
 
+bool ShouldPrefetch(const AutocompleteMatch& match) {
+  // Prerender's threshold should definitely be higher than prefetch's. So a
+  // prerender hints can be treated as a prefetch hint.
+  return BaseSearchProvider::ShouldPrefetch(match) ||
+         BaseSearchProvider::ShouldPrerender(match);
+}
+
 }  // namespace
 
 // static
@@ -209,7 +218,7 @@
 
   std::unique_ptr<BaseSearchPrefetchRequest> prefetch_request =
       std::make_unique<StreamingSearchPrefetchRequest>(
-          url, navigation_prefetch,
+          search_terms, url, navigation_prefetch,
           base::BindOnce(&SearchPrefetchService::ReportFetchResult,
                          base::Unretained(this)));
 
@@ -464,9 +473,18 @@
   for (const auto& kv_pair : prefetches_) {
     const auto& search_terms = kv_pair.first;
     auto& prefetch_request = kv_pair.second;
+
     if (prefetch_request->current_status() != SearchPrefetchStatus::kInFlight &&
         prefetch_request->current_status() !=
             SearchPrefetchStatus::kCanBeServed) {
+      // Reset all pending prerenders. It will be set soon if service still
+      // wants clients to prerender a SearchTerms.
+      // TODO(https://crbug.com/1295170): Unlike prefetch, which does not
+      // discard completed response to avoid wasting, prerender would like
+      // to cancel itself given the cost of a prerender. For now prenderer is
+      // canceled when the prerender hints changed, we need to revisit this
+      // decision.
+      prefetch_request->ResetPrerenderUpgrader();
       continue;
     }
     bool should_cancel_request = true;
@@ -486,16 +504,32 @@
     if (should_cancel_request) {
       prefetch_request->CancelPrefetch();
     }
+
+    // Reset all pending prerenders. It will be set soon if service still wants
+    // clients to prerender a SearchTerms.
+    prefetch_request->ResetPrerenderUpgrader();
   }
 
+  // Do not perform preloading if there is no active tab.
+  if (!web_contents)
+    return;
   for (const auto& match : result) {
+    if (SearchPrefetchUpgradeToPrerenderIsEnabled()) {
+      if (!ShouldPrefetch(match))
+        continue;
+      CoordinatePrefetchWithPrerender(match, *web_contents,
+                                      template_url_service);
+      continue;
+    }
+
     if (BaseSearchProvider::ShouldPrefetch(match)) {
       MaybePrefetchURL(GetPrefetchURLFromMatch(match, template_url_service));
     }
     if (prerender_utils::IsSearchSuggestionPrerenderEnabled() &&
-        BaseSearchProvider::ShouldPrerender(match) && web_contents) {
+        BaseSearchProvider::ShouldPrerender(match)) {
       PrerenderManager::CreateForWebContents(web_contents);
       auto* prerender_manager = PrerenderManager::FromWebContents(web_contents);
+      DCHECK(prerender_manager);
       prerender_manager->StartPrerenderSearchSuggestion(match);
     }
   }
@@ -684,3 +718,23 @@
                                 base::Unretained(this)));
   }
 }
+
+void SearchPrefetchService::CoordinatePrefetchWithPrerender(
+    const AutocompleteMatch& match,
+    content::WebContents& web_contents,
+    TemplateURLService* template_url_service) {
+  GURL prefetch_url = GetPrefetchURLFromMatch(match, template_url_service);
+  MaybePrefetchURL(prefetch_url);
+  if (!BaseSearchProvider::ShouldPrerender(match))
+    return;
+
+  if (auto prefetch_request_iter =
+          prefetches_.find(match.search_terms_args->search_terms);
+      prefetch_request_iter != prefetches_.end()) {
+    PrerenderManager::CreateForWebContents(&web_contents);
+    auto* prerender_manager = PrerenderManager::FromWebContents(&web_contents);
+    DCHECK(prerender_manager);
+    prefetch_request_iter->second->MaybeStartPrerenderSearchResult(
+        *prerender_manager);
+  }
+}
diff --git a/chrome/browser/prefetch/search_prefetch/search_prefetch_service.h b/chrome/browser/prefetch/search_prefetch/search_prefetch_service.h
index 0c31132a..b40769e 100644
--- a/chrome/browser/prefetch/search_prefetch/search_prefetch_service.h
+++ b/chrome/browser/prefetch/search_prefetch/search_prefetch_service.h
@@ -22,6 +22,7 @@
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "url/gurl.h"
 
+struct AutocompleteMatch;
 struct OmniboxLog;
 class PrefRegistrySimple;
 class Profile;
@@ -179,6 +180,17 @@
   bool LoadFromPrefs();
   void SaveToPrefs() const;
 
+  // Called when this receives preloadable hints, and iff the
+  // SearchPrefetchUpgradeToPrerender feature is enabled. The feature is running
+  // on the assumption that Prerender is triggered after Prefetch receives
+  // servable response, so some specific logic is required and implemented by
+  // this method, e.g., it prefetches a prerender hint regardless of whether it
+  // is a prefetch hint, since a prerenderable result should be prefetchable.
+  void CoordinatePrefetchWithPrerender(
+      const AutocompleteMatch& match,
+      content::WebContents& web_contents,
+      TemplateURLService* template_url_service);
+
   // Prefetches that are started are stored using search terms as a key. Only
   // one prefetch should be started for a given search term until the old
   // prefetch expires.
diff --git a/chrome/browser/prefetch/search_prefetch/search_preload_unified_browsertest.cc b/chrome/browser/prefetch/search_prefetch/search_preload_unified_browsertest.cc
index a1402f1..63ca9a6a 100644
--- a/chrome/browser/prefetch/search_prefetch/search_preload_unified_browsertest.cc
+++ b/chrome/browser/prefetch/search_prefetch/search_preload_unified_browsertest.cc
@@ -6,9 +6,11 @@
 
 #include "base/bind.h"
 #include "base/containers/adapters.h"
+#include "base/containers/contains.h"
 #include "base/files/file_path.h"
 #include "base/path_service.h"
 #include "base/strings/string_number_conversions.h"
+#include "base/test/metrics/histogram_tester.h"
 #include "build/build_config.h"
 #include "chrome/browser/autocomplete/chrome_autocomplete_scheme_classifier.h"
 #include "chrome/browser/prefetch/search_prefetch/field_trial_settings.h"
@@ -36,7 +38,7 @@
 
 #if BUILDFLAG(IS_ANDROID)
 #include "chrome/test/base/android/android_browser_test.h"
-#else
+#else  // BUILDFLAG(IS_ANDROID)
 #include "chrome/test/base/in_process_browser_test.h"
 #endif  // BUILDFLAG(IS_ANDROID)
 
@@ -60,7 +62,8 @@
          {kSearchPrefetchServicePrefetching,
           {{"max_attempts_per_caching_duration", "3"},
            {"cache_size", "1"},
-           {"device_memory_threshold_MB", "0"}}}},
+           {"device_memory_threshold_MB", "0"}}},
+         {kSearchPrefetchUpgradeToPrerender, {}}},
         /*disabled_features=*/{kSearchPrefetchBlockBeforeHeaders});
   }
 
@@ -109,8 +112,24 @@
 
   std::unique_ptr<net::test_server::HttpResponse> HandleSearchRequest(
       const net::test_server::HttpRequest& request) {
-    if (request.GetURL().spec().find("favicon") != std::string::npos)
+    if (request.GetURL().spec().find("favicon") != std::string::npos) {
       return nullptr;
+    }
+    if (request.GetURL().spec().find("failed_terms") != std::string::npos) {
+      std::unique_ptr<net::test_server::BasicHttpResponse> resp =
+          std::make_unique<net::test_server::BasicHttpResponse>();
+      resp->set_code(net::HTTP_SERVICE_UNAVAILABLE);
+      return resp;
+    }
+    if (request.GetURL().spec().find("hang_response") != std::string::npos) {
+      return std::make_unique<net::test_server::HungResponse>();
+    }
+    if (request.GetURL().spec().find("hang_body") != std::string::npos) {
+      base::StringPairs headers = {{"Content-Length", "100"},
+                                   {"content-type", "text/html"}};
+      return std::make_unique<net::test_server::HungAfterHeadersHttpResponse>(
+          headers);
+    }
 
     std::unique_ptr<net::test_server::BasicHttpResponse> resp =
         std::make_unique<net::test_server::BasicHttpResponse>();
@@ -147,6 +166,10 @@
     return chrome_test_utils::GetActiveWebContents(this);
   }
 
+ protected:
+  enum class PrerenderHint { kEnabled, kDisabled };
+  enum class PrefetchHint { kEnabled, kDisabled };
+
   void SetUpContext() {
     // Have SearchPrefetchService and PrerenderManager prepared.
     PrerenderManager::CreateForWebContents(GetActiveWebContents());
@@ -160,23 +183,36 @@
 
   Profile* GetProfile() { return chrome_test_utils::GetProfile(this); }
 
-  AutocompleteMatch CreateSearchSuggestionMatch(
-      const std::string& original_query,
-      const std::string& search_terms,
-      bool is_prerender_hint,
-      bool is_prefetch_hint) {
-    AutocompleteMatch match;
-    match.search_terms_args = std::make_unique<TemplateURLRef::SearchTermsArgs>(
-        base::UTF8ToUTF16(search_terms));
-    match.search_terms_args->original_query = base::UTF8ToUTF16(original_query);
-    match.destination_url =
-        GetSearchUrl(search_terms, /*attach_prefetch_flag=*/false);
-    match.keyword = base::UTF8ToUTF16(original_query);
-    if (is_prerender_hint)
-      match.RecordAdditionalInfo("should_prerender", "true");
-    if (is_prefetch_hint)
-      match.RecordAdditionalInfo("should_prefetch", "true");
-    return match;
+  void ChangeAutocompleteResult(const std::string& original_query,
+                                const std::string& search_terms,
+                                PrerenderHint prerender_hint,
+                                PrefetchHint prefetch_hint) {
+    AutocompleteInput input(base::ASCIIToUTF16(original_query),
+                            metrics::OmniboxEventProto::BLANK,
+                            ChromeAutocompleteSchemeClassifier(
+                                chrome_test_utils::GetProfile(this)));
+    AutocompleteMatch autocomplete_match = CreateSearchSuggestionMatch(
+        original_query, search_terms, prerender_hint, prefetch_hint);
+    AutocompleteResult autocomplete_result;
+    autocomplete_result.AppendMatches({autocomplete_match});
+    search_prefetch_service()->OnResultChanged(GetActiveWebContents(),
+                                               autocomplete_result);
+  }
+
+  void WaitUntilStatusChangesTo(
+      std::u16string search_terms,
+      std::vector<SearchPrefetchStatus> acceptable_status) {
+    while (true) {
+      if (absl::optional<SearchPrefetchStatus> current_status =
+              search_prefetch_service()->GetSearchPrefetchStatusForTesting(
+                  search_terms);
+          current_status &&
+          base::Contains(acceptable_status, current_status.value())) {
+        break;
+      }
+      base::RunLoop run_loop;
+      run_loop.RunUntilIdle();
+    }
   }
 
   content::test::PrerenderTestHelper& prerender_helper() {
@@ -190,6 +226,25 @@
   }
 
  private:
+  AutocompleteMatch CreateSearchSuggestionMatch(
+      const std::string& original_query,
+      const std::string& search_terms,
+      PrerenderHint prerender_hint,
+      PrefetchHint prefetch_hint) {
+    AutocompleteMatch match;
+    match.search_terms_args = std::make_unique<TemplateURLRef::SearchTermsArgs>(
+        base::UTF8ToUTF16(search_terms));
+    match.search_terms_args->original_query = base::UTF8ToUTF16(original_query);
+    match.destination_url =
+        GetSearchUrl(search_terms, /*attach_prefetch_flag=*/false);
+    match.keyword = base::UTF8ToUTF16(original_query);
+    if (prerender_hint == PrerenderHint::kEnabled)
+      match.RecordAdditionalInfo("should_prerender", "true");
+    if (prefetch_hint == PrefetchHint::kEnabled)
+      match.RecordAdditionalInfo("should_prefetch", "true");
+    return match;
+  }
+
   constexpr static char kSearchDomain[] = "a.test";
   constexpr static char16_t kSearchDomain16[] = u"a.test";
   raw_ptr<PrerenderManager> prerender_manager_ = nullptr;
@@ -200,9 +255,10 @@
   base::test::ScopedFeatureList scoped_feature_list_;
 };
 
-// Tests that the SearchSuggestionService can trigger prerendering when it
-// receives prerender hints.
-IN_PROC_BROWSER_TEST_F(SearchPreloadUnifiedBrowserTest, PrerenderBeTriggered) {
+// Tests that the SearchSuggestionService can trigger prerendering after the
+// corresponding prefetch request succeeds.
+IN_PROC_BROWSER_TEST_F(SearchPreloadUnifiedBrowserTest,
+                       PrerenderHintReceivedBeforeSucceed) {
   const GURL kInitialUrl = embedded_test_server()->GetURL("/empty.html");
   ASSERT_TRUE(GetActiveWebContents());
   ASSERT_TRUE(content::NavigateToURL(GetActiveWebContents(), kInitialUrl));
@@ -216,16 +272,8 @@
   content::test::PrerenderHostRegistryObserver registry_observer(
       *GetActiveWebContents());
 
-  AutocompleteInput input(
-      base::ASCIIToUTF16(search_query), metrics::OmniboxEventProto::BLANK,
-      ChromeAutocompleteSchemeClassifier(chrome_test_utils::GetProfile(this)));
-  AutocompleteMatch autocomplete_match = CreateSearchSuggestionMatch(
-      search_query, prerender_query, /*is_prerender_hint=*/true,
-      /*is_prefetch_hint=*/true);
-  AutocompleteResult autocomplete_result;
-  autocomplete_result.AppendMatches({autocomplete_match});
-  search_prefetch_service()->OnResultChanged(GetActiveWebContents(),
-                                             autocomplete_result);
+  ChangeAutocompleteResult(search_query, prerender_query,
+                           PrerenderHint::kEnabled, PrefetchHint::kEnabled);
 
   // The suggestion service should hint expected_prerender_url, and prerendering
   // for this url should start.
@@ -233,10 +281,181 @@
   prerender_helper().WaitForPrerenderLoadCompletion(*GetActiveWebContents(),
                                                     expected_prerender_url);
   // Prefetch should be triggered as well.
-  auto prefetch_status =
+  absl::optional<SearchPrefetchStatus> prefetch_status =
       search_prefetch_service()->GetSearchPrefetchStatusForTesting(
           base::ASCIIToUTF16(prerender_query));
   EXPECT_TRUE(prefetch_status.has_value());
 }
 
+// Tests that the SearchSuggestionService can trigger prerendering if it
+// receives prerender hints after the previous prefetch request succeeds.
+IN_PROC_BROWSER_TEST_F(SearchPreloadUnifiedBrowserTest,
+                       PrerenderHintReceivedAfterSucceed) {
+  base::HistogramTester histogram_tester;
+  const GURL kInitialUrl = embedded_test_server()->GetURL("/empty.html");
+  ASSERT_TRUE(GetActiveWebContents());
+  ASSERT_TRUE(content::NavigateToURL(GetActiveWebContents(), kInitialUrl));
+  SetUpContext();
+
+  std::string search_query_1 = "pre";
+  std::string prerender_query = "prerender";
+  GURL expected_prerender_url =
+      GetSearchUrl(prerender_query, /*attach_prefetch_flag=*/true);
+
+  content::test::PrerenderHostRegistryObserver registry_observer(
+      *GetActiveWebContents());
+
+  ChangeAutocompleteResult(search_query_1, prerender_query,
+                           PrerenderHint::kDisabled, PrefetchHint::kEnabled);
+
+  // Wait until prefetch request succeeds.
+  absl::optional<SearchPrefetchStatus> prefetch_status =
+      search_prefetch_service()->GetSearchPrefetchStatusForTesting(
+          base::ASCIIToUTF16(prerender_query));
+  EXPECT_TRUE(prefetch_status.has_value());
+  WaitUntilStatusChangesTo(
+      base::ASCIIToUTF16(prerender_query),
+      {SearchPrefetchStatus::kCanBeServed, SearchPrefetchStatus::kComplete});
+
+  std::string search_query_2 = "prer";
+  ChangeAutocompleteResult(search_query_2, prerender_query,
+                           PrerenderHint::kEnabled, PrefetchHint::kEnabled);
+
+  // The suggestion service should hint `expected_prerender_url`, and
+  // prerendering for this url should start.
+  registry_observer.WaitForTrigger(expected_prerender_url);
+  prerender_helper().WaitForPrerenderLoadCompletion(*GetActiveWebContents(),
+                                                    expected_prerender_url);
+
+  // Activate.
+  content::TestActivationManager activation_manager(GetActiveWebContents(),
+                                                    expected_prerender_url);
+  GetActiveWebContents()->OpenURL(content::OpenURLParams(
+      expected_prerender_url, content::Referrer(),
+      WindowOpenDisposition::CURRENT_TAB,
+      ui::PageTransitionFromInt(ui::PAGE_TRANSITION_GENERATED |
+                                ui::PAGE_TRANSITION_FROM_ADDRESS_BAR),
+      /*is_renderer_initiated=*/false));
+  activation_manager.WaitForNavigationFinished();
+  EXPECT_TRUE(activation_manager.was_activated());
+}
+
+// Tests that the SearchSuggestionService will not trigger prerender if the
+// prefetch failed.
+IN_PROC_BROWSER_TEST_F(SearchPreloadUnifiedBrowserTest,
+                       FailedPrefetchCannotBeUpgraded) {
+  base::HistogramTester histogram_tester;
+  const GURL kInitialUrl = embedded_test_server()->GetURL("/empty.html");
+  ASSERT_TRUE(GetActiveWebContents());
+  ASSERT_TRUE(content::NavigateToURL(GetActiveWebContents(), kInitialUrl));
+  SetUpContext();
+
+  std::string search_query = "fail";
+  std::string prerender_query = "failed_terms";
+
+  ChangeAutocompleteResult(search_query, prerender_query,
+                           PrerenderHint::kEnabled, PrefetchHint::kEnabled);
+
+  // Prefetch should be triggered, and the prefetch request should fail.
+  absl::optional<SearchPrefetchStatus> prefetch_status =
+      search_prefetch_service()->GetSearchPrefetchStatusForTesting(
+          base::ASCIIToUTF16(prerender_query));
+  EXPECT_TRUE(prefetch_status.has_value());
+  WaitUntilStatusChangesTo(base::ASCIIToUTF16(prerender_query),
+                           {SearchPrefetchStatus::kRequestFailed});
+  EXPECT_FALSE(prerender_manager()->HasSearchResultPagePrerendered());
+}
+
+// Tests that the SearchSuggestionService will not trigger prerender if the
+// suggestions changes before SearchSuggestionService receives a servable
+// response.
+IN_PROC_BROWSER_TEST_F(SearchPreloadUnifiedBrowserTest,
+                       SuggestionChangeBeforeStartPrerender) {
+  base::HistogramTester histogram_tester;
+  const GURL kInitialUrl = embedded_test_server()->GetURL("/empty.html");
+  ASSERT_TRUE(GetActiveWebContents());
+  ASSERT_TRUE(content::NavigateToURL(GetActiveWebContents(), kInitialUrl));
+  SetUpContext();
+
+  // 1. Type the first query.
+  std::string search_query_1 = "hang";
+  std::string prerender_query_1 = "hang_response";
+  GURL expected_prerender_url =
+      GetSearchUrl(prerender_query_1, /*attach_prefetch_flag=*/true);
+  ChangeAutocompleteResult(search_query_1, prerender_query_1,
+                           PrerenderHint::kEnabled, PrefetchHint::kEnabled);
+
+  // 2. Prefetch should be triggered.
+  auto prefetch_status =
+      search_prefetch_service()->GetSearchPrefetchStatusForTesting(
+          base::ASCIIToUTF16(prerender_query_1));
+  EXPECT_TRUE(prefetch_status.has_value());
+  WaitUntilStatusChangesTo(base::ASCIIToUTF16(prerender_query_1),
+                           {SearchPrefetchStatus::kInFlight});
+
+  // 3. Type a different query which results in different suggestions.
+  std::string search_query_2 = "pre";
+  ChangeAutocompleteResult(search_query_2, search_query_2,
+                           PrerenderHint::kDisabled, PrefetchHint::kEnabled);
+
+  // 4. The old prefetch should be cancelled.
+  prefetch_status =
+      search_prefetch_service()->GetSearchPrefetchStatusForTesting(
+          base::ASCIIToUTF16(prerender_query_1));
+  EXPECT_TRUE(prefetch_status.has_value());
+  WaitUntilStatusChangesTo(base::ASCIIToUTF16(prerender_query_1),
+                           {SearchPrefetchStatus::kRequestCancelled});
+
+  EXPECT_FALSE(prerender_manager()->HasSearchResultPagePrerendered());
+}
+
+// Tests prerender is cancelled after SearchPrefetchService cancels prefetch
+// requests.
+IN_PROC_BROWSER_TEST_F(SearchPreloadUnifiedBrowserTest,
+                       SuggestionChangeAfterStartPrerender) {
+  base::HistogramTester histogram_tester;
+  const GURL kInitialUrl = embedded_test_server()->GetURL("/empty.html");
+  ASSERT_TRUE(GetActiveWebContents());
+  ASSERT_TRUE(content::NavigateToURL(GetActiveWebContents(), kInitialUrl));
+  SetUpContext();
+  content::test::PrerenderHostRegistryObserver registry_observer(
+      *GetActiveWebContents());
+
+  // 1. Type the first query.
+  std::string search_query_1 = "hang";
+  std::string prerender_query_1 = "hang_body";
+  GURL expected_prerender_url =
+      GetSearchUrl(prerender_query_1, /*attach_prefetch_flag=*/true);
+  ChangeAutocompleteResult(search_query_1, prerender_query_1,
+                           PrerenderHint::kEnabled, PrefetchHint::kEnabled);
+
+  // 2. Prefetch should be triggered, and chrome is receiving the response body.
+  absl::optional<SearchPrefetchStatus> prefetch_status =
+      search_prefetch_service()->GetSearchPrefetchStatusForTesting(
+          base::ASCIIToUTF16(prerender_query_1));
+  EXPECT_TRUE(prefetch_status.has_value());
+  WaitUntilStatusChangesTo(base::ASCIIToUTF16(prerender_query_1),
+                           {SearchPrefetchStatus::kCanBeServed});
+
+  // 3. prerendering should be triggered.
+  registry_observer.WaitForTrigger(expected_prerender_url);
+  EXPECT_TRUE(prerender_manager()->HasSearchResultPagePrerendered());
+
+  // 4. Type a different query which results in different suggestions.
+  std::string search_query_2 = "pre";
+  ChangeAutocompleteResult(search_query_2, search_query_2,
+                           PrerenderHint::kDisabled, PrefetchHint::kEnabled);
+
+  // 5. The old prefetch should be cancelled.
+  prefetch_status =
+      search_prefetch_service()->GetSearchPrefetchStatusForTesting(
+          base::ASCIIToUTF16(prerender_query_1));
+  EXPECT_TRUE(prefetch_status.has_value());
+  WaitUntilStatusChangesTo(base::ASCIIToUTF16(prerender_query_1),
+                           {SearchPrefetchStatus::kRequestCancelled});
+
+  // 6. Prerender should be cancelled as well.
+  EXPECT_FALSE(prerender_manager()->HasSearchResultPagePrerendered());
+}
+
 }  // namespace
diff --git a/chrome/browser/prefetch/search_prefetch/streaming_search_prefetch_request.cc b/chrome/browser/prefetch/search_prefetch/streaming_search_prefetch_request.cc
index 11bb21e..5c8ceeea 100644
--- a/chrome/browser/prefetch/search_prefetch/streaming_search_prefetch_request.cc
+++ b/chrome/browser/prefetch/search_prefetch/streaming_search_prefetch_request.cc
@@ -11,10 +11,12 @@
 #include "net/base/load_flags.h"
 
 StreamingSearchPrefetchRequest::StreamingSearchPrefetchRequest(
+    const std::u16string& prefetch_search_terms,
     const GURL& prefetch_url,
     bool navigation_prefetch,
     base::OnceCallback<void(bool)> report_error_callback)
-    : BaseSearchPrefetchRequest(prefetch_url,
+    : BaseSearchPrefetchRequest(prefetch_search_terms,
+                                prefetch_url,
                                 navigation_prefetch,
                                 std::move(report_error_callback)) {}
 
diff --git a/chrome/browser/prefetch/search_prefetch/streaming_search_prefetch_request.h b/chrome/browser/prefetch/search_prefetch/streaming_search_prefetch_request.h
index 51e8620..9b9a655 100644
--- a/chrome/browser/prefetch/search_prefetch/streaming_search_prefetch_request.h
+++ b/chrome/browser/prefetch/search_prefetch/streaming_search_prefetch_request.h
@@ -26,6 +26,7 @@
 class StreamingSearchPrefetchRequest : public BaseSearchPrefetchRequest {
  public:
   StreamingSearchPrefetchRequest(
+      const std::u16string& prefetch_search_terms,
       const GURL& prefetch_url,
       bool navigation_prefetch,
       base::OnceCallback<void(bool)> report_error_callback);
diff --git a/chrome/browser/prefetch/search_prefetch/streaming_search_prefetch_url_loader.cc b/chrome/browser/prefetch/search_prefetch/streaming_search_prefetch_url_loader.cc
index f10594b..4d1da86 100644
--- a/chrome/browser/prefetch/search_prefetch/streaming_search_prefetch_url_loader.cc
+++ b/chrome/browser/prefetch/search_prefetch/streaming_search_prefetch_url_loader.cc
@@ -104,6 +104,14 @@
   streaming_prefetch_request_->MarkPrefetchAsServable();
 }
 
+void StreamingSearchPrefetchURLLoader::OnServableResponseCodeReceived() {
+  // This means that the navigation stack is already running for the navigation
+  // to this term, and chrome does not need to prerender.
+  if (!streaming_prefetch_request_)
+    return;
+  streaming_prefetch_request_->OnServableResponseCodeReceived();
+}
+
 SearchPrefetchURLLoader::RequestHandler
 StreamingSearchPrefetchURLLoader::ServingResponseHandlerImpl(
     std::unique_ptr<SearchPrefetchURLLoader> loader) {
@@ -244,6 +252,7 @@
   }
 
   MarkPrefetchAsServable();
+  OnServableResponseCodeReceived();
 
   // Store head and pause new messages until the forwarding client is set up.
   resource_response_ = std::move(head);
diff --git a/chrome/browser/prefetch/search_prefetch/streaming_search_prefetch_url_loader.h b/chrome/browser/prefetch/search_prefetch/streaming_search_prefetch_url_loader.h
index 13f6908..b84a83f 100644
--- a/chrome/browser/prefetch/search_prefetch/streaming_search_prefetch_url_loader.h
+++ b/chrome/browser/prefetch/search_prefetch/streaming_search_prefetch_url_loader.h
@@ -151,6 +151,9 @@
   // Marks the parent prefetch request as servable. Called as delayed task.
   void MarkPrefetchAsServable();
 
+  // Called on `this` receives servable response.
+  void OnServableResponseCodeReceived();
+
   // The network URLLoader that fetches the prefetch URL and its receiver.
   mojo::Remote<network::mojom::URLLoader> network_url_loader_;
   mojo::Receiver<network::mojom::URLLoaderClient> url_loader_receiver_{this};
diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc
index 1d873735..92a3788a 100644
--- a/chrome/browser/prefs/browser_prefs.cc
+++ b/chrome/browser/prefs/browser_prefs.cc
@@ -168,6 +168,7 @@
 #if BUILDFLAG(ENABLE_EXTENSIONS)
 #include "chrome/browser/accessibility/animation_policy_prefs.h"
 #include "chrome/browser/apps/platform_apps/shortcut_manager.h"
+#include "chrome/browser/ash/system_web_apps/system_web_app_manager.h"
 #include "chrome/browser/extensions/activity_log/activity_log.h"
 #include "chrome/browser/extensions/api/commands/command_service.h"
 #include "chrome/browser/extensions/api/cryptotoken_private/cryptotoken_private_api.h"
@@ -1320,6 +1321,7 @@
   extensions::RegisterSettingsOverriddenUiPrefs(registry);
   update_client::RegisterProfilePrefs(registry);
   web_app::WebAppProvider::RegisterProfilePrefs(registry);
+  ash::SystemWebAppManager::RegisterProfilePrefs(registry);
 #endif  // BUILDFLAG(ENABLE_EXTENSIONS)
 
 #if BUILDFLAG(ENABLE_OFFLINE_PAGES)
diff --git a/chrome/browser/prefs/chrome_command_line_pref_store.cc b/chrome/browser/prefs/chrome_command_line_pref_store.cc
index 3fdef6b..6543e497 100644
--- a/chrome/browser/prefs/chrome_command_line_pref_store.cc
+++ b/chrome/browser/prefs/chrome_command_line_pref_store.cc
@@ -47,6 +47,7 @@
         {switches::kAuthServerAllowlist, prefs::kAuthServerAllowlist},
         {switches::kSSLVersionMin, prefs::kSSLVersionMin},
         {switches::kSSLVersionMax, prefs::kSSLVersionMax},
+        {switches::kWebRtcIPHandlingPolicy, prefs::kWebRTCIPHandlingPolicy},
 #if BUILDFLAG(IS_ANDROID)
         {switches::kAuthAndroidNegotiateAccountType,
          prefs::kAuthAndroidNegotiateAccountType},
diff --git a/chrome/browser/prerender/prerender_manager.cc b/chrome/browser/prerender/prerender_manager.cc
index f5ba6da0..8af7e4c 100644
--- a/chrome/browser/prerender/prerender_manager.cc
+++ b/chrome/browser/prerender/prerender_manager.cc
@@ -332,26 +332,11 @@
   TemplateURLRef::SearchTermsArgs& search_terms_args =
       *(match.search_terms_args);
   const std::u16string& search_terms = search_terms_args.search_terms;
-  // Do not re-prerender the same search result.
-  if (search_prerender_task_) {
-    // TODO(https://crbug.com/1278634): re-prerender the search result if the
-    // prerendered content has been removed.
-    if (search_prerender_task_->prerendered_search_terms() == search_terms) {
-      return;
-    }
 
-    base::UmaHistogramEnumeration(
-        internal::kHistogramPrerenderPredictionStatusDefaultSearchEngine,
-        PrerenderPredictionStatus::kCancelled);
-    search_prerender_task_.reset();
-  }
-
-  // web_contents() owns the instance that stores this callback, so it is safe
-  // to call std::ref.
-  base::RepeatingCallback<bool(const GURL&)> url_match_predicate =
-      base::BindRepeating(&IsSearchDestinationMatch,
-                          search_terms_args.search_terms,
-                          std::ref(*web_contents()));
+  // If the caller does not want to prerender a new result, this does not need
+  // to do anything.
+  if (!ResetSearchPrerenderTaskIfNecessary(search_terms))
+    return;
 
   GURL prerender_url = match.destination_url;
   // Skip changing the prerender URL in tests as they may not have Profile or
@@ -386,17 +371,30 @@
     }
     DCHECK(!search_terms_args.is_prefetch);
   }
-  std::unique_ptr<content::PrerenderHandle> prerender_handle =
-      web_contents()->StartPrerendering(
-          prerender_url, content::PrerenderTriggerType::kEmbedder,
-          prerender_utils::kDefaultSearchEngineMetricSuffix,
-          ui::PageTransitionFromInt(ui::PAGE_TRANSITION_GENERATED |
-                                    ui::PAGE_TRANSITION_FROM_ADDRESS_BAR),
-          std::move(url_match_predicate));
 
-  if (prerender_handle) {
-    search_prerender_task_ = std::make_unique<SearchPrerenderTask>(
-        search_terms, std::move(prerender_handle));
+  StartPrerenderSearchResultInternal(search_terms, prerender_url);
+}
+
+void PrerenderManager::StartPrerenderSearchResult(
+    const std::u16string& search_terms,
+    const GURL& prerendering_url) {
+  DCHECK(SearchPrefetchUpgradeToPrerenderIsEnabled());
+
+  // If the caller does not want to prerender a new result, this does not need
+  // to do anything.
+  if (!ResetSearchPrerenderTaskIfNecessary(search_terms))
+    return;
+  StartPrerenderSearchResultInternal(search_terms, prerendering_url);
+}
+
+void PrerenderManager::StopPrerenderSearchResult(
+    const std::u16string& search_terms) {
+  if (search_prerender_task_ &&
+      search_prerender_task_->prerendered_search_terms() == search_terms) {
+    // TODO(https://crbug.com/1295170): Record
+    // PrerenderPredictionStatus::kCancelled here. And double check if we update
+    // kNotStarted.
+    search_prerender_task_.reset();
   }
 }
 
@@ -404,6 +402,10 @@
   return !!search_prerender_task_;
 }
 
+base::WeakPtr<PrerenderManager> PrerenderManager::GetWeakPtr() {
+  return weak_factory_.GetWeakPtr();
+}
+
 const std::u16string PrerenderManager::GetPrerenderSearchTermForTesting()
     const {
   return search_prerender_task_
@@ -462,6 +464,48 @@
   }
 }
 
+bool PrerenderManager::ResetSearchPrerenderTaskIfNecessary(
+    const std::u16string& search_terms) {
+  if (!search_prerender_task_)
+    return true;
+
+  // Do not re-prerender the same search result.
+  // TODO(https://crbug.com/1278634): re-prerender the search result if the
+  // prerendered content has been removed.
+  if (search_prerender_task_->prerendered_search_terms() == search_terms) {
+    return false;
+  }
+
+  base::UmaHistogramEnumeration(
+      internal::kHistogramPrerenderPredictionStatusDefaultSearchEngine,
+      PrerenderPredictionStatus::kCancelled);
+  search_prerender_task_.reset();
+  return true;
+}
+
+void PrerenderManager::StartPrerenderSearchResultInternal(
+    const std::u16string& search_terms,
+    const GURL& prerendering_url) {
+  // web_contents() owns the instance that stores this callback, so it is safe
+  // to call std::ref.
+  base::RepeatingCallback<bool(const GURL&)> url_match_predicate =
+      base::BindRepeating(&IsSearchDestinationMatch, search_terms,
+                          std::ref(*web_contents()));
+
+  std::unique_ptr<content::PrerenderHandle> prerender_handle =
+      web_contents()->StartPrerendering(
+          prerendering_url, content::PrerenderTriggerType::kEmbedder,
+          prerender_utils::kDefaultSearchEngineMetricSuffix,
+          ui::PageTransitionFromInt(ui::PAGE_TRANSITION_GENERATED |
+                                    ui::PAGE_TRANSITION_FROM_ADDRESS_BAR),
+          std::move(url_match_predicate));
+
+  if (prerender_handle) {
+    search_prerender_task_ = std::make_unique<SearchPrerenderTask>(
+        search_terms, std::move(prerender_handle));
+  }
+}
+
 PrerenderManager::PrerenderManager(content::WebContents* web_contents)
     : content::WebContentsObserver(web_contents),
       content::WebContentsUserData<PrerenderManager>(*web_contents) {}
diff --git a/chrome/browser/prerender/prerender_manager.h b/chrome/browser/prerender/prerender_manager.h
index a3f0499..70c269128 100644
--- a/chrome/browser/prerender/prerender_manager.h
+++ b/chrome/browser/prerender/prerender_manager.h
@@ -5,6 +5,8 @@
 #ifndef CHROME_BROWSER_PRERENDER_PRERENDER_MANAGER_H_
 #define CHROME_BROWSER_PRERENDER_PRERENDER_MANAGER_H_
 
+#include <string>
+
 #include "components/omnibox/browser/autocomplete_match.h"
 #include "content/public/browser/prerender_handle.h"
 #include "content/public/browser/web_contents.h"
@@ -52,11 +54,23 @@
   void DidFinishNavigation(
       content::NavigationHandle* navigation_handle) override;
 
-  // The entry of prerender.
-  // Calling this method will lead to the cancellation of the previous prerender
-  // if the given `match`'s search terms differ from the ongoing one's.
+  // The entry of Default Search Engine prerender. Calling this method will lead
+  // to the cancellation of the previous prerender if the given `match`'s search
+  // terms differs from the ongoing one's.
+  // TODO(https://crbug.com/1295170): Remove this method after Search prerender
+  // work properly with Search prefetch.
   void StartPrerenderSearchSuggestion(const AutocompleteMatch& match);
 
+  // Calling this method will lead to the cancellation of the previous prerender
+  // if the given `search_terms` differs from the ongoing one's.
+  void StartPrerenderSearchResult(const std::u16string& search_terms,
+                                  const GURL& prerendering_url);
+
+  // Cancels the prerender that is prerendering the given `search_terms`.
+  // TODO(https://crbug.com/1295170): Use the creator's address to identify the
+  // owner that can cancels the corresponding prerendering?
+  void StopPrerenderSearchResult(const std::u16string& search_terms);
+
   // The entry of direct url input prerender.
   // Calling this method will return WeakPtr of the started prerender, and lead
   // to the cancellation of the previous prerender if the given url is different
@@ -71,6 +85,8 @@
   // inputs.
   bool HasSearchResultPagePrerendered() const;
 
+  base::WeakPtr<PrerenderManager> GetWeakPtr();
+
   // Returns the prerendered search terms if search_prerender_task_ exists.
   // Returns empty string otherwise.
   const std::u16string GetPrerenderSearchTermForTesting() const;
@@ -88,6 +104,19 @@
   void ResetPrerenderHandlesOnPrimaryPageChanged(
       content::NavigationHandle* navigation_handle);
 
+  // Maybe cancel the ongoing search prerender to restart a new one if this
+  // finds the callers' intentions changed. The number of concurrence search
+  // prerender is limited to 1, so it is needed to cancel the old one in order
+  // to start a new one. Returns true if this finds the caller wants to
+  // prerender another search result.
+  bool ResetSearchPrerenderTaskIfNecessary(const std::u16string& search_terms);
+
+  void StartPrerenderSearchResultInternal(const std::u16string& search_terms,
+                                          const GURL& prerendering_url);
+
+  // Stops search prefetch from being upgraded to prerender.
+  void UnregisterSearchPrerender();
+
   // Stores the prerender which serves for search results. It is responsible for
   // tracking a started search prerender, and it keeps alive even if the
   // prerender has been destroyed by the timer. With its help, PrerenderManager
@@ -98,6 +127,8 @@
 
   bool skip_template_url_service_for_testing_ = false;
 
+  base::WeakPtrFactory<PrerenderManager> weak_factory_{this};
+
   WEB_CONTENTS_USER_DATA_KEY_DECL();
 };
 
diff --git a/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc b/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc
index c474322..16b1c4c 100644
--- a/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc
+++ b/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc
@@ -202,6 +202,7 @@
 #include "apps/browser_context_keyed_service_factories.h"
 #include "chrome/browser/apps/platform_apps/api/browser_context_keyed_service_factories.h"
 #include "chrome/browser/apps/platform_apps/browser_context_keyed_service_factories.h"
+#include "chrome/browser/ash/system_web_apps/system_web_app_manager_factory.h"
 #include "chrome/browser/extensions/browser_context_keyed_service_factories.h"
 #include "chrome/browser/ui/web_applications/web_app_metrics_factory.h"
 #include "chrome/browser/web_applications/adjustments/web_app_adjustments.h"
@@ -579,6 +580,10 @@
   web_app::WebAppMetricsFactory::GetInstance();
   web_app::WebAppProviderFactory::GetInstance();
   web_app::WebAppAdjustmentsFactory::GetInstance();
+  // TODO(crbug.com/1321984): SystemWebAppManagerFactory is instantiated on WML
+  // and ChromeOS currently. Move it under the IS_CHROMEOS_ASH guard (compile it
+  // out).
+  ash::SystemWebAppManagerFactory::GetInstance();
 #endif
   WebDataServiceFactory::GetInstance();
   webrtc_event_logging::WebRtcEventLogManagerKeyedServiceFactory::GetInstance();
diff --git a/chrome/browser/resources/app_service_internals/app_service_internals.ts b/chrome/browser/resources/app_service_internals/app_service_internals.ts
index 6562e8e..540e2fc 100644
--- a/chrome/browser/resources/app_service_internals/app_service_internals.ts
+++ b/chrome/browser/resources/app_service_internals/app_service_internals.ts
@@ -24,10 +24,10 @@
   }
 
   /** List containing debug information for all installed apps. */
-  appList_: Array<AppInfo> = [];
-  hashChangeListener_ = () => this.onHashChanged_();
+  private appList_: Array<AppInfo> = [];
+  private hashChangeListener_ = () => this.onHashChanged_();
   /** List containing preferred app debug information for installed apps. */
-  preferredAppList_: Array<PreferredAppInfo> = [];
+  private preferredAppList_: Array<PreferredAppInfo> = [];
 
   override ready() {
     super.ready();
diff --git a/chrome/browser/resources/chromeos/assistant_optin/assistant_related_info.js b/chrome/browser/resources/chromeos/assistant_optin/assistant_related_info.js
index e7786e6..3a317e37 100644
--- a/chrome/browser/resources/chromeos/assistant_optin/assistant_related_info.js
+++ b/chrome/browser/resources/chromeos/assistant_optin/assistant_related_info.js
@@ -90,7 +90,7 @@
      * @private {string}
      */
     this.urlTemplate_ =
-        'https://www.gstatic.com/opa-android/oobe/a02187e41eed9e42/v3_omni_$.html';
+        'https://www.gstatic.com/opa-android/oobe/a02187e41eed9e42/v5_omni_$.html';
 
     /**
      * Whether try to reload with the default url when a 404 error occurred.
@@ -231,6 +231,13 @@
     if (this.consentStringLoaded_) {
       this.onPageLoaded();
     }
+
+    // The webview animation only starts playing when it is focused (in order
+    // to make sure the animation and the caption are in sync).
+    this.webview_.focus();
+    this.async(() => {
+      this.$['next-button'].focus();
+    }, 300);
   }
 
   /**
diff --git a/chrome/browser/resources/chromeos/assistant_optin/assistant_value_prop.html b/chrome/browser/resources/chromeos/assistant_optin/assistant_value_prop.html
index ebbae58..f4647b2 100644
--- a/chrome/browser/resources/chromeos/assistant_optin/assistant_value_prop.html
+++ b/chrome/browser/resources/chromeos/assistant_optin/assistant_value_prop.html
@@ -76,7 +76,7 @@
       </iron-icon>
       <h1 slot="title" id="title-text"></h1>
       <div id="animation-container">
-          <webview id="value-prop-view" tabindex="-1"></webview>
+        <webview id="value-prop-view" tabindex="-1"></webview>
       </div>
       <div slot="content" id="content-container"
           class="landscape-vertical-centered">
diff --git a/chrome/browser/resources/chromeos/assistant_optin/assistant_value_prop.js b/chrome/browser/resources/chromeos/assistant_optin/assistant_value_prop.js
index 2911a8c3..9ff6882 100644
--- a/chrome/browser/resources/chromeos/assistant_optin/assistant_value_prop.js
+++ b/chrome/browser/resources/chromeos/assistant_optin/assistant_value_prop.js
@@ -53,7 +53,7 @@
        */
       urlTemplate_: {
         value:
-            'https://www.gstatic.com/opa-android/oobe/a02187e41eed9e42/v4_omni_$.html',
+            'https://www.gstatic.com/opa-android/oobe/a02187e41eed9e42/v5_omni_$.html',
       },
 
       /**
diff --git a/chrome/browser/resources/extensions/activity_log/activity_log.ts b/chrome/browser/resources/extensions/activity_log/activity_log.ts
index aa19c42..dfaa4e7 100644
--- a/chrome/browser/resources/extensions/activity_log/activity_log.ts
+++ b/chrome/browser/resources/extensions/activity_log/activity_log.ts
@@ -95,7 +95,7 @@
   extensionInfo: chrome.developerPrivate.ExtensionInfo|
       ActivityLogExtensionPlaceholder;
   delegate: ActivityLogDelegate;
-  selectedSubpage_: ActivityLogSubpage;
+  private selectedSubpage_: ActivityLogSubpage;
   private tabNames_: string[];
 
   override ready() {
diff --git a/chrome/browser/resources/new_tab_page/modules/cart/module.html b/chrome/browser/resources/new_tab_page/modules/cart/module.html
index 528a76bc..2a3dda5a 100644
--- a/chrome/browser/resources/new_tab_page/modules/cart/module.html
+++ b/chrome/browser/resources/new_tab_page/modules/cart/module.html
@@ -82,7 +82,7 @@
     width: 0;
   }
 
-  :host([discount-consent-visible_]) #consentContainer {
+  :host([discount-consent-visible]) #consentContainer {
     opacity: 1;
     width: 254px;
   }
diff --git a/chrome/browser/resources/new_tab_page/modules/cart/module.ts b/chrome/browser/resources/new_tab_page/modules/cart/module.ts
index 93694944..d7a5f26 100644
--- a/chrome/browser/resources/new_tab_page/modules/cart/module.ts
+++ b/chrome/browser/resources/new_tab_page/modules/cart/module.ts
@@ -93,7 +93,7 @@
           {type: Array, computed: 'computeFirstThreeCartItems_(cartItems)'},
 
       /** This is used for animation when the consent become invisible. */
-      discountConsentVisible_: {
+      discountConsentVisible: {
         type: Boolean,
         reflectToAttribute: true,
       }
@@ -104,8 +104,8 @@
   headerChipText: string;
   headerDescriptionText: string;
   showDiscountConsent: boolean;
-  discountConsentVisible_: boolean;
   scrollBehavior: ScrollBehavior = 'smooth';
+  discountConsentVisible: boolean;
   private showLeftScrollButton_: boolean;
   private showRightScrollButton_: boolean;
   private cartMenuHideItem_: string;
@@ -421,7 +421,7 @@
   }
 
   private onDiscountConsentHidden_() {
-    if (this.showDiscountConsent && !this.discountConsentVisible_ &&
+    if (this.showDiscountConsent && !this.discountConsentVisible &&
         this.consentStatus_ !== undefined) {
       this.showDiscountConsent = false;
       switch (this.consentStatus_) {
@@ -452,7 +452,7 @@
 
   private onDiscountConsentRejected_() {
     this.consentStatus_ = ConsentStatus.REJECTED;
-    this.discountConsentVisible_ = false;
+    this.discountConsentVisible = false;
     ChromeCartProxy.getHandler().onDiscountConsentAcknowledged(false);
     chrome.metricsPrivate.recordUserAction(
         'NewTabPage.Carts.RejectDiscountConsent');
@@ -460,7 +460,7 @@
 
   private onDiscountConsentAccepted_() {
     this.consentStatus_ = ConsentStatus.ACCEPTED;
-    this.discountConsentVisible_ = false;
+    this.discountConsentVisible = false;
     ChromeCartProxy.getHandler().onDiscountConsentAcknowledged(true);
     chrome.metricsPrivate.recordUserAction(
         'NewTabPage.Carts.AcceptDiscountConsent');
@@ -468,7 +468,7 @@
 
   private onDiscountConsentDismissed_() {
     this.consentStatus_ = ConsentStatus.DISMISSED;
-    this.discountConsentVisible_ = false;
+    this.discountConsentVisible = false;
     ChromeCartProxy.getHandler().onDiscountConsentDismissed();
     chrome.metricsPrivate.recordUserAction(
         'NewTabPage.Carts.DismissDiscountConsent');
@@ -556,7 +556,7 @@
   }
   element.cartItems = carts;
   element.showDiscountConsent = consentVisible;
-  element.discountConsentVisible_ = consentVisible;
+  element.discountConsentVisible = consentVisible;
   return element;
 }
 
diff --git a/chrome/browser/resources/print_preview/ui/destination_list_item.ts b/chrome/browser/resources/print_preview/ui/destination_list_item.ts
index f0e97acb..0fc85b6c 100644
--- a/chrome/browser/resources/print_preview/ui/destination_list_item.ts
+++ b/chrome/browser/resources/print_preview/ui/destination_list_item.ts
@@ -29,9 +29,7 @@
   static get properties() {
     return {
       destination: Object,
-
       searchQuery: Object,
-
       searchHint_: String,
     };
   }
@@ -46,7 +44,7 @@
 
   destination: Destination;
   searchQuery: RegExp|null;
-  destinationIcon_: string;
+  private destinationIcon_: string;
   private searchHint_: string;
 
   private highlights_: Node[] = [];
diff --git a/chrome/browser/resources/print_preview/ui/destination_list_item_cros.ts b/chrome/browser/resources/print_preview/ui/destination_list_item_cros.ts
index 5f4f037..4683d14 100644
--- a/chrome/browser/resources/print_preview/ui/destination_list_item_cros.ts
+++ b/chrome/browser/resources/print_preview/ui/destination_list_item_cros.ts
@@ -43,9 +43,7 @@
   static get properties() {
     return {
       destination: Object,
-
       searchQuery: Object,
-
       searchHint_: String,
 
       destinationIcon_: {
@@ -96,7 +94,7 @@
 
   destination: Destination;
   searchQuery: RegExp|null;
-  destinationIcon_: string;
+  private destinationIcon_: string;
   private searchHint_: string;
   private statusText_: string;
   private isDarkModeActive_: boolean;
diff --git a/chrome/browser/resources/settings/people_page/manage_profile.ts b/chrome/browser/resources/settings/people_page/manage_profile.ts
index 8d5b3ad..e22b5ca 100644
--- a/chrome/browser/resources/settings/people_page/manage_profile.ts
+++ b/chrome/browser/resources/settings/people_page/manage_profile.ts
@@ -112,7 +112,7 @@
   availableIcons: Array<AvatarIcon>;
   syncStatus: SyncStatus|null;
   private isProfileShortcutSettingVisible_: boolean;
-  pattern_: string;
+  private pattern_: string;
   private browserProxy_: ManageProfileBrowserProxy =
       ManageProfileBrowserProxyImpl.getInstance();
 
diff --git a/chrome/browser/resources/settings/people_page/sync_account_control.ts b/chrome/browser/resources/settings/people_page/sync_account_control.ts
index 068713a..09f83a40 100644
--- a/chrome/browser/resources/settings/people_page/sync_account_control.ts
+++ b/chrome/browser/resources/settings/people_page/sync_account_control.ts
@@ -146,8 +146,8 @@
   promoLabelWithNoAccount: string;
   promoSecondaryLabelWithAccount: string;
   promoSecondaryLabelWithNoAccount: string;
-  signedIn_: boolean;
-  storedAccounts_: Array<StoredAccount>;
+  private signedIn_: boolean;
+  private storedAccounts_: Array<StoredAccount>;
   private shownAccount_: StoredAccount|null;
   showingPromo: boolean;
   embeddedInSubpage: boolean;
diff --git a/chrome/browser/resources/settings/prefs/prefs_types.ts b/chrome/browser/resources/settings/prefs/prefs_types.ts
index ec00d9e..69158f0 100644
--- a/chrome/browser/resources/settings/prefs/prefs_types.ts
+++ b/chrome/browser/resources/settings/prefs/prefs_types.ts
@@ -11,7 +11,7 @@
 class CrSettingsPrefsInternal {
   isInitialized: boolean = false;
   deferInitialization: boolean;
-  initializedResolver_: PromiseResolver<void> = new PromiseResolver();
+  private initializedResolver_: PromiseResolver<void> = new PromiseResolver();
 
   constructor() {
     /**
diff --git a/chrome/browser/resources/settings/privacy_sandbox/app.html b/chrome/browser/resources/settings/privacy_sandbox/app.html
index 01d52ef..a80635f 100644
--- a/chrome/browser/resources/settings/privacy_sandbox/app.html
+++ b/chrome/browser/resources/settings/privacy_sandbox/app.html
@@ -229,12 +229,12 @@
     </div>
     <template is="dom-if"
         if="[[!showFragment_(privacySandboxSettingsViewEnum_.MAIN,
-                privacySandboxSettingsView_)]]" restamp>
+                privacySandboxSettingsView)]]" restamp>
       <cr-dialog id="dialogWrapper" show-on-attach on-close="onDialogClose_">
         <template id="learnMoreDialog" is="dom-if"
             if="[[showFragment_(
                     privacySandboxSettingsViewEnum_.LEARN_MORE_DIALOG,
-                    privacySandboxSettingsView_)]]">
+                    privacySandboxSettingsView)]]">
           <div slot="title">$i18n{privacySandboxLearnMoreDialogTitle}</div>
           <div slot="body">
             <div class="learn-more-section-title">
@@ -292,7 +292,7 @@
         <template id="adPersonalizationDialog" is="dom-if"
             if="[[showFragment_(
                     privacySandboxSettingsViewEnum_.AD_PERSONALIZATION_DIALOG,
-                    privacySandboxSettingsView_)]]">
+                    privacySandboxSettingsView)]]">
           <div class="ad-personalization-title" slot="title">
             $i18n{privacySandboxAdPersonalizationDialogTitle}
           </div>
@@ -384,7 +384,7 @@
         <template id="adPersonalizationRemovedDialog" is="dom-if"
             if="[[showFragment_(
                     privacySandboxSettingsViewEnum_.AD_PERSONALIZATION_REMOVED_DIALOG,
-                    privacySandboxSettingsView_)]]">
+                    privacySandboxSettingsView)]]">
           <div class="ad-personalization-title" slot="title">
             <cr-icon-button id="adPersonalizationBackButton"
                 class="icon-arrow-back" aria-label="$i18n{back}"
@@ -433,7 +433,7 @@
         <template id="adMeasurementDialog" is="dom-if"
             if="[[showFragment_(
                     privacySandboxSettingsViewEnum_.AD_MEASUREMENT_DIALOG,
-                    privacySandboxSettingsView_)]]">
+                    privacySandboxSettingsView)]]">
           <div slot="title">$i18n{privacySandboxAdMeasurementDialogTitle}</div>
           <div slot="body">
             <div class="dialog-description">
@@ -449,7 +449,7 @@
         <template id="spamAndFraudDialog" is="dom-if"
             if="[[showFragment_(
                     privacySandboxSettingsViewEnum_.SPAM_AND_FRAUD_DIALOG,
-                    privacySandboxSettingsView_)]]">
+                    privacySandboxSettingsView)]]">
           <div slot="title">$i18n{privacySandboxSpamAndFraudDialogTitle}</div>
           <div slot="body">
             <div class="dialog-description">
diff --git a/chrome/browser/resources/settings/privacy_sandbox/app.ts b/chrome/browser/resources/settings/privacy_sandbox/app.ts
index ff4f1af..907cf8e8 100644
--- a/chrome/browser/resources/settings/privacy_sandbox/app.ts
+++ b/chrome/browser/resources/settings/privacy_sandbox/app.ts
@@ -58,7 +58,7 @@
       },
 
       /** The current view. */
-      privacySandboxSettingsView_: {
+      privacySandboxSettingsView: {
         type: String,
         value: PrivacySandboxSettingsView.MAIN,
       },
@@ -108,7 +108,7 @@
   private privacySandboxBrowserProxy_: PrivacySandboxBrowserProxy =
       PrivacySandboxBrowserProxyImpl.getInstance();
   private privacySandboxSettings3Enabled_: boolean;
-  privacySandboxSettingsView_: PrivacySandboxSettingsView;
+  privacySandboxSettingsView: PrivacySandboxSettingsView;
   private topTopics_: Array<PrivacySandboxInterest>;
   private blockedTopics_: Array<PrivacySandboxInterest>;
   private joiningSites_: Array<PrivacySandboxInterest>;
@@ -153,10 +153,10 @@
       const view = new URLSearchParams(window.location.search).get('view');
       if (Object.values(PrivacySandboxSettingsView)
               .includes(view as PrivacySandboxSettingsView)) {
-        this.privacySandboxSettingsView_ = view as PrivacySandboxSettingsView;
+        this.privacySandboxSettingsView = view as PrivacySandboxSettingsView;
       } else {
         // If no view has been specified, then navigate to main page.
-        this.privacySandboxSettingsView_ = PrivacySandboxSettingsView.MAIN;
+        this.privacySandboxSettingsView = PrivacySandboxSettingsView.MAIN;
       }
     });
 
@@ -200,15 +200,15 @@
   }
 
   private showFragment_(view: PrivacySandboxSettingsView): boolean {
-    return this.privacySandboxSettingsView_ === view;
+    return this.privacySandboxSettingsView === view;
   }
 
   private onDialogClose_() {
     // This function will only be called once, regardless of how the dialog is
     // shut (either via ESC or via the button), as in the latter the dialog is
     // not "closed", but rather removed from the DOM.
-    const lastView = this.privacySandboxSettingsView_;
-    this.privacySandboxSettingsView_ = PrivacySandboxSettingsView.MAIN;
+    const lastView = this.privacySandboxSettingsView;
+    this.privacySandboxSettingsView = PrivacySandboxSettingsView.MAIN;
     afterNextRender(this, async () => {
       switch (lastView) {
         case PrivacySandboxSettingsView.LEARN_MORE_DIALOG:
@@ -238,14 +238,14 @@
     e.stopPropagation();
     this.metricsBrowserProxy_.recordAction(
         'Settings.PrivacySandbox.AdPersonalization.LearnMoreClicked');
-    this.privacySandboxSettingsView_ =
+    this.privacySandboxSettingsView =
         PrivacySandboxSettingsView.LEARN_MORE_DIALOG;
   }
 
   private onAdPersonalizationRowClick_() {
     this.metricsBrowserProxy_.recordAction(
         'Settings.PrivacySandbox.AdPersonalization.Opened');
-    this.privacySandboxSettingsView_ =
+    this.privacySandboxSettingsView =
         PrivacySandboxSettingsView.AD_PERSONALIZATION_DIALOG;
   }
 
@@ -265,19 +265,19 @@
   private onAdPersonalizationRemovedRowClick_() {
     this.metricsBrowserProxy_.recordAction(
         'Settings.PrivacySandbox.RemovedInterests.Opened');
-    this.privacySandboxSettingsView_ =
+    this.privacySandboxSettingsView =
         PrivacySandboxSettingsView.AD_PERSONALIZATION_REMOVED_DIALOG;
   }
 
   private onAdPersonalizationBackButtonClick_() {
-    this.privacySandboxSettingsView_ =
+    this.privacySandboxSettingsView =
         PrivacySandboxSettingsView.AD_PERSONALIZATION_DIALOG;
   }
 
   private onAdMeasurementRowClick_() {
     this.metricsBrowserProxy_.recordAction(
         'Settings.PrivacySandbox.AdMeasurement.Opened');
-    this.privacySandboxSettingsView_ =
+    this.privacySandboxSettingsView =
         PrivacySandboxSettingsView.AD_MEASUREMENT_DIALOG;
   }
 
@@ -291,7 +291,7 @@
   private onSpamAndFraudRowClick_() {
     this.metricsBrowserProxy_.recordAction(
         'Settings.PrivacySandbox.SpamFraud.Opened');
-    this.privacySandboxSettingsView_ =
+    this.privacySandboxSettingsView =
         PrivacySandboxSettingsView.SPAM_AND_FRAUD_DIALOG;
   }
 
diff --git a/chrome/browser/resources/settings/search_page/search_page.ts b/chrome/browser/resources/settings/search_page/search_page.ts
index c296603..df7721b 100644
--- a/chrome/browser/resources/settings/search_page/search_page.ts
+++ b/chrome/browser/resources/settings/search_page/search_page.ts
@@ -74,7 +74,7 @@
   }
 
   prefs: Object;
-  searchEnginesPageTitle_: String;
+  private searchEnginesPageTitle_: String;
   private isActiveSearchEnginesFlagEnabled_: boolean;
   private searchEngines_: Array<SearchEngine>;
   private searchEnginesFilter_: string;
diff --git a/chrome/browser/resources/signin/profile_customization/profile_customization_app.html b/chrome/browser/resources/signin/profile_customization/profile_customization_app.html
index fdadeb4..b63bcbb 100644
--- a/chrome/browser/resources/signin/profile_customization/profile_customization_app.html
+++ b/chrome/browser/resources/signin/profile_customization/profile_customization_app.html
@@ -157,11 +157,11 @@
   </cr-input>
 
   <div id="pickThemeContainer">
-    <cr-customize-themes id="themeSelector" auto-confirm-theme-changes>
+    <cr-customize-themes id="themeSelector">
     </cr-customize-themes>
   </div>
 
-  <div class$="action-container 
+  <div class$="action-container
       [[getDialogDesignClass_(profileCustomizationInDialogDesign_)]]">
     <cr-button id="doneButton" class="action-button"
         disabled="[[isDoneButtonDisabled_(profileName_)]]"
@@ -169,8 +169,8 @@
       $i18n{profileCustomizationDoneLabel}
     </cr-button>
     <template is="dom-if" if="[[profileCustomizationInDialogDesign_]]">
-      <!-- TODO: add action on Skip -->
-      <cr-button id="skipButton" class="cancel-button">
+      <cr-button id="skipButton" class="cancel-button"
+          on-click="onSkipCustomizationClicked_">
         $i18n{profileCustomizationSkipLabel}
       </cr-button>
     </template>
diff --git a/chrome/browser/resources/signin/profile_customization/profile_customization_app.ts b/chrome/browser/resources/signin/profile_customization/profile_customization_app.ts
index e48e05b..5dd83f51 100644
--- a/chrome/browser/resources/signin/profile_customization/profile_customization_app.ts
+++ b/chrome/browser/resources/signin/profile_customization/profile_customization_app.ts
@@ -12,6 +12,7 @@
 import './signin_shared.css.js';
 import './signin_vars.css.js';
 
+import {CustomizeThemesElement} from 'chrome://resources/cr_components/customize_themes/customize_themes.js';
 import {CrButtonElement} from 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
 import {CrInputElement} from 'chrome://resources/cr_elements/cr_input/cr_input.m.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
@@ -27,6 +28,7 @@
     doneButton: CrButtonElement,
     nameInput: CrInputElement,
     title: HTMLElement,
+    themeSelector: CustomizeThemesElement,
   };
 }
 
@@ -96,6 +98,7 @@
    * native.
    */
   private onDoneCustomizationClicked_() {
+    this.$.themeSelector.confirmThemeChanges();
     this.profileCustomizationBrowserProxy_.done(this.profileName_);
   }
 
@@ -111,6 +114,10 @@
     this.welcomeTitle_ = profileInfo.welcomeTitle;
   }
 
+  private onSkipCustomizationClicked_() {
+    this.profileCustomizationBrowserProxy_.skip();
+  }
+
   private getDialogDesignClass_(inDialogDesign: boolean): string {
     return inDialogDesign ? 'in-dialog-design' : '';
   }
diff --git a/chrome/browser/resources/signin/profile_customization/profile_customization_browser_proxy.ts b/chrome/browser/resources/signin/profile_customization/profile_customization_browser_proxy.ts
index c1b9855..47a7a75 100644
--- a/chrome/browser/resources/signin/profile_customization/profile_customization_browser_proxy.ts
+++ b/chrome/browser/resources/signin/profile_customization/profile_customization_browser_proxy.ts
@@ -23,6 +23,9 @@
 
   // Called when the user clicks the done button.
   done(profileName: string): void;
+
+  // Called when the user clicks the skip button.
+  skip(): void;
 }
 
 export class ProfileCustomizationBrowserProxyImpl implements
@@ -35,6 +38,10 @@
     chrome.send('done', [profileName]);
   }
 
+  skip() {
+    chrome.send('skip');
+  }
+
   static getInstance(): ProfileCustomizationBrowserProxy {
     return instance || (instance = new ProfileCustomizationBrowserProxyImpl());
   }
diff --git a/chrome/browser/resources/tab_search/app.ts b/chrome/browser/resources/tab_search/app.ts
index 59ae9b7..f93fd1f 100644
--- a/chrome/browser/resources/tab_search/app.ts
+++ b/chrome/browser/resources/tab_search/app.ts
@@ -119,7 +119,7 @@
 
   private searchText_: string;
   private availableHeight_: number;
-  filteredItems_: Array<TitleItem|TabData|TabGroupData>;
+  private filteredItems_: Array<TitleItem|TabData|TabGroupData>;
   private fuzzySearchOptions_: FuzzySearchOptions<TabData|TabGroupData>;
   private moveActiveTabToBottom_: boolean;
   private recentlyClosedDefaultItemDisplayCount_: number;
diff --git a/chrome/browser/ui/app_list/app_service/app_service_app_icon_loader.cc b/chrome/browser/ui/app_list/app_service/app_service_app_icon_loader.cc
index 20c9d02..a6c1fc32 100644
--- a/chrome/browser/ui/app_list/app_service/app_service_app_icon_loader.cc
+++ b/chrome/browser/ui/app_list/app_service/app_service_app_icon_loader.cc
@@ -188,13 +188,6 @@
   }
 }
 
-void AppServiceAppIconLoader::OnLoadMojomIcon(
-    const std::string& app_id,
-    apps::mojom::IconValuePtr icon_value) {
-  OnLoadIcon(app_id,
-             apps::ConvertMojomIconValueToIconValue(std::move(icon_value)));
-}
-
 bool AppServiceAppIconLoader::Exist(const std::string& app_id) {
   if (!base::Contains(shelf_app_id_map_, app_id)) {
     return false;
diff --git a/chrome/browser/ui/app_list/app_service/app_service_app_icon_loader.h b/chrome/browser/ui/app_list/app_service/app_service_app_icon_loader.h
index d5b8933..13ae22c4 100644
--- a/chrome/browser/ui/app_list/app_service/app_service_app_icon_loader.h
+++ b/chrome/browser/ui/app_list/app_service/app_service_app_icon_loader.h
@@ -54,11 +54,6 @@
   // Callback invoked when the icon is loaded.
   void OnLoadIcon(const std::string& app_id, apps::IconValuePtr icon_value);
 
-  // Callback invoked when the icon is loaded.
-  // TODO(crbug.com/1251501): Remove this mojom callback.
-  void OnLoadMojomIcon(const std::string& app_id,
-                       apps::mojom::IconValuePtr icon_value);
-
   // Returns true if the app_id does exist in icon_map_.
   bool Exist(const std::string& app_id);
 
diff --git a/chrome/browser/ui/app_list/app_service/app_service_app_model_builder_unittest.cc b/chrome/browser/ui/app_list/app_service/app_service_app_model_builder_unittest.cc
index 246c862..c4ab347 100644
--- a/chrome/browser/ui/app_list/app_service/app_service_app_model_builder_unittest.cc
+++ b/chrome/browser/ui/app_list/app_service/app_service_app_model_builder_unittest.cc
@@ -619,25 +619,23 @@
   apps::IconEffects icon_effects = apps::IconEffects::kCrOsStandardIcon;
 
   base::RunLoop run_loop;
-  apps::mojom::IconValuePtr dst_icon;
+  apps::IconValuePtr dst_icon;
   apps::LoadIconFromExtension(
       apps::IconType::kCompressed,
       ash::SharedAppListConfig::instance().default_grid_icon_dimension(),
       profile(), kPackagedApp1Id, icon_effects,
-      apps::IconValueToMojomIconValueCallback(
-          base::BindLambdaForTesting([&](apps::mojom::IconValuePtr icon) {
-            dst_icon = std::move(icon);
-            run_loop.Quit();
-          })));
+      base::BindLambdaForTesting([&](apps::IconValuePtr icon) {
+        dst_icon = std::move(icon);
+        run_loop.Quit();
+      }));
   run_loop.Run();
 
-  ASSERT_FALSE(dst_icon.is_null());
-  ASSERT_EQ(apps::mojom::IconType::kCompressed, dst_icon->icon_type);
+  ASSERT_TRUE(dst_icon);
+  ASSERT_EQ(apps::IconType::kCompressed, dst_icon->icon_type);
   ASSERT_FALSE(dst_icon->is_placeholder_icon);
-  ASSERT_TRUE(dst_icon->compressed.has_value());
-  ASSERT_FALSE(dst_icon->compressed.value().empty());
+  ASSERT_FALSE(dst_icon->compressed.empty());
 
-  ASSERT_EQ(src_data, dst_icon->compressed.value());
+  ASSERT_EQ(src_data, dst_icon->compressed);
 }
 
 // This test adds a web app to the app list.
diff --git a/chrome/browser/ui/ash/shelf/browser_app_shelf_controller.cc b/chrome/browser/ui/ash/shelf/browser_app_shelf_controller.cc
index ab483b0..9983ffd 100644
--- a/chrome/browser/ui/ash/shelf/browser_app_shelf_controller.cc
+++ b/chrome/browser/ui/ash/shelf/browser_app_shelf_controller.cc
@@ -8,8 +8,6 @@
 
 #include "ash/public/cpp/window_properties.h"
 #include "base/debug/dump_without_crashing.h"
-#include "chrome/browser/apps/app_service/app_service_proxy.h"
-#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
 #include "chrome/browser/apps/app_service/browser_app_instance.h"
 #include "chrome/browser/apps/app_service/browser_app_instance_registry.h"
 #include "chrome/browser/ash/crosapi/browser_util.h"
@@ -49,6 +47,7 @@
 
 BrowserAppShelfController::BrowserAppShelfController(
     Profile* profile,
+    apps::BrowserAppInstanceRegistry& browser_app_instance_registry,
     ash::ShelfModel& model,
     ChromeShelfItemFactory& shelf_item_factory,
     ShelfSpinnerController& shelf_spinner_controller)
@@ -56,9 +55,7 @@
       model_(model),
       shelf_item_factory_(shelf_item_factory),
       shelf_spinner_controller_(shelf_spinner_controller),
-      browser_app_instance_registry_(
-          *apps::AppServiceProxyFactory::GetForProfile(profile)
-               ->BrowserAppInstanceRegistry()) {
+      browser_app_instance_registry_(browser_app_instance_registry) {
   CHECK(web_app::IsWebAppsCrosapiEnabled());
   registry_observation_.Observe(&browser_app_instance_registry_);
   shelf_model_observation_.Observe(&model);
diff --git a/chrome/browser/ui/ash/shelf/browser_app_shelf_controller.h b/chrome/browser/ui/ash/shelf/browser_app_shelf_controller.h
index 0801bc1..9849a66 100644
--- a/chrome/browser/ui/ash/shelf/browser_app_shelf_controller.h
+++ b/chrome/browser/ui/ash/shelf/browser_app_shelf_controller.h
@@ -42,10 +42,12 @@
 class BrowserAppShelfController : public apps::BrowserAppInstanceObserver,
                                   public ash::ShelfModelObserver {
  public:
-  BrowserAppShelfController(Profile* profile,
-                            ash::ShelfModel& model,
-                            ChromeShelfItemFactory& shelf_item_factory,
-                            ShelfSpinnerController& shelf_spinner_controller);
+  BrowserAppShelfController(
+      Profile* profile,
+      apps::BrowserAppInstanceRegistry& browser_app_instance_registry,
+      ash::ShelfModel& model,
+      ChromeShelfItemFactory& shelf_item_factory,
+      ShelfSpinnerController& shelf_spinner_controller);
 
   BrowserAppShelfController(const BrowserAppShelfController&) = delete;
   BrowserAppShelfController& operator=(const BrowserAppShelfController&) =
diff --git a/chrome/browser/ui/ash/shelf/chrome_shelf_controller.cc b/chrome/browser/ui/ash/shelf/chrome_shelf_controller.cc
index 072095bb..53d2848 100644
--- a/chrome/browser/ui/ash/shelf/chrome_shelf_controller.cc
+++ b/chrome/browser/ui/ash/shelf/chrome_shelf_controller.cc
@@ -256,9 +256,14 @@
       std::make_unique<AppServiceAppWindowShelfController>(this);
   app_service_app_window_controller_ = app_service_controller.get();
   app_window_controllers_.emplace_back(std::move(app_service_controller));
-  if (web_app::IsWebAppsCrosapiEnabled()) {
+  if (web_app::IsWebAppsCrosapiEnabled() &&
+      apps::AppServiceProxyFactory::IsAppServiceAvailableForProfile(profile)) {
+    apps::AppServiceProxy* proxy =
+        apps::AppServiceProxyFactory::GetForProfile(profile);
+    DCHECK(proxy);
     browser_app_shelf_controller_ = std::make_unique<BrowserAppShelfController>(
-        profile, *model_, *shelf_item_factory_, *shelf_spinner_controller_);
+        profile, *proxy->BrowserAppInstanceRegistry(), *model_,
+        *shelf_item_factory_, *shelf_spinner_controller_);
   } else {
     // Create the browser monitor which will inform the shelf of status changes.
     browser_status_monitor_ = std::make_unique<BrowserStatusMonitor>(this);
diff --git a/chrome/browser/ui/ash/shelf/chrome_shelf_controller_unittest.cc b/chrome/browser/ui/ash/shelf/chrome_shelf_controller_unittest.cc
index 9612edb3..f66efaa 100644
--- a/chrome/browser/ui/ash/shelf/chrome_shelf_controller_unittest.cc
+++ b/chrome/browser/ui/ash/shelf/chrome_shelf_controller_unittest.cc
@@ -532,13 +532,15 @@
   virtual bool StartWebAppProviderForMainProfile() const { return true; }
 
   void StartWebAppProvider(Profile* profile) {
-    auto system_web_app_manager =
-        std::make_unique<web_app::TestSystemWebAppManager>(profile);
-
     auto* provider = web_app::FakeWebAppProvider::Get(profile);
-    provider->SetSystemWebAppManager(std::move(system_web_app_manager));
+
+    auto* system_web_app_manager =
+        web_app::TestSystemWebAppManager::Get(profile);
+
     provider->SetRunSubsystemStartupTasks(true);
     provider->Start();
+
+    system_web_app_manager->ScheduleStart();
   }
 
   ui::BaseWindow* GetLastActiveWindowForItemController(
@@ -1697,6 +1699,20 @@
   EXPECT_FALSE(proxy()->InstanceRegistry().ContainsAppId(kDummyAppId));
 }
 
+// Regression test for crash. crbug.com/1296949
+TEST_F(ChromeShelfControllerLacrosPrimaryTest, WithoutAppService) {
+  Profile* const controller_profile = profile()->GetOffTheRecordProfile(
+      Profile::OTRProfileID::CreateUniqueForTesting(),
+      /*create_if_needed=*/true);
+  EXPECT_FALSE(apps::AppServiceProxyFactory::IsAppServiceAvailableForProfile(
+      controller_profile));
+
+  ChromeShelfPrefs::SkipPinnedAppsFromSyncForTest();
+  ash::ShelfModel model;
+  FakeChromeShelfItemFactory shelf_item_factory(controller_profile);
+  ChromeShelfController(controller_profile, &model, &shelf_item_factory).Init();
+}
+
 TEST_F(ChromeShelfControllerWithArcTest, ArcAppsHiddenFromLaunchCanBePinned) {
   InitShelfController();
 
diff --git a/chrome/browser/ui/browser.cc b/chrome/browser/ui/browser.cc
index ffd7b80..458af71 100644
--- a/chrome/browser/ui/browser.cc
+++ b/chrome/browser/ui/browser.cc
@@ -34,7 +34,6 @@
 #include "build/chromeos_buildflags.h"
 #include "chrome/app/chrome_command_ids.h"
 #include "chrome/browser/app_mode/app_mode_utils.h"
-#include "chrome/browser/autofill/personal_data_manager_factory.h"
 #include "chrome/browser/background/background_contents.h"
 #include "chrome/browser/background/background_contents_service.h"
 #include "chrome/browser/background/background_contents_service_factory.h"
@@ -90,7 +89,6 @@
 #include "chrome/browser/themes/theme_service.h"
 #include "chrome/browser/themes/theme_service_factory.h"
 #include "chrome/browser/translate/chrome_translate_client.h"
-#include "chrome/browser/ui/autofill/chrome_autofill_client.h"
 #include "chrome/browser/ui/blocked_content/chrome_popup_navigation_delegate.h"
 #include "chrome/browser/ui/blocked_content/framebust_block_tab_helper.h"
 #include "chrome/browser/ui/bookmarks/bookmark_tab_helper.h"
@@ -251,7 +249,6 @@
 #include <shellapi.h>
 
 #include "chrome/browser/ui/view_ids.h"
-#include "components/autofill/core/browser/autofill_ie_toolbar_import_win.h"
 #include "ui/base/win/shell.h"
 #endif  // BUILDFLAG(IS_WIN)
 
@@ -558,15 +555,6 @@
   if (service)
     service->WindowOpened(this);
 
-  // TODO(beng): move to ChromeBrowserMain:
-  if (first_run::ShouldDoPersonalDataManagerFirstRun()) {
-#if BUILDFLAG(IS_WIN)
-    // Notify PDM that this is a first run.
-    ImportAutofillDataWin(
-        autofill::PersonalDataManagerFactory::GetForProfile(profile_));
-#endif  // BUILDFLAG(IS_WIN)
-  }
-
   exclusive_access_manager_ = std::make_unique<ExclusiveAccessManager>(
       window_->GetExclusiveAccessContext());
 
diff --git a/chrome/browser/ui/signin_intercept_first_run_experience_dialog.cc b/chrome/browser/ui/signin_intercept_first_run_experience_dialog.cc
index c8372f72..d59a15de 100644
--- a/chrome/browser/ui/signin_intercept_first_run_experience_dialog.cc
+++ b/chrome/browser/ui/signin_intercept_first_run_experience_dialog.cc
@@ -373,7 +373,7 @@
   DCHECK(web_ui);
   web_ui->Initialize(
       base::BindOnce(&SigninInterceptFirstRunExperienceDialog::
-                         OnProfileCustomizationDoneButtonClicked,
+                         ProfileCustomizationCloseOnCompletion,
                      // Unretained is fine because `this` owns the web contents.
                      base::Unretained(this)));
 }
@@ -397,7 +397,15 @@
 }
 
 void SigninInterceptFirstRunExperienceDialog::
-    OnProfileCustomizationDoneButtonClicked() {
-  RecordDialogEvent(DialogEvent::kProfileCustomizationClickDone);
+    ProfileCustomizationCloseOnCompletion(
+        ProfileCustomizationHandler::CustomizationResult customization_result) {
+  switch (customization_result) {
+    case ProfileCustomizationHandler::CustomizationResult::kDone:
+      RecordDialogEvent(DialogEvent::kProfileCustomizationClickDone);
+      break;
+    case ProfileCustomizationHandler::CustomizationResult::kSkip:
+      RecordDialogEvent(DialogEvent::kProfileCustomizationClickSkip);
+      break;
+  }
   DoNextStep(Step::kProfileCustomization, Step::kProfileSwitchIPHAndCloseModal);
 }
diff --git a/chrome/browser/ui/signin_intercept_first_run_experience_dialog.h b/chrome/browser/ui/signin_intercept_first_run_experience_dialog.h
index aa37dcd..6def12d 100644
--- a/chrome/browser/ui/signin_intercept_first_run_experience_dialog.h
+++ b/chrome/browser/ui/signin_intercept_first_run_experience_dialog.h
@@ -12,6 +12,7 @@
 #include "chrome/browser/ui/signin/profile_customization_synced_theme_waiter.h"
 #include "chrome/browser/ui/signin_modal_dialog.h"
 #include "chrome/browser/ui/signin_view_controller_delegate.h"
+#include "chrome/browser/ui/webui/signin/profile_customization_handler.h"
 #include "google_apis/gaia/core_account_id.h"
 
 class Browser;
@@ -49,8 +50,10 @@
     kShowProfileCustomization = 5,
     // The user completed profile customization.
     kProfileCustomizationClickDone = 6,
+    // The user skipped profile customization.
+    kProfileCustomizationClickSkip = 7,
 
-    kMaxValue = kProfileCustomizationClickDone
+    kMaxValue = kProfileCustomizationClickSkip
   };
 
   explicit SigninInterceptFirstRunExperienceDialog(
@@ -103,7 +106,8 @@
   void PreloadProfileCustomizationUI();
   void OnSyncedThemeReady(
       ProfileCustomizationSyncedThemeWaiter::Outcome outcome);
-  void OnProfileCustomizationDoneButtonClicked();
+  void ProfileCustomizationCloseOnCompletion(
+      ProfileCustomizationHandler::CustomizationResult customization_result);
 
   const raw_ptr<Browser> browser_;
   const CoreAccountId account_id_;
diff --git a/chrome/browser/ui/signin_intercept_first_run_experience_dialog_browsertest.cc b/chrome/browser/ui/signin_intercept_first_run_experience_dialog_browsertest.cc
index b144996..403f6291 100644
--- a/chrome/browser/ui/signin_intercept_first_run_experience_dialog_browsertest.cc
+++ b/chrome/browser/ui/signin_intercept_first_run_experience_dialog_browsertest.cc
@@ -24,6 +24,7 @@
 #include "chrome/browser/ui/views/profiles/avatar_toolbar_button.h"
 #include "chrome/browser/ui/webui/signin/login_ui_service.h"
 #include "chrome/browser/ui/webui/signin/login_ui_service_factory.h"
+#include "chrome/browser/ui/webui/signin/profile_customization_handler.h"
 #include "chrome/browser/ui/webui/signin/turn_sync_on_helper.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "components/feature_engagement/public/feature_constants.h"
@@ -196,8 +197,14 @@
         ->SyncConfirmationUIClosed(result);
   }
 
-  void SimulateProfileCustomizationUIClosing() {
-    dialog()->OnProfileCustomizationDoneButtonClicked();
+  void SimulateProfileCustomizationDoneButtonClicked() {
+    dialog()->ProfileCustomizationCloseOnCompletion(
+        ProfileCustomizationHandler::CustomizationResult::kDone);
+  }
+
+  void SimulateProfileCustomizationSkipButtonClicked() {
+    dialog()->ProfileCustomizationCloseOnCompletion(
+        ProfileCustomizationHandler::CustomizationResult::kSkip);
   }
 
   void ExpectRecordedEvents(DialogEventSet events) {
@@ -325,7 +332,7 @@
       dialog()->GetModalDialogWebContentsForTesting()->GetLastCommittedURL(),
       kProfileCustomizationUrl);
 
-  SimulateProfileCustomizationUIClosing();
+  SimulateProfileCustomizationDoneButtonClicked();
   EXPECT_FALSE(controller()->ShowsModalDialog());
   EXPECT_TRUE(ProfileSwitchPromoHasBeenShown());
   ExpectRecordedEvents({DialogEvent::kStart, DialogEvent::kShowSyncConfirmation,
@@ -335,6 +342,55 @@
   ExpectSigninHistogramsRecorded();
 }
 
+// Goes through all steps of the fre dialog and skips profile customization.
+// The user enables sync.
+IN_PROC_BROWSER_TEST_F(SigninInterceptFirstRunExperienceDialogBrowserTest,
+                       AcceptSync_SkipCustomization) {
+  SignIn(kConsumerEmail);
+  content::TestNavigationObserver sync_confirmation_observer(
+      kSyncConfirmationUrl);
+  content::TestNavigationObserver profile_customization_observer(
+      kProfileCustomizationUrl);
+  sync_confirmation_observer.StartWatchingNewWebContents();
+  profile_customization_observer.StartWatchingNewWebContents();
+
+  controller()->ShowModalInterceptFirstRunExperienceDialog(
+      account_id(), /* is_forced_intercept = */ false);
+  EXPECT_TRUE(controller()->ShowsModalDialog());
+  sync_confirmation_observer.Wait();
+  EXPECT_EQ(
+      dialog()->GetModalDialogWebContentsForTesting()->GetLastCommittedURL(),
+      kSyncConfirmationUrl);
+
+  SimulateSyncConfirmationUIClosing(LoginUIService::SYNC_WITH_DEFAULT_SETTINGS);
+  ExpectPrimaryAccountWithExactConsentLevel(signin::ConsentLevel::kSync);
+  // The dialog still shows the sync confirmation while waiting for the synced
+  // theme to be applied.
+  EXPECT_TRUE(controller()->ShowsModalDialog());
+  EXPECT_EQ(
+      dialog()->GetModalDialogWebContentsForTesting()->GetLastCommittedURL(),
+      kSyncConfirmationUrl);
+
+  theme_service()->GetThemeSyncableService()->NotifyOnSyncStartedForTesting(
+      ThemeSyncableService::ThemeSyncState::kApplied);
+
+  profile_customization_observer.Wait();
+  EXPECT_EQ(
+      dialog()->GetModalDialogWebContentsForTesting()->GetLastCommittedURL(),
+      kProfileCustomizationUrl);
+
+  SimulateProfileCustomizationSkipButtonClicked();
+  EXPECT_FALSE(controller()->ShowsModalDialog());
+  EXPECT_TRUE(ProfileSwitchPromoHasBeenShown());
+  ExpectRecordedEvents({DialogEvent::kStart, DialogEvent::kShowSyncConfirmation,
+                        DialogEvent::kSyncConfirmationClickConfirm,
+                        DialogEvent::kShowProfileCustomization,
+                        DialogEvent::kProfileCustomizationClickSkip});
+  ExpectSigninHistogramsRecorded();
+  // TODO(https://crbug.com/1282157): test that the Skip button undoes the
+  // changes in the theme color and the profile name.
+}
+
 // The user enables sync and has a synced extension theme. Tests that the dialog
 // waits on the sync confirmation page until the extension theme is applied.
 IN_PROC_BROWSER_TEST_F(SigninInterceptFirstRunExperienceDialogBrowserTest,
@@ -383,7 +439,7 @@
       dialog()->GetModalDialogWebContentsForTesting()->GetLastCommittedURL(),
       kProfileCustomizationUrl);
 
-  SimulateProfileCustomizationUIClosing();
+  SimulateProfileCustomizationDoneButtonClicked();
   EXPECT_FALSE(controller()->ShowsModalDialog());
   EXPECT_TRUE(ProfileSwitchPromoHasBeenShown());
 }
@@ -446,7 +502,7 @@
       dialog()->GetModalDialogWebContentsForTesting()->GetLastCommittedURL(),
       kProfileCustomizationUrl);
 
-  SimulateProfileCustomizationUIClosing();
+  SimulateProfileCustomizationDoneButtonClicked();
   EXPECT_FALSE(controller()->ShowsModalDialog());
   EXPECT_TRUE(ProfileSwitchPromoHasBeenShown());
   ExpectRecordedEvents({DialogEvent::kStart, DialogEvent::kShowSyncConfirmation,
@@ -582,7 +638,7 @@
   // Sync consent is granted even though Sync cannot be enabled.
   ExpectPrimaryAccountWithExactConsentLevel(signin::ConsentLevel::kSync);
 
-  SimulateProfileCustomizationUIClosing();
+  SimulateProfileCustomizationDoneButtonClicked();
   EXPECT_FALSE(controller()->ShowsModalDialog());
   EXPECT_TRUE(ProfileSwitchPromoHasBeenShown());
   ExpectRecordedEvents({DialogEvent::kStart,
@@ -609,7 +665,7 @@
       dialog()->GetModalDialogWebContentsForTesting()->GetLastCommittedURL(),
       kProfileCustomizationUrl);
 
-  SimulateProfileCustomizationUIClosing();
+  SimulateProfileCustomizationDoneButtonClicked();
   EXPECT_FALSE(controller()->ShowsModalDialog());
   EXPECT_TRUE(ProfileSwitchPromoHasBeenShown());
   ExpectRecordedEvents({DialogEvent::kStart,
diff --git a/chrome/browser/ui/views/extensions/extension_popup_interactive_uitest.cc b/chrome/browser/ui/views/extensions/extension_popup_interactive_uitest.cc
index 6eac1ee..57008e3 100644
--- a/chrome/browser/ui/views/extensions/extension_popup_interactive_uitest.cc
+++ b/chrome/browser/ui/views/extensions/extension_popup_interactive_uitest.cc
@@ -125,9 +125,9 @@
   // The permission may be shown using a chip UI instead of a popped-up bubble.
   // If so, click on the chip to open the bubble.
   BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser());
-  PermissionChip* chip = browser_view->toolbar()->location_bar()->chip();
-  if (chip) {
-    views::test::ButtonTestApi(chip->button())
+  LocationBarView* lbv = browser_view->toolbar()->location_bar();
+  if (lbv->IsChipActive() && !lbv->chip()->IsBubbleShowing()) {
+    views::test::ButtonTestApi(lbv->chip()->button())
         .NotifyClick(ui::MouseEvent(ui::ET_MOUSE_PRESSED, gfx::Point(),
                                     gfx::Point(), ui::EventTimeForNow(),
                                     ui::EF_LEFT_MOUSE_BUTTON, 0));
diff --git a/chrome/browser/ui/views/extensions/extensions_request_access_button_hover_card_browsertest.cc b/chrome/browser/ui/views/extensions/extensions_request_access_button_hover_card_browsertest.cc
index 046b4a2..5d2a58da 100644
--- a/chrome/browser/ui/views/extensions/extensions_request_access_button_hover_card_browsertest.cc
+++ b/chrome/browser/ui/views/extensions/extensions_request_access_button_hover_card_browsertest.cc
@@ -6,6 +6,7 @@
 
 #include "chrome/browser/ui/toolbar/test_toolbar_action_view_controller.h"
 #include "chrome/browser/ui/views/extensions/extensions_dialogs_browsertest.h"
+#include "chrome/browser/ui/views/extensions/extensions_request_access_button.h"
 #include "chrome/browser/ui/views/extensions/extensions_toolbar_container.h"
 #include "content/public/test/browser_test.h"
 #include "extensions/common/extension.h"
@@ -31,11 +32,10 @@
     auto controllerA = std::make_unique<TestToolbarActionViewController>("A");
     std::vector<ToolbarActionViewController*> extensions_requesting_access;
     extensions_requesting_access.push_back(controllerA.get());
-    extensions_container()
-        ->GetExtensionsToolbarControls()
-        ->UpdateRequestAccessButton(extensions_requesting_access);
+    request_access_button()->UpdateExtensionsRequestingAccess(
+        extensions_requesting_access);
+    request_access_button()->SetVisible(true);
 
-    EXPECT_TRUE(request_access_button()->GetVisible());
     request_access_button()->MaybeShowHoverCard();
   }
 
diff --git a/chrome/browser/ui/views/extensions/extensions_toolbar_container.cc b/chrome/browser/ui/views/extensions/extensions_toolbar_container.cc
index 64e065a71..2007bef 100644
--- a/chrome/browser/ui/views/extensions/extensions_toolbar_container.cc
+++ b/chrome/browser/ui/views/extensions/extensions_toolbar_container.cc
@@ -120,6 +120,8 @@
   SetVisible(false);
 
   model_observation_.Observe(model_.get());
+  permissions_manager_observation_.Observe(
+      extensions::PermissionsManager::Get(browser_->profile()));
 
   const views::FlexSpecification hide_icon_flex_specification =
       views::FlexSpecification(views::LayoutOrientation::kHorizontal,
@@ -536,6 +538,11 @@
   drop_weak_ptr_factory_.InvalidateWeakPtrs();
 }
 
+void ExtensionsToolbarContainer::UserPermissionsSettingsChanged(
+    const extensions::PermissionsManager::UserPermissionsSettings& settings) {
+  UpdateControlsVisibility();
+}
+
 void ExtensionsToolbarContainer::ReorderViews() {
   const auto& pinned_action_ids = model_->pinned_action_ids();
   for (size_t i = 0; i < pinned_action_ids.size(); ++i)
@@ -864,17 +871,14 @@
     return;
 
   content::WebContents* web_contents = GetCurrentWebContents();
+  if (!web_contents)
+    return;
 
-  extensions_controls_->UpdateSiteAccessButtonVisibility(
-      ExtensionActionViewController::AnyActionHasCurrentSiteAccess(
-          actions_, web_contents));
-
-  std::vector<ToolbarActionViewController*> extensions_requesting_access;
-  for (const auto& action : actions_) {
-    if (action->IsRequestingSiteAccess(web_contents))
-      extensions_requesting_access.push_back(action.get());
-  }
-  extensions_controls_->UpdateRequestAccessButton(extensions_requesting_access);
+  extensions::PermissionsManager::UserSiteSetting site_setting =
+      extensions::PermissionsManager::Get(browser_->profile())
+          ->GetUserSiteSetting(
+              web_contents->GetMainFrame()->GetLastCommittedOrigin());
+  extensions_controls_->UpdateControls(actions_, site_setting, web_contents);
 }
 
 BEGIN_METADATA(ExtensionsToolbarContainer, ToolbarIconContainerView)
diff --git a/chrome/browser/ui/views/extensions/extensions_toolbar_container.h b/chrome/browser/ui/views/extensions/extensions_toolbar_container.h
index 4955db35..e21c435 100644
--- a/chrome/browser/ui/views/extensions/extensions_toolbar_container.h
+++ b/chrome/browser/ui/views/extensions/extensions_toolbar_container.h
@@ -31,12 +31,14 @@
 // Container for extensions shown in the toolbar. These include pinned
 // extensions and extensions that are 'popped out' transitively to show dialogs
 // or be called out to the user.
-class ExtensionsToolbarContainer : public ToolbarIconContainerView,
-                                   public ExtensionsContainer,
-                                   public TabStripModelObserver,
-                                   public ToolbarActionsModel::Observer,
-                                   public ToolbarActionView::Delegate,
-                                   public views::WidgetObserver {
+class ExtensionsToolbarContainer
+    : public ToolbarIconContainerView,
+      public ExtensionsContainer,
+      public TabStripModelObserver,
+      public ToolbarActionsModel::Observer,
+      public ToolbarActionView::Delegate,
+      public views::WidgetObserver,
+      public extensions::PermissionsManager::Observer {
  public:
   METADATA_HEADER(ExtensionsToolbarContainer);
 
@@ -239,6 +241,11 @@
   void OnToolbarModelInitialized() override;
   void OnToolbarPinnedActionsChanged() override;
 
+  // PermissionsManager::Observer:
+  void UserPermissionsSettingsChanged(
+      const extensions::PermissionsManager::UserPermissionsSettings& settings)
+      override;
+
   // views::WidgetObserver:
   void OnWidgetDestroying(views::Widget* widget) override;
 
@@ -255,8 +262,13 @@
 
   const raw_ptr<Browser> browser_;
   const raw_ptr<ToolbarActionsModel> model_;
+
   base::ScopedObservation<ToolbarActionsModel, ToolbarActionsModel::Observer>
       model_observation_{this};
+  base::ScopedObservation<extensions::PermissionsManager,
+                          extensions::PermissionsManager::Observer>
+      permissions_manager_observation_{this};
+
   // TODO(emiliapaz): Remove `extensions_button_` once
   // `extensions_features::kExtensionsMenuAccessControl` experiment is released.
   // Exactly one of `extensions_button_ and `extensions_controls_` is created;
diff --git a/chrome/browser/ui/views/extensions/extensions_toolbar_controls.cc b/chrome/browser/ui/views/extensions/extensions_toolbar_controls.cc
index 26a6c0a4..420c29bc 100644
--- a/chrome/browser/ui/views/extensions/extensions_toolbar_controls.cc
+++ b/chrome/browser/ui/views/extensions/extensions_toolbar_controls.cc
@@ -6,9 +6,11 @@
 
 #include <memory>
 
+#include "chrome/browser/ui/extensions/extension_action_view_controller.h"
 #include "chrome/browser/ui/toolbar/toolbar_action_view_controller.h"
 #include "chrome/browser/ui/views/extensions/extensions_request_access_button.h"
 #include "chrome/browser/ui/views/extensions/extensions_toolbar_button.h"
+#include "content/public/browser/web_contents.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
 
 ExtensionsToolbarControls::ExtensionsToolbarControls(
@@ -29,15 +31,48 @@
 
 void ExtensionsToolbarControls::UpdateAllIcons() {}
 
-void ExtensionsToolbarControls::UpdateSiteAccessButtonVisibility(
-    bool visibility) {
-  site_access_button_->SetVisible(visibility);
+void ExtensionsToolbarControls::UpdateControls(
+    const std::vector<std::unique_ptr<ToolbarActionViewController>>& actions,
+    extensions::PermissionsManager::UserSiteSetting site_setting,
+    content::WebContents* current_web_contents) {
+  UpdateSiteAccessButton(actions, current_web_contents);
+  UpdateRequestAccessButton(actions, site_setting, current_web_contents);
 
-  ResetLayout();
+  // Resets the layout since layout animation does not handle host view
+  // visibility changing. This should be called after any visibility changes.
+  GetAnimatingLayoutManager()->ResetLayout();
+}
+
+void ExtensionsToolbarControls::UpdateSiteAccessButton(
+    const std::vector<std::unique_ptr<ToolbarActionViewController>>& actions,
+    content::WebContents* web_contents) {
+  site_access_button_->SetVisible(
+      ExtensionActionViewController::AnyActionHasCurrentSiteAccess(
+          actions, web_contents));
 }
 
 void ExtensionsToolbarControls::UpdateRequestAccessButton(
-    std::vector<ToolbarActionViewController*> extensions_requesting_access) {
+    const std::vector<std::unique_ptr<ToolbarActionViewController>>& actions,
+    extensions::PermissionsManager::UserSiteSetting site_setting,
+    content::WebContents* web_contents) {
+  // User site settings takes precedence over extension site access. If the user
+  // has allowed or blocked all extensions, individual extensions cannot grant
+  // access to the page and therefore the request access button is not
+  // displayed.
+  if (site_setting == extensions::PermissionsManager::UserSiteSetting::
+                          kGrantAllExtensions ||
+      site_setting == extensions::PermissionsManager::UserSiteSetting::
+                          kBlockAllExtensions) {
+    request_access_button_->SetVisible(false);
+    return;
+  }
+
+  // Request access button is displayed if any extension requests access.
+  std::vector<ToolbarActionViewController*> extensions_requesting_access;
+  for (const auto& action : actions) {
+    if (action->IsRequestingSiteAccess(web_contents))
+      extensions_requesting_access.push_back(action.get());
+  }
   if (extensions_requesting_access.empty()) {
     request_access_button_->SetVisible(false);
   } else {
@@ -50,12 +85,6 @@
         extensions_requesting_access);
     request_access_button_->SetVisible(true);
   }
-
-  ResetLayout();
-}
-
-void ExtensionsToolbarControls::ResetLayout() {
-  GetAnimatingLayoutManager()->ResetLayout();
 }
 
 BEGIN_METADATA(ExtensionsToolbarControls, ToolbarIconContainerView)
diff --git a/chrome/browser/ui/views/extensions/extensions_toolbar_controls.h b/chrome/browser/ui/views/extensions/extensions_toolbar_controls.h
index 910c2aa..8f912d0 100644
--- a/chrome/browser/ui/views/extensions/extensions_toolbar_controls.h
+++ b/chrome/browser/ui/views/extensions/extensions_toolbar_controls.h
@@ -8,9 +8,12 @@
 #include <memory>
 
 #include "base/memory/raw_ptr.h"
-#include "chrome/browser/ui/views/extensions/extensions_request_access_button.h"
 #include "chrome/browser/ui/views/toolbar/toolbar_icon_container_view.h"
-#include "content/public/browser/web_contents.h"
+#include "extensions/browser/permissions_manager.h"
+
+namespace content {
+class WebContents;
+}
 
 class ExtensionsToolbarButton;
 class ExtensionsRequestAccessButton;
@@ -41,22 +44,29 @@
     return request_access_button_;
   }
 
-  // Updates `site_access_button_` visibility to the given one.
-  void UpdateSiteAccessButtonVisibility(bool visibility);
-
-  // Updates `request_access_button_` visibility and content based on the given
-  // `count_requesting_extensions`.
-  void UpdateRequestAccessButton(
-      std::vector<ToolbarActionViewController*> extensions_requesting_access);
-
-  // Resets the layout since layout animation does not handle host view
-  // visibility changing. This should be called after any visibility changes.
-  void ResetLayout();
+  // Update the controls given `actions` and the user `site_setting` in the
+  // `current_web_contents`.
+  void UpdateControls(
+      const std::vector<std::unique_ptr<ToolbarActionViewController>>& actions,
+      extensions::PermissionsManager::UserSiteSetting site_setting,
+      content::WebContents* current_web_contents);
 
   // ToolbarIconContainerView:
   void UpdateAllIcons() override;
 
  private:
+  // Updates `site_access_button_` visibility given `actions` in `web_contents`.
+  void UpdateSiteAccessButton(
+      const std::vector<std::unique_ptr<ToolbarActionViewController>>& actions,
+      content::WebContents* web_contents);
+
+  // Updates `request_access_button_` visibility given the user `site_setting`
+  // and `actions` in `web_contents`.
+  void UpdateRequestAccessButton(
+      const std::vector<std::unique_ptr<ToolbarActionViewController>>& actions,
+      extensions::PermissionsManager::UserSiteSetting site_setting,
+      content::WebContents* web_contents);
+
   const raw_ptr<ExtensionsRequestAccessButton> request_access_button_;
   const raw_ptr<ExtensionsToolbarButton> site_access_button_;
   const raw_ptr<ExtensionsToolbarButton> extensions_button_;
diff --git a/chrome/browser/ui/views/extensions/extensions_toolbar_controls_unittest.cc b/chrome/browser/ui/views/extensions/extensions_toolbar_controls_unittest.cc
index 28cdd8e..0aa9b7f1 100644
--- a/chrome/browser/ui/views/extensions/extensions_toolbar_controls_unittest.cc
+++ b/chrome/browser/ui/views/extensions/extensions_toolbar_controls_unittest.cc
@@ -5,6 +5,8 @@
 #include "chrome/browser/ui/views/extensions/extensions_toolbar_controls.h"
 
 #include "chrome/browser/extensions/extension_context_menu_model.h"
+#include "chrome/browser/extensions/site_permissions_helper.h"
+#include "chrome/browser/ui/views/extensions/extensions_request_access_button.h"
 #include "chrome/browser/ui/views/extensions/extensions_toolbar_button.h"
 #include "chrome/browser/ui/views/extensions/extensions_toolbar_container.h"
 #include "chrome/browser/ui/views/extensions/extensions_toolbar_unittest.h"
@@ -12,7 +14,9 @@
 #include "content/public/browser/notification_service.h"
 #include "extensions/browser/notification_types.h"
 #include "extensions/common/extension_features.h"
+#include "extensions/test/permissions_manager_waiter.h"
 #include "ui/views/view_utils.h"
+#include "url/origin.h"
 
 class ExtensionsToolbarControlsUnitTest : public ExtensionsToolbarUnitTest {
  public:
@@ -306,3 +310,60 @@
   InstallExtensionWithPermissions("Extension", {"activeTab"});
   EXPECT_FALSE(IsRequestAccessButtonVisible());
 }
+
+// Test that request access button is visible based on the user site setting
+// selected.
+TEST_F(ExtensionsToolbarControlsUnitTest,
+       RequestAccessButtonVisibility_UserSiteSetting) {
+  content::WebContentsTester* web_contents_tester =
+      AddWebContentsAndGetTester();
+  const GURL url("http://www.url.com");
+  auto url_origin = url::Origin::Create(url);
+
+  // Install an extension and withhold permissions so request access button can
+  // be visible.
+  auto extension =
+      InstallExtensionWithHostPermissions("Extension", {"<all_urls>"});
+  WithholdHostPermissions(extension.get());
+
+  web_contents_tester->NavigateAndCommit(url);
+  WaitForAnimation();
+
+  // A site has "customize by extensions" site setting by default,
+  ASSERT_EQ(
+      GetUserSiteSetting(url),
+      extensions::PermissionsManager::UserSiteSetting::kCustomizeByExtension);
+  EXPECT_TRUE(IsRequestAccessButtonVisible());
+
+  auto* manager = extensions::PermissionsManager::Get(profile());
+  {
+    // Request access button is not visible in permitted sites.
+    extensions::PermissionsManagerWaiter manager_waiter(
+        extensions::PermissionsManager::Get(profile()));
+    manager->AddUserPermittedSite(url_origin);
+    manager_waiter.WaitForPermissionsChange();
+    WaitForAnimation();
+    EXPECT_FALSE(IsRequestAccessButtonVisible());
+  }
+
+  {
+    // Request access button is not visible in restricted sites.
+    extensions::PermissionsManagerWaiter manager_waiter(
+        extensions::PermissionsManager::Get(profile()));
+    manager->AddUserRestrictedSite(url_origin);
+    manager_waiter.WaitForPermissionsChange();
+    WaitForAnimation();
+    EXPECT_FALSE(IsRequestAccessButtonVisible());
+  }
+
+  {
+    // Request acesss button is visible if site is not permitted or restricted,
+    // and at least one extension is requesting access.
+    extensions::PermissionsManagerWaiter manager_waiter(
+        extensions::PermissionsManager::Get(profile()));
+    manager->RemoveUserRestrictedSite(url_origin);
+    manager_waiter.WaitForPermissionsChange();
+    WaitForAnimation();
+    EXPECT_TRUE(IsRequestAccessButtonVisible());
+  }
+}
diff --git a/chrome/browser/ui/views/permission_bubble/permission_bubble_interactive_uitest.cc b/chrome/browser/ui/views/permission_bubble/permission_bubble_interactive_uitest.cc
index 4928dc5f..0eb630f 100644
--- a/chrome/browser/ui/views/permission_bubble/permission_bubble_interactive_uitest.cc
+++ b/chrome/browser/ui/views/permission_bubble/permission_bubble_interactive_uitest.cc
@@ -108,9 +108,9 @@
     // click on the chip to trigger showing the prompt.
     BrowserView* browser_view =
         BrowserView::GetBrowserViewForBrowser(browser());
-    PermissionChip* chip = browser_view->toolbar()->location_bar()->chip();
-    if (chip) {
-      views::test::ButtonTestApi(chip->button())
+    LocationBarView* lbv = browser_view->toolbar()->location_bar();
+    if (lbv->IsChipActive() && !lbv->chip()->IsBubbleShowing()) {
+      views::test::ButtonTestApi(lbv->chip()->button())
           .NotifyClick(ui::MouseEvent(ui::ET_MOUSE_PRESSED, gfx::Point(),
                                       gfx::Point(), ui::EventTimeForNow(),
                                       ui::EF_LEFT_MOUSE_BUTTON, 0));
@@ -162,8 +162,7 @@
   EXPECT_EQ(0u, views::test::WidgetTest::GetAllWidgets().size());
 }
 
-#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_CHROMEOS_ASH)
-// TODO(crbug.com/1336247): Flaky on Chrome OS.
+#if BUILDFLAG(IS_MAC)
 // TODO(crbug.com/1324444): For Mac builders, the test fails after activating
 // the browser and cannot spot the widget. Needs investigation and fix.
 #define MAYBE_SwitchTabs DISABLED_SwitchTabs
diff --git a/chrome/browser/ui/views/permission_bubble/permission_chip_interactive_test.cc b/chrome/browser/ui/views/permission_bubble/permission_chip_interactive_test.cc
index 1bee088..ef418501 100644
--- a/chrome/browser/ui/views/permission_bubble/permission_chip_interactive_test.cc
+++ b/chrome/browser/ui/views/permission_bubble/permission_chip_interactive_test.cc
@@ -97,11 +97,15 @@
   PermissionChip* GetChip() {
     BrowserView* browser_view =
         BrowserView::GetBrowserViewForBrowser(browser());
-    return browser_view->toolbar()->location_bar()->chip();
+    LocationBarView* lbv = browser_view->toolbar()->location_bar();
+
+    return lbv->chip();
   }
 
   void ClickOnChip(PermissionChip* chip) {
     ASSERT_TRUE(chip != nullptr);
+    ASSERT_TRUE(chip->IsActive());
+    ASSERT_TRUE(!chip->IsBubbleShowing());
     views::test::ButtonTestApi(chip->button())
         .NotifyClick(ui::MouseEvent(ui::ET_MOUSE_PRESSED, gfx::Point(),
                                     gfx::Point(), ui::EventTimeForNow(),
diff --git a/chrome/browser/ui/views/permission_bubble/permission_prompt_impl.cc b/chrome/browser/ui/views/permission_bubble/permission_prompt_impl.cc
index acbfad10..ec348f2 100644
--- a/chrome/browser/ui/views/permission_bubble/permission_prompt_impl.cc
+++ b/chrome/browser/ui/views/permission_bubble/permission_prompt_impl.cc
@@ -255,7 +255,7 @@
 
   LocationBarView* lbv = GetLocationBarView();
 
-  return lbv->chip()
+  return lbv->IsChipActive() && lbv->chip()->IsBubbleShowing()
              ? lbv->chip()->GetPromptBubbleWidgetForTesting()  // IN-TEST
              : nullptr;
 }
diff --git a/chrome/browser/ui/views/profiles/profile_customization_bubble_view.cc b/chrome/browser/ui/views/profiles/profile_customization_bubble_view.cc
index 93a4dcc..46517529 100644
--- a/chrome/browser/ui/views/profiles/profile_customization_bubble_view.cc
+++ b/chrome/browser/ui/views/profiles/profile_customization_bubble_view.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/ui/views/profiles/profile_customization_bubble_view.h"
 
+#include "base/callback_helpers.h"
 #include "base/feature_list.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/signin/dice_web_signin_interceptor_delegate.h"
@@ -62,7 +63,7 @@
   SetInitiallyFocusedView(web_view.get());
   DCHECK(web_ui);
   web_ui->Initialize(
-      base::BindOnce(&ProfileCustomizationBubbleView::OnDoneButtonClicked,
+      base::BindOnce(&ProfileCustomizationBubbleView::OnCompletionButtonClicked,
                      // Unretained is fine because this owns the web view.
                      base::Unretained(this)));
   AddChildView(std::move(web_view));
@@ -72,11 +73,20 @@
   SetLayoutManager(std::make_unique<views::FillLayout>());
 }
 
-void ProfileCustomizationBubbleView::OnDoneButtonClicked() {
+void ProfileCustomizationBubbleView::OnCompletionButtonClicked(
+    ProfileCustomizationHandler::CustomizationResult customization_result) {
   BrowserView* browser_view = BrowserView::GetBrowserViewForNativeWindow(
       GetAnchorView()->GetWidget()->GetNativeWindow());
-  GetWidget()->CloseWithReason(
-      views::Widget::ClosedReason::kCloseButtonClicked);
+  views::Widget::ClosedReason closed_reason;
+  switch (customization_result) {
+    case ProfileCustomizationHandler::CustomizationResult::kDone:
+      closed_reason = views::Widget::ClosedReason::kAcceptButtonClicked;
+      break;
+    case ProfileCustomizationHandler::CustomizationResult::kSkip:
+      closed_reason = views::Widget::ClosedReason::kCancelButtonClicked;
+      break;
+  }
+  GetWidget()->CloseWithReason(closed_reason);
   browser_view->MaybeShowProfileSwitchIPH();
 }
 
diff --git a/chrome/browser/ui/views/profiles/profile_customization_bubble_view.h b/chrome/browser/ui/views/profiles/profile_customization_bubble_view.h
index b1a85047..ea1ae53 100644
--- a/chrome/browser/ui/views/profiles/profile_customization_bubble_view.h
+++ b/chrome/browser/ui/views/profiles/profile_customization_bubble_view.h
@@ -6,6 +6,7 @@
 #define CHROME_BROWSER_UI_VIEWS_PROFILES_PROFILE_CUSTOMIZATION_BUBBLE_VIEW_H_
 
 #include "base/gtest_prod_util.h"
+#include "chrome/browser/ui/webui/signin/profile_customization_handler.h"
 #include "ui/base/metadata/metadata_header_macros.h"
 #include "ui/views/bubble/bubble_dialog_delegate_view.h"
 
@@ -37,8 +38,9 @@
 
   ProfileCustomizationBubbleView(Profile* profile, views::View* anchor_view);
 
-  // Called when the "Done" button is clicked in the inner WebUI.
-  void OnDoneButtonClicked();
+  // Called when the "Done" or "Skip" button is clicked in the inner WebUI.
+  void OnCompletionButtonClicked(
+      ProfileCustomizationHandler::CustomizationResult customization_result);
 };
 
 #endif  // CHROME_BROWSER_UI_VIEWS_PROFILES_PROFILE_CUSTOMIZATION_BUBBLE_VIEW_H_
diff --git a/chrome/browser/ui/views/profiles/profile_customization_bubble_view_browsertest.cc b/chrome/browser/ui/views/profiles/profile_customization_bubble_view_browsertest.cc
index 1a71b99..8a390c65 100644
--- a/chrome/browser/ui/views/profiles/profile_customization_bubble_view_browsertest.cc
+++ b/chrome/browser/ui/views/profiles/profile_customization_bubble_view_browsertest.cc
@@ -18,6 +18,7 @@
 #include "chrome/browser/ui/views/frame/toolbar_button_provider.h"
 #include "chrome/browser/ui/views/profiles/avatar_toolbar_button.h"
 #include "chrome/browser/ui/views/user_education/browser_feature_promo_controller.h"
+#include "chrome/browser/ui/webui/signin/profile_customization_handler.h"
 #include "components/feature_engagement/public/feature_constants.h"
 #include "components/feature_engagement/public/tracker.h"
 #include "components/feature_engagement/test/test_tracker.h"
@@ -99,7 +100,8 @@
       tracker->GetTriggerState(feature_engagement::kIPHProfileSwitchFeature),
       feature_engagement::Tracker::TriggerState::HAS_BEEN_DISPLAYED);
 
-  bubble->OnDoneButtonClicked();
+  bubble->OnCompletionButtonClicked(
+      ProfileCustomizationHandler::CustomizationResult::kDone);
 
   base::RunLoop loop;
   tracker->AddOnInitializedCallback(
diff --git a/chrome/browser/ui/views/profiles/signin_view_controller_delegate_views.cc b/chrome/browser/ui/views/profiles/signin_view_controller_delegate_views.cc
index 27dbd7ff..3818ef1 100644
--- a/chrome/browser/ui/views/profiles/signin_view_controller_delegate_views.cc
+++ b/chrome/browser/ui/views/profiles/signin_view_controller_delegate_views.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/ui/views/profiles/signin_view_controller_delegate_views.h"
 
 #include "base/bind.h"
+#include "base/callback_helpers.h"
 #include "base/memory/weak_ptr.h"
 #include "build/build_config.h"
 #include "chrome/browser/profiles/profile.h"
@@ -65,7 +66,9 @@
 }
 
 #if BUILDFLAG(ENABLE_DICE_SUPPORT)
-void CloseModalSigninInBrowser(base::WeakPtr<Browser> browser) {
+void CloseModalSigninInBrowser(
+    base::WeakPtr<Browser> browser,
+    ProfileCustomizationHandler::CustomizationResult result) {
   if (browser)
     browser->signin_view_controller()->CloseModalSignin();
 }
diff --git a/chrome/browser/ui/webui/signin/profile_customization_handler.cc b/chrome/browser/ui/webui/signin/profile_customization_handler.cc
index 989cd92..bc0ec9cb 100644
--- a/chrome/browser/ui/webui/signin/profile_customization_handler.cc
+++ b/chrome/browser/ui/webui/signin/profile_customization_handler.cc
@@ -33,8 +33,8 @@
 }
 
 ProfileCustomizationHandler::ProfileCustomizationHandler(
-    base::OnceClosure done_closure)
-    : done_closure_(std::move(done_closure)) {}
+    base::OnceCallback<void(CustomizationResult)> completion_callback)
+    : completion_callback_(std::move(completion_callback)) {}
 
 ProfileCustomizationHandler::~ProfileCustomizationHandler() = default;
 
@@ -47,6 +47,9 @@
   web_ui()->RegisterMessageCallback(
       "done", base::BindRepeating(&ProfileCustomizationHandler::HandleDone,
                                   base::Unretained(this)));
+  web_ui()->RegisterMessageCallback(
+      "skip", base::BindRepeating(&ProfileCustomizationHandler::HandleSkip,
+                                  base::Unretained(this)));
 }
 
 void ProfileCustomizationHandler::OnJavascriptAllowed() {
@@ -101,8 +104,15 @@
   GetProfileEntry()->SetLocalProfileName(profile_name,
                                          /*is_default_name=*/false);
 
-  if (done_closure_)
-    std::move(done_closure_).Run();
+  if (completion_callback_)
+    std::move(completion_callback_).Run(CustomizationResult::kDone);
+}
+
+void ProfileCustomizationHandler::HandleSkip(const base::Value::List& args) {
+  CHECK_EQ(0u, args.size());
+
+  if (completion_callback_)
+    std::move(completion_callback_).Run(CustomizationResult::kSkip);
 }
 
 void ProfileCustomizationHandler::UpdateProfileInfo(
diff --git a/chrome/browser/ui/webui/signin/profile_customization_handler.h b/chrome/browser/ui/webui/signin/profile_customization_handler.h
index 2386414..82b30da 100644
--- a/chrome/browser/ui/webui/signin/profile_customization_handler.h
+++ b/chrome/browser/ui/webui/signin/profile_customization_handler.h
@@ -18,7 +18,15 @@
 class ProfileCustomizationHandler : public content::WebUIMessageHandler,
                                     public ProfileAttributesStorage::Observer {
  public:
-  explicit ProfileCustomizationHandler(base::OnceClosure done_closure);
+  enum class CustomizationResult {
+    // User clicked on the "Done" button.
+    kDone = 0,
+    // User clicked on the "Skip" button.
+    kSkip = 1,
+  };
+
+  explicit ProfileCustomizationHandler(
+      base::OnceCallback<void(CustomizationResult)> completion_callback);
   ~ProfileCustomizationHandler() override;
 
   ProfileCustomizationHandler(const ProfileCustomizationHandler&) = delete;
@@ -44,6 +52,7 @@
   // Handlers for messages from javascript.
   void HandleInitialized(const base::Value::List& args);
   void HandleDone(const base::Value::List& args);
+  void HandleSkip(const base::Value::List& args);
 
   // Sends an updated profile info (avatar and colors) to the WebUI.
   // `profile_path` is the path of the profile being updated, this function does
@@ -61,8 +70,9 @@
                           ProfileAttributesStorage::Observer>
       observed_profile_{this};
 
-  // Called when the "Done" button has been pressed.
-  base::OnceClosure done_closure_;
+  // Called when the "Done" or "Skip" button has been clicked. The callback
+  // normally closes native widget hosting Profile Customization webUI.
+  base::OnceCallback<void(CustomizationResult)> completion_callback_;
 };
 
 #endif  // CHROME_BROWSER_UI_WEBUI_SIGNIN_PROFILE_CUSTOMIZATION_HANDLER_H_
diff --git a/chrome/browser/ui/webui/signin/profile_customization_ui.cc b/chrome/browser/ui/webui/signin/profile_customization_ui.cc
index 8fa13af..39687b2 100644
--- a/chrome/browser/ui/webui/signin/profile_customization_ui.cc
+++ b/chrome/browser/ui/webui/signin/profile_customization_ui.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/ui/webui/signin/profile_customization_ui.h"
 
+#include "base/callback_helpers.h"
 #include "base/feature_list.h"
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/browser/browser_process.h"
@@ -89,9 +90,11 @@
 
 ProfileCustomizationUI::~ProfileCustomizationUI() = default;
 
-void ProfileCustomizationUI::Initialize(base::OnceClosure done_closure) {
-  web_ui()->AddMessageHandler(
-      std::make_unique<ProfileCustomizationHandler>(std::move(done_closure)));
+void ProfileCustomizationUI::Initialize(
+    base::OnceCallback<void(ProfileCustomizationHandler::CustomizationResult)>
+        completion_callback) {
+  web_ui()->AddMessageHandler(std::make_unique<ProfileCustomizationHandler>(
+      std::move(completion_callback)));
 }
 
 void ProfileCustomizationUI::BindInterface(
diff --git a/chrome/browser/ui/webui/signin/profile_customization_ui.h b/chrome/browser/ui/webui/signin/profile_customization_ui.h
index 2d069bd4..026a71c 100644
--- a/chrome/browser/ui/webui/signin/profile_customization_ui.h
+++ b/chrome/browser/ui/webui/signin/profile_customization_ui.h
@@ -6,6 +6,7 @@
 #define CHROME_BROWSER_UI_WEBUI_SIGNIN_PROFILE_CUSTOMIZATION_UI_H_
 
 #include "base/callback.h"
+#include "chrome/browser/ui/webui/signin/profile_customization_handler.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/receiver.h"
@@ -33,7 +34,9 @@
   ProfileCustomizationUI& operator=(const ProfileCustomizationUI&) = delete;
 
   // Initializes the ProfileCustomizationUI.
-  void Initialize(base::OnceClosure done_closure);
+  void Initialize(
+      base::OnceCallback<void(ProfileCustomizationHandler::CustomizationResult)>
+          completion_callback);
 
   // Instantiates the implementor of the
   // customize_themes::mojom::CustomizeThemesHandlerFactory mojo interface
diff --git a/chrome/browser/ui/webui/web_app_internals/web_app_internals_source.cc b/chrome/browser/ui/webui/web_app_internals/web_app_internals_source.cc
index 2d94714..dbde99b 100644
--- a/chrome/browser/ui/webui/web_app_internals/web_app_internals_source.cc
+++ b/chrome/browser/ui/webui/web_app_internals/web_app_internals_source.cc
@@ -268,7 +268,7 @@
   // reference.
   if (!info.is_directory) {
     folder->Set(file_or_folder.AsUTF8Unsafe(),
-                base::StrCat({base::NumberToString(info.size / 1024), "kb"}));
+                base::StrCat({base::NumberToString(info.size), " bytes"}));
     return;
   }
 
diff --git a/chrome/browser/web_applications/BUILD.gn b/chrome/browser/web_applications/BUILD.gn
index cd4064b..694ad48a 100644
--- a/chrome/browser/web_applications/BUILD.gn
+++ b/chrome/browser/web_applications/BUILD.gn
@@ -12,6 +12,8 @@
     "../ash/system_web_apps/system_web_app_background_task.h",
     "../ash/system_web_apps/system_web_app_manager.cc",
     "../ash/system_web_apps/system_web_app_manager.h",
+    "../ash/system_web_apps/system_web_app_manager_factory.cc",
+    "../ash/system_web_apps/system_web_app_manager_factory.h",
     "app_registrar_observer.h",
     "commands/callback_command.cc",
     "commands/callback_command.h",
diff --git a/chrome/browser/web_applications/system_web_apps/test/test_system_web_app_installation.cc b/chrome/browser/web_applications/system_web_apps/test/test_system_web_app_installation.cc
index d97c012e..d9d00eb 100644
--- a/chrome/browser/web_applications/system_web_apps/test/test_system_web_app_installation.cc
+++ b/chrome/browser/web_applications/system_web_apps/test/test_system_web_app_installation.cc
@@ -297,19 +297,29 @@
                           // startup. TestSystemWebAppInstallation is intended
                           // to have the same lifecycle as the test, it won't be
                           // destroyed before the test finishes.
-                          base::Unretained(this), delegate_ptr));
+                          base::Unretained(this)));
+
+  test_system_web_app_manager_creator_ =
+      std::make_unique<TestSystemWebAppManagerCreator>(base::BindRepeating(
+          &TestSystemWebAppInstallation::CreateSystemWebAppManager,
+          base::Unretained(this), delegate_ptr));
 }
 
 TestSystemWebAppInstallation::TestSystemWebAppInstallation() {
-  fake_web_app_provider_creator_ = std::make_unique<
-      FakeWebAppProviderCreator>(base::BindRepeating(
-      &TestSystemWebAppInstallation::CreateWebAppProviderWithNoSystemWebApps,
-      // base::Unretained is safe here. This callback is called
-      // at TestingProfile::Init, which is at test startup.
-      // TestSystemWebAppInstallation is intended to have the
-      // same lifecycle as the test, it won't be destroyed before
-      // the test finishes.
-      base::Unretained(this)));
+  fake_web_app_provider_creator_ = std::make_unique<FakeWebAppProviderCreator>(
+      base::BindRepeating(&TestSystemWebAppInstallation::CreateWebAppProvider,
+                          // base::Unretained is safe here. This callback is
+                          // called at TestingProfile::Init, which is at test
+                          // startup. TestSystemWebAppInstallation is intended
+                          // to have the same lifecycle as the test, it won't be
+                          // destroyed before the test finishes.
+                          base::Unretained(this)));
+
+  test_system_web_app_manager_creator_ =
+      std::make_unique<TestSystemWebAppManagerCreator>(
+          base::BindRepeating(&TestSystemWebAppInstallation::
+                                  CreateSystemWebAppManagerWithNoSystemWebApps,
+                              base::Unretained(this)));
 }
 
 TestSystemWebAppInstallation::~TestSystemWebAppInstallation() = default;
@@ -769,17 +779,31 @@
 }
 
 std::unique_ptr<KeyedService>
-TestSystemWebAppInstallation::CreateWebAppProvider(
+TestSystemWebAppInstallation::CreateWebAppProvider(Profile* profile) {
+  profile_ = profile;
+
+  auto provider = std::make_unique<FakeWebAppProvider>(profile);
+  provider->Start();
+
+  return provider;
+}
+
+std::unique_ptr<KeyedService>
+TestSystemWebAppInstallation::CreateSystemWebAppManager(
     UnittestingSystemAppDelegate* delegate,
     Profile* profile) {
-  profile_ = profile;
+  // `CreateWebAppProvider` gets called first and assigns `profile_`.
+  DCHECK_EQ(profile_, profile);
+
   if (GetWebUIType(delegate->GetInstallUrl()) == WebUIType::kChromeUntrusted) {
     AddTestURLDataSource(GetChromeUntrustedDataSourceNameFromInstallUrl(
                              delegate->GetInstallUrl()),
                          profile);
   }
 
-  auto provider = std::make_unique<FakeWebAppProvider>(profile);
+  WebAppProvider* provider = WebAppProvider::GetForLocalAppsUnchecked(profile);
+  DCHECK(provider);
+
   auto system_web_app_manager =
       std::make_unique<ash::SystemWebAppManager>(profile);
 
@@ -787,38 +811,36 @@
       std::move(system_app_delegates_));
   system_web_app_manager->SetUpdatePolicyForTesting(update_policy_);
 
-  provider->on_registry_ready().Post(
-      FROM_HERE, base::BindOnce(&ash::SystemWebAppManager::Start,
-                                system_web_app_manager->GetWeakPtr()));
-
-  provider->SetSystemWebAppManager(std::move(system_web_app_manager));
-  provider->Start();
+  system_web_app_manager->ConnectSubsystems(provider);
+  system_web_app_manager->ScheduleStart();
 
   const url::Origin app_origin = url::Origin::Create(delegate->GetInstallUrl());
   auto* allowlist = WebUIAllowlist::GetOrCreate(profile);
   for (const auto& permission : auto_granted_permissions_)
     allowlist->RegisterAutoGrantedPermission(app_origin, permission);
 
-  return provider;
+  return system_web_app_manager;
 }
 
 std::unique_ptr<KeyedService>
-TestSystemWebAppInstallation::CreateWebAppProviderWithNoSystemWebApps(
+TestSystemWebAppInstallation::CreateSystemWebAppManagerWithNoSystemWebApps(
     Profile* profile) {
-  profile_ = profile;
-  auto provider = std::make_unique<FakeWebAppProvider>(profile);
+  // `CreateWebAppProvider` gets called first and assigns `profile_`.
+  DCHECK_EQ(profile_, profile);
+
+  WebAppProvider* provider = WebAppProvider::GetForLocalAppsUnchecked(profile);
+  DCHECK(provider);
+
   auto system_web_app_manager =
       std::make_unique<ash::SystemWebAppManager>(profile);
+
   system_web_app_manager->SetSystemAppsForTesting({});
   system_web_app_manager->SetUpdatePolicyForTesting(update_policy_);
 
-  provider->on_registry_ready().Post(
-      FROM_HERE, base::BindOnce(&ash::SystemWebAppManager::Start,
-                                system_web_app_manager->GetWeakPtr()));
+  system_web_app_manager->ConnectSubsystems(provider);
+  system_web_app_manager->ScheduleStart();
 
-  provider->SetSystemWebAppManager(std::move(system_web_app_manager));
-  provider->Start();
-  return provider;
+  return system_web_app_manager;
 }
 
 void TestSystemWebAppInstallation::WaitForAppInstall() {
diff --git a/chrome/browser/web_applications/system_web_apps/test/test_system_web_app_installation.h b/chrome/browser/web_applications/system_web_apps/test/test_system_web_app_installation.h
index 8cb7980b8..5d0b304 100644
--- a/chrome/browser/web_applications/system_web_apps/test/test_system_web_app_installation.h
+++ b/chrome/browser/web_applications/system_web_apps/test/test_system_web_app_installation.h
@@ -10,6 +10,7 @@
 #include "base/memory/raw_ptr.h"
 #include "chrome/browser/ash/system_web_apps/system_web_app_manager.h"
 #include "chrome/browser/ash/system_web_apps/types/system_web_app_delegate.h"
+#include "chrome/browser/web_applications/system_web_apps/test/test_system_web_app_manager.h"
 #include "chrome/browser/web_applications/system_web_apps/test/test_system_web_app_web_ui_controller_factory.h"
 #include "chrome/browser/web_applications/test/fake_web_app_provider.h"
 #include "chrome/browser/web_applications/web_app_install_info.h"
@@ -232,10 +233,12 @@
       std::unique_ptr<UnittestingSystemAppDelegate> system_app_delegate);
   TestSystemWebAppInstallation();
 
-  std::unique_ptr<KeyedService> CreateWebAppProvider(
+  std::unique_ptr<KeyedService> CreateWebAppProvider(Profile* profile);
+  std::unique_ptr<KeyedService> CreateSystemWebAppManager(
       UnittestingSystemAppDelegate* system_app_delegate,
       Profile* profile);
-  std::unique_ptr<KeyedService> CreateWebAppProviderWithNoSystemWebApps(
+
+  std::unique_ptr<KeyedService> CreateSystemWebAppManagerWithNoSystemWebApps(
       Profile* profile);
 
   // Must be called in SetUp*App() methods, before WebAppProvider is created.
@@ -244,7 +247,11 @@
   raw_ptr<Profile> profile_;
   ash::SystemWebAppManager::UpdatePolicy update_policy_ =
       ash::SystemWebAppManager::UpdatePolicy::kAlwaysUpdate;
+
   std::unique_ptr<FakeWebAppProviderCreator> fake_web_app_provider_creator_;
+  std::unique_ptr<TestSystemWebAppManagerCreator>
+      test_system_web_app_manager_creator_;
+
   // nullopt if SetUpWithoutApps() was used.
   const absl::optional<ash::SystemWebAppType> type_;
   std::vector<std::unique_ptr<TestSystemWebAppWebUIControllerFactory>>
diff --git a/chrome/browser/web_applications/system_web_apps/test/test_system_web_app_manager.cc b/chrome/browser/web_applications/system_web_apps/test/test_system_web_app_manager.cc
index f1ad60d5..f8cd939 100644
--- a/chrome/browser/web_applications/system_web_apps/test/test_system_web_app_manager.cc
+++ b/chrome/browser/web_applications/system_web_apps/test/test_system_web_app_manager.cc
@@ -7,10 +7,42 @@
 #include <memory>
 #include <string>
 #include <utility>
+
+#include "chrome/browser/ash/system_web_apps/system_web_app_manager_factory.h"
 #include "chrome/browser/ash/system_web_apps/types/system_web_app_delegate.h"
+#include "chrome/browser/web_applications/test/fake_web_app_provider.h"
+#include "chrome/browser/web_applications/web_app_utils.h"
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "components/keyed_service/core/keyed_service.h"
 
 namespace web_app {
 
+// static
+std::unique_ptr<KeyedService> TestSystemWebAppManager::BuildDefault(
+    content::BrowserContext* context) {
+  Profile* profile = Profile::FromBrowserContext(context);
+
+  WebAppProvider* provider = WebAppProvider::GetForLocalAppsUnchecked(profile);
+  DCHECK(provider);
+
+  auto test_swa_manager = std::make_unique<TestSystemWebAppManager>(profile);
+
+  test_swa_manager->ConnectSubsystems(provider);
+
+  // We don't auto-install system web apps in `TestingProfile`. Tests must
+  // opt-in to call `ScheduleStart()` or `Start()` when they need.
+
+  return test_swa_manager;
+}
+
+// static
+TestSystemWebAppManager* TestSystemWebAppManager::Get(Profile* profile) {
+  CHECK(profile->AsTestingProfile());
+  auto* test_swa_manager = static_cast<TestSystemWebAppManager*>(
+      TestSystemWebAppManager::GetForLocalAppsUnchecked(profile));
+  return test_swa_manager;
+}
+
 TestSystemWebAppManager::TestSystemWebAppManager(Profile* profile)
     : SystemWebAppManager(profile) {
   SetSystemAppsForTesting(
@@ -33,4 +65,35 @@
   return current_locale_;
 }
 
+TestSystemWebAppManagerCreator::TestSystemWebAppManagerCreator(
+    CreateSystemWebAppManagerCallback callback)
+    : callback_(std::move(callback)) {
+  create_services_subscription_ =
+      BrowserContextDependencyManager::GetInstance()
+          ->RegisterCreateServicesCallbackForTesting(
+              base::BindRepeating(&TestSystemWebAppManagerCreator::
+                                      OnWillCreateBrowserContextServices,
+                                  base::Unretained(this)));
+}
+
+TestSystemWebAppManagerCreator::~TestSystemWebAppManagerCreator() = default;
+
+void TestSystemWebAppManagerCreator::OnWillCreateBrowserContextServices(
+    content::BrowserContext* context) {
+  ash::SystemWebAppManagerFactory::GetInstance()->SetTestingFactory(
+      context, base::BindRepeating(
+                   &TestSystemWebAppManagerCreator::CreateSystemWebAppManager,
+                   base::Unretained(this)));
+}
+
+std::unique_ptr<KeyedService>
+TestSystemWebAppManagerCreator::CreateSystemWebAppManager(
+    content::BrowserContext* context) {
+  Profile* profile = Profile::FromBrowserContext(context);
+  DCHECK(!ash::SystemWebAppManagerFactory::IsServiceCreatedForProfile(profile));
+  if (!AreWebAppsEnabled(profile) || !callback_)
+    return nullptr;
+  return callback_.Run(profile);
+}
+
 }  // namespace web_app
diff --git a/chrome/browser/web_applications/system_web_apps/test/test_system_web_app_manager.h b/chrome/browser/web_applications/system_web_apps/test/test_system_web_app_manager.h
index 262912e..da31832 100644
--- a/chrome/browser/web_applications/system_web_apps/test/test_system_web_app_manager.h
+++ b/chrome/browser/web_applications/system_web_apps/test/test_system_web_app_manager.h
@@ -8,16 +8,36 @@
 #include <string>
 #include <vector>
 
+#include "base/callback.h"
+#include "base/callback_list.h"
 #include "base/version.h"
 #include "chrome/browser/ash/system_web_apps/system_web_app_manager.h"
 #include "url/gurl.h"
 
+class KeyedService;
 class Profile;
 
+namespace content {
+class BrowserContext;
+}
+
 namespace web_app {
 
+// This class is used in unit tests only, browser tests use real
+// `SystemWebAppManager`.
 class TestSystemWebAppManager : public ash::SystemWebAppManager {
  public:
+  // Used by the TestingProfile in unit tests.
+  // Builds a stub `TestSystemWebAppManager` that needs to be manually started
+  // by calling `ScheduleStart()`. Use `TestSystemWebAppManager::Get()` to use
+  // testing methods.
+  static std::unique_ptr<KeyedService> BuildDefault(
+      content::BrowserContext* context);
+
+  // Gets a TestSystemWebAppManager. Clients must call `ScheduleStart()` to make
+  // `on_apps_synchronized()` event ready.
+  static TestSystemWebAppManager* Get(Profile* profile);
+
   explicit TestSystemWebAppManager(Profile* profile);
   TestSystemWebAppManager(const TestSystemWebAppManager&) = delete;
   TestSystemWebAppManager& operator=(const TestSystemWebAppManager&) = delete;
@@ -42,6 +62,28 @@
   std::string current_locale_;
 };
 
+// Used in tests to ensure that the SystemWebAppManager that is created on
+// profile startup is the TestSystemWebAppManager. Hooks into the
+// BrowserContextKeyedService initialization pipeline.
+class TestSystemWebAppManagerCreator {
+ public:
+  using CreateSystemWebAppManagerCallback =
+      base::RepeatingCallback<std::unique_ptr<KeyedService>(Profile* profile)>;
+
+  explicit TestSystemWebAppManagerCreator(
+      CreateSystemWebAppManagerCallback callback);
+  ~TestSystemWebAppManagerCreator();
+
+ private:
+  void OnWillCreateBrowserContextServices(content::BrowserContext* context);
+  std::unique_ptr<KeyedService> CreateSystemWebAppManager(
+      content::BrowserContext* context);
+
+  CreateSystemWebAppManagerCallback callback_;
+
+  base::CallbackListSubscription create_services_subscription_;
+};
+
 }  // namespace web_app
 
 #endif  // CHROME_BROWSER_WEB_APPLICATIONS_SYSTEM_WEB_APPS_TEST_TEST_SYSTEM_WEB_APP_MANAGER_H_
diff --git a/chrome/browser/web_applications/test/fake_web_app_provider.cc b/chrome/browser/web_applications/test/fake_web_app_provider.cc
index d860b61..eddf9fa0 100644
--- a/chrome/browser/web_applications/test/fake_web_app_provider.cc
+++ b/chrome/browser/web_applications/test/fake_web_app_provider.cc
@@ -17,7 +17,6 @@
 #include "chrome/browser/web_applications/externally_managed_app_manager.h"
 #include "chrome/browser/web_applications/os_integration/os_integration_manager.h"
 #include "chrome/browser/web_applications/policy/web_app_policy_manager.h"
-#include "chrome/browser/web_applications/system_web_apps/test/test_system_web_app_manager.h"
 #include "chrome/browser/web_applications/test/fake_externally_managed_app_manager.h"
 #include "chrome/browser/web_applications/test/fake_os_integration_manager.h"
 #include "chrome/browser/web_applications/test/fake_web_app_database_factory.h"
@@ -142,12 +141,6 @@
   ui_manager_ = std::move(ui_manager);
 }
 
-void FakeWebAppProvider::SetSystemWebAppManager(
-    std::unique_ptr<ash::SystemWebAppManager> system_web_app_manager) {
-  CheckNotStarted();
-  system_web_app_manager_ = std::move(system_web_app_manager);
-}
-
 void FakeWebAppProvider::SetWebAppPolicyManager(
     std::unique_ptr<WebAppPolicyManager> web_app_policy_manager) {
   CheckNotStarted();
@@ -185,12 +178,6 @@
 void FakeWebAppProvider::StartWithSubsystems() {
   CheckNotStarted();
   SetRunSubsystemStartupTasks(true);
-  // Use a TestSystemWebAppManager to skip system web apps being
-  // auto-installed on |Start|.
-  // TODO(crbug.com/973324): This is set in `SetDefaultFakeSubsystems`. Remove
-  // it from here.
-  SetSystemWebAppManager(
-      std::make_unique<web_app::TestSystemWebAppManager>(profile_));
   Start();
 }
 
@@ -225,11 +212,6 @@
 
   SetWebAppPolicyManager(std::make_unique<WebAppPolicyManager>(profile_));
 
-  // Use a TestSystemWebAppManager to skip system web apps being
-  // auto-installed on |Start|.
-  SetSystemWebAppManager(
-      std::make_unique<web_app::TestSystemWebAppManager>(profile_));
-
   SetCommandManager(std::make_unique<WebAppCommandManager>(profile_));
 
   ON_CALL(processor(), IsTrackingMetadata())
diff --git a/chrome/browser/web_applications/test/fake_web_app_provider.h b/chrome/browser/web_applications/test/fake_web_app_provider.h
index b899b3a11..e5f8f9f4 100644
--- a/chrome/browser/web_applications/test/fake_web_app_provider.h
+++ b/chrome/browser/web_applications/test/fake_web_app_provider.h
@@ -8,7 +8,6 @@
 #include <memory>
 
 #include "base/callback.h"
-#include "base/callback_forward.h"
 #include "base/callback_list.h"
 #include "chrome/browser/web_applications/web_app_provider.h"
 #include "components/sync/test/model/mock_model_type_change_processor.h"
@@ -17,10 +16,6 @@
 class KeyedService;
 class Profile;
 
-namespace ash {
-class SystemWebAppManager;
-}
-
 namespace content {
 class BrowserContext;
 }
@@ -83,8 +78,6 @@
       std::unique_ptr<ExternallyManagedAppManager>
           externally_managed_app_manager);
   void SetWebAppUiManager(std::unique_ptr<WebAppUiManager> ui_manager);
-  void SetSystemWebAppManager(
-      std::unique_ptr<ash::SystemWebAppManager> system_web_app_manager);
   void SetWebAppPolicyManager(
       std::unique_ptr<WebAppPolicyManager> web_app_policy_manager);
   void SetCommandManager(std::unique_ptr<WebAppCommandManager> command_manager);
@@ -126,12 +119,9 @@
 // BrowserContextKeyedService initialization pipeline.
 class FakeWebAppProviderCreator {
  public:
-  using OnceCreateWebAppProviderCallback =
-      base::OnceCallback<std::unique_ptr<KeyedService>(Profile* profile)>;
   using CreateWebAppProviderCallback =
       base::RepeatingCallback<std::unique_ptr<KeyedService>(Profile* profile)>;
 
-  explicit FakeWebAppProviderCreator(OnceCreateWebAppProviderCallback callback);
   explicit FakeWebAppProviderCreator(CreateWebAppProviderCallback callback);
   ~FakeWebAppProviderCreator();
 
diff --git a/chrome/browser/web_applications/web_app_provider.cc b/chrome/browser/web_applications/web_app_provider.cc
index 8bbb79d08..fbfe42e 100644
--- a/chrome/browser/web_applications/web_app_provider.cc
+++ b/chrome/browser/web_applications/web_app_provider.cc
@@ -13,7 +13,6 @@
 #include "base/run_loop.h"
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
-#include "chrome/browser/ash/system_web_apps/system_web_app_manager.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/web_applications/daily_metrics_helper.h"
 #include "chrome/browser/web_applications/externally_installed_web_app_prefs.h"
@@ -216,7 +215,6 @@
   ui_manager_->Shutdown();
   externally_managed_app_manager_->Shutdown();
   manifest_update_manager_->Shutdown();
-  system_web_app_manager_->Shutdown();
   install_manager_->Shutdown();
   icon_manager_->Shutdown();
   install_finalizer_->Shutdown();
@@ -237,7 +235,6 @@
       std::make_unique<ExternallyManagedAppManagerImpl>(profile);
   preinstalled_web_app_manager_ =
       std::make_unique<PreinstalledWebAppManager>(profile);
-  system_web_app_manager_ = std::make_unique<ash::SystemWebAppManager>(profile);
   web_app_policy_manager_ = std::make_unique<WebAppPolicyManager>(profile);
 
   database_factory_ = std::make_unique<WebAppDatabaseFactory>(profile);
@@ -313,9 +310,6 @@
   preinstalled_web_app_manager_->SetSubsystems(
       registrar_.get(), ui_manager_.get(),
       externally_managed_app_manager_.get());
-  system_web_app_manager_->SetSubsystems(
-      externally_managed_app_manager_.get(), registrar_.get(),
-      sync_bridge_.get(), ui_manager_.get(), web_app_policy_manager_.get());
   web_app_policy_manager_->SetSubsystems(externally_managed_app_manager_.get(),
                                          registrar_.get(), sync_bridge_.get(),
                                          os_integration_manager_.get());
@@ -328,13 +322,6 @@
 
   command_manager_->SetSubsystems(install_manager_.get());
   connected_ = true;
-
-  // TODO(crbug.com/1321984): Extract this code to SystemWebAppManager
-  // KeyedService.
-  manifest_update_manager_->SetSystemWebAppDelegateMap(
-      &system_web_app_manager_->system_app_delegates());
-  web_app_policy_manager_->SetSystemWebAppDelegateMap(
-      &system_web_app_manager_->system_app_delegates());
 }
 
 void WebAppProvider::StartSyncBridge() {
@@ -376,7 +363,6 @@
   ExternallyInstalledWebAppPrefs::RegisterProfilePrefs(registry);
   PreinstalledWebAppManager::RegisterProfilePrefs(registry);
   WebAppPolicyManager::RegisterProfilePrefs(registry);
-  ash::SystemWebAppManager::RegisterProfilePrefs(registry);
   WebAppPrefsUtilsRegisterProfilePrefs(registry);
   IsolationPrefsUtilsRegisterProfilePrefs(registry);
   RegisterInstallBounceMetricProfilePrefs(registry);
diff --git a/chrome/browser/web_applications/web_app_provider.h b/chrome/browser/web_applications/web_app_provider.h
index 48fdb956..08055e3 100644
--- a/chrome/browser/web_applications/web_app_provider.h
+++ b/chrome/browser/web_applications/web_app_provider.h
@@ -18,10 +18,6 @@
 
 class Profile;
 
-namespace ash {
-class SystemWebAppManager;
-}
-
 namespace content {
 class WebContents;
 }
@@ -148,10 +144,6 @@
   }
 
  protected:
-  // TODO(crbug.com/1321984): Delete system_web_app_manager_.
-  friend class ash::SystemWebAppManager;
-  friend class WebAppProviderFactory;
-
   virtual void StartImpl();
 
   void CreateSubsystems(Profile* profile);
@@ -179,9 +171,6 @@
   std::unique_ptr<WebAppInstallFinalizer> install_finalizer_;
   std::unique_ptr<ManifestUpdateManager> manifest_update_manager_;
   std::unique_ptr<ExternallyManagedAppManager> externally_managed_app_manager_;
-  // TODO(crbug.com/1321984): Extract system web app manager as
-  // chrome/browser/ash/ keyed service.
-  std::unique_ptr<ash::SystemWebAppManager> system_web_app_manager_;
   std::unique_ptr<WebAppAudioFocusIdMap> audio_focus_id_map_;
   std::unique_ptr<WebAppInstallManager> install_manager_;
   std::unique_ptr<WebAppPolicyManager> web_app_policy_manager_;
diff --git a/chrome/browser/web_applications/web_app_provider_factory.cc b/chrome/browser/web_applications/web_app_provider_factory.cc
index d7c6293..6876e61 100644
--- a/chrome/browser/web_applications/web_app_provider_factory.cc
+++ b/chrome/browser/web_applications/web_app_provider_factory.cc
@@ -4,7 +4,6 @@
 
 #include "chrome/browser/web_applications/web_app_provider_factory.h"
 
-#include "chrome/browser/ash/system_web_apps/system_web_app_manager.h"
 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
 #include "chrome/browser/metrics/ukm_background_recorder_service.h"
 #include "chrome/browser/profiles/profile.h"
@@ -53,13 +52,6 @@
   WebAppProvider* provider = new WebAppProvider(profile);
   provider->Start();
 
-  // TODO(crbug.com/1321984): Make SWAM a KeyedService and move this scheduling
-  // to that new service factory.
-  provider->on_registry_ready().Post(
-      FROM_HERE,
-      base::BindOnce(&ash::SystemWebAppManager::Start,
-                     provider->system_web_app_manager_->GetWeakPtr()));
-
   return provider;
 }
 
diff --git a/chrome/build/linux.pgo.txt b/chrome/build/linux.pgo.txt
index d080325..4e2296a2 100644
--- a/chrome/build/linux.pgo.txt
+++ b/chrome/build/linux.pgo.txt
@@ -1 +1 @@
-chrome-linux-main-1655229557-36aeb1dcde852e0e6daabaedec9aed25e8446d58.profdata
+chrome-linux-main-1655271799-e052a880a182ec4f533846fb5fa76276479ad24b.profdata
diff --git a/chrome/build/mac.pgo.txt b/chrome/build/mac.pgo.txt
index 78bb747..42b42fb0 100644
--- a/chrome/build/mac.pgo.txt
+++ b/chrome/build/mac.pgo.txt
@@ -1 +1 @@
-chrome-mac-main-1655250914-1f025f2d0cf31dca0d1885c0e412e79c8f096d4f.profdata
+chrome-mac-main-1655271799-3dbce1d5b32c25a17124fc1c88b26cc010397167.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index f04ed15..e719a47 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-main-1655229557-066d5add5fce3d3ed8e49b23efab1500633c1555.profdata
+chrome-win32-main-1655271799-afc5146a664cfc20dfe535291a0926e4788db342.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index 4d54d983..b9d106a 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-main-1655250914-706926b27021a324e0e16e0d619fb284315f3b44.profdata
+chrome-win64-main-1655283589-6861ba398f596394a34c3f5d7c9d94d8f4106210.profdata
diff --git a/chrome/common/chrome_switches.cc b/chrome/common/chrome_switches.cc
index 39dd67f..c3d5bd5c 100644
--- a/chrome/common/chrome_switches.cc
+++ b/chrome/common/chrome_switches.cc
@@ -590,6 +590,10 @@
 const char kWebRtcRemoteEventLogUploadNoSuppression[] =
     "webrtc-event-log-upload-no-suppression";
 
+// Override WebRTC IP handling policy to mimic the behavior when WebRTC IP
+// handling policy is specified in Preferences.
+const char kWebRtcIPHandlingPolicy[] = "webrtc-ip-handling-policy";
+
 // Specify the initial window position: --window-position=x,y
 const char kWindowPosition[] = "window-position";
 
diff --git a/chrome/common/chrome_switches.h b/chrome/common/chrome_switches.h
index 12c9b02..42d445af 100644
--- a/chrome/common/chrome_switches.h
+++ b/chrome/common/chrome_switches.h
@@ -178,6 +178,7 @@
 extern const char kWebRtcRemoteEventLogProactivePruningDelta[];
 extern const char kWebRtcRemoteEventLogUploadDelayMs[];
 extern const char kWebRtcRemoteEventLogUploadNoSuppression[];
+extern const char kWebRtcIPHandlingPolicy[];
 extern const char kWindowPosition[];
 extern const char kWindowSize[];
 extern const char kWindowWorkspace[];
diff --git a/chrome/common/extensions/api/file_manager_private.idl b/chrome/common/extensions/api/file_manager_private.idl
index 84fac49..49eb994 100644
--- a/chrome/common/extensions/api/file_manager_private.idl
+++ b/chrome/common/extensions/api/file_manager_private.idl
@@ -365,6 +365,15 @@
   zip
 };
 
+enum RecentDateBucket {
+  today,
+  yesterday,
+  earlier_this_week,
+  earlier_this_month,
+  earlier_this_year,
+  older
+};
+
 // These three fields together uniquely identify a task.
 dictionary FileTaskDescriptor {
   DOMString appId;
@@ -410,6 +419,9 @@
   // Timestamp of entry update time by me, in milliseconds past the epoch.
   double? modificationByMeTime;
 
+  // Date bucket calculated by |modificationTime| or |modificationByMeTime|.
+  RecentDateBucket? recentDateBucket;
+
   // URL to the Drive thumbnail image for this file.
   DOMString? thumbnailUrl;
 
@@ -982,6 +994,17 @@
 
   // The name of the last error that happened.
   DOMString errorName;
+
+  // The files affected by the IOTask. Currently only returned for TrashIOTask.
+  [instanceOf=Entry] object[]? outputs;
+};
+
+dictionary DlpMetadata {
+  // The source URL of the file, if it's been downloaded.
+  DOMString sourceUrl;
+
+  // True if there is any DLP policy on the file, false otherwise.
+  boolean isDlpRestricted;
 };
 
 // A Guest OS that supports guest->host file sharing. This definition should
@@ -1043,9 +1066,10 @@
 callback GetDisallowedTransfersCallback =
     void([instanceOf=Entry] object[] disallowedEntries);
 
-// |restrictedFiles| A list of files with any Data Leak Prevention restriction.
-callback GetFilesRestrictedByDlpCallback =
-    void([instanceOf=Entry] object[] restrictedFiles);
+// |dlpMetadata| A list of DlpMetadata containing DLP information about
+// the entries.
+callback GetDlpMetadataCallback =
+    void(DlpMetadata[] dlpMetadata);
 
 // |copyId| ID of the copy task. Can be used to identify the progress, and to
 // cancel the task.
@@ -1328,13 +1352,14 @@
       [instanceOf=DirectoryEntry] object destinationEntry,
       GetDisallowedTransfersCallback callback);
 
-  // Returns the list of files that have any Data Leak Prevention restriction rule set.
+  // Returns the list of DlpMetadata containing DLP information
+  // about the entries.
   // |entries| List of the source entries to be checked.
   // |callback| Result callback.
   [nocompile]
-  static void getFilesRestrictedByDlp(
+  static void getDlpMetadata(
       [instanceOf=Entry] object[] entries,
-      GetFilesRestrictedByDlpCallback callback);
+      GetDlpMetadataCallback callback);
 
   // Starts to copy an entry. If the source is a directory, the copy is done
   // recursively.
diff --git a/chrome/common/extensions/api/file_manager_private_internal.idl b/chrome/common/extensions/api/file_manager_private_internal.idl
index ca236f0..7ead75b 100644
--- a/chrome/common/extensions/api/file_manager_private_internal.idl
+++ b/chrome/common/extensions/api/file_manager_private_internal.idl
@@ -36,8 +36,8 @@
   callback GetUrlCallback = void(DOMString url);
   callback GetDisallowedTransfersCallback =
       void(EntryDescription[] entries);
-  callback GetFilesRestrictedByDlpCallback =
-      void(EntryDescription[] entries);
+  callback GetDlpMetadataCallback =
+      void(fileManagerPrivate.DlpMetadata[] entries);
   callback StartCopyCallback = void(long copyId);
   callback IOTaskIdCallback = void(long taskId);
   callback ZipSelectionCallback = void(long zipId, double totalBytes);
@@ -98,8 +98,8 @@
     static void getDisallowedTransfers(DOMString[] entries,
                                        DOMString destinationEntry,
                                        GetDisallowedTransfersCallback callback);
-    static void getFilesRestrictedByDlp(DOMString[] entries,
-                                        GetFilesRestrictedByDlpCallback callback);
+    static void getDlpMetadata(DOMString[] entries,
+                               GetDlpMetadataCallback callback);
     static void startCopy(DOMString url,
                           DOMString parentUrl,
                           DOMString newName,
diff --git a/chrome/renderer/resources/extensions/file_manager_private_custom_bindings.js b/chrome/renderer/resources/extensions/file_manager_private_custom_bindings.js
index 2bc4603..24f8f72 100644
--- a/chrome/renderer/resources/extensions/file_manager_private_custom_bindings.js
+++ b/chrome/renderer/resources/extensions/file_manager_private_custom_bindings.js
@@ -225,9 +225,9 @@
       });
 
   apiFunctions.setHandleRequest(
-      'getFilesRestrictedByDlp', function(entries, callback) {
+      'getDlpMetadata', function(entries, callback) {
         var sourceUrls = entries.map(getEntryURL);
-        fileManagerPrivateInternal.getFilesRestrictedByDlp(
+        fileManagerPrivateInternal.getDlpMetadata(
             sourceUrls, callback);
       });
 
@@ -405,3 +405,15 @@
   }
   dispatch(args);
 });
+
+bindingUtil.registerEventArgumentMassager(
+    'fileManagerPrivate.onIOTaskProgressStatus', function(args, dispatch) {
+      // Convert outputs arguments into real Entry objects if they exist.
+      const outputs = args[0].outputs;
+      if (outputs) {
+        for (let i = 0; i < outputs.length; i++) {
+          outputs[i] = GetExternalFileEntry(outputs[i]);
+        }
+      }
+      dispatch(args);
+    });
diff --git a/chrome/services/sharing/nearby/platform/webrtc.cc b/chrome/services/sharing/nearby/platform/webrtc.cc
index 21d054f9..cc1753d 100644
--- a/chrome/services/sharing/nearby/platform/webrtc.cc
+++ b/chrome/services/sharing/nearby/platform/webrtc.cc
@@ -19,7 +19,7 @@
 #include "third_party/nearby/src/internal/platform/logging.h"
 #include "third_party/webrtc/api/jsep.h"
 #include "third_party/webrtc/api/peer_connection_interface.h"
-#include "third_party/webrtc_overrides/task_queue_factory.h"
+#include "third_party/webrtc_overrides/metronome_task_queue_factory.h"
 #include "unicode/locid.h"
 
 namespace location {
@@ -415,7 +415,8 @@
   DCHECK(rtc_worker_thread_);
 
   webrtc::PeerConnectionFactoryDependencies factory_dependencies;
-  factory_dependencies.task_queue_factory = CreateWebRtcTaskQueueFactory();
+  factory_dependencies.task_queue_factory =
+      CreateWebRtcMetronomeTaskQueueFactory();
   factory_dependencies.network_thread = rtc_network_thread_;
   factory_dependencies.worker_thread = rtc_worker_thread_;
   factory_dependencies.signaling_thread = rtc_signaling_thread_;
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 0442ad7..a18e842 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -6858,6 +6858,8 @@
       "//components/send_tab_to_self:test_support",
       "//components/services/app_service:unit_tests",
       "//components/services/app_service/public/cpp:icon_loader_test_support",
+      "//components/services/app_service/public/cpp:preferred_app",
+      "//components/services/app_service/public/cpp:preferred_apps",
       "//components/services/app_service/public/cpp:publisher",
       "//components/services/app_service/public/cpp:test_support",
       "//components/services/app_service/public/cpp:unit_tests",
diff --git a/chrome/test/base/test_chrome_web_ui_controller_factory.cc b/chrome/test/base/test_chrome_web_ui_controller_factory.cc
index 2c72f8a..0827243e 100644
--- a/chrome/test/base/test_chrome_web_ui_controller_factory.cc
+++ b/chrome/test/base/test_chrome_web_ui_controller_factory.cc
@@ -71,6 +71,8 @@
                               std::make_unique<TestDataSource>("webui"));
 
   content::WebUIDataSource* source = webui::CreateWebUITestDataSource();
+  if (provider)
+    provider->DataSourceOverrides(source);
   content::WebUIDataSource::Add(profile, source);
 
   return controller;
diff --git a/chrome/test/base/test_chrome_web_ui_controller_factory.h b/chrome/test/base/test_chrome_web_ui_controller_factory.h
index f5b6ab9..25b654458 100644
--- a/chrome/test/base/test_chrome_web_ui_controller_factory.h
+++ b/chrome/test/base/test_chrome_web_ui_controller_factory.h
@@ -11,6 +11,7 @@
 
 #include "chrome/browser/ui/webui/chrome_web_ui_controller_factory.h"
 #include "content/public/browser/web_ui.h"
+#include "content/public/browser/web_ui_data_source.h"
 
 // A test implementation of ChromeWebUIControllerFactory that provides a
 // registry to override CreateWebUIControllerForURL() by host.
@@ -25,6 +26,10 @@
         content::WebUI* web_ui,
         const GURL& url) = 0;
 
+    // Override this method to customize `source` for the newly created WebUI
+    // controller.
+    virtual void DataSourceOverrides(content::WebUIDataSource* source) {}
+
    protected:
     virtual ~WebUIProvider();
   };
diff --git a/chrome/test/base/testing_profile.cc b/chrome/test/base/testing_profile.cc
index 9531e5c..c30a0e8 100644
--- a/chrome/test/base/testing_profile.cc
+++ b/chrome/test/base/testing_profile.cc
@@ -108,10 +108,12 @@
 #include "testing/gmock/include/gmock/gmock.h"
 
 #if BUILDFLAG(ENABLE_EXTENSIONS)
+#include "chrome/browser/ash/system_web_apps/system_web_app_manager_factory.h"
 #include "chrome/browser/extensions/extension_service.h"
 #include "chrome/browser/extensions/extension_special_storage_policy.h"
 #include "chrome/browser/extensions/extension_system_factory.h"
 #include "chrome/browser/extensions/test_extension_system.h"
+#include "chrome/browser/web_applications/system_web_apps/test/test_system_web_app_manager.h"
 #include "chrome/browser/web_applications/test/fake_web_app_provider.h"
 #include "chrome/browser/web_applications/web_app_provider_factory.h"
 #include "components/guest_view/browser/guest_view_manager.h"
@@ -413,6 +415,9 @@
 
   web_app::WebAppProviderFactory::GetInstance()->SetTestingFactory(
       this, base::BindRepeating(&web_app::FakeWebAppProvider::BuildDefault));
+  ash::SystemWebAppManagerFactory::GetInstance()->SetTestingFactory(
+      this,
+      base::BindRepeating(&web_app::TestSystemWebAppManager::BuildDefault));
 #endif
 
   // Prefs for incognito profiles are set in CreateIncognitoPrefService().
diff --git a/chrome/test/data/webrtc/region_capture_embedded.html b/chrome/test/data/webrtc/region_capture_embedded.html
index 03591155..3f1cdce1 100644
--- a/chrome/test/data/webrtc/region_capture_embedded.html
+++ b/chrome/test/data/webrtc/region_capture_embedded.html
@@ -19,17 +19,11 @@
       }
     </script>
   </head>
-  <body onload="onLoad();">
+  <body onload="reportEmbeddingSuccess();">
     <div id="div">
       <!-- This DIV is just a convenient target for cropTargetFromElement. -->
-      <p id="p_id">0</p>
+      <h1>Region Capture Test - Page 1 (Embedded)</h1>
     </div>
-    <iframe id="mailman_frame" hidden></iframe>
-    <script>
-      function onLoad() {
-        animate(document.getElementById('p_id'));
-        reportEmbeddingSuccess();
-      }
-    </script>
+    <iframe id="mailman_frame"></iframe>
   </body>
 </html>
diff --git a/chrome/test/data/webrtc/region_capture_helpers.js b/chrome/test/data/webrtc/region_capture_helpers.js
index 2ccdcd1..4e888f69 100644
--- a/chrome/test/data/webrtc/region_capture_helpers.js
+++ b/chrome/test/data/webrtc/region_capture_helpers.js
@@ -65,12 +65,6 @@
   }
 }
 
-function animate(element) {
-  const animationCallback = function() {
-    element.innerHTML = parseInt(element.innerHTML) + 1;
-  };
-  setInterval(animationCallback, 20);
-}
 
 /////////////////////////////////////////
 // Main actions from C++ test fixture. //
diff --git a/chrome/test/data/webrtc/region_capture_main.html b/chrome/test/data/webrtc/region_capture_main.html
index 50b7b6bc3..e521ac3 100644
--- a/chrome/test/data/webrtc/region_capture_main.html
+++ b/chrome/test/data/webrtc/region_capture_main.html
@@ -54,18 +54,13 @@
       }
     </script>
   </head>
-  <body onload="onLoad();">
+  <body>
     <div id="div">
       <!-- This DIV is just a convenient target for cropTargetFromElement. -->
-      <p id="p_id">0</p>
+      <h1>Region Capture Test - Page 1 (Main)</h1>
+      <br/>
     </div>
-    <br />
     <iframe id="embedded_frame" allow="display-capture *"></iframe>
-    <iframe id="mailman_frame" hidden></iframe>
-    <script>
-      function onLoad() {
-        animate(document.getElementById('p_id'));
-      }
-    </script>
+    <iframe id="mailman_frame"></iframe>
   </body>
 </html>
diff --git a/chrome/test/data/webrtc/region_capture_other_embedded.html b/chrome/test/data/webrtc/region_capture_other_embedded.html
index ae0f488..3c1e6f4 100644
--- a/chrome/test/data/webrtc/region_capture_other_embedded.html
+++ b/chrome/test/data/webrtc/region_capture_other_embedded.html
@@ -14,17 +14,11 @@
       }
     </script>
   </head>
-  <body onload="onLoad();">
+  <body onload="reportEmbeddingSuccess();">
     <div id="div">
       <!-- This DIV is just a convenient target for cropTargetFromElement. -->
-      <p id="p_id">0</p>
+      <h1>Region Capture Test - Page 2 (Embedded)</h1>
     </div>
-    <iframe id="mailman_frame" hidden></iframe>
-    <script>
-      function onLoad() {
-        animate(document.getElementById('p_id'));
-        reportEmbeddingSuccess();
-      }
-    </script>
+    <iframe id="mailman_frame"></iframe>
   </body>
 </html>
diff --git a/chrome/test/data/webrtc/region_capture_other_main.html b/chrome/test/data/webrtc/region_capture_other_main.html
index 0b69455..8ad30d5e 100644
--- a/chrome/test/data/webrtc/region_capture_other_main.html
+++ b/chrome/test/data/webrtc/region_capture_other_main.html
@@ -10,18 +10,13 @@
       setRole("top-level");
     </script>
   </head>
-  <body onload="onLoad();">
+  <body>
     <div id="div">
       <!-- This DIV is just a convenient target for cropTargetFromElement. -->
-      <p id="p_id">0</p>
-      <br />
+      <h1>Region Capture Test - Page 2 (Main)</h1>
+      <br/>
       <iframe id="embedded_frame" allow="display-capture *"></iframe>
     </div>
-    <iframe id="mailman_frame" hidden></iframe>
-    <script>
-      function onLoad() {
-        animate(document.getElementById('p_id'));
-      }
-    </script>
+    <iframe id="mailman_frame"></iframe>
   </body>
 </html>
diff --git a/chrome/test/data/webui/BUILD.gn b/chrome/test/data/webui/BUILD.gn
index 00129822..466d0147 100644
--- a/chrome/test/data/webui/BUILD.gn
+++ b/chrome/test/data/webui/BUILD.gn
@@ -359,6 +359,7 @@
 }
 
 grit("resources") {
+  testonly = true
   defines = chrome_grit_defines
 
   # These arguments are needed since the grd is generated at build time.
@@ -376,6 +377,7 @@
 }
 
 generate_grd("build_chai_grdp") {
+  testonly = true
   grd_prefix = "webui_generated_test"
   out_grd = "$target_gen_dir/chai_resources.grdp"
   input_files_base_dir = rebase_path("//third_party/chaijs", "//")
@@ -383,6 +385,7 @@
 }
 
 generate_grd("build_web_ui_test_mojo_grdp") {
+  testonly = true
   grd_prefix = "webui_generated_test"
   out_grd = "$target_gen_dir/web_ui_test_mojo_resources.grdp"
   input_files_base_dir = rebase_path(target_gen_dir, root_build_dir)
@@ -391,6 +394,7 @@
 }
 
 generate_grd("build_grd") {
+  testonly = true
   grd_prefix = "webui_generated_test"
   output_files_base_dir = "test/data/grit"
   out_grd = "$target_gen_dir/resources.grd"
@@ -500,9 +504,16 @@
   }
 
   if (is_chromeos_ash) {
-    deps += [ "chromeos/personalization_app:build_grdp" ]
-    grdp_files +=
-        [ "$target_gen_dir/chromeos/personalization_app/resources.grdp" ]
+    deps += [
+      "chromeos/personalization_app:build_grdp",
+      "//ui/file_manager:build_tests_gen_grdp",
+      "//ui/file_manager:build_tests_grdp",
+    ]
+    grdp_files += [
+      "$target_gen_dir/chromeos/personalization_app/resources.grdp",
+      "$root_gen_dir/ui/file_manager/tests_resources.grdp",
+      "$root_gen_dir/ui/file_manager/tests_gen_resources.grdp",
+    ]
   }
 
   manifest_files = [ "$target_gen_dir/tsconfig.manifest" ]
diff --git a/chrome/test/data/webui/chromeos/os_feedback_ui/confirmation_page_test.js b/chrome/test/data/webui/chromeos/os_feedback_ui/confirmation_page_test.js
index 651a90fd..0a0d8440 100644
--- a/chrome/test/data/webui/chromeos/os_feedback_ui/confirmation_page_test.js
+++ b/chrome/test/data/webui/chromeos/os_feedback_ui/confirmation_page_test.js
@@ -117,6 +117,7 @@
         getElement(diagnosticsLink, '#startIcon').icon);
     assertEquals(
         'Diagnostics app', getElementContent(diagnosticsLink, '#label'));
+    assertTrue(page.i18nExists('diagnosticsAppLabel'));
     assertEquals(
         'Run tests and troubleshooting for hardware issues',
         getElementContent(diagnosticsLink, '#subLabel'));
diff --git a/chrome/test/data/webui/chromeos/os_feedback_ui/share_data_page_test.js b/chrome/test/data/webui/chromeos/os_feedback_ui/share_data_page_test.js
index f298064..d36ba6f 100644
--- a/chrome/test/data/webui/chromeos/os_feedback_ui/share_data_page_test.js
+++ b/chrome/test/data/webui/chromeos/os_feedback_ui/share_data_page_test.js
@@ -108,6 +108,7 @@
     // Screenshot elements.
     assertTrue(!!getElement('#screenshotCheckbox'));
     assertEquals('Screenshot', getElementContent('#screenshotCheckLabel'));
+    assertTrue(page.i18nExists('attachScreenshotLabel'));
     assertTrue(!!getElement('#screenshotImage'));
 
     // Add file attachment element.
diff --git a/chrome/test/data/webui/settings/privacy_sandbox_test.ts b/chrome/test/data/webui/settings/privacy_sandbox_test.ts
index 0be4dfc..1bd0b37e 100644
--- a/chrome/test/data/webui/settings/privacy_sandbox_test.ts
+++ b/chrome/test/data/webui/settings/privacy_sandbox_test.ts
@@ -267,7 +267,7 @@
 
   function assertMainViewVisible() {
     assertEquals(
-        page.privacySandboxSettingsView_, PrivacySandboxSettingsView.MAIN);
+        page.privacySandboxSettingsView, PrivacySandboxSettingsView.MAIN);
     const dialogWrapper =
         page.shadowRoot!.querySelector<CrDialogElement>('#dialogWrapper');
     assertFalse(!!dialogWrapper);
@@ -275,7 +275,7 @@
 
   function assertLearnMoreDialogVisible() {
     assertEquals(
-        page.privacySandboxSettingsView_,
+        page.privacySandboxSettingsView,
         PrivacySandboxSettingsView.LEARN_MORE_DIALOG);
     const dialogWrapper =
         page.shadowRoot!.querySelector<CrDialogElement>('#dialogWrapper');
@@ -289,7 +289,7 @@
 
   function assertAdPersonalizationDialogVisible() {
     assertEquals(
-        page.privacySandboxSettingsView_,
+        page.privacySandboxSettingsView,
         PrivacySandboxSettingsView.AD_PERSONALIZATION_DIALOG);
     const dialogWrapper =
         page.shadowRoot!.querySelector<CrDialogElement>('#dialogWrapper');
@@ -310,7 +310,7 @@
 
   function assertAdPersonalizationRemovedDialogVisible() {
     assertEquals(
-        page.privacySandboxSettingsView_,
+        page.privacySandboxSettingsView,
         PrivacySandboxSettingsView.AD_PERSONALIZATION_REMOVED_DIALOG);
     const dialogWrapper =
         page.shadowRoot!.querySelector<CrDialogElement>('#dialogWrapper');
@@ -331,7 +331,7 @@
 
   function assertAdMeasurementDialogVisible() {
     assertEquals(
-        page.privacySandboxSettingsView_,
+        page.privacySandboxSettingsView,
         PrivacySandboxSettingsView.AD_MEASUREMENT_DIALOG);
     const dialogWrapper =
         page.shadowRoot!.querySelector<CrDialogElement>('#dialogWrapper');
@@ -345,7 +345,7 @@
 
   function assertSpamAndFraudDialogVisible() {
     assertEquals(
-        page.privacySandboxSettingsView_,
+        page.privacySandboxSettingsView,
         PrivacySandboxSettingsView.SPAM_AND_FRAUD_DIALOG);
     const dialogWrapper =
         page.shadowRoot!.querySelector<CrDialogElement>('#dialogWrapper');
diff --git a/chrome/test/data/webui/signin/profile_customization_test.ts b/chrome/test/data/webui/signin/profile_customization_test.ts
index 18d72e6..6c938bea 100644
--- a/chrome/test/data/webui/signin/profile_customization_test.ts
+++ b/chrome/test/data/webui/signin/profile_customization_test.ts
@@ -6,9 +6,9 @@
 
 import {ProfileCustomizationAppElement} from 'chrome://profile-customization/profile_customization_app.js';
 import {ProfileCustomizationBrowserProxyImpl} from 'chrome://profile-customization/profile_customization_browser_proxy.js';
+import {CrButtonElement} from 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
 import {webUIListenerCallback} from 'chrome://resources/js/cr.m.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
-
 import {assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
 import {isChildVisible} from 'chrome://webui-test/test_util.js';
 
@@ -126,9 +126,20 @@
     });
 
     // Checks that the Skip button is present when the page is displayed in a
-    // dialog in Sync Promo
+    // dialog
     test('HasSkipButton', function() {
       assertEquals(inDialogDesign, isChildVisible(app, '#skipButton'));
     });
+
+    // Checks that clicking the Skip button triggers the correct browser proxy
+    // method.
+    if (inDialogDesign) {
+      test('ClickSkip', function() {
+        const skipButton =
+            app.shadowRoot!.querySelector<CrButtonElement>('#skipButton')!;
+        skipButton.click();
+        return browserProxy.whenCalled('skip');
+      });
+    }
   });
 });
diff --git a/chrome/test/data/webui/signin/test_profile_customization_browser_proxy.ts b/chrome/test/data/webui/signin/test_profile_customization_browser_proxy.ts
index cc125a2..5738ab4 100644
--- a/chrome/test/data/webui/signin/test_profile_customization_browser_proxy.ts
+++ b/chrome/test/data/webui/signin/test_profile_customization_browser_proxy.ts
@@ -10,7 +10,7 @@
   private profileInfo_: ProfileInfo;
 
   constructor() {
-    super(['done', 'initialized']);
+    super(['done', 'initialized', 'skip']);
 
     this.profileInfo_ = {
       backgroundColor: '',
@@ -32,4 +32,8 @@
   done(profileName: string) {
     this.methodCalled('done', profileName);
   }
+
+  skip() {
+    this.methodCalled('skip');
+  }
 }
diff --git a/chromeos/CHROMEOS_LKGM b/chromeos/CHROMEOS_LKGM
index ae0de0b..3e05b16 100644
--- a/chromeos/CHROMEOS_LKGM
+++ b/chromeos/CHROMEOS_LKGM
@@ -1 +1 @@
-14906.0.0
\ No newline at end of file
+14916.0.0
\ No newline at end of file
diff --git a/chromeos/ash/services/federated/public/cpp/fake_service_connection.cc b/chromeos/ash/services/federated/public/cpp/fake_service_connection.cc
index 1adac6f..8fb049570 100644
--- a/chromeos/ash/services/federated/public/cpp/fake_service_connection.cc
+++ b/chromeos/ash/services/federated/public/cpp/fake_service_connection.cc
@@ -4,27 +4,30 @@
 
 #include "chromeos/ash/services/federated/public/cpp/fake_service_connection.h"
 
-namespace chromeos {
+namespace ash {
 namespace federated {
 
 FakeServiceConnectionImpl::FakeServiceConnectionImpl() = default;
 FakeServiceConnectionImpl::~FakeServiceConnectionImpl() = default;
 
 void FakeServiceConnectionImpl::BindReceiver(
-    mojo::PendingReceiver<mojom::FederatedService> receiver) {
+    mojo::PendingReceiver<chromeos::federated::mojom::FederatedService>
+        receiver) {
   Clone(std::move(receiver));
 }
 
 void FakeServiceConnectionImpl::Clone(
-    mojo::PendingReceiver<mojom::FederatedService> receiver) {
+    mojo::PendingReceiver<chromeos::federated::mojom::FederatedService>
+        receiver) {
   receivers_.Add(this, std::move(receiver));
 }
 
-void FakeServiceConnectionImpl::ReportExample(const std::string& client_name,
-                                              mojom::ExamplePtr example) {
+void FakeServiceConnectionImpl::ReportExample(
+    const std::string& client_name,
+    chromeos::federated::mojom::ExamplePtr example) {
   LOG(INFO) << "In FakeServiceConnectionImpl::ReportExample, does nothing";
   return;
 }
 
 }  // namespace federated
-}  // namespace chromeos
+}  // namespace ash
diff --git a/chromeos/ash/services/federated/public/cpp/fake_service_connection.h b/chromeos/ash/services/federated/public/cpp/fake_service_connection.h
index caf2d936..4066bf91 100644
--- a/chromeos/ash/services/federated/public/cpp/fake_service_connection.h
+++ b/chromeos/ash/services/federated/public/cpp/fake_service_connection.h
@@ -13,14 +13,15 @@
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/receiver_set.h"
 
-namespace chromeos {
+namespace ash {
 namespace federated {
 
-// Fake implementation of chromeos::federated::ServiceConnection.
+// Fake implementation of ash::federated::ServiceConnection.
 // Handles BindReceiver by binding the receiver to itself.
 // For use with ServiceConnection::UseFakeServiceConnectionForTesting().
-class FakeServiceConnectionImpl : public ServiceConnection,
-                                  public mojom::FederatedService {
+class FakeServiceConnectionImpl
+    : public ServiceConnection,
+      public chromeos::federated::mojom::FederatedService {
  public:
   FakeServiceConnectionImpl();
   FakeServiceConnectionImpl(const FakeServiceConnectionImpl&) = delete;
@@ -30,18 +31,20 @@
 
   // ServiceConnection:
   void BindReceiver(
-      mojo::PendingReceiver<mojom::FederatedService> receiver) override;
+      mojo::PendingReceiver<chromeos::federated::mojom::FederatedService>
+          receiver) override;
 
   // mojom::FederatedService:
-  void Clone(mojo::PendingReceiver<mojom::FederatedService> receiver) override;
+  void Clone(mojo::PendingReceiver<chromeos::federated::mojom::FederatedService>
+                 receiver) override;
   void ReportExample(const std::string& client_name,
-                     mojom::ExamplePtr example) override;
+                     chromeos::federated::mojom::ExamplePtr example) override;
 
  private:
-  mojo::ReceiverSet<mojom::FederatedService> receivers_;
+  mojo::ReceiverSet<chromeos::federated::mojom::FederatedService> receivers_;
 };
 
 }  // namespace federated
-}  // namespace chromeos
+}  // namespace ash
 
 #endif  // CHROMEOS_ASH_SERVICES_FEDERATED_PUBLIC_CPP_FAKE_SERVICE_CONNECTION_H_
diff --git a/chromeos/ash/services/federated/public/cpp/federated_example_util.cc b/chromeos/ash/services/federated/public/cpp/federated_example_util.cc
index 3eaae3efb0d..b3355c5 100644
--- a/chromeos/ash/services/federated/public/cpp/federated_example_util.cc
+++ b/chromeos/ash/services/federated/public/cpp/federated_example_util.cc
@@ -4,20 +4,26 @@
 
 #include "chromeos/ash/services/federated/public/cpp/federated_example_util.h"
 
-namespace chromeos {
+namespace ash {
 namespace federated {
 
-mojom::ValueListPtr CreateInt64List(const std::vector<int64_t>& values) {
-  return mojom::ValueList::NewInt64List(mojom::Int64List::New(values));
+chromeos::federated::mojom::ValueListPtr CreateInt64List(
+    const std::vector<int64_t>& values) {
+  return chromeos::federated::mojom::ValueList::NewInt64List(
+      chromeos::federated::mojom::Int64List::New(values));
 }
 
-mojom::ValueListPtr CreateFloatList(const std::vector<double>& values) {
-  return mojom::ValueList::NewFloatList(mojom::FloatList::New(values));
+chromeos::federated::mojom::ValueListPtr CreateFloatList(
+    const std::vector<double>& values) {
+  return chromeos::federated::mojom::ValueList::NewFloatList(
+      chromeos::federated::mojom::FloatList::New(values));
 }
 
-mojom::ValueListPtr CreateStringList(const std::vector<std::string>& values) {
-  return mojom::ValueList::NewStringList(mojom::StringList::New(values));
+chromeos::federated::mojom::ValueListPtr CreateStringList(
+    const std::vector<std::string>& values) {
+  return chromeos::federated::mojom::ValueList::NewStringList(
+      chromeos::federated::mojom::StringList::New(values));
 }
 
 }  // namespace federated
-}  // namespace chromeos
+}  // namespace ash
diff --git a/chromeos/ash/services/federated/public/cpp/federated_example_util.h b/chromeos/ash/services/federated/public/cpp/federated_example_util.h
index 7437dc2..b22fc7a 100644
--- a/chromeos/ash/services/federated/public/cpp/federated_example_util.h
+++ b/chromeos/ash/services/federated/public/cpp/federated_example_util.h
@@ -10,15 +10,18 @@
 
 #include "chromeos/ash/services/federated/public/mojom/example.mojom.h"
 
-namespace chromeos {
+namespace ash {
 namespace federated {
 
 // Helper functions for creating different ValueList.
-mojom::ValueListPtr CreateInt64List(const std::vector<int64_t>& values);
-mojom::ValueListPtr CreateFloatList(const std::vector<double>& values);
-mojom::ValueListPtr CreateStringList(const std::vector<std::string>& values);
+chromeos::federated::mojom::ValueListPtr CreateInt64List(
+    const std::vector<int64_t>& values);
+chromeos::federated::mojom::ValueListPtr CreateFloatList(
+    const std::vector<double>& values);
+chromeos::federated::mojom::ValueListPtr CreateStringList(
+    const std::vector<std::string>& values);
 
 }  // namespace federated
-}  // namespace chromeos
+}  // namespace ash
 
 #endif  // CHROMEOS_ASH_SERVICES_FEDERATED_PUBLIC_CPP_FEDERATED_EXAMPLE_UTIL_H_
diff --git a/chromeos/ash/services/federated/public/cpp/service_connection.cc b/chromeos/ash/services/federated/public/cpp/service_connection.cc
index 6e334ea4..2f58667 100644
--- a/chromeos/ash/services/federated/public/cpp/service_connection.cc
+++ b/chromeos/ash/services/federated/public/cpp/service_connection.cc
@@ -14,7 +14,7 @@
 #include "mojo/public/cpp/system/invitation.h"
 #include "third_party/cros_system_api/dbus/service_constants.h"
 
-namespace chromeos {
+namespace ash {
 namespace federated {
 
 namespace {
@@ -31,7 +31,8 @@
 
   // ServiceConnection:
   void BindReceiver(
-      mojo::PendingReceiver<mojom::FederatedService> receiver) override;
+      mojo::PendingReceiver<chromeos::federated::mojom::FederatedService>
+          receiver) override;
 
  private:
   // Binds the top level interface |federated_service_| to an
@@ -46,7 +47,7 @@
   // Response callback for FederatedClient::BootstrapMojoConnection.
   void OnBootstrapMojoConnectionResponse(bool success);
 
-  mojo::Remote<mojom::FederatedService> federated_service_;
+  mojo::Remote<chromeos::federated::mojom::FederatedService> federated_service_;
 
   SEQUENCE_CHECKER(sequence_checker_);
 };
@@ -56,7 +57,8 @@
 }
 
 void ServiceConnectionImpl::BindReceiver(
-    mojo::PendingReceiver<mojom::FederatedService> receiver) {
+    mojo::PendingReceiver<chromeos::federated::mojom::FederatedService>
+        receiver) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   BindFederatedServiceIfNeeded();
   federated_service_->Clone(std::move(receiver));
@@ -81,8 +83,9 @@
 
   // Bind our end of |pipe| to our mojo::Remote<FederatedService>. The daemon
   // should bind its end to a FederatedService implementation.
-  federated_service_.Bind(mojo::PendingRemote<mojom::FederatedService>(
-      std::move(pipe), 0u /* version */));
+  federated_service_.Bind(
+      mojo::PendingRemote<chromeos::federated::mojom::FederatedService>(
+          std::move(pipe), 0u /* version */));
   federated_service_.set_disconnect_handler(base::BindOnce(
       &ServiceConnectionImpl::OnMojoDisconnect, base::Unretained(this)));
 
@@ -131,4 +134,4 @@
 }
 
 }  // namespace federated
-}  // namespace chromeos
+}  // namespace ash
diff --git a/chromeos/ash/services/federated/public/cpp/service_connection.h b/chromeos/ash/services/federated/public/cpp/service_connection.h
index 122fbdfa1..1f27bef 100644
--- a/chromeos/ash/services/federated/public/cpp/service_connection.h
+++ b/chromeos/ash/services/federated/public/cpp/service_connection.h
@@ -8,13 +8,13 @@
 #include "chromeos/ash/services/federated/public/mojom/federated_service.mojom.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 
-namespace chromeos {
+namespace ash {
 namespace federated {
 
 // Encapsulates a connection to the Chrome OS Federated Service daemon via its
 // Mojo interface. Usage:
 //  mojo::Remote<FederatedService> federated_service;
-//  chromeos::federated::ServiceConnection::GetInstance()->BindReceiver(
+//  ash::federated::ServiceConnection::GetInstance()->BindReceiver(
 //        federated_service.BindNewPipeAndPassReceiver());
 //  if (federated_service) {
 //    chromeos::federated::mojom::ExamplePtr example = ...;
@@ -31,7 +31,8 @@
 
   // Binds the receiver to the implementation in the Federated Service daemon.
   virtual void BindReceiver(
-      mojo::PendingReceiver<mojom::FederatedService> receiver) = 0;
+      mojo::PendingReceiver<chromeos::federated::mojom::FederatedService>
+          receiver) = 0;
 
  protected:
   ServiceConnection() = default;
@@ -54,6 +55,6 @@
 };
 
 }  // namespace federated
-}  // namespace chromeos
+}  // namespace ash
 
 #endif  // CHROMEOS_ASH_SERVICES_FEDERATED_PUBLIC_CPP_SERVICE_CONNECTION_H_
diff --git a/chromeos/ash/services/federated/public/cpp/service_connection_unittest.cc b/chromeos/ash/services/federated/public/cpp/service_connection_unittest.cc
index cd67e459..6dacd08 100644
--- a/chromeos/ash/services/federated/public/cpp/service_connection_unittest.cc
+++ b/chromeos/ash/services/federated/public/cpp/service_connection_unittest.cc
@@ -16,7 +16,7 @@
 #include "mojo/core/embedder/scoped_ipc_support.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
-namespace chromeos {
+namespace ash {
 namespace federated {
 namespace {
 
@@ -58,8 +58,8 @@
 
 // Tests that BindReceiver runs OK (no crash) in a basic Mojo environment.
 TEST_F(FederatedServiceConnectionTest, ReportExample) {
-  mojo::Remote<mojom::FederatedService> federated_service;
-  chromeos::federated::ServiceConnection::GetInstance()->BindReceiver(
+  mojo::Remote<chromeos::federated::mojom::FederatedService> federated_service;
+  ServiceConnection::GetInstance()->BindReceiver(
       federated_service.BindNewPipeAndPassReceiver());
 }
 
@@ -70,8 +70,8 @@
   ScopedFakeServiceConnectionForTest scoped_fake_for_test(
       &fake_service_connection);
 
-  mojo::Remote<mojom::FederatedService> federated_service;
-  chromeos::federated::ServiceConnection::GetInstance()->BindReceiver(
+  mojo::Remote<chromeos::federated::mojom::FederatedService> federated_service;
+  ServiceConnection::GetInstance()->BindReceiver(
       federated_service.BindNewPipeAndPassReceiver());
   EXPECT_TRUE(federated_service.is_bound());
   EXPECT_TRUE(federated_service.is_connected());
@@ -82,4 +82,4 @@
 
 }  // namespace
 }  // namespace federated
-}  // namespace chromeos
+}  // namespace ash
diff --git a/chromeos/chromeos_strings.grd b/chromeos/chromeos_strings.grd
index 0b95496..bb3e9dd9 100644
--- a/chromeos/chromeos_strings.grd
+++ b/chromeos/chromeos_strings.grd
@@ -3413,10 +3413,16 @@
       <message name="IDS_FEEDBACK_TOOL_SHARE_DIAGNOSTIC_DATA_LABEL" desc="Label showing the share diagnostic data.">
         Share diagnostic data
       </message>
+      <message name="IDS_FEEDBACK_TOOL_SCREENSHOT_LABEL" desc="Label for the screenshot field">
+        Screenshot
+      </message>
       <!-- Confirmation Page -->
       <message name="IDS_FEEDBACK_TOOL_PAGE_TITLE_AFTER_SENT" desc="Label showing a thank you message as the title of the confirmation page after a report has been sent.">
         Thanks for your feedback
       </message>
+      <message name="IDS_FEEDBACK_TOOL_RESOURCES_DIAGNOSTICS_APP_LABEL" desc="Label of the diagnostics app">
+        Diagnostics app
+      </message>
       <!-- End of Feedback Tool -->
     </messages>
   </release>
diff --git a/chromeos/chromeos_strings_grd/IDS_FEEDBACK_TOOL_RESOURCES_DIAGNOSTICS_APP_LABEL.png.sha1 b/chromeos/chromeos_strings_grd/IDS_FEEDBACK_TOOL_RESOURCES_DIAGNOSTICS_APP_LABEL.png.sha1
new file mode 100644
index 0000000..4f607c0
--- /dev/null
+++ b/chromeos/chromeos_strings_grd/IDS_FEEDBACK_TOOL_RESOURCES_DIAGNOSTICS_APP_LABEL.png.sha1
@@ -0,0 +1 @@
+1a29394e35efa33c04ab03a3e42de689305d0ef2
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_FEEDBACK_TOOL_SCREENSHOT_LABEL.png.sha1 b/chromeos/chromeos_strings_grd/IDS_FEEDBACK_TOOL_SCREENSHOT_LABEL.png.sha1
new file mode 100644
index 0000000..e0278c0
--- /dev/null
+++ b/chromeos/chromeos_strings_grd/IDS_FEEDBACK_TOOL_SCREENSHOT_LABEL.png.sha1
@@ -0,0 +1 @@
+956296e35b4f3c6c8c023b03684fabeba4e929f2
\ No newline at end of file
diff --git a/chromeos/crosapi/mojom/networking_private.mojom b/chromeos/crosapi/mojom/networking_private.mojom
index 631ee57a..209b34d 100644
--- a/chromeos/crosapi/mojom/networking_private.mojom
+++ b/chromeos/crosapi/mojom/networking_private.mojom
@@ -82,6 +82,10 @@
   [MinVersion=2]
   OnPortalDetectionCompleted@3(string networkGuid,
                                CaptivePortalStatus status);
+
+  // Fired when any certificate list has changed.
+  [MinVersion=3]
+  OnCertificateListsChanged@4();
 };
 
 // This interface mirrors the NetworkingPrivateDelegate from Lacros to Ash to
diff --git a/components/autofill/content/renderer/form_autofill_util.cc b/components/autofill/content/renderer/form_autofill_util.cc
index 4e9c8d3d..1b5cbac 100644
--- a/components/autofill/content/renderer/form_autofill_util.cc
+++ b/components/autofill/content/renderer/form_autofill_util.cc
@@ -253,34 +253,37 @@
     return std::u16string();
 
   // Ignore elements known not to contain inferable labels.
+  bool skip_node = false;
   if (node.IsElementNode()) {
     const WebElement element = node.To<WebElement>();
-    if (IsOptionElement(element) || IsScriptElement(element) ||
-        IsNoScriptElement(element) ||
+    if (IsOptionElement(element) ||
+        (element.HasHTMLTagName("div") && base::Contains(divs_to_skip, node)) ||
         (element.IsFormControlElement() &&
          IsAutofillableElement(element.To<WebFormControlElement>()))) {
       return std::u16string();
     }
-
-    if (element.HasHTMLTagName("div") && base::Contains(divs_to_skip, node))
-      return std::u16string();
+    skip_node = IsScriptElement(element) || IsNoScriptElement(element);
   }
 
-  // Extract the text exactly at this node.
-  std::u16string node_text = node.NodeValue().Utf16();
+  std::u16string node_text;
 
-  // Recursively compute the children's text.
-  // Preserve inter-element whitespace separation.
-  std::u16string child_text =
-      FindChildTextInner(node.FirstChild(), depth - 1, divs_to_skip);
-  bool add_space = node.IsTextNode() && node_text.empty();
-  node_text = CombineAndCollapseWhitespace(node_text, child_text, add_space);
+  if (!skip_node) {
+    // Extract the text exactly at this node.
+    node_text = node.NodeValue().Utf16();
+
+    // Recursively compute the children's text.
+    // Preserve inter-element whitespace separation.
+    std::u16string child_text =
+        FindChildTextInner(node.FirstChild(), depth - 1, divs_to_skip);
+    bool add_space = node.IsTextNode() && node_text.empty();
+    node_text = CombineAndCollapseWhitespace(node_text, child_text, add_space);
+  }
 
   // Recursively compute the siblings' text.
   // Again, preserve inter-element whitespace separation.
   std::u16string sibling_text =
       FindChildTextInner(node.NextSibling(), depth - 1, divs_to_skip);
-  add_space = node.IsTextNode() && node_text.empty();
+  bool add_space = node.IsTextNode() && node_text.empty();
   node_text = CombineAndCollapseWhitespace(node_text, sibling_text, add_space);
 
   return node_text;
@@ -358,8 +361,13 @@
       // A text node's value will be empty if it is for a line break.
       bool add_space = sibling.IsTextNode() && value.empty();
       inferred_label_source = FormFieldData::LabelSource::kCombined;
-      inferred_label =
-          CombineAndCollapseWhitespace(value, inferred_label, add_space);
+      if (forward) {
+        inferred_label =
+            CombineAndCollapseWhitespace(inferred_label, value, add_space);
+      } else {
+        inferred_label =
+            CombineAndCollapseWhitespace(value, inferred_label, add_space);
+      }
       continue;
     }
 
@@ -649,8 +657,14 @@
 // e.g. <div>Some Text<span><input ...></span></div>
 // e.g. <div>Some Text</div><div><input ...></div>
 //
-// Because this is already traversing the <div> structure, if it finds a <label>
-// sibling along the way, infer from that <label>.
+// Contrary to the other InferLabelFrom* functions, this functions walks up
+// the DOM tree from the original input, instead of down from the surrounding
+// tag. While doing so, if a <label> or text node sibling are found along the
+// way, a label is inferred from them directly. For example, <div>First
+// name<div><input></div>Last name<div><input></div></div> infers "First name"
+// and "Last name" for the two inputs, respectively, by picking up the text
+// nodes on the way to the surrounding div. Without doing so, the label of both
+// inputs becomes "First nameLast name".
 std::u16string InferLabelFromDivTable(const WebFormControlElement& element) {
   WebNode node = element.ParentNode();
   bool looking_for_parent = true;
@@ -679,11 +693,21 @@
       }
 
       looking_for_parent = false;
-    } else if (!looking_for_parent && HasTagName(node, *kLabel)) {
-      WebLabelElement label_element = node.To<WebLabelElement>();
-      if (label_element.CorrespondingControl().IsNull())
+    } else if (!looking_for_parent) {
+      // Infer a label from text nodes and unassigned <label> siblings.
+      if (HasTagName(node, *kLabel) &&
+          node.To<WebLabelElement>().CorrespondingControl().IsNull()) {
         inferred_label = FindChildText(node);
-    } else if (looking_for_parent && IsTraversableContainerElement(node)) {
+      } else if (node.IsTextNode()) {
+        // TODO(crbug.com/796918): Ideally `FindChildText()` should be used
+        // here as well. But because the function doesn't trim it's return
+        // value on every code path, the `NodeValue()` is explicitly extracted
+        // here. Trimming is necessary to skip indentation.
+        inferred_label = node.NodeValue().Utf16();
+        base::TrimWhitespace(inferred_label, base::TrimPositions::TRIM_ALL,
+                             &inferred_label);
+      }
+    } else if (IsTraversableContainerElement(node)) {
       // If the element is in a non-div container, its label most likely is too.
       break;
     }
diff --git a/components/autofill/content/renderer/form_autofill_util_browsertest.cc b/components/autofill/content/renderer/form_autofill_util_browsertest.cc
index 6c2874f..62445ea 100644
--- a/components/autofill/content/renderer/form_autofill_util_browsertest.cc
+++ b/components/autofill/content/renderer/form_autofill_util_browsertest.cc
@@ -127,7 +127,12 @@
        "</div></div></div></div></div></div></div></div></div></div></div></"
        "div>",
        u"child0child1child2child3child4"},
-  };
+      {"Skip script tags",
+       "<div id='target'><script>alert('hello');</script>label</div>",
+       u"label"},
+      {"Script tag whitespacing",
+       "<div id='target'>Auto<script>alert('hello');</script>fill</div>",
+       u"Autofill"}};
   for (auto test_case : test_cases) {
     SCOPED_TRACE(test_case.description);
     LoadHTML(test_case.html);
@@ -188,20 +193,18 @@
          <div><input id=target></div>
        </div>)",
        u"label"},
-      // TODO(crbug.com/796918): Should be label
       {"DIV table test 4", R"(
        <div>
          <div>should be skipped<input></div>
          label
          <div><input id=target></div>
        </div>)",
-       u""},
-      // TODO(crbug.com/796918): Should be label
+       u"label"},
       {"DIV table test 5",
        "<div>"
        "<div>label<div><input id='target'/></div>behind</div>"
        "</div>",
-       u"labelbehind"},
+       u"label"},
       {"DIV table test 6", R"(
        <div>
          label
@@ -211,6 +214,8 @@
        // TODO(crbug.com/796918): Should be "label" or "label-". This happens
        // because "-" is inferred, but discarded because `!IsLabelValid()`.
        u""},
+      {"Infer from next sibling",
+       "<input id='target' type='checkbox'>hello <b>world</b>", u"hello world"},
   };
   for (auto test_case : test_cases) {
     SCOPED_TRACE(test_case.description);
@@ -249,7 +254,11 @@
        FormFieldData::LabelSource::kAriaLabel},
       {"<input id='target' value='label'/>",
        FormFieldData::LabelSource::kValue},
+      // In the next test, the text node is picked up on the way up the DOM-tree
+      // by the div extraction logic.
       {"<li>label<div><input id='target'/></div></li>",
+       FormFieldData::LabelSource::kDivTable},
+      {"<li><span>label</span><div><input id='target'/></div></li>",
        FormFieldData::LabelSource::kLiTag},
       {"<table><tr><td>label</td><td><input id='target'/></td></tr></table>",
        FormFieldData::LabelSource::kTdTag},
diff --git a/components/autofill/core/browser/BUILD.gn b/components/autofill/core/browser/BUILD.gn
index 41504cf..37d2480 100644
--- a/components/autofill/core/browser/BUILD.gn
+++ b/components/autofill/core/browser/BUILD.gn
@@ -421,13 +421,6 @@
 
   sources += get_target_outputs(":regex_patterns_inl_h")
 
-  if (is_win) {
-    sources += [
-      "autofill_ie_toolbar_import_win.cc",
-      "autofill_ie_toolbar_import_win.h",
-    ]
-  }
-
   if (is_ios) {
     sources += [
       "autofill_save_update_address_profile_delegate_ios.cc",
@@ -876,10 +869,6 @@
     "webdata/web_data_service_unittest.cc",
   ]
 
-  if (is_win) {
-    sources += [ "autofill_ie_toolbar_import_win_unittest.cc" ]
-  }
-
   if (is_ios) {
     sources +=
         [ "autofill_save_update_address_profile_delegate_ios_unittest.cc" ]
diff --git a/components/autofill/core/browser/autofill_ie_toolbar_import_win.cc b/components/autofill/core/browser/autofill_ie_toolbar_import_win.cc
deleted file mode 100644
index bf3af43c9..0000000
--- a/components/autofill/core/browser/autofill_ie_toolbar_import_win.cc
+++ /dev/null
@@ -1,306 +0,0 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/autofill/core/browser/autofill_ie_toolbar_import_win.h"
-
-#include <stddef.h>
-#include <stdint.h>
-#include <map>
-#include <string>
-#include <vector>
-
-#include "base/check.h"
-#include "base/compiler_specific.h"
-#include "base/memory/raw_ptr.h"
-#include "base/win/registry.h"
-#include "components/autofill/core/browser/crypto/rc4_decryptor.h"
-#include "components/autofill/core/browser/data_model/autofill_profile.h"
-#include "components/autofill/core/browser/data_model/credit_card.h"
-#include "components/autofill/core/browser/data_model/form_group.h"
-#include "components/autofill/core/browser/data_model/phone_number.h"
-#include "components/autofill/core/browser/field_types.h"
-#include "components/autofill/core/browser/geo/autofill_country.h"
-#include "components/autofill/core/browser/geo/phone_number_i18n.h"
-#include "components/autofill/core/browser/personal_data_manager.h"
-#include "components/autofill/core/browser/personal_data_manager_observer.h"
-#include "components/os_crypt/os_crypt.h"
-
-using base::win::RegKey;
-
-namespace autofill {
-
-// Forward declaration. This function is not in unnamed namespace as it
-// is referenced in the unittest.
-bool ImportCurrentUserProfiles(const std::string& app_locale,
-                               std::vector<AutofillProfile>* profiles,
-                               std::vector<CreditCard>* credit_cards);
-namespace {
-
-const wchar_t* const kProfileKey =
-    L"Software\\Google\\Google Toolbar\\4.0\\Autofill\\Profiles";
-const wchar_t* const kCreditCardKey =
-    L"Software\\Google\\Google Toolbar\\4.0\\Autofill\\Credit Cards";
-const wchar_t* const kPasswordHashValue = L"password_hash";
-const wchar_t* const kSaltValue = L"salt";
-
-// This string is stored along with saved addresses and credit cards in the
-// WebDB, and hence should not be modified, so that it remains consistent over
-// time.
-const char kIEToolbarImportOrigin[] = "Imported from Internet Explorer";
-
-// This is RC4 decryption for Toolbar credit card data. This is necessary
-// because it is not standard, so Crypto API cannot be used.
-std::wstring DecryptCCNumber(const std::wstring& data) {
-  const wchar_t* kEmptyKey =
-    L"\x3605\xCEE5\xCE49\x44F7\xCF4E\xF6CC\x604B\xFCBE\xC70A\x08FD";
-  const size_t kMacLen = 10;
-
-  if (data.length() <= kMacLen)
-    return std::wstring();
-
-  RC4Decryptor rc4_algorithm(kEmptyKey);
-  return rc4_algorithm.Run(data.substr(kMacLen));
-}
-
-bool IsEmptySalt(std::wstring const& salt) {
-  // Empty salt in IE Toolbar is \x1\x2...\x14
-  if (salt.length() != 20)
-    return false;
-  for (size_t i = 0; i < salt.length(); ++i) {
-    if (salt[i] != i + 1)
-      return false;
-  }
-  return true;
-}
-
-std::wstring ReadAndDecryptValue(const RegKey& key, const wchar_t* value_name) {
-  DWORD data_type = REG_BINARY;
-  DWORD data_size = 0;
-  LONG result = key.ReadValue(value_name, nullptr, &data_size, &data_type);
-  if ((result != ERROR_SUCCESS) || !data_size || data_type != REG_BINARY)
-    return std::wstring();
-  std::string data;
-  data.resize(data_size);
-  result = key.ReadValue(value_name, &(data[0]), &data_size, &data_type);
-  if (result == ERROR_SUCCESS) {
-    std::string out_data;
-    if (OSCrypt::DecryptString(data, &out_data)) {
-      // The actual data is in UTF16 already.
-      if (!(out_data.size() & 1) && (out_data.size() > 2) &&
-          !out_data[out_data.size() - 1] && !out_data[out_data.size() - 2]) {
-        return reinterpret_cast<const wchar_t*>(out_data.c_str());
-      }
-    }
-  }
-  return std::wstring();
-}
-
-struct {
-  ServerFieldType field_type;
-  const wchar_t *reg_value_name;
-} profile_reg_values[] = {
-    {NAME_FIRST, L"name_first"},
-    {NAME_MIDDLE, L"name_middle"},
-    {NAME_LAST, L"name_last"},
-    {NAME_SUFFIX, L"name_suffix"},
-    {EMAIL_ADDRESS, L"email"},
-    {COMPANY_NAME, L"company_name"},
-    {PHONE_HOME_NUMBER, L"phone_home_number"},
-    {PHONE_HOME_CITY_CODE, L"phone_home_city_code"},
-    {PHONE_HOME_COUNTRY_CODE, L"phone_home_country_code"},
-    {ADDRESS_HOME_LINE1, L"address_home_line1"},
-    {ADDRESS_HOME_LINE2, L"address_home_line2"},
-    {ADDRESS_HOME_CITY, L"address_home_city"},
-    {ADDRESS_HOME_STATE, L"address_home_state"},
-    {ADDRESS_HOME_ZIP, L"address_home_zip"},
-    {ADDRESS_HOME_COUNTRY, L"address_home_country"},
-    {ADDRESS_BILLING_LINE1, L"address_billing_line1"},
-    {ADDRESS_BILLING_LINE2, L"address_billing_line2"},
-    {ADDRESS_BILLING_CITY, L"address_billing_city"},
-    {ADDRESS_BILLING_STATE, L"address_billing_state"},
-    {ADDRESS_BILLING_ZIP, L"address_billing_zip"},
-    {ADDRESS_BILLING_COUNTRY, L"address_billing_country"},
-    {CREDIT_CARD_NAME_FULL, L"credit_card_name_full"},
-    {CREDIT_CARD_NUMBER, L"credit_card_number"},
-    {CREDIT_CARD_EXP_MONTH, L"credit_card_exp_month"},
-    {CREDIT_CARD_EXP_4_DIGIT_YEAR, L"credit_card_exp_4_digit_year"},
-    {CREDIT_CARD_TYPE, L"credit_card_type"},
-    // We do not import verification code.
-};
-
-typedef std::map<std::wstring, ServerFieldType> RegToFieldMap;
-
-// Imports address or credit card data from the given registry |key| into the
-// given |form_group|, with the help of |reg_to_field|.  When importing address
-// data, writes the phone data into |phone|; otherwise, |phone| should be null.
-// Returns true if any fields were set, false otherwise.
-bool ImportSingleFormGroup(const RegKey& key,
-                           const RegToFieldMap& reg_to_field,
-                           const std::string& app_locale,
-                           FormGroup* form_group,
-                           PhoneNumber::PhoneCombineHelper* phone) {
-  if (!key.Valid())
-    return false;
-
-  bool has_non_empty_fields = false;
-
-  for (uint32_t i = 0; i < key.GetValueCount(); ++i) {
-    std::wstring value_name;
-    if (key.GetValueNameAt(i, &value_name) != ERROR_SUCCESS)
-      continue;
-
-    RegToFieldMap::const_iterator it = reg_to_field.find(value_name);
-    if (it == reg_to_field.end())
-      continue;  // This field is not imported.
-
-    std::wstring field_value = ReadAndDecryptValue(key, value_name.c_str());
-    if (!field_value.empty()) {
-      if (it->second == CREDIT_CARD_NUMBER)
-        field_value = DecryptCCNumber(field_value);
-
-      // Phone numbers are stored piece-by-piece, and then reconstructed from
-      // the pieces.  The rest of the fields are set "as is".
-      if (!phone || !phone->SetInfo(AutofillType(it->second),
-                                    base::WideToUTF16(field_value))) {
-        has_non_empty_fields = true;
-        form_group->SetInfo(AutofillType(it->second),
-                            base::WideToUTF16(field_value), app_locale);
-      }
-    }
-  }
-
-  return has_non_empty_fields;
-}
-
-// Imports address data from the given registry |key| into the given |profile|,
-// with the help of |reg_to_field|.  Returns true if any fields were set, false
-// otherwise.
-bool ImportSingleProfile(const std::string& app_locale,
-                         const RegKey& key,
-                         const RegToFieldMap& reg_to_field,
-                         AutofillProfile* profile) {
-  PhoneNumber::PhoneCombineHelper phone;
-  bool has_non_empty_fields =
-      ImportSingleFormGroup(key, reg_to_field, app_locale, profile, &phone);
-
-  // Now re-construct the phones if needed.
-  std::u16string constructed_number;
-  if (phone.ParseNumber(*profile, app_locale, &constructed_number)) {
-    has_non_empty_fields = true;
-    profile->SetRawInfo(PHONE_HOME_WHOLE_NUMBER, constructed_number);
-  }
-
-  return has_non_empty_fields;
-}
-
-// Imports profiles from the IE toolbar and stores them. Asynchronous
-// if PersonalDataManager has not been loaded yet. Deletes itself on completion.
-class AutofillImporter : public PersonalDataManagerObserver {
- public:
-  explicit AutofillImporter(PersonalDataManager* personal_data_manager)
-    : personal_data_manager_(personal_data_manager) {
-      personal_data_manager_->AddObserver(this);
-  }
-
-  bool ImportProfiles() {
-    if (!ImportCurrentUserProfiles(personal_data_manager_->app_locale(),
-                                   &profiles_,
-                                   &credit_cards_)) {
-      delete this;
-      return false;
-    }
-    if (personal_data_manager_->IsDataLoaded())
-      OnPersonalDataChanged();
-    return true;
-  }
-
-  // PersonalDataManagerObserver:
-  void OnPersonalDataChanged() override {
-    for (const AutofillProfile& it : profiles_)
-      personal_data_manager_->AddProfile(it);
-    for (const CreditCard& it : credit_cards_)
-      personal_data_manager_->AddCreditCard(it);
-    delete this;
-  }
-
- private:
-  ~AutofillImporter() override { personal_data_manager_->RemoveObserver(this); }
-
-  raw_ptr<PersonalDataManager> personal_data_manager_;
-  std::vector<AutofillProfile> profiles_;
-  std::vector<CreditCard> credit_cards_;
-};
-
-}  // namespace
-
-// Imports Autofill profiles and credit cards from IE Toolbar if present and not
-// password protected. Returns true if data is successfully retrieved. False if
-// there is no data, data is password protected or error occurred.
-bool ImportCurrentUserProfiles(const std::string& app_locale,
-                               std::vector<AutofillProfile>* profiles,
-                               std::vector<CreditCard>* credit_cards) {
-  DCHECK(profiles);
-  DCHECK(credit_cards);
-
-  // Create a map of possible fields for a quick access.
-  RegToFieldMap reg_to_field;
-  for (const auto& profile_reg_value : profile_reg_values) {
-    reg_to_field[std::wstring(profile_reg_value.reg_value_name)] =
-        profile_reg_value.field_type;
-  }
-
-  base::win::RegistryKeyIterator iterator_profiles(HKEY_CURRENT_USER,
-                                                   kProfileKey);
-  for (; iterator_profiles.Valid(); ++iterator_profiles) {
-    std::wstring key_name(kProfileKey);
-    key_name.append(L"\\");
-    key_name.append(iterator_profiles.Name());
-    RegKey key(HKEY_CURRENT_USER, key_name.c_str(), KEY_READ);
-    AutofillProfile profile;
-    profile.set_origin(kIEToolbarImportOrigin);
-    if (ImportSingleProfile(app_locale, key, reg_to_field, &profile)) {
-      // Combine phones into whole phone #.
-      profiles->push_back(profile);
-    }
-  }
-  std::wstring password_hash;
-  std::wstring salt;
-  RegKey cc_key(HKEY_CURRENT_USER, kCreditCardKey, KEY_READ);
-  if (cc_key.Valid()) {
-    password_hash = ReadAndDecryptValue(cc_key, kPasswordHashValue);
-    salt = ReadAndDecryptValue(cc_key, kSaltValue);
-  }
-
-  // We import CC profiles only if they are not password protected.
-  if (password_hash.empty() && IsEmptySalt(salt)) {
-    base::win::RegistryKeyIterator iterator_cc(HKEY_CURRENT_USER,
-                                               kCreditCardKey);
-    for (; iterator_cc.Valid(); ++iterator_cc) {
-      std::wstring key_name(kCreditCardKey);
-      key_name.append(L"\\");
-      key_name.append(iterator_cc.Name());
-      RegKey key(HKEY_CURRENT_USER, key_name.c_str(), KEY_READ);
-      CreditCard credit_card;
-      credit_card.set_origin(kIEToolbarImportOrigin);
-      if (ImportSingleFormGroup(key, reg_to_field, app_locale, &credit_card,
-                                nullptr)) {
-        std::u16string cc_number = credit_card.GetRawInfo(CREDIT_CARD_NUMBER);
-        if (!cc_number.empty())
-          credit_cards->push_back(credit_card);
-      }
-    }
-  }
-  return (profiles->size() + credit_cards->size()) > 0;
-}
-
-bool ImportAutofillDataWin(PersonalDataManager* pdm) {
-  // In incognito mode we do not have PDM - and we should not import anything.
-  if (!pdm)
-    return false;
-  AutofillImporter* importer = new AutofillImporter(pdm);
-  // importer will self delete.
-  return importer->ImportProfiles();
-}
-
-}  // namespace autofill
diff --git a/components/autofill/core/browser/autofill_ie_toolbar_import_win.h b/components/autofill/core/browser/autofill_ie_toolbar_import_win.h
deleted file mode 100644
index 830aac25..0000000
--- a/components/autofill/core/browser/autofill_ie_toolbar_import_win.h
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef COMPONENTS_AUTOFILL_CORE_BROWSER_AUTOFILL_IE_TOOLBAR_IMPORT_WIN_H_
-#define COMPONENTS_AUTOFILL_CORE_BROWSER_AUTOFILL_IE_TOOLBAR_IMPORT_WIN_H_
-
-namespace autofill {
-
-// This importer is here and not in chrome/browser/importer/toolbar_importer.cc
-// because of the following:
-// 1. The data is not saved in profile, but rather in registry, thus it is
-//   accessed without going through toolbar front end.
-// 2. This applies to IE (thus Windows) toolbar only.
-// 3. The functionality relevant only to and completely encapsulated in the
-//   autofill.
-// 4. This is completely automated as opposed to Importers, which are explicit.
-class PersonalDataManager;
-
-bool ImportAutofillDataWin(PersonalDataManager* pdm);
-
-}  // namespace autofill
-
-#endif  // COMPONENTS_AUTOFILL_CORE_BROWSER_AUTOFILL_IE_TOOLBAR_IMPORT_WIN_H_
diff --git a/components/autofill/core/browser/autofill_ie_toolbar_import_win_unittest.cc b/components/autofill/core/browser/autofill_ie_toolbar_import_win_unittest.cc
deleted file mode 100644
index 22051ec..0000000
--- a/components/autofill/core/browser/autofill_ie_toolbar_import_win_unittest.cc
+++ /dev/null
@@ -1,231 +0,0 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/autofill/core/browser/autofill_ie_toolbar_import_win.h"
-
-#include <stddef.h>
-#include <windows.h>
-
-#include <string>
-
-#include "base/strings/utf_string_conversions.h"
-#include "base/win/registry.h"
-#include "components/autofill/core/browser/data_model/autofill_profile.h"
-#include "components/autofill/core/browser/data_model/credit_card.h"
-#include "components/autofill/core/browser/field_types.h"
-#include "components/os_crypt/os_crypt.h"
-#include "components/os_crypt/os_crypt_mocker.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-using base::win::RegKey;
-
-namespace autofill {
-
-// Defined in autofill_ie_toolbar_import_win.cc. Not exposed in the header file.
-bool ImportCurrentUserProfiles(const std::string& app_locale,
-                               std::vector<AutofillProfile>* profiles,
-                               std::vector<CreditCard>* credit_cards);
-
-namespace {
-
-const wchar_t kUnitTestRegistrySubKey[] = L"SOFTWARE\\Chromium Unit Tests";
-const wchar_t kUnitTestUserOverrideSubKey[] =
-    L"SOFTWARE\\Chromium Unit Tests\\HKCU Override";
-
-const wchar_t kProfileKey[] =
-    L"Software\\Google\\Google Toolbar\\4.0\\Autofill\\Profiles";
-const wchar_t kCreditCardKey[] =
-    L"Software\\Google\\Google Toolbar\\4.0\\Autofill\\Credit Cards";
-const wchar_t kPasswordHashValue[] = L"password_hash";
-const wchar_t kSaltValue[] = L"salt";
-
-struct ValueDescription {
-  wchar_t const* const value_name;
-  wchar_t const* const value;
-};
-
-ValueDescription profile1[] = {
-  { L"name_first", L"John" },
-  { L"name_middle", L"Herman" },
-  { L"name_last", L"Doe" },
-  { L"email", L"jdoe@test.com" },
-  { L"company_name", L"Testcompany" },
-  { L"phone_home_number", L"555-5555" },
-  { L"phone_home_city_code", L"650" },
-  { L"phone_home_country_code", L"1" },
-};
-
-ValueDescription profile2[] = {
-  { L"name_first", L"Jane" },
-  { L"name_last", L"Doe" },
-  { L"email", L"janedoe@test.com" },
-  { L"company_name", L"Testcompany" },
-};
-
-ValueDescription credit_card[] = {
-    {L"credit_card_name_full", L"Tommy Gun"},
-    // "4111111111111111" encrypted:
-    {L"credit_card_number",
-     L"\xE53F\x19AB\xC1BF\xC9EB\xECCC\x9BDA\x8515"
-     L"\xE14D\x6852\x80A8\x50A3\x4375\xFD9F\x1E07"
-     L"\x790E\x7336\xB773\xAF33\x93EA\xB846\xEC89"
-     L"\x265C\xD0E6\x4E23\xB75F\x7983"},
-    {L"credit_card_exp_month", L"11"},
-    {L"credit_card_exp_4_digit_year", L"2011"},
-};
-
-ValueDescription empty_salt = {
-  kSaltValue,
-  L"\x1\x2\x3\x4\x5\x6\x7\x8\x9\xA\xB\xC\xD\xE\xF\x10\x11\x12\x13\x14"
-};
-
-ValueDescription empty_password = {
-  kPasswordHashValue, L""
-};
-
-ValueDescription protected_salt = {
-  kSaltValue, L"\x4854\xB906\x9C7C\x50A6\x4376\xFD9D\x1E02"
-};
-
-ValueDescription protected_password = {
-  kPasswordHashValue, L"\x18B7\xE586\x459B\x7457\xA066\x3842\x71DA"
-};
-
-void EncryptAndWrite(RegKey* key, const ValueDescription* value) {
-  std::string data;
-  size_t data_size = (lstrlen(value->value) + 1) * sizeof(wchar_t);
-  data.resize(data_size);
-  memcpy(&data[0], value->value, data_size);
-
-  std::string encrypted_data;
-  OSCrypt::EncryptString(data, &encrypted_data);
-  EXPECT_EQ(ERROR_SUCCESS, key->WriteValue(value->value_name,
-      &encrypted_data[0], encrypted_data.size(), REG_BINARY));
-}
-
-void CreateSubkey(RegKey* key, wchar_t const* subkey_name,
-                  const ValueDescription* values, size_t values_size) {
-  RegKey subkey;
-  subkey.Create(key->Handle(), subkey_name, KEY_ALL_ACCESS);
-  EXPECT_TRUE(subkey.Valid());
-  for (size_t i = 0; i < values_size; ++i)
-    EncryptAndWrite(&subkey, values + i);
-}
-
-}  // namespace
-
-class AutofillIeToolbarImportTest : public testing::Test {
- public:
-  AutofillIeToolbarImportTest();
-
-  AutofillIeToolbarImportTest(const AutofillIeToolbarImportTest&) = delete;
-  AutofillIeToolbarImportTest& operator=(const AutofillIeToolbarImportTest&) =
-      delete;
-
-  // testing::Test method overrides:
-  void SetUp() override;
-  void TearDown() override;
-
- private:
-  RegKey temp_hkcu_hive_key_;
-};
-
-AutofillIeToolbarImportTest::AutofillIeToolbarImportTest() {
-}
-
-void AutofillIeToolbarImportTest::SetUp() {
-  OSCryptMocker::SetUp();
-  temp_hkcu_hive_key_.Create(HKEY_CURRENT_USER,
-                             kUnitTestUserOverrideSubKey,
-                             KEY_ALL_ACCESS);
-  EXPECT_TRUE(temp_hkcu_hive_key_.Valid());
-  EXPECT_EQ(ERROR_SUCCESS, RegOverridePredefKey(HKEY_CURRENT_USER,
-                                                temp_hkcu_hive_key_.Handle()));
-}
-
-void AutofillIeToolbarImportTest::TearDown() {
-  EXPECT_EQ(ERROR_SUCCESS, RegOverridePredefKey(HKEY_CURRENT_USER, nullptr));
-  temp_hkcu_hive_key_.Close();
-  RegKey key(HKEY_CURRENT_USER, kUnitTestRegistrySubKey, KEY_ALL_ACCESS);
-  key.DeleteKey(L"");
-  OSCryptMocker::TearDown();
-}
-
-TEST_F(AutofillIeToolbarImportTest, TestAutofillImport) {
-  RegKey profile_key;
-  profile_key.Create(HKEY_CURRENT_USER, kProfileKey, KEY_ALL_ACCESS);
-  EXPECT_TRUE(profile_key.Valid());
-
-  CreateSubkey(&profile_key, L"0", profile1, std::size(profile1));
-  CreateSubkey(&profile_key, L"1", profile2, std::size(profile2));
-
-  RegKey cc_key;
-  cc_key.Create(HKEY_CURRENT_USER, kCreditCardKey, KEY_ALL_ACCESS);
-  EXPECT_TRUE(cc_key.Valid());
-  CreateSubkey(&cc_key, L"0", credit_card, std::size(credit_card));
-  EncryptAndWrite(&cc_key, &empty_password);
-  EncryptAndWrite(&cc_key, &empty_salt);
-
-  profile_key.Close();
-  cc_key.Close();
-
-  std::vector<AutofillProfile> profiles;
-  std::vector<CreditCard> credit_cards;
-  EXPECT_TRUE(ImportCurrentUserProfiles("en-US", &profiles, &credit_cards));
-  ASSERT_EQ(2U, profiles.size());
-  // The profiles are read in reverse order.
-  EXPECT_EQ(base::WideToUTF16(profile1[0].value),
-            profiles[1].GetRawInfo(NAME_FIRST));
-  EXPECT_EQ(base::WideToUTF16(profile1[1].value),
-            profiles[1].GetRawInfo(NAME_MIDDLE));
-  EXPECT_EQ(base::WideToUTF16(profile1[2].value),
-            profiles[1].GetRawInfo(NAME_LAST));
-  EXPECT_EQ(base::WideToUTF16(profile1[3].value),
-            profiles[1].GetRawInfo(EMAIL_ADDRESS));
-  EXPECT_EQ(base::WideToUTF16(profile1[4].value),
-            profiles[1].GetRawInfo(COMPANY_NAME));
-  EXPECT_EQ(base::WideToUTF16(profile1[7].value),
-            profiles[1].GetInfo(AutofillType(PHONE_HOME_COUNTRY_CODE), "US"));
-  EXPECT_EQ(base::WideToUTF16(profile1[6].value),
-            profiles[1].GetInfo(AutofillType(PHONE_HOME_CITY_CODE), "US"));
-  EXPECT_EQ(u"5555555",
-            profiles[1].GetInfo(AutofillType(PHONE_HOME_NUMBER), "US"));
-  EXPECT_EQ(u"1 650-555-5555", profiles[1].GetRawInfo(PHONE_HOME_WHOLE_NUMBER));
-
-  EXPECT_EQ(base::WideToUTF16(profile2[0].value),
-            profiles[0].GetRawInfo(NAME_FIRST));
-  EXPECT_EQ(base::WideToUTF16(profile2[1].value),
-            profiles[0].GetRawInfo(NAME_LAST));
-  EXPECT_EQ(base::WideToUTF16(profile2[2].value),
-            profiles[0].GetRawInfo(EMAIL_ADDRESS));
-  EXPECT_EQ(base::WideToUTF16(profile2[3].value),
-            profiles[0].GetRawInfo(COMPANY_NAME));
-
-  ASSERT_EQ(1U, credit_cards.size());
-  EXPECT_EQ(base::WideToUTF16(credit_card[0].value),
-            credit_cards[0].GetRawInfo(CREDIT_CARD_NAME_FULL));
-  EXPECT_EQ(u"4111111111111111",
-            credit_cards[0].GetRawInfo(CREDIT_CARD_NUMBER));
-  EXPECT_EQ(base::WideToUTF16(credit_card[2].value),
-            credit_cards[0].GetRawInfo(CREDIT_CARD_EXP_MONTH));
-  EXPECT_EQ(base::WideToUTF16(credit_card[3].value),
-            credit_cards[0].GetRawInfo(CREDIT_CARD_EXP_4_DIGIT_YEAR));
-
-  // Mock password encrypted cc.
-  cc_key.Open(HKEY_CURRENT_USER, kCreditCardKey, KEY_ALL_ACCESS);
-  EXPECT_TRUE(cc_key.Valid());
-  EncryptAndWrite(&cc_key, &protected_password);
-  EncryptAndWrite(&cc_key, &protected_salt);
-  cc_key.Close();
-
-  profiles.clear();
-  credit_cards.clear();
-  EXPECT_TRUE(ImportCurrentUserProfiles("en-US", &profiles, &credit_cards));
-  // Profiles are not protected.
-  EXPECT_EQ(2U, profiles.size());
-  // Credit cards are.
-  EXPECT_EQ(0U, credit_cards.size());
-}
-
-}  // namespace autofill
diff --git a/components/autofill/core/browser/crypto/rc4_decryptor.h b/components/autofill/core/browser/crypto/rc4_decryptor.h
deleted file mode 100644
index 4598274..0000000
--- a/components/autofill/core/browser/crypto/rc4_decryptor.h
+++ /dev/null
@@ -1,112 +0,0 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef COMPONENTS_AUTOFILL_CORE_BROWSER_CRYPTO_RC4_DECRYPTOR_H_
-#define COMPONENTS_AUTOFILL_CORE_BROWSER_CRYPTO_RC4_DECRYPTOR_H_
-
-#include <stdint.h>
-#include <string.h>
-
-#include <memory>
-#include <string>
-
-namespace autofill {
-
-// This is modified RC4 decryption used for import of Toolbar autofill data
-// only. The difference from the Crypto Api implementation is twofold:
-// First, it uses a non-standard key size (160 bit), not supported by Microsoft
-// (it supports only 40 and 128 bit for RC4). Second, it codes 128 words with
-// value 0x0020 at the beginning of the code to enhance security.
-//
-// This class used in
-// components/autofill/core/browser/autofill_ie_toolbar_import_win.cc.
-//
-// This class should not be used anywhere else!!!
-class RC4Decryptor {
- public:
-  explicit RC4Decryptor(wchar_t const* password) {
-    PrepareKey(reinterpret_cast<const uint8_t*>(password),
-               wcslen(password) * sizeof(wchar_t));
-    std::wstring data;
-    // First 128 bytes should be spaces.
-    data.resize(128, L' ');
-    Run(data.c_str());
-  }
-
-  // Run the algorithm
-  std::wstring Run(const std::wstring& data) {
-    int data_size = data.length() * sizeof(wchar_t);
-
-    std::unique_ptr<wchar_t[]> buffer(new wchar_t[data.length() + 1]);
-    memset(buffer.get(), 0, (data.length() + 1) * sizeof(wchar_t));
-    memcpy(buffer.get(), data.c_str(), data_size);
-
-    RunInternal(reinterpret_cast<uint8_t*>(buffer.get()), data_size);
-
-    std::wstring result(buffer.get());
-
-    // Clear the memory
-    memset(buffer.get(), 0, data_size);
-    return result;
-  }
-
- private:
-  static const int kKeyDataSize = 256;
-  struct Rc4Key {
-    uint8_t state[kKeyDataSize];
-    uint8_t x;
-    uint8_t y;
-  };
-
-  void SwapByte(uint8_t* byte1, uint8_t* byte2) {
-    uint8_t temp = *byte1;
-    *byte1 = *byte2;
-    *byte2 = temp;
-  }
-
-  void PrepareKey(const uint8_t* key_data, int key_data_len) {
-    uint8_t index1 = 0;
-    uint8_t index2 = 0;
-    uint8_t* state;
-    short counter;
-
-    state = &key_.state[0];
-    for (counter = 0; counter < kKeyDataSize; ++counter)
-      state[counter] = static_cast<uint8_t>(counter);
-
-    key_.x = key_.y = 0;
-
-    for (counter = 0; counter < kKeyDataSize; counter++) {
-      index2 = (key_data[index1] + state[counter] + index2) % kKeyDataSize;
-      SwapByte(&state[counter], &state[index2]);
-      index1 = (index1 + 1) % key_data_len;
-    }
-  }
-
-  void RunInternal(uint8_t* buffer, int buffer_len) {
-    uint8_t x, y;
-    uint8_t xor_index = 0;
-    uint8_t* state;
-    int counter;
-
-    x = key_.x;
-    y = key_.y;
-    state = &key_.state[0];
-    for (counter = 0; counter < buffer_len; ++counter) {
-      x = (x + 1) % kKeyDataSize;
-      y = (state[x] + y) % kKeyDataSize;
-      SwapByte(&state[x], &state[y]);
-      xor_index = (state[x] + state[y]) % kKeyDataSize;
-      buffer[counter] ^= state[xor_index];
-    }
-    key_.x = x;
-    key_.y = y;
-  }
-
-  Rc4Key key_;
-};
-
-}  // namespace autofill
-
-#endif  // COMPONENTS_AUTOFILL_CORE_BROWSER_CRYPTO_RC4_DECRYPTOR_H_
diff --git a/components/autofill/ios/form_util/resources/fill.js b/components/autofill/ios/form_util/resources/fill.js
index 5c0b8f9..e71e7590 100644
--- a/components/autofill/ios/form_util/resources/fill.js
+++ b/components/autofill/ios/form_util/resources/fill.js
@@ -1025,9 +1025,9 @@
   }
 
   // Ignore elements known not to contain inferable labels.
+  let skipNode = false;
   if (node.nodeType === Node.ELEMENT_NODE) {
-    if (node.tagName === 'OPTION' || node.tagName === 'SCRIPT' ||
-        node.tagName === 'NOSCRIPT') {
+    if (node.tagName === 'OPTION') {
       return '';
     }
     if (__gCrWeb.form.isFormControlElement(/** @type {Element} */ (node))) {
@@ -1036,6 +1036,7 @@
         return '';
       }
     }
+    skipNode = node.tagName === 'SCRIPT' || node.tagName === 'NOSCRIPT';
   }
 
   if (node.tagName === 'DIV') {
@@ -1047,30 +1048,33 @@
   }
 
   // Extract the text exactly at this node.
-  let nodeText = __gCrWeb.fill.nodeValue(node);
-  if (node.nodeType === Node.TEXT_NODE && !nodeText) {
-    // In the C++ version, this text node would have been stripped completely.
-    // Just pass the buck.
-    return __gCrWeb.fill.findChildTextInner(
-        node.nextSibling, depth, divsToSkip);
-  }
+  let nodeText = '';
+  if (!skipNode) {
+    nodeText = __gCrWeb.fill.nodeValue(node);
+    if (node.nodeType === Node.TEXT_NODE && !nodeText) {
+      // In the C++ version, this text node would have been stripped completely.
+      // Just pass the buck.
+      return __gCrWeb.fill.findChildTextInner(
+          node.nextSibling, depth, divsToSkip);
+    }
 
-  // Recursively compute the children's text.
-  // Preserve inter-element whitespace separation.
-  const childText =
-      __gCrWeb.fill.findChildTextInner(node.firstChild, depth - 1, divsToSkip);
-  let addSpace = node.nodeType === Node.TEXT_NODE && !nodeText;
-  // Emulate apparently incorrect Chromium behavior tracked in
-  // https://crbug.com/239819.
-  addSpace = false;
-  nodeText =
-      __gCrWeb.fill.combineAndCollapseWhitespace(nodeText, childText, addSpace);
+    // Recursively compute the children's text.
+    // Preserve inter-element whitespace separation.
+    const childText = __gCrWeb.fill.findChildTextInner(
+        node.firstChild, depth - 1, divsToSkip);
+    let addSpace = node.nodeType === Node.TEXT_NODE && !nodeText;
+    // Emulate apparently incorrect Chromium behavior tracked in
+    // https://crbug.com/239819.
+    addSpace = false;
+    nodeText = __gCrWeb.fill.combineAndCollapseWhitespace(
+        nodeText, childText, addSpace);
+  }
 
   // Recursively compute the siblings' text.
   // Again, preserve inter-element whitespace separation.
   const siblingText =
       __gCrWeb.fill.findChildTextInner(node.nextSibling, depth - 1, divsToSkip);
-  addSpace = node.nodeType === Node.TEXT_NODE && !nodeText;
+  let addSpace = node.nodeType === Node.TEXT_NODE && !nodeText;
   // Emulate apparently incorrect Chromium behavior tracked in
   // https://crbug.com/239819.
   addSpace = false;
@@ -1174,8 +1178,13 @@
       const value = __gCrWeb.fill.findChildText(sibling);
       // A text node's value will be empty if it is for a line break.
       const addSpace = nodeType === Node.TEXT_NODE && value.length === 0;
-      inferredLabel = __gCrWeb.fill.combineAndCollapseWhitespace(
-          value, inferredLabel, addSpace);
+      if (forward) {
+        inferredLabel = __gCrWeb.fill.combineAndCollapseWhitespace(
+            inferredLabel, value, addSpace);
+      } else {
+        inferredLabel = __gCrWeb.fill.combineAndCollapseWhitespace(
+            value, inferredLabel, addSpace);
+      }
       continue;
     }
 
@@ -1575,8 +1584,14 @@
  * e.g. <div>Some Text<span><input ...></span></div>
  * e.g. <div>Some Text</div><div><input ...></div>
  *
- * Because this is already traversing the <div> structure, if it finds a <label>
- * sibling along the way, infer from that <label>.
+ * Contrary to the other InferLabelFrom* functions, this functions walks up
+ * the DOM tree from the original input, instead of down from the surrounding
+ * tag. While doing so, if a <label> or text node sibling are found along the
+ * way, a label is inferred from them directly. For example, <div>First
+ * name<div><input></div>Last name<div><input></div></div> infers "First name"
+ * and "Last name" for the two inputs, respectively, by picking up the text
+ * nodes on the way to the surrounding div. Without doing so, the label of both
+ * inputs becomes "First nameLast name".
  *
  * It is based on the logic in
  *    string16 InferLabelFromDivTable(const WebFormControlElement& element)
@@ -1623,12 +1638,14 @@
       }
 
       lookingForParent = false;
-    } else if (!lookingForParent && __gCrWeb.fill.hasTagName(node, 'label')) {
-      if (!node.control) {
+    } else if (!lookingForParent) {
+      // Infer a label from text nodes and unassigned <label> siblings.
+      if (__gCrWeb.fill.hasTagName(node, 'label') && !node.control) {
         inferredLabel = __gCrWeb.fill.findChildText(node);
+      } else if (node.nodeType === Node.TEXT_NODE) {
+        inferredLabel = __gCrWeb.fill.nodeValue(node).trim();
       }
-    } else if (
-        lookingForParent && __gCrWeb.fill.isTraversableContainerElement(node)) {
+    } else if (__gCrWeb.fill.isTraversableContainerElement(node)) {
       // If the element is in a non-div container, its label most likely is too.
       break;
     }
diff --git a/components/exo/wayland/clients/client_base.cc b/components/exo/wayland/clients/client_base.cc
index 430f0b8..50f1a59f 100644
--- a/components/exo/wayland/clients/client_base.cc
+++ b/components/exo/wayland/clients/client_base.cc
@@ -556,9 +556,8 @@
     ui::OzonePlatform::InitParams ozone_params;
     ozone_params.single_process = true;
     ui::OzonePlatform::InitializeForGPU(ozone_params);
-    gl::GLDisplayEGL* display = static_cast<gl::GLDisplayEGL*>(
-        gl::init::InitializeGLOneOff(/*system_device_id=*/0));
-    DCHECK(display);
+    bool gl_initialized = gl::init::InitializeGLOneOff(/*system_device_id=*/0);
+    DCHECK(gl_initialized);
     gl_surface_ = gl::init::CreateOffscreenGLSurface(gfx::Size());
     gl_context_ =
         gl::init::CreateGLContext(nullptr,  // share_group
@@ -567,11 +566,14 @@
     make_current_ = std::make_unique<ui::ScopedMakeCurrent>(gl_context_.get(),
                                                             gl_surface_.get());
 
-    if (display->ext->b_EGL_EXT_image_flush_external ||
-        display->ext->b_EGL_ARM_implicit_external_sync) {
+    if (gl::GLSurfaceEGL::GetGLDisplayEGL()->HasEGLExtension(
+            "EGL_EXT_image_flush_external") ||
+        gl::GLSurfaceEGL::GetGLDisplayEGL()->HasEGLExtension(
+            "EGL_ARM_implicit_external_sync")) {
       egl_sync_type_ = EGL_SYNC_FENCE_KHR;
     }
-    if (display->ext->b_EGL_ANDROID_native_fence_sync) {
+    if (gl::GLSurfaceEGL::GetGLDisplayEGL()->HasEGLExtension(
+            "EGL_ANDROID_native_fence_sync")) {
       egl_sync_type_ = EGL_SYNC_NATIVE_FENCE_ANDROID;
     }
 
diff --git a/components/security_interstitials/content/cert_logger.proto b/components/security_interstitials/content/cert_logger.proto
index 7b58b5b..750f7a8 100644
--- a/components/security_interstitials/content/cert_logger.proto
+++ b/components/security_interstitials/content/cert_logger.proto
@@ -304,6 +304,7 @@
     MAC_TRUST_IMPL_DOMAIN_CACHE = 1;
     MAC_TRUST_IMPL_SIMPLE = 2;
     MAC_TRUST_IMPL_MRU_CACHE = 3;
+    MAC_TRUST_IMPL_DOMAIN_CACHE_FULL_CERTS = 4;
   };
   optional MacTrustImplType mac_trust_impl = 13;
 
diff --git a/components/security_interstitials/content/certificate_error_report.cc b/components/security_interstitials/content/certificate_error_report.cc
index fa95730..d73084a3 100644
--- a/components/security_interstitials/content/certificate_error_report.cc
+++ b/components/security_interstitials/content/certificate_error_report.cc
@@ -151,20 +151,20 @@
 chrome_browser_ssl::TrialVerificationInfo::MacTrustImplType
 TrustImplTypeFromMojom(
     cert_verifier::mojom::CertVerifierDebugInfo::MacTrustImplType input) {
+  using mojom_MacTrustImplType =
+      cert_verifier::mojom::CertVerifierDebugInfo::MacTrustImplType;
+  using chrome_browser_ssl::TrialVerificationInfo;
   switch (input) {
-    case cert_verifier::mojom::CertVerifierDebugInfo::MacTrustImplType::
-        kUnknown:
-      return chrome_browser_ssl::TrialVerificationInfo::MAC_TRUST_IMPL_UNKNOWN;
-    case cert_verifier::mojom::CertVerifierDebugInfo::MacTrustImplType::
-        kDomainCache:
-      return chrome_browser_ssl::TrialVerificationInfo::
-          MAC_TRUST_IMPL_DOMAIN_CACHE;
-    case cert_verifier::mojom::CertVerifierDebugInfo::MacTrustImplType::kSimple:
-      return chrome_browser_ssl::TrialVerificationInfo::MAC_TRUST_IMPL_SIMPLE;
-    case cert_verifier::mojom::CertVerifierDebugInfo::MacTrustImplType::
-        kLruCache:
-      return chrome_browser_ssl::TrialVerificationInfo::
-          MAC_TRUST_IMPL_MRU_CACHE;
+    case mojom_MacTrustImplType::kUnknown:
+      return TrialVerificationInfo::MAC_TRUST_IMPL_UNKNOWN;
+    case mojom_MacTrustImplType::kDomainCache:
+      return TrialVerificationInfo::MAC_TRUST_IMPL_DOMAIN_CACHE;
+    case mojom_MacTrustImplType::kSimple:
+      return TrialVerificationInfo::MAC_TRUST_IMPL_SIMPLE;
+    case mojom_MacTrustImplType::kLruCache:
+      return TrialVerificationInfo::MAC_TRUST_IMPL_MRU_CACHE;
+    case mojom_MacTrustImplType::kDomainCacheFullCerts:
+      return TrialVerificationInfo::MAC_TRUST_IMPL_DOMAIN_CACHE_FULL_CERTS;
   }
 }
 #endif  // BUILDFLAG(IS_APPLE)
diff --git a/components/services/app_service/public/cpp/icon_types.cc b/components/services/app_service/public/cpp/icon_types.cc
index 6d0bf3e..84155785 100644
--- a/components/services/app_service/public/cpp/icon_types.cc
+++ b/components/services/app_service/public/cpp/icon_types.cc
@@ -76,78 +76,4 @@
   }
 }
 
-apps::mojom::IconValuePtr ConvertIconValueToMojomIconValue(
-    IconValuePtr icon_value) {
-  apps::mojom::IconValuePtr iv = apps::mojom::IconValue::New();
-  if (!icon_value || icon_value->icon_type == IconType::kUnknown) {
-    return iv;
-  }
-
-  iv->icon_type = ConvertIconTypeToMojomIconType(icon_value->icon_type);
-  iv->is_placeholder_icon = icon_value->is_placeholder_icon;
-
-  switch (icon_value->icon_type) {
-    case IconType::kUnknown:
-      break;
-    case IconType::kCompressed:
-      iv->compressed = std::move(icon_value->compressed);
-      break;
-    case IconType::kUncompressed:
-    case IconType::kStandard:
-      iv->uncompressed = icon_value->uncompressed;
-      break;
-  }
-
-  return iv;
-}
-
-IconValuePtr ConvertMojomIconValueToIconValue(
-    apps::mojom::IconValuePtr mojom_icon_value) {
-  auto iv = std::make_unique<IconValue>();
-  if (!mojom_icon_value) {
-    return iv;
-  }
-
-  iv->icon_type = ConvertMojomIconTypeToIconType(mojom_icon_value->icon_type);
-  iv->is_placeholder_icon = mojom_icon_value->is_placeholder_icon;
-
-  switch (mojom_icon_value->icon_type) {
-    case mojom::IconType::kUnknown:
-      break;
-    case mojom::IconType::kCompressed:
-      DCHECK(mojom_icon_value->compressed.has_value());
-      iv->compressed = std::move(mojom_icon_value->compressed.value());
-      break;
-    case mojom::IconType::kUncompressed:
-    case mojom::IconType::kStandard:
-      iv->uncompressed = mojom_icon_value->uncompressed;
-      break;
-  }
-
-  return iv;
-}
-
-base::OnceCallback<void(IconValuePtr)> IconValueToMojomIconValueCallback(
-    base::OnceCallback<void(apps::mojom::IconValuePtr)> callback) {
-  return base::BindOnce(
-      [](base::OnceCallback<void(apps::mojom::IconValuePtr)> inner_callback,
-         IconValuePtr icon_value) {
-        std::move(inner_callback)
-            .Run(ConvertIconValueToMojomIconValue(std::move(icon_value)));
-      },
-      std::move(callback));
-}
-
-base::OnceCallback<void(apps::mojom::IconValuePtr)>
-MojomIconValueToIconValueCallback(
-    base::OnceCallback<void(IconValuePtr)> callback) {
-  return base::BindOnce(
-      [](base::OnceCallback<void(IconValuePtr)> inner_callback,
-         apps::mojom::IconValuePtr icon_value) {
-        std::move(inner_callback)
-            .Run(ConvertMojomIconValueToIconValue(std::move(icon_value)));
-      },
-      std::move(callback));
-}
-
 }  // namespace apps
diff --git a/components/services/app_service/public/cpp/icon_types.h b/components/services/app_service/public/cpp/icon_types.h
index 136028b..64bdd086 100644
--- a/components/services/app_service/public/cpp/icon_types.h
+++ b/components/services/app_service/public/cpp/icon_types.h
@@ -121,23 +121,6 @@
 COMPONENT_EXPORT(ICON_TYPES)
 IconType ConvertMojomIconTypeToIconType(apps::mojom::IconType mojom_icon_type);
 
-COMPONENT_EXPORT(ICON_TYPES)
-apps::mojom::IconValuePtr ConvertIconValueToMojomIconValue(
-    IconValuePtr icon_value);
-
-COMPONENT_EXPORT(ICON_TYPES)
-IconValuePtr ConvertMojomIconValueToIconValue(
-    apps::mojom::IconValuePtr mojom_icon_value);
-
-COMPONENT_EXPORT(ICON_TYPES)
-base::OnceCallback<void(IconValuePtr)> IconValueToMojomIconValueCallback(
-    base::OnceCallback<void(apps::mojom::IconValuePtr)> callback);
-
-COMPONENT_EXPORT(ICON_TYPES)
-base::OnceCallback<void(apps::mojom::IconValuePtr)>
-MojomIconValueToIconValueCallback(
-    base::OnceCallback<void(IconValuePtr)> callback);
-
 }  // namespace apps
 
 #endif  // COMPONENTS_SERVICES_APP_SERVICE_PUBLIC_CPP_ICON_TYPES_H_
diff --git a/components/services/app_service/public/mojom/types.mojom b/components/services/app_service/public/mojom/types.mojom
index 7783e70..c59aa337 100644
--- a/components/services/app_service/public/mojom/types.mojom
+++ b/components/services/app_service/public/mojom/types.mojom
@@ -249,13 +249,6 @@
   kStandard,
 };
 
-struct IconValue {
-  IconType icon_type;
-  gfx.mojom.ImageSkia? uncompressed;
-  array<uint8>? compressed;
-  bool is_placeholder_icon;
-};
-
 // Enumeration of possible app launch sources.
 // This should be kept in sync with metadata/apps/histograms.xml, and
 // LaunchSource in enums.xml.
diff --git a/components/services/screen_ai/proto/chrome_screen_ai.proto b/components/services/screen_ai/proto/chrome_screen_ai.proto
index 4467ccb..2017ee7a 100644
--- a/components/services/screen_ai/proto/chrome_screen_ai.proto
+++ b/components/services/screen_ai/proto/chrome_screen_ai.proto
@@ -239,10 +239,6 @@
       Type enum_type = 1;
       string string_type = 2;
     }
-
-    // A confidence score in the range of [0, 1], indicating how confident the
-    // model is of the given type prediction. Closer to 1 means more confident.
-    float confidence = 3;
   }
 
   PredictedType predicted_type = 1;
@@ -265,10 +261,10 @@
 }
 
 enum Direction {
-  LEFT_TO_RIGHT = 0;
-  RIGHT_TO_LEFT = 1;
-  UNSPECIFIED = 2;
-  TOP_TO_BOTTOM = 3;
+  DIRECTION_UNSPECIFIED = 0;
+  DIRECTION_LEFT_TO_RIGHT = 1;
+  DIRECTION_RIGHT_TO_LEFT = 2;
+  DIRECTION_TOP_TO_BOTTOM = 3;
 }
 
 // Text line with associated bounding box.
@@ -283,26 +279,22 @@
   // Text line in UTF8 format.
   string utf8_string = 3;
 
-  // Confidence as computed by the OCR engine.
-  float confidence = 4;
-
   // Language guess for the line. The format  is the ISO 639-1 two-letter
   // language code if that is defined (e.g. "en"), or else the ISO 639-2
   // three-letter code if that is defined, or else a Google-specific code.
-  string language = 5;
+  string language = 4;
 
   // ID of the text block that this line belongs to.
-  int32 block_id = 6;
+  int32 block_id = 5;
 
   // Index within the block that this line belongs to.
-  int32 order_within_block = 7;
+  int32 order_within_block = 6;
 
-  // The direction of the script contained in the line. Directed by the enum
-  // Direction defined above.
-  int32 direction = 8;
+  // The direction of the script contained in the line.
+  Direction direction = 7;
 
   // Content type for this line.
-  ContentType content_type = 9;
+  ContentType content_type = 8;
 }
 
 // Word with associated bounding box.
@@ -318,52 +310,42 @@
   // Word string in UTF8 format.
   string utf8_string = 3;
 
-  // Confidence as computed by the OCR engine.
-  float confidence = 4;
-
   // True if the word passes the internal beamsearch dictionary check.
-  bool dictionary_word = 5;
+  bool dictionary_word = 4;
 
   // Language guess for the word. The format  is the ISO 639-1 two-letter
   // language code if that is defined (e.g. "en"), or else the ISO 639-2
   // three-letter code if that is defined, or else a Google-specific code.
-  string language = 6;
+  string language = 5;
 
   // This word is separated from next word by space.
-  bool has_space_after = 7;
+  bool has_space_after = 6;
 
   // If true, foreground and background colors successfully detected.
-  bool estimate_color_success = 8;
+  bool estimate_color_success = 7;
 
   // Estimated grayscale value of foreground.
-  int32 foreground_gray_value = 9;
+  int32 foreground_gray_value = 8;
 
   // Estimated grayscale value of background.
-  int32 background_gray_value = 10;
+  int32 background_gray_value = 9;
 
   // Estimated RGB of foreground. Extracting RGB channels from this
   // integer is best done using the leptonica helper extractRGBValues().
-  int32 foreground_rgb_value = 11;
+  int32 foreground_rgb_value = 10;
 
   // Estimated RGB of background. Extracting RGB channels from this
   // integer is best done using the leptonica helper extractRGBValues().
-  int32 background_rgb_value = 12;
+  int32 background_rgb_value = 11;
 
-  // The direction of the script contained in the word. Directed by the enum
-  // Direction defined above.
-  int32 direction = 13;
+  // The direction of the script contained in the word.
+  Direction direction = 12;
 
   // Content type for this word.
-  ContentType content_type = 14;
-
-  // If content_type == HANDWRITTEN, confidence on this word being handwritten.
-  float handwriting_confidence = 15;
+  ContentType content_type = 13;
 
   // Detected orientation of the text.
-  Orientation orientation = 16;
-
-  // Confidence of langid language identification.
-  float language_confidence = 17;
+  Orientation orientation = 14;
 }
 
 // Symbol with associated bounding box.
@@ -400,8 +382,6 @@
   CONTENT_TYPE_HANDWRITTEN_FORMULA = 7;
   // Signature or intitals.
   CONTENT_TYPE_SIGNATURE = 8;
-
-  CONTENT_TYPE_UNKNOWN = 100;
 }
 
 message VisualAnnotation {
diff --git a/components/services/screen_ai/proto/proto_convertor.cc b/components/services/screen_ai/proto/proto_convertor.cc
index ba27b51..0e4fc82 100644
--- a/components/services/screen_ai/proto/proto_convertor.cc
+++ b/components/services/screen_ai/proto/proto_convertor.cc
@@ -17,6 +17,9 @@
 
 #include "base/check_op.h"
 #include "base/notreached.h"
+#include "base/numerics/checked_math.h"
+#include "base/numerics/clamped_math.h"
+#include "base/ranges/ranges.h"
 #include "components/services/screen_ai/proto/chrome_screen_ai.pb.h"
 #include "components/services/screen_ai/proto/dimension.pb.h"
 #include "components/services/screen_ai/proto/view_hierarchy.pb.h"
@@ -30,13 +33,9 @@
 #include "ui/gfx/geometry/rect_f.h"
 #include "ui/gfx/geometry/transform.h"
 
-namespace {
+namespace ranges = base::ranges;
 
-// The minimum confidence level that a Screen AI annotation should have to be
-// accepted.
-// TODO(https://crbug.com/1278249): Add experiment or heuristics to better
-// adjust this threshold.
-constexpr float kScreenAIMinConfidenceThreshold = 0.1f;
+namespace {
 
 // Returns the next valid ID that can be used for identifying `AXNode`s in the
 // accessibility tree.
@@ -45,6 +44,23 @@
   return next_node_id++;
 }
 
+bool HaveIdenticalFormattingStyle(const chrome_screen_ai::WordBox& word_1,
+                                  const chrome_screen_ai::WordBox& word_2) {
+  if (word_1.language() != word_2.language())
+    return false;
+  if (word_1.estimate_color_success() && word_2.estimate_color_success()) {
+    if (word_1.foreground_rgb_value() != word_2.foreground_rgb_value())
+      return false;
+    if (word_1.background_rgb_value() != word_2.background_rgb_value())
+      return false;
+  }
+  if (word_1.direction() != word_2.direction())
+    return false;
+  if (word_1.content_type() != word_2.content_type())
+    return false;
+  return true;
+}
+
 // Returns whether the provided `predicted_type` is:
 // A) set, and
 // B) has a confidence that is above our acceptance threshold.
@@ -52,15 +68,6 @@
     const chrome_screen_ai::UIComponent::PredictedType& predicted_type,
     ui::AXNodeData& out_data) {
   DCHECK_EQ(out_data.role, ax::mojom::Role::kUnknown);
-  if (predicted_type.confidence() < 0.0f ||
-      predicted_type.confidence() > 1.0f) {
-    NOTREACHED()
-        << "Unrecognized chrome_screen_ai::PredictedType::confidence value: "
-        << predicted_type.confidence();
-    return false;  // Confidence is out of bounds.
-  }
-  if (predicted_type.confidence() < kScreenAIMinConfidenceThreshold)
-    return false;
   switch (predicted_type.type_of_case()) {
     case chrome_screen_ai::UIComponent::PredictedType::kEnumType:
       // TODO(https://crbug.com/1278249): We do not actually need an enum. All
@@ -88,9 +95,7 @@
   out_data.relative_bounds.bounds =
       gfx::RectF(bounding_box.x(), bounding_box.y(), bounding_box.width(),
                  bounding_box.height());
-  // A negative width or height will result in an empty rect.
-  if (out_data.relative_bounds.bounds.IsEmpty())
-    return;
+  DCHECK(!out_data.relative_bounds.bounds.IsEmpty());
   if (container_id != ui::kInvalidAXNodeID)
     out_data.relative_bounds.offset_container_id = container_id;
   if (bounding_box.angle()) {
@@ -101,25 +106,21 @@
 
 void SerializeDirection(const chrome_screen_ai::Direction& direction,
                         ui::AXNodeData& out_data) {
-  if (!chrome_screen_ai::Direction_IsValid(direction)) {
-    NOTREACHED() << "Unrecognized chrome_screen_ai::Direction value: "
-                 << direction;
-    return;
-  }
+  DCHECK(chrome_screen_ai::Direction_IsValid(direction));
   switch (direction) {
-    case chrome_screen_ai::Direction::UNSPECIFIED:
+    case chrome_screen_ai::DIRECTION_UNSPECIFIED:
     // We assume that LEFT_TO_RIGHT is the default direction.
-    case chrome_screen_ai::Direction::LEFT_TO_RIGHT:
+    case chrome_screen_ai::DIRECTION_LEFT_TO_RIGHT:
       out_data.AddIntAttribute(
           ax::mojom::IntAttribute::kTextDirection,
           static_cast<int32_t>(ax::mojom::WritingDirection::kLtr));
       break;
-    case chrome_screen_ai::Direction::RIGHT_TO_LEFT:
+    case chrome_screen_ai::DIRECTION_RIGHT_TO_LEFT:
       out_data.AddIntAttribute(
           ax::mojom::IntAttribute::kTextDirection,
           static_cast<int32_t>(ax::mojom::WritingDirection::kRtl));
       break;
-    case chrome_screen_ai::Direction::TOP_TO_BOTTOM:
+    case chrome_screen_ai::DIRECTION_TOP_TO_BOTTOM:
       out_data.AddIntAttribute(
           ax::mojom::IntAttribute::kTextDirection,
           static_cast<int32_t>(ax::mojom::WritingDirection::kTtb));
@@ -138,11 +139,7 @@
 
 void SerializeContentType(const chrome_screen_ai::ContentType& content_type,
                           ui::AXNodeData& out_data) {
-  if (!chrome_screen_ai::ContentType_IsValid(content_type)) {
-    NOTREACHED() << "Unrecognized chrome_screen_ai::ContentType value: "
-                 << content_type;
-    return;
-  }
+  DCHECK(chrome_screen_ai::ContentType_IsValid(content_type));
   switch (content_type) {
     case chrome_screen_ai::CONTENT_TYPE_PRINTED_TEXT:
     case chrome_screen_ai::CONTENT_TYPE_HANDWRITTEN_TEXT:
@@ -175,13 +172,6 @@
       // the user that this is a signature, e.g. via ARIA Annotations.
       out_data.role = ax::mojom::Role::kStaticText;
       break;
-    case chrome_screen_ai::CONTENT_TYPE_UNKNOWN:
-      // This should be "Role::kPresentational" but it has been erroniously
-      // removed from the codebase.
-      // TODO(nektar): Add presentational role back to avoid confusion with the
-      // meaning of kNone vs. kUnknown.
-      out_data.role = ax::mojom::Role::kNone;  // Presentational.
-      break;
     case google::protobuf::kint32min:
     case google::protobuf::kint32max:
       // Ordinarily, a default case should have been added to permit future
@@ -195,43 +185,145 @@
 }
 
 void SerializeWordBox(const chrome_screen_ai::WordBox& word_box,
-                      const size_t index,
-                      ui::AXNodeData& parent_node,
-                      std::vector<ui::AXNodeData>& node_data) {
-  DCHECK_LT(index, node_data.size());
-  DCHECK_NE(parent_node.id, ui::kInvalidAXNodeID);
-  ui::AXNodeData& word_box_node = node_data[index];
-  DCHECK_EQ(word_box_node.role, ax::mojom::Role::kUnknown);
-  if (word_box.confidence() < 0.0f || word_box.confidence() > 1.0f) {
-    NOTREACHED() << "Unrecognized chrome_screen_ai::WordBox::confidence value: "
-                 << word_box.confidence();
-    return;  // Confidence is out of bounds.
-  }
-  if (word_box.confidence() < kScreenAIMinConfidenceThreshold)
-    return;
-  word_box_node.role = ax::mojom::Role::kInlineTextBox;
-  word_box_node.id = GetNextNodeID();
-  SerializeBoundingBox(word_box.bounding_box(), parent_node.id, word_box_node);
-  // Since the role is `kInlineTextBox`, NameFrom would automatically and
-  // correctly be set to `ax::mojom::NameFrom::kContents`.
+                      ui::AXNodeData& inline_text_box) {
+  DCHECK_NE(inline_text_box.id, ui::kInvalidAXNodeID);
+  // TODO(nektar): What if the angles of orientation are different, would the
+  // following DCHECK unnecessarily? Do we need to apply the related transform,
+  // or is the fact that the transform is the same between line and word boxes
+  // results in no difference?
+  DCHECK(inline_text_box.relative_bounds.bounds.Contains(gfx::RectF(
+      word_box.bounding_box().x(), word_box.bounding_box().y(),
+      word_box.bounding_box().width(), word_box.bounding_box().height())));
+
+  std::vector<int32_t> character_offsets;
+  // TODO(nektar): Handle writing directions other than LEFT_TO_RIGHT.
+  int32_t line_offset =
+      base::ClampRound(inline_text_box.relative_bounds.bounds.x());
+  ranges::transform(word_box.symbols(), std::back_inserter(character_offsets),
+                    [line_offset](const chrome_screen_ai::SymbolBox& symbol) {
+                      return symbol.bounding_box().x() - line_offset;
+                    });
+
+  std::string inner_text =
+      inline_text_box.GetStringAttribute(ax::mojom::StringAttribute::kName);
+  inner_text += word_box.utf8_string();
+  size_t word_length = word_box.utf8_string().length();
   if (word_box.has_space_after()) {
-    word_box_node.SetName(word_box.utf8_string() + " ");
-  } else {
-    word_box_node.SetName(word_box.utf8_string());
+    inner_text += " ";
+    ++word_length;
   }
-  // TODO(nektar): DCHECK that line box's text is equal to the concatenation of
-  // the text found in all contained word boxes.
-  // TODO(nektar): Set character bounding box information.
+  inline_text_box.SetName(inner_text);
+
+  std::vector<int32_t> word_starts = inline_text_box.GetIntListAttribute(
+      ax::mojom::IntListAttribute::kWordStarts);
+  std::vector<int32_t> word_ends = inline_text_box.GetIntListAttribute(
+      ax::mojom::IntListAttribute::kWordEnds);
+  int32_t new_word_start = 0;
+  int32_t new_word_end = base::checked_cast<int32_t>(word_length);
+  if (!word_ends.empty()) {
+    new_word_start += word_ends[word_ends.size() - 1];
+    new_word_end += new_word_start;
+  }
+  word_starts.push_back(new_word_start);
+  word_ends.push_back(new_word_end);
+  inline_text_box.AddIntListAttribute(ax::mojom::IntListAttribute::kWordStarts,
+                                      word_starts);
+  inline_text_box.AddIntListAttribute(ax::mojom::IntListAttribute::kWordEnds,
+                                      word_ends);
+  DCHECK_LE(new_word_start, new_word_end);
+  DCHECK_LE(
+      new_word_end,
+      base::checked_cast<int32_t>(
+          inline_text_box.GetStringAttribute(ax::mojom::StringAttribute::kName)
+              .length()));
+
+  if (!word_box.language().empty()) {
+    DCHECK_EQ(inline_text_box.GetStringAttribute(
+                  ax ::mojom::StringAttribute::kLanguage),
+              word_box.language())
+        << "A `WordBox` has a different language than its enclosing `LineBox`.";
+  }
+
   if (word_box.estimate_color_success()) {
-    word_box_node.AddIntAttribute(ax::mojom::IntAttribute::kBackgroundColor,
-                                  word_box.background_rgb_value());
-    word_box_node.AddIntAttribute(ax::mojom::IntAttribute::kColor,
-                                  word_box.foreground_rgb_value());
+    if (!inline_text_box.HasIntAttribute(
+            ax::mojom::IntAttribute::kBackgroundColor)) {
+      inline_text_box.AddIntAttribute(ax::mojom::IntAttribute::kBackgroundColor,
+                                      word_box.background_rgb_value());
+    } else {
+      DCHECK_EQ(inline_text_box.GetIntAttribute(
+                    ax::mojom::IntAttribute::kBackgroundColor),
+                word_box.background_rgb_value())
+          << "A `WordBox` has a different background color than its enclosing "
+             "`LineBox`.";
+    }
+    if (!inline_text_box.HasIntAttribute(ax::mojom::IntAttribute::kColor)) {
+      inline_text_box.AddIntAttribute(ax::mojom::IntAttribute::kColor,
+                                      word_box.foreground_rgb_value());
+    } else {
+      DCHECK_EQ(
+          inline_text_box.GetIntAttribute(ax::mojom::IntAttribute::kColor),
+          word_box.foreground_rgb_value())
+          << "A `WordBox` has a different foreground color than its enclosing "
+             "`LineBox`.";
+    }
   }
-  SerializeDirection(
-      static_cast<chrome_screen_ai::Direction>(word_box.direction()),
-      word_box_node);
-  parent_node.child_ids.push_back(word_box_node.id);
+  SerializeDirection(word_box.direction(), inline_text_box);
+}
+
+// Creates an inline text box for every style span in the provided
+// `static_text_node`, starting from `start_from_word_index` in the node's
+// `word_boxes`. Returns the number of inline text box nodes that have been
+// initialized in `node_data`.
+size_t SerializeWordBoxes(const google::protobuf::RepeatedPtrField<
+                              chrome_screen_ai::WordBox>& word_boxes,
+                          const int start_from_word_index,
+                          const size_t node_index,
+                          ui::AXNodeData& static_text_node,
+                          std::vector<ui::AXNodeData>& node_data) {
+  if (word_boxes.empty())
+    return 0u;
+  DCHECK_LT(start_from_word_index, word_boxes.size());
+  DCHECK_LT(node_index, node_data.size());
+  DCHECK_NE(static_text_node.id, ui::kInvalidAXNodeID);
+  ui::AXNodeData& inline_text_box_node = node_data[node_index];
+  DCHECK_EQ(inline_text_box_node.role, ax::mojom::Role::kUnknown);
+  inline_text_box_node.role = ax::mojom::Role::kInlineTextBox;
+  inline_text_box_node.id = GetNextNodeID();
+  // TODO(nektar): Find the union of the bounding boxes in this formatting
+  // context and set it as the bounding box of `inline_text_box_node`.
+  inline_text_box_node.relative_bounds.bounds =
+      static_text_node.relative_bounds.bounds;
+
+  std::string language;
+  if (static_text_node.GetStringAttribute(ax::mojom::StringAttribute::kLanguage,
+                                          &language)) {
+    // TODO(nektar): Only set language if different from parent node (i.e. the
+    // static text node), in order to minimize memory usage.
+    inline_text_box_node.AddStringAttribute(
+        ax::mojom::StringAttribute::kLanguage, language);
+  }
+  static_text_node.child_ids.push_back(inline_text_box_node.id);
+
+  const auto formatting_context_start =
+      std::cbegin(word_boxes) + start_from_word_index;
+  const auto formatting_context_end =
+      ranges::find_if_not(formatting_context_start, ranges::end(word_boxes),
+                          [formatting_context_start](const auto& word_box) {
+                            return HaveIdenticalFormattingStyle(
+                                *formatting_context_start, word_box);
+                          });
+  for (auto word_iter = formatting_context_start;
+       word_iter != formatting_context_end; ++word_iter) {
+    SerializeWordBox(*word_iter, inline_text_box_node);
+  }
+  if (formatting_context_end != std::cend(word_boxes)) {
+    return 1u +
+           SerializeWordBoxes(
+               word_boxes,
+               std::distance(std::cbegin(word_boxes), formatting_context_end),
+               (node_index + 1u), static_text_node, node_data);
+  }
+  return 1u;
 }
 
 void SerializeUIComponent(const chrome_screen_ai::UIComponent& ui_component,
@@ -249,42 +341,39 @@
   parent_node.child_ids.push_back(current_node.id);
 }
 
-void SerializeLineBox(const chrome_screen_ai::LineBox& line_box,
-                      const size_t index,
-                      ui::AXNodeData& parent_node,
-                      std::vector<ui::AXNodeData>& node_data) {
+// Returns the number of accessibility nodes that have been initialized in
+// `node_data`. A single `line_box` may turn into a number of inline text boxes
+// depending on how many formatting contexts it contains. If `line_box` is of a
+// non-textual nature, only one node will be initialized.
+size_t SerializeLineBox(const chrome_screen_ai::LineBox& line_box,
+                        const size_t index,
+                        ui::AXNodeData& parent_node,
+                        std::vector<ui::AXNodeData>& node_data) {
   DCHECK_LT(index, node_data.size());
   DCHECK_NE(parent_node.id, ui::kInvalidAXNodeID);
   ui::AXNodeData& line_box_node = node_data[index];
   DCHECK_EQ(line_box_node.role, ax::mojom::Role::kUnknown);
-  if (line_box.confidence() < 0.0f || line_box.confidence() > 1.0f) {
-    NOTREACHED() << "Unrecognized chrome_screen_ai::LineBox::confidence value: "
-                 << line_box.confidence();
-    return;  // Confidence is out of bounds.
-  }
-  if (line_box.confidence() < kScreenAIMinConfidenceThreshold)
-    return;
+
   SerializeContentType(line_box.content_type(), line_box_node);
   line_box_node.id = GetNextNodeID();
-  if (ui::IsText(line_box_node.role)) {
-    size_t word_node_index = index + 1u;
-    for (const auto& word : line_box.words())
-      SerializeWordBox(word, word_node_index++, line_box_node, node_data);
-  }
   SerializeBoundingBox(line_box.bounding_box(), parent_node.id, line_box_node);
-  // Since the role is `kStaticText`, NameFrom would automatically and correctly
-  // be set to `ax::mojom::NameFrom::kContents`.
+  // `ax::mojom::NameFrom` should be set to the correct value based on the
+  // role.
   line_box_node.SetName(line_box.utf8_string());
   if (!line_box.language().empty()) {
-    // TODO(nektar): Only set language if different from parent node to
-    // minimize memory usage.
+    // TODO(nektar): Only set language if different from parent node (i.e. the
+    // page node), in order to minimize memory usage.
     line_box_node.AddStringAttribute(ax::mojom::StringAttribute::kLanguage,
                                      line_box.language());
   }
-  SerializeDirection(
-      static_cast<chrome_screen_ai::Direction>(line_box.direction()),
-      line_box_node);
+  SerializeDirection(line_box.direction(), line_box_node);
   parent_node.child_ids.push_back(line_box_node.id);
+
+  if (!ui::IsText(line_box_node.role))
+    return 1u;
+  return 1u + SerializeWordBoxes(line_box.words(),
+                                 /* start_from_word_index */ 0, (index + 1u),
+                                 line_box_node, node_data);
 }
 
 // Adds the subtree of |nodes[node_index_to_add]| to |nodes_order| with
@@ -319,26 +408,39 @@
   // TODO(https://crbug.com/1278249): Create an AXTreeSource and create the
   // update using AXTreeSerializer.
 
-  // Each `UIComponent` and `LineBox` will take up one node in the accessibility
+  // Each `UIComponent`, `LineBox`, as well as every `WordBox` that results in a
+  // different formatting context, will take up one node in the accessibility
   // tree, resulting in hundreds of nodes, making it inefficient to push_back
   // one node at a time. We pre-allocate the needed nodes making node creation
   // an O(n) operation.
-  const size_t word_count = std::accumulate(
-      std::begin(visual_annotation.lines()),
-      std::end(visual_annotation.lines()), 0u,
-      [](const size_t& count, const chrome_screen_ai::LineBox& line_box) {
-        return count + line_box.words().size();
-      });
+  size_t formatting_context_count = 0u;
+  for (const chrome_screen_ai::LineBox& line : visual_annotation.lines()) {
+    // By design, and same as in Blink, every line creates a separate formatting
+    // context regardless as to whether the format styles are identical with
+    // previous lines or not.
+    ++formatting_context_count;
+    DCHECK(!line.words().empty())
+        << "Empty lines should have been pruned in the Screen AI library.";
+    for (auto iter = std::cbegin(line.words());
+         std::next(iter) != std::cend(line.words()); ++iter) {
+      if (!HaveIdenticalFormattingStyle(*iter, *std::next(iter)))
+        ++formatting_context_count;
+    }
+  }
 
-  // Each unique `chrome_screen_ai::LineBox::block_id` creates a new
-  // paragraph, each paragraph is placed in its correct reading order,
-  // and each paragraph has a sorted set of line boxes. Line boxes are sorted
-  // using their `chrome_screen_ai::LineBox::order_within_block` member and they
-  // are identified by their index in the container of line boxes. Use std::map
-  // to sort both paragraphs and lines, both operations having an O(n * log(n))
-  // complexity.
+  // Each unique `chrome_screen_ai::LineBox::block_id` signifies a different
+  // block of text, and so it creates a new static text node in the
+  // accessibility tree. Each block has a sorted set of line boxes, everyone of
+  // which is turned into one or more inline text box nodes in the accessibility
+  // tree. Line boxes are sorted using their
+  // `chrome_screen_ai::LineBox::order_within_block` member and are identified
+  // by their index in the container of line boxes. Use std::map to sort both
+  // text blocks and the line boxes that belong to each one, both operations
+  // having an O(n * log(n)) complexity.
+  // TODO(accessibility): Create separate paragraphs based on the blocks'
+  // spacing.
   // TODO(accessibility): Determine reading order based on visual positioning of
-  // paragraphs, not on their block IDs.
+  // text blocks, not on the order of their block IDs.
   std::map<int32_t, std::map<int32_t, int>> blocks_to_lines_map;
   for (int i = 0; i < visual_annotation.lines_size(); ++i) {
     const chrome_screen_ai::LineBox& line = visual_annotation.lines(i);
@@ -354,8 +456,8 @@
 
   std::vector<ui::AXNodeData> nodes(
       rootnodes_count + visual_annotation.ui_component().size() +
-      blocks_to_lines_map.size() + visual_annotation.lines().size() +
-      word_count);
+      visual_annotation.lines().size() + formatting_context_count);
+
   size_t index = 0u;
 
   if (!visual_annotation.ui_component().empty()) {
@@ -380,24 +482,26 @@
            block_to_lines_pair.second) {
         const chrome_screen_ai::LineBox& line_box =
             visual_annotation.lines(line_sequence_number_to_index_pair.second);
-        SerializeLineBox(line_box, index++, page_node, nodes);
-        index += line_box.words().size();
+        // Every line with a textual accessibility role should turn into one or
+        // more inline text boxes, each one  representing a formatting context.
+        // If the line is not of a textual role, only one node is initialized
+        // having a more specific role such as `ax::mojom::Role::kImage`.
+        index += SerializeLineBox(line_box, index, page_node, nodes);
       }
     }
   }
 
   // Filter out invalid / unrecognized / unused nodes from the update.
   update.nodes.resize(nodes.size());
-  auto end_node_iter =
-      std::copy_if(std::begin(nodes), std::end(nodes), std::begin(update.nodes),
-                   [](const ui::AXNodeData& node_data) {
-                     return node_data.role != ax::mojom::Role::kUnknown &&
-                            node_data.id != ui::kInvalidAXNodeID;
-                   });
+  const auto end_node_iter = ranges::copy_if(
+      nodes, ranges::begin(update.nodes), [](const ui::AXNodeData& node_data) {
+        return node_data.role != ax::mojom::Role::kUnknown &&
+               node_data.id != ui::kInvalidAXNodeID;
+      });
   update.nodes.resize(std::distance(std::begin(update.nodes), end_node_iter));
 
   // TODO(https://crbug.com/1278249): Add UMA metrics to record the number of
-  // annotations, item types, confidence levels, etc.
+  // annotations, item types, etc.
 
   return update;
 }
diff --git a/components/services/screen_ai/proto/proto_convertor_unittest.cc b/components/services/screen_ai/proto/proto_convertor_unittest.cc
index d9eba124..a7eb8fff 100644
--- a/components/services/screen_ai/proto/proto_convertor_unittest.cc
+++ b/components/services/screen_ai/proto/proto_convertor_unittest.cc
@@ -62,7 +62,6 @@
     chrome_screen_ai::UIComponent::PredictedType* type_0 =
         component_0->mutable_predicted_type();
     type_0->set_enum_type(chrome_screen_ai::UIComponent::BUTTON);
-    type_0->set_confidence(0.8f);
     chrome_screen_ai::Rect* box_0 = component_0->mutable_bounding_box();
     box_0->set_x(0);
     box_0->set_y(1);
@@ -73,26 +72,12 @@
     chrome_screen_ai::UIComponent* component_1 = annotation.add_ui_component();
     chrome_screen_ai::UIComponent::PredictedType* type_1 =
         component_1->mutable_predicted_type();
-    type_1->set_string_type("Presentational");
-    // If the confidence is low, this component together with all its fields
-    // should be ignored.
-    type_1->set_confidence(0.05f);
+    type_1->set_string_type("Signature");
     chrome_screen_ai::Rect* box_1 = component_1->mutable_bounding_box();
-    box_1->set_x(0);
-    box_1->set_y(0);
-    box_1->set_width(5);
-    box_1->set_height(5);
-
-    chrome_screen_ai::UIComponent* component_2 = annotation.add_ui_component();
-    chrome_screen_ai::UIComponent::PredictedType* type_2 =
-        component_2->mutable_predicted_type();
-    type_2->set_string_type("Signature");
-    type_2->set_confidence(0.6f);
-    chrome_screen_ai::Rect* box_2 = component_2->mutable_bounding_box();
     // `x`, `y`, and `angle` should be defaulted to 0 since they are singular
     // proto3 fields, not proto2.
-    box_2->set_width(5);
-    box_2->set_height(5);
+    box_1->set_width(5);
+    box_1->set_height(5);
   }
 
   {
@@ -116,7 +101,7 @@
 
   {
     chrome_screen_ai::LineBox* line_0 = annotation.add_lines();
-    line_0->set_direction(chrome_screen_ai::Direction::RIGHT_TO_LEFT);
+    line_0->set_direction(chrome_screen_ai::DIRECTION_RIGHT_TO_LEFT);
 
     chrome_screen_ai::WordBox* word_0_0 = line_0->add_words();
     chrome_screen_ai::Rect* box_0_0 = word_0_0->mutable_bounding_box();
@@ -125,12 +110,12 @@
     box_0_0->set_width(250);
     box_0_0->set_height(20);
     word_0_0->set_utf8_string("Hello");
+    word_0_0->set_language("en");
     word_0_0->set_has_space_after(true);
-    word_0_0->set_confidence(0.9f);
     word_0_0->set_estimate_color_success(true);
     word_0_0->set_background_rgb_value(50000);
     word_0_0->set_foreground_rgb_value(25000);
-    word_0_0->set_direction(chrome_screen_ai::Direction::RIGHT_TO_LEFT);
+    word_0_0->set_direction(chrome_screen_ai::DIRECTION_RIGHT_TO_LEFT);
 
     chrome_screen_ai::WordBox* word_0_1 = line_0->add_words();
     chrome_screen_ai::Rect* box_0_1 = word_0_1->mutable_bounding_box();
@@ -139,12 +124,12 @@
     box_0_1->set_width(250);
     box_0_1->set_height(20);
     word_0_1->set_utf8_string("world");
+    word_0_1->set_language("en");
     // `word_0_1.has_space_after()` should be defaulted to false.
-    word_0_1->set_confidence(0.9f);
     word_0_1->set_estimate_color_success(true);
     word_0_1->set_background_rgb_value(50000);
     word_0_1->set_foreground_rgb_value(25000);
-    word_0_1->set_direction(chrome_screen_ai::Direction::RIGHT_TO_LEFT);
+    word_0_1->set_direction(chrome_screen_ai::DIRECTION_RIGHT_TO_LEFT);
 
     chrome_screen_ai::Rect* box_0 = line_0->mutable_bounding_box();
     box_0->set_x(100);
@@ -152,28 +137,9 @@
     box_0->set_width(500);
     box_0->set_height(20);
     line_0->set_utf8_string("Hello world");
-    line_0->set_confidence(0.9f);
     line_0->set_language("en");
-    line_0->set_block_id(2);
+    line_0->set_block_id(1);
     line_0->set_order_within_block(1);
-
-    chrome_screen_ai::LineBox* line_1 = annotation.add_lines();
-    line_1->set_confidence(0.0f);
-    // Language, and the line as a whole,  should be ignored since the
-    // confidence is zero.
-    line_1->set_language("en");
-    line_1->set_block_id(1);
-    line_1->set_order_within_block(0);
-
-    chrome_screen_ai::LineBox* line_2 = annotation.add_lines();
-    line_2->set_confidence(0.7f);
-    chrome_screen_ai::Rect* box_2 = line_2->mutable_bounding_box();
-    // No bounding box should be created in the AXTree because the height is -5.
-    box_2->set_width(5);
-    box_2->set_height(-5);
-    line_2->set_block_id(2);
-    line_2->set_order_within_block(0);
-    line_2->set_direction(chrome_screen_ai::Direction::UNSPECIFIED);
   }
 
   {
@@ -193,18 +159,13 @@
         "  id=6 genericContainer offset_container_id=4 (0, 0)-(5, 5) "
         "role_description=Signature\n"
         "id=7 region (0, 0)-(800, 900) is_page_breaking_object=true "
-        "child_ids=8,9\n"
-        "  id=8 staticText (0, 0)-(5, 0) name_from=contents text_direction=ltr "
-        "name=\n"
-        "  id=9 staticText offset_container_id=7 (100, 100)-(500, 20) "
+        "child_ids=8\n"
+        "  id=8 staticText offset_container_id=7 (100, 100)-(500, 20) "
         "name_from=contents text_direction=rtl name=Hello world language=en "
-        "child_ids=10,11\n"
-        "    id=10 inlineTextBox offset_container_id=9 (100, 100)-(250, 20) "
-        "name_from=contents background_color=&C350 color=&61A8 "
-        "text_direction=rtl name=Hello \n"
-        "    id=11 inlineTextBox offset_container_id=9 (350, 100)-(250, 20) "
-        "name_from=contents background_color=&C350 color=&61A8 "
-        "text_direction=rtl name=world\n");
+        "child_ids=9\n"
+        "    id=9 inlineTextBox (100, 100)-(500, 20) name_from=contents "
+        "background_color=&C350 color=&61A8 text_direction=rtl language=en "
+        "name=Hello world word_starts=0,6 word_ends=6,11\n");
     EXPECT_EQ(expected_update, update.ToString());
   }
 }
diff --git a/components/test/data/autofill/heuristics/output/022_checkout_crutchfield.com.out b/components/test/data/autofill/heuristics/output/022_checkout_crutchfield.com.out
index 2fba472..acd2b0e 100644
--- a/components/test/data/autofill/heuristics/output/022_checkout_crutchfield.com.out
+++ b/components/test/data/autofill/heuristics/output/022_checkout_crutchfield.com.out
@@ -10,7 +10,7 @@
 EMAIL_ADDRESS | ctl00$ctl00$MainContentPlaceHolder$MainContentPlaceHolder$email2 | Email |  | ctl00$ctl00$MainContentPlaceHolder$MainContentPlaceHolder$firstname_1-default
 UNKNOWN_TYPE | ctl00$ctl00$MainContentPlaceHolder$MainContentPlaceHolder$chbox2 | Send me exclusive offers, deals and expert reviews. | on | ctl00$ctl00$MainContentPlaceHolder$MainContentPlaceHolder$firstname_1-default
 UNKNOWN_TYPE | ctl00$ctl00$MainContentPlaceHolder$MainContentPlaceHolder$same | Same as billing address. | same | ctl00$ctl00$MainContentPlaceHolder$MainContentPlaceHolder$firstname_1-default
-UNKNOWN_TYPE | ctl00$ctl00$MainContentPlaceHolder$MainContentPlaceHolder$giftOrder | Prices will not appear on invoice. This order is a gift. | gift | ctl00$ctl00$MainContentPlaceHolder$MainContentPlaceHolder$firstname_1-default
+UNKNOWN_TYPE | ctl00$ctl00$MainContentPlaceHolder$MainContentPlaceHolder$giftOrder | This order is a gift. Prices will not appear on invoice. | gift | ctl00$ctl00$MainContentPlaceHolder$MainContentPlaceHolder$firstname_1-default
 NAME_FIRST | ctl00$ctl00$MainContentPlaceHolder$MainContentPlaceHolder$shippingFirstname | First Name |  | ctl00$ctl00$MainContentPlaceHolder$MainContentPlaceHolder$shippingFirstname_2-default
 NAME_MIDDLE_INITIAL | ctl00$ctl00$MainContentPlaceHolder$MainContentPlaceHolder$shippingMi | M.I. |  | ctl00$ctl00$MainContentPlaceHolder$MainContentPlaceHolder$shippingFirstname_2-default
 NAME_LAST | ctl00$ctl00$MainContentPlaceHolder$MainContentPlaceHolder$shippingLastname | Last Name |  | ctl00$ctl00$MainContentPlaceHolder$MainContentPlaceHolder$shippingFirstname_2-default
diff --git a/components/test/data/autofill/heuristics/output/034_checkout_overstock.com.out b/components/test/data/autofill/heuristics/output/034_checkout_overstock.com.out
index 9953e27..2c4557a 100644
--- a/components/test/data/autofill/heuristics/output/034_checkout_overstock.com.out
+++ b/components/test/data/autofill/heuristics/output/034_checkout_overstock.com.out
@@ -25,7 +25,7 @@
 UNKNOWN_TYPE | CC | PayPal | PayPal | ShippingFirstName_2-default
 UNKNOWN_TYPE | CC | bml | bml | ShippingFirstName_2-default
 UNKNOWN_TYPE | UsePromoCode | Use Promo Code | on | ShippingFirstName_2-default
-MERCHANT_PROMO_CODE | PromoCode | See Terms |  | ShippingFirstName_2-default
+MERCHANT_PROMO_CODE | PromoCode | Use Promo Code |  | ShippingFirstName_2-default
 UNKNOWN_TYPE | UseGiftCards | Use Gift Card | on | ShippingFirstName_2-default
 UNKNOWN_TYPE | GiftCardNumber0 | Gift Card 1: |  | ShippingFirstName_2-default
 UNKNOWN_TYPE | PINNumber0 | PIN: |  | ShippingFirstName_2-default
diff --git a/components/test/data/autofill/heuristics/output/037_checkout_qvc.com.out b/components/test/data/autofill/heuristics/output/037_checkout_qvc.com.out
index 519e7d0..d1cd8df 100644
--- a/components/test/data/autofill/heuristics/output/037_checkout_qvc.com.out
+++ b/components/test/data/autofill/heuristics/output/037_checkout_qvc.com.out
@@ -26,4 +26,4 @@
 ADDRESS_HOME_ZIP | ShiptoZipCode | * Postal Code: |  | ShiptoAddress1_2-default
 ADDRESS_HOME_COUNTRY | ShiptoCountry | * Country: | US | ShiptoAddress1_2-default
 UNKNOWN_TYPE | ShiptoRadiobutton | This order only | ThisOrderOnly | ShiptoAddress1_2-default
-UNKNOWN_TYPE | ShiptoRadiobutton | (Permanent Ship-To)**All future orders | PermanentShipto | ShiptoAddress1_2-default
+UNKNOWN_TYPE | ShiptoRadiobutton | All future orders (Permanent Ship-To)** | PermanentShipto | ShiptoAddress1_2-default
diff --git a/components/test/data/autofill/heuristics/output/094_checkout_staples.com.out b/components/test/data/autofill/heuristics/output/094_checkout_staples.com.out
index a1f83c7..b6c1d2c6 100644
--- a/components/test/data/autofill/heuristics/output/094_checkout_staples.com.out
+++ b/components/test/data/autofill/heuristics/output/094_checkout_staples.com.out
@@ -1,5 +1,5 @@
 UNKNOWN_TYPE | emailAddress | * Indicates a required field |  | emailAddress_1-default
-UNKNOWN_TYPE | emailPreference | , I would like to receive emails about special money-saving offers from Staples. Yes | emailYes | emailAddress_1-default
+UNKNOWN_TYPE | emailPreference | Yes, I would like to receive emails about special money-saving offers from Staples. | emailYes | emailAddress_1-default
 NAME_FIRST | sFirstName | First Name * |  | sFirstName_1-default
 NAME_LAST | sLastName | Last Name * |  | sFirstName_1-default
 COMPANY_NAME | sCompany | Company Name (optional) |  | sFirstName_1-default
diff --git a/components/test/data/autofill/heuristics/output/109_checkout_m_nordstroms.com.out b/components/test/data/autofill/heuristics/output/109_checkout_m_nordstroms.com.out
index 96b7dd4..cea2e63 100644
--- a/components/test/data/autofill/heuristics/output/109_checkout_m_nordstroms.com.out
+++ b/components/test/data/autofill/heuristics/output/109_checkout_m_nordstroms.com.out
@@ -38,4 +38,4 @@
 ADDRESS_HOME_COUNTRY |  | Country | 0 | _1-default
 EMAIL_ADDRESS |  | E-mail Information Information |  | _1-default
 PHONE_HOME_WHOLE_NUMBER |  | Phone Information Information |  | _1-default
-UNKNOWN_TYPE |  | Send me e-mail updates about the latest trends, products and promotions online and in store. Yes! | on | _1-default
+UNKNOWN_TYPE |  | Yes! Send me e-mail updates about the latest trends, products and promotions online and in store. | on | _1-default
diff --git a/components/test/data/autofill/heuristics/output/149_checkout_qvc.com_non_hidden.out b/components/test/data/autofill/heuristics/output/149_checkout_qvc.com_non_hidden.out
index ac5a6cd5..d1cd8df 100644
--- a/components/test/data/autofill/heuristics/output/149_checkout_qvc.com_non_hidden.out
+++ b/components/test/data/autofill/heuristics/output/149_checkout_qvc.com_non_hidden.out
@@ -26,4 +26,4 @@
 ADDRESS_HOME_ZIP | ShiptoZipCode | * Postal Code: |  | ShiptoAddress1_2-default
 ADDRESS_HOME_COUNTRY | ShiptoCountry | * Country: | US | ShiptoAddress1_2-default
 UNKNOWN_TYPE | ShiptoRadiobutton | This order only | ThisOrderOnly | ShiptoAddress1_2-default
-UNKNOWN_TYPE | ShiptoRadiobutton | (Permanent Ship-To)** All future orders | PermanentShipto | ShiptoAddress1_2-default
+UNKNOWN_TYPE | ShiptoRadiobutton | All future orders (Permanent Ship-To)** | PermanentShipto | ShiptoAddress1_2-default
diff --git a/components/viz/service/display_embedder/compositor_gpu_thread.cc b/components/viz/service/display_embedder/compositor_gpu_thread.cc
index e3a72cb..06e2cb2 100644
--- a/components/viz/service/display_embedder/compositor_gpu_thread.cc
+++ b/components/viz/service/display_embedder/compositor_gpu_thread.cc
@@ -20,7 +20,6 @@
 #include "gpu/ipc/common/gpu_client_ids.h"
 #include "gpu/ipc/service/gpu_channel_manager.h"
 #include "gpu/vulkan/buildflags.h"
-#include "ui/gl/gl_bindings.h"
 #include "ui/gl/gl_context.h"
 #include "ui/gl/gl_surface_egl.h"
 #include "ui/gl/init/gl_factory.h"
@@ -49,7 +48,7 @@
   // that instead of enabling/disabling DrDc based on the extension.
   if (gl::GetGLImplementation() == gl::kGLImplementationEGLANGLE)
     DCHECK(gl::GLSurfaceEGL::GetGLDisplayEGL()
-               ->ext->b_EGL_ANGLE_context_virtualization);
+               ->IsANGLEContextVirtualizationSupported());
 #endif
 
   scoped_refptr<VulkanContextProvider> vulkan_context_provider;
diff --git a/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl.cc b/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl.cc
index 411603d4..443b418 100644
--- a/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl.cc
+++ b/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl.cc
@@ -854,6 +854,7 @@
   metadata.page_scale_factor = frame_metadata.page_scale_factor;
   metadata.root_scroll_offset_x = frame_metadata.root_scroll_offset.x();
   metadata.root_scroll_offset_y = frame_metadata.root_scroll_offset.y();
+  metadata.source_size = source_size;
   if (frame_metadata.top_controls_visible_height.has_value()) {
     last_top_controls_visible_height_ =
         *frame_metadata.top_controls_visible_height;
@@ -976,6 +977,7 @@
     metadata.region_capture_rect =
         scale_factor ? ScaleToEnclosingRect(capture_region, 1.0f / scale_factor)
                      : capture_region;
+    metadata.source_size = capture_region.size();
   }
   // Note that this is done unconditionally, as a new crop version may indicate
   // that the stream has been successfully uncropped.
diff --git a/content/app/content_main_runner_impl_browsertest.cc b/content/app/content_main_runner_impl_browsertest.cc
new file mode 100644
index 0000000..1356879
--- /dev/null
+++ b/content/app/content_main_runner_impl_browsertest.cc
@@ -0,0 +1,249 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <memory>
+#include <ostream>
+#include <string>
+#include <tuple>
+#include <utility>
+#include <vector>
+
+#include "base/command_line.h"
+#include "base/feature_list.h"
+#include "base/task/thread_pool/thread_pool_instance.h"
+#include "base/test/scoped_field_trial_list_resetter.h"
+#include "build/buildflag.h"
+#include "content/browser/startup_helper.h"
+#include "content/public/app/content_main_delegate.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/gpu/content_gpu_client.h"
+#include "content/public/renderer/content_renderer_client.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/content_browser_test.h"
+#include "content/public/utility/content_utility_client.h"
+#include "sandbox/policy/switches.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/abseil-cpp/absl/types/variant.h"
+
+namespace variations {
+class VariationsIdsProvider;
+};
+
+namespace content {
+
+class ContentClient;
+
+namespace {
+
+using ::testing::_;
+using ::testing::AtMost;
+using ::testing::ByMove;
+using ::testing::DoAll;
+using ::testing::Invoke;
+using ::testing::Return;
+using ::testing::SetArgPointee;
+using InvokedIn = ContentMainDelegate::InvokedIn;
+
+#if BUILDFLAG(IS_ANDROID)
+// TODO(joenotcharles): Find out why this test crashes on Android, which uses
+// custom startup code in BrowserTestBase::SetUp instead of calling ContentMain.
+#error This test is not supported on Android.
+#endif
+
+// Mocks only the cross-platform methods of ContentMainDelegate.
+class MockContentMainDelegate : public ContentMainDelegate {
+ public:
+  MOCK_METHOD(bool, BasicStartupComplete, (int*), (override));
+  MOCK_METHOD(void, PreSandboxStartup, (), (override));
+  MOCK_METHOD(void, SandboxInitialized, (const std::string&), (override));
+  MOCK_METHOD((absl::variant<int, MainFunctionParams>),
+              RunProcess,
+              (const std::string&, MainFunctionParams),
+              (override));
+  MOCK_METHOD(void, ProcessExiting, (const std::string&), (override));
+  MOCK_METHOD(int, TerminateForFatalInitializationError, (), (override));
+  MOCK_METHOD(bool, ShouldLockSchemeRegistry, (), (override));
+  MOCK_METHOD(void, PreBrowserMain, (), (override));
+  MOCK_METHOD(bool, ShouldCreateFeatureList, (InvokedIn), (override));
+  MOCK_METHOD(variations::VariationsIdsProvider*,
+              CreateVariationsIdsProvider,
+              (),
+              (override));
+  MOCK_METHOD(void, PostEarlyInitialization, (InvokedIn), (override));
+  MOCK_METHOD(ContentClient*, CreateContentClient, (), (override));
+  MOCK_METHOD(ContentBrowserClient*,
+              CreateContentBrowserClient,
+              (),
+              (override));
+  MOCK_METHOD(ContentGpuClient*, CreateContentGpuClient, (), (override));
+  MOCK_METHOD(ContentRendererClient*,
+              CreateContentRendererClient,
+              (),
+              (override));
+  MOCK_METHOD(ContentUtilityClient*,
+              CreateContentUtilityClient,
+              (),
+              (override));
+};
+
+// Parameters to control the expectations for MockContentMainDelegate. Each test
+// case uses a different set of parameters, since the expectations need to be
+// installed before SetUp() is called.
+struct MockExpectations {
+  bool exit_after_basic_startup = false;
+  bool content_main_should_create_feature_list = true;
+};
+
+std::ostream& operator<<(std::ostream& os, const MockExpectations& m) {
+  return os << "exit_after_basic_startup:" << m.exit_after_basic_startup
+            << ",content_main_should_create_feature_list:"
+            << m.content_main_should_create_feature_list;
+}
+
+// Tests that methods of ContentMainDelegate are called in the expected order.
+// The string parameter is the process name (empty for the browser process).
+class ContentMainRunnerImplBrowserTest
+    : public ContentBrowserTest,
+      public ::testing::WithParamInterface<
+          std::tuple<std::string, MockExpectations>> {
+ protected:
+  ContentMainRunnerImplBrowserTest() {
+    std::string process_type;
+    std::tie(process_type, mock_expectations_) = GetParam();
+
+    // Start without a feature list so the startup sequence can create one.
+    // `scoped_field_trial_list_resetter_` will do the same for the field trial
+    // list.
+    original_feature_list_ = base::FeatureList::ClearInstanceForTesting();
+
+    const bool is_browser_process = process_type.empty();
+    if (!is_browser_process) {
+      // Fool ContentMain() into thinking this is a different process type.
+      base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
+      command_line->AppendSwitchASCII(switches::kProcessType, process_type);
+      command_line->AppendSwitch(sandbox::policy::switches::kNoSandbox);
+    }
+    const InvokedIn invoked_in = is_browser_process
+                                     ? InvokedIn::kBrowserProcessUnderTest
+                                     : InvokedIn::kChildProcess;
+
+    // These methods may or may not be called, depending on configuration.
+    EXPECT_CALL(mock_delegate_, ShouldLockSchemeRegistry()).Times(AtMost(1));
+    EXPECT_CALL(mock_delegate_, CreateVariationsIdsProvider()).Times(AtMost(1));
+    // CreateContentClient() is only called if GetContentClient() returns null.
+    EXPECT_CALL(mock_delegate_, CreateContentClient()).Times(AtMost(1));
+
+    // Expect the following entry points to be called, in order.
+    //
+    // BrowserTestBase::SetUp() calls ContentMain(), which instantiates a
+    // ContentMainRunnerImpl, which calls the entry points in
+    // ContentMainDelegate. So test expectations must be installed before
+    // calling the inherited SetUp().
+    ::testing::InSequence s;
+    EXPECT_CALL(mock_delegate_, BasicStartupComplete(_))
+        .WillOnce(DoAll(
+            // Set the exit code.
+            SetArgPointee<0>(0),
+            // Set the return value.
+            Return(mock_expectations_.exit_after_basic_startup)));
+    if (mock_expectations_.exit_after_basic_startup) {
+      // Expect no more calls.
+      return;
+    }
+
+    if (is_browser_process) {
+      EXPECT_CALL(mock_delegate_, CreateContentBrowserClient())
+          .WillOnce(Return(&content_browser_client_));
+    } else if (process_type == switches::kGpuProcess) {
+      EXPECT_CALL(mock_delegate_, CreateContentGpuClient())
+          .WillOnce(Return(&content_gpu_client_));
+    } else if (process_type == switches::kRendererProcess) {
+      EXPECT_CALL(mock_delegate_, CreateContentRendererClient())
+          .WillOnce(Return(&content_renderer_client_));
+    } else {
+      EXPECT_CALL(mock_delegate_, CreateContentUtilityClient())
+          .WillOnce(Return(&content_utility_client_));
+    }
+    EXPECT_CALL(mock_delegate_, PreSandboxStartup());
+    EXPECT_CALL(mock_delegate_, SandboxInitialized(process_type));
+    EXPECT_CALL(mock_delegate_, ShouldCreateFeatureList(invoked_in))
+        .WillOnce(
+            Return(mock_expectations_.content_main_should_create_feature_list));
+    // PreBrowserMain is only called in the browser process.
+    EXPECT_CALL(mock_delegate_, PreBrowserMain())
+        .Times(is_browser_process ? 1 : 0);
+    EXPECT_CALL(mock_delegate_, PostEarlyInitialization(invoked_in))
+        .WillOnce(Invoke(
+            this, &ContentMainRunnerImplBrowserTest::PostEarlyInitialization));
+    EXPECT_CALL(mock_delegate_, RunProcess(process_type, _))
+        .WillOnce(Return(ByMove(0)));
+    EXPECT_CALL(mock_delegate_, ProcessExiting(process_type));
+  }
+
+  ~ContentMainRunnerImplBrowserTest() override {
+    // Restore the original feature list for other tests. Any temporary
+    // FeatureList created during the test (by ContentMainRunnerImpl or
+    // PostEarlyInitialization, depending on the value of the
+    // `content_main_should_create_feature_list` parameter) must be removed
+    // first.
+    base::FeatureList::ClearInstanceForTesting();
+    base::FeatureList::RestoreInstanceForTesting(
+        std::move(original_feature_list_));
+  }
+
+  ContentMainDelegate* GetCustomContentMainDelegate() override {
+    return &mock_delegate_;
+  }
+
+  void PostEarlyInitialization() {
+    ASSERT_TRUE(base::ThreadPoolInstance::Get());
+    if (mock_expectations_.content_main_should_create_feature_list) {
+      ASSERT_TRUE(base::FeatureList::GetInstance());
+    } else {
+      ASSERT_FALSE(base::FeatureList::GetInstance());
+
+      // ContentMainRunnerImpl will try to use the feature list after
+      // PostEarlyInitialization, so we need to create one.
+      field_trials_ = SetUpFieldTrialsAndFeatureList();
+    }
+  }
+
+  ::testing::StrictMock<MockContentMainDelegate> mock_delegate_;
+  MockExpectations mock_expectations_;
+
+  // Stubs to return from CreateContent*Client mocks. These will be deleted at
+  // the end of the test to satisfy the leak checker.
+  ContentBrowserClient content_browser_client_;
+  ContentGpuClient content_gpu_client_;
+  ContentRendererClient content_renderer_client_;
+  ContentUtilityClient content_utility_client_;
+
+  // Ensure that the test can create its own `field_trials_`.
+  base::test::ScopedFieldTrialListResetter scoped_field_trial_list_resetter_;
+  std::unique_ptr<base::FeatureList> original_feature_list_;
+  std::unique_ptr<base::FieldTrialList> field_trials_;
+};
+
+INSTANTIATE_TEST_SUITE_P(
+    All,
+    ContentMainRunnerImplBrowserTest,
+    ::testing::Combine(
+        ::testing::Values("",
+                          switches::kGpuProcess,
+                          switches::kRendererProcess,
+                          switches::kUtilityProcess),
+        ::testing::Values(MockExpectations{},
+                          MockExpectations{
+                              .content_main_should_create_feature_list = false},
+                          MockExpectations{.exit_after_basic_startup = true})));
+
+IN_PROC_BROWSER_TEST_P(ContentMainRunnerImplBrowserTest, StartupSequence) {
+  // All of the work is done in the test suite constructor and SetUp().
+}
+
+}  // namespace
+
+}  // namespace content
diff --git a/content/browser/media/capture/web_contents_frame_tracker.cc b/content/browser/media/capture/web_contents_frame_tracker.cc
index 10048ba..b2db5b20 100644
--- a/content/browser/media/capture/web_contents_frame_tracker.cc
+++ b/content/browser/media/capture/web_contents_frame_tracker.cc
@@ -23,6 +23,7 @@
 #include "content/public/browser/web_contents_media_capture_id.h"
 #include "content/public/browser/web_contents_observer.h"
 #include "media/base/media_switches.h"
+#include "media/base/video_util.h"
 #include "media/capture/video_capture_types.h"
 #include "ui/base/layout.h"
 #include "ui/gfx/geometry/dip_util.h"
@@ -149,6 +150,9 @@
 
 void WebContentsFrameTracker::SetCapturedContentSize(
     const gfx::Size& content_size) {
+  // For efficiency, this function should only be called when the captured
+  // content size changes. The caller is responsible for enforcing that.
+
   DVLOG(3) << __func__ << ": content_size=" << content_size.ToString();
   if (base::FeatureList::IsEnabled(media::kWebContentsCaptureHiDpi)) {
     // Check if the capture scale needs to be modified. The content_size
@@ -201,6 +205,9 @@
         preferred_size = gfx::ScaleToFlooredSize(
             preferred_size, static_cast<float>(1 / scale_ratio));
       }
+      DVLOG(3) << __func__ << ": x_ratio=" << x_ratio << " y_ratio=" << y_ratio
+               << " scale_ratio=" << scale_ratio
+               << " preferred_size=" << preferred_size.ToString();
     }
   }
   return preferred_size;
@@ -222,10 +229,18 @@
   // capture size, which should never happen.
   constexpr float kMinFactor = 1.0f;
 
-  // Ideally is that the |content_size| is the same as |capture_size_|, so if
-  // we are achieving that with current settings we can exit early.
-  if (content_size.width() == capture_size_.width() &&
-      content_size.height() == capture_size_.height()) {
+  // The content rectangle is letterboxed within the capture size rectangle.
+  // Calculate the effective scaled size after this is applied, and use
+  // this size for ratio calculations.
+  gfx::Size letterbox_size =
+      media::ComputeLetterboxRegion(gfx::Rect(capture_size_), content_size)
+          .size();
+
+  // Ideally the |content_size| should be the same as |letterbox_size|, so if
+  // we are achieving that with current settings we can exit early. Also
+  // accept almost-equal sizes to avoid oscillations due to rounding errors.
+  if (std::abs(content_size.width() - letterbox_size.width()) < 4 &&
+      std::abs(content_size.height() - letterbox_size.height()) < 4) {
     return capture_scale_override_;
   }
 
@@ -236,15 +251,25 @@
       content_size, 1.0f / context_->GetScaleOverrideForCapture());
 
   // Next, determine what the ideal scale factors in each direction would have
-  // been for this frame.
-  const gfx::Vector2dF factors(
-      static_cast<float>(capture_size_.width()) / unscaled_content_size.width(),
-      static_cast<float>(capture_size_.height()) /
-          unscaled_content_size.height());
+  // been for this frame. The factors should be nearly equal after letterboxing.
+  const gfx::Vector2dF factors(static_cast<float>(letterbox_size.width()) /
+                                   unscaled_content_size.width(),
+                               static_cast<float>(letterbox_size.height()) /
+                                   unscaled_content_size.height());
 
   // We prefer to err on the side of having to downscale in one direction rather
   // than upscale in the other direction, so we use the largest scale factor.
   const float largest_factor = std::max(factors.x(), factors.y());
+
+  DVLOG(3) << __func__ << ":"
+           << " capture_size_=" << capture_size_.ToString()
+           << " letterbox_size=" << letterbox_size.ToString()
+           << " content_size=" << content_size.ToString()
+           << " unscaled_content_size=" << unscaled_content_size.ToString()
+           << " context_->GetScaleOverrideForCapture()="
+           << context_->GetScaleOverrideForCapture()
+           << " factors.x()=" << factors.x() << " factors.y()=" << factors.y()
+           << " largest_factor=" << largest_factor;
   return std::clamp(largest_factor, kMinFactor, kMaxFactor);
 }
 
diff --git a/content/browser/media/capture/web_contents_frame_tracker_unittest.cc b/content/browser/media/capture/web_contents_frame_tracker_unittest.cc
index 1585fe8..df9f4e1 100644
--- a/content/browser/media/capture/web_contents_frame_tracker_unittest.cc
+++ b/content/browser/media/capture/web_contents_frame_tracker_unittest.cc
@@ -394,10 +394,26 @@
   tracker()->SetCapturedContentSize(gfx::Size(1920, 1080));
   EXPECT_DOUBLE_EQ(context()->scale_override(), 2.0f);
 
-  // If a frame ends up being larger than the capture_size, we should
-  // end up using the maximum scale override of 2.0.
-  tracker()->SetCapturedContentSize(gfx::Size(2304, 1080));
-  EXPECT_DOUBLE_EQ(context()->scale_override(), 2.0f);
+  // If a frame ends up being larger than the capture_size, the scale
+  // should get adjusted downwards so that the post-scaling size matches
+  // the capture size. This assumes a current scale override of 2.0f.
+  tracker()->SetCapturedContentSize(gfx::Size(2560, 1440));
+  EXPECT_DOUBLE_EQ(context()->scale_override(), 1.5f);
+
+  // The scaled size should now match the capture size with a scale
+  // override of 1.5.
+  tracker()->SetCapturedContentSize(gfx::Size(1920, 1080));
+  EXPECT_DOUBLE_EQ(context()->scale_override(), 1.5f);
+
+  // The scaling calculation is based on fitting a scaled copy of the
+  // source rectangle within the capture region, preserving aspect ratio.
+  // If the content size changes in a way that doesn't affect the scale
+  // factor (i.e. letterboxing or pillarboxing), the scale override remains
+  // unchanged.
+  tracker()->SetCapturedContentSize(gfx::Size(1080, 1080));
+  EXPECT_DOUBLE_EQ(context()->scale_override(), 1.5f);
+  tracker()->SetCapturedContentSize(gfx::Size(1920, 540));
+  EXPECT_DOUBLE_EQ(context()->scale_override(), 1.5f);
 
   // When we stop the tracker, the web contents issues a preferred size change
   // of the "old" size--so it shouldn't change.
diff --git a/content/browser/media/capture/web_contents_video_capture_device.cc b/content/browser/media/capture/web_contents_video_capture_device.cc
index a95698a9..925b0026 100644
--- a/content/browser/media/capture/web_contents_video_capture_device.cc
+++ b/content/browser/media/capture/web_contents_video_capture_device.cc
@@ -75,13 +75,18 @@
     const gfx::Rect& content_rect,
     mojo::PendingRemote<viz::mojom::FrameSinkVideoConsumerFrameCallbacks>
         callbacks) {
-  const gfx::Size new_size = content_rect.size();
-  if (new_size != content_size_) {
-    GetUIThreadTaskRunner({})->PostTask(
-        FROM_HERE,
-        base::BindOnce(&WebContentsFrameTracker::SetCapturedContentSize,
-                       tracker_->AsWeakPtr(), content_rect.size()));
-    content_size_ = new_size;
+  if (info->metadata.source_size.has_value()) {
+    const gfx::Size new_size = *info->metadata.source_size;
+    // Only update the captured content size when the size changes. See also
+    // the comment in WebContentsFrameTracker::SetCapturedContentSize which
+    // expects that behavior.
+    if (new_size != content_size_) {
+      GetUIThreadTaskRunner({})->PostTask(
+          FROM_HERE,
+          base::BindOnce(&WebContentsFrameTracker::SetCapturedContentSize,
+                         tracker_->AsWeakPtr(), new_size));
+      content_size_ = new_size;
+    }
   }
 
   FrameSinkVideoCaptureDevice::OnFrameCaptured(
@@ -103,6 +108,12 @@
       FROM_HERE,
       base::BindOnce(&WebContentsFrameTracker::DidStopCapturingWebContents,
                      tracker_->AsWeakPtr()));
+
+  // Currently, the video capture device is effectively a single-use object, so
+  // resetting capture_size_ isn't strictly necessary, but it helps ensure that
+  // SetCapturedContentSize works consistently in case the objects get reused in
+  // the future.
+  content_size_.SetSize(0, 0);
 }
 
 }  // namespace content
diff --git a/content/public/test/browser_test_base.cc b/content/public/test/browser_test_base.cc
index fdf71ef..b4a8a8b 100644
--- a/content/public/test/browser_test_base.cc
+++ b/content/public/test/browser_test_base.cc
@@ -597,7 +597,10 @@
   base::i18n::AllowMultipleInitializeCallsForTesting();
   base::i18n::InitializeICU();
 
-  ContentMainDelegate* delegate = GetContentMainDelegateForTesting();
+  ContentMainDelegate* delegate = GetCustomContentMainDelegate();
+  if (!delegate)
+    delegate = GetContentMainDelegateForTesting();
+
   // The delegate should have been set by JNI_OnLoad for the test target.
   DCHECK(delegate);
 
@@ -725,6 +728,8 @@
   auto params = CopyContentMainParams();
   params.ui_task = std::move(ui_task);
   params.created_main_parts_closure = std::move(created_main_parts_closure);
+  if (auto* custom_delegate = GetCustomContentMainDelegate())
+    params.delegate = custom_delegate;
   EXPECT_EQ(expected_exit_code_, ContentMain(std::move(params)));
 #endif  // BUILDFLAG(IS_ANDROID)
 
@@ -1125,4 +1130,8 @@
   CreatedBrowserMainParts(browser_main_parts);
 }
 
+ContentMainDelegate* BrowserTestBase::GetCustomContentMainDelegate() {
+  return nullptr;
+}
+
 }  // namespace content
diff --git a/content/public/test/browser_test_base.h b/content/public/test/browser_test_base.h
index c91fdc83..a537032 100644
--- a/content/public/test/browser_test_base.h
+++ b/content/public/test/browser_test_base.h
@@ -42,6 +42,7 @@
 
 namespace content {
 class BrowserMainParts;
+class ContentMainDelegate;
 class WebContents;
 
 class BrowserTestBase : public ::testing::Test {
@@ -122,6 +123,12 @@
   // PreEarlyInitialization() has been called.
   virtual void CreatedBrowserMainParts(BrowserMainParts* browser_main_parts) {}
 
+  // Override this to provide a custom ContentMainDelegate for the test. The
+  // returned object must live at least until
+  // TearDownInProcessBrowserTextFixture is called. If nullptr is returned the
+  // test will use the default delegate.
+  virtual ContentMainDelegate* GetCustomContentMainDelegate();
+
   // GTest assertions that the connection to `network_service_test_` did not get
   // dropped unexpectedly.
   void AssertThatNetworkServiceDidNotCrash();
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index d824c2a..78e1f80 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -1826,6 +1826,7 @@
   } else {
     # Non-Android.
     sources += [
+      "../app/content_main_runner_impl_browsertest.cc",
       "../browser/direct_sockets/direct_sockets_open_browsertest.cc",
       "../browser/direct_sockets/direct_sockets_tcp_browsertest.cc",
       "../browser/direct_sockets/direct_sockets_udp_browsertest.cc",
@@ -1846,6 +1847,7 @@
     ]
     deps += [
       "//content/public/browser:proto",
+      "//sandbox/policy",
       "//ui/base/clipboard:clipboard_test_support",
     ]
     data += [ "//content/test/data/clipboard/" ]
diff --git a/extensions/browser/api/audio/audio_api.cc b/extensions/browser/api/audio/audio_api.cc
index 7c11629..f0e715cb 100644
--- a/extensions/browser/api/audio/audio_api.cc
+++ b/extensions/browser/api/audio/audio_api.cc
@@ -15,9 +15,6 @@
 #include "extensions/browser/api/audio/pref_names.h"
 #include "extensions/browser/event_router.h"
 #include "extensions/common/api/audio.h"
-#include "extensions/common/extension.h"
-#include "extensions/common/features/behavior_feature.h"
-#include "extensions/common/features/feature_provider.h"
 
 namespace {
 const char* const kGetDevicesErrorMsg =
@@ -39,34 +36,6 @@
   return std::make_unique<AudioDeviceIdCalculator>(context);
 }
 
-// Checks if an extension is allowlisted to use deprecated version of audio API.
-// TODO(tbarzic): Retire this allowlist and remove the deprecated API methods,
-//     properties and events. This is currently targeted for M-60
-//     (http://crbug.com/697279).
-bool CanUseDeprecatedAudioApi(const Extension* extension) {
-  if (!extension)
-    return false;
-
-  const Feature* allow_deprecated_audio_api_feature =
-      FeatureProvider::GetBehaviorFeatures()->GetFeature(
-          behavior_feature::kAllowDeprecatedAudioApi);
-  if (!allow_deprecated_audio_api_feature)
-    return false;
-
-  return allow_deprecated_audio_api_feature->IsAvailableToExtension(extension)
-      .is_available();
-}
-
-bool CanReceiveDeprecatedAudioEvent(
-    content::BrowserContext* browser_context,
-    Feature::Context target_context,
-    const Extension* extension,
-    const base::DictionaryValue* filter,
-    std::unique_ptr<base::Value::List>* event_args,
-    mojom::EventFilteringInfoPtr* event_filtering_info_out) {
-  return CanUseDeprecatedAudioApi(extension);
-}
-
 }  // namespace
 
 static base::LazyInstance<
@@ -95,19 +64,6 @@
   return service_.get();
 }
 
-void AudioAPI::OnDeviceChanged() {
-  EventRouter* event_router = EventRouter::Get(browser_context_);
-  if (!event_router)
-    return;
-
-  auto event = std::make_unique<Event>(events::AUDIO_ON_DEVICE_CHANGED,
-                                       audio::OnDeviceChanged::kEventName,
-                                       std::vector<base::Value>());
-  event->will_dispatch_callback =
-      base::BindRepeating(&CanReceiveDeprecatedAudioEvent);
-  event_router->BroadcastEvent(std::move(event));
-}
-
 void AudioAPI::OnLevelChanged(const std::string& id, int level) {
   EventRouter* event_router = EventRouter::Get(browser_context_);
   if (!event_router)
@@ -155,28 +111,6 @@
 
 ///////////////////////////////////////////////////////////////////////////////
 
-ExtensionFunction::ResponseAction AudioGetInfoFunction::Run() {
-  if (!CanUseDeprecatedAudioApi(extension())) {
-    return RespondNow(
-        Error("audio.getInfo is deprecated, use audio.getDevices instead."));
-  }
-
-  AudioService* service =
-      AudioAPI::GetFactoryInstance()->Get(browser_context())->GetService();
-  DCHECK(service);
-  OutputInfo output_info;
-  InputInfo input_info;
-  if (!service->GetInfo(&output_info, &input_info)) {
-    return RespondNow(
-        Error("Error occurred when querying audio device information."));
-  }
-
-  return RespondNow(
-      ArgumentList(audio::GetInfo::Results::Create(output_info, input_info)));
-}
-
-///////////////////////////////////////////////////////////////////////////////
-
 ExtensionFunction::ResponseAction AudioGetDevicesFunction::Run() {
   std::unique_ptr<audio::GetDevices::Params> params(
       audio::GetDevices::Params::Create(args()));
@@ -225,31 +159,19 @@
       AudioAPI::GetFactoryInstance()->Get(browser_context())->GetService();
   DCHECK(service);
 
-  if (params->ids.as_device_id_lists) {
 #if BUILDFLAG(IS_CHROMEOS_LACROS)
-    // TODO: deprecated string lists are not implemented in Lacros
-    if (service->SetActiveDeviceLists(
-            params->ids.as_device_id_lists->input.get(),
-            params->ids.as_device_id_lists->output.get(),
-            base::BindOnce(&AudioSetActiveDevicesFunction::OnResponse, this))) {
-      return RespondLater();
-    }
-    return RespondNow(Error(kSetActiveDevicesErrorMsg));
-#else
-    if (!service->SetActiveDeviceLists(
-            params->ids.as_device_id_lists->input.get(),
-            params->ids.as_device_id_lists->output.get())) {
-      return RespondNow(Error(kSetActiveDevicesErrorMsg));
-    }
-#endif
-  } else if (params->ids.as_strings) {
-    if (!CanUseDeprecatedAudioApi(extension())) {
-      return RespondNow(
-          Error("String list |ids| is deprecated, use DeviceIdLists type."));
-    }
-    service->SetActiveDevices(*params->ids.as_strings);
+  if (service->SetActiveDeviceLists(
+          params->ids.input.get(), params->ids.output.get(),
+          base::BindOnce(&AudioSetActiveDevicesFunction::OnResponse, this))) {
+    return RespondLater();
   }
-  return RespondNow(NoArguments());
+#else
+  if (service->SetActiveDeviceLists(params->ids.input.get(),
+                                    params->ids.output.get())) {
+    return RespondNow(NoArguments());
+  }
+#endif
+  return RespondNow(Error(kSetActiveDevicesErrorMsg));
 }
 
 void AudioSetActiveDevicesFunction::OnResponse(bool success) {
@@ -270,55 +192,22 @@
       AudioAPI::GetFactoryInstance()->Get(browser_context())->GetService();
   DCHECK(service);
 
-  if (!CanUseDeprecatedAudioApi(extension())) {
-    if (params->properties.volume)
-      return RespondNow(Error("|volume| property is deprecated, use |level|."));
-
-    if (params->properties.gain)
-      return RespondNow(Error("|gain| property is deprecated, use |level|."));
-
-    if (params->properties.is_muted) {
-      return RespondNow(
-          Error("|isMuted| property is deprecated, use |audio.setMute|."));
-    }
-  }
-
   bool level_set = !!params->properties.level;
   int level_value = level_set ? *params->properties.level : -1;
 
 #if BUILDFLAG(IS_CHROMEOS_LACROS)
-  // deprecated volume_value and gain_value are not implemented in lacros
-  // see (http://crbug.com/697279)
   if (level_set &&
       service->SetDeviceSoundLevel(
           params->id, level_value,
           base::BindOnce(&AudioSetPropertiesFunction::OnResponse, this))) {
     return RespondLater();
   }
-  return RespondNow(Error(kLevelPropErrorMsg));
 #else
-
-  int volume_value = params->properties.volume.get() ?
-      *params->properties.volume : -1;
-
-  int gain_value = params->properties.gain.get() ?
-      *params->properties.gain : -1;
-
-  // |volume_value| and |gain_value| are deprecated in favor of |level_value|;
-  // they are kept around only to ensure backward-compatibility and should be
-  // ignored if |level_value| is set.
-  if (!service->SetDeviceSoundLevel(params->id,
-                                    level_set ? level_value : volume_value,
-                                    level_set ? level_value : gain_value))
-    return RespondNow(Error(kLevelPropErrorMsg));
-
-  if (params->properties.is_muted.get() &&
-      !service->SetMuteForDevice(params->id, *params->properties.is_muted)) {
-    return RespondNow(Error("Could not set mute property."));
+  if (level_set && service->SetDeviceSoundLevel(params->id, level_value)) {
+    return RespondNow(NoArguments());
   }
-
-  return RespondNow(NoArguments());
 #endif
+  return RespondNow(Error(kLevelPropErrorMsg));
 }
 
 void AudioSetPropertiesFunction::OnResponse(bool success) {
diff --git a/extensions/browser/api/audio/audio_api.h b/extensions/browser/api/audio/audio_api.h
index 61f8e71..11b3b95 100644
--- a/extensions/browser/api/audio/audio_api.h
+++ b/extensions/browser/api/audio/audio_api.h
@@ -39,7 +39,6 @@
   static const bool kServiceRedirectedInIncognito = true;
 
   // AudioService::Observer implementation.
-  void OnDeviceChanged() override;
   void OnLevelChanged(const std::string& id, int level) override;
   void OnMuteChanged(bool is_input, bool is_muted) override;
   void OnDevicesChanged(const DeviceInfoList& devices) override;
@@ -60,15 +59,6 @@
       audio_service_observation_{this};
 };
 
-class AudioGetInfoFunction : public ExtensionFunction {
- public:
-  DECLARE_EXTENSION_FUNCTION("audio.getInfo", AUDIO_GETINFO)
-
- protected:
-  ~AudioGetInfoFunction() override {}
-  ResponseAction Run() override;
-};
-
 class AudioGetDevicesFunction : public ExtensionFunction {
  public:
   DECLARE_EXTENSION_FUNCTION("audio.getDevices", AUDIO_GETDEVICES)
diff --git a/extensions/browser/api/audio/audio_apitest_chromeos.cc b/extensions/browser/api/audio/audio_apitest_chromeos.cc
index c2cc292..f38ae9b 100644
--- a/extensions/browser/api/audio/audio_apitest_chromeos.cc
+++ b/extensions/browser/api/audio/audio_apitest_chromeos.cc
@@ -278,28 +278,4 @@
   EXPECT_TRUE(result_catcher.GetNextResult()) << result_catcher.message();
 }
 
-class AllowlistedAudioApiTest : public AudioApiTest {
- public:
-  AllowlistedAudioApiTest() = default;
-  ~AllowlistedAudioApiTest() override = default;
-
-  void SetUpCommandLine(base::CommandLine* command_line) override {
-    command_line->AppendSwitchASCII(
-        extensions::switches::kAllowlistedExtensionID,
-        "jlgnoeceollaejlkenecblnjmdcfhfgc");
-  }
-};
-
-IN_PROC_BROWSER_TEST_F(AllowlistedAudioApiTest, DeprecatedApi) {
-  // Set up the audio nodes for testing.
-  AudioNodeList audio_nodes = {
-      CreateAudioNode(kJabraSpeaker1, 2), CreateAudioNode(kJabraSpeaker2, 2),
-      CreateAudioNode(kHDMIOutput, 2),    CreateAudioNode(kJabraMic1, 2),
-      CreateAudioNode(kJabraMic2, 2),     CreateAudioNode(kUSBCameraMic, 2)};
-
-  ChangeAudioNodes(audio_nodes);
-
-  EXPECT_TRUE(RunAppTest("api_test/audio/deprecated_api")) << message_;
-}
-
 }  // namespace extensions
diff --git a/extensions/browser/api/audio/audio_service.cc b/extensions/browser/api/audio/audio_service.cc
index c2dffa4..e7d6d5d 100644
--- a/extensions/browser/api/audio/audio_service.cc
+++ b/extensions/browser/api/audio/audio_service.cc
@@ -16,25 +16,21 @@
   void RemoveObserver(Observer* observer) override;
 
   // Start to query audio device information.
-  bool GetInfo(OutputInfo* output_info_out, InputInfo* input_info_out) override;
   bool GetDevices(const api::audio::DeviceFilter* filter,
                   DeviceInfoList* devices_out) override;
   bool GetDevices(
       const api::audio::DeviceFilter* filter,
       base::OnceCallback<void(bool, DeviceInfoList)> callback) override;
-  void SetActiveDevices(const DeviceIdList& device_list) override;
   bool SetActiveDeviceLists(const DeviceIdList* input_devices,
                             const DeviceIdList* output_devives) override;
   bool SetActiveDeviceLists(const DeviceIdList* input_devices,
                             const DeviceIdList* output_devives,
                             base::OnceCallback<void(bool)> callback) override;
   bool SetDeviceSoundLevel(const std::string& device_id,
-                           int volume,
-                           int gain) override;
+                           int level_value) override;
   bool SetDeviceSoundLevel(const std::string& device_id,
                            int level_value,
                            base::OnceCallback<void(bool)> callback) override;
-  bool SetMuteForDevice(const std::string& device_id, bool value) override;
   bool SetMute(bool is_input, bool value) override;
   bool SetMute(bool is_input,
                bool value,
@@ -57,12 +53,6 @@
   return std::make_unique<AudioServiceImpl>();
 }
 
-bool AudioServiceImpl::GetInfo(OutputInfo* output_info_out,
-                               InputInfo* input_info_out) {
-  // TODO: implement this for platforms other than Chrome OS.
-  return false;
-}
-
 bool AudioServiceImpl::GetDevices(const api::audio::DeviceFilter* filter,
                                   DeviceInfoList* devices_out) {
   return false;
@@ -87,12 +77,8 @@
   return false;
 }
 
-void AudioServiceImpl::SetActiveDevices(const DeviceIdList& device_list) {
-}
-
 bool AudioServiceImpl::SetDeviceSoundLevel(const std::string& device_id,
-                                           int volume,
-                                           int gain) {
+                                           int level_value) {
   return false;
 }
 
@@ -103,11 +89,6 @@
   return false;
 }
 
-bool AudioServiceImpl::SetMuteForDevice(const std::string& device_id,
-                                        bool value) {
-  return false;
-}
-
 bool AudioServiceImpl::SetMute(bool is_input, bool value) {
   return false;
 }
diff --git a/extensions/browser/api/audio/audio_service.h b/extensions/browser/api/audio/audio_service.h
index ddb7cd3b..84e7d2e 100644
--- a/extensions/browser/api/audio/audio_service.h
+++ b/extensions/browser/api/audio/audio_service.h
@@ -14,8 +14,6 @@
 
 namespace extensions {
 
-using OutputInfo = std::vector<api::audio::OutputDeviceInfo>;
-using InputInfo = std::vector<api::audio::InputDeviceInfo>;
 using DeviceIdList = std::vector<std::string>;
 using DeviceInfoList = std::vector<api::audio::AudioDeviceInfo>;
 
@@ -25,9 +23,6 @@
  public:
   class Observer {
    public:
-    // Called when anything changes to the audio device configuration.
-    virtual void OnDeviceChanged() = 0;
-
     // Called when the sound level of an active audio device changes.
     virtual void OnLevelChanged(const std::string& id, int level) = 0;
 
@@ -56,13 +51,6 @@
   virtual void AddObserver(Observer* observer) = 0;
   virtual void RemoveObserver(Observer* observer) = 0;
 
-  // Start to query audio device information. Should be called on UI thread.
-  // Populates |output_info_out| and |input_info_out| with the results.
-  // Returns true on success.
-  // DEPRECATED: Use |GetDevices| instead.
-  virtual bool GetInfo(OutputInfo* output_info_out,
-                       InputInfo* input_info_out) = 0;
-
   // Retrieves list of audio devices that satisfy |filter|. Populates
   // |devices_out| with retrieved devices.
   // If |filter->is_active| is set, |devices_out| will contain only devices
@@ -100,29 +88,15 @@
       const DeviceIdList* output_devives,
       base::OnceCallback<void(bool)> callback) = 0;
 
-  // Sets the active devices to the devices specified by |device_list|.
-  // It can pass in the "complete" active device list of either input
-  // devices, or output devices, or both. If only input devices are passed in,
-  // it will only change the input devices' active status, output devices will
-  // NOT be changed; similarly for the case if only output devices are passed.
-  // If the devices specified in |new_active_ids| are already active, they will
-  // remain active. Otherwise, the old active devices will be de-activated
-  // before we activate the new devices with the same type(input/output).
-  virtual void SetActiveDevices(const DeviceIdList& device_list) = 0;
-
   // Set the sound level properties (volume or gain) of a device.
   virtual bool SetDeviceSoundLevel(const std::string& device_id,
-                                   int volume,
-                                   int gain) = 0;
+                                   int level_value) = 0;
 
   // Same as above, but also passes if operation succeeded into a |callback|.
   virtual bool SetDeviceSoundLevel(const std::string& device_id,
                                    int level_value,
                                    base::OnceCallback<void(bool)> callback) = 0;
 
-  // Sets the mute property of a device.
-  virtual bool SetMuteForDevice(const std::string& device_id, bool value) = 0;
-
   // Sets mute property for audio input (if |is_input| is true) or output (if
   // |is_input| is false).
   virtual bool SetMute(bool is_input, bool value) = 0;
diff --git a/extensions/browser/api/audio/audio_service_chromeos.cc b/extensions/browser/api/audio/audio_service_chromeos.cc
index 0d727c17..adf380b 100644
--- a/extensions/browser/api/audio/audio_service_chromeos.cc
+++ b/extensions/browser/api/audio/audio_service_chromeos.cc
@@ -20,8 +20,6 @@
 namespace extensions {
 
 using api::audio::AudioDeviceInfo;
-using api::audio::InputDeviceInfo;
-using api::audio::OutputDeviceInfo;
 using ::ash::AudioDevice;
 using ::ash::AudioDeviceType;
 using ::ash::CrasAudioHandler;
@@ -87,25 +85,21 @@
   void RemoveObserver(AudioService::Observer* observer) override;
 
   // Start to query audio device information.
-  bool GetInfo(OutputInfo* output_info_out, InputInfo* input_info_out) override;
   bool GetDevices(const api::audio::DeviceFilter* filter,
                   DeviceInfoList* devices_out) override;
   bool GetDevices(
       const api::audio::DeviceFilter* filter,
       base::OnceCallback<void(bool, DeviceInfoList)> callback) override;
-  void SetActiveDevices(const DeviceIdList& device_list) override;
   bool SetActiveDeviceLists(const DeviceIdList* input_devices,
                             const DeviceIdList* output_devives) override;
   bool SetActiveDeviceLists(const DeviceIdList* input_devices,
                             const DeviceIdList* output_devives,
                             base::OnceCallback<void(bool)> callback) override;
   bool SetDeviceSoundLevel(const std::string& device_id,
-                           int volume,
-                           int gain) override;
+                           int level_value) override;
   bool SetDeviceSoundLevel(const std::string& device_id,
                            int level_value,
                            base::OnceCallback<void(bool)> callback) override;
-  bool SetMuteForDevice(const std::string& device_id, bool value) override;
   bool SetMute(bool is_input, bool value) override;
   bool SetMute(bool is_input,
                bool value,
@@ -125,7 +119,6 @@
   void OnActiveInputNodeChanged() override;
 
  private:
-  void NotifyDeviceChanged();
   void NotifyLevelChanged(uint64_t id, int level);
   void NotifyMuteChanged(bool is_input, bool is_muted);
   void NotifyDevicesChanged();
@@ -172,43 +165,6 @@
   observer_list_.RemoveObserver(observer);
 }
 
-bool AudioServiceImpl::GetInfo(OutputInfo* output_info_out,
-                               InputInfo* input_info_out) {
-  DCHECK_CURRENTLY_ON(BrowserThread::UI);
-  DCHECK(cras_audio_handler_);
-  DCHECK(output_info_out);
-  DCHECK(input_info_out);
-
-  if (!cras_audio_handler_)
-    return false;
-
-  ash::AudioDeviceList devices;
-  cras_audio_handler_->GetAudioDevices(&devices);
-  for (size_t i = 0; i < devices.size(); ++i) {
-    if (!devices[i].is_input) {
-      OutputDeviceInfo info;
-      info.id = base::NumberToString(devices[i].id);
-      info.name = devices[i].device_name + ": " + devices[i].display_name;
-      info.is_active = devices[i].active;
-      info.volume =
-          cras_audio_handler_->GetOutputVolumePercentForDevice(devices[i].id);
-      info.is_muted =
-          cras_audio_handler_->IsOutputMutedForDevice(devices[i].id);
-      output_info_out->push_back(std::move(info));
-    } else {
-      InputDeviceInfo info;
-      info.id = base::NumberToString(devices[i].id);
-      info.name = devices[i].device_name + ": " + devices[i].display_name;
-      info.is_active = devices[i].active;
-      info.gain =
-          cras_audio_handler_->GetInputGainPercentForDevice(devices[i].id);
-      info.is_muted = cras_audio_handler_->IsInputMutedForDevice(devices[i].id);
-      input_info_out->push_back(std::move(info));
-    }
-  }
-  return true;
-}
-
 bool AudioServiceImpl::GetDevices(const api::audio::DeviceFilter* filter,
                                   DeviceInfoList* devices_out) {
   if (!cras_audio_handler_)
@@ -245,21 +201,6 @@
   return false;
 }
 
-void AudioServiceImpl::SetActiveDevices(const DeviceIdList& device_list) {
-  DCHECK(cras_audio_handler_);
-  if (!cras_audio_handler_)
-    return;
-
-  CrasAudioHandler::NodeIdList id_list;
-  for (const auto& id : device_list) {
-    const AudioDevice* device =
-        cras_audio_handler_->GetDeviceFromId(GetIdFromStr(id));
-    if (device)
-      id_list.push_back(device->id);
-  }
-  cras_audio_handler_->ChangeActiveNodes(id_list);
-}
-
 bool AudioServiceImpl::SetActiveDeviceLists(
     const DeviceIdList* input_devices,
     const DeviceIdList* output_devives) {
@@ -300,8 +241,7 @@
 }
 
 bool AudioServiceImpl::SetDeviceSoundLevel(const std::string& device_id,
-                                           int volume,
-                                           int gain) {
+                                           int level_value) {
   DCHECK(cras_audio_handler_);
   if (!cras_audio_handler_)
     return false;
@@ -311,11 +251,8 @@
   if (!device)
     return false;
 
-  if (!device->is_input && volume != -1) {
-    cras_audio_handler_->SetVolumeGainPercentForDevice(device->id, volume);
-    return true;
-  } else if (device->is_input && gain != -1) {
-    cras_audio_handler_->SetVolumeGainPercentForDevice(device->id, gain);
+  if (level_value != -1) {
+    cras_audio_handler_->SetVolumeGainPercentForDevice(device->id, level_value);
     return true;
   }
 
@@ -331,21 +268,6 @@
   return false;
 }
 
-bool AudioServiceImpl::SetMuteForDevice(const std::string& device_id,
-                                        bool value) {
-  DCHECK(cras_audio_handler_);
-  if (!cras_audio_handler_)
-    return false;
-
-  const AudioDevice* device =
-      cras_audio_handler_->GetDeviceFromId(GetIdFromStr(device_id));
-  if (!device)
-    return false;
-
-  cras_audio_handler_->SetMuteForDevice(device->id, value);
-  return true;
-}
-
 bool AudioServiceImpl::SetMute(bool is_input, bool value) {
   DCHECK(cras_audio_handler_);
   if (!cras_audio_handler_)
@@ -449,35 +371,18 @@
   NotifyDevicesChanged();
 }
 
-void AudioServiceImpl::OnActiveOutputNodeChanged() {
-  NotifyDeviceChanged();
-}
+void AudioServiceImpl::OnActiveOutputNodeChanged() {}
 
-void AudioServiceImpl::OnActiveInputNodeChanged() {
-  NotifyDeviceChanged();
-}
-
-void AudioServiceImpl::NotifyDeviceChanged() {
-  for (auto& observer : observer_list_)
-    observer.OnDeviceChanged();
-}
+void AudioServiceImpl::OnActiveInputNodeChanged() {}
 
 void AudioServiceImpl::NotifyLevelChanged(uint64_t id, int level) {
   for (auto& observer : observer_list_)
     observer.OnLevelChanged(base::NumberToString(id), level);
-
-  // Notify DeviceChanged event for backward compatibility.
-  // TODO(jennyz): remove this code when the old version of hotrod retires.
-  NotifyDeviceChanged();
 }
 
 void AudioServiceImpl::NotifyMuteChanged(bool is_input, bool is_muted) {
   for (auto& observer : observer_list_)
     observer.OnMuteChanged(is_input, is_muted);
-
-  // Notify DeviceChanged event for backward compatibility.
-  // TODO(jennyz): remove this code when the old version of hotrod retires.
-  NotifyDeviceChanged();
 }
 
 void AudioServiceImpl::NotifyDevicesChanged() {
@@ -493,10 +398,6 @@
 
   for (auto& observer : observer_list_)
     observer.OnDevicesChanged(device_info_list);
-
-  // Notify DeviceChanged event for backward compatibility.
-  // TODO(jennyz): remove this code when the old version of hotrod retires.
-  NotifyDeviceChanged();
 }
 
 AudioService::Ptr AudioService::CreateInstance(
diff --git a/extensions/browser/api/audio/audio_service_lacros.cc b/extensions/browser/api/audio/audio_service_lacros.cc
index 7a49a3cc..bfa84e9 100644
--- a/extensions/browser/api/audio/audio_service_lacros.cc
+++ b/extensions/browser/api/audio/audio_service_lacros.cc
@@ -85,15 +85,6 @@
 
 AudioServiceLacros::~AudioServiceLacros() = default;
 
-bool AudioServiceLacros::GetInfo(OutputInfo* output_info_out,
-                                 InputInfo* input_info_out) {
-  // This method is used by a deprecated GetInfo method.
-  // Not implemented in lacros-chrome.
-  // TODO: remove deprecated methods (b/235198864)
-  NOTIMPLEMENTED();
-  return false;
-}
-
 bool AudioServiceLacros::GetDevices(const api::audio::DeviceFilter* filter,
                                     DeviceInfoList* devices_out) {
   // Not used in lacros-chrome, chrome.audio is asynchronous via a callback.
@@ -157,16 +148,8 @@
   return true;
 }
 
-void AudioServiceLacros::SetActiveDevices(const DeviceIdList& device_list) {
-  // This method is used by a deprecated SetActiveDevices variant.
-  // Not implemented in lacros-chrome.
-  // TODO: remove deprecated methods (b/235198864)
-  NOTIMPLEMENTED();
-}
-
 bool AudioServiceLacros::SetDeviceSoundLevel(const std::string& device_id,
-                                             int volume,
-                                             int gain) {
+                                             int level_value) {
   // Not used in lacros-chrome, chrome.audio is asynchronous via a callback.
   NOTREACHED();
   return false;
@@ -188,11 +171,6 @@
   return true;
 }
 
-bool AudioServiceLacros::SetMuteForDevice(const std::string& device_id,
-                                          bool value) {
-  return false;
-}
-
 bool AudioServiceLacros::SetMute(bool is_input, bool value) {
   // Not used in lacros-chrome, chrome.audio is asynchronous via a callback.
   NOTREACHED();
diff --git a/extensions/browser/api/audio/audio_service_lacros.h b/extensions/browser/api/audio/audio_service_lacros.h
index db9d1ba..a44cf6e 100644
--- a/extensions/browser/api/audio/audio_service_lacros.h
+++ b/extensions/browser/api/audio/audio_service_lacros.h
@@ -27,25 +27,21 @@
   void AddObserver(Observer* observer) override;
   void RemoveObserver(Observer* observer) override;
 
-  bool GetInfo(OutputInfo* output_info_out, InputInfo* input_info_out) override;
   bool GetDevices(const api::audio::DeviceFilter* filter,
                   DeviceInfoList* devices_out) override;
   bool GetDevices(
       const api::audio::DeviceFilter* filter,
       base::OnceCallback<void(bool, DeviceInfoList)> callback) override;
-  void SetActiveDevices(const DeviceIdList& device_list) override;
   bool SetActiveDeviceLists(const DeviceIdList* input_devices,
                             const DeviceIdList* output_devives) override;
   bool SetActiveDeviceLists(const DeviceIdList* input_devices,
                             const DeviceIdList* output_devives,
                             base::OnceCallback<void(bool)> callback) override;
   bool SetDeviceSoundLevel(const std::string& device_id,
-                           int volume,
-                           int gain) override;
+                           int level_value) override;
   bool SetDeviceSoundLevel(const std::string& device_id,
                            int level_value,
                            base::OnceCallback<void(bool)> callback) override;
-  bool SetMuteForDevice(const std::string& device_id, bool value) override;
   bool SetMute(bool is_input, bool value) override;
   bool SetMute(bool is_input,
                bool value,
diff --git a/extensions/browser/api/networking_private/lacros_networking_private_observer.cc b/extensions/browser/api/networking_private/lacros_networking_private_observer.cc
index 5c2be3e74..efa68a3f 100644
--- a/extensions/browser/api/networking_private/lacros_networking_private_observer.cc
+++ b/extensions/browser/api/networking_private/lacros_networking_private_observer.cc
@@ -79,6 +79,12 @@
   }
 }
 
+void LacrosNetworkingPrivateObserver::OnCertificateListsChanged() {
+  for (auto& observer : lacros_observers_) {
+    observer.OnCertificateListsChanged();
+  }
+}
+
 void LacrosNetworkingPrivateObserver::AddObserver(
     extensions::NetworkingPrivateDelegateObserver* observer) {
   lacros_observers_.AddObserver(observer);
diff --git a/extensions/browser/api/networking_private/lacros_networking_private_observer.h b/extensions/browser/api/networking_private/lacros_networking_private_observer.h
index 34e99af..752bf2e 100644
--- a/extensions/browser/api/networking_private/lacros_networking_private_observer.h
+++ b/extensions/browser/api/networking_private/lacros_networking_private_observer.h
@@ -32,6 +32,7 @@
   void OnPortalDetectionCompleted(
       const std::string& networkGuid,
       crosapi::mojom::CaptivePortalStatus status) override;
+  void OnCertificateListsChanged() override;
 
   void AddObserver(extensions::NetworkingPrivateDelegateObserver* observer);
   void RemoveObserver(extensions::NetworkingPrivateDelegateObserver* observer);
diff --git a/extensions/browser/api/networking_private/networking_private_delegate_observer.h b/extensions/browser/api/networking_private/networking_private_delegate_observer.h
index 9a784d6..fb49bfc 100644
--- a/extensions/browser/api/networking_private/networking_private_delegate_observer.h
+++ b/extensions/browser/api/networking_private/networking_private_delegate_observer.h
@@ -39,6 +39,9 @@
       std::string networkGuid,
       api::networking_private::CaptivePortalStatus status) = 0;
 
+  // Notifies observers when any certificate list has changed.
+  virtual void OnCertificateListsChanged() = 0;
+
  protected:
   virtual ~NetworkingPrivateDelegateObserver() {}
 };
diff --git a/extensions/browser/api/networking_private/networking_private_event_router_nonchromeos.cc b/extensions/browser/api/networking_private/networking_private_event_router_nonchromeos.cc
index 2a8761a..bee455a4 100644
--- a/extensions/browser/api/networking_private/networking_private_event_router_nonchromeos.cc
+++ b/extensions/browser/api/networking_private/networking_private_event_router_nonchromeos.cc
@@ -46,6 +46,7 @@
   void OnPortalDetectionCompleted(
       std::string networkGuid,
       api::networking_private::CaptivePortalStatus status) override;
+  void OnCertificateListsChanged() override;
 
  private:
   // Decide if we should listen for network changes or not. If there are any
@@ -195,6 +196,20 @@
   event_router->BroadcastEvent(std::move(extension_event));
 }
 
+void NetworkingPrivateEventRouterImpl::OnCertificateListsChanged() {
+  EventRouter* event_router = EventRouter::Get(browser_context_);
+  if (!event_router) {
+    return;
+  }
+
+  auto args(api::networking_private::OnCertificateListsChanged::Create());
+  auto extension_event = std::make_unique<Event>(
+      events::NETWORKING_PRIVATE_ON_CERTIFICATE_LISTS_CHANGED,
+      api::networking_private::OnCertificateListsChanged::kEventName,
+      std::move(args));
+  event_router->BroadcastEvent(std::move(extension_event));
+}
+
 NetworkingPrivateEventRouter* NetworkingPrivateEventRouter::Create(
     content::BrowserContext* browser_context) {
   return new NetworkingPrivateEventRouterImpl(browser_context);
diff --git a/extensions/browser/extension_event_histogram_value.h b/extensions/browser/extension_event_histogram_value.h
index 07fb305b..4afa5df 100644
--- a/extensions/browser/extension_event_histogram_value.h
+++ b/extensions/browser/extension_event_histogram_value.h
@@ -42,7 +42,7 @@
   APP_WINDOW_ON_RESTORED = 21,
   DELETED_AUDIO_MODEM_ON_RECEIVED = 22,
   DELETED_AUDIO_MODEM_ON_TRANSMIT_FAIL = 23,
-  AUDIO_ON_DEVICE_CHANGED = 24,
+  DELETED_AUDIO_ON_DEVICE_CHANGED = 24,
   AUDIO_ON_DEVICES_CHANGED = 25,
   AUDIO_ON_LEVEL_CHANGED = 26,
   AUDIO_ON_MUTE_CHANGED = 27,
diff --git a/extensions/browser/extension_function_histogram_value.h b/extensions/browser/extension_function_histogram_value.h
index 69781d5..2e2cc3d 100644
--- a/extensions/browser/extension_function_histogram_value.h
+++ b/extensions/browser/extension_function_histogram_value.h
@@ -515,7 +515,7 @@
   DELETED_BLUETOOTH_REMOVEPROFILE = 454,
   DELETED_BLUETOOTH_GETPROFILES = 455,
   EXPERIMENTAL_IDENTITY_REMOVECACHEDAUTHTOKEN = 456,
-  AUDIO_GETINFO = 457,
+  DELETED_AUDIO_GETINFO = 457,
   AUDIO_SETACTIVEDEVICES = 458,
   AUDIO_SETPROPERTIES = 459,
   USB_RESETDEVICE = 460,
@@ -1723,7 +1723,7 @@
   SHAREDSTORAGEPRIVATE_GET = 1660,
   SHAREDSTORAGEPRIVATE_SET = 1661,
   SHAREDSTORAGEPRIVATE_REMOVE = 1662,
-  FILEMANAGERPRIVATEINTERNAL_GETFILESRESTRICTEDBYDLP = 1663,
+  FILEMANAGERPRIVATEINTERNAL_GETDLPMETADATA = 1663,
   WMDESKSPRIVATE_GETALLDESKS = 1664,
   AUTOTESTPRIVATE_FORCEAUTOTHEMEMODE = 1665,
   OS_TELEMETRY_GETSTATEFULPARTITIONINFO = 1666,
diff --git a/extensions/common/api/_behavior_features.json b/extensions/common/api/_behavior_features.json
index 7a546ee7..e9a19c4 100644
--- a/extensions/common/api/_behavior_features.json
+++ b/extensions/common/api/_behavior_features.json
@@ -110,21 +110,6 @@
     "location": "policy",
     "platforms": ["chromeos"]
   }],
-  "allow_deprecated_audio_api": {
-    "channel": "stable",
-    "extension_types": ["platform_app"],
-    "allowlist": [
-      "8C3741E3AF0B93B6E8E0DDD499BB0B74839EA578",
-      "E703483CEF33DEC18B4B6DD84B5C776FB9182BDB",
-      "A3BC37E2148AC4E99BE4B16AF9D42DD1E592BBBE",  // http://crbug.com/335729
-      "1C93BD3CF875F4A73C0B2A163BB8FBDA8B8B3D80",  // http://crbug.com/335729
-      "307E96539209F95A1A8740C713E6998A73657D96",  // http://crbug.com/335729
-      "4F25792AF1AA7483936DE29C07806F203C7170A0",  // http://crbug.com/407693
-      "BD8781D757D830FC2E85470A1B6E8A718B7EE0D9",  // http://crbug.com/407693
-      "4AC2B6C63C6480D150DFDA13E4A5956EB1D0DDBB",  // http://crbug.com/407693
-      "81986D4F846CEDDDB962643FA501D1780DD441BB"   // http://crbug.com/407693
-    ]
-  },
   "allow_secondary_kiosk_app_enabled_on_launch": {
     "channel": "dev",
     "extension_types": ["platform_app"]
diff --git a/extensions/common/api/audio.idl b/extensions/common/api/audio.idl
index cb821b4..917be46 100644
--- a/extensions/common/api/audio.idl
+++ b/extensions/common/api/audio.idl
@@ -34,34 +34,6 @@
     OTHER
   };
 
-  [nodoc, deprecated = "Used only with the deprecated $(ref:getInfo)."]
-  dictionary OutputDeviceInfo {
-    // The unique identifier of the audio output device.
-    DOMString id;
-    // The user-friendly name (e.g. "Bose Amplifier").
-    DOMString name;
-    // True if this is the current active device.
-    boolean isActive;
-    // True if this is muted.
-    boolean isMuted;
-    // The output volume ranging from 0.0 to 100.0.
-    double volume;
-  };
-
-  [nodoc, deprecated = "Used only with the deprecated $(ref:getInfo)."]
-  dictionary InputDeviceInfo {
-    // The unique identifier of the audio input device.
-    DOMString id;
-    // The user-friendly name (e.g. "USB Microphone").
-    DOMString name;
-    // True if this is the current active device.
-    boolean isActive;
-    // True if this is muted.
-    boolean isMuted;
-    // The input gain ranging from 0.0 to 100.0.
-    double gain;
-  };
-
   dictionary AudioDeviceInfo {
     // The unique identifier of the audio device.
     DOMString id;
@@ -92,22 +64,10 @@
   };
 
   dictionary DeviceProperties {
-    // True if this is muted.
-    [nodoc, deprecated="Use $(ref:setMute) to set mute state."]
-    boolean? isMuted;
-
-    // If this is an output device then this field indicates the output volume.
-    // If this is an input device then this field is ignored.
-    [nodoc, deprecated="Use |level| to set output volume."] double? volume;
-
-    // If this is an input device then this field indicates the input gain.
-    // If this is an output device then this field is ignored.
-    [nodoc, deprecated="Use |level| to set input gain."] double? gain;
- 
     // <p>
     //   The audio device's desired sound level. Defaults to the device's
     //   current sound level.
-    // </p> 
+    // </p>
     // <p>If used with audio input device, represents audio device gain.</p>
     // <p>If used with audio output device, represents audio device volume.</p>
     long? level;
@@ -142,8 +102,6 @@
     long level;
   };
 
-  callback GetInfoCallback = void(OutputDeviceInfo[] outputInfo,
-                                  InputDeviceInfo[] inputInfo);
   callback GetDevicesCallback = void(AudioDeviceInfo[] devices);
   callback GetMuteCallback = void(boolean value);
   callback EmptyCallback = void();
@@ -153,7 +111,7 @@
     // |filter|: Device properties by which to filter the list of returned
     //     audio devices. If the filter is not set or set to <code>{}</code>,
     //     returned device list will contain all available audio devices.
-    // |callback|: Reports the requested list of audio devices. 
+    // |callback|: Reports the requested list of audio devices.
     static void getDevices(optional DeviceFilter filter,
                            GetDevicesCallback callback);
 
@@ -163,12 +121,7 @@
     //     unaffected.
     //     </p>
     //     <p>It is an error to pass in a non-existent device ID.</p>
-    //     <p><b>NOTE:</b> While the method signature allows device IDs to be
-    //     passed as a list of strings, this method of setting active devices
-    //     is deprecated and should not be relied upon to work. Please use
-    //     $(ref:DeviceIdLists) instead.
-    //     </p>
-    static void setActiveDevices((DeviceIdLists or DOMString[]) ids,
+    static void setActiveDevices(DeviceIdLists ids,
                                  EmptyCallback callback);
 
     // Sets the properties for the input or output device.
@@ -189,10 +142,6 @@
     static void setMute(StreamType streamType,
                         boolean isMuted,
                         optional EmptyCallback callback);
-
-    // Gets the information of all audio output and input devices.
-    [nodoc, deprecated="Use $(ref:getDevices) instead."]
-    static void getInfo(GetInfoCallback callback);
   };
 
   interface Events {
@@ -208,10 +157,5 @@
     // existing devices being removed.
     // |devices|: List of all present audio devices after the change.
     static void onDeviceListChanged(AudioDeviceInfo[] devices);
-
-    // Fired when anything changes to the audio device configuration.
-    [nodoc, deprecated="Use more granular $(ref:onLevelChanged),
-         $(ref:onMuteChanged) and $(ref:onDeviceListChanged) instead."]
-    static void onDeviceChanged();
   };
 };
diff --git a/extensions/common/features/behavior_feature.cc b/extensions/common/features/behavior_feature.cc
index db470df..68eb0a0 100644
--- a/extensions/common/features/behavior_feature.cc
+++ b/extensions/common/features/behavior_feature.cc
@@ -17,8 +17,6 @@
 
 const char kSigninScreen[] = "signin_screen";
 
-const char kAllowDeprecatedAudioApi[] = "allow_deprecated_audio_api";
-
 const char kAllowSecondaryKioskAppEnabledOnLaunch[] =
     "allow_secondary_kiosk_app_enabled_on_launch";
 
diff --git a/extensions/common/features/behavior_feature.h b/extensions/common/features/behavior_feature.h
index 3c4b6312..7ed3e17 100644
--- a/extensions/common/features/behavior_feature.h
+++ b/extensions/common/features/behavior_feature.h
@@ -15,7 +15,6 @@
 extern const char kZoomWithoutBubble[];
 extern const char kAllowUsbDevicesPermissionInterfaceClass[];
 extern const char kSigninScreen[];
-extern const char kAllowDeprecatedAudioApi[];
 extern const char kAllowSecondaryKioskAppEnabledOnLaunch[];
 extern const char kKeyPermissionsInLoginScreen[];
 extern const char kImprivataInSessionExtension[];
diff --git a/extensions/test/data/api_test/audio/deprecated_api/manifest.json b/extensions/test/data/api_test/audio/deprecated_api/manifest.json
deleted file mode 100644
index 3cde7f13..0000000
--- a/extensions/test/data/api_test/audio/deprecated_api/manifest.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
-  "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4Gb8dd92HGWI64TMqr7Qr+A0WwG2kMhoNYR6bYO/0klVnNdZ5q8u46wvqu3aNkzhbe5r7Zf4utKenC6dPMm22b/4+Qge1ng+NHRbCSiwcy3JMhrO93gGQLueP5KRQhy+fXTeAJugAj4vR8nkfYOJaeauosh1f7I4D3Gy2XI+E5mo3/TUVxxRgOYMZIYsXlPStdhUuPe3QltZZxPCNv98UX30F1NhrNoeKIHyf83++Egxb/CwMtX9Dog15V56qBbiuXLECWu7+GY/GCKt9LAX5ieyEziV6Uq8/ISB1hwOThQWPFRd7NbHVisPGwyoMPX4DL57eYfKAwl1U2KbP+KdAQIDAQAB",
-  "app": { "background": { "scripts": ["test.js"] } },
-  "manifest_version": 2,
-  "name": "Audio API Test Extension",
-  "permissions": ["audio"],
-  "version": "1.0"
-}
diff --git a/extensions/test/data/api_test/audio/deprecated_api/test.js b/extensions/test/data/api_test/audio/deprecated_api/test.js
deleted file mode 100644
index e6658789..0000000
--- a/extensions/test/data/api_test/audio/deprecated_api/test.js
+++ /dev/null
@@ -1,237 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * Asserts that device property values match properties in |expectedProperties|.
- * The method will *not* assert that the device contains *only* properties
- * specified in expected properties.
- * @param {Object} expectedProperties Expected device properties.
- * @param {Object} device Device object to test.
- */
-function assertDeviceMatches(expectedProperties, device) {
-  Object.keys(expectedProperties).forEach(function(key) {
-    chrome.test.assertEq(expectedProperties[key], device[key],
-        'Property ' + key + ' of device ' + device.id);
-  });
-}
-
-/**
- * Verifies that list of devices contains all and only devices from set of
- * expected devices. If will fail the test if an unexpected device is found.
- *
- * @param {Object.<string, Object>} expectedDevices Expected set of test
- *     devices. Maps device ID to device properties.
- * @param {Array.<Object>} devices List of input devices.
- */
-function assertDevicesMatch(expectedDevices, devices) {
-  var deviceIds = {};
-  devices.forEach(function(device) {
-    chrome.test.assertFalse(!!deviceIds[device.id],
-                            'Duplicated device id: \'' + device.id + '\'.');
-    deviceIds[device.id] = true;
-  });
-
-  function sortedKeys(obj) {
-    return Object.keys(obj).sort();
-  }
-  chrome.test.assertEq(sortedKeys(expectedDevices), sortedKeys(deviceIds));
-
-  devices.forEach(function(device) {
-    assertDeviceMatches(expectedDevices[device.id], device);
-  });
-}
-
-/**
- * @param {Array.<Object>} devices List of devices returned by
- *    chrome.audio.getInfo or chrome.audio.getDevices.
- * @return {Object.<string, Object>} List of devices formatted as map of
- *      expected devices used to assert devices match expectation.
- */
-function deviceListToExpectedDevicesMap(devices) {
-  var expectedDevicesMap = {};
-  devices.forEach(function(device) {
-    expectedDevicesMap[device.id] = device;
-  });
-  return expectedDevicesMap;
-}
-
-function EventListener(targetEvent) {
-  this.targetEvent = targetEvent;
-  this.listener = this.handleEvent.bind(this);
-  this.targetEvent.addListener(this.listener);
-  this.eventCount = 0;
-}
-
-EventListener.prototype.handleEvent = function() {
-  ++this.eventCount;
-}
-
-EventListener.prototype.reset = function() {
-  this.targetEvent.removeListener(this.listener);
-}
-
-var deviceChangedListener = null;
-
-chrome.test.runTests([
-  // Sets up a listener for audio.onDeviceChanged event -
-  // |verifyDeviceChangedEvents| test will later verify that
-  // onDeviceChanged events have been observed.
-  function startDeviceChangedListener() {
-    deviceChangedListener = new EventListener(chrome.audio.onDeviceChanged);
-    chrome.test.succeed();
-  },
-
-  function getInfoTest() {
-    // Test output devices. Maps device ID -> tested device properties.
-    var kTestOutputDevices = {
-      '30001': {
-        id: '30001',
-        name: 'Jabra Speaker: Jabra Speaker 1'
-      },
-      '30002': {
-        id: '30002',
-        name: 'Jabra Speaker: Jabra Speaker 2'
-      },
-      '30003': {
-        id: '30003',
-        name: 'HDMI output: HDA Intel MID'
-      }
-    };
-
-    // Test input devices. Maps device ID -> tested device properties.
-    var kTestInputDevices = {
-      '40001': {
-        id: '40001',
-        name: 'Jabra Mic: Jabra Mic 1'
-      },
-      '40002': {
-        id: '40002',
-        name: 'Jabra Mic: Jabra Mic 2'
-      },
-      '40003': {
-        id: '40003',
-        name: 'Webcam Mic: Logitech Webcam'
-      }
-    };
-
-    chrome.audio.getInfo(chrome.test.callbackPass(
-        function(outputInfo, inputInfo) {
-          assertDevicesMatch(kTestOutputDevices, outputInfo);
-          assertDevicesMatch(kTestInputDevices, inputInfo);
-        }));
-  },
-
-  function setActiveDevicesTest() {
-    //Test output devices. Maps device ID -> tested device properties.
-    var kTestDevices = {
-      '30001': {
-        id: '30001',
-        isActive: false
-      },
-      '30002': {
-        id: '30002',
-        isActive: false
-      },
-      '30003': {
-        id: '30003',
-        isActive: true
-      },
-      '40001': {
-        id: '40001',
-        isActive: false
-      },
-      '40002': {
-        id: '40002',
-        isActive: true
-      },
-      '40003': {
-        id: '40003',
-        isActive: false
-      }
-    };
-
-    chrome.audio.setActiveDevices([
-      '30003',
-      '40002'
-    ], chrome.test.callbackPass(function() {
-      chrome.audio.getDevices(chrome.test.callbackPass(function(devices) {
-        assertDevicesMatch(kTestDevices, devices);
-      }));
-    }));
-  },
-
-  function setPropertiesTest() {
-    chrome.audio.getInfo(chrome.test.callbackPass(function(
-        initialOutput, initialInput) {
-      var expectedInput = deviceListToExpectedDevicesMap(initialInput);
-      var expectedOutput = deviceListToExpectedDevicesMap(initialOutput);
-
-      // Update expected input devices with values that should be changed in
-      // test.
-      var updatedInput = expectedInput['40002'];
-      chrome.test.assertFalse(updatedInput.isMuted);
-      chrome.test.assertFalse(updatedInput.gain === 55);
-      updatedInput.isMuted = true;
-      updatedInput.gain = 55;
-
-      // Update expected output devices with values that should be changed in
-      // test.
-      var updatedOutput = expectedOutput['30001'];
-      chrome.test.assertFalse(updatedOutput.volume === 35);
-      updatedOutput.volume = 35;
-
-      chrome.audio.setProperties('30001', {
-        volume: 35
-      }, chrome.test.callbackPass(function() {
-        chrome.audio.setProperties('40002', {
-          isMuted: true,
-          gain: 55
-        }, chrome.test.callbackPass(function() {
-          chrome.audio.getInfo(chrome.test.callbackPass(function(
-              output, input) {
-            assertDevicesMatch(expectedOutput, output);
-            assertDevicesMatch(expectedInput, input);
-          }));
-        }));
-      }));
-    }));
-  },
-
-  function setPropertiesInvalidValuesTest() {
-    chrome.audio.getInfo(chrome.test.callbackPass(function(
-        initialOutput, initialInput) {
-      var expectedOutput = deviceListToExpectedDevicesMap(initialOutput);
-      var expectedInput = deviceListToExpectedDevicesMap(initialInput);
-      var expectedError = 'Could not set volume/gain properties';
-
-      chrome.audio.setProperties('30001', {
-        isMuted: true,
-        // Output device - should have volume set.
-        gain: 55
-      }, chrome.test.callbackFail(expectedError, function() {
-        chrome.audio.setProperties('40002', {
-          isMuted: true,
-          // Input device - should have gain set.
-          volume:55
-        }, chrome.test.callbackFail(expectedError, function() {
-          // Assert that device properties haven't changed.
-          chrome.audio.getInfo(chrome.test.callbackPass(function(
-              output, input) {
-            assertDevicesMatch(expectedOutput, output);
-            assertDevicesMatch(expectedInput, input);
-          }));
-        }));
-      }));
-    }));
-  },
-
-  function verifyDeviceChangedEvents() {
-    chrome.test.assertTrue(!!deviceChangedListener);
-    chrome.test.assertTrue(deviceChangedListener.eventCount > 0);
-    deviceChangedListener.reset();
-    deviceChangedListener = null;
-    chrome.test.succeed();
-  },
-
-]);
diff --git a/extensions/test/data/api_test/audio/test.js b/extensions/test/data/api_test/audio/test.js
index eeb5d8db..215571f 100644
--- a/extensions/test/data/api_test/audio/test.js
+++ b/extensions/test/data/api_test/audio/test.js
@@ -66,32 +66,9 @@
   return devices.map(function(device) {return device.id;}).sort();
 }
 
-function EventListener(targetEvent) {
-  this.targetEvent = targetEvent;
-  this.listener = this.handleEvent.bind(this);
-  this.targetEvent.addListener(this.listener);
-  this.eventCount = 0;
-}
-
-EventListener.prototype.handleEvent = function() {
-  ++this.eventCount;
-}
-
-EventListener.prototype.reset = function() {
-  this.targetEvent.removeListener(this.listener);
-}
-
 var deviceChangedListener = null;
 
 chrome.test.runTests([
-  // Sets up a listener for audio.onDeviceChanged event -
-  // |verifyNoDeviceChangedEvents| test will later verify that no
-  // onDeviceChanged events have been observed.
-  function startDeviceChangedListener() {
-    deviceChangedListener = new EventListener(chrome.audio.onDeviceChanged);
-    chrome.test.succeed();
-  },
-
   function getDevicesTest() {
     // Test output devices. Maps device ID -> tested device properties.
     var kTestDevices = {
@@ -456,82 +433,4 @@
       }));
     }));
   },
-
-  function verifyNoDeviceChangedEvents() {
-    chrome.test.assertTrue(!!deviceChangedListener);
-    chrome.test.assertEq(0, deviceChangedListener.eventCount);
-    deviceChangedListener.reset();
-    deviceChangedListener = null;
-    chrome.test.succeed();
-  },
-
-  // Tests verifying the app doesn't have access to deprecated part of the API:
-  function deprecated_GetInfoTest() {
-    chrome.audio.getInfo(chrome.test.callbackFail(
-        'audio.getInfo is deprecated, use audio.getDevices instead.'));
-  },
-
-  function deprecated_setProperties_isMuted() {
-    chrome.audio.getDevices(chrome.test.callbackPass(function(initial) {
-      var expectedDevices = deviceListToExpectedDevicesMap(initial);
-      var expectedError =
-          '|isMuted| property is deprecated, use |audio.setMute|.';
-
-      chrome.audio.setProperties('30001', {
-        isMuted: true,
-        // Output device - should have volume set.
-        level: 55
-      }, chrome.test.callbackFail(expectedError, function() {
-        // Assert that device properties haven't changed.
-        chrome.audio.getDevices(chrome.test.callbackPass(function(devices) {
-          assertDevicesMatch(expectedDevices, devices);
-        }));
-      }));
-    }));
-  },
-
-  function deprecated_setProperties_volume() {
-    chrome.audio.getDevices(chrome.test.callbackPass(function(initial) {
-      var expectedDevices = deviceListToExpectedDevicesMap(initial);
-      var expectedError = '|volume| property is deprecated, use |level|.';
-
-      chrome.audio.setProperties('30001', {
-        volume: 2,
-        // Output device - should have volume set.
-        level: 55
-      }, chrome.test.callbackFail(expectedError, function() {
-        // Assert that device properties haven't changed.
-        chrome.audio.getDevices(chrome.test.callbackPass(function(devices) {
-          assertDevicesMatch(expectedDevices, devices);
-        }));
-      }));
-    }));
-  },
-
-  function deprecated_setProperties_gain() {
-    chrome.audio.getDevices(chrome.test.callbackPass(function(initial) {
-      var expectedDevices = deviceListToExpectedDevicesMap(initial);
-      var expectedError = '|gain| property is deprecated, use |level|.';
-
-      chrome.audio.setProperties('40001', {
-        gain: 2,
-        // Output device - should have volume set.
-        level: 55
-      }, chrome.test.callbackFail(expectedError, function() {
-        // Assert that device properties haven't changed.
-        chrome.audio.getDevices(chrome.test.callbackPass(function(devices) {
-          assertDevicesMatch(expectedDevices, devices);
-        }));
-      }));
-    }));
-  },
-
-  function deprecated_SetActiveDevicesTest() {
-    var kExpectedError =
-        'String list |ids| is deprecated, use DeviceIdLists type.';
-    chrome.audio.setActiveDevices([
-      '30003',
-      '40002'
-    ], chrome.test.callbackFail(kExpectedError));
-  },
 ]);
diff --git a/gpu/command_buffer/service/shared_image_backing_factory_angle_vulkan.cc b/gpu/command_buffer/service/shared_image_backing_factory_angle_vulkan.cc
index 33206e1f..b7bfe43d 100644
--- a/gpu/command_buffer/service/shared_image_backing_factory_angle_vulkan.cc
+++ b/gpu/command_buffer/service/shared_image_backing_factory_angle_vulkan.cc
@@ -597,7 +597,7 @@
                                         context_state->progress_reporter()),
       context_state_(context_state) {
   DCHECK(context_state_->GrContextIsVulkan());
-  DCHECK(gl::GLSurfaceEGL::GetGLDisplayEGL()->ext->b_EGL_ANGLE_vulkan_image);
+  DCHECK(gl::GLSurfaceEGL::GetGLDisplayEGL()->IsANGLEVulkanImageSupported());
 }
 
 SharedImageBackingFactoryAngleVulkan::~SharedImageBackingFactoryAngleVulkan() =
diff --git a/gpu/command_buffer/service/shared_image_backing_factory_ozone.cc b/gpu/command_buffer/service/shared_image_backing_factory_ozone.cc
index 8d5944e..ae564df0 100644
--- a/gpu/command_buffer/service/shared_image_backing_factory_ozone.cc
+++ b/gpu/command_buffer/service/shared_image_backing_factory_ozone.cc
@@ -22,7 +22,6 @@
 #include "ui/gfx/gpu_memory_buffer.h"
 #include "ui/gfx/native_pixmap.h"
 #include "ui/gl/buildflags.h"
-#include "ui/gl/gl_bindings.h"
 #include "ui/gl/gl_surface_egl.h"
 #include "ui/ozone/public/ozone_platform.h"
 #include "ui/ozone/public/surface_factory_ozone.h"
@@ -235,7 +234,7 @@
     return false;
   }
   if (used_by_gl &&
-      !gl::GLSurfaceEGL::GetGLDisplayEGL()->ext->b_EGL_KHR_image) {
+      !gl::GLSurfaceEGL::GetGLDisplayEGL()->HasEGLExtension("EGL_KHR_image")) {
     return false;
   }
 #else
diff --git a/gpu/command_buffer/service/shared_image_factory.cc b/gpu/command_buffer/service/shared_image_factory.cc
index 1f37a592b..a92b327f 100644
--- a/gpu/command_buffer/service/shared_image_factory.cc
+++ b/gpu/command_buffer/service/shared_image_factory.cc
@@ -264,7 +264,7 @@
 #endif  // BUILDFLAG(ENABLE_VULKAN)
 
     bool egl_ext_supported =
-        gl::GLSurfaceEGL::GetGLDisplayEGL()->ext->b_EGL_KHR_image;
+        gl::GLSurfaceEGL::GetGLDisplayEGL()->HasEGLExtension("EGL_KHR_image");
     bool glx_ext_supported = false;
 #if defined(USE_OZONE)
 #if BUILDFLAG(OZONE_PLATFORM_X11)
diff --git a/gpu/config/gpu_info_collector.cc b/gpu/config/gpu_info_collector.cc
index d95ffa8..56406bb 100644
--- a/gpu/config/gpu_info_collector.cc
+++ b/gpu/config/gpu_info_collector.cc
@@ -386,11 +386,13 @@
   base::UmaHistogramSparse("GPU.MaxMSAASampleCount", max_samples);
 
 #if BUILDFLAG(IS_ANDROID)
-  gl::GLDisplayEGL* display = gl::GLSurfaceEGL::GetGLDisplayEGL();
   gpu_info->can_support_threaded_texture_mailbox =
-      display->ext->b_EGL_KHR_fence_sync &&
-      display->ext->b_EGL_KHR_image_base &&
-      display->ext->b_EGL_KHR_gl_texture_2D_image &&
+      gl::GLSurfaceEGL::GetGLDisplayEGL()->HasEGLExtension(
+          "EGL_KHR_fence_sync") &&
+      gl::GLSurfaceEGL::GetGLDisplayEGL()->HasEGLExtension(
+          "EGL_KHR_image_base") &&
+      gl::GLSurfaceEGL::GetGLDisplayEGL()->HasEGLExtension(
+          "EGL_KHR_gl_texture_2D_image") &&
       gfx::HasExtension(extension_set, "GL_OES_EGL_image");
 #else
   gl::GLWindowSystemBindingInfo window_system_binding_info;
@@ -564,7 +566,7 @@
                          const GpuPreferences& prefs) {
   // Populate the list of ANGLE features by querying the functions exposed by
   // EGL_ANGLE_feature_control if it's available.
-  if (gl::g_driver_egl.client_ext.b_EGL_ANGLE_feature_control) {
+  if (gl::GLSurfaceEGL::GetGLDisplayEGL()->IsANGLEFeatureControlSupported()) {
     EGLDisplay display =
         gl::GLSurfaceEGL::GetGLDisplayEGL()->GetHardwareDisplay();
     EGLAttrib feature_count = 0;
diff --git a/headless/lib/browser/headless_web_contents_impl.cc b/headless/lib/browser/headless_web_contents_impl.cc
index 9108af1..807879aa 100644
--- a/headless/lib/browser/headless_web_contents_impl.cc
+++ b/headless/lib/browser/headless_web_contents_impl.cc
@@ -10,6 +10,7 @@
 #include <vector>
 
 #include "base/bind.h"
+#include "base/command_line.h"
 #include "base/memory/ptr_util.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
@@ -34,6 +35,7 @@
 #include "content/public/browser/web_contents.h"
 #include "content/public/browser/web_contents_delegate.h"
 #include "content/public/common/bindings_policy.h"
+#include "content/public/common/content_switches.h"
 #include "content/public/common/origin_util.h"
 #include "headless/lib/browser/headless_browser_context_impl.h"
 #include "headless/lib/browser/headless_browser_impl.h"
@@ -320,6 +322,14 @@
       browser_context->options()->accept_language();
   web_contents_->GetMutableRendererPrefs()->hinting =
       browser_context->options()->font_render_hinting();
+
+  base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
+  if (command_line->HasSwitch(switches::kForceWebRtcIPHandlingPolicy)) {
+    web_contents_->GetMutableRendererPrefs()->webrtc_ip_handling_policy =
+        command_line->GetSwitchValueASCII(
+            switches::kForceWebRtcIPHandlingPolicy);
+  }
+
   web_contents_->SetDelegate(web_contents_delegate_.get());
   render_process_host_->AddObserver(this);
   agent_host_->AddObserver(this);
diff --git a/ios/build/tools/convert_gn_xcodeproj.py b/ios/build/tools/convert_gn_xcodeproj.py
index 1f30bd9d..603d343 100755
--- a/ios/build/tools/convert_gn_xcodeproj.py
+++ b/ios/build/tools/convert_gn_xcodeproj.py
@@ -351,39 +351,42 @@
         root_dir, project_dir, old_project_dir,
         obj['name'], product_path, tests)
 
+  root_object = project.objects[json_data['rootObject']]
+  main_group = project.objects[root_object['mainGroup']]
 
-  source = GetOrCreateRootGroup(project, json_data['rootObject'], 'Source')
-  AddMarkdownToProject(project, root_dir, source)
-  SortFileReferencesByName(project, source)
+  sources = None
+  for child_key in main_group['children']:
+    child = project.objects[child_key]
+    if child.get('name') == 'Source' or child.get('name') == 'Sources':
+      sources = child
+      break
+
+  if sources is None:
+    sources = main_group
+
+  AddMarkdownToProject(project, root_dir, sources, sources is main_group)
+  SortFileReferencesByName(project, sources, root_object.get('productRefGroup'))
 
   objects = collections.OrderedDict(sorted(project.objects.items()))
   WriteXcodeProject(project_dir, json.dumps(json_data))
 
 
-def CreateGroup(project, parent_group, group_name, path=None):
+def CreateGroup(project, parent_group, group_name, use_relative_paths):
   group_object = {
     'children': [],
     'isa': 'PBXGroup',
-    'name': group_name,
     'sourceTree': '<group>',
   }
-  if path is not None:
-    group_object['path'] = path
+  if use_relative_paths:
+    group_object['path'] = group_name
+  else:
+    group_object['name'] = group_name
   parent_group_name = parent_group.get('name', '')
   group_object_key = project.AddObject(parent_group_name, group_object)
   parent_group['children'].append(group_object_key)
   return group_object
 
 
-def GetOrCreateRootGroup(project, root_object, group_name):
-  main_group = project.objects[project.objects[root_object]['mainGroup']]
-  for child_key in main_group['children']:
-    child = project.objects[child_key]
-    if child['name'] == group_name:
-      return child
-  return CreateGroup(project, main_group, group_name, path='../..')
-
-
 class ObjectKey(object):
 
   """Wrapper around PBXFileReference and PBXGroup for sorting.
@@ -401,19 +404,24 @@
   is checked and compared in alphabetic order.
   """
 
-  def __init__(self, obj):
+  def __init__(self, obj, last):
     self.isa = obj['isa']
     if 'name' in obj:
       self.name = obj['name']
     else:
       self.name = obj['path']
+    self.last = last
 
   def __lt__(self, other):
+    if self.last != other.last:
+      return other.last
     if self.isa != other.isa:
       return self.isa > other.isa
     return self.name < other.name
 
   def __gt__(self, other):
+    if self.last != other.last:
+      return self.last
     if self.isa != other.isa:
       return self.isa < other.isa
     return self.name > other.name
@@ -422,9 +430,10 @@
     return self.isa == other.isa and self.name == other.name
 
 
-def SortFileReferencesByName(project, group_object):
+def SortFileReferencesByName(project, group_object, products_group_ref):
   SortFileReferencesByNameWithSortKey(
-      project, group_object, lambda ref: ObjectKey(project.objects[ref]))
+      project, group_object,
+      lambda ref: ObjectKey(project.objects[ref], ref == products_group_ref))
 
 
 def SortFileReferencesByNameWithSortKey(project, group_object, sort_key):
@@ -435,7 +444,7 @@
       SortFileReferencesByNameWithSortKey(project, child, sort_key)
 
 
-def AddMarkdownToProject(project, root_dir, group_object):
+def AddMarkdownToProject(project, root_dir, group_object, use_relative_paths):
   list_files_cmd = ['git', '-C', root_dir, 'ls-files', '*.md']
   paths = check_output(list_files_cmd).splitlines()
   ios_internal_dir = os.path.join(root_dir, 'ios_internal')
@@ -448,31 +457,43 @@
       "fileEncoding": "4",
       "isa": "PBXFileReference",
       "lastKnownFileType": "net.daringfireball.markdown",
-      "name": os.path.basename(path),
-      "path": path,
       "sourceTree": "<group>"
     }
-    new_markdown_entry_id = project.AddObject('sources', new_markdown_entry)
-    folder = GetFolderForPath(project, group_object, os.path.dirname(path))
+    if use_relative_paths:
+      new_markdown_entry['path'] = os.path.basename(path)
+    else:
+      new_markdown_entry['name'] = os.path.basename(path)
+      new_markdown_entry['path'] = path
+    folder = GetFolderForPath(
+        project, group_object, os.path.dirname(path),
+        use_relative_paths)
+    folder_name = folder.get('name', None)
+    if folder_name is None:
+      folder_name = folder.get('path', 'sources')
+    new_markdown_entry_id = project.AddObject(folder_name, new_markdown_entry)
     folder['children'].append(new_markdown_entry_id)
 
 
-def GetFolderForPath(project, group_object, path):
+def GetFolderForPath(project, group_object, path, use_relative_paths):
   objects = project.objects
   if not path:
     return group_object
   for folder in path.split('/'):
     children = group_object['children']
     new_root = None
-    for child in children:
-      if objects[child]['isa'] == 'PBXGroup' and \
-         objects[child]['name'] == folder:
-        new_root = objects[child]
-        break
+    for child_key in children:
+      child = objects[child_key]
+      if child['isa'] == 'PBXGroup':
+        child_name = child.get('name', None)
+        if child_name is None:
+          child_name = child.get('path')
+        if child_name == folder:
+          new_root = child
+          break
     if not new_root:
       # If the folder isn't found we could just cram it into the leaf existing
       # folder, but that leads to folders with tons of README.md inside.
-      new_root = CreateGroup(project, group_object, folder)
+      new_root = CreateGroup(project, group_object, folder, use_relative_paths)
     group_object = new_root
   return group_object
 
diff --git a/ios/chrome/browser/ui/first_run/OWNERS b/ios/chrome/browser/ui/first_run/OWNERS
new file mode 100644
index 0000000..ce41692
--- /dev/null
+++ b/ios/chrome/browser/ui/first_run/OWNERS
@@ -0,0 +1 @@
+jlebel@chromium.org
diff --git a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1
index 00029465..1ce55b2 100644
--- a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-6a201768b2d0f44fec5aae063409d5a13e75425d
\ No newline at end of file
+7776f31abf78d11b7350d5b31744300cfba5a2ab
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1
index a365619a..7bdb288d 100644
--- a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-71a4795d3484ebfe20945bd7ecb90914184c43b9
\ No newline at end of file
+98188e2515d1e375167883bd88154e4e6437703a
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1
index fa680399..9570e9b4 100644
--- a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-a4d2036d8be0d9f73eddc1d69a08ec2be052b002
\ No newline at end of file
+443a1b06914c5b658a657fca986dc598aea86e87
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1
index 5f9aeeee..9816eed 100644
--- a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-2b38eb6cd6862433dc763c33d82e99656d2394a4
\ No newline at end of file
+3f86dac1459635954bd1e4d2f20af844e66e6dcc
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.ios.zip.sha1
index e9fc6685..e1c924a 100644
--- a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-985d24c939126ea502c1b0455a5f8f1eee2462d2
\ No newline at end of file
+6ecf51009361a4779fe8bc09f0ff11e6acfa8ecc
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.iossimulator.zip.sha1
index 1b80ee9..eaf9926 100644
--- a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-a6c33beeab199b66dce268531869022274356913
\ No newline at end of file
+df9d5e658b048ae09fb1e9794b8c9982763d1399
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1
index 0ab9beed..e7424e3 100644
--- a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-1463b9b38c667cb45723ad93fed31b93902ea6a7
\ No newline at end of file
+e6eb9295cf0298158b80aaa2b35419cdf9f2805e
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1
index 47655229..18b241d 100644
--- a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-ecaae56795b8d4b3f2b8a0facc093e3dde87b131
\ No newline at end of file
+2ae51309119eb83d7eec7a086dc4fc5eda8588f4
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1
index ddf4bdf..d13d47f 100644
--- a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-3cb20e6d61c8152a1e0c08517c90a42792ecf5a2
\ No newline at end of file
+05c64536bde680a3cec23c08416479c38e99216b
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1
index e8524c0..3fe28fb 100644
--- a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-2296badc07a9bc678307abbab68b8c2234a7b147
\ No newline at end of file
+92ebcb591c43b55551d76bc7d88631c8798d7f25
\ No newline at end of file
diff --git a/media/base/video_frame_metadata.cc b/media/base/video_frame_metadata.cc
index 6e82873..26214645 100644
--- a/media/base/video_frame_metadata.cc
+++ b/media/base/video_frame_metadata.cc
@@ -35,6 +35,7 @@
   MERGE_OPTIONAL_FIELD(capture_end_time, metadata_source);
   MERGE_OPTIONAL_FIELD(capture_counter, metadata_source);
   MERGE_OPTIONAL_FIELD(capture_update_rect, metadata_source);
+  MERGE_OPTIONAL_FIELD(source_size, metadata_source);
   MERGE_OPTIONAL_FIELD(region_capture_rect, metadata_source);
   MERGE_VALUE_FIELD(crop_version, metadata_source);
   MERGE_OPTIONAL_FIELD(copy_required, metadata_source);
diff --git a/media/base/video_frame_metadata.h b/media/base/video_frame_metadata.h
index 26dbe8a..26ea562 100644
--- a/media/base/video_frame_metadata.h
+++ b/media/base/video_frame_metadata.h
@@ -54,6 +54,12 @@
   // fully contained within visible_rect().
   absl::optional<gfx::Rect> capture_update_rect;
 
+  // For encoded frames, this is the original source size which may be different
+  // from the encoded size. It's used for the HiDPI tab capture heuristic.
+  // The size corresponds to the active region if region capture is active,
+  // or otherwise the full size of the captured source.
+  absl::optional<gfx::Size> source_size;
+
   // If cropping was applied due to Region Capture to produce this frame,
   // then this reflects where the frame's contents originate from in the
   // original uncropped frame.
diff --git a/media/gpu/gpu_video_encode_accelerator_helpers.cc b/media/gpu/gpu_video_encode_accelerator_helpers.cc
index 2f057cf..f9221bf 100644
--- a/media/gpu/gpu_video_encode_accelerator_helpers.cc
+++ b/media/gpu/gpu_video_encode_accelerator_helpers.cc
@@ -9,6 +9,7 @@
 #include "base/check_op.h"
 #include "base/notreached.h"
 #include "base/numerics/safe_conversions.h"
+#include "media/base/bitrate.h"
 
 namespace media {
 namespace {
@@ -193,8 +194,7 @@
 VideoBitrateAllocation AllocateDefaultBitrateForTesting(
     const size_t num_spatial_layers,
     const size_t num_temporal_layers,
-    const uint32_t bitrate,
-    const bool uses_vbr) {
+    const Bitrate& bitrate) {
   // Higher spatial layers (those to the right) get more bitrate.
   constexpr double kSpatialLayersBitrateScaleFactors[][kMaxSpatialLayers] = {
       {1.00, 0.00, 0.00},  // For one spatial layer.
@@ -210,11 +210,15 @@
   for (size_t sid = 0; sid < num_spatial_layers; ++sid) {
     const double bitrate_factor =
         kSpatialLayersBitrateScaleFactors[num_spatial_layers - 1][sid];
-    bitrates[sid] = bitrate * bitrate_factor;
+    bitrates[sid] = bitrate.target_bps() * bitrate_factor;
   }
 
-  return AllocateBitrateForDefaultEncodingWithBitrates(
-      bitrates, num_temporal_layers, uses_vbr);
+  const bool use_vbr = bitrate.mode() == Bitrate::Mode::kVariable;
+  auto allocation = AllocateBitrateForDefaultEncodingWithBitrates(
+      bitrates, num_temporal_layers, use_vbr);
+  if (use_vbr)
+    allocation.SetPeakBps(bitrate.peak_bps());
+  return allocation;
 }
 
 }  // namespace media
diff --git a/media/gpu/gpu_video_encode_accelerator_helpers.h b/media/gpu/gpu_video_encode_accelerator_helpers.h
index cc454d7..32d88109 100644
--- a/media/gpu/gpu_video_encode_accelerator_helpers.h
+++ b/media/gpu/gpu_video_encode_accelerator_helpers.h
@@ -7,6 +7,7 @@
 
 #include <vector>
 
+#include "media/base/bitrate.h"
 #include "media/base/video_bitrate_allocation.h"
 #include "media/gpu/media_gpu_export.h"
 #include "media/video/video_encode_accelerator.h"
@@ -14,6 +15,8 @@
 
 namespace media {
 
+class Bitrate;
+
 // Helper functions for VideoEncodeAccelerator implementations in GPU process.
 
 // Calculate the bitstream buffer size for VideoEncodeAccelerator.
@@ -43,23 +46,19 @@
 AllocateBitrateForDefaultEncoding(const VideoEncodeAccelerator::Config& config);
 
 // Create VideoBitrateAllocation with |num_spatial_layers|,
-// |num_temporal_layers| and |bitrate|, additionally indicating if the
-// constructed bitrate should |use_vbr|. |bitrate| is the bitrate of the entire
-// stream. |num_temporal_layers| is the number of temporal layers in each
-// spatial layer. |use_vbr| indicates whether the bitrate should have
-// |Bitrate::Mode::kVariable.|
-// First, |bitrate| is distributed to spatial layers based on libwebrtc bitrate
-// division. Then the bitrate of each spatial layer is distributed to temporal
-// layers in the spatial layer based on the same bitrate division ratio as a
-// software encoder. If a variable bitrate is requested, the peak will be set
-// equal to the target.
-// TODO(crbug.com/1335250): merge |bitrate| and |uses_vbr| into a single Bitrate
-// field.
+// |num_temporal_layers| and |bitrate|. |bitrate.target_bps()| is the bitrate of
+// the entire stream. |num_temporal_layers| is the number of temporal layers in
+// each spatial layer.
+// First, |bitrate.target_bps()| is distributed to spatial layers based on
+// libwebrtc bitrate division. Then the bitrate of each spatial layer is
+// distributed to temporal layers in the spatial layer based on the same bitrate
+// division ratio as a software encoder. If a variable bitrate is requested,
+// that is, |bitrate.mode()| is Bitrate::Mode::Variable, the peak will be set
+// equal to the |bitrate.peak_bps()|.
 MEDIA_GPU_EXPORT VideoBitrateAllocation
 AllocateDefaultBitrateForTesting(const size_t num_spatial_layers,
                                  const size_t num_temporal_layers,
-                                 const uint32_t bitrate,
-                                 const bool uses_vbr);
+                                 const Bitrate& bitrate);
 }  // namespace media
 
 #endif  // MEDIA_GPU_GPU_VIDEO_ENCODE_ACCELERATOR_HELPERS_H_
diff --git a/media/gpu/test/video_encoder/video_encoder_test_environment.cc b/media/gpu/test/video_encoder/video_encoder_test_environment.cc
index a3c6b906..3c8cf5e 100644
--- a/media/gpu/test/video_encoder/video_encoder_test_environment.cc
+++ b/media/gpu/test/video_encoder/video_encoder_test_environment.cc
@@ -15,8 +15,8 @@
 #include "build/buildflag.h"
 #include "build/chromeos_buildflags.h"
 #include "gpu/ipc/service/gpu_memory_buffer_factory.h"
+#include "media/base/bitrate.h"
 #include "media/base/media_switches.h"
-#include "media/base/video_bitrate_allocation.h"
 #include "media/gpu/buildflags.h"
 #include "media/gpu/gpu_video_encode_accelerator_helpers.h"
 #include "media/gpu/macros.h"
@@ -174,18 +174,24 @@
   combined_enabled_features.push_back(media::kVaapiVideoEncodeLinux);
 #endif
 
-  const bool use_vbr = bitrate_mode == Bitrate::Mode::kVariable;
-  const uint32_t bitrate = encode_bitrate.value_or(
+  const uint32_t target_bitrate = encode_bitrate.value_or(
       GetDefaultTargetBitrate(video->Resolution(), video->FrameRate()));
-  if (use_vbr && VideoCodecProfileToVideoCodec(profile) != VideoCodec::kH264) {
+  // TODO(b/181797390): Reconsider if this peak bitrate is reasonable.
+  const media::Bitrate bitrate =
+      bitrate_mode == media::Bitrate::Mode::kVariable
+          ? media::Bitrate::VariableBitrate(target_bitrate,
+                                            /*peak_bps=*/target_bitrate * 2)
+          : media::Bitrate::ConstantBitrate(target_bitrate);
+  if (bitrate.mode() == media::Bitrate::Mode::kVariable &&
+      VideoCodecProfileToVideoCodec(profile) != VideoCodec::kH264) {
     LOG(ERROR) << "VBR is only supported for H264 encoding";
     return nullptr;
   }
   return new VideoEncoderTestEnvironment(
       std::move(video), enable_bitstream_validator, output_folder, profile,
-      num_temporal_layers, num_spatial_layers, bitrate, use_vbr,
-      save_output_bitstream, reverse, frame_output_config,
-      combined_enabled_features, combined_disabled_features);
+      num_temporal_layers, num_spatial_layers, bitrate, save_output_bitstream,
+      reverse, frame_output_config, combined_enabled_features,
+      combined_disabled_features);
 }
 
 VideoEncoderTestEnvironment::VideoEncoderTestEnvironment(
@@ -195,8 +201,7 @@
     VideoCodecProfile profile,
     size_t num_temporal_layers,
     size_t num_spatial_layers,
-    uint32_t bitrate,
-    bool use_vbr,
+    const Bitrate& bitrate,
     bool save_output_bitstream,
     bool reverse,
     const FrameOutputConfig& frame_output_config,
@@ -211,8 +216,7 @@
       num_spatial_layers_(num_spatial_layers),
       bitrate_(AllocateDefaultBitrateForTesting(num_spatial_layers_,
                                                 num_temporal_layers_,
-                                                bitrate,
-                                                use_vbr)),
+                                                bitrate)),
       spatial_layers_(GetDefaultSpatialLayers(bitrate_,
                                               video_.get(),
                                               num_spatial_layers_,
@@ -254,7 +258,8 @@
   return spatial_layers_;
 }
 
-VideoBitrateAllocation VideoEncoderTestEnvironment::Bitrate() const {
+const VideoBitrateAllocation& VideoEncoderTestEnvironment::BitrateAllocation()
+    const {
   return bitrate_;
 }
 
diff --git a/media/gpu/test/video_encoder/video_encoder_test_environment.h b/media/gpu/test/video_encoder/video_encoder_test_environment.h
index 77ed7c63..6099d22 100644
--- a/media/gpu/test/video_encoder/video_encoder_test_environment.h
+++ b/media/gpu/test/video_encoder/video_encoder_test_environment.h
@@ -21,6 +21,9 @@
 }
 
 namespace media {
+
+class Bitrate;
+
 namespace test {
 class Video;
 
@@ -67,7 +70,7 @@
   const std::vector<VideoEncodeAccelerator::Config::SpatialLayer>&
   SpatialLayers() const;
   // Get the target bitrate (bits/second).
-  VideoBitrateAllocation Bitrate() const;
+  const VideoBitrateAllocation& BitrateAllocation() const;
   // Whether the encoded bitstream is saved to disk.
   bool SaveOutputBitstream() const;
   // True if the video should play backwards at reaching the end of video.
@@ -96,8 +99,7 @@
       VideoCodecProfile profile,
       size_t num_temporal_layers,
       size_t num_spatial_layers,
-      uint32_t bitrate,
-      bool use_vbr,
+      const media::Bitrate& bitrate,
       bool save_output_bitstream,
       bool reverse,
       const FrameOutputConfig& frame_output_config,
diff --git a/media/gpu/vaapi/vp9_vaapi_video_encoder_delegate_unittest.cc b/media/gpu/vaapi/vp9_vaapi_video_encoder_delegate_unittest.cc
index 8f07a9f1..478259a9 100644
--- a/media/gpu/vaapi/vp9_vaapi_video_encoder_delegate_unittest.cc
+++ b/media/gpu/vaapi/vp9_vaapi_video_encoder_delegate_unittest.cc
@@ -344,7 +344,7 @@
 
   auto initial_bitrate_allocation = AllocateDefaultBitrateForTesting(
       num_spatial_layers, num_temporal_layers,
-      kDefaultVideoEncodeAcceleratorConfig.bitrate.target_bps(), false);
+      kDefaultVideoEncodeAcceleratorConfig.bitrate);
   std::vector<gfx::Size> svc_layer_size =
       GetDefaultSpatialLayerResolutions(num_spatial_layers);
   if (num_spatial_layers > 1u || num_temporal_layers > 1u) {
@@ -365,12 +365,12 @@
     }
   }
 
-  EXPECT_CALL(*mock_rate_ctrl_, UpdateRateControl(MatchRtcConfigWithRates(
-                                    AllocateDefaultBitrateForTesting(
-                                        num_spatial_layers, num_temporal_layers,
-                                        config.bitrate.target_bps(), false),
-                                    VideoEncodeAccelerator::kDefaultFramerate,
-                                    num_temporal_layers, svc_layer_size)))
+  EXPECT_CALL(*mock_rate_ctrl_,
+              UpdateRateControl(MatchRtcConfigWithRates(
+                  AllocateDefaultBitrateForTesting(
+                      num_spatial_layers, num_temporal_layers, config.bitrate),
+                  VideoEncodeAccelerator::kDefaultFramerate,
+                  num_temporal_layers, svc_layer_size)))
       .Times(1)
       .WillOnce(Return());
 
@@ -496,7 +496,8 @@
                                      uint8_t expected_temporal_layer_id,
                                      uint32_t bitrate, uint32_t framerate) {
     auto bitrate_allocation = AllocateDefaultBitrateForTesting(
-        num_spatial_layers, num_temporal_layers, bitrate, false);
+        num_spatial_layers, num_temporal_layers,
+        media::Bitrate::ConstantBitrate(bitrate));
     UpdateRatesAndEncode(bitrate_allocation, framerate,
                          /*valid_rates_request=*/true, is_key_pic,
                          spatial_layer_resolutions, num_temporal_layers,
@@ -627,7 +628,7 @@
   const VideoBitrateAllocation kDefaultBitrateAllocation =
       AllocateDefaultBitrateForTesting(
           num_spatial_layers, num_temporal_layers,
-          kDefaultVideoEncodeAcceleratorConfig.bitrate.target_bps(), false);
+          kDefaultVideoEncodeAcceleratorConfig.bitrate);
   const std::vector<gfx::Size> kDefaultSpatialLayers =
       GetDefaultSpatialLayerResolutions(num_spatial_layers);
   const uint32_t kFramerate =
@@ -657,7 +658,7 @@
   const VideoBitrateAllocation kDefaultBitrateAllocation =
       AllocateDefaultBitrateForTesting(
           num_spatial_layers, num_temporal_layers,
-          kDefaultVideoEncodeAcceleratorConfig.bitrate.target_bps(), false);
+          kDefaultVideoEncodeAcceleratorConfig.bitrate);
   std::vector<VideoBitrateAllocation> invalid_bitrate_allocations;
   constexpr uint32_t kBitrate = 1234u;
   auto bitrate_allocation = kDefaultBitrateAllocation;
diff --git a/media/gpu/video_encode_accelerator_perf_tests.cc b/media/gpu/video_encode_accelerator_perf_tests.cc
index 8095496..7323234 100644
--- a/media/gpu/video_encode_accelerator_perf_tests.cc
+++ b/media/gpu/video_encode_accelerator_perf_tests.cc
@@ -512,7 +512,7 @@
       bool measure_quality) {
     Video* video = g_env->GenerateNV12Video();
     VideoCodecProfile profile = g_env->Profile();
-    const media::VideoBitrateAllocation& bitrate = g_env->Bitrate();
+    const media::VideoBitrateAllocation& bitrate = g_env->BitrateAllocation();
     const std::vector<VideoEncodeAccelerator::Config::SpatialLayer>&
         spatial_layers = g_env->SpatialLayers();
     std::vector<std::unique_ptr<BitstreamProcessor>> bitstream_processors;
@@ -707,14 +707,15 @@
     uint32_t target_bitrate = 0;
     uint32_t actual_bitrate = 0;
     if (!spatial_idx && !temporal_idx) {
-      target_bitrate = g_env->Bitrate().GetSumBps();
+      target_bitrate = g_env->BitrateAllocation().GetSumBps();
       actual_bitrate = stats.Bitrate();
     } else {
       CHECK(spatial_idx && temporal_idx);
       // Target and actual bitrates in temporal layer encoding are the sum of
       // bitrates of the temporal layers in the spatial layer.
       for (size_t tid = 0; tid <= *temporal_idx; ++tid) {
-        target_bitrate += g_env->Bitrate().GetBitrateBps(*spatial_idx, tid);
+        target_bitrate +=
+            g_env->BitrateAllocation().GetBitrateBps(*spatial_idx, tid);
         actual_bitrate += stats.LayerBitrate(*spatial_idx, tid);
       }
     }
diff --git a/media/gpu/video_encode_accelerator_tests.cc b/media/gpu/video_encode_accelerator_tests.cc
index c8c21cb9..6208f28 100644
--- a/media/gpu/video_encode_accelerator_tests.cc
+++ b/media/gpu/video_encode_accelerator_tests.cc
@@ -132,7 +132,7 @@
     CHECK_LE(spatial_layers.size(), 1u);
 
     return VideoEncoderClientConfig(g_env->Video(), g_env->Profile(),
-                                    spatial_layers, g_env->Bitrate(),
+                                    spatial_layers, g_env->BitrateAllocation(),
                                     g_env->Reverse());
   }
 
@@ -498,11 +498,6 @@
     GTEST_SKIP() << "Skip SHMEM input test cases in spatial SVC encoding";
 
   auto config = GetDefaultConfig();
-  const Bitrate::Mode bitrate_mode = config.bitrate_allocation.GetMode();
-  if (bitrate_mode == Bitrate::Mode::kVariable) {
-    config.bitrate_allocation.SetPeakBps(config.bitrate_allocation.GetSumBps() *
-                                         2);
-  }
   config.num_frames_to_encode = kNumFramesToEncodeForBitrateCheck;
   auto encoder = CreateVideoEncoder(g_env->Video(), config);
   // Set longer event timeout than the default (30 sec) because encoding 2160p
@@ -517,9 +512,10 @@
   EXPECT_TRUE(encoder->WaitForBitstreamProcessors());
   // TODO(b/181797390): Reconsider bitrate check for VBR encoding if this fails
   // on some boards.
-  const double tolerance = bitrate_mode == Bitrate::Mode::kConstant
-                               ? kBitrateTolerance
-                               : kVariableBitrateTolerance;
+  const double tolerance =
+      config.bitrate_allocation.GetMode() == Bitrate::Mode::kConstant
+          ? kBitrateTolerance
+          : kVariableBitrateTolerance;
   EXPECT_NEAR(encoder->GetStats().Bitrate(),
               config.bitrate_allocation.GetSumBps(),
               tolerance * config.bitrate_allocation.GetSumBps());
@@ -528,7 +524,7 @@
 TEST_F(VideoEncoderTest, BitrateCheck_DynamicBitrate) {
   if (g_env->SpatialLayers().size() > 1)
     GTEST_SKIP() << "Skip SHMEM input test cases in spatial SVC encoding";
-  if (g_env->Bitrate().GetMode() != Bitrate::Mode::kConstant) {
+  if (g_env->BitrateAllocation().GetMode() != Bitrate::Mode::kConstant) {
     GTEST_SKIP()
         << "Skip Dynamic bitrate change checks for non-CBR bitrate mode";
   }
@@ -552,9 +548,9 @@
   const uint32_t second_bitrate = first_bitrate * 3 / 2;
   encoder->ResetStats();
   encoder->UpdateBitrate(
-      AllocateDefaultBitrateForTesting(config.num_spatial_layers,
-                                       config.num_temporal_layers,
-                                       second_bitrate, /*uses_vbr=*/false),
+      AllocateDefaultBitrateForTesting(
+          config.num_spatial_layers, config.num_temporal_layers,
+          Bitrate::ConstantBitrate(second_bitrate)),
       config.framerate);
   encoder->Encode();
   EXPECT_TRUE(encoder->WaitForFlushDone());
@@ -569,7 +565,7 @@
 TEST_F(VideoEncoderTest, BitrateCheck_DynamicFramerate) {
   if (g_env->SpatialLayers().size() > 1)
     GTEST_SKIP() << "Skip SHMEM input test cases in spatial SVC encoding";
-  if (g_env->Bitrate().GetMode() != Bitrate::Mode::kConstant) {
+  if (g_env->BitrateAllocation().GetMode() != Bitrate::Mode::kConstant) {
     GTEST_SKIP()
         << "Skip dynamic framerate change checks for non-CBR bitrate mode";
   }
@@ -614,8 +610,8 @@
 
   Video* nv12_video = g_env->GenerateNV12Video();
   VideoEncoderClientConfig config(nv12_video, g_env->Profile(),
-                                  g_env->SpatialLayers(), g_env->Bitrate(),
-                                  g_env->Reverse());
+                                  g_env->SpatialLayers(),
+                                  g_env->BitrateAllocation(), g_env->Reverse());
   config.input_storage_type =
       VideoEncodeAccelerator::Config::StorageType::kGpuMemoryBuffer;
 
@@ -652,7 +648,14 @@
   auto* nv12_video = g_env->GenerateNV12Video();
   // Set 1/4 of the original bitrate because the area of |output_resolution| is
   // 1/4 of the original resolution.
-  uint32_t new_bitrate = g_env->Bitrate().GetSumBps() / 4;
+  uint32_t new_target_bitrate = g_env->BitrateAllocation().GetSumBps() / 4;
+  // TODO(b/181797390): Reconsider if this peak bitrate is reasonable.
+  const Bitrate new_bitrate =
+      g_env->BitrateAllocation().GetMode() == Bitrate::Mode::kConstant
+          ? Bitrate::ConstantBitrate(new_target_bitrate)
+          : Bitrate::VariableBitrate(new_target_bitrate,
+                                     new_target_bitrate * 2);
+
   auto spatial_layers = g_env->SpatialLayers();
   size_t num_temporal_layers = 1u;
   if (!spatial_layers.empty()) {
@@ -662,12 +665,10 @@
     spatial_layers[0].bitrate_bps /= 4;
     num_temporal_layers = spatial_layers[0].num_of_temporal_layers;
   }
-  bool uses_vbr = g_env->Bitrate().GetMode() == Bitrate::Mode::kVariable;
   VideoEncoderClientConfig config(
       nv12_video, g_env->Profile(), spatial_layers,
       AllocateDefaultBitrateForTesting(/*num_spatial_layers=*/1u,
-                                       num_temporal_layers, new_bitrate,
-                                       uses_vbr),
+                                       num_temporal_layers, new_bitrate),
       g_env->Reverse());
   config.output_resolution = output_resolution;
   config.input_storage_type =
@@ -709,8 +710,8 @@
       expanded_resolution, expanded_visible_rect);
   ASSERT_TRUE(nv12_expanded_video);
   VideoEncoderClientConfig config(nv12_expanded_video.get(), g_env->Profile(),
-                                  g_env->SpatialLayers(), g_env->Bitrate(),
-                                  g_env->Reverse());
+                                  g_env->SpatialLayers(),
+                                  g_env->BitrateAllocation(), g_env->Reverse());
   config.output_resolution = original_resolution;
   config.input_storage_type =
       VideoEncodeAccelerator::Config::StorageType::kGpuMemoryBuffer;
@@ -751,8 +752,8 @@
       expanded_resolution, expanded_visible_rect);
   ASSERT_TRUE(nv12_expanded_video);
   VideoEncoderClientConfig config(nv12_expanded_video.get(), g_env->Profile(),
-                                  g_env->SpatialLayers(), g_env->Bitrate(),
-                                  g_env->Reverse());
+                                  g_env->SpatialLayers(),
+                                  g_env->BitrateAllocation(), g_env->Reverse());
   config.output_resolution = original_resolution;
   config.input_storage_type =
       VideoEncodeAccelerator::Config::StorageType::kGpuMemoryBuffer;
@@ -785,7 +786,7 @@
     return bitrate_allocation;
   };
 
-  const auto& default_allocation = g_env->Bitrate();
+  const auto& default_allocation = g_env->BitrateAllocation();
   std::vector<VideoBitrateAllocation> bitrate_allocations;
 
   // Deactivate the top layer.
@@ -812,8 +813,8 @@
   bitrate_allocations.emplace_back(bitrate_allocation);
 
   VideoEncoderClientConfig config(nv12_video, g_env->Profile(),
-                                  g_env->SpatialLayers(), g_env->Bitrate(),
-                                  g_env->Reverse());
+                                  g_env->SpatialLayers(),
+                                  g_env->BitrateAllocation(), g_env->Reverse());
   config.input_storage_type =
       VideoEncodeAccelerator::Config::StorageType::kGpuMemoryBuffer;
   std::vector<size_t> num_frames_to_encode(bitrate_allocations.size());
diff --git a/media/gpu/windows/dxva_video_decode_accelerator_win.cc b/media/gpu/windows/dxva_video_decode_accelerator_win.cc
index 88c4996..622686b1 100644
--- a/media/gpu/windows/dxva_video_decode_accelerator_win.cc
+++ b/media/gpu/windows/dxva_video_decode_accelerator_win.cc
@@ -1554,11 +1554,11 @@
     RETURN_ON_HR_FAILURE(hr, "Failed to pass D3D manager to decoder", false);
   }
 
-  gl::GLDisplayEGL* display = gl::GLSurfaceEGL::GetGLDisplayEGL();
-  if (!display->ext->b_EGL_EXT_pixel_format_float)
+  if (!gl::GLSurfaceEGL::GetGLDisplayEGL()->IsPixelFormatFloatSupported())
     use_fp16_ = false;
 
-  EGLDisplay egl_display = display->GetHardwareDisplay();
+  EGLDisplay egl_display =
+      gl::GLSurfaceEGL::GetGLDisplayEGL()->GetHardwareDisplay();
 
   while (true) {
     std::vector<EGLint> config_attribs = {EGL_BUFFER_SIZE,  32,
diff --git a/media/mojo/mojom/media_types.mojom b/media/mojo/mojom/media_types.mojom
index ba8b55be..77dab2c7 100644
--- a/media/mojo/mojom/media_types.mojom
+++ b/media/mojo/mojom/media_types.mojom
@@ -287,6 +287,8 @@
 
   gfx.mojom.Rect? capture_update_rect;
 
+  gfx.mojom.Size? source_size;
+
   gfx.mojom.Rect? region_capture_rect;
   uint32 crop_version;
 
diff --git a/media/mojo/mojom/video_frame_metadata_mojom_traits.cc b/media/mojo/mojom/video_frame_metadata_mojom_traits.cc
index c8417a4b..c325ce7 100644
--- a/media/mojo/mojom/video_frame_metadata_mojom_traits.cc
+++ b/media/mojo/mojom/video_frame_metadata_mojom_traits.cc
@@ -63,6 +63,7 @@
 
   READ_AND_ASSIGN_OPT(base::UnguessableToken, overlay_plane_id, OverlayPlaneId);
 
+  READ_AND_ASSIGN_OPT(gfx::Size, source_size, SourceSize);
   READ_AND_ASSIGN_OPT(gfx::Rect, capture_update_rect, CaptureUpdateRect);
   READ_AND_ASSIGN_OPT(gfx::Rect, region_capture_rect, RegionCaptureRect);
 
@@ -81,4 +82,4 @@
   return true;
 }
 
-}  // namespace mojo
\ No newline at end of file
+}  // namespace mojo
diff --git a/media/mojo/mojom/video_frame_metadata_mojom_traits.h b/media/mojo/mojom/video_frame_metadata_mojom_traits.h
index 1ce7fbd2..3f8ac38 100644
--- a/media/mojo/mojom/video_frame_metadata_mojom_traits.h
+++ b/media/mojo/mojom/video_frame_metadata_mojom_traits.h
@@ -104,6 +104,11 @@
     return input.capture_update_rect;
   }
 
+  static const absl::optional<gfx::Size>& source_size(
+      const media::VideoFrameMetadata& input) {
+    return input.source_size;
+  }
+
   static const absl::optional<gfx::Rect>& region_capture_rect(
       const media::VideoFrameMetadata& input) {
     return input.region_capture_rect;
diff --git a/media/webrtc/audio_processor.cc b/media/webrtc/audio_processor.cc
index 586fea2..17e440b 100644
--- a/media/webrtc/audio_processor.cc
+++ b/media/webrtc/audio_processor.cc
@@ -30,7 +30,7 @@
 #include "media/webrtc/helpers.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/webrtc/modules/audio_processing/include/audio_processing.h"
-#include "third_party/webrtc_overrides/task_queue_factory.h"
+#include "third_party/webrtc_overrides/metronome_task_queue_factory.h"
 
 namespace media {
 
diff --git a/net/cert/internal/cert_issuer_source_static.cc b/net/cert/internal/cert_issuer_source_static.cc
index e6ddb27..592b7cca 100644
--- a/net/cert/internal/cert_issuer_source_static.cc
+++ b/net/cert/internal/cert_issuer_source_static.cc
@@ -14,6 +14,10 @@
       cert->normalized_subject().AsStringPiece(), std::move(cert)));
 }
 
+void CertIssuerSourceStatic::Clear() {
+  intermediates_.clear();
+}
+
 void CertIssuerSourceStatic::SyncGetIssuersOf(const ParsedCertificate* cert,
                                               ParsedCertificateList* issuers) {
   auto range =
diff --git a/net/cert/internal/cert_issuer_source_static.h b/net/cert/internal/cert_issuer_source_static.h
index 39d3187..1dfd3a0 100644
--- a/net/cert/internal/cert_issuer_source_static.h
+++ b/net/cert/internal/cert_issuer_source_static.h
@@ -27,6 +27,9 @@
   // provide.
   void AddCert(scoped_refptr<ParsedCertificate> cert);
 
+  // Clears the set of certificates.
+  void Clear();
+
   // CertIssuerSource implementation:
   void SyncGetIssuersOf(const ParsedCertificate* cert,
                         ParsedCertificateList* issuers) override;
diff --git a/net/cert/internal/system_trust_store.cc b/net/cert/internal/system_trust_store.cc
index a665c3b..5124a6e 100644
--- a/net/cert/internal/system_trust_store.cc
+++ b/net/cert/internal/system_trust_store.cc
@@ -217,6 +217,8 @@
 TrustStoreMac::TrustImplType ParamToTrustImplType(
     int param,
     TrustStoreMac::TrustImplType default_impl) {
+  // These values are used in experiment configs, do not change or reuse the
+  // numbers.
   switch (param) {
     case 1:
       return TrustStoreMac::TrustImplType::kDomainCache;
@@ -224,6 +226,8 @@
       return TrustStoreMac::TrustImplType::kSimple;
     case 3:
       return TrustStoreMac::TrustImplType::kLruCache;
+    case 4:
+      return TrustStoreMac::TrustImplType::kDomainCacheFullCerts;
     default:
       return default_impl;
   }
@@ -310,7 +314,7 @@
 
 TrustStoreMac* GetGlobalTrustStoreMacForCRS() {
   constexpr TrustStoreMac::TrustImplType kDefaultMacTrustImplForCRS =
-      TrustStoreMac::TrustImplType::kDomainCache;
+      TrustStoreMac::TrustImplType::kDomainCacheFullCerts;
   static base::NoDestructor<TrustStoreMac> static_trust_store_mac(
       kSecPolicyAppleSSL, GetTrustStoreImplParam(kDefaultMacTrustImplForCRS),
       GetTrustStoreCacheSize(), TrustStoreMac::TrustDomains::kUserAndAdmin);
diff --git a/net/cert/internal/trust_store_mac.cc b/net/cert/internal/trust_store_mac.cc
index 5a179e71..26950081 100644
--- a/net/cert/internal/trust_store_mac.cc
+++ b/net/cert/internal/trust_store_mac.cc
@@ -14,12 +14,15 @@
 #include "base/logging.h"
 #include "base/mac/foundation_util.h"
 #include "base/mac/mac_logging.h"
+#include "base/metrics/histogram_functions.h"
 #include "base/no_destructor.h"
+#include "base/strings/strcat.h"
 #include "base/synchronization/lock.h"
 #include "crypto/mac_security_services_lock.h"
 #include "net/base/hash_value.h"
 #include "net/base/network_notification_thread_mac.h"
 #include "net/cert/internal/cert_errors.h"
+#include "net/cert/internal/cert_issuer_source_static.h"
 #include "net/cert/internal/parse_name.h"
 #include "net/cert/internal/parsed_certificate.h"
 #include "net/cert/known_roots_mac.h"
@@ -447,6 +450,139 @@
   base::flat_map<SHA256HashValue, TrustStatusDetails> trust_status_cache_;
 };
 
+// Caches certificates and calculated trust status for certificates present in
+// a single trust domain.
+class TrustDomainCacheFullCerts {
+ public:
+  struct TrustStatusDetails {
+    TrustStatus trust_status = TrustStatus::UNKNOWN;
+    int debug_info = 0;
+  };
+
+  TrustDomainCacheFullCerts(SecTrustSettingsDomain domain,
+                            CFStringRef policy_oid)
+      : domain_(domain), policy_oid_(policy_oid) {
+    DCHECK(policy_oid_);
+  }
+
+  TrustDomainCacheFullCerts(const TrustDomainCacheFullCerts&) = delete;
+  TrustDomainCacheFullCerts& operator=(const TrustDomainCacheFullCerts&) =
+      delete;
+
+  // (Re-)Initializes the cache with the certs in |domain_| set to UNKNOWN trust
+  // status.
+  void Initialize() {
+    trust_status_cache_.clear();
+    cert_issuer_source_.Clear();
+
+    base::ScopedCFTypeRef<CFArrayRef> cert_array;
+    OSStatus rv;
+    {
+      base::AutoLock lock(crypto::GetMacSecurityServicesLock());
+      rv = SecTrustSettingsCopyCertificates(domain_,
+                                            cert_array.InitializeInto());
+    }
+    if (rv != noErr) {
+      // Note: SecTrustSettingsCopyCertificates can legitimately return
+      // errSecNoTrustSettings if there are no trust settings in |domain_|.
+      HistogramTrustDomainCertCount(0U);
+      return;
+    }
+    std::vector<std::pair<SHA256HashValue, TrustStatusDetails>>
+        trust_status_vector;
+    for (CFIndex i = 0, size = CFArrayGetCount(cert_array); i < size; ++i) {
+      SecCertificateRef cert = reinterpret_cast<SecCertificateRef>(
+          const_cast<void*>(CFArrayGetValueAtIndex(cert_array, i)));
+      base::ScopedCFTypeRef<CFDataRef> der_data(SecCertificateCopyData(cert));
+      if (!der_data) {
+        LOG(ERROR) << "SecCertificateCopyData error";
+        continue;
+      }
+      auto buffer = x509_util::CreateCryptoBuffer(base::make_span(
+          CFDataGetBytePtr(der_data.get()), CFDataGetLength(der_data.get())));
+      CertErrors errors;
+      ParseCertificateOptions options;
+      options.allow_invalid_serial_numbers = true;
+      scoped_refptr<ParsedCertificate> parsed_cert =
+          ParsedCertificate::Create(std::move(buffer), options, &errors);
+      if (!parsed_cert) {
+        LOG(ERROR) << "Error parsing certificate:\n" << errors.ToDebugString();
+        continue;
+      }
+      cert_issuer_source_.AddCert(parsed_cert);
+      trust_status_vector.emplace_back(x509_util::CalculateFingerprint256(cert),
+                                       TrustStatusDetails());
+    }
+    HistogramTrustDomainCertCount(trust_status_vector.size());
+    trust_status_cache_ = base::flat_map<SHA256HashValue, TrustStatusDetails>(
+        std::move(trust_status_vector));
+  }
+
+  // Returns the trust status for |cert| in |domain_|.
+  TrustStatus IsCertTrusted(const ParsedCertificate* cert,
+                            const SHA256HashValue& cert_hash,
+                            base::SupportsUserData* debug_data) {
+    auto cache_iter = trust_status_cache_.find(cert_hash);
+    if (cache_iter == trust_status_cache_.end()) {
+      // Cert does not have trust settings in this domain, return UNSPECIFIED.
+      UpdateUserData(0, debug_data,
+                     TrustStoreMac::TrustImplType::kDomainCacheFullCerts);
+      return TrustStatus::UNSPECIFIED;
+    }
+
+    if (cache_iter->second.trust_status != TrustStatus::UNKNOWN) {
+      // Cert has trust settings and trust has already been calculated, return
+      // the cached value.
+      UpdateUserData(cache_iter->second.debug_info, debug_data,
+                     TrustStoreMac::TrustImplType::kDomainCacheFullCerts);
+      return cache_iter->second.trust_status;
+    }
+
+    // Cert has trust settings but trust has not been calculated yet.
+    // Calculate it now, insert into cache, and return.
+    TrustStatus cert_trust = IsCertificateTrustedForPolicyInDomain(
+        cert, policy_oid_, domain_, &cache_iter->second.debug_info);
+    cache_iter->second.trust_status = cert_trust;
+    UpdateUserData(cache_iter->second.debug_info, debug_data,
+                   TrustStoreMac::TrustImplType::kDomainCacheFullCerts);
+    return cert_trust;
+  }
+
+  // Returns true if the certificate with |cert_hash| is present in |domain_|.
+  bool ContainsCert(const SHA256HashValue& cert_hash) const {
+    return trust_status_cache_.find(cert_hash) != trust_status_cache_.end();
+  }
+
+  // Returns a CertIssuerSource containing all the certificates that are
+  // present in |domain_|.
+  CertIssuerSource& cert_issuer_source() { return cert_issuer_source_; }
+
+ private:
+  void HistogramTrustDomainCertCount(size_t count) const {
+    base::StringPiece domain_name;
+    switch (domain_) {
+      case kSecTrustSettingsDomainUser:
+        domain_name = "User";
+        break;
+      case kSecTrustSettingsDomainAdmin:
+        domain_name = "Admin";
+        break;
+      case kSecTrustSettingsDomainSystem:
+        domain_name = "System";
+        break;
+    }
+    base::UmaHistogramCounts1000(
+        base::StrCat(
+            {"Net.CertVerifier.MacTrustDomainCertCount.", domain_name}),
+        count);
+  }
+
+  const SecTrustSettingsDomain domain_;
+  const CFStringRef policy_oid_;
+  base::flat_map<SHA256HashValue, TrustStatusDetails> trust_status_cache_;
+  CertIssuerSourceStatic cert_issuer_source_;
+};
+
 SHA256HashValue CalculateFingerprint256(const der::Input& buffer) {
   SHA256HashValue sha256;
   SHA256(buffer.UnsafeData(), buffer.Length(), sha256.data);
@@ -592,6 +728,9 @@
   virtual bool IsKnownRoot(const ParsedCertificate* cert) = 0;
   virtual TrustStatus IsCertTrusted(const ParsedCertificate* cert,
                                     base::SupportsUserData* debug_data) = 0;
+  virtual bool ImplementsSyncGetIssuersOf() const { return false; }
+  virtual void SyncGetIssuersOf(const ParsedCertificate* cert,
+                                ParsedCertificateList* issuers) {}
   virtual void InitializeTrustCache() = 0;
 };
 
@@ -699,6 +838,128 @@
   TrustDomainCache user_domain_cache_ GUARDED_BY(cache_lock_);
 };
 
+// TrustImplDomainCacheFullCerts uses SecTrustSettingsCopyCertificates to get
+// the list of certs in each trust domain and caches the full certificates so
+// that pathbuilding does not need to touch any Mac APIs unless one of those
+// certificates is encountered, at which point the calculated trust status of
+// that cert is cached. The cache is reset if trust settings are modified.
+class TrustStoreMac::TrustImplDomainCacheFullCerts
+    : public TrustStoreMac::TrustImpl {
+ public:
+  explicit TrustImplDomainCacheFullCerts(CFStringRef policy_oid,
+                                         TrustDomains domains)
+      : use_system_domain_cache_(domains == TrustDomains::kAll),
+        admin_domain_cache_(kSecTrustSettingsDomainAdmin, policy_oid),
+        user_domain_cache_(kSecTrustSettingsDomainUser, policy_oid) {
+    if (use_system_domain_cache_) {
+      system_domain_cache_ = std::make_unique<TrustDomainCacheFullCerts>(
+          kSecTrustSettingsDomainSystem, policy_oid);
+    }
+    keychain_observer_ = std::make_unique<KeychainTrustObserver>();
+  }
+
+  TrustImplDomainCacheFullCerts(const TrustImplDomainCacheFullCerts&) = delete;
+  TrustImplDomainCacheFullCerts& operator=(
+      const TrustImplDomainCacheFullCerts&) = delete;
+
+  ~TrustImplDomainCacheFullCerts() override {
+    GetNetworkNotificationThreadMac()->DeleteSoon(
+        FROM_HERE, std::move(keychain_observer_));
+  }
+
+  // Returns true if |cert| is present in kSecTrustSettingsDomainSystem.
+  bool IsKnownRoot(const ParsedCertificate* cert) override {
+    if (!use_system_domain_cache_)
+      return false;
+    SHA256HashValue cert_hash = CalculateFingerprint256(cert->der_cert());
+
+    base::AutoLock lock(cache_lock_);
+    MaybeInitializeCache();
+    return system_domain_cache_->ContainsCert(cert_hash);
+  }
+
+  // Returns the trust status for |cert|.
+  TrustStatus IsCertTrusted(const ParsedCertificate* cert,
+                            base::SupportsUserData* debug_data) override {
+    SHA256HashValue cert_hash = CalculateFingerprint256(cert->der_cert());
+
+    base::AutoLock lock(cache_lock_);
+    MaybeInitializeCache();
+
+    // Evaluate trust domains in user, admin, system order. Admin settings can
+    // override system ones, and user settings can override both admin and
+    // system.
+    for (TrustDomainCacheFullCerts* trust_domain_cache :
+         {&user_domain_cache_, &admin_domain_cache_}) {
+      TrustStatus ts =
+          trust_domain_cache->IsCertTrusted(cert, cert_hash, debug_data);
+      if (ts != TrustStatus::UNSPECIFIED)
+        return ts;
+    }
+    if (use_system_domain_cache_) {
+      return system_domain_cache_->IsCertTrusted(cert, cert_hash, debug_data);
+    }
+
+    // Cert did not have trust settings in any domain.
+    return TrustStatus::UNSPECIFIED;
+  }
+
+  bool ImplementsSyncGetIssuersOf() const override { return true; }
+
+  void SyncGetIssuersOf(const ParsedCertificate* cert,
+                        ParsedCertificateList* issuers) override {
+    base::AutoLock lock(cache_lock_);
+    MaybeInitializeCache();
+    user_domain_cache_.cert_issuer_source().SyncGetIssuersOf(cert, issuers);
+    admin_domain_cache_.cert_issuer_source().SyncGetIssuersOf(cert, issuers);
+    if (system_domain_cache_) {
+      system_domain_cache_->cert_issuer_source().SyncGetIssuersOf(cert,
+                                                                  issuers);
+    }
+  }
+
+  // Initializes the cache, if it isn't already initialized.
+  void InitializeTrustCache() override {
+    base::AutoLock lock(cache_lock_);
+    MaybeInitializeCache();
+  }
+
+ private:
+  // (Re-)Initialize the cache if necessary. Must be called after acquiring
+  // |cache_lock_| and before accessing any of the |*_domain_cache_| members.
+  void MaybeInitializeCache() EXCLUSIVE_LOCKS_REQUIRED(cache_lock_) {
+    cache_lock_.AssertAcquired();
+    int64_t keychain_iteration = keychain_observer_->Iteration();
+    if (iteration_ == keychain_iteration)
+      return;
+
+    iteration_ = keychain_iteration;
+    user_domain_cache_.Initialize();
+    admin_domain_cache_.Initialize();
+    if (use_system_domain_cache_ && !system_domain_initialized_) {
+      // In practice, the system trust domain does not change during runtime,
+      // and SecTrustSettingsCopyCertificates on the system domain is quite
+      // slow, so the system domain cache is not reset on keychain changes.
+      system_domain_cache_->Initialize();
+      system_domain_initialized_ = true;
+    }
+  }
+
+  std::unique_ptr<KeychainTrustObserver> keychain_observer_;
+  // Store whether to use the system domain in a const bool that is initialized
+  // in constructor so it is safe to read without having to lock first.
+  const bool use_system_domain_cache_;
+
+  base::Lock cache_lock_;
+  // |cache_lock_| must be held while accessing any following members.
+  int64_t iteration_ GUARDED_BY(cache_lock_) = -1;
+  bool system_domain_initialized_ GUARDED_BY(cache_lock_) = false;
+  std::unique_ptr<TrustDomainCacheFullCerts> system_domain_cache_
+      GUARDED_BY(cache_lock_);
+  TrustDomainCacheFullCerts admin_domain_cache_ GUARDED_BY(cache_lock_);
+  TrustDomainCacheFullCerts user_domain_cache_ GUARDED_BY(cache_lock_);
+};
+
 // TrustImplNoCache is the simplest approach which calls
 // SecTrustSettingsCopyTrustSettings on every cert checked, with no caching.
 class TrustStoreMac::TrustImplNoCache : public TrustStoreMac::TrustImpl {
@@ -906,6 +1167,10 @@
       trust_cache_ =
           std::make_unique<TrustImplLRUCache>(policy_oid, cache_size, domains);
       break;
+    case TrustImplType::kDomainCacheFullCerts:
+      trust_cache_ =
+          std::make_unique<TrustImplDomainCacheFullCerts>(policy_oid, domains);
+      break;
   }
 }
 
@@ -921,6 +1186,11 @@
 
 void TrustStoreMac::SyncGetIssuersOf(const ParsedCertificate* cert,
                                      ParsedCertificateList* issuers) {
+  if (trust_cache_->ImplementsSyncGetIssuersOf()) {
+    trust_cache_->SyncGetIssuersOf(cert, issuers);
+    return;
+  }
+
   base::ScopedCFTypeRef<CFDataRef> name_data = GetMacNormalizedIssuer(cert);
   if (!name_data)
     return;
diff --git a/net/cert/internal/trust_store_mac.h b/net/cert/internal/trust_store_mac.h
index 693cb122..971173aa 100644
--- a/net/cert/internal/trust_store_mac.h
+++ b/net/cert/internal/trust_store_mac.h
@@ -74,11 +74,14 @@
     COPY_TRUST_SETTINGS_ERROR = 1 << 11,
   };
 
+  // NOTE: When updating this enum, also update ParamToTrustImplType in
+  // system_trust_store.cc
   enum class TrustImplType {
     kUnknown = 0,
     kDomainCache = 1,
     kSimple = 2,
     kLruCache = 3,
+    kDomainCacheFullCerts = 4,
   };
 
   enum class TrustDomains {
@@ -148,6 +151,7 @@
  private:
   class TrustImpl;
   class TrustImplDomainCache;
+  class TrustImplDomainCacheFullCerts;
   class TrustImplNoCache;
   class TrustImplLRUCache;
 
diff --git a/net/cert/internal/trust_store_mac_unittest.cc b/net/cert/internal/trust_store_mac_unittest.cc
index 1d5ab31..2ac15591 100644
--- a/net/cert/internal/trust_store_mac_unittest.cc
+++ b/net/cert/internal/trust_store_mac_unittest.cc
@@ -16,6 +16,7 @@
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_split.h"
 #include "base/synchronization/lock.h"
+#include "base/test/metrics/histogram_tester.h"
 #include "crypto/mac_security_services_lock.h"
 #include "crypto/sha2.h"
 #include "net/cert/internal/cert_errors.h"
@@ -282,6 +283,8 @@
   const TrustStoreMac::TrustImplType trust_impl = std::get<0>(GetParam());
   const IsKnownRootTestOrder is_known_root_test_order = std::get<1>(GetParam());
   const TrustStoreMac::TrustDomains trust_domains = std::get<2>(GetParam());
+
+  base::HistogramTester histogram_tester;
   TrustStoreMac trust_store(kSecPolicyAppleX509Basic, trust_impl,
                             kDefaultCacheSize, trust_domains);
 
@@ -402,6 +405,20 @@
               trust_debug_data2->combined_trust_debug_info());
     EXPECT_EQ(trust_debug_data->trust_impl(), trust_debug_data2->trust_impl());
   }
+
+  if (trust_impl == TrustStoreMac::TrustImplType::kDomainCacheFullCerts) {
+    // Since this is testing the actual platform trust settings, we don't know
+    // what values the histogram should be for each domain, so just verify that
+    // the histogram is recorded (or not) depending on the requested trust
+    // domains.
+    histogram_tester.ExpectTotalCount(
+        "Net.CertVerifier.MacTrustDomainCertCount.User", 1);
+    histogram_tester.ExpectTotalCount(
+        "Net.CertVerifier.MacTrustDomainCertCount.Admin", 1);
+    histogram_tester.ExpectTotalCount(
+        "Net.CertVerifier.MacTrustDomainCertCount.System",
+        (trust_domains == TrustStoreMac::TrustDomains::kAll) ? 1 : 0);
+  }
 }
 
 INSTANTIATE_TEST_SUITE_P(
@@ -410,7 +427,8 @@
     testing::Combine(
         testing::Values(TrustStoreMac::TrustImplType::kDomainCache,
                         TrustStoreMac::TrustImplType::kSimple,
-                        TrustStoreMac::TrustImplType::kLruCache),
+                        TrustStoreMac::TrustImplType::kLruCache,
+                        TrustStoreMac::TrustImplType::kDomainCacheFullCerts),
         // Some TrustImpls may calculate/cache IsKnownRoot values and trust
         // values independently, so test with calling IsKnownRoot both before
         // and after GetTrust to try to ensure there is no ordering issue with
diff --git a/remoting/protocol/webrtc_transport.cc b/remoting/protocol/webrtc_transport.cc
index 2760bc0c..348f68b 100644
--- a/remoting/protocol/webrtc_transport.cc
+++ b/remoting/protocol/webrtc_transport.cc
@@ -44,7 +44,7 @@
 #include "third_party/webrtc/api/video_codecs/builtin_video_decoder_factory.h"
 #include "third_party/webrtc/media/engine/webrtc_media_engine.h"
 #include "third_party/webrtc/modules/audio_processing/include/audio_processing.h"
-#include "third_party/webrtc_overrides/task_queue_factory.h"
+#include "third_party/webrtc_overrides/metronome_task_queue_factory.h"
 
 using jingle_xmpp::QName;
 using jingle_xmpp::XmlElement;
@@ -278,7 +278,7 @@
     pcf_deps.network_thread = worker_thread;
     pcf_deps.worker_thread = worker_thread;
     pcf_deps.signaling_thread = rtc::Thread::Current();
-    pcf_deps.task_queue_factory = CreateWebRtcTaskQueueFactory();
+    pcf_deps.task_queue_factory = CreateWebRtcMetronomeTaskQueueFactory();
     pcf_deps.call_factory = webrtc::CreateCallFactory();
     pcf_deps.event_log_factory = std::make_unique<webrtc::RtcEventLogFactory>(
         pcf_deps.task_queue_factory.get());
diff --git a/services/cert_verifier/public/mojom/trial_comparison_cert_verifier.mojom b/services/cert_verifier/public/mojom/trial_comparison_cert_verifier.mojom
index 5e92388c..8461107 100644
--- a/services/cert_verifier/public/mojom/trial_comparison_cert_verifier.mojom
+++ b/services/cert_verifier/public/mojom/trial_comparison_cert_verifier.mojom
@@ -71,6 +71,7 @@
     kDomainCache = 1,
     kSimple = 2,
     kLruCache = 3,
+    kDomainCacheFullCerts = 4,
   };
   [EnableIf=is_mac]
   MacTrustImplType mac_trust_impl;
diff --git a/services/cert_verifier/trial_comparison_cert_verifier_mojo.cc b/services/cert_verifier/trial_comparison_cert_verifier_mojo.cc
index aba886e8..d75a2461 100644
--- a/services/cert_verifier/trial_comparison_cert_verifier_mojo.cc
+++ b/services/cert_verifier/trial_comparison_cert_verifier_mojo.cc
@@ -41,6 +41,9 @@
     case net::TrustStoreMac::TrustImplType::kLruCache:
       return cert_verifier::mojom::CertVerifierDebugInfo::MacTrustImplType::
           kLruCache;
+    case net::TrustStoreMac::TrustImplType::kDomainCacheFullCerts:
+      return cert_verifier::mojom::CertVerifierDebugInfo::MacTrustImplType::
+          kDomainCacheFullCerts;
   }
 }
 #endif
diff --git a/services/device/generic_sensor/README.md b/services/device/generic_sensor/README.md
index a15397c..3674c23 100644
--- a/services/device/generic_sensor/README.md
+++ b/services/device/generic_sensor/README.md
@@ -1,160 +1,630 @@
 # Sensors
 
-`services/device/generic_sensor` contains the platform-specific parts of the Sensor APIs
-implementation.
+[TOC]
 
-Sensors Mojo interfaces are defined in the `services/device/public/mojom` subdirectory.
+## Introduction
 
-## Web-exposed Interfaces
+This document explains how sensor APIs (such as Ambient Light Sensor,
+Accelerometer, Gyroscope, Magnetometer) are implemented in Chromium.
 
-### [Generic Sensors](https://www.w3.org/TR/generic-sensor/)
+This directory contains the platform-specific parts of the
+implementation, which is used, among others, by the [Generic Sensor
+API](https://w3c.github.io/sensors/) and the [Device Orientation
+API](https://w3c.github.io/deviceorientation/).
 
-The Generic Sensors API is implemented in `third_party/blink/renderer/modules/sensor` and exposes the following sensor types as JavaScript objects:
+The document describes the Generic Sensor API implementation in both
+the renderer and the browser process and lists important
+implementation details, such as how data from a single platform sensor
+is distributed among multiple JavaScript sensor instances and how
+sensor configurations are managed.
 
-* [AbsoluteOrientationSensor] &rarr; ABSOLUTE_ORIENTATION_QUATERNION
-* [Accelerometer] &rarr; ACCELEROMETER
-* [AmbientLightSensor] &rarr; AMBIENT_LIGHT
-* [GravitySensor] &rarr; GRAVITY
-* [Gyroscope] &rarr; GYROSCOPE
-* [LinearAccelerationSensor] &rarr; LINEAR_ACCELEROMETER
-* [Magnetometer] &rarr; MAGNETOMETER
-* [RelativeOrientationSensor] &rarr; RELATIVE_ORIENTATION_QUATERNION
+## Background
 
-[AbsoluteOrientationSensor]: ../../../third_party/blink/renderer/modules/sensor/absolute_orientation_sensor.idl
-[Accelerometer]: ../../../third_party/blink/renderer/modules/sensor/accelerometer.idl
-[AmbientLightSensor]: ../../../third_party/blink/renderer/modules/sensor/ambient_light_sensor.idl
-[GravitySensor]: ../../../third_party/blink/renderer/modules/sensor/gravity_sensor.idl
-[Gyroscope]: ../../../third_party/blink/renderer/modules/sensor/gyroscope.idl
-[LinearAccelerationSensor]: ../../../third_party/blink/renderer/modules/sensor/linear_acceleration_sensor.idl
-[Magnetometer]: ../../../third_party/blink/renderer/modules/sensor/magnetometer.idl
-[RelativeOrientationSensor]: ../../../third_party/blink/renderer/modules/sensor/relative_orientation_sensor.idl
+The Generic Sensor API defines base interfaces that should be implemented by
+concrete sensors. In most cases, concrete sensors should only define
+sensor-specific data structures and, if required, sensor configuration options.
 
-### [DeviceOrientation Events](https://www.w3.org/TR/orientation-event/)
+The same approach is applied to the implementation in Chromium, which was
+designed with the following requirements in mind:
 
-The DeviceOrientation Events API is implemented in `third_party/blink/renderer/modules/device_orientation` and exposes two events based on the following sensors:
+1.  Share the crucial parts of functionality between the concrete sensor
+    implementations. Avoid the code duplication and thus simplify maintenance and
+    development of new features.
+1.  Support simultaneous existence and functioning of multiple JS Sensor
+    instances of the same type that can have different configurations and
+    lifetimes.
+1.  Support for both “slow” sensors that provide periodic updates (e.g.
+    Ambient Light, Proximity), and “fast” streaming sensors that have low-latency
+    requirements for sensor reading updates (motion sensors).
 
-* [DeviceMotionEvent]
-  * ACCELEROMETER: populates the `accelerationIncludingGravity` field
-  * LINEAR_ACCELEROMETER: populates the `acceleration` field
-  * GRAVITY: populates the `gravity` field
-  * GYROSCOPE: populates the `rotationRate` field
-* [DeviceOrientationEvent]
-  * ABSOLUTE_ORIENTATION_EULER_ANGLES (when a listener for the `'deviceorientationabsolute'` event is added)
-  * RELATIVE_ORIENTATION_EULER_ANGLES (when a listener for the `'deviceorientation'` event is added)
+**Note**: the implementation is architected in such a way that Blink (i.e.
+`third_party/blink/renderer/modules/sensor`) is just a consumer of the data from
+`services/device/generic_sensor` like any other. For example, the Blink [Device
+Orientation API](/third_party/blink/renderer/modules/device_orientation/)
+consumes sensor data from `//services` independently from the Blink Generic
+Sensor implementation. The same applies to
+[`device/vr/orientation`](/device/vr/orientation).
 
-[DeviceMotionEvent]: ../../../third_party/blink/renderer/modules/device_orientation/device_motion_event.idl
-[DeviceOrientationEvent]: ../../../third_party/blink/renderer/modules/device_orientation/device_orientation_event.idl
+## Implementation Design
 
-The content renderer layer is located in `third_party/blink/renderer/modules/device_orientation`.
+### Main components and APIs
 
-Testing:
+The Generic Sensor API implementation consists of two main components: the
+`sensor` module in Blink
+([//third_party/blink/renderer/modules/sensor/](/third_party/blink/renderer/modules/sensor/))
+which contains JS bindings for Generic Sensor API and concrete sensors APIs, and
+the `generic_sensor` device service
+([//services/device/generic_sensor/](/services/device/generic_sensor/)) \- a set
+of classes running on the service process side that eventually call system APIs
+to access the actual device sensor data.
 
-* Browser tests are located in `content/browser/device_sensors`.
-* Layout tests are located in `third_party/WebKit/LayoutTests/device_orientation`.
-* Web platform tests are located in `third_party/WebKit/LayoutTests/external/wpt/orientation-event` and are a mirror of the [web-platform-tests GitHub repository](https://github.com/web-platform-tests/wpt).
+The `//services` side also includes a few other directories:
 
-## Permissions
+*   `//services/device/public/cpp/generic_sensor` contains C++ classes and data
+    structures used by both `//services/device/generic_sensor` as well as its
+    consumers.
+*   `//services/device/public/mojom` contains Mojo interfaces by the Generic
+    Sensor implementation.
+    *   [SensorProvider](/services/device/public/mojom/sensor_provider.mojom) is
+        a “factory-like” interface that provides data about the sensors present
+        on the device and their capabilities (reporting mode, maximum sampling
+        frequency), and allows users to request a specific sensor.
+    *   [Sensor](/services/device/public/mojom/sensor.mojom) is an interface
+        wrapping a concrete device sensor.
+    *   [SensorClient](/services/device/public/mojom/sensor.mojom) is
+        implemented by Blink (and other consumers) to be notified about errors
+        occurred on platform side and about sensor reading updates for sensors
+        with ‘onchange’ reporting mode.
 
-The device service provides no support for permission checks. When the render process requests access to a sensor type this request is proxied through the browser process by [SensorProviderProxyImpl] which is responsible for checking the permissions granted to the requesting origin.
+Actual sensor data is not passed to consumers (such as Blink) via Mojo
+calls \- a shared memory buffer is used instead, thus we avoid filling up the
+Mojo IPC channel with sensor data (for sensors with continuous reporting mode) when
+the platform sensor has a high sampling frequency, and also avoid adding extra
+latency.
 
-[SensorProviderProxyImpl]: ../../../content/browser/generic_sensor/sensor_provider_proxy_impl.h
+A high-level diagram of the Mojo architecture looks like this:
 
-## Platform Support
+![Generic Sensor Framework component diagram](docs/generic_sensor_framework_component_diagram.png)
 
-Support for the SensorTypes defined by the Mojo interface is summarized in this
-table. An empty cell indicates that the sensor type is not supported on that
-platform.
+#### Sensor Fusion
 
-| SensorType                        | Android                   | Linux and ChromeOS                    | macOS                                 | Windows                                   |
-| --------------------------------- | ------------------------- | ------------------------------------- | ------------------------------------- | ----------------------------------------- |
-| AMBIENT_LIGHT                     | TYPE_LIGHT                | in_illuminance                        | AppleLMUController                    | Yes                                       |
-| PROXIMITY                         |                           |                                       |                                       |                                           |
-| ACCELEROMETER                     | TYPE_ACCELEROMETER        | in_accel                              | SMCMotionSensor                       | Yes                                       |
-| LINEAR_ACCELEROMETER              | See below                 | ACCELEROMETER (*)                     |                                       | ACCELEROMETER (*)                         |
-| GRAVITY                           | See below                 | ACCELEROMETER (*)                     |                                       | ACCELEROMETER (*)                         |
-| GYROSCOPE                         | TYPE_GYROSCOPE            | in_anglvel                            |                                       | Yes                                       |
-| MAGNETOMETER                      | TYPE_MAGNETIC_FIELD       | in_magn                               |                                       | Yes                                       |
-| PRESSURE                          |                           |                                       |                                       |                                           |
-| ABSOLUTE_ORIENTATION_EULER_ANGLES | See below                 | ACCELEROMETER and MAGNETOMETER (*)    |                                       | Yes                                       |
-| ABSOLUTE_ORIENTATION_QUATERNION   | See below                 | ABSOLUTE_ORIENTATION_EULER_ANGLES (*) |                                       | Yes                                       |
-| RELATIVE_ORIENTATION_EULER_ANGLES | See below                 | ACCELEROMETER and GYROSCOPE (*)       | ACCELEROMETER (*)                     |                                           |
-|                                   |                           | or ACCELEROMETER (*)                  |                                       |                                           |
-| RELATIVE_ORIENTATION_QUATERNION   | TYPE_GAME_ROTATION_VECTOR | RELATIVE_ORIENTATION_EULER_ANGLES (*) | RELATIVE_ORIENTATION_EULER_ANGLES (*) |                                           |
+Some sensors provide data that is obtained by combining readings from other
+sensors (so-called low-level sensors). This process is called **sensor fusion**.
+It can be done in hardware or software.
 
-(Note: "*" means the sensor type is provided by sensor fusion.)
+In Chromium, we sometimes perform software sensor fusion when a certain
+hardware sensor is not available but "fusing" readings from other sensors
+provides a similar reading. The fusion process involves reading data from one or
+more sensors and applying a fusion algorithm to derive another reading from them
+(possibly in a different unit).
 
-### Android
+The figure below figure shows an overview of the fusion sensor flow:
 
-Sensors are implemented by passing through values provided by the
-[Sensor](https://developer.android.com/reference/android/hardware/Sensor.html)
-class. The TYPE_* values in the below descriptions correspond to the integer
-constants from the android.hardware.Sensor used to provide data for a
-SensorType.
+![Overview of fusion sensor flow](docs/overall_picture_of_sensor_flow.png)
 
-For GRAVITY, the following sensor fallback is used:
-1. Use TYPE_GRAVITY directly
-2. ACCELEROMETER, with a low-pass filter to isolate the effect of gravity
+In the code, the main classes are
+[`PlatformSensorFusion`](platform_sensor_fusion.h) and
+[`PlatformSensorFusionAlgorithm`](platform_sensor_fusion_algorithm.h).
 
-For LINEAR_ACCELEROMETER, the following sensor fallback is used:
-1. Use TYPE_LINEAR_ACCELERATION directly
-2. ACCELEROMETER, with a low-pass filter to isolate the effect of gravity
+`PlatformSensorFusion` owns a `PlatformSensorFusionAlgorithm` instance. It
+inherits from both `PlatformSensor` as well as `PlatformSensor::Client`. The
+former indicates it can be treated by consumers as a regular sensor, while the
+latter means that it subscribes to updates from low-level sensors (like
+[`SensorImpl`](sensor_impl.h) itself).  It is in its implementation of
+`OnSensorReadingChanged()` that it invokes its `PlatformSensorFusionAlgorithm`
+to fuse data from the underlying sensors.
 
-For ABSOLUTE_ORIENTATION_EULER_ANGLES, the following sensor fallback is used:
-1. ABSOLUTE_ORIENTATION_QUATERNION (if it uses TYPE_ROTATION_VECTOR
-     directly)
-2. Combination of ACCELEROMETER and MAGNETOMETER
+Once any of the low-level sensors receive a new value, it notifies its clients
+(including the fusion sensor). The fusion sensor algorithm reads the low-level
+sensor raw values and outputs a new reading, which is fed to
+`PlatformSensor::UpdateSharedBufferAndNotifyClients()` as usual.
 
-For ABSOLUTE_ORIENTATION_QUATERNION, the following sensor fallback is used:
-1. Use TYPE_ROTATION_VECTOR directly
-2. ABSOLUTE_ORIENTATION_EULER_ANGLES
+### Security and Privacy
 
-For RELATIVE_ORIENTATION_EULER_ANGLES, converts the data produced by
-RELATIVE_ORIENTATION_QUATERNION to euler angles.
+Platform sensor readings can expose more information about a device and
+consequently lead to an increase in the [fingerprinting
+surface](https://w3c.github.io/fingerprinting-guidance/) exposed by the
+browser, eavesdropping, and keystroke monitoring.
 
-### Linux and Chrome OS
+The security and anti-fingerprinting considerations are also based on existing
+literature on the topic, especially research on sensors exposed to native
+applications on mobile devices:
 
-On Linux, sensors are implemented by reading values from the IIO subsystem.
-The values in the "Linux" column of the table above are the prefix of the
-sysfs files Chrome searches for to provide data for a SensorType.
+*   [Gyrophone: Recognizing Speech from Gyroscope
+    Signals](https://crypto.stanford.edu/gyrophone/files/gyromic.pdf)
+*   [ACCessory: password inference using accelerometers on
+    smartphones](https://pdfs.semanticscholar.org/3673/2ae9fbf61f84eab43e60bc2bcb0a48d05b67.pdf)
+*   [Touchsignatures: identification of user touch actions and pins based on
+    mobile sensor data via javascript](https://arxiv.org/pdf/1602.04115.pdf)
+*   [SoK: Systematic Classification of Side-Channel Attacks on Mobile
+    Devices](https://arxiv.org/pdf/1611.03748.pdf)
+*   [Pin skimming: Exploiting the ambient-light sensor in mobile
+    devices](https://arxiv.org/pdf/1405.3760.pdf)
+*   [TapLogger: Inferring User Inputs On Smartphone Touchscreens Using On-board
+    Motion Sensors](https://pdfs.semanticscholar.org/c860/4311321f1b8f8fdc8acff8871a5bad2ad4ac.pdf)
+
+The Generic Sensor implementation in Chromium follows the [Mitigation
+Strategies](https://w3c.github.io/sensors/#mitigation-strategies) section of
+the Generic Sensor API specification. Namely, this means that:
+
+*   The sensor APIs are only exposed to secure contexts (the same also applies
+    to the API exposed by the [Device Orientation
+    spec](https://w3c.github.io/deviceorientation/#idl-index)).
+*   There is integration with both the Permissions API and the Permissions
+    Policy API.
+*   Sensor readings are only available to documents whose visibility state is
+    "visible" (this also applies to the Device Orientation API).
+*   Sensor readings are only available for active documents whose origin is same
+    origin-domain with the currently focused area document.
+
+The Chromium implementation also applies additional privacy measures (some of
+which are making their way back to the specification):
+
+*   **Frequency**: The maximum sampling frequency is
+    [capped](/services/device/public/cpp/generic_sensor/sensor_traits.h) at 60Hz
+    for most sensor types. Ambient Light sensors and magnetometers are capped at
+    10Hz.
+*   **Accuracy**: Readings are [quantized](#Rounding), and for some sensor types
+    readings which do not differ by a certain [threshold](#Threshold-checks) are
+    discarded and never exposed.
+
+There is no distinction in how the Generic Sensor APIs are exposed to regular
+and incognito windows.
+
+### Classes & APIs
+
+#### //services main classes
+
+*   `PlatformSensorProvider`: singleton class whose main functionality is to
+    create and track `PlatformSensor` instances. `PlatformSensorProvider` is
+    also responsible for creating a shared buffer for sensor readings. Every
+    platform has its own implementation of `PlatformSensorProvider`
+    (`PlatformSensorProviderAndroid`, `PlatformSensorProviderWin` etc), and the
+    generic part of the functionality is encapsulated inside the
+    `PlatformSensorProviderBase` class.
+
+*   `PlatformSensor`: represents device sensor of a given type. There can be
+    only one `PlatformSensor` instance of the same type at a time, its ownership
+    is shared between existing `SensorImpl` instances. `PlatformSensor` is an
+    abstract class which encapsulates generic functionality and is inherited by
+    the platform-specific implementations (`PlatformSensorAndroid`,
+    `PlatformSensorWin` etc).
+
+*   `SensorImpl`: implements the exposed `Sensor` Mojo interface and forwards
+    IPC calls to the owned PlatformSensor instance. `SensorImpl` implements the
+    `PlatformSensor::Client` interface to receive notifications from
+    `PlatformSensor`.
+
+*   `SensorProviderImpl`: implements the exposed `SensorProvider` Mojo interface
+    and forwards IPC calls to the `PlatformSensorProvider` singleton instance.
+
+The classes above have the following ownership relationships:
+
+*   `SensorProviderImpl` owns a single `PlatformSensorProvider` instance via a
+    `std::unique_ptr`.
+*   `SensorProviderImpl` owns all `SensorImpl` instances via a
+    `mojo::UniqueReceiverSet`.
+*   `PlatformSensor` is a ref-counted class, and a `SensorImpl` has a reference
+    to a `PlatformSensor`.
+*   `DeviceService` owns a single `SensorProviderImpl` instance.
+    `DeviceService::BindSensorProvider()` is responsible for creating a
+    `PlatformSensorProvider` if one does not exist and pass it to
+    `SensorProviderImpl`.
+
+#### Blink main classes
+
+*   `Sensor`: implements bindings for the `Sensor` IDL interface. All classes
+    that implement concrete sensor interfaces (such as `AmbientLightSensor`,
+    `Gyroscope`, `Accelerometer`) must inherit from it.
+
+*   `SensorProviderProxy`: owns one side of the `SensorProvider` Mojo interface
+    pipe and manages `SensorProxy` instances. This class supplements
+    `DOMWindow`, so `Sensor` obtains a `SensorProviderProxy` instance via
+    `SensorProviderProxy::From()` and uses it to the get `SensorProxy` instance
+    for a given sensor type.
+
+*   `SensorProxy`: owns one side of the `Sensor` Mojo interface and implements
+    the `device::mojom::blink::SensorClient` Mojo interface. It also defines a
+    `SensorProxy::Observer` interface that is used to notify `Sensor` and its
+    subclasses of errors or data updates from the platform side. `Sensor` and
+    its subclasses interact with the `//services` side via `SensorProxy` (and
+    `SensorProviderProxy`) rather than owning the Mojo pipes themselves.
+
+In a `LocalDOMWindow`, there is one `SensorProxy` instance for a given sensor
+type (ambient light, accelerometer, etc) whose ownership is shared among
+`Sensor` instances. `SensorProxy` instances are created when `Sensor::start()`
+is called and are destroyed when there are no more active Sensor instances left.
+
+### Code flow
+
+#### Low-level sensor
+
+The figure below shows the code flow for a low-level (i.e. non-fusion) sensor:
+
+![Low-level sensor
+flow](docs/low_level_sensor_flow.png)
+
+Each OS-specific `PlatformSensor` implementation retrieves sensor readings
+differently, but they all ultimately call
+`PlatformSensor::UpdateSharedBufferAndNotifyClients()`. This function invokes
+`PlatformSensor::UpdateSharedBuffer()` in
+[`platform_sensor.cc`](platform_sensor.cc), which checks and transforms a
+reading before storing it in the shared buffer:
+
+1.  Sensors whose reporting mode is `mojom::ReportingMode::ON_CHANGE` (i.e. they
+    only send notifications when the reading has changed) first check if the new
+    value is different enough compared to the current value. What is considered
+    different enough (i.e. the threshold check) varies per sensor type (see
+    `PlatformSensor::IsSignificantlyDifferent()` for the base implementation).
+    [Threshold](#threshold)-chapter has more information why code uses threshold
+    value. And [Used threshold values](#used-threshold-values)-chapter has the
+    actual values.
+1.  If the check above passes, the so-called "raw" (unrounded) reading is stored
+    to [`last_raw_reading_`](platform_sensor.h).
+1.  The reading is rounded via `RoundSensorReading()` (in
+    [`platform_sensor_util.cc`](platform_sensor_util.cc)) using a per-type
+    algorithm. [Rounding](#rounding)-chapter has more information why sensor
+    values are rounded.
+1.  The rounded reading is stored in the shared buffer and becomes the value
+    that clients can read.
+
+#### Fusion sensor
+
+Fusion sensors behave similarly, but with extra steps at the end:
+
+1.  They get notified of a new reading when
+    `PlatformSensor::UpdateSharedBufferAndNotifyClients()` invokes
+    `PlatformSensorFusion::OnSensorReadingChanged()`.
+1.  `PlatformSensorFusion::OnSensorReadingChanged()` invokes the sensor's fusion
+    algorithm, which fetches the low-level sensors' **raw** readings and fuses
+    them.
+1.  It invokes `UpdateSharedBufferAndNotifyClients()`, which will go through the
+    same threshold check and rounding process described above, but for the fused
+    reading.
+
+The figure below shows an example of the code flow:
+
+![Fusion sensor
+flow](docs/fusion_sensor_flow.png)
+
+#### Rounding and threshold checks
+
+##### Rounding
+
+Rounding is a form of
+[quantization](https://en.wikipedia.org/wiki/Quantization_(signal_processing)).
+It is used to reduce the accuracy of raw sensor readings in order to help reduce
+the fingerprinting surface exposed by sensors. For example, instead of exposing
+an accelerometer reading of 12.34567m/s^2, we expose a value of 12.3m/s^2
+instead. https://crbug.com/1018180 and https://crbug.com/1031190 show examples
+of issues we try to avoid by providing rounded readings.
+
+Choosing how much rounding to apply is a balance between reducing the
+fingerprinting surface while also still providing values that are meaningful
+enough to users. [`platform_sensor_util.h`](platform_sensor_util.h) contains the
+values we use for each different sensor type.
+
+Currently magnetometer, pressure and proximity sensor readings are not rounded
+(magnetometer is not exposed by default, and there is no backend implementation
+for pressure and proximity sensors).
+
+##### Threshold checks
+
+To prevent small changes around the rounding border from triggering
+notifications, a threshold check is performed and readings that fail it are
+discarded. Rounding is the main measure to prevent fingerprinting and data
+leaks, and the threshold checks play an auxiliary role in conjunction with it by
+reducing the number of updates when a raw reading is hovering around a certain
+value.
+
+**Note:** see the discussions in https://crrev.com/c/3666917 and
+https://crbug.com/1332536 about the role threshold checks play as a mitigation
+strategy. On the one hand, there is no mathematical analysis behind its use as a
+security measure, on the other hand we know that it does reduce the number of
+updates and does not increase the attack surface.
+
+**Note:** threshold checks are only performed for sensors whose reporting mode
+is `ON_CHANGE`. We consider that for sensors with a `CONTINUOUS` reporting mode
+it is more important to report readings at a certain rate than to ignore similar
+readings.
+
+For example, if the code rounded readings to the nearest multiple of 50 and no
+threshold checks were done, a change from a raw value of 24 (rounded to 0) to
+25 (rounded to 50) would trigger a new reading notification, whereas requiring
+the raw readings to differ by 25 guarantees that the readings must differ more
+significantly to trigger a new notification.
+
+The actual checks differ by sensor type:
+
+*   Ambient Light Sensor readings must differ by half of the rounding multiple
+    (`kAlsRoundingMultiple`), or 25 lux in practice.
+*   Fusion sensors default to requiring a difference of 0.1 in readings, which
+    can be changed by calling `PlatformSensorFusionAlgorithm::set_threshold()`
+*   Other low-level sensors only check for equality between current and new
+    readings at the moment.
+
+##### Execution order of rounding and threshold check
+
+We first perform a threshold check on the current and new raw readings and only
+perform rounding and store the new rounded reading if the threshold check
+passes.
+
+If we did rounding first, we would increase even more the area which rounding
+already protects, and callers would get values that are too inaccurate.
+
+#### Permissions
+
+##### Permissions API integration
+
+The [`Sensor.start()`](https://w3c.github.io/sensors/#sensor-start) operation
+in the Generic Sensor spec invokes the [request sensor
+access](https://w3c.github.io/sensors/#request-sensor-access) abstract
+operation so that permission checks (via the [Permissions
+API](https://w3c.github.io/permissions/) are performed before a sensor is
+started.
+
+In Chromium, the permission checks are done in the `//content/browser` side:
+`SensorProviderProxyImpl::GetSensor()` invokes
+`PermissionController::RequestPermissionFromCurrentDocument()` and only
+connects to the `//services` side if permission has been granted.
+
+**Note**: At the moment, users are never prompted to grant access to a device's
+sensors, access is either allowed or denied. This ended up happening for
+historical reasons: the Device Orientation API was implemented first and neither
+spec nor implementation were supposed to prompt for sensor access (it was
+retrofitted into the spec years later), the Generic Sensor API was added to
+Chromium later, the Device Orientation API in Blink was changed to use the same
+backend in `//services`, and the permission behavior was kept to avoid breaking
+user compatibility. Work to improve the situation for both the Device
+Orientation API and the Generic Sensor API in Chromium is tracked in
+https://crbug.com/947112.
+
+From a UI perspective, users are able to grant or deny access to sensors in the
+global settings (chrome://settings/content) or on a per-site basis even before
+work on issue 947112 is finished.
+
+##### Permissions policy integration
+
+The Chromium implementation follows the spec and integrates with the Permissions
+Policy spec.
+
+The [Policy Controlled
+Features](https://github.com/w3c/webappsec-permissions-policy/blob/main/features.md)
+page lists the current status of different features.
+
+Blink performs checks in
+[`Sensor::Sensor()`](/third_party/blink/renderer/modules/sensor/sensor.cc) by
+calling `AreFeaturesEnabled()`, while the `content/` side also performs checks
+in
+[`SensorProviderProxyImpl::CheckFeaturePolicies()`](/content/browser/generic_sensor/sensor_provider_proxy_impl.cc).
+
+#### Reporting values to consumer
+
+##### Reporting modes
+
+Sensors have two ways of reporting new values as defined in
+[`ReportingMode`](/services/device/public/mojom/sensor.mojom). The values are
+based on the [Android sensor reporting
+modes](https://source.android.com/devices/sensors/report-modes):
+
+*   `ON_CHANGE`: Clients are notified via
+    `PlatformSensor::Client::OnSensorReadingChanged()` when the measured values
+    have changed. Different sensor types have different thresholds for
+    considering whether a change is significant enough to be reported as
+    discussed in [the round and thresholds
+    section](#rounding-and-threshold-checks).
+*   `CONTINUOUS`: Sensor readings are continuously updated at a frequency
+    derived from
+    [`PlatformSensorConfiguration.frequency`](/services/device/public/cpp/generic_sensor/platform_sensor_configuration.h).
+    The sampling frequency value is capped to 10Hz or 60Hz (defined in
+    [`sensor_traits.h`](/services/device/public/cpp/generic_sensor/sensor_traits.h))
+    for security reasons as explained in the [Security and
+    Privacy](#Security-and-Privacy) section of this document.
+    `PlatformSensor::Client::OnSensorReadingChanged()` is never invoked, and
+    clients are expected to periodically fetch readings on their own via
+    `SensorReadingSharedBufferReader::GetReading()`.
+
+There is no default reporting mode: `PlatformSensor` subclasses are expected to
+implement the `GetReportingMode()` method and return a value fit for the given
+OS and sensor type.
+
+##### Configuration and frequency management
+
+As described above, there is always at most one `PlatformSensor` instance for a
+given sensor type, and it can have multiple clients consuming its readings. Each
+client might be interested in receiving readings at a different frequency, and
+it is up to `PlatformSensor` to coordinate these requests and choose a reading
+frequency.
+
+Clients first invoke the [`SensorProvider.GetSensor()` Mojo
+method](/services/device/public/mojom/sensor_provider.mojom); on success, the
+return values include a [`Sensor` Mojo
+interface](/services/device/public/mojom/sensor.mojom). Clients then invoke the
+`Sensor.AddConfiguration()` and `Sensor.RemoveConfiguration()` to add and remove
+`SensorConfiguration` instances from the given `Sensor`.
+
+When a new configuration is added, `PlatformSensor::UpdateSensorInternal()` is
+called and will ultimately choose the highest of all requested frequencies
+(within the boundaries defined in
+[sensor_traits.h](/services/device/public/cpp/generic_sensor/sensor_traits.h)
+for security reasons). In other words, a client's requested frequency does not
+necessarily match `PlatformSensor`'s actual sampling frequency (or the frequency
+the OS ends up using).
+
+It is then up to each client to check a reading's timestamp and decide whether
+to ignore it if not enough time has passed since the previous reading was
+delivered. It is also possible for clients to simply process all readings
+regardless of the actual sampling frequency.
+
+Blink adopts the former strategy:
+
+*   For sensors with a `CONTINUOUS` reporting mode, `SensorProxyImpl` polls the
+    shared memory buffer at the highest requested frequency. For sensors with a
+    `ON_CHANGE` reporting mode, it will receive updates from the services side
+    via `SensorProxyImpl::SensorReadingChanged()`.
+*   Each individual `Sensor` instance connected to a `SensorProxyImpl` will be
+    notified via `Sensor::OnSensorReadingChanged()`, which takes care of
+    avoiding sending too many "reading" JS notifications depending on the Sensor
+    instance's requested frequency.
+
+##### Sensor readings shared buffer
+
+As mentioned above, sensor readings are not transmitted from the services side
+to the consumers (such as the renderer process) via IPC calls, but with a
+[shared memory buffer](/base/memory/read_only_shared_memory_region.h).
+Read-write operations are synchronized via a seqlock mechanism.
+
+The `PlatformSensorProvider` singleton owned by `DeviceService` maintains a
+shared memory mapping of the entire buffer. Only `PlatformSensorProvider` has
+write access to it; all consumers only have read-access to the buffer. This
+shared buffer is a contiguous sequence of `SensorReadingSharedBuffer`s, one per
+`mojom::SensorType`.
+
+Each `SensorReadingBuffer` structure has 6 tightly-packed 64-bit floating
+fields: **seqlock**, **timestamp**, **sensor reading 1**, **sensor reading 2**,
+**sensor reading 3**, and **sensor reading 4**. This has a fixed size of 6 * 8 =
+48 bytes. The whole shared buffer's size depends on `mojom::SensorType`'s size;
+at the time of writing, there are 12 distinct sensor types, which means the
+entire shared buffer handle is 12 * 48 = 576 bytes in size.
+[sensor_reading.h](/services/device/public/cpp/generic_sensor/sensor_reading.h)
+has the actual `SensorReading` data structure as well as the
+`SensorReadingSharedBuffer` structure.
+
+The code treats each `SensorReadingBuffer` embedded in the mapping as completely
+independent from the others: `SensorReadingSharedBuffer::GetOffset()` provides
+the offset in the buffer corresponding to the `SensorReadingBuffer` representing
+a given sensor type, `SensorReadingSharedBufferReader::Create()` requires an
+offset to map only a specific portion of the shared buffer and, additionally,
+each `SensorReadingBuffer` has its own `seqlock` for coordinating reads and
+writes.
+
+![Shared buffer diagram](docs/shared_buffer.png)
+
+In terms of code flow, the creation and sharing of the shared buffer when
+between Blink calls `SensorProviderProxy::GetSensor()` works roughly like this:
+
+1.  `SensorProviderProxy::GetSensor()` calls the `GetSensor()` operation in the
+    Sensor Mojo interface.
+1.  That is implemented by `SensorProviderImpl::GetSensor()`. It invokes
+    `PlatformSensorProviderBase::CloneSharedMemoryRegion()`.
+    1.  `PlatformSensorProviderBase::CloneSharedMemoryRegion()` initializes the
+        shared buffer and the mapping if necessary in
+        `PlatformSensorProviderBase::CreateSharedBufferIfNeeded()`.
+    1.  `PlatformSensorProviderBase::CloneSharedMemoryRegion()` returns a
+        read-only mapping handle.
+1.  If the platform sensor still needs to be created,
+    `SensorProviderImpl::GetSensor()` will invoke
+    `PlatformSensorProviderBase::CreateSensor()`, which invokes
+    `PlatformSensorProviderBase::GetSensorReadingSharedBufferForType()`. The
+    `SensorReadingSharedBuffer` pointer returned by this function is later
+    passed to `PlatformSensor` and its subclasses, which update it when the
+    readings change.
+1.  Ultimately, `PlatformSensorProviderBase::SensorCreated()` is called, and the
+    read-only shared buffer handle and the offset in it corresponding to the
+    sensor type being created are passed in `mojom::SensorInitParams`.
+1.  On the Blink side, `SensorProxyImpl::OnSensorCreated()` is invoked with the
+    `SensorInitParams` initialized above. It invokes
+    `SensorReadingSharedBufferReader::Create()` with the buffer and offset from
+    `SensorInitParams`, and that is what it later used to obtain readings.
+
+### Platform-specific details
+
+#### Android
+
+The Android implementation in `//services` consists of two parts, native (C++)
+and Java. The native side includes `PlatformSensorProviderAndroid` and
+`PlatformSensorAndroid`, while the Java side consists of the
+`PlatformSensorProvider` and `PlatformSensor` classes that are included in the
+`org.chromium.device.sensors` package. Java classes interface with Android
+Sensor API to fetch readings from device sensors. The native and Java sides
+communicate via JNI.
+
+The [`PlatformSensorProviderAndroid`](platform_sensor_provider_android.h) C++
+class inherits from `PlatformSensorProvider` and is responsible for creating a
+`PlatformSensorProvider` (Java) instance via JNI. When the Java object is
+created, all sensor creation requests are forwarded to the Java object.
+
+The [`PlatformSensorAndroid`](platform_sensor_android.h) C++ class inherits from
+`PlatformSensor`, owns the `PlatformSensor` Java object and forwards start, stop
+and other requests to it.
+
+The
+[`PlatformSensorProvider`](android/java/src/org/chromium/device/sensors/PlatformSensorProvider.java)
+Java class is responsible for thread management, and `PlatformSensor` creation.
+It also owns the Android `SensorManager` object that is accessed by
+`PlatformSensor` Java objects.
+
+The
+[`PlatformSensor`](android/java/src/org/chromium/device/sensors/PlatformSensor.java)
+Java class implements the `SensorEventListener` interface and owns the Android
+`Sensor` object. `PlatformSensor` adds itself as an event listener to receive
+sensor reading updates and forwards them to native side using the `native*`
+methods.
+
+#### Windows
+
+The Windows backend has its [own README.md](windows/README.md).
+
+#### ChromeOS
+
 On ChromeOS, sensors are implemented with Mojo connections to IIO Service, a
 CrOS daemon that provides sensors' data to other applications.
-The ABSOLUTE_ORIENTATION_EULER_ANGLES sensor type is provided by interpreting
-the value that can be read from the ACCELEROMETER and MAGNETOMETER. The
-ABSOLUTE_ORIENTATION_QUATERNION sensor type is provided by interpreting the
-value that can be read from the ABSOLUTE_ORIENTATION_EULER_ANGLES. The
-RELATIVE_ORIENTATION_EULER_ANGLES sensor type is provided by interpreting the
-value that can be read from the ACCELEROMETER and GYROSCOPE, or ACCELEROMETER.
-The RELATIVE_ORIENTATION_QUATERNION sensor type is provided by interpreting the
-value that can be read from the RELATIVE_ORIENTATION_EULER_ANGLES.
-LINEAR_ACCELEROMETER sensor type is provided by implementing a low-pass-filter
-over the values returned by the ACCELEROMETER in order to remove the
-contribution of the gravitational force.
 
-### macOS
+_Need to add information about iio service and the platform-specific
+implementation that reads sensor data and feeds it to Chromium_
 
-On this platform there is limited support for sensors. The AMBIENT_LIGHT sensor
-type is provided by interpreting the value that can be read from the LMU. The
-ACCELEROMETER sensor type is provided by interpreting the value that can be read
-from the SMCMotionSensor. The RELATIVE_ORIENTATION_EULER_ANGLES sensor type is
-provided by interpreting the value that can be read from the ACCELEROMETER. The
-RELATIVE_ORIENTATION_QUATERNION sensor type is provided by interpreting the
-value that can be read from the RELATIVE_ORIENTATION_EULER_ANGLES.
+#### Linux
 
-### Windows
+Sensor data is exposed by the Linux kernel via its Industrial I/O (iio)
+subsystem. Sensor readings and metadata are readable via sysfs; udev is used for
+device enumeration and getting notifications about sensors that are added or
+removed from the system.
 
-Please refer to this [document](windows/README.md).
+[`PlatformSensorProviderLinuxBase`](platform_sensor_provider_linux_base.h) is
+shared between the Linux and ChromeOS implementations, and
+[`PlatformSensorProviderLinux`](platform_sensor_provider_linux.h) is its
+Linux-specific subclass. Similarly,
+[`PlatformSensorLinux`](platform_sensor_linux.h) inherits from the base
+`PlatformSensor` class.
 
-## Testing
+The implementation is supported by a few more classes:
 
-Sensors platform unit tests are located in the current directory and its
-subdirectories.
+*   [`SensorDeviceManager`](linux/sensor_device_manager.h): interfaces with udev
+    by subclassing `device::UdevWatcher::Observer`. It enumerates all sensors
+    exposed by the Linux kernel via udev, caches their information (frequency,
+    scaling value, sysfs location etc) and notifies users (i.e.
+    `PlatformSensorProviderLinux`) of changes later.
+*   [`SensorInfoLinux`](linux/sensor_data_linux.h): gathers all iio sensor
+    information in a single struct (frequency, scaling value, sysfs location
+    etc).
+*   [`SensorReader`](platform_sensor_reader_linux.h) and
+    [`PollingSensorReader`](platform_sensor_reader_linux.cc) implement the
+    reading and reporting of sensor data from sysfs.
 
-The sensors unit tests file for Android is
-`android/junit/src/org/chromium/device/sensors/PlatformSensorAndProviderTest.java`.
+When a request for a specific type of sensor comes and
+`PlatformSensorProviderLinux::CreateSensorInternal()` is called, it will either
+cause `SensorDeviceManager` to start enumerating all available sensors if that
+has not happened yet or look for a specific sensor in `SensorDeviceManager`'s
+cache. That is then used to construct a new `PlatformSensorLinux` object (which
+takes a `SensorInfoLinux` pointer), which then owns a `SensorReader` that uses
+the `SensorInfoLinux` to know which sensor data to read and how to parse it.
 
-Sensors browser tests are located in `content/test/data/generic_sensor`.
+#### macOS
 
-## Design Documents
+`PlatformSensorProviderMac` implements `PlatformSensorProvider` and is
+responsible for creating one of the two classes implementing sensor support on
+Mac:
 
-Please refer to the [design documentation](https://docs.google.com/document/d/1Ml65ZdW5AgIsZTszk4mD_ohr40pcrdVFOIf0ZtWxDv0)
-for more details.
+*   [`PlatformSensorAccelerometerMac`](platform_sensor_accelerometer_mac.h)
+    exposes accelerometer data and can be in sensor fusion (e.g.
+    [`RelativeOrientationEulerAnglesFusionAlgorithmUsingAccelerometer`](relative_orientation_euler_angles_fusion_algorithm_using_accelerometer.h)).
+    It is backed by the
+    [`SuddenMotionSensor`](/third_party/sudden_motion_sensor/sudden_motion_sensor_mac.h)
+    code in third_party and thus relies on being able to read from the
+    SMCMotionSensor.
+*   [`PlatformSensorAmbientLightMac`](platform_sensor_ambient_light_mac.h)
+    exposed ambient light data from laptop models that support and expose it.
+
+As evidenced above, macOS provides limited support for reading sensor data, and
+it is mostly restricted to older laptop models. In the future, it might be
+possible to remove all the Mac code from `//services/device/generic_sensor`.
diff --git a/services/device/generic_sensor/docs/fusion_sensor_flow.png b/services/device/generic_sensor/docs/fusion_sensor_flow.png
new file mode 100644
index 0000000..c16a41f7
--- /dev/null
+++ b/services/device/generic_sensor/docs/fusion_sensor_flow.png
Binary files differ
diff --git a/services/device/generic_sensor/docs/generic_sensor_framework_component_diagram.png b/services/device/generic_sensor/docs/generic_sensor_framework_component_diagram.png
new file mode 100644
index 0000000..fec970d
--- /dev/null
+++ b/services/device/generic_sensor/docs/generic_sensor_framework_component_diagram.png
Binary files differ
diff --git a/services/device/generic_sensor/docs/low_level_sensor_flow.png b/services/device/generic_sensor/docs/low_level_sensor_flow.png
new file mode 100644
index 0000000..61e0a39
--- /dev/null
+++ b/services/device/generic_sensor/docs/low_level_sensor_flow.png
Binary files differ
diff --git a/services/device/generic_sensor/docs/overall_picture_of_sensor_flow.png b/services/device/generic_sensor/docs/overall_picture_of_sensor_flow.png
new file mode 100644
index 0000000..dc5439b
--- /dev/null
+++ b/services/device/generic_sensor/docs/overall_picture_of_sensor_flow.png
Binary files differ
diff --git a/services/device/generic_sensor/docs/shared_buffer.png b/services/device/generic_sensor/docs/shared_buffer.png
new file mode 100644
index 0000000..59d0efb
--- /dev/null
+++ b/services/device/generic_sensor/docs/shared_buffer.png
Binary files differ
diff --git a/services/network/restricted_cookie_manager.cc b/services/network/restricted_cookie_manager.cc
index cee388b..2f2199c 100644
--- a/services/network/restricted_cookie_manager.cc
+++ b/services/network/restricted_cookie_manager.cc
@@ -638,7 +638,11 @@
   // TODO(pwnall): Validate the CanonicalCookie fields.
 
   // Update the creation and last access times.
-  base::Time now = base::Time::NowFromSystemTime();
+  // Note: This used to be a call to NowFromSystemTime, but this caused
+  // inconsistency with the expiration date, which was capped checking
+  // against Now. If any issues crop up related to this change please
+  // contact the owners of http://crbug.com/1335859.
+  base::Time now = base::Time::Now();
   // TODO(http://crbug.com/1024053): Log metrics
   const GURL& origin_url = origin_.GetURL();
   net::CookieSourceScheme source_scheme =
diff --git a/testing/buildbot/chromium.android.fyi.json b/testing/buildbot/chromium.android.fyi.json
index 7317259..41e109b 100644
--- a/testing/buildbot/chromium.android.fyi.json
+++ b/testing/buildbot/chromium.android.fyi.json
@@ -8325,15 +8325,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
-          "--webview-apk-path=apks/SystemWebView.apk",
           "--test-runner-outdir",
           ".",
-          "--client-outdir",
-          "../../weblayer_instrumentation_test_M103/out/Release",
           "--implementation-outdir",
           ".",
           "--test-expectations",
           "../../weblayer/browser/android/javatests/skew/expectations.txt",
+          "--webview-apk-path=apks/SystemWebView.apk",
+          "--client-outdir",
+          "../../weblayer_instrumentation_test_M103/out/Release",
           "--client-version=103",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -8359,7 +8359,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M103",
-              "revision": "version:103.0.5060.52"
+              "revision": "version:103.0.5060.53"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -8410,15 +8410,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
-          "--webview-apk-path=apks/SystemWebView.apk",
           "--test-runner-outdir",
           ".",
-          "--client-outdir",
-          "../../weblayer_instrumentation_test_M104/out/Release",
           "--implementation-outdir",
           ".",
           "--test-expectations",
           "../../weblayer/browser/android/javatests/skew/expectations.txt",
+          "--webview-apk-path=apks/SystemWebView.apk",
+          "--client-outdir",
+          "../../weblayer_instrumentation_test_M104/out/Release",
           "--client-version=104",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -8835,15 +8835,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
-          "--webview-apk-path=apks/AOSP_SystemWebView.apk",
           "--test-runner-outdir",
           ".",
           "--client-outdir",
           ".",
-          "--implementation-outdir",
-          "../../weblayer_instrumentation_test_M103/out/Release",
           "--test-expectations",
           "../../weblayer/browser/android/javatests/skew/expectations.txt",
+          "--webview-apk-path=apks/AOSP_SystemWebView.apk",
+          "--implementation-outdir",
+          "../../weblayer_instrumentation_test_M103/out/Release",
           "--impl-version=103",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -8869,7 +8869,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M103",
-              "revision": "version:103.0.5060.52"
+              "revision": "version:103.0.5060.53"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -8920,15 +8920,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
-          "--webview-apk-path=apks/AOSP_SystemWebView.apk",
           "--test-runner-outdir",
           ".",
           "--client-outdir",
           ".",
-          "--implementation-outdir",
-          "../../weblayer_instrumentation_test_M104/out/Release",
           "--test-expectations",
           "../../weblayer/browser/android/javatests/skew/expectations.txt",
+          "--webview-apk-path=apks/AOSP_SystemWebView.apk",
+          "--implementation-outdir",
+          "../../weblayer_instrumentation_test_M104/out/Release",
           "--impl-version=104",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
diff --git a/testing/buildbot/chromium.android.json b/testing/buildbot/chromium.android.json
index ac73252..5e98dd5 100644
--- a/testing/buildbot/chromium.android.json
+++ b/testing/buildbot/chromium.android.json
@@ -14740,10 +14740,11 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "webview_trichrome_cts_tests"
+            "webview_trichrome_cts_tests full_mode"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
+        "name": "webview_trichrome_cts_tests full_mode",
         "resultdb": {
           "enable": true,
           "has_native_resultdb_integration": true
@@ -14795,11 +14796,87 @@
               "name": "shard #${SHARD_INDEX} logcats"
             }
           ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
-          "shards": 2
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
         "test": "webview_trichrome_cts_tests",
-        "test_id_prefix": "ninja://android_webview/test:webview_trichrome_cts_tests/"
+        "test_id_prefix": "ninja://android_webview/test:webview_trichrome_cts_tests/",
+        "variant_id": "full_mode"
+      },
+      {
+        "args": [
+          "--exclude-annotation",
+          "AppModeFull",
+          "--test-apk-as-instant",
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "webview_trichrome_cts_tests instant_mode"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "name": "webview_trichrome_cts_tests instant_mode",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/android_webview/tools/cts_archive",
+              "location": "android_webview/tools/cts_archive",
+              "revision": "qF6dhyFMW7qFOzHo_Lu-bWxpbe-zRfL1KvHPQtQA3d0C"
+            },
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4|e2-standard-4",
+              "os": "Ubuntu-18.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "generic_android30",
+              "path": ".android_emulator/generic_android30"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "generic_android30"
+              }
+            ]
+          },
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "webview_trichrome_cts_tests",
+        "test_id_prefix": "ninja://android_webview/test:webview_trichrome_cts_tests/",
+        "variant_id": "instant_mode"
       },
       {
         "args": [
@@ -19551,10 +19628,11 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "webview_trichrome_64_cts_tests"
+            "webview_trichrome_64_cts_tests full_mode"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
+        "name": "webview_trichrome_64_cts_tests full_mode",
         "resultdb": {
           "enable": true,
           "has_native_resultdb_integration": true
@@ -19606,11 +19684,87 @@
               "name": "shard #${SHARD_INDEX} logcats"
             }
           ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
-          "shards": 2
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
         "test": "webview_trichrome_64_cts_tests",
-        "test_id_prefix": "ninja://android_webview/test:webview_trichrome_64_cts_tests/"
+        "test_id_prefix": "ninja://android_webview/test:webview_trichrome_64_cts_tests/",
+        "variant_id": "full_mode"
+      },
+      {
+        "args": [
+          "--exclude-annotation",
+          "AppModeFull",
+          "--test-apk-as-instant",
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android31.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "webview_trichrome_64_cts_tests instant_mode"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "name": "webview_trichrome_64_cts_tests instant_mode",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/android_webview/tools/cts_archive",
+              "location": "android_webview/tools/cts_archive",
+              "revision": "qF6dhyFMW7qFOzHo_Lu-bWxpbe-zRfL1KvHPQtQA3d0C"
+            },
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "e2-standard-8",
+              "os": "Ubuntu-18.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "generic_android31",
+              "path": ".android_emulator/generic_android31"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "generic_android31"
+              }
+            ]
+          },
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "webview_trichrome_64_cts_tests",
+        "test_id_prefix": "ninja://android_webview/test:webview_trichrome_64_cts_tests/",
+        "variant_id": "instant_mode"
       },
       {
         "args": [
@@ -46297,15 +46451,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
-          "--webview-apk-path=apks/SystemWebView.apk",
           "--test-runner-outdir",
           ".",
-          "--client-outdir",
-          "../../weblayer_instrumentation_test_M103/out/Release",
           "--implementation-outdir",
           ".",
           "--test-expectations",
           "../../weblayer/browser/android/javatests/skew/expectations.txt",
+          "--webview-apk-path=apks/SystemWebView.apk",
+          "--client-outdir",
+          "../../weblayer_instrumentation_test_M103/out/Release",
           "--client-version=103",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -46331,7 +46485,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M103",
-              "revision": "version:103.0.5060.52"
+              "revision": "version:103.0.5060.53"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -46382,15 +46536,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
-          "--webview-apk-path=apks/SystemWebView.apk",
           "--test-runner-outdir",
           ".",
-          "--client-outdir",
-          "../../weblayer_instrumentation_test_M104/out/Release",
           "--implementation-outdir",
           ".",
           "--test-expectations",
           "../../weblayer/browser/android/javatests/skew/expectations.txt",
+          "--webview-apk-path=apks/SystemWebView.apk",
+          "--client-outdir",
+          "../../weblayer_instrumentation_test_M104/out/Release",
           "--client-version=104",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -46807,15 +46961,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
-          "--webview-apk-path=apks/AOSP_SystemWebView.apk",
           "--test-runner-outdir",
           ".",
           "--client-outdir",
           ".",
-          "--implementation-outdir",
-          "../../weblayer_instrumentation_test_M103/out/Release",
           "--test-expectations",
           "../../weblayer/browser/android/javatests/skew/expectations.txt",
+          "--webview-apk-path=apks/AOSP_SystemWebView.apk",
+          "--implementation-outdir",
+          "../../weblayer_instrumentation_test_M103/out/Release",
           "--impl-version=103",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -46841,7 +46995,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M103",
-              "revision": "version:103.0.5060.52"
+              "revision": "version:103.0.5060.53"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -46892,15 +47046,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
-          "--webview-apk-path=apks/AOSP_SystemWebView.apk",
           "--test-runner-outdir",
           ".",
           "--client-outdir",
           ".",
-          "--implementation-outdir",
-          "../../weblayer_instrumentation_test_M104/out/Release",
           "--test-expectations",
           "../../weblayer/browser/android/javatests/skew/expectations.txt",
+          "--webview-apk-path=apks/AOSP_SystemWebView.apk",
+          "--implementation-outdir",
+          "../../weblayer_instrumentation_test_M104/out/Release",
           "--impl-version=104",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -47321,15 +47475,15 @@
       {
         "args": [
           "--additional-apk=apks/ChromePublic.apk",
-          "--webview-apk-path=apks/SystemWebView.apk",
           "--test-runner-outdir",
           ".",
-          "--client-outdir",
-          "../../weblayer_instrumentation_test_M103/out/Release",
           "--implementation-outdir",
           ".",
           "--test-expectations",
           "../../weblayer/browser/android/javatests/skew/expectations.txt",
+          "--webview-apk-path=apks/SystemWebView.apk",
+          "--client-outdir",
+          "../../weblayer_instrumentation_test_M103/out/Release",
           "--client-version=103",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -47355,7 +47509,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M103",
-              "revision": "version:103.0.5060.52"
+              "revision": "version:103.0.5060.53"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -47406,15 +47560,15 @@
       {
         "args": [
           "--additional-apk=apks/ChromePublic.apk",
-          "--webview-apk-path=apks/SystemWebView.apk",
           "--test-runner-outdir",
           ".",
-          "--client-outdir",
-          "../../weblayer_instrumentation_test_M104/out/Release",
           "--implementation-outdir",
           ".",
           "--test-expectations",
           "../../weblayer/browser/android/javatests/skew/expectations.txt",
+          "--webview-apk-path=apks/SystemWebView.apk",
+          "--client-outdir",
+          "../../weblayer_instrumentation_test_M104/out/Release",
           "--client-version=104",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -47831,15 +47985,15 @@
       {
         "args": [
           "--additional-apk=apks/ChromePublic.apk",
-          "--webview-apk-path=apks/AOSP_SystemWebView.apk",
           "--test-runner-outdir",
           ".",
           "--client-outdir",
           ".",
-          "--implementation-outdir",
-          "../../weblayer_instrumentation_test_M103/out/Release",
           "--test-expectations",
           "../../weblayer/browser/android/javatests/skew/expectations.txt",
+          "--webview-apk-path=apks/AOSP_SystemWebView.apk",
+          "--implementation-outdir",
+          "../../weblayer_instrumentation_test_M103/out/Release",
           "--impl-version=103",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -47865,7 +48019,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M103",
-              "revision": "version:103.0.5060.52"
+              "revision": "version:103.0.5060.53"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -47916,15 +48070,15 @@
       {
         "args": [
           "--additional-apk=apks/ChromePublic.apk",
-          "--webview-apk-path=apks/AOSP_SystemWebView.apk",
           "--test-runner-outdir",
           ".",
           "--client-outdir",
           ".",
-          "--implementation-outdir",
-          "../../weblayer_instrumentation_test_M104/out/Release",
           "--test-expectations",
           "../../weblayer/browser/android/javatests/skew/expectations.txt",
+          "--webview-apk-path=apks/AOSP_SystemWebView.apk",
+          "--implementation-outdir",
+          "../../weblayer_instrumentation_test_M104/out/Release",
           "--impl-version=104",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -48413,15 +48567,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
-          "--webview-apk-path=apks/SystemWebView.apk",
           "--test-runner-outdir",
           ".",
-          "--client-outdir",
-          "../../weblayer_instrumentation_test_M103/out/Release",
           "--implementation-outdir",
           ".",
           "--test-expectations",
           "../../weblayer/browser/android/javatests/skew/expectations.txt",
+          "--webview-apk-path=apks/SystemWebView.apk",
+          "--client-outdir",
+          "../../weblayer_instrumentation_test_M103/out/Release",
           "--client-version=103",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -48447,7 +48601,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M103",
-              "revision": "version:103.0.5060.52"
+              "revision": "version:103.0.5060.53"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -48498,15 +48652,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
-          "--webview-apk-path=apks/SystemWebView.apk",
           "--test-runner-outdir",
           ".",
-          "--client-outdir",
-          "../../weblayer_instrumentation_test_M104/out/Release",
           "--implementation-outdir",
           ".",
           "--test-expectations",
           "../../weblayer/browser/android/javatests/skew/expectations.txt",
+          "--webview-apk-path=apks/SystemWebView.apk",
+          "--client-outdir",
+          "../../weblayer_instrumentation_test_M104/out/Release",
           "--client-version=104",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -48923,15 +49077,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
-          "--webview-apk-path=apks/SystemWebView.apk",
           "--test-runner-outdir",
           ".",
           "--client-outdir",
           ".",
-          "--implementation-outdir",
-          "../../weblayer_instrumentation_test_M103/out/Release",
           "--test-expectations",
           "../../weblayer/browser/android/javatests/skew/expectations.txt",
+          "--webview-apk-path=apks/SystemWebView.apk",
+          "--implementation-outdir",
+          "../../weblayer_instrumentation_test_M103/out/Release",
           "--impl-version=103",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -48957,7 +49111,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M103",
-              "revision": "version:103.0.5060.52"
+              "revision": "version:103.0.5060.53"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -49008,15 +49162,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
-          "--webview-apk-path=apks/SystemWebView.apk",
           "--test-runner-outdir",
           ".",
           "--client-outdir",
           ".",
-          "--implementation-outdir",
-          "../../weblayer_instrumentation_test_M104/out/Release",
           "--test-expectations",
           "../../weblayer/browser/android/javatests/skew/expectations.txt",
+          "--webview-apk-path=apks/SystemWebView.apk",
+          "--implementation-outdir",
+          "../../weblayer_instrumentation_test_M104/out/Release",
           "--impl-version=104",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -49505,15 +49659,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
-          "--webview-apk-path=apks/SystemWebView.apk",
           "--test-runner-outdir",
           ".",
-          "--client-outdir",
-          "../../weblayer_instrumentation_test_M103/out/Release",
           "--implementation-outdir",
           ".",
           "--test-expectations",
           "../../weblayer/browser/android/javatests/skew/expectations.txt",
+          "--webview-apk-path=apks/SystemWebView.apk",
+          "--client-outdir",
+          "../../weblayer_instrumentation_test_M103/out/Release",
           "--client-version=103",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -49539,7 +49693,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M103",
-              "revision": "version:103.0.5060.52"
+              "revision": "version:103.0.5060.53"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -49590,15 +49744,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
-          "--webview-apk-path=apks/SystemWebView.apk",
           "--test-runner-outdir",
           ".",
-          "--client-outdir",
-          "../../weblayer_instrumentation_test_M104/out/Release",
           "--implementation-outdir",
           ".",
           "--test-expectations",
           "../../weblayer/browser/android/javatests/skew/expectations.txt",
+          "--webview-apk-path=apks/SystemWebView.apk",
+          "--client-outdir",
+          "../../weblayer_instrumentation_test_M104/out/Release",
           "--client-version=104",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -50015,15 +50169,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
-          "--webview-apk-path=apks/SystemWebView.apk",
           "--test-runner-outdir",
           ".",
           "--client-outdir",
           ".",
-          "--implementation-outdir",
-          "../../weblayer_instrumentation_test_M103/out/Release",
           "--test-expectations",
           "../../weblayer/browser/android/javatests/skew/expectations.txt",
+          "--webview-apk-path=apks/SystemWebView.apk",
+          "--implementation-outdir",
+          "../../weblayer_instrumentation_test_M103/out/Release",
           "--impl-version=103",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -50049,7 +50203,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M103",
-              "revision": "version:103.0.5060.52"
+              "revision": "version:103.0.5060.53"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -50100,15 +50254,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
-          "--webview-apk-path=apks/SystemWebView.apk",
           "--test-runner-outdir",
           ".",
           "--client-outdir",
           ".",
-          "--implementation-outdir",
-          "../../weblayer_instrumentation_test_M104/out/Release",
           "--test-expectations",
           "../../weblayer/browser/android/javatests/skew/expectations.txt",
+          "--webview-apk-path=apks/SystemWebView.apk",
+          "--implementation-outdir",
+          "../../weblayer_instrumentation_test_M104/out/Release",
           "--impl-version=104",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -50282,10 +50436,11 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "webview_trichrome_cts_tests"
+            "webview_trichrome_cts_tests full_mode"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
+        "name": "webview_trichrome_cts_tests full_mode",
         "resultdb": {
           "enable": true,
           "has_native_resultdb_integration": true
@@ -50337,11 +50492,87 @@
               "name": "shard #${SHARD_INDEX} logcats"
             }
           ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
-          "shards": 2
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
         "test": "webview_trichrome_cts_tests",
-        "test_id_prefix": "ninja://android_webview/test:webview_trichrome_cts_tests/"
+        "test_id_prefix": "ninja://android_webview/test:webview_trichrome_cts_tests/",
+        "variant_id": "full_mode"
+      },
+      {
+        "args": [
+          "--exclude-annotation",
+          "AppModeFull",
+          "--test-apk-as-instant",
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android29.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "webview_trichrome_cts_tests instant_mode"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "name": "webview_trichrome_cts_tests instant_mode",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/android_webview/tools/cts_archive",
+              "location": "android_webview/tools/cts_archive",
+              "revision": "qF6dhyFMW7qFOzHo_Lu-bWxpbe-zRfL1KvHPQtQA3d0C"
+            },
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4|e2-standard-4",
+              "os": "Ubuntu-18.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "generic_android29",
+              "path": ".android_emulator/generic_android29"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "generic_android29"
+              }
+            ]
+          },
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "webview_trichrome_cts_tests",
+        "test_id_prefix": "ninja://android_webview/test:webview_trichrome_cts_tests/",
+        "variant_id": "instant_mode"
       }
     ]
   }
diff --git a/testing/buildbot/test_suites.pyl b/testing/buildbot/test_suites.pyl
index c612a30..c51929d 100644
--- a/testing/buildbot/test_suites.pyl
+++ b/testing/buildbot/test_suites.pyl
@@ -5364,34 +5364,12 @@
       },
     },
 
-    'webview_trichrome_64_cts_tests_gtest': {
-      'webview_trichrome_64_cts_tests': {
-        'swarming': {
-          'shards': 2,
-          'cipd_packages': [
-            {
-              "cipd_package": 'chromium/android_webview/tools/cts_archive',
-              'location': 'android_webview/tools/cts_archive',
-              'revision': 'qF6dhyFMW7qFOzHo_Lu-bWxpbe-zRfL1KvHPQtQA3d0C',
-            }
-          ]
-        },
-      },
+    'webview_trichrome_64_cts_tests': {
+      'webview_trichrome_64_cts_tests': {},
     },
 
-    'webview_trichrome_cts_tests_gtest': {
-      'webview_trichrome_cts_tests': {
-        'swarming': {
-          'shards': 2,
-          'cipd_packages': [
-            {
-              "cipd_package": 'chromium/android_webview/tools/cts_archive',
-              'location': 'android_webview/tools/cts_archive',
-              'revision': 'qF6dhyFMW7qFOzHo_Lu-bWxpbe-zRfL1KvHPQtQA3d0C',
-            }
-          ]
-        },
-      },
+    'webview_trichrome_cts_tests': {
+      'webview_trichrome_cts_tests': {},
     },
 
     'webview_ui_instrumentation_tests': {
@@ -5599,40 +5577,10 @@
       'android_ddready_vr_gtests',
     ],
 
-    'android_11_emulator_gtests': [
-      'android_emulator_specific_chrome_public_tests',
-      'android_monochrome_smoke_tests',
-      'android_smoke_tests',
-      'android_specific_chromium_gtests',  # Already includes gl_gtests.
-      'chromium_gtests',
-      'chromium_gtests_for_devices_with_graphical_output',
-      'linux_flavor_specific_chromium_gtests',
-      'system_webview_shell_instrumentation_tests', # Not an experimental test
-      'weblayer_android_gtests',
-      'weblayer_gtests',
-      'webview_trichrome_cts_tests_gtest',
-      'webview_ui_instrumentation_tests',
-    ],
-
     'android_12_dbg_emulator_gtests': [
       'android_trichrome_smoke_tests',
     ],
 
-    'android_12_emulator_gtests': [
-      'android_emulator_specific_chrome_public_tests',
-      'android_monochrome_smoke_tests',
-      'android_smoke_tests',
-      'android_specific_chromium_gtests',  # Already includes gl_gtests.
-      'chromium_gtests',
-      'chromium_gtests_for_devices_with_graphical_output',
-      'linux_flavor_specific_chromium_gtests',
-      'system_webview_shell_instrumentation_tests', # Not an experimental test
-      'weblayer_android_gtests',
-      'weblayer_gtests',
-      'webview_trichrome_64_cts_tests_gtest',
-      'webview_ui_instrumentation_tests',
-    ],
-
     # This is the same as 'android_marshmallow_gtests'
     # with the addition of 'webview_cts_tests_gtest' and
     # 'webview_ui_instrumentation_tests'
@@ -6701,6 +6649,46 @@
 
   'matrix_compound_suites': {
 
+    'android_11_emulator_gtests': {
+      'android_emulator_specific_chrome_public_tests': {},
+      'android_monochrome_smoke_tests': {},
+      'android_smoke_tests': {},
+      'android_specific_chromium_gtests': {},  # Already includes gl_gtests.
+      'chromium_gtests': {},
+      'chromium_gtests_for_devices_with_graphical_output': {},
+      'linux_flavor_specific_chromium_gtests': {},
+      'system_webview_shell_instrumentation_tests': {}, # Not an experimental test
+      'weblayer_android_gtests': {},
+      'weblayer_gtests': {},
+      'webview_trichrome_cts_tests': {
+        'variants': [
+          'WEBVIEW_TRICHROME_FULL_CTS_TESTS',
+          'WEBVIEW_TRICHROME_INSTANT_CTS_TESTS',
+        ]
+      },
+      'webview_ui_instrumentation_tests': {},
+    },
+
+    'android_12_emulator_gtests': {
+      'android_emulator_specific_chrome_public_tests': {},
+      'android_monochrome_smoke_tests': {},
+      'android_smoke_tests': {},
+      'android_specific_chromium_gtests': {},  # Already includes gl_gtests.
+      'chromium_gtests': {},
+      'chromium_gtests_for_devices_with_graphical_output': {},
+      'linux_flavor_specific_chromium_gtests': {},
+      'system_webview_shell_instrumentation_tests': {}, # Not an experimental test
+      'weblayer_android_gtests': {},
+      'weblayer_gtests': {},
+      'webview_trichrome_64_cts_tests': {
+        'variants': [
+          'WEBVIEW_TRICHROME_FULL_CTS_TESTS',
+          'WEBVIEW_TRICHROME_INSTANT_CTS_TESTS',
+        ]
+      },
+      'webview_ui_instrumentation_tests': {},
+    },
+
     'android_weblayer_x86_10_gtests': {
       'weblayer_instrumentation_tests': {
         'variants': [
@@ -7591,6 +7579,15 @@
       },
     },
 
+    'webview_trichrome_10_cts_tests_gtest': {
+      'webview_trichrome_cts_tests': {
+        'variants': [
+          'WEBVIEW_TRICHROME_FULL_CTS_TESTS',
+          'WEBVIEW_TRICHROME_INSTANT_CTS_TESTS',
+        ]
+      }
+    },
+
     'win_optional_gpu_tests_rel_gpu_telemetry_tests': {
       'gpu_common_and_optional_telemetry_tests': {
         'variants': [
diff --git a/testing/buildbot/variants.pyl b/testing/buildbot/variants.pyl
index 8497fbe..864399af 100644
--- a/testing/buildbot/variants.pyl
+++ b/testing/buildbot/variants.pyl
@@ -460,18 +460,48 @@
       'ios_runtime_cache_16_0',
     ],
   },
+  'WEBVIEW_TRICHROME_FULL_CTS_TESTS': {
+    'identifier': 'full_mode',
+    'swarming': {
+      'cipd_packages': [
+        {
+          'cipd_package': 'chromium/android_webview/tools/cts_archive',
+          'location': 'android_webview/tools/cts_archive',
+          'revision': 'qF6dhyFMW7qFOzHo_Lu-bWxpbe-zRfL1KvHPQtQA3d0C',
+        }
+      ]
+    },
+  },
+  'WEBVIEW_TRICHROME_INSTANT_CTS_TESTS': {
+    'identifier': 'instant_mode',
+    'swarming': {
+      'cipd_packages': [
+        {
+          'cipd_package': 'chromium/android_webview/tools/cts_archive',
+          'location': 'android_webview/tools/cts_archive',
+          'revision': 'qF6dhyFMW7qFOzHo_Lu-bWxpbe-zRfL1KvHPQtQA3d0C',
+        }
+      ]
+    },
+    'args': [
+      '--exclude-annotation',
+      'AppModeFull',
+      '--test-apk-as-instant',
+    ],
+    'test': 'webview_trichrome_cts_tests',
+  },
   'WEBLAYER_10_AND_M_IMPL_SKEW_TESTS_NTH_MILESTONE': {
     'args': [
-      '--webview-apk-path=apks/AOSP_SystemWebView.apk',
       '--test-runner-outdir',
       '.',
       '--client-outdir',
       '.',
-      '--implementation-outdir',
-      '../../weblayer_instrumentation_test_M104/out/Release',
       '--test-expectations',
       '../../weblayer/browser/android/javatests/skew/expectations.txt',
-      '--impl-version=104',
+      '--webview-apk-path=apks/AOSP_SystemWebView.apk',
+      '--implementation-outdir',
+      '../../weblayer_instrumentation_test_M104/out/Release',
+      '--impl-version=104'
     ],
     'identifier': 'with_impl_from_104',
     'swarming': {
@@ -479,23 +509,23 @@
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M104',
-          'revision': 'version:104.0.5112.8',
+          'revision': 'version:104.0.5112.8'
         }
-      ],
-    },
+      ]
+    }
   },
   'WEBLAYER_10_AND_M_IMPL_SKEW_TESTS_NTH_MINUS_ONE_MILESTONE': {
     'args': [
-      '--webview-apk-path=apks/AOSP_SystemWebView.apk',
       '--test-runner-outdir',
       '.',
       '--client-outdir',
       '.',
-      '--implementation-outdir',
-      '../../weblayer_instrumentation_test_M103/out/Release',
       '--test-expectations',
       '../../weblayer/browser/android/javatests/skew/expectations.txt',
-      '--impl-version=103',
+      '--webview-apk-path=apks/AOSP_SystemWebView.apk',
+      '--implementation-outdir',
+      '../../weblayer_instrumentation_test_M103/out/Release',
+      '--impl-version=103'
     ],
     'identifier': 'with_impl_from_103',
     'swarming': {
@@ -503,10 +533,10 @@
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M103',
-          'revision': 'version:103.0.5060.52',
+          'revision': 'version:103.0.5060.53'
         }
-      ],
-    },
+      ]
+    }
   },
   'WEBLAYER_10_AND_M_IMPL_SKEW_TESTS_NTH_MINUS_TWO_MILESTONE': {
     'args': [
@@ -606,16 +636,16 @@
   },
   'WEBLAYER_IMPL_SKEW_TESTS_NTH_MILESTONE': {
     'args': [
-      '--webview-apk-path=apks/SystemWebView.apk',
       '--test-runner-outdir',
       '.',
       '--client-outdir',
       '.',
-      '--implementation-outdir',
-      '../../weblayer_instrumentation_test_M104/out/Release',
       '--test-expectations',
       '../../weblayer/browser/android/javatests/skew/expectations.txt',
-      '--impl-version=104',
+      '--webview-apk-path=apks/SystemWebView.apk',
+      '--implementation-outdir',
+      '../../weblayer_instrumentation_test_M104/out/Release',
+      '--impl-version=104'
     ],
     'identifier': 'with_impl_from_104',
     'swarming': {
@@ -623,23 +653,23 @@
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M104',
-          'revision': 'version:104.0.5112.8',
+          'revision': 'version:104.0.5112.8'
         }
-      ],
-    },
+      ]
+    }
   },
   'WEBLAYER_IMPL_SKEW_TESTS_NTH_MINUS_ONE_MILESTONE': {
     'args': [
-      '--webview-apk-path=apks/SystemWebView.apk',
       '--test-runner-outdir',
       '.',
       '--client-outdir',
       '.',
-      '--implementation-outdir',
-      '../../weblayer_instrumentation_test_M103/out/Release',
       '--test-expectations',
       '../../weblayer/browser/android/javatests/skew/expectations.txt',
-      '--impl-version=103',
+      '--webview-apk-path=apks/SystemWebView.apk',
+      '--implementation-outdir',
+      '../../weblayer_instrumentation_test_M103/out/Release',
+      '--impl-version=103'
     ],
     'identifier': 'with_impl_from_103',
     'swarming': {
@@ -647,10 +677,10 @@
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M103',
-          'revision': 'version:103.0.5060.52',
+          'revision': 'version:103.0.5060.53'
         }
-      ],
-    },
+      ]
+    }
   },
   'WEBLAYER_IMPL_SKEW_TESTS_NTH_MINUS_TWO_MILESTONE': {
     'args': [
@@ -750,16 +780,16 @@
   },
   'WEBLAYER_CLIENT_SKEW_TESTS_NTH_MILESTONE': {
     'args': [
-      '--webview-apk-path=apks/SystemWebView.apk',
       '--test-runner-outdir',
       '.',
-      '--client-outdir',
-      '../../weblayer_instrumentation_test_M104/out/Release',
       '--implementation-outdir',
       '.',
       '--test-expectations',
       '../../weblayer/browser/android/javatests/skew/expectations.txt',
-      '--client-version=104',
+      '--webview-apk-path=apks/SystemWebView.apk',
+      '--client-outdir',
+      '../../weblayer_instrumentation_test_M104/out/Release',
+      '--client-version=104'
     ],
     'identifier': 'with_client_from_104',
     'swarming': {
@@ -767,23 +797,23 @@
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M104',
-          'revision': 'version:104.0.5112.8',
+          'revision': 'version:104.0.5112.8'
         }
-      ],
-    },
+      ]
+    }
   },
   'WEBLAYER_CLIENT_SKEW_TESTS_NTH_MINUS_ONE_MILESTONE': {
     'args': [
-      '--webview-apk-path=apks/SystemWebView.apk',
       '--test-runner-outdir',
       '.',
-      '--client-outdir',
-      '../../weblayer_instrumentation_test_M103/out/Release',
       '--implementation-outdir',
       '.',
       '--test-expectations',
       '../../weblayer/browser/android/javatests/skew/expectations.txt',
-      '--client-version=103',
+      '--webview-apk-path=apks/SystemWebView.apk',
+      '--client-outdir',
+      '../../weblayer_instrumentation_test_M103/out/Release',
+      '--client-version=103'
     ],
     'identifier': 'with_client_from_103',
     'swarming': {
@@ -791,10 +821,10 @@
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M103',
-          'revision': 'version:103.0.5060.52',
+          'revision': 'version:103.0.5060.53'
         }
-      ],
-    },
+      ]
+    }
   },
   'WEBLAYER_CLIENT_SKEW_TESTS_NTH_MINUS_TWO_MILESTONE': {
     'args': [
diff --git a/testing/buildbot/waterfalls.pyl b/testing/buildbot/waterfalls.pyl
index 89505c16..9b56e1a 100644
--- a/testing/buildbot/waterfalls.pyl
+++ b/testing/buildbot/waterfalls.pyl
@@ -1104,7 +1104,7 @@
         ],
         'os_type': 'android',
         'test_suites': {
-          'gtest_tests': 'webview_trichrome_cts_tests_gtest',
+          'gtest_tests': 'webview_trichrome_10_cts_tests_gtest',
         },
         'use_swarming': True,
       },
diff --git a/third_party/blink/renderer/core/html/forms/resources/calendar_picker.js b/third_party/blink/renderer/core/html/forms/resources/calendar_picker.js
index 371591d..bc92bd5 100644
--- a/third_party/blink/renderer/core/html/forms/resources/calendar_picker.js
+++ b/third_party/blink/renderer/core/html/forms/resources/calendar_picker.js
@@ -1525,6 +1525,8 @@
 
     this._onWindowTouchMoveBound = this.onWindowTouchMove.bind(this);
     this._onWindowTouchEndBound = this.onWindowTouchEnd.bind(this);
+    this._onFlingGestureAnimatorStepBound =
+        this.onFlingGestureAnimatorStep.bind(this);
 
     this.element.addEventListener(
         'mousewheel', this.onMouseWheel.bind(this), false);
@@ -1580,7 +1582,7 @@
     if (Math.abs(this._lastTouchVelocity) > 0.01) {
       this._scrollAnimator = new FlingGestureAnimator(
           this._lastTouchVelocity, this._contentOffset);
-      this._scrollAnimator.step = this.onFlingGestureAnimatorStep;
+      this._scrollAnimator.step = this._onFlingGestureAnimatorStepBound;
       this._scrollAnimator.start();
     }
     window.removeEventListener('touchmove', this._onWindowTouchMoveBound);
diff --git a/third_party/blink/renderer/core/layout/list_marker.cc b/third_party/blink/renderer/core/layout/list_marker.cc
index 7f2b47e..47b82da 100644
--- a/third_party/blink/renderer/core/layout/list_marker.cc
+++ b/third_party/blink/renderer/core/layout/list_marker.cc
@@ -404,7 +404,7 @@
         margin_start = -marker_inline_size;
     }
   }
-  DCHECK_EQ(margin_start + margin_end, -marker_inline_size);
+  DCHECK_EQ(-margin_start - margin_end, marker_inline_size);
   return {margin_start, margin_end};
 }
 
diff --git a/third_party/blink/renderer/core/layout/list_marker_test.cc b/third_party/blink/renderer/core/layout/list_marker_test.cc
index b2cf869..de9c4569 100644
--- a/third_party/blink/renderer/core/layout/list_marker_test.cc
+++ b/third_party/blink/renderer/core/layout/list_marker_test.cc
@@ -321,4 +321,23 @@
             ListMarker::WidthOfSymbol(target_layout_object.StyleRef()));
 }
 
+// crbug.com/1310599
+TEST_F(ListMarkerTest, InlineMarginsForOutside) {
+  GetDocument().body()->setInnerHTML(
+      R"HTML(<details open><summary id="target" style="
+  font-size: 536870912px;
+  zoom: 65536;
+  list-style-position: outside;
+  ">foo</summary></details>)HTML",
+      ASSERT_NO_EXCEPTION);
+  GetDocument().UpdateStyleAndLayoutTree();
+  auto* item_object = GetLayoutObjectByElementId("target");
+  auto* marker_object = ListMarker::MarkerFromListItem(item_object);
+  auto [start, end] = ListMarker::InlineMarginsForOutside(
+      GetDocument(), marker_object->StyleRef(), item_object->StyleRef(),
+      LayoutUnit::Max());
+  EXPECT_EQ(LayoutUnit::Min(), start);
+  EXPECT_EQ(LayoutUnit(), end);
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/paint/paint_layer.cc b/third_party/blink/renderer/core/paint/paint_layer.cc
index b1e066e2..8d20b85 100644
--- a/third_party/blink/renderer/core/paint/paint_layer.cc
+++ b/third_party/blink/renderer/core/paint/paint_layer.cc
@@ -1211,21 +1211,6 @@
   const auto& first_root_fragment_data =
       root_layer->GetLayoutObject().FirstFragment();
 
-  // If both |this| and |root_layer| are fragmented and are inside the same
-  // pagination container, then try to match fragments from |root_layer| to
-  // |this|, so that any fragment clip for |root_layer|'s fragment matches
-  // |this|'s. Note we check both EnclosingPaginationLayer() and next
-  // fragment here because the former may return false even if |this| is
-  // fragmented, e.g. for fixed-position objects in paged media, and the next
-  // fragment can be null even if the first fragment is actually in a fragmented
-  // context when the current layer appears in only one of the multiple
-  // fragments of the pagination container.
-  bool is_fragmented =
-      EnclosingPaginationLayer() || first_fragment_data.NextFragment();
-  bool should_match_fragments =
-      is_fragmented &&
-      root_layer->EnclosingPaginationLayer() == EnclosingPaginationLayer();
-
   const LayoutBox* layout_box_with_fragments = GetLayoutBoxWithBlockFragments();
 
   // The inherited offset_from_root does not include any pagination offsets.
@@ -1245,24 +1230,10 @@
       root_fragment_data = root_fragment_arg;
     } else if (root_layer == this) {
       root_fragment_data = fragment_data;
-    } else if (should_match_fragments) {
-      for (root_fragment_data = &first_root_fragment_data; root_fragment_data;
-           root_fragment_data = root_fragment_data->NextFragment()) {
-        if (root_fragment_data->FragmentID() == fragment_data->FragmentID())
-          break;
-      }
     } else {
       root_fragment_data = &first_root_fragment_data;
     }
 
-    bool cant_find_fragment = !root_fragment_data;
-    if (cant_find_fragment) {
-      DCHECK(should_match_fragments);
-      // Fall back to the first fragment, in order to have
-      // PaintLayerClipper at least compute |fragment.layer_bounds|.
-      root_fragment_data = &first_root_fragment_data;
-    }
-
     ClipRectsContext clip_rects_context(
         root_layer, root_fragment_data,
         kExcludeOverlayScrollbarSizeForHitTesting, respect_overflow_clip,
@@ -1273,13 +1244,6 @@
                         fragment.layer_offset, fragment.background_rect,
                         fragment.foreground_rect);
 
-    if (cant_find_fragment) {
-      // If we couldn't find a matching fragment when |should_match_fragments|
-      // was true, then fall back to no clip.
-      fragment.background_rect.Reset();
-      fragment.foreground_rect.Reset();
-    }
-
     fragment.fragment_data = fragment_data;
 
     if (layout_box_with_fragments) {
diff --git a/third_party/blink/renderer/modules/mediastream/browser_capture_media_stream_track.cc b/third_party/blink/renderer/modules/mediastream/browser_capture_media_stream_track.cc
index 5fb6a90c..4f0edb2 100644
--- a/third_party/blink/renderer/modules/mediastream/browser_capture_media_stream_track.cc
+++ b/third_party/blink/renderer/modules/mediastream/browser_capture_media_stream_track.cc
@@ -183,54 +183,34 @@
     return promise;
   }
 
-  MediaStreamComponent* const component = Component();
-  DCHECK(component);
-
-  MediaStreamSource* const source = component->Source();
-  DCHECK(component->Source());
   // We don't currently instantiate BrowserCaptureMediaStreamTrack for audio
   // tracks. If we do in the future, we'll have to raise an exception if
   // cropTo() is called on a non-video track.
+  DCHECK(Component());
+  DCHECK(Component()->Source());
+  MediaStreamSource* const source = Component()->Source();
   DCHECK_EQ(source->GetType(), MediaStreamSource::kTypeVideo);
-
   MediaStreamVideoSource* const native_source =
       MediaStreamVideoSource::GetVideoSource(source);
-  MediaStreamTrackPlatform* const native_track =
-      MediaStreamTrackPlatform::GetTrack(WebMediaStreamTrack(component));
-  if (!native_source || !native_track) {
-    // TODO(crbug.com/1266378): Use dedicate UMA values.
-    RecordUma(CropToResult::kRejectedWithErrorGeneric);
-    resolver->Reject(MakeGarbageCollected<DOMException>(
-        DOMExceptionCode::kUnknownError, "Native/platform track missing."));
-    return promise;
-  }
+  DCHECK(native_source);
 
   // TODO(crbug.com/1332628): Instead of using GetNextCropVersion(), move the
   // ownership of the Promises from this->pending_promises_ into native_source.
-  const absl::optional<uint32_t> optional_crop_version =
+  const absl::optional<uint32_t> crop_version =
       native_source->GetNextCropVersion();
-  if (!optional_crop_version.has_value()) {
+  if (!crop_version.has_value()) {
     resolver->Reject(MakeGarbageCollected<DOMException>(
         DOMExceptionCode::kOperationError,
         "Can't change crop-target while clones exist."));
     return promise;
   }
-  const uint32_t crop_version = optional_crop_version.value();
 
-  pending_promises_.Set(crop_version,
-                        MakeGarbageCollected<CropPromiseInfo>(resolver));
-
-  // Register for a one-off notification when the first frame cropped
-  // to the new crop-target is observed.
-  native_track->AddCropVersionCallback(
-      crop_version,
-      WTF::Bind(&BrowserCaptureMediaStreamTrack::OnCropVersionObserved,
-                WrapWeakPersistent(this), crop_version));
+  pending_promises_.Set(crop_version.value(), resolver);
 
   native_source->Crop(
-      crop_id_token.value(), crop_version,
-      WTF::Bind(&BrowserCaptureMediaStreamTrack::OnResultFromBrowserProcess,
-                WrapWeakPersistent(this), crop_version));
+      crop_id_token.value(), crop_version.value(),
+      WTF::Bind(&BrowserCaptureMediaStreamTrack::ResolveCropPromise,
+                WrapPersistent(this), crop_version.value()));
 
   return promise;
 #endif
@@ -252,74 +232,15 @@
 }
 
 #if !BUILDFLAG(IS_ANDROID)
-void BrowserCaptureMediaStreamTrack::OnResultFromBrowserProcess(
+void BrowserCaptureMediaStreamTrack::ResolveCropPromise(
     uint32_t crop_version,
     media::mojom::CropRequestResult result) {
-  DCHECK(IsMainThread());
-  DCHECK_GT(crop_version, 0u);
-
-  const auto iter = pending_promises_.find(crop_version);
-  if (iter == pending_promises_.end()) {
+  const auto promise_it = pending_promises_.find(crop_version);
+  if (promise_it == pending_promises_.end()) {
     return;
   }
-  CropPromiseInfo* const info = iter->value;
-
-  DCHECK(!info->crop_result.has_value()) << "Invoked twice.";
-  info->crop_result = result;
-
-  MaybeFinalizeCropPromise(iter);
-}
-
-void BrowserCaptureMediaStreamTrack::OnCropVersionObserved(
-    uint32_t crop_version) {
-  DCHECK(IsMainThread());
-  DCHECK_GT(crop_version, 0u);
-
-  const auto iter = pending_promises_.find(crop_version);
-  if (iter == pending_promises_.end()) {
-    return;
-  }
-  CropPromiseInfo* const info = iter->value;
-
-  DCHECK(!info->crop_version_observed) << "Invoked twice.";
-  info->crop_version_observed = true;
-
-  MaybeFinalizeCropPromise(iter);
-}
-
-void BrowserCaptureMediaStreamTrack::MaybeFinalizeCropPromise(
-    BrowserCaptureMediaStreamTrack::PromiseMapIterator iter) {
-  DCHECK(IsMainThread());
-  DCHECK_NE(iter, pending_promises_.end());
-
-  CropPromiseInfo* const info = iter->value;
-
-  if (!info->crop_result.has_value()) {
-    return;
-  }
-
-  const media::mojom::CropRequestResult result = info->crop_result.value();
-
-  // Failure can be reported immediately, but success is only reported once
-  // the new crop-version is observed.
-  if (result == media::mojom::CropRequestResult::kSuccess &&
-      !info->crop_version_observed) {
-    return;
-  }
-
-  // When `result == kSuccess`, the callback will be removed by the track
-  // itself as it invokes it. For failure, we remove the callback immediately,
-  // since there's no need to wait.
-  if (result != media::mojom::CropRequestResult::kSuccess) {
-    MediaStreamTrackPlatform* const native_track =
-        MediaStreamTrackPlatform::GetTrack(WebMediaStreamTrack(Component()));
-    if (native_track) {
-      native_track->RemoveCropVersionCallback(iter->key);
-    }
-  }
-
-  ScriptPromiseResolver* const resolver = info->promise_resolver;
-  pending_promises_.erase(iter);
+  ScriptPromiseResolver* const resolver = promise_it->value;
+  pending_promises_.erase(promise_it);
   ResolveCropPromiseHelper(resolver, result);
 }
 #endif  // !BUILDFLAG(IS_ANDROID)
diff --git a/third_party/blink/renderer/modules/mediastream/browser_capture_media_stream_track.h b/third_party/blink/renderer/modules/mediastream/browser_capture_media_stream_track.h
index 452d86a..05021fa 100644
--- a/third_party/blink/renderer/modules/mediastream/browser_capture_media_stream_track.h
+++ b/third_party/blink/renderer/modules/mediastream/browser_capture_media_stream_track.h
@@ -5,7 +5,6 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_MEDIASTREAM_BROWSER_CAPTURE_MEDIA_STREAM_TRACK_H_
 #define THIRD_PARTY_BLINK_RENDERER_MODULES_MEDIASTREAM_BROWSER_CAPTURE_MEDIA_STREAM_TRACK_H_
 
-#include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
 #include "third_party/blink/renderer/core/dom/dom_exception.h"
 #include "third_party/blink/renderer/modules/mediastream/crop_target.h"
 #include "third_party/blink/renderer/modules/mediastream/focusable_media_stream_track.h"
@@ -32,16 +31,8 @@
                                  const String& descriptor_id,
                                  bool is_clone = false);
 
-  ~BrowserCaptureMediaStreamTrack() override = default;
-
 #if !BUILDFLAG(IS_ANDROID)
   void Trace(Visitor*) const override;
-
-  // Allows tests to invoke OnCropVersionObserved() directly, since triggering
-  // it via mocks would be prohibitively difficult.
-  void OnCropVersionObservedForTesting(uint32_t crop_version) {
-    OnCropVersionObserved(crop_version);
-  }
 #endif
 
   ScriptPromise cropTo(ScriptState*, CropTarget*, ExceptionState&);
@@ -50,48 +41,9 @@
 
  private:
 #if !BUILDFLAG(IS_ANDROID)
-  struct CropPromiseInfo : GarbageCollected<CropPromiseInfo> {
-    explicit CropPromiseInfo(ScriptPromiseResolver* promise_resolver)
-        : promise_resolver(promise_resolver) {}
-
-    void Trace(Visitor* visitor) const { visitor->Trace(promise_resolver); }
-
-    const Member<ScriptPromiseResolver> promise_resolver;
-    absl::optional<media::mojom::CropRequestResult> crop_result;
-    bool crop_version_observed = false;
-  };
-
-  using CropVersionToPromiseInfoMap =
-      HeapHashMap<uint32_t,
-                  Member<BrowserCaptureMediaStreamTrack::CropPromiseInfo>>;
-  using PromiseMapIterator = CropVersionToPromiseInfoMap::iterator;
-
-  // Each cropTo() call is associated with a unique |crop_version| which
-  // identifies this specific cropTo() invocation. When the browser process
-  // responds with the result of the cropTo() invocation, it triggers
-  // a call to OnResultFromBrowserProcess() with that |crop_version|.
-  void OnResultFromBrowserProcess(uint32_t crop_version,
-                                  media::mojom::CropRequestResult result);
-
-  // OnCropVersionObserved() is posted as a callback, bound to a unique
-  // |crop_version|. This callback be invoked when the first frame is observed
-  // which is associated with that |crop_version|.
-  // TODO(crbug.com/1266378): The Promise should also be resolved if a
-  // a barrier event is observed. (That is, although no frame is delivered,
-  // there is a guarantee that all future frames will be of this version
-  // or later. This would happen if cropping a muted track, for instance.)
-  void OnCropVersionObserved(uint32_t crop_version);
-
-  // The Promise that cropTo() issued is resolved when both conditions
-  // are fulfulled:
-  // 1. OnResultFromBrowserProcess(kSuccess) called.
-  // 2. OnCropVersionObserved() called for the associated |crop_version|.
-  //
-  // The order of fulfillment does not matter.
-  //
-  // The Promise is rejected if OnResultFromBrowserProcess() is called with
-  // an error value.
-  void MaybeFinalizeCropPromise(PromiseMapIterator iter);
+  // Resolves the Promise associated with |crop_version|.
+  void ResolveCropPromise(uint32_t crop_version,
+                          media::mojom::CropRequestResult result);
 
   // Each time cropTo() is called on a given track, its crop version increments.
   // Associate each Promise with its crop version, so that Viz can easily stamp
@@ -102,7 +54,7 @@
   //
   // Note that frames before the first call to cropTo() will be associated
   // with a version of 0, both here and in Viz.
-  HeapHashMap<uint32_t, Member<CropPromiseInfo>> pending_promises_;
+  HeapHashMap<uint32_t, Member<ScriptPromiseResolver>> pending_promises_;
 #endif  // !BUILDFLAG(IS_ANDROID)
 };
 
diff --git a/third_party/blink/renderer/modules/mediastream/browser_capture_media_stream_track_test.cc b/third_party/blink/renderer/modules/mediastream/browser_capture_media_stream_track_test.cc
index abb11de..1d49522 100644
--- a/third_party/blink/renderer/modules/mediastream/browser_capture_media_stream_track_test.cc
+++ b/third_party/blink/renderer/modules/mediastream/browser_capture_media_stream_track_test.cc
@@ -8,22 +8,16 @@
 #include "build/build_config.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/web/web_heap.h"
-#include "third_party/blink/renderer/bindings/core/v8/script_promise_tester.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_testing.h"
 #include "third_party/blink/renderer/modules/mediastream/crop_target.h"
-#include "third_party/blink/renderer/modules/mediastream/media_stream_video_track.h"
 #include "third_party/blink/renderer/modules/mediastream/mock_media_stream_video_source.h"
 #include "third_party/blink/renderer/platform/region_capture_crop_id.h"
-#include "third_party/blink/renderer/platform/testing/io_task_runner_testing_platform_support.h"
 
 namespace blink {
 
 namespace {
 
 using ::testing::_;
-using ::testing::Args;
-using ::testing::Invoke;
-using ::testing::Mock;
 using ::testing::Return;
 
 std::unique_ptr<MockMediaStreamVideoSource> MakeMockMediaStreamVideoSource() {
@@ -36,18 +30,12 @@
 BrowserCaptureMediaStreamTrack* MakeTrack(
     V8TestingScope& v8_scope,
     std::unique_ptr<MockMediaStreamVideoSource> media_stream_video_source) {
-  auto media_stream_video_track = std::make_unique<MediaStreamVideoTrack>(
-      media_stream_video_source.get(),
-      WebPlatformMediaStreamSource::ConstraintsOnceCallback(),
-      /*enabled=*/true);
-
   MediaStreamSource* const source = MakeGarbageCollected<MediaStreamSource>(
       "id", MediaStreamSource::StreamType::kTypeVideo, "name",
       /*remote=*/false, std::move(media_stream_video_source));
 
   MediaStreamComponent* const component =
-      MakeGarbageCollected<MediaStreamComponent>(
-          "component_id", source, std::move(media_stream_video_track));
+      MakeGarbageCollected<MediaStreamComponent>(source);
 
   return MakeGarbageCollected<BrowserCaptureMediaStreamTrack>(
       v8_scope.GetExecutionContext(), component, /*callback=*/base::DoNothing(),
@@ -58,16 +46,13 @@
 
 class BrowserCaptureMediaStreamTrackTest : public testing::Test {
  public:
-  ~BrowserCaptureMediaStreamTrackTest() override = default;
-
-  void TearDown() override { WebHeap::CollectAllGarbageForTesting(); }
-
- protected:
-  ScopedTestingPlatformSupport<IOTaskRunnerTestingPlatformSupport> platform_;
+  ~BrowserCaptureMediaStreamTrackTest() override {
+    WebHeap::CollectAllGarbageForTesting();
+  }
 };
 
 #if !BUILDFLAG(IS_ANDROID)
-TEST_F(BrowserCaptureMediaStreamTrackTest, CropToOnValidIdResultFirst) {
+TEST_F(BrowserCaptureMediaStreamTrackTest, CropToOnValidId) {
   V8TestingScope v8_scope;
 
   const base::GUID valid_id = base::GUID::GenerateRandomV4();
@@ -80,11 +65,7 @@
       .WillOnce(Return(absl::optional<uint32_t>(1)));
 
   EXPECT_CALL(*media_stream_video_source, Crop(GUIDToToken(valid_id), _, _))
-      .Times(1)
-      .WillOnce(::testing::WithArg<2>(::testing::Invoke(
-          [](base::OnceCallback<void(media::mojom::CropRequestResult)> cb) {
-            std::move(cb).Run(media::mojom::CropRequestResult::kSuccess);
-          })));
+      .Times(1);
 
   BrowserCaptureMediaStreamTrack* const track =
       MakeTrack(v8_scope, std::move(media_stream_video_source));
@@ -94,78 +75,6 @@
                     MakeGarbageCollected<CropTarget>(
                         WTF::String(valid_id.AsLowercaseString())),
                     v8_scope.GetExceptionState());
-
-  track->OnCropVersionObservedForTesting(/*crop_version=*/1);
-
-  ScriptPromiseTester script_promise_tester(v8_scope.GetScriptState(), promise);
-  script_promise_tester.WaitUntilSettled();
-  EXPECT_TRUE(script_promise_tester.IsFulfilled());
-}
-
-TEST_F(BrowserCaptureMediaStreamTrackTest,
-       CropToRejectsIfResultFromBrowserProcessIsNotSuccess) {
-  V8TestingScope v8_scope;
-
-  const base::GUID valid_id = base::GUID::GenerateRandomV4();
-
-  std::unique_ptr<MockMediaStreamVideoSource> media_stream_video_source =
-      MakeMockMediaStreamVideoSource();
-
-  EXPECT_CALL(*media_stream_video_source, GetNextCropVersion)
-      .Times(1)
-      .WillOnce(Return(absl::optional<uint32_t>(1)));
-
-  EXPECT_CALL(*media_stream_video_source, Crop(GUIDToToken(valid_id), _, _))
-      .Times(1)
-      .WillOnce(::testing::WithArg<2>(::testing::Invoke(
-          [](base::OnceCallback<void(media::mojom::CropRequestResult)> cb) {
-            std::move(cb).Run(media::mojom::CropRequestResult::kErrorGeneric);
-          })));
-
-  BrowserCaptureMediaStreamTrack* const track =
-      MakeTrack(v8_scope, std::move(media_stream_video_source));
-
-  const ScriptPromise promise =
-      track->cropTo(v8_scope.GetScriptState(),
-                    MakeGarbageCollected<CropTarget>(
-                        WTF::String(valid_id.AsLowercaseString())),
-                    v8_scope.GetExceptionState());
-
-  track->OnCropVersionObservedForTesting(/*crop_version=*/1);
-
-  ScriptPromiseTester script_promise_tester(v8_scope.GetScriptState(), promise);
-  script_promise_tester.WaitUntilSettled();
-  EXPECT_TRUE(script_promise_tester.IsRejected());
-}
-
-TEST_F(BrowserCaptureMediaStreamTrackTest,
-       CropToRejectsIfSourceReturnsNulloptForNextCropVersion) {
-  V8TestingScope v8_scope;
-
-  const base::GUID valid_id = base::GUID::GenerateRandomV4();
-
-  std::unique_ptr<MockMediaStreamVideoSource> media_stream_video_source =
-      MakeMockMediaStreamVideoSource();
-
-  EXPECT_CALL(*media_stream_video_source, GetNextCropVersion)
-      .Times(1)
-      .WillOnce(Return(absl::nullopt));
-
-  EXPECT_CALL(*media_stream_video_source, Crop(GUIDToToken(valid_id), _, _))
-      .Times(0);
-
-  BrowserCaptureMediaStreamTrack* const track =
-      MakeTrack(v8_scope, std::move(media_stream_video_source));
-
-  const ScriptPromise promise =
-      track->cropTo(v8_scope.GetScriptState(),
-                    MakeGarbageCollected<CropTarget>(
-                        WTF::String(valid_id.AsLowercaseString())),
-                    v8_scope.GetExceptionState());
-
-  ScriptPromiseTester script_promise_tester(v8_scope.GetScriptState(), promise);
-  script_promise_tester.WaitUntilSettled();
-  EXPECT_TRUE(script_promise_tester.IsRejected());
 }
 
 #else
@@ -188,10 +97,7 @@
                     MakeGarbageCollected<CropTarget>(
                         WTF::String(valid_id.AsLowercaseString())),
                     v8_scope.GetExceptionState());
-
-  ScriptPromiseTester script_promise_tester(v8_scope.GetScriptState(), promise);
-  script_promise_tester.WaitUntilSettled();
-  EXPECT_TRUE(script_promise_tester.IsRejected());
+  EXPECT_EQ(promise.V8Promise()->State(), v8::Promise::kRejected);
 }
 #endif
 
diff --git a/third_party/blink/renderer/modules/mediastream/media_stream_video_track.cc b/third_party/blink/renderer/modules/mediastream/media_stream_video_track.cc
index 61e5294e..a48c2c6 100644
--- a/third_party/blink/renderer/modules/mediastream/media_stream_video_track.cc
+++ b/third_party/blink/renderer/modules/mediastream/media_stream_video_track.cc
@@ -124,10 +124,6 @@
 
   void SetIsRefreshingForMinFrameRate(bool is_refreshing_for_min_frame_rate);
 
-  void AddCropVersionCallback(uint32_t crop_version,
-                              base::OnceClosure callback);
-  void RemoveCropVersionCallback(uint32_t crop_version);
-
  private:
   friend class WTF::ThreadSafeRefCounted<FrameDeliverer>;
   virtual ~FrameDeliverer();
@@ -148,12 +144,6 @@
   void SetIsRefreshingForMinFrameRateOnIO(
       bool is_refreshing_for_min_frame_rate);
 
-  void AddCropVersionCallbackOnIO(uint32_t crop_version,
-                                  WTF::CrossThreadOnceClosure callback);
-  void RemoveCropVersionCallbackOnIO(uint32_t crop_version);
-
-  void MaybeInvokeNewCropVersionCallbacksOnIO(uint32_t crop_version);
-
   // Returns a black frame where the size and time stamp is set to the same as
   // as in |reference_frame|.
   scoped_refptr<media::VideoFrame> GetBlackFrame(
@@ -175,13 +165,6 @@
       std::pair<VideoSinkId, VideoCaptureDeliverFrameInternalCallback>;
   Vector<VideoIdCallbackPair> callbacks_;
   HashMap<VideoSinkId, EncodedVideoFrameInternalCallback> encoded_callbacks_;
-
-  // Callbacks that will be invoked a single time when a crop-version
-  // is observed that is at least equal to the key.
-  // The map itself (crop_version_callbacks_) is bound to the IO thread.
-  // The callbacks are bound to their respective threads (BindPostTask).
-  HashMap<uint32_t, WTF::CrossThreadOnceClosure> crop_version_callbacks_;
-
   bool await_next_key_frame_;
 
   // This should only be accessed on the IO thread.
@@ -329,76 +312,18 @@
                           is_refreshing_for_min_frame_rate));
 }
 
-void MediaStreamVideoTrack::FrameDeliverer::AddCropVersionCallback(
-    uint32_t crop_version,
-    base::OnceClosure callback) {
-  DCHECK_CALLED_ON_VALID_THREAD(main_render_thread_checker_);
-
-  PostCrossThreadTask(
-      *io_task_runner_, FROM_HERE,
-      CrossThreadBindOnce(&FrameDeliverer::AddCropVersionCallbackOnIO,
-                          WrapRefCounted(this), crop_version,
-                          CrossThreadBindOnce(std::move(callback))));
-}
-
-void MediaStreamVideoTrack::FrameDeliverer::RemoveCropVersionCallback(
-    uint32_t crop_version) {
-  DCHECK_CALLED_ON_VALID_THREAD(main_render_thread_checker_);
-
-  PostCrossThreadTask(
-      *io_task_runner_, FROM_HERE,
-      CrossThreadBindOnce(&FrameDeliverer::RemoveCropVersionCallbackOnIO,
-                          WrapRefCounted(this), crop_version));
-}
-
 void MediaStreamVideoTrack::FrameDeliverer::SetIsRefreshingForMinFrameRateOnIO(
     bool is_refreshing_for_min_frame_rate) {
   DCHECK(io_task_runner_->BelongsToCurrentThread());
   is_refreshing_for_min_frame_rate_ = is_refreshing_for_min_frame_rate;
 }
 
-void MediaStreamVideoTrack::FrameDeliverer::AddCropVersionCallbackOnIO(
-    uint32_t crop_version,
-    WTF::CrossThreadOnceClosure callback) {
-  DCHECK(io_task_runner_->BelongsToCurrentThread());
-  DCHECK(!base::Contains(crop_version_callbacks_, crop_version));
-
-  crop_version_callbacks_.Set(crop_version, std::move(callback));
-}
-
-void MediaStreamVideoTrack::FrameDeliverer::RemoveCropVersionCallbackOnIO(
-    uint32_t crop_version) {
-  DCHECK(io_task_runner_->BelongsToCurrentThread());
-
-  // Note: Might or might not be here, depending on whether a later crop
-  // version has already been observed or not.
-  crop_version_callbacks_.erase(crop_version);
-}
-
-void MediaStreamVideoTrack::FrameDeliverer::
-    MaybeInvokeNewCropVersionCallbacksOnIO(uint32_t crop_version) {
-  DCHECK(io_task_runner_->BelongsToCurrentThread());
-
-  Vector<uint32_t> to_be_removed_keys;
-  for (auto& iter : crop_version_callbacks_) {
-    if (iter.key > crop_version) {
-      continue;
-    }
-    std::move(iter.value).Run();
-    to_be_removed_keys.push_back(iter.key);
-  }
-  crop_version_callbacks_.RemoveAll(to_be_removed_keys);
-}
-
 void MediaStreamVideoTrack::FrameDeliverer::DeliverFrameOnIO(
     scoped_refptr<media::VideoFrame> frame,
     std::vector<scoped_refptr<media::VideoFrame>> scaled_video_frames,
     base::TimeTicks estimated_capture_time) {
   DCHECK(io_task_runner_->BelongsToCurrentThread());
-
-  const uint32_t crop_version = frame->metadata().crop_version;
-
-  if (!enabled_ && main_render_task_runner_ && emit_frame_drop_events_) {
+  if (!enabled_ && emit_frame_drop_events_) {
     emit_frame_drop_events_ = false;
 
     // TODO(crbug.com/964947): A weak ptr instance of MediaStreamVideoTrack is
@@ -434,8 +359,6 @@
         CrossThreadBindOnce(&MediaStreamVideoTrack::ResetRefreshTimer,
                             media_stream_video_track_));
   }
-
-  MaybeInvokeNewCropVersionCallbacksOnIO(crop_version);
 }
 
 void MediaStreamVideoTrack::FrameDeliverer::DeliverEncodedVideoFrameOnIO(
@@ -820,21 +743,6 @@
   return capture_handle;
 }
 
-void MediaStreamVideoTrack::AddCropVersionCallback(uint32_t crop_version,
-                                                   base::OnceClosure callback) {
-  DCHECK_CALLED_ON_VALID_THREAD(main_render_thread_checker_);
-
-  frame_deliverer_->AddCropVersionCallback(
-      crop_version, base::BindPostTask(base::ThreadTaskRunnerHandle::Get(),
-                                       std::move(callback)));
-}
-
-void MediaStreamVideoTrack::RemoveCropVersionCallback(uint32_t crop_version) {
-  DCHECK_CALLED_ON_VALID_THREAD(main_render_thread_checker_);
-
-  frame_deliverer_->RemoveCropVersionCallback(crop_version);
-}
-
 void MediaStreamVideoTrack::OnReadyStateChanged(
     WebMediaStreamSource::ReadyState state) {
   DCHECK_CALLED_ON_VALID_THREAD(main_render_thread_checker_);
diff --git a/third_party/blink/renderer/modules/mediastream/media_stream_video_track.h b/third_party/blink/renderer/modules/mediastream/media_stream_video_track.h
index 414b90f..44b96b4 100644
--- a/third_party/blink/renderer/modules/mediastream/media_stream_video_track.h
+++ b/third_party/blink/renderer/modules/mediastream/media_stream_video_track.h
@@ -88,9 +88,6 @@
   void StopAndNotify(base::OnceClosure callback) override;
   void GetSettings(MediaStreamTrackPlatform::Settings& settings) override;
   MediaStreamTrackPlatform::CaptureHandle GetCaptureHandle() override;
-  void AddCropVersionCallback(uint32_t crop_version,
-                              base::OnceClosure callback) override;
-  void RemoveCropVersionCallback(uint32_t crop_version) override;
 
   // Add |sink| to receive state changes on the main render thread and video
   // frames in the |callback| method on the IO-thread.
diff --git a/third_party/blink/renderer/modules/peerconnection/peer_connection_dependency_factory.cc b/third_party/blink/renderer/modules/peerconnection/peer_connection_dependency_factory.cc
index 77a16802..cbbf6686 100644
--- a/third_party/blink/renderer/modules/peerconnection/peer_connection_dependency_factory.cc
+++ b/third_party/blink/renderer/modules/peerconnection/peer_connection_dependency_factory.cc
@@ -74,7 +74,6 @@
 #include "third_party/webrtc/rtc_base/ref_counted_object.h"
 #include "third_party/webrtc/rtc_base/ssl_adapter.h"
 #include "third_party/webrtc_overrides/metronome_task_queue_factory.h"
-#include "third_party/webrtc_overrides/task_queue_factory.h"
 
 namespace WTF {
 template <>
@@ -648,10 +647,7 @@
       GetWorkerThread() ? GetWorkerThread() : GetSignalingThread();
   pcf_deps.signaling_thread = GetSignalingThread();
   pcf_deps.network_thread = GetNetworkThread();
-  pcf_deps.task_queue_factory =
-      !base::FeatureList::IsEnabled(kWebRtcMetronomeTaskQueue)
-          ? CreateWebRtcTaskQueueFactory()
-          : CreateWebRtcMetronomeTaskQueueFactory();
+  pcf_deps.task_queue_factory = CreateWebRtcMetronomeTaskQueueFactory();
   DCHECK(metronome_source_);
   pcf_deps.metronome = metronome_source_->CreateWebRtcMetronome();
   pcf_deps.call_factory = webrtc::CreateCallFactory();
diff --git a/third_party/blink/renderer/modules/peerconnection/webrtc_audio_renderer_test.cc b/third_party/blink/renderer/modules/peerconnection/webrtc_audio_renderer_test.cc
index fef8283c..860bf95 100644
--- a/third_party/blink/renderer/modules/peerconnection/webrtc_audio_renderer_test.cc
+++ b/third_party/blink/renderer/modules/peerconnection/webrtc_audio_renderer_test.cc
@@ -115,14 +115,7 @@
 
 }  // namespace
 
-// Flaky on TSAN. See https://crbug.com/1127211
-// Flaky with CFI. See https://crbug.com/1294176
-#if defined(THREAD_SANITIZER) || BUILDFLAG(CFI_CAST_CHECK)
-#define MAYBE_WebRtcAudioRendererTest DISABLED_WebRtcAudioRendererTest
-#else
-#define MAYBE_WebRtcAudioRendererTest WebRtcAudioRendererTest
-#endif
-class MAYBE_WebRtcAudioRendererTest : public testing::Test {
+class WebRtcAudioRendererTest : public testing::Test {
  public:
   MOCK_METHOD1(MockSwitchDeviceCallback, void(media::OutputDeviceStatus));
   void SwitchDeviceCallback(base::RunLoop* loop,
@@ -132,7 +125,7 @@
   }
 
  protected:
-  MAYBE_WebRtcAudioRendererTest()
+  WebRtcAudioRendererTest()
       : source_(new MockAudioRendererSource())
 // Tests crash on Android if these are defined. https://crbug.com/1119689
 #if !BUILDFLAG(IS_ANDROID)
@@ -240,7 +233,7 @@
 };
 
 // Verify that the renderer will be stopped if the only proxy is stopped.
-TEST_F(MAYBE_WebRtcAudioRendererTest, DISABLED_StopRenderer) {
+TEST_F(WebRtcAudioRendererTest, DISABLED_StopRenderer) {
   SetupRenderer(kDefaultOutputDeviceId);
   renderer_proxy_->Start();
 
@@ -253,7 +246,7 @@
 
 // Verify that the renderer will not be stopped unless the last proxy is
 // stopped.
-TEST_F(MAYBE_WebRtcAudioRendererTest, DISABLED_MultipleRenderers) {
+TEST_F(WebRtcAudioRendererTest, DISABLED_MultipleRenderers) {
   SetupRenderer(kDefaultOutputDeviceId);
   renderer_proxy_->Start();
 
@@ -287,7 +280,7 @@
 
 // Verify that the sink of the renderer is using the expected sample rate and
 // buffer size.
-TEST_F(MAYBE_WebRtcAudioRendererTest, DISABLED_VerifySinkParameters) {
+TEST_F(WebRtcAudioRendererTest, DISABLED_VerifySinkParameters) {
   SetupRenderer(kDefaultOutputDeviceId);
   renderer_proxy_->Start();
 #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_MAC) || \
@@ -309,7 +302,7 @@
   renderer_proxy_->Stop();
 }
 
-TEST_F(MAYBE_WebRtcAudioRendererTest, NonDefaultDevice) {
+TEST_F(WebRtcAudioRendererTest, NonDefaultDevice) {
   SetupRenderer(kDefaultOutputDeviceId);
   EXPECT_EQ(kDefaultOutputDeviceId,
             mock_sink()->GetOutputDeviceInfo().device_id());
@@ -329,7 +322,7 @@
   renderer_proxy_->Stop();
 }
 
-TEST_F(MAYBE_WebRtcAudioRendererTest, SwitchOutputDevice) {
+TEST_F(WebRtcAudioRendererTest, SwitchOutputDevice) {
   SetupRenderer(kDefaultOutputDeviceId);
   EXPECT_EQ(kDefaultOutputDeviceId,
             mock_sink()->GetOutputDeviceInfo().device_id());
@@ -349,7 +342,7 @@
   base::RunLoop loop;
   renderer_proxy_->SwitchOutputDevice(
       kOtherOutputDeviceId,
-      base::BindOnce(&MAYBE_WebRtcAudioRendererTest::SwitchDeviceCallback,
+      base::BindOnce(&WebRtcAudioRendererTest::SwitchDeviceCallback,
                      base::Unretained(this), &loop));
   loop.Run();
   EXPECT_EQ(kOtherOutputDeviceId,
@@ -362,7 +355,7 @@
   renderer_proxy_->Stop();
 }
 
-TEST_F(MAYBE_WebRtcAudioRendererTest, SwitchOutputDeviceInvalidDevice) {
+TEST_F(WebRtcAudioRendererTest, SwitchOutputDeviceInvalidDevice) {
   SetupRenderer(kDefaultOutputDeviceId);
   EXPECT_EQ(kDefaultOutputDeviceId,
             mock_sink()->GetOutputDeviceInfo().device_id());
@@ -379,7 +372,7 @@
   base::RunLoop loop;
   renderer_proxy_->SwitchOutputDevice(
       kInvalidOutputDeviceId,
-      base::BindOnce(&MAYBE_WebRtcAudioRendererTest::SwitchDeviceCallback,
+      base::BindOnce(&WebRtcAudioRendererTest::SwitchDeviceCallback,
                      base::Unretained(this), &loop));
   loop.Run();
   EXPECT_EQ(kDefaultOutputDeviceId,
@@ -392,7 +385,7 @@
   renderer_proxy_->Stop();
 }
 
-TEST_F(MAYBE_WebRtcAudioRendererTest, InitializeWithInvalidDevice) {
+TEST_F(WebRtcAudioRendererTest, InitializeWithInvalidDevice) {
   renderer_ = new blink::WebRtcAudioRenderer(
       scheduler::GetSingleThreadTaskRunnerForTesting(), stream_descriptor_,
       nullptr /*blink::WebLocalFrame*/, base::UnguessableToken::Create(),
@@ -416,7 +409,7 @@
             mock_sink()->GetOutputDeviceInfo().device_id());
 }
 
-TEST_F(MAYBE_WebRtcAudioRendererTest, SwitchOutputDeviceStoppedSource) {
+TEST_F(WebRtcAudioRendererTest, SwitchOutputDeviceStoppedSource) {
   SetupRenderer(kDefaultOutputDeviceId);
   auto* original_sink = mock_sink();
   renderer_proxy_->Start();
@@ -429,7 +422,7 @@
   renderer_proxy_->Stop();
   renderer_proxy_->SwitchOutputDevice(
       kInvalidOutputDeviceId,
-      base::BindOnce(&MAYBE_WebRtcAudioRendererTest::SwitchDeviceCallback,
+      base::BindOnce(&WebRtcAudioRendererTest::SwitchDeviceCallback,
                      base::Unretained(this), &loop));
   loop.Run();
 }
diff --git a/third_party/blink/renderer/platform/BUILD.gn b/third_party/blink/renderer/platform/BUILD.gn
index 61f875dd..b034c83 100644
--- a/third_party/blink/renderer/platform/BUILD.gn
+++ b/third_party/blink/renderer/platform/BUILD.gn
@@ -2117,7 +2117,6 @@
     "peerconnection/stats_collecting_decoder_test.cc",
     "peerconnection/stats_collecting_encoder_test.cc",
     "peerconnection/stats_collector_test.cc",
-    "peerconnection/task_queue_factory_test.cc",
     "peerconnection/two_keys_adapter_map_unittest.cc",
     "peerconnection/webrtc_audio_sink_test.cc",
     "peerconnection/webrtc_decoding_info_handler_test.cc",
diff --git a/third_party/blink/renderer/platform/fonts/font_platform_data.cc b/third_party/blink/renderer/platform/fonts/font_platform_data.cc
index c6b31da..8f4c979 100644
--- a/third_party/blink/renderer/platform/fonts/font_platform_data.cc
+++ b/third_party/blink/renderer/platform/fonts/font_platform_data.cc
@@ -29,6 +29,8 @@
 #include "third_party/blink/public/platform/platform.h"
 #include "third_party/blink/renderer/platform/fonts/font_cache.h"
 #include "third_party/blink/renderer/platform/fonts/shaping/harfbuzz_face.h"
+#include "third_party/blink/renderer/platform/fonts/shaping/harfbuzz_font_cache.h"
+#include "third_party/blink/renderer/platform/fonts/shaping/harfbuzz_font_data.h"
 #include "third_party/blink/renderer/platform/text/character.h"
 #include "third_party/blink/renderer/platform/web_test_support.h"
 #include "third_party/blink/renderer/platform/wtf/hash_map.h"
@@ -200,10 +202,14 @@
 }
 
 HarfBuzzFace* FontPlatformData::GetHarfBuzzFace() const {
+#if defined(USE_PARALLEL_TEXT_SHAPING)
+  return &harfbuzz_face_.GetOrCreate(const_cast<FontPlatformData*>(this));
+#else
   if (!harfbuzz_face_)
     harfbuzz_face_ = HarfBuzzFace::Create(const_cast<FontPlatformData*>(this));
 
   return harfbuzz_face_.get();
+#endif
 }
 
 bool FontPlatformData::HasSpaceInLigaturesOrKerning(
@@ -344,4 +350,22 @@
   return success ? postscript_name.c_str() : String();
 }
 
+#if defined(USE_PARALLEL_TEXT_SHAPING)
+// --
+
+FontPlatformData::ThreadSpecificHarfBuzzFace::ThreadSpecificHarfBuzzFace() =
+    default;
+FontPlatformData::ThreadSpecificHarfBuzzFace::~ThreadSpecificHarfBuzzFace() =
+    default;
+
+HarfBuzzFace& FontPlatformData::ThreadSpecificHarfBuzzFace::GetOrCreate(
+    FontPlatformData* platform_data) {
+  base::AutoLock guard(lock_);
+  const auto result = map_.insert(CurrentThread(), nullptr);
+  if (result.is_new_entry)
+    result.stored_value->value = HarfBuzzFace::Create(platform_data);
+  return *result.stored_value->value;
+}
+#endif
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/fonts/font_platform_data.h b/third_party/blink/renderer/platform/fonts/font_platform_data.h
index 6a20a8f..37627e9 100644
--- a/third_party/blink/renderer/platform/fonts/font_platform_data.h
+++ b/third_party/blink/renderer/platform/fonts/font_platform_data.h
@@ -43,6 +43,7 @@
 #include "third_party/blink/renderer/platform/wtf/forward.h"
 #include "third_party/blink/renderer/platform/wtf/hash_table_deleted_value_type.h"
 #include "third_party/blink/renderer/platform/wtf/text/string_impl.h"
+#include "third_party/blink/renderer/platform/wtf/threading.h"
 #include "third_party/blink/renderer/platform/wtf/vector.h"
 #include "third_party/skia/include/core/SkFont.h"
 #include "third_party/skia/include/core/SkRefCnt.h"
@@ -176,7 +177,38 @@
   WebFontRenderStyle style_;
 #endif
 
+#if defined(USE_PARALLEL_TEXT_SHAPING)
+  // The class maps from thread id to `HarfBuzzFace`.
+  // Note: We can not use `base::SequenceLocalStorageSlot` or
+  // `base::ThreadLocalStorage` here, because number of instances are limited,
+  // e.g. 255.
+  class ThreadSpecificHarfBuzzFace final {
+   public:
+    ThreadSpecificHarfBuzzFace();
+    ~ThreadSpecificHarfBuzzFace();
+
+    ThreadSpecificHarfBuzzFace(const ThreadSpecificHarfBuzzFace&) = delete;
+    ThreadSpecificHarfBuzzFace(ThreadSpecificHarfBuzzFace&&) = delete;
+
+    ThreadSpecificHarfBuzzFace operator=(const ThreadSpecificHarfBuzzFace&) =
+        delete;
+    ThreadSpecificHarfBuzzFace operator=(ThreadSpecificHarfBuzzFace&&) = delete;
+
+    HarfBuzzFace& GetOrCreate(FontPlatformData* platform_data)
+        LOCKS_EXCLUDED(lock_);
+
+   private:
+    // TODO(yosin): Once all platforms support parallel text shaping, we should
+    // use `std::unique_ptr<T>` for `HarfBuzzFace`.
+    using Map = HashMap<base::PlatformThreadId, scoped_refptr<HarfBuzzFace>>;
+    base::Lock lock_;
+    Map map_ GUARDED_BY(lock_);
+  };
+
+  mutable ThreadSpecificHarfBuzzFace harfbuzz_face_;
+#else
   mutable std::unique_ptr<HarfBuzzFace> harfbuzz_face_;
+#endif
   bool is_hash_table_deleted_value_ = false;
 };
 
diff --git a/third_party/blink/renderer/platform/fonts/opentype/open_type_caps_support.cc b/third_party/blink/renderer/platform/fonts/opentype/open_type_caps_support.cc
index 3c34ffc..f29234e 100644
--- a/third_party/blink/renderer/platform/fonts/opentype/open_type_caps_support.cc
+++ b/third_party/blink/renderer/platform/fonts/opentype/open_type_caps_support.cc
@@ -143,8 +143,8 @@
 
 OpenTypeCapsSupport::FontFormat OpenTypeCapsSupport::GetFontFormat() const {
   if (font_format_ == FontFormat::kUndetermined) {
-    hb_face_t* hb_face = hb_font_get_face(harfbuzz_face_->GetScaledFont(
-        nullptr, HarfBuzzFace::kNoVerticalLayout));
+    hb_face_t* const hb_face =
+        hb_font_get_face(harfbuzz_face_->GetScaledFont());
 
     HbScoped<hb_blob_t> morx_blob(
         hb_face_reference_table(hb_face, HB_TAG('m', 'o', 'r', 'x')));
@@ -177,8 +177,7 @@
     return false;
   }
 
-  hb_face_t* hb_face = hb_font_get_face(
-      harfbuzz_face_->GetScaledFont(nullptr, HarfBuzzFace::kNoVerticalLayout));
+  hb_face_t* const hb_face = hb_font_get_face(harfbuzz_face_->GetScaledFont());
 
   Vector<hb_aat_layout_feature_type_t> aat_features;
   unsigned feature_count =
diff --git a/third_party/blink/renderer/platform/fonts/opentype/open_type_caps_support_mpl.cc b/third_party/blink/renderer/platform/fonts/opentype/open_type_caps_support_mpl.cc
index 83ed7cc..2bbd9731 100644
--- a/third_party/blink/renderer/platform/fonts/opentype/open_type_caps_support_mpl.cc
+++ b/third_party/blink/renderer/platform/fonts/opentype/open_type_caps_support_mpl.cc
@@ -14,8 +14,7 @@
 
 bool OpenTypeCapsSupport::SupportsOpenTypeFeature(hb_script_t script,
                                                   uint32_t tag) const {
-  hb_face_t* face = hb_font_get_face(
-      harfbuzz_face_->GetScaledFont(nullptr, HarfBuzzFace::kNoVerticalLayout));
+  hb_face_t* const face = hb_font_get_face(harfbuzz_face_->GetScaledFont());
   DCHECK(face);
 
   DCHECK(
diff --git a/third_party/blink/renderer/platform/fonts/opentype/open_type_math_support.cc b/third_party/blink/renderer/platform/fonts/opentype/open_type_math_support.cc
index a721a88..4660a18 100644
--- a/third_party/blink/renderer/platform/fonts/opentype/open_type_math_support.cc
+++ b/third_party/blink/renderer/platform/fonts/opentype/open_type_math_support.cc
@@ -40,8 +40,7 @@
   if (!harfbuzz_face)
     return false;
 
-  hb_font_t* font =
-      harfbuzz_face->GetScaledFont(nullptr, HarfBuzzFace::kNoVerticalLayout);
+  hb_font_t* font = harfbuzz_face->GetScaledFont();
   DCHECK(font);
   hb_face_t* face = hb_font_get_face(font);
   DCHECK(face);
@@ -55,8 +54,7 @@
   if (!HasMathData(harfbuzz_face))
     return absl::nullopt;
 
-  hb_font_t* font =
-      harfbuzz_face->GetScaledFont(nullptr, HarfBuzzFace::kNoVerticalLayout);
+  hb_font_t* const font = harfbuzz_face->GetScaledFont();
   DCHECK(font);
 
   hb_position_t harfbuzz_value = hb_ot_math_get_constant(
@@ -133,8 +131,7 @@
   if (!harfbuzz_face)
     return absl::nullopt;
 
-  hb_font_t* font =
-      harfbuzz_face->GetScaledFont(nullptr, HarfBuzzFace::kNoVerticalLayout);
+  hb_font_t* const font = harfbuzz_face->GetScaledFont();
 
   return absl::optional<float>(HarfBuzzUnitsToFloat(
       hb_ot_math_get_glyph_italics_correction(font, glyph)));
@@ -161,8 +158,7 @@
     GetHarfBuzzMathRecordGetter<HarfBuzzRecordType> getter,
     HarfBuzzMathRecordConverter<HarfBuzzRecordType, RecordType> converter,
     absl::optional<RecordType> prepended_record) {
-  hb_font_t* hb_font =
-      harfbuzz_face->GetScaledFont(nullptr, HarfBuzzFace::kNoVerticalLayout);
+  hb_font_t* const hb_font = harfbuzz_face->GetScaledFont();
   DCHECK(hb_font);
 
   hb_direction_t hb_stretch_axis = HarfBuzzDirection(stretch_axis);
@@ -241,8 +237,7 @@
           std::move(converter),
           absl::optional<OpenTypeMathStretchData::GlyphPartRecord>());
   if (italic_correction && !parts.IsEmpty()) {
-    hb_font_t* hb_font =
-        harfbuzz_face->GetScaledFont(nullptr, HarfBuzzFace::kNoVerticalLayout);
+    hb_font_t* const hb_font = harfbuzz_face->GetScaledFont();
     // A GlyphAssembly subtable exists for the specified font, glyph and stretch
     // axis since it has been possible to retrieve the GlyphPartRecords. This
     // means that the following call is guaranteed to get an italic correction.
diff --git a/third_party/blink/renderer/platform/fonts/shaping/harfbuzz_face.cc b/third_party/blink/renderer/platform/fonts/shaping/harfbuzz_face.cc
index 0385b908..d964d87 100644
--- a/third_party/blink/renderer/platform/fonts/shaping/harfbuzz_face.cc
+++ b/third_party/blink/renderer/platform/fonts/shaping/harfbuzz_face.cc
@@ -485,6 +485,11 @@
   return unscaled_font_;
 }
 
+hb_font_t* HarfBuzzFace::GetScaledFont() const {
+  return GetScaledFont(nullptr, HarfBuzzFace::kNoVerticalLayout,
+                       platform_data_->size());
+}
+
 void HarfBuzzFace::Init() {
   DCHECK(IsMainThread());
   HarfBuzzSkiaGetFontFuncs::Get();
diff --git a/third_party/blink/renderer/platform/fonts/shaping/harfbuzz_face.h b/third_party/blink/renderer/platform/fonts/shaping/harfbuzz_face.h
index c6c88c3..cbfb1c15 100644
--- a/third_party/blink/renderer/platform/fonts/shaping/harfbuzz_face.h
+++ b/third_party/blink/renderer/platform/fonts/shaping/harfbuzz_face.h
@@ -70,7 +70,12 @@
   // object will be used.
   hb_font_t* GetScaledFont(scoped_refptr<UnicodeRangeSet>,
                            VerticalLayoutCallbacks,
-                           float specified_size = -1) const;
+                           float specified_size) const;
+
+  // Returns `hb_font_t` as same as `GetScaledFont()` with null
+  // `UnicodeRangeSet`, `HarfBuzzFace::kNoVerticalLayout`, and
+  // `platform_data_.size()`.
+  hb_font_t* GetScaledFont() const;
 
   bool HasSpaceInLigaturesOrKerning(TypesettingFeatures);
   unsigned UnitsPerEmFromHeadTable();
diff --git a/third_party/blink/renderer/platform/fonts/shaping/shape_result_inline_headers.h b/third_party/blink/renderer/platform/fonts/shaping/shape_result_inline_headers.h
index 33ccb0d..fe139f5 100644
--- a/third_party/blink/renderer/platform/fonts/shaping/shape_result_inline_headers.h
+++ b/third_party/blink/renderer/platform/fonts/shaping/shape_result_inline_headers.h
@@ -85,7 +85,6 @@
                                        unsigned start_index,
                                        unsigned num_glyphs,
                                        unsigned num_characters) {
-    CHECK_GT(num_characters, 0u);
     return base::AdoptRef(new RunInfo(font, dir, canvas_rotation, script,
                                       start_index, num_glyphs, num_characters));
   }
diff --git a/third_party/blink/renderer/platform/fonts/shaping/stretchy_operator_shaper.cc b/third_party/blink/renderer/platform/fonts/shaping/stretchy_operator_shaper.cc
index 3bc8435..79a2626 100644
--- a/third_party/blink/renderer/platform/fonts/shaping/stretchy_operator_shaper.cc
+++ b/third_party/blink/renderer/platform/fonts/shaping/stretchy_operator_shaper.cc
@@ -39,8 +39,7 @@
   if (parts.IsEmpty())
     return absl::nullopt;
 
-  hb_font_t* hb_font =
-      harfbuzz_face->GetScaledFont(nullptr, HarfBuzzFace::kNoVerticalLayout);
+  hb_font_t* const hb_font = harfbuzz_face->GetScaledFont();
 
   auto hb_stretch_axis =
       stretch_axis == OpenTypeMathStretchData::StretchAxis::Horizontal
diff --git a/third_party/blink/renderer/platform/mediastream/media_stream_track_platform.h b/third_party/blink/renderer/platform/mediastream/media_stream_track_platform.h
index 36d8156..cc9a4ec 100644
--- a/third_party/blink/renderer/platform/mediastream/media_stream_track_platform.h
+++ b/third_party/blink/renderer/platform/mediastream/media_stream_track_platform.h
@@ -84,14 +84,6 @@
   virtual void GetSettings(Settings& settings) {}
   virtual CaptureHandle GetCaptureHandle();
 
-  // Adds a one off callback that will be invoked when observing the first frame
-  // where |metadata.crop_version >= crop_version|.
-  virtual void AddCropVersionCallback(uint32_t crop_version,
-                                      base::OnceClosure callback) {}
-
-  // Removes the callback that was associated with this |crop_version|, if any.
-  virtual void RemoveCropVersionCallback(uint32_t crop_version) {}
-
   bool is_local_track() const { return is_local_track_; }
 
   enum class StreamType { kAudio, kVideo };
diff --git a/third_party/blink/renderer/platform/peerconnection/task_queue_factory_test.cc b/third_party/blink/renderer/platform/peerconnection/task_queue_factory_test.cc
deleted file mode 100644
index 8a34a046..0000000
--- a/third_party/blink/renderer/platform/peerconnection/task_queue_factory_test.cc
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "third_party/webrtc_overrides/task_queue_factory.h"
-
-#include "base/task/task_traits.h"
-#include "base/test/task_environment.h"
-#include "testing/gmock/include/gmock/gmock.h"
-#include "third_party/webrtc/api/task_queue/task_queue_test.h"
-
-namespace {
-
-using ::webrtc::TaskQueueTest;
-
-// Wrapper around WebrtcTaskQueueFactory to set up required testing environment.
-class TestTaskQueueFactory final : public webrtc::TaskQueueFactory {
- public:
-  TestTaskQueueFactory() : factory_(CreateWebRtcTaskQueueFactory()) {}
-
-  std::unique_ptr<webrtc::TaskQueueBase, webrtc::TaskQueueDeleter>
-  CreateTaskQueue(absl::string_view name, Priority priority) const override {
-    return factory_->CreateTaskQueue(name, priority);
-  }
-
- private:
-  base::test::TaskEnvironment task_environment_;
-  std::unique_ptr<webrtc::TaskQueueFactory> factory_;
-};
-
-}  // namespace
-
-INSTANTIATE_TEST_SUITE_P(
-    WebRtc,
-    TaskQueueTest,
-    ::testing::Values(std::make_unique<TestTaskQueueFactory>));
diff --git a/third_party/blink/renderer/platform/peerconnection/webrtc_timer_test.cc b/third_party/blink/renderer/platform/peerconnection/webrtc_timer_test.cc
index 3355e145..807e1463 100644
--- a/third_party/blink/renderer/platform/peerconnection/webrtc_timer_test.cc
+++ b/third_party/blink/renderer/platform/peerconnection/webrtc_timer_test.cc
@@ -5,7 +5,6 @@
 #include "third_party/webrtc_overrides/webrtc_timer.h"
 
 #include "base/task/thread_pool.h"
-#include "base/test/scoped_feature_list.h"
 #include "base/test/task_environment.h"
 #include "base/time/time.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -129,42 +128,7 @@
 
 }  // namespace
 
-TEST_F(WebRtcTimerTest, StartOneShotWithoutMetronome) {
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndDisableFeature(kWebRtcTimerUsesMetronome);
-
-  CallbackListener listener;
-  WebRtcTimer timer(listener.task_runner(),
-                    base::BindRepeating(&CallbackListener::Callback,
-                                        base::Unretained(&listener)));
-
-  // Ensure the timer fires on the target millisecond.
-  timer.StartOneShot(base::Milliseconds(10));
-  task_environment_.FastForwardBy(base::Milliseconds(9));
-  EXPECT_EQ(listener.callback_count(), 0u);
-  task_environment_.FastForwardBy(base::Milliseconds(1));
-  EXPECT_EQ(listener.callback_count(), 1u);
-
-  // The task does not repeat automatically.
-  task_environment_.FastForwardBy(base::Milliseconds(10));
-  EXPECT_EQ(listener.callback_count(), 1u);
-
-  // The task can be manually scheduled to fire again.
-  timer.StartOneShot(base::Milliseconds(5));
-  task_environment_.FastForwardBy(base::Milliseconds(5));
-  EXPECT_EQ(listener.callback_count(), 2u);
-
-  // Schedule to fire but shutdown the timer before it has time to fire.
-  timer.StartOneShot(base::Milliseconds(5));
-  timer.Shutdown();
-
-  task_environment_.FastForwardBy(base::Milliseconds(5));
-  EXPECT_EQ(listener.callback_count(), 2u);
-}
-
-TEST_F(WebRtcTimerTest, StartOneShotWithMetronome) {
-  base::test::ScopedFeatureList scoped_feature_list(kWebRtcTimerUsesMetronome);
-
+TEST_F(WebRtcTimerTest, StartOneShot) {
   CallbackListener listener;
   WebRtcTimer timer(listener.task_runner(),
                     base::BindRepeating(&CallbackListener::Callback,
@@ -205,27 +169,7 @@
   EXPECT_EQ(listener.callback_count(), 3u);
 }
 
-TEST_F(WebRtcTimerTest, RecursiveStartOneShotWithoutMetronome) {
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndDisableFeature(kWebRtcTimerUsesMetronome);
-
-  base::TimeDelta delay = base::Milliseconds(1);
-  RecursiveStartOneShotter recursive_shotter(/*repeat_count=*/2, delay);
-
-  // Ensure the callback is repeated twice.
-  EXPECT_EQ(recursive_shotter.callback_count(), 0u);
-  task_environment_.FastForwardBy(delay);
-  EXPECT_EQ(recursive_shotter.callback_count(), 1u);
-  task_environment_.FastForwardBy(delay);
-  EXPECT_EQ(recursive_shotter.callback_count(), 2u);
-  // It is not repeated a third time.
-  task_environment_.FastForwardBy(delay);
-  EXPECT_EQ(recursive_shotter.callback_count(), 2u);
-}
-
-TEST_F(WebRtcTimerTest, RecursiveStartOneShotWithMetronome) {
-  base::test::ScopedFeatureList scoped_feature_list(kWebRtcTimerUsesMetronome);
-
+TEST_F(WebRtcTimerTest, RecursiveStartOneShot) {
   base::TimeDelta delay = base::Milliseconds(1);
   RecursiveStartOneShotter recursive_shotter(/*repeat_count=*/2, delay);
 
@@ -249,36 +193,7 @@
   EXPECT_EQ(recursive_shotter.callback_count(), 2u);
 }
 
-TEST_F(WebRtcTimerTest, MoveToNewTaskRunnerWithoutMetronome) {
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndDisableFeature(kWebRtcTimerUsesMetronome);
-
-  CallbackListener listener;
-  WebRtcTimer timer(listener.task_runner(),
-                    base::BindRepeating(&CallbackListener::Callback,
-                                        base::Unretained(&listener)));
-
-  // Schedule in less than a tick, and advance time half-way there.
-  timer.StartOneShot(base::Milliseconds(6));
-  task_environment_.FastForwardBy(base::Milliseconds(3));
-  EXPECT_EQ(listener.callback_count(), 0u);
-
-  // Move to a new task runner. The CallbackListener will EXPECT_TRUE that the
-  // correct task runner is used.
-  listener.set_task_runner(base::ThreadPool::CreateSequencedTaskRunner({}));
-  timer.MoveToNewTaskRunner(listener.task_runner());
-
-  // Advance to scheduled time.
-  task_environment_.FastForwardBy(base::Milliseconds(3));
-  EXPECT_EQ(listener.callback_count(), 1u);
-
-  // Cleanup.
-  timer.Shutdown();
-}
-
-TEST_F(WebRtcTimerTest, MoveToNewTaskRunnerWithMetronome) {
-  base::test::ScopedFeatureList scoped_feature_list(kWebRtcTimerUsesMetronome);
-
+TEST_F(WebRtcTimerTest, MoveToNewTaskRunner) {
   CallbackListener listener;
   WebRtcTimer timer(listener.task_runner(),
                     base::BindRepeating(&CallbackListener::Callback,
@@ -303,33 +218,7 @@
   timer.Shutdown();
 }
 
-TEST_F(WebRtcTimerTest, StartRepeatingWithoutMetronome) {
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndDisableFeature(kWebRtcTimerUsesMetronome);
-
-  CallbackListener listener;
-  WebRtcTimer timer(listener.task_runner(),
-                    base::BindRepeating(&CallbackListener::Callback,
-                                        base::Unretained(&listener)));
-
-  // Ensure the timer repeats every 10 milliseconds.
-  timer.StartRepeating(base::Milliseconds(10));
-  task_environment_.FastForwardBy(base::Milliseconds(10));
-  EXPECT_EQ(listener.callback_count(), 1u);
-  task_environment_.FastForwardBy(base::Milliseconds(10));
-  EXPECT_EQ(listener.callback_count(), 2u);
-  task_environment_.FastForwardBy(base::Milliseconds(10));
-  EXPECT_EQ(listener.callback_count(), 3u);
-  timer.Shutdown();
-
-  // The timer stops on shutdown.
-  task_environment_.FastForwardBy(base::Milliseconds(10));
-  EXPECT_EQ(listener.callback_count(), 3u);
-}
-
-TEST_F(WebRtcTimerTest, StartRepeatingWithMetronome) {
-  base::test::ScopedFeatureList scoped_feature_list(kWebRtcTimerUsesMetronome);
-
+TEST_F(WebRtcTimerTest, StartRepeating) {
   CallbackListener listener;
   WebRtcTimer timer(listener.task_runner(),
                     base::BindRepeating(&CallbackListener::Callback,
@@ -355,58 +244,39 @@
 }
 
 TEST_F(WebRtcTimerTest, StopRepeatingTimer) {
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndDisableFeature(kWebRtcTimerUsesMetronome);
-
   CallbackListener listener;
   WebRtcTimer timer(listener.task_runner(),
                     base::BindRepeating(&CallbackListener::Callback,
                                         base::Unretained(&listener)));
 
-  // Repeat every 10 ms.
-  timer.StartRepeating(base::Milliseconds(10));
-  task_environment_.FastForwardBy(base::Milliseconds(10));
+  // Repeat every tick.
+  timer.StartRepeating(MetronomeSource::Tick());
+  task_environment_.FastForwardBy(MetronomeSource::Tick());
   EXPECT_EQ(listener.callback_count(), 1u);
-  task_environment_.FastForwardBy(base::Milliseconds(10));
+  task_environment_.FastForwardBy(MetronomeSource::Tick());
   EXPECT_EQ(listener.callback_count(), 2u);
 
   // Stop the timer and ensure it stops repeating.
   timer.Stop();
-  task_environment_.FastForwardBy(base::Milliseconds(10));
+  task_environment_.FastForwardBy(MetronomeSource::Tick());
   EXPECT_EQ(listener.callback_count(), 2u);
 
   // The timer is reusable - can start and stop again.
-  timer.StartRepeating(base::Milliseconds(10));
-  task_environment_.FastForwardBy(base::Milliseconds(10));
+  timer.StartRepeating(MetronomeSource::Tick());
+  task_environment_.FastForwardBy(MetronomeSource::Tick());
   EXPECT_EQ(listener.callback_count(), 3u);
-  task_environment_.FastForwardBy(base::Milliseconds(10));
+  task_environment_.FastForwardBy(MetronomeSource::Tick());
   EXPECT_EQ(listener.callback_count(), 4u);
   timer.Stop();
-  task_environment_.FastForwardBy(base::Milliseconds(10));
+  task_environment_.FastForwardBy(MetronomeSource::Tick());
   EXPECT_EQ(listener.callback_count(), 4u);
 
   // Cleanup.
   timer.Shutdown();
 }
 
-TEST_F(WebRtcTimerTest, StopTimerFromInsideCallbackWithoutMetronome) {
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndDisableFeature(kWebRtcTimerUsesMetronome);
-
-  // Stops its own timer from inside the callback after 10 ms.
-  RecursiveStopper recursive_stopper(base::Milliseconds(10));
-  task_environment_.FastForwardBy(base::Milliseconds(10));
-  EXPECT_EQ(recursive_stopper.callback_count(), 1u);
-
-  // Ensure we are stopped, the callback count does not increase.
-  task_environment_.FastForwardBy(base::Milliseconds(10));
-  EXPECT_EQ(recursive_stopper.callback_count(), 1u);
-}
-
 // Ensures stopping inside the timer callback does not deadlock.
-TEST_F(WebRtcTimerTest, StopTimerFromInsideCallbackWithMetronome) {
-  base::test::ScopedFeatureList scoped_feature_list(kWebRtcTimerUsesMetronome);
-
+TEST_F(WebRtcTimerTest, StopTimerFromInsideCallback) {
   // Stops its own timer from inside the callback after a tick.
   RecursiveStopper recursive_stopper(MetronomeSource::Tick());
   task_environment_.FastForwardBy(MetronomeSource::Tick());
@@ -420,9 +290,6 @@
 // Ensures in-parallel stopping while the task may be running does not
 // deadlock in race condition. Coverage for https://crbug.com/1281399.
 TEST(WebRtcTimerRealThreadsTest, StopTimerWithRaceCondition) {
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndDisableFeature(kWebRtcTimerUsesMetronome);
-
   base::test::TaskEnvironment task_environment(
       base::test::TaskEnvironment::ThreadingMode::MULTIPLE_THREADS,
       base::test::TaskEnvironment::TimeSource::SYSTEM_TIME);
@@ -453,26 +320,22 @@
 }
 
 TEST_F(WebRtcTimerTest, IsActive) {
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndDisableFeature(kWebRtcTimerUsesMetronome);
-
-  constexpr base::TimeDelta kDelay = base::Milliseconds(10);
   IsActiveChecker is_active_checker;
 
   // StartOneShot() makes the timer temporarily active.
   EXPECT_FALSE(is_active_checker.timer().IsActive());
-  is_active_checker.timer().StartOneShot(kDelay);
+  is_active_checker.timer().StartOneShot(MetronomeSource::Tick());
   EXPECT_TRUE(is_active_checker.timer().IsActive());
-  task_environment_.FastForwardBy(kDelay);
+  task_environment_.FastForwardBy(MetronomeSource::Tick());
   EXPECT_FALSE(is_active_checker.timer().IsActive());
   // The timer is said to be inactive inside the one-shot callback.
   EXPECT_FALSE(is_active_checker.was_active_in_last_callback());
 
   // StartRepeating() makes the timer active until stopped.
   EXPECT_FALSE(is_active_checker.timer().IsActive());
-  is_active_checker.timer().StartRepeating(kDelay);
+  is_active_checker.timer().StartRepeating(MetronomeSource::Tick());
   EXPECT_TRUE(is_active_checker.timer().IsActive());
-  task_environment_.FastForwardBy(kDelay);
+  task_environment_.FastForwardBy(MetronomeSource::Tick());
   EXPECT_TRUE(is_active_checker.timer().IsActive());
   // The timer is said to be active inside the repeating callback.
   EXPECT_TRUE(is_active_checker.was_active_in_last_callback());
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index f566f22..4df80546 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -1611,6 +1611,9 @@
 virtual/layout_ng_table_frag/external/wpt/css/css-break/table/repeated-section/header-after-break.tentative.html [ Pass ]
 virtual/layout_ng_table_frag/external/wpt/css/css-break/table/repeated-section/header-footer.tentative.html [ Pass ]
 virtual/layout_ng_table_frag/external/wpt/css/css-break/table/repeated-section/header.tentative.html [ Pass ]
+virtual/layout_ng_table_frag/external/wpt/css/css-break/table/repeated-section/hit-test-relative-in-transform.tentative.html [ Pass ]
+virtual/layout_ng_table_frag/external/wpt/css/css-break/table/repeated-section/hit-test-relative.tentative.html [ Pass ]
+virtual/layout_ng_table_frag/external/wpt/css/css-break/table/repeated-section/hit-test.tentative.html [ Pass ]
 virtual/layout_ng_table_frag/external/wpt/css/css-break/table/repeated-section/inline-block.tentative.html [ Pass ]
 virtual/layout_ng_table_frag/external/wpt/css/css-break/table/repeated-section/multicol.tentative.html [ Pass ]
 virtual/layout_ng_table_frag/external/wpt/css/css-break/table/table-border-005.html [ Pass ]
@@ -3981,6 +3984,9 @@
 crbug.com/1078927 external/wpt/css/css-break/table/repeated-section/header-after-break.tentative.html [ Failure ]
 crbug.com/1078927 external/wpt/css/css-break/table/repeated-section/header-footer.tentative.html [ Failure ]
 crbug.com/1078927 external/wpt/css/css-break/table/repeated-section/header.tentative.html [ Failure ]
+crbug.com/1078927 external/wpt/css/css-break/table/repeated-section/hit-test-relative-in-transform.tentative.html [ Failure ]
+crbug.com/1078927 external/wpt/css/css-break/table/repeated-section/hit-test-relative.tentative.html [ Failure ]
+crbug.com/1078927 external/wpt/css/css-break/table/repeated-section/hit-test.tentative.html [ Failure ]
 crbug.com/1078927 external/wpt/css/css-break/table/repeated-section/inline-block.tentative.html [ Failure ]
 crbug.com/1078927 external/wpt/css/css-break/table/repeated-section/multicol.tentative.html [ Failure ]
 crbug.com/1078927 external/wpt/css/css-break/table/table-border-005.html [ Failure ]
@@ -6853,7 +6859,6 @@
 crbug.com/1312164 webaudio/internals/audiocontext-gc.html [ Skip ]
 
 # Sheriff 2022-03-21: More flaky tests.
-crbug.com/1277696 [ Mac ] fast/loader/reload-zero-byte-plugin.html [ Failure Pass ]
 crbug.com/1272376 [ Mac ] plugins/plugin-document-back-forward.html [ Failure Pass ]
 
 # Tests that need updates due to rounding math in layout
@@ -7070,3 +7075,6 @@
 
 # Sheriff 2022-06-09
 crbug.com/1335002 [ Linux ] external/wpt/event-timing/first-input-interactionid-tap.html [ Skip ]
+
+# Flaky on multiple platforms
+crbug.com/1277696 fast/loader/reload-zero-byte-plugin.html [ Failure Pass ]
diff --git a/third_party/blink/web_tests/external/Version b/third_party/blink/web_tests/external/Version
index 432e7f12..20af476 100644
--- a/third_party/blink/web_tests/external/Version
+++ b/third_party/blink/web_tests/external/Version
@@ -1 +1 @@
-Version: edca84af42bd7e84da94e63b322fe343191842d2
+Version: 9894fff50b438d61b269e559ac004ba515f72637
diff --git a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
index a349398..122bbc4 100644
--- a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
+++ b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
@@ -237146,6 +237146,45 @@
       }
      },
      "popups": {
+      "popup-animated-hide-display.tentative.html": [
+       "410639e67152c895d2ad09bcef33224333a8ce08",
+       [
+        null,
+        [
+         [
+          "/html/semantics/popups/popup-animated-display-ref.tentative.html",
+          "=="
+         ]
+        ],
+        {}
+       ]
+      ],
+      "popup-animated-hide-finishes.tentative.html": [
+       "88e37cb4e3bd7d18fb983c1c04d012ec9f6fde98",
+       [
+        null,
+        [
+         [
+          "/html/semantics/popups/popup-animated-hide-finishes-ref.tentative.html",
+          "=="
+         ]
+        ],
+        {}
+       ]
+      ],
+      "popup-animated-show-display.tentative.html": [
+       "deb7550926422983b684b01609c4e68a67a08b97",
+       [
+        null,
+        [
+         [
+          "/html/semantics/popups/popup-animated-display-ref.tentative.html",
+          "=="
+         ]
+        ],
+        {}
+       ]
+      ],
       "popup-backdrop-appearance.tentative.html": [
        "0b348f85d0103c36ea71e6c7f420df3709c0bb9b",
        [
@@ -313274,6 +313313,14 @@
       }
      },
      "popups": {
+      "popup-animated-display-ref.tentative.html": [
+       "77f15441ae95dfd719065d47499866e68fa540e7",
+       []
+      ],
+      "popup-animated-hide-finishes-ref.tentative.html": [
+       "d8334f985ed51f3277d069838132876d8aad2ffc",
+       []
+      ],
       "popup-backdrop-appearance-ref.tentative.html": [
        "0327f3886fbceec18f21e2146a91a59053bfc954",
        []
@@ -379324,34 +379371,6 @@
         {}
        ]
       ],
-      "grid-template-columns-neutral-keyframe-001.html": [
-       "070539e2c0eda9cbc8585f8bd1fc4217485d97e4",
-       [
-        null,
-        {}
-       ]
-      ],
-      "grid-template-columns-neutral-keyframe-002.html": [
-       "6cd163711ee599366280b352dcb808e83ea2899f",
-       [
-        null,
-        {}
-       ]
-      ],
-      "grid-template-columns-neutral-keyframe-003.html": [
-       "80187cf68487bf3af4d39a0799b30886600e61f3",
-       [
-        null,
-        {}
-       ]
-      ],
-      "grid-template-columns-neutral-keyframe-004.html": [
-       "a207aa713c971cda94052dc8abf810f1e1cb29fd",
-       [
-        null,
-        {}
-       ]
-      ],
       "grid-template-rows-composition.html": [
        "42f9c92e9ad6d2042553e7e4b440adeeddf09bab",
        [
@@ -379365,34 +379384,6 @@
         null,
         {}
        ]
-      ],
-      "grid-template-rows-neutral-keyframe-001.html": [
-       "ecbe0481eded5ede38ab482b9175555258f82861",
-       [
-        null,
-        {}
-       ]
-      ],
-      "grid-template-rows-neutral-keyframe-002.html": [
-       "f63d45b4a71929ae703bd1dc51b875c0c843b12e",
-       [
-        null,
-        {}
-       ]
-      ],
-      "grid-template-rows-neutral-keyframe-003.html": [
-       "5ffd67528ca91f27ae801fe8930745a27c61df0b",
-       [
-        null,
-        {}
-       ]
-      ],
-      "grid-template-rows-neutral-keyframe-004.html": [
-       "941e843624837b16506b9900cc0736f3ca8a5a5f",
-       [
-        null,
-        {}
-       ]
       ]
      },
      "chrome-crash-001.html": [
@@ -427799,6 +427790,69 @@
         }
        ]
       ],
+      "response-stream-bad-chunk.any.js": [
+       "d3d92e16772d10a8c91ee42d4c6bcaf0d4cc4fa0",
+       [
+        "fetch/api/response/response-stream-bad-chunk.any.html",
+        {
+         "script_metadata": [
+          [
+           "global",
+           "window,worker"
+          ],
+          [
+           "title",
+           "Response causes TypeError from bad chunk type"
+          ]
+         ]
+        }
+       ],
+       [
+        "fetch/api/response/response-stream-bad-chunk.any.serviceworker.html",
+        {
+         "script_metadata": [
+          [
+           "global",
+           "window,worker"
+          ],
+          [
+           "title",
+           "Response causes TypeError from bad chunk type"
+          ]
+         ]
+        }
+       ],
+       [
+        "fetch/api/response/response-stream-bad-chunk.any.sharedworker.html",
+        {
+         "script_metadata": [
+          [
+           "global",
+           "window,worker"
+          ],
+          [
+           "title",
+           "Response causes TypeError from bad chunk type"
+          ]
+         ]
+        }
+       ],
+       [
+        "fetch/api/response/response-stream-bad-chunk.any.worker.html",
+        {
+         "script_metadata": [
+          [
+           "global",
+           "window,worker"
+          ],
+          [
+           "title",
+           "Response causes TypeError from bad chunk type"
+          ]
+         ]
+        }
+       ]
+      ],
       "response-stream-disturbed-1.any.js": [
        "64f65f16f23e7d797451c634a2f472a51d6955c0",
        [
@@ -473847,6 +473901,15 @@
         }
        ]
       ],
+      "popup-animated-hide-cleanup.tentative.html": [
+       "762458ea669dda88b9f725c7d4dabf8179cf2b44",
+       [
+        null,
+        {
+         "testdriver": true
+        }
+       ]
+      ],
       "popup-attribute-basic.tentative.html": [
        "8ca5195a435016a81b1cef988560a1f503968e12",
        [
@@ -545521,15 +545584,6 @@
       }
      ]
     ],
-    "createcredential-badargs-attestation.https.html": [
-     "1bdebbbf2b286f69eecd0774ac9bdfb96c20a50b",
-     [
-      null,
-      {
-       "testdriver": true
-      }
-     ]
-    ],
     "createcredential-badargs-authnrselection.https.html": [
      "85b0f9053866a878185b0a2aaf6bab068bd65b86",
      [
@@ -545631,7 +545685,7 @@
      ]
     ],
     "createcredential-passing.https.html": [
-     "2d8fb14fc796991291fd68d862f2aad9299d1646",
+     "2c744b25585552188253720882e919718ac5c1a3",
      [
       null,
       {
diff --git a/third_party/blink/web_tests/external/wpt/cookies/attributes/expires.html b/third_party/blink/web_tests/external/wpt/cookies/attributes/expires.html
index f61c405..a6bacfd 100644
--- a/third_party/blink/web_tests/external/wpt/cookies/attributes/expires.html
+++ b/third_party/blink/web_tests/external/wpt/cookies/attributes/expires.html
@@ -14,9 +14,6 @@
   <body>
     <div id=log></div>
     <script>
-      // TODO: there is more to test here, these tests capture the old
-      // ported http-state tests. Feel free to delete this comment when more
-      // are added.
       const expiresTests = [
         {
           cookie: "test=1; Expires=Fri, 01 Jan 2038 00:00:00 GMT",
@@ -45,8 +42,14 @@
         },
       ];
 
+      // These tests evaluate setting cookies with expiration via HTTP headers.
       for (const test of expiresTests) {
-        httpCookieTest(test.cookie, test.expected, test.name);
+        httpCookieTest(test.cookie, test.expected, test.name + " via HTTP headers");
+      }
+
+      // These tests evaluate setting cookies with expiration via document.cookie.
+      for (const test of expiresTests) {
+        domCookieTest(test.cookie, test.expected, test.name + " via document.cookie");
       }
     </script>
   </body>
diff --git a/third_party/blink/web_tests/external/wpt/cookies/resources/cookie-test.js b/third_party/blink/web_tests/external/wpt/cookies/resources/cookie-test.js
index 7c3b861..c73d4d75 100644
--- a/third_party/blink/web_tests/external/wpt/cookies/resources/cookie-test.js
+++ b/third_party/blink/web_tests/external/wpt/cookies/resources/cookie-test.js
@@ -119,7 +119,15 @@
     await test_driver.delete_all_cookies();
     t.add_cleanup(test_driver.delete_all_cookies);
 
-    document.cookie = cookie;
+    if (typeof cookie === "string") {
+      document.cookie = cookie;
+    } else if (Array.isArray(cookie)) {
+      for (const singlecookie of cookie) {
+        document.cookie = singlecookie;
+      }
+    } else {
+      throw new Error('Unexpected type passed into domCookieTest as cookie: ' + typeof cookie);
+    }
     let cookies = document.cookie;
     assert_equals(cookies, expectedValue, Boolean(expectedValue) ?
                                           'The cookie was set as expected.' :
diff --git a/third_party/blink/web_tests/external/wpt/css/css-break/hit-test-hidden-overflow.html b/third_party/blink/web_tests/external/wpt/css/css-break/hit-test-hidden-overflow.html
new file mode 100644
index 0000000..ec766e0
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-break/hit-test-hidden-overflow.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
+<link rel="help" href="https://bugs.chromium.org/p/chromium/issues/detail?id=1330515">
+<style>
+  body { margin: 0; }
+</style>
+<div id="first" style="margin-top:20px; height:20px;">
+  <div style="overflow:hidden; width:500px; margin-left:-100px; height:0;">
+    <div style="columns:2; gap:0; width:200%;">
+      <div style="position:relative; margin-left:-100%; height:800px;"></div>
+    </div>
+  </div>
+</div>
+<div id="second" style="height:20px;"></div>
+<div id="log" style="margin-top:200px;"></div>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+  test(()=> { assert_equals(document.elementFromPoint(50, 10), document.documentElement); }, "above first block");
+  test(()=> { assert_equals(document.elementFromPoint(50, 30), first); }, "first block");
+  test(()=> { assert_equals(document.elementFromPoint(50, 50), second); }, "second block");
+  test(()=> { assert_equals(document.elementFromPoint(50, 70), document.documentElement); }, "below first block");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-break/table/repeated-section/hit-test-relative-in-transform.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-break/table/repeated-section/hit-test-relative-in-transform.tentative.html
new file mode 100644
index 0000000..43a36f185
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-break/table/repeated-section/hit-test-relative-in-transform.tentative.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<style>
+  body { margin: 0; }
+</style>
+<div style="columns:2; width:200px; gap:0; column-fill:auto; height:100px;">
+  <div style="transform:translateX(30px);">
+    <div id="table" style="display:table; width:100%;">
+      <div id="header" style="display:table-header-group; break-inside:avoid;">
+        <div id="hitme" style="position:relative; left:30px; width:20px; height:20px;"></div>
+      </div>
+      <div style="display:table-row; break-inside:avoid;">
+        <div style="height:60px; background:blue;"></div>
+      </div>
+      <div style="display:table-row; break-inside:avoid;">
+        <div style="height:60px; background:blue;"></div>
+      </div>
+    </div>
+  </div>
+</div>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+  test(()=> { assert_equals(document.elementFromPoint(59, 10), header); }, "before first");
+  test(()=> { assert_equals(document.elementFromPoint(70, 10), hitme); }, "first");
+  test(()=> { assert_equals(document.elementFromPoint(91, 10), header); }, "after first");
+  test(()=> { assert_equals(document.elementFromPoint(159, 10), header); }, "before second");
+  test(()=> { assert_equals(document.elementFromPoint(170, 10), hitme); }, "second");
+  test(()=> { assert_equals(document.elementFromPoint(181, 10), header); }, "after second");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-break/table/repeated-section/hit-test-relative.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-break/table/repeated-section/hit-test-relative.tentative.html
new file mode 100644
index 0000000..348bb0d
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-break/table/repeated-section/hit-test-relative.tentative.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<style>
+  body { margin: 0; }
+</style>
+<div style="columns:2; width:200px; gap:0; column-fill:auto; height:100px;">
+  <div id="table" style="display:table; width:100%;">
+    <div id="header" style="display:table-header-group; break-inside:avoid;">
+      <div id="hitme" style="position:relative; left:30px; width:20px; height:20px;"></div>
+    </div>
+    <div style="display:table-row; break-inside:avoid;">
+      <div style="height:60px; background:blue;"></div>
+    </div>
+    <div style="display:table-row; break-inside:avoid;">
+      <div style="height:60px; background:blue;"></div>
+    </div>
+  </div>
+</div>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+  test(()=> { assert_equals(document.elementFromPoint(29, 10), header); }, "before first");
+  test(()=> { assert_equals(document.elementFromPoint(40, 10), hitme); }, "first");
+  test(()=> { assert_equals(document.elementFromPoint(61, 10), header); }, "after first");
+  test(()=> { assert_equals(document.elementFromPoint(129, 10), header); }, "before second");
+  test(()=> { assert_equals(document.elementFromPoint(140, 10), hitme); }, "second");
+  test(()=> { assert_equals(document.elementFromPoint(151, 10), header); }, "after second");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-break/table/repeated-section/hit-test.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-break/table/repeated-section/hit-test.tentative.html
new file mode 100644
index 0000000..9b462887c
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-break/table/repeated-section/hit-test.tentative.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<style>
+  body { margin: 0; }
+</style>
+<div style="columns:2; width:200px; gap:0; column-fill:auto; height:100px;">
+  <div id="table" style="display:table; width:100%;">
+    <div id="header" style="display:table-header-group; break-inside:avoid;">
+      <div id="hitme" style="width:20px; height:20px;"></div>
+    </div>
+    <div style="display:table-row; break-inside:avoid;">
+      <div style="height:60px; background:blue;"></div>
+    </div>
+    <div style="display:table-row; break-inside:avoid;">
+      <div style="height:60px; background:blue;"></div>
+    </div>
+  </div>
+</div>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+  test(()=> { assert_equals(document.elementFromPoint(10, 10), hitme); }, "first");
+  test(()=> { assert_equals(document.elementFromPoint(21, 10), header); }, "after first");
+  test(()=> { assert_equals(document.elementFromPoint(99, 10), header); }, "before second");
+  test(()=> { assert_equals(document.elementFromPoint(110, 10), hitme); }, "second");
+  test(()=> { assert_equals(document.elementFromPoint(121, 10), header); }, "after second");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/fetch/api/response/response-stream-bad-chunk.any.js b/third_party/blink/web_tests/external/wpt/fetch/api/response/response-stream-bad-chunk.any.js
new file mode 100644
index 0000000..d3d92e1
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/fetch/api/response/response-stream-bad-chunk.any.js
@@ -0,0 +1,24 @@
+// META: global=window,worker
+// META: title=Response causes TypeError from bad chunk type
+
+function runChunkTest(responseReaderMethod, testDescription) {
+  promise_test(test => {
+    let stream = new ReadableStream({
+      start(controller) {
+        controller.enqueue("not Uint8Array");
+        controller.close();
+      }
+    });
+
+    return promise_rejects_js(test, TypeError,
+      new Response(stream)[responseReaderMethod](),
+      'TypeError should propagate'
+    )
+  }, testDescription)
+}
+
+runChunkTest('arrayBuffer', 'ReadableStream with non-Uint8Array chunk passed to Response.arrayBuffer() causes TypeError');
+runChunkTest('blob',        'ReadableStream with non-Uint8Array chunk passed to Response.blob() causes TypeError');
+runChunkTest('formData',    'ReadableStream with non-Uint8Array chunk passed to Response.formData() causes TypeError');
+runChunkTest('json',        'ReadableStream with non-Uint8Array chunk passed to Response.json() causes TypeError');
+runChunkTest('text',        'ReadableStream with non-Uint8Array chunk passed to Response.text() causes TypeError');
diff --git a/third_party/blink/web_tests/fast/forms/calendar-picker/month-picker-touch-operations.html b/third_party/blink/web_tests/fast/forms/calendar-picker/month-picker-touch-operations.html
new file mode 100644
index 0000000..b9a4ab8
--- /dev/null
+++ b/third_party/blink/web_tests/fast/forms/calendar-picker/month-picker-touch-operations.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<link rel=help href="https://bugs.chromium.org/p/chromium/issues/detail?id=1321616">
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<script src="../../../resources/testdriver.js"></script>
+<script src="../../../resources/testdriver-vendor.js"></script>
+<script src="../resources/common.js"></script>
+<script src="../resources/picker-common.js"></script>
+<script src="resources/calendar-picker-common.js"></script>
+
+<input type=month id=picker>
+
+<script>
+const forceRunAnimation = () => {
+  internals.pagePopupWindow.AnimationManager.shared._animationFrameCallback(0);
+}
+
+promise_test(async () => {
+  await openPicker(picker);
+
+  const scrollView = internals.pagePopupWindow.global.picker.yearListView_.scrollView;
+  const scrollViewOffset = cumulativeOffset(scrollView.element);
+
+  eventSender.clearTouchPoints();
+  eventSender.addTouchPoint(scrollViewOffset[0] + 1, scrollViewOffset[1] + 1);
+  eventSender.touchStart();
+  eventSender.updateTouchPoint(0, scrollViewOffset[0] + 1, scrollViewOffset[1] + 50);
+  eventSender.touchMove();
+  eventSender.releaseTouchPoint(0);
+  eventSender.touchEnd();
+  forceRunAnimation();
+
+  const firstYearCell = internals.pagePopupWindow.global.picker.yearListView_.firstVisibleRow();
+  assert_not_equals(firstYearCell, null);
+}, `Verifies that the month picker should show year list`);
+
+</script>
diff --git a/third_party/blink/web_tests/printing/fixed-positioned-composited.html b/third_party/blink/web_tests/printing/fixed-positioned-composited.html
index b9230a5..70ce43e 100644
--- a/third_party/blink/web_tests/printing/fixed-positioned-composited.html
+++ b/third_party/blink/web_tests/printing/fixed-positioned-composited.html
@@ -3,7 +3,7 @@
 if (window.testRunner)
   testRunner.setPrinting();
 </script>
-<div style="position: fixed; top: 0; will-change-transform">
+<div style="position: fixed; top: 0; will-change: transform">
   crbug.com/848839: Composited fixed-position should repeat on every page.
 </div>
 <div style="height:10px;"></div>
diff --git a/third_party/closure_compiler/externs/file_manager_private.js b/third_party/closure_compiler/externs/file_manager_private.js
index c659a4c..3f60dc7 100644
--- a/third_party/closure_compiler/externs/file_manager_private.js
+++ b/third_party/closure_compiler/externs/file_manager_private.js
@@ -325,6 +325,16 @@
   ZIP: 'zip',
 };
 
+/** @enum {string} */
+chrome.fileManagerPrivate.RecentDateBucket = {
+  TODAY: 'today',
+  YESTERDAY: 'yesterday',
+  EARLIER_THIS_WEEK: 'earlier_this_week',
+  EARLIER_THIS_MONTH: 'earlier_this_month',
+  EARLIER_THIS_YEAR: 'earlier_this_year',
+  OLDER: 'older',
+};
+
 /**
  * @typedef {{
  *   appId: string,
@@ -703,11 +713,20 @@
  *   taskId: number,
  *   remainingSeconds: number,
  *   errorName: string,
+ *   outputs: (Array<Entry>|undefined),
  * }}
  */
 chrome.fileManagerPrivate.ProgressStatus;
 
 /**
+ * @typedef {{
+ *   sourceUrl: string,
+ *   isDlpRestricted: boolean,
+ * }}
+ */
+chrome.fileManagerPrivate.DlpMetadata;
+
+/**
  * Logout the current user for navigating to the re-authentication screen for
  * the Google account.
  */
@@ -925,10 +944,11 @@
  * Returns a list of files that are restricted by any Data Leak Prevention
  * (DLP) rule. |entries| list of source entries to be checked.
  * @param {!Array<!Entry>} entries
- * @param {!Array<!Entry>} callback Entries of files that are restricted
- * by at least one DLP rule.
+ * @param {function((!Array<!chrome.fileManagerPrivate.DlpMetadata>|undefined))} 
+ * callback Callback with the list of chrome.fileManagerPrivate.DlpMetadata
+ * containing DLP information about the entries.  
  */
-chrome.fileManagerPrivate.getFilesRestrictedByDlp = function(entries, callback) {};
+chrome.fileManagerPrivate.getDlpMetadata = function(entries, callback) {};
 
 /**
  * Starts to copy an entry. If the source is a directory, the copy is done
diff --git a/third_party/webrtc_overrides/BUILD.gn b/third_party/webrtc_overrides/BUILD.gn
index 330d0bcc..7ed3fd91 100644
--- a/third_party/webrtc_overrides/BUILD.gn
+++ b/third_party/webrtc_overrides/BUILD.gn
@@ -213,11 +213,6 @@
     "metronome_task_queue_factory.h",
 
     # Tested in
-    # third_party/blink/renderer/platform/peerconnection/task_queue_factory_test.cc
-    "task_queue_factory.cc",
-    "task_queue_factory.h",
-
-    # Tested in
     # third_party/blink/renderer/platform/peerconnection/webrtc_timer_test.cc
     "webrtc_timer.cc",
     "webrtc_timer.h",
@@ -256,18 +251,3 @@
   sources = [ "rtc_base/system_time.cc" ]
   deps = [ "//base" ]
 }
-
-if (is_android) {
-  java_cpp_features("java_features_srcjar") {
-    # External code should depend on ":webrtc_overrides_java" instead.
-    visibility = [ ":*" ]
-    sources = [
-      "//third_party/webrtc_overrides/metronome_task_queue_factory.cc",
-      "//third_party/webrtc_overrides/webrtc_timer.cc",
-    ]
-    template = "//third_party/webrtc_overrides/java_templates/WebRtcOverridesFeatures.java.tmpl"
-  }
-  android_library("webrtc_overrides_java") {
-    srcjar_deps = [ ":java_features_srcjar" ]
-  }
-}
diff --git a/third_party/webrtc_overrides/java_templates/WebRtcOverridesFeatures.java.tmpl b/third_party/webrtc_overrides/java_templates/WebRtcOverridesFeatures.java.tmpl
deleted file mode 100644
index f053669..0000000
--- a/third_party/webrtc_overrides/java_templates/WebRtcOverridesFeatures.java.tmpl
+++ /dev/null
@@ -1,16 +0,0 @@
-// Copyright 2022 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.webrtc_overrides;
-
-/**
- * Contains features that are specific to third_party/webrtc_overrides/.
- */
-public final class WebRtcOverridesFeatures {{
-
-{NATIVE_FEATURES}
-
-    // Prevents instantiation.
-    private WebRtcOverridesFeatures() {{}}
-}}
diff --git a/third_party/webrtc_overrides/metronome_source.cc b/third_party/webrtc_overrides/metronome_source.cc
index c04d2c3a..c064801 100644
--- a/third_party/webrtc_overrides/metronome_source.cc
+++ b/third_party/webrtc_overrides/metronome_source.cc
@@ -21,7 +21,6 @@
 #include "base/trace_event/typed_macros.h"
 #include "third_party/webrtc/api/metronome/metronome.h"
 #include "third_party/webrtc/rtc_base/task_utils/to_queued_task.h"
-#include "third_party/webrtc_overrides/task_queue_factory.h"
 
 namespace blink {
 
diff --git a/third_party/webrtc_overrides/metronome_task_queue_factory.cc b/third_party/webrtc_overrides/metronome_task_queue_factory.cc
index 84e09e2f..79bb617 100644
--- a/third_party/webrtc_overrides/metronome_task_queue_factory.cc
+++ b/third_party/webrtc_overrides/metronome_task_queue_factory.cc
@@ -17,13 +17,9 @@
 #include "third_party/webrtc/api/task_queue/task_queue_factory.h"
 #include "third_party/webrtc_overrides/coalesced_tasks.h"
 #include "third_party/webrtc_overrides/metronome_source.h"
-#include "third_party/webrtc_overrides/task_queue_factory.h"
 
 namespace blink {
 
-const base::Feature kWebRtcMetronomeTaskQueue{"WebRtcMetronomeTaskQueue",
-                                              base::FEATURE_ENABLED_BY_DEFAULT};
-
 class WebRtcMetronomeTaskQueue : public webrtc::TaskQueueBase {
  public:
   explicit WebRtcMetronomeTaskQueue(base::TaskTraits traits);
@@ -154,6 +150,31 @@
 
 namespace {
 
+base::TaskTraits TaskQueuePriority2Traits(
+    webrtc::TaskQueueFactory::Priority priority) {
+  // The content/renderer/media/webrtc/rtc_video_encoder.* code
+  // employs a PostTask/Wait pattern that uses TQ in a way that makes it
+  // blocking and synchronous, which is why we allow WithBaseSyncPrimitives()
+  // for OS_ANDROID.
+  switch (priority) {
+    case webrtc::TaskQueueFactory::Priority::HIGH:
+#if defined(OS_ANDROID)
+      return {base::WithBaseSyncPrimitives(), base::TaskPriority::HIGHEST};
+#else
+      return {base::TaskPriority::HIGHEST};
+#endif
+    case webrtc::TaskQueueFactory::Priority::LOW:
+      return {base::MayBlock(), base::TaskPriority::BEST_EFFORT};
+    case webrtc::TaskQueueFactory::Priority::NORMAL:
+    default:
+#if defined(OS_ANDROID)
+      return {base::WithBaseSyncPrimitives()};
+#else
+      return {};
+#endif
+  }
+}
+
 class WebrtcMetronomeTaskQueueFactory final : public webrtc::TaskQueueFactory {
  public:
   std::unique_ptr<webrtc::TaskQueueBase, webrtc::TaskQueueDeleter>
@@ -172,3 +193,10 @@
   return std::unique_ptr<webrtc::TaskQueueFactory>(
       new blink::WebrtcMetronomeTaskQueueFactory());
 }
+
+std::unique_ptr<webrtc::TaskQueueBase, webrtc::TaskQueueDeleter>
+CreateWebRtcTaskQueue(webrtc::TaskQueueFactory::Priority priority) {
+  return std::unique_ptr<webrtc::TaskQueueBase, webrtc::TaskQueueDeleter>(
+      new blink::WebRtcMetronomeTaskQueue(
+          blink::TaskQueuePriority2Traits(priority)));
+}
diff --git a/third_party/webrtc_overrides/metronome_task_queue_factory.h b/third_party/webrtc_overrides/metronome_task_queue_factory.h
index bca8a5a..40bc4a97 100644
--- a/third_party/webrtc_overrides/metronome_task_queue_factory.h
+++ b/third_party/webrtc_overrides/metronome_task_queue_factory.h
@@ -12,17 +12,13 @@
 #include "third_party/webrtc/api/task_queue/task_queue_factory.h"
 #include "third_party/webrtc/rtc_base/system/rtc_export.h"
 
-namespace blink {
-
-// Whether WebRTC should use a metronome-backed task queue. Default: disabled.
-RTC_EXPORT extern const base::Feature kWebRtcMetronomeTaskQueue;
-
-}  // namespace blink
-
 // Creates a factory for webrtc::TaskQueueBase that is backed by a
 // blink::MetronomeSource. Tested by
 // /third_party/blink/renderer/platform/peerconnection/metronome_task_queue_factory_test.cc
 RTC_EXPORT std::unique_ptr<webrtc::TaskQueueFactory>
 CreateWebRtcMetronomeTaskQueueFactory();
 
+RTC_EXPORT std::unique_ptr<webrtc::TaskQueueBase, webrtc::TaskQueueDeleter>
+CreateWebRtcTaskQueue(webrtc::TaskQueueFactory::Priority priority);
+
 #endif  // THIRD_PARTY_WEBRTC_OVERRIDES_METRONOME_TASK_QUEUE_FACTORY_H_
diff --git a/third_party/webrtc_overrides/task_queue_factory.cc b/third_party/webrtc_overrides/task_queue_factory.cc
deleted file mode 100644
index 3490768..0000000
--- a/third_party/webrtc_overrides/task_queue_factory.cc
+++ /dev/null
@@ -1,146 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "third_party/webrtc_overrides/task_queue_factory.h"
-
-#include "base/bind.h"
-#include "base/memory/ref_counted.h"
-#include "base/memory/scoped_refptr.h"
-#include "base/synchronization/waitable_event.h"
-#include "base/task/task_traits.h"
-#include "base/task/thread_pool.h"
-#include "build/build_config.h"
-#include "third_party/webrtc/api/task_queue/task_queue_base.h"
-#include "third_party/webrtc/api/task_queue/task_queue_factory.h"
-
-namespace blink {
-
-class WebrtcTaskQueue final : public webrtc::TaskQueueBase {
- public:
-  explicit WebrtcTaskQueue(const base::TaskTraits& traits)
-      : task_runner_(base::ThreadPool::CreateSequencedTaskRunner(traits)),
-        is_active_(new base::RefCountedData<bool>(true)) {
-    DCHECK(task_runner_);
-  }
-
-  void Delete() override;
-  void PostTask(std::unique_ptr<webrtc::QueuedTask> task) override;
-  void PostDelayedTask(std::unique_ptr<webrtc::QueuedTask> task,
-                       uint32_t milliseconds) override;
-
- private:
-  ~WebrtcTaskQueue() override = default;
-
-  static void RunTask(WebrtcTaskQueue* task_queue,
-                      scoped_refptr<base::RefCountedData<bool>> is_active,
-                      std::unique_ptr<webrtc::QueuedTask> task);
-
-  const scoped_refptr<base::SequencedTaskRunner> task_runner_;
-  // Value of |is_active_| is checked and set on |task_runner_|.
-  const scoped_refptr<base::RefCountedData<bool>> is_active_;
-};
-
-void Deactivate(scoped_refptr<base::RefCountedData<bool>> is_active,
-                base::WaitableEvent* event) {
-  is_active->data = false;
-  event->Signal();
-}
-
-void WebrtcTaskQueue::Delete() {
-  DCHECK(!IsCurrent());
-  base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL,
-                            base::WaitableEvent::InitialState::NOT_SIGNALED);
-  task_runner_->PostTask(FROM_HERE,
-                         base::BindOnce(&Deactivate, is_active_, &event));
-  event.Wait();
-  delete this;
-}
-
-void WebrtcTaskQueue::RunTask(
-    WebrtcTaskQueue* task_queue,
-    scoped_refptr<base::RefCountedData<bool>> is_active,
-    std::unique_ptr<webrtc::QueuedTask> task) {
-  if (!is_active->data)
-    return;
-
-  CurrentTaskQueueSetter set_current(task_queue);
-  webrtc::QueuedTask* task_ptr = task.release();
-  if (task_ptr->Run()) {
-    // Delete task_ptr before CurrentTaskQueueSetter clears state that this code
-    // is running on the task queue.
-    delete task_ptr;
-  }
-}
-
-void WebrtcTaskQueue::PostTask(std::unique_ptr<webrtc::QueuedTask> task) {
-  // Posted Task might outlive this, but access to this is guarded by
-  // ref-counted |is_active_| flag.
-  task_runner_->PostTask(
-      FROM_HERE,
-      base::BindOnce(&WebrtcTaskQueue::RunTask, base::Unretained(this),
-                     is_active_, std::move(task)));
-}
-
-void WebrtcTaskQueue::PostDelayedTask(std::unique_ptr<webrtc::QueuedTask> task,
-                                      uint32_t milliseconds) {
-  task_runner_->PostDelayedTaskAt(
-      base::subtle::PostDelayedTaskPassKey(), FROM_HERE,
-      base::BindOnce(&WebrtcTaskQueue::RunTask, base::Unretained(this),
-                     is_active_, std::move(task)),
-      base::TimeTicks::Now() + base::Milliseconds(milliseconds),
-      base::subtle::DelayPolicy::kPrecise);
-}
-
-base::TaskTraits TaskQueuePriority2Traits(
-    webrtc::TaskQueueFactory::Priority priority) {
-  // The content/renderer/media/webrtc/rtc_video_encoder.* code
-  // employs a PostTask/Wait pattern that uses TQ in a way that makes it
-  // blocking and synchronous, which is why we allow WithBaseSyncPrimitives()
-  // for OS_ANDROID.
-  switch (priority) {
-    case webrtc::TaskQueueFactory::Priority::HIGH:
-#if defined(OS_ANDROID)
-      return {base::WithBaseSyncPrimitives(), base::TaskPriority::HIGHEST};
-#else
-      return {base::TaskPriority::HIGHEST};
-#endif
-    case webrtc::TaskQueueFactory::Priority::LOW:
-      return {base::MayBlock(), base::TaskPriority::BEST_EFFORT};
-    case webrtc::TaskQueueFactory::Priority::NORMAL:
-    default:
-#if defined(OS_ANDROID)
-      return {base::WithBaseSyncPrimitives()};
-#else
-      return {};
-#endif
-  }
-}
-
-namespace {
-
-class WebrtcTaskQueueFactory final : public webrtc::TaskQueueFactory {
- public:
-  WebrtcTaskQueueFactory() = default;
-
-  std::unique_ptr<webrtc::TaskQueueBase, webrtc::TaskQueueDeleter>
-  CreateTaskQueue(absl::string_view /*name*/,
-                  Priority priority) const override {
-    return std::unique_ptr<webrtc::TaskQueueBase, webrtc::TaskQueueDeleter>(
-        new WebrtcTaskQueue(TaskQueuePriority2Traits(priority)));
-  }
-};
-
-}  // namespace
-
-}  // namespace blink
-
-std::unique_ptr<webrtc::TaskQueueFactory> CreateWebRtcTaskQueueFactory() {
-  return std::make_unique<blink::WebrtcTaskQueueFactory>();
-}
-
-std::unique_ptr<webrtc::TaskQueueBase, webrtc::TaskQueueDeleter>
-CreateWebRtcTaskQueue(webrtc::TaskQueueFactory::Priority priority) {
-  return std::unique_ptr<webrtc::TaskQueueBase, webrtc::TaskQueueDeleter>(
-      new blink::WebrtcTaskQueue(blink::TaskQueuePriority2Traits(priority)));
-}
diff --git a/third_party/webrtc_overrides/task_queue_factory.h b/third_party/webrtc_overrides/task_queue_factory.h
deleted file mode 100644
index 27dcd07..0000000
--- a/third_party/webrtc_overrides/task_queue_factory.h
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef THIRD_PARTY_WEBRTC_OVERRIDES_TASK_QUEUE_FACTORY_H_
-#define THIRD_PARTY_WEBRTC_OVERRIDES_TASK_QUEUE_FACTORY_H_
-
-#include <memory>
-
-#include "base/task/task_traits.h"
-#include "third_party/webrtc/api/task_queue/task_queue_base.h"
-#include "third_party/webrtc/api/task_queue/task_queue_factory.h"
-#include "third_party/webrtc/rtc_base/system/rtc_export.h"
-
-namespace blink {
-
-RTC_EXPORT base::TaskTraits TaskQueuePriority2Traits(
-    webrtc::TaskQueueFactory::Priority priority);
-
-}  // namespace blink
-
-// Creates factory for webrtc::TaskQueueBase backed by base::SequencedTaskRunner
-// Tested by
-// /third_party/blink/renderer/platform/peerconnection/task_queue_factory_test.cc
-RTC_EXPORT std::unique_ptr<webrtc::TaskQueueFactory>
-CreateWebRtcTaskQueueFactory();
-
-RTC_EXPORT std::unique_ptr<webrtc::TaskQueueBase, webrtc::TaskQueueDeleter>
-CreateWebRtcTaskQueue(webrtc::TaskQueueFactory::Priority priority);
-
-#endif  // THIRD_PARTY_WEBRTC_OVERRIDES_TASK_QUEUE_FACTORY_H_
diff --git a/third_party/webrtc_overrides/webrtc_timer.cc b/third_party/webrtc_overrides/webrtc_timer.cc
index 534ec6f4..b0999add 100644
--- a/third_party/webrtc_overrides/webrtc_timer.cc
+++ b/third_party/webrtc_overrides/webrtc_timer.cc
@@ -9,17 +9,12 @@
 
 namespace blink {
 
-const base::Feature kWebRtcTimerUsesMetronome{"WebRtcTimerUsesMetronome",
-                                              base::FEATURE_ENABLED_BY_DEFAULT};
-
 WebRtcTimer::SchedulableCallback::SchedulableCallback(
     scoped_refptr<base::SequencedTaskRunner> task_runner,
     base::RepeatingCallback<void()> callback,
-    bool use_metronome,
     base::TimeDelta repeated_delay)
     : task_runner_(std::move(task_runner)),
       callback_(std::move(callback)),
-      use_metronome_(use_metronome),
       repeated_delay_(std::move(repeated_delay)) {}
 
 WebRtcTimer::SchedulableCallback::~SchedulableCallback() {
@@ -32,11 +27,9 @@
   DCHECK_EQ(scheduled_time_, base::TimeTicks::Max())
       << "The callback has already been scheduled.";
   scheduled_time_ = scheduled_time;
-  base::TimeTicks target_time = scheduled_time_;
-  if (use_metronome_) {
-    // Snap target time to metronome tick!
-    target_time = MetronomeSource::TimeSnappedToNextTick(target_time);
-  }
+  // Snap target time to metronome tick!
+  base::TimeTicks target_time =
+      MetronomeSource::TimeSnappedToNextTick(scheduled_time_);
   task_runner_->PostDelayedTaskAt(
       base::subtle::PostDelayedTaskPassKey(), FROM_HERE,
       base::BindOnce(&WebRtcTimer::SchedulableCallback::MaybeRun, this),
@@ -88,9 +81,7 @@
 
 WebRtcTimer::WebRtcTimer(scoped_refptr<base::SequencedTaskRunner> task_runner,
                          base::RepeatingCallback<void()> callback)
-    : callback_(std::move(callback)),
-      use_metronome_(base::FeatureList::IsEnabled(kWebRtcTimerUsesMetronome)),
-      task_runner_(std::move(task_runner)) {}
+    : callback_(std::move(callback)), task_runner_(std::move(task_runner)) {}
 
 WebRtcTimer::~WebRtcTimer() {
   DCHECK(is_shutdown_);
@@ -150,7 +141,7 @@
 void WebRtcTimer::ScheduleCallback(base::TimeTicks scheduled_time) {
   if (!schedulable_callback_) {
     schedulable_callback_ = base::MakeRefCounted<SchedulableCallback>(
-        task_runner_, callback_, use_metronome_, repeated_delay_);
+        task_runner_, callback_, repeated_delay_);
   }
   schedulable_callback_->Schedule(scheduled_time);
 }
diff --git a/third_party/webrtc_overrides/webrtc_timer.h b/third_party/webrtc_overrides/webrtc_timer.h
index 04c2a55..62fdc1b6 100644
--- a/third_party/webrtc_overrides/webrtc_timer.h
+++ b/third_party/webrtc_overrides/webrtc_timer.h
@@ -18,18 +18,14 @@
 
 namespace blink {
 
-// Whether WebRtcTimer should use the metronome source. Default: false.
-RTC_EXPORT extern const base::Feature kWebRtcTimerUsesMetronome;
-
-// Implements a timer that is NOT guaranteed to have high precision.
+// Implements a low precision timer, expect it to fire up to ~16 ms late (plus
+// any OS or workload related delays).
 //
-// When a metronome source is available and kWebRtcTimerUsesMetronome is
-// enabled, the timer will fire on metronome ticks. This allows running the
-// timer without increasing Idle Wake Ups at the cost of reducing the timer
-// precision to the metronome tick frequency. When a metronome source isn't
-// available, the timer has high precision.
-//
-// Prefer this timer for WebRTC use cases that does not require high precision.
+// This timer only fires on metronome ticks as specified by
+// MetronomeSource::TimeSnappedToNextTick(). This allows running numerous timers
+// without increasing the Idle Wake Ups frequency beyond the metronome tick
+// frequency, i.e. when each tick is already scheduled adding a WebRtcTimer
+// should not add any Idle Wake Ups.
 class RTC_EXPORT WebRtcTimer final {
  public:
   WebRtcTimer(scoped_refptr<base::SequencedTaskRunner> task_runner,
@@ -64,7 +60,6 @@
    public:
     SchedulableCallback(scoped_refptr<base::SequencedTaskRunner> task_runner,
                         base::RepeatingCallback<void()> callback,
-                        bool use_metronome,
                         base::TimeDelta repeated_delay);
     ~SchedulableCallback();
 
@@ -81,7 +76,6 @@
 
     const scoped_refptr<base::SequencedTaskRunner> task_runner_;
     const base::RepeatingCallback<void()> callback_;
-    const bool use_metronome_;
 
     // Only accessed on |task_runner_|.
     bool is_currently_running_ = false;
@@ -106,7 +100,6 @@
   void RescheduleCallback() EXCLUSIVE_LOCKS_REQUIRED(lock_);
 
   const base::RepeatingCallback<void()> callback_;
-  const bool use_metronome_;
   base::Lock lock_;
   bool is_shutdown_ GUARDED_BY(lock_) = false;
   scoped_refptr<base::SequencedTaskRunner> task_runner_ GUARDED_BY(lock_);
diff --git a/tools/gritsettings/resource_ids.spec b/tools/gritsettings/resource_ids.spec
index f239150..626187e 100644
--- a/tools/gritsettings/resource_ids.spec
+++ b/tools/gritsettings/resource_ids.spec
@@ -451,7 +451,7 @@
     "structures": [2800],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/test/data/webui/resources.grd": {
-    "META": {"sizes": {"includes": [600],}},
+    "META": {"sizes": {"includes": [900],}},
     "includes": [2810],
   },
   "chrome/test/data/webui_test_resources.grd": {
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index a866f7c..ad464dd4 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -34740,7 +34740,7 @@
   <int value="1660" label="SHAREDSTORAGEPRIVATE_GET"/>
   <int value="1661" label="SHAREDSTORAGEPRIVATE_SET"/>
   <int value="1662" label="SHAREDSTORAGEPRIVATE_REMOVE"/>
-  <int value="1663" label="FILEMANAGERPRIVATEINTERNAL_GETFILESRESTRICTEDBYDLP"/>
+  <int value="1663" label="FILEMANAGERPRIVATEINTERNAL_GETDLPMETADATA"/>
   <int value="1664" label="WMDESKSPRIVATE_GETALLDESKS"/>
   <int value="1665" label="AUTOTESTPRIVATE_FORCEAUTOTHEMEMODE"/>
   <int value="1666" label="OS_TELEMETRY_GETSTATEFULPARTITIONINFO"/>
diff --git a/tools/metrics/histograms/metadata/net/histograms.xml b/tools/metrics/histograms/metadata/net/histograms.xml
index 3aa5600..ab810fae 100644
--- a/tools/metrics/histograms/metadata/net/histograms.xml
+++ b/tools/metrics/histograms/metadata/net/histograms.xml
@@ -324,6 +324,25 @@
   </summary>
 </histogram>
 
+<histogram name="Net.CertVerifier.MacTrustDomainCertCount.{Domain}"
+    units="certificates" expires_after="2023-02-01">
+  <owner>mattm@chromium.org</owner>
+  <owner>hchao@chromium.org</owner>
+  <summary>
+    When the builtin certificate verifier is used on Mac and the platform
+    trusted certificates cache is initialized (or re-initialized if keychain
+    trust settings changed), records the number of certificates read from the
+    {Domain} trust domain. Only counts certificates that were successfully
+    parsed. Only recorded by the DomainCacheFullCerts implementation, which may
+    or may not be used depending on Finch trial parameters.
+  </summary>
+  <token key="Domain">
+    <variant name="Admin"/>
+    <variant name="System"/>
+    <variant name="User"/>
+  </token>
+</histogram>
+
 <histogram base="true" name="Net.CertVerifier.NameNormalizationPrivateRoots"
     enum="NetCertificateNameNormalization" expires_after="2022-11-13">
   <owner>mattm@chromium.org</owner>
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json
index 17f81de4..7bd91ec7 100644
--- a/tools/perf/core/perfetto_binary_roller/binary_deps.json
+++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -5,24 +5,24 @@
             "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux_arm64/49b4b5dcbc312d8d2c3751cf29238b8efeb4e494/trace_processor_shell"
         },
         "win": {
-            "hash": "9c03bc914458eff778dc222763c10570b342fb97",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/aee702e883cb0fd9a087a71b467746f54d68ca53/trace_processor_shell.exe"
+            "hash": "5c26236044a135f11d8afc32a4e512aab2e8bb16",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/ddc88694c41b71562428bd08b168666c8d42eaf3/trace_processor_shell.exe"
         },
         "linux_arm": {
             "hash": "58893933be305d3bfe0a72ebebcacde2ac3ca893",
             "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux_arm/49b4b5dcbc312d8d2c3751cf29238b8efeb4e494/trace_processor_shell"
         },
         "mac": {
-            "hash": "1c9ca8c85a340fdd3998ac85a68cfdbb78aa07d3",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/mac/aee702e883cb0fd9a087a71b467746f54d68ca53/trace_processor_shell"
+            "hash": "a5c7698f7767316fcaf1731d44ec2901f235b719",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/mac/29c99a813c0b03800cc2b06f521d9dfbf590194b/trace_processor_shell"
         },
         "mac_arm64": {
             "hash": "e1ad4861384b06d911a65f035317914b8cc975c6",
             "full_remote_path": "perfetto-luci-artifacts/v25.0/mac-arm64/trace_processor_shell"
         },
         "linux": {
-            "hash": "9a7e89dd21b196a0486e1f1a0e4fb5a028f0e75e",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/aee702e883cb0fd9a087a71b467746f54d68ca53/trace_processor_shell"
+            "hash": "8d7b1bfa3e5c905d2286b2379534d9c48f4bc5f8",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/ddc88694c41b71562428bd08b168666c8d42eaf3/trace_processor_shell"
         }
     },
     "power_profile.sql": {
diff --git a/ui/file_manager/BUILD.gn b/ui/file_manager/BUILD.gn
index 944dd4e8..3fa34577 100644
--- a/ui/file_manager/BUILD.gn
+++ b/ui/file_manager/BUILD.gn
@@ -213,16 +213,7 @@
       "//ui/file_manager/file_manager/foreground/js:build_worker",
     ]
   } else {
-    manifest_files = [
-      "$target_gen_dir/manifest_preprocess_generated_image_loader.json",
-      "$target_gen_dir/manifest_preprocess_static_image_loader.json",
-      "$target_gen_dir/tsconfig.manifest",
-    ]
-
-    deps += [
-      ":preprocess_generated_image_loader",
-      ":preprocess_static_image_loader",
-    ]
+    manifest_files = [ "$target_gen_dir/tsconfig.manifest" ]
   }
 }
 
@@ -258,10 +249,44 @@
   extra_deps = [
     ":copy_ts",
     ":preprocess_generated",
+    ":preprocess_generated_image_loader",
     ":preprocess_static",
+    ":preprocess_static_image_loader",
   ]
 
   deps = [ "//ui/webui/resources:library" ]
 
-  in_files = static_js_files + generated_js_files + ts_files
+  in_files = static_js_files + generated_js_files + ts_files +
+             image_loader_static_js_files + image_loader_generated_js_files
+
+  definitions = [
+    "//ui/file_manager/file_manager/definitions/file_manager.d.ts",
+    "//ui/file_manager/file_manager/definitions/volume_manager.d.ts",
+  ]
+}
+
+# GRD for test files.
+generate_grd("build_tests_grdp") {
+  testonly = true
+  grd_prefix = "file_manager_test"
+  out_grd = "$target_gen_dir/tests_resources.grdp"
+
+  input_files_base_dir = rebase_path(".", "//")
+  input_files = unittest_files
+  deps = [ ":unit_test_data" ]
+}
+
+# GRD for the actual application files that are processed by TS compiler.
+generate_grd("build_tests_gen_grdp") {
+  testonly = true
+  grd_prefix = "file_manager_test"
+  out_grd = "$target_gen_dir/tests_gen_resources.grdp"
+
+  input_files_base_dir = rebase_path(target_gen_dir, root_build_dir)
+  input_files = generated_test_htmls
+  manifest_files = [ "$target_gen_dir/tsconfig.manifest" ]
+  deps = [
+    ":build_ts",
+    ":unit_test_data",
+  ]
 }
diff --git a/ui/file_manager/base/gn/js_test_gen_html.py b/ui/file_manager/base/gn/js_test_gen_html.py
index 761a57be..0454e2d0 100644
--- a/ui/file_manager/base/gn/js_test_gen_html.py
+++ b/ui/file_manager/base/gn/js_test_gen_html.py
@@ -24,53 +24,55 @@
 
 
 def _process_js_module(input_file, output_filename):
-  """Generates the HTML for a unittest based on JS Modules.
+    """Generates the HTML for a unittest based on JS Modules.
 
   Args:
     input_file: The path for the unittest JS module.
     output_filename: The path/filename for HTML to be generated.
   """
 
-  # Map //ui/file_manager files to test URL:
-  js_module_url = input_file.replace(
-      'ui/file_manager/', 'chrome://file_manager_test/ui/file_manager/', 1)
+    # Map //ui/file_manager files to test URL:
+    js_module_url = input_file.replace('ui/file_manager/',
+                                       'chrome://webui-test/', 1)
 
-  with open(output_filename, 'w') as out:
-    out.write(_HTML_FILE_START + '\n')
+    with open(output_filename, 'w') as out:
+        out.write(_HTML_FILE_START + '\n')
 
-    line = _JS_MODULE % (js_module_url)
-    out.write(line + '\n')
+        line = _JS_MODULE % (js_module_url)
+        out.write(line + '\n')
 
-    line = _JS_MODULE_REGISTER_TESTS % (js_module_url)
-    out.write(line + '\n')
+        line = _JS_MODULE_REGISTER_TESTS % (js_module_url)
+        out.write(line + '\n')
 
 
 def main():
-  parser = ArgumentParser()
-  parser.add_argument(
-      '-s', '--src_path', help='Path to //src/ directory', required=True)
-  parser.add_argument(
-      '-i',
-      '--input',
-      help='Input dependency file generated by js_library.py',
-      required=True)
-  parser.add_argument(
-      '-o',
-      '--output',
-      help='Generated html output with flattened dependencies',
-      required=True)
-  parser.add_argument('-t', '--target_name', help='Test target name')
-  args = parser.parse_args()
+    parser = ArgumentParser()
+    parser.add_argument('-s',
+                        '--src_path',
+                        help='Path to //src/ directory',
+                        required=True)
+    parser.add_argument(
+        '-i',
+        '--input',
+        help='Input dependency file generated by js_library.py',
+        required=True)
+    parser.add_argument(
+        '-o',
+        '--output',
+        help='Generated html output with flattened dependencies',
+        required=True)
+    parser.add_argument('-t', '--target_name', help='Test target name')
+    args = parser.parse_args()
 
-  # Convert from:
-  # gen/ui/file_manager/file_manager/common/js/example_unittest.m.js_library
-  # To:
-  # ui/file_manager/file_manager/common/js/example_unittest.m.js
-  path_test_file = args.input.replace('gen/', '', 1)
-  path_test_file = path_test_file.replace('.js_library', '.js')
-  _process_js_module(path_test_file, args.output)
-  return
+    # Convert from:
+    # gen/ui/file_manager/file_manager/common/js/example_unittest.m.js_library
+    # To:
+    # ui/file_manager/file_manager/common/js/example_unittest.m.js
+    path_test_file = args.input.replace('gen/', '', 1)
+    path_test_file = path_test_file.replace('.js_library', '.js')
+    _process_js_module(path_test_file, args.output)
+    return
 
 
 if __name__ == '__main__':
-  main()
+    main()
diff --git a/ui/file_manager/file_manager/common/js/BUILD.gn b/ui/file_manager/file_manager/common/js/BUILD.gn
index d2681715..aa762f3 100644
--- a/ui/file_manager/file_manager/common/js/BUILD.gn
+++ b/ui/file_manager/file_manager/common/js/BUILD.gn
@@ -42,6 +42,7 @@
     ":notifications_browser_proxy",
     ":power",
     ":progress_center_common",
+    ":recent_date_bucket",
     ":storage_adapter",
     ":trash",
     ":url_constants",
@@ -72,6 +73,7 @@
     ":notifications_browser_proxy",
     ":power",
     ":progress_center_common",
+    ":recent_date_bucket",
     ":storage_adapter",
     ":trash",
     ":url_constants",
@@ -297,6 +299,22 @@
 js_library("power") {
 }
 
+js_library("recent_date_bucket") {
+  deps = [
+    "//ui/file_manager/file_manager/externs:file_manager_private",
+    "//ui/webui/resources/js:load_time_data.m",
+  ]
+}
+
+js_unittest("recent_date_bucket_unittest.m") {
+  deps = [
+    ":mock_chrome",
+    ":recent_date_bucket",
+    "//ui/webui/resources/js:assert.m",
+    "//ui/webui/resources/js:load_time_data.m",
+  ]
+}
+
 js_library("storage_adapter") {
   externs_list = [ "$externs_path/chrome_extensions.js" ]
 }
@@ -389,6 +407,7 @@
     ":filtered_volume_manager_unittest.m",
     ":importer_common_unittest.m",
     ":lru_cache_unittest.m",
+    ":recent_date_bucket_unittest.m",
     ":storage_adapter_unittest.m",
     ":util_unittest.m",
     ":volume_manager_types_unittest.m",
diff --git a/ui/file_manager/file_manager/common/js/api.js b/ui/file_manager/file_manager/common/js/api.js
index 675ddfea..8dadd0b 100644
--- a/ui/file_manager/file_manager/common/js/api.js
+++ b/ui/file_manager/file_manager/common/js/api.js
@@ -101,6 +101,17 @@
 }
 
 /**
+ * Wrap the chrome.fileManagerPrivate.getDlpMetadata function in an async/await
+ * compatible style.
+ * @param {!Array<!Entry>} entries entries to be checked
+ * @return {!Promise<!Array<!chrome.fileManagerPrivate.DlpMetadata>>} list of
+ *     DlpMetadata
+ */
+export async function getDlpMetadata(entries) {
+  return promisify(chrome.fileManagerPrivate.getDlpMetadata, entries);
+}
+
+/**
  * Lists Guest OSs which support having their files mounted.
  * @return {!Promise<!Array<!chrome.fileManagerPrivate.MountableGuest>>}
  */
diff --git a/ui/file_manager/file_manager/common/js/recent_date_bucket.js b/ui/file_manager/file_manager/common/js/recent_date_bucket.js
new file mode 100644
index 0000000..c45b800
--- /dev/null
+++ b/ui/file_manager/file_manager/common/js/recent_date_bucket.js
@@ -0,0 +1,48 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview Recent date bucket definition and util functions.
+ */
+
+import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
+
+/**
+ * Given a date and now date, return the date bucket it belongs to.
+ *
+ * @param {!Date|undefined} date
+ * @param {!Date} now
+ * @return {!chrome.fileManagerPrivate.RecentDateBucket}
+ */
+export function getRecentDateBucket(date, now) {
+  if (!date) {
+    return chrome.fileManagerPrivate.RecentDateBucket.OLDER;
+  }
+  const startOfToday = new Date(now);
+  startOfToday.setHours(0, 0, 0);
+  if (date >= startOfToday) {
+    return chrome.fileManagerPrivate.RecentDateBucket.TODAY;
+  }
+  const startOfYesterday = new Date(startOfToday);
+  startOfYesterday.setDate(startOfToday.getDate() - 1);
+  if (date >= startOfYesterday) {
+    return chrome.fileManagerPrivate.RecentDateBucket.YESTERDAY;
+  }
+  const startOfThisWeek = new Date(startOfToday);
+  const localeBasedWeekStart = loadTimeData.getInteger('WEEK_START_FROM') || 0;
+  const daysDiff = (startOfToday.getDay() - localeBasedWeekStart + 7) % 7;
+  startOfThisWeek.setDate(startOfToday.getDate() - daysDiff);
+  if (date >= startOfThisWeek) {
+    return chrome.fileManagerPrivate.RecentDateBucket.EARLIER_THIS_WEEK;
+  }
+  const startOfThisMonth = new Date(now.getFullYear(), now.getMonth(), 1);
+  if (date >= startOfThisMonth) {
+    return chrome.fileManagerPrivate.RecentDateBucket.EARLIER_THIS_MONTH;
+  }
+  const startOfThisYear = new Date(now.getFullYear(), 0, 1);
+  if (date >= startOfThisYear) {
+    return chrome.fileManagerPrivate.RecentDateBucket.EARLIER_THIS_YEAR;
+  }
+  return chrome.fileManagerPrivate.RecentDateBucket.OLDER;
+}
diff --git a/ui/file_manager/file_manager/common/js/recent_date_bucket_unittest.m.js b/ui/file_manager/file_manager/common/js/recent_date_bucket_unittest.m.js
new file mode 100644
index 0000000..c11f153
--- /dev/null
+++ b/ui/file_manager/file_manager/common/js/recent_date_bucket_unittest.m.js
@@ -0,0 +1,157 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
+import {assertEquals} from 'chrome://test/chai_assert.js';
+
+import {installMockChrome} from '../../common/js/mock_chrome.js';
+
+import {getRecentDateBucket} from './recent_date_bucket.js';
+
+export function setUp() {
+  loadTimeData.resetForTesting({
+    WEEK_START_FROM: 1,
+  });
+
+  const mockChrome = {
+    fileManagerPrivate: {
+      RecentDateBucket: {
+        TODAY: 'today',
+        YESTERDAY: 'yesterday',
+        EARLIER_THIS_WEEK: 'earlier_this_week',
+        EARLIER_THIS_MONTH: 'earlier_this_month',
+        EARLIER_THIS_YEAR: 'earlier_this_year',
+        OLDER: 'older',
+      },
+    }
+  };
+  installMockChrome(mockChrome);
+}
+
+export function testGetRecentDateBucket() {
+  assertEquals(
+      getRecentDateBucket(undefined, new Date()),
+      chrome.fileManagerPrivate.RecentDateBucket.OLDER);
+
+  // May 9, 2022, Monday, 12:00pm Local time.
+  const middleOfMonth = new Date(2022, 4, 9, 12, 0, 0);
+  // Mar 1, 2022, Tuesday, 8:00am Local time.
+  const startOfMonth = new Date(2022, 2, 1, 8, 0, 0);
+  // Jan 1, 2022, Saturday, 4:00pm Local time.
+  const startOfYear = new Date(2022, 0, 1, 16, 0, 0);
+
+  const testSamples = [
+    {
+      today: middleOfMonth,
+      tests: [
+        {
+          // Future date: June 1, 2022, Wednesday, 12:00pm Local time.
+          date: new Date(2022, 5, 1, 12, 0, 0),
+          bucket: chrome.fileManagerPrivate.RecentDateBucket.TODAY,
+        },
+        {
+          // May 9, 2022, Monday, 08:30am Local time.
+          date: new Date(2022, 4, 9, 8, 30, 0),
+          bucket: chrome.fileManagerPrivate.RecentDateBucket.TODAY
+        },
+        {
+          // May 8, 2022, Sunday, 10:30am Local time.
+          date: new Date(2022, 4, 8, 10, 30, 0),
+          bucket: chrome.fileManagerPrivate.RecentDateBucket.YESTERDAY,
+        },
+        {
+          // May 7, 2022, Saturday, 07:30am Local time.
+          date: new Date(2022, 4, 7, 7, 30, 0),
+          bucket: chrome.fileManagerPrivate.RecentDateBucket.EARLIER_THIS_MONTH,
+        },
+        {
+          // May 1, 2022, Monday, 10:30am Local time.
+          date: new Date(2022, 4, 1, 10, 30, 0),
+          bucket: chrome.fileManagerPrivate.RecentDateBucket.EARLIER_THIS_MONTH,
+        },
+        {
+          // April 28, 2022, Thursday, 10:30am Local time.
+          date: new Date(2022, 3, 28, 10, 30, 0),
+          bucket: chrome.fileManagerPrivate.RecentDateBucket.EARLIER_THIS_YEAR,
+        },
+      ]
+    },
+
+    {
+      today: startOfMonth,
+      tests: [
+        {
+          // Future date: Mar 1, 2022, Tuesday, 12:00pm Local time.
+          date: new Date(2022, 2, 1, 12, 0, 0),
+          bucket: chrome.fileManagerPrivate.RecentDateBucket.TODAY,
+        },
+        {
+          // Mar 1, 2022, Tuesday, 3:00am Local time.
+          date: new Date(2022, 2, 1, 3, 0, 0),
+          bucket: chrome.fileManagerPrivate.RecentDateBucket.TODAY,
+        },
+        {
+          // Feb 28, 2022, Monday, 08:30am Local time.
+          date: new Date(2022, 1, 28, 8, 30, 0),
+          bucket: chrome.fileManagerPrivate.RecentDateBucket.YESTERDAY,
+        },
+        {
+          // Feb 27, 2022, Sunday, 10:30am Local time.
+          date: new Date(2022, 1, 27, 10, 30, 0),
+          bucket: chrome.fileManagerPrivate.RecentDateBucket.EARLIER_THIS_YEAR,
+        },
+        {
+          // Feb 10, 2022, Thursday, 10:30am Local time.
+          date: new Date(2022, 1, 10, 10, 30, 0),
+          bucket: chrome.fileManagerPrivate.RecentDateBucket.EARLIER_THIS_YEAR,
+        }
+      ]
+    },
+
+    {
+      today: startOfYear,
+      tests: [
+        {
+
+          // Future date: Jan 2, 2022, Sunday, 12:00pm Local time.
+          date: new Date(2022, 0, 2, 12, 0, 0),
+          bucket: chrome.fileManagerPrivate.RecentDateBucket.TODAY,
+        },
+        {
+          // Jan 1, 2022, Saturday, 08:30am Local time.
+          date: new Date(2022, 0, 1, 8, 30, 0),
+          bucket: chrome.fileManagerPrivate.RecentDateBucket.TODAY,
+        },
+        {
+          // Dec 31, 2021, Friday, 10:30am Local time.
+          date: new Date(2021, 11, 31, 10, 30, 0),
+          bucket: chrome.fileManagerPrivate.RecentDateBucket.YESTERDAY,
+        },
+        {
+          // Dec 27, 2021, Monday, 07:30am Local time.
+          date: new Date(2021, 11, 27, 7, 30, 0),
+          bucket: chrome.fileManagerPrivate.RecentDateBucket.EARLIER_THIS_WEEK,
+        },
+        {
+          // Dec 2, 2021, Thursday, 10:30am Local time.
+          date: new Date(2021, 11, 2, 10, 30, 0),
+          bucket: chrome.fileManagerPrivate.RecentDateBucket.OLDER,
+        },
+        {
+          // Nov 28, 2021, Sunday, 10:30am Local time.
+          date: new Date(2021, 10, 28, 10, 30, 0),
+          bucket: chrome.fileManagerPrivate.RecentDateBucket.OLDER,
+        }
+      ]
+    }
+
+  ];
+
+  for (const testSample of testSamples) {
+    for (const test of testSample.tests) {
+      assertEquals(
+          getRecentDateBucket(test.date, testSample.today), test.bucket);
+    }
+  }
+}
diff --git a/ui/file_manager/file_manager/definitions/file_manager.d.ts b/ui/file_manager/file_manager/definitions/file_manager.d.ts
new file mode 100644
index 0000000..8d7f6df1
--- /dev/null
+++ b/ui/file_manager/file_manager/definitions/file_manager.d.ts
@@ -0,0 +1,25 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/// <reference types="./volume_manager" />
+
+/**
+ * Type definition for foreground/js/file_manager.js:FileManager.
+ *
+ * For now only defining the bare minimum.
+ */
+interface FileManager {
+  volumeManager: VolumeManager;
+}
+
+/**
+ * The singleton instance for FileManager is available in the Window object.
+ */
+declare global {
+  interface Window {
+    fileManager: FileManager;
+  }
+}
+
+export {};
diff --git a/ui/file_manager/file_manager/definitions/volume_manager.d.ts b/ui/file_manager/file_manager/definitions/volume_manager.d.ts
new file mode 100644
index 0000000..928f334
--- /dev/null
+++ b/ui/file_manager/file_manager/definitions/volume_manager.d.ts
@@ -0,0 +1,10 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * Type definition for file_manager/externs/volume_manager.js:VolumeManager.
+ *
+ * For now only defining the bare minimum.
+ */
+interface VolumeManager {}
diff --git a/ui/file_manager/file_manager/externs/ts/store.js b/ui/file_manager/file_manager/externs/ts/store.js
new file mode 100644
index 0000000..a4dfa22
--- /dev/null
+++ b/ui/file_manager/file_manager/externs/ts/store.js
@@ -0,0 +1,34 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview Simplified interfaces of the Store used by Files app. Used to
+ * be able to type check the JS files using Closure compiler.
+ * See lib/base_store.ts and state/store.ts for the implementation.
+ */
+
+/** @record */
+class Store {
+  /** @param {!Object} action */
+  dispatch(action) {}
+
+  /**
+   *  @param {!StoreObserver} observer
+   *  @returns {function()} the function to unsubscribe.
+   */
+  subscribe(observer) {}
+
+  /** @param {!StoreObserver} observer */
+  usubscribe(observer) {}
+}
+
+/**
+ * Interface marked as `record` so it doesn't have to mark implements
+ * explicitly.
+ * @record
+ */
+class StoreObserver {
+  /** @param {!Object} newState */
+  onStateChanged(newState) {}
+}
diff --git a/ui/file_manager/file_manager/foreground/js/BUILD.gn b/ui/file_manager/file_manager/foreground/js/BUILD.gn
index 43a0255..b17f217 100644
--- a/ui/file_manager/file_manager/foreground/js/BUILD.gn
+++ b/ui/file_manager/file_manager/foreground/js/BUILD.gn
@@ -444,6 +444,8 @@
     "//ui/webui/resources/js/cr/ui:list_selection_model.m",
     "//ui/webui/resources/js/cr/ui:list_single_selection_model.m",
   ]
+
+  externs_list = [ "//ui/file_manager/file_manager/externs/ts/store.js" ]
 }
 
 js_unittest("directory_model_unittest.m") {
diff --git a/ui/file_manager/file_manager/foreground/js/directory_model.js b/ui/file_manager/file_manager/foreground/js/directory_model.js
index a23fda5..71543c64 100644
--- a/ui/file_manager/file_manager/foreground/js/directory_model.js
+++ b/ui/file_manager/file_manager/foreground/js/directory_model.js
@@ -18,6 +18,8 @@
 import {FakeEntry, FilesAppDirEntry, FilesAppEntry} from '../../externs/files_app_entry_interfaces.js';
 import {VolumeInfo} from '../../externs/volume_info.js';
 import {VolumeManager} from '../../externs/volume_manager.js';
+import {changeDirectory} from '../../state/actions.js';
+import {getStore} from '../../state/store.js';
 
 import {constants} from './constants.js';
 import {ContentScanner, CrostiniMounter, DirectoryContents, DirectoryContentScanner, DriveMetadataSearchContentScanner, DriveSearchContentScanner, FileFilter, FileListContext, GuestOsMounter, LocalSearchContentScanner, MediaViewContentScanner, RecentContentScanner} from './directory_contents.js';
@@ -129,6 +131,9 @@
 
     /** @private {FilesAppDirEntry} */
     this.myFilesEntry_ = null;
+
+    /** @private {!Store} */
+    this.store_ = getStore();
   }
 
   /**
@@ -1174,6 +1179,9 @@
       event.newDirEntry = dirEntry;
       event.volumeChanged = previousVolumeInfo !== currentVolumeInfo;
       this.dispatchEvent(event);
+      if (util.isFilesAppExperimental()) {
+        this.store_.dispatch(changeDirectory({to: dirEntry}));
+      }
     });
   }
 
diff --git a/ui/file_manager/file_manager/foreground/js/metadata/BUILD.gn b/ui/file_manager/file_manager/foreground/js/metadata/BUILD.gn
index 7a32d2e6..6ce18d3 100644
--- a/ui/file_manager/file_manager/foreground/js/metadata/BUILD.gn
+++ b/ui/file_manager/file_manager/foreground/js/metadata/BUILD.gn
@@ -112,6 +112,7 @@
   deps = [
     ":metadata_item",
     ":metadata_provider",
+    "//ui/file_manager/file_manager/common/js:api",
   ]
 }
 
@@ -314,6 +315,7 @@
 js_library("multi_metadata_provider") {
   deps = [
     ":content_metadata_provider",
+    ":dlp_metadata_provider",
     ":external_metadata_provider",
     ":file_system_metadata_provider",
     ":metadata_item",
@@ -328,6 +330,7 @@
 js_unittest("multi_metadata_provider_unittest.m") {
   deps = [
     ":content_metadata_provider",
+    ":dlp_metadata_provider",
     ":external_metadata_provider",
     ":file_system_metadata_provider",
     ":metadata_request",
diff --git a/ui/file_manager/file_manager/foreground/js/metadata/dlp_metadata_provider.js b/ui/file_manager/file_manager/foreground/js/metadata/dlp_metadata_provider.js
index bdbc67a..6d22d9c 100644
--- a/ui/file_manager/file_manager/foreground/js/metadata/dlp_metadata_provider.js
+++ b/ui/file_manager/file_manager/foreground/js/metadata/dlp_metadata_provider.js
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import {getDlpMetadata} from '../../../common/js/api.js';
+
 import {MetadataItem} from './metadata_item.js';
 import {MetadataProvider} from './metadata_provider.js';
 
@@ -23,14 +25,30 @@
     }
 
     // TODO(crbug.com/1297603): Early return if DLP isn't enabled.
-    // TODO(crbug.com/1326932): Call chrome.fileManagerPrivate to check if the
-    // file is managed.
-    return Promise.all(requests.map(_request => new MetadataItem()));
+    const entries = requests.map(_request => {
+      return _request.entry;
+    });
+    try {
+      const dlpMetadataList = await getDlpMetadata(entries);
+      const results = [];
+      for (let i = 0; i < dlpMetadataList.length; i++) {
+        const item = new MetadataItem();
+        item.isDlpRestricted = dlpMetadataList[i].isDlpRestricted;
+        item.sourceUrl = dlpMetadataList[i].sourceUrl;
+        results.push(item);
+      }
+      return results;
+    } catch (error) {
+      console.warn(error);
+      return requests.map(() => {
+        return new MetadataItem();
+      });
+    }
   }
 }
 
 /** @const {!Array<string>} */
 DlpMetadataProvider.PROPERTY_NAMES = [
   // TODO(crbug.com/1329770): Consider using an enum for this property.
-  'isDlpRestricted',
+  'isDlpRestricted', 'sourceUrl'
 ];
\ No newline at end of file
diff --git a/ui/file_manager/file_manager/foreground/js/metadata/metadata_item.js b/ui/file_manager/file_manager/foreground/js/metadata/metadata_item.js
index 3c8d9080..4c7138c 100644
--- a/ui/file_manager/file_manager/foreground/js/metadata/metadata_item.js
+++ b/ui/file_manager/file_manager/foreground/js/metadata/metadata_item.js
@@ -280,5 +280,17 @@
      * @public {boolean|undefined}
      */
     this.isExternalMedia;
+
+    /**
+     * Whether the entry is under any DataLeakPrevention policy.
+     * @public {boolean|undefined}
+     */
+    this.isDlpRestricted;
+
+    /**
+     * Source URL that can be used to check DataLeakPrevention policy.
+     * @public {string|undefined}
+     */
+    this.sourceUrl;
   }
 }
diff --git a/ui/file_manager/file_manager/foreground/js/metadata/metadata_model.js b/ui/file_manager/file_manager/foreground/js/metadata/metadata_model.js
index cdfb8550..bb41b60 100644
--- a/ui/file_manager/file_manager/foreground/js/metadata/metadata_model.js
+++ b/ui/file_manager/file_manager/foreground/js/metadata/metadata_model.js
@@ -6,6 +6,7 @@
 import {VolumeManager} from '../../../externs/volume_manager.js';
 
 import {ContentMetadataProvider} from './content_metadata_provider.js';
+import {DlpMetadataProvider} from './dlp_metadata_provider.js';
 import {ExternalMetadataProvider} from './external_metadata_provider.js';
 import {FileSystemMetadataProvider} from './file_system_metadata_provider.js';
 import {MetadataCacheSet, MetadataCacheSetStorageForObject} from './metadata_cache_set.js';
@@ -64,7 +65,8 @@
   static create(volumeManager) {
     return new MetadataModel(new MultiMetadataProvider(
         new FileSystemMetadataProvider(), new ExternalMetadataProvider(),
-        new ContentMetadataProvider(), volumeManager));
+        new ContentMetadataProvider(), new DlpMetadataProvider(),
+        volumeManager));
   }
 
   /**
diff --git a/ui/file_manager/file_manager/foreground/js/metadata/multi_metadata_provider.js b/ui/file_manager/file_manager/foreground/js/metadata/multi_metadata_provider.js
index 929d11d5..3abdef6 100644
--- a/ui/file_manager/file_manager/foreground/js/metadata/multi_metadata_provider.js
+++ b/ui/file_manager/file_manager/foreground/js/metadata/multi_metadata_provider.js
@@ -9,6 +9,7 @@
 import {VolumeManager} from '../../../externs/volume_manager.js';
 
 import {ContentMetadataProvider} from './content_metadata_provider.js';
+import {DlpMetadataProvider} from './dlp_metadata_provider.js';
 import {ExternalMetadataProvider} from './external_metadata_provider.js';
 import {FileSystemMetadataProvider} from './file_system_metadata_provider.js';
 import {MetadataItem} from './metadata_item.js';
@@ -21,14 +22,16 @@
    * @param {!FileSystemMetadataProvider} fileSystemMetadataProvider
    * @param {!ExternalMetadataProvider} externalMetadataProvider
    * @param {!ContentMetadataProvider} contentMetadataProvider
+   * @param {!DlpMetadataProvider} dlpMetadataProvider
    * @param {!VolumeManager} volumeManager
    */
   constructor(
       fileSystemMetadataProvider, externalMetadataProvider,
-      contentMetadataProvider, volumeManager) {
+      contentMetadataProvider, dlpMetadataProvider, volumeManager) {
     super(FileSystemMetadataProvider.PROPERTY_NAMES
               .concat(ExternalMetadataProvider.PROPERTY_NAMES)
-              .concat(ContentMetadataProvider.PROPERTY_NAMES));
+              .concat(ContentMetadataProvider.PROPERTY_NAMES)
+              .concat(DlpMetadataProvider.PROPERTY_NAMES));
 
     /** @private @const {!FileSystemMetadataProvider} */
     this.fileSystemMetadataProvider_ = fileSystemMetadataProvider;
@@ -39,6 +42,9 @@
     /** @private @const {!ContentMetadataProvider} */
     this.contentMetadataProvider_ = contentMetadataProvider;
 
+    /** @private @const {!DlpMetadataProvider} */
+    this.dlpMetadataProvider_ = dlpMetadataProvider;
+
     /** @private @const {!VolumeManager} */
     this.volumeManager_ = volumeManager;
   }
@@ -53,12 +59,14 @@
     const externalRequests = [];
     const contentRequests = [];
     const fallbackContentRequests = [];
+    const dlpRequests = [];
     requests.forEach(request => {
       // Group property names.
       const fileSystemPropertyNames = [];
       const externalPropertyNames = [];
       const contentPropertyNames = [];
       const fallbackContentPropertyNames = [];
+      const dlpPropertyNames = [];
       for (let i = 0; i < request.names.length; i++) {
         const name = request.names[i];
         const isFileSystemProperty =
@@ -67,7 +75,11 @@
             ExternalMetadataProvider.PROPERTY_NAMES.indexOf(name) !== -1;
         const isContentProperty =
             ContentMetadataProvider.PROPERTY_NAMES.indexOf(name) !== -1;
-        assert(isFileSystemProperty || isExternalProperty || isContentProperty);
+        const isDlpProperty =
+            DlpMetadataProvider.PROPERTY_NAMES.indexOf(name) !== -1;
+        assert(
+            isFileSystemProperty || isExternalProperty || isContentProperty ||
+            isDlpProperty);
         assert(!(isFileSystemProperty && isContentProperty));
         // If the property can be obtained both from ExternalProvider and from
         // ContentProvider, we can obtain the property from ExternalProvider
@@ -88,6 +100,9 @@
         if (isContentProperty) {
           contentPropertyNames.push(name);
         }
+        if (isDlpProperty) {
+          dlpPropertyNames.push(name);
+        }
       }
       const volumeInfo = this.volumeManager_.getVolumeInfo(request.entry);
       const addRequests = (list, names) => {
@@ -128,6 +143,7 @@
             contentRequests,
             contentPropertyNames.concat(fallbackContentPropertyNames));
       }
+      addRequests(dlpRequests, dlpPropertyNames);
     });
 
     const get = (provider, inRequests) => {
@@ -156,14 +172,13 @@
             return dirtyMap[request.entry.toURL()];
           }));
     });
+    const dlpPromise = get(this.dlpMetadataProvider_, dlpRequests);
 
     // Merge results.
     return Promise
         .all([
-          fileSystemPromise,
-          externalPromise,
-          contentPromise,
-          fallbackContentPromise,
+          fileSystemPromise, externalPromise, contentPromise,
+          fallbackContentPromise, dlpPromise
         ])
         .then(resultsList => {
           const integratedResults = {};
diff --git a/ui/file_manager/file_manager/foreground/js/metadata/multi_metadata_provider_unittest.m.js b/ui/file_manager/file_manager/foreground/js/metadata/multi_metadata_provider_unittest.m.js
index 89f9b6a..f1d4b50a 100644
--- a/ui/file_manager/file_manager/foreground/js/metadata/multi_metadata_provider_unittest.m.js
+++ b/ui/file_manager/file_manager/foreground/js/metadata/multi_metadata_provider_unittest.m.js
@@ -9,6 +9,7 @@
 import {VolumeManager} from '../../../externs/volume_manager.js';
 
 import {ContentMetadataProvider} from './content_metadata_provider.js';
+import {DlpMetadataProvider} from './dlp_metadata_provider.js';
 import {ExternalMetadataProvider} from './external_metadata_provider.js';
 import {FileSystemMetadataProvider} from './file_system_metadata_provider.js';
 import {MetadataRequest} from './metadata_request.js';
@@ -97,6 +98,12 @@
           ]);
         }
       }),
+      /** @type {!DlpMetadataProvider} */ ({
+        get: function(requests) {
+          assertEquals(0, requests.length);
+          return Promise.resolve([]);
+        }
+      }),
       volumeManager);
 
   reportPromise(
@@ -155,6 +162,12 @@
           return Promise.resolve([results[requests[0].entry.toURL()]]);
         },
       }),
+      /** @type {!DlpMetadataProvider} */ ({
+        get: function(requests) {
+          assertEquals(0, requests.length);
+          return Promise.resolve([]);
+        }
+      }),
       volumeManager);
 
   reportPromise(
@@ -212,6 +225,12 @@
           return Promise.resolve([]);
         },
       }),
+      /** @type {!DlpMetadataProvider} */ ({
+        get: function(requests) {
+          assertEquals(0, requests.length);
+          return Promise.resolve([]);
+        }
+      }),
       volumeManager);
 
   reportPromise(
@@ -236,3 +255,47 @@
           }),
       callback);
 }
+
+export function testDlpMetadataProvider(callback) {
+  const model = new MultiMetadataProvider(
+      /** @type {!FileSystemMetadataProvider} */ ({
+        get: function(requests) {
+          assertEquals(0, requests.length);
+          return Promise.resolve([]);
+        }
+      }),
+      /** @type {!ExternalMetadataProvider} */ ({
+        get: function(requests) {
+          assertEquals(0, requests.length);
+          return Promise.resolve([]);
+        }
+      }),
+      /** @type {!ContentMetadataProvider} */ ({
+        get: function(requests) {
+          assertEquals(0, requests.length);
+          return Promise.resolve([]);
+        },
+      }),
+      /** @type {!DlpMetadataProvider} */ ({
+        get: function(requests) {
+          assertEquals(1, requests.length);
+          return Promise.resolve([{
+            sourceUrl: 'url',
+            isDlpRestricted: true,
+          }]);
+        }
+      }),
+      volumeManager);
+
+  reportPromise(
+      model
+          .get([
+            new MetadataRequest(entryA, ['sourceUrl', 'isDlpRestricted']),
+          ])
+          .then(results => {
+            assertEquals(1, results.length);
+            assertEquals('url', results[0].sourceUrl);
+            assertEquals(true, results[0].isDlpRestricted);
+          }),
+      callback);
+}
diff --git a/ui/file_manager/file_manager/lib/base_store.ts b/ui/file_manager/file_manager/lib/base_store.ts
index fe700a2..618ffe8 100644
--- a/ui/file_manager/file_manager/lib/base_store.ts
+++ b/ui/file_manager/file_manager/lib/base_store.ts
@@ -87,7 +87,7 @@
   /**
    * Subscribe to Store changes/updates.
    * @param observer Callback called whenever the Store is updated.
-   * @returns callback to unsusbscribe the obserer.
+   * @returns callback to unsusbscribe the observer.
    */
   subscribe(observer: StoreObserver<StateType>):
       (observer: StoreObserver<StateType>) => void {
diff --git a/ui/file_manager/file_manager/state/actions.ts b/ui/file_manager/file_manager/state/actions.ts
new file mode 100644
index 0000000..2cf33a5
--- /dev/null
+++ b/ui/file_manager/file_manager/state/actions.ts
@@ -0,0 +1,36 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import {BaseAction} from '../lib/base_store.js';
+
+import {FileKey} from './file_key.js';
+
+/**
+ * Base class for all Actions in Files app.
+ *
+ * It enforces the type/enum for the `type` attribute.
+ */
+export interface Action extends BaseAction {
+  type: Actions;
+}
+
+/** Enum to identify every Action in Files app. */
+export const enum Actions {
+  CHANGE_DIRECTORY = 'change-directory',
+}
+
+/** Action to request to change the Current Directory. */
+export interface ChangeDirectoryAction extends Action {
+  newDirectory: Entry;
+  key: FileKey;
+}
+
+/** Factory for the ChangeDirectoryAction. */
+export function changeDirectory({to}: {to: Entry}): ChangeDirectoryAction {
+  return {
+    type: Actions.CHANGE_DIRECTORY,
+    newDirectory: to,
+    key: to.toURL(),
+  };
+}
diff --git a/ui/file_manager/file_manager/state/file_key.ts b/ui/file_manager/file_manager/state/file_key.ts
new file mode 100644
index 0000000..280da3ac
--- /dev/null
+++ b/ui/file_manager/file_manager/state/file_key.ts
@@ -0,0 +1,10 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * Key to identify a file/entry.
+ *
+ * Type to distinguish it from a regular string.
+ */
+export type FileKey = string;
diff --git a/ui/file_manager/file_manager/state/reducers.ts b/ui/file_manager/file_manager/state/reducers.ts
index 0ed20d6..94d70063 100644
--- a/ui/file_manager/file_manager/state/reducers.ts
+++ b/ui/file_manager/file_manager/state/reducers.ts
@@ -2,16 +2,77 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import {BaseAction} from '../lib/base_store.js';
+import {PathComponent} from '../foreground/js/path_component.js';
 
-import {State} from './state.js';
+import {Action, Actions, ChangeDirectoryAction} from './actions.js';
+import {CurrentDirectory, State} from './state.js';
 
-/** TODO(lucmult): Document this. */
-export function rootReducer(currentState: State, action: BaseAction): State {
-  if (action.type) {
-    return Object.assign(currentState, {});
+/**
+ * Root reducer for Files app.
+ * It dispatches to other reducers to update different parts of the State.
+ */
+export function rootReducer(currentState: State, action: Action): State {
+  // Before any actual Reducer, we cache the entries, so the reducers can just
+  // use any entry from `allEntries`.
+  const state = cacheEntries(currentState, action);
+
+  switch (action.type) {
+    case Actions.CHANGE_DIRECTORY:
+      return Object.assign(state, {
+        currentDirectory:
+            changeDirectory(state, action as ChangeDirectoryAction)
+      });
+
+    default:
+      console.error(`invalid action: ${action.type}`);
+      return state;
+  }
+}
+
+/** Caches the Action's entry in the `allEntries` attribute. */
+export function cacheEntries(currentState: State, action: Action): State {
+  if (action.type === Actions.CHANGE_DIRECTORY) {
+    const {newDirectory, key} = (action as ChangeDirectoryAction);
+
+    const allEntries = currentState.allEntries || {};
+    const entryData = allEntries[key] || {};
+    allEntries[key] = Object.assign(entryData, {
+      entry: newDirectory,
+    });
+
+    currentState.allEntries = allEntries;
   }
 
-  console.error('invalid action');
   return currentState;
 }
+
+/** Reducer that updates the `currentDirectory` attributes. */
+export function changeDirectory(
+    currentState: State, action: ChangeDirectoryAction): CurrentDirectory|null {
+  const fileData = currentState.allEntries[action.key];
+  if (!fileData) {
+    console.debug(`Can't find the entry with ${action.key}`);
+    return currentState.currentDirectory || null;
+  }
+
+  // TODO(lucmult): Find a correct way to grab the VolumeManager.
+  const volumeManager = window.fileManager.volumeManager;
+  if (!volumeManager) {
+    console.debug(`VolumeManager not available yet.`);
+    return currentState.currentDirectory || null;
+  }
+
+  const components =
+      PathComponent.computeComponentsFromEntry(fileData.entry, volumeManager);
+
+  return Object.assign(currentState.currentDirectory || {}, {
+    key: (action as ChangeDirectoryAction).key,
+    pathComponents: components.map(c => {
+      return {
+        name: c.name,
+        label: c.name,
+        key: c.url_,
+      };
+    })
+  });
+}
diff --git a/ui/file_manager/file_manager/state/state.ts b/ui/file_manager/file_manager/state/state.ts
index 7b60eed..21596cf 100644
--- a/ui/file_manager/file_manager/state/state.ts
+++ b/ui/file_manager/file_manager/state/state.ts
@@ -2,5 +2,49 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-/** TODO(lucmult): Document this. */
-export interface State {}
+import {FilesAppDirEntry, FilesAppEntry} from '../externs/files_app_entry_interfaces.js';
+
+import {FileKey} from './file_key.js';
+
+export type AnyEntry = Entry|FilesAppEntry;
+export type OnlyDirEntry = DirectoryEntry|FilesAppDirEntry;
+
+/** The data for each individual file/entry. */
+export interface FileData {
+  entry: AnyEntry;
+}
+
+/**
+ * Describes each part of the path, as in each parent folder and/or root volume.
+ */
+export interface PathComponent {
+  // The actual name for folder/volume.
+  name: string;
+
+  // Label to display to the user, it might differ from the name when the folder
+  // or volume has a translation or comercial name.
+  label: string;
+
+  // The key to the actual folder or root, it might be a key for a fake entry.
+  key: FileKey;
+}
+
+/** The Current Directory. */
+export interface CurrentDirectory {
+  // Key to the current directory.
+  key: FileKey;
+
+  // Elements for the user facing path, e.g. the breadcrumbs.
+  pathComponents: PathComponent[];
+}
+
+/** Files app's state. */
+export interface State {
+  // A big bucket with all entries managed by the app.
+  allEntries: {
+    [key: FileKey]: FileData,
+  };
+
+  // The currently selected directory.
+  currentDirectory?: CurrentDirectory;
+}
diff --git a/ui/file_manager/file_manager/state/store.ts b/ui/file_manager/file_manager/state/store.ts
index 769e428..ad017739 100644
--- a/ui/file_manager/file_manager/state/store.ts
+++ b/ui/file_manager/file_manager/state/store.ts
@@ -2,8 +2,9 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import {BaseAction, BaseStore} from '../lib/base_store.js';
+import {BaseStore} from '../lib/base_store.js';
 
+import {Action} from './actions.js';
 import {rootReducer} from './reducers.js';
 import {State} from './state.js';
 
@@ -12,7 +13,7 @@
  *
  * It enforces the types for the State and the Actions managed by Files app.
  */
-export type Store = BaseStore<State, BaseAction>;
+export type Store = BaseStore<State, Action>;
 
 /**
  * Store singleton instance.
@@ -28,7 +29,7 @@
  */
 export function getStore(): Store {
   if (!store) {
-    store = new BaseStore<State, BaseAction>({}, rootReducer);
+    store = new BaseStore<State, Action>({allEntries: {}}, rootReducer);
   }
 
   return store;
diff --git a/ui/file_manager/file_names.gni b/ui/file_manager/file_names.gni
index febc286..a22a24fb 100644
--- a/ui/file_manager/file_names.gni
+++ b/ui/file_manager/file_names.gni
@@ -42,7 +42,10 @@
   "file_manager/background/js/media_scanner.js",
   "file_manager/background/js/metadata_proxy.js",
   "file_manager/background/js/metrics_start.js",
+
+  # TODO(lucmult): Check if we can move those mocks to the test files section.
   "file_manager/background/js/mock_crostini.js",
+  "file_manager/background/js/mock_drive_sync_handler.js",
   "file_manager/background/js/mock_file_operation_manager.js",
   "file_manager/background/js/mock_media_scanner.js",
   "file_manager/background/js/mock_progress_center.js",
@@ -83,6 +86,7 @@
   "file_manager/common/js/notifications_browser_proxy.js",
   "file_manager/common/js/power.js",
   "file_manager/common/js/progress_center_common.js",
+  "file_manager/common/js/recent_date_bucket.js",
   "file_manager/common/js/storage_adapter.js",
   "file_manager/common/js/test_error_reporting.js",
   "file_manager/common/js/test_importer_common.js",
@@ -259,6 +263,8 @@
   "file_manager/state/store.ts",
   "file_manager/state/state.ts",
   "file_manager/state/reducers.ts",
+  "file_manager/state/actions.ts",
+  "file_manager/state/file_key.ts",
 ]
 
 # Generated files are built from the repository and the final JS files is only
@@ -312,4 +318,90 @@
   "file_manager/foreground/js/ui/banners/warning_banner.js",
   "file_manager/foreground/js/ui/breadcrumb.js",
 ]
+
 # END: generated_js_files.
+
+# Test files:
+unittest_files = [
+  "file_manager/common/js/util_unittest.m.js",
+  "file_manager/common/js/filtered_volume_manager_unittest.m.js",
+  "file_manager/common/js/file_type_unittest.m.js",
+  "file_manager/common/js/lru_cache_unittest.m.js",
+  "file_manager/common/js/importer_common_unittest.m.js",
+  "file_manager/common/js/files_app_entry_types_unittest.m.js",
+  "file_manager/common/js/recent_date_bucket_unittest.m.js",
+  "file_manager/common/js/storage_adapter_unittest.m.js",
+  "file_manager/common/js/volume_manager_types_unittest.m.js",
+  "file_manager/background/js/mount_metrics_unittest.m.js",
+  "file_manager/background/js/drive_sync_handler_unittest.m.js",
+  "file_manager/background/js/media_import_handler_unittest.m.js",
+  "file_manager/background/js/task_queue_unittest.m.js",
+  "file_manager/background/js/file_operation_handler_unittest.m.js",
+  "file_manager/background/js/file_operation_manager_unittest.m.js",
+  "file_manager/background/js/trash_unittest.m.js",
+  "file_manager/background/js/duplicate_finder_unittest.m.js",
+  "file_manager/background/js/volume_manager_unittest.m.js",
+  "file_manager/background/js/media_scanner_unittest.m.js",
+  "file_manager/background/js/import_history_unittest.m.js",
+  "file_manager/background/js/metadata_proxy_unittest.m.js",
+  "file_manager/background/js/crostini_unittest.m.js",
+  "file_manager/background/js/device_handler_unittest.m.js",
+  "file_manager/foreground/elements/files_password_dialog_unittest.m.js",
+  "file_manager/foreground/elements/files_xf_elements_unittest.m.js",
+  "file_manager/foreground/elements/files_toast_unittest.m.js",
+  "file_manager/foreground/elements/files_tooltip_unittest.m.js",
+  "file_manager/foreground/js/metadata/image_orientation_unittest.m.js",
+  "file_manager/foreground/js/metadata/metadata_cache_item_unittest.m.js",
+  "file_manager/foreground/js/metadata/exif_parser_unittest.m.js",
+  "file_manager/foreground/js/metadata/thumbnail_model_unittest.m.js",
+  "file_manager/foreground/js/metadata/metadata_model_unittest.m.js",
+  "file_manager/foreground/js/metadata/content_metadata_provider_unittest.m.js",
+  "file_manager/foreground/js/metadata/external_metadata_provider_unittest.m.js",
+  "file_manager/foreground/js/metadata/file_system_metadata_provider_unittest.m.js",
+  "file_manager/foreground/js/metadata/metadata_cache_set_unittest.m.js",
+  "file_manager/foreground/js/metadata/multi_metadata_provider_unittest.m.js",
+  "file_manager/foreground/js/file_manager_commands_unittest.m.js",
+  "file_manager/foreground/js/task_controller_unittest.m.js",
+  "file_manager/foreground/js/thumbnail_loader_unittest.m.js",
+  "file_manager/foreground/js/directory_contents_unittest.m.js",
+  "file_manager/foreground/js/file_list_model_unittest.m.js",
+  "file_manager/foreground/js/banner_controller_unittest.m.js",
+  "file_manager/foreground/js/providers_model_unittest.m.js",
+  "file_manager/foreground/js/spinner_controller_unittest.m.js",
+  "file_manager/foreground/js/banner_util_unittest.m.js",
+  "file_manager/foreground/js/list_thumbnail_loader_unittest.m.js",
+  "file_manager/foreground/js/file_type_filters_controller_unittest.m.js",
+  "file_manager/foreground/js/path_component_unittest.m.js",
+  "file_manager/foreground/js/actions_model_unittest.m.js",
+  "file_manager/foreground/js/ui/file_manager_dialog_base_unittest.m.js",
+  "file_manager/foreground/js/ui/actions_submenu_unittest.m.js",
+  "file_manager/foreground/js/ui/file_table_unittest.m.js",
+  "file_manager/foreground/js/ui/file_tap_handler_unittest.m.js",
+  "file_manager/foreground/js/ui/install_linux_package_dialog_unittest.m.js",
+  "file_manager/foreground/js/ui/file_table_list_unittest.m.js",
+  "file_manager/foreground/js/ui/multi_menu_unittest.m.js",
+  "file_manager/foreground/js/ui/banners/state_banner_unittest.m.js",
+  "file_manager/foreground/js/ui/banners/educational_banner_unittest.m.js",
+  "file_manager/foreground/js/ui/banners/warning_banner_unittest.m.js",
+  "file_manager/foreground/js/ui/breadcrumb_unittest.m.js",
+  "file_manager/foreground/js/ui/file_list_selection_model_unittest.m.js",
+  "file_manager/foreground/js/ui/directory_tree_unittest.m.js",
+  "file_manager/foreground/js/navigation_list_model_unittest.m.js",
+  "file_manager/foreground/js/directory_model_unittest.m.js",
+  "file_manager/foreground/js/file_tasks_unittest.m.js",
+  "file_manager/foreground/js/file_transfer_controller_unittest.m.js",
+  "file_manager/foreground/js/import_controller_unittest.m.js",
+  "image_loader/scheduler_unittest.m.js",
+  "image_loader/image_loader_unittest.m.js",
+  "image_loader/cache_unittest.m.js",
+  "image_loader/image_loader_client_unittest.m.js",
+]
+
+generated_test_htmls = []
+foreach(_t, unittest_files) {
+  generated_test_htmls += [ string_replace(_t, ".js", "_gen.html") ]
+}
+
+# Files that don't have the generated HTML, but are used for tests.
+unittest_files += [ "file_manager/common/js/mock_util.js" ]
+# END: Test files.
diff --git a/ui/file_manager/tsconfig_base.json b/ui/file_manager/tsconfig_base.json
index f79e3053..024050e 100644
--- a/ui/file_manager/tsconfig_base.json
+++ b/ui/file_manager/tsconfig_base.json
@@ -2,6 +2,13 @@
   "extends": "../../tools/typescript/tsconfig_base.json",
   "compilerOptions": {
     "allowJs": true,
-    "importsNotUsedAsValues": "preserve"
+    "importsNotUsedAsValues": "preserve",
+    "typeRoots": [
+      "../../third_party/node/node_modules/@types"
+    ],
+    "types": [
+      "filesystem",
+      "filewriter"
+    ]
   }
 }
diff --git a/ui/gl/direct_composition_surface_win.cc b/ui/gl/direct_composition_surface_win.cc
index cab0de03..480c380 100644
--- a/ui/gl/direct_composition_surface_win.cc
+++ b/ui/gl/direct_composition_surface_win.cc
@@ -431,7 +431,7 @@
 
   // EGL_KHR_no_config_context surface compatibility is required to be able to
   // MakeCurrent with the default pbuffer surface.
-  if (!display->ext->b_EGL_KHR_no_config_context) {
+  if (!display->IsEGLNoConfigContextSupported()) {
     DLOG(ERROR) << "EGL_KHR_no_config_context not supported";
     return;
   }
diff --git a/ui/gl/generate_bindings.py b/ui/gl/generate_bindings.py
index 9970647..dfd2196 100755
--- a/ui/gl/generate_bindings.py
+++ b/ui/gl/generate_bindings.py
@@ -2814,23 +2814,18 @@
   'EGL_ANGLE_display_semaphore_share_group',
   'EGL_ANGLE_display_texture_share_group',
   'EGL_ANGLE_context_virtualization',
-  'EGL_ANGLE_create_context_backwards_compatible',
   'EGL_ANGLE_create_context_client_arrays',
   'EGL_ANGLE_create_context_webgl_compatibility',
   'EGL_ANGLE_external_context_and_surface',
   'EGL_ANGLE_keyed_mutex',
-  'EGL_ANGLE_robust_resource_initialization',
   'EGL_ANGLE_surface_orientation',
   'EGL_ANGLE_window_fixed_size',
-  'EGL_ARM_implicit_external_sync',
   'EGL_CHROMIUM_create_context_bind_generates_resource',
   'EGL_EXT_create_context_robustness',
   'EGL_EXT_gl_colorspace_display_p3',
   'EGL_EXT_gl_colorspace_display_p3_passthrough',
-  'EGL_EXT_image_dma_buf_import',
   'EGL_EXT_pixel_format_float',
   'EGL_IMG_context_priority',
-  'EGL_KHR_create_context',
   'EGL_KHR_gl_colorspace',
   'EGL_KHR_no_config_context',
   'EGL_KHR_surfaceless_context',
diff --git a/ui/gl/gl_angle_util_vulkan.cc b/ui/gl/gl_angle_util_vulkan.cc
index 847e477..c0099aa 100644
--- a/ui/gl/gl_angle_util_vulkan.cc
+++ b/ui/gl/gl_angle_util_vulkan.cc
@@ -19,7 +19,7 @@
     return nullptr;
   }
 
-  if (!gl::g_driver_egl.client_ext.b_EGL_EXT_device_query) {
+  if (!gl::GLSurfaceEGL::GetGLDisplayEGL()->IsEGLQueryDeviceSupported()) {
     LOG(ERROR) << "EGL_EXT_device_query not supported";
     return nullptr;
   }
diff --git a/ui/gl/gl_angle_util_win.cc b/ui/gl/gl_angle_util_win.cc
index 69f460e..3427df23 100644
--- a/ui/gl/gl_angle_util_win.cc
+++ b/ui/gl/gl_angle_util_win.cc
@@ -24,7 +24,7 @@
     return nullptr;
   }
 
-  if (!gl::g_driver_egl.client_ext.b_EGL_EXT_device_query) {
+  if (!gl::GLSurfaceEGL::GetGLDisplayEGL()->IsEGLQueryDeviceSupported()) {
     DVLOG(1) << "EGL_EXT_device_query not supported";
     return nullptr;
   }
diff --git a/ui/gl/gl_bindings_autogen_egl.cc b/ui/gl/gl_bindings_autogen_egl.cc
index 33bb5b2..8cbbadd99 100644
--- a/ui/gl/gl_bindings_autogen_egl.cc
+++ b/ui/gl/gl_bindings_autogen_egl.cc
@@ -285,8 +285,6 @@
       gfx::HasExtension(extensions, "EGL_ANDROID_native_fence_sync");
   b_EGL_ANGLE_context_virtualization =
       gfx::HasExtension(extensions, "EGL_ANGLE_context_virtualization");
-  b_EGL_ANGLE_create_context_backwards_compatible = gfx::HasExtension(
-      extensions, "EGL_ANGLE_create_context_backwards_compatible");
   b_EGL_ANGLE_create_context_client_arrays =
       gfx::HasExtension(extensions, "EGL_ANGLE_create_context_client_arrays");
   b_EGL_ANGLE_create_context_webgl_compatibility = gfx::HasExtension(
@@ -305,8 +303,6 @@
       gfx::HasExtension(extensions, "EGL_ANGLE_power_preference");
   b_EGL_ANGLE_query_surface_pointer =
       gfx::HasExtension(extensions, "EGL_ANGLE_query_surface_pointer");
-  b_EGL_ANGLE_robust_resource_initialization =
-      gfx::HasExtension(extensions, "EGL_ANGLE_robust_resource_initialization");
   b_EGL_ANGLE_stream_producer_d3d_texture =
       gfx::HasExtension(extensions, "EGL_ANGLE_stream_producer_d3d_texture");
   b_EGL_ANGLE_surface_d3d_texture_2d_share_handle = gfx::HasExtension(
@@ -319,8 +315,6 @@
       gfx::HasExtension(extensions, "EGL_ANGLE_vulkan_image");
   b_EGL_ANGLE_window_fixed_size =
       gfx::HasExtension(extensions, "EGL_ANGLE_window_fixed_size");
-  b_EGL_ARM_implicit_external_sync =
-      gfx::HasExtension(extensions, "EGL_ARM_implicit_external_sync");
   b_EGL_CHROMIUM_create_context_bind_generates_resource = gfx::HasExtension(
       extensions, "EGL_CHROMIUM_create_context_bind_generates_resource");
   b_EGL_CHROMIUM_sync_control =
@@ -331,8 +325,6 @@
       gfx::HasExtension(extensions, "EGL_EXT_gl_colorspace_display_p3");
   b_EGL_EXT_gl_colorspace_display_p3_passthrough = gfx::HasExtension(
       extensions, "EGL_EXT_gl_colorspace_display_p3_passthrough");
-  b_EGL_EXT_image_dma_buf_import =
-      gfx::HasExtension(extensions, "EGL_EXT_image_dma_buf_import");
   b_EGL_EXT_image_dma_buf_import_modifiers =
       gfx::HasExtension(extensions, "EGL_EXT_image_dma_buf_import_modifiers");
   b_EGL_EXT_image_flush_external =
@@ -341,8 +333,6 @@
       gfx::HasExtension(extensions, "EGL_EXT_pixel_format_float");
   b_EGL_IMG_context_priority =
       gfx::HasExtension(extensions, "EGL_IMG_context_priority");
-  b_EGL_KHR_create_context =
-      gfx::HasExtension(extensions, "EGL_KHR_create_context");
   b_EGL_KHR_fence_sync = gfx::HasExtension(extensions, "EGL_KHR_fence_sync");
   b_EGL_KHR_gl_colorspace =
       gfx::HasExtension(extensions, "EGL_KHR_gl_colorspace");
diff --git a/ui/gl/gl_bindings_autogen_egl.h b/ui/gl/gl_bindings_autogen_egl.h
index 985e75a..5819b20 100644
--- a/ui/gl/gl_bindings_autogen_egl.h
+++ b/ui/gl/gl_bindings_autogen_egl.h
@@ -347,7 +347,6 @@
   bool b_EGL_ANDROID_get_native_client_buffer;
   bool b_EGL_ANDROID_native_fence_sync;
   bool b_EGL_ANGLE_context_virtualization;
-  bool b_EGL_ANGLE_create_context_backwards_compatible;
   bool b_EGL_ANGLE_create_context_client_arrays;
   bool b_EGL_ANGLE_create_context_webgl_compatibility;
   bool b_EGL_ANGLE_d3d_share_handle_client_buffer;
@@ -357,25 +356,21 @@
   bool b_EGL_ANGLE_keyed_mutex;
   bool b_EGL_ANGLE_power_preference;
   bool b_EGL_ANGLE_query_surface_pointer;
-  bool b_EGL_ANGLE_robust_resource_initialization;
   bool b_EGL_ANGLE_stream_producer_d3d_texture;
   bool b_EGL_ANGLE_surface_d3d_texture_2d_share_handle;
   bool b_EGL_ANGLE_surface_orientation;
   bool b_EGL_ANGLE_sync_control_rate;
   bool b_EGL_ANGLE_vulkan_image;
   bool b_EGL_ANGLE_window_fixed_size;
-  bool b_EGL_ARM_implicit_external_sync;
   bool b_EGL_CHROMIUM_create_context_bind_generates_resource;
   bool b_EGL_CHROMIUM_sync_control;
   bool b_EGL_EXT_create_context_robustness;
   bool b_EGL_EXT_gl_colorspace_display_p3;
   bool b_EGL_EXT_gl_colorspace_display_p3_passthrough;
-  bool b_EGL_EXT_image_dma_buf_import;
   bool b_EGL_EXT_image_dma_buf_import_modifiers;
   bool b_EGL_EXT_image_flush_external;
   bool b_EGL_EXT_pixel_format_float;
   bool b_EGL_IMG_context_priority;
-  bool b_EGL_KHR_create_context;
   bool b_EGL_KHR_fence_sync;
   bool b_EGL_KHR_gl_colorspace;
   bool b_EGL_KHR_gl_texture_2D_image;
diff --git a/ui/gl/gl_context_egl.cc b/ui/gl/gl_context_egl.cc
index d210618..ef7207b 100644
--- a/ui/gl/gl_context_egl.cc
+++ b/ui/gl/gl_context_egl.cc
@@ -130,7 +130,7 @@
 
   // Always prefer to use EGL_KHR_no_config_context so that all surfaces and
   // contexts are compatible
-  if (!gl_display_->ext->b_EGL_KHR_no_config_context) {
+  if (!gl_display_->IsEGLNoConfigContextSupported()) {
     config_ = compatible_surface->GetConfig();
     EGLint config_renderable_type = 0;
     if (!eglGetConfigAttrib(gl_display_->GetDisplay(), config_,
@@ -158,7 +158,7 @@
 
   // EGL_KHR_create_context allows requesting both a major and minor context
   // version
-  if (gl_display_->ext->b_EGL_KHR_create_context) {
+  if (gl_display_->HasEGLExtension("EGL_KHR_create_context")) {
     context_attributes.push_back(EGL_CONTEXT_MAJOR_VERSION);
     context_attributes.push_back(context_client_major_version);
 
@@ -176,7 +176,7 @@
 
   bool is_swangle = IsSoftwareGLImplementation(GetGLImplementationParts());
 
-  if (gl_display_->ext->b_EGL_EXT_create_context_robustness || is_swangle) {
+  if (gl_display_->IsCreateContextRobustnessSupported() || is_swangle) {
     DVLOG(1) << "EGL_EXT_create_context_robustness supported.";
     context_attributes.push_back(EGL_CONTEXT_OPENGL_ROBUST_ACCESS_EXT);
     context_attributes.push_back(
@@ -186,7 +186,7 @@
           EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_EXT);
       context_attributes.push_back(EGL_LOSE_CONTEXT_ON_RESET_EXT);
 
-      if (gl_display_->ext->b_EGL_NV_robustness_video_memory_purge) {
+      if (gl_display_->IsRobustnessVideoMemoryPurgeSupported()) {
         context_attributes.push_back(
             EGL_GENERATE_RESET_ON_VIDEO_MEMORY_PURGE_NV);
         context_attributes.push_back(EGL_TRUE);
@@ -204,7 +204,7 @@
     return false;
   }
 
-  if (gl_display_->ext->b_EGL_CHROMIUM_create_context_bind_generates_resource) {
+  if (gl_display_->IsCreateContextBindGeneratesResourceSupported()) {
     context_attributes.push_back(EGL_CONTEXT_BIND_GENERATES_RESOURCE_CHROMIUM);
     context_attributes.push_back(attribs.bind_generates_resource ? EGL_TRUE
                                                                  : EGL_FALSE);
@@ -212,7 +212,7 @@
     DCHECK(attribs.bind_generates_resource);
   }
 
-  if (gl_display_->ext->b_EGL_ANGLE_create_context_webgl_compatibility) {
+  if (gl_display_->IsCreateContextWebGLCompatabilitySupported()) {
     context_attributes.push_back(EGL_CONTEXT_WEBGL_COMPATIBILITY_ANGLE);
     context_attributes.push_back(
         attribs.webgl_compatibility_context ? EGL_TRUE : EGL_FALSE);
@@ -234,7 +234,7 @@
     }
   }
 
-  if (gl_display_->ext->b_EGL_ANGLE_display_texture_share_group) {
+  if (gl_display_->IsDisplayTextureShareGroupSupported()) {
     context_attributes.push_back(EGL_DISPLAY_TEXTURE_SHARE_GROUP_ANGLE);
     context_attributes.push_back(
         attribs.global_texture_share_group ? EGL_TRUE : EGL_FALSE);
@@ -242,7 +242,7 @@
     DCHECK(!attribs.global_texture_share_group);
   }
 
-  if (gl_display_->ext->b_EGL_ANGLE_display_semaphore_share_group) {
+  if (gl_display_->IsDisplaySemaphoreShareGroupSupported()) {
     context_attributes.push_back(EGL_DISPLAY_SEMAPHORE_SHARE_GROUP_ANGLE);
     context_attributes.push_back(
         attribs.global_semaphore_share_group ? EGL_TRUE : EGL_FALSE);
@@ -250,14 +250,13 @@
     DCHECK(!attribs.global_semaphore_share_group);
   }
 
-  if (gl_display_->ext->b_EGL_ANGLE_create_context_client_arrays) {
+  if (gl_display_->IsCreateContextClientArraysSupported()) {
     // Disable client arrays if the context supports it
     context_attributes.push_back(EGL_CONTEXT_CLIENT_ARRAYS_ENABLED_ANGLE);
     context_attributes.push_back(EGL_FALSE);
   }
 
-  if (gl_display_->ext->b_EGL_ANGLE_robust_resource_initialization ||
-      is_swangle) {
+  if (gl_display_->IsRobustResourceInitSupported() || is_swangle) {
     context_attributes.push_back(EGL_ROBUST_RESOURCE_INITIALIZATION_ANGLE);
     context_attributes.push_back(
         (attribs.robust_resource_initialization || is_swangle) ? EGL_TRUE
@@ -266,14 +265,15 @@
     DCHECK(!attribs.robust_resource_initialization);
   }
 
-  if (gl_display_->ext->b_EGL_ANGLE_create_context_backwards_compatible) {
+  if (gl_display_->HasEGLExtension(
+          "EGL_ANGLE_create_context_backwards_compatible")) {
     // Request a specific context version. The Passthrough command decoder
     // relies on the returned context being the exact version it requested.
     context_attributes.push_back(EGL_CONTEXT_OPENGL_BACKWARDS_COMPATIBLE_ANGLE);
     context_attributes.push_back(EGL_FALSE);
   }
 
-  if (gl_display_->ext->b_EGL_ANGLE_power_preference) {
+  if (gl_display_->IsANGLEPowerPreferenceSupported()) {
     GpuPreference pref = attribs.gpu_preference;
     pref = GLSurface::AdjustGpuPreference(pref);
     switch (pref) {
@@ -293,7 +293,7 @@
     }
   }
 
-  if (gl_display_->ext->b_EGL_ANGLE_external_context_and_surface) {
+  if (gl_display_->IsANGLEExternalContextAndSurfaceSupported()) {
     if (attribs.angle_create_from_external_context) {
       context_attributes.push_back(EGL_EXTERNAL_CONTEXT_ANGLE);
       context_attributes.push_back(EGL_TRUE);
@@ -304,7 +304,7 @@
     }
   }
 
-  if (gl_display_->ext->b_EGL_ANGLE_context_virtualization) {
+  if (gl_display_->IsANGLEContextVirtualizationSupported()) {
     context_attributes.push_back(EGL_CONTEXT_VIRTUALIZATION_GROUP_ANGLE);
     context_attributes.push_back(
         static_cast<EGLint>(attribs.angle_context_virtualization_group_number));
@@ -322,7 +322,7 @@
   // If EGL_KHR_no_config_context is in use and context creation failed,
   // it might indicate that an unsupported ES version was requested. Try
   // falling back to a lower version.
-  if (!context_ && gl_display_->ext->b_EGL_KHR_no_config_context &&
+  if (!context_ && gl_display_->IsEGLNoConfigContextSupported() &&
       eglGetError() == EGL_BAD_MATCH) {
     // Set up the list of versions to try: 3.1 -> 3.0 -> 2.0
     std::vector<std::pair<EGLint, EGLint>> candidate_versions;
@@ -395,7 +395,7 @@
 }
 
 void GLContextEGL::SetVisibility(bool visibility) {
-  if (gl_display_->ext->b_EGL_ANGLE_power_preference) {
+  if (gl_display_->IsANGLEPowerPreferenceSupported()) {
     // It doesn't matter whether this context was explicitly allocated
     // with a power preference - ANGLE will take care of any default behavior.
     if (visibility) {
@@ -555,7 +555,7 @@
   DCHECK(g_current_gl_driver);
   const ExtensionsGL& ext = g_current_gl_driver->ext;
   if ((graphics_reset_status_ == GL_NO_ERROR) &&
-      gl_display_->ext->b_EGL_EXT_create_context_robustness &&
+      gl_display_->IsCreateContextRobustnessSupported() &&
       (ext.b_GL_KHR_robustness || ext.b_GL_EXT_robustness ||
        ext.b_GL_ARB_robustness)) {
     graphics_reset_status_ = glGetGraphicsResetStatusARB();
diff --git a/ui/gl/gl_display.cc b/ui/gl/gl_display.cc
index 92dfb472..7ee36c2 100644
--- a/ui/gl/gl_display.cc
+++ b/ui/gl/gl_display.cc
@@ -55,6 +55,32 @@
   return context ? context->GetGLDisplayEGL() : nullptr;
 }
 
+bool GLDisplayEGL::HasEGLClientExtension(const char* name) {
+  if (!egl_client_extensions)
+    return false;
+  return GLSurface::ExtensionsContain(egl_client_extensions, name);
+}
+
+bool GLDisplayEGL::HasEGLExtension(const char* name) {
+  return GLSurface::ExtensionsContain(egl_extensions, name);
+}
+
+bool GLDisplayEGL::IsCreateContextRobustnessSupported() {
+  return egl_create_context_robustness_supported;
+}
+
+bool GLDisplayEGL::IsRobustnessVideoMemoryPurgeSupported() {
+  return egl_robustness_video_memory_purge_supported;
+}
+
+bool GLDisplayEGL::IsCreateContextBindGeneratesResourceSupported() {
+  return egl_create_context_bind_generates_resource_supported;
+}
+
+bool GLDisplayEGL::IsCreateContextWebGLCompatabilitySupported() {
+  return egl_create_context_webgl_compatability_supported;
+}
+
 bool GLDisplayEGL::IsEGLSurfacelessContextSupported() {
   return egl_surfaceless_context_supported;
 }
@@ -63,13 +89,66 @@
   return egl_context_priority_supported;
 }
 
+bool GLDisplayEGL::IsEGLNoConfigContextSupported() {
+  return egl_no_config_context_supported;
+}
+
+bool GLDisplayEGL::IsRobustResourceInitSupported() {
+  return egl_robust_resource_init_supported;
+}
+
+bool GLDisplayEGL::IsDisplayTextureShareGroupSupported() {
+  return egl_display_texture_share_group_supported;
+}
+
+bool GLDisplayEGL::IsDisplaySemaphoreShareGroupSupported() {
+  return egl_display_semaphore_share_group_supported;
+}
+
+bool GLDisplayEGL::IsCreateContextClientArraysSupported() {
+  return egl_create_context_client_arrays_supported;
+}
+
 bool GLDisplayEGL::IsAndroidNativeFenceSyncSupported() {
   return egl_android_native_fence_sync_supported;
 }
 
-bool GLDisplayEGL::IsANGLEExternalContextAndSurfaceSupported() {
-  return this->ext->b_EGL_ANGLE_external_context_and_surface;
+bool GLDisplayEGL::IsPixelFormatFloatSupported() {
+  return egl_ext_pixel_format_float_supported;
 }
+
+bool GLDisplayEGL::IsANGLEFeatureControlSupported() {
+  return egl_angle_feature_control_supported;
+}
+
+bool GLDisplayEGL::IsANGLEPowerPreferenceSupported() {
+  return egl_angle_power_preference_supported;
+}
+
+bool GLDisplayEGL::IsANGLEDisplayPowerPreferenceSupported() {
+  return egl_angle_display_power_preference_supported;
+}
+
+bool GLDisplayEGL::IsANGLEPlatformANGLEDeviceIdSupported() {
+  return egl_angle_platform_angle_device_id_supported;
+}
+
+bool GLDisplayEGL::IsANGLEExternalContextAndSurfaceSupported() {
+  return egl_angle_external_context_and_surface_supported;
+}
+
+bool GLDisplayEGL::IsANGLEContextVirtualizationSupported() {
+  return egl_angle_context_virtualization_supported;
+}
+
+bool GLDisplayEGL::IsANGLEVulkanImageSupported() {
+  return egl_angle_vulkan_image_supported;
+}
+
+bool GLDisplayEGL::IsEGLQueryDeviceSupported() {
+  return egl_ext_query_device_supported;
+}
+
 #endif  // defined(USE_EGL)
 
 #if defined(USE_GLX)
diff --git a/ui/gl/gl_display.h b/ui/gl/gl_display.h
index ef4f069..7ce5bec 100644
--- a/ui/gl/gl_display.h
+++ b/ui/gl/gl_display.h
@@ -99,18 +99,64 @@
   EGLNativeDisplayType GetNativeDisplay();
   DisplayType GetDisplayType();
 
+  bool HasEGLClientExtension(const char* name);
+  bool HasEGLExtension(const char* name);
+  bool IsCreateContextRobustnessSupported();
+  bool IsRobustnessVideoMemoryPurgeSupported();
+  bool IsCreateContextBindGeneratesResourceSupported();
+  bool IsCreateContextWebGLCompatabilitySupported();
   bool IsEGLSurfacelessContextSupported();
   bool IsEGLContextPrioritySupported();
+  bool IsEGLNoConfigContextSupported();
+  bool IsRobustResourceInitSupported();
+  bool IsDisplayTextureShareGroupSupported();
+  bool IsDisplaySemaphoreShareGroupSupported();
+  bool IsCreateContextClientArraysSupported();
   bool IsAndroidNativeFenceSyncSupported();
+  bool IsPixelFormatFloatSupported();
+  bool IsANGLEFeatureControlSupported();
+  bool IsANGLEPowerPreferenceSupported();
+  bool IsANGLEDisplayPowerPreferenceSupported();
+  bool IsANGLEPlatformANGLEDeviceIdSupported();
   bool IsANGLEExternalContextAndSurfaceSupported();
+  bool IsANGLEContextVirtualizationSupported();
+  bool IsANGLEVulkanImageSupported();
+  bool IsEGLQueryDeviceSupported();
 
   EGLDisplayPlatform native_display = EGLDisplayPlatform(EGL_DEFAULT_DISPLAY);
 
   DisplayType display_type = DisplayType::DEFAULT;
 
+  const char* egl_client_extensions = nullptr;
+  const char* egl_extensions = nullptr;
+  bool egl_create_context_robustness_supported = false;
+  bool egl_robustness_video_memory_purge_supported = false;
+  bool egl_create_context_bind_generates_resource_supported = false;
+  bool egl_create_context_webgl_compatability_supported = false;
+  bool egl_sync_control_supported = false;
+  bool egl_sync_control_rate_supported = false;
+  bool egl_window_fixed_size_supported = false;
   bool egl_surfaceless_context_supported = false;
+  bool egl_surface_orientation_supported = false;
   bool egl_context_priority_supported = false;
+  bool egl_khr_colorspace = false;
+  bool egl_ext_colorspace_display_p3 = false;
+  bool egl_ext_colorspace_display_p3_passthrough = false;
+  bool egl_no_config_context_supported = false;
+  bool egl_robust_resource_init_supported = false;
+  bool egl_display_texture_share_group_supported = false;
+  bool egl_display_semaphore_share_group_supported = false;
+  bool egl_create_context_client_arrays_supported = false;
   bool egl_android_native_fence_sync_supported = false;
+  bool egl_ext_pixel_format_float_supported = false;
+  bool egl_angle_feature_control_supported = false;
+  bool egl_angle_power_preference_supported = false;
+  bool egl_angle_display_power_preference_supported = false;
+  bool egl_angle_platform_angle_device_id_supported = false;
+  bool egl_angle_external_context_and_surface_supported = false;
+  bool egl_ext_query_device_supported = false;
+  bool egl_angle_context_virtualization_supported = false;
+  bool egl_angle_vulkan_image_supported = false;
 
   std::unique_ptr<DisplayExtensionsEGL> ext;
 
diff --git a/ui/gl/gl_image_native_pixmap.cc b/ui/gl/gl_image_native_pixmap.cc
index 299eb9e3..effe6136 100644
--- a/ui/gl/gl_image_native_pixmap.cc
+++ b/ui/gl/gl_image_native_pixmap.cc
@@ -131,10 +131,12 @@
     : GLImageEGL(size),
       format_(format),
       plane_(plane),
-      has_image_flush_external_(gl::GLSurfaceEGL::GetGLDisplayEGL()
-                                    ->ext->b_EGL_EXT_image_flush_external),
-      has_image_dma_buf_export_(gl::GLSurfaceEGL::GetGLDisplayEGL()
-                                    ->ext->b_EGL_MESA_image_dma_buf_export) {}
+      has_image_flush_external_(
+          gl::GLSurfaceEGL::GetGLDisplayEGL()->HasEGLExtension(
+              "EGL_EXT_image_flush_external")),
+      has_image_dma_buf_export_(
+          gl::GLSurfaceEGL::GetGLDisplayEGL()->HasEGLExtension(
+              "EGL_MESA_image_dma_buf_export")) {}
 
 GLImageNativePixmap::~GLImageNativePixmap() {}
 
@@ -172,8 +174,8 @@
                                            EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT,
                                            EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT};
       bool has_dma_buf_import_modifier =
-          gl::GLSurfaceEGL::GetGLDisplayEGL()
-              ->ext->b_EGL_EXT_image_dma_buf_import_modifiers;
+          gl::GLSurfaceEGL::GetGLDisplayEGL()->HasEGLExtension(
+              "EGL_EXT_image_dma_buf_import_modifiers");
 
       for (size_t attrs_plane = 0; attrs_plane < pixmap->GetNumberOfPlanes();
            ++attrs_plane) {
diff --git a/ui/gl/gl_surface_egl.cc b/ui/gl/gl_surface_egl.cc
index 7a8f3ee..9906e709 100644
--- a/ui/gl/gl_surface_egl.cc
+++ b/ui/gl/gl_surface_egl.cc
@@ -228,7 +228,7 @@
   static bool IsSupported(GLDisplayEGL* display) {
     DCHECK(display);
     return SyncControlVSyncProvider::IsSupported() &&
-           display->ext->b_EGL_CHROMIUM_sync_control;
+           display->egl_sync_control_supported;
   }
 
  protected:
@@ -249,7 +249,7 @@
   }
 
   bool GetMscRate(int32_t* numerator, int32_t* denominator) override {
-    if (!display_->ext->b_EGL_ANGLE_sync_control_rate) {
+    if (!display_->egl_sync_control_rate_supported) {
       return false;
     }
 
@@ -272,7 +272,7 @@
   }
 
   void OnGpuSwitched(gl::GpuPreference active_gpu_heuristic) override {
-    DCHECK(display_->ext->b_EGL_ANGLE_power_preference);
+    DCHECK(display_->IsANGLEPowerPreferenceSupported());
     eglHandleGPUSwitchANGLE(display_->GetDisplay());
   }
 
@@ -340,7 +340,7 @@
       GetAttribArrayFromStringVector(enabled_features);
   std::vector<const char*> disabled_features_attribs =
       GetAttribArrayFromStringVector(disabled_features);
-  if (g_driver_egl.client_ext.b_EGL_ANGLE_feature_control) {
+  if (gl_display->egl_angle_feature_control_supported) {
     if (!enabled_features_attribs.empty()) {
       display_attribs.push_back(EGL_FEATURE_OVERRIDES_ENABLED_ANGLE);
       display_attribs.push_back(
@@ -354,7 +354,7 @@
   }
   // TODO(dbehr) Add an attrib to Angle to pass EGL platform.
 
-  if (g_driver_egl.client_ext.b_EGL_ANGLE_display_power_preference) {
+  if (gl_display->IsANGLEDisplayPowerPreferenceSupported()) {
     GpuPreference pref =
         GLSurface::AdjustGpuPreference(GpuPreference::kDefault);
     switch (pref) {
@@ -398,7 +398,7 @@
     extra_display_attribs.push_back(EGL_TRUE);
   }
   if (system_device_id != 0 &&
-      g_driver_egl.client_ext.b_EGL_ANGLE_platform_angle_device_id) {
+      gl_display->IsANGLEPlatformANGLEDeviceIdSupported()) {
     uint32_t low_part = system_device_id & 0xffffffff;
     extra_display_attribs.push_back(EGL_PLATFORM_ANGLE_DEVICE_ID_LOW_ANGLE);
     extra_display_attribs.push_back(low_part);
@@ -1030,6 +1030,34 @@
 
 // static
 void GLSurfaceEGL::InitializeOneOffCommon(GLDisplayEGL* display) {
+  display->egl_client_extensions =
+      eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS);
+  display->egl_extensions =
+      eglQueryString(display->GetDisplay(), EGL_EXTENSIONS);
+
+  display->egl_create_context_robustness_supported =
+      display->HasEGLExtension("EGL_EXT_create_context_robustness");
+  display->egl_robustness_video_memory_purge_supported =
+      display->HasEGLExtension("EGL_NV_robustness_video_memory_purge");
+  display->egl_create_context_bind_generates_resource_supported =
+      display->HasEGLExtension(
+          "EGL_CHROMIUM_create_context_bind_generates_resource");
+  display->egl_create_context_webgl_compatability_supported =
+      display->HasEGLExtension("EGL_ANGLE_create_context_webgl_compatibility");
+  display->egl_sync_control_supported =
+      display->HasEGLExtension("EGL_CHROMIUM_sync_control");
+  display->egl_sync_control_rate_supported =
+      display->HasEGLExtension("EGL_ANGLE_sync_control_rate");
+  display->egl_window_fixed_size_supported =
+      display->HasEGLExtension("EGL_ANGLE_window_fixed_size");
+  display->egl_surface_orientation_supported =
+      display->HasEGLExtension("EGL_ANGLE_surface_orientation");
+  display->egl_khr_colorspace =
+      display->HasEGLExtension("EGL_KHR_gl_colorspace");
+  display->egl_ext_colorspace_display_p3 =
+      display->HasEGLExtension("EGL_EXT_gl_colorspace_display_p3");
+  display->egl_ext_colorspace_display_p3_passthrough =
+      display->HasEGLExtension("EGL_EXT_gl_colorspace_display_p3_passthrough");
   // According to https://source.android.com/compatibility/android-cdd.html the
   // EGL_IMG_context_priority extension is mandatory for Virtual Reality High
   // Performance support, but due to a bug in Android Nougat the extension
@@ -1038,13 +1066,27 @@
   // that this implies context priority is also supported. See also:
   // https://github.com/googlevr/gvr-android-sdk/issues/330
   display->egl_context_priority_supported =
-      display->ext->b_EGL_IMG_context_priority ||
-      (display->ext->b_EGL_ANDROID_front_buffer_auto_refresh &&
-       display->ext->b_EGL_ANDROID_create_native_client_buffer);
+      display->HasEGLExtension("EGL_IMG_context_priority") ||
+      (display->HasEGLExtension("EGL_ANDROID_front_buffer_auto_refresh") &&
+       display->HasEGLExtension("EGL_ANDROID_create_native_client_buffer"));
+
+  // Need EGL_KHR_no_config_context to allow surfaces with and without alpha to
+  // be bound to the same context.
+  display->egl_no_config_context_supported =
+      display->HasEGLExtension("EGL_KHR_no_config_context");
+
+  display->egl_display_texture_share_group_supported =
+      display->HasEGLExtension("EGL_ANGLE_display_texture_share_group");
+  display->egl_display_semaphore_share_group_supported =
+      display->HasEGLExtension("EGL_ANGLE_display_semaphore_share_group");
+  display->egl_create_context_client_arrays_supported =
+      display->HasEGLExtension("EGL_ANGLE_create_context_client_arrays");
+  display->egl_robust_resource_init_supported =
+      display->HasEGLExtension("EGL_ANGLE_robust_resource_initialization");
 
   // Check if SurfacelessEGL is supported.
   display->egl_surfaceless_context_supported =
-      display->ext->b_EGL_KHR_surfaceless_context;
+      display->HasEGLExtension("EGL_KHR_surfaceless_context");
 
   // TODO(oetuaho@nvidia.com): Surfaceless is disabled on Android as a temporary
   // workaround, since code written for Android WebView takes different paths
@@ -1056,7 +1098,7 @@
 #if BUILDFLAG(IS_ANDROID)
   // Use the WebGL compatibility extension for detecting ANGLE. ANGLE always
   // exposes it.
-  bool is_angle = display->ext->b_EGL_ANGLE_create_context_webgl_compatibility;
+  bool is_angle = display->egl_create_context_webgl_compatability_supported;
   if (!is_angle) {
     display->egl_surfaceless_context_supported = false;
   }
@@ -1091,7 +1133,7 @@
   // Android level, update the heuristic to trust the reported extension from
   // that version onward.
   display->egl_android_native_fence_sync_supported =
-      display->ext->b_EGL_ANDROID_native_fence_sync;
+      display->HasEGLExtension("EGL_ANDROID_native_fence_sync");
 #if BUILDFLAG(IS_ANDROID)
   if (!display->egl_android_native_fence_sync_supported &&
       base::android::BuildInfo::GetInstance()->sdk_int() >=
@@ -1103,7 +1145,25 @@
   }
 #endif
 
-  if (display->ext->b_EGL_ANGLE_power_preference) {
+  display->egl_ext_pixel_format_float_supported =
+      display->HasEGLExtension("EGL_EXT_pixel_format_float");
+
+  display->egl_angle_power_preference_supported =
+      display->HasEGLExtension("EGL_ANGLE_power_preference");
+
+  display->egl_angle_external_context_and_surface_supported =
+      display->HasEGLExtension("EGL_ANGLE_external_context_and_surface");
+
+  display->egl_ext_query_device_supported =
+      display->HasEGLClientExtension("EGL_EXT_device_query");
+
+  display->egl_angle_context_virtualization_supported =
+      display->HasEGLExtension("EGL_ANGLE_context_virtualization");
+
+  display->egl_angle_vulkan_image_supported =
+      display->HasEGLExtension("EGL_ANGLE_vulkan_image");
+
+  if (display->egl_angle_power_preference_supported) {
     g_egl_gpu_switching_observer = new EGLGpuSwitchingObserver(display);
     ui::GpuSwitchingManager::GetInstance()->AddObserver(
         g_egl_gpu_switching_observer);
@@ -1116,6 +1176,11 @@
   if (display->GetDisplay() == EGL_NO_DISPLAY)
     return false;
   display->ext->UpdateConditionalExtensionSettings(display);
+  display->egl_client_extensions =
+      eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS);
+  display->egl_extensions =
+      eglQueryString(display->GetDisplay(), EGL_EXTENSIONS);
+
   return true;
 }
 
@@ -1136,9 +1201,21 @@
   eglTerminate(display->GetDisplay());
   display->SetDisplay(EGL_NO_DISPLAY);
 
+  display->egl_client_extensions = nullptr;
+  display->egl_extensions = nullptr;
+  display->egl_create_context_robustness_supported = false;
+  display->egl_robustness_video_memory_purge_supported = false;
+  display->egl_create_context_bind_generates_resource_supported = false;
+  display->egl_create_context_webgl_compatability_supported = false;
+  display->egl_sync_control_supported = false;
+  display->egl_sync_control_rate_supported = false;
+  display->egl_window_fixed_size_supported = false;
+  display->egl_surface_orientation_supported = false;
   display->egl_surfaceless_context_supported = false;
-  display->egl_context_priority_supported = false;
-  display->egl_android_native_fence_sync_supported = false;
+  display->egl_robust_resource_init_supported = false;
+  display->egl_display_texture_share_group_supported = false;
+  display->egl_create_context_client_arrays_supported = false;
+  display->egl_angle_feature_control_supported = false;
 }
 
 GLSurfaceEGL::~GLSurfaceEGL() = default;
@@ -1156,7 +1233,12 @@
 
   gl_display->native_display = native_display;
 
-  bool supports_egl_debug = g_driver_egl.client_ext.b_EGL_KHR_debug;
+  // If EGL_EXT_client_extensions not supported this call to eglQueryString
+  // will return nullptr.
+  gl_display->egl_client_extensions =
+      eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS);
+
+  bool supports_egl_debug = gl_display->HasEGLClientExtension("EGL_KHR_debug");
   if (supports_egl_debug) {
     EGLAttrib controls[] = {
         EGL_DEBUG_MSG_CRITICAL_KHR,
@@ -1182,27 +1264,36 @@
   bool supports_angle_egl = false;
   bool supports_angle_metal = false;
   // Check for availability of ANGLE extensions.
-  if (g_driver_egl.client_ext.b_EGL_ANGLE_platform_angle) {
-    supports_angle_d3d = g_driver_egl.client_ext.b_EGL_ANGLE_platform_angle_d3d;
+  if (gl_display->HasEGLClientExtension("EGL_ANGLE_platform_angle")) {
+    supports_angle_d3d =
+        gl_display->HasEGLClientExtension("EGL_ANGLE_platform_angle_d3d");
     supports_angle_opengl =
-        g_driver_egl.client_ext.b_EGL_ANGLE_platform_angle_opengl;
+        gl_display->HasEGLClientExtension("EGL_ANGLE_platform_angle_opengl");
     supports_angle_null =
-        g_driver_egl.client_ext.b_EGL_ANGLE_platform_angle_null;
+        gl_display->HasEGLClientExtension("EGL_ANGLE_platform_angle_null");
     supports_angle_vulkan =
-        g_driver_egl.client_ext.b_EGL_ANGLE_platform_angle_vulkan;
-    supports_angle_swiftshader =
-        g_driver_egl.client_ext
-            .b_EGL_ANGLE_platform_angle_device_type_swiftshader;
-    supports_angle_egl = g_driver_egl.client_ext
-                             .b_EGL_ANGLE_platform_angle_device_type_egl_angle;
+        gl_display->HasEGLClientExtension("EGL_ANGLE_platform_angle_vulkan");
+    supports_angle_swiftshader = gl_display->HasEGLClientExtension(
+        "EGL_ANGLE_platform_angle_device_type_swiftshader");
+    supports_angle_egl = gl_display->HasEGLClientExtension(
+        "EGL_ANGLE_platform_angle_device_type_egl_angle");
     supports_angle_metal =
-        g_driver_egl.client_ext.b_EGL_ANGLE_platform_angle_metal;
+        gl_display->HasEGLClientExtension("EGL_ANGLE_platform_angle_metal");
   }
 
   bool supports_angle = supports_angle_d3d || supports_angle_opengl ||
                         supports_angle_null || supports_angle_vulkan ||
                         supports_angle_swiftshader || supports_angle_metal;
 
+  gl_display->egl_angle_feature_control_supported =
+      gl_display->HasEGLClientExtension("EGL_ANGLE_feature_control");
+
+  gl_display->egl_angle_display_power_preference_supported =
+      gl_display->HasEGLClientExtension("EGL_ANGLE_display_power_preference");
+
+  gl_display->egl_angle_platform_angle_device_id_supported =
+      gl_display->HasEGLClientExtension("EGL_ANGLE_platform_angle_device_id");
+
   std::vector<DisplayType> init_displays;
   base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
   GetEGLInitDisplays(supports_angle_d3d, supports_angle_opengl,
@@ -1317,8 +1408,7 @@
 
   std::vector<EGLint> egl_window_attributes;
 
-  if (display_->ext->b_EGL_ANGLE_window_fixed_size &&
-      enable_fixed_size_angle_) {
+  if (display_->egl_window_fixed_size_supported && enable_fixed_size_angle_) {
     egl_window_attributes.push_back(EGL_FIXED_SIZE_ANGLE);
     egl_window_attributes.push_back(EGL_TRUE);
     egl_window_attributes.push_back(EGL_WIDTH);
@@ -1332,7 +1422,7 @@
     egl_window_attributes.push_back(EGL_TRUE);
   }
 
-  if (display_->ext->b_EGL_ANGLE_surface_orientation) {
+  if (display_->egl_surface_orientation_supported) {
     EGLint attrib;
     eglGetConfigAttrib(display_->GetDisplay(), GetConfig(),
                        EGL_OPTIMAL_SURFACE_ORIENTATION_ANGLE, &attrib);
@@ -1353,7 +1443,7 @@
       // Note that COLORSPACE_LINEAR refers to the sRGB color space, but
       // without opting into sRGB blending. It is equivalent to
       // COLORSPACE_SRGB with Disable(FRAMEBUFFER_SRGB).
-      if (display_->ext->b_EGL_KHR_gl_colorspace) {
+      if (display_->egl_khr_colorspace) {
         egl_window_attributes.push_back(EGL_GL_COLORSPACE_KHR);
         egl_window_attributes.push_back(EGL_GL_COLORSPACE_LINEAR_KHR);
       }
@@ -1367,16 +1457,15 @@
       // with the P3 gamut instead of the the sRGB gamut.
       // COLORSPACE_DISPLAY_P3_LINEAR has a linear transfer function, and is
       // intended for use with 16-bit formats.
-      bool p3_supported =
-          display_->ext->b_EGL_EXT_gl_colorspace_display_p3 ||
-          display_->ext->b_EGL_EXT_gl_colorspace_display_p3_passthrough;
-      if (display_->ext->b_EGL_KHR_gl_colorspace && p3_supported) {
+      bool p3_supported = display_->egl_ext_colorspace_display_p3 ||
+                          display_->egl_ext_colorspace_display_p3_passthrough;
+      if (display_->egl_khr_colorspace && p3_supported) {
         egl_window_attributes.push_back(EGL_GL_COLORSPACE_KHR);
         // Chrome relied on incorrect Android behavior when dealing with P3 /
         // framebuffer_srgb interactions. This behavior was fixed in Q, which
         // causes invalid Chrome rendering. To achieve Android-P behavior in Q+,
         // use EGL_GL_COLORSPACE_P3_PASSTHROUGH_EXT where possible.
-        if (display_->ext->b_EGL_EXT_gl_colorspace_display_p3_passthrough) {
+        if (display_->egl_ext_colorspace_display_p3_passthrough) {
           egl_window_attributes.push_back(
               EGL_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH_EXT);
         } else {
@@ -2002,7 +2091,7 @@
 
   // Enable robust resource init when using SwANGLE
   if (IsSoftwareGLImplementation(GetGLImplementationParts()) &&
-      display_->ext->b_EGL_ANGLE_robust_resource_initialization) {
+      display_->IsRobustResourceInitSupported()) {
     pbuffer_attribs.push_back(EGL_ROBUST_RESOURCE_INITIALIZATION_ANGLE);
     pbuffer_attribs.push_back(EGL_TRUE);
   }
diff --git a/ui/gl/gl_utils.cc b/ui/gl/gl_utils.cc
index dffa2e5..c98a7cd 100644
--- a/ui/gl/gl_utils.cc
+++ b/ui/gl/gl_utils.cc
@@ -104,11 +104,11 @@
   GLDisplayEGL* display = gl::GLSurfaceEGL::GetGLDisplayEGL();
   // Using the passthrough command buffer requires that specific ANGLE
   // extensions are exposed
-  return display->ext->b_EGL_CHROMIUM_create_context_bind_generates_resource &&
-         display->ext->b_EGL_ANGLE_create_context_webgl_compatibility &&
-         display->ext->b_EGL_ANGLE_robust_resource_initialization &&
-         display->ext->b_EGL_ANGLE_display_texture_share_group &&
-         display->ext->b_EGL_ANGLE_create_context_client_arrays;
+  return display->IsCreateContextBindGeneratesResourceSupported() &&
+         display->IsCreateContextWebGLCompatabilitySupported() &&
+         display->IsRobustResourceInitSupported() &&
+         display->IsDisplayTextureShareGroupSupported() &&
+         display->IsCreateContextClientArraysSupported();
 #else
   // The passthrough command buffer is only supported on top of ANGLE/EGL
   return false;
diff --git a/ui/ozone/platform/drm/gpu/gbm_surfaceless.cc b/ui/ozone/platform/drm/gpu/gbm_surfaceless.cc
index 60b3bfa9..43cb0c2 100644
--- a/ui/ozone/platform/drm/gpu/gbm_surfaceless.cc
+++ b/ui/ozone/platform/drm/gpu/gbm_surfaceless.cc
@@ -45,9 +45,9 @@
       window_(std::move(window)),
       widget_(widget),
       has_implicit_external_sync_(
-          GetGLDisplayEGL()->ext->b_EGL_ARM_implicit_external_sync),
+          GetGLDisplayEGL()->HasEGLExtension("EGL_ARM_implicit_external_sync")),
       has_image_flush_external_(
-          GetGLDisplayEGL()->ext->b_EGL_EXT_image_flush_external) {
+          GetGLDisplayEGL()->HasEGLExtension("EGL_EXT_image_flush_external")) {
   surface_factory_->RegisterSurface(window_->widget(), this);
   supports_plane_gpu_fences_ = window_->SupportsGpuFences();
   unsubmitted_frames_.push_back(std::make_unique<PendingFrame>());
diff --git a/ui/ozone/platform/wayland/gpu/wayland_buffer_manager_gpu.cc b/ui/ozone/platform/wayland/gpu/wayland_buffer_manager_gpu.cc
index ae16deb..ebaf402 100644
--- a/ui/ozone/platform/wayland/gpu/wayland_buffer_manager_gpu.cc
+++ b/ui/ozone/platform/wayland/gpu/wayland_buffer_manager_gpu.cc
@@ -16,7 +16,6 @@
 #include "ui/gfx/geometry/rrect_f.h"
 #include "ui/gfx/linux/drm_util_linux.h"
 #include "ui/gfx/overlay_priority_hint.h"
-#include "ui/gl/gl_bindings.h"
 #include "ui/gl/gl_surface_egl.h"
 #include "ui/ozone/platform/wayland/common/wayland_overlay_config.h"
 #include "ui/ozone/platform/wayland/gpu/wayland_surface_gpu.h"
@@ -298,9 +297,10 @@
 #if defined(WAYLAND_GBM)
 GbmDevice* WaylandBufferManagerGpu::GetGbmDevice() {
   // Wayland won't support wl_drm or zwp_linux_dmabuf without this extension.
-  if (!supports_dmabuf_ || (!gl::GLSurfaceEGL::GetGLDisplayEGL()
-                                 ->ext->b_EGL_EXT_image_dma_buf_import &&
-                            !use_fake_gbm_device_for_test_)) {
+  if (!supports_dmabuf_ ||
+      (!gl::GLSurfaceEGL::GetGLDisplayEGL()->HasEGLExtension(
+           "EGL_EXT_image_dma_buf_import") &&
+       !use_fake_gbm_device_for_test_)) {
     supports_dmabuf_ = false;
     return nullptr;
   }
diff --git a/ui/ozone/platform/wayland/host/shell_toplevel_wrapper.h b/ui/ozone/platform/wayland/host/shell_toplevel_wrapper.h
index 4177604..76a9dfe3 100644
--- a/ui/ozone/platform/wayland/host/shell_toplevel_wrapper.h
+++ b/ui/ozone/platform/wayland/host/shell_toplevel_wrapper.h
@@ -123,6 +123,10 @@
   // Checks if the server supports chrome to control the window position in
   // screen coordinates.
   virtual bool SupportsScreenCoordinates() const = 0;
+
+  // Enables screen coordinates support. This is no-op if the server does not
+  // support the screen coordinates.
+  virtual void EnableScreenCoordinates() = 0;
 };
 
 // Look for |value| in |wl_array| in C++ style.
diff --git a/ui/ozone/platform/wayland/host/wayland_toplevel_window.cc b/ui/ozone/platform/wayland/host/wayland_toplevel_window.cc
index 844bcbc..27b58bf 100644
--- a/ui/ozone/platform/wayland/host/wayland_toplevel_window.cc
+++ b/ui/ozone/platform/wayland/host/wayland_toplevel_window.cc
@@ -69,6 +69,11 @@
     LOG(ERROR) << "Failed to create a ShellToplevel.";
     return false;
   }
+  screen_coordinates_enabled_ &= shell_toplevel_->SupportsScreenCoordinates();
+  screen_coordinates_enabled_ &= !use_native_frame_;
+
+  if (screen_coordinates_enabled_)
+    shell_toplevel_->EnableScreenCoordinates();
 
 #if BUILDFLAG(IS_CHROMEOS_LACROS)
   shell_toplevel_->SetAppId(window_unique_id_);
@@ -88,7 +93,6 @@
                             ZAURA_SURFACE_FRAME_TYPE_SHADOW);
   }
 
-  screen_coordinates_enabled_ &= shell_toplevel_->SupportsScreenCoordinates();
   // TODO(oshima): Change to use DIP.
   if (screen_coordinates_enabled_)
     SetBoundsInPixels(GetBoundsInPixels());
diff --git a/ui/ozone/platform/wayland/host/xdg_toplevel_wrapper_impl.cc b/ui/ozone/platform/wayland/host/xdg_toplevel_wrapper_impl.cc
index 14d1c943..9935ab94 100644
--- a/ui/ozone/platform/wayland/host/xdg_toplevel_wrapper_impl.cc
+++ b/ui/ozone/platform/wayland/host/xdg_toplevel_wrapper_impl.cc
@@ -109,23 +109,6 @@
         zaura_toplevel_surface_submission_in_pixel_coordinates(
             aura_toplevel_.get());
       }
-
-      if (features::IsWaylandScreenCoordinatesEnabled()) {
-        if (SupportsScreenCoordinates()) {
-          zaura_toplevel_set_supports_screen_coordinates(aura_toplevel_.get());
-
-          static constexpr zaura_toplevel_listener aura_toplevel_listener = {
-              &ConfigureAuraTopLevel,
-              &OnOriginChange,
-          };
-
-          zaura_toplevel_add_listener(aura_toplevel_.get(),
-                                      &aura_toplevel_listener, this);
-        } else {
-          LOG(WARNING) << "Server implementation of wayland is incompatible, "
-                          "WaylandScreenCoordinatesEnabled has no effect.";
-        }
-      }
     }
   }
 
@@ -299,14 +282,6 @@
     DecorationMode requested_mode) {
   if (!zxdg_toplevel_decoration_ || requested_mode == decoration_mode_)
     return;
-  // TODO(crbug.com/1261321): Server side decoration decoration will not work
-  // when the screen coordinate is enabled.
-  if (SupportsScreenCoordinates() &&
-      requested_mode == DecorationMode::kServerSide) {
-    LOG(WARNING) << "Server side decoration is not supported when window "
-                    "positioning is enabled";
-    return;
-  }
 
   zxdg_toplevel_decoration_v1_set_mode(zxdg_toplevel_decoration_.get(),
                                        ToInt32(requested_mode));
@@ -416,6 +391,25 @@
              ZAURA_TOPLEVEL_SET_SUPPORTS_SCREEN_COORDINATES_SINCE_VERSION;
 }
 
+void XDGToplevelWrapperImpl::EnableScreenCoordinates() {
+  if (!features::IsWaylandScreenCoordinatesEnabled())
+    return;
+  if (!SupportsScreenCoordinates()) {
+    LOG(WARNING) << "Server implementation of wayland is incompatible, "
+                    "WaylandScreenCoordinatesEnabled has no effect.";
+    return;
+  }
+  zaura_toplevel_set_supports_screen_coordinates(aura_toplevel_.get());
+
+  static constexpr zaura_toplevel_listener aura_toplevel_listener = {
+      &ConfigureAuraTopLevel,
+      &OnOriginChange,
+  };
+
+  zaura_toplevel_add_listener(aura_toplevel_.get(), &aura_toplevel_listener,
+                              this);
+}
+
 void XDGToplevelWrapperImpl::SetRestoreInfo(int32_t restore_session_id,
                                             int32_t restore_window_id) {
   if (aura_toplevel_ && zaura_toplevel_get_version(aura_toplevel_.get()) >=
diff --git a/ui/ozone/platform/wayland/host/xdg_toplevel_wrapper_impl.h b/ui/ozone/platform/wayland/host/xdg_toplevel_wrapper_impl.h
index 5bdf82dc..c8d7830 100644
--- a/ui/ozone/platform/wayland/host/xdg_toplevel_wrapper_impl.h
+++ b/ui/ozone/platform/wayland/host/xdg_toplevel_wrapper_impl.h
@@ -49,6 +49,7 @@
   void SetRestoreInfoWithWindowIdSource(int32_t, const std::string&) override;
   void SetSystemModal(bool modal) override;
   bool SupportsScreenCoordinates() const override;
+  void EnableScreenCoordinates() override;
 
   XDGSurfaceWrapperImpl* xdg_surface_wrapper() const;
 
diff --git a/ui/ozone/platform/wayland/host/zxdg_toplevel_v6_wrapper_impl.cc b/ui/ozone/platform/wayland/host/zxdg_toplevel_v6_wrapper_impl.cc
index ce770a5d9..4ddb3a6 100644
--- a/ui/ozone/platform/wayland/host/zxdg_toplevel_v6_wrapper_impl.cc
+++ b/ui/ozone/platform/wayland/host/zxdg_toplevel_v6_wrapper_impl.cc
@@ -211,4 +211,6 @@
   return false;
 }
 
+void ZXDGToplevelV6WrapperImpl::EnableScreenCoordinates() {}
+
 }  // namespace ui
diff --git a/ui/ozone/platform/wayland/host/zxdg_toplevel_v6_wrapper_impl.h b/ui/ozone/platform/wayland/host/zxdg_toplevel_v6_wrapper_impl.h
index 15cc90f0..140c6778 100644
--- a/ui/ozone/platform/wayland/host/zxdg_toplevel_v6_wrapper_impl.h
+++ b/ui/ozone/platform/wayland/host/zxdg_toplevel_v6_wrapper_impl.h
@@ -53,6 +53,7 @@
       const std::string& restore_window_id_source) override;
   void SetSystemModal(bool modal) override;
   bool SupportsScreenCoordinates() const override;
+  void EnableScreenCoordinates() override;
 
   ZXDGSurfaceV6WrapperImpl* zxdg_surface_v6_wrapper() const;
 
diff --git a/ui/webui/resources/tools/generate_grd.gni b/ui/webui/resources/tools/generate_grd.gni
index 7a99fc8..8f8836f7 100644
--- a/ui/webui/resources/tools/generate_grd.gni
+++ b/ui/webui/resources/tools/generate_grd.gni
@@ -10,6 +10,7 @@
                            [
                              "deps",
                              "public_deps",
+                             "testonly",
                            ])
 
     inputs = []