diff --git a/DEPS b/DEPS
index 738651c..067bbadb 100644
--- a/DEPS
+++ b/DEPS
@@ -304,19 +304,19 @@
   # 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': '89742d768c973ea0e07676713825a105039b9a5d',
+  'skia_revision': '51c838db272c8543f258bf5d5c6a84f4cf677676',
   # 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': 'e528f9f7a7367505aa70b3dba2190cf8468cb74c',
+  'v8_revision': '67b838d1b1df5f9bf137cca57fea1e524dd5e756',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ANGLE
   # and whatever else without interference from each other.
-  'angle_revision': '7d8cbb600e5ef9bc63d039bd422af8e5c3e2a281',
+  'angle_revision': 'a0b0ec00baead8d24541b5f4279fe3568318cb8b',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
-  'swiftshader_revision': 'd0aa9ad9447025a42f17df1b93bd71183e9b2d1f',
+  'swiftshader_revision': 'ff8cc02ea659b8d64cf45f2cdd425ee293ddac03',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
@@ -331,7 +331,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Fuchsia sdk
   # and whatever else without interference from each other.
-  'fuchsia_version': 'version:11.20221222.0.1',
+  'fuchsia_version': 'version:11.20221222.1.1',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling google-toolbox-for-mac
   # and whatever else without interference from each other.
@@ -375,7 +375,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling catapult
   # and whatever else without interference from each other.
-  'catapult_revision': 'b10b1305cef856fefe0827de2a1ba20d52c0580a',
+  'catapult_revision': 'ebbb83f192fe9ee3214119184001b9ddcfd44fb0',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -383,7 +383,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': '37d35582c97bf3017430fb6e93e167911e64316e',
+  'devtools_frontend_revision': 'a1a6fc838a1eee5579553a942f726585482c5cb3',
   # 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.
@@ -419,7 +419,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': '906fc9df206d668191e9660a16688e27eb3d97ce',
+  'dawn_revision': 'ffcd024aaac27858071fdd37ff6b979e9fb20253',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -463,7 +463,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':    'df3cc8ecee393c765a7274a4687f8dff3558d590',
+  'libcxxabi_revision':    'dc82f3042daa8b06d34e51d8492d37ce901a6f8d',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -776,7 +776,7 @@
 
   'src/clank': {
     'url': 'https://chrome-internal.googlesource.com/clank/internal/apps.git' + '@' +
-    '3e0d5ccc185f18759008e2f588f33e31cdf52104',
+    'c70b5dd91cb22039dab4a0272fa6169c4f2cb9ac',
     'condition': 'checkout_android and checkout_src_internal',
   },
 
@@ -965,7 +965,7 @@
     'packages': [
       {
           'package': 'chromium/third_party/androidx',
-          'version': 'XNQCcCxsa8Vznu546BS4QiWwlsCp1_1rV1J_5rSghKwC',
+          'version': 'Q0RddCfn0BxFtUdiySojmUsDxQcuYrX4W1JRRHXk-30C',
       },
     ],
     'condition': 'checkout_android',
@@ -1210,13 +1210,13 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + 'cf31045b347e24e6619f2564fdb0c2490f661745',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '0b96058844728db8040a7348cc4c61fde453401a',
 
   'src/third_party/devtools-frontend/src':
     Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'),
 
   'src/third_party/devtools-frontend-internal': {
-      'url': 'https://chrome-internal.googlesource.com/devtools/devtools-internal.git' + '@' + 'aefadfcacebbc1d64d8fd81f07a7dbf4bf12cec2',
+      'url': 'https://chrome-internal.googlesource.com/devtools/devtools-internal.git' + '@' + 'd3bfcd511b8a7d0d1250ae7b6a8c538691437c32',
     'condition': 'checkout_src_internal',
   },
 
@@ -1900,7 +1900,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@0eb5bc35c0153313afefab2a3b3e1b4196c6640a',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@3d7e0ba34a931e67b9165cb4b54f037d0f04479d',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index e5d93a18..74e6d0d 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -1075,6 +1075,7 @@
     "style/style_viewer/pill_button_instances_grid_view_factory.cc",
     "style/style_viewer/radio_button_group_instances_grid_view_factory.cc",
     "style/style_viewer/radio_button_instances_grid_view_factory.cc",
+    "style/style_viewer/system_text_instances_grid_view_factory.cc",
     "style/style_viewer/system_ui_components_grid_view.cc",
     "style/style_viewer/system_ui_components_grid_view.h",
     "style/style_viewer/system_ui_components_grid_view_factories.h",
@@ -2058,6 +2059,10 @@
     "wm/default_window_resizer.cc",
     "wm/default_window_resizer.h",
     "wm/desks/autotest_desks_api.cc",
+    "wm/desks/cros_next_desk_button.cc",
+    "wm/desks/cros_next_desk_button.h",
+    "wm/desks/cros_next_desk_button_base.cc",
+    "wm/desks/cros_next_desk_button_base.h",
     "wm/desks/desk.cc",
     "wm/desks/desk.h",
     "wm/desks/desk_action_context_menu.cc",
diff --git a/ash/accelerators/debug_commands.cc b/ash/accelerators/debug_commands.cc
index 2b5dad4..0c6477d 100644
--- a/ash/accelerators/debug_commands.cc
+++ b/ash/accelerators/debug_commands.cc
@@ -233,9 +233,10 @@
 
   DCHECK(floated_window);
 
-  float_controller->OnFlingOrSwipeForTablet(
-      floated_window,
-      /*left=*/action == DEBUG_TUCK_FLOATED_WINDOW_LEFT, /*up=*/true);
+  const float velocity_x =
+      action == DEBUG_TUCK_FLOATED_WINDOW_LEFT ? -500.f : 500.f;
+  float_controller->OnFlingOrSwipeForTablet(floated_window, velocity_x,
+                                            /*velocity_y=*/0.f);
 }
 
 }  // namespace
diff --git a/ash/constants/ash_pref_names.cc b/ash/constants/ash_pref_names.cc
index 72664e5..bda31f6 100644
--- a/ash/constants/ash_pref_names.cc
+++ b/ash/constants/ash_pref_names.cc
@@ -1171,6 +1171,11 @@
 // An boolean pref that indicates whether portrait relighting is applied.
 const char kPortraitRelighting[] = "ash.camera.portrait_relighting";
 
+// Specifies if ARC app sync metrics should be recorded, i.e. this is the
+// initial session after sync consent screen.
+const char kRecordArcAppSyncMetrics[] =
+    "ash.should_record_arc_app_sync_metrics";
+
 // NOTE: New prefs should start with the "ash." prefix. Existing prefs moved
 // into this file should not be renamed, since they may be synced.
 
diff --git a/ash/constants/ash_pref_names.h b/ash/constants/ash_pref_names.h
index 058885d..b1eb935f 100644
--- a/ash/constants/ash_pref_names.h
+++ b/ash/constants/ash_pref_names.h
@@ -538,6 +538,8 @@
 
 COMPONENT_EXPORT(ASH_CONSTANTS) extern const char kPortraitRelighting[];
 
+COMPONENT_EXPORT(ASH_CONSTANTS) extern const char kRecordArcAppSyncMetrics[];
+
 }  // namespace prefs
 }  // namespace ash
 
diff --git a/ash/quick_pair/common/fast_pair/fast_pair_metrics.cc b/ash/quick_pair/common/fast_pair/fast_pair_metrics.cc
index 222feda..da54311 100644
--- a/ash/quick_pair/common/fast_pair/fast_pair_metrics.cc
+++ b/ash/quick_pair/common/fast_pair/fast_pair_metrics.cc
@@ -820,6 +820,9 @@
     "FastPair.SubsequentPairing.Initialization.RetriesBeforeSuccess";
 constexpr char kInitializePairingProcessRetriesBeforeSuccessRetroactive[] =
     "FastPair.RetroactivePairing.Initialization.RetriesBeforeSuccess";
+const char kHandshakeEffectiveSuccessRate[] =
+    "FastPair.Handshake.EffectiveSuccessRate";
+const char kHandshakeAttemptCount[] = "FastPair.Handshake.AttemptCount";
 
 const std::string GetEngagementFlowInitialModelIdMetric(
     const ash::quick_pair::Device& device) {
@@ -1363,6 +1366,15 @@
                                 initialization_step);
 }
 
+void RecordEffectiveHandshakeSuccess(bool success) {
+  base::UmaHistogramBoolean(kHandshakeEffectiveSuccessRate, success);
+}
+
+void RecordHandshakeAttemptCount(int num_attempts) {
+  base::UmaHistogramExactLinear(kHandshakeAttemptCount, num_attempts,
+                                /*exclusive_max=*/10);
+}
+
 void RecordHandshakeResult(bool success) {
   base::UmaHistogramBoolean(kHandshakeResult, success);
 }
diff --git a/ash/quick_pair/common/fast_pair/fast_pair_metrics.h b/ash/quick_pair/common/fast_pair/fast_pair_metrics.h
index 84f3c16b..e805bc55 100644
--- a/ash/quick_pair/common/fast_pair/fast_pair_metrics.h
+++ b/ash/quick_pair/common/fast_pair/fast_pair_metrics.h
@@ -381,6 +381,12 @@
     FastPairGattConnectionSteps initialization_step);
 
 COMPONENT_EXPORT(QUICK_PAIR_COMMON)
+void RecordEffectiveHandshakeSuccess(bool success);
+
+COMPONENT_EXPORT(QUICK_PAIR_COMMON)
+void RecordHandshakeAttemptCount(int num_attempts);
+
+COMPONENT_EXPORT(QUICK_PAIR_COMMON)
 void RecordHandshakeResult(bool success);
 
 COMPONENT_EXPORT(QUICK_PAIR_COMMON)
diff --git a/ash/quick_pair/pairing/pairer_broker_impl.cc b/ash/quick_pair/pairing/pairer_broker_impl.cc
index b22802f..fe1bff1 100644
--- a/ash/quick_pair/pairing/pairer_broker_impl.cc
+++ b/ash/quick_pair/pairing/pairer_broker_impl.cc
@@ -27,6 +27,7 @@
 namespace {
 
 constexpr int kMaxFailureRetryCount = 3;
+constexpr int kMaxNumHandshakeAttempts = 3;
 
 // 1s delay after cancelling pairing was chosen to align with Android's Fast
 // Pair implementation.
@@ -113,19 +114,26 @@
 }
 
 void PairerBrokerImpl::CreateHandshake(scoped_refptr<Device> device) {
-  // TODO(b/259429032): Add 3 retries for this handshake.
   auto* fast_pair_handshake =
       FastPairHandshakeLookup::GetInstance()->Get(device);
-  if (fast_pair_handshake && fast_pair_handshake->completed_successfully()) {
-    QP_LOG(INFO) << __func__
-                 << ": Reusing existing handshake for pair attempt.";
-    RecordFastPairInitializePairingProcessEvent(
-        *device, FastPairInitializePairingProcessEvent::kHandshakeReused);
-    StartBondingAttempt(device);
-    return;
+
+  if (fast_pair_handshake) {
+    if (fast_pair_handshake->completed_successfully()) {
+      QP_LOG(INFO) << __func__
+                   << ": Reusing existing handshake for pair attempt.";
+      RecordFastPairInitializePairingProcessEvent(
+          *device, FastPairInitializePairingProcessEvent::kHandshakeReused);
+      StartBondingAttempt(device);
+      return;
+    } else {
+      // If the previous handshake did not complete successfully, erase it
+      // before attempting to create a new handshake for the device.
+      FastPairHandshakeLookup::GetInstance()->Erase(device);
+    }
   }
 
   QP_LOG(INFO) << __func__ << ": Creating new handshake for pair attempt.";
+  num_handshake_attempts_[device->ble_address]++;
   FastPairHandshakeLookup::GetInstance()->Create(
       adapter_, device,
       base::BindOnce(&PairerBrokerImpl::OnHandshakeComplete,
@@ -165,13 +173,27 @@
         device->ble_address, true);
   }
 
+  RecordEffectiveHandshakeSuccess(/*success=*/true);
+  RecordHandshakeAttemptCount(num_handshake_attempts_[device->ble_address]);
+
+  // Reset |num_handshake_attempts_| so if the handshake is lost during pairing,
+  // we will attempt to create it 3 more times. This should be an extremely rare
+  // situation, such as handshake happening directly before the device rotates
+  // ble addresses.
+  num_handshake_attempts_[device->ble_address] = 0;
   StartBondingAttempt(device);
 }
 
 void PairerBrokerImpl::OnHandshakeFailure(scoped_refptr<Device> device,
                                           PairFailure failure) {
+  if (num_handshake_attempts_[device->ble_address] < kMaxNumHandshakeAttempts) {
+    CreateHandshake(device);
+    return;
+  }
+
   QP_LOG(INFO) << __func__
                << ": Handshake failed to be created. Notifying observers.";
+  RecordEffectiveHandshakeSuccess(/*success=*/false);
   RecordInitializationFailureReason(*device, failure);
   for (auto& observer : observers_) {
     observer.OnPairFailure(device, failure);
diff --git a/ash/quick_pair/pairing/pairer_broker_impl.h b/ash/quick_pair/pairing/pairer_broker_impl.h
index 0a645117..78ba9d4 100644
--- a/ash/quick_pair/pairing/pairer_broker_impl.h
+++ b/ash/quick_pair/pairing/pairer_broker_impl.h
@@ -73,6 +73,7 @@
   base::flat_map<std::string, int> pair_failure_counts_;
   base::flat_map<std::string, bool>
       did_handshake_previously_complete_successfully_map_;
+  base::flat_map<std::string, int> num_handshake_attempts_;
 
   scoped_refptr<device::BluetoothAdapter> adapter_;
   std::unique_ptr<FastPairUnpairHandler> fast_pair_unpair_handler_;
diff --git a/ash/quick_pair/pairing/pairer_broker_impl_unittest.cc b/ash/quick_pair/pairing/pairer_broker_impl_unittest.cc
index 849c4e7..5292eee 100644
--- a/ash/quick_pair/pairing/pairer_broker_impl_unittest.cc
+++ b/ash/quick_pair/pairing/pairer_broker_impl_unittest.cc
@@ -58,6 +58,9 @@
     "FastPair.InitialPairing.Pairing";
 constexpr char kProtocolPairingStepSubsequent[] =
     "FastPair.SubsequentPairing.Pairing";
+const char kHandshakeEffectiveSuccessRate[] =
+    "FastPair.Handshake.EffectiveSuccessRate";
+const char kHandshakeAttemptCount[] = "FastPair.Handshake.AttemptCount";
 
 class FakeFastPairPairer : public ash::quick_pair::FastPairPairer {
  public:
@@ -700,11 +703,88 @@
             1);
 }
 
-TEST_F(PairerBrokerImplTest, NoPairingIfHandshakeFailed_Initial) {
+TEST_F(PairerBrokerImplTest, PairAfterTwoHandshakeFailures_Initial) {
+  histogram_tester_.ExpectTotalCount(kHandshakeEffectiveSuccessRate, 0);
+  histogram_tester_.ExpectTotalCount(kHandshakeAttemptCount, 0);
+
   CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
                    /*protocol=*/Protocol::kFastPairInitial);
   pairer_broker_->PairDevice(device_);
   fake_fast_pair_handshake_->InvokeCallback(PairFailure::kCreateGattConnection);
+  fake_fast_pair_handshake_->InvokeCallback(PairFailure::kCreateGattConnection);
+  fake_fast_pair_handshake_->InvokeCallback();
+  EXPECT_TRUE(pairer_broker_->IsPairing());
+
+  fast_pair_pairer_factory_->fake_fast_pair_pairer()->TriggerPairedCallback();
+
+  EXPECT_EQ(device_paired_count_, 1);
+  EXPECT_EQ(pair_failure_count_, 0);
+  histogram_tester_.ExpectTotalCount(kHandshakeEffectiveSuccessRate, 1);
+  histogram_tester_.ExpectTotalCount(kHandshakeAttemptCount, 1);
+
+  fast_pair_pairer_factory_->fake_fast_pair_pairer()
+      ->TriggerPairingProcedureCompleteCallback();
+  EXPECT_FALSE(pairer_broker_->IsPairing());
+}
+
+TEST_F(PairerBrokerImplTest, PairAfterTwoHandshakeFailures_Subsequent) {
+  histogram_tester_.ExpectTotalCount(kHandshakeEffectiveSuccessRate, 0);
+  histogram_tester_.ExpectTotalCount(kHandshakeAttemptCount, 0);
+
+  CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
+                   /*protocol=*/Protocol::kFastPairSubsequent);
+  pairer_broker_->PairDevice(device_);
+  fake_fast_pair_handshake_->InvokeCallback(PairFailure::kCreateGattConnection);
+  fake_fast_pair_handshake_->InvokeCallback(PairFailure::kCreateGattConnection);
+  fake_fast_pair_handshake_->InvokeCallback();
+  EXPECT_TRUE(pairer_broker_->IsPairing());
+
+  fast_pair_pairer_factory_->fake_fast_pair_pairer()->TriggerPairedCallback();
+
+  EXPECT_EQ(device_paired_count_, 1);
+  EXPECT_EQ(pair_failure_count_, 0);
+  histogram_tester_.ExpectTotalCount(kHandshakeEffectiveSuccessRate, 1);
+  histogram_tester_.ExpectTotalCount(kHandshakeAttemptCount, 1);
+
+  fast_pair_pairer_factory_->fake_fast_pair_pairer()
+      ->TriggerPairingProcedureCompleteCallback();
+  EXPECT_FALSE(pairer_broker_->IsPairing());
+}
+
+TEST_F(PairerBrokerImplTest, PairAfterTwoHandshakeFailures_Retroactive) {
+  histogram_tester_.ExpectTotalCount(kHandshakeEffectiveSuccessRate, 0);
+  histogram_tester_.ExpectTotalCount(kHandshakeAttemptCount, 0);
+
+  CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
+                   /*protocol=*/Protocol::kFastPairRetroactive);
+  pairer_broker_->PairDevice(device_);
+  fake_fast_pair_handshake_->InvokeCallback(PairFailure::kCreateGattConnection);
+  fake_fast_pair_handshake_->InvokeCallback(PairFailure::kCreateGattConnection);
+  fake_fast_pair_handshake_->InvokeCallback();
+  EXPECT_TRUE(pairer_broker_->IsPairing());
+
+  fast_pair_pairer_factory_->fake_fast_pair_pairer()->TriggerPairedCallback();
+
+  EXPECT_EQ(device_paired_count_, 1);
+  EXPECT_EQ(pair_failure_count_, 0);
+  histogram_tester_.ExpectTotalCount(kHandshakeEffectiveSuccessRate, 1);
+  histogram_tester_.ExpectTotalCount(kHandshakeAttemptCount, 1);
+
+  fast_pair_pairer_factory_->fake_fast_pair_pairer()
+      ->TriggerPairingProcedureCompleteCallback();
+  EXPECT_FALSE(pairer_broker_->IsPairing());
+}
+
+TEST_F(PairerBrokerImplTest, NoPairingIfHandshakeFailed_Initial) {
+  histogram_tester_.ExpectTotalCount(kHandshakeEffectiveSuccessRate, 0);
+  histogram_tester_.ExpectTotalCount(kHandshakeAttemptCount, 0);
+
+  CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
+                   /*protocol=*/Protocol::kFastPairInitial);
+  pairer_broker_->PairDevice(device_);
+  fake_fast_pair_handshake_->InvokeCallback(PairFailure::kCreateGattConnection);
+  fake_fast_pair_handshake_->InvokeCallback(PairFailure::kCreateGattConnection);
+  fake_fast_pair_handshake_->InvokeCallback(PairFailure::kCreateGattConnection);
   EXPECT_FALSE(pairer_broker_->IsPairing());
 
   EXPECT_EQ(device_paired_count_, 0);
@@ -713,13 +793,19 @@
                 kInitializePairingProcessFailureReasonInitial,
                 PairFailure::kCreateGattConnection),
             1);
+  histogram_tester_.ExpectTotalCount(kHandshakeEffectiveSuccessRate, 1);
 }
 
 TEST_F(PairerBrokerImplTest, NoPairingIfHandshakeFailed_Subsequent) {
+  histogram_tester_.ExpectTotalCount(kHandshakeEffectiveSuccessRate, 0);
+  histogram_tester_.ExpectTotalCount(kHandshakeAttemptCount, 0);
+
   CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
                    /*protocol=*/Protocol::kFastPairSubsequent);
   pairer_broker_->PairDevice(device_);
   fake_fast_pair_handshake_->InvokeCallback(PairFailure::kCreateGattConnection);
+  fake_fast_pair_handshake_->InvokeCallback(PairFailure::kCreateGattConnection);
+  fake_fast_pair_handshake_->InvokeCallback(PairFailure::kCreateGattConnection);
   EXPECT_FALSE(pairer_broker_->IsPairing());
 
   EXPECT_EQ(device_paired_count_, 0);
@@ -728,13 +814,19 @@
                 kInitializePairingProcessFailureReasonSubsequent,
                 PairFailure::kCreateGattConnection),
             1);
+  histogram_tester_.ExpectTotalCount(kHandshakeEffectiveSuccessRate, 1);
 }
 
 TEST_F(PairerBrokerImplTest, NoPairingIfHandshakeFailed_Retroactive) {
+  histogram_tester_.ExpectTotalCount(kHandshakeEffectiveSuccessRate, 0);
+  histogram_tester_.ExpectTotalCount(kHandshakeAttemptCount, 0);
+
   CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
                    /*protocol=*/Protocol::kFastPairRetroactive);
   pairer_broker_->PairDevice(device_);
   fake_fast_pair_handshake_->InvokeCallback(PairFailure::kCreateGattConnection);
+  fake_fast_pair_handshake_->InvokeCallback(PairFailure::kCreateGattConnection);
+  fake_fast_pair_handshake_->InvokeCallback(PairFailure::kCreateGattConnection);
   EXPECT_FALSE(pairer_broker_->IsPairing());
 
   EXPECT_EQ(device_paired_count_, 0);
@@ -743,6 +835,7 @@
                 kInitializePairingProcessFailureReasonRetroactive,
                 PairFailure::kCreateGattConnection),
             1);
+  histogram_tester_.ExpectTotalCount(kHandshakeEffectiveSuccessRate, 1);
 }
 }  // namespace quick_pair
 }  // namespace ash
diff --git a/ash/style/ash_color_mixer.cc b/ash/style/ash_color_mixer.cc
index dad978d..92ba2bb 100644
--- a/ash/style/ash_color_mixer.cc
+++ b/ash/style/ash_color_mixer.cc
@@ -5,7 +5,6 @@
 #include "ash/style/ash_color_mixer.h"
 
 #include "ash/constants/ash_features.h"
-#include "ash/public/cpp/style/scoped_light_mode_as_default.h"
 #include "ash/style/ash_color_id.h"
 #include "ash/style/dark_light_mode_controller_impl.h"
 #include "ash/style/harmonized_colors.h"
@@ -63,9 +62,7 @@
   }
 
   const bool use_dark_color =
-      features::IsDarkLightModeEnabled()
-          ? key.color_mode == ui::ColorProviderManager::ColorMode::kDark
-          : DarkLightModeControllerImpl::Get()->IsDarkModeEnabled();
+      key.color_mode == ui::ColorProviderManager::ColorMode::kDark;
 
   // Colors of the Shield and Base layers.
   const SkColor default_background_color =
@@ -103,9 +100,7 @@
 void AddControlsColors(ui::ColorMixer& mixer,
                        const ui::ColorProviderManager::Key& key) {
   const bool use_dark_color =
-      features::IsDarkLightModeEnabled()
-          ? key.color_mode == ui::ColorProviderManager::ColorMode::kDark
-          : DarkLightModeControllerImpl::Get()->IsDarkModeEnabled();
+      key.color_mode == ui::ColorProviderManager::ColorMode::kDark;
 
   // ControlsLayer colors
   mixer[kColorAshHairlineBorderColor] =
@@ -128,9 +123,7 @@
 void AddContentColors(ui::ColorMixer& mixer,
                       const ui::ColorProviderManager::Key& key) {
   const bool use_dark_color =
-      features::IsDarkLightModeEnabled()
-          ? key.color_mode == ui::ColorProviderManager::ColorMode::kDark
-          : DarkLightModeControllerImpl::Get()->IsDarkModeEnabled();
+      key.color_mode == ui::ColorProviderManager::ColorMode::kDark;
 
   // ContentLayer colors.
   mixer[kColorAshScrollBarColor] =
@@ -172,12 +165,9 @@
       ui::SetAlpha(kColorAshAppStateIndicatorColor, kDisabledColorOpacity);
   mixer[kColorAshShelfHandleColor] = {cros_tokens::kIconColorSecondary};
   mixer[kColorAshShelfTooltipBackgroundColor] = {
-      features::IsDarkLightModeEnabled() ? kColorAshInvertedShieldAndBase80
-                                         : kColorAshShieldAndBase80};
+      kColorAshInvertedShieldAndBase80};
   mixer[kColorAshShelfTooltipForegroundColor] = {
-      features::IsDarkLightModeEnabled()
-          ? cros_tokens::kTextColorPrimaryInverted
-          : cros_tokens::kTextColorPrimary};
+      cros_tokens::kTextColorPrimaryInverted};
   mixer[kColorAshSliderColorActive] = {kColorAshTextColorURL};
   mixer[kColorAshSliderColorInactive] = {kColorAshScrollBarColor};
   mixer[kColorAshRadioColorActive] = {kColorAshTextColorURL};
@@ -384,11 +374,7 @@
 void AddCrosStylesColorMixer(ui::ColorProvider* provider,
                              const ui::ColorProviderManager::Key& key) {
   ui::ColorMixer& mixer = provider->AddMixer();
-  bool dark_mode =
-      features::IsDarkLightModeEnabled()
-          ? key.color_mode == ui::ColorProviderManager::ColorMode::kDark
-          : DarkLightModeControllerImpl::Get()->IsDarkModeEnabled();
-
+  bool dark_mode = key.color_mode == ui::ColorProviderManager::ColorMode::kDark;
   if (ash::features::IsJellyEnabled()) {
     AddRefPalette(mixer, key);
   } else {
@@ -409,9 +395,7 @@
                       const ui::ColorProviderManager::Key& key) {
   ui::ColorMixer& mixer = provider->AddMixer();
   const bool use_dark_color =
-      features::IsDarkLightModeEnabled()
-          ? key.color_mode == ui::ColorProviderManager::ColorMode::kDark
-          : DarkLightModeControllerImpl::Get()->IsDarkModeEnabled();
+      key.color_mode == ui::ColorProviderManager::ColorMode::kDark;
 
   AddShieldAndBaseColors(mixer, key);
   AddControlsColors(mixer, key);
@@ -452,19 +436,6 @@
 
   mixer[ui::kColorAshOnboardingFocusRing] = {cros_tokens::kColorProminentDark};
 
-  if (!features::IsDarkLightModeEnabled()) {
-    ash::ScopedLightModeAsDefault scoped_light_mode_as_default;
-    mixer[ui::kColorAshSystemUILightBorderColor1] = {
-        ui::kColorHighlightBorderBorder1};
-    mixer[ui::kColorAshSystemUILightBorderColor2] = {
-        ui::kColorHighlightBorderBorder2};
-    mixer[ui::kColorAshSystemUILightHighlightColor1] = {
-        ui::kColorHighlightBorderHighlight1};
-    mixer[ui::kColorAshSystemUILightHighlightColor2] = {
-        ui::kColorHighlightBorderHighlight2};
-    return;
-  }
-
   mixer[ui::kColorAshSystemUIMenuBackground] = {kColorAshShieldAndBase80};
   mixer[ui::kColorAshSystemUIMenuIcon] = {kColorAshIconColorPrimary};
   mixer[ui::kColorAshSystemUIMenuItemBackgroundSelected] = {kColorAshInkDrop};
diff --git a/ash/style/pill_button.cc b/ash/style/pill_button.cc
index 7314727..1ff9b8f 100644
--- a/ash/style/pill_button.cc
+++ b/ash/style/pill_button.cc
@@ -5,7 +5,6 @@
 #include "ash/style/pill_button.h"
 
 #include "ash/constants/ash_features.h"
-#include "ash/public/cpp/style/scoped_light_mode_as_default.h"
 #include "ash/style/ash_color_id.h"
 #include "ash/style/color_util.h"
 #include "ash/style/style_util.h"
diff --git a/ash/style/style_viewer/system_text_instances_grid_view_factory.cc b/ash/style/style_viewer/system_text_instances_grid_view_factory.cc
new file mode 100644
index 0000000..fbf0d853
--- /dev/null
+++ b/ash/style/style_viewer/system_text_instances_grid_view_factory.cc
@@ -0,0 +1,89 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/style/style_viewer/system_ui_components_grid_view_factories.h"
+
+#include "ash/style/style_viewer/system_ui_components_grid_view.h"
+#include "ash/style/system_textfield.h"
+#include "ash/style/system_textfield_controller.h"
+
+namespace ash {
+
+namespace {
+
+// Configurations of grid view for `SystemTextfield` instances.
+constexpr size_t kGridViewRowNum = 4;
+constexpr size_t kGridViewColNum = 1;
+constexpr size_t kGridViewRowGroupSize = 4;
+constexpr size_t kGirdViewColGroupSize = 1;
+
+}  // namespace
+
+// A component grid view class which allows to add a system textfield with
+// system textfield controller. It manages the unique pointers of textfield
+// controller.
+class TextfieldGridView : public SystemUIComponentsGridView {
+ public:
+  TextfieldGridView()
+      : SystemUIComponentsGridView(kGridViewRowNum,
+                                   kGridViewColNum,
+                                   kGridViewRowGroupSize,
+                                   kGirdViewColGroupSize) {}
+  TextfieldGridView(const TextfieldGridView&) = delete;
+  TextfieldGridView& operator=(const TextfieldGridView&) = delete;
+  ~TextfieldGridView() override = default;
+
+  // Adds a textfield with `SystemTextController` as its controller.
+  void AddTextfieldWithController(const std::u16string& name,
+                                  std::unique_ptr<SystemTextfield> textfield) {
+    controllers_.emplace_back(
+        std::make_unique<SystemTextfieldController>(textfield.get()));
+    AddInstance(name, std::move(textfield));
+  }
+
+ private:
+  std::vector<std::unique_ptr<SystemTextfieldController>> controllers_;
+};
+
+std::unique_ptr<SystemUIComponentsGridView>
+CreateSystemTextfieldInstancesGridView() {
+  auto grid_view = std::make_unique<TextfieldGridView>();
+
+  // Small size textfield.
+  auto textfield_small =
+      std::make_unique<SystemTextfield>(SystemTextfield::Type::kSmall);
+  textfield_small->SetAccessibleName(u"Small Text");
+  textfield_small->SetPlaceholderText(u"Small Text");
+
+  // Medium size textfield.
+  auto textfield_medium =
+      std::make_unique<SystemTextfield>(SystemTextfield::Type::kMedium);
+  textfield_medium->SetAccessibleName(u"Medium Text");
+  textfield_medium->SetPlaceholderText(u"Medium Text");
+
+  // Large size textfield.
+  auto textfield_large =
+      std::make_unique<SystemTextfield>(SystemTextfield::Type::kLarge);
+  textfield_large->SetAccessibleName(u"Large Text");
+  textfield_large->SetPlaceholderText(u"Large Text");
+
+  // Disabled textfield.
+  auto textfield_disabled =
+      std::make_unique<SystemTextfield>(SystemTextfield::Type::kMedium);
+  textfield_disabled->SetAccessibleName(u"Disabled Text");
+  textfield_disabled->SetPlaceholderText(u"Disable Text");
+  textfield_disabled->SetEnabled(false);
+
+  grid_view->AddTextfieldWithController(u"Textfield Small",
+                                        std::move(textfield_small));
+  grid_view->AddTextfieldWithController(u"Textfield Medium",
+                                        std::move(textfield_medium));
+  grid_view->AddTextfieldWithController(u"Textfield Large",
+                                        std::move(textfield_large));
+  grid_view->AddTextfieldWithController(u"Textfield Disabled",
+                                        std::move(textfield_disabled));
+  return grid_view;
+}
+
+}  // namespace ash
diff --git a/ash/style/style_viewer/system_ui_components_grid_view_factories.h b/ash/style/style_viewer/system_ui_components_grid_view_factories.h
index e20e5fd6..66cf6f7 100644
--- a/ash/style/style_viewer/system_ui_components_grid_view_factories.h
+++ b/ash/style/style_viewer/system_ui_components_grid_view_factories.h
@@ -25,6 +25,8 @@
 CreateRadioButtonGroupInstancesGridView();
 std::unique_ptr<SystemUIComponentsGridView> CreateKnobSwitchInstancesGridView();
 std::unique_ptr<SystemUIComponentsGridView> CreateTabSliderInstancesGridView();
+std::unique_ptr<SystemUIComponentsGridView>
+CreateSystemTextfieldInstancesGridView();
 
 }  // namespace ash
 
diff --git a/ash/style/style_viewer/system_ui_components_style_viewer_view.cc b/ash/style/style_viewer/system_ui_components_style_viewer_view.cc
index 0efd46e..cc825f7 100644
--- a/ash/style/style_viewer/system_ui_components_style_viewer_view.cc
+++ b/ash/style/style_viewer/system_ui_components_style_viewer_view.cc
@@ -169,6 +169,9 @@
       u"KnobSwitch", base::BindRepeating(&CreateKnobSwitchInstancesGridView));
   viewer_view->AddComponent(
       u"TabSlider", base::BindRepeating(&CreateTabSliderInstancesGridView));
+  viewer_view->AddComponent(
+      u"System Textfield",
+      base::BindRepeating(&CreateSystemTextfieldInstancesGridView));
 
   // Show PillButton on start.
   viewer_view->ShowComponentInstances(u"PillButton");
diff --git a/ash/style/system_textfield.cc b/ash/style/system_textfield.cc
index 1f99590..945b3ea9 100644
--- a/ash/style/system_textfield.cc
+++ b/ash/style/system_textfield.cc
@@ -93,8 +93,9 @@
 SystemTextfield::~SystemTextfield() = default;
 
 void SystemTextfield::SetActive(bool active) {
-  if (active_ == active)
+  if (active_ == active) {
     return;
+  }
 
   active_ = active;
 
@@ -180,8 +181,9 @@
 }
 
 void SystemTextfield::UpdateTextColor() {
-  if (!GetWidget())
+  if (!GetWidget()) {
     return;
+  }
 
   // Set text color.
   auto* color_provider = GetColorProvider();
diff --git a/ash/style/system_textfield_controller.cc b/ash/style/system_textfield_controller.cc
index 4b239d4..cb6d2d6 100644
--- a/ash/style/system_textfield_controller.cc
+++ b/ash/style/system_textfield_controller.cc
@@ -35,8 +35,9 @@
                                                const ui::KeyEvent& key_event) {
   DCHECK_EQ(textfield_, sender);
 
-  if (key_event.type() != ui::ET_KEY_PRESSED)
+  if (key_event.type() != ui::ET_KEY_PRESSED) {
     return false;
+  }
 
   const bool active = textfield_->active();
   if (key_event.key_code() == ui::VKEY_RETURN) {
@@ -90,8 +91,9 @@
       // When selecting all text was deferred, do it if there is no selection.
       if (defer_select_all_) {
         defer_select_all_ = false;
-        if (!textfield_->HasSelection())
+        if (!textfield_->HasSelection()) {
           textfield_->SelectAll(false);
+        }
         return true;
       }
       break;
diff --git a/ash/system/network/network_list_network_item_view.cc b/ash/system/network/network_list_network_item_view.cc
index d97ff04..70252ec 100644
--- a/ash/system/network/network_list_network_item_view.cc
+++ b/ash/system/network/network_list_network_item_view.cc
@@ -93,20 +93,6 @@
   return ActivationStateType::kUnknown;
 }
 
-bool IsNetworkInhibited(const NetworkStatePropertiesPtr& network_properties) {
-  if (!NetworkTypeMatchesType(network_properties->type,
-                              NetworkType::kCellular)) {
-    return false;
-  }
-
-  const chromeos::network_config::mojom::DeviceStateProperties*
-      cellular_device =
-          Shell::Get()->system_tray_model()->network_state_model()->GetDevice(
-              NetworkType::kCellular);
-
-  return cellular_device && IsInhibited(cellular_device);
-}
-
 bool IsCellularNetworkSimLocked(
     const NetworkStatePropertiesPtr& network_properties) {
   DCHECK(
@@ -157,35 +143,6 @@
   return false;
 }
 
-bool IsNetworkDisabled(const NetworkStatePropertiesPtr& network_properties) {
-  if (network_properties->prohibited_by_policy) {
-    return true;
-  }
-
-  if (!NetworkTypeMatchesType(network_properties->type,
-                              NetworkType::kCellular)) {
-    return false;
-  }
-
-  const CellularStateProperties* cellular =
-      network_properties->type_state->get_cellular().get();
-
-  if (!Shell::Get()->session_controller()->IsActiveUserSessionStarted() &&
-      cellular->sim_locked) {
-    return true;
-  }
-
-  if (cellular->activation_state == ActivationStateType::kActivating) {
-    return true;
-  }
-
-  if (IsNetworkInhibited(network_properties)) {
-    return true;
-  }
-
-  return false;
-}
-
 bool IsWifiNetworkSecured(const NetworkStatePropertiesPtr& network_properties) {
   DCHECK(NetworkTypeMatchesType(network_properties->type, NetworkType::kWiFi));
   return network_properties->type_state->get_wifi()->security !=
@@ -291,7 +248,6 @@
 
   if (IsNetworkDisabled(network_properties)) {
     UpdateDisabledTextColor();
-    SetEnabled(false);
   }
 
   if (network_properties_->prohibited_by_policy) {
diff --git a/ash/system/network/network_list_network_item_view_unittest.cc b/ash/system/network/network_list_network_item_view_unittest.cc
index a538f9b..a2282999e 100644
--- a/ash/system/network/network_list_network_item_view_unittest.cc
+++ b/ash/system/network/network_list_network_item_view_unittest.cc
@@ -385,11 +385,6 @@
   wifi_network->prohibited_by_policy = true;
   UpdateViewForNetwork(wifi_network);
 
-  // When prohibited by policy network row is not clickable.
-  EXPECT_FALSE(LastClickedNetworkListItem());
-  LeftClickOn(network_list_network_item_view());
-  EXPECT_FALSE(LastClickedNetworkListItem());
-
   ASSERT_TRUE(network_list_network_item_view()->right_view());
   EXPECT_TRUE(network_list_network_item_view()->right_view()->GetVisible());
   ASSERT_TRUE(views::IsViewClass<views::ImageView>(
diff --git a/ash/system/network/network_list_view_controller_impl.cc b/ash/system/network/network_list_view_controller_impl.cc
index d50f8a22..2acd231 100644
--- a/ash/system/network/network_list_view_controller_impl.cc
+++ b/ash/system/network/network_list_view_controller_impl.cc
@@ -818,6 +818,7 @@
     network_view->UpdateViewForNetwork(network);
     network_detailed_network_view()->GetNetworkList(type)->ReorderChildView(
         network_view, index);
+    network_view->SetEnabled(!IsNetworkDisabled(network));
 
     // Only emit ethernet metric each time we show Ethernet section
     // for the first time. We use `has_reordered_a_network` to determine
diff --git a/ash/system/network/network_list_view_controller_unittest.cc b/ash/system/network/network_list_view_controller_unittest.cc
index e5d6780..e0ef9cb 100644
--- a/ash/system/network/network_list_view_controller_unittest.cc
+++ b/ash/system/network/network_list_view_controller_unittest.cc
@@ -427,6 +427,17 @@
     }
   }
 
+  bool GetNetworkListItemIsEnabled(NetworkType type, size_t index) {
+    EXPECT_STREQ(network_list(type)->children().at(index)->GetClassName(),
+                 kNetworkListNetworkItemView);
+
+    NetworkListNetworkItemView* network =
+        static_cast<NetworkListNetworkItemView*>(
+            network_list(type)->children().at(index));
+
+    return network->GetEnabled();
+  }
+
   void SetBluetoothAdapterState(BluetoothSystemState system_state) {
     bluetooth_config_test_helper()
         ->fake_adapter_state_controller()
@@ -1256,4 +1267,48 @@
             cros_network()->GetScanCount(NetworkType::kTether));
 }
 
+TEST_P(NetworkListViewControllerTest, NetworkItemIsEnabled) {
+  auto properties =
+      chromeos::network_config::mojom::DeviceStateProperties::New();
+  properties->type = NetworkType::kCellular;
+  properties->device_state = DeviceStateType::kEnabled;
+  properties->sim_infos = CellularSIMInfos(kCellularTestIccid, kTestBaseEid);
+
+  cros_network()->SetDeviceProperties(properties.Clone());
+  ASSERT_THAT(GetMobileSubHeader(), NotNull());
+  EXPECT_TRUE(GetAddEsimButton()->GetEnabled());
+
+  cros_network()->AddNetworkAndDevice(
+      CrosNetworkConfigTestHelper::CreateStandaloneNetworkProperties(
+          kCellularName, NetworkType::kCellular,
+          ConnectionStateType::kConnected));
+
+  if (IsQsRevampEnabled()) {
+    CheckNetworkListItem(NetworkType::kCellular, /*index=*/0u, kCellularName);
+    EXPECT_TRUE(GetNetworkListItemIsEnabled(NetworkType::kCellular, 0u));
+  } else {
+    CheckNetworkListItem(NetworkType::kCellular, /*index=*/1u, kCellularName);
+    EXPECT_TRUE(GetNetworkListItemIsEnabled(NetworkType::kCellular, 1u));
+  }
+
+  // Inhibit cellular device.
+  properties->inhibit_reason = InhibitReason::kResettingEuiccMemory;
+  cros_network()->SetDeviceProperties(properties.Clone());
+
+  if (IsQsRevampEnabled()) {
+    EXPECT_FALSE(GetNetworkListItemIsEnabled(NetworkType::kCellular, 0u));
+  } else {
+    EXPECT_FALSE(GetNetworkListItemIsEnabled(NetworkType::kCellular, 1u));
+  }
+
+  // Uninhibit the device.
+  properties->inhibit_reason = InhibitReason::kNotInhibited;
+  cros_network()->SetDeviceProperties(properties.Clone());
+  if (IsQsRevampEnabled()) {
+    EXPECT_TRUE(GetNetworkListItemIsEnabled(NetworkType::kCellular, 0u));
+  } else {
+    EXPECT_TRUE(GetNetworkListItemIsEnabled(NetworkType::kCellular, 1u));
+  }
+}
+
 }  // namespace ash
diff --git a/ash/system/network/network_utils.cc b/ash/system/network/network_utils.cc
index 998eb6e..66675f8 100644
--- a/ash/system/network/network_utils.cc
+++ b/ash/system/network/network_utils.cc
@@ -5,9 +5,14 @@
 #include "ash/system/network/network_utils.h"
 
 #include "ash/constants/ash_features.h"
+#include "ash/session/session_controller_impl.h"
+#include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
+#include "ash/system/model/system_tray_model.h"
+#include "ash/system/network/tray_network_state_model.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/strings/strcat.h"
+#include "chromeos/services/network_config/public/cpp/cros_network_config_util.h"
 #include "ui/base/l10n/l10n_util.h"
 
 namespace ash {
@@ -83,4 +88,55 @@
   }
 }
 
+bool IsNetworkDisabled(
+    const chromeos::network_config::mojom::NetworkStatePropertiesPtr&
+        network_properties) {
+  if (network_properties->prohibited_by_policy) {
+    return true;
+  }
+
+  if (!chromeos::network_config::NetworkTypeMatchesType(
+          network_properties->type,
+          chromeos::network_config::mojom::NetworkType::kCellular)) {
+    return false;
+  }
+
+  const chromeos::network_config::mojom::CellularStateProperties* cellular =
+      network_properties->type_state->get_cellular().get();
+
+  if (!Shell::Get()->session_controller()->IsActiveUserSessionStarted() &&
+      cellular->sim_locked) {
+    return true;
+  }
+
+  if (cellular->activation_state ==
+      chromeos::network_config::mojom::ActivationStateType::kActivating) {
+    return true;
+  }
+
+  if (IsNetworkInhibited(network_properties)) {
+    return true;
+  }
+
+  return false;
+}
+
+bool IsNetworkInhibited(
+    const chromeos::network_config::mojom::NetworkStatePropertiesPtr&
+        network_properties) {
+  if (!chromeos::network_config::NetworkTypeMatchesType(
+          network_properties->type,
+          chromeos::network_config::mojom::NetworkType::kCellular)) {
+    return false;
+  }
+
+  const chromeos::network_config::mojom::DeviceStateProperties*
+      cellular_device =
+          Shell::Get()->system_tray_model()->network_state_model()->GetDevice(
+              chromeos::network_config::mojom::NetworkType::kCellular);
+
+  return cellular_device &&
+         chromeos::network_config::IsInhibited(cellular_device);
+}
+
 }  // namespace ash
diff --git a/ash/system/network/network_utils.h b/ash/system/network/network_utils.h
index 8d8c795..56ab0cb5d 100644
--- a/ash/system/network/network_utils.h
+++ b/ash/system/network/network_utils.h
@@ -46,6 +46,16 @@
 ASH_EXPORT absl::optional<std::u16string> GetPortalStateSubtext(
     const chromeos::network_config::mojom::PortalState& portal_state);
 
+// Returns true if current network row is disabled.
+ASH_EXPORT bool IsNetworkDisabled(
+    const chromeos::network_config::mojom::NetworkStatePropertiesPtr&
+        network_properties);
+
+// Returns true if current network is a cellular network and is inhibited.
+ASH_EXPORT bool IsNetworkInhibited(
+    const chromeos::network_config::mojom::NetworkStatePropertiesPtr&
+        network_properties);
+
 }  // namespace ash
 
 #endif  // ASH_SYSTEM_NETWORK_NETWORK_UTILS_H_
diff --git a/ash/system/phonehub/phone_hub_tray.cc b/ash/system/phonehub/phone_hub_tray.cc
index a0af5d8..b8fdff9 100644
--- a/ash/system/phonehub/phone_hub_tray.cc
+++ b/ash/system/phonehub/phone_hub_tray.cc
@@ -371,6 +371,12 @@
     phone_status_view_dont_use_ = nullptr;
   }
 
+  if (features::IsEcheSWAEnabled() && features::IsEcheLauncherEnabled() &&
+      phone_hub_manager_->GetAppStreamLauncherDataModel()) {
+    phone_hub_manager_->GetAppStreamLauncherDataModel()
+        ->SetShouldShowMiniLauncher(false);
+  }
+
   bubble_.reset();
   SetIsActive(false);
   shelf()->UpdateAutoHideState();
diff --git a/ash/system/phonehub/phone_hub_tray_unittest.cc b/ash/system/phonehub/phone_hub_tray_unittest.cc
index f574254..c7096f29 100644
--- a/ash/system/phonehub/phone_hub_tray_unittest.cc
+++ b/ash/system/phonehub/phone_hub_tray_unittest.cc
@@ -97,6 +97,10 @@
     return phone_hub_manager_.fake_onboarding_ui_tracker();
   }
 
+  phonehub::AppStreamLauncherDataModel* GetAppStreamLauncherDataModel() {
+    return phone_hub_manager_.fake_app_stream_launcher_data_model();
+  }
+
   void PressReturnKeyOnTrayButton() {
     const ui::KeyEvent key_event(ui::ET_KEY_PRESSED, ui::VKEY_RETURN,
                                  ui::EF_NONE);
@@ -614,6 +618,34 @@
   EXPECT_FALSE(GetOnboardingUiTracker()->ShouldShowOnboardingUi());
 }
 
+TEST_F(PhoneHubTrayTest, ShouldNotShowMiniLauncherOnCloseBubble) {
+  GetFeatureStatusProvider()->SetStatus(
+      phonehub::FeatureStatus::kEnabledAndConnected);
+
+  ClickTrayButton();
+  EXPECT_TRUE(phone_hub_tray_->is_active());
+
+  // Simulate showing the app stream mini launcher
+  GetAppStreamLauncherDataModel()->SetShouldShowMiniLauncher(true);
+  EXPECT_TRUE(GetAppStreamLauncherDataModel()->GetShouldShowMiniLauncher());
+
+  // Simulate a click outside the bubble.
+  phone_hub_tray_->ClickedOutsideBubble();
+
+  // Clicking outside should dismiss the bubble and should not show the app
+  // stream mini launcher.
+  EXPECT_FALSE(phone_hub_tray_->GetBubbleView());
+  EXPECT_TRUE(phone_hub_tray_->GetVisible());
+  EXPECT_FALSE(GetAppStreamLauncherDataModel()->GetShouldShowMiniLauncher());
+
+  // Opening the bubble again should still have the app stream mini launcher
+  // not shown.
+  ClickTrayButton();
+  EXPECT_TRUE(phone_hub_tray_->GetBubbleView());
+  EXPECT_TRUE(phone_hub_tray_->GetVisible());
+  EXPECT_FALSE(GetAppStreamLauncherDataModel()->GetShouldShowMiniLauncher());
+}
+
 TEST_F(PhoneHubTrayTest, ClickButtonsOnDisconnectedView) {
   // Simulates a phone disconnected error state to show the disconnected view.
   GetFeatureStatusProvider()->SetStatus(
diff --git a/ash/system/screen_layout_observer.cc b/ash/system/screen_layout_observer.cc
index 0d9b957..e3a77d6 100644
--- a/ash/system/screen_layout_observer.cc
+++ b/ash/system/screen_layout_observer.cc
@@ -8,7 +8,6 @@
 #include <utility>
 #include <vector>
 
-#include "ash/constants/ash_features.h"
 #include "ash/constants/notifier_catalogs.h"
 #include "ash/display/screen_orientation_controller.h"
 #include "ash/public/cpp/notification_utils.h"
diff --git a/ash/system/screen_layout_observer_unittest.cc b/ash/system/screen_layout_observer_unittest.cc
index cb142d4..0a61e81 100644
--- a/ash/system/screen_layout_observer_unittest.cc
+++ b/ash/system/screen_layout_observer_unittest.cc
@@ -6,7 +6,6 @@
 
 #include <string>
 
-#include "ash/constants/ash_features.h"
 #include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
 #include "ash/test/ash_test_base.h"
@@ -16,7 +15,6 @@
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
-#include "base/test/scoped_feature_list.h"
 #include "ui/accessibility/ax_node_data.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/chromeos/devicetype_utils.h"
@@ -63,8 +61,6 @@
 
   bool IsNotificationShown() const;
 
-  base::test::ScopedFeatureList scoped_feature_list_;
-
  private:
   const message_center::Notification* GetDisplayNotification() const;
 };
@@ -260,8 +256,6 @@
 }
 
 TEST_F(ScreenLayoutObserverTest, DisplayNotificationsDisabled) {
-  scoped_feature_list_.Reset();
-
   UpdateDisplay("500x400");
   display::SetInternalDisplayIds({display_manager()->first_display_id()});
   EXPECT_TRUE(GetDisplayNotificationText().empty());
diff --git a/ash/system/time/calendar_event_list_view.cc b/ash/system/time/calendar_event_list_view.cc
index 4543953..a4500e9 100644
--- a/ash/system/time/calendar_event_list_view.cc
+++ b/ash/system/time/calendar_event_list_view.cc
@@ -47,11 +47,8 @@
 constexpr auto kContentInsets = gfx::Insets::TLBR(0, 0, 20, 0);
 constexpr auto kContentInsetsJelly = gfx::Insets::TLBR(0, 16, 20, 16);
 
-// The insets for `CalendarEmptyEventListView` label.
-constexpr auto kOpenGoogleCalendarInsets = gfx::Insets::VH(6, 16);
-
 // The insets for `CalendarEmptyEventListView`.
-constexpr auto kOpenGoogleCalendarContainerInsets = gfx::Insets::VH(20, 80);
+constexpr auto kOpenGoogleCalendarContainerInsets = gfx::Insets::VH(20, 60);
 
 // Border thickness for `CalendarEmptyEventListView`.
 constexpr int kOpenGoogleCalendarBorderThickness = 1;
@@ -87,7 +84,6 @@
                    /*icon=*/nullptr),
         controller_(controller) {
     SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_CENTER);
-    label()->SetBorder(views::CreateEmptyBorder(kOpenGoogleCalendarInsets));
     label()->SetTextContext(CONTEXT_CALENDAR_DATE);
     SetBorder(views::CreateRoundedRectBorder(
         kOpenGoogleCalendarBorderThickness, GetPreferredSize().height() / 2,
diff --git a/ash/webui/common/resources/network/apn_list.d.ts b/ash/webui/common/resources/network/apn_list.d.ts
index 983fe59..93b4d72 100644
--- a/ash/webui/common/resources/network/apn_list.d.ts
+++ b/ash/webui/common/resources/network/apn_list.d.ts
@@ -2,4 +2,42 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-export {};
+import {PolymerElement} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {ManagedCellularProperties} from 'chrome://resources/mojo/chromeos/services/network_config/public/mojom/cros_network_config.mojom-webui.js';
+
+export class ApnList extends PolymerElement {
+  static get is(): string;
+  static get template(): HTMLTemplateElement;
+  static get properties(): {
+    guid: StringConstructor,
+    managedCellularProperties: ManagedCellularProperties,
+    shouldOmitLinks: {
+      type: BooleanConstructor,
+      value: boolean,
+    },
+    shouldShowApnDetailDialog_: {
+      type: BooleanConstructor,
+      value: boolean,
+    },
+    isConnectedApnAutoDetected_: {
+      type: BooleanConstructor,
+      value: boolean,
+    },
+  };
+  openApnDetailDialogInCreateMode(): void;
+  private getApns_;
+  private isConnectedApnAutoDetected_: boolean;
+  private isApnConnected_;
+  private isApnAutoDetected_;
+  private onLearnMoreClicked_;
+  private onShowApnDetailDialog_;
+  private showApnDetailDialog_;
+  private shouldShowApnDetailDialog_: boolean;
+  private onApnDetailDialogClose_;
+}
+
+declare global {
+  interface HTMLElementTagNameMap {
+    'apn-list': ApnList;
+  }
+}
diff --git a/ash/webui/diagnostics_ui/resources/.eslintrc.js b/ash/webui/diagnostics_ui/resources/.eslintrc.js
new file mode 100644
index 0000000..4d830da
--- /dev/null
+++ b/ash/webui/diagnostics_ui/resources/.eslintrc.js
@@ -0,0 +1,35 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Disable clang-format because it produces odd formatting.
+// clang-format off
+module.exports = {
+    'parserOptions': {
+      'project': ['./tsconfig_base.json'],
+    },
+    'rules': {
+      '@typescript-eslint/explicit-function-return-type': ['error'],
+    },
+    'overrides': [{
+      'files': ['**/*.ts'],
+      'parser':
+        '../../../../third_party/node/node_modules/@typescript-eslint/parser',
+      'parserOptions': {
+        tsconfigRootDir: __dirname,
+      },
+      'plugins': ['@typescript-eslint'],
+      'rules': {
+        '@typescript-eslint/naming-convention':
+          ['warn',
+            {
+              selector: ['classMethod', 'classProperty'],
+              format: ['camelCase'],
+              modifiers: ['private'],
+              trailingUnderscore: 'forbid',
+            },
+          ],
+      },
+    }],
+  };
+// clang-format on
\ No newline at end of file
diff --git a/ash/webui/diagnostics_ui/resources/battery_status_card.ts b/ash/webui/diagnostics_ui/resources/battery_status_card.ts
index d8a4a14..8a41d62 100644
--- a/ash/webui/diagnostics_ui/resources/battery_status_card.ts
+++ b/ash/webui/diagnostics_ui/resources/battery_status_card.ts
@@ -15,6 +15,7 @@
 import {loadTimeData} from 'chrome://resources/ash/common/load_time_data.m.js';
 import {I18nMixin} from 'chrome://resources/cr_elements/i18n_mixin.js';
 import {assertNotReached} from 'chrome://resources/js/assert_ts.js';
+import {PolymerElementProperties} from 'chrome://resources/polymer/v3_0/polymer/interfaces.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {getTemplate} from './battery_status_card.html.js';
@@ -51,15 +52,15 @@
 const BatteryStatusCardElementBase = I18nMixin(PolymerElement);
 
 export class BatteryStatusCardElement extends BatteryStatusCardElementBase {
-  static get is() {
+  static get is(): string {
     return 'battery-status-card';
   }
 
-  static get template() {
+  static get template(): HTMLTemplateElement {
     return getTemplate();
   }
 
-  static get properties() {
+  static get properties(): PolymerElementProperties {
     return {
       batteryChargeStatus_: {
         type: Object,
@@ -133,7 +134,7 @@
     this.observeBatteryHealth_();
   }
 
-  override disconnectedCallback() {
+  override disconnectedCallback(): void {
     super.disconnectedCallback();
 
     if (this.batteryChargeStatusObserverReceiver_) {
diff --git a/ash/webui/diagnostics_ui/resources/cellular_info.ts b/ash/webui/diagnostics_ui/resources/cellular_info.ts
index 08d498e..4023da79 100644
--- a/ash/webui/diagnostics_ui/resources/cellular_info.ts
+++ b/ash/webui/diagnostics_ui/resources/cellular_info.ts
@@ -7,6 +7,7 @@
 
 import {I18nMixin} from 'chrome://resources/cr_elements/i18n_mixin.js';
 import {assert, assertNotReached} from 'chrome://resources/js/assert_ts.js';
+import {PolymerElementProperties} from 'chrome://resources/polymer/v3_0/polymer/interfaces.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {getTemplate} from './cellular_info.html.js';
@@ -22,15 +23,15 @@
 const CellularInfoElementBase = I18nMixin(PolymerElement);
 
 export class CellularInfoElement extends CellularInfoElementBase {
-  static get is() {
+  static get is(): string {
     return 'cellular-info';
   }
 
-  static get template() {
+  static get template(): HTMLTemplateElement {
     return getTemplate();
   }
 
-  static get properties() {
+  static get properties(): PolymerElementProperties {
     return {
       network: {
         type: Object,
diff --git a/ash/webui/diagnostics_ui/resources/connectivity_card.ts b/ash/webui/diagnostics_ui/resources/connectivity_card.ts
index acf1ea5..6d4bdae9 100644
--- a/ash/webui/diagnostics_ui/resources/connectivity_card.ts
+++ b/ash/webui/diagnostics_ui/resources/connectivity_card.ts
@@ -11,6 +11,7 @@
 
 import {I18nMixin} from 'chrome://resources/cr_elements/i18n_mixin.js';
 import {assert} from 'chrome://resources/js/assert_ts.js';
+import {PolymerElementProperties} from 'chrome://resources/polymer/v3_0/polymer/interfaces.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {getTemplate} from './connectivity_card.html.js';
@@ -29,15 +30,15 @@
 const ConnectivityCardElementBase = I18nMixin(PolymerElement);
 
 export class ConnectivityCardElement extends ConnectivityCardElementBase {
-  static get is() {
+  static get is(): string {
     return 'connectivity-card';
   }
 
-  static get template() {
+  static get template(): HTMLTemplateElement {
     return getTemplate();
   }
 
-  static get properties() {
+  static get properties(): PolymerElementProperties {
     return {
       testSuiteStatus: {
         type: Number,
@@ -102,7 +103,7 @@
     return routineSection;
   }
 
-  override disconnectedCallback() {
+  override disconnectedCallback(): void {
     super.disconnectedCallback();
 
     this.getRoutineSectionElem_().stopTests();
diff --git a/ash/webui/diagnostics_ui/resources/cpu_card.ts b/ash/webui/diagnostics_ui/resources/cpu_card.ts
index 2fbc810..64c051ef0 100644
--- a/ash/webui/diagnostics_ui/resources/cpu_card.ts
+++ b/ash/webui/diagnostics_ui/resources/cpu_card.ts
@@ -14,6 +14,7 @@
 
 import {loadTimeData} from 'chrome://resources/ash/common/load_time_data.m.js';
 import {I18nMixin} from 'chrome://resources/cr_elements/i18n_mixin.js';
+import {PolymerElementProperties} from 'chrome://resources/polymer/v3_0/polymer/interfaces.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {getTemplate} from './cpu_card.html.js';
@@ -30,26 +31,24 @@
 const CpuCardElementBase = I18nMixin(PolymerElement);
 
 export class CpuCardElement extends CpuCardElementBase {
-  static get is() {
+  static get is(): string {
     return 'cpu-card';
   }
 
-  static get template() {
+  static get template(): HTMLTemplateElement {
     return getTemplate();
   }
 
-  static get properties() {
+  static get properties(): PolymerElementProperties {
     return {
       routines_: {
         type: Array,
-        value: () => {
-          return [
-            RoutineType.kCpuStress,
-            RoutineType.kCpuCache,
-            RoutineType.kCpuFloatingPoint,
-            RoutineType.kCpuPrime,
-          ];
-        },
+        value: () =>
+            [RoutineType.kCpuStress,
+             RoutineType.kCpuCache,
+             RoutineType.kCpuFloatingPoint,
+             RoutineType.kCpuPrime,
+    ],
       },
 
       cpuUsage_: {
@@ -89,7 +88,7 @@
     this.fetchSystemInfo_();
   }
 
-  override disconnectedCallback() {
+  override disconnectedCallback(): void {
     super.disconnectedCallback();
 
     if (this.cpuUsageObserverReceiver_) {
diff --git a/ash/webui/diagnostics_ui/resources/data_point.ts b/ash/webui/diagnostics_ui/resources/data_point.ts
index 6461218..0616cb7 100644
--- a/ash/webui/diagnostics_ui/resources/data_point.ts
+++ b/ash/webui/diagnostics_ui/resources/data_point.ts
@@ -7,6 +7,7 @@
 import './diagnostics_shared.css.js';
 import './icons.html.js';
 
+import {PolymerElementProperties} from 'chrome://resources/polymer/v3_0/polymer/interfaces.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {getTemplate} from './data_point.html.js';
@@ -19,15 +20,15 @@
  */
 
 export class DataPointElement extends PolymerElement {
-  static get is() {
+  static get is(): string {
     return 'data-point';
   }
 
-  static get template() {
+  static get template(): HTMLTemplateElement {
     return getTemplate();
   }
 
-  static get properties() {
+  static get properties(): PolymerElementProperties {
     return {
       header: {
         type: String,
diff --git a/ash/webui/diagnostics_ui/resources/diagnostics_app.ts b/ash/webui/diagnostics_ui/resources/diagnostics_app.ts
index 46df49e..022aee8 100644
--- a/ash/webui/diagnostics_ui/resources/diagnostics_app.ts
+++ b/ash/webui/diagnostics_ui/resources/diagnostics_app.ts
@@ -19,6 +19,7 @@
 import {CrToastElement} from 'chrome://resources/cr_elements/cr_toast/cr_toast.js';
 import {I18nMixin} from 'chrome://resources/cr_elements/i18n_mixin.js';
 import {assert} from 'chrome://resources/js/assert_ts.js';
+import {PolymerElementProperties} from 'chrome://resources/polymer/v3_0/polymer/interfaces.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {getTemplate} from './diagnostics_app.html.js';
@@ -59,15 +60,15 @@
 const DiagnosticsAppElementBase = I18nMixin(PolymerElement);
 
 export class DiagnosticsAppElement extends DiagnosticsAppElementBase {
-  static get is() {
+  static get is(): string {
     return 'diagnostics-app';
   }
 
-  static get template() {
+  static get template(): HTMLTemplateElement {
     return getTemplate();
   }
 
-  static get properties() {
+  static get properties(): PolymerElementProperties {
     return {
       /**
        * Used in navigation-view-panel to set show-banner when banner is
@@ -130,7 +131,7 @@
    * will contain message to display on message property of event found on
    * event found on path `e.detail.message`.
    */
-  private showToastHandler = (e: ShowToastEvent) => {
+  private showToastHandler = (e: ShowToastEvent): void => {
     assert(e.detail.message);
     this.toastText_ = e.detail.message;
     this.$.toast.show();
@@ -203,14 +204,14 @@
     this.$.navigationPanel.addSelectors(await this.getNavPages());
   }
 
-  override connectedCallback() {
+  override connectedCallback(): void {
     super.connectedCallback();
     this.createNavigationPanel();
     window.addEventListener(
         'show-toast', (e) => this.showToastHandler((e as ShowToastEvent)));
   }
 
-  override disconnectedCallback() {
+  override disconnectedCallback(): void {
     super.disconnectedCallback();
     window.removeEventListener(
         'show-toast', (e) => this.showToastHandler((e as ShowToastEvent)));
diff --git a/ash/webui/diagnostics_ui/resources/diagnostics_browser_proxy.ts b/ash/webui/diagnostics_ui/resources/diagnostics_browser_proxy.ts
index 4d2fb7d..678c4181 100644
--- a/ash/webui/diagnostics_ui/resources/diagnostics_browser_proxy.ts
+++ b/ash/webui/diagnostics_ui/resources/diagnostics_browser_proxy.ts
@@ -70,7 +70,7 @@
     return browserProxy || (browserProxy = new DiagnosticsBrowserProxyImpl());
   }
 
-  static setInstance(obj: DiagnosticsBrowserProxyImpl) {
+  static setInstance(obj: DiagnosticsBrowserProxyImpl): void {
     browserProxy = obj;
   }
 }
diff --git a/ash/webui/diagnostics_ui/resources/diagnostics_card.ts b/ash/webui/diagnostics_ui/resources/diagnostics_card.ts
index c8d0d37b..d9b3cec 100644
--- a/ash/webui/diagnostics_ui/resources/diagnostics_card.ts
+++ b/ash/webui/diagnostics_ui/resources/diagnostics_card.ts
@@ -5,6 +5,7 @@
 import './diagnostics_card_frame.js';
 import './diagnostics_shared.css.js';
 
+import {PolymerElementProperties} from 'chrome://resources/polymer/v3_0/polymer/interfaces.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {getTemplate} from './diagnostics_card.html.js';
@@ -16,15 +17,15 @@
  */
 
 export class DiagnosticsCardElement extends PolymerElement {
-  static get is() {
+  static get is(): string {
     return 'diagnostics-card';
   }
 
-  static get template() {
+  static get template(): HTMLTemplateElement {
     return getTemplate();
   }
 
-  static get properties() {
+  static get properties(): PolymerElementProperties {
     return {
       hideDataPoints: {
         type: Boolean,
diff --git a/ash/webui/diagnostics_ui/resources/diagnostics_card_frame.ts b/ash/webui/diagnostics_ui/resources/diagnostics_card_frame.ts
index 5baa66b..4bcda2e4 100644
--- a/ash/webui/diagnostics_ui/resources/diagnostics_card_frame.ts
+++ b/ash/webui/diagnostics_ui/resources/diagnostics_card_frame.ts
@@ -15,11 +15,11 @@
  */
 
 class DiagnosticsCardFrameElement extends PolymerElement {
-  static get is() {
+  static get is(): string {
     return 'diagnostics-card-frame';
   }
 
-  static get template() {
+  static get template(): HTMLTemplateElement {
     return getTemplate();
   }
 }
diff --git a/ash/webui/diagnostics_ui/resources/diagnostics_network_icon.ts b/ash/webui/diagnostics_ui/resources/diagnostics_network_icon.ts
index 957408f..497b2e05 100644
--- a/ash/webui/diagnostics_ui/resources/diagnostics_network_icon.ts
+++ b/ash/webui/diagnostics_ui/resources/diagnostics_network_icon.ts
@@ -17,6 +17,7 @@
 import {assert, assertNotReached} from 'chrome://resources/js/assert_ts.js';
 import {CellularStateProperties, NetworkStateProperties, SecurityType as MojomSecurityType, WiFiStateProperties} from 'chrome://resources/mojo/chromeos/services/network_config/public/mojom/cros_network_config.mojom-webui.js';
 import {ConnectionStateType as MojomConnectionStateType, NetworkType as MojomNetworkType} from 'chrome://resources/mojo/chromeos/services/network_config/public/mojom/network_types.mojom-webui.js';
+import {PolymerElementProperties} from 'chrome://resources/polymer/v3_0/polymer/interfaces.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {getTemplate} from './diagnostics_network_icon.html.js';
@@ -215,15 +216,15 @@
 const DiagnosticsNetworkIconBase = I18nMixin(PolymerElement);
 
 export class DiagnosticsNetworkIconElement extends DiagnosticsNetworkIconBase {
-  static get is() {
+  static get is(): string {
     return 'diagnostics-network-icon';
   }
 
-  static get template() {
+  static get template(): HTMLTemplateElement {
     return getTemplate();
   }
 
-  static get properties() {
+  static get properties(): PolymerElementProperties {
     return {
       network: {
         type: Object,
diff --git a/ash/webui/diagnostics_ui/resources/diagnostics_sticky_banner.ts b/ash/webui/diagnostics_ui/resources/diagnostics_sticky_banner.ts
index 3278478..9f8fd5f 100644
--- a/ash/webui/diagnostics_ui/resources/diagnostics_sticky_banner.ts
+++ b/ash/webui/diagnostics_ui/resources/diagnostics_sticky_banner.ts
@@ -7,6 +7,7 @@
 import './diagnostics_shared.css.js';
 
 import {assert} from 'chrome://resources/js/assert_ts.js';
+import {PolymerElementProperties} from 'chrome://resources/polymer/v3_0/polymer/interfaces.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {getTemplate} from './diagnostics_sticky_banner.html.js';
@@ -21,15 +22,15 @@
 }
 
 export class DiagnosticsStickyBannerElement extends PolymerElement {
-  static get is() {
+  static get is(): string {
     return 'diagnostics-sticky-banner';
   }
 
-  static get template() {
+  static get template(): HTMLTemplateElement {
     return getTemplate();
   }
 
-  static get properties() {
+  static get properties(): PolymerElementProperties {
     return {
       bannerMessage: {
         type: String,
@@ -53,7 +54,7 @@
   protected scrollingClass_: string;
   private scrollTimerId_: number;
 
-  override connectedCallback() {
+  override connectedCallback(): void {
     super.connectedCallback();
     window.addEventListener(
         'show-caution-banner',
@@ -63,7 +64,7 @@
     window.addEventListener('scroll', this.scrollClassHandler_);
   }
 
-  override disconnectedCallback() {
+  override disconnectedCallback(): void {
     super.disconnectedCallback();
     window.removeEventListener(
         'show-caution-banner',
@@ -78,7 +79,7 @@
    * section. Event will contain message to display on message property of
    * event found on path `event.detail.message`.
    */
-  private showCautionBannerHandler_ = (e: ShowCautionBannerEvent) => {
+  private showCautionBannerHandler_ = (e: ShowCautionBannerEvent): void => {
     assert(e.detail.message);
     this.bannerMessage = e.detail.message;
   };
@@ -87,14 +88,14 @@
    * Event callback for 'dismiss-caution-banner' which is triggered from
    * routine-section.
    */
-  private dismissCautionBannerHandler_ = () => {
+  private dismissCautionBannerHandler_ = (): void => {
     this.bannerMessage = '';
   };
 
   /**
    * Event callback for 'scroll'.
    */
-  private scrollClassHandler_ = () => {
+  private scrollClassHandler_ = (): void => {
     this.onScroll_();
   };
 
diff --git a/ash/webui/diagnostics_ui/resources/drawing_provider.ts b/ash/webui/diagnostics_ui/resources/drawing_provider.ts
index 79e4b2c..6372beb 100644
--- a/ash/webui/diagnostics_ui/resources/drawing_provider.ts
+++ b/ash/webui/diagnostics_ui/resources/drawing_provider.ts
@@ -47,7 +47,7 @@
     this.setup();
   }
 
-  setup() {
+  setup(): void {
     assert(this.ctx);
     this.ctx.lineCap = LINE_CAP;
     this.ctx.lineWidth = LINE_WIDTH;
diff --git a/ash/webui/diagnostics_ui/resources/ethernet_info.ts b/ash/webui/diagnostics_ui/resources/ethernet_info.ts
index e5add252..e4aec50 100644
--- a/ash/webui/diagnostics_ui/resources/ethernet_info.ts
+++ b/ash/webui/diagnostics_ui/resources/ethernet_info.ts
@@ -6,6 +6,7 @@
 import './diagnostics_shared.css.js';
 
 import {I18nMixin} from 'chrome://resources/cr_elements/i18n_mixin.js';
+import {PolymerElementProperties} from 'chrome://resources/polymer/v3_0/polymer/interfaces.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {getTemplate} from './ethernet_info.html.js';
@@ -20,15 +21,15 @@
 const EthernetInfoElementBase = I18nMixin(PolymerElement);
 
 export class EthernetInfoElement extends EthernetInfoElementBase {
-  static get is() {
+  static get is(): string {
     return 'ethernet-info';
   }
 
-  static get template() {
+  static get template(): HTMLTemplateElement {
     return getTemplate();
   }
 
-  static get properties() {
+  static get properties(): PolymerElementProperties {
     return {
       authentication_: {
         type: String,
@@ -51,7 +52,7 @@
   protected authentication_: string;
   protected ipAddress_: string;
 
-  protected computeAuthentication_() {
+  protected computeAuthentication_(): string {
     if (this.network?.typeProperties?.ethernet) {
       const authentication: AuthenticationType =
           this.network.typeProperties.ethernet.authentication;
diff --git a/ash/webui/diagnostics_ui/resources/input_card.ts b/ash/webui/diagnostics_ui/resources/input_card.ts
index 09297f7..430430f 100644
--- a/ash/webui/diagnostics_ui/resources/input_card.ts
+++ b/ash/webui/diagnostics_ui/resources/input_card.ts
@@ -12,6 +12,7 @@
 import {CrButtonElement} from 'chrome://resources/cr_elements/cr_button/cr_button.js';
 import {I18nMixin} from 'chrome://resources/cr_elements/i18n_mixin.js';
 import {assert} from 'chrome://resources/js/assert_ts.js';
+import {PolymerElementProperties} from 'chrome://resources/polymer/v3_0/polymer/interfaces.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {getTemplate} from './input_card.html.js';
@@ -43,15 +44,15 @@
 const InputCardElementBase = I18nMixin(PolymerElement);
 
 export class InputCardElement extends InputCardElementBase {
-  static get is() {
+  static get is(): string {
     return 'input-card';
   }
 
-  static get template() {
+  static get template(): HTMLTemplateElement {
     return getTemplate();
   }
 
-  static get properties() {
+  static get properties(): PolymerElementProperties {
     return {
       /**
        * The type of input device to be displayed. Valid values are 'keyboard',
diff --git a/ash/webui/diagnostics_ui/resources/input_list.ts b/ash/webui/diagnostics_ui/resources/input_list.ts
index 7c76a51..c7e49df 100644
--- a/ash/webui/diagnostics_ui/resources/input_list.ts
+++ b/ash/webui/diagnostics_ui/resources/input_list.ts
@@ -10,6 +10,7 @@
 import {loadTimeData} from 'chrome://resources/ash/common/load_time_data.m.js';
 import {I18nMixin} from 'chrome://resources/cr_elements/i18n_mixin.js';
 import {assert} from 'chrome://resources/js/assert_ts.js';
+import {PolymerElementProperties} from 'chrome://resources/polymer/v3_0/polymer/interfaces.js';
 import {afterNextRender, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {DiagnosticsBrowserProxy, DiagnosticsBrowserProxyImpl} from './diagnostics_browser_proxy.js';
@@ -35,15 +36,15 @@
 }
 
 export class InputListElement extends InputListElementBase {
-  static get is() {
+  static get is(): string {
     return 'input-list';
   }
 
-  static get template() {
+  static get template(): HTMLTemplateElement {
     return getTemplate();
   }
 
-  static get properties() {
+  static get properties(): PolymerElementProperties {
     return {
       keyboards_: {
         type: Array,
@@ -124,7 +125,7 @@
     this.observeTabletMode();
   }
 
-  override connectedCallback() {
+  override connectedCallback(): void {
     super.connectedCallback();
     const keyboardTester = this.shadowRoot!.querySelector('keyboard-tester');
     assert(keyboardTester);
@@ -215,7 +216,7 @@
    */
 
   private removeDeviceById_(
-      path: 'keyboards_'|'touchpads_'|'touchscreens_', id: number) {
+      path: 'keyboards_'|'touchpads_'|'touchscreens_', id: number): void {
     const index = this.get(path).findIndex(
         (device: KeyboardInfo|TouchDeviceInfo) => device.id === id);
     if (index !== -1) {
diff --git a/ash/webui/diagnostics_ui/resources/ip_config_info_drawer.ts b/ash/webui/diagnostics_ui/resources/ip_config_info_drawer.ts
index 25c6f86..a33fd28 100644
--- a/ash/webui/diagnostics_ui/resources/ip_config_info_drawer.ts
+++ b/ash/webui/diagnostics_ui/resources/ip_config_info_drawer.ts
@@ -8,6 +8,7 @@
 
 import {loadTimeData} from 'chrome://resources/ash/common/load_time_data.m.js';
 import {I18nMixin} from 'chrome://resources/cr_elements/i18n_mixin.js';
+import {PolymerElementProperties} from 'chrome://resources/polymer/v3_0/polymer/interfaces.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {DiagnosticsBrowserProxyImpl} from './diagnostics_browser_proxy.js';
@@ -24,15 +25,15 @@
 const IpConfigInfoDrawerElementBase = I18nMixin(PolymerElement);
 
 export class IpConfigInfoDrawerElement extends IpConfigInfoDrawerElementBase {
-  static get is() {
+  static get is(): string {
     return 'ip-config-info-drawer';
   }
 
-  static get template() {
+  static get template(): HTMLTemplateElement {
     return getTemplate();
   }
 
-  static get properties() {
+  static get properties(): PolymerElementProperties {
     return {
       expanded_: {
         type: Boolean,
@@ -74,7 +75,7 @@
   private browserProxy_: DiagnosticsBrowserProxyImpl =
       DiagnosticsBrowserProxyImpl.getInstance();
 
-  static get observers() {
+  static get observers(): string[] {
     return ['getNameServersHeader_(network.ipConfig.nameServers)'];
   }
 
diff --git a/ash/webui/diagnostics_ui/resources/keyboard_tester.ts b/ash/webui/diagnostics_ui/resources/keyboard_tester.ts
index df8aab5..c59ed30a 100644
--- a/ash/webui/diagnostics_ui/resources/keyboard_tester.ts
+++ b/ash/webui/diagnostics_ui/resources/keyboard_tester.ts
@@ -17,6 +17,7 @@
 import {I18nMixin} from 'chrome://resources/cr_elements/i18n_mixin.js';
 import {assert} from 'chrome://resources/js/assert_ts.js';
 import {EventTracker} from 'chrome://resources/js/event_tracker.js';
+import {PolymerElementProperties} from 'chrome://resources/polymer/v3_0/polymer/interfaces.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {InputDataProviderInterface, KeyboardInfo, KeyboardObserverReceiver, KeyEvent, KeyEventType, MechanicalLayout, NumberPadPresence, PhysicalLayout, TopRightKey, TopRowKey} from './input_data_provider.mojom-webui.js';
@@ -124,15 +125,15 @@
 const KeyboardTesterElementBase = I18nMixin(PolymerElement);
 
 export class KeyboardTesterElement extends KeyboardTesterElementBase {
-  static get is() {
+  static get is(): string {
     return 'keyboard-tester';
   }
 
-  static get template() {
+  static get template(): HTMLTemplateElement {
     return getTemplate();
   }
 
-  static get properties() {
+  static get properties(): PolymerElementProperties {
     return {
       /**
        * The keyboard being tested, or null if none is being tested at the
@@ -205,7 +206,7 @@
       getInputDataProvider();
   private eventTracker: EventTracker = new EventTracker();
 
-  override disconnectedCallback() {
+  override disconnectedCallback(): void {
     super.disconnectedCallback();
     this.eventTracker.removeAll();
   }
@@ -214,7 +215,7 @@
    * Event callback for 'announce-text' which is triggered from keyboard-key.
    * Event will contain text to announce to screen readers.
    */
-  private announceTextHandler = (e: AnnounceTextEvent) => {
+  private announceTextHandler = (e: AnnounceTextEvent): void => {
     assert(e.detail.text);
     e.stopPropagation();
     getInstance(this.$.dialog.getNative()).announce(e.detail.text);
diff --git a/ash/webui/diagnostics_ui/resources/memory_card.ts b/ash/webui/diagnostics_ui/resources/memory_card.ts
index e89460f..9f2f759 100644
--- a/ash/webui/diagnostics_ui/resources/memory_card.ts
+++ b/ash/webui/diagnostics_ui/resources/memory_card.ts
@@ -14,6 +14,7 @@
 
 import {loadTimeData} from 'chrome://resources/ash/common/load_time_data.m.js';
 import {I18nMixin} from 'chrome://resources/cr_elements/i18n_mixin.js';
+import {PolymerElementProperties} from 'chrome://resources/polymer/v3_0/polymer/interfaces.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {convertKibToGibDecimalString, convertKibToMib} from './diagnostics_utils.js';
@@ -31,15 +32,15 @@
 const MemoryCardElementBase = I18nMixin(PolymerElement);
 
 export class MemoryCardElement extends MemoryCardElementBase {
-  static get is() {
+  static get is(): string {
     return 'memory-card';
   }
 
-  static get template() {
+  static get template(): HTMLTemplateElement {
     return getTemplate();
   }
 
-  static get properties() {
+  static get properties(): PolymerElementProperties {
     return {
       routines_: {
         type: Array,
@@ -75,7 +76,7 @@
     this.observeMemoryUsage_();
   }
 
-  override disconnectedCallback() {
+  override disconnectedCallback(): void {
     super.disconnectedCallback();
     if (this.memoryUsageObserverReceiver_) {
       this.memoryUsageObserverReceiver_.$.close();
diff --git a/ash/webui/diagnostics_ui/resources/network_card.ts b/ash/webui/diagnostics_ui/resources/network_card.ts
index f5c010c..cce80572 100644
--- a/ash/webui/diagnostics_ui/resources/network_card.ts
+++ b/ash/webui/diagnostics_ui/resources/network_card.ts
@@ -10,6 +10,7 @@
 import './network_troubleshooting.js';
 
 import {I18nMixin} from 'chrome://resources/cr_elements/i18n_mixin.js';
+import {PolymerElementProperties} from 'chrome://resources/polymer/v3_0/polymer/interfaces.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {TroubleshootingInfo} from './diagnostics_types.js';
@@ -39,15 +40,15 @@
 const NetworkCardElementBase = I18nMixin(PolymerElement);
 
 export class NetworkCardElement extends NetworkCardElementBase {
-  static get is() {
+  static get is(): string {
     return 'network-card';
   }
 
-  static get template() {
+  static get template(): HTMLTemplateElement {
     return getTemplate();
   }
 
-  static get properties() {
+  static get properties(): PolymerElementProperties {
     return {
       guid: {
         type: String,
@@ -130,11 +131,11 @@
   private networkStateObserverReceiver_: NetworkStateObserverReceiver|null =
       null;
 
-  static get observers() {
+  static get observers(): string[] {
     return ['observeNetwork_(guid)'];
   }
 
-  override disconnectedCallback() {
+  override disconnectedCallback(): void {
     super.disconnectedCallback();
 
     this.resetTimer_();
diff --git a/ash/webui/diagnostics_ui/resources/network_info.ts b/ash/webui/diagnostics_ui/resources/network_info.ts
index c09ca25..f15b438 100644
--- a/ash/webui/diagnostics_ui/resources/network_info.ts
+++ b/ash/webui/diagnostics_ui/resources/network_info.ts
@@ -7,6 +7,7 @@
 import './ethernet_info.js';
 import './wifi_info.js';
 
+import {PolymerElementProperties} from 'chrome://resources/polymer/v3_0/polymer/interfaces.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {Network, NetworkType} from './network_health_provider.mojom-webui.js';
@@ -19,15 +20,15 @@
  */
 
 export class NetworkInfoElement extends PolymerElement {
-  static get is() {
+  static get is(): string {
     return 'network-info';
   }
 
-  static get template() {
+  static get template(): HTMLTemplateElement {
     return getTemplate();
   }
 
-  static get properties() {
+  static get properties(): PolymerElementProperties {
     return {
       /** @type {!Network} */
       network: {
diff --git a/ash/webui/diagnostics_ui/resources/network_list.ts b/ash/webui/diagnostics_ui/resources/network_list.ts
index ef01dfc..bb55f940 100644
--- a/ash/webui/diagnostics_ui/resources/network_list.ts
+++ b/ash/webui/diagnostics_ui/resources/network_list.ts
@@ -10,6 +10,7 @@
 import {loadTimeData} from 'chrome://resources/ash/common/load_time_data.m.js';
 import {I18nMixin} from 'chrome://resources/cr_elements/i18n_mixin.js';
 import {assert} from 'chrome://resources/js/assert_ts.js';
+import {PolymerElementProperties} from 'chrome://resources/polymer/v3_0/polymer/interfaces.js';
 import {afterNextRender, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {ConnectivityCardElement} from './connectivity_card.js';
@@ -35,15 +36,15 @@
 const NetworkListElementBase = I18nMixin(PolymerElement);
 
 export class NetworkListElement extends NetworkListElementBase {
-  static get is() {
+  static get is(): string {
     return 'network-list';
   }
 
-  static get template() {
+  static get template(): HTMLTemplateElement {
     return getTemplate();
   }
 
-  static get properties() {
+  static get properties(): PolymerElementProperties {
     return {
       testSuiteStatus: {
         type: Number,
@@ -90,7 +91,7 @@
     this.observeNetworkList_();
   }
 
-  override disconnectedCallback() {
+  override disconnectedCallback(): void {
     super.disconnectedCallback();
 
     if (this.networkListObserverReceiver_) {
diff --git a/ash/webui/diagnostics_ui/resources/network_troubleshooting.ts b/ash/webui/diagnostics_ui/resources/network_troubleshooting.ts
index 279d71b..f8f95d35 100644
--- a/ash/webui/diagnostics_ui/resources/network_troubleshooting.ts
+++ b/ash/webui/diagnostics_ui/resources/network_troubleshooting.ts
@@ -6,6 +6,7 @@
 
 import {loadTimeData} from 'chrome://resources/ash/common/load_time_data.m.js';
 import {I18nMixin} from 'chrome://resources/cr_elements/i18n_mixin.js';
+import {PolymerElementProperties} from 'chrome://resources/polymer/v3_0/polymer/interfaces.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {TroubleshootingInfo} from './diagnostics_types.js';
@@ -15,15 +16,15 @@
 
 export class NetworkTroubleshootingElement extends
     NetworkTroubleshootingElementBase {
-  static get is() {
+  static get is(): string {
     return 'network-troubleshooting';
   }
 
-  static get template() {
+  static get template(): HTMLTemplateElement {
     return getTemplate();
   }
 
-  static get properties() {
+  static get properties(): PolymerElementProperties {
     return {
       isLoggedIn_: {
         type: Boolean,
diff --git a/ash/webui/diagnostics_ui/resources/overview_card.ts b/ash/webui/diagnostics_ui/resources/overview_card.ts
index d54aae6..500e410 100644
--- a/ash/webui/diagnostics_ui/resources/overview_card.ts
+++ b/ash/webui/diagnostics_ui/resources/overview_card.ts
@@ -5,6 +5,7 @@
 import './diagnostics_shared.css.js';
 
 import {loadTimeData} from 'chrome://resources/ash/common/load_time_data.m.js';
+import {PolymerElementProperties} from 'chrome://resources/polymer/v3_0/polymer/interfaces.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {getSystemDataProvider} from './mojo_interface_provider.js';
@@ -18,15 +19,15 @@
  */
 
 export class OverviewCardElement extends PolymerElement {
-  static get is() {
+  static get is(): string {
     return 'overview-card';
   }
 
-  static get template() {
+  static get template(): HTMLTemplateElement {
     return getTemplate();
   }
 
-  static get properties() {
+  static get properties(): PolymerElementProperties {
     return {
       systemInfo_: {
         type: Object,
diff --git a/ash/webui/diagnostics_ui/resources/percent_bar_chart.ts b/ash/webui/diagnostics_ui/resources/percent_bar_chart.ts
index 1e2b9d4..139d741 100644
--- a/ash/webui/diagnostics_ui/resources/percent_bar_chart.ts
+++ b/ash/webui/diagnostics_ui/resources/percent_bar_chart.ts
@@ -8,6 +8,7 @@
 import './diagnostics_shared.css.js';
 import './strings.m.js';
 
+import {PolymerElementProperties} from 'chrome://resources/polymer/v3_0/polymer/interfaces.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {getTemplate} from './percent_bar_chart.html.js';
@@ -19,15 +20,15 @@
  */
 
 export class PercentBarChartElement extends PolymerElement {
-  static get is() {
+  static get is(): string {
     return 'percent-bar-chart';
   }
 
-  static get template() {
+  static get template(): HTMLTemplateElement {
     return getTemplate();
   }
 
-  static get properties() {
+  static get properties(): PolymerElementProperties {
     return {
       header: {
         type: String,
diff --git a/ash/webui/diagnostics_ui/resources/routine_result_entry.ts b/ash/webui/diagnostics_ui/resources/routine_result_entry.ts
index 9ef2b8d..909b045b 100644
--- a/ash/webui/diagnostics_ui/resources/routine_result_entry.ts
+++ b/ash/webui/diagnostics_ui/resources/routine_result_entry.ts
@@ -9,6 +9,7 @@
 import {loadTimeData} from 'chrome://resources/ash/common/load_time_data.m.js';
 import {assert, assertNotReached} from 'chrome://resources/js/assert_ts.js';
 import {IronA11yAnnouncer} from 'chrome://resources/polymer/v3_0/iron-a11y-announcer/iron-a11y-announcer.js';
+import {PolymerElementProperties} from 'chrome://resources/polymer/v3_0/polymer/interfaces.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {getRoutineFailureMessage} from './diagnostics_utils.js';
@@ -92,15 +93,15 @@
  */
 
 export class RoutineResultEntryElement extends PolymerElement {
-  static get is() {
+  static get is(): string {
     return 'routine-result-entry';
   }
 
-  static get template() {
+  static get template(): HTMLTemplateElement {
     return getTemplate();
   }
 
-  static get properties() {
+  static get properties(): PolymerElementProperties {
     return {
       /**
        * Added to support testing of announce behavior.
@@ -157,11 +158,11 @@
   private routineType_: string;
 
 
-  static get observers() {
+  static get observers(): string[] {
     return ['entryStatusChanged_(item.*)'];
   }
 
-  override connectedCallback() {
+  override connectedCallback(): void {
     super.connectedCallback();
 
     IronA11yAnnouncer.requestAvailability();
diff --git a/ash/webui/diagnostics_ui/resources/routine_result_list.ts b/ash/webui/diagnostics_ui/resources/routine_result_list.ts
index a4a1145..9a22d343 100644
--- a/ash/webui/diagnostics_ui/resources/routine_result_list.ts
+++ b/ash/webui/diagnostics_ui/resources/routine_result_list.ts
@@ -7,6 +7,7 @@
 import './routine_result_entry.js';
 
 import {assert} from 'chrome://resources/js/assert_ts.js';
+import {PolymerElementProperties} from 'chrome://resources/polymer/v3_0/polymer/interfaces.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {RoutineGroup} from './routine_group.js';
@@ -29,15 +30,15 @@
  */
 
 export class RoutineResultListElement extends PolymerElement {
-  static get is() {
+  static get is(): string {
     return 'routine-result-list';
   }
 
-  static get template() {
+  static get template(): HTMLTemplateElement {
     return getTemplate();
   }
 
-  static get properties() {
+  static get properties(): PolymerElementProperties {
     return {
       results_: {
         type: Array,
diff --git a/ash/webui/diagnostics_ui/resources/routine_section.ts b/ash/webui/diagnostics_ui/resources/routine_section.ts
index 0462d66..91923fa 100644
--- a/ash/webui/diagnostics_ui/resources/routine_section.ts
+++ b/ash/webui/diagnostics_ui/resources/routine_section.ts
@@ -17,6 +17,7 @@
 import {assert, assertNotReached} from 'chrome://resources/js/assert_ts.js';
 import {IronA11yAnnouncer} from 'chrome://resources/polymer/v3_0/iron-a11y-announcer/iron-a11y-announcer.js';
 import {IronCollapseElement} from 'chrome://resources/polymer/v3_0/iron-collapse/iron-collapse.js';
+import {PolymerElementProperties} from 'chrome://resources/polymer/v3_0/polymer/interfaces.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {getSystemRoutineController} from './mojo_interface_provider.js';
@@ -46,15 +47,15 @@
 const RoutineSectionElementBase = I18nMixin(PolymerElement);
 
 export class RoutineSectionElement extends RoutineSectionElementBase {
-  static get is() {
+  static get is(): string {
     return 'routine-section';
   }
 
-  static get template() {
+  static get template(): HTMLTemplateElement {
     return getTemplate();
   }
 
-  static get properties() {
+  static get properties(): PolymerElementProperties {
     return {
       /**
        * Added to support testing of announce behavior.
@@ -226,7 +227,7 @@
   private systemRoutineController_: SystemRoutineControllerInterface|null =
       null;
 
-  static get observers() {
+  static get observers(): string[] {
     return [
       'routineStatusChanged_(executionStatus_, currentTestName_,' +
           'additionalMessage)',
@@ -235,7 +236,7 @@
     ];
   }
 
-  override connectedCallback() {
+  override connectedCallback(): void {
     super.connectedCallback();
 
     IronA11yAnnouncer.requestAvailability();
@@ -602,7 +603,7 @@
     return !this.isLoggedIn_ || this.hideRoutineStatus;
   }
 
-  override disconnectedCallback() {
+  override disconnectedCallback(): void {
     super.disconnectedCallback();
 
     this.cleanUp_();
diff --git a/ash/webui/diagnostics_ui/resources/system_page.ts b/ash/webui/diagnostics_ui/resources/system_page.ts
index 12f68e1..1c177363 100644
--- a/ash/webui/diagnostics_ui/resources/system_page.ts
+++ b/ash/webui/diagnostics_ui/resources/system_page.ts
@@ -17,6 +17,7 @@
 import {CrToastElement} from 'chrome://resources/cr_elements/cr_toast/cr_toast.js';
 import {I18nMixin} from 'chrome://resources/cr_elements/i18n_mixin.js';
 import {assert} from 'chrome://resources/js/assert_ts.js';
+import {PolymerElementProperties} from 'chrome://resources/polymer/v3_0/polymer/interfaces.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {DiagnosticsBrowserProxyImpl} from './diagnostics_browser_proxy.js';
@@ -41,15 +42,15 @@
 const SystemPageElementBase = I18nMixin(PolymerElement);
 
 export class SystemPageElement extends SystemPageElementBase {
-  static get is() {
+  static get is(): string {
     return 'system-page';
   }
 
-  static get template() {
+  static get template(): HTMLTemplateElement {
     return getTemplate();
   }
 
-  static get properties() {
+  static get properties(): PolymerElementProperties {
     return {
       saveSessionLogEnabled_: {
         type: Boolean,
diff --git a/ash/webui/diagnostics_ui/resources/text_badge.ts b/ash/webui/diagnostics_ui/resources/text_badge.ts
index c9b29aad..11ff88a 100644
--- a/ash/webui/diagnostics_ui/resources/text_badge.ts
+++ b/ash/webui/diagnostics_ui/resources/text_badge.ts
@@ -4,6 +4,7 @@
 
 import './diagnostics_shared.css.js';
 
+import {PolymerElementProperties} from 'chrome://resources/polymer/v3_0/polymer/interfaces.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {getTemplate} from './text_badge.html.js';
@@ -28,15 +29,15 @@
  */
 
 export class TextBadgeElement extends PolymerElement {
-  static get is() {
+  static get is(): string {
     return 'text-badge';
   }
 
-  static get template() {
+  static get template(): HTMLTemplateElement {
     return getTemplate();
   }
 
-  static get properties() {
+  static get properties(): PolymerElementProperties {
     return {
       badgeType: {
         type: String,
diff --git a/ash/webui/diagnostics_ui/resources/touchpad_tester.ts b/ash/webui/diagnostics_ui/resources/touchpad_tester.ts
index 1166b88..8804a6eed 100644
--- a/ash/webui/diagnostics_ui/resources/touchpad_tester.ts
+++ b/ash/webui/diagnostics_ui/resources/touchpad_tester.ts
@@ -42,7 +42,7 @@
 
 export class TouchpadTesterElement extends TouchpadTesterElementBase implements
     TouchEventObserver {
-  static get is() {
+  static get is(): string {
     return 'touchpad-tester';
   }
 
@@ -58,7 +58,7 @@
   // Touchpad device being tested.
   touchpad: TouchDeviceInfo|null = null;
 
-  override connectedCallback() {
+  override connectedCallback(): void {
     super.connectedCallback();
     const ctx = this.$.testerCanvas.getContext('2d');
     assert(!!ctx);
diff --git a/ash/webui/diagnostics_ui/resources/touchscreen_tester.ts b/ash/webui/diagnostics_ui/resources/touchscreen_tester.ts
index 1d6f3fe..cd4430e 100644
--- a/ash/webui/diagnostics_ui/resources/touchscreen_tester.ts
+++ b/ash/webui/diagnostics_ui/resources/touchscreen_tester.ts
@@ -8,6 +8,7 @@
 import {I18nMixin} from 'chrome://resources/cr_elements/i18n_mixin.js';
 import {assert} from 'chrome://resources/js/assert_ts.js';
 import {EventTracker} from 'chrome://resources/js/event_tracker.js';
+import {PolymerElementProperties} from 'chrome://resources/polymer/v3_0/polymer/interfaces.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {CanvasDrawingProvider} from './drawing_provider.js';
@@ -44,15 +45,15 @@
 const TouchscreenTesterElementBase = I18nMixin(PolymerElement);
 
 export class TouchscreenTesterElement extends TouchscreenTesterElementBase {
-  static get is() {
+  static get is(): string {
     return 'touchscreen-tester';
   }
 
-  static get template() {
+  static get template(): HTMLTemplateElement {
     return getTemplate();
   }
 
-  static get properties() {
+  static get properties(): PolymerElementProperties {
     return {
       touchscreenIdUnderTesting: {
         type: Number,
@@ -299,7 +300,7 @@
    * Implements TabletModeObserver.OnTabletModeChanged.
    * @param isTabletMode Is current display on tablet mode.
    */
-  onTabletModeChanged(isTabletMode: boolean) {
+  onTabletModeChanged(isTabletMode: boolean): void {
     this.isTabletMode = isTabletMode;
     // TODO(wenyu): Show exit instruction toaster.
   }
diff --git a/ash/webui/diagnostics_ui/resources/wifi_info.ts b/ash/webui/diagnostics_ui/resources/wifi_info.ts
index b42cd1d7..20880ff 100644
--- a/ash/webui/diagnostics_ui/resources/wifi_info.ts
+++ b/ash/webui/diagnostics_ui/resources/wifi_info.ts
@@ -8,6 +8,7 @@
 import {loadTimeData} from 'chrome://resources/ash/common/load_time_data.m.js';
 import {I18nMixin} from 'chrome://resources/cr_elements/i18n_mixin.js';
 import {assertNotReached} from 'chrome://resources/js/assert_ts.js';
+import {PolymerElementProperties} from 'chrome://resources/polymer/v3_0/polymer/interfaces.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {getSignalStrength} from './diagnostics_utils.js';
@@ -26,15 +27,15 @@
 
 
 export class WifiInfoElement extends WifiInfoElementBase {
-  static get is() {
+  static get is(): string {
     return 'wifi-info';
   }
 
-  static get template() {
+  static get template(): HTMLTemplateElement {
     return getTemplate();
   }
 
-  static get properties() {
+  static get properties(): PolymerElementProperties {
     return {
       /** @type {!Network} */
       network: {
diff --git a/ash/webui/personalization_app/resources/js/ambient/ambient_observer.ts b/ash/webui/personalization_app/resources/js/ambient/ambient_observer.ts
index 55f2b28..18ffa8e3 100644
--- a/ash/webui/personalization_app/resources/js/ambient/ambient_observer.ts
+++ b/ash/webui/personalization_app/resources/js/ambient/ambient_observer.ts
@@ -5,7 +5,10 @@
 import {Url} from 'chrome://resources/mojo/url/mojom/url.mojom-webui.js';
 
 import {AmbientModeAlbum, AmbientObserverInterface, AmbientObserverReceiver, AmbientProviderInterface, AmbientUiVisibility, AnimationTheme, TemperatureUnit, TopicSource} from '../personalization_app.mojom-webui.js';
+import {logGooglePhotosPreviewsLoadTime} from '../personalization_metrics_logger.js';
+import {Paths} from '../personalization_router_element.js';
 import {PersonalizationStore} from '../personalization_store.js';
+import {isNonEmptyArray} from '../utils.js';
 
 import {setAlbumsAction, setAmbientModeEnabledAction, setAmbientUiVisibilityAction, setAnimationThemeAction, setGooglePhotosAlbumsPreviewsAction, setTemperatureUnitAction, setTopicSourceAction} from './ambient_actions.js';
 import {getAmbientProvider} from './ambient_interface_provider.js';
@@ -19,6 +22,12 @@
  * Observes ambient mode changes and saves updates to PersonalizationStore.
  */
 export class AmbientObserver implements AmbientObserverInterface {
+  // Allow logging first load performance if the user began on a page where
+  // google photos preview images are loaded immediately.
+  static shouldLogGooglePhotosPreviewsLoadPerformance: boolean =
+      window.location.pathname === Paths.ROOT ||
+      window.location.pathname === Paths.AMBIENT;
+
   static initAmbientObserverIfNeeded(): void {
     if (!instance) {
       instance = new AmbientObserver();
@@ -48,6 +57,11 @@
   }
 
   onAmbientModeEnabledChanged(ambientModeEnabled: boolean) {
+    // Only record google photos previews load performance if ambient mode
+    // starts enabled.
+    AmbientObserver.shouldLogGooglePhotosPreviewsLoadPerformance =
+        AmbientObserver.shouldLogGooglePhotosPreviewsLoadPerformance &&
+        ambientModeEnabled;
     const store = PersonalizationStore.getInstance();
     store.dispatch(setAmbientModeEnabledAction(ambientModeEnabled));
   }
@@ -59,6 +73,12 @@
 
   onTopicSourceChanged(topicSource: TopicSource) {
     const store = PersonalizationStore.getInstance();
+    // If the first time receiving `topicSource` and it is already set to
+    // `kGooglePhotos`, allow logging google photos load performance.
+    AmbientObserver.shouldLogGooglePhotosPreviewsLoadPerformance =
+        AmbientObserver.shouldLogGooglePhotosPreviewsLoadPerformance &&
+        store.data.ambient.topicSource === null &&
+        topicSource === TopicSource.kGooglePhotos;
     store.dispatch(setTopicSourceAction(topicSource));
   }
 
@@ -89,7 +109,21 @@
 
   onGooglePhotosAlbumsPreviewsFetched(previews: Url[]) {
     const store = PersonalizationStore.getInstance();
+
+    // Only log performance metrics if this is the first time receiving google
+    // photos previews.
+    AmbientObserver.shouldLogGooglePhotosPreviewsLoadPerformance =
+        AmbientObserver.shouldLogGooglePhotosPreviewsLoadPerformance &&
+        (!store.data.ambient.googlePhotosAlbumsPreviews ||
+         store.data.ambient.googlePhotosAlbumsPreviews.length === 0);
+
     store.dispatch(setGooglePhotosAlbumsPreviewsAction(previews));
+
+    if (AmbientObserver.shouldLogGooglePhotosPreviewsLoadPerformance &&
+        isNonEmptyArray(previews)) {
+      logGooglePhotosPreviewsLoadTime();
+      AmbientObserver.shouldLogGooglePhotosPreviewsLoadPerformance = false;
+    }
   }
 
   onAmbientUiVisibilityChanged(ambientUiVisibility: AmbientUiVisibility) {
diff --git a/ash/webui/personalization_app/resources/js/personalization_metrics_logger.ts b/ash/webui/personalization_app/resources/js/personalization_metrics_logger.ts
index 049d4a2..f1d8993 100644
--- a/ash/webui/personalization_app/resources/js/personalization_metrics_logger.ts
+++ b/ash/webui/personalization_app/resources/js/personalization_metrics_logger.ts
@@ -22,9 +22,12 @@
   MAX_VALUE = USER,
 }
 
-const PersonalizationPathHistogramName: string = 'Ash.Personalization.Path';
-const PersonalizationAmbientModeOptInHistogramName: string =
-    'Ash.Personalization.AmbientMode.OptIn';
+const enum HistogramName {
+  PATH = 'Ash.Personalization.Path',
+  AMBIENT_OPTIN = 'Ash.Personalization.AmbientMode.OptIn',
+  AMBIENT_PERFORMANCE_GOOGLE_PHOTOS_PREVIEWS =
+      'Ash.Personalization.Ambient.GooglePhotosPreviewsLoadTime',
+}
 
 function toMetricsEnum(path: Paths) {
   switch (path) {
@@ -51,10 +54,18 @@
   const metricsPath = toMetricsEnum(path);
   assert(metricsPath <= MetricsPath.MAX_VALUE);
   chrome.metricsPrivate.recordEnumerationValue(
-      PersonalizationPathHistogramName, metricsPath, MetricsPath.MAX_VALUE + 1);
+      HistogramName.PATH, metricsPath, MetricsPath.MAX_VALUE + 1);
 }
 
 export function logAmbientModeOptInUMA() {
-  chrome.metricsPrivate.recordBoolean(
-      PersonalizationAmbientModeOptInHistogramName, true);
+  chrome.metricsPrivate.recordBoolean(HistogramName.AMBIENT_OPTIN, true);
+}
+
+export function logGooglePhotosPreviewsLoadTime() {
+  // Get elapsed time in ms since the page initialized.
+  const timeMs = Math.round(performance.now());
+  console.debug(
+      HistogramName.AMBIENT_PERFORMANCE_GOOGLE_PHOTOS_PREVIEWS, timeMs);
+  chrome.metricsPrivate.recordTime(
+      HistogramName.AMBIENT_PERFORMANCE_GOOGLE_PHOTOS_PREVIEWS, timeMs);
 }
diff --git a/ash/webui/personalization_app/resources/tsconfig_base.json b/ash/webui/personalization_app/resources/tsconfig_base.json
index 3e71f763..a4137c0 100644
--- a/ash/webui/personalization_app/resources/tsconfig_base.json
+++ b/ash/webui/personalization_app/resources/tsconfig_base.json
@@ -1,6 +1,7 @@
 {
   "extends": "../../../../tools/typescript/tsconfig_base.json",
   "compilerOptions": {
+    "target": "es2021",
     "allowJs": true,
     "noUncheckedIndexedAccess": false,
     "noUnusedLocals": false,
diff --git a/ash/webui/shimless_rma/resources/calibration_component_chip.js b/ash/webui/shimless_rma/resources/calibration_component_chip.js
index 363b44c6b..4f3b1ce 100644
--- a/ash/webui/shimless_rma/resources/calibration_component_chip.js
+++ b/ash/webui/shimless_rma/resources/calibration_component_chip.js
@@ -56,7 +56,8 @@
       isFirstClickableComponent: {
         type: Boolean,
         value: false,
-        observer: 'onIsFirstClickableComponentChanged_',
+        observer: CalibrationComponentChipElement.prototype
+                      .onIsFirstClickableComponentChanged_,
       },
 
       /** @type {number} */
diff --git a/ash/webui/shimless_rma/resources/onboarding_enter_rsu_wp_disable_code_page.js b/ash/webui/shimless_rma/resources/onboarding_enter_rsu_wp_disable_code_page.js
index a6b2c5d..5d15113 100644
--- a/ash/webui/shimless_rma/resources/onboarding_enter_rsu_wp_disable_code_page.js
+++ b/ash/webui/shimless_rma/resources/onboarding_enter_rsu_wp_disable_code_page.js
@@ -57,7 +57,8 @@
        */
       errorCode: {
         type: Object,
-        observer: 'onErrorCodeChanged_',
+        observer:
+            OnboardingEnterRsuWpDisableCodePage.prototype.onErrorCodeChanged_,
       },
 
       /** @protected */
@@ -82,7 +83,8 @@
       rsuCode_: {
         type: String,
         value: '',
-        observer: 'onRsuCodeChanged_',
+        observer:
+            OnboardingEnterRsuWpDisableCodePage.prototype.onRsuCodeChanged_,
       },
 
       /** @protected */
diff --git a/ash/webui/shimless_rma/resources/onboarding_network_page.js b/ash/webui/shimless_rma/resources/onboarding_network_page.js
index 57c0711f..c94e0c0 100644
--- a/ash/webui/shimless_rma/resources/onboarding_network_page.js
+++ b/ash/webui/shimless_rma/resources/onboarding_network_page.js
@@ -132,7 +132,7 @@
       isOnline_: {
         type: Boolean,
         value: false,
-        observer: 'onIsOnlineChange_',
+        observer: OnboardingNetworkPage.prototype.onIsOnlineChange_,
       },
     };
   }
diff --git a/ash/webui/shimless_rma/resources/onboarding_update_page.js b/ash/webui/shimless_rma/resources/onboarding_update_page.js
index 28f845e..740fb97 100644
--- a/ash/webui/shimless_rma/resources/onboarding_update_page.js
+++ b/ash/webui/shimless_rma/resources/onboarding_update_page.js
@@ -82,7 +82,8 @@
       updateInProgress_: {
         type: Boolean,
         value: false,
-        observer: 'onUpdateInProgressChange_',
+        observer:
+            OnboardingUpdatePageElement.prototype.onUpdateInProgressChange_,
       },
 
       /** @protected */
diff --git a/ash/webui/shimless_rma/resources/repair_component_chip.js b/ash/webui/shimless_rma/resources/repair_component_chip.js
index 7894cf7..3b900da 100644
--- a/ash/webui/shimless_rma/resources/repair_component_chip.js
+++ b/ash/webui/shimless_rma/resources/repair_component_chip.js
@@ -71,7 +71,8 @@
       isFirstClickableComponent: {
         type: Boolean,
         value: false,
-        observer: 'onIsFirstClickableComponentChanged_',
+        observer:
+            RepairComponentChip.prototype.onIsFirstClickableComponentChanged_,
       },
 
     };
diff --git a/ash/wm/ash_focus_rules.cc b/ash/wm/ash_focus_rules.cc
index 6f09f03d..2b72c28ca 100644
--- a/ash/wm/ash_focus_rules.cc
+++ b/ash/wm/ash_focus_rules.cc
@@ -95,12 +95,19 @@
   if (window_state->IsMinimized())
     return true;
 
-  // Floated windows are hidden if they belong to inactive desks, but they can
-  // always be activated.
-  if (window_state->IsFloated() &&
-      shell->float_controller()->FindDeskOfFloatedWindow(window) !=
-          DesksController::Get()->active_desk()) {
-    return true;
+  if (window_state->IsFloated()) {
+    auto* float_controller = shell->float_controller();
+    // Floated windows are hidden if they belong to inactive desks, but they can
+    // always be activated.
+    if (float_controller->FindDeskOfFloatedWindow(window) !=
+        DesksController::Get()->active_desk()) {
+      return true;
+    }
+
+    // Tucked windows are hidden offscreen, but they can be activated.
+    if (float_controller->IsFloatedWindowTuckedForTablet(window)) {
+      return true;
+    }
   }
 
   if (!window->TargetVisibility())
diff --git a/ash/wm/desks/cros_next_desk_button.cc b/ash/wm/desks/cros_next_desk_button.cc
new file mode 100644
index 0000000..0adc51d
--- /dev/null
+++ b/ash/wm/desks/cros_next_desk_button.cc
@@ -0,0 +1,181 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/wm/desks/cros_next_desk_button.h"
+
+#include "ash/strings/grit/ash_strings.h"
+#include "ash/style/ash_color_id.h"
+#include "ash/wm/desks/desk.h"
+#include "ash/wm/desks/desk_mini_view.h"
+#include "ash/wm/desks/desk_preview_view.h"
+#include "ash/wm/desks/desks_bar_view.h"
+#include "ash/wm/desks/desks_controller.h"
+#include "ash/wm/overview/overview_constants.h"
+#include "base/check_op.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/chromeos/styles/cros_tokens_color_mappings.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/geometry/rounded_corners_f.h"
+#include "ui/gfx/text_elider.h"
+#include "ui/views/accessibility/view_accessibility.h"
+#include "ui/views/background.h"
+#include "ui/views/controls/highlight_path_generator.h"
+
+namespace ash {
+
+namespace {
+
+constexpr int kIconButtonCornerRadius = 18;
+
+constexpr int kDefaultButtonCornerRadius = 14;
+
+constexpr int kZeroStateButtonHeight = 28;
+
+constexpr int kZeroStateButtonWidth = 28;
+
+constexpr int kExpandedStateButtonWidth = 44;
+
+constexpr int kDefaultButtonHorizontalPadding = 16;
+
+constexpr int kDefaultDeskButtonMinWidth = 56;
+
+}  // namespace
+
+// -----------------------------------------------------------------------------
+// CrOSNextDefaultDeskButton:
+
+CrOSNextDefaultDeskButton::CrOSNextDefaultDeskButton(DesksBarView* bar_view)
+    : CrOSNextDeskButtonBase(
+          DesksController::Get()->desks()[0]->name(),
+          /*set_text=*/true,
+          base::BindRepeating(&CrOSNextDefaultDeskButton::OnButtonPressed,
+                              base::Unretained(this))),
+      bar_view_(bar_view) {
+  layer()->SetRoundedCornerRadius(
+      gfx::RoundedCornersF(kDefaultButtonCornerRadius));
+  GetViewAccessibility().OverrideName(
+      l10n_util::GetStringFUTF16(IDS_ASH_DESKS_DESK_ACCESSIBLE_NAME,
+                                 DesksController::Get()->desks()[0]->name()));
+
+  SetBackground(
+      views::CreateThemedSolidBackground(cros_tokens::kCrosSysSystemOnBase));
+}
+
+gfx::Size CrOSNextDefaultDeskButton::CalculatePreferredSize() const {
+  auto* root_window =
+      bar_view_->GetWidget()->GetNativeWindow()->GetRootWindow();
+  const int preview_width = DeskMiniView::GetPreviewWidth(
+      root_window->bounds().size(), DeskPreviewView::GetHeight(root_window));
+  int label_width = 0, label_height = 0;
+  gfx::Canvas::SizeStringInt(DesksController::Get()->desks()[0]->name(),
+                             gfx::FontList(), &label_width, &label_height, 0,
+                             gfx::Canvas::NO_ELLIPSIS);
+
+  // `preview_width` is supposed to be larger than
+  // `kZeroStateDefaultDeskButtonMinWidth`, but it might be not the truth for
+  // tests with extreme abnormal size of display.
+  const int min_width = std::min(preview_width, kDefaultDeskButtonMinWidth);
+  const int max_width = std::max(preview_width, kDefaultDeskButtonMinWidth);
+  const int width = base::clamp(
+      label_width + 2 * kDefaultButtonHorizontalPadding, min_width, max_width);
+  return gfx::Size(width, kZeroStateButtonHeight);
+}
+
+void CrOSNextDefaultDeskButton::UpdateLabelText() {
+  SetText(gfx::ElideText(
+      DesksController::Get()->desks()[0]->name(), gfx::FontList(),
+      bounds().width() - 2 * kDefaultButtonHorizontalPadding, gfx::ELIDE_TAIL));
+}
+
+void CrOSNextDefaultDeskButton::OnButtonPressed() {
+  bar_view_->UpdateNewMiniViews(/*initializing_bar_view=*/false,
+                                /*expanding_bar_view=*/true);
+  bar_view_->NudgeDeskName(/*desk_index=*/0);
+}
+
+BEGIN_METADATA(CrOSNextDefaultDeskButton, CrOSNextDeskButtonBase)
+END_METADATA
+
+// -----------------------------------------------------------------------------
+// CrOSNextDeskIconButton:
+
+CrOSNextDeskIconButton::CrOSNextDeskIconButton(
+    DesksBarView* bar_view,
+    const gfx::VectorIcon* button_icon,
+    const std::u16string& text,
+    ui::ColorId icon_color_id,
+    ui::ColorId background_color_id,
+    base::RepeatingClosure callback)
+    : CrOSNextDeskButtonBase(text, /*set_text=*/false, callback),
+      bar_view_(bar_view),
+      state_(bar_view_->IsZeroState() ? State::kZero : State::kExpanded) {
+  layer()->SetRoundedCornerRadius(
+      gfx::RoundedCornersF(kIconButtonCornerRadius));
+  SetImageModel(views::Button::STATE_NORMAL,
+                ui::ImageModel::FromVectorIcon(*button_icon, icon_color_id));
+  SetBackground(views::CreateThemedSolidBackground(background_color_id));
+
+  views::InstallRoundRectHighlightPathGenerator(
+      this, gfx::Insets(kFocusRingHaloInset), kIconButtonCornerRadius);
+  views::FocusRing::Get(this)->SetHasFocusPredicate([&](views::View* view) {
+    return IsViewHighlighted() ||
+           ((bar_view_->dragged_item_over_bar() &&
+             IsPointOnButton(bar_view_->last_dragged_item_screen_location())) ||
+            paint_as_active_);
+  });
+}
+
+CrOSNextDeskIconButton::~CrOSNextDeskIconButton() = default;
+
+bool CrOSNextDeskIconButton::IsPointOnButton(
+    const gfx::Point& screen_location) const {
+  gfx::Point point_in_view = screen_location;
+  ConvertPointFromScreen(this, &point_in_view);
+  return HitTestPoint(point_in_view);
+}
+
+gfx::Size CrOSNextDeskIconButton::CalculatePreferredSize() const {
+  if (state_ == State::kZero) {
+    return gfx::Size(kZeroStateButtonWidth, kZeroStateButtonHeight);
+  }
+
+  gfx::Rect desk_preview_bounds = DeskMiniView::GetDeskPreviewBounds(
+      GetWidget()->GetNativeWindow()->GetRootWindow());
+  if (state_ == State::kExpanded) {
+    return gfx::Size(kExpandedStateButtonWidth, desk_preview_bounds.height());
+  }
+
+  DCHECK_EQ(state_, State::kDragAndDrop);
+  return gfx::Size(desk_preview_bounds.width(), desk_preview_bounds.height());
+}
+
+void CrOSNextDeskIconButton::UpdateFocusState() {
+  absl::optional<ui::ColorId> new_focus_color_id;
+
+  if (IsViewHighlighted() ||
+      (bar_view_->dragged_item_over_bar() &&
+       IsPointOnButton(bar_view_->last_dragged_item_screen_location()))) {
+    new_focus_color_id = ui::kColorAshFocusRing;
+  } else if (paint_as_active_) {
+    new_focus_color_id = kColorAshCurrentDeskColor;
+  } else {
+    new_focus_color_id = absl::nullopt;
+  }
+
+  if (focus_color_id_ == new_focus_color_id) {
+    return;
+  }
+
+  focus_color_id_ = new_focus_color_id;
+
+  // Only repaint the focus ring if the color gets updated.
+  auto* focus_ring = views::FocusRing::Get(this);
+  focus_ring->SetColorId(new_focus_color_id);
+  focus_ring->SchedulePaint();
+}
+
+BEGIN_METADATA(CrOSNextDeskIconButton, CrOSNextDeskButtonBase)
+END_METADATA
+
+}  // namespace ash
diff --git a/ash/wm/desks/cros_next_desk_button.h b/ash/wm/desks/cros_next_desk_button.h
new file mode 100644
index 0000000..1cb75d6
--- /dev/null
+++ b/ash/wm/desks/cros_next_desk_button.h
@@ -0,0 +1,100 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ASH_WM_DESKS_CROS_NEXT_DESK_BUTTON_H_
+#define ASH_WM_DESKS_CROS_NEXT_DESK_BUTTON_H_
+
+#include "ash/wm/desks/cros_next_desk_button_base.h"
+#include "ui/base/metadata/metadata_impl_macros.h"
+
+namespace gfx {
+struct VectorIcon;
+}
+
+namespace ash {
+
+class DesksBarView;
+
+// A button in zero state bar showing the name of the desk. Zero state is the
+// state of the desks bar when there's only a single desk available, in which
+// case the bar is shown in a minimized state. Clicking the button will switch
+// to the expanded desks bar and focus on the single desk's name view.
+// TODO(conniekxu): Remove `ZeroStateDefaultDeskButton`, replace it with this
+// class, and rename this class by removing the prefix CrOSNext.
+class CrOSNextDefaultDeskButton : public CrOSNextDeskButtonBase {
+ public:
+  METADATA_HEADER(CrOSNextDefaultDeskButton);
+
+  explicit CrOSNextDefaultDeskButton(DesksBarView* bar_view);
+  CrOSNextDefaultDeskButton(const CrOSNextDefaultDeskButton&) = delete;
+  CrOSNextDefaultDeskButton& operator=(const CrOSNextDefaultDeskButton&) =
+      delete;
+  ~CrOSNextDefaultDeskButton() override = default;
+
+  void UpdateLabelText();
+
+  // CrOSNextDeskButtonBase:
+  gfx::Size CalculatePreferredSize() const override;
+
+ private:
+  void OnButtonPressed();
+
+  DesksBarView* const bar_view_;
+};
+
+// A button view in the desks bar with an icon. The button have three different
+// states, and the three states are interchangeable.
+// TODO(conniekxu): Remove `ZeroStateIconButton` and `ExpandedDesksBarButton`,
+// replace them with this class, and rename this class by removing the prefix
+// CrOSNext.
+class CrOSNextDeskIconButton : public CrOSNextDeskButtonBase {
+ public:
+  METADATA_HEADER(CrOSNextDeskIconButton);
+
+  // The enum class defines three states for the button. The button at different
+  // states has different sizes. Any state could be transformed into another
+  // state under certain conditions.
+  enum class State {
+    // The state of the button when the DesksBarView is in zero state.
+    kZero,
+    // The state of the button when the DesksBarView is in expanded state.
+    kExpanded,
+    // The state of when a window is dragged over the new desk button and held
+    // for 500 milliseconds, we can create a new desk. The new desk button state
+    // will change to reflect that.
+    kDragAndDrop,
+  };
+
+  CrOSNextDeskIconButton(DesksBarView* bar_view,
+                         const gfx::VectorIcon* button_icon,
+                         const std::u16string& text,
+                         ui::ColorId icon_color_id,
+                         ui::ColorId background_color_id,
+                         base::RepeatingClosure callback);
+  CrOSNextDeskIconButton(const CrOSNextDeskIconButton&) = delete;
+  CrOSNextDeskIconButton& operator=(const CrOSNextDeskIconButton&) = delete;
+  ~CrOSNextDeskIconButton() override;
+
+  bool IsPointOnButton(const gfx::Point& screen_location) const;
+
+  // CrOSNextDeskButtonBase:
+  gfx::Size CalculatePreferredSize() const override;
+  // Updates the focus ring based on the dragged item's position and `active_`.
+  void UpdateFocusState() override;
+
+ private:
+  DesksBarView* const bar_view_;
+
+  State state_;
+
+  // If `active_` is true, then focus ring will be painted with color id
+  // `kColorAshCurrentDeskColor` even if it's not already focused.
+  bool paint_as_active_ = false;
+
+  absl::optional<ui::ColorId> focus_color_id_;
+};
+
+}  // namespace ash
+
+#endif  // ASH_WM_DESKS_CROS_NEXT_DESK_BUTTON_H_
diff --git a/ash/wm/desks/cros_next_desk_button_base.cc b/ash/wm/desks/cros_next_desk_button_base.cc
new file mode 100644
index 0000000..e372d352
--- /dev/null
+++ b/ash/wm/desks/cros_next_desk_button_base.cc
@@ -0,0 +1,92 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/wm/desks/cros_next_desk_button_base.h"
+
+#include "ash/wm/overview/overview_utils.h"
+#include "ui/base/metadata/metadata_impl_macros.h"
+#include "ui/compositor/layer.h"
+#include "ui/views/border.h"
+#include "ui/views/controls/focus_ring.h"
+#include "ui/views/controls/highlight_path_generator.h"
+
+namespace ash {
+
+namespace {
+
+constexpr int kFocusRingRadius = 8;
+
+}  // namespace
+
+CrOSNextDeskButtonBase::CrOSNextDeskButtonBase(
+    const std::u16string& text,
+    bool set_text,
+    base::RepeatingClosure pressed_callback)
+    : LabelButton(pressed_callback), pressed_callback_(pressed_callback) {
+  DCHECK(!text.empty());
+  if (set_text) {
+    SetText(text);
+  }
+
+  // Call `SetPaintToLayer` explicitly here since we need to do the layer
+  // animations on `this`.
+  SetPaintToLayer();
+  layer()->SetFillsBoundsOpaquely(false);
+
+  SetAccessibleName(text);
+  SetTooltipText(text);
+
+  // Create an empty border, otherwise in `LabelButton` a default border with
+  // non-empty insets will be created.
+  SetBorder(views::CreateEmptyBorder(gfx::Insets()));
+
+  views::InstallRoundRectHighlightPathGenerator(this, gfx::Insets(),
+                                                kFocusRingRadius);
+  views::FocusRing* focus_ring = views::FocusRing::Get(this);
+  focus_ring->SetColorId(ui::kColorAshFocusRing);
+  focus_ring->SetHasFocusPredicate(
+      [&](views::View* view) { return IsViewHighlighted(); });
+}
+
+CrOSNextDeskButtonBase::~CrOSNextDeskButtonBase() = default;
+
+void CrOSNextDeskButtonBase::OnFocus() {
+  UpdateOverviewHighlightForFocusAndSpokenFeedback(this);
+  UpdateFocusState();
+  View::OnFocus();
+}
+
+void CrOSNextDeskButtonBase::OnBlur() {
+  UpdateFocusState();
+  View::OnBlur();
+}
+
+views::View* CrOSNextDeskButtonBase::GetView() {
+  return this;
+}
+
+void CrOSNextDeskButtonBase::MaybeActivateHighlightedView() {
+  pressed_callback_.Run();
+}
+
+void CrOSNextDeskButtonBase::MaybeCloseHighlightedView(bool primary_action) {}
+
+void CrOSNextDeskButtonBase::MaybeSwapHighlightedView(bool right) {}
+
+void CrOSNextDeskButtonBase::OnViewHighlighted() {
+  UpdateFocusState();
+}
+
+void CrOSNextDeskButtonBase::OnViewUnhighlighted() {
+  UpdateFocusState();
+}
+
+void CrOSNextDeskButtonBase::UpdateFocusState() {
+  views::FocusRing::Get(this)->SchedulePaint();
+}
+
+BEGIN_METADATA(CrOSNextDeskButtonBase, views::LabelButton)
+END_METADATA
+
+}  // namespace ash
\ No newline at end of file
diff --git a/ash/wm/desks/cros_next_desk_button_base.h b/ash/wm/desks/cros_next_desk_button_base.h
new file mode 100644
index 0000000..ca7da77a
--- /dev/null
+++ b/ash/wm/desks/cros_next_desk_button_base.h
@@ -0,0 +1,51 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ASH_WM_DESKS_CROS_NEXT_DESK_BUTTON_BASE_H_
+#define ASH_WM_DESKS_CROS_NEXT_DESK_BUTTON_BASE_H_
+
+#include "ash/wm/overview/overview_highlightable_view.h"
+#include "base/functional/callback_forward.h"
+#include "ui/views/controls/button/label_button.h"
+
+namespace ash {
+
+// The base class of buttons (default desk button, new desk button and library
+// button) on desks bar.
+// TODO(conniekxu): Remove `DeskButtonBase`, replace it with this class and
+// rename this class by removing the prefix CrOSNext.
+class CrOSNextDeskButtonBase : public views::LabelButton,
+                               public OverviewHighlightableView {
+ public:
+  METADATA_HEADER(CrOSNextDeskButtonBase);
+
+  explicit CrOSNextDeskButtonBase(const std::u16string& text,
+                                  bool set_text,
+                                  base::RepeatingClosure pressed_callback);
+  CrOSNextDeskButtonBase(const CrOSNextDeskButtonBase&) = delete;
+  CrOSNextDeskButtonBase& operator=(const CrOSNextDeskButtonBase&) = delete;
+  ~CrOSNextDeskButtonBase() override;
+
+  // views::LabelButton:
+  void OnFocus() override;
+  void OnBlur() override;
+
+  // OverviewHighlightableView:
+  views::View* GetView() override;
+  void MaybeActivateHighlightedView() override;
+  void MaybeCloseHighlightedView(bool primary_action) override;
+  void MaybeSwapHighlightedView(bool right) override;
+  void OnViewHighlighted() override;
+  void OnViewUnhighlighted() override;
+
+ protected:
+  virtual void UpdateFocusState();
+
+ private:
+  base::RepeatingClosure pressed_callback_;
+};
+
+}  // namespace ash
+
+#endif  // ASH_WM_DESKS_CROS_NEXT_DESK_BUTTON_BASE_H_
\ No newline at end of file
diff --git a/ash/wm/float/float_controller_unittest.cc b/ash/wm/float/float_controller_unittest.cc
index 74653d99..9496263 100644
--- a/ash/wm/float/float_controller_unittest.cc
+++ b/ash/wm/float/float_controller_unittest.cc
@@ -1294,6 +1294,35 @@
   EXPECT_TRUE(WindowState::Get(window.get())->IsFloated());
 }
 
+// Tests that the tucked window is invisible while it is fully tucked.
+TEST_F(TabletWindowFloatTest, TuckedWindowVisibility) {
+  Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
+  std::unique_ptr<aura::Window> window = CreateFloatedWindow();
+
+  ui::ScopedAnimationDurationScaleMode test_duration_mode(
+      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
+
+  // Fling to tuck the window in the bottom right. Test that the window is
+  // invisible once the animation is finished.
+  auto* float_controller = Shell::Get()->float_controller();
+  FlingWindow(window.get(), /*left=*/false, /*up=*/false);
+  EXPECT_TRUE(window->IsVisible());
+  ShellTestApi().WaitForWindowFinishAnimating(window.get());
+  EXPECT_FALSE(window->IsVisible());
+  ASSERT_TRUE(float_controller->IsFloatedWindowTuckedForTablet(window.get()));
+
+  // Tests that there is an overview item created for the tucked window.
+  ToggleOverview();
+  WaitForOverviewEnterAnimation();
+  EXPECT_TRUE(GetOverviewItemForWindow(window.get()));
+
+  // Tests that after we activate the window, the window is visible again as it
+  // is getting untucked.
+  wm::ActivateWindow(window.get());
+  ShellTestApi().WaitForWindowFinishAnimating(window.get());
+  EXPECT_TRUE(window->IsVisible());
+}
+
 // Tests that the expected window gets activation after tucking a floated
 // window, and that on untucking the floated window, it gains activation.
 TEST_F(TabletWindowFloatTest, WindowActivationAfterTuckingUntucking) {
diff --git a/ash/wm/float/scoped_window_tucker.cc b/ash/wm/float/scoped_window_tucker.cc
index a5aaf62..4ce5dac67 100644
--- a/ash/wm/float/scoped_window_tucker.cc
+++ b/ash/wm/float/scoped_window_tucker.cc
@@ -8,6 +8,7 @@
 #include "ash/public/cpp/shell_window_ids.h"
 #include "ash/public/cpp/window_properties.h"
 #include "ash/resources/vector_icons/vector_icons.h"
+#include "ash/scoped_animation_disabler.h"
 #include "ash/shell.h"
 #include "ash/style/color_util.h"
 #include "ash/style/dark_light_mode_controller_impl.h"
@@ -227,6 +228,10 @@
       left_ ? -kTuckOffscreenPaddingDp : kTuckOffscreenPaddingDp, 0);
 
   views::AnimationBuilder()
+      .OnAborted(base::BindOnce(&ScopedWindowTucker::OnAnimateTuckEnded,
+                                weak_factory_.GetWeakPtr()))
+      .OnEnded(base::BindOnce(&ScopedWindowTucker::OnAnimateTuckEnded,
+                              weak_factory_.GetWeakPtr()))
       .SetPreemptionStrategy(
           ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET)
       .Once()
@@ -246,6 +251,9 @@
 }
 
 void ScopedWindowTucker::AnimateUntuck(base::OnceClosure callback) {
+  ScopedAnimationDisabler disable(window_);
+  window_->Show();
+
   const gfx::RectF initial_bounds(window_->bounds());
 
   TabletModeWindowState::UpdateWindowPosition(
@@ -320,6 +328,11 @@
                     gfx::Tween::ACCEL_20_DECEL_100);
 }
 
+void ScopedWindowTucker::OnAnimateTuckEnded() {
+  ScopedAnimationDisabler disable(window_);
+  window_->Hide();
+}
+
 void ScopedWindowTucker::UntuckWindow() {
   Shell::Get()->float_controller()->MaybeUntuckFloatedWindowForTablet(window_);
 }
diff --git a/ash/wm/float/scoped_window_tucker.h b/ash/wm/float/scoped_window_tucker.h
index 3f0bca9..8af1d288 100644
--- a/ash/wm/float/scoped_window_tucker.h
+++ b/ash/wm/float/scoped_window_tucker.h
@@ -9,6 +9,7 @@
 
 #include "ash/wm/overview/overview_controller.h"
 #include "ash/wm/overview/overview_observer.h"
+#include "base/memory/weak_ptr.h"
 #include "ui/views/widget/unique_widget_ptr.h"
 #include "ui/wm/public/activation_change_observer.h"
 
@@ -54,6 +55,10 @@
   // overview mode respectively.
   void OnOverviewModeChanged(bool in_overview);
 
+  // Hides the window after the tuck animation is finished. This is so it will
+  // behave similarly to a minimized window in overview.
+  void OnAnimateTuckEnded();
+
   // Destroys `this_`, which will untuck `window_` and set the window bounds
   // back onscreen.
   void UntuckWindow();
@@ -75,6 +80,8 @@
 
   base::ScopedObservation<OverviewController, OverviewObserver>
       overview_observer_{this};
+
+  base::WeakPtrFactory<ScopedWindowTucker> weak_factory_{this};
 };
 
 }  // namespace ash
diff --git a/ash/wm/overview/scoped_overview_transform_window.cc b/ash/wm/overview/scoped_overview_transform_window.cc
index fe18b816..4e0e8dc 100644
--- a/ash/wm/overview/scoped_overview_transform_window.cc
+++ b/ash/wm/overview/scoped_overview_transform_window.cc
@@ -10,6 +10,7 @@
 #include "ash/constants/ash_features.h"
 #include "ash/public/cpp/window_properties.h"
 #include "ash/shell.h"
+#include "ash/wm/float/float_controller.h"
 #include "ash/wm/overview/delayed_animation_observer_impl.h"
 #include "ash/wm/overview/overview_constants.h"
 #include "ash/wm/overview/overview_controller.h"
@@ -508,7 +509,7 @@
 }
 
 bool ScopedOverviewTransformWindow::IsMinimized() const {
-  return WindowState::Get(window_)->IsMinimized();
+  return window_util::IsMinimizedOrTucked(window_);
 }
 
 void ScopedOverviewTransformWindow::PrepareForOverview() {
diff --git a/ash/wm/overview/scoped_overview_transform_window.h b/ash/wm/overview/scoped_overview_transform_window.h
index 15c2878c..a66df540 100644
--- a/ash/wm/overview/scoped_overview_transform_window.h
+++ b/ash/wm/overview/scoped_overview_transform_window.h
@@ -126,6 +126,8 @@
   // Closes the transient root of the window managed by |this|.
   void Close();
 
+  // TODO(sammiequon): Rename this function as tucked floated windows behave the
+  // same way as minimized windows.
   bool IsMinimized() const;
 
   // Ensures that a window is visible by setting its opacity to 1.
diff --git a/ash/wm/tablet_mode/tablet_mode_window_state.cc b/ash/wm/tablet_mode/tablet_mode_window_state.cc
index 06fb51a..c0e1282 100644
--- a/ash/wm/tablet_mode/tablet_mode_window_state.cc
+++ b/ash/wm/tablet_mode/tablet_mode_window_state.cc
@@ -89,8 +89,7 @@
 }
 
 // Returns the maximized/full screen and/or centered bounds of a window.
-gfx::Rect GetBoundsInTabletMode(WindowState* state_object,
-                                absl::optional<float> snap_ratio) {
+gfx::Rect GetBoundsInTabletMode(WindowState* state_object) {
   aura::Window* window = state_object->window();
 
   if (state_object->IsFullscreen() || state_object->IsPinned())
@@ -100,14 +99,14 @@
     return SplitViewController::Get(Shell::GetPrimaryRootWindow())
         ->GetSnappedWindowBoundsInParent(
             SplitViewController::SnapPosition::kPrimary, window,
-            snap_ratio ? *snap_ratio : kDefaultSnapRatio);
+            state_object->snap_ratio().value_or(kDefaultSnapRatio));
   }
 
   if (state_object->GetStateType() == WindowStateType::kSecondarySnapped) {
     return SplitViewController::Get(Shell::GetPrimaryRootWindow())
         ->GetSnappedWindowBoundsInParent(
             SplitViewController::SnapPosition::kSecondary, window,
-            snap_ratio ? *snap_ratio : kDefaultSnapRatio);
+            state_object->snap_ratio().value_or(kDefaultSnapRatio));
   }
 
   if (chromeos::wm::features::IsFloatWindowEnabled() &&
@@ -254,8 +253,7 @@
 void TabletModeWindowState::UpdateWindowPosition(
     WindowState* window_state,
     WindowState::BoundsChangeAnimationType animation_type) {
-  const gfx::Rect bounds_in_parent =
-      GetBoundsInTabletMode(window_state, window_state->snap_ratio());
+  const gfx::Rect bounds_in_parent = GetBoundsInTabletMode(window_state);
   if (bounds_in_parent == window_state->window()->GetTargetBounds())
     return;
 
@@ -311,24 +309,23 @@
       break;
     case WM_EVENT_FULLSCREEN:
       UpdateWindow(window_state, WindowStateType::kFullscreen,
-                   /*animate=*/true, /*new_snap_ratio=*/absl::nullopt);
+                   /*animate=*/true);
       break;
     case WM_EVENT_PIN:
       if (!Shell::Get()->screen_pinning_controller()->IsPinned()) {
         UpdateWindow(window_state, WindowStateType::kPinned,
-                     /*animate=*/true, /*new_snap_ratio=*/absl::nullopt);
+                     /*animate=*/true);
       }
       break;
     case WM_EVENT_PIP:
       if (!window_state->IsPip()) {
-        UpdateWindow(window_state, WindowStateType::kPip, /*animate=*/true,
-                     /*new_snap_ratio=*/absl::nullopt);
+        UpdateWindow(window_state, WindowStateType::kPip, /*animate=*/true);
       }
       break;
     case WM_EVENT_TRUSTED_PIN:
       if (!Shell::Get()->screen_pinning_controller()->IsPinned()) {
         UpdateWindow(window_state, WindowStateType::kTrustedPinned,
-                     /*animate=*/true, /*new_snap_ratio=*/absl::nullopt);
+                     /*animate=*/true);
       }
       break;
     case WM_EVENT_TOGGLE_MAXIMIZE_CAPTION:
@@ -339,7 +336,7 @@
     case WM_EVENT_MAXIMIZE:
       UpdateWindow(window_state,
                    window_state->GetMaximizedOrCenteredWindowType(),
-                   /*animate=*/true, /*new_snap_ratio=*/absl::nullopt);
+                   /*animate=*/true);
       return;
     case WM_EVENT_NORMAL: {
       // `WM_EVENT_NORMAL` may be restoring state from minimized.
@@ -348,7 +345,7 @@
       } else {
         UpdateWindow(window_state,
                      window_state->GetMaximizedOrCenteredWindowType(),
-                     /*animate=*/true, /*new_snap_ratio=*/absl::nullopt);
+                     /*animate=*/true);
       }
       return;
     }
@@ -363,13 +360,11 @@
         return;
 
       UpdateWindow(window_state, WindowStateType::kFloated,
-                   /*=animate=*/true, /*new_snap_ratio=*/absl::nullopt);
+                   /*=animate=*/true);
       break;
     case WM_EVENT_SNAP_PRIMARY:
     case WM_EVENT_SNAP_SECONDARY:
-      // TODO(b/259302867): Remove `window_state->snap_ratio()` since it can be
-      // gotten from `window_state`.
-      DoTabletSnap(window_state, event->type(), window_state->snap_ratio());
+      DoTabletSnap(window_state, event->type());
       return;
     case WM_EVENT_CYCLE_SNAP_PRIMARY:
       CycleTabletSnap(window_state,
@@ -381,7 +376,7 @@
       return;
     case WM_EVENT_MINIMIZE:
       UpdateWindow(window_state, WindowStateType::kMinimized,
-                   /*=animate=*/true, /*new_snap_ratio=*/absl::nullopt);
+                   /*=animate=*/true);
       return;
     case WM_EVENT_SHOW_INACTIVE:
     case WM_EVENT_SYSTEM_UI_AREA_CHANGED:
@@ -437,20 +432,17 @@
             IsSnapped(current_state_type_)
                 ? window_state->GetStateType()
                 : window_state->GetMaximizedOrCenteredWindowType();
-        UpdateWindow(window_state, new_state, /*animate=*/true,
-                     /*new_snap_ratio=*/window_state->snap_ratio());
+        UpdateWindow(window_state, new_state, /*animate=*/true);
       }
       break;
     case WM_EVENT_WORKAREA_BOUNDS_CHANGED:
       if (current_state_type_ != WindowStateType::kMinimized)
-        UpdateBounds(window_state, /*animate=*/true,
-                     /*new_snap_ratio=*/window_state->snap_ratio());
+        UpdateBounds(window_state, /*animate=*/true);
       break;
     case WM_EVENT_DISPLAY_BOUNDS_CHANGED:
       // Don't animate on a screen rotation - just snap to new size.
       if (current_state_type_ != WindowStateType::kMinimized)
-        UpdateBounds(window_state, /*animate=*/false,
-                     /*new_snap_ratio=*/window_state->snap_ratio());
+        UpdateBounds(window_state, /*animate=*/false);
       break;
   }
 }
@@ -480,8 +472,8 @@
       current_state_type_ != WindowStateType::kFloated &&
       current_state_type_ != WindowStateType::kPinned &&
       current_state_type_ != WindowStateType::kTrustedPinned) {
-    UpdateWindow(window_state, state_type_on_attach_, animate_bounds_on_attach_,
-                 window_state->snap_ratio());
+    UpdateWindow(window_state, state_type_on_attach_,
+                 animate_bounds_on_attach_);
   }
 }
 
@@ -493,8 +485,7 @@
 
 void TabletModeWindowState::UpdateWindow(WindowState* window_state,
                                          WindowStateType target_state,
-                                         bool animated,
-                                         absl::optional<float> new_snap_ratio) {
+                                         bool animated) {
   aura::Window* window = window_state->window();
 
   DCHECK(target_state == WindowStateType::kMinimized ||
@@ -512,7 +503,7 @@
     if (target_state == WindowStateType::kMinimized)
       return;
     // If the state type did not change, update it accordingly.
-    UpdateBounds(window_state, animated, new_snap_ratio);
+    UpdateBounds(window_state, animated);
     return;
   }
 
@@ -535,7 +526,7 @@
     if (window_state->IsActive())
       window_state->Deactivate();
   } else {
-    UpdateBounds(window_state, animated, new_snap_ratio);
+    UpdateBounds(window_state, animated);
   }
 
   if ((window->layer()->GetTargetVisibility() ||
@@ -565,8 +556,7 @@
 }
 
 void TabletModeWindowState::UpdateBounds(WindowState* window_state,
-                                         bool animated,
-                                         absl::optional<float> new_snap_ratio) {
+                                         bool animated) {
   // Do not update window's bounds if it's in tab-dragging process. The bounds
   // will be updated later when the drag ends.
   if (window_util::IsDraggingTabs(window_state->window()))
@@ -576,8 +566,7 @@
   if (current_state_type_ == WindowStateType::kMinimized)
     return;
 
-  gfx::Rect bounds_in_parent =
-      GetBoundsInTabletMode(window_state, new_snap_ratio);
+  gfx::Rect bounds_in_parent = GetBoundsInTabletMode(window_state);
   // If we have a target bounds rectangle, we center it and set it
   // accordingly.
   if (!bounds_in_parent.IsEmpty() &&
@@ -612,7 +601,7 @@
   // If |window| is already snapped in |snap_position|, then unsnap |window|.
   if (window == split_view_controller->GetSnappedWindow(snap_position)) {
     UpdateWindow(window_state, window_state->GetMaximizedOrCenteredWindowType(),
-                 /*animate=*/true, /*new_snap_ratio=*/absl::nullopt);
+                 /*animate=*/true);
     window_state->ReadOutWindowCycleSnapAction(
         IDS_WM_RESTORE_SNAPPED_WINDOW_ON_SHORTCUT);
     return;
@@ -631,8 +620,7 @@
 }
 
 void TabletModeWindowState::DoTabletSnap(WindowState* window_state,
-                                         WMEventType snap_event_type,
-                                         absl::optional<float> new_snap_ratio) {
+                                         WMEventType snap_event_type) {
   DCHECK(snap_event_type == WM_EVENT_SNAP_PRIMARY ||
          snap_event_type == WM_EVENT_SNAP_SECONDARY);
 
@@ -655,7 +643,7 @@
   split_view_controller->OnWMEvent(window, snap_event_type);
 
   // Change window state and bounds to the snapped window state and bounds.
-  UpdateWindow(window_state, new_state_type, /*animate=*/false, new_snap_ratio);
+  UpdateWindow(window_state, new_state_type, /*animate=*/false);
 }
 
 void TabletModeWindowState::DoRestore(WindowState* window_state) {
@@ -663,16 +651,13 @@
   if (chromeos::IsSnappedWindowStateType(restore_state)) {
     window_state->set_snap_action_source(
         WindowSnapActionSource::kSnapByWindowStateRestore);
-    DoTabletSnap(window_state,
-                 restore_state == WindowStateType::kPrimarySnapped
-                     ? WM_EVENT_SNAP_PRIMARY
-                     : WM_EVENT_SNAP_SECONDARY,
-                 window_state->snap_ratio());
+    DoTabletSnap(window_state, restore_state == WindowStateType::kPrimarySnapped
+                                   ? WM_EVENT_SNAP_PRIMARY
+                                   : WM_EVENT_SNAP_SECONDARY);
     return;
   }
 
-  UpdateWindow(window_state, restore_state, /*animate=*/true,
-               /*new_snap_ratio=*/absl::nullopt);
+  UpdateWindow(window_state, restore_state, /*animate=*/true);
 }
 
 }  // namespace ash
diff --git a/ash/wm/tablet_mode/tablet_mode_window_state.h b/ash/wm/tablet_mode/tablet_mode_window_state.h
index cbf27547..21dabd8 100644
--- a/ash/wm/tablet_mode/tablet_mode_window_state.h
+++ b/ash/wm/tablet_mode/tablet_mode_window_state.h
@@ -66,12 +66,10 @@
   // Updates the window to `new_state_type` and resulting bounds:
   // Either full screen, maximized centered or minimized. If the state does not
   // change, only the bounds will be changed. If `animate` is set, the bound
-  // change get animated. If `new_snap_ratio` is set, uses it to update snapped
-  // window bounds.
+  // change get animated.
   void UpdateWindow(WindowState* window_state,
                     chromeos::WindowStateType new_state_type,
-                    bool animate,
-                    absl::optional<float> new_snap_ratio);
+                    bool animate);
 
   // If `target_state` is PRIMARY/SECONDARY_SNAPPED and the window can be
   // snapped, returns `target_state`. Otherwise depending on the capabilities
@@ -82,11 +80,8 @@
       chromeos::WindowStateType target_state);
 
   // Updates the bounds to the maximum possible bounds according to the current
-  // window state. If `animate` is set we animate the change. If
-  // `new_snap_ratio` is set, uses it to update snapped window bounds.
-  void UpdateBounds(WindowState* window_state,
-                    bool animate,
-                    absl::optional<float> new_snap_ratio);
+  // window state. If `animate` is set we animate the change.
+  void UpdateBounds(WindowState* window_state, bool animate);
 
   // Handles Alt+[ if `snap_position` is
   // `SplitViewController::SnapPosition::kPrimary`; handles // Alt+] if
@@ -95,9 +90,7 @@
                        SplitViewController::SnapPosition snap_position);
 
   // Snap the window in tablet split view if it can be snapped.
-  void DoTabletSnap(WindowState* window_state,
-                    WMEventType snap_event_type,
-                    absl::optional<float> new_snap_ratio);
+  void DoTabletSnap(WindowState* window_state, WMEventType snap_event_type);
 
   // Called by `WM_EVENT_RESTORE`, or a `WM_EVENT_NORMAL` that is restoring.
   // Restores to the state in `window_states`'s restore history.
diff --git a/ash/wm/window_cycle/window_cycle_controller_unittest.cc b/ash/wm/window_cycle/window_cycle_controller_unittest.cc
index cc2a64a..59fe95c7 100644
--- a/ash/wm/window_cycle/window_cycle_controller_unittest.cc
+++ b/ash/wm/window_cycle/window_cycle_controller_unittest.cc
@@ -1968,7 +1968,7 @@
       Shell::Get()->session_controller()->GetActivePrefService();
   pref->SetBoolean(prefs::kNaturalScroll, false);
 
-  // Start cycle, scroll right with two finger gesture. Note: two figner swipes
+  // Start cycle, scroll right with two finger gesture. Note: two finger swipes
   // are negated, so negate in tests to mimic how this actually behaves on
   // devices.
   // Current order is [5,4,3,2,1].
@@ -2093,7 +2093,7 @@
   CompleteCycling(cycle_controller);
 }
 
-// Tests that when user taps tab slider buttons, but then scrolles and releases
+// Tests that when user taps tab slider buttons, but then scrolls and releases
 // finger on a window. Mode change should not happen in this use case.
 TEST_F(ModeSelectionWindowCycleControllerTest,
        TapTabSliderButtonButReleaseOnWindow) {
@@ -3698,4 +3698,89 @@
   EXPECT_TRUE(wm::IsActiveWindow(w5.get()));
 }
 
+// Tests that same app window cycling works in all desk mode, current desk mode,
+// switching between the two modes, and switching between same app window
+// cycling and normal window cycling.
+TEST_F(SameAppWindowCycleControllerTest, PerDeskMode) {
+  // On desk 1 create 1 window of app A and 3 windows of app B.
+  std::unique_ptr<aura::Window> w0(CreateTestWindowWithAppID(std::string("A")));
+  std::unique_ptr<aura::Window> w1(CreateTestWindowWithAppID(std::string("B")));
+  std::unique_ptr<aura::Window> w2(CreateTestWindowWithAppID(std::string("B")));
+  std::unique_ptr<aura::Window> w3(CreateTestWindowWithAppID(std::string("B")));
+
+  // On desk 2 create 2 windows of app A and 4 windows of app B.
+  auto* desks_controller = DesksController::Get();
+  desks_controller->NewDesk(DesksCreationRemovalSource::kButton);
+  ASSERT_EQ(2u, desks_controller->desks().size());
+  const Desk* desk_2 = desks_controller->desks()[1].get();
+  ActivateDesk(desk_2);
+  EXPECT_EQ(desk_2, desks_controller->active_desk());
+  std::unique_ptr<aura::Window> w4(CreateTestWindowWithAppID(std::string("A")));
+  std::unique_ptr<aura::Window> w5(CreateTestWindowWithAppID(std::string("A")));
+  std::unique_ptr<aura::Window> w6(CreateTestWindowWithAppID(std::string("B")));
+  std::unique_ptr<aura::Window> w7(CreateTestWindowWithAppID(std::string("B")));
+  std::unique_ptr<aura::Window> w8(CreateTestWindowWithAppID(std::string("B")));
+  std::unique_ptr<aura::Window> w9(CreateTestWindowWithAppID(std::string("B")));
+
+  // Start cycling, all desks mode should be default so we should see 7 windows
+  // of app B.
+  auto* generator = GetEventGenerator();
+  WindowCycleController* cycle_controller =
+      Shell::Get()->window_cycle_controller();
+  generator->PressKey(ui::VKEY_MENU, ui::EF_NONE);
+  generator->PressAndReleaseKey(ui::VKEY_OEM_3, ui::EF_ALT_DOWN);
+  EXPECT_FALSE(cycle_controller->IsAltTabPerActiveDesk());
+  auto cycle_windows = GetWindows(cycle_controller);
+  EXPECT_EQ(7u, cycle_windows.size());
+  EXPECT_EQ(cycle_windows.size(), GetWindowCycleItemViews().size());
+  EXPECT_TRUE(base::Contains(cycle_windows, w1.get()));
+  EXPECT_TRUE(base::Contains(cycle_windows, w2.get()));
+  EXPECT_TRUE(base::Contains(cycle_windows, w3.get()));
+  EXPECT_TRUE(base::Contains(cycle_windows, w6.get()));
+  EXPECT_TRUE(base::Contains(cycle_windows, w7.get()));
+  EXPECT_TRUE(base::Contains(cycle_windows, w8.get()));
+  EXPECT_TRUE(base::Contains(cycle_windows, w9.get()));
+
+  // Select current-desk mode. We should see 4 windows of app B.
+  generator->MoveMouseTo(
+      GetWindowCycleTabSliderButtons()[1]->GetBoundsInScreen().CenterPoint());
+  generator->ClickLeftButton();
+  cycle_windows = GetWindows(cycle_controller);
+  EXPECT_EQ(4u, GetWindowCycleItemViews().size());
+  EXPECT_EQ(cycle_windows.size(), GetWindowCycleItemViews().size());
+  EXPECT_TRUE(base::Contains(cycle_windows, w6.get()));
+  EXPECT_TRUE(base::Contains(cycle_windows, w7.get()));
+  EXPECT_TRUE(base::Contains(cycle_windows, w8.get()));
+  EXPECT_TRUE(base::Contains(cycle_windows, w9.get()));
+  generator->ReleaseKey(ui::VKEY_MENU, ui::EF_NONE);
+
+  // Go to desk 1 and start cycling, we should still be on current-desk mode and
+  // see 3 windows of app B.
+  ActivateDesk(desks_controller->desks()[0].get());
+  generator->PressKey(ui::VKEY_MENU, ui::EF_NONE);
+  generator->PressAndReleaseKey(ui::VKEY_OEM_3, ui::EF_ALT_DOWN);
+  EXPECT_TRUE(cycle_controller->IsAltTabPerActiveDesk());
+  cycle_windows = GetWindows(cycle_controller);
+  EXPECT_EQ(3u, GetWindowCycleItemViews().size());
+  EXPECT_EQ(cycle_windows.size(), GetWindowCycleItemViews().size());
+  EXPECT_TRUE(base::Contains(cycle_windows, w1.get()));
+  EXPECT_TRUE(base::Contains(cycle_windows, w2.get()));
+  EXPECT_TRUE(base::Contains(cycle_windows, w3.get()));
+  generator->ReleaseKey(ui::VKEY_MENU, ui::EF_NONE);
+
+  // Start alt tabbing. The mode selection should be shared between alt tab and
+  // alt backtick so we should still be on current-desk mode and see 4 windows.
+  generator->PressKey(ui::VKEY_MENU, ui::EF_NONE);
+  generator->PressAndReleaseKey(ui::VKEY_TAB, ui::EF_ALT_DOWN);
+  EXPECT_TRUE(cycle_controller->IsAltTabPerActiveDesk());
+  cycle_windows = GetWindows(cycle_controller);
+  EXPECT_EQ(4u, GetWindowCycleItemViews().size());
+  EXPECT_EQ(cycle_windows.size(), GetWindowCycleItemViews().size());
+  EXPECT_TRUE(base::Contains(cycle_windows, w0.get()));
+  EXPECT_TRUE(base::Contains(cycle_windows, w1.get()));
+  EXPECT_TRUE(base::Contains(cycle_windows, w2.get()));
+  EXPECT_TRUE(base::Contains(cycle_windows, w3.get()));
+  generator->ReleaseKey(ui::VKEY_MENU, ui::EF_NONE);
+}
+
 }  // namespace ash
diff --git a/ash/wm/window_cycle/window_cycle_list.cc b/ash/wm/window_cycle/window_cycle_list.cc
index e03b41c..33a88850 100644
--- a/ash/wm/window_cycle/window_cycle_list.cc
+++ b/ash/wm/window_cycle/window_cycle_list.cc
@@ -91,26 +91,14 @@
 }  // namespace
 
 WindowCycleList::WindowCycleList(const WindowList& windows, bool same_app_only)
-    : windows_(windows) {
+    : windows_(windows), same_app_only_(same_app_only) {
   if (!ShouldShowUi())
     Shell::Get()->mru_window_tracker()->SetIgnoreActivations(true);
 
   active_window_before_window_cycle_ = window_util::GetActiveWindow();
 
-  if (same_app_only && windows_.size() > 1) {
-    WindowCycleController::WindowList same_app_window_list;
-    const std::string* const mru_window_app_id =
-        windows_.front()->GetProperty(kAppIDKey);
-    if (mru_window_app_id) {
-      windows_.erase(base::ranges::remove_if(
-                         windows_.begin(), windows_.end(),
-                         [&mru_window_app_id](aura::Window* window) {
-                           const auto* const app_id =
-                               window->GetProperty(kAppIDKey);
-                           return !app_id || *app_id != *mru_window_app_id;
-                         }),
-                     windows_.end());
-    }
+  if (same_app_only) {
+    MakeSameAppOnly();
   }
 
   for (auto* window : windows_)
@@ -175,6 +163,10 @@
   RemoveAllWindows();
   windows_ = windows;
 
+  if (same_app_only_) {
+    MakeSameAppOnly();
+  }
+
   for (auto* new_window : windows_)
     new_window->AddObserver(this);
 
@@ -456,6 +448,26 @@
   }
 }
 
+void WindowCycleList::MakeSameAppOnly() {
+  DCHECK(same_app_only_);
+  if (windows_.size() < 2) {
+    return;
+  }
+  const std::string* const mru_window_app_id =
+      windows_.front()->GetProperty(kAppIDKey);
+  if (!mru_window_app_id) {
+    return;
+  }
+  windows_.erase(
+      base::ranges::remove_if(windows_.begin(), windows_.end(),
+                              [&mru_window_app_id](aura::Window* window) {
+                                const auto* const app_id =
+                                    window->GetProperty(kAppIDKey);
+                                return !app_id || *app_id != *mru_window_app_id;
+                              }),
+      windows_.end());
+}
+
 int WindowCycleList::GetOffsettedWindowIndex(int offset) const {
   DCHECK(!windows_.empty());
 
diff --git a/ash/wm/window_cycle/window_cycle_list.h b/ash/wm/window_cycle/window_cycle_list.h
index d5cad9d..b60b59eb 100644
--- a/ash/wm/window_cycle/window_cycle_list.h
+++ b/ash/wm/window_cycle/window_cycle_list.h
@@ -134,6 +134,10 @@
   // SetFocusedWindow() before this.
   void Scroll(int offset);
 
+  // Removes windows from `windows_` if they don't have the same app id as the
+  // MRU window.
+  void MakeSameAppOnly();
+
   // Returns the index for the window |offset| away from |current_index_|. Can
   // only be called if |windows_| is not empty. Also checks that the window for
   // the returned index exists.
@@ -163,6 +167,9 @@
   // True if one of the windows in the list has already been selected.
   bool window_selected_ = false;
 
+  // True if we are only cycling through windows of the same app.
+  const bool same_app_only_;
+
   // The top level View for the window cycle UI. May be null if the UI is not
   // showing.
   WindowCycleView* cycle_view_ = nullptr;
diff --git a/ash/wm/window_mirror_view.cc b/ash/wm/window_mirror_view.cc
index 76f6d73..67664e03 100644
--- a/ash/wm/window_mirror_view.cc
+++ b/ash/wm/window_mirror_view.cc
@@ -9,6 +9,7 @@
 
 #include "ash/wm/desks/desks_util.h"
 #include "ash/wm/window_state.h"
+#include "ash/wm/window_util.h"
 #include "ui/aura/client/aura_constants.h"
 #include "ui/aura/env.h"
 #include "ui/aura/window.h"
@@ -141,9 +142,9 @@
   // This causes us to clip the non-client areas of the window.
   layer()->SetMasksToBounds(true);
 
-  // Some extra work is needed when the source window is minimized or is on an
-  // inactive desk.
-  if (WindowState::Get(source_)->IsMinimized() ||
+  // Some extra work is needed when the source window is minimized, tucked
+  // offscreen or is on an inactive desk.
+  if (window_util::IsMinimizedOrTucked(source_) ||
       !desks_util::BelongsToActiveDesk(source_)) {
     EnsureAllChildrenAreVisible(mirror_layer);
   }
diff --git a/ash/wm/window_util.cc b/ash/wm/window_util.cc
index 8f06b056..f8496e0 100644
--- a/ash/wm/window_util.cc
+++ b/ash/wm/window_util.cc
@@ -20,6 +20,7 @@
 #include "ash/shelf/shelf.h"
 #include "ash/shell.h"
 #include "ash/shell_delegate.h"
+#include "ash/wm/float/float_controller.h"
 #include "ash/wm/mru_window_tracker.h"
 #include "ash/wm/overview/overview_controller.h"
 #include "ash/wm/overview/overview_session.h"
@@ -402,6 +403,21 @@
   return !shell->shell_delegate()->CanGoBack(window);
 }
 
+bool IsMinimizedOrTucked(aura::Window* window) {
+  DCHECK(window->parent());
+
+  WindowState* window_state = WindowState::Get(window);
+  if (!window_state) {
+    return false;
+  }
+  if (window_state->IsFloated()) {
+    return !window->is_destroying() &&
+           Shell::Get()->float_controller()->IsFloatedWindowTuckedForTablet(
+               window);
+  }
+  return window_state->IsMinimized();
+}
+
 void SendBackKeyEvent(aura::Window* root_window) {
   // Send up event as well as down event as ARC++ clients expect this
   // sequence.
diff --git a/ash/wm/window_util.h b/ash/wm/window_util.h
index c2d1978..a9c45e42 100644
--- a/ash/wm/window_util.h
+++ b/ash/wm/window_util.h
@@ -128,6 +128,10 @@
 // Returns whether the top window should be minimized on back action.
 ASH_EXPORT bool ShouldMinimizeTopWindowOnBack();
 
+// Returns true if `window` is in minimized state, or is in floated state and
+// tucked to the side in tablet mode.
+bool IsMinimizedOrTucked(aura::Window* window);
+
 // Sends |ui::VKEY_BROWSER_BACK| key press and key release event to the
 // WindowTreeHost associated with |root_window|.
 void SendBackKeyEvent(aura::Window* root_window);
diff --git a/build/config/compiler/BUILD.gn b/build/config/compiler/BUILD.gn
index d272842..50be57f 100644
--- a/build/config/compiler/BUILD.gn
+++ b/build/config/compiler/BUILD.gn
@@ -137,6 +137,11 @@
   # needs to be evaluated before enabling it there as well.
   init_stack_vars = !(is_android && is_official_build)
 
+  # Zero init has favorable performance/size tradeoffs for Chrome OS
+  # but was not evaluated for other platforms.
+  # TODO(adriandole) enable for both ash and lacros once lacros updates toolchain.
+  init_stack_vars_zero = is_chromeos_ash
+
   # This argument is to control whether enabling text section splitting in the
   # final binary. When enabled, the separated text sections with prefix
   # '.text.hot', '.text.unlikely', '.text.startup' and '.text.exit' will not be
@@ -2653,12 +2658,7 @@
 config("default_init_stack_vars") {
   cflags = []
   if (init_stack_vars && is_clang && !is_nacl && !using_sanitizer) {
-    if (is_chromeos && !chromeos_is_browser_only) {
-      # TODO(adriandole) remove chromeos_is_browser_only condition
-      # once lacros updates toolchain
-
-      # Zero init has favorable performance/size tradeoffs for Chrome OS
-      # but was not evaluated for other platforms.
+    if (init_stack_vars_zero) {
       cflags += [ "-ftrivial-auto-var-init=zero" ]
     } else {
       cflags += [ "-ftrivial-auto-var-init=pattern" ]
diff --git a/build/fuchsia/linux_internal.sdk.sha1 b/build/fuchsia/linux_internal.sdk.sha1
index 80e2d539..84c24cce 100644
--- a/build/fuchsia/linux_internal.sdk.sha1
+++ b/build/fuchsia/linux_internal.sdk.sha1
@@ -1 +1 @@
-11.20221222.0.1
+11.20221222.1.1
diff --git a/chrome/android/chrome_java_resources.gni b/chrome/android/chrome_java_resources.gni
index e25e5f1..9a6a141 100644
--- a/chrome/android/chrome_java_resources.gni
+++ b/chrome/android/chrome_java_resources.gni
@@ -620,14 +620,12 @@
   "java/res/values-night/dimens.xml",
   "java/res/values-night/drawables.xml",
   "java/res/values-night/values.xml",
-  "java/res/values-sw600dp-v17/styles.xml",
   "java/res/values-sw600dp-v26/styles.xml",
   "java/res/values-sw600dp-v27/styles.xml",
   "java/res/values-sw600dp/dimens.xml",
   "java/res/values-sw600dp/drawables.xml",
+  "java/res/values-sw600dp/styles.xml",
   "java/res/values-sw600dp/values.xml",
-  "java/res/values-v21/styles.xml",
-  "java/res/values-v24/styles.xml",
   "java/res/values-v25/styles.xml",
   "java/res/values-v26/styles.xml",
   "java/res/values-v27/styles.xml",
diff --git a/chrome/android/features/tab_ui/BUILD.gn b/chrome/android/features/tab_ui/BUILD.gn
index f9bf3fcb..4c508c2 100644
--- a/chrome/android/features/tab_ui/BUILD.gn
+++ b/chrome/android/features/tab_ui/BUILD.gn
@@ -68,7 +68,6 @@
     "java/res/layout/price_card.xml",
     "java/res/layout/selectable_tab_grid_card_item.xml",
     "java/res/layout/selectable_tab_list_card_item.xml",
-    "java/res/layout/store_hours_card.xml",
     "java/res/layout/tab_grid_card_item.xml",
     "java/res/layout/tab_grid_dialog_layout.xml",
     "java/res/layout/tab_grid_message_card_item.xml",
@@ -114,7 +113,6 @@
     "java/src/org/chromium/chrome/browser/tasks/tab_management/PriceMessageCardViewModel.java",
     "java/src/org/chromium/chrome/browser/tasks/tab_management/PriceMessageService.java",
     "java/src/org/chromium/chrome/browser/tasks/tab_management/SelectableTabGridView.java",
-    "java/src/org/chromium/chrome/browser/tasks/tab_management/StoreHoursCardView.java",
     "java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogCoordinator.java",
     "java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogMediator.java",
     "java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogMenuCoordinator.java",
diff --git a/chrome/android/features/tab_ui/java/res/layout/store_hours_card.xml b/chrome/android/features/tab_ui/java/res/layout/store_hours_card.xml
deleted file mode 100644
index 9528fe7..0000000
--- a/chrome/android/features/tab_ui/java/res/layout/store_hours_card.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-Copyright 2021 The Chromium Authors
-Use of this source code is governed by a BSD-style license that can be
-found in the LICENSE file.
--->
-<merge xmlns:android="http://schemas.android.com/apk/res/android"
-       xmlns:app="http://schemas.android.com/apk/res-auto">
-    <LinearLayout
-        android:id="@+id/store_hours_box"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_marginTop="4dp"
-        android:layout_marginStart="4dp"
-        android:orientation="horizontal"
-        android:background="@drawable/price_card_background">
-        <TextView
-            android:id="@+id/store_hours"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_marginTop="4dp"
-            android:layout_marginStart="10dp"
-            android:layout_marginEnd="4dp"
-            android:layout_marginBottom="4dp"
-            android:singleLine="true"
-            android:textAppearance="@style/TextAppearance.TextMediumThick.Green.Dark"
-            android:textAlignment="viewStart" />
-    </LinearLayout>
-</merge>
\ No newline at end of file
diff --git a/chrome/android/features/tab_ui/java/res/layout/tab_grid_card_item.xml b/chrome/android/features/tab_ui/java/res/layout/tab_grid_card_item.xml
index a2625a0..12d323b 100644
--- a/chrome/android/features/tab_ui/java/res/layout/tab_grid_card_item.xml
+++ b/chrome/android/features/tab_ui/java/res/layout/tab_grid_card_item.xml
@@ -79,14 +79,6 @@
                 android:layout_marginStart="4dp"
                 android:layout_marginEnd="4dp"
                 android:visibility="gone"/>
-            <!-- TODO(crbug/1198288): Make decision on duplication of PriceCardView code.-->
-            <org.chromium.chrome.browser.tasks.tab_management.StoreHoursCardView
-                android:id="@+id/store_hours_box_outer"
-                android:layout_below="@id/tab_title"
-                android:background="@drawable/price_card_scrim"
-                android:layout_width="match_parent"
-                android:layout_height="56dp"
-                android:visibility="gone"/>
             <org.chromium.ui.widget.ButtonCompat
                 android:id="@+id/create_group_button"
                 android:layout_width="wrap_content"
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/StoreHoursCardView.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/StoreHoursCardView.java
deleted file mode 100644
index 366a2370..0000000
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/StoreHoursCardView.java
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright 2021 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-package org.chromium.chrome.browser.tasks.tab_management;
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.LayoutInflater;
-import android.widget.FrameLayout;
-import android.widget.TextView;
-
-import org.chromium.chrome.tab_ui.R;
-
-/**
- * Contains store opening and closing times for physical store website.
- */
-public class StoreHoursCardView extends FrameLayout {
-    private TextView mStoreHoursBox;
-    public StoreHoursCardView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    /**
-     * Sets the store hours.
-     */
-    public void setStoreHours(String storeHours) {
-        mStoreHoursBox.setText(storeHours);
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-        LayoutInflater.from(getContext()).inflate(R.layout.store_hours_card, this);
-        mStoreHoursBox = (TextView) findViewById(R.id.store_hours);
-    }
-}
\ No newline at end of file
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/StoreTrackingUtilities.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/StoreTrackingUtilities.java
deleted file mode 100644
index c7b58093..0000000
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/StoreTrackingUtilities.java
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright 2021 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.tasks.tab_management;
-import org.chromium.chrome.browser.flags.ChromeFeatureList;
-import org.chromium.chrome.browser.price_tracking.PriceTrackingUtilities;
-
-/**
- * A class to handle whether store hours feature is enabled.
- */
-public class StoreTrackingUtilities {
-    /**
-     * @return Whether the show store hours on tabs feature is enabled.
-     */
-    public static boolean isStoreHoursOnTabsEnabled() {
-        return ChromeFeatureList.sStoreHoursAndroid.isEnabled()
-                && !PriceTrackingUtilities.isTrackPricesOnTabsEnabled();
-    }
-}
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridViewBinder.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridViewBinder.java
index 7c405e4..2db0fb7b 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridViewBinder.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridViewBinder.java
@@ -9,7 +9,6 @@
 import android.content.res.ColorStateList;
 import android.graphics.Bitmap;
 import android.graphics.Matrix;
-import android.text.TextUtils;
 import android.util.Size;
 import android.view.View;
 import android.view.ViewGroup;
@@ -228,25 +227,6 @@
                             }
                         });
             }, false);
-        } else if (TabProperties.STORE_PERSISTED_TAB_DATA_FETCHER == propertyKey) {
-            StoreHoursCardView storeHoursCardView =
-                    (StoreHoursCardView) view.fastFindViewById(R.id.store_hours_box_outer);
-            if (model.get(TabProperties.STORE_PERSISTED_TAB_DATA_FETCHER) != null) {
-                model.get(TabProperties.STORE_PERSISTED_TAB_DATA_FETCHER)
-                        .fetch((storePersistedTabData) -> {
-                            if (storePersistedTabData == null
-                                    || TextUtils.isEmpty(
-                                            storePersistedTabData.getStoreHoursString())) {
-                                storeHoursCardView.setVisibility(View.GONE);
-                            } else {
-                                storeHoursCardView.setStoreHours(
-                                        storePersistedTabData.getStoreHoursString());
-                                storeHoursCardView.setVisibility(View.VISIBLE);
-                            }
-                        });
-            } else {
-                storeHoursCardView.setVisibility(View.GONE);
-            }
         } else if (TabProperties.SHOULD_SHOW_PRICE_DROP_TOOLTIP == propertyKey) {
             if (model.get(TabProperties.SHOULD_SHOW_PRICE_DROP_TOOLTIP)) {
                 PriceCardView priceCardView =
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediator.java
index e333a78..e0b42f7 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediator.java
@@ -55,7 +55,6 @@
 import org.chromium.chrome.browser.tab.TabSelectionType;
 import org.chromium.chrome.browser.tab.state.CouponPersistedTabData;
 import org.chromium.chrome.browser.tab.state.ShoppingPersistedTabData;
-import org.chromium.chrome.browser.tab.state.StorePersistedTabData;
 import org.chromium.chrome.browser.tabmodel.EmptyTabModelFilter;
 import org.chromium.chrome.browser.tabmodel.TabList;
 import org.chromium.chrome.browser.tabmodel.TabModel;
@@ -223,28 +222,6 @@
     }
 
     /**
-     * Provides capability to asynchronously acquire {@link StorePersistedTabData}
-     */
-    static class StorePersistedTabDataFetcher {
-        protected Tab mTab;
-
-        /**
-         * @param tab {@link Tab} {@link StorePersistedTabData} will be acquired for.
-         */
-        StorePersistedTabDataFetcher(Tab tab) {
-            mTab = tab;
-        }
-
-        /**
-         * Asynchronously acquire {@link StorePersistedTabData}
-         * @param callback {@link Callback} to pass {@link StorePersistedTabData} back in
-         */
-        public void fetch(Callback<StorePersistedTabData> callback) {
-            StorePersistedTabData.from(mTab, (res) -> { callback.onResult(res); });
-        }
-    }
-
-    /**
      * Asynchronously acquire {@link CouponPersistedTabData}
      */
     static class CouponPersistedTabDataFetcher {
@@ -1827,13 +1804,6 @@
                 mModel.get(index).model.set(
                         TabProperties.SHOPPING_PERSISTED_TAB_DATA_FETCHER, null);
             }
-            if (StoreTrackingUtilities.isStoreHoursOnTabsEnabled()
-                    && isUngroupedTab(pseudoTab.getId())) {
-                mModel.get(index).model.set(TabProperties.STORE_PERSISTED_TAB_DATA_FETCHER,
-                        new StorePersistedTabDataFetcher(pseudoTab.getTab()));
-            } else {
-                mModel.get(index).model.set(TabProperties.STORE_PERSISTED_TAB_DATA_FETCHER, null);
-            }
             if (CouponUtilities.isCouponsOnTabsEnabled() && isUngroupedTab(pseudoTab.getId())) {
                 mModel.get(index).model.set(TabProperties.COUPON_PERSISTED_TAB_DATA_FETCHER,
                         new CouponPersistedTabDataFetcher(pseudoTab.getTab()));
@@ -1843,7 +1813,6 @@
         } else {
             mModel.get(index).model.set(TabProperties.COUPON_PERSISTED_TAB_DATA_FETCHER, null);
             mModel.get(index).model.set(TabProperties.SHOPPING_PERSISTED_TAB_DATA_FETCHER, null);
-            mModel.get(index).model.set(TabProperties.STORE_PERSISTED_TAB_DATA_FETCHER, null);
         }
     }
 
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabProperties.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabProperties.java
index 38d7209..5c73bfc 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabProperties.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabProperties.java
@@ -107,9 +107,6 @@
     public static final WritableObjectPropertyKey<TabListMediator.ShoppingPersistedTabDataFetcher>
             SHOPPING_PERSISTED_TAB_DATA_FETCHER = new WritableObjectPropertyKey<>(true);
 
-    public static final WritableObjectPropertyKey<TabListMediator.StorePersistedTabDataFetcher>
-            STORE_PERSISTED_TAB_DATA_FETCHER = new WritableObjectPropertyKey<>(true);
-
     public static final WritableObjectPropertyKey<TabListMediator.CouponPersistedTabDataFetcher>
             COUPON_PERSISTED_TAB_DATA_FETCHER = new WritableObjectPropertyKey<>(true);
 
@@ -129,8 +126,8 @@
             TABSTRIP_FAVICON_BACKGROUND_COLOR_ID, SELECTABLE_TAB_ACTION_BUTTON_BACKGROUND,
             SELECTABLE_TAB_ACTION_BUTTON_SELECTED_BACKGROUND, URL_DOMAIN, ACCESSIBILITY_DELEGATE,
             CARD_TYPE, CONTENT_DESCRIPTION_STRING, CLOSE_BUTTON_DESCRIPTION_STRING,
-            SHOPPING_PERSISTED_TAB_DATA_FETCHER, STORE_PERSISTED_TAB_DATA_FETCHER,
-            COUPON_PERSISTED_TAB_DATA_FETCHER, SHOULD_SHOW_PRICE_DROP_TOOLTIP};
+            SHOPPING_PERSISTED_TAB_DATA_FETCHER, COUPON_PERSISTED_TAB_DATA_FETCHER,
+            SHOULD_SHOW_PRICE_DROP_TOOLTIP};
 
     public static final PropertyKey[] ALL_KEYS_TAB_STRIP =
             new PropertyKey[] {TAB_ID, TAB_SELECTED_LISTENER, TAB_CLOSED_LISTENER, FAVICON,
diff --git a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediatorUnitTest.java b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediatorUnitTest.java
index 62da69c..a518f6db 100644
--- a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediatorUnitTest.java
+++ b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediatorUnitTest.java
@@ -1424,71 +1424,6 @@
         setUpForTabGroupOperation(TabListMediatorType.TAB_SWITCHER, TabListMode.GRID);
     }
 
-    @Test
-    public void testStoreHoursFetcherActiveForForUngroupedTabs() {
-        prepareForStoreHours();
-        resetWithRegularTabs(false);
-
-        assertThat(mModel.size(), equalTo(2));
-        assertThat(mModel.get(0).model.get(TabProperties.STORE_PERSISTED_TAB_DATA_FETCHER),
-                instanceOf(TabListMediator.StorePersistedTabDataFetcher.class));
-        assertThat(mModel.get(1).model.get(TabProperties.STORE_PERSISTED_TAB_DATA_FETCHER),
-                instanceOf(TabListMediator.StorePersistedTabDataFetcher.class));
-    }
-
-    @Test
-    public void testStoreHoursFetcherInactiveForForGroupedTabs() {
-        prepareForStoreHours();
-        resetWithRegularTabs(true);
-
-        assertThat(mModel.size(), equalTo(2));
-        assertNull(mModel.get(0).model.get(TabProperties.STORE_PERSISTED_TAB_DATA_FETCHER));
-        assertNull(mModel.get(1).model.get(TabProperties.STORE_PERSISTED_TAB_DATA_FETCHER));
-    }
-
-    @Test
-    public void testStoreHoursFetcherGroupedThenUngrouped() {
-        prepareForStoreHours();
-        resetWithRegularTabs(true);
-
-        assertThat(mModel.size(), equalTo(2));
-        assertNull(mModel.get(0).model.get(TabProperties.STORE_PERSISTED_TAB_DATA_FETCHER));
-        assertNull(mModel.get(1).model.get(TabProperties.STORE_PERSISTED_TAB_DATA_FETCHER));
-        resetWithRegularTabs(false);
-        assertThat(mModel.size(), equalTo(2));
-        assertThat(mModel.get(0).model.get(TabProperties.STORE_PERSISTED_TAB_DATA_FETCHER),
-                instanceOf(TabListMediator.StorePersistedTabDataFetcher.class));
-        assertThat(mModel.get(1).model.get(TabProperties.STORE_PERSISTED_TAB_DATA_FETCHER),
-                instanceOf(TabListMediator.StorePersistedTabDataFetcher.class));
-    }
-
-    @Test
-    public void testStoreHoursFetcherUngroupedThenGrouped() {
-        prepareForStoreHours();
-        resetWithRegularTabs(false);
-
-        assertThat(mModel.size(), equalTo(2));
-        assertThat(mModel.get(0).model.get(TabProperties.STORE_PERSISTED_TAB_DATA_FETCHER),
-                instanceOf(TabListMediator.StorePersistedTabDataFetcher.class));
-        assertThat(mModel.get(1).model.get(TabProperties.STORE_PERSISTED_TAB_DATA_FETCHER),
-                instanceOf(TabListMediator.StorePersistedTabDataFetcher.class));
-        resetWithRegularTabs(true);
-        assertThat(mModel.size(), equalTo(2));
-        assertNull(mModel.get(0).model.get(TabProperties.STORE_PERSISTED_TAB_DATA_FETCHER));
-        assertNull(mModel.get(1).model.get(TabProperties.STORE_PERSISTED_TAB_DATA_FETCHER));
-    }
-
-    /**
-     * Set flags and initialize for verifying store hours behavior
-     */
-    private void prepareForStoreHours() {
-        ChromeFeatureList.sStoreHoursAndroid.setForTesting(true);
-        PriceTrackingFeatures.setIsSignedInAndSyncEnabledForTesting(true);
-        PersistedTabDataConfiguration.setUseTestConfig(true);
-        initAndAssertAllProperties();
-        setUpForTabGroupOperation(TabListMediatorType.TAB_SWITCHER, TabListMode.GRID);
-    }
-
     /**
      * Reset mediator with non-incognito tabs which are optionally grouped
      * @param isGrouped true if the tabs should be grouped
diff --git a/chrome/android/features/tab_ui/tab_management_java_sources.gni b/chrome/android/features/tab_ui/tab_management_java_sources.gni
index e6dff07..7df1d2c 100644
--- a/chrome/android/features/tab_ui/tab_management_java_sources.gni
+++ b/chrome/android/features/tab_ui/tab_management_java_sources.gni
@@ -7,7 +7,6 @@
   "//chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/pseudotab/TabAttributeCache.java",
   "//chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/CloseAllTabsDialog.java",
   "//chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/CouponUtilities.java",
-  "//chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/StoreTrackingUtilities.java",
   "//chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUi.java",
   "//chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListFaviconProvider.java",
   "//chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabManagementDelegate.java",
diff --git a/chrome/android/java/res/values-sw600dp-v17/styles.xml b/chrome/android/java/res/values-sw600dp/styles.xml
similarity index 100%
rename from chrome/android/java/res/values-sw600dp-v17/styles.xml
rename to chrome/android/java/res/values-sw600dp/styles.xml
diff --git a/chrome/android/java/res/values-v21/styles.xml b/chrome/android/java/res/values-v21/styles.xml
deleted file mode 100644
index 87fcd92..0000000
--- a/chrome/android/java/res/values-v21/styles.xml
+++ /dev/null
@@ -1,55 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-Copyright 2014 The Chromium Authors
-Use of this source code is governed by a BSD-style license that can be
-found in the LICENSE file.
--->
-
-<resources>
-    <style name="NavigationPopupDialog" parent="Widget.AppCompat.Light.ListPopupWindow">
-        <item name="android:popupElevation">0dp</item>
-    </style>
-
-    <style name="Base.V21.Theme.Chromium" parent="Base.V17.Theme.Chromium">
-        <!-- Set android alert dialog attributes because the context menu dialog is
-             OS-dependent. Not setting alertDialogTheme pre-v21 because the window background
-             causes bad visual states with alert dialog list. -->
-        <item name="android:alertDialogTheme">@style/ThemeOverlay.BrowserUI.AlertDialog</item>
-    </style>
-    <style name="Base.Theme.Chromium" parent="Base.V21.Theme.Chromium" />
-
-    <!-- Theme variation for low-end devices.
-
-        Since ChromeActivities are not HW accelerated, they don't get fancy
-        material shadows for popups. This theme sets drawable with pre-baked
-        shadows to those popups to make them look better.
-        '*_bg_tinted' are a 9-patchs similar to 'abc_popup_background_mtrl_mult'
-        drawable from Android support library, where it's used to simulate
-        material design on earlier Android versions.
-    -->
-    <style name="ListPopupWindow.LowEnd" parent="android:Widget.Material.Light.ListPopupWindow">
-        <item name="android:popupBackground">@drawable/menu_bg_tinted</item>
-    </style>
-    <style name="Spinner.LowEnd" parent="android:Widget.Material.Light.Spinner">
-        <item name="android:popupBackground">@drawable/dialog_bg_tinted</item>
-    </style>
-    <style name="AutoCompleteTextView.LowEnd" parent="android:Widget.Material.Light.AutoCompleteTextView">
-        <item name="android:popupBackground">@drawable/menu_bg_tinted</item>
-    </style>
-    <style name="Theme.Chromium.WithWindowAnimation.LowEnd"
-        parent="Theme.Chromium.WithWindowAnimation">
-        <item name="android:popupWindowStyle">@style/ListPopupWindow.LowEnd</item>
-        <item name="android:listPopupWindowStyle">@style/ListPopupWindow.LowEnd</item>
-        <item name="android:spinnerStyle">@style/Spinner.LowEnd</item>
-        <item name="android:autoCompleteTextViewStyle">@style/AutoCompleteTextView.LowEnd</item>
-
-        <!-- Without HW acceleration the default text cursor looks weird (top
-             half is way brighter than the bottom), so we use our own. -->
-        <item name="android:textCursorDrawable">@drawable/text_cursor_lowend</item>
-    </style>
-
-    <!-- Match the fontFamily in ui/android/java/res/values-v21/styles.xml -->
-    <style name="TextAppearance.FreFirstFrameButton" parent="TextAppearance.Button.Text.Filled">
-        <item name="android:fontFamily">sans-serif-medium</item>
-    </style>
-  </resources>
diff --git a/chrome/android/java/res/values-v24/styles.xml b/chrome/android/java/res/values-v24/styles.xml
deleted file mode 100644
index 7d495bf1..0000000
--- a/chrome/android/java/res/values-v24/styles.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-Copyright 2016 The Chromium Authors
-Use of this source code is governed by a BSD-style license that can be
-found in the LICENSE file.
--->
-
-<resources xmlns:tools="http://schemas.android.com/tools">
-    <!-- On Android N, if the ChromeLauncherActivity is NoDisplay then
-         intents are not always immediately sent when Chrome is docked to
-         the top of the screen in multi-window mode. Use Translucent.NoTitleBar
-         for the theme instead. See crbug.com/645074. -->
-    <style name="LauncherTheme" parent="Theme.BrowserUI.Translucent.NoTitleBar" />
-</resources>
diff --git a/chrome/android/java/res/values/styles.xml b/chrome/android/java/res/values/styles.xml
index 065f03a..2acd008 100644
--- a/chrome/android/java/res/values/styles.xml
+++ b/chrome/android/java/res/values/styles.xml
@@ -8,10 +8,18 @@
 <resources xmlns:tools="http://schemas.android.com/tools">
     <!-- TODO(huayinz): Move themes to another xml file. -->
     <!-- Application themes -->
-    <style name="LauncherTheme" parent="Theme.BrowserUI.NoDisplay" />
+    <!-- On Android N, if the ChromeLauncherActivity is NoDisplay then
+         intents are not always immediately sent when Chrome is docked to
+         the top of the screen in multi-window mode. Use Translucent.NoTitleBar
+         for the theme instead. See crbug.com/645074. -->
+    <style name="LauncherTheme" parent="Theme.BrowserUI.Translucent.NoTitleBar" />
 
-    <style name="Base.V17.Theme.Chromium" parent="Theme.BrowserUI.DayNight" />
-    <style name="Base.Theme.Chromium" parent="Base.V17.Theme.Chromium" />
+    <style name="Base.V21.Theme.Chromium" parent="Theme.BrowserUI.DayNight">
+        <!-- Set android alert dialog attributes because the context menu dialog is
+             OS-dependent. -->
+        <item name="android:alertDialogTheme">@style/ThemeOverlay.BrowserUI.AlertDialog</item>
+    </style>
+    <style name="Base.Theme.Chromium" parent="Base.V21.Theme.Chromium" />
 
     <style name="Base.Theme.Chromium.WithWindowAnimation">
         <item name="android:windowContentOverlay">@null</item>
@@ -130,6 +138,37 @@
         <item name="android:windowLightNavigationBar" tools:targetApi="28">false</item>
     </style>
 
+    <!-- Theme variation for low-end devices.
+
+        Since ChromeActivities are not HW accelerated, they don't get fancy
+        material shadows for popups. This theme sets drawable with pre-baked
+        shadows to those popups to make them look better.
+        '*_bg_tinted' are a 9-patchs similar to 'abc_popup_background_mtrl_mult'
+        drawable from Android support library, where it's used to simulate
+        material design on earlier Android versions.
+    -->
+    <style name="ListPopupWindow.LowEnd" parent="android:Widget.Material.Light.ListPopupWindow">
+        <item name="android:popupBackground">@drawable/menu_bg_tinted</item>
+    </style>
+    <style name="Spinner.LowEnd" parent="android:Widget.Material.Light.Spinner">
+        <item name="android:popupBackground">@drawable/dialog_bg_tinted</item>
+    </style>
+    <style name="AutoCompleteTextView.LowEnd" parent="android:Widget.Material.Light.AutoCompleteTextView">
+        <item name="android:popupBackground">@drawable/menu_bg_tinted</item>
+    </style>
+    <style name="Theme.Chromium.WithWindowAnimation.LowEnd"
+        parent="Theme.Chromium.WithWindowAnimation">
+        <item name="android:popupWindowStyle">@style/ListPopupWindow.LowEnd</item>
+        <item name="android:listPopupWindowStyle">@style/ListPopupWindow.LowEnd</item>
+        <item name="android:spinnerStyle">@style/Spinner.LowEnd</item>
+        <item name="android:autoCompleteTextViewStyle">@style/AutoCompleteTextView.LowEnd</item>
+
+        <!-- Without HW acceleration the default text cursor looks weird (top
+             half is way brighter than the bottom), so we use our own. -->
+        <item name="android:textCursorDrawable">@drawable/text_cursor_lowend</item>
+    </style>
+
+
     <!-- Settings -->
     <style name="SettingsToolbarStyle" parent="Widget.MaterialComponents.Toolbar">
         <item name="titleTextAppearance">@style/TextAppearance.Headline.Primary</item>
@@ -198,8 +237,9 @@
     <style name="TextAppearance.FreFirstFrameTitle" parent="TextAppearance.Headline.Primary" >
         <item name="android:fontFamily">sans-serif</item>
     </style>
+    <!-- Match the fontFamily in ui/android/java/res/values/styles.xml -->
     <style name="TextAppearance.FreFirstFrameButton" parent="TextAppearance.Button.Text.Filled">
-        <item name="android:fontFamily">sans-serif</item>
+        <item name="android:fontFamily">sans-serif-medium</item>
     </style>
     <style name="FreTitle">
         <item name="android:layout_width">wrap_content</item>
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/browserservices/ui/splashscreen/SplashController.java b/chrome/android/java/src/org/chromium/chrome/browser/browserservices/ui/splashscreen/SplashController.java
index 7729b3a..429704a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/browserservices/ui/splashscreen/SplashController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/browserservices/ui/splashscreen/SplashController.java
@@ -6,7 +6,6 @@
 
 import android.app.Activity;
 import android.graphics.PixelFormat;
-import android.os.Build;
 import android.os.SystemClock;
 import android.view.View;
 import android.view.ViewGroup;
@@ -283,8 +282,7 @@
         // we also see visual glitches in the following cases:
         // - closing activity (example: https://crbug.com/856544#c41)
         // - send activity to the background (example: https://crbug.com/856544#c30)
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
-                && ChromeFeatureList.sSwapPixelFormatToFixConvertFromTranslucent.isEnabled()) {
+        if (ChromeFeatureList.sSwapPixelFormatToFixConvertFromTranslucent.isEnabled()) {
             return TranslucencyRemoval.ON_SPLASH_HIDDEN;
         }
         return TranslucencyRemoval.ON_SPLASH_SHOWN;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/CompositorViewHolder.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/CompositorViewHolder.java
index 351e3d2..913cd8ab 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/CompositorViewHolder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/CompositorViewHolder.java
@@ -286,7 +286,6 @@
 
     @Override
     public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
-        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) return null;
         View activeView = getContentView();
         if (activeView == null || !ViewCompat.isAttachedToWindow(activeView)) return null;
         return ApiHelperForN.onResolvePointerIcon(activeView, event, pointerIndex);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/datareduction/DataSaverOSSetting.java b/chrome/android/java/src/org/chromium/chrome/browser/datareduction/DataSaverOSSetting.java
index 172973ab..ada82f5 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/datareduction/DataSaverOSSetting.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/datareduction/DataSaverOSSetting.java
@@ -6,7 +6,6 @@
 
 import android.content.Context;
 import android.net.ConnectivityManager;
-import android.os.Build;
 
 import org.chromium.base.ContextUtils;
 import org.chromium.base.annotations.CalledByNative;
@@ -23,7 +22,7 @@
         Context context = ContextUtils.getApplicationContext();
         ConnectivityManager connMgr =
                 (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
-        if (connMgr.isActiveNetworkMetered() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+        if (connMgr.isActiveNetworkMetered()) {
             return ApiHelperForN.getRestrictBackgroundStatus(connMgr)
                     == ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED;
         }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/incognito/IncognitoNotificationManager.java b/chrome/android/java/src/org/chromium/chrome/browser/incognito/IncognitoNotificationManager.java
index de91a6c..b6a9414 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/incognito/IncognitoNotificationManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/incognito/IncognitoNotificationManager.java
@@ -7,7 +7,6 @@
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.content.Context;
-import android.os.Build;
 
 import org.chromium.base.ContextUtils;
 import org.chromium.chrome.R;
@@ -38,9 +37,8 @@
 
         // From Android N, notification by default has the app name and title should not be the same
         // as app name.
-        String title = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
-                ? context.getResources().getString(R.string.close_all_incognito_notification_title)
-                : context.getResources().getString(R.string.app_name);
+        String title =
+                context.getResources().getString(R.string.close_all_incognito_notification_title);
 
         NotificationWrapperBuilder builder =
                 NotificationWrapperBuilderFactory
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/init/ChromeBrowserInitializer.java b/chrome/android/java/src/org/chromium/chrome/browser/init/ChromeBrowserInitializer.java
index ac87ade0..235072ba 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/init/ChromeBrowserInitializer.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/init/ChromeBrowserInitializer.java
@@ -5,7 +5,6 @@
 package org.chromium.chrome.browser.init;
 
 import android.app.Activity;
-import android.os.Build;
 import android.os.Process;
 import android.os.StrictMode;
 
@@ -199,15 +198,10 @@
      * Running in an AsyncTask as pre-loading itself may cause I/O.
      */
     private void warmUpSharedPrefs() {
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
-            PostTask.postTask(TaskTraits.BEST_EFFORT_MAY_BLOCK, () -> {
-                DownloadManagerService.warmUpSharedPrefs();
-                BackgroundTaskSchedulerFactory.warmUpSharedPrefs();
-            });
-        } else {
+        PostTask.postTask(TaskTraits.BEST_EFFORT_MAY_BLOCK, () -> {
             DownloadManagerService.warmUpSharedPrefs();
             BackgroundTaskSchedulerFactory.warmUpSharedPrefs();
-        }
+        });
     }
 
     private void preInflationStartup() {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/MediaViewerUtils.java b/chrome/android/java/src/org/chromium/chrome/browser/media/MediaViewerUtils.java
index f9871e1..c2e26225 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/media/MediaViewerUtils.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/media/MediaViewerUtils.java
@@ -15,7 +15,6 @@
 import android.graphics.BitmapFactory;
 import android.graphics.Color;
 import android.net.Uri;
-import android.os.Build;
 import android.provider.Browser;
 import android.text.TextUtils;
 
@@ -274,9 +273,6 @@
 
     private static boolean willExposeFileUri(Uri uri) {
         assert uri != null && !uri.equals(Uri.EMPTY) : "URI is not successfully generated.";
-
-        // On Android N and later, an Exception is thrown if we try to expose a file:// URI.
-        return uri.getScheme().equals(ContentResolver.SCHEME_FILE)
-                && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N;
+        return uri.getScheme().equals(ContentResolver.SCHEME_FILE);
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/RecentTabsRowAdapter.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/RecentTabsRowAdapter.java
index d4d86f1d..61fc6090 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/RecentTabsRowAdapter.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/RecentTabsRowAdapter.java
@@ -10,7 +10,6 @@
 import android.graphics.BitmapFactory;
 import android.graphics.PorterDuff;
 import android.graphics.drawable.Drawable;
-import android.os.Build;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.LruCache;
@@ -954,8 +953,6 @@
      * Retrieves the user's preferred locale from the app's configurations.
      */
     private Locale getPreferredLocale() {
-        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
-                ? mActivity.getResources().getConfiguration().getLocales().get(0)
-                : mActivity.getResources().getConfiguration().locale;
+        return mActivity.getResources().getConfiguration().getLocales().get(0);
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/OfflinePageArchivePublisherBridge.java b/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/OfflinePageArchivePublisherBridge.java
index 9ce821b..b976387 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/OfflinePageArchivePublisherBridge.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/OfflinePageArchivePublisherBridge.java
@@ -14,7 +14,6 @@
 import android.provider.MediaStore.MediaColumns;
 import android.text.format.DateUtils;
 
-import androidx.annotation.RequiresApi;
 import androidx.annotation.VisibleForTesting;
 
 import org.chromium.base.ContentUriUtils;
@@ -69,11 +68,6 @@
     public static long addCompletedDownload(String title, String description, String path,
             long length, String uri, String referer) {
         try {
-            // Call the proper version of the pass through based on the supported API level.
-            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
-                return callAddCompletedDownload(title, description, path, length);
-            }
-
             return callAddCompletedDownload(title, description, path, length, uri, referer);
         } catch (Exception e) {
             // In case of exception, we return a download id of 0.
@@ -82,18 +76,6 @@
         }
     }
 
-    // Use this pass through before API level 24.
-    private static long callAddCompletedDownload(
-            String title, String description, String path, long length) {
-        DownloadManager downloadManager = getDownloadManager();
-        if (downloadManager == null) return 0;
-
-        return downloadManager.addCompletedDownload(title, description, IS_MEDIA_SCANNER_SCANNABLE,
-                MIME_TYPE, path, length, SHOW_NOTIFICATION);
-    }
-
-    // Use this pass through for API levels 24 and higher.
-    @RequiresApi(Build.VERSION_CODES.N)
     private static long callAddCompletedDownload(String title, String description, String path,
             long length, String uri, String referer) {
         DownloadManager downloadManager = getDownloadManager();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/services/gcm/ChromeGcmListenerServiceImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/services/gcm/ChromeGcmListenerServiceImpl.java
index e0cedef..73a22d0 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/services/gcm/ChromeGcmListenerServiceImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/services/gcm/ChromeGcmListenerServiceImpl.java
@@ -6,7 +6,6 @@
 
 import android.content.Context;
 import android.content.Intent;
-import android.os.Build;
 import android.os.Bundle;
 import android.text.TextUtils;
 
@@ -193,12 +192,6 @@
             return;
         }
 
-        // Dispatch message immediately on pre N versions of Android.
-        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
-            dispatchMessageToDriver(message);
-            return;
-        }
-
         // Check if we should bypass the scheduler for high priority messages.
         if (!maybeBypassScheduler(message)) {
             scheduleBackgroundTask(message);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/services/gcm/GCMBackgroundServiceImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/services/gcm/GCMBackgroundServiceImpl.java
index 9de3837..6d3f4eb3 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/services/gcm/GCMBackgroundServiceImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/services/gcm/GCMBackgroundServiceImpl.java
@@ -5,11 +5,8 @@
 package org.chromium.chrome.browser.services.gcm;
 
 import android.content.Intent;
-import android.os.Build;
 import android.os.Bundle;
 
-import androidx.annotation.RequiresApi;
-
 import org.chromium.base.Log;
 import org.chromium.base.task.PostTask;
 import org.chromium.components.gcm_driver.GCMMessage;
@@ -20,7 +17,6 @@
  * if we received a high priority push message, as that should allow us to start a background
  * service even if Chrome is not running.
  */
-@RequiresApi(Build.VERSION_CODES.N)
 public class GCMBackgroundServiceImpl extends GCMBackgroundService.Impl {
     private static final String TAG = "GCMBackgroundService";
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/services/gcm/GCMBackgroundTask.java b/chrome/android/java/src/org/chromium/chrome/browser/services/gcm/GCMBackgroundTask.java
index 33dc0b2..23258aca 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/services/gcm/GCMBackgroundTask.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/services/gcm/GCMBackgroundTask.java
@@ -5,11 +5,9 @@
 package org.chromium.chrome.browser.services.gcm;
 
 import android.content.Context;
-import android.os.Build;
 import android.os.Bundle;
 
 import androidx.annotation.MainThread;
-import androidx.annotation.RequiresApi;
 
 import org.chromium.base.Log;
 import org.chromium.components.background_task_scheduler.BackgroundTask;
@@ -20,7 +18,6 @@
  * Processes jobs that have been scheduled for delivering GCM messages to the native GCM Driver,
  * processing for which may exceed the lifetime of the GcmListenerService.
  */
-@RequiresApi(Build.VERSION_CODES.N)
 public class GCMBackgroundTask implements BackgroundTask {
     private static final String TAG = "GCMBackgroundTask";
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ui/system/StatusBarColorController.java b/chrome/android/java/src/org/chromium/chrome/browser/ui/system/StatusBarColorController.java
index b3c5630..d4f57d0f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ui/system/StatusBarColorController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ui/system/StatusBarColorController.java
@@ -210,9 +210,7 @@
                             return;
                         }
                         mIsInOverviewMode = true;
-                        if (!OmniboxFeatures.shouldMatchToolbarAndStatusBarColor()) {
-                            updateStatusBarColor();
-                        }
+                        updateStatusBarColor();
                     }
 
                     @Override
@@ -286,6 +284,7 @@
     }
 
     // StatusIndicatorCoordinator.StatusIndicatorObserver implementation.
+
     @Override
     public void onStatusIndicatorColorChanged(@ColorInt int newColor) {
         mStatusIndicatorColor = newColor;
@@ -381,12 +380,6 @@
 
         // Return status bar color in overview mode.
         if (mIsInOverviewMode) {
-            // Toolbar will notify status bar color controller about the toolbar color during
-            // overview animation.
-            if (OmniboxFeatures.shouldMatchToolbarAndStatusBarColor()) {
-                return mToolbarColor;
-            }
-
             return (mIsIncognito
                            && ToolbarColors.canUseIncognitoToolbarThemeColorInOverview(
                                    mWindow.getContext()))
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkIntentDataProviderFactory.java b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkIntentDataProviderFactory.java
index 1b023056..e1d4646 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkIntentDataProviderFactory.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkIntentDataProviderFactory.java
@@ -338,8 +338,7 @@
         }
 
         boolean isSplashProvidedByWebApk =
-                (canUseSplashFromContentProvider && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
-                        && hasContentProviderForSplash(webApkPackageName));
+                (canUseSplashFromContentProvider && hasContentProviderForSplash(webApkPackageName));
 
         return create(intent, url, scope,
                 new WebappIcon(webApkPackageName,
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManagerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManagerTest.java
index e1fcc94f..becc710 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManagerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManagerTest.java
@@ -43,7 +43,6 @@
 import org.chromium.base.test.util.DisabledTest;
 import org.chromium.base.test.util.Feature;
 import org.chromium.base.test.util.MaxAndroidSdkLevel;
-import org.chromium.base.test.util.MinAndroidSdkLevel;
 import org.chromium.base.test.util.Restriction;
 import org.chromium.base.test.util.UserActionTester;
 import org.chromium.chrome.R;
@@ -895,7 +894,6 @@
     @LargeTest
     @Feature({"ContextualSearch"})
     @CommandLineFlags.Add(ChromeSwitches.DISABLE_TAB_MERGING_FOR_TESTING)
-    @MinAndroidSdkLevel(Build.VERSION_CODES.N)
     @MaxAndroidSdkLevel(value = Build.VERSION_CODES.R, reason = "crbug.com/1301017")
     @ParameterAnnotations.UseMethodParameter(FeatureParamProvider.class)
     public void testTabReparenting(@EnabledFeature int enabledFeature) throws Exception {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchTriggerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchTriggerTest.java
index 06a1aff..8b2b5237 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchTriggerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchTriggerTest.java
@@ -6,7 +6,6 @@
 
 import static org.chromium.base.test.util.Restriction.RESTRICTION_TYPE_NON_LOW_END_DEVICE;
 
-import android.os.Build;
 import android.text.TextUtils;
 import android.view.ViewConfiguration;
 
@@ -22,7 +21,6 @@
 import org.chromium.base.test.params.ParameterizedRunner;
 import org.chromium.base.test.util.Batch;
 import org.chromium.base.test.util.CommandLineFlags;
-import org.chromium.base.test.util.DisableIf;
 import org.chromium.base.test.util.DisabledTest;
 import org.chromium.base.test.util.Feature;
 import org.chromium.base.test.util.Restriction;
@@ -148,8 +146,7 @@
      * Tests that a Tap gesture followed by scrolling clears the selection.
      */
     @Test
-    @DisableIf.
-    Build(sdk_is_greater_than = Build.VERSION_CODES.LOLLIPOP, message = "crbug.com/841017")
+    @DisabledTest(message = "crbug.com/841017")
     @SmallTest
     @Feature({"ContextualSearch"})
     public void testTapGestureFollowedByScrollClearsSelection() throws Exception {
@@ -256,11 +253,9 @@
     @SmallTest
     @Feature({"ContextualSearch"})
     @ParameterAnnotations.UseMethodParameter(FeatureParamProvider.class)
-    @DisableIf.Build(sdk_is_greater_than = Build.VERSION_CODES.M,
-            message = "crbug.com/1071080, crbug.com/1362185")
-    public void
-    testLongPressGestureFollowedByScrollMaintainsSelection(@EnabledFeature int enabledFeature)
-            throws Exception {
+    @DisabledTest(message = "crbug.com/1071080, crbug.com/1362185")
+    public void testLongPressGestureFollowedByScrollMaintainsSelection(
+            @EnabledFeature int enabledFeature) throws Exception {
         longPressNode("intelligence");
         waitForPanelToPeek();
         scrollBasePage();
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/ui/system/StatusBarColorControllerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/ui/system/StatusBarColorControllerTest.java
index 99ee0b4..91ef262 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/ui/system/StatusBarColorControllerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/ui/system/StatusBarColorControllerTest.java
@@ -389,7 +389,8 @@
                 "ToolbarLayout should be of type ToolbarPhone to get and check toolbar background.",
                 toolbar instanceof ToolbarPhone);
 
-        final int toolbarColor = ((ToolbarPhone) toolbar).getToolbarBackgroundColor();
+        final int toolbarColor =
+                ((ToolbarPhone) toolbar).getToolbarBackgroundColorForTesting(activity);
         CriteriaHelper.pollUiThread(() -> {
             Criteria.checkThat(activity.getWindow().getStatusBarColor(), Matchers.is(toolbarColor));
         }, CriteriaHelper.DEFAULT_MAX_TIME_TO_POLL, CriteriaHelper.DEFAULT_POLLING_INTERVAL);
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/media/ui/MediaNotificationManagerNotificationTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/media/ui/MediaNotificationManagerNotificationTest.java
index b5b35b4..34758ec2 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/media/ui/MediaNotificationManagerNotificationTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/media/ui/MediaNotificationManagerNotificationTest.java
@@ -26,8 +26,6 @@
 import org.robolectric.shadows.ShadowNotification;
 
 import org.chromium.base.test.BaseRobolectricTestRunner;
-import org.chromium.chrome.R;
-import org.chromium.components.browser_ui.media.MediaNotificationController;
 import org.chromium.components.browser_ui.media.MediaNotificationInfo;
 import org.chromium.services.media_session.MediaMetadata;
 import org.chromium.services.media_session.MediaPosition;
@@ -43,62 +41,7 @@
         sdk = Build.VERSION_CODES.N_MR1, shadows = MediaNotificationTestShadowResources.class)
 public class MediaNotificationManagerNotificationTest extends MediaNotificationTestBase {
     @Test
-    public void updateNotificationBuilderDisplaysCorrectMetadata_PreN_NonEmptyArtistAndAlbum() {
-        MediaNotificationController.sOverrideIsRunningNForTesting = false;
-
-        mMediaNotificationInfoBuilder.setMetadata(new MediaMetadata("title", "artist", "album"));
-        mMediaNotificationInfoBuilder.setOrigin("https://example.com/");
-
-        MediaNotificationInfo info = mMediaNotificationInfoBuilder.build();
-        Notification notification = updateNotificationBuilderAndBuild(info);
-
-        ShadowNotification shadowNotification = Shadows.shadowOf(notification);
-
-        if (info.isPrivate) {
-            assertNotEquals("title", shadowNotification.getContentTitle());
-            assertNotEquals("artist - album", shadowNotification.getContentText());
-            if (hasNApis()) {
-                assertNull(notification.extras.getString(Notification.EXTRA_SUB_TEXT));
-            }
-        } else {
-            assertEquals("title", shadowNotification.getContentTitle());
-            assertEquals("artist - album", shadowNotification.getContentText());
-
-            if (hasNApis()) {
-                assertEquals("https://example.com/",
-                        notification.extras.getString(Notification.EXTRA_SUB_TEXT));
-            }
-        }
-    }
-
-    @Test
-    public void updateNotificationBuilderDisplaysCorrectMetadata_PreN_EmptyArtistAndAlbum() {
-        MediaNotificationController.sOverrideIsRunningNForTesting = false;
-
-        mMediaNotificationInfoBuilder.setMetadata(new MediaMetadata("title", "", ""));
-        mMediaNotificationInfoBuilder.setOrigin("https://example.com/");
-
-        MediaNotificationInfo info = mMediaNotificationInfoBuilder.build();
-        Notification notification = updateNotificationBuilderAndBuild(info);
-
-        ShadowNotification shadowNotification = Shadows.shadowOf(notification);
-
-        if (info.isPrivate) {
-            assertNotEquals(info.metadata.getTitle(), shadowNotification.getContentTitle());
-            assertNotNull(shadowNotification.getContentText());
-        } else {
-            assertEquals(info.metadata.getTitle(), shadowNotification.getContentTitle());
-            assertEquals(info.origin, shadowNotification.getContentText());
-        }
-        if (hasNApis()) {
-            assertEquals(null, notification.extras.getString(Notification.EXTRA_SUB_TEXT));
-        }
-    }
-
-    @Test
-    public void updateNotificationBuilderDisplaysCorrectMetadata_AtLeastN_EmptyArtistAndAlbum() {
-        MediaNotificationController.sOverrideIsRunningNForTesting = true;
-
+    public void updateNotificationBuilderDisplaysCorrectMetadata_EmptyArtistAndAlbum() {
         mMediaNotificationInfoBuilder.setMetadata(new MediaMetadata("title", "", ""));
         mMediaNotificationInfoBuilder.setOrigin("https://example.com/");
 
@@ -155,26 +98,6 @@
     }
 
     @Test
-    public void updateNotificationBuilderDisplaysCorrectLargeIcon_WithoutLargeIcon_PreN() {
-        MediaNotificationController.sOverrideIsRunningNForTesting = false;
-        assertNull(getController().mDefaultNotificationLargeIcon);
-
-        mMediaNotificationInfoBuilder.setNotificationLargeIcon(null);
-
-        MediaNotificationInfo info =
-                mMediaNotificationInfoBuilder
-                        .setDefaultNotificationLargeIcon(R.drawable.audio_playing_square)
-                        .build();
-        Notification notification = updateNotificationBuilderAndBuild(info);
-
-        assertNotNull(getController().mDefaultNotificationLargeIcon);
-        if (hasNApis()) {
-            assertTrue(getController().mDefaultNotificationLargeIcon.sameAs(
-                    iconToBitmap(notification.getLargeIcon())));
-        }
-    }
-
-    @Test
     public void updateNotificationBuilderDisplaysCorrectLargeIcon_DontSupportPlayPause() {
         Bitmap largeIcon = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
         mMediaNotificationInfoBuilder.setNotificationLargeIcon(largeIcon).setActions(0);
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/toolbar/top/StartSurfaceToolbarMediatorUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/toolbar/top/StartSurfaceToolbarMediatorUnitTest.java
index db8fe61..a00b552 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/toolbar/top/StartSurfaceToolbarMediatorUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/toolbar/top/StartSurfaceToolbarMediatorUnitTest.java
@@ -575,7 +575,7 @@
                         -> false,
                 /*logoClickedCallback=*/null,
                 /*isRefactorEnabled=*/false, /*shouldFetchDoodle=*/false, shouldCreateLogoInToolbar,
-                mFinishedShowingCallback, null);
+                mFinishedShowingCallback);
 
         mMediator.onLogoViewReady(mLogoView);
         mMediator.initLogoWithNative();
diff --git a/chrome/app/app-Info.plist b/chrome/app/app-Info.plist
index ba0f3008..ff644f1 100644
--- a/chrome/app/app-Info.plist
+++ b/chrome/app/app-Info.plist
@@ -257,9 +257,10 @@
 			<key>UTTypeConformsTo</key>
 			<array>
 				<string>public.data</string>
+				<string>public.content</string>
 			</array>
 			<key>UTTypeDescription</key>
-			<string>Chromium Extra</string>
+			<string>Chromium Extension</string>
 			<key>UTTypeIdentifier</key>
 			<string>org.chromium.extension</string>
 			<key>UTTypeTagSpecification</key>
@@ -268,6 +269,10 @@
 				<array>
 					<string>crx</string>
 				</array>
+				<key>public.mime-type</key>
+				<array>
+					<string>application/x-chrome-extension</string>
+				</array>
 			</dict>
 		</dict>
 	</array>
@@ -277,6 +282,7 @@
 			<key>UTTypeConformsTo</key>
 			<array>
 				<string>public.data</string>
+				<string>public.content</string>
 			</array>
 			<key>UTTypeDescription</key>
 			<string>MIME HTML document</string>
@@ -284,6 +290,8 @@
 			<string>document.icns</string>
 			<key>UTTypeIdentifier</key>
 			<string>org.ietf.mhtml</string>
+			<key>UTTypeReferenceURL</key>
+			<string>https://www.ietf.org/rfc/rfc2557</string>
 			<key>UTTypeTagSpecification</key>
 			<dict>
 				<key>com.apple.ostype</key>
@@ -293,6 +301,11 @@
 					<string>mht</string>
 					<string>mhtml</string>
 				</array>
+				<key>public.mime-type</key>
+				<array>
+					<string>multipart/related</string>
+					<string>application/x-mimearchive</string>
+				</array>
 			</dict>
 		</dict>
 		<dict>
@@ -307,7 +320,7 @@
 			<key>UTTypeIdentifier</key>
 			<string>org.xiph.ogg-audio</string>
 			<key>UTTypeReferenceURL</key>
-			<string>http://xiph.org/ogg</string>
+			<string>https://xiph.org/ogg/</string>
 			<key>UTTypeTagSpecification</key>
 			<dict>
 				<key>public.filename-extension</key>
@@ -315,6 +328,10 @@
 					<string>ogg</string>
 					<string>oga</string>
 				</array>
+				<key>public.mime-type</key>
+				<array>
+					<string>audio/ogg</string>
+				</array>
 			</dict>
 		</dict>
 		<dict>
@@ -329,7 +346,7 @@
 			<key>UTTypeIdentifier</key>
 			<string>org.xiph.ogv</string>
 			<key>UTTypeReferenceURL</key>
-			<string>http://xiph.org/ogg</string>
+			<string>https://xiph.org/ogg/</string>
 			<key>UTTypeTagSpecification</key>
 			<dict>
 				<key>public.filename-extension</key>
@@ -337,6 +354,10 @@
 					<string>ogm</string>
 					<string>ogv</string>
 				</array>
+				<key>public.mime-type</key>
+				<array>
+					<string>video/ogg</string>
+				</array>
 			</dict>
 		</dict>
 		<dict>
@@ -345,7 +366,7 @@
 				<string>public.movie</string>
 			</array>
 			<key>UTTypeDescription</key>
-			<string>HTML5 Video (WebM)</string>
+			<string>WebM media</string>
 			<key>UTTypeIconFile</key>
 			<string>document.icns</string>
 			<key>UTTypeIdentifier</key>
@@ -358,6 +379,11 @@
 				<array>
 					<string>webm</string>
 				</array>
+				<key>public.mime-type</key>
+				<array>
+					<string>video/webm</string>
+					<string>audio/webm</string>
+				</array>
 			</dict>
 		</dict>
 		<dict>
@@ -379,6 +405,10 @@
 				<array>
 					<string>webp</string>
 				</array>
+				<key>public.mime-type</key>
+				<array>
+					<string>image/webp</string>
+				</array>
 			</dict>
 		</dict>
 	</array>
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index cdc2630..75730da 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -9520,10 +9520,6 @@
             desc="Button text for the Screenshot dialog edit button.">
           Edit
         </message>
-        <message name="IDS_BROWSER_SHARING_SCREENSHOT_DIALOG_SEARCH_IMAGE_BUTTON_LABEL"
-            desc="Button text for the Screenshot dialog search image button.">
-          Search with Google
-        </message>
         <message name="IDS_BROWSER_SHARING_SCREENSHOT_DIALOG_SHARE_BUTTON_LABEL"
             desc="Button text for the Screenshot dialog share button.">
           Share
diff --git a/chrome/app/generated_resources_grd/IDS_BROWSER_SHARING_SCREENSHOT_DIALOG_SEARCH_IMAGE_BUTTON_LABEL.png.sha1 b/chrome/app/generated_resources_grd/IDS_BROWSER_SHARING_SCREENSHOT_DIALOG_SEARCH_IMAGE_BUTTON_LABEL.png.sha1
deleted file mode 100644
index 6aa7e3ab..0000000
--- a/chrome/app/generated_resources_grd/IDS_BROWSER_SHARING_SCREENSHOT_DIALOG_SEARCH_IMAGE_BUTTON_LABEL.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-193406ee37dbba1ac00127dd327151c847b41672
\ No newline at end of file
diff --git a/chrome/app/os_settings_strings.grdp b/chrome/app/os_settings_strings.grdp
index 24dd75c..dca4d11 100644
--- a/chrome/app/os_settings_strings.grdp
+++ b/chrome/app/os_settings_strings.grdp
@@ -4402,7 +4402,7 @@
     When turned on, all notifications will be silenced
   </message>
   <message name="IDS_SETTINGS_APP_NOTIFICATIONS_LINK_TO_BROWSER_SETTINGS_DESCRIPTION" desc="In notifications OS Settings subpage, explanatory text for website notifications link.">
-    For browser notifications, go to <ph name="LINK_BEGIN">&lt;a&gt;</ph>Chrome browser Settings<ph name="LINK_END">&lt;/a&gt;</ph>
+    For browser notifications, go to <ph name="LINK_BEGIN">&lt;a href="#"&gt;</ph>Chrome browser Settings<ph name="LINK_END">&lt;/a&gt;</ph>
   </message>
   <message name="IDS_SETTINGS_APP_BADGING_TOGGLE_LABEL" desc="The label for the app badging toggle in the App Notifications page of OS Settings.">
     App badging
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 7cd4b3f..071bb48 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -5404,6 +5404,11 @@
                                     kOmniboxSuggestionHeightVariations,
                                     "Uniform Omnibox Suggest Heights")},
 
+    {"omnibox-grouping-framework",
+     flag_descriptions::kOmniboxGroupingFrameworkName,
+     flag_descriptions::kOmniboxGroupingFrameworkDescription, kOsAll,
+     FEATURE_VALUE_TYPE(omnibox::kGroupingFramework)},
+
     {"optimization-guide-debug-logs",
      flag_descriptions::kOptimizationGuideDebugLogsName,
      flag_descriptions::kOptimizationGuideDebugLogsDescription, kOsAll,
diff --git a/chrome/browser/ash/BUILD.gn b/chrome/browser/ash/BUILD.gn
index ebf7602..ba97112 100644
--- a/chrome/browser/ash/BUILD.gn
+++ b/chrome/browser/ash/BUILD.gn
@@ -4389,6 +4389,7 @@
     "../ui/webui/settings/ash/search/per_session_settings_user_action_tracker_unittest.cc",
     "../ui/webui/settings/ash/search/search_handler_unittest.cc",
     "../ui/webui/settings/ash/search/search_tag_registry_unittest.cc",
+    "../ui/webui/settings/ash/search_engines_handler_unittest.cc",
     "../ui/webui/settings/ash/settings_user_action_tracker_unittest.cc",
     "../ui/webui/settings/chromeos/constants/routes_util_unittest.cc",
     "accessibility/pumpkin_installer_unittest.cc",
diff --git a/chrome/browser/ash/app_list/arc/arc_app_sync_metrics_helper.cc b/chrome/browser/ash/app_list/arc/arc_app_sync_metrics_helper.cc
new file mode 100644
index 0000000..73ca0f1
--- /dev/null
+++ b/chrome/browser/ash/app_list/arc/arc_app_sync_metrics_helper.cc
@@ -0,0 +1,61 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ash/app_list/arc/arc_app_sync_metrics_helper.h"
+
+#include "base/logging.h"
+#include "base/metrics/histogram_functions.h"
+#include "base/strings/strcat.h"
+
+namespace {
+
+constexpr char kHistogramNameBase[] = "Arc.AppSync.InitialSession.";
+constexpr int kAppCountUmaExclusiveMax = 101;
+
+}  // namespace
+
+namespace arc {
+
+ArcAppSyncMetricsHelper::ArcAppSyncMetricsHelper() = default;
+
+ArcAppSyncMetricsHelper::~ArcAppSyncMetricsHelper() = default;
+
+void ArcAppSyncMetricsHelper::SetTimeSyncStarted() {
+  time_sync_started_ = base::TimeTicks::Now();
+  time_last_install_finished_ = time_sync_started_;
+}
+
+void ArcAppSyncMetricsHelper::OnAppInstalled() {
+  time_last_install_finished_ = base::TimeTicks::Now();
+  num_installed_apps_++;
+}
+
+void ArcAppSyncMetricsHelper::SetAndRecordNumExpectedApps(
+    uint64_t num_expected_apps) {
+  num_expected_apps_ = num_expected_apps;
+  base::UmaHistogramExactLinear(
+      base::StrCat({kHistogramNameBase, "NumAppsExpected"}), num_expected_apps_,
+      kAppCountUmaExclusiveMax);
+}
+
+void ArcAppSyncMetricsHelper::RecordMetrics() {
+  if (time_sync_started_ != time_last_install_finished_) {
+    const base::TimeDelta latency =
+        time_last_install_finished_ - time_sync_started_;
+    // Min is set to 30s since app installs typically take longer and an
+    // underflow bucket will be created
+    base::UmaHistogramCustomCounts(
+        base::StrCat({kHistogramNameBase, "Latency"}), latency.InSeconds(),
+        /*min=*/30, /*max=*/base::Hours(3).InSeconds(), /*buckets=*/50);
+  }
+
+  base::UmaHistogramExactLinear(
+      base::StrCat({kHistogramNameBase, "NumAppsInstalled"}),
+      num_installed_apps_, kAppCountUmaExclusiveMax);
+  base::UmaHistogramExactLinear(
+      base::StrCat({kHistogramNameBase, "NumAppsNotInstalled"}),
+      num_expected_apps_ - num_installed_apps_, kAppCountUmaExclusiveMax);
+}
+
+}  // namespace arc
diff --git a/chrome/browser/ash/app_list/arc/arc_app_sync_metrics_helper.h b/chrome/browser/ash/app_list/arc/arc_app_sync_metrics_helper.h
new file mode 100644
index 0000000..8dbb4c6
--- /dev/null
+++ b/chrome/browser/ash/app_list/arc/arc_app_sync_metrics_helper.h
@@ -0,0 +1,42 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_ASH_APP_LIST_ARC_ARC_APP_SYNC_METRICS_HELPER_H_
+#define CHROME_BROWSER_ASH_APP_LIST_ARC_ARC_APP_SYNC_METRICS_HELPER_H_
+
+#include "base/time/time.h"
+
+namespace arc {
+
+// Handles metrics for app sync.
+class ArcAppSyncMetricsHelper {
+ public:
+  ArcAppSyncMetricsHelper();
+  ~ArcAppSyncMetricsHelper();
+  ArcAppSyncMetricsHelper(const ArcAppSyncMetricsHelper& other) = delete;
+  ArcAppSyncMetricsHelper& operator=(const ArcAppSyncMetricsHelper&) = delete;
+
+  // Sets `time_sync_started_` to current time.
+  void SetTimeSyncStarted();
+
+  // When an app is installed, count of installed apps is incremented and
+  // the current time is recorded.
+  void OnAppInstalled();
+
+  // Sets `num_expected_apps_` and records the count in UMA.
+  void SetAndRecordNumExpectedApps(uint64_t num_expected_apps);
+
+  // Records the remaining metrics in UMA.
+  void RecordMetrics();
+
+ private:
+  base::TimeTicks time_sync_started_;
+  base::TimeTicks time_last_install_finished_;
+  uint64_t num_installed_apps_ = 0;
+  uint64_t num_expected_apps_ = 0;
+};
+
+}  // namespace arc
+
+#endif  // CHROME_BROWSER_ASH_APP_LIST_ARC_ARC_APP_SYNC_METRICS_HELPER_H_
diff --git a/chrome/browser/ash/app_list/arc/arc_app_sync_metrics_helper_unittest.cc b/chrome/browser/ash/app_list/arc/arc_app_sync_metrics_helper_unittest.cc
new file mode 100644
index 0000000..52fd1298
--- /dev/null
+++ b/chrome/browser/ash/app_list/arc/arc_app_sync_metrics_helper_unittest.cc
@@ -0,0 +1,103 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ash/app_list/arc/arc_app_sync_metrics_helper.h"
+
+#include "base/test/metrics/histogram_tester.h"
+#include "base/time/time.h"
+#include "content/public/test/browser_task_environment.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+constexpr char kLatencyHistogramName[] = "Arc.AppSync.InitialSession.Latency";
+constexpr char kExpectedAppHistogramName[] =
+    "Arc.AppSync.InitialSession.NumAppsExpected";
+constexpr char kInstalledAppHistogramName[] =
+    "Arc.AppSync.InitialSession.NumAppsInstalled";
+constexpr char kNotInstalledAppHistogramName[] =
+    "Arc.AppSync.InitialSession.NumAppsNotInstalled";
+
+}  // namespace
+
+namespace arc {
+
+class ArcAppSyncMetricsHelperTest : public testing::Test {
+ public:
+  ArcAppSyncMetricsHelperTest(const ArcAppSyncMetricsHelperTest&) = delete;
+  ArcAppSyncMetricsHelperTest& operator=(const ArcAppSyncMetricsHelperTest&) =
+      delete;
+
+ protected:
+  ArcAppSyncMetricsHelperTest() = default;
+
+  base::test::TaskEnvironment task_environment{
+      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
+  ArcAppSyncMetricsHelper metrics_helper_;
+  base::HistogramTester tester;
+};
+
+TEST_F(ArcAppSyncMetricsHelperTest,
+       OneAppExpectedAndNotDownloaded_ThenDontRecordLatency) {
+  metrics_helper_.SetTimeSyncStarted();
+  metrics_helper_.SetAndRecordNumExpectedApps(1);
+  metrics_helper_.RecordMetrics();
+
+  tester.ExpectUniqueSample(kNotInstalledAppHistogramName, 1, 1);
+  tester.ExpectUniqueSample(kInstalledAppHistogramName, 0, 1);
+  tester.ExpectUniqueSample(kExpectedAppHistogramName, 1, 1);
+  tester.ExpectTotalCount(kLatencyHistogramName, 0);
+}
+
+TEST_F(ArcAppSyncMetricsHelperTest, OneAppExpectedAndDownloaded) {
+  base::TimeDelta expected_latency = base::Minutes(1);
+  metrics_helper_.SetTimeSyncStarted();
+  metrics_helper_.SetAndRecordNumExpectedApps(1);
+  task_environment.AdvanceClock(expected_latency);
+  metrics_helper_.OnAppInstalled();
+  metrics_helper_.RecordMetrics();
+
+  tester.ExpectUniqueSample(kNotInstalledAppHistogramName, 0, 1);
+  tester.ExpectUniqueSample(kInstalledAppHistogramName, 1, 1);
+  tester.ExpectUniqueSample(kExpectedAppHistogramName, 1, 1);
+  tester.ExpectUniqueSample(kLatencyHistogramName, expected_latency.InSeconds(),
+                            1);
+}
+
+TEST_F(ArcAppSyncMetricsHelperTest, TwoAppsExpectedAndOneDownloaded) {
+  base::TimeDelta expected_latency = base::Minutes(5);
+  metrics_helper_.SetTimeSyncStarted();
+  metrics_helper_.SetAndRecordNumExpectedApps(2);
+  task_environment.AdvanceClock(expected_latency);
+  metrics_helper_.OnAppInstalled();
+  metrics_helper_.RecordMetrics();
+
+  tester.ExpectUniqueSample(kNotInstalledAppHistogramName, 1, 1);
+  tester.ExpectUniqueSample(kInstalledAppHistogramName, 1, 1);
+  tester.ExpectUniqueSample(kExpectedAppHistogramName, 2, 1);
+  tester.ExpectUniqueSample(kLatencyHistogramName, expected_latency.InSeconds(),
+                            1);
+}
+
+TEST_F(ArcAppSyncMetricsHelperTest,
+       ThreeAppsExpectedAndDownloaded_ThenRecordTotalLatency) {
+  int32_t num_expected_apps = 3;
+  base::TimeDelta latency_per_app = base::Minutes(1);
+  metrics_helper_.SetTimeSyncStarted();
+  metrics_helper_.SetAndRecordNumExpectedApps(num_expected_apps);
+  for (int i = 0; i < num_expected_apps; i++) {
+    task_environment.AdvanceClock(latency_per_app);
+    metrics_helper_.OnAppInstalled();
+  }
+  metrics_helper_.RecordMetrics();
+  base::TimeDelta expected_latency = latency_per_app * num_expected_apps;
+
+  tester.ExpectUniqueSample(kNotInstalledAppHistogramName, 0, 1);
+  tester.ExpectUniqueSample(kInstalledAppHistogramName, 3, 1);
+  tester.ExpectUniqueSample(kExpectedAppHistogramName, 3, 1);
+  tester.ExpectUniqueSample(kLatencyHistogramName, expected_latency.InSeconds(),
+                            1);
+}
+
+}  // namespace arc
diff --git a/chrome/browser/ash/app_list/arc/arc_package_syncable_service.cc b/chrome/browser/ash/app_list/arc/arc_package_syncable_service.cc
index 108c6cf4..bbf8168 100644
--- a/chrome/browser/ash/app_list/arc/arc_package_syncable_service.cc
+++ b/chrome/browser/ash/app_list/arc/arc_package_syncable_service.cc
@@ -10,9 +10,11 @@
 
 #include "ash/components/arc/arc_util.h"
 #include "ash/components/arc/session/connection_holder.h"
+#include "ash/constants/ash_pref_names.h"
 #include "base/containers/contains.h"
 #include "chrome/browser/ash/app_list/arc/arc_app_list_prefs.h"
 #include "chrome/browser/ash/app_list/arc/arc_package_syncable_service_factory.h"
+#include "chrome/browser/ash/arc/session/arc_session_manager.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/common/pref_names.h"
 #include "components/prefs/scoped_user_pref_update.h"
@@ -95,11 +97,20 @@
       prefs_(prefs) {
   if (prefs_)
     prefs_->AddObserver(this);
+
+  auto* arc_session_manager = arc::ArcSessionManager::Get();
+  DCHECK(arc_session_manager);
+  arc_session_manager->AddObserver(this);
 }
 
 ArcPackageSyncableService::~ArcPackageSyncableService() {
   if (prefs_)
     prefs_->RemoveObserver(this);
+
+  // arc::ArcSessionManager may be released first.
+  if (auto* arc_session_manager = ArcSessionManager::Get()) {
+    arc_session_manager->RemoveObserver(this);
+  }
 }
 
 // static
@@ -149,6 +160,8 @@
 
   sync_processor_ = std::move(sync_processor);
   sync_error_handler_ = std::move(error_handler);
+  metrics_helper_.SetTimeSyncStarted();
+  uint64_t num_expected_apps = 0;
 
   const std::vector<std::string> local_packages =
       prefs_->GetPackagesFromPrefs();
@@ -167,11 +180,15 @@
     if (!base::Contains(local_package_set, package_name)) {
       pending_install_items_[package_name] = std::move(sync_item);
       InstallPackage(pending_install_items_[package_name].get());
+      num_expected_apps++;
     } else {
       // TODO(lgcheng@) may need to handle update exsiting package here.
       sync_items_[package_name] = std::move(sync_item);
     }
   }
+  if (profile_->GetPrefs()->GetBoolean(ash::prefs::kRecordArcAppSyncMetrics)) {
+    metrics_helper_.SetAndRecordNumExpectedApps(num_expected_apps);
+  }
 
   // Creates sync items for local unsynced packages.
   syncer::SyncChangeList change_list;
@@ -308,6 +325,7 @@
 
     sync_items_[package_name] = std::move(install_iter->second);
     pending_install_items_.erase(install_iter);
+    metrics_helper_.OnAppInstalled();
     return;
   }
 
@@ -478,4 +496,11 @@
   return true;
 }
 
+void ArcPackageSyncableService::OnArcSessionStopped(ArcStopReason stop_reason) {
+  if (profile_->GetPrefs()->GetBoolean(ash::prefs::kRecordArcAppSyncMetrics)) {
+    metrics_helper_.RecordMetrics();
+  }
+  profile_->GetPrefs()->ClearPref(ash::prefs::kRecordArcAppSyncMetrics);
+}
+
 }  // namespace arc
diff --git a/chrome/browser/ash/app_list/arc/arc_package_syncable_service.h b/chrome/browser/ash/app_list/arc/arc_package_syncable_service.h
index 1a6f255..25bc617 100644
--- a/chrome/browser/ash/app_list/arc/arc_package_syncable_service.h
+++ b/chrome/browser/ash/app_list/arc/arc_package_syncable_service.h
@@ -12,6 +12,8 @@
 #include <unordered_map>
 
 #include "chrome/browser/ash/app_list/arc/arc_app_list_prefs.h"
+#include "chrome/browser/ash/app_list/arc/arc_app_sync_metrics_helper.h"
+#include "chrome/browser/ash/arc/session/arc_session_manager_observer.h"
 #include "chrome/browser/sync/glue/sync_start_util.h"
 #include "components/keyed_service/core/keyed_service.h"
 #include "components/sync/model/sync_change.h"
@@ -28,10 +30,11 @@
 
 namespace arc {
 
-// Class that syncs ARC pakcages install/uninstall.
+// Class that syncs ARC packages install/uninstall.
 class ArcPackageSyncableService : public syncer::SyncableService,
                                   public KeyedService,
-                                  public ArcAppListPrefs::Observer {
+                                  public ArcAppListPrefs::Observer,
+                                  public ArcSessionManagerObserver {
  public:
   struct SyncItem {
     SyncItem(const std::string& package_name,
@@ -84,6 +87,9 @@
                         bool uninstalled) override;
   void OnPackageListInitialRefreshed() override;
 
+  // ArcSessionManagerObserver:
+  void OnArcSessionStopped(ArcStopReason stop_reason) override;
+
   // Sends adds/updates sync change to sync server.
   void SendSyncChange(
       const mojom::ArcPackageInfo& package_info,
@@ -135,6 +141,8 @@
   syncer::SyncableService::StartSyncFlare flare_;
 
   ArcAppListPrefs* const prefs_;
+
+  ArcAppSyncMetricsHelper metrics_helper_;
 };
 
 }  // namespace arc
diff --git a/chrome/browser/ash/arc/policy/arc_policy_util_unittest.cc b/chrome/browser/ash/arc/policy/arc_policy_util_unittest.cc
index ad77c25d..4ebabca9 100644
--- a/chrome/browser/ash/arc/policy/arc_policy_util_unittest.cc
+++ b/chrome/browser/ash/arc/policy/arc_policy_util_unittest.cc
@@ -32,18 +32,6 @@
     {"testPackage3", "BLOCKED"},        {"testPackage4", "AVAILABLE"},
     {"testPackage5", "AVAILABLE"},      {"testPackage6", "REQUIRED"}};
 
-}  // namespace
-
-class ArcPolicyUtilTest : public testing::Test {
- public:
-  ArcPolicyUtilTest(const ArcPolicyUtilTest&) = delete;
-  ArcPolicyUtilTest& operator=(const ArcPolicyUtilTest&) = delete;
-
- protected:
-  ArcPolicyUtilTest() = default;
-  base::HistogramTester tester;
-};
-
 std::string CreatePolicyWithAppInstalls(
     std::map<std::string, std::string> package_map) {
   base::Value::Dict arc_policy;
@@ -62,6 +50,19 @@
   return arc_policy_string;
 }
 
+}  // namespace
+
+class ArcPolicyUtilTest : public testing::Test {
+ public:
+  ArcPolicyUtilTest(const ArcPolicyUtilTest&) = delete;
+  ArcPolicyUtilTest& operator=(const ArcPolicyUtilTest&) = delete;
+
+ protected:
+  ArcPolicyUtilTest() = default;
+
+  base::HistogramTester tester_;
+};
+
 TEST_F(ArcPolicyUtilTest, GetRequestedPackagesFromArcPolicy) {
   std::set<std::string> expected = {"testPackage", "testPackage6"};
   std::string policy = CreatePolicyWithAppInstalls(kTestMap);
@@ -71,7 +72,7 @@
   EXPECT_EQ(result, expected);
 }
 
-TEST_F(ArcPolicyUtilTest, RecordInstallTypesInPolicy_OneOfEachType) {
+TEST_F(ArcPolicyUtilTest, RecordInstallTypesInPolicyWithOneOfEachType) {
   std::map<std::string, std::string> test_map = {
       {"testPackage", "OPTIONAL"},
       {"testPackage2", "REQUIRED"},
@@ -86,49 +87,46 @@
   std::string policy = CreatePolicyWithAppInstalls(test_map);
   arc::policy_util::RecordInstallTypesInPolicy(policy);
 
-  tester.ExpectBucketCount(kInstallTypeHistogram, kUnknownBucket, 1);
-  tester.ExpectBucketCount(kInstallTypeHistogram, kOptionalBucket, 1);
-  tester.ExpectBucketCount(kInstallTypeHistogram, kRequiredBucket, 1);
-  tester.ExpectBucketCount(kInstallTypeHistogram, kPreloadBucket, 1);
-  tester.ExpectBucketCount(kInstallTypeHistogram, kForceInstalledBucket, 1);
-  tester.ExpectBucketCount(kInstallTypeHistogram, kBlockedBucket, 1);
-  tester.ExpectBucketCount(kInstallTypeHistogram, kAvailableBucket, 1);
-  tester.ExpectBucketCount(kInstallTypeHistogram, kRequiredForSetupBucket, 1);
-  tester.ExpectBucketCount(kInstallTypeHistogram, kKioskBucket, 1);
-  tester.ExpectTotalCount(kInstallTypeHistogram, 9);
+  tester_.ExpectBucketCount(kInstallTypeHistogram, kUnknownBucket, 1);
+  tester_.ExpectBucketCount(kInstallTypeHistogram, kOptionalBucket, 1);
+  tester_.ExpectBucketCount(kInstallTypeHistogram, kRequiredBucket, 1);
+  tester_.ExpectBucketCount(kInstallTypeHistogram, kPreloadBucket, 1);
+  tester_.ExpectBucketCount(kInstallTypeHistogram, kForceInstalledBucket, 1);
+  tester_.ExpectBucketCount(kInstallTypeHistogram, kBlockedBucket, 1);
+  tester_.ExpectBucketCount(kInstallTypeHistogram, kAvailableBucket, 1);
+  tester_.ExpectBucketCount(kInstallTypeHistogram, kRequiredForSetupBucket, 1);
+  tester_.ExpectBucketCount(kInstallTypeHistogram, kKioskBucket, 1);
+  tester_.ExpectTotalCount(kInstallTypeHistogram, 9);
 }
 
-TEST_F(ArcPolicyUtilTest, RecordInstallTypesInPolicy_ComplexPolicy) {
+TEST_F(ArcPolicyUtilTest, RecordInstallTypesInPolicyWithComplexPolicy) {
   std::string policy = CreatePolicyWithAppInstalls(kTestMap);
   arc::policy_util::RecordInstallTypesInPolicy(policy);
 
-  tester.ExpectBucketCount(kInstallTypeHistogram, kForceInstalledBucket, 1);
-  tester.ExpectBucketCount(kInstallTypeHistogram, kBlockedBucket, 1);
-  tester.ExpectBucketCount(kInstallTypeHistogram, kAvailableBucket, 1);
-  tester.ExpectBucketCount(kInstallTypeHistogram, kRequiredBucket, 1);
-  tester.ExpectTotalCount(kInstallTypeHistogram, 4);
+  tester_.ExpectBucketCount(kInstallTypeHistogram, kForceInstalledBucket, 1);
+  tester_.ExpectBucketCount(kInstallTypeHistogram, kBlockedBucket, 1);
+  tester_.ExpectBucketCount(kInstallTypeHistogram, kAvailableBucket, 1);
+  tester_.ExpectBucketCount(kInstallTypeHistogram, kRequiredBucket, 1);
+  tester_.ExpectTotalCount(kInstallTypeHistogram, 4);
 }
 
-TEST_F(ArcPolicyUtilTest, RecordInstallTypesInPolicy_PolicyUpdate) {
-  std::string policy = CreatePolicyWithAppInstalls(kTestMap);
+TEST_F(ArcPolicyUtilTest, RecordInstallTypesInPolicyAfterPolicyUpdate) {
+  std::map<std::string, std::string> test_map = {
+      {"testPackage", "FORCE_INSTALLED"}};
+  std::string policy = CreatePolicyWithAppInstalls(test_map);
   arc::policy_util::RecordInstallTypesInPolicy(policy);
 
-  tester.ExpectBucketCount(kInstallTypeHistogram, kForceInstalledBucket, 1);
-  tester.ExpectBucketCount(kInstallTypeHistogram, kBlockedBucket, 1);
-  tester.ExpectBucketCount(kInstallTypeHistogram, kAvailableBucket, 1);
-  tester.ExpectBucketCount(kInstallTypeHistogram, kRequiredBucket, 1);
-  tester.ExpectTotalCount(kInstallTypeHistogram, 4);
+  tester_.ExpectBucketCount(kInstallTypeHistogram, kForceInstalledBucket, 1);
+  tester_.ExpectTotalCount(kInstallTypeHistogram, 1);
 
-  kTestMap["anotherTestPackage"] = "BLOCKED";
-  kTestMap["anotherTestPackage2"] = "KIOSK";
-  policy = CreatePolicyWithAppInstalls(kTestMap);
+  test_map["anotherTestPackage"] = "BLOCKED";
+  test_map["anotherTestPackage2"] = "KIOSK";
+  policy = CreatePolicyWithAppInstalls(test_map);
   arc::policy_util::RecordInstallTypesInPolicy(policy);
-  tester.ExpectBucketCount(kInstallTypeHistogram, kForceInstalledBucket, 2);
-  tester.ExpectBucketCount(kInstallTypeHistogram, kBlockedBucket, 2);
-  tester.ExpectBucketCount(kInstallTypeHistogram, kAvailableBucket, 2);
-  tester.ExpectBucketCount(kInstallTypeHistogram, kRequiredBucket, 2);
-  tester.ExpectBucketCount(kInstallTypeHistogram, kKioskBucket, 1);
-  tester.ExpectTotalCount(kInstallTypeHistogram, 9);
+  tester_.ExpectBucketCount(kInstallTypeHistogram, kForceInstalledBucket, 2);
+  tester_.ExpectBucketCount(kInstallTypeHistogram, kBlockedBucket, 1);
+  tester_.ExpectBucketCount(kInstallTypeHistogram, kKioskBucket, 1);
+  tester_.ExpectTotalCount(kInstallTypeHistogram, 4);
 }
 
 }  // namespace arc::policy_util
diff --git a/chrome/browser/ash/crosapi/persistent_forced_extension_keep_alive_unittest.cc b/chrome/browser/ash/crosapi/persistent_forced_extension_keep_alive_unittest.cc
index b006502..277f93a 100644
--- a/chrome/browser/ash/crosapi/persistent_forced_extension_keep_alive_unittest.cc
+++ b/chrome/browser/ash/crosapi/persistent_forced_extension_keep_alive_unittest.cc
@@ -62,12 +62,12 @@
   }
 
   void SetInstallForceList(const std::string& extension_id) {
-    std::unique_ptr<base::Value> dict =
+    base::Value::Dict dict =
         extensions::DictionaryBuilder()
             .Set(extension_id, extensions::DictionaryBuilder().Build())
             .Build();
     profile_->GetPrefs()->Set(extensions::pref_names::kInstallForceList,
-                              std::move(*dict));
+                              base::Value(std::move(dict)));
   }
 
   FakeBrowserManager& browser_manager() { return *browser_manager_; }
diff --git a/chrome/browser/ash/dbus/org.chromium.ChromeFeaturesService.conf b/chrome/browser/ash/dbus/org.chromium.ChromeFeaturesService.conf
index 86ad8fe..180c30ff 100644
--- a/chrome/browser/ash/dbus/org.chromium.ChromeFeaturesService.conf
+++ b/chrome/browser/ash/dbus/org.chromium.ChromeFeaturesService.conf
@@ -54,4 +54,11 @@
            send_interface="org.chromium.ChromeFeaturesServiceInterface"
            send_member="IsFeatureEnabled"/>
   </policy>
+
+  <!-- limit dlp visibility to only IsFeatureEnabled -->
+  <policy user="dlp">
+    <allow send_destination="org.chromium.ChromeFeaturesService"
+           send_interface="org.chromium.ChromeFeaturesServiceInterface"
+           send_member="IsFeatureEnabled"/>
+  </policy>
 </busconfig>
diff --git a/chrome/browser/ash/extensions/autotest_private/autotest_private_api.cc b/chrome/browser/ash/extensions/autotest_private/autotest_private_api.cc
index c5b5a67..7535bda 100644
--- a/chrome/browser/ash/extensions/autotest_private/autotest_private_api.cc
+++ b/chrome/browser/ash/extensions/autotest_private/autotest_private_api.cc
@@ -589,6 +589,8 @@
     DCHECK(value.is_bool());
   } else if (pref_name == quick_answers::prefs::kQuickAnswersConsentStatus) {
     DCHECK(value.is_int());
+  } else if (pref_name == arc::prefs::kArcShowResizeLockSplashScreenLimits) {
+    DCHECK(value.is_int());
   } else {
     return "The pref " + pref_name + " is not allowed.";
   }
diff --git a/chrome/browser/ash/login/screens/multidevice_setup_screen.cc b/chrome/browser/ash/login/screens/multidevice_setup_screen.cc
index 032f982..9a7efae5 100644
--- a/chrome/browser/ash/login/screens/multidevice_setup_screen.cc
+++ b/chrome/browser/ash/login/screens/multidevice_setup_screen.cc
@@ -8,12 +8,15 @@
 #include "base/logging.h"
 #include "base/memory/weak_ptr.h"
 #include "base/metrics/histogram_macros.h"
+#include "chrome/browser/ash/device_sync/device_sync_client_factory.h"
 #include "chrome/browser/ash/login/users/chrome_user_manager_util.h"
 #include "chrome/browser/ash/login/wizard_context.h"
 #include "chrome/browser/ash/multidevice_setup/multidevice_setup_client_factory.h"
 #include "chrome/browser/ash/multidevice_setup/oobe_completion_tracker_factory.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/ui/webui/ash/login/multidevice_setup_screen_handler.h"
+#include "chromeos/ash/components/multidevice/logging/logging.h"
+#include "chromeos/ash/services/device_sync/public/cpp/device_sync_client.h"
 #include "chromeos/ash/services/multidevice_setup/public/cpp/multidevice_setup_client.h"
 #include "chromeos/ash/services/multidevice_setup/public/cpp/oobe_completion_tracker.h"
 
@@ -50,7 +53,13 @@
   DCHECK(view_);
 }
 
-MultiDeviceSetupScreen::~MultiDeviceSetupScreen() = default;
+MultiDeviceSetupScreen::~MultiDeviceSetupScreen() {
+  if (skipped_ && !skipped_reason_determined_) {
+    RecordOobeMultideviceScreenSkippedReasonHistogram(
+        OobeMultideviceScreenSkippedReason::
+            kDestroyedBeforeReasonCouldBeDetermined);
+  }
+}
 
 void MultiDeviceSetupScreen::TryInitSetupClient() {
   if (!setup_client_) {
@@ -65,25 +74,39 @@
   if (context.skip_post_login_screens_for_tests ||
       chrome_user_manager_util::IsPublicSessionOrEphemeralLogin()) {
     exit_callback_.Run(Result::NOT_APPLICABLE);
+    RecordOobeMultideviceScreenSkippedReasonHistogram(
+        OobeMultideviceScreenSkippedReason::kPublicSessionOrEphemeralLogin);
+    skipped_ = true;
     return true;
   }
 
   TryInitSetupClient();
-  // If there is no eligible multi-device host phone or if there is a phone and
-  // it has already been set, skip the setup flow.
+
+  // Skip if the setup client wasn't successfully initialized.
   if (!setup_client_) {
+    RecordOobeMultideviceScreenSkippedReasonHistogram(
+        OobeMultideviceScreenSkippedReason::kSetupClientNotInitialized);
     exit_callback_.Run(Result::NOT_APPLICABLE);
-    return true;
-  }
-  if (setup_client_->GetHostStatus().first !=
-      multidevice_setup::mojom::HostStatus::kEligibleHostExistsButNoHostSet) {
-    VLOG(1) << "Skipping MultiDevice setup screen; host status: "
-            << setup_client_->GetHostStatus().first;
-    exit_callback_.Run(Result::NOT_APPLICABLE);
+    skipped_ = true;
     return true;
   }
 
-  return false;
+  // Do not skip if potential host exists but none is set yet.
+  if (setup_client_->GetHostStatus().first ==
+      multidevice_setup::mojom::HostStatus::kEligibleHostExistsButNoHostSet) {
+    skipped_ = false;
+    return false;
+  }
+
+  skipped_ = true;
+  VLOG(1) << "Skipping MultiDevice setup screen; host status: "
+          << setup_client_->GetHostStatus().first;
+  exit_callback_.Run(Result::NOT_APPLICABLE);
+
+  // Determine underlying reason why the screen is being skipped.
+  GetBetterTogetherMetadataStatus();
+
+  return true;
 }
 
 void MultiDeviceSetupScreen::ShowImpl() {
@@ -119,6 +142,123 @@
   }
 }
 
+void MultiDeviceSetupScreen::GetBetterTogetherMetadataStatus() {
+  if (!device_sync_client_) {
+    device_sync_client_ = device_sync::DeviceSyncClientFactory::GetForProfile(
+        ProfileManager::GetActiveUserProfile());
+  }
+
+  device_sync_client_->GetBetterTogetherMetadataStatus(
+      base::BindOnce(&MultiDeviceSetupScreen::OnGetBetterTogetherMetadataStatus,
+                     weak_factory_.GetWeakPtr()));
+}
+
+void MultiDeviceSetupScreen::OnGetBetterTogetherMetadataStatus(
+    device_sync::BetterTogetherMetadataStatus status) {
+  PA_LOG(INFO) << "Skipped MultiDevice setup screen; "
+                  "better_together_metadata_status: "
+               << status;
+  switch (status) {
+    case device_sync::BetterTogetherMetadataStatus::kMetadataDecrypted:
+      // If the better together metadata status is in its expected final state,
+      // then we know that device sync successfully finished. Investigate the
+      // host status for more granular information.
+      setup_client_->GetHostStatus().first ==
+              multidevice_setup::mojom::HostStatus::kNoEligibleHosts
+          ? RecordOobeMultideviceScreenSkippedReasonHistogram(
+                OobeMultideviceScreenSkippedReason::
+                    kDeviceSyncFinishedAndNoEligibleHostPhone)
+          : RecordOobeMultideviceScreenSkippedReasonHistogram(
+                OobeMultideviceScreenSkippedReason::kHostPhoneAlreadySet);
+      return;
+    case device_sync::BetterTogetherMetadataStatus::
+        kWaitingToProcessDeviceMetadata:
+      [[fallthrough]];
+    case device_sync::BetterTogetherMetadataStatus::kGroupPrivateKeyMissing:
+      // If the better together metadata status is
+      // kWaitingToProcessDeviceMetadata or kGroupPrivateKeyMissing, we must
+      // inspect the group private key status to get a more granular
+      // understanding.
+      GetGroupPrivateKeyStatus();
+      return;
+    case device_sync::BetterTogetherMetadataStatus::
+        kStatusUnavailableBecauseDeviceSyncIsNotInitialized:
+      RecordOobeMultideviceScreenSkippedReasonHistogram(
+          OobeMultideviceScreenSkippedReason::
+              kDeviceSyncNotInitializedDuringBetterTogetherMetadataStatusFetch);
+      return;
+    case device_sync::BetterTogetherMetadataStatus::kEncryptedMetadataEmpty:
+      RecordOobeMultideviceScreenSkippedReasonHistogram(
+          OobeMultideviceScreenSkippedReason::kEncryptedMetadataEmpty);
+      return;
+  }
+}
+
+void MultiDeviceSetupScreen::GetGroupPrivateKeyStatus() {
+  if (!device_sync_client_) {
+    device_sync_client_ = device_sync::DeviceSyncClientFactory::GetForProfile(
+        ProfileManager::GetActiveUserProfile());
+  }
+
+  device_sync_client_->GetGroupPrivateKeyStatus(
+      base::BindOnce(&MultiDeviceSetupScreen::OnGetGroupPrivateKeyStatus,
+                     weak_factory_.GetWeakPtr()));
+}
+
+void MultiDeviceSetupScreen::OnGetGroupPrivateKeyStatus(
+    device_sync::GroupPrivateKeyStatus status) {
+  PA_LOG(INFO) << "Skipped MultiDevice setup screen; group private key status: "
+               << status;
+
+  switch (status) {
+    case device_sync::GroupPrivateKeyStatus::kWaitingForGroupPrivateKey:
+      RecordOobeMultideviceScreenSkippedReasonHistogram(
+          OobeMultideviceScreenSkippedReason::kWaitingForGroupPrivateKey);
+      return;
+    case device_sync::GroupPrivateKeyStatus::
+        kNoEncryptedGroupPrivateKeyReceived:
+      RecordOobeMultideviceScreenSkippedReasonHistogram(
+          OobeMultideviceScreenSkippedReason::
+              kNoEncryptedGroupPrivateKeyReceived);
+      return;
+    case device_sync::GroupPrivateKeyStatus::kEncryptedGroupPrivateKeyEmpty:
+      RecordOobeMultideviceScreenSkippedReasonHistogram(
+          OobeMultideviceScreenSkippedReason::kEncryptedGroupPrivateKeyEmpty);
+      return;
+    case device_sync::GroupPrivateKeyStatus::
+        kLocalDeviceSyncBetterTogetherKeyMissing:
+      RecordOobeMultideviceScreenSkippedReasonHistogram(
+          OobeMultideviceScreenSkippedReason::
+              kLocalDeviceSyncBetterTogetherKeyMissing);
+      return;
+    case device_sync::GroupPrivateKeyStatus::kGroupPrivateKeyDecryptionFailed:
+      RecordOobeMultideviceScreenSkippedReasonHistogram(
+          OobeMultideviceScreenSkippedReason::kGroupPrivateKeyDecryptionFailed);
+      return;
+    case device_sync::GroupPrivateKeyStatus::
+        kStatusUnavailableBecauseDeviceSyncIsNotInitialized:
+      RecordOobeMultideviceScreenSkippedReasonHistogram(
+          OobeMultideviceScreenSkippedReason::
+              kDeviceSyncNotInitializedDuringGroupPrivateKeyStatusFetch);
+      return;
+    case device_sync::GroupPrivateKeyStatus::
+        kGroupPrivateKeySuccessfullyDecrypted:
+      // This is the expected finished status of the GroupPrivateKey. If this
+      // point is reached, there's no known reason why the setup client wouldn't
+      // be initialized.
+      RecordOobeMultideviceScreenSkippedReasonHistogram(
+          OobeMultideviceScreenSkippedReason::kUnknown);
+      return;
+  }
+}
+
+void MultiDeviceSetupScreen::RecordOobeMultideviceScreenSkippedReasonHistogram(
+    OobeMultideviceScreenSkippedReason reason) {
+  skipped_reason_determined_ = true;
+  UMA_HISTOGRAM_ENUMERATION(
+      "OOBE.StepShownStatus.Multidevice-setup-screen.Skipped", reason);
+}
+
 void MultiDeviceSetupScreen::RecordMultiDeviceSetupOOBEUserChoiceHistogram(
     MultiDeviceSetupOOBEUserChoice value) {
   UMA_HISTOGRAM_ENUMERATION("MultiDeviceSetup.OOBE.UserChoice", value);
diff --git a/chrome/browser/ash/login/screens/multidevice_setup_screen.h b/chrome/browser/ash/login/screens/multidevice_setup_screen.h
index e856903c..62875ecb 100644
--- a/chrome/browser/ash/login/screens/multidevice_setup_screen.h
+++ b/chrome/browser/ash/login/screens/multidevice_setup_screen.h
@@ -11,6 +11,7 @@
 #include "base/callback.h"
 #include "base/memory/weak_ptr.h"
 #include "chrome/browser/ash/login/screens/base_screen.h"
+#include "chromeos/ash/services/device_sync/group_private_key_and_better_together_metadata_status.h"
 
 namespace ash {
 
@@ -20,6 +21,10 @@
 class MultiDeviceSetupClient;
 }
 
+namespace device_sync {
+class DeviceSyncClient;
+}
+
 class MultiDeviceSetupScreen : public BaseScreen {
  public:
   enum class Result { NEXT, NOT_APPLICABLE };
@@ -50,6 +55,11 @@
     setup_client_ = client;
   }
 
+  void set_device_sync_client_for_testing(
+      device_sync::DeviceSyncClient* client) {
+    device_sync_client_ = client;
+  }
+
  protected:
   // BaseScreen:
   bool MaybeSkip(WizardContext& context) override;
@@ -70,16 +80,54 @@
     kMaxValue = kDeclined
   };
 
+  // This enum is tied directly to the OobeMultideviceScreenSkippedReason UMA
+  // enum defined in //tools/metrics/histograms/enums.xml, and should always
+  // reflect it (do not change one without changing the other).  Entries should
+  // be never modified or deleted.  Only additions possible.
+  enum class OobeMultideviceScreenSkippedReason {
+    kPublicSessionOrEphemeralLogin = 0,
+    kHostPhoneAlreadySet = 1,
+    kDeviceSyncFinishedAndNoEligibleHostPhone = 2,
+    kSetupClientNotInitialized = 3,
+    kDeviceSyncNotInitializedDuringBetterTogetherMetadataStatusFetch = 4,
+    kDeviceSyncNotInitializedDuringGroupPrivateKeyStatusFetch = 5,
+    kEncryptedMetadataEmpty = 6,
+    kWaitingForGroupPrivateKey = 7,
+    kNoEncryptedGroupPrivateKeyReceived = 8,
+    kEncryptedGroupPrivateKeyEmpty = 9,
+    kLocalDeviceSyncBetterTogetherKeyMissing = 10,
+    kGroupPrivateKeyDecryptionFailed = 11,
+    kDestroyedBeforeReasonCouldBeDetermined = 12,
+    kUnknown = 13,
+    kMaxValue = kUnknown
+  };
+
   // Inits `setup_client_` if it was not initialized before.
   void TryInitSetupClient();
 
+  void GetBetterTogetherMetadataStatus();
+
+  void OnGetBetterTogetherMetadataStatus(
+      device_sync::BetterTogetherMetadataStatus status);
+
+  void GetGroupPrivateKeyStatus();
+
+  void OnGetGroupPrivateKeyStatus(device_sync::GroupPrivateKeyStatus status);
+
+  void RecordOobeMultideviceScreenSkippedReasonHistogram(
+      OobeMultideviceScreenSkippedReason reason);
+
   static void RecordMultiDeviceSetupOOBEUserChoiceHistogram(
       MultiDeviceSetupOOBEUserChoice value);
 
   multidevice_setup::MultiDeviceSetupClient* setup_client_ = nullptr;
+  device_sync::DeviceSyncClient* device_sync_client_ = nullptr;
+  bool skipped_ = false;
+  bool skipped_reason_determined_ = false;
 
   base::WeakPtr<MultiDeviceSetupScreenView> view_;
   ScreenExitCallback exit_callback_;
+  base::WeakPtrFactory<MultiDeviceSetupScreen> weak_factory_{this};
 };
 
 }  // namespace ash
diff --git a/chrome/browser/ash/login/screens/multidevice_setup_screen_browsertest.cc b/chrome/browser/ash/login/screens/multidevice_setup_screen_browsertest.cc
index b73e680..6318ef45 100644
--- a/chrome/browser/ash/login/screens/multidevice_setup_screen_browsertest.cc
+++ b/chrome/browser/ash/login/screens/multidevice_setup_screen_browsertest.cc
@@ -17,6 +17,7 @@
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/ui/webui/ash/login/gaia_screen_handler.h"
 #include "chrome/browser/ui/webui/ash/login/multidevice_setup_screen_handler.h"
+#include "chromeos/ash/services/device_sync/public/cpp/fake_device_sync_client.h"
 #include "chromeos/ash/services/multidevice_setup/public/cpp/fake_multidevice_setup_client.h"
 #include "content/public/test/browser_test.h"
 
@@ -41,6 +42,9 @@
         std::make_unique<multidevice_setup::FakeMultiDeviceSetupClient>();
     screen->set_multidevice_setup_client_for_testing(
         fake_multidevice_setup_client_.get());
+    fake_device_sync_client_ =
+        std::make_unique<device_sync::FakeDeviceSyncClient>();
+    screen->set_device_sync_client_for_testing(fake_device_sync_client_.get());
     OobeBaseTest::SetUpOnMainThread();
   }
 
@@ -102,10 +106,202 @@
         !Accepted);
   }
 
+  void CheckSkipped(bool should_be_skipped) {
+    if (should_be_skipped) {
+      EXPECT_EQ(screen_result_.value(),
+                MultiDeviceSetupScreen::Result::NOT_APPLICABLE);
+      histogram_tester_.ExpectBucketCount(
+          "OOBE.StepShownStatus.Multidevice-setup-screen", true, 0);
+      histogram_tester_.ExpectBucketCount(
+          "OOBE.StepShownStatus.Multidevice-setup-screen", false, 1);
+      return;
+    }
+
+    histogram_tester_.ExpectBucketCount(
+        "OOBE.StepShownStatus.Multidevice-setup-screen", true, 1);
+    histogram_tester_.ExpectBucketCount(
+        "OOBE.StepShownStatus.Multidevice-setup-screen", false, 0);
+  }
+
+  void CheckHostPhoneAlreadySetSkippedReason() {
+    multidevice_setup::MultiDeviceSetupClient::HostStatusWithDevice
+        host_status_with_device = multidevice_setup::MultiDeviceSetupClient::
+            GenerateDefaultHostStatusWithDevice();
+    host_status_with_device.first =
+        multidevice_setup::mojom::HostStatus::kHostSetButNotYetVerified;
+    fake_multidevice_setup_client_->SetHostStatusWithDevice(
+        host_status_with_device);
+
+    CheckSkippedReason(
+        /*better_together_metadata_status=*/device_sync::
+            BetterTogetherMetadataStatus::kMetadataDecrypted,
+        /*group_private_key_status=*/
+        device_sync::GroupPrivateKeyStatus::
+            kGroupPrivateKeySuccessfullyDecrypted,
+        /*expected_skipped_reason=*/
+        MultiDeviceSetupScreen::OobeMultideviceScreenSkippedReason::
+            kHostPhoneAlreadySet);
+  }
+
+  void CheckDeviceSyncFinishedAndNoEligibleHostPhoneSkippedReason() {
+    CheckSkippedReason(
+        /*better_together_metadata_status=*/device_sync::
+            BetterTogetherMetadataStatus::kMetadataDecrypted,
+        /*group_private_key_status=*/
+        device_sync::GroupPrivateKeyStatus::
+            kGroupPrivateKeySuccessfullyDecrypted,
+        /*expected_skipped_reason=*/
+        MultiDeviceSetupScreen::OobeMultideviceScreenSkippedReason::
+            kDeviceSyncFinishedAndNoEligibleHostPhone);
+  }
+
+  void
+  CheckDeviceSyncNotInitializedDuringBetterTogetherMetadataStatusFetchSkippedReason() {
+    CheckSkippedReason(
+        /*better_together_metadata_status=*/device_sync::
+            BetterTogetherMetadataStatus::
+                kStatusUnavailableBecauseDeviceSyncIsNotInitialized,
+        /*group_private_key_status=*/
+        device_sync::GroupPrivateKeyStatus::
+            kStatusUnavailableBecauseDeviceSyncIsNotInitialized,
+        /*expected_skipped_reason=*/
+        MultiDeviceSetupScreen::OobeMultideviceScreenSkippedReason::
+            kDeviceSyncNotInitializedDuringBetterTogetherMetadataStatusFetch);
+  }
+
+  void
+  CheckDeviceSyncNotInitializedDuringGroupPrivateKeyStatusFetchSkippedReason() {
+    CheckSkippedReason(
+        /*better_together_metadata_status=*/device_sync::
+            BetterTogetherMetadataStatus::kWaitingToProcessDeviceMetadata,
+        /*group_private_key_status=*/
+        device_sync::GroupPrivateKeyStatus::
+            kStatusUnavailableBecauseDeviceSyncIsNotInitialized,
+        /*expected_skipped_reason=*/
+        MultiDeviceSetupScreen::OobeMultideviceScreenSkippedReason::
+            kDeviceSyncNotInitializedDuringGroupPrivateKeyStatusFetch);
+  }
+
+  void CheckEncryptedMetadataEmptySkippedReason() {
+    CheckSkippedReason(
+        /*better_together_metadata_status=*/device_sync::
+            BetterTogetherMetadataStatus::kEncryptedMetadataEmpty,
+        /*group_private_key_status=*/
+        device_sync::GroupPrivateKeyStatus::
+            kGroupPrivateKeySuccessfullyDecrypted,
+        /*expected_skipped_reason=*/
+        MultiDeviceSetupScreen::OobeMultideviceScreenSkippedReason::
+            kEncryptedMetadataEmpty);
+  }
+
+  void CheckWaitingForGroupPrivateKeySkippedReason() {
+    CheckSkippedReason(
+        /*better_together_metadata_status=*/device_sync::
+            BetterTogetherMetadataStatus::kGroupPrivateKeyMissing,
+        /*group_private_key_status=*/
+        device_sync::GroupPrivateKeyStatus::kWaitingForGroupPrivateKey,
+        /*expected_skipped_reason=*/
+        MultiDeviceSetupScreen::OobeMultideviceScreenSkippedReason::
+            kWaitingForGroupPrivateKey);
+  }
+
+  void CheckNoEncryptedGroupPrivateKeyReceivedSkippedReason() {
+    CheckSkippedReason(
+        /*better_together_metadata_status=*/device_sync::
+            BetterTogetherMetadataStatus::kGroupPrivateKeyMissing,
+        /*group_private_key_status=*/
+        device_sync::GroupPrivateKeyStatus::kNoEncryptedGroupPrivateKeyReceived,
+        /*expected_skipped_reason=*/
+        MultiDeviceSetupScreen::OobeMultideviceScreenSkippedReason::
+            kNoEncryptedGroupPrivateKeyReceived);
+  }
+
+  void CheckEncryptedGroupPrivateKeyEmptySkippedReason() {
+    CheckSkippedReason(
+        /*better_together_metadata_status=*/device_sync::
+            BetterTogetherMetadataStatus::kGroupPrivateKeyMissing,
+        /*group_private_key_status=*/
+        device_sync::GroupPrivateKeyStatus::kEncryptedGroupPrivateKeyEmpty,
+        /*expected_skipped_reason=*/
+        MultiDeviceSetupScreen::OobeMultideviceScreenSkippedReason::
+            kEncryptedGroupPrivateKeyEmpty);
+  }
+
+  void CheckLocalDeviceSyncBetterTogetherKeyMissingSkippedReason() {
+    CheckSkippedReason(
+        /*better_together_metadata_status=*/device_sync::
+            BetterTogetherMetadataStatus::kGroupPrivateKeyMissing,
+        /*group_private_key_status=*/
+        device_sync::GroupPrivateKeyStatus::
+            kLocalDeviceSyncBetterTogetherKeyMissing,
+        /*expected_skipped_reason=*/
+        MultiDeviceSetupScreen::OobeMultideviceScreenSkippedReason::
+            kLocalDeviceSyncBetterTogetherKeyMissing);
+  }
+
+  void CheckGroupPrivateKeyDecryptionFailedSkippedReason() {
+    CheckSkippedReason(
+        /*better_together_metadata_status=*/device_sync::
+            BetterTogetherMetadataStatus::kGroupPrivateKeyMissing,
+        /*group_private_key_status=*/
+        device_sync::GroupPrivateKeyStatus::kGroupPrivateKeyDecryptionFailed,
+        /*expected_skipped_reason=*/
+        MultiDeviceSetupScreen::OobeMultideviceScreenSkippedReason::
+            kGroupPrivateKeyDecryptionFailed);
+  }
+
+  void CheckUnknownSkippedReason() {
+    // This combination of better together metadata status and group private key
+    // status should never actually occur together
+    CheckSkippedReason(
+        /*better_together_metadata_status=*/device_sync::
+            BetterTogetherMetadataStatus::kGroupPrivateKeyMissing,
+        /*group_private_key_status=*/
+        device_sync::GroupPrivateKeyStatus::
+            kGroupPrivateKeySuccessfullyDecrypted,
+        /*expected_skipped_reason=*/
+        MultiDeviceSetupScreen::OobeMultideviceScreenSkippedReason::kUnknown);
+  }
+
   absl::optional<MultiDeviceSetupScreen::Result> screen_result_;
   base::HistogramTester histogram_tester_;
 
  private:
+  void CheckSkippedReason(
+      device_sync::BetterTogetherMetadataStatus better_together_metadata_status,
+      device_sync::GroupPrivateKeyStatus group_private_key_status,
+      MultiDeviceSetupScreen::OobeMultideviceScreenSkippedReason
+          expected_skipped_reason) {
+    CheckSkipped(/*should_be_skipped=*/true);
+    EXPECT_TRUE(fake_device_sync_client_
+                    ->GetBetterTogetherMetadataStatusCallbackQueueSize() > 0);
+    fake_device_sync_client_
+        ->InvokePendingGetBetterTogetherMetadataStatusCallback(
+            better_together_metadata_status);
+
+    // The screen should only attempt to fetch the group private key status when
+    // the better together metadata status is kWaitingToProcessDeviceMetadata or
+    // kGroupPrivateKeyMissing
+    if (better_together_metadata_status ==
+            device_sync::BetterTogetherMetadataStatus::
+                kWaitingToProcessDeviceMetadata ||
+        better_together_metadata_status ==
+            device_sync::BetterTogetherMetadataStatus::
+                kGroupPrivateKeyMissing) {
+      EXPECT_TRUE(fake_device_sync_client_
+                      ->GetGroupPrivateKeyStatusCallbackQueueSize() > 0);
+      fake_device_sync_client_->InvokePendingGetGroupPrivateKeyStatusCallback(
+          group_private_key_status);
+    } else {
+      EXPECT_FALSE(fake_device_sync_client_
+                       ->GetGroupPrivateKeyStatusCallbackQueueSize() > 0);
+    }
+
+    histogram_tester_.ExpectBucketCount(
+        "OOBE.StepShownStatus.Multidevice-setup-screen.Skipped",
+        expected_skipped_reason, 1);
+  }
+
   void HandleScreenExit(MultiDeviceSetupScreen::Result result) {
     ASSERT_FALSE(screen_exited_);
     screen_exited_ = true;
@@ -118,6 +314,7 @@
   base::RepeatingClosure screen_exit_callback_;
   std::unique_ptr<multidevice_setup::FakeMultiDeviceSetupClient>
       fake_multidevice_setup_client_;
+  std::unique_ptr<device_sync::FakeDeviceSyncClient> fake_device_sync_client_;
 
   LoginManagerMixin login_manager_mixin_{&mixin_host_};
 };
@@ -135,6 +332,7 @@
       "OOBE.StepCompletionTimeByExitReason.Multidevice-setup-screen.Next", 1);
   histogram_tester_.ExpectTotalCount(
       "OOBE.StepCompletionTime.Multidevice-setup-screen", 1);
+  CheckSkipped(/*should_be_skipped=*/false);
   CheckUserChoice(true);
 }
 
@@ -151,19 +349,86 @@
       "OOBE.StepCompletionTimeByExitReason.Multidevice-setup-screen.Next", 1);
   histogram_tester_.ExpectTotalCount(
       "OOBE.StepCompletionTime.Multidevice-setup-screen", 1);
+  CheckSkipped(/*should_be_skipped=*/false);
   CheckUserChoice(false);
 }
 
-IN_PROC_BROWSER_TEST_F(MultiDeviceSetupScreenTest, Skipped) {
+IN_PROC_BROWSER_TEST_F(MultiDeviceSetupScreenTest,
+                       SkippedReason_HostPhoneAlreadySet) {
   ShowMultiDeviceSetupScreen();
-
   WaitForScreenExit();
-  EXPECT_EQ(screen_result_.value(),
-            MultiDeviceSetupScreen::Result::NOT_APPLICABLE);
-  histogram_tester_.ExpectTotalCount(
-      "OOBE.StepCompletionTimeByExitReason.Multidevice-setup-screen.Next", 0);
-  histogram_tester_.ExpectTotalCount(
-      "OOBE.StepCompletionTime.Multidevice-setup-screen", 0);
+  CheckHostPhoneAlreadySetSkippedReason();
+}
+
+IN_PROC_BROWSER_TEST_F(MultiDeviceSetupScreenTest,
+                       SkippedReason_DeviceSyncFinishedAndNoEligibleHostPhone) {
+  ShowMultiDeviceSetupScreen();
+  WaitForScreenExit();
+  CheckDeviceSyncFinishedAndNoEligibleHostPhoneSkippedReason();
+}
+
+IN_PROC_BROWSER_TEST_F(
+    MultiDeviceSetupScreenTest,
+    SkippedReason_DeviceSyncNotInitializedDuringBetterTogetherMetadataStatusFetch) {
+  ShowMultiDeviceSetupScreen();
+  WaitForScreenExit();
+  CheckDeviceSyncNotInitializedDuringBetterTogetherMetadataStatusFetchSkippedReason();
+}
+
+IN_PROC_BROWSER_TEST_F(
+    MultiDeviceSetupScreenTest,
+    SkippedReason_DeviceSyncNotInitializedDuringGroupPrivateKeyStatusFetch) {
+  ShowMultiDeviceSetupScreen();
+  WaitForScreenExit();
+  CheckDeviceSyncNotInitializedDuringGroupPrivateKeyStatusFetchSkippedReason();
+}
+
+IN_PROC_BROWSER_TEST_F(MultiDeviceSetupScreenTest,
+                       SkippedReason_EncryptedMetadataEmpty) {
+  ShowMultiDeviceSetupScreen();
+  WaitForScreenExit();
+  CheckEncryptedMetadataEmptySkippedReason();
+}
+
+IN_PROC_BROWSER_TEST_F(MultiDeviceSetupScreenTest,
+                       SkippedReason_WaitingForGroupPrivateKey) {
+  ShowMultiDeviceSetupScreen();
+  WaitForScreenExit();
+  CheckWaitingForGroupPrivateKeySkippedReason();
+}
+
+IN_PROC_BROWSER_TEST_F(MultiDeviceSetupScreenTest,
+                       SkippedReason_NoEncryptedGroupPrivateKeyReceived) {
+  ShowMultiDeviceSetupScreen();
+  WaitForScreenExit();
+  CheckNoEncryptedGroupPrivateKeyReceivedSkippedReason();
+}
+
+IN_PROC_BROWSER_TEST_F(MultiDeviceSetupScreenTest,
+                       SkippedReason_EncryptedGroupPrivateKeyEmpty) {
+  ShowMultiDeviceSetupScreen();
+  WaitForScreenExit();
+  CheckEncryptedGroupPrivateKeyEmptySkippedReason();
+}
+
+IN_PROC_BROWSER_TEST_F(MultiDeviceSetupScreenTest,
+                       SkippedReason_LocalDeviceSyncBetterTogetherKeyMissing) {
+  ShowMultiDeviceSetupScreen();
+  WaitForScreenExit();
+  CheckLocalDeviceSyncBetterTogetherKeyMissingSkippedReason();
+}
+
+IN_PROC_BROWSER_TEST_F(MultiDeviceSetupScreenTest,
+                       SkippedReason_GroupPrivateKeyDecryptionFailed) {
+  ShowMultiDeviceSetupScreen();
+  WaitForScreenExit();
+  CheckGroupPrivateKeyDecryptionFailedSkippedReason();
+}
+
+IN_PROC_BROWSER_TEST_F(MultiDeviceSetupScreenTest, SkippedReason_Unknown) {
+  ShowMultiDeviceSetupScreen();
+  WaitForScreenExit();
+  CheckUnknownSkippedReason();
 }
 
 }  // namespace ash
diff --git a/chrome/browser/ash/login/screens/sync_consent_screen.cc b/chrome/browser/ash/login/screens/sync_consent_screen.cc
index 10174cc..f98e60e 100644
--- a/chrome/browser/ash/login/screens/sync_consent_screen.cc
+++ b/chrome/browser/ash/login/screens/sync_consent_screen.cc
@@ -166,6 +166,7 @@
 
 void SyncConsentScreen::Finish(Result result) {
   DCHECK(profile_);
+  profile_->GetPrefs()->SetBoolean(prefs::kRecordArcAppSyncMetrics, true);
   // Always set completed, even if the dialog was skipped (e.g. by policy).
   profile_->GetPrefs()->SetBoolean(prefs::kSyncOobeCompleted, true);
   // Record whether the dialog was shown, skipped, etc.
diff --git a/chrome/browser/ash/login/users/avatar/user_image_manager_browsertest.cc b/chrome/browser/ash/login/users/avatar/user_image_manager_browsertest.cc
index 9cc90a89..154c9a93 100644
--- a/chrome/browser/ash/login/users/avatar/user_image_manager_browsertest.cc
+++ b/chrome/browser/ash/login/users/avatar/user_image_manager_browsertest.cc
@@ -602,7 +602,7 @@
       ADD_FAILURE();
     }
     std::string policy;
-    base::JSONWriter::Write(*policy::test::ConstructExternalDataReference(
+    base::JSONWriter::Write(policy::test::ConstructExternalDataReference(
                                 embedded_test_server()
                                     ->GetURL(std::string("/") + relative_path)
                                     .spec(),
diff --git a/chrome/browser/ash/login/users/wallpaper_policy_browsertest.cc b/chrome/browser/ash/login/users/wallpaper_policy_browsertest.cc
index dff1ad3..f9f6eff 100644
--- a/chrome/browser/ash/login/users/wallpaper_policy_browsertest.cc
+++ b/chrome/browser/ash/login/users/wallpaper_policy_browsertest.cc
@@ -241,7 +241,7 @@
       ADD_FAILURE();
     }
     std::string policy;
-    base::JSONWriter::Write(*policy::test::ConstructExternalDataReference(
+    base::JSONWriter::Write(policy::test::ConstructExternalDataReference(
                                 embedded_test_server()
                                     ->GetURL(std::string("/") + relative_path)
                                     .spec(),
diff --git a/chrome/browser/ash/mobile/mobile_activator.cc b/chrome/browser/ash/mobile/mobile_activator.cc
index 9f104c18..b120b09 100644
--- a/chrome/browser/ash/mobile/mobile_activator.cc
+++ b/chrome/browser/ash/mobile/mobile_activator.cc
@@ -180,9 +180,8 @@
   StartActivation();
 }
 
-void MobileActivator::GetPropertiesFailure(
-    const std::string& error_name,
-    std::unique_ptr<base::Value> error_data) {
+void MobileActivator::GetPropertiesFailure(const std::string& error_name,
+                                           base::Value error_data) {
   NET_LOG(ERROR) << "MobileActivator GetProperties failed for "
                  << NetworkPathId(service_path_) << " Error: " << error_name;
 }
diff --git a/chrome/browser/ash/mobile/mobile_activator.h b/chrome/browser/ash/mobile/mobile_activator.h
index 8704d465..87ba69f 100644
--- a/chrome/browser/ash/mobile/mobile_activator.h
+++ b/chrome/browser/ash/mobile/mobile_activator.h
@@ -18,6 +18,10 @@
 #include "base/values.h"
 #include "chromeos/ash/components/network/network_state_handler_observer.h"
 
+namespace base {
+class Value;
+}  // namespace base
+
 namespace ash {
 
 class NetworkState;
@@ -152,7 +156,7 @@
   void OnShuttingDown() override;
 
   void GetPropertiesFailure(const std::string& error_name,
-                            std::unique_ptr<base::Value> error_data);
+                            base::Value error_data);
   // Handles the signal that the payment portal has finished loading.
   void HandlePortalLoaded(bool success);
   // Handles the signal that the user has finished with the portal.
diff --git a/chrome/browser/ash/net/network_diagnostics/dns_resolver_present_routine_unittest.cc b/chrome/browser/ash/net/network_diagnostics/dns_resolver_present_routine_unittest.cc
index 7ff2832..092a8ad 100644
--- a/chrome/browser/ash/net/network_diagnostics/dns_resolver_present_routine_unittest.cc
+++ b/chrome/browser/ash/net/network_diagnostics/dns_resolver_present_routine_unittest.cc
@@ -73,15 +73,16 @@
 
     // Set up the IP config
     base::Value::Dict ip_config_properties;
-    ip_config_properties.Set(shill::kMethodProperty, base::Value(type));
-    ip_config_properties.Set(shill::kNameServersProperty, dns_servers.Clone());
+    ip_config_properties.Set(shill::kMethodProperty, type);
+    ip_config_properties.Set(shill::kNameServersProperty,
+                             base::Value(dns_servers.Clone()));
     helper()->ip_config_test()->AddIPConfig(
-        kIPConfigPath, base::Value(std::move(ip_config_properties)));
+        kIPConfigPath, base::Value(ip_config_properties.Clone()));
     std::string wifi_device_path =
         helper()->device_test()->GetDevicePathForType(shill::kTypeWifi);
     helper()->device_test()->SetDeviceProperty(
         wifi_device_path, shill::kIPConfigsProperty,
-        base::Value(std::move(ip_config_properties)),
+        base::Value(ip_config_properties.Clone()),
         /*notify_changed=*/true);
     SetServiceProperty(wifi_path(), shill::kIPConfigProperty,
                        base::Value(kIPConfigPath));
diff --git a/chrome/browser/ash/net/network_diagnostics/network_diagnostics_unittest.cc b/chrome/browser/ash/net/network_diagnostics/network_diagnostics_unittest.cc
index 0057e33..ab3e958 100644
--- a/chrome/browser/ash/net/network_diagnostics/network_diagnostics_unittest.cc
+++ b/chrome/browser/ash/net/network_diagnostics/network_diagnostics_unittest.cc
@@ -106,9 +106,9 @@
     // Set up the IP v4 config
     base::Value::Dict ip_config_v4_properties;
     ip_config_v4_properties.Set(shill::kNameServersProperty,
-                                dns_servers.Clone());
+                                base::Value(dns_servers.Clone()));
     helper()->ip_config_test()->AddIPConfig(
-        kIPv4ConfigPath, base::Value(std::move(ip_config_v4_properties)));
+        kIPv4ConfigPath, base::Value(ip_config_v4_properties.Clone()));
     std::string wifi_device_path =
         helper()->device_test()->GetDevicePathForType(shill::kTypeWifi);
     helper()->device_test()->SetDeviceProperty(
diff --git a/chrome/browser/ash/net/network_pref_state_observer_unittest.cc b/chrome/browser/ash/net/network_pref_state_observer_unittest.cc
index de6e0f4..e4ee978 100644
--- a/chrome/browser/ash/net/network_pref_state_observer_unittest.cc
+++ b/chrome/browser/ash/net/network_pref_state_observer_unittest.cc
@@ -105,8 +105,8 @@
   base::Value::Dict proxy_config;
   proxy_config.Set("mode", ProxyPrefs::kPacScriptProxyModeName);
   proxy_config.Set("pac_url", "http://proxy");
-  profile->GetPrefs()->SetDict(proxy_config::prefs::kProxy,
-                               std::move(proxy_config));
+  profile->GetPrefs()->Set(proxy_config::prefs::kProxy,
+                           base::Value(std::move(proxy_config)));
   base::RunLoop().RunUntilIdle();
 
   // Mode should now be MODE_PAC_SCRIPT.
diff --git a/chrome/browser/ash/note_taking_helper_unittest.cc b/chrome/browser/ash/note_taking_helper_unittest.cc
index f789d1d1..d05cc88d 100644
--- a/chrome/browser/ash/note_taking_helper_unittest.cc
+++ b/chrome/browser/ash/note_taking_helper_unittest.cc
@@ -309,10 +309,10 @@
                           extensions::DictionaryBuilder()
                               .Set("scripts", extensions::ListBuilder()
                                                   .Append("background.js")
-                                                  .BuildList())
-                              .BuildDict())
-                     .BuildDict())
-            .BuildDict();
+                                                  .Build())
+                              .Build())
+                     .Build())
+            .Build();
 
     if (action_handlers)
       manifest.Set("action_handlers", std::move(*action_handlers));
diff --git a/chrome/browser/ash/platform_keys/key_permissions/arc_key_permissions_manager_delegate_unittest.cc b/chrome/browser/ash/platform_keys/key_permissions/arc_key_permissions_manager_delegate_unittest.cc
index 017d48d..96e11bdc 100644
--- a/chrome/browser/ash/platform_keys/key_permissions/arc_key_permissions_manager_delegate_unittest.cc
+++ b/chrome/browser/ash/platform_keys/key_permissions/arc_key_permissions_manager_delegate_unittest.cc
@@ -104,8 +104,7 @@
   void SetCorporateUsageInPolicyForPackage(const std::string& package_name,
                                            bool allowed) {
     base::Value::Dict corporate_key_usage;
-    corporate_key_usage.SetByDottedPath("allowCorporateKeyUsage",
-                                        base::Value(allowed));
+    corporate_key_usage.SetByDottedPath("allowCorporateKeyUsage", allowed);
 
     base::Value::Dict policy_value;
     policy_value.Set(package_name, base::Value(std::move(corporate_key_usage)));
@@ -113,7 +112,7 @@
     policy::PolicyMap policy_map;
     policy_map.Set(policy::key::kKeyPermissions, policy::POLICY_LEVEL_MANDATORY,
                    policy::POLICY_SCOPE_MACHINE, policy::POLICY_SOURCE_PLATFORM,
-                   base::Value(std::move(policy_value)), nullptr);
+                   base::Value(policy_value.Clone()), nullptr);
 
     policy_provider_->UpdateChromePolicy(policy_map);
   }
diff --git a/chrome/browser/ash/policy/core/device_local_account_browsertest.cc b/chrome/browser/ash/policy/core/device_local_account_browsertest.cc
index e311f9e..1d9127f 100644
--- a/chrome/browser/ash/policy/core/device_local_account_browsertest.cc
+++ b/chrome/browser/ash/policy/core/device_local_account_browsertest.cc
@@ -1315,12 +1315,10 @@
   embedded_test_server()->StartAcceptingConnections();
 
   // Specify an external data reference for the key::kUserAvatarImage policy.
-  std::unique_ptr<base::Value::Dict> metadata =
-      test::ConstructExternalDataReference(
-          embedded_test_server()->GetURL(kExternalDataPath).spec(),
-          kExternalData);
+  base::Value metadata = test::ConstructExternalDataReference(
+      embedded_test_server()->GetURL(kExternalDataPath).spec(), kExternalData);
   std::string policy;
-  base::JSONWriter::Write(*metadata, &policy);
+  base::JSONWriter::Write(metadata, &policy);
   device_local_account_policy_.payload().mutable_useravatarimage()->set_value(
       policy);
   UploadAndInstallDeviceLocalAccountPolicy();
@@ -1366,7 +1364,7 @@
       PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()));
   policy_entry = policies.Get(key::kUserAvatarImage);
   ASSERT_TRUE(policy_entry);
-  EXPECT_EQ(*metadata, *policy_entry->value(base::Value::Type::DICT));
+  EXPECT_EQ(metadata, *policy_entry->value(base::Value::Type::DICT));
   ASSERT_TRUE(policy_entry->external_data_fetcher);
 
   // Retrieve the external data via the ProfilePolicyConnector. The retrieval
@@ -1404,7 +1402,7 @@
 
   std::string policy;
   base::JSONWriter::Write(
-      *test::ConstructExternalDataReference(
+      test::ConstructExternalDataReference(
           embedded_test_server()
               ->GetURL(std::string("/") +
                        ash::test::kUserAvatarImage1RelativePath)
diff --git a/chrome/browser/ash/policy/core/device_policy_decoder.cc b/chrome/browser/ash/policy/core/device_policy_decoder.cc
index e369e7ae..89ced73 100644
--- a/chrome/browser/ash/policy/core/device_policy_decoder.cc
+++ b/chrome/browser/ash/policy/core/device_policy_decoder.cc
@@ -1642,8 +1642,8 @@
   if (policy.has_tpm_firmware_update_settings()) {
     policies->Set(key::kTPMFirmwareUpdateSettings, POLICY_LEVEL_MANDATORY,
                   POLICY_SCOPE_MACHINE, POLICY_SOURCE_CLOUD,
-                  std::move(*(ash::tpm_firmware_update::DecodeSettingsProto(
-                      policy.tpm_firmware_update_settings()))),
+                  ash::tpm_firmware_update::DecodeSettingsProto(
+                      policy.tpm_firmware_update_settings()),
                   nullptr);
   }
 
diff --git a/chrome/browser/ash/policy/core/device_policy_decoder_unittest.cc b/chrome/browser/ash/policy/core/device_policy_decoder_unittest.cc
index 1f70eb3..86af49d 100644
--- a/chrome/browser/ash/policy/core/device_policy_decoder_unittest.cc
+++ b/chrome/browser/ash/policy/core/device_policy_decoder_unittest.cc
@@ -67,8 +67,8 @@
   ~DevicePolicyDecoderTest() override = default;
 
  protected:
-  std::unique_ptr<base::Value> GetWallpaperDict() const;
-  std::unique_ptr<base::Value> GetBluetoothServiceAllowedList() const;
+  base::Value GetWallpaperDict() const;
+  base::Value GetBluetoothServiceAllowedList() const;
   void DecodeDevicePolicyTestHelper(
       const em::ChromeDeviceSettingsProto& device_policy,
       const std::string& policy_path,
@@ -78,20 +78,19 @@
       const std::string& policy_path) const;
 };
 
-std::unique_ptr<base::Value> DevicePolicyDecoderTest::GetWallpaperDict() const {
+base::Value DevicePolicyDecoderTest::GetWallpaperDict() const {
   base::Value::Dict dict;
   dict.Set(kWallpaperUrlPropertyName, kWallpaperUrlPropertyValue);
   dict.Set(kWallpaperHashPropertyName, kWallpaperHashPropertyValue);
-  return std::make_unique<base::Value>(std::move(dict));
+  return base::Value(std::move(dict));
 }
 
-std::unique_ptr<base::Value>
-DevicePolicyDecoderTest::GetBluetoothServiceAllowedList() const {
+base::Value DevicePolicyDecoderTest::GetBluetoothServiceAllowedList() const {
   base::Value::List list;
   list.Append(kValidBluetoothServiceUUID4);
   list.Append(kValidBluetoothServiceUUID8);
   list.Append(kValidBluetoothServiceUUID32);
-  return std::make_unique<base::Value>(std::move(list));
+  return base::Value(std::move(list));
 }
 
 void DevicePolicyDecoderTest::DecodeDevicePolicyTestHelper(
@@ -168,7 +167,7 @@
       kWallpaperJsonUnknownProperty, key::kDeviceWallpaperImage, &error);
   std::string localized_error = l10n_util::GetStringFUTF8(
       IDS_POLICY_PROTO_PARSING_ERROR, base::UTF8ToUTF16(error));
-  EXPECT_EQ(*GetWallpaperDict(), decoded_json.value());
+  EXPECT_EQ(GetWallpaperDict(), decoded_json.value());
   EXPECT_EQ(
       "Policy parsing error: Dropped unknown properties: Unknown property: "
       "unknown-field (at DeviceWallpaperImage)",
@@ -179,7 +178,7 @@
   std::string error;
   absl::optional<base::Value> decoded_json = DecodeJsonStringAndNormalize(
       kWallpaperJson, key::kDeviceWallpaperImage, &error);
-  EXPECT_EQ(*GetWallpaperDict(), decoded_json.value());
+  EXPECT_EQ(GetWallpaperDict(), decoded_json.value());
   EXPECT_TRUE(error.empty());
 }
 
@@ -370,7 +369,7 @@
   absl::optional<base::Value> decoded_json = DecodeJsonStringAndNormalize(
       kValidBluetoothServiceUUIDList, key::kDeviceAllowedBluetoothServices,
       &error);
-  EXPECT_EQ(*GetBluetoothServiceAllowedList(), decoded_json.value());
+  EXPECT_EQ(GetBluetoothServiceAllowedList(), decoded_json.value());
   EXPECT_TRUE(error.empty());
 }
 
diff --git a/chrome/browser/ash/policy/external_data/cloud_external_data_manager_base.cc b/chrome/browser/ash/policy/external_data/cloud_external_data_manager_base.cc
index 27c1f9f1..fa7e95a 100644
--- a/chrome/browser/ash/policy/external_data/cloud_external_data_manager_base.cc
+++ b/chrome/browser/ash/policy/external_data/cloud_external_data_manager_base.cc
@@ -45,7 +45,7 @@
 // external data even if no |max_size| was specified in policy_templates.json.
 int g_max_external_data_size_for_testing = 0;
 
-// Keys for 'DictionaryValue' objects
+// Keys for 'Value::Dict' objects
 const char kUrlKey[] = "url";
 const char kHashKey[] = "hash";
 const char kCustomIconKey[] = "custom_icon";
diff --git a/chrome/browser/ash/policy/external_data/cloud_external_data_manager_base_test_util.cc b/chrome/browser/ash/policy/external_data/cloud_external_data_manager_base_test_util.cc
index 474dcbf..b5834d9d 100644
--- a/chrome/browser/ash/policy/external_data/cloud_external_data_manager_base_test_util.cc
+++ b/chrome/browser/ash/policy/external_data/cloud_external_data_manager_base_test_util.cc
@@ -31,7 +31,7 @@
 namespace policy {
 
 namespace {
-// Keys for the 'Value' objects
+// Keys for 'Value::Dict' objects
 const char kUrlKey[] = "url";
 const char kHashKey[] = "hash";
 }  // namespace
@@ -48,14 +48,13 @@
   std::move(done_callback).Run();
 }
 
-std::unique_ptr<base::Value::Dict> ConstructExternalDataReference(
-    const std::string& url,
-    const std::string& data) {
+base::Value ConstructExternalDataReference(const std::string& url,
+                                           const std::string& data) {
   const std::string hash = crypto::SHA256HashString(data);
-  auto metadata = std::make_unique<base::Value::Dict>();
-  metadata->Set(kUrlKey, url);
-  metadata->Set(kHashKey, base::HexEncode(hash.c_str(), hash.size()));
-  return metadata;
+  base::Value::Dict metadata;
+  metadata.Set(kUrlKey, url);
+  metadata.Set(kHashKey, base::HexEncode(hash.c_str(), hash.size()));
+  return base::Value(std::move(metadata));
 }
 
 std::string ConstructExternalDataPolicy(
@@ -75,7 +74,7 @@
 
   std::string policy;
   EXPECT_TRUE(base::JSONWriter::Write(
-      *ConstructExternalDataReference(url, external_data), &policy));
+      ConstructExternalDataReference(url, external_data), &policy));
   return policy;
 }
 
diff --git a/chrome/browser/ash/policy/external_data/cloud_external_data_manager_base_test_util.h b/chrome/browser/ash/policy/external_data/cloud_external_data_manager_base_test_util.h
index 94377433..43643f9 100644
--- a/chrome/browser/ash/policy/external_data/cloud_external_data_manager_base_test_util.h
+++ b/chrome/browser/ash/policy/external_data/cloud_external_data_manager_base_test_util.h
@@ -10,7 +10,10 @@
 
 #include "base/callback_forward.h"
 #include "base/files/file_path.h"
-#include "base/values.h"
+
+namespace base {
+class Value;
+}
 
 namespace net {
 namespace test_server {
@@ -20,8 +23,6 @@
 
 namespace policy {
 
-class CloudPolicyCore;
-
 namespace test {
 
 // Passes |data| to |destination| and invokes |done_callback| to indicate that
@@ -34,9 +35,8 @@
 
 // Constructs a value that points a policy referencing external data at |url|
 // and sets the expected hash of the external data to that of |data|.
-std::unique_ptr<base::Value::Dict> ConstructExternalDataReference(
-    const std::string& url,
-    const std::string& data);
+base::Value ConstructExternalDataReference(const std::string& url,
+                                           const std::string& data);
 
 // Constructs the external data policy from the content of the file located on
 // |external_data_path|.
@@ -44,15 +44,6 @@
     const net::test_server::EmbeddedTestServer& test_server,
     const std::string& external_data_path);
 
-// TODO(bartfab): Makes an arbitrary |policy| in |core| reference external data
-// as specified in |metadata|. This is only done because there are no policies
-// that reference external data yet. Once the first such policy is added, it
-// will be sufficient to set its value to |metadata| and this method should be
-// removed.
-void SetExternalDataReference(CloudPolicyCore* core,
-                              const std::string& policy,
-                              std::unique_ptr<base::Value::Dict> metadata);
-
 }  // namespace test
 }  // namespace policy
 
diff --git a/chrome/browser/ash/policy/external_data/cloud_external_data_manager_base_unittest.cc b/chrome/browser/ash/policy/external_data/cloud_external_data_manager_base_unittest.cc
index 75cc8b6..6f7f898d 100644
--- a/chrome/browser/ash/policy/external_data/cloud_external_data_manager_base_unittest.cc
+++ b/chrome/browser/ash/policy/external_data/cloud_external_data_manager_base_unittest.cc
@@ -175,10 +175,10 @@
 base::Value CloudExternalDataManagerBaseTest::ConstructMetadata(
     const std::string& url,
     const std::string& hash) {
-  base::Value metadata(base::Value::Type::DICTIONARY);
-  metadata.SetStringKey("url", url);
-  metadata.SetStringKey("hash", base::HexEncode(hash.c_str(), hash.size()));
-  return metadata;
+  base::Value::Dict metadata;
+  metadata.Set("url", url);
+  metadata.Set("hash", base::HexEncode(hash.c_str(), hash.size()));
+  return base::Value(std::move(metadata));
 }
 
 void CloudExternalDataManagerBaseTest::AddMetadataToWebAppPolicyValue(
diff --git a/chrome/browser/ash/policy/external_data/cloud_external_data_policy_observer_unittest.cc b/chrome/browser/ash/policy/external_data/cloud_external_data_policy_observer_unittest.cc
index 1e55dad3..fa49967 100644
--- a/chrome/browser/ash/policy/external_data/cloud_external_data_policy_observer_unittest.cc
+++ b/chrome/browser/ash/policy/external_data/cloud_external_data_policy_observer_unittest.cc
@@ -83,7 +83,7 @@
   ASSERT_TRUE(base::ReadFileToString(
       test_data_dir.Append("chromeos").Append(file_name), policy_data));
   base::JSONWriter::Write(
-      *test::ConstructExternalDataReference(url, *policy_data), policy);
+      test::ConstructExternalDataReference(url, *policy_data), policy);
 }
 
 }  // namespace
diff --git a/chrome/browser/ash/policy/external_data/user_cloud_external_data_manager_browsertest.cc b/chrome/browser/ash/policy/external_data/user_cloud_external_data_manager_browsertest.cc
index 746443c..41c8ad2 100644
--- a/chrome/browser/ash/policy/external_data/user_cloud_external_data_manager_browsertest.cc
+++ b/chrome/browser/ash/policy/external_data/user_cloud_external_data_manager_browsertest.cc
@@ -68,7 +68,7 @@
   }
 
   std::string external_data_;
-  std::unique_ptr<base::Value::Dict> metadata_;
+  base::Value metadata_;
 };
 
 IN_PROC_BROWSER_TEST_F(UserCloudExternalDataManagerTest, FetchExternalData) {
@@ -81,7 +81,7 @@
   ASSERT_TRUE(profile);
 
   std::string value;
-  ASSERT_TRUE(base::JSONWriter::Write(*metadata_, &value));
+  ASSERT_TRUE(base::JSONWriter::Write(metadata_, &value));
   enterprise_management::CloudPolicySettings policy;
   policy.mutable_wallpaperimage()->set_value(value);
   user_policy_helper()->SetPolicyAndWait(policy, profile);
@@ -104,7 +104,7 @@
       PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()));
   const PolicyMap::Entry* policy_entry = policies.Get(key::kWallpaperImage);
   ASSERT_TRUE(policy_entry);
-  EXPECT_EQ(*metadata_, *policy_entry->value(base::Value::Type::DICT));
+  EXPECT_EQ(metadata_, *policy_entry->value(base::Value::Type::DICT));
   ASSERT_TRUE(policy_entry->external_data_fetcher);
 
   base::RunLoop run_loop;
diff --git a/chrome/browser/ash/policy/handlers/tpm_auto_update_mode_policy_handler_unittest.cc b/chrome/browser/ash/policy/handlers/tpm_auto_update_mode_policy_handler_unittest.cc
index a6b9083..c3659f1 100644
--- a/chrome/browser/ash/policy/handlers/tpm_auto_update_mode_policy_handler_unittest.cc
+++ b/chrome/browser/ash/policy/handlers/tpm_auto_update_mode_policy_handler_unittest.cc
@@ -54,7 +54,7 @@
   void SetAutoUpdateMode(AutoUpdateMode auto_update_mode) {
     base::Value::Dict dict;
     dict.Set(ash::tpm_firmware_update::kSettingsKeyAutoUpdateMode,
-             base::Value(static_cast<int>(auto_update_mode)));
+             static_cast<int>(auto_update_mode));
     scoped_testing_cros_settings_.device_settings()->Set(
         ash::kTPMFirmwareUpdateSettings, base::Value(std::move(dict)));
     base::RunLoop().RunUntilIdle();
diff --git a/chrome/browser/ash/policy/off_hours/off_hours_proto_parser.h b/chrome/browser/ash/policy/off_hours/off_hours_proto_parser.h
index 81fab000..3e5cb97 100644
--- a/chrome/browser/ash/policy/off_hours/off_hours_proto_parser.h
+++ b/chrome/browser/ash/policy/off_hours/off_hours_proto_parser.h
@@ -39,7 +39,7 @@
 absl::optional<std::string> ExtractTimezoneFromProto(
     const enterprise_management::DeviceOffHoursProto& container);
 
-// Return DictionaryValue in format:
+// Return Value::Dict in format:
 // { "timezone" : string,
 //   "intervals" : list of "OffHours" Intervals,
 //   "ignored_policy_proto_tags" : integer list }
diff --git a/chrome/browser/ash/policy/reporting/arc_app_install_event_logger_unittest.cc b/chrome/browser/ash/policy/reporting/arc_app_install_event_logger_unittest.cc
index 20c1d0f..a02df85d 100644
--- a/chrome/browser/ash/policy/reporting/arc_app_install_event_logger_unittest.cc
+++ b/chrome/browser/ash/policy/reporting/arc_app_install_event_logger_unittest.cc
@@ -265,7 +265,7 @@
     return policy_map;
   }
 
-  base::Value::Dict CreateComplianceReport(
+  base::Value CreateComplianceReport(
       std::set<std::string> noncompliant_packages) {
     base::Value::List details;
 
@@ -278,7 +278,7 @@
 
     base::Value::Dict compliance_report;
     compliance_report.Set("nonComplianceDetails", std::move(details));
-    return compliance_report;
+    return base::Value(std::move(compliance_report));
   }
 
   content::BrowserTaskEnvironment task_environment_;
diff --git a/chrome/browser/ash/policy/reporting/metrics_reporting/network/network_telemetry_sampler_unittest.cc b/chrome/browser/ash/policy/reporting/metrics_reporting/network/network_telemetry_sampler_unittest.cc
index eebdd25..1b32183 100644
--- a/chrome/browser/ash/policy/reporting/metrics_reporting/network/network_telemetry_sampler_unittest.cc
+++ b/chrome/browser/ash/policy/reporting/metrics_reporting/network/network_telemetry_sampler_unittest.cc
@@ -136,9 +136,8 @@
                                          base::Value(device_path));
       base::Value::Dict ip_config_properties;
       ip_config_properties.Set(shill::kAddressProperty,
-                               base::Value(network_data.ip_address));
-      ip_config_properties.Set(shill::kGatewayProperty,
-                               base::Value(network_data.gateway));
+                               network_data.ip_address);
+      ip_config_properties.Set(shill::kGatewayProperty, network_data.gateway);
       const std::string kIPConfigPath =
           base::StrCat({"test_ip_config", network_data.guid});
       ip_config_client->AddIPConfig(
diff --git a/chrome/browser/ash/policy/status_collector/activity_storage.cc b/chrome/browser/ash/policy/status_collector/activity_storage.cc
index 2797539..38d53622b 100644
--- a/chrome/browser/ash/policy/status_collector/activity_storage.cc
+++ b/chrome/browser/ash/policy/status_collector/activity_storage.cc
@@ -77,7 +77,7 @@
         if (duration <= 0)
           return;
         const std::string key = MakeActivityPeriodPrefKey(day_key, activity_id);
-        copy.Set(key, base::saturated_cast<int>(duration));
+        copy.SetByDottedPath(key, base::saturated_cast<int>(duration));
       },
       std::ref(copy), min_day_key, max_day_key));
 
diff --git a/chrome/browser/ash/policy/status_collector/device_status_collector_browsertest.cc b/chrome/browser/ash/policy/status_collector/device_status_collector_browsertest.cc
index 6f7dea6..ee33574 100644
--- a/chrome/browser/ash/policy/status_collector/device_status_collector_browsertest.cc
+++ b/chrome/browser/ash/policy/status_collector/device_status_collector_browsertest.cc
@@ -3936,13 +3936,12 @@
                                          base::Value(kShillFakeProfilePath));
       if (strlen(fake_network.address) > 0) {
         // Set the IP config.
-        base::DictionaryValue ip_config_properties;
-        ip_config_properties.SetKey(shill::kAddressProperty,
-                                    base::Value(fake_network.address));
-        ip_config_properties.SetKey(shill::kGatewayProperty,
-                                    base::Value(fake_network.gateway));
+        base::Value::Dict ip_config_properties;
+        ip_config_properties.Set(shill::kAddressProperty, fake_network.address);
+        ip_config_properties.Set(shill::kGatewayProperty, fake_network.gateway);
         const std::string kIPConfigPath = "test_ip_config";
-        ip_config_client->AddIPConfig(kIPConfigPath, ip_config_properties);
+        ip_config_client->AddIPConfig(
+            kIPConfigPath, base::Value(std::move(ip_config_properties)));
         service_client->SetServiceProperty(fake_network.name,
                                            shill::kIPConfigProperty,
                                            base::Value(kIPConfigPath));
diff --git a/chrome/browser/ash/preferences.cc b/chrome/browser/ash/preferences.cc
index fba03e8b..83e5eadb 100644
--- a/chrome/browser/ash/preferences.cc
+++ b/chrome/browser/ash/preferences.cc
@@ -517,6 +517,8 @@
 
   registry->RegisterBooleanPref(prefs::kSyncOobeCompleted, false);
 
+  registry->RegisterBooleanPref(prefs::kRecordArcAppSyncMetrics, false);
+
   registry->RegisterBooleanPref(::prefs::kTPMFirmwareUpdateCleanupDismissed,
                                 false);
 
diff --git a/chrome/browser/ash/settings/device_settings_provider.cc b/chrome/browser/ash/settings/device_settings_provider.cc
index b3120a9..109216c 100644
--- a/chrome/browser/ash/settings/device_settings_provider.cc
+++ b/chrome/browser/ash/settings/device_settings_provider.cc
@@ -968,9 +968,8 @@
 
   if (policy.has_tpm_firmware_update_settings()) {
     new_values_cache->SetValue(kTPMFirmwareUpdateSettings,
-                               base::Value::FromUniquePtrValue(
-                                   tpm_firmware_update::DecodeSettingsProto(
-                                       policy.tpm_firmware_update_settings())));
+                               tpm_firmware_update::DecodeSettingsProto(
+                                   policy.tpm_firmware_update_settings()));
   }
 
   if (policy.has_device_minimum_version()) {
diff --git a/chrome/browser/ash/tpm_firmware_update.cc b/chrome/browser/ash/tpm_firmware_update.cc
index a10db25d..bfb1438b 100644
--- a/chrome/browser/ash/tpm_firmware_update.cc
+++ b/chrome/browser/ash/tpm_firmware_update.cc
@@ -63,25 +63,24 @@
     "allow-user-initiated-preserve-device-state";
 const char kSettingsKeyAutoUpdateMode[] = "auto-update-mode";
 
-std::unique_ptr<base::Value> DecodeSettingsProto(
+base::Value DecodeSettingsProto(
     const enterprise_management::TPMFirmwareUpdateSettingsProto& settings) {
   base::Value::Dict result;
+
   if (settings.has_allow_user_initiated_powerwash()) {
     result.Set(kSettingsKeyAllowPowerwash,
-               base::Value(settings.allow_user_initiated_powerwash()));
+               settings.allow_user_initiated_powerwash());
   }
   if (settings.has_allow_user_initiated_preserve_device_state()) {
-    result.Set(
-        kSettingsKeyAllowPreserveDeviceState,
-        base::Value(settings.allow_user_initiated_preserve_device_state()));
+    result.Set(kSettingsKeyAllowPreserveDeviceState,
+               settings.allow_user_initiated_preserve_device_state());
   }
 
   if (settings.has_auto_update_mode()) {
-    result.Set(kSettingsKeyAutoUpdateMode,
-               base::Value(settings.auto_update_mode()));
+    result.Set(kSettingsKeyAutoUpdateMode, settings.auto_update_mode());
   }
 
-  return std::make_unique<base::Value>(std::move(result));
+  return base::Value(std::move(result));
 }
 
 // AvailabilityChecker tracks TPM firmware update availability information
diff --git a/chrome/browser/ash/tpm_firmware_update.h b/chrome/browser/ash/tpm_firmware_update.h
index fb47182f..c1b37d9 100644
--- a/chrome/browser/ash/tpm_firmware_update.h
+++ b/chrome/browser/ash/tpm_firmware_update.h
@@ -10,7 +10,10 @@
 
 #include "base/callback_forward.h"
 #include "base/time/time.h"
-#include "base/values.h"
+
+namespace base {
+class Value;
+}
 
 namespace enterprise_management {
 class TPMFirmwareUpdateSettingsProto;
@@ -40,7 +43,7 @@
 extern const char kSettingsKeyAutoUpdateMode[];
 
 // Decodes the TPM firmware update settings into base::Value representation.
-std::unique_ptr<base::Value> DecodeSettingsProto(
+base::Value DecodeSettingsProto(
     const enterprise_management::TPMFirmwareUpdateSettingsProto& settings);
 
 // Check what update modes are allowed. The |timeout| parameter determines how
diff --git a/chrome/browser/ash/tpm_firmware_update_unittest.cc b/chrome/browser/ash/tpm_firmware_update_unittest.cc
index f34a298..41b3fe3f 100644
--- a/chrome/browser/ash/tpm_firmware_update_unittest.cc
+++ b/chrome/browser/ash/tpm_firmware_update_unittest.cc
@@ -40,12 +40,14 @@
       enterprise_management::
           TPMFirmwareUpdateSettingsProto_AutoUpdateMode_USER_ACKNOWLEDGMENT);
   auto dict = DecodeSettingsProto(settings);
-  ASSERT_TRUE(dict);
-  EXPECT_THAT(dict->FindBoolKey("allow-user-initiated-powerwash"),
+  ASSERT_TRUE(dict.is_dict());
+  EXPECT_THAT(dict.GetDict().FindBool("allow-user-initiated-powerwash"),
               Optional(true));
-  EXPECT_THAT(dict->FindBoolKey("allow-user-initiated-preserve-device-state"),
-              Optional(true));
-  int update_mode_value = dict->FindIntKey("auto-update-mode").value_or(0);
+  EXPECT_THAT(
+      dict.GetDict().FindBool("allow-user-initiated-preserve-device-state"),
+      Optional(true));
+  int update_mode_value =
+      dict.GetDict().FindInt("auto-update-mode").value_or(0);
   EXPECT_EQ(2, update_mode_value);
 }
 
@@ -292,10 +294,9 @@
 
   void SetPolicy(const std::set<Mode>& modes) {
     base::Value::Dict dict;
-    dict.Set(kSettingsKeyAllowPowerwash,
-             base::Value(modes.count(Mode::kPowerwash) > 0));
+    dict.Set(kSettingsKeyAllowPowerwash, modes.count(Mode::kPowerwash) > 0);
     dict.Set(kSettingsKeyAllowPreserveDeviceState,
-             base::Value(modes.count(Mode::kPreserveDeviceState) > 0));
+             modes.count(Mode::kPreserveDeviceState) > 0);
     cros_settings_test_helper_.Set(kTPMFirmwareUpdateSettings,
                                    base::Value(std::move(dict)));
   }
diff --git a/chrome/browser/background/background_application_list_model_unittest.cc b/chrome/browser/background/background_application_list_model_unittest.cc
index 83af0e8..f58088ed 100644
--- a/chrome/browser/background/background_application_list_model_unittest.cc
+++ b/chrome/browser/background/background_application_list_model_unittest.cc
@@ -87,23 +87,20 @@
 static scoped_refptr<Extension> CreateExtension(
     const std::string& name,
     bool background_permission) {
-  base::DictionaryValue manifest;
-  manifest.SetStringPath(extensions::manifest_keys::kVersion, "1.0.0.0");
-  manifest.SetIntPath(extensions::manifest_keys::kManifestVersion, 2);
-  manifest.SetStringPath(extensions::manifest_keys::kName, name);
-  base::ListValue permissions;
+  base::Value::Dict manifest;
+  manifest.Set(extensions::manifest_keys::kVersion, "1.0.0.0");
+  manifest.Set(extensions::manifest_keys::kManifestVersion, 2);
+  manifest.Set(extensions::manifest_keys::kName, name);
+  base::Value::List permissions;
   if (background_permission) {
     permissions.Append("background");
   }
-  manifest.SetKey(extensions::manifest_keys::kPermissions,
-                  std::move(permissions));
+  manifest.Set(extensions::manifest_keys::kPermissions, std::move(permissions));
 
   std::string error;
-  scoped_refptr<Extension> extension;
-
-  extension = Extension::Create(
+  scoped_refptr<Extension> extension = Extension::Create(
       bogus_file_pathname(name), extensions::mojom::ManifestLocation::kInternal,
-      manifest.GetDict(), Extension::NO_FLAGS, &error);
+      manifest, Extension::NO_FLAGS, &error);
 
   // Cannot ASSERT_* here because that attempts an illegitimate return.
   // Cannot EXPECT_NE here because that assumes non-pointers unlike EXPECT_EQ
diff --git a/chrome/browser/background/background_contents_service.cc b/chrome/browser/background/background_contents_service.cc
index a6a2b9d..957dd05b 100644
--- a/chrome/browser/background/background_contents_service.cc
+++ b/chrome/browser/background/background_contents_service.cc
@@ -229,13 +229,13 @@
 }  // namespace
 
 // Keys for the information we store about individual BackgroundContents in
-// prefs. There is one top-level DictionaryValue (stored at
+// prefs. There is one top-level base::Value::Dict (stored at
 // prefs::kRegisteredBackgroundContents). Information about each
-// BackgroundContents is stored under that top-level DictionaryValue, keyed
+// BackgroundContents is stored under that top-level base::Value::Dict, keyed
 // by the parent application ID for easy lookup.
 //
 // kRegisteredBackgroundContents:
-//    DictionaryValue {
+//    base::Value::Dict {
 //       <appid_1>: { "url": <url1>, "name": <frame_name> },
 //       <appid_2>: { "url": <url2>, "name": <frame_name> },
 //         ... etc ...
diff --git a/chrome/browser/bookmarks/bookmark_html_writer.cc b/chrome/browser/bookmarks/bookmark_html_writer.cc
index 5c83e0a2..ec8daad 100644
--- a/chrome/browser/bookmarks/bookmark_html_writer.cc
+++ b/chrome/browser/bookmarks/bookmark_html_writer.cc
@@ -180,27 +180,25 @@
       return;
     }
 
-    base::Value* roots = bookmarks_.FindDictKey(BookmarkCodec::kRootsKey);
+    base::Value::Dict* roots =
+        bookmarks_.GetDict().FindDict(BookmarkCodec::kRootsKey);
     DCHECK(roots);
 
-    base::Value* root_folder_value =
-        roots->FindDictKey(BookmarkCodec::kBookmarkBarFolderNameKey);
-    base::Value* other_folder_value =
-        roots->FindDictKey(BookmarkCodec::kOtherBookmarkFolderNameKey);
-    base::Value* mobile_folder_value =
-        roots->FindDictKey(BookmarkCodec::kMobileBookmarkFolderNameKey);
+    base::Value::Dict* root_folder_value =
+        roots->FindDict(BookmarkCodec::kBookmarkBarFolderNameKey);
+    base::Value::Dict* other_folder_value =
+        roots->FindDict(BookmarkCodec::kOtherBookmarkFolderNameKey);
+    base::Value::Dict* mobile_folder_value =
+        roots->FindDict(BookmarkCodec::kMobileBookmarkFolderNameKey);
     DCHECK(root_folder_value);
     DCHECK(other_folder_value);
     DCHECK(mobile_folder_value);
 
     IncrementIndent();
 
-    if (!WriteNode(*static_cast<base::DictionaryValue*>(root_folder_value),
-                   BookmarkNode::BOOKMARK_BAR) ||
-        !WriteNode(*static_cast<base::DictionaryValue*>(other_folder_value),
-                   BookmarkNode::OTHER_NODE) ||
-        !WriteNode(*static_cast<base::DictionaryValue*>(mobile_folder_value),
-                   BookmarkNode::MOBILE)) {
+    if (!WriteNode(*root_folder_value, BookmarkNode::BOOKMARK_BAR) ||
+        !WriteNode(*other_folder_value, BookmarkNode::OTHER_NODE) ||
+        !WriteNode(*mobile_folder_value, BookmarkNode::MOBILE)) {
       NotifyOnFinish(BookmarksExportObserver::Result::kCouldNotWriteNodes);
       return;
     }
@@ -313,13 +311,12 @@
   }
 
   // Writes the node and all its children, returning true on success.
-  bool WriteNode(const base::Value& value, BookmarkNode::Type folder_type) {
-    DCHECK(value.is_dict());
-    const std::string* title_ptr = value.FindStringKey(BookmarkCodec::kNameKey);
+  bool WriteNode(const base::Value::Dict& value,
+                 BookmarkNode::Type folder_type) {
+    const std::string* title_ptr = value.FindString(BookmarkCodec::kNameKey);
     const std::string* date_added_string =
-        value.FindStringKey(BookmarkCodec::kDateAddedKey);
-    const std::string* type_string =
-        value.FindStringKey(BookmarkCodec::kTypeKey);
+        value.FindString(BookmarkCodec::kDateAddedKey);
+    const std::string* type_string = value.FindString(BookmarkCodec::kTypeKey);
     if (!title_ptr || !date_added_string || !type_string ||
         (*type_string != BookmarkCodec::kTypeURL &&
          *type_string != BookmarkCodec::kTypeFolder)) {
@@ -329,8 +326,7 @@
 
     std::string title = *title_ptr;
     if (*type_string == BookmarkCodec::kTypeURL) {
-      const std::string* url_string =
-          value.FindStringKey(BookmarkCodec::kURLKey);
+      const std::string* url_string = value.FindString(BookmarkCodec::kURLKey);
       if (!url_string) {
         NOTREACHED();
         return false;
@@ -362,9 +358,9 @@
 
     // Folder.
     const std::string* last_modified_date =
-        value.FindStringKey(BookmarkCodec::kDateModifiedKey);
-    const base::Value* child_values =
-        value.FindListKey(BookmarkCodec::kChildrenKey);
+        value.FindString(BookmarkCodec::kDateModifiedKey);
+    const base::Value::List* child_values =
+        value.FindList(BookmarkCodec::kChildrenKey);
     if (!last_modified_date || !child_values) {
       NOTREACHED();
       return false;
@@ -398,13 +394,14 @@
     }
 
     // Write the children.
-    for (const base::Value& child_value : child_values->GetList()) {
+    for (const base::Value& child_value : *child_values) {
       if (!child_value.is_dict()) {
         NOTREACHED();
         return false;
       }
-      if (!WriteNode(child_value, BookmarkNode::FOLDER))
+      if (!WriteNode(child_value.GetDict(), BookmarkNode::FOLDER)) {
         return false;
+      }
     }
     if (folder_type != BookmarkNode::OTHER_NODE &&
         folder_type != BookmarkNode::MOBILE) {
diff --git a/chrome/browser/browsing_data/counters/hosted_apps_counter_unittest.cc b/chrome/browser/browsing_data/counters/hosted_apps_counter_unittest.cc
index 4b3c5bb..b712814 100644
--- a/chrome/browser/browsing_data/counters/hosted_apps_counter_unittest.cc
+++ b/chrome/browser/browsing_data/counters/hosted_apps_counter_unittest.cc
@@ -23,6 +23,7 @@
 #include "extensions/common/extension_builder.h"
 #include "extensions/common/value_builder.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace {
 
@@ -42,7 +43,7 @@
   // Adding and removing apps and extensions. ----------------------------------
 
   std::string AddExtension() {
-    return AddItem(base::GenerateGUID(), /*app_manifest=*/nullptr);
+    return AddItem(base::GenerateGUID(), /*app_manifest=*/absl::nullopt);
   }
 
   std::string AddPackagedApp() {
@@ -70,15 +71,16 @@
   }
 
   std::string AddItem(const std::string& name,
-                      std::unique_ptr<base::Value> app_manifest) {
+                      absl::optional<base::Value::Dict> app_manifest) {
     DictionaryBuilder manifest_builder;
     manifest_builder
         .Set("manifest_version", 2)
         .Set("name", name)
         .Set("version", "1");
 
-    if (app_manifest)
-        manifest_builder.Set("app", std::move(app_manifest));
+    if (app_manifest) {
+      manifest_builder.Set("app", std::move(*app_manifest));
+    }
 
     scoped_refptr<const extensions::Extension> item =
         extensions::ExtensionBuilder()
diff --git a/chrome/browser/chromeos/extensions/login_screen/login/cleanup/extension_cleanup_handler_browsertest.cc b/chrome/browser/chromeos/extensions/login_screen/login/cleanup/extension_cleanup_handler_browsertest.cc
index 20a0260..98c561a 100644
--- a/chrome/browser/chromeos/extensions/login_screen/login/cleanup/extension_cleanup_handler_browsertest.cc
+++ b/chrome/browser/chromeos/extensions/login_screen/login/cleanup/extension_cleanup_handler_browsertest.cc
@@ -134,13 +134,12 @@
     extensions::ExtensionService* extension_service =
         extensions::ExtensionSystem::Get(GetActiveUserProfile())
             ->extension_service();
-    std::unique_ptr<base::DictionaryValue> manifest(
-        extensions::DictionaryBuilder()
-            .Set("name", "Foo")
-            .Set("description", "Bar")
-            .Set("manifest_version", 2)
-            .Set("version", "1.0")
-            .Build());
+    base::Value::Dict manifest(extensions::DictionaryBuilder()
+                                   .Set("name", "Foo")
+                                   .Set("description", "Bar")
+                                   .Set("manifest_version", 2)
+                                   .Set("version", "1.0")
+                                   .Build());
 
     auto observer = GetTestExtensionRegistryObserver(extension_id);
     scoped_refptr<const extensions::Extension> extension =
diff --git a/chrome/browser/content_creation/notes/internal/android/java/src/org/chromium/chrome/browser/content_creation/notes/NoteCreationCoordinatorImpl.java b/chrome/browser/content_creation/notes/internal/android/java/src/org/chromium/chrome/browser/content_creation/notes/NoteCreationCoordinatorImpl.java
index 8ecb3e9..c0fa8ef5 100644
--- a/chrome/browser/content_creation/notes/internal/android/java/src/org/chromium/chrome/browser/content_creation/notes/NoteCreationCoordinatorImpl.java
+++ b/chrome/browser/content_creation/notes/internal/android/java/src/org/chromium/chrome/browser/content_creation/notes/NoteCreationCoordinatorImpl.java
@@ -8,7 +8,6 @@
 import android.content.ComponentName;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
-import android.os.Build;
 import android.view.View;
 
 import androidx.fragment.app.FragmentActivity;
@@ -205,9 +204,7 @@
      * Retrieves the user's preferred locale from the app's configurations.
      */
     private Locale getPreferredLocale() {
-        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
-                ? mActivity.getResources().getConfiguration().getLocales().get(0)
-                : mActivity.getResources().getConfiguration().locale;
+        return mActivity.getResources().getConfiguration().getLocales().get(0);
     }
 
     private String addQuotes(String text) {
diff --git a/chrome/browser/dips/dips_utils.cc b/chrome/browser/dips/dips_utils.cc
index c543a99..a8ef2f9 100644
--- a/chrome/browser/dips/dips_utils.cc
+++ b/chrome/browser/dips/dips_utils.cc
@@ -27,14 +27,18 @@
 }
 
 bool TimestampRange::IsNullOrWithin(TimestampRange other) const {
-  if (!first.has_value()) {
-    return true;
+  if (first.has_value()) {
+    if (!other.first.has_value() || other.first.value() > first.value()) {
+      return false;
+    }
   }
-  if (!other.first.has_value()) {
-    return false;
+  if (last.has_value()) {
+    if (!other.last.has_value() || other.last.value() < last.value()) {
+      return false;
+    }
   }
-  return first.value() >= other.first.value() &&
-         last.value() <= other.last.value();
+
+  return true;
 }
 
 std::ostream& operator<<(std::ostream& os, absl::optional<base::Time> time) {
diff --git a/chrome/browser/dips/dips_utils_unittest.cc b/chrome/browser/dips/dips_utils_unittest.cc
index a09003bf..76b0aa0 100644
--- a/chrome/browser/dips/dips_utils_unittest.cc
+++ b/chrome/browser/dips/dips_utils_unittest.cc
@@ -100,6 +100,27 @@
   EXPECT_TRUE(range.IsNullOrWithin(range));
 }
 
+// This tests verifies that open-ended ranges work for this IsNullOrWithin.
+// TODO(kaklilu): remove this test when we update TimestampRange to not support
+// open-ended ranges.
+TEST(TimestampRangeTest, IsNullOrWithin_Regression_OpenEndedRanges) {
+  // Open-end range with lower bound.
+  TimestampRange inner = {base::Time::FromDoubleT(2), absl::nullopt};
+  TimestampRange outer = {base::Time::FromDoubleT(1), absl::nullopt};
+
+  EXPECT_TRUE(inner.IsNullOrWithin(outer));
+  // An open-ended range isn't within an empty range
+  EXPECT_FALSE(inner.IsNullOrWithin(TimestampRange()));
+
+  // Open-end range with upper bound.
+  outer = {absl::nullopt, base::Time::FromDoubleT(2)};
+  inner = {absl::nullopt, base::Time::FromDoubleT(1)};
+
+  EXPECT_TRUE(inner.IsNullOrWithin(outer));
+  // An open-ended range isn't within an empty range
+  EXPECT_FALSE(inner.IsNullOrWithin(TimestampRange()));
+}
+
 TEST(BucketizeBounceDelayTest, BucketizeBounceDelay) {
   // any TimeDelta in (-inf, 1s) should return 0
   EXPECT_EQ(0, BucketizeBounceDelay(base::Days(-1)));
diff --git a/chrome/browser/extensions/api/declarative_webrequest/webrequest_action_unittest.cc b/chrome/browser/extensions/api/declarative_webrequest/webrequest_action_unittest.cc
index 317c9fd..327e0e0 100644
--- a/chrome/browser/extensions/api/declarative_webrequest/webrequest_action_unittest.cc
+++ b/chrome/browser/extensions/api/declarative_webrequest/webrequest_action_unittest.cc
@@ -35,7 +35,6 @@
 namespace keys = extensions::declarative_webrequest_constants;
 
 using base::DictionaryValue;
-using base::ListValue;
 using extension_test_util::LoadManifestUnchecked;
 using helpers::EventResponseDeltas;
 using testing::HasSubstr;
@@ -172,9 +171,8 @@
 
   // Test wrong data type passed.
   error.clear();
-  base::ListValue empty_list;
-  result = WebRequestAction::Create(nullptr, nullptr, empty_list, &error,
-                                    &bad_message);
+  result = WebRequestAction::Create(
+      nullptr, nullptr, base::Value(base::Value::List()), &error, &bad_message);
   EXPECT_TRUE(bad_message);
   EXPECT_FALSE(result.get());
 
diff --git a/chrome/browser/extensions/api/developer_private/extension_info_generator_unittest.cc b/chrome/browser/extensions/api/developer_private/extension_info_generator_unittest.cc
index f6d16d13..23443da 100644
--- a/chrome/browser/extensions/api/developer_private/extension_info_generator_unittest.cc
+++ b/chrome/browser/extensions/api/developer_private/extension_info_generator_unittest.cc
@@ -889,7 +889,7 @@
 
   for (const auto& test_case : test_cases) {
     SCOPED_TRACE(test_case.name);
-    std::unique_ptr<base::Value> command_dict =
+    base::Value::Dict command_dict =
         DictionaryBuilder()
             .Set("suggested_key",
                  DictionaryBuilder().Set("default", "Ctrl+Shift+P").BuildDict())
diff --git a/chrome/browser/extensions/api/enterprise_networking_attributes/enterprise_networking_attributes_ash_apitest.cc b/chrome/browser/extensions/api/enterprise_networking_attributes/enterprise_networking_attributes_ash_apitest.cc
index 8012291..644b46f 100644
--- a/chrome/browser/extensions/api/enterprise_networking_attributes/enterprise_networking_attributes_ash_apitest.cc
+++ b/chrome/browser/extensions/api/enterprise_networking_attributes/enterprise_networking_attributes_ash_apitest.cc
@@ -111,12 +111,13 @@
     shill_ipconfig_client->AddIPConfig(kWifiIPConfigV6Path,
                                        ipconfig_v6_dictionary);
 
-    base::ListValue ip_configs;
+    base::Value::List ip_configs;
     ip_configs.Append(kWifiIPConfigV4Path);
     ip_configs.Append(kWifiIPConfigV6Path);
-    shill_device_client->SetDeviceProperty(
-        kWifiDevicePath, shill::kIPConfigsProperty, ip_configs,
-        /*notify_changed=*/false);
+    shill_device_client->SetDeviceProperty(kWifiDevicePath,
+                                           shill::kIPConfigsProperty,
+                                           base::Value(std::move(ip_configs)),
+                                           /*notify_changed=*/false);
 
     shill_service_client->AddService(kWifiServicePath, "wifi_guid",
                                      "wifi_network_name", shill::kTypeWifi,
diff --git a/chrome/browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_api_unittest.cc b/chrome/browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_api_unittest.cc
index fe1cf78..ea609ff 100644
--- a/chrome/browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_api_unittest.cc
+++ b/chrome/browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_api_unittest.cc
@@ -204,8 +204,9 @@
 };
 
 TEST_F(EPKChallengeMachineKeyTest, ExtensionNotAllowed) {
-  base::Value empty_allowlist(base::Value::Type::LIST);
-  prefs_->Set(prefs::kAttestationExtensionAllowlist, empty_allowlist);
+  base::Value::List empty_allowlist;
+  prefs_->SetList(prefs::kAttestationExtensionAllowlist,
+                  std::move(empty_allowlist));
 
   EXPECT_EQ(
       ash::attestation::TpmChallengeKeyResult::kExtensionNotAllowedErrorMsg,
@@ -215,9 +216,9 @@
 TEST_F(EPKChallengeMachineKeyTest, Success) {
   SetMockTpmChallenger();
 
-  base::Value allowlist(base::Value::Type::LIST);
-  allowlist.GetList().Append(extension_->id());
-  prefs_->Set(prefs::kAttestationExtensionAllowlist, allowlist);
+  base::Value::List allowlist;
+  allowlist.Append(extension_->id());
+  prefs_->SetList(prefs::kAttestationExtensionAllowlist, std::move(allowlist));
 
   base::Value value(
       RunFunctionAndReturnSingleResult(func_.get(), CreateArgs(), browser()));
@@ -230,9 +231,9 @@
 TEST_F(EPKChallengeMachineKeyTest, BadChallengeThenErrorMessageReturned) {
   SetMockTpmChallengerBadBase64Error();
 
-  base::Value allowlist(base::Value::Type::LIST);
-  allowlist.GetList().Append(extension_->id());
-  prefs_->Set(prefs::kAttestationExtensionAllowlist, allowlist);
+  base::Value::List allowlist;
+  allowlist.Append(extension_->id());
+  prefs_->SetList(prefs::kAttestationExtensionAllowlist, std::move(allowlist));
 
   base::Value value(
       RunFunctionAndReturnError(func_.get(), CreateArgs(), browser()));
@@ -245,9 +246,9 @@
 TEST_F(EPKChallengeMachineKeyTest, KeyNotRegisteredByDefault) {
   SetMockTpmChallenger();
 
-  base::Value allowlist(base::Value::Type::LIST);
-  allowlist.GetList().Append(extension_->id());
-  prefs_->Set(prefs::kAttestationExtensionAllowlist, allowlist);
+  base::Value::List allowlist;
+  allowlist.Append(extension_->id());
+  prefs_->SetList(prefs::kAttestationExtensionAllowlist, std::move(allowlist));
 
   EXPECT_CALL(*mock_tpm_challenge_key_, BuildResponse)
       .WillOnce(Invoke(FakeRunCheckNotRegister));
@@ -290,9 +291,9 @@
 TEST_F(EPKChallengeUserKeyTest, Success) {
   SetMockTpmChallenger();
 
-  base::Value allowlist(base::Value::Type::LIST);
-  allowlist.GetList().Append(extension_->id());
-  prefs_->Set(prefs::kAttestationExtensionAllowlist, allowlist);
+  base::Value::List allowlist;
+  allowlist.Append(extension_->id());
+  prefs_->SetList(prefs::kAttestationExtensionAllowlist, std::move(allowlist));
 
   base::Value value(
       RunFunctionAndReturnSingleResult(func_.get(), CreateArgs(), browser()));
@@ -305,9 +306,9 @@
 TEST_F(EPKChallengeUserKeyTest, BadChallengeThenErrorMessageReturned) {
   SetMockTpmChallengerBadBase64Error();
 
-  base::Value allowlist(base::Value::Type::LIST);
-  allowlist.GetList().Append(extension_->id());
-  prefs_->Set(prefs::kAttestationExtensionAllowlist, allowlist);
+  base::Value::List allowlist;
+  allowlist.Append(extension_->id());
+  prefs_->SetList(prefs::kAttestationExtensionAllowlist, std::move(allowlist));
 
   base::Value value(
       RunFunctionAndReturnError(func_.get(), CreateArgs(), browser()));
@@ -318,8 +319,9 @@
 }
 
 TEST_F(EPKChallengeUserKeyTest, ExtensionNotAllowedThenErrorMessageReturned) {
-  base::Value empty_allowlist(base::Value::Type::LIST);
-  prefs_->Set(prefs::kAttestationExtensionAllowlist, empty_allowlist);
+  base::Value::List empty_allowlist;
+  prefs_->SetList(prefs::kAttestationExtensionAllowlist,
+                  std::move(empty_allowlist));
 
   EXPECT_EQ(
       ash::attestation::TpmChallengeKeyResult::kExtensionNotAllowedErrorMsg,
@@ -341,9 +343,10 @@
   }
 
   void AllowlistExtension() {
-    base::Value allowlist(base::Value::Type::LIST);
+    base::Value::List allowlist;
     allowlist.Append(extension_->id());
-    prefs_->Set(prefs::kAttestationExtensionAllowlist, allowlist);
+    prefs_->SetList(prefs::kAttestationExtensionAllowlist,
+                    std::move(allowlist));
   }
 
   base::Value::List CreateArgs(
@@ -364,7 +367,7 @@
   }
 
   scoped_refptr<EnterprisePlatformKeysChallengeKeyFunction> func_;
-  base::ListValue args_;
+  base::Value::List args_;
 };
 
 // This test ensures challengeKey propagates algorithm, scope, and registerKey
@@ -418,8 +421,9 @@
 // This test ensures challengeKey cannot be called by extensions not on the
 // allow list.
 TEST_P(EPKChallengeKeyTest, ExtensionNotAllowed) {
-  base::ListValue empty_allowlist;
-  prefs_->Set(prefs::kAttestationExtensionAllowlist, empty_allowlist);
+  base::Value::List empty_allowlist;
+  prefs_->SetList(prefs::kAttestationExtensionAllowlist,
+                  std::move(empty_allowlist));
 
   auto scope = std::get<0>(GetParam());
   auto algorithm_opt = std::get<1>(GetParam());
diff --git a/chrome/browser/extensions/api/mdns/mdns_api_unittest.cc b/chrome/browser/extensions/api/mdns/mdns_api_unittest.cc
index 1642f9e..ce3a55b9 100644
--- a/chrome/browser/extensions/api/mdns/mdns_api_unittest.cc
+++ b/chrome/browser/extensions/api/mdns/mdns_api_unittest.cc
@@ -128,18 +128,14 @@
                 << e.event_args.size();
       return false;
     }
-    const base::ListValue* services = nullptr;
-    {
-      const base::Value& out = e.event_args[0];
-      services = static_cast<const base::ListValue*>(&out);
-    }
+    const base::Value::List* services = e.event_args[0].GetIfList();
     if (!services) {
-      *listener << "event's service list argument is not a ListValue";
+      *listener << "event's service list argument is not a Value::List";
       return false;
     }
-    *listener << "number of services is " << services->GetList().size();
+    *listener << "number of services is " << services->size();
     return static_cast<testing::Matcher<size_t>>(testing::Eq(expected_size_))
-        .MatchAndExplain(services->GetList().size(), listener);
+        .MatchAndExplain(services->size(), listener);
   }
 
   virtual void DescribeTo(::std::ostream* os) const {
diff --git a/chrome/browser/extensions/api/settings_private/settings_private_api.cc b/chrome/browser/extensions/api/settings_private/settings_private_api.cc
index 7d3aec4..3fa7a77 100644
--- a/chrome/browser/extensions/api/settings_private/settings_private_api.cc
+++ b/chrome/browser/extensions/api/settings_private/settings_private_api.cc
@@ -66,8 +66,7 @@
   SettingsPrivateDelegate* delegate =
       SettingsPrivateDelegateFactory::GetForBrowserContext(browser_context());
   DCHECK(delegate);
-  return RespondNow(
-      OneArgument(base::Value::FromUniquePtrValue(delegate->GetAllPrefs())));
+  return RespondNow(OneArgument(base::Value(delegate->GetAllPrefs())));
 }
 
 ////////////////////////////////////////////////////////////////////////////////
diff --git a/chrome/browser/extensions/api/settings_private/settings_private_delegate.cc b/chrome/browser/extensions/api/settings_private/settings_private_delegate.cc
index 68e5e77..24a4ffb 100644
--- a/chrome/browser/extensions/api/settings_private/settings_private_delegate.cc
+++ b/chrome/browser/extensions/api/settings_private/settings_private_delegate.cc
@@ -38,17 +38,17 @@
   return base::Value(pref->ToValue());
 }
 
-std::unique_ptr<base::Value> SettingsPrivateDelegate::GetAllPrefs() {
-  std::unique_ptr<base::ListValue> prefs(new base::ListValue());
+base::Value::List SettingsPrivateDelegate::GetAllPrefs() {
+  base::Value::List prefs;
 
   const TypedPrefMap& keys = prefs_util_->GetAllowlistedKeys();
   for (const auto& it : keys) {
     base::Value pref = GetPref(it.first);
     if (!pref.is_none())
-      prefs->Append(std::move(pref));
+      prefs.Append(std::move(pref));
   }
 
-  return std::move(prefs);
+  return prefs;
 }
 
 settings_private::SetPrefResult SettingsPrivateDelegate::SetPref(
diff --git a/chrome/browser/extensions/api/settings_private/settings_private_delegate.h b/chrome/browser/extensions/api/settings_private/settings_private_delegate.h
index 78fd94a..03b231e9 100644
--- a/chrome/browser/extensions/api/settings_private/settings_private_delegate.h
+++ b/chrome/browser/extensions/api/settings_private/settings_private_delegate.h
@@ -46,7 +46,7 @@
   base::Value GetPref(const std::string& name);
 
   // Gets the values of all allowlisted prefs.
-  virtual std::unique_ptr<base::Value> GetAllPrefs();
+  virtual base::Value::List GetAllPrefs();
 
   // Gets the value.
   virtual std::unique_ptr<base::Value> GetDefaultZoom();
diff --git a/chrome/browser/extensions/api/web_request/web_request_apitest.cc b/chrome/browser/extensions/api/web_request/web_request_apitest.cc
index 45009df..5b5bccb7 100644
--- a/chrome/browser/extensions/api/web_request/web_request_apitest.cc
+++ b/chrome/browser/extensions/api/web_request/web_request_apitest.cc
@@ -36,6 +36,7 @@
 #include "chrome/browser/extensions/active_tab_permission_granter.h"
 #include "chrome/browser/extensions/api/extension_action/test_extension_action_api_observer.h"
 #include "chrome/browser/extensions/error_console/error_console.h"
+#include "chrome/browser/extensions/error_console/error_console_test_observer.h"
 #include "chrome/browser/extensions/extension_action_runner.h"
 #include "chrome/browser/extensions/extension_apitest.h"
 #include "chrome/browser/extensions/extension_service.h"
@@ -63,7 +64,6 @@
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/common/chrome_features.h"
 #include "chrome/common/chrome_switches.h"
-#include "chrome/common/pref_names.h"
 #include "chrome/common/url_constants.h"
 #include "chrome/test/base/search_test_utils.h"
 #include "chrome/test/base/ui_test_utils.h"
@@ -6120,42 +6120,77 @@
   will_register_listener.Reply("unused");
 }
 
-namespace {
+// Tests behavior when a service worker is stopped while processing an event.
+IN_PROC_BROWSER_TEST_F(ManifestV3WebRequestApiTest,
+                       ServiceWorkerGoesAwayWhileHandlingRequest) {
+  ASSERT_TRUE(StartEmbeddedTestServer());
+  static constexpr char kManifest[] =
+      R"({
+           "name": "MV3 WebRequest",
+           "version": "0.1",
+           "manifest_version": 3,
+           "permissions": ["webRequest", "webRequestBlocking"],
+           "host_permissions": [
+             "http://example.com/*"
+           ],
+           "background": {"service_worker": "background.js"}
+         })";
+  // An extension with a listener that will spin forever on example.com
+  // requests.
+  static constexpr char kBackgroundJs[] =
+      R"(chrome.webRequest.onBeforeRequest.addListener(
+             (details) => {
+               if (details.url.includes('example.com')) {
+                 chrome.test.sendMessage('received');
+                 // Spin FOREVER.
+                 while (true) { }
+               }
+               return {};
+             },
+             {urls: ['<all_urls>'], types: ['main_frame']},
+             ['blocking']);
+         chrome.test.sendMessage('ready');)";
 
-// A helper to wait for an error to be added for an extension.
-// TODO(devlin): Pull this into a central test util file.
-class ErrorObserver : public ErrorConsole::Observer {
- public:
-  ErrorObserver(size_t errors_expected, ErrorConsole* error_console)
-      : errors_expected_(errors_expected), error_console_(error_console) {
-    observation_.Observe(error_console_.get());
-  }
+  TestExtensionDir test_dir;
+  test_dir.WriteManifest(kManifest);
+  test_dir.WriteFile(FILE_PATH_LITERAL("background.js"), kBackgroundJs);
+  const Extension* extension = LoadPolicyExtension(test_dir);
+  ASSERT_TRUE(extension);
 
-  // ErrorConsole::Observer implementation.
-  void OnErrorAdded(const ExtensionError* error) override {
-    ++errors_observed_;
-    if (errors_observed_ >= errors_expected_) {
-      run_loop_.Quit();
-    }
-  }
+  // A single webRequest listener should be registered.
+  EXPECT_EQ(1u, web_request_router()->GetListenerCountForTesting(
+                    profile(), "webRequest.onBeforeRequest"));
 
-  // Spin until the appropriate number of errors have been observed.
-  void WaitForErrors() {
-    if (errors_observed_ < errors_expected_) {
-      run_loop_.Run();
-    }
-  }
+  // Navigate to example.com; the extension will receive the event and spin
+  // indefinitely.
+  // We navigate in a new tab to have a better signal of "request started".
+  // We can't wait for the request to finish, since the extension's listener
+  // never returns, which blocks the request.
+  const GURL url =
+      embedded_test_server()->GetURL("example.com", "/simple.html");
+  content::TestNavigationObserver nav_observer(url);
+  nav_observer.StartWatchingNewWebContents();
+  ExtensionTestMessageListener test_listener("received");
+  ui_test_utils::NavigateToURLWithDisposition(
+      browser(), url, WindowOpenDisposition::NEW_FOREGROUND_TAB,
+      ui_test_utils::BROWSER_TEST_WAIT_FOR_TAB);
+  content::WebContents* web_contents =
+      browser()->tab_strip_model()->GetActiveWebContents();
+  EXPECT_TRUE(test_listener.WaitUntilSatisfied());
+  // The web contents should still be loading, and should have no last
+  // committed URL since the extension is blocking the request.
+  EXPECT_TRUE(web_contents->IsLoading());
+  EXPECT_EQ(GURL(), web_contents->GetLastCommittedURL());
 
- private:
-  size_t errors_expected_;
-  raw_ptr<ErrorConsole, DanglingUntriaged> error_console_;
-  size_t errors_observed_ = 0;
-  base::ScopedObservation<ErrorConsole, ErrorConsole::Observer> observation_{
-      this};
-  base::RunLoop run_loop_;
-};
+  // Stop the extension service worker.
+  browsertest_util::StopServiceWorkerForExtensionGlobalScope(profile(),
+                                                             extension->id());
 
-}  // namespace
+  // The request should be unblocked.
+  EXPECT_TRUE(content::WaitForLoadStop(web_contents));
+  EXPECT_TRUE(nav_observer.last_navigation_succeeded());
+  EXPECT_EQ(url, web_contents->GetLastCommittedURL());
+}
 
 // Tests that a MV3 extension that doesn't have the `webRequestAuthProvider`
 // permission cannot use blocking listeners for `onAuthRequired`.
@@ -6184,11 +6219,9 @@
              ['asyncBlocking']);)";
 
   // Since we can't catch the error in the extension's JS, we instead listen to
-  // the error come into the error console. This also requires setting the user
-  // in developer mode.
-  ErrorConsole* error_console = ErrorConsole::Get(profile());
-  profile()->GetPrefs()->SetBoolean(prefs::kExtensionsUIDeveloperMode, true);
-  ErrorObserver error_observer(1u, error_console);
+  // the error come into the error console.
+  ErrorConsoleTestObserver error_observer(1u, profile());
+  error_observer.EnableErrorCollection();
 
   // Load the extension and wait for the error to come.
   TestExtensionDir test_dir;
@@ -6200,7 +6233,7 @@
   error_observer.WaitForErrors();
 
   const ErrorList& errors =
-      error_console->GetErrorsForExtension(extension->id());
+      ErrorConsole::Get(profile())->GetErrorsForExtension(extension->id());
   ASSERT_EQ(1u, errors.size());
   EXPECT_TRUE(
       base::StartsWith(errors[0]->message(),
diff --git a/chrome/browser/extensions/api/webrtc_audio_private/webrtc_audio_private_browsertest.cc b/chrome/browser/extensions/api/webrtc_audio_private/webrtc_audio_private_browsertest.cc
index 0f7a550..bef6d768 100644
--- a/chrome/browser/extensions/api/webrtc_audio_private/webrtc_audio_private_browsertest.cc
+++ b/chrome/browser/extensions/api/webrtc_audio_private/webrtc_audio_private_browsertest.cc
@@ -116,7 +116,7 @@
   }
 
  protected:
-  void AppendTabIdToRequestInfo(base::ListValue* params, int tab_id) {
+  void AppendTabIdToRequestInfo(base::Value::List* params, int tab_id) {
     base::Value::Dict request_info;
     request_info.Set("tabId", tab_id);
     params->Append(base::Value(std::move(request_info)));
@@ -203,7 +203,7 @@
         profile()->GetMediaDeviceIDSalt(), url::Origin::Create(origin),
         raw_device_id);
 
-    base::ListValue parameters;
+    base::Value::List parameters;
     parameters.Append(origin.spec());
     parameters.Append(source_id_in_origin);
     std::string parameter_string;
diff --git a/chrome/browser/extensions/error_console/error_console_test_observer.cc b/chrome/browser/extensions/error_console/error_console_test_observer.cc
new file mode 100644
index 0000000..06b51a4e9
--- /dev/null
+++ b/chrome/browser/extensions/error_console/error_console_test_observer.cc
@@ -0,0 +1,40 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/extensions/error_console/error_console_test_observer.h"
+
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/common/pref_names.h"
+#include "components/prefs/pref_service.h"
+
+namespace extensions {
+
+ErrorConsoleTestObserver::ErrorConsoleTestObserver(size_t errors_expected,
+                                                   Profile* profile)
+    : errors_expected_(errors_expected), profile_(profile) {
+  observation_.Observe(ErrorConsole::Get(profile_.get()));
+}
+
+ErrorConsoleTestObserver::~ErrorConsoleTestObserver() = default;
+
+void ErrorConsoleTestObserver::EnableErrorCollection() {
+  // Errors are collected for extensions when the user preferences are set to
+  // enable developer mode.
+  profile_->GetPrefs()->SetBoolean(prefs::kExtensionsUIDeveloperMode, true);
+}
+
+void ErrorConsoleTestObserver::OnErrorAdded(const ExtensionError* error) {
+  ++errors_observed_;
+  if (errors_observed_ >= errors_expected_) {
+    run_loop_.Quit();
+  }
+}
+
+void ErrorConsoleTestObserver::WaitForErrors() {
+  if (errors_observed_ < errors_expected_) {
+    run_loop_.Run();
+  }
+}
+
+}  // namespace extensions
diff --git a/chrome/browser/extensions/error_console/error_console_test_observer.h b/chrome/browser/extensions/error_console/error_console_test_observer.h
new file mode 100644
index 0000000..3943976
--- /dev/null
+++ b/chrome/browser/extensions/error_console/error_console_test_observer.h
@@ -0,0 +1,43 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_EXTENSIONS_ERROR_CONSOLE_ERROR_CONSOLE_TEST_OBSERVER_H_
+#define CHROME_BROWSER_EXTENSIONS_ERROR_CONSOLE_ERROR_CONSOLE_TEST_OBSERVER_H_
+
+#include "base/memory/raw_ptr.h"
+#include "base/run_loop.h"
+#include "base/scoped_observation.h"
+#include "chrome/browser/extensions/error_console/error_console.h"
+
+class Profile;
+
+namespace extensions {
+
+// A helper class to listen for extension errors being emitted in tests.
+class ErrorConsoleTestObserver : public ErrorConsole::Observer {
+ public:
+  ErrorConsoleTestObserver(size_t errors_expected, Profile* profile);
+  ~ErrorConsoleTestObserver();
+
+  // Enables error collection for the associated profile.
+  void EnableErrorCollection();
+
+  // Waits until at least `errors_expected` errors have been seen.
+  void WaitForErrors();
+
+ private:
+  // ErrorConsole::Observer implementation.
+  void OnErrorAdded(const ExtensionError* error) override;
+
+  size_t errors_expected_;
+  raw_ptr<Profile, DanglingUntriaged> profile_;
+  size_t errors_observed_ = 0;
+  base::ScopedObservation<ErrorConsole, ErrorConsole::Observer> observation_{
+      this};
+  base::RunLoop run_loop_;
+};
+
+}  // namespace extensions
+
+#endif  // CHROME_BROWSER_EXTENSIONS_ERROR_CONSOLE_ERROR_CONSOLE_TEST_OBSERVER_H_
diff --git a/chrome/browser/extensions/permission_messages_unittest.cc b/chrome/browser/extensions/permission_messages_unittest.cc
index 293beb1..3131793 100644
--- a/chrome/browser/extensions/permission_messages_unittest.cc
+++ b/chrome/browser/extensions/permission_messages_unittest.cc
@@ -265,13 +265,14 @@
     const char16_t kMessage[] =
         u"Access any PVR Mass Storage from HUMAX Co., Ltd. via USB";
 
-    std::unique_ptr<base::ListValue> permission_list(new base::ListValue());
-    permission_list->Append(base::Value::FromUniquePtrValue(
+    base::Value::List permission_list;
+    permission_list.Append(base::Value::FromUniquePtrValue(
         UsbDevicePermissionData(0x02ad, 0x138c, -1, -1).ToValue()));
+    base::Value permission_value = base::Value(std::move(permission_list));
 
     UsbDevicePermission permission(
         PermissionsInfo::GetInstance()->GetByID(APIPermissionID::kUsbDevice));
-    ASSERT_TRUE(permission.FromValue(permission_list.get(), nullptr, nullptr));
+    ASSERT_TRUE(permission.FromValue(&permission_value, nullptr, nullptr));
 
     PermissionMessages messages = GetMessages(permission.GetPermissions());
     ASSERT_EQ(1U, messages.size());
@@ -280,13 +281,14 @@
   {
     const char16_t kMessage[] = u"Access USB devices from HUMAX Co., Ltd.";
 
-    std::unique_ptr<base::ListValue> permission_list(new base::ListValue());
-    permission_list->Append(base::Value::FromUniquePtrValue(
+    base::Value::List permission_list;
+    permission_list.Append(base::Value::FromUniquePtrValue(
         UsbDevicePermissionData(0x02ad, 0x138d, -1, -1).ToValue()));
+    base::Value permission_value = base::Value(std::move(permission_list));
 
     UsbDevicePermission permission(
         PermissionsInfo::GetInstance()->GetByID(APIPermissionID::kUsbDevice));
-    ASSERT_TRUE(permission.FromValue(permission_list.get(), nullptr, nullptr));
+    ASSERT_TRUE(permission.FromValue(&permission_value, nullptr, nullptr));
 
     PermissionMessages messages = GetMessages(permission.GetPermissions());
     ASSERT_EQ(1U, messages.size());
@@ -295,13 +297,14 @@
   {
     const char16_t kMessage[] = u"Access USB devices from an unknown vendor";
 
-    std::unique_ptr<base::ListValue> permission_list(new base::ListValue());
-    permission_list->Append(base::Value::FromUniquePtrValue(
+    base::Value::List permission_list;
+    permission_list.Append(base::Value::FromUniquePtrValue(
         UsbDevicePermissionData(0x02ae, 0x138d, -1, -1).ToValue()));
+    base::Value permission_value = base::Value(std::move(permission_list));
 
     UsbDevicePermission permission(
         PermissionsInfo::GetInstance()->GetByID(APIPermissionID::kUsbDevice));
-    ASSERT_TRUE(permission.FromValue(permission_list.get(), nullptr, nullptr));
+    ASSERT_TRUE(permission.FromValue(&permission_value, nullptr, nullptr));
 
     PermissionMessages messages = GetMessages(permission.GetPermissions());
     ASSERT_EQ(1U, messages.size());
@@ -318,25 +321,27 @@
   };
 
   // Prepare data set
-  std::unique_ptr<base::ListValue> permission_list(new base::ListValue());
-  permission_list->Append(base::Value::FromUniquePtrValue(
+  base::Value::List permission_list;
+  permission_list.Append(base::Value::FromUniquePtrValue(
       UsbDevicePermissionData(0x02ad, 0x138c, -1, -1).ToValue()));
   // This device's product ID is not in Chrome's database.
-  permission_list->Append(base::Value::FromUniquePtrValue(
+  permission_list.Append(base::Value::FromUniquePtrValue(
       UsbDevicePermissionData(0x02ad, 0x138d, -1, -1).ToValue()));
   // This additional unknown product will be collapsed into the entry above.
-  permission_list->Append(base::Value::FromUniquePtrValue(
+  permission_list.Append(base::Value::FromUniquePtrValue(
       UsbDevicePermissionData(0x02ad, 0x138e, -1, -1).ToValue()));
   // This device's vendor ID is not in Chrome's database.
-  permission_list->Append(base::Value::FromUniquePtrValue(
+  permission_list.Append(base::Value::FromUniquePtrValue(
       UsbDevicePermissionData(0x02ae, 0x138d, -1, -1).ToValue()));
   // This additional unknown vendor will be collapsed into the entry above.
-  permission_list->Append(base::Value::FromUniquePtrValue(
+  permission_list.Append(base::Value::FromUniquePtrValue(
       UsbDevicePermissionData(0x02af, 0x138d, -1, -1).ToValue()));
 
+  base::Value permission_value = base::Value(std::move(permission_list));
+
   UsbDevicePermission permission(
       PermissionsInfo::GetInstance()->GetByID(APIPermissionID::kUsbDevice));
-  ASSERT_TRUE(permission.FromValue(permission_list.get(), nullptr, nullptr));
+  ASSERT_TRUE(permission.FromValue(&permission_value, nullptr, nullptr));
 
   PermissionMessages messages = GetMessages(permission.GetPermissions());
   ASSERT_EQ(1U, messages.size());
diff --git a/chrome/browser/extensions/permissions_based_management_policy_provider_unittest.cc b/chrome/browser/extensions/permissions_based_management_policy_provider_unittest.cc
index d24778e..dd9098d3d 100644
--- a/chrome/browser/extensions/permissions_based_management_policy_provider_unittest.cc
+++ b/chrome/browser/extensions/permissions_based_management_policy_provider_unittest.cc
@@ -61,8 +61,8 @@
   // |optional_permissions|.
   scoped_refptr<const Extension> CreateExtensionWithPermission(
       mojom::ManifestLocation location,
-      const base::ListValue* required_permissions,
-      const base::ListValue* optional_permissions) {
+      const base::Value::List* required_permissions,
+      const base::Value::List* optional_permissions) {
     base::Value::Dict manifest_dict;
     manifest_dict.Set(manifest_keys::kName, "test");
     manifest_dict.Set(manifest_keys::kVersion, "0.1");
@@ -95,11 +95,11 @@
 // Verifies that extensions with conflicting permissions cannot be loaded.
 TEST_F(PermissionsBasedManagementPolicyProviderTest, APIPermissions) {
   // Prepares the extension manifest.
-  base::ListValue required_permissions;
+  base::Value::List required_permissions;
   required_permissions.Append(
       GetAPIPermissionName(APIPermissionID::kDownloads));
   required_permissions.Append(GetAPIPermissionName(APIPermissionID::kCookie));
-  base::ListValue optional_permissions;
+  base::Value::List optional_permissions;
   optional_permissions.Append(GetAPIPermissionName(APIPermissionID::kProxy));
 
   scoped_refptr<const Extension> extension = CreateExtensionWithPermission(
diff --git a/chrome/browser/extensions/policy_handlers_unittest.cc b/chrome/browser/extensions/policy_handlers_unittest.cc
index b1e1bbc8..2955b1b 100644
--- a/chrome/browser/extensions/policy_handlers_unittest.cc
+++ b/chrome/browser/extensions/policy_handlers_unittest.cc
@@ -105,7 +105,7 @@
     base::JSON_PARSE_CHROMIUM_EXTENSIONS | base::JSON_ALLOW_TRAILING_COMMAS;
 
 TEST(ExtensionListPolicyHandlerTest, CheckPolicySettings) {
-  base::ListValue list;
+  base::Value::List list;
   policy::PolicyMap policy_map;
   policy::PolicyErrorMap errors;
   ExtensionListPolicyHandler handler(policy::key::kExtensionInstallBlocklist,
@@ -113,7 +113,8 @@
 
   policy_map.Set(policy::key::kExtensionInstallBlocklist,
                  policy::POLICY_LEVEL_MANDATORY, policy::POLICY_SCOPE_USER,
-                 policy::POLICY_SOURCE_CLOUD, list.Clone(), nullptr);
+                 policy::POLICY_SOURCE_CLOUD, base::Value(list.Clone()),
+                 nullptr);
   errors.Clear();
   EXPECT_TRUE(handler.CheckPolicySettings(policy_map, &errors));
   EXPECT_TRUE(errors.empty());
@@ -121,7 +122,8 @@
   list.Append("abcdefghijklmnopabcdefghijklmnop");
   policy_map.Set(policy::key::kExtensionInstallBlocklist,
                  policy::POLICY_LEVEL_MANDATORY, policy::POLICY_SCOPE_USER,
-                 policy::POLICY_SOURCE_CLOUD, list.Clone(), nullptr);
+                 policy::POLICY_SOURCE_CLOUD, base::Value(list.Clone()),
+                 nullptr);
   errors.Clear();
   EXPECT_TRUE(handler.CheckPolicySettings(policy_map, &errors));
   EXPECT_TRUE(errors.empty());
@@ -129,7 +131,8 @@
   list.Append("*");
   policy_map.Set(policy::key::kExtensionInstallBlocklist,
                  policy::POLICY_LEVEL_MANDATORY, policy::POLICY_SCOPE_USER,
-                 policy::POLICY_SOURCE_CLOUD, list.Clone(), nullptr);
+                 policy::POLICY_SOURCE_CLOUD, base::Value(list.Clone()),
+                 nullptr);
   errors.Clear();
   EXPECT_TRUE(handler.CheckPolicySettings(policy_map, &errors));
   EXPECT_TRUE(errors.empty());
@@ -137,7 +140,8 @@
   list.Append("invalid");
   policy_map.Set(policy::key::kExtensionInstallBlocklist,
                  policy::POLICY_LEVEL_MANDATORY, policy::POLICY_SCOPE_USER,
-                 policy::POLICY_SOURCE_CLOUD, list.Clone(), nullptr);
+                 policy::POLICY_SOURCE_CLOUD, base::Value(list.Clone()),
+                 nullptr);
   errors.Clear();
   EXPECT_TRUE(handler.CheckPolicySettings(policy_map, &errors));
   EXPECT_FALSE(errors.empty());
@@ -190,8 +194,8 @@
 }
 
 TEST(ExtensionListPolicyHandlerTest, ApplyPolicySettings) {
-  base::ListValue policy;
-  base::ListValue expected;
+  base::Value::List policy;
+  base::Value::List expected;
   policy::PolicyMap policy_map;
   PrefValueMap prefs;
   base::Value* value = nullptr;
@@ -203,22 +207,24 @@
 
   policy_map.Set(policy::key::kExtensionInstallBlocklist,
                  policy::POLICY_LEVEL_MANDATORY, policy::POLICY_SCOPE_USER,
-                 policy::POLICY_SOURCE_CLOUD, policy.Clone(), nullptr);
+                 policy::POLICY_SOURCE_CLOUD, base::Value(policy.Clone()),
+                 nullptr);
   handler.ApplyPolicySettings(policy_map, &prefs);
   EXPECT_TRUE(prefs.GetValue(kTestPref, &value));
-  EXPECT_EQ(expected, *value);
+  EXPECT_EQ(expected, value->GetList());
 
   policy.Append("invalid");
   policy_map.Set(policy::key::kExtensionInstallBlocklist,
                  policy::POLICY_LEVEL_MANDATORY, policy::POLICY_SCOPE_USER,
-                 policy::POLICY_SOURCE_CLOUD, policy.Clone(), nullptr);
+                 policy::POLICY_SOURCE_CLOUD, base::Value(policy.Clone()),
+                 nullptr);
   handler.ApplyPolicySettings(policy_map, &prefs);
   EXPECT_TRUE(prefs.GetValue(kTestPref, &value));
-  EXPECT_EQ(expected, *value);
+  EXPECT_EQ(expected, value->GetList());
 }
 
 TEST(ExtensionInstallForceListPolicyHandlerTest, CheckPolicySettings) {
-  base::ListValue list;
+  base::Value::List list;
   policy::PolicyMap policy_map;
   policy::PolicyErrorMap errors;
   ExtensionInstallForceListPolicyHandler handler;
@@ -226,7 +232,8 @@
   // Start with an empty policy.
   policy_map.Set(policy::key::kExtensionInstallForcelist,
                  policy::POLICY_LEVEL_MANDATORY, policy::POLICY_SCOPE_USER,
-                 policy::POLICY_SOURCE_CLOUD, list.Clone(), nullptr);
+                 policy::POLICY_SOURCE_CLOUD, base::Value(list.Clone()),
+                 nullptr);
   errors.Clear();
   EXPECT_TRUE(handler.CheckPolicySettings(policy_map, &errors));
   EXPECT_TRUE(errors.empty());
@@ -235,7 +242,8 @@
   list.Append("abcdefghijklmnopabcdefghijklmnop;http://example.com");
   policy_map.Set(policy::key::kExtensionInstallForcelist,
                  policy::POLICY_LEVEL_MANDATORY, policy::POLICY_SCOPE_USER,
-                 policy::POLICY_SOURCE_CLOUD, list.Clone(), nullptr);
+                 policy::POLICY_SOURCE_CLOUD, base::Value(list.Clone()),
+                 nullptr);
   errors.Clear();
   EXPECT_TRUE(handler.CheckPolicySettings(policy_map, &errors));
   EXPECT_TRUE(errors.empty());
@@ -245,7 +253,8 @@
   list.Append("adfasdf;http://example.com");
   policy_map.Set(policy::key::kExtensionInstallForcelist,
                  policy::POLICY_LEVEL_MANDATORY, policy::POLICY_SCOPE_USER,
-                 policy::POLICY_SOURCE_CLOUD, list.Clone(), nullptr);
+                 policy::POLICY_SOURCE_CLOUD, base::Value(list.Clone()),
+                 nullptr);
   errors.Clear();
   EXPECT_TRUE(handler.CheckPolicySettings(policy_map, &errors));
   EXPECT_EQ(1U, errors.size());
@@ -254,7 +263,8 @@
   list.Append("abcdefghijklmnopabcdefghijklmnop;nourl");
   policy_map.Set(policy::key::kExtensionInstallForcelist,
                  policy::POLICY_LEVEL_MANDATORY, policy::POLICY_SCOPE_USER,
-                 policy::POLICY_SOURCE_CLOUD, list.Clone(), nullptr);
+                 policy::POLICY_SOURCE_CLOUD, base::Value(list.Clone()),
+                 nullptr);
   errors.Clear();
   EXPECT_TRUE(handler.CheckPolicySettings(policy_map, &errors));
   EXPECT_EQ(2U, errors.size());
@@ -263,14 +273,15 @@
   list.Append("abcdefghijklmnopabcdefghijklmnop");
   policy_map.Set(policy::key::kExtensionInstallForcelist,
                  policy::POLICY_LEVEL_MANDATORY, policy::POLICY_SCOPE_USER,
-                 policy::POLICY_SOURCE_CLOUD, list.Clone(), nullptr);
+                 policy::POLICY_SOURCE_CLOUD, base::Value(list.Clone()),
+                 nullptr);
   errors.Clear();
   EXPECT_TRUE(handler.CheckPolicySettings(policy_map, &errors));
   EXPECT_EQ(2U, errors.size());
 }
 
 TEST(ExtensionInstallForceListPolicyHandlerTest, ApplyPolicySettings) {
-  base::ListValue policy;
+  base::Value::List policy;
   base::Value::Dict expected;
   policy::PolicyMap policy_map;
   PrefValueMap prefs;
@@ -286,7 +297,8 @@
   // Set the policy to an empty value. This shouldn't affect the pref.
   policy_map.Set(policy::key::kExtensionInstallForcelist,
                  policy::POLICY_LEVEL_MANDATORY, policy::POLICY_SCOPE_USER,
-                 policy::POLICY_SOURCE_CLOUD, policy.Clone(), nullptr);
+                 policy::POLICY_SOURCE_CLOUD, base::Value(policy.Clone()),
+                 nullptr);
   handler.ApplyPolicySettings(policy_map, &prefs);
   EXPECT_TRUE(prefs.GetValue(pref_names::kInstallForceList, &value));
   EXPECT_EQ(expected, *value);
@@ -299,7 +311,8 @@
       expected, "abcdefghijklmnopabcdefghijklmnop", "http://example.com");
   policy_map.Set(policy::key::kExtensionInstallForcelist,
                  policy::POLICY_LEVEL_MANDATORY, policy::POLICY_SCOPE_USER,
-                 policy::POLICY_SOURCE_CLOUD, policy.Clone(), nullptr);
+                 policy::POLICY_SOURCE_CLOUD, base::Value(policy.Clone()),
+                 nullptr);
   handler.ApplyPolicySettings(policy_map, &prefs);
   EXPECT_TRUE(prefs.GetValue(pref_names::kInstallForceList, &value));
   EXPECT_EQ(expected, *value);
@@ -316,7 +329,8 @@
       "https://clients2.google.com/service/update2/crx");
   policy_map.Set(policy::key::kExtensionInstallForcelist,
                  policy::POLICY_LEVEL_MANDATORY, policy::POLICY_SCOPE_USER,
-                 policy::POLICY_SOURCE_CLOUD, policy.Clone(), nullptr);
+                 policy::POLICY_SOURCE_CLOUD, base::Value(policy.Clone()),
+                 nullptr);
   handler.ApplyPolicySettings(policy_map, &prefs);
   EXPECT_TRUE(prefs.GetValue(pref_names::kInstallForceList, &value));
   EXPECT_EQ(expected, *value);
@@ -326,7 +340,8 @@
   policy.Append("invalid");
   policy_map.Set(policy::key::kExtensionInstallForcelist,
                  policy::POLICY_LEVEL_MANDATORY, policy::POLICY_SCOPE_USER,
-                 policy::POLICY_SOURCE_CLOUD, policy.Clone(), nullptr);
+                 policy::POLICY_SOURCE_CLOUD, base::Value(policy.Clone()),
+                 nullptr);
   handler.ApplyPolicySettings(policy_map, &prefs);
   EXPECT_TRUE(prefs.GetValue(pref_names::kInstallForceList, &value));
   EXPECT_EQ(expected, *value);
@@ -334,7 +349,7 @@
 }
 
 TEST(ExtensionURLPatternListPolicyHandlerTest, CheckPolicySettings) {
-  base::ListValue list;
+  base::Value::List list;
   policy::PolicyMap policy_map;
   policy::PolicyErrorMap errors;
   ExtensionURLPatternListPolicyHandler handler(
@@ -342,7 +357,8 @@
 
   policy_map.Set(policy::key::kExtensionInstallSources,
                  policy::POLICY_LEVEL_MANDATORY, policy::POLICY_SCOPE_USER,
-                 policy::POLICY_SOURCE_CLOUD, list.Clone(), nullptr);
+                 policy::POLICY_SOURCE_CLOUD, base::Value(list.Clone()),
+                 nullptr);
   errors.Clear();
   EXPECT_TRUE(handler.CheckPolicySettings(policy_map, &errors));
   EXPECT_TRUE(errors.empty());
@@ -350,7 +366,8 @@
   list.Append("http://*.google.com/*");
   policy_map.Set(policy::key::kExtensionInstallSources,
                  policy::POLICY_LEVEL_MANDATORY, policy::POLICY_SCOPE_USER,
-                 policy::POLICY_SOURCE_CLOUD, list.Clone(), nullptr);
+                 policy::POLICY_SOURCE_CLOUD, base::Value(list.Clone()),
+                 nullptr);
   errors.Clear();
   EXPECT_TRUE(handler.CheckPolicySettings(policy_map, &errors));
   EXPECT_TRUE(errors.empty());
@@ -358,7 +375,8 @@
   list.Append("<all_urls>");
   policy_map.Set(policy::key::kExtensionInstallSources,
                  policy::POLICY_LEVEL_MANDATORY, policy::POLICY_SCOPE_USER,
-                 policy::POLICY_SOURCE_CLOUD, list.Clone(), nullptr);
+                 policy::POLICY_SOURCE_CLOUD, base::Value(list.Clone()),
+                 nullptr);
   errors.Clear();
   EXPECT_TRUE(handler.CheckPolicySettings(policy_map, &errors));
   EXPECT_TRUE(errors.empty());
@@ -366,7 +384,8 @@
   list.Append("invalid");
   policy_map.Set(policy::key::kExtensionInstallSources,
                  policy::POLICY_LEVEL_MANDATORY, policy::POLICY_SCOPE_USER,
-                 policy::POLICY_SOURCE_CLOUD, list.Clone(), nullptr);
+                 policy::POLICY_SOURCE_CLOUD, base::Value(list.Clone()),
+                 nullptr);
   errors.Clear();
   EXPECT_FALSE(handler.CheckPolicySettings(policy_map, &errors));
   EXPECT_FALSE(errors.empty());
@@ -378,7 +397,8 @@
   list.Append("*");
   policy_map.Set(policy::key::kExtensionInstallSources,
                  policy::POLICY_LEVEL_MANDATORY, policy::POLICY_SCOPE_USER,
-                 policy::POLICY_SOURCE_CLOUD, list.Clone(), nullptr);
+                 policy::POLICY_SOURCE_CLOUD, base::Value(list.Clone()),
+                 nullptr);
   errors.Clear();
   EXPECT_FALSE(handler.CheckPolicySettings(policy_map, &errors));
   EXPECT_FALSE(errors.empty());
@@ -387,7 +407,7 @@
 }
 
 TEST(ExtensionURLPatternListPolicyHandlerTest, ApplyPolicySettings) {
-  base::ListValue list;
+  base::Value::List list;
   policy::PolicyMap policy_map;
   PrefValueMap prefs;
   base::Value* value = nullptr;
@@ -397,7 +417,8 @@
   list.Append("https://corp.monkey.net/*");
   policy_map.Set(policy::key::kExtensionInstallSources,
                  policy::POLICY_LEVEL_MANDATORY, policy::POLICY_SCOPE_USER,
-                 policy::POLICY_SOURCE_CLOUD, list.Clone(), nullptr);
+                 policy::POLICY_SOURCE_CLOUD, base::Value(list.Clone()),
+                 nullptr);
   handler.ApplyPolicySettings(policy_map, &prefs);
   ASSERT_TRUE(prefs.GetValue(kTestPref, &value));
   EXPECT_EQ(list, *value);
diff --git a/chrome/browser/extensions/service_worker_apitest.cc b/chrome/browser/extensions/service_worker_apitest.cc
index a505835..9e326f8 100644
--- a/chrome/browser/extensions/service_worker_apitest.cc
+++ b/chrome/browser/extensions/service_worker_apitest.cc
@@ -24,6 +24,7 @@
 #include "chrome/browser/extensions/chrome_test_extension_loader.h"
 #include "chrome/browser/extensions/crx_installer.h"
 #include "chrome/browser/extensions/error_console/error_console.h"
+#include "chrome/browser/extensions/error_console/error_console_test_observer.h"
 #include "chrome/browser/extensions/extension_action_runner.h"
 #include "chrome/browser/extensions/extension_apitest.h"
 #include "chrome/browser/extensions/extension_service.h"
@@ -40,7 +41,6 @@
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/common/chrome_switches.h"
 #include "chrome/common/extensions/api/web_navigation.h"
-#include "chrome/common/pref_names.h"
 #include "chrome/test/base/ui_test_utils.h"
 #include "components/content_settings/core/common/content_settings_types.h"
 #include "components/gcm_driver/fake_gcm_profile_service.h"
@@ -138,39 +138,6 @@
 
 }  // namespace
 
-class ErrorObserver : public ErrorConsole::Observer {
- public:
-  ErrorObserver(size_t errors_expected, ErrorConsole* error_console)
-      : errors_expected_(errors_expected),
-        error_console_(error_console),
-        errors_observed_(0) {
-    observation_.Observe(error_console_.get());
-  }
-
-  // ErrorConsole::Observer implementation.
-  void OnErrorAdded(const ExtensionError* error) override {
-    ++errors_observed_;
-    if (errors_observed_ >= errors_expected_) {
-      run_loop_.Quit();
-    }
-  }
-
-  // Spin until the appropriate number of errors have been observed.
-  void WaitForErrors() {
-    if (errors_observed_ < errors_expected_) {
-      run_loop_.Run();
-    }
-  }
-
- private:
-  size_t errors_expected_;
-  raw_ptr<ErrorConsole> error_console_;
-  size_t errors_observed_;
-  base::ScopedObservation<ErrorConsole, ErrorConsole::Observer> observation_{
-      this};
-  base::RunLoop run_loop_;
-};
-
 class ServiceWorkerTest : public ExtensionApiTest {
  public:
   ServiceWorkerTest(const ServiceWorkerTest&) = delete;
@@ -422,10 +389,9 @@
 // Tests that registering a module service worker with dynamic import fails.
 IN_PROC_BROWSER_TEST_F(ServiceWorkerBasedBackgroundTest,
                        ModuleServiceWorkerWithDynamicImport) {
-  ErrorConsole* error_console = ErrorConsole::Get(profile());
-  profile()->GetPrefs()->SetBoolean(prefs::kExtensionsUIDeveloperMode, true);
   constexpr size_t kErrorsExpected = 1u;
-  ErrorObserver observer(kErrorsExpected, error_console);
+  ErrorConsoleTestObserver observer(kErrorsExpected, profile());
+  observer.EnableErrorCollection();
 
   const Extension* extension = LoadExtension(
       test_data_dir_.AppendASCII("service_worker/worker_based_background/"
@@ -433,7 +399,7 @@
 
   observer.WaitForErrors();
   const ErrorList& error_list =
-      error_console->GetErrorsForExtension(extension->id());
+      ErrorConsole::Get(profile())->GetErrorsForExtension(extension->id());
   ASSERT_EQ(kErrorsExpected, error_list.size());
   ASSERT_EQ(
       error_list[0]->message(),
@@ -446,12 +412,9 @@
 // synchronously throwing a runtime error.
 IN_PROC_BROWSER_TEST_F(ServiceWorkerBasedBackgroundTest,
                        ServiceWorkerWithRegistrationFailure) {
-  // The error console only captures errors if the user is in dev mode.
-  profile()->GetPrefs()->SetBoolean(prefs::kExtensionsUIDeveloperMode, true);
-
   constexpr size_t kErrorsExpected = 2u;
-  ErrorConsole* error_console = ErrorConsole::Get(profile());
-  ErrorObserver observer(kErrorsExpected, error_console);
+  ErrorConsoleTestObserver observer(kErrorsExpected, profile());
+  observer.EnableErrorCollection();
 
   const Extension* extension = LoadExtension(
       test_data_dir_.AppendASCII("service_worker/worker_based_background/"
@@ -461,7 +424,7 @@
   ASSERT_TRUE(extension);
   observer.WaitForErrors();
   const ErrorList& error_list =
-      error_console->GetErrorsForExtension(extension->id());
+      ErrorConsole::Get(profile())->GetErrorsForExtension(extension->id());
   ASSERT_EQ(kErrorsExpected, error_list.size());
 
   std::vector<std::u16string> error_message_list;
@@ -478,11 +441,9 @@
 // Tests that an error is generated if there is a syntax error in the service
 // worker script.
 IN_PROC_BROWSER_TEST_F(ServiceWorkerBasedBackgroundTest, SyntaxError) {
-  ErrorConsole* error_console = ErrorConsole::Get(profile());
-  // Error is observed on extension UI for developer mode only.
-  profile()->GetPrefs()->SetBoolean(prefs::kExtensionsUIDeveloperMode, true);
   const size_t kErrorsExpected = 1u;
-  ErrorObserver observer(kErrorsExpected, error_console);
+  ErrorConsoleTestObserver observer(kErrorsExpected, profile());
+  observer.EnableErrorCollection();
 
   ExtensionTestMessageListener test_listener("ready",
                                              ReplyBehavior::kWillReply);
@@ -495,7 +456,7 @@
   observer.WaitForErrors();
 
   const ErrorList& error_list =
-      error_console->GetErrorsForExtension(extension->id());
+      ErrorConsole::Get(profile())->GetErrorsForExtension(extension->id());
   ASSERT_EQ(kErrorsExpected, error_list.size());
   EXPECT_EQ(ExtensionError::RUNTIME_ERROR, error_list[0]->type());
   EXPECT_THAT(base::UTF16ToUTF8(error_list[0]->message()),
@@ -506,11 +467,9 @@
 // Tests that an error is generated if there is an undefined variable in the
 // service worker script.
 IN_PROC_BROWSER_TEST_F(ServiceWorkerBasedBackgroundTest, UndefinedVariable) {
-  ErrorConsole* error_console = ErrorConsole::Get(profile());
-  // Error is observed on extension UI for developer mode only.
-  profile()->GetPrefs()->SetBoolean(prefs::kExtensionsUIDeveloperMode, true);
   const size_t kErrorsExpected = 1u;
-  ErrorObserver observer(kErrorsExpected, error_console);
+  ErrorConsoleTestObserver observer(kErrorsExpected, profile());
+  observer.EnableErrorCollection();
 
   ExtensionTestMessageListener test_listener("ready",
                                              ReplyBehavior::kWillReply);
@@ -523,7 +482,7 @@
   observer.WaitForErrors();
 
   const ErrorList& error_list =
-      error_console->GetErrorsForExtension(extension->id());
+      ErrorConsole::Get(profile())->GetErrorsForExtension(extension->id());
   ASSERT_EQ(kErrorsExpected, error_list.size());
   EXPECT_EQ(ExtensionError::RUNTIME_ERROR, error_list[0]->type());
   EXPECT_THAT(base::UTF16ToUTF8(error_list[0]->message()),
@@ -534,19 +493,18 @@
 // Tests that an error is generated if console.error() is called from an
 // extension's service worker.
 IN_PROC_BROWSER_TEST_F(ServiceWorkerBasedBackgroundTest, ConsoleError) {
-  ErrorConsole* error_console = ErrorConsole::Get(profile());
-  // Error is observed on extension UI for developer mode only.
-  profile()->GetPrefs()->SetBoolean(prefs::kExtensionsUIDeveloperMode, true);
   const size_t kErrorsExpected = 1u;
-  ErrorObserver observer(kErrorsExpected, error_console);
+  ErrorConsoleTestObserver observer(kErrorsExpected, profile());
+  observer.EnableErrorCollection();
 
   ASSERT_TRUE(
       RunExtensionTest("service_worker/worker_based_background/console_error"))
       << message_;
 
   observer.WaitForErrors();
-  const ErrorList& error_list = error_console->GetErrorsForExtension(
-      ExtensionBrowserTest::last_loaded_extension_id());
+  const ErrorList& error_list =
+      ErrorConsole::Get(profile())->GetErrorsForExtension(
+          ExtensionBrowserTest::last_loaded_extension_id());
   ASSERT_EQ(kErrorsExpected, error_list.size());
   EXPECT_EQ(ExtensionError::RUNTIME_ERROR, error_list[0]->type());
   EXPECT_THAT(base::UTF16ToUTF8(error_list[0]->message()),
diff --git a/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/FeedServiceBridge.java b/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/FeedServiceBridge.java
index c511455..fc3dcad 100644
--- a/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/FeedServiceBridge.java
+++ b/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/FeedServiceBridge.java
@@ -5,11 +5,8 @@
 package org.chromium.chrome.browser.feed;
 
 import android.content.Context;
-import android.os.Build;
 import android.util.DisplayMetrics;
 
-import androidx.annotation.RequiresApi;
-
 import org.chromium.base.ContextUtils;
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.base.annotations.JNINamespace;
@@ -89,11 +86,8 @@
     }
 
     /** Returns the top user specified locale. */
-    @RequiresApi(Build.VERSION_CODES.N)
     private static Locale getLocale(Context context) {
-        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
-                ? context.getResources().getConfiguration().getLocales().get(0)
-                : context.getResources().getConfiguration().locale;
+        return context.getResources().getConfiguration().getLocales().get(0);
     }
 
     // Java functionality needed for the native FeedService.
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 937095c..c741605c 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -5007,6 +5007,11 @@
     "expiry_milestone": 115
   },
   {
+    "name": "omnibox-grouping-framework",
+    "owners": ["manukh", "mahmadi", "ender", "chrome-desktop-search@google.com" ],
+    "expiry_milestone": 120
+  },
+  {
     "name": "omnibox-history-quick-provider-specificity-score-count-unique-hosts",
     "owners": [ "manukh", "chrome-omnibox-team@google.com" ],
     "expiry_milestone": 120
@@ -5801,11 +5806,6 @@
     "expiry_milestone": 114
   },
   {
-    "name": "reduce-display-notifications",
-    "owners": [ "baileyberro", "zentaro" ],
-    "expiry_milestone": 112
-  },
-  {
     "name": "reduce-horizontal-fling-velocity",
     "owners": [ "flackr", "input-dev" ],
     "expiry_milestone": 95
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 972f87fb..e522d9a 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -1998,6 +1998,11 @@
 const char kOmniboxFuzzyUrlSuggestionsDescription[] =
     "Enables URL suggestions for inputs that may contain typos.";
 
+const char kOmniboxGroupingFrameworkName[] = "Omnibox Grouping Framework";
+const char kOmniboxGroupingFrameworkDescription[] =
+    "Enables an alternative grouping implementation for omnibox "
+    "autocompletion.";
+
 const char kOmniboxHistoryQuickProviderSpecificityScoreCountUniqueHostsName[] =
     "Omnibox HQP Specificity";
 const char
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index b5481b28..69151e6 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -1127,6 +1127,9 @@
 extern const char kOmniboxFuzzyUrlSuggestionsName[];
 extern const char kOmniboxFuzzyUrlSuggestionsDescription[];
 
+extern const char kOmniboxGroupingFrameworkName[];
+extern const char kOmniboxGroupingFrameworkDescription[];
+
 extern const char
     kOmniboxHistoryQuickProviderSpecificityScoreCountUniqueHostsName[];
 extern const char
diff --git a/chrome/browser/notifications/android/java/src/org/chromium/chrome/browser/notifications/NotificationIntentInterceptor.java b/chrome/browser/notifications/android/java/src/org/chromium/chrome/browser/notifications/NotificationIntentInterceptor.java
index 6e393ce..ca2de33e 100644
--- a/chrome/browser/notifications/android/java/src/org/chromium/chrome/browser/notifications/NotificationIntentInterceptor.java
+++ b/chrome/browser/notifications/android/java/src/org/chromium/chrome/browser/notifications/NotificationIntentInterceptor.java
@@ -163,9 +163,8 @@
 
         // This flag ensures the broadcast is delivered with foreground priority to speed up the
         // broadcast delivery.
-        if (shouldUseBroadcast && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
-            intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
-        }
+        if (shouldUseBroadcast) intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+
         // Use request code to distinguish different PendingIntents on Android.
         int originalRequestCode =
                 pendingIntentProvider != null ? pendingIntentProvider.getRequestCode() : 0;
diff --git a/chrome/browser/page_load_metrics/observers/ad_metrics/ads_page_load_metrics_observer_browsertest.cc b/chrome/browser/page_load_metrics/observers/ad_metrics/ads_page_load_metrics_observer_browsertest.cc
index 15805f8c..a98d28d 100644
--- a/chrome/browser/page_load_metrics/observers/ad_metrics/ads_page_load_metrics_observer_browsertest.cc
+++ b/chrome/browser/page_load_metrics/observers/ad_metrics/ads_page_load_metrics_observer_browsertest.cc
@@ -306,12 +306,12 @@
       ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL)));
 
   auto entries = ukm_recorder.GetEntriesByName(
-      ukm::builders::AdPageLoadCustomSampling2::kEntryName);
+      ukm::builders::AdPageLoadCustomSampling3::kEntryName);
   EXPECT_EQ(1u, entries.size());
 
   const int64_t* reported_average_viewport_density =
       ukm_recorder.GetEntryMetric(entries.front(),
-                                  ukm::builders::AdPageLoadCustomSampling2::
+                                  ukm::builders::AdPageLoadCustomSampling3::
                                       kAverageViewportAdDensityName);
 
   EXPECT_TRUE(reported_average_viewport_density);
@@ -323,6 +323,59 @@
             expected_final_viewport_density);
 }
 
+IN_PROC_BROWSER_TEST_F(AdsPageLoadMetricsObserverBrowserTest,
+                       AverageViewportAdDensity_ImageAd) {
+  SetRulesetWithRules(
+      {subresource_filter::testing::CreateSuffixRule("pixel.png")});
+
+  ukm::TestAutoSetUkmRecorder ukm_recorder;
+
+  auto waiter = CreatePageLoadMetricsTestWaiter();
+
+  GURL url = embedded_test_server()->GetURL(
+      "a.com", "/ads_observer/blank_with_adiframe_writer.html");
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
+
+  content::WebContents* web_contents =
+      browser()->tab_strip_model()->GetActiveWebContents();
+
+  waiter->SetMainFrameImageAdRectsExpectation();
+
+  GURL image_url =
+      embedded_test_server()->GetURL("b.com", "/ads_observer/pixel.png");
+
+  std::string create_image_script = content::JsReplace(R"(
+          const img = document.createElement('img');
+          img.style.position = 'fixed';
+          img.style.left = 0;
+          img.style.top = 0;
+          img.width = 5;
+          img.height = 5;
+          img.src = $1;
+          document.body.appendChild(img);)",
+                                                       image_url.spec());
+
+  EXPECT_TRUE(ExecJs(web_contents, create_image_script));
+
+  waiter->Wait();
+
+  EXPECT_TRUE(waiter->DidObserveMainFrameImageAdRect(gfx::Rect(0, 0, 5, 5)));
+
+  ASSERT_TRUE(
+      ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL)));
+
+  auto entries = ukm_recorder.GetEntriesByName(
+      ukm::builders::AdPageLoadCustomSampling3::kEntryName);
+  EXPECT_EQ(1u, entries.size());
+
+  const int64_t* reported_average_viewport_density =
+      ukm_recorder.GetEntryMetric(entries.front(),
+                                  ukm::builders::AdPageLoadCustomSampling3::
+                                      kAverageViewportAdDensityName);
+
+  EXPECT_TRUE(reported_average_viewport_density);
+}
+
 // Verifies that the page ad density records the maximum value during
 // a page's lifecycling by creating a large ad frame, destroying it, and
 // creating a smaller iframe. The ad density recorded is the density with
diff --git a/chrome/browser/password_entry_edit/android/internal/java/src/org/chromium/chrome/browser/password_entry_edit/CredentialEditControllerTest.java b/chrome/browser/password_entry_edit/android/internal/java/src/org/chromium/chrome/browser/password_entry_edit/CredentialEditControllerTest.java
index 8305c70b..99d94abed 100644
--- a/chrome/browser/password_entry_edit/android/internal/java/src/org/chromium/chrome/browser/password_entry_edit/CredentialEditControllerTest.java
+++ b/chrome/browser/password_entry_edit/android/internal/java/src/org/chromium/chrome/browser/password_entry_edit/CredentialEditControllerTest.java
@@ -40,7 +40,6 @@
 import android.content.ClipboardManager;
 import android.content.Context;
 import android.content.res.Resources;
-import android.os.Build;
 import android.os.PersistableBundle;
 
 import androidx.test.core.app.ApplicationProvider;
@@ -92,10 +91,8 @@
     PropertyModel mModel;
 
     private void verifyTheClipdataContainSensitiveExtra(ClipData clipData) {
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
-            PersistableBundle extras = clipData.getDescription().getExtras();
-            assertTrue(extras.getBoolean("android.content.extra.IS_SENSITIVE"));
-        }
+        PersistableBundle extras = clipData.getDescription().getExtras();
+        assertTrue(extras.getBoolean("android.content.extra.IS_SENSITIVE"));
     }
 
     @Before
diff --git a/chrome/browser/printing/print_backend_service_manager.cc b/chrome/browser/printing/print_backend_service_manager.cc
index 2fa2bf9..761aad3 100644
--- a/chrome/browser/printing/print_backend_service_manager.cc
+++ b/chrome/browser/printing/print_backend_service_manager.cc
@@ -56,7 +56,7 @@
 
 size_t GetClientsCountForRemoteId(
     const PrintBackendServiceManager::PrintClientsMap& clients,
-    const std::string& remote_id) {
+    const PrintBackendServiceManager::RemoteId& remote_id) {
   auto iter = clients.find(remote_id);
   if (iter != clients.end()) {
     DCHECK(!iter->second.empty());
@@ -75,6 +75,13 @@
 
 }  // namespace
 
+PrintBackendServiceManager::CallbackContext::CallbackContext() = default;
+
+PrintBackendServiceManager::CallbackContext::CallbackContext(
+    PrintBackendServiceManager::CallbackContext&& other) noexcept = default;
+
+PrintBackendServiceManager::CallbackContext::~CallbackContext() = default;
+
 PrintBackendServiceManager::PrintBackendServiceManager() = default;
 
 PrintBackendServiceManager::~PrintBackendServiceManager() = default;
@@ -126,7 +133,7 @@
 void PrintBackendServiceManager::UnregisterClient(uint32_t id) {
   // Determine which client type has this ID, and remove it once found.
   absl::optional<ClientType> client_type;
-  std::string remote_id = GetRemoteIdForPrinterName(kEmptyPrinterName);
+  RemoteId remote_id = GetRemoteIdForPrinterName(kEmptyPrinterName);
   if (query_clients_.erase(id) != 0) {
     client_type = ClientType::kQuery;
   } else if (query_with_ui_clients_.erase(id) != 0) {
@@ -178,7 +185,7 @@
   LogCallToRemote("EnumeratePrinters", context);
   service->EnumeratePrinters(
       base::BindOnce(&PrintBackendServiceManager::OnDidEnumeratePrinters,
-                     base::Unretained(this), context));
+                     base::Unretained(this), std::move(context)));
 }
 
 void PrintBackendServiceManager::FetchCapabilities(
@@ -198,7 +205,7 @@
   service->FetchCapabilities(
       printer_name,
       base::BindOnce(&PrintBackendServiceManager::OnDidFetchCapabilities,
-                     base::Unretained(this), context));
+                     base::Unretained(this), std::move(context)));
 }
 
 void PrintBackendServiceManager::GetDefaultPrinterName(
@@ -214,7 +221,7 @@
   LogCallToRemote("GetDefaultPrinterName", context);
   service->GetDefaultPrinterName(
       base::BindOnce(&PrintBackendServiceManager::OnDidGetDefaultPrinterName,
-                     base::Unretained(this), context));
+                     base::Unretained(this), std::move(context)));
 }
 
 void PrintBackendServiceManager::GetPrinterSemanticCapsAndDefaults(
@@ -237,7 +244,7 @@
       printer_name,
       base::BindOnce(
           &PrintBackendServiceManager::OnDidGetPrinterSemanticCapsAndDefaults,
-          base::Unretained(this), context));
+          base::Unretained(this), std::move(context)));
 }
 
 void PrintBackendServiceManager::UseDefaultSettings(
@@ -261,7 +268,7 @@
   LogCallToRemote("UseDefaultSettings", context);
   service->UseDefaultSettings(
       base::BindOnce(&PrintBackendServiceManager::OnDidUseDefaultSettings,
-                     base::Unretained(this), context));
+                     base::Unretained(this), std::move(context)));
 }
 
 #if BUILDFLAG(IS_WIN)
@@ -286,7 +293,7 @@
   service->AskUserForSettings(
       NativeViewToUint(parent_view), max_pages, has_selection, is_scripted,
       base::BindOnce(&PrintBackendServiceManager::OnDidAskUserForSettings,
-                     base::Unretained(this), context));
+                     base::Unretained(this), std::move(context)));
 }
 #endif  // BUILDFLAG(IS_WIN)
 
@@ -308,7 +315,7 @@
   service->UpdatePrintSettings(
       std::move(job_settings),
       base::BindOnce(&PrintBackendServiceManager::OnDidUpdatePrintSettings,
-                     base::Unretained(this), context));
+                     base::Unretained(this), std::move(context)));
 }
 
 void PrintBackendServiceManager::StartPrinting(
@@ -332,7 +339,7 @@
   service->StartPrinting(
       document_cookie, document_name, target_type, settings,
       base::BindOnce(&PrintBackendServiceManager::OnDidStartPrinting,
-                     base::Unretained(this), context));
+                     base::Unretained(this), std::move(context)));
 }
 
 #if BUILDFLAG(IS_WIN)
@@ -362,7 +369,7 @@
       std::move(serialized_page_data), page.page_size(),
       page.page_content_rect(), page.shrink_factor(),
       base::BindOnce(&PrintBackendServiceManager::OnDidRenderPrintedPage,
-                     base::Unretained(this), context));
+                     base::Unretained(this), std::move(context)));
 }
 #endif  // BUILDFLAG(IS_WIN)
 
@@ -387,7 +394,7 @@
   service->RenderPrintedDocument(
       document_cookie, page_count, data_type, std::move(serialized_data),
       base::BindOnce(&PrintBackendServiceManager::OnDidRenderPrintedDocument,
-                     base::Unretained(this), context));
+                     base::Unretained(this), std::move(context)));
 }
 
 void PrintBackendServiceManager::DocumentDone(
@@ -408,7 +415,7 @@
   service->DocumentDone(
       document_cookie,
       base::BindOnce(&PrintBackendServiceManager::OnDidDocumentDone,
-                     base::Unretained(this), context));
+                     base::Unretained(this), std::move(context)));
 }
 
 void PrintBackendServiceManager::Cancel(
@@ -428,7 +435,7 @@
   LogCallToRemote("Cancel", context);
   service->Cancel(document_cookie,
                   base::BindOnce(&PrintBackendServiceManager::OnDidCancel,
-                                 base::Unretained(this), context));
+                                 base::Unretained(this), std::move(context)));
 }
 
 bool PrintBackendServiceManager::PrinterDriverFoundToRequireElevatedPrivilege(
@@ -455,7 +462,8 @@
   sandboxed_service_remote_for_test_ = remote;
   sandboxed_service_remote_for_test_->set_disconnect_handler(base::BindOnce(
       &PrintBackendServiceManager::OnRemoteDisconnected, base::Unretained(this),
-      /*sandboxed=*/true, /*remote_id=*/std::string()));
+      /*sandboxed=*/true,
+      GetRemoteIdForPrinterName(/*printer_name=*/std::string())));
 }
 
 void PrintBackendServiceManager::SetServiceForFallbackTesting(
@@ -463,7 +471,8 @@
   unsandboxed_service_remote_for_test_ = remote;
   unsandboxed_service_remote_for_test_->set_disconnect_handler(base::BindOnce(
       &PrintBackendServiceManager::OnRemoteDisconnected, base::Unretained(this),
-      /*sandboxed=*/false, /*remote_id=*/std::string()));
+      /*sandboxed=*/false,
+      GetRemoteIdForPrinterName(/*printer_name=*/std::string())));
 }
 
 // static
@@ -483,28 +492,38 @@
   }
 }
 
-std::string PrintBackendServiceManager::GetRemoteIdForPrinterName(
-    const std::string& printer_name) const {
-  if (sandboxed_service_remote_for_test_) {
-    // Test environment is always just one instance for all printers.
-    return std::string();
-  }
-
+PrintBackendServiceManager::RemoteId
+PrintBackendServiceManager::GetRemoteIdForPrinterName(
+    const std::string& printer_name) {
 #if BUILDFLAG(IS_WIN)
-  // Windows drivers are not thread safe.  Use a process per driver to prevent
-  // bad interactions when interfacing to multiple drivers in parallel.
-  // https://crbug.com/957242
-  return printer_name;
-#else
-  return std::string();
+  if (!sandboxed_service_remote_for_test_) {
+    // Windows drivers are not thread safe.  Use a process per driver to prevent
+    // bad interactions when interfacing to multiple drivers in parallel.
+    // https://crbug.com/957242
+    auto iter = remote_id_map_.find(printer_name);
+    if (iter != remote_id_map_.end()) {
+      return iter->second;
+    }
+
+    // No remote yet for this printer so make one.  RemoteId is only used within
+    // browse process management code, so a simple incrementing sequence is
+    // sufficient.
+    static uint32_t id_sequence = 0;
+    return remote_id_map_.insert({printer_name, RemoteId(++id_sequence)})
+        .first->second;
+  }
 #endif
+
+  // Non-Windows platforms and the testing environment always just use one
+  // instance for all printers.
+  return RemoteId(1);
 }
 
 absl::optional<uint32_t> PrintBackendServiceManager::RegisterClient(
     ClientType client_type,
     const std::string& printer_name) {
   uint32_t client_id = ++last_client_id_;
-  std::string remote_id = GetRemoteIdForPrinterName(printer_name);
+  RemoteId remote_id = GetRemoteIdForPrinterName(printer_name);
 
   VLOG(1) << "Registering a client with ID " << client_id
           << " for print backend service.";
@@ -627,7 +646,7 @@
     }
   }
 
-  std::string remote_id = GetRemoteIdForPrinterName(printer_name);
+  RemoteId remote_id = GetRemoteIdForPrinterName(printer_name);
   if (should_sandbox) {
     return GetServiceFromBundle(remote_id, client_type, /*sandboxed=*/true,
                                 sandboxed_remotes_bundles_);
@@ -639,7 +658,7 @@
 template <class T>
 mojo::Remote<mojom::PrintBackendService>&
 PrintBackendServiceManager::GetServiceFromBundle(
-    const std::string& remote_id,
+    const RemoteId& remote_id,
     ClientType client_type,
     bool sandboxed,
     RemotesBundleMap<T>& bundle_map) {
@@ -732,7 +751,7 @@
 absl::optional<base::TimeDelta>
 PrintBackendServiceManager::DetermineIdleTimeoutUpdateOnRegisteredClient(
     ClientType registered_client_type,
-    const std::string& remote_id) const {
+    const RemoteId& remote_id) const {
   switch (registered_client_type) {
     case ClientType::kQuery:
       DCHECK(!query_clients_.empty());
@@ -790,7 +809,7 @@
 absl::optional<base::TimeDelta>
 PrintBackendServiceManager::DetermineIdleTimeoutUpdateOnUnregisteredClient(
     ClientType unregistered_client_type,
-    const std::string& remote_id) const {
+    const RemoteId& remote_id) const {
   switch (unregistered_client_type) {
     case ClientType::kQuery:
       // Other query types have longer timeouts, so no need to update if
@@ -847,7 +866,7 @@
 void PrintBackendServiceManager::SetServiceIdleHandler(
     mojo::Remote<printing::mojom::PrintBackendService>& service,
     bool sandboxed,
-    const std::string& remote_id,
+    const RemoteId& remote_id,
     const base::TimeDelta& timeout) {
   DVLOG(1) << "Updating idle timeout for "
            << (sandboxed ? "sandboxed" : "unsandboxed")
@@ -864,7 +883,7 @@
 }
 
 void PrintBackendServiceManager::UpdateServiceIdleTimeoutByRemoteId(
-    const std::string& remote_id,
+    const RemoteId& remote_id,
     const base::TimeDelta& timeout) {
   auto sandboxed_iter = sandboxed_remotes_bundles_.find(remote_id);
   if (sandboxed_iter != sandboxed_remotes_bundles_.end()) {
@@ -883,7 +902,7 @@
 }
 
 void PrintBackendServiceManager::OnIdleTimeout(bool sandboxed,
-                                               const std::string& remote_id) {
+                                               const RemoteId& remote_id) {
   DVLOG(1) << "Print Backend service idle timeout for "
            << (sandboxed ? "sandboxed" : "unsandboxed") << " remote id `"
            << remote_id << "`";
@@ -896,7 +915,7 @@
 
 void PrintBackendServiceManager::OnRemoteDisconnected(
     bool sandboxed,
-    const std::string& remote_id) {
+    const RemoteId& remote_id) {
   DVLOG(1) << "Print Backend service disconnected for "
            << (sandboxed ? "sandboxed" : "unsandboxed") << " remote id `"
            << remote_id << "`";
@@ -1048,7 +1067,7 @@
 template <class... T, class... X>
 void PrintBackendServiceManager::SaveCallback(
     RemoteSavedCallbacks<T...>& saved_callbacks,
-    const std::string& remote_id,
+    const RemoteId& remote_id,
     const base::UnguessableToken& saved_callback_id,
     base::OnceCallback<void(X...)> callback) {
   saved_callbacks[remote_id].emplace(saved_callback_id, std::move(callback));
@@ -1057,7 +1076,7 @@
 template <class... T, class... X>
 void PrintBackendServiceManager::ServiceCallbackDone(
     RemoteSavedCallbacks<T...>& saved_callbacks,
-    const std::string& remote_id,
+    const RemoteId& remote_id,
     const base::UnguessableToken& saved_callback_id,
     X... data) {
   auto found_callback_map = saved_callbacks.find(remote_id);
@@ -1187,7 +1206,7 @@
 template <class T>
 void PrintBackendServiceManager::RunSavedCallbacksStructResult(
     RemoteSavedStructCallbacks<T>& saved_callbacks,
-    const std::string& remote_id,
+    const RemoteId& remote_id,
     mojo::StructPtr<T> result_to_clone) {
   auto found_callbacks_map = saved_callbacks.find(remote_id);
   if (found_callbacks_map == saved_callbacks.end())
@@ -1213,7 +1232,7 @@
 template <class... T>
 void PrintBackendServiceManager::RunSavedCallbacks(
     RemoteSavedCallbacks<T...>& saved_callbacks,
-    const std::string& remote_id,
+    const RemoteId& remote_id,
     T... result) {
   auto found_callbacks_map = saved_callbacks.find(remote_id);
   if (found_callbacks_map == saved_callbacks.end())
diff --git a/chrome/browser/printing/print_backend_service_manager.h b/chrome/browser/printing/print_backend_service_manager.h
index 9371c475..e03ab472 100644
--- a/chrome/browser/printing/print_backend_service_manager.h
+++ b/chrome/browser/printing/print_backend_service_manager.h
@@ -12,6 +12,7 @@
 #include "base/containers/flat_set.h"
 #include "base/memory/raw_ptr.h"
 #include "base/no_destructor.h"
+#include "base/types/strong_alias.h"
 #include "base/unguessable_token.h"
 #include "base/values.h"
 #include "build/build_config.h"
@@ -43,8 +44,13 @@
 
 class PrintBackendServiceManager {
  public:
+  using RemoteId = base::StrongAlias<class RemoteIdTag, uint32_t>;
+
+  // Contains set of client IDs.
   using ClientsSet = base::flat_set<uint32_t>;
-  using PrintClientsMap = base::flat_map<std::string, ClientsSet>;
+
+  // Mapping of clients to each remote ID that is for printing.
+  using PrintClientsMap = base::flat_map<RemoteId, ClientsSet>;
 
   // Amount of idle time to wait before resetting the connection to the service.
   static constexpr base::TimeDelta kNoClientsRegisteredResetOnIdleTimeout =
@@ -201,8 +207,7 @@
   // Key is the remote ID that enables finding the correct remote.  Note that
   // the remote ID does not necessarily mean the printer name.
   template <class... T>
-  using RemoteSavedCallbacks =
-      base::flat_map<std::string, SavedCallbacks<T...>>;
+  using RemoteSavedCallbacks = base::flat_map<RemoteId, SavedCallbacks<T...>>;
   template <class... T>
   using RemoteSavedStructCallbacks =
       RemoteSavedCallbacks<mojo::StructPtr<T...>>;
@@ -247,14 +252,18 @@
 
   template <class T>
   using RemotesBundleMap =
-      base::flat_map<std::string, std::unique_ptr<RemotesBundle<T>>>;
+      base::flat_map<RemoteId, std::unique_ptr<RemotesBundle<T>>>;
 
   // PrintBackendServiceManager needs to be able to run a callback either after
   // a successful return from the service or after the remote was disconnected.
   // This structure is used to save the callback's context.
   struct CallbackContext {
+    CallbackContext();
+    CallbackContext(CallbackContext&& other) noexcept;
+    ~CallbackContext();
+
     bool is_sandboxed;
-    std::string remote_id;
+    RemoteId remote_id;
     base::UnguessableToken saved_callback_id;
   };
 
@@ -269,7 +278,7 @@
   void SetCrashKeys(const std::string& printer_name);
 
   // Determine the remote ID that is used for the specified `printer_name`.
-  std::string GetRemoteIdForPrinterName(const std::string& printer_name) const;
+  RemoteId GetRemoteIdForPrinterName(const std::string& printer_name);
 
   // Common helper for registering clients.
   absl::optional<uint32_t> RegisterClient(ClientType client_type,
@@ -299,7 +308,7 @@
   // Helper to `GetService` for a particular remotes bundle type.
   template <class T>
   mojo::Remote<mojom::PrintBackendService>& GetServiceFromBundle(
-      const std::string& remote_id,
+      const RemoteId& remote_id,
       ClientType client_type,
       bool sandboxed,
       RemotesBundleMap<T>& bundle_map);
@@ -312,33 +321,33 @@
   // a new client registered for `registered_client_type`.
   absl::optional<base::TimeDelta> DetermineIdleTimeoutUpdateOnRegisteredClient(
       ClientType registered_client_type,
-      const std::string& remote_id) const;
+      const RemoteId& remote_id) const;
 
   // Determine if idle timeout should be modified after a client of type
   // `unregistered_client_type` has been unregistered.
   absl::optional<base::TimeDelta>
   DetermineIdleTimeoutUpdateOnUnregisteredClient(
       ClientType unregistered_client_type,
-      const std::string& remote_id) const;
+      const RemoteId& remote_id) const;
 
   // Helper functions to adjust service idle timeout duration.
   void SetServiceIdleHandler(
       mojo::Remote<printing::mojom::PrintBackendService>& service,
       bool sandboxed,
-      const std::string& remote_id,
+      const RemoteId& remote_id,
       const base::TimeDelta& timeout);
-  void UpdateServiceIdleTimeoutByRemoteId(const std::string& remote_id,
+  void UpdateServiceIdleTimeoutByRemoteId(const RemoteId& remote_id,
                                           const base::TimeDelta& timeout);
 
   // Callback when predetermined idle timeout occurs indicating no in-flight
   // messages for a short period of time.  `sandboxed` is used to distinguish
   // which mapping of remotes the timeout applies to.
-  void OnIdleTimeout(bool sandboxed, const std::string& remote_id);
+  void OnIdleTimeout(bool sandboxed, const RemoteId& remote_id);
 
   // Callback when service has disconnected (e.g., process crashes).
   // `sandboxed` is used to distinguish which mapping of remotes the
   // disconnection applies to.
-  void OnRemoteDisconnected(bool sandboxed, const std::string& remote_id);
+  void OnRemoteDisconnected(bool sandboxed, const RemoteId& remote_id);
 
   // Helper function to choose correct saved callbacks mapping.
   RemoteSavedEnumeratePrintersCallbacks&
@@ -379,14 +388,14 @@
   // Helper functions to save outstanding callbacks.
   template <class... T, class... X>
   void SaveCallback(RemoteSavedCallbacks<T...>& saved_callbacks,
-                    const std::string& remote_id,
+                    const RemoteId& remote_id,
                     const base::UnguessableToken& saved_callback_id,
                     base::OnceCallback<void(X...)> callback);
 
   // Helper functions for local callback wrappers for mojom calls.
   template <class... T, class... X>
   void ServiceCallbackDone(RemoteSavedCallbacks<T...>& saved_callbacks,
-                           const std::string& remote_id,
+                           const RemoteId& remote_id,
                            const base::UnguessableToken& saved_callback_id,
                            X... data);
 
@@ -427,11 +436,11 @@
   template <class T>
   void RunSavedCallbacksStructResult(
       RemoteSavedStructCallbacks<T>& saved_callbacks,
-      const std::string& remote_id,
+      const RemoteId& remote_id,
       mojo::StructPtr<T> result_to_clone);
   template <class... T>
   void RunSavedCallbacks(RemoteSavedCallbacks<T...>& saved_callbacks,
-                         const std::string& remote_id,
+                         const RemoteId& remote_id,
                          T... result);
 
   // Test support for client ID management.
@@ -525,6 +534,13 @@
   // performing the operation with modified restrictions.
   base::flat_set<std::string> drivers_requiring_elevated_privilege_;
 
+#if BUILDFLAG(IS_WIN)
+  // Support for process model where there can be multiple PrintBackendService
+  // instances.  This is necessary because Windows printer drivers are not
+  // thread safe.  Map key is a printer name.
+  base::flat_map<std::string, RemoteId> remote_id_map_;
+#endif
+
   // Crash key is kept at class level so that we can obtain printer driver
   // information for a prior call should the process be terminated due to Mojo
   // message response validation.
diff --git a/chrome/browser/printing/print_backend_service_manager_unittest.cc b/chrome/browser/printing/print_backend_service_manager_unittest.cc
index 62545ca..5602f141a 100644
--- a/chrome/browser/printing/print_backend_service_manager_unittest.cc
+++ b/chrome/browser/printing/print_backend_service_manager_unittest.cc
@@ -17,11 +17,12 @@
 
 using ClientsSet = PrintBackendServiceManager::ClientsSet;
 using PrintClientsMap = PrintBackendServiceManager::PrintClientsMap;
+using RemoteId = PrintBackendServiceManager::RemoteId;
 
 namespace {
 
-constexpr char kRemoteIdEmpty[] = "";
-constexpr char kRemoteIdTestPrinter[] = "test-printer";
+const RemoteId kRemoteIdEmpty{1};
+const RemoteId kRemoteIdTestPrinter{2};
 
 const ClientsSet kTestQueryNoClients;
 const ClientsSet kTestQueryWithOneClient{1};
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/background.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/background.js
index 48af186..4892db1 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/background.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/background.js
@@ -45,6 +45,7 @@
 import {PanelBackground} from './panel/panel_background.js';
 import {ChromeVoxPrefs} from './prefs.js';
 import {RangeAutomationHandler} from './range_automation_handler.js';
+import {SmartStickyMode} from './smart_sticky_mode.js';
 import {TtsBackground} from './tts_background.js';
 
 /**
@@ -135,6 +136,7 @@
     PageLoadSoundHandler.init();
     PanelBackground.init();
     RangeAutomationHandler.init();
+    SmartStickyMode.init();
 
     // Allow all async initializers to run simultaneously, but wait for them to
     // complete before continuing.
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/command_handler.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/command_handler.js
index bc160a6..24132634 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/command_handler.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/command_handler.js
@@ -82,7 +82,6 @@
     /** @private {boolean} */
     this.languageLoggingEnabled_ = false;
 
-    SmartStickyMode.init();
     this.init_();
   }
 
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/output/output.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/output/output.js
index 52a083f3..875c577 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/output/output.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/output/output.js
@@ -672,57 +672,6 @@
   }
 
   /** @override */
-  formatNode_(data, token, tree, options) {
-    const buff = data.outputBuffer;
-    const node = data.node;
-    const formatLog = data.outputFormatLogger;
-    let prevNode = data.opt_prevNode;
-
-    if (!tree.firstChild) {
-      return;
-    }
-
-    const relationName = tree.firstChild.value;
-    if (relationName === 'tableCellColumnHeaders') {
-      // Skip output when previous position falls on the same column.
-      while (prevNode && !AutomationPredicate.cellLike(prevNode)) {
-        prevNode = prevNode.parent;
-      }
-      if (prevNode &&
-          prevNode.tableCellColumnIndex === node.tableCellColumnIndex) {
-        return;
-      }
-
-      const headers = node.tableCellColumnHeaders;
-      if (headers) {
-        for (let i = 0; i < headers.length; i++) {
-          const header = headers[i].name;
-          if (header) {
-            this.append_(buff, header, options);
-            formatLog.writeTokenWithValue(token, header);
-          }
-        }
-      }
-    } else if (relationName === 'tableCellRowHeaders') {
-      const headers = node.tableCellRowHeaders;
-      if (headers) {
-        for (let i = 0; i < headers.length; i++) {
-          const header = headers[i].name;
-          if (header) {
-            this.append_(buff, header, options);
-            formatLog.writeTokenWithValue(token, header);
-          }
-        }
-      }
-    } else if (node[relationName]) {
-      const related = node[relationName];
-      this.node_(
-          related, related, outputTypes.OutputCustomEvent.NAVIGATE, buff,
-          formatLog);
-    }
-  }
-
-  /** @override */
   formatTextContent_(data, token, options) {
     const buff = data.outputBuffer;
     const node = data.node;
@@ -1045,7 +994,7 @@
             node, prevNode, type, buff, formatLog,
             {preferStart: preferStartOrEndAncestry});
       }
-      this.node_(node, prevNode, type, buff, formatLog);
+      this.formatNode(node, prevNode, type, buff, formatLog);
       if (addContextAfter) {
         this.ancestry_(
             node, prevNode, type, buff, formatLog,
@@ -1310,9 +1259,9 @@
    * @param {!outputTypes.OutputEventType} type
    * @param {!Array<Spannable>} buff
    * @param {!OutputFormatLogger} formatLog
-   * @private
+   * @override
    */
-  node_(node, prevNode, type, buff, formatLog) {
+  formatNode(node, prevNode, type, buff, formatLog) {
     const originalBuff = buff;
 
     if (this.formatOptions_.braille) {
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/output/output_formatter.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/output/output_formatter.js
index 9031378d..8e9ff21b 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/output/output_formatter.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/output/output_formatter.js
@@ -99,7 +99,7 @@
     } else if (token === 'cellIndexText') {
       this.formatCellIndexText_(this.params_, token, options);
     } else if (token === 'node') {
-      this.output_.formatNode_(this.params_, token, tree, options);
+      this.formatNode_(this.params_, token, tree, options);
     } else if (token === 'nameOrTextContent' || token === 'textContent') {
       this.output_.formatTextContent_(this.params_, token, options);
     } else if (this.params_.node[token] !== undefined) {
@@ -507,6 +507,63 @@
   /**
    * @param {!outputTypes.OutputFormattingData} data
    * @param {string} token
+   * @param {!OutputFormatTree} tree
+   * @param {!{annotation: Array<*>, isUnique: (boolean|undefined)}} options
+   * @private
+   */
+  formatNode_(data, token, tree, options) {
+    const buff = data.outputBuffer;
+    const node = data.node;
+    const formatLog = data.outputFormatLogger;
+    let prevNode = data.opt_prevNode;
+
+    if (!tree.firstChild) {
+      return;
+    }
+
+    const relationName = tree.firstChild.value;
+    if (relationName === 'tableCellColumnHeaders') {
+      // Skip output when previous position falls on the same column.
+      while (prevNode && !AutomationPredicate.cellLike(prevNode)) {
+        prevNode = prevNode.parent;
+      }
+      if (prevNode &&
+          prevNode.tableCellColumnIndex === node.tableCellColumnIndex) {
+        return;
+      }
+
+      const headers = node.tableCellColumnHeaders;
+      if (headers) {
+        for (let i = 0; i < headers.length; i++) {
+          const header = headers[i].name;
+          if (header) {
+            this.output_.append_(buff, header, options);
+            formatLog.writeTokenWithValue(token, header);
+          }
+        }
+      }
+    } else if (relationName === 'tableCellRowHeaders') {
+      const headers = node.tableCellRowHeaders;
+      if (headers) {
+        for (let i = 0; i < headers.length; i++) {
+          const header = headers[i].name;
+          if (header) {
+            this.output_.append_(buff, header, options);
+            formatLog.writeTokenWithValue(token, header);
+          }
+        }
+      }
+    } else if (node[relationName]) {
+      const related = node[relationName];
+      this.output_.formatNode(
+          related, related, outputTypes.OutputCustomEvent.NAVIGATE, buff,
+          formatLog);
+    }
+  }
+
+  /**
+   * @param {!outputTypes.OutputFormattingData} data
+   * @param {string} token
    * @private
    */
   formatPressed_(data, token) {
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/output/output_interface.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/output/output_interface.js
index 7bae0e6..d041f78f 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/output/output_interface.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/output/output_interface.js
@@ -85,14 +85,6 @@
 
   /**
    * @param {!outputTypes.OutputFormattingData} data
-   * @param {string} token
-   * @param {!OutputFormatTree} tree
-   * @param {!{annotation: Array<*>, isUnique: (boolean|undefined)}} options
-   */
-  formatNode_(data, token, tree, options) {}
-
-  /**
-   * @param {!outputTypes.OutputFormattingData} data
    */
   formatPhoneticReading_(data) {}
 
@@ -109,6 +101,15 @@
   formatTextContent_(data, token, options) {}
 
   /**
+   * @param {!AutomationNode} node
+   * @param {!AutomationNode} prevNode
+   * @param {!outputTypes.OutputEventType} type
+   * @param {!Array<Spannable>} buff
+   * @param {!OutputFormatLogger} formatLog
+   */
+  formatNode(node, prevNode, type, buff, formatLog) {}
+
+  /**
    * Renders the given range using optional context previous range and event
    * type.
    * @param {!CursorRange} range
diff --git a/chrome/browser/resources/chromeos/drive_internals.html b/chrome/browser/resources/chromeos/drive_internals.html
index 7cc3715..1609308f 100644
--- a/chrome/browser/resources/chromeos/drive_internals.html
+++ b/chrome/browser/resources/chromeos/drive_internals.html
@@ -109,39 +109,33 @@
 
     <section id="bulk-pinning-section" hidden>
       <h2>Bulk Pinning</h2>
-      <label>
-        Enable Bulk Pinning
-        <input type="checkbox" id="bulk-pinning-toggle">
-      </label>
       <table>
-        <tr colspan="2">
-          <td><progress id="bulk-pinning-progress"></progress></td>
-        </tr>
         <tr>
-          <th>Current Stage</th>
+          <th>
+            <label for="bulk-pinning-toggle">Enable</label>
+          </th>
+          <td>
+            <input type="checkbox" id="bulk-pinning-toggle">
+          </td>
+        <tr>
+          <th>Stage</th>
           <td id="bulk-pinning-setup-stage">?</td>
         </tr>
         <tr>
-          <th>Setup Stage Error</th>
+          <th>Error</th>
           <td id="bulk-pinning-setup-stage-error">?</td>
         </tr>
         <tr>
-          <th>Available Space</th>
-          <td>
-            <span id="bulk-pinning-available-disk-space">?</span> MB
-           </td>
+          <th>Free Space</th>
+          <td id="bulk-pinning-available-disk-space">?</td>
         </tr>
         <tr>
           <th>Required Space</th>
-          <td>
-            <span id="bulk-pinning-required-disk-space">?</span> MB
-           </td>
+          <td id="bulk-pinning-required-disk-space">?</td>
         </tr>
         <tr>
           <th>Pinned Space</th>
-          <td>
-            <span id="bulk-pinning-pinned-disk-space">?</span> MB
-           </td>
+          <td id="bulk-pinning-pinned-disk-space">?</td>
         </tr>
       </table>
     </section>
diff --git a/chrome/browser/resources/chromeos/drive_internals.js b/chrome/browser/resources/chromeos/drive_internals.js
index 9c6b291..bea7b23 100644
--- a/chrome/browser/resources/chromeos/drive_internals.js
+++ b/chrome/browser/resources/chromeos/drive_internals.js
@@ -102,16 +102,11 @@
 
 function updateBulkPinning(enabled) {
   $('bulk-pinning-toggle').checked = enabled;
-  if (enabled) {
-    return;
-  }
   $('bulk-pinning-setup-stage').innerText = '?';
   $('bulk-pinning-setup-stage-error').innerText = '?';
   $('bulk-pinning-available-disk-space').innerText = '?';
   $('bulk-pinning-required-disk-space').innerText = '?';
   $('bulk-pinning-pinned-disk-space').innerText = '?';
-  $('bulk-pinning-progress').value = 0;
-  $('bulk-pinning-progress').max = 1;
 }
 
 function onBulkPinningProgress(progress) {
@@ -120,17 +115,11 @@
     return;
   }
   $('bulk-pinning-setup-stage').innerText = progress.stage;
-  if (progress.setupError) {
-    $('bulk-pinning-setup-stage-error').innerText = progress.setupError;
-  }
+  $('bulk-pinning-setup-stage-error').innerText = progress.setupError;
   $('bulk-pinning-available-disk-space').innerText =
-      toMegaByteString(Number(progress.availableDiskSpace));
-  $('bulk-pinning-required-disk-space').innerText =
-      toMegaByteString(Number(progress.requiredDiskSpace));
-  $('bulk-pinning-pinned-disk-space').innerText =
-      toMegaByteString(Number(progress.pinnedDiskSpace));
-  $('bulk-pinning-progress').value = Number(progress.pinnedDiskSpace);
-  $('bulk-pinning-progress').max = Number(progress.requiredDiskSpace);
+      progress.availableDiskSpace;
+  $('bulk-pinning-required-disk-space').innerText = progress.requiredDiskSpace;
+  $('bulk-pinning-pinned-disk-space').innerText = progress.pinnedDiskSpace;
 }
 
 function updateStartupArguments(args) {
diff --git a/chrome/browser/resources/settings/chromeos/device_page/audio.html b/chrome/browser/resources/settings/chromeos/device_page/audio.html
index 3318dc74..7fef6b2 100644
--- a/chrome/browser/resources/settings/chromeos/device_page/audio.html
+++ b/chrome/browser/resources/settings/chromeos/device_page/audio.html
@@ -154,7 +154,9 @@
         </div>
         <div id="audioInputNoiseCancellationSubsection">
           <div id="audioInputNoiseCancellationLabel">Noise Cancellation</div>
-          <cr-toggle id="audioInputNoiseCancellationToggle"></cr-toggle>
+          <cr-toggle id="audioInputNoiseCancellationToggle"
+              checked="{{isNoiseCancellationEnabled_}}">
+          </cr-toggle>
         </div>
       </div>
     </div>
diff --git a/chrome/browser/resources/settings/chromeos/device_page/audio.ts b/chrome/browser/resources/settings/chromeos/device_page/audio.ts
index 5fb1c4e..e69e74df 100644
--- a/chrome/browser/resources/settings/chromeos/device_page/audio.ts
+++ b/chrome/browser/resources/settings/chromeos/device_page/audio.ts
@@ -27,7 +27,7 @@
 import {CrosAudioConfigInterface, getCrosAudioConfig} from './cros_audio_config.js';
 // TODO(b/260277007): Update import to get `AudioSystemProperties` from
 // `cros_audio_config.mojom-webui.js` once mojo updated to handle audio input.
-import {AudioSystemProperties, FakeCrosAudioConfig} from './fake_cros_audio_config.js';
+import {AudioDevice, AudioEffectState, AudioSystemProperties, FakeCrosAudioConfig} from './fake_cros_audio_config.js';
 
 /** Utility for keeping percent in inclusive range of [0,100].  */
 function clampPercent(percent: number): number {
@@ -59,6 +59,12 @@
         type: Boolean,
         reflectToAttribute: true,
       },
+
+      isNoiseCancellationEnabled_: {
+        type: Boolean,
+        observer:
+            SettingsAudioElement.prototype.onNoiseCancellationEnabledChanged,
+      },
     };
   }
 
@@ -68,6 +74,7 @@
   private crosAudioConfig_: CrosAudioConfigInterface;
   private isOutputMuted_: boolean;
   private isInputMuted_: boolean;
+  private isNoiseCancellationEnabled_: boolean;
 
   constructor() {
     super();
@@ -95,6 +102,11 @@
         this.audioSystemProperties_.outputMuteState !== MuteState.kNotMuted;
     this.isInputMuted_ =
         this.audioSystemProperties_.inputMuteState !== MuteState.kNotMuted;
+    const activeInputDevice = this.audioSystemProperties_.inputDevices.find(
+        (device: AudioDevice) => device.isActive);
+    this.isNoiseCancellationEnabled_ =
+        (activeInputDevice?.noiseCancellationState ===
+         AudioEffectState.ENABLED);
   }
 
   getIsOutputMutedForTest(): boolean {
@@ -145,6 +157,18 @@
     this.crosAudioConfig_.setActiveDevice(BigInt(inputDeviceSelect.value));
   }
 
+  /** Handles updates to noise cancellation state. */
+  protected onNoiseCancellationEnabledChanged(
+      enabled: SettingsAudioElement['isNoiseCancellationEnabled_']): void {
+    // TODO(b/260277007): Remove condition when setActiveDevice added to mojo
+    // definition.
+    if (!this.crosAudioConfig_.setNoiseCancellationEnabled) {
+      return;
+    }
+
+    this.crosAudioConfig_.setNoiseCancellationEnabled(enabled);
+  }
+
   /**
    * Handles the event where the input volume slider is being changed.
    */
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/internet_page/BUILD.gn
index a9c555c8..b3e63e73 100644
--- a/chrome/browser/resources/settings/chromeos/internet_page/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/internet_page/BUILD.gn
@@ -11,31 +11,12 @@
   closure_flags = os_settings_closure_flags
   is_polymer3 = true
   deps = [
-    ":apn_subpage",
     ":internet_page",
     ":internet_page_browser_proxy",
     ":internet_subpage",
   ]
 }
 
-js_library("apn_subpage") {
-  deps = [
-    "//ash/webui/common/resources:assert",
-    "//ash/webui/common/resources/network:apn_list",
-    "//ash/webui/common/resources/network:cellular_utils",
-    "//ash/webui/common/resources/network:mojo_interface_provider",
-    "//ash/webui/common/resources/network:network_listener_behavior",
-    "//ash/webui/common/resources/network:onc_mojo",
-    "//chrome/browser/resources/settings/chromeos:route_observer_behavior",
-    "//chrome/browser/resources/settings/chromeos:router",
-    "//chromeos/services/network_config/public/mojom:mojom_webui_js",
-    "//chromeos/services/network_config/public/mojom:network_types_webui_js",
-    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
-  ]
-
-  externs_list = []
-}
-
 js_library("internet_page") {
   deps = [
     ":internet_page_browser_proxy",
@@ -98,7 +79,6 @@
 
 html_to_js("web_components") {
   js_files = [
-    "apn_subpage.js",
     "internet_page.js",
     "internet_subpage.js",
   ]
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/apn_subpage.js b/chrome/browser/resources/settings/chromeos/internet_page/apn_subpage.js
deleted file mode 100644
index 947cd72..0000000
--- a/chrome/browser/resources/settings/chromeos/internet_page/apn_subpage.js
+++ /dev/null
@@ -1,235 +0,0 @@
-// Copyright 2022 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * Settings subpage for managing APNs.
- */
-
-import './internet_shared.css.js';
-import 'chrome://resources/ash/common/network/apn_list.js';
-
-import {assert} from 'chrome://resources/ash/common/assert.js';
-import {processDeviceState} from 'chrome://resources/ash/common/network/cellular_utils.js';
-import {MojoInterfaceProviderImpl} from 'chrome://resources/ash/common/network/mojo_interface_provider.js';
-import {NetworkListenerBehavior, NetworkListenerBehaviorInterface} from 'chrome://resources/ash/common/network/network_listener_behavior.js';
-import {OncMojo} from 'chrome://resources/ash/common/network/onc_mojo.js';
-import {CrosNetworkConfigRemote, ManagedCellularProperties, ManagedProperties, MAX_NUM_CUSTOM_APNS, NetworkStateProperties} from 'chrome://resources/mojo/chromeos/services/network_config/public/mojom/cros_network_config.mojom-webui.js';
-import {NetworkType} from 'chrome://resources/mojo/chromeos/services/network_config/public/mojom/network_types.mojom-webui.js';
-import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
-
-import {routes} from '../os_route.js';
-import {RouteObserverBehavior, RouteObserverBehaviorInterface} from '../route_observer_behavior.js';
-import {Route, Router} from '../router.js';
-
-/**
- * @constructor
- * @extends {PolymerElement}
- * @implements {NetworkListenerBehaviorInterface}
- * @implements {RouteObserverBehaviorInterface}
- */
-const ApnSubpageElementBase = mixinBehaviors(
-    [
-      NetworkListenerBehavior,
-      RouteObserverBehavior,
-    ],
-    PolymerElement);
-
-/** @polymer */
-export class ApnSubpageElement extends ApnSubpageElementBase {
-  static get is() {
-    return 'apn-subpage';
-  }
-
-  static get template() {
-    return html`{__html_template__}`;
-  }
-
-  static get properties() {
-    return {
-      /** @private The GUID of the network to display details for. */
-      guid_: String,
-
-      /** @private {!ManagedProperties|undefined} */
-      managedProperties_: {
-        type: Object,
-      },
-
-      /** @private {?OncMojo.DeviceStateProperties} */
-      deviceState_: {
-        type: Object,
-        value: null,
-      },
-
-      isNumCustomApnsLimitReached: {
-        type: Boolean,
-        notify: true,
-        value: false,
-        computed: 'computeIsNumCustomApnsLimitReached_(managedProperties_)',
-      },
-    };
-  }
-
-  constructor() {
-    super();
-    /** @private {!CrosNetworkConfigRemote} */
-    this.networkConfig_ =
-        MojoInterfaceProviderImpl.getInstance().getMojoServiceRemote();
-  }
-
-  close() {
-    // If the page is already closed, return early to avoid navigating backward
-    // erroneously.
-    if (!this.guid_) {
-      return;
-    }
-
-    this.guid_ = '';
-    this.managedProperties_ = undefined;
-    this.deviceState_ = null;
-    Router.getInstance().navigateToPreviousRoute();
-  }
-
-  /**
-   * RouteObserverBehavior
-   * @param {!Route} route
-   * @protected
-   */
-  currentRouteChanged(route) {
-    if (route !== routes.APN) {
-      return;
-    }
-
-    const queryParams = Router.getInstance().getQueryParameters();
-    const guid = queryParams.get('guid') || '';
-    if (!guid) {
-      console.warn('No guid specified for page:' + route);
-      Router.getInstance().navigateToPreviousRoute();
-      return;
-    }
-
-    this.guid_ = guid;
-    // Set default properties until they are loaded.
-    this.deviceState_ = null;
-    this.managedProperties_ = OncMojo.getDefaultManagedProperties(
-        NetworkType.kCellular, this.guid_,
-        OncMojo.getNetworkTypeString(NetworkType.kCellular));
-    this.getNetworkDetails_();
-  }
-
-  /**
-   * CrosNetworkConfigObserver impl
-   * @param {!NetworkStateProperties} network
-   */
-  onNetworkStateChanged(network) {
-    if (!this.guid_ || !this.managedProperties_) {
-      return;
-    }
-    if (network.guid === this.guid_) {
-      this.getNetworkDetails_();
-    }
-  }
-
-  /** CrosNetworkConfigObserver impl */
-  onDeviceStateListChanged() {
-    if (!this.guid_ || !this.managedProperties_) {
-      return;
-    }
-    this.getDeviceState_();
-  }
-
-  /**
-   * Helper method that can be used by parent elements to open the APN
-   * creation dialog.
-   */
-  openApnDetailDialogInCreateMode() {
-    assert(!!this.guid_);
-    assert(!!this.$.apnList);
-    this.$.apnList.openApnDetailDialogInCreateMode();
-  }
-
-  /** @private */
-  getNetworkDetails_() {
-    assert(this.guid_);
-    this.networkConfig_.getManagedProperties(this.guid_).then(response => {
-      // Details page was closed while request was in progress, ignore the
-      // result.
-      if (!this.guid_) {
-        return;
-      }
-
-      if (!response.result) {
-        // Close the page if the network was removed and no longer exists.
-        this.close();
-        return;
-      }
-
-      if (!this.isCellular_(response.result)) {
-        // Close the page if there are no cellular properties.
-        this.close();
-        return;
-      }
-
-      if (this.deviceState_ && this.deviceState_.scanning) {
-        // Cellular properties may be invalid while scanning, so keep the
-        // existing properties instead.
-        response.result.typeProperties.cellular =
-            this.managedProperties_.typeProperties.cellular;
-      }
-      this.managedProperties_ = response.result;
-
-      if (!this.deviceState_) {
-        this.getDeviceState_();
-      }
-    });
-  }
-
-  /** @private */
-  getDeviceState_() {
-    if (!this.isCellular_(this.managedProperties_)) {
-      return;
-    }
-    const type = this.managedProperties_.type;
-    this.networkConfig_.getDeviceStateList().then(response => {
-      // If there is no GUID, the page was closed between requesting the device
-      // state and receiving it. If this occurs, there is no need to process the
-      // response. Note that if this subpage is reopened later, we'll request
-      // this data again.
-      if (!this.guid_) {
-        return;
-      }
-
-      const {deviceState, shouldGetNetworkDetails} =
-          processDeviceState(type, response.result, this.deviceState_);
-
-      this.deviceState_ = deviceState;
-      if (shouldGetNetworkDetails) {
-        this.getNetworkDetails_();
-      }
-    });
-  }
-
-  /**
-   * @param {!ManagedProperties|undefined} managedProperties
-   * @return {boolean}
-   * @private
-   */
-  isCellular_(managedProperties) {
-    return !!managedProperties &&
-        managedProperties.type === NetworkType.kCellular;
-  }
-
-  /**
-   * @return {boolean}
-   * @private
-   */
-  computeIsNumCustomApnsLimitReached_() {
-    return this.isCellular_(this.managedProperties_) &&
-        !!this.managedProperties_.typeProperties.cellular.customApnList &&
-        this.managedProperties_.typeProperties.cellular.customApnList.length >=
-        MAX_NUM_CUSTOM_APNS;
-  }
-}
-
-customElements.define(ApnSubpageElement.is, ApnSubpageElement);
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/apn_subpage.ts b/chrome/browser/resources/settings/chromeos/internet_page/apn_subpage.ts
new file mode 100644
index 0000000..e33983a
--- /dev/null
+++ b/chrome/browser/resources/settings/chromeos/internet_page/apn_subpage.ts
@@ -0,0 +1,225 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview
+ * Settings subpage for managing APNs.
+ */
+
+import './internet_shared.css.js';
+import 'chrome://resources/ash/common/network/apn_list.js';
+
+import {ApnList} from 'chrome://resources/ash/common/network/apn_list.js';
+import {processDeviceState} from 'chrome://resources/ash/common/network/cellular_utils.js';
+import {MojoInterfaceProviderImpl} from 'chrome://resources/ash/common/network/mojo_interface_provider.js';
+import {NetworkListenerBehavior, NetworkListenerBehaviorInterface} from 'chrome://resources/ash/common/network/network_listener_behavior.js';
+import {OncMojo} from 'chrome://resources/ash/common/network/onc_mojo.js';
+import {assert} from 'chrome://resources/js/assert_ts.js';
+import {CrosNetworkConfigRemote, ManagedProperties, MAX_NUM_CUSTOM_APNS, NetworkStateProperties} from 'chrome://resources/mojo/chromeos/services/network_config/public/mojom/cros_network_config.mojom-webui.js';
+import {NetworkType} from 'chrome://resources/mojo/chromeos/services/network_config/public/mojom/network_types.mojom-webui.js';
+import {mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {routes} from '../os_route.js';
+import {RouteObserverMixin, RouteObserverMixinInterface} from '../route_observer_mixin.js';
+import {Route, Router} from '../router.js';
+
+import {getTemplate} from './apn_subpage.html.js';
+
+export interface ApnSubpageElement {
+  $: {
+    apnList: ApnList,
+  };
+}
+
+const ApnSubpageElementBase = mixinBehaviors(
+                                  [
+                                    NetworkListenerBehavior,
+                                  ],
+                                  RouteObserverMixin(PolymerElement)) as {
+  new (): PolymerElement & RouteObserverMixinInterface &
+      NetworkListenerBehaviorInterface,
+};
+
+export class ApnSubpageElement extends ApnSubpageElementBase {
+  static get is() {
+    return 'apn-subpage' as const;
+  }
+
+  static get template() {
+    return getTemplate();
+  }
+
+  static get properties() {
+    return {
+      /** The GUID of the network to display details for. */
+      guid_: String,
+
+      isNumCustomApnsLimitReached: {
+        type: Boolean,
+        notify: true,
+        value: false,
+        computed: 'computeIsNumCustomApnsLimitReached_(managedProperties_)',
+      },
+
+      managedProperties_: {
+        type: Object,
+      },
+
+      deviceState_: {
+        type: Object,
+        value: null,
+      },
+    };
+  }
+
+  isNumCustomApnsLimitReached: boolean;
+  private deviceState_: OncMojo.DeviceStateProperties|null;
+  private guid_: string;
+  private managedProperties_: ManagedProperties|undefined;
+  private networkConfig_: CrosNetworkConfigRemote;
+
+  constructor() {
+    super();
+    this.networkConfig_ =
+        MojoInterfaceProviderImpl.getInstance().getMojoServiceRemote();
+  }
+
+  close(): void {
+    // If the page is already closed, return early to avoid navigating backward
+    // erroneously.
+    if (!this.guid_) {
+      return;
+    }
+
+    this.guid_ = '';
+    this.managedProperties_ = undefined;
+    this.deviceState_ = null;
+    Router.getInstance().navigateToPreviousRoute();
+  }
+
+  override currentRouteChanged(route: Route): void {
+    if (route !== routes.APN) {
+      return;
+    }
+
+    const queryParams = Router.getInstance().getQueryParameters();
+    const guid = queryParams.get('guid') || '';
+    if (!guid) {
+      console.warn('No guid specified for page:' + route);
+      Router.getInstance().navigateToPreviousRoute();
+      return;
+    }
+
+    this.guid_ = guid;
+    // Set default properties until they are loaded.
+    this.deviceState_ = null;
+    this.managedProperties_ = OncMojo.getDefaultManagedProperties(
+        NetworkType.kCellular, this.guid_,
+        OncMojo.getNetworkTypeString(NetworkType.kCellular));
+    this.getNetworkDetails_();
+  }
+
+  override onNetworkStateChanged(network: NetworkStateProperties): void {
+    if (!this.guid_ || !this.managedProperties_) {
+      return;
+    }
+    if (network.guid === this.guid_) {
+      this.getNetworkDetails_();
+    }
+  }
+
+  override onDeviceStateListChanged(): void {
+    if (!this.guid_ || !this.managedProperties_) {
+      return;
+    }
+    this.getDeviceState_();
+  }
+
+  /**
+   * Helper method that can be used by parent elements to open the APN
+   * creation dialog.
+   */
+  openApnDetailDialogInCreateMode() {
+    assert(this.guid_);
+    this.$.apnList.openApnDetailDialogInCreateMode();
+  }
+
+  private async getNetworkDetails_(): Promise<void> {
+    assert(this.guid_);
+
+    const response = await this.networkConfig_.getManagedProperties(this.guid_);
+    // Details page was closed while request was in progress, ignore the
+    // result.
+    if (!this.guid_) {
+      return;
+    }
+
+    if (!response.result) {
+      // Close the page if the network was removed and no longer exists.
+      this.close();
+      return;
+    }
+
+    if (!this.isCellular_(response.result)) {
+      // Close the page if there are no cellular properties.
+      this.close();
+      return;
+    }
+
+    if (this.deviceState_ && this.deviceState_.scanning) {
+      // Cellular properties may be invalid while scanning, so keep the
+      // existing properties instead.
+      response.result.typeProperties.cellular =
+          this.managedProperties_!.typeProperties.cellular;
+    }
+    this.managedProperties_ = response.result;
+
+    if (!this.deviceState_) {
+      this.getDeviceState_();
+    }
+  }
+
+  private async getDeviceState_(): Promise<void> {
+    if (!this.isCellular_(this.managedProperties_)) {
+      return;
+    }
+    const type = this.managedProperties_!.type;
+    const response = await this.networkConfig_.getDeviceStateList();
+    // If there is no GUID, the page was closed between requesting the device
+    // state and receiving it. If this occurs, there is no need to process the
+    // response. Note that if this subpage is reopened later, we'll request
+    // this data again.
+    if (!this.guid_) {
+      return;
+    }
+
+    const {deviceState, shouldGetNetworkDetails} =
+        processDeviceState(type, response.result, this.deviceState_);
+
+    this.deviceState_ = deviceState;
+    if (shouldGetNetworkDetails) {
+      this.getNetworkDetails_();
+    }
+  }
+
+  private isCellular_(managedProperties: ManagedProperties|undefined): boolean {
+    return !!managedProperties &&
+        managedProperties.type === NetworkType.kCellular;
+  }
+
+  private computeIsNumCustomApnsLimitReached_(): boolean {
+    return this.isCellular_(this.managedProperties_) &&
+        !!this.managedProperties_!.typeProperties.cellular!.customApnList &&
+        this.managedProperties_!.typeProperties.cellular!.customApnList
+            .length >= MAX_NUM_CUSTOM_APNS;
+  }
+}
+
+declare global {
+  interface HTMLElementTagNameMap {
+    [ApnSubpageElement.is]: ApnSubpageElement;
+  }
+}
+
+customElements.define(ApnSubpageElement.is, ApnSubpageElement);
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/internet_page.js b/chrome/browser/resources/settings/chromeos/internet_page/internet_page.js
index 45b1d4bb..cacc54de 100644
--- a/chrome/browser/resources/settings/chromeos/internet_page/internet_page.js
+++ b/chrome/browser/resources/settings/chromeos/internet_page/internet_page.js
@@ -18,10 +18,11 @@
 import 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js';
 import 'chrome://resources/polymer/v3_0/paper-tooltip/paper-tooltip.js';
 import '../../prefs/prefs.js';
+import '../../settings_shared.css.js';
 import '../os_settings_page/os_settings_animated_pages.js';
 import '../os_settings_page/os_settings_subpage.js';
-import '../../settings_shared.css.js';
 import '../os_settings_icons.css.js';
+import './apn_subpage.js';
 import './cellular_setup_dialog.js';
 import './internet_config.js';
 import './internet_detail_menu.js';
@@ -54,7 +55,6 @@
 import {RouteObserverBehavior, RouteObserverBehaviorInterface} from '../route_observer_behavior.js';
 import {Route, Router} from '../router.js';
 
-import {ApnSubpageElement} from './apn_subpage';
 import {InternetPageBrowserProxy, InternetPageBrowserProxyImpl} from './internet_page_browser_proxy.js';
 
 // TODO(crbug/1315757) The following type definitions are only needed for
@@ -93,6 +93,13 @@
 InternetConfigElement.prototype.type;
 InternetConfigElement.prototype.open = function() {};
 
+/**
+ * @constructor
+ * @extends {HTMLElement}
+ */
+function ApnSubpageElement() {}
+ApnSubpageElement.prototype.openApnDetailDialogInCreateMode = function() {};
+
 /** @type {number} */
 const ESIM_PROFILE_LIMIT = 5;
 
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/app_notifications_page/app_notifications_subpage.html b/chrome/browser/resources/settings/chromeos/os_apps_page/app_notifications_page/app_notifications_subpage.html
index 389a62e3..b49f5be 100644
--- a/chrome/browser/resources/settings/chromeos/os_apps_page/app_notifications_page/app_notifications_subpage.html
+++ b/chrome/browser/resources/settings/chromeos/os_apps_page/app_notifications_page/app_notifications_subpage.html
@@ -33,6 +33,6 @@
 <div class="cr-row" id="browserSettings">
   <localized-link class="secondary"
       localized-string="$i18n{appNotificationsLinkToBrowserSettingsDescription}"
-      link-url="$i18n{appNotificationsBrowserSettingsURL}">
+      on-link-clicked="onBrowserSettingsLinkClicked_">
   </localized-link>
 </div>
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/app_notifications_page/app_notifications_subpage.ts b/chrome/browser/resources/settings/chromeos/os_apps_page/app_notifications_page/app_notifications_subpage.ts
index 0ccb3d9..90d1c7d 100644
--- a/chrome/browser/resources/settings/chromeos/os_apps_page/app_notifications_page/app_notifications_subpage.ts
+++ b/chrome/browser/resources/settings/chromeos/os_apps_page/app_notifications_page/app_notifications_subpage.ts
@@ -203,6 +203,15 @@
   private alphabeticalSort_(first: App, second: App): number {
     return first.title!.localeCompare(second.title!);
   }
+
+  private onBrowserSettingsLinkClicked_(event: CustomEvent<{event: Event}>):
+      void {
+    // Prevent the default link click behavior.
+    event.detail.event.preventDefault();
+
+    // Programmatically open browser settings.
+    this.mojoInterfaceProvider_.openBrowserNotificationSettings();
+  }
 }
 
 declare global {
diff --git a/chrome/browser/resources/settings/chromeos/os_search_page/search_engine.ts b/chrome/browser/resources/settings/chromeos/os_search_page/search_engine.ts
index 53b6d63..2376144f 100644
--- a/chrome/browser/resources/settings/chromeos/os_search_page/search_engine.ts
+++ b/chrome/browser/resources/settings/chromeos/os_search_page/search_engine.ts
@@ -88,7 +88,7 @@
   }
 
   private onSearchEngineLinkClick_() {
-    window.open('chrome://settings/search');
+    this.browserProxy_.openBrowserSearchSettings();
   }
 
   private getBrowserSearchSettingsLink_() {
diff --git a/chrome/browser/resources/settings/chromeos/os_search_page/search_engines_browser_proxy.ts b/chrome/browser/resources/settings/chromeos/os_search_page/search_engines_browser_proxy.ts
index e9438d5..e1c0c4e 100644
--- a/chrome/browser/resources/settings/chromeos/os_search_page/search_engines_browser_proxy.ts
+++ b/chrome/browser/resources/settings/chromeos/os_search_page/search_engines_browser_proxy.ts
@@ -44,6 +44,7 @@
 export interface SearchEnginesBrowserProxy {
   setDefaultSearchEngine(modelIndex: number): void;
   getSearchEnginesList(): Promise<SearchEnginesInfo>;
+  openBrowserSearchSettings(): void;
 }
 
 let instance: SearchEnginesBrowserProxy|null = null;
@@ -65,4 +66,8 @@
   getSearchEnginesList() {
     return sendWithPromise('getSearchEnginesList');
   }
+
+  openBrowserSearchSettings() {
+    chrome.send('openBrowserSearchSettings');
+  }
 }
diff --git a/chrome/browser/resources/settings/chromeos/os_settings.gni b/chrome/browser/resources/settings/chromeos/os_settings.gni
index 4f860ce..42ad0044 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings.gni
+++ b/chrome/browser/resources/settings/chromeos/os_settings.gni
@@ -59,6 +59,7 @@
   "chromeos/guest_os/guest_os_shared_paths.ts",
   "chromeos/guest_os/guest_os_shared_usb_devices.ts",
   "chromeos/guest_os/guest_os_shared_usb_devices_add_dialog.ts",
+  "chromeos/internet_page/apn_subpage.ts",
   "chromeos/internet_page/cellular_networks_list.ts",
   "chromeos/internet_page/cellular_roaming_toggle_button.ts",
   "chromeos/internet_page/cellular_setup_dialog.ts",
@@ -374,7 +375,6 @@
 
 # Files that are generated by html_to_js() or other build rule.
 generated_web_component_files = [
-  "chromeos/internet_page/apn_subpage.js",
   "chromeos/internet_page/internet_page.js",
   "chromeos/internet_page/internet_subpage.js",
   "chromeos/multidevice_page/multidevice_combined_setup_item.js",
diff --git a/chrome/browser/resources/side_panel/bookmarks/bookmarks_api_proxy.ts b/chrome/browser/resources/side_panel/bookmarks/bookmarks_api_proxy.ts
index f663ac7..c4a9c61 100644
--- a/chrome/browser/resources/side_panel/bookmarks/bookmarks_api_proxy.ts
+++ b/chrome/browser/resources/side_panel/bookmarks/bookmarks_api_proxy.ts
@@ -13,6 +13,11 @@
   callbackRouter: {[key: string]: ChromeEvent<Function>};
   bookmarkCurrentTabInFolder(folderId: string): void;
   cutBookmark(id: string): void;
+  contextMenuOpenBookmarkInNewTab(id: string, source: ActionSource): void;
+  contextMenuOpenBookmarkInNewWindow(id: string, source: ActionSource): void;
+  contextMenuOpenBookmarkInIncognitoWindow(id: string, source: ActionSource):
+      void;
+  contextMenuDelete(id: string, source: ActionSource): void;
   copyBookmark(id: string): Promise<void>;
   createFolder(parentId: string, title: string): void;
   deleteBookmarks(ids: string[]): Promise<void>;
@@ -57,6 +62,22 @@
     chrome.bookmarkManagerPrivate.cut([id]);
   }
 
+  contextMenuOpenBookmarkInNewTab(id: string, source: ActionSource) {
+    this.handler.executeOpenInNewTabCommand(BigInt(id), source);
+  }
+
+  contextMenuOpenBookmarkInNewWindow(id: string, source: ActionSource) {
+    this.handler.executeOpenInNewWindowCommand(BigInt(id), source);
+  }
+
+  contextMenuOpenBookmarkInIncognitoWindow(id: string, source: ActionSource) {
+    this.handler.executeOpenInIncognitoWindowCommand(BigInt(id), source);
+  }
+
+  contextMenuDelete(id: string, source: ActionSource) {
+    this.handler.executeDeleteCommand(BigInt(id), source);
+  }
+
   copyBookmark(id: string) {
     return new Promise<void>(resolve => {
       chrome.bookmarkManagerPrivate.copy([id], resolve);
diff --git a/chrome/browser/resources/side_panel/bookmarks/power_bookmarks_context_menu.html b/chrome/browser/resources/side_panel/bookmarks/power_bookmarks_context_menu.html
index 68bbfeb..0ca96fc3 100644
--- a/chrome/browser/resources/side_panel/bookmarks/power_bookmarks_context_menu.html
+++ b/chrome/browser/resources/side_panel/bookmarks/power_bookmarks_context_menu.html
@@ -11,9 +11,14 @@
 </style>
 
 <cr-action-menu id="menu">
-  <template is="dom-repeat" items="[[menuItems_]]">
-    <button class="dropdown-item" on-click="onMenuItemClicked_">
-      [[item]]
-    </button>
+  <template is="dom-repeat" items="[[getMenuItemsForBookmark_(bookmark_)]]">
+    <template is="dom-if" if="[[!showDivider_(item)]]" restamp>
+      <button class="dropdown-item" on-click="onMenuItemClicked_">
+        [[item.label]]
+      </button>
+    </template>
+    <template is="dom-if" if="[[showDivider_(item)]]" restamp>
+      <hr>
+    </template>
   </template>
 </cr-action-menu>
diff --git a/chrome/browser/resources/side_panel/bookmarks/power_bookmarks_context_menu.ts b/chrome/browser/resources/side_panel/bookmarks/power_bookmarks_context_menu.ts
index 4658bee9..abb506a 100644
--- a/chrome/browser/resources/side_panel/bookmarks/power_bookmarks_context_menu.ts
+++ b/chrome/browser/resources/side_panel/bookmarks/power_bookmarks_context_menu.ts
@@ -22,6 +22,19 @@
   };
 }
 
+export enum MenuItemId {
+  OPEN_NEW_TAB = 0,
+  OPEN_NEW_WINDOW = 1,
+  OPEN_INCOGNITO = 2,
+  DELETE = 3,
+  DIVIDER = 4,
+}
+
+export interface MenuItem {
+  id: MenuItemId;
+  label?: string;
+}
+
 export class PowerBookmarksContextMenuElement extends PolymerElement {
   static get is() {
     return 'power-bookmarks-context-menu';
@@ -36,11 +49,6 @@
       bookmark_: Object,
 
       depth_: Number,
-
-      menuItems_: {
-        type: Array,
-        value: () => [loadTimeData.getString('menuOpenNewTab')],
-      },
     };
   }
 
@@ -48,7 +56,6 @@
       BookmarksApiProxyImpl.getInstance();
   private bookmark_: chrome.bookmarks.BookmarkTreeNode;
   private depth_: number;
-  private menuItems_: string[];
 
   showAt(
       event: MouseEvent, bookmark: chrome.bookmarks.BookmarkTreeNode,
@@ -66,21 +73,57 @@
     this.$.menu.showAtPosition({top: event.clientY, left: event.clientX});
   }
 
-  private onMenuItemClicked_(event: DomRepeatEvent<string>) {
+  private getMenuItemsForBookmark_(): MenuItem[] {
+    const menuItems: MenuItem[] = [
+      {
+        id: MenuItemId.OPEN_NEW_TAB,
+        label: loadTimeData.getString('menuOpenNewTab'),
+      },
+      {
+        id: MenuItemId.OPEN_NEW_WINDOW,
+        label: loadTimeData.getString('menuOpenNewWindow'),
+      },
+    ];
+
+    if (!loadTimeData.getBoolean('incognitoMode')) {
+      menuItems.push({
+        id: MenuItemId.OPEN_INCOGNITO,
+        label: loadTimeData.getString('menuOpenIncognito'),
+      });
+    }
+
+    menuItems.push(
+        {id: MenuItemId.DIVIDER},
+        {id: MenuItemId.DELETE, label: loadTimeData.getString('tooltipDelete')},
+    );
+
+    return menuItems;
+  }
+
+  private showDivider_(menuItem: MenuItem): boolean {
+    return menuItem.id === MenuItemId.DIVIDER;
+  }
+
+  private onMenuItemClicked_(event: DomRepeatEvent<MenuItem>) {
     event.preventDefault();
     event.stopPropagation();
-    switch (event.model.index) {
-      case 0:
-        // Open in new tab
-        this.bookmarksApi_.openBookmark(
-            this.bookmark_!.id, this.depth_, {
-              middleButton: true,
-              altKey: false,
-              ctrlKey: false,
-              metaKey: false,
-              shiftKey: false,
-            },
-            ActionSource.kBookmark);
+    switch (event.model.item.id) {
+      case MenuItemId.OPEN_NEW_TAB:
+        this.bookmarksApi_.contextMenuOpenBookmarkInNewTab(
+            this.bookmark_!.id, ActionSource.kBookmark);
+        break;
+      case MenuItemId.OPEN_NEW_WINDOW:
+        this.bookmarksApi_.contextMenuOpenBookmarkInNewWindow(
+            this.bookmark_!.id, ActionSource.kBookmark);
+        break;
+      case MenuItemId.OPEN_INCOGNITO:
+        this.bookmarksApi_.contextMenuOpenBookmarkInIncognitoWindow(
+            this.bookmark_!.id, ActionSource.kBookmark);
+        break;
+      case MenuItemId.DELETE:
+        this.bookmarksApi_.contextMenuDelete(
+            this.bookmark_!.id, ActionSource.kBookmark);
+        break;
     }
     this.$.menu.close();
   }
diff --git a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ChromeProvidedSharingOptionsProvider.java b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ChromeProvidedSharingOptionsProvider.java
index c2a5724d..8d26727 100644
--- a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ChromeProvidedSharingOptionsProvider.java
+++ b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ChromeProvidedSharingOptionsProvider.java
@@ -9,8 +9,6 @@
 import android.content.ClipboardManager;
 import android.content.ComponentName;
 import android.content.Context;
-import android.os.Build;
-import android.os.Build.VERSION_CODES;
 import android.view.View;
 
 import androidx.appcompat.content.res.AppCompatResources;
@@ -290,7 +288,7 @@
         mOrderedFirstPartyOptions.add(createScreenshotFirstPartyOption());
         // TODO(crbug.com/1250871): Long Screenshots on by default; supported on Android 7.0+.
         if (ChromeFeatureList.isEnabled(ChromeFeatureList.CHROME_SHARE_LONG_SCREENSHOT)
-                && mTabProvider.hasValue() && Build.VERSION.SDK_INT >= VERSION_CODES.N) {
+                && mTabProvider.hasValue()) {
             mOrderedFirstPartyOptions.add(createLongScreenshotsFirstPartyOption());
         }
         mOrderedFirstPartyOptions.add(createCopyLinkFirstPartyOption());
diff --git a/chrome/browser/share/android/javatests/src/org/chromium/chrome/browser/share/share_sheet/ChromeProvidedSharingOptionsProviderTest.java b/chrome/browser/share/android/javatests/src/org/chromium/chrome/browser/share/share_sheet/ChromeProvidedSharingOptionsProviderTest.java
index 1f251ff..d3af6c7b 100644
--- a/chrome/browser/share/android/javatests/src/org/chromium/chrome/browser/share/share_sheet/ChromeProvidedSharingOptionsProviderTest.java
+++ b/chrome/browser/share/android/javatests/src/org/chromium/chrome/browser/share/share_sheet/ChromeProvidedSharingOptionsProviderTest.java
@@ -12,7 +12,6 @@
 import static org.mockito.ArgumentMatchers.anyString;
 
 import android.app.Activity;
-import android.os.Build;
 import android.support.test.runner.lifecycle.Stage;
 import android.view.View;
 
@@ -323,10 +322,7 @@
         // Long Screenshots is supported >= Android N (7.0).
         List<String> expectedModels = new ArrayList<String>();
         expectedModels.add(mActivity.getResources().getString(R.string.sharing_screenshot));
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
-            expectedModels.add(
-                    mActivity.getResources().getString(R.string.sharing_long_screenshot));
-        }
+        expectedModels.add(mActivity.getResources().getString(R.string.sharing_long_screenshot));
         expectedModels.addAll(ImmutableList.of(
                 mActivity.getResources().getString(R.string.sharing_copy_url),
                 mActivity.getResources().getString(R.string.sharing_copy_image),
@@ -349,10 +345,7 @@
 
         List<String> expectedModels = new ArrayList<String>();
         expectedModels.add(mActivity.getResources().getString(R.string.sharing_screenshot));
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
-            expectedModels.add(
-                    mActivity.getResources().getString(R.string.sharing_long_screenshot));
-        }
+        expectedModels.add(mActivity.getResources().getString(R.string.sharing_long_screenshot));
         expectedModels.addAll(ImmutableList.of(
                 mActivity.getResources().getString(R.string.sharing_copy_image),
                 mActivity.getResources().getString(R.string.send_tab_to_self_share_activity_title),
diff --git a/chrome/browser/ssl/https_upgrades_browsertest.cc b/chrome/browser/ssl/https_upgrades_browsertest.cc
index fcf1665..e75472da 100644
--- a/chrome/browser/ssl/https_upgrades_browsertest.cc
+++ b/chrome/browser/ssl/https_upgrades_browsertest.cc
@@ -44,14 +44,33 @@
 using security_interstitials::https_only_mode::Event;
 using security_interstitials::https_only_mode::kEventHistogram;
 
+// Many of the following tests have only minor variations for HTTPS-First Mode
+// vs. HTTPS-Upgrades. These get parameterized so the tests run under both
+// versions on their own as well as when both HTTPS Upgrades and HTTPS-First
+// Mode are enabled (to test any interactions between the two upgrade modes).
+enum class HttpsUpgradesTestType {
+  kHttpsFirstModeOnly,
+  kHttpsUpgradesOnly,
+  kBoth
+};
+
 // Tests for the v2 implementation of HTTPS-First Mode.
-class HttpsUpgradesBrowserTest : public InProcessBrowserTest {
+class HttpsUpgradesBrowserTest
+    : public testing::WithParamInterface<HttpsUpgradesTestType>,
+      public InProcessBrowserTest {
  public:
   HttpsUpgradesBrowserTest() = default;
   ~HttpsUpgradesBrowserTest() override = default;
 
   void SetUp() override {
-    feature_list_.InitAndEnableFeature(features::kHttpsFirstModeV2);
+    if (GetParam() != HttpsUpgradesTestType::kHttpsFirstModeOnly) {
+      feature_list_.InitWithFeatures(
+          /*enabled_features=*/{features::kHttpsFirstModeV2,
+                                features::kHttpsUpgrades},
+          /*disabled_features=*/{});
+    } else {
+      feature_list_.InitAndEnableFeature(features::kHttpsFirstModeV2);
+    }
     InProcessBrowserTest::SetUp();
   }
 
@@ -81,7 +100,13 @@
     HttpsOnlyModeUpgradeInterceptor::SetHttpPortForTesting(
         http_server()->port());
 
-    SetPref(true);
+    // For the kHttpsUpgradesOnly test variant, don't enable the HTTPS-First
+    // Mode pref.
+    if (GetParam() == HttpsUpgradesTestType::kHttpsUpgradesOnly) {
+      SetPref(false);
+    } else {
+      SetPref(true);
+    }
   }
 
   void TearDownOnMainThread() override { SetPref(false); }
@@ -131,6 +156,13 @@
     content::NavigateToURLBlockUntilNavigationsComplete(tab, url, 2);
   }
 
+  // Whether the tests should run steps that assume the HTTP interstitial will
+  // trigger (i.e., for fallback HTTP navigations when HTTPS-First Mode is
+  // enabled).
+  bool IsHttpInterstitialEnabled() const {
+    return GetParam() != HttpsUpgradesTestType::kHttpsUpgradesOnly;
+  }
+
   net::EmbeddedTestServer* http_server() { return &http_server_; }
   net::EmbeddedTestServer* https_server() { return &https_server_; }
   base::HistogramTester* histograms() { return &histograms_; }
@@ -143,9 +175,28 @@
   base::HistogramTester histograms_;
 };
 
+INSTANTIATE_TEST_SUITE_P(
+    /* no prefix */,
+    HttpsUpgradesBrowserTest,
+    ::testing::Values(HttpsUpgradesTestType::kHttpsFirstModeOnly,
+                      HttpsUpgradesTestType::kHttpsUpgradesOnly,
+                      HttpsUpgradesTestType::kBoth),
+    // Map param to a human-readable string for better test output.
+    [](testing::TestParamInfo<HttpsUpgradesTestType> input_type)
+        -> std::string {
+      switch (input_type.param) {
+        case HttpsUpgradesTestType::kHttpsFirstModeOnly:
+          return "HttpsFirstModeOnly";
+        case HttpsUpgradesTestType::kHttpsUpgradesOnly:
+          return "HttpsUpgradesOnly";
+        case HttpsUpgradesTestType::kBoth:
+          return "BothHttpsFirstModeAndHttpsUpgrades";
+      }
+    });
+
 // If the user navigates to an HTTP URL for a site that supports HTTPS, the
 // navigation should end up on the HTTPS version of the URL.
-IN_PROC_BROWSER_TEST_F(HttpsUpgradesBrowserTest,
+IN_PROC_BROWSER_TEST_P(HttpsUpgradesBrowserTest,
                        UrlWithHttpScheme_ShouldUpgrade) {
   GURL http_url = http_server()->GetURL("foo.test", "/simple.html");
   GURL https_url = https_server()->GetURL("foo.test", "/simple.html");
@@ -169,7 +220,7 @@
 
 // If the user navigates to an HTTPS URL for a site that supports HTTPS, the
 // navigation should end up on that exact URL.
-IN_PROC_BROWSER_TEST_F(HttpsUpgradesBrowserTest,
+IN_PROC_BROWSER_TEST_P(HttpsUpgradesBrowserTest,
                        UrlWithHttpsScheme_ShouldLoad) {
   GURL https_url = https_server()->GetURL("foo.test", "/simple.html");
   auto* contents = browser()->tab_strip_model()->GetActiveWebContents();
@@ -182,7 +233,7 @@
 
 // If the user navigates to a localhost URL, the navigation should end up on
 // that exact URL.
-IN_PROC_BROWSER_TEST_F(HttpsUpgradesBrowserTest, Localhost_ShouldNotUpgrade) {
+IN_PROC_BROWSER_TEST_P(HttpsUpgradesBrowserTest, Localhost_ShouldNotUpgrade) {
   GURL localhost_url = http_server()->GetURL("localhost", "/simple.html");
   auto* contents = browser()->tab_strip_model()->GetActiveWebContents();
   EXPECT_TRUE(content::NavigateToURL(contents, localhost_url));
@@ -194,14 +245,18 @@
 
 // If the user navigates to an HTTPS URL, the navigation should end up on that
 // exact URL, even if the site has an SSL error.
-IN_PROC_BROWSER_TEST_F(HttpsUpgradesBrowserTest,
+IN_PROC_BROWSER_TEST_P(HttpsUpgradesBrowserTest,
                        UrlWithHttpsScheme_BrokenSSL_ShouldNotFallback) {
   GURL https_url = https_server()->GetURL("bad-https.test", "/simple.html");
 
   auto* contents = browser()->tab_strip_model()->GetActiveWebContents();
   EXPECT_FALSE(content::NavigateToURL(contents, https_url));
   EXPECT_EQ(https_url, contents->GetLastCommittedURL());
-  EXPECT_TRUE(chrome_browser_interstitials::IsShowingSSLInterstitial(contents));
+
+  if (IsHttpInterstitialEnabled()) {
+    EXPECT_TRUE(
+        chrome_browser_interstitials::IsShowingSSLInterstitial(contents));
+  }
 
   // Verify that navigation event metrics were not recorded as the navigation
   // was not upgraded.
@@ -211,7 +266,7 @@
 // If the user navigates to an HTTP URL for a site with broken HTTPS, the
 // navigation should end up on the HTTPS URL and show the HTTPS-Only Mode
 // interstitial.
-IN_PROC_BROWSER_TEST_F(HttpsUpgradesBrowserTest,
+IN_PROC_BROWSER_TEST_P(HttpsUpgradesBrowserTest,
                        UrlWithHttpScheme_BrokenSSL_ShouldInterstitial) {
   GURL http_url = http_server()->GetURL("bad-https.test", "/simple.html");
   GURL https_url = https_server()->GetURL("bad-https.test", "/simple.html");
@@ -220,8 +275,11 @@
   NavigateAndWaitForFallback(contents, http_url);
   EXPECT_EQ(http_url, contents->GetLastCommittedURL());
 
-  EXPECT_TRUE(chrome_browser_interstitials::IsShowingHttpsFirstModeInterstitial(
-      contents));
+  if (IsHttpInterstitialEnabled()) {
+    EXPECT_TRUE(
+        chrome_browser_interstitials::IsShowingHttpsFirstModeInterstitial(
+            contents));
+  }
 
   // Verify that navigation event metrics were correctly recorded.
   histograms()->ExpectTotalCount(kEventHistogram, 3);
@@ -232,18 +290,32 @@
 
 // If the user triggers an HTTPS-Only Mode interstitial for a host and then
 // clicks through the interstitial, they should end up on the HTTP URL.
-IN_PROC_BROWSER_TEST_F(HttpsUpgradesBrowserTest,
+IN_PROC_BROWSER_TEST_P(HttpsUpgradesBrowserTest,
                        InterstitialBypassed_HttpFallbackLoaded) {
   GURL http_url = http_server()->GetURL("bad-https.test", "/simple.html");
 
   auto* contents = browser()->tab_strip_model()->GetActiveWebContents();
   NavigateAndWaitForFallback(contents, http_url);
-  EXPECT_TRUE(chrome_browser_interstitials::IsShowingHttpsFirstModeInterstitial(
-      contents));
 
-  // Proceed through the interstitial, which will add the host to the allowlist
-  // and navigate to the HTTP fallback URL.
-  ProceedThroughInterstitial(contents);
+  if (IsHttpInterstitialEnabled()) {
+    EXPECT_TRUE(
+        chrome_browser_interstitials::IsShowingHttpsFirstModeInterstitial(
+            contents));
+
+    // Proceed through the interstitial, which will add the host to the
+    // allowlist and navigate to the HTTP fallback URL.
+    ProceedThroughInterstitial(contents);
+
+    // Verify that the interstitial metrics were correctly recorded.
+    histograms()->ExpectTotalCount("interstitial.https_first_mode.decision", 2);
+    histograms()->ExpectBucketCount(
+        "interstitial.https_first_mode.decision",
+        security_interstitials::MetricsHelper::Decision::SHOW, 1);
+    histograms()->ExpectBucketCount(
+        "interstitial.https_first_mode.decision",
+        security_interstitials::MetricsHelper::Decision::PROCEED, 1);
+  }
+
   EXPECT_EQ(http_url, contents->GetLastCommittedURL());
 
   // Verify that navigation event metrics were correctly recorded.
@@ -251,20 +323,11 @@
   histograms()->ExpectBucketCount(kEventHistogram, Event::kUpgradeAttempted, 1);
   histograms()->ExpectBucketCount(kEventHistogram, Event::kUpgradeFailed, 1);
   histograms()->ExpectBucketCount(kEventHistogram, Event::kUpgradeCertError, 1);
-
-  // Verify that the interstitial metrics were correctly recorded.
-  histograms()->ExpectTotalCount("interstitial.https_first_mode.decision", 2);
-  histograms()->ExpectBucketCount(
-      "interstitial.https_first_mode.decision",
-      security_interstitials::MetricsHelper::Decision::SHOW, 1);
-  histograms()->ExpectBucketCount(
-      "interstitial.https_first_mode.decision",
-      security_interstitials::MetricsHelper::Decision::PROCEED, 1);
 }
 
 // If the upgraded HTTPS URL is not available due to a net error, it should
 // trigger the HTTPS-Only Mode interstitial and offer fallback.
-IN_PROC_BROWSER_TEST_F(HttpsUpgradesBrowserTest,
+IN_PROC_BROWSER_TEST_P(HttpsUpgradesBrowserTest,
                        NetErrorOnUpgrade_ShouldInterstitial) {
   GURL http_url = http_server()->GetURL("foo.test", "/close-socket");
   GURL https_url = https_server()->GetURL("foo.test", "/close-socket");
@@ -273,8 +336,11 @@
   NavigateAndWaitForFallback(contents, http_url);
   EXPECT_EQ(http_url, contents->GetLastCommittedURL());
 
-  EXPECT_TRUE(chrome_browser_interstitials::IsShowingHttpsFirstModeInterstitial(
-      contents));
+  if (IsHttpInterstitialEnabled()) {
+    EXPECT_TRUE(
+        chrome_browser_interstitials::IsShowingHttpsFirstModeInterstitial(
+            contents));
+  }
 
   // Verify that navigation event metrics were correctly recorded.
   histograms()->ExpectTotalCount(kEventHistogram, 3);
@@ -285,7 +351,7 @@
 
 // Navigations in subframes should not get upgraded by HTTPS-Only Mode. They
 // should be blocked as mixed content.
-IN_PROC_BROWSER_TEST_F(HttpsUpgradesBrowserTest,
+IN_PROC_BROWSER_TEST_P(HttpsUpgradesBrowserTest,
                        HttpsParentHttpSubframeNavigation_Blocked) {
   const GURL parent_url(
       https_server()->GetURL("foo.test", "/iframe_blank.html"));
@@ -306,7 +372,7 @@
 // Navigating to an HTTP URL in a subframe of an HTTP page should not upgrade
 // the subframe navigation to HTTPS (even if the subframe navigation is to a
 // different host than the parent frame).
-IN_PROC_BROWSER_TEST_F(HttpsUpgradesBrowserTest,
+IN_PROC_BROWSER_TEST_P(HttpsUpgradesBrowserTest,
                        HttpParentHttpSubframeNavigation_NotUpgraded) {
   // The parent frame will fail to upgrade to HTTPS.
   const GURL parent_url(
@@ -317,10 +383,14 @@
   auto* contents = browser()->tab_strip_model()->GetActiveWebContents();
   NavigateAndWaitForFallback(contents, parent_url);
 
-  EXPECT_TRUE(chrome_browser_interstitials::IsShowingHttpsFirstModeInterstitial(
-      contents));
-  // Proceeding through the interstitial will add the hostname to the allowlist.
-  ProceedThroughInterstitial(contents);
+  if (IsHttpInterstitialEnabled()) {
+    EXPECT_TRUE(
+        chrome_browser_interstitials::IsShowingHttpsFirstModeInterstitial(
+            contents));
+    // Proceeding through the interstitial will add the hostname to the
+    // allowlist.
+    ProceedThroughInterstitial(contents);
+  }
 
   // Verify that navigation event metrics were recorded for the main frame.
   histograms()->ExpectTotalCount(kEventHistogram, 3);
@@ -339,23 +409,38 @@
 // Tests that a navigation to the HTTP version of a site with an HTTPS version
 // that is slow to respond gets upgraded to HTTPS but times out and shows the
 // HTTPS-Only Mode interstitial.
-IN_PROC_BROWSER_TEST_F(HttpsUpgradesBrowserTest, SlowHttps_ShouldInterstitial) {
+IN_PROC_BROWSER_TEST_P(HttpsUpgradesBrowserTest, SlowHttps_ShouldInterstitial) {
   // Set timeout to zero so that HTTPS upgrades immediately timeout.
   HttpsOnlyModeNavigationThrottle::set_timeout_for_testing(0);
 
-  const GURL http_url = http_server()->GetURL("foo.test", "/hung");
+  // Set up a custom HTTPS server that times out without sending a response.
+  net::EmbeddedTestServer timeout_server{net::EmbeddedTestServer::TYPE_HTTPS};
+  timeout_server.RegisterRequestHandler(base::BindLambdaForTesting(
+      [&](const net::test_server::HttpRequest& request)
+          -> std::unique_ptr<net::test_server::HttpResponse> {
+        // Server will hang until destroyed.
+        return std::make_unique<net::test_server::HungResponse>();
+      }));
+  ASSERT_TRUE(timeout_server.Start());
+  HttpsOnlyModeUpgradeInterceptor::SetHttpsPortForTesting(
+      timeout_server.port());
+
+  const GURL http_url = http_server()->GetURL("foo.test", "/simple.html");
   auto* contents = browser()->tab_strip_model()->GetActiveWebContents();
   NavigateAndWaitForFallback(contents, http_url);
 
-  EXPECT_TRUE(chrome_browser_interstitials::IsShowingHttpsFirstModeInterstitial(
-      contents));
+  if (IsHttpInterstitialEnabled()) {
+    EXPECT_TRUE(
+        chrome_browser_interstitials::IsShowingHttpsFirstModeInterstitial(
+            contents));
+  }
   EXPECT_EQ(http_url, contents->GetLastCommittedURL());
 }
 
 // Tests that an HTTP POST form navigation to "bar.test" from an HTTP page on
 // "foo.test" is not upgraded to HTTPS. (HTTP form navigations from HTTPS are
 // blocked by the Mixed Forms warning.)
-IN_PROC_BROWSER_TEST_F(HttpsUpgradesBrowserTest, HttpPageHttpPost_NotUpgraded) {
+IN_PROC_BROWSER_TEST_P(HttpsUpgradesBrowserTest, HttpPageHttpPost_NotUpgraded) {
   // Point the HTTP form target to "bar.test".
   base::StringPairs replacement_text;
   replacement_text.emplace_back(make_pair(
@@ -365,16 +450,19 @@
   auto replacement_path = net::test_server::GetFilePathWithReplacements(
       "/ssl/page_with_form_targeting_http_url.html", replacement_text);
 
-  // Navigate to the page hosting the form on "foo.test". The HTTPS-Only Mode
-  // interstitial should trigger.
+  // Navigate to the page hosting the form on "foo.test".
   auto* contents = browser()->tab_strip_model()->GetActiveWebContents();
   content::NavigateToURLBlockUntilNavigationsComplete(
       contents, http_server()->GetURL("bad-https.test", replacement_path), 2);
-  EXPECT_TRUE(chrome_browser_interstitials::IsShowingHttpsFirstModeInterstitial(
-      contents));
 
-  // Proceed through the interstitial to add the hostname to the allowlist.
-  ProceedThroughInterstitial(contents);
+  if (IsHttpInterstitialEnabled()) {
+    // The HTTPS-Only Mode interstitial should trigger.
+    EXPECT_TRUE(
+        chrome_browser_interstitials::IsShowingHttpsFirstModeInterstitial(
+            contents));
+    // Proceed through the interstitial to add the hostname to the allowlist.
+    ProceedThroughInterstitial(contents);
+  }
 
   // Verify that navigation event metrics were recorded for the initial page.
   histograms()->ExpectTotalCount(kEventHistogram, 3);
@@ -397,7 +485,7 @@
 // Tests that if an HTTPS navigation redirects to HTTP on a different host, it
 // should upgrade to HTTPS on that new host. (A downgrade redirect on the same
 // host would imply a redirect loop.)
-IN_PROC_BROWSER_TEST_F(HttpsUpgradesBrowserTest,
+IN_PROC_BROWSER_TEST_P(HttpsUpgradesBrowserTest,
                        HttpsToHttpRedirect_ShouldUpgrade) {
   GURL target_url = http_server()->GetURL("bar.test", "/title1.html");
   GURL url = https_server()->GetURL("foo.test",
@@ -425,7 +513,7 @@
 // Tests that navigating to an HTTPS page that downgrades to HTTP on the same
 // host will fail and trigger the HTTPS-Only Mode interstitial (due to the
 // redirect loop hitting the redirect limit).
-IN_PROC_BROWSER_TEST_F(HttpsUpgradesBrowserTest,
+IN_PROC_BROWSER_TEST_P(HttpsUpgradesBrowserTest,
                        RedirectLoop_ShouldInterstitial) {
   // Set up a new test server instance so it can have a custom handler.
   net::EmbeddedTestServer downgrading_server{
@@ -456,8 +544,12 @@
   GURL url = downgrading_server.GetURL("foo.test", "/");
   auto* contents = browser()->tab_strip_model()->GetActiveWebContents();
   NavigateAndWaitForFallback(contents, url);
-  EXPECT_TRUE(chrome_browser_interstitials::IsShowingHttpsFirstModeInterstitial(
-      contents));
+
+  if (IsHttpInterstitialEnabled()) {
+    EXPECT_TRUE(
+        chrome_browser_interstitials::IsShowingHttpsFirstModeInterstitial(
+            contents));
+  }
 
   // Verify that navigation event metrics were correctly recorded.
   histograms()->ExpectTotalCount(kEventHistogram, 3);
@@ -469,25 +561,29 @@
 // Tests that the security level is WARNING when the HTTPS-Only Mode
 // interstitial is shown for a net error on HTTPS. (Without HTTPS-Only Mode, a
 // net error would be a security level of NONE.)
-IN_PROC_BROWSER_TEST_F(HttpsUpgradesBrowserTest,
+IN_PROC_BROWSER_TEST_P(HttpsUpgradesBrowserTest,
                        NetErrorOnUpgrade_SecurityLevelWarning) {
   GURL http_url = http_server()->GetURL("foo.test", "/close-socket");
   GURL https_url = https_server()->GetURL("foo.test", "/close-socket");
 
   auto* contents = browser()->tab_strip_model()->GetActiveWebContents();
+  auto* helper = SecurityStateTabHelper::FromWebContents(contents);
   NavigateAndWaitForFallback(contents, http_url);
   EXPECT_EQ(http_url, contents->GetLastCommittedURL());
 
-  EXPECT_TRUE(chrome_browser_interstitials::IsShowingHttpsFirstModeInterstitial(
-      contents));
+  if (IsHttpInterstitialEnabled()) {
+    EXPECT_TRUE(
+        chrome_browser_interstitials::IsShowingHttpsFirstModeInterstitial(
+            contents));
 
-  auto* helper = SecurityStateTabHelper::FromWebContents(contents);
-  EXPECT_EQ(security_state::WARNING, helper->GetSecurityLevel());
+    EXPECT_EQ(security_state::WARNING, helper->GetSecurityLevel());
 
-  // Proceed through the interstitial to navigate to the HTTP site. The HTTP
-  // site results in a net error, which should have security level NONE (as no
-  // connection was made).
-  ProceedThroughInterstitial(contents);
+    // Proceed through the interstitial to navigate to the HTTP site.
+    ProceedThroughInterstitial(contents);
+  }
+
+  // The HTTP site results in a net error, which should have security level NONE
+  // (as no connection was made).
   EXPECT_EQ(security_state::NONE, helper->GetSecurityLevel());
 }
 
@@ -495,24 +591,28 @@
 // interstitial is shown for a cert error on HTTPS. (Without HTTPS-Only Mode, a
 // a cert error would be a security level of DANGEROUS.) After clicking through
 // the interstitial, the security level should still be WARNING.
-IN_PROC_BROWSER_TEST_F(HttpsUpgradesBrowserTest,
+IN_PROC_BROWSER_TEST_P(HttpsUpgradesBrowserTest,
                        BrokenSSLOnUpgrade_SecurityLevelWarning) {
   GURL http_url = http_server()->GetURL("bad-https.test", "/simple.html");
   GURL https_url = https_server()->GetURL("bad-https.test", "/simple.html");
 
   auto* contents = browser()->tab_strip_model()->GetActiveWebContents();
+  auto* helper = SecurityStateTabHelper::FromWebContents(contents);
   NavigateAndWaitForFallback(contents, http_url);
   EXPECT_EQ(http_url, contents->GetLastCommittedURL());
 
-  EXPECT_TRUE(chrome_browser_interstitials::IsShowingHttpsFirstModeInterstitial(
-      contents));
+  if (IsHttpInterstitialEnabled()) {
+    EXPECT_TRUE(
+        chrome_browser_interstitials::IsShowingHttpsFirstModeInterstitial(
+            contents));
 
-  auto* helper = SecurityStateTabHelper::FromWebContents(contents);
-  EXPECT_EQ(security_state::WARNING, helper->GetSecurityLevel());
+    EXPECT_EQ(security_state::WARNING, helper->GetSecurityLevel());
 
-  // Proceed through the interstitial to navigate to the HTTP page. The security
-  // level should still be WARNING.
-  ProceedThroughInterstitial(contents);
+    // Proceed through the interstitial to navigate to the HTTP page.
+    ProceedThroughInterstitial(contents);
+  }
+
+  // The security level should still be WARNING.
   EXPECT_EQ(security_state::WARNING, helper->GetSecurityLevel());
 }
 
@@ -526,7 +626,7 @@
 // be shown and the user should be able to click through the SSL interstitial to
 // visit the HTTPS version of the site (but in a DANGEROUS security level
 // state).
-IN_PROC_BROWSER_TEST_F(HttpsUpgradesBrowserTest,
+IN_PROC_BROWSER_TEST_P(HttpsUpgradesBrowserTest,
                        HttpsUpgradeWithBrokenSSL_ShouldTriggerSSLInterstitial) {
   // Set up a new test server instance so it can have a custom handler that
   // redirects to the HTTPS server.
@@ -554,16 +654,20 @@
 
   auto* contents = browser()->tab_strip_model()->GetActiveWebContents();
   NavigateAndWaitForFallback(contents, http_url);
-  EXPECT_EQ(http_url, contents->GetLastCommittedURL());
 
-  // The HTTPS-First Mode interstitial should trigger first.
-  EXPECT_TRUE(chrome_browser_interstitials::IsShowingHttpsFirstModeInterstitial(
-      contents));
+  if (IsHttpInterstitialEnabled()) {
+    // The HTTPS-First Mode interstitial should trigger first.
+    EXPECT_TRUE(
+        chrome_browser_interstitials::IsShowingHttpsFirstModeInterstitial(
+            contents));
 
-  // Proceeding through the HTTPS-First Mode interstitial will hit the upgrading
-  // server's HTTP->HTTPS redirect. This should result in an SSL interstitial
-  // (not an HTTPS-First Mode interstitial).
-  ProceedThroughInterstitial(contents);
+    // Proceeding through the HTTPS-First Mode interstitial will hit the
+    // upgrading server's HTTP->HTTPS redirect. This should result in an SSL
+    // interstitial (not an HTTPS-First Mode interstitial).
+    ProceedThroughInterstitial(contents);
+  }
+
+  EXPECT_EQ(https_url, contents->GetLastCommittedURL());
   EXPECT_TRUE(chrome_browser_interstitials::IsShowingSSLInterstitial(contents));
 
   // Proceeding through the SSL interstitial should navigate to the HTTPS
@@ -581,18 +685,25 @@
   histograms()->ExpectBucketCount(kEventHistogram, Event::kUpgradeFailed, 1);
   histograms()->ExpectBucketCount(kEventHistogram, Event::kUpgradeCertError, 1);
 
-  // Verify that the interstitial metrics were correctly recorded.
-  histograms()->ExpectBucketCount(
-      "interstitial.https_first_mode.decision",
-      security_interstitials::MetricsHelper::Decision::SHOW, 1);
-  histograms()->ExpectBucketCount(
-      "interstitial.https_first_mode.decision",
-      security_interstitials::MetricsHelper::Decision::PROCEED, 1);
+  if (IsHttpInterstitialEnabled()) {
+    // Verify that the interstitial metrics were correctly recorded.
+    histograms()->ExpectBucketCount(
+        "interstitial.https_first_mode.decision",
+        security_interstitials::MetricsHelper::Decision::SHOW, 1);
+    histograms()->ExpectBucketCount(
+        "interstitial.https_first_mode.decision",
+        security_interstitials::MetricsHelper::Decision::PROCEED, 1);
+  }
 }
 
 // Tests that clicking the "Learn More" link in the HTTPS-First Mode
 // interstitial opens a new tab for the help center article.
-IN_PROC_BROWSER_TEST_F(HttpsUpgradesBrowserTest, InterstitialLearnMoreLink) {
+IN_PROC_BROWSER_TEST_P(HttpsUpgradesBrowserTest, InterstitialLearnMoreLink) {
+  // This test is only relevant to HTTPS-First Mode.
+  if (!IsHttpInterstitialEnabled()) {
+    return;
+  }
+
   GURL http_url = http_server()->GetURL("foo.test", "/close-socket");
   GURL https_url = https_server()->GetURL("foo.test", "/close-socket");
 
@@ -631,7 +742,13 @@
 // later the server fixes their HTTPS support and the user successfully connects
 // over HTTPS, the allowlist entry is cleared (so HFM will kick in again for
 // that site).
-IN_PROC_BROWSER_TEST_F(HttpsUpgradesBrowserTest, BadHttpsFollowedByGoodHttps) {
+IN_PROC_BROWSER_TEST_P(HttpsUpgradesBrowserTest, BadHttpsFollowedByGoodHttps) {
+  // TODO(crbug.com/1394910): This test is flakey when only HTTPS Upgrades are
+  // enabled.
+  if (!IsHttpInterstitialEnabled()) {
+    return;
+  }
+
   GURL http_url = http_server()->GetURL("foo.test", "/close-socket");
   GURL bad_https_url = https_server()->GetURL("foo.test", "/close-socket");
   GURL good_https_url = https_server()->GetURL("foo.test", "/ssl/google.html");
@@ -649,9 +766,12 @@
   // Navigate to `http_url`, which will get upgraded to `bad_https_url`.
   NavigateAndWaitForFallback(tab, http_url);
 
-  ASSERT_TRUE(
-      chrome_browser_interstitials::IsShowingHttpsFirstModeInterstitial(tab));
-  ProceedThroughInterstitial(tab);
+  if (IsHttpInterstitialEnabled()) {
+    ASSERT_TRUE(
+        chrome_browser_interstitials::IsShowingHttpsFirstModeInterstitial(tab));
+    ProceedThroughInterstitial(tab);
+  }
+
   EXPECT_TRUE(state->HasAllowException(
       http_url.host(), tab->GetPrimaryMainFrame()->GetStoragePartition()));
 
@@ -673,9 +793,12 @@
   // Navigate to `http_url`, which will get upgraded to `bad_https_url`.
   NavigateAndWaitForFallback(tab, http_url);
 
-  ASSERT_TRUE(
-      chrome_browser_interstitials::IsShowingHttpsFirstModeInterstitial(tab));
-  ProceedThroughInterstitial(tab);
+  if (IsHttpInterstitialEnabled()) {
+    ASSERT_TRUE(
+        chrome_browser_interstitials::IsShowingHttpsFirstModeInterstitial(tab));
+    ProceedThroughInterstitial(tab);
+  }
+
   EXPECT_TRUE(state->HasAllowException(
       http_url.host(), tab->GetPrimaryMainFrame()->GetStoragePartition()));
 
@@ -698,7 +821,12 @@
 
 // Tests that clicking the "Go back" button in the HTTPS-First Mode interstitial
 // navigates back to the previous page (about:blank in this case).
-IN_PROC_BROWSER_TEST_F(HttpsUpgradesBrowserTest, InterstitialGoBack) {
+IN_PROC_BROWSER_TEST_P(HttpsUpgradesBrowserTest, InterstitialGoBack) {
+  // This test is only relevant to HTTPS-First Mode.
+  if (!IsHttpInterstitialEnabled()) {
+    return;
+  }
+
   GURL http_url = http_server()->GetURL("foo.test", "/close-socket");
   GURL https_url = https_server()->GetURL("foo.test", "/close-socket");
 
@@ -725,7 +853,12 @@
 
 // Tests that closing the tab of the HTTPS-First Mode interstitial counts as
 // not proceeding through the interstitial for metrics.
-IN_PROC_BROWSER_TEST_F(HttpsUpgradesBrowserTest, CloseInterstitialTab) {
+IN_PROC_BROWSER_TEST_P(HttpsUpgradesBrowserTest, CloseInterstitialTab) {
+  // This test is only relevant to HTTPS-First Mode.
+  if (!IsHttpInterstitialEnabled()) {
+    return;
+  }
+
   GURL http_url = http_server()->GetURL("foo.test", "/close-socket");
   GURL https_url = https_server()->GetURL("foo.test", "/close-socket");
 
@@ -751,7 +884,7 @@
 // Tests that if a user allowlists a host and then does not visit it again for
 // seven days (the expiration period), then the interstitial will be shown again
 // the next time they visit the host.
-IN_PROC_BROWSER_TEST_F(HttpsUpgradesBrowserTest, AllowlistEntryExpires) {
+IN_PROC_BROWSER_TEST_P(HttpsUpgradesBrowserTest, AllowlistEntryExpires) {
   content::WebContents* contents =
       browser()->tab_strip_model()->GetActiveWebContents();
   Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext());
@@ -773,9 +906,14 @@
   // through the HTTPS-First Mode interstitial to allowlist the host.
   GURL http_url = http_server()->GetURL("bad-https.test", "/simple.html");
   NavigateAndWaitForFallback(contents, http_url);
-  EXPECT_TRUE(chrome_browser_interstitials::IsShowingHttpsFirstModeInterstitial(
-      contents));
-  ProceedThroughInterstitial(contents);
+
+  if (IsHttpInterstitialEnabled()) {
+    EXPECT_TRUE(
+        chrome_browser_interstitials::IsShowingHttpsFirstModeInterstitial(
+            contents));
+    ProceedThroughInterstitial(contents);
+  }
+
   EXPECT_EQ(http_url, contents->GetLastCommittedURL());
   EXPECT_TRUE(state->IsHttpAllowedForHost(
       http_url.host(), contents->GetPrimaryMainFrame()->GetStoragePartition()));
@@ -789,13 +927,17 @@
   EXPECT_FALSE(state->IsHttpAllowedForHost(
       http_url.host(), contents->GetPrimaryMainFrame()->GetStoragePartition()));
   NavigateAndWaitForFallback(contents, http_url);
-  EXPECT_TRUE(chrome_browser_interstitials::IsShowingHttpsFirstModeInterstitial(
-      contents));
+
+  if (IsHttpInterstitialEnabled()) {
+    EXPECT_TRUE(
+        chrome_browser_interstitials::IsShowingHttpsFirstModeInterstitial(
+            contents));
+  }
 }
 
 // Tests that re-visiting an allowlisted host bumps the expiration time to a new
 // seven days in the future from now.
-IN_PROC_BROWSER_TEST_F(HttpsUpgradesBrowserTest, RevisitingBumpsExpiration) {
+IN_PROC_BROWSER_TEST_P(HttpsUpgradesBrowserTest, RevisitingBumpsExpiration) {
   content::WebContents* contents =
       browser()->tab_strip_model()->GetActiveWebContents();
   Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext());
@@ -817,9 +959,14 @@
   // through the HTTPS-First Mode interstitial to allowlist the host.
   GURL http_url = http_server()->GetURL("bad-https.test", "/simple.html");
   NavigateAndWaitForFallback(contents, http_url);
-  EXPECT_TRUE(chrome_browser_interstitials::IsShowingHttpsFirstModeInterstitial(
-      contents));
-  ProceedThroughInterstitial(contents);
+
+  if (IsHttpInterstitialEnabled()) {
+    EXPECT_TRUE(
+        chrome_browser_interstitials::IsShowingHttpsFirstModeInterstitial(
+            contents));
+    ProceedThroughInterstitial(contents);
+  }
+
   EXPECT_EQ(http_url, contents->GetLastCommittedURL());
   EXPECT_TRUE(state->IsHttpAllowedForHost(
       http_url.host(), contents->GetPrimaryMainFrame()->GetStoragePartition()));
@@ -846,7 +993,7 @@
 // Tests that if a hostname has an HSTS entry registered, then HTTPS-First Mode
 // should not try to upgrade it (instead allowing HSTS to handle the upgrade as
 // it is more strict).
-IN_PROC_BROWSER_TEST_F(HttpsUpgradesBrowserTest, PreferHstsOverHttpsFirstMode) {
+IN_PROC_BROWSER_TEST_P(HttpsUpgradesBrowserTest, PreferHstsOverHttpsFirstMode) {
   content::WebContents* contents =
       browser()->tab_strip_model()->GetActiveWebContents();
   Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext());
@@ -893,6 +1040,11 @@
 // linux-bfcache-rel bots).
 IN_PROC_BROWSER_TEST_F(HttpsUpgradesBrowserTest,
                        DISABLED_InterstitialFallbackMaintainsHistory) {
+  // This test only applies to HTTPS-First Mode.
+  if (!IsHttpInterstitialEnabled()) {
+    return;
+  }
+
   GURL good_https_url =
       https_server()->GetURL("site1.test", "/defaultresponse");
 
@@ -950,8 +1102,8 @@
   auto* helper = SecurityStateTabHelper::FromWebContents(contents);
   EXPECT_EQ(security_state::WARNING, helper->GetSecurityLevel());
 
-  // Simulate clicking the browser "forward" button. (The HistoryGoForward()
-  // call returns `false` because it is an error page.)
+  // Simulate clicking the browser "forward" button. The HistoryGoForward()
+  // call returns `false` because it is an error page.
   EXPECT_FALSE(content::HistoryGoForward(contents));
   EXPECT_EQ(downgrading_http_url, contents->GetLastCommittedURL());
   EXPECT_TRUE(chrome_browser_interstitials::IsShowingHttpsFirstModeInterstitial(
@@ -967,8 +1119,7 @@
 
   // Repeat forward one last time. (Previously the user would no longer be able
   // to go back any more as the history entries were lost.)
-  // The HistoryGoForward() call returns `false` because it is an error page.
-  EXPECT_FALSE(content::HistoryGoForward(contents));
+  EXPECT_FALSE(content::HistoryGoForward(contents));  // error page -> false
   EXPECT_EQ(downgrading_http_url, contents->GetLastCommittedURL());
   EXPECT_TRUE(chrome_browser_interstitials::IsShowingHttpsFirstModeInterstitial(
       contents));
diff --git a/chrome/browser/ssl/https_upgrades_interceptor.cc b/chrome/browser/ssl/https_upgrades_interceptor.cc
index 955bb9d..a22761e 100644
--- a/chrome/browser/ssl/https_upgrades_interceptor.cc
+++ b/chrome/browser/ssl/https_upgrades_interceptor.cc
@@ -11,6 +11,7 @@
 #include "chrome/browser/ssl/https_only_mode_tab_helper.h"
 #include "chrome/browser/ssl/https_only_mode_upgrade_url_loader.h"
 #include "chrome/browser/ssl/stateful_ssl_host_state_delegate_factory.h"
+#include "chrome/common/chrome_features.h"
 #include "chrome/common/pref_names.h"
 #include "components/prefs/pref_service.h"
 #include "components/security_interstitials/content/stateful_ssl_host_state_delegate.h"
@@ -39,6 +40,8 @@
 // Only serve upgrade redirects for main frame, GET requests to HTTP URLs. This
 // excludes "localhost" (and loopback addresses) as they do not expose traffic
 // over the network.
+// TODO(crbug.com/1394910): Extend the exemption list for HTTPS-Upgrades
+// beyond just localhost.
 bool ShouldCreateLoader(const network::ResourceRequest& resource_request,
                         HttpsOnlyModeTabHelper* tab_helper) {
   if (resource_request.is_outermost_main_frame &&
@@ -72,9 +75,18 @@
     return;
   }
 
-  // Don't upgrade if the HTTPS-First Mode setting isn't enabled.
+  // TODO(crbug.com/1394910): Check for HttpsUpgrades and HttpsAllowlist
+  // enterprise policies as well. It might be best to consolidate these checks
+  // into the HttpsUpgradesNavigationThrottle which sees the navigation first.
   auto* prefs = profile->GetPrefs();
-  if (!prefs || !prefs->GetBoolean(prefs::kHttpsOnlyModeEnabled)) {
+  bool https_first_mode_enabled =
+      base::FeatureList::IsEnabled(features::kHttpsFirstModeV2) && prefs &&
+      prefs->GetBoolean(prefs::kHttpsOnlyModeEnabled);
+  bool https_upgrades_enabled =
+      base::FeatureList::IsEnabled(features::kHttpsUpgrades) ||
+      https_first_mode_enabled;
+  if (!https_upgrades_enabled) {
+    // Don't upgrade the request and let the default loader continue.
     std::move(callback).Run({});
     return;
   }
@@ -103,6 +115,9 @@
   }
 
   // Don't upgrade navigation if it is allowlisted.
+  // TODO(crbug.com/1394910): Distinguish HTTPS-First Mode and HTTPS-Upgrades
+  // allowlist entries, and ensure that HTTPS-Upgrades allowlist entries don't
+  // downgrade Page Info.
   StatefulSSLHostStateDelegate* state =
       static_cast<StatefulSSLHostStateDelegate*>(
           profile->GetSSLHostStateDelegate());
diff --git a/chrome/browser/ssl/https_upgrades_navigation_throttle.cc b/chrome/browser/ssl/https_upgrades_navigation_throttle.cc
index a425532e..da37c67 100644
--- a/chrome/browser/ssl/https_upgrades_navigation_throttle.cc
+++ b/chrome/browser/ssl/https_upgrades_navigation_throttle.cc
@@ -5,14 +5,18 @@
 #include "chrome/browser/ssl/https_upgrades_navigation_throttle.h"
 
 #include "base/feature_list.h"
+#include "base/memory/weak_ptr.h"
 #include "base/metrics/histogram_functions.h"
+#include "base/task/sequenced_task_runner.h"
 #include "base/time/time.h"
+#include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ssl/https_only_mode_tab_helper.h"
 #include "chrome/browser/ssl/https_upgrades_navigation_throttle.h"
 #include "chrome/common/chrome_features.h"
 #include "chrome/common/pref_names.h"
 #include "components/prefs/pref_service.h"
 #include "components/security_interstitials/content/security_interstitial_tab_helper.h"
+#include "components/security_interstitials/content/stateful_ssl_host_state_delegate.h"
 #include "components/security_interstitials/core/https_only_mode_metrics.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/navigation_handle.h"
@@ -29,6 +33,8 @@
 base::TimeDelta g_fallback_delay = base::Seconds(3);
 
 // Helper to record an HTTPS-First Mode navigation event.
+// TODO(crbug.com/1394910): Rename these metrics now that they apply to both
+// HTTPS-First Mode and HTTPS Upgrades.
 void RecordHttpsFirstModeNavigation(
     security_interstitials::https_only_mode::Event event) {
   base::UmaHistogramEnumeration(
@@ -52,8 +58,13 @@
     return nullptr;
   }
 
-  if (!base::FeatureList::IsEnabled(features::kHttpsOnlyMode) || !prefs ||
-      !prefs->GetBoolean(prefs::kHttpsOnlyModeEnabled)) {
+  bool https_first_mode_enabled =
+      base::FeatureList::IsEnabled(features::kHttpsFirstModeV2) && prefs &&
+      prefs->GetBoolean(prefs::kHttpsOnlyModeEnabled);
+  bool https_upgrades_enabled =
+      https_first_mode_enabled ||
+      base::FeatureList::IsEnabled(features::kHttpsUpgrades);
+  if (!https_upgrades_enabled) {
     return nullptr;
   }
 
@@ -65,25 +76,28 @@
   HttpsOnlyModeTabHelper::CreateForWebContents(handle->GetWebContents());
 
   return std::make_unique<HttpsUpgradesNavigationThrottle>(
-      handle, std::move(blocking_page_factory));
+      handle, std::move(blocking_page_factory), https_first_mode_enabled);
 }
 
 HttpsUpgradesNavigationThrottle::HttpsUpgradesNavigationThrottle(
     content::NavigationHandle* handle,
-    std::unique_ptr<SecurityBlockingPageFactory> blocking_page_factory)
+    std::unique_ptr<SecurityBlockingPageFactory> blocking_page_factory,
+    bool http_interstitial_enabled)
     : content::NavigationThrottle(handle),
-      blocking_page_factory_(std::move(blocking_page_factory)) {}
+      blocking_page_factory_(std::move(blocking_page_factory)),
+      http_interstitial_enabled_(http_interstitial_enabled) {}
 
 HttpsUpgradesNavigationThrottle::~HttpsUpgradesNavigationThrottle() = default;
 
 content::NavigationThrottle::ThrottleCheckResult
 HttpsUpgradesNavigationThrottle::WillStartRequest() {
-  // If the navigation is fallback to HTTP, trigger the interstitial.
+  // If the navigation is fallback to HTTP, trigger the HTTP interstitial (if
+  // enabled).
   auto* handle = navigation_handle();
   auto* contents = handle->GetWebContents();
   auto* tab_helper = HttpsOnlyModeTabHelper::FromWebContents(contents);
   if (tab_helper->is_navigation_fallback() &&
-      !handle->GetURL().SchemeIsCryptographic()) {
+      !handle->GetURL().SchemeIsCryptographic() && http_interstitial_enabled_) {
     std::unique_ptr<security_interstitials::HttpsOnlyModeBlockingPage>
         blocking_page = blocking_page_factory_->CreateHttpsOnlyModeBlockingPage(
             contents, handle->GetURL());
@@ -114,7 +128,7 @@
     return content::NavigationThrottle::PROCEED;
   }
 
-  // Only show the interstitial if the Interceptor attempted to upgrade the
+  // Only fallback to HTTP if the Interceptor attempted to upgrade the
   // navigation.
   auto* contents = handle->GetWebContents();
   auto* tab_helper = HttpsOnlyModeTabHelper::FromWebContents(contents);
@@ -129,6 +143,25 @@
       RecordHttpsFirstModeNavigation(Event::kUpgradeNetError);
     }
 
+    // If HTTPS-First Mode is not enabled (so no interstitial will be shown),
+    // add the hostname to the allowlist now before triggering fallback.
+    // HTTPS-First Mode handles this on the user proceeding through the
+    // interstitial only.
+    if (!http_interstitial_enabled_) {
+      Profile* profile =
+          Profile::FromBrowserContext(contents->GetBrowserContext());
+      StatefulSSLHostStateDelegate* state =
+          static_cast<StatefulSSLHostStateDelegate*>(
+              profile->GetSSLHostStateDelegate());
+      // StatefulSSLHostStateDelegate can be null during tests.
+      if (state) {
+        state->AllowHttpForHost(
+            handle->GetURL().host(),
+            contents->GetPrimaryMainFrame()->GetStoragePartition());
+      }
+      tab_helper->set_is_navigation_upgraded(false);
+    }
+
     // Mark the navigation as fallback and trigger a new navigation to the
     // fallback URL.
     tab_helper->set_is_navigation_fallback(true);
diff --git a/chrome/browser/ssl/https_upgrades_navigation_throttle.h b/chrome/browser/ssl/https_upgrades_navigation_throttle.h
index e31f9f64..802e2e5 100644
--- a/chrome/browser/ssl/https_upgrades_navigation_throttle.h
+++ b/chrome/browser/ssl/https_upgrades_navigation_throttle.h
@@ -33,7 +33,8 @@
 
   HttpsUpgradesNavigationThrottle(
       content::NavigationHandle* handle,
-      std::unique_ptr<SecurityBlockingPageFactory> blocking_page_factory);
+      std::unique_ptr<SecurityBlockingPageFactory> blocking_page_factory,
+      bool http_interstitial_enabled);
   ~HttpsUpgradesNavigationThrottle() override;
 
   HttpsUpgradesNavigationThrottle(const HttpsUpgradesNavigationThrottle&) =
@@ -54,6 +55,10 @@
 
  private:
   std::unique_ptr<SecurityBlockingPageFactory> blocking_page_factory_;
+
+  // Whether the throttle should trigger the interstitial warning before
+  // navigating to the HTTP fallback URL.
+  bool http_interstitial_enabled_;
 };
 
 #endif  // CHROME_BROWSER_SSL_HTTPS_UPGRADES_NAVIGATION_THROTTLE_H_
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index 46aec62..f757adba 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -1993,6 +1993,8 @@
       "../ash/app_list/arc/arc_app_list_prefs_factory.h",
       "../ash/app_list/arc/arc_app_scoped_pref_update.cc",
       "../ash/app_list/arc/arc_app_scoped_pref_update.h",
+      "../ash/app_list/arc/arc_app_sync_metrics_helper.cc",
+      "../ash/app_list/arc/arc_app_sync_metrics_helper.h",
       "../ash/app_list/arc/arc_app_utils.cc",
       "../ash/app_list/arc/arc_app_utils.h",
       "../ash/app_list/arc/arc_data_removal_dialog.h",
diff --git a/chrome/browser/ui/android/autofill/internal/java/src/org/chromium/chrome/browser/ui/autofill/OtpVerificationDialogBridge.java b/chrome/browser/ui/android/autofill/internal/java/src/org/chromium/chrome/browser/ui/autofill/OtpVerificationDialogBridge.java
index d5387641..e4d4c98b 100644
--- a/chrome/browser/ui/android/autofill/internal/java/src/org/chromium/chrome/browser/ui/autofill/OtpVerificationDialogBridge.java
+++ b/chrome/browser/ui/android/autofill/internal/java/src/org/chromium/chrome/browser/ui/autofill/OtpVerificationDialogBridge.java
@@ -5,10 +5,8 @@
 package org.chromium.chrome.browser.ui.autofill;
 
 import android.content.Context;
-import android.os.Build.VERSION_CODES;
 
 import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
 
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.base.annotations.JNINamespace;
@@ -73,7 +71,6 @@
      * @param otpLength The expected length of the OTP. This is used for showing a hint in the input
      *         field as well as some basic error handling.
      */
-    @RequiresApi(api = VERSION_CODES.N)
     @CalledByNative
     void showDialog(int otpLength) {
         mDialogCoordinator.show(otpLength);
@@ -84,7 +81,6 @@
      *
      * @param errorMessage The error message to be displayed below the OTP input field.
      */
-    @RequiresApi(api = VERSION_CODES.N)
     @CalledByNative
     void showOtpErrorMessage(String errorMessage) {
         mDialogCoordinator.showOtpErrorMessage(errorMessage);
diff --git a/chrome/browser/ui/android/autofill/internal/java/src/org/chromium/chrome/browser/ui/autofill/OtpVerificationDialogCoordinator.java b/chrome/browser/ui/android/autofill/internal/java/src/org/chromium/chrome/browser/ui/autofill/OtpVerificationDialogCoordinator.java
index bf48b9f9..10e7f6ce 100644
--- a/chrome/browser/ui/android/autofill/internal/java/src/org/chromium/chrome/browser/ui/autofill/OtpVerificationDialogCoordinator.java
+++ b/chrome/browser/ui/android/autofill/internal/java/src/org/chromium/chrome/browser/ui/autofill/OtpVerificationDialogCoordinator.java
@@ -9,11 +9,9 @@
 import static org.chromium.chrome.browser.ui.autofill.OtpVerificationDialogProperties.OTP_LENGTH;
 
 import android.content.Context;
-import android.os.Build.VERSION_CODES;
 import android.view.LayoutInflater;
 import android.view.View;
 
-import androidx.annotation.RequiresApi;
 import androidx.annotation.VisibleForTesting;
 import androidx.core.content.res.ResourcesCompat;
 
@@ -87,7 +85,6 @@
      *
      * @param otpLength The expected length of the OTP input field.
      */
-    @RequiresApi(api = VERSION_CODES.N)
     void show(int otpLength) {
         PropertyModel otpVerificationDialogModel = buildOtpVerificationDialogModel(otpLength);
         PropertyModelChangeProcessor.create(
@@ -100,7 +97,6 @@
      *
      * @param errorMessage The string that is displayed in the error message.
      */
-    @RequiresApi(api = VERSION_CODES.N)
     void showOtpErrorMessage(String errorMessage) {
         mMediator.showOtpErrorMessage(Optional.of(errorMessage));
     }
@@ -127,7 +123,6 @@
      * @param otpLength The only non-static state of the dialog, needs to be passed in so that it
      * can be added to the model.
      */
-    @RequiresApi(api = VERSION_CODES.N)
     private PropertyModel buildOtpVerificationDialogModel(int otpLength) {
         return new PropertyModel.Builder(ALL_KEYS)
                 .with(OTP_LENGTH, otpLength)
diff --git a/chrome/browser/ui/android/autofill/internal/java/src/org/chromium/chrome/browser/ui/autofill/OtpVerificationDialogMediator.java b/chrome/browser/ui/android/autofill/internal/java/src/org/chromium/chrome/browser/ui/autofill/OtpVerificationDialogMediator.java
index 9ed1cdb..48eeb367 100644
--- a/chrome/browser/ui/android/autofill/internal/java/src/org/chromium/chrome/browser/ui/autofill/OtpVerificationDialogMediator.java
+++ b/chrome/browser/ui/android/autofill/internal/java/src/org/chromium/chrome/browser/ui/autofill/OtpVerificationDialogMediator.java
@@ -12,11 +12,8 @@
 import static org.chromium.chrome.browser.ui.autofill.OtpVerificationDialogProperties.SHOW_PROGRESS_BAR_OVERLAY;
 import static org.chromium.chrome.browser.ui.autofill.OtpVerificationDialogProperties.VIEW_DELEGATE;
 
-import android.os.Build.VERSION_CODES;
 import android.os.Handler;
 
-import androidx.annotation.RequiresApi;
-
 import org.chromium.chrome.browser.ui.autofill.OtpVerificationDialogCoordinator.Delegate;
 import org.chromium.ui.modaldialog.DialogDismissalCause;
 import org.chromium.ui.modaldialog.ModalDialogManager;
@@ -44,7 +41,6 @@
         mDelegate.onDialogDismissed();
     }
 
-    @RequiresApi(api = VERSION_CODES.N)
     @Override
     public void onClick(PropertyModel model, int buttonType) {
         switch (buttonType) {
@@ -64,7 +60,6 @@
         }
     }
 
-    @RequiresApi(api = VERSION_CODES.N)
     @Override
     public void onTextChanged(CharSequence s) {
         mModalDialogModel.set(ModalDialogProperties.POSITIVE_BUTTON_DISABLED,
@@ -73,7 +68,6 @@
         mOtpVerificationDialogModel.set(EDIT_TEXT, Optional.of(s));
     }
 
-    @RequiresApi(api = VERSION_CODES.N)
     @Override
     public void onResendLinkClicked() {
         clearEditText();
@@ -97,7 +91,6 @@
     }
 
     /** Clear the text in the Edit Text field. */
-    @RequiresApi(api = VERSION_CODES.N)
     void clearEditText() {
         mOtpVerificationDialogModel.set(EDIT_TEXT, Optional.empty());
     }
@@ -112,7 +105,6 @@
     }
 
     /** Show an error message for the submitted otp. */
-    @RequiresApi(api = VERSION_CODES.N)
     void showOtpErrorMessage(Optional<String> errorMessage) {
         mOtpVerificationDialogModel.set(SHOW_PROGRESS_BAR_OVERLAY, false);
         mOtpVerificationDialogModel.set(OTP_ERROR_MESSAGE, errorMessage);
diff --git a/chrome/browser/ui/android/autofill/internal/java/src/org/chromium/chrome/browser/ui/autofill/OtpVerificationDialogView.java b/chrome/browser/ui/android/autofill/internal/java/src/org/chromium/chrome/browser/ui/autofill/OtpVerificationDialogView.java
index 51928b1e1..91f4d77 100644
--- a/chrome/browser/ui/android/autofill/internal/java/src/org/chromium/chrome/browser/ui/autofill/OtpVerificationDialogView.java
+++ b/chrome/browser/ui/android/autofill/internal/java/src/org/chromium/chrome/browser/ui/autofill/OtpVerificationDialogView.java
@@ -7,7 +7,6 @@
 import static org.chromium.chrome.browser.ui.autofill.OtpVerificationDialogProperties.ANIMATION_DURATION_MS;
 
 import android.content.Context;
-import android.os.Build.VERSION_CODES;
 import android.text.Editable;
 import android.text.SpannableString;
 import android.text.TextWatcher;
@@ -18,8 +17,6 @@
 import android.widget.RelativeLayout;
 import android.widget.TextView;
 
-import androidx.annotation.RequiresApi;
-
 import org.chromium.chrome.browser.ui.autofill.OtpVerificationDialogProperties.ViewDelegate;
 import org.chromium.chrome.browser.ui.autofill.internal.R;
 import org.chromium.ui.text.NoUnderlineClickableSpan;
@@ -72,7 +69,6 @@
      *
      * @param viewDelegate The view delegate for this specific view.
      */
-    @RequiresApi(api = VERSION_CODES.N)
     void setViewDelegate(ViewDelegate viewDelegate) {
         mOtpEditText.addTextChangedListener(buildTextWatcher(viewDelegate));
         mOtpResendMessageTextView.setText(buildOtpResendMessageLink(getContext(), viewDelegate));
@@ -107,7 +103,6 @@
      * @param errorMessage The error message that gets displayed to the user. Can be empty,
      * indicating there should be no error message shown on the dialog (so we hide it).
      */
-    @RequiresApi(api = VERSION_CODES.N)
     void showOtpErrorMessage(Optional<String> errorMessage) {
         mOtpErrorMessageTextView.setVisibility(View.VISIBLE);
         mOtpErrorMessageTextView.setText(errorMessage.get());
@@ -151,7 +146,6 @@
     }
 
     /** Builds Otp Resend Message Link **/
-    @RequiresApi(api = VERSION_CODES.N)
     private SpannableString buildOtpResendMessageLink(Context context, ViewDelegate viewDelegate) {
         return SpanApplier.applySpans(
                 context.getResources().getString(
diff --git a/chrome/browser/ui/android/autofill/internal/java/src/org/chromium/chrome/browser/ui/autofill/OtpVerificationDialogViewBinder.java b/chrome/browser/ui/android/autofill/internal/java/src/org/chromium/chrome/browser/ui/autofill/OtpVerificationDialogViewBinder.java
index 494db3a..dd7ad16 100644
--- a/chrome/browser/ui/android/autofill/internal/java/src/org/chromium/chrome/browser/ui/autofill/OtpVerificationDialogViewBinder.java
+++ b/chrome/browser/ui/android/autofill/internal/java/src/org/chromium/chrome/browser/ui/autofill/OtpVerificationDialogViewBinder.java
@@ -11,10 +11,6 @@
 import static org.chromium.chrome.browser.ui.autofill.OtpVerificationDialogProperties.SHOW_PROGRESS_BAR_OVERLAY;
 import static org.chromium.chrome.browser.ui.autofill.OtpVerificationDialogProperties.VIEW_DELEGATE;
 
-import android.os.Build.VERSION_CODES;
-
-import androidx.annotation.RequiresApi;
-
 import org.chromium.ui.modelutil.PropertyKey;
 import org.chromium.ui.modelutil.PropertyModel;
 
@@ -24,7 +20,6 @@
 class OtpVerificationDialogViewBinder {
     private OtpVerificationDialogViewBinder() {}
 
-    @RequiresApi(api = VERSION_CODES.N)
     static void bind(
             PropertyModel model, OtpVerificationDialogView dialogView, PropertyKey propertyKey) {
         if (propertyKey.equals(EDIT_TEXT)) {
diff --git a/chrome/browser/ui/android/omnibox/BUILD.gn b/chrome/browser/ui/android/omnibox/BUILD.gn
index 76a9c5df..39ecac9 100644
--- a/chrome/browser/ui/android/omnibox/BUILD.gn
+++ b/chrome/browser/ui/android/omnibox/BUILD.gn
@@ -271,16 +271,16 @@
     "java/res/drawable-hdpi/btn_suggestion_refine.png",
     "java/res/drawable-hdpi/ic_history_googblue_24dp.png",
     "java/res/drawable-hdpi/ic_suggestion_magnifier.png",
-    "java/res/drawable-ldrtl-hdpi-v17/btn_suggestion_refine.png",
-    "java/res/drawable-ldrtl-hdpi-v17/ic_suggestion_magnifier.png",
-    "java/res/drawable-ldrtl-mdpi-v17/btn_suggestion_refine.png",
-    "java/res/drawable-ldrtl-mdpi-v17/ic_suggestion_magnifier.png",
-    "java/res/drawable-ldrtl-xhdpi-v17/btn_suggestion_refine.png",
-    "java/res/drawable-ldrtl-xhdpi-v17/ic_suggestion_magnifier.png",
-    "java/res/drawable-ldrtl-xxhdpi-v17/btn_suggestion_refine.png",
-    "java/res/drawable-ldrtl-xxhdpi-v17/ic_suggestion_magnifier.png",
-    "java/res/drawable-ldrtl-xxxhdpi-v17/btn_suggestion_refine.png",
-    "java/res/drawable-ldrtl-xxxhdpi-v17/ic_suggestion_magnifier.png",
+    "java/res/drawable-ldrtl-hdpi/btn_suggestion_refine.png",
+    "java/res/drawable-ldrtl-hdpi/ic_suggestion_magnifier.png",
+    "java/res/drawable-ldrtl-mdpi/btn_suggestion_refine.png",
+    "java/res/drawable-ldrtl-mdpi/ic_suggestion_magnifier.png",
+    "java/res/drawable-ldrtl-xhdpi/btn_suggestion_refine.png",
+    "java/res/drawable-ldrtl-xhdpi/ic_suggestion_magnifier.png",
+    "java/res/drawable-ldrtl-xxhdpi/btn_suggestion_refine.png",
+    "java/res/drawable-ldrtl-xxhdpi/ic_suggestion_magnifier.png",
+    "java/res/drawable-ldrtl-xxxhdpi/btn_suggestion_refine.png",
+    "java/res/drawable-ldrtl-xxxhdpi/ic_suggestion_magnifier.png",
     "java/res/drawable-mdpi/bookmark_edit_active.png",
     "java/res/drawable-mdpi/btn_mic.png",
     "java/res/drawable-mdpi/btn_star.png",
diff --git a/chrome/browser/ui/android/omnibox/java/res/drawable-ldrtl-hdpi-v17/btn_suggestion_refine.png b/chrome/browser/ui/android/omnibox/java/res/drawable-ldrtl-hdpi/btn_suggestion_refine.png
similarity index 100%
rename from chrome/browser/ui/android/omnibox/java/res/drawable-ldrtl-hdpi-v17/btn_suggestion_refine.png
rename to chrome/browser/ui/android/omnibox/java/res/drawable-ldrtl-hdpi/btn_suggestion_refine.png
Binary files differ
diff --git a/chrome/browser/ui/android/omnibox/java/res/drawable-ldrtl-hdpi-v17/ic_suggestion_magnifier.png b/chrome/browser/ui/android/omnibox/java/res/drawable-ldrtl-hdpi/ic_suggestion_magnifier.png
similarity index 100%
rename from chrome/browser/ui/android/omnibox/java/res/drawable-ldrtl-hdpi-v17/ic_suggestion_magnifier.png
rename to chrome/browser/ui/android/omnibox/java/res/drawable-ldrtl-hdpi/ic_suggestion_magnifier.png
Binary files differ
diff --git a/chrome/browser/ui/android/omnibox/java/res/drawable-ldrtl-mdpi-v17/btn_suggestion_refine.png b/chrome/browser/ui/android/omnibox/java/res/drawable-ldrtl-mdpi/btn_suggestion_refine.png
similarity index 100%
rename from chrome/browser/ui/android/omnibox/java/res/drawable-ldrtl-mdpi-v17/btn_suggestion_refine.png
rename to chrome/browser/ui/android/omnibox/java/res/drawable-ldrtl-mdpi/btn_suggestion_refine.png
Binary files differ
diff --git a/chrome/browser/ui/android/omnibox/java/res/drawable-ldrtl-mdpi-v17/ic_suggestion_magnifier.png b/chrome/browser/ui/android/omnibox/java/res/drawable-ldrtl-mdpi/ic_suggestion_magnifier.png
similarity index 100%
rename from chrome/browser/ui/android/omnibox/java/res/drawable-ldrtl-mdpi-v17/ic_suggestion_magnifier.png
rename to chrome/browser/ui/android/omnibox/java/res/drawable-ldrtl-mdpi/ic_suggestion_magnifier.png
Binary files differ
diff --git a/chrome/browser/ui/android/omnibox/java/res/drawable-ldrtl-xhdpi-v17/btn_suggestion_refine.png b/chrome/browser/ui/android/omnibox/java/res/drawable-ldrtl-xhdpi/btn_suggestion_refine.png
similarity index 100%
rename from chrome/browser/ui/android/omnibox/java/res/drawable-ldrtl-xhdpi-v17/btn_suggestion_refine.png
rename to chrome/browser/ui/android/omnibox/java/res/drawable-ldrtl-xhdpi/btn_suggestion_refine.png
Binary files differ
diff --git a/chrome/browser/ui/android/omnibox/java/res/drawable-ldrtl-xhdpi-v17/ic_suggestion_magnifier.png b/chrome/browser/ui/android/omnibox/java/res/drawable-ldrtl-xhdpi/ic_suggestion_magnifier.png
similarity index 100%
rename from chrome/browser/ui/android/omnibox/java/res/drawable-ldrtl-xhdpi-v17/ic_suggestion_magnifier.png
rename to chrome/browser/ui/android/omnibox/java/res/drawable-ldrtl-xhdpi/ic_suggestion_magnifier.png
Binary files differ
diff --git a/chrome/browser/ui/android/omnibox/java/res/drawable-ldrtl-xxhdpi-v17/btn_suggestion_refine.png b/chrome/browser/ui/android/omnibox/java/res/drawable-ldrtl-xxhdpi/btn_suggestion_refine.png
similarity index 100%
rename from chrome/browser/ui/android/omnibox/java/res/drawable-ldrtl-xxhdpi-v17/btn_suggestion_refine.png
rename to chrome/browser/ui/android/omnibox/java/res/drawable-ldrtl-xxhdpi/btn_suggestion_refine.png
Binary files differ
diff --git a/chrome/browser/ui/android/omnibox/java/res/drawable-ldrtl-xxhdpi-v17/ic_suggestion_magnifier.png b/chrome/browser/ui/android/omnibox/java/res/drawable-ldrtl-xxhdpi/ic_suggestion_magnifier.png
similarity index 100%
rename from chrome/browser/ui/android/omnibox/java/res/drawable-ldrtl-xxhdpi-v17/ic_suggestion_magnifier.png
rename to chrome/browser/ui/android/omnibox/java/res/drawable-ldrtl-xxhdpi/ic_suggestion_magnifier.png
Binary files differ
diff --git a/chrome/browser/ui/android/omnibox/java/res/drawable-ldrtl-xxxhdpi-v17/btn_suggestion_refine.png b/chrome/browser/ui/android/omnibox/java/res/drawable-ldrtl-xxxhdpi/btn_suggestion_refine.png
similarity index 100%
rename from chrome/browser/ui/android/omnibox/java/res/drawable-ldrtl-xxxhdpi-v17/btn_suggestion_refine.png
rename to chrome/browser/ui/android/omnibox/java/res/drawable-ldrtl-xxxhdpi/btn_suggestion_refine.png
Binary files differ
diff --git a/chrome/browser/ui/android/omnibox/java/res/drawable-ldrtl-xxxhdpi-v17/ic_suggestion_magnifier.png b/chrome/browser/ui/android/omnibox/java/res/drawable-ldrtl-xxxhdpi/ic_suggestion_magnifier.png
similarity index 100%
rename from chrome/browser/ui/android/omnibox/java/res/drawable-ldrtl-xxxhdpi-v17/ic_suggestion_magnifier.png
rename to chrome/browser/ui/android/omnibox/java/res/drawable-ldrtl-xxxhdpi/ic_suggestion_magnifier.png
Binary files differ
diff --git a/chrome/browser/ui/android/toolbar/BUILD.gn b/chrome/browser/ui/android/toolbar/BUILD.gn
index b188c7ca..7c61bc95 100644
--- a/chrome/browser/ui/android/toolbar/BUILD.gn
+++ b/chrome/browser/ui/android/toolbar/BUILD.gn
@@ -97,7 +97,6 @@
     "java/src/org/chromium/chrome/browser/toolbar/top/ToggleTabStackButtonCoordinator.java",
     "java/src/org/chromium/chrome/browser/toolbar/top/Toolbar.java",
     "java/src/org/chromium/chrome/browser/toolbar/top/ToolbarActionModeCallback.java",
-    "java/src/org/chromium/chrome/browser/toolbar/top/ToolbarColorObserverManager.java",
     "java/src/org/chromium/chrome/browser/toolbar/top/ToolbarControlContainer.java",
     "java/src/org/chromium/chrome/browser/toolbar/top/ToolbarLayout.java",
     "java/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhone.java",
@@ -205,12 +204,12 @@
     "java/res/drawable-hdpi/incognito_switch.png",
     "java/res/drawable-hdpi/modern_location_bar.9.png",
     "java/res/drawable-hdpi/popup_bg_bottom.9.png",
-    "java/res/drawable-ldrtl-hdpi-v17/btn_toolbar_reload.png",
-    "java/res/drawable-ldrtl-mdpi-v17/btn_toolbar_reload.png",
-    "java/res/drawable-ldrtl-sw600dp-xhdpi-v17/toolbar_background.9.png",
-    "java/res/drawable-ldrtl-xhdpi-v17/btn_toolbar_reload.png",
-    "java/res/drawable-ldrtl-xxhdpi-v17/btn_toolbar_reload.png",
-    "java/res/drawable-ldrtl-xxxhdpi-v17/btn_toolbar_reload.png",
+    "java/res/drawable-ldrtl-hdpi/btn_toolbar_reload.png",
+    "java/res/drawable-ldrtl-mdpi/btn_toolbar_reload.png",
+    "java/res/drawable-ldrtl-sw600dp-xhdpi/toolbar_background.9.png",
+    "java/res/drawable-ldrtl-xhdpi/btn_toolbar_reload.png",
+    "java/res/drawable-ldrtl-xxhdpi/btn_toolbar_reload.png",
+    "java/res/drawable-ldrtl-xxxhdpi/btn_toolbar_reload.png",
     "java/res/drawable-mdpi/badge_update_dark.png",
     "java/res/drawable-mdpi/badge_update_light.png",
     "java/res/drawable-mdpi/btn_tabswitcher_modern.png",
diff --git a/chrome/browser/ui/android/toolbar/java/res/drawable-ldrtl-hdpi-v17/btn_toolbar_reload.png b/chrome/browser/ui/android/toolbar/java/res/drawable-ldrtl-hdpi/btn_toolbar_reload.png
similarity index 100%
rename from chrome/browser/ui/android/toolbar/java/res/drawable-ldrtl-hdpi-v17/btn_toolbar_reload.png
rename to chrome/browser/ui/android/toolbar/java/res/drawable-ldrtl-hdpi/btn_toolbar_reload.png
Binary files differ
diff --git a/chrome/browser/ui/android/toolbar/java/res/drawable-ldrtl-mdpi-v17/btn_toolbar_reload.png b/chrome/browser/ui/android/toolbar/java/res/drawable-ldrtl-mdpi/btn_toolbar_reload.png
similarity index 100%
rename from chrome/browser/ui/android/toolbar/java/res/drawable-ldrtl-mdpi-v17/btn_toolbar_reload.png
rename to chrome/browser/ui/android/toolbar/java/res/drawable-ldrtl-mdpi/btn_toolbar_reload.png
Binary files differ
diff --git a/chrome/browser/ui/android/toolbar/java/res/drawable-ldrtl-sw600dp-xhdpi-v17/toolbar_background.9.png b/chrome/browser/ui/android/toolbar/java/res/drawable-ldrtl-sw600dp-xhdpi/toolbar_background.9.png
similarity index 100%
rename from chrome/browser/ui/android/toolbar/java/res/drawable-ldrtl-sw600dp-xhdpi-v17/toolbar_background.9.png
rename to chrome/browser/ui/android/toolbar/java/res/drawable-ldrtl-sw600dp-xhdpi/toolbar_background.9.png
Binary files differ
diff --git a/chrome/browser/ui/android/toolbar/java/res/drawable-ldrtl-xhdpi-v17/btn_toolbar_reload.png b/chrome/browser/ui/android/toolbar/java/res/drawable-ldrtl-xhdpi/btn_toolbar_reload.png
similarity index 100%
rename from chrome/browser/ui/android/toolbar/java/res/drawable-ldrtl-xhdpi-v17/btn_toolbar_reload.png
rename to chrome/browser/ui/android/toolbar/java/res/drawable-ldrtl-xhdpi/btn_toolbar_reload.png
Binary files differ
diff --git a/chrome/browser/ui/android/toolbar/java/res/drawable-ldrtl-xxhdpi-v17/btn_toolbar_reload.png b/chrome/browser/ui/android/toolbar/java/res/drawable-ldrtl-xxhdpi/btn_toolbar_reload.png
similarity index 100%
rename from chrome/browser/ui/android/toolbar/java/res/drawable-ldrtl-xxhdpi-v17/btn_toolbar_reload.png
rename to chrome/browser/ui/android/toolbar/java/res/drawable-ldrtl-xxhdpi/btn_toolbar_reload.png
Binary files differ
diff --git a/chrome/browser/ui/android/toolbar/java/res/drawable-ldrtl-xxxhdpi-v17/btn_toolbar_reload.png b/chrome/browser/ui/android/toolbar/java/res/drawable-ldrtl-xxxhdpi/btn_toolbar_reload.png
similarity index 100%
rename from chrome/browser/ui/android/toolbar/java/res/drawable-ldrtl-xxxhdpi-v17/btn_toolbar_reload.png
rename to chrome/browser/ui/android/toolbar/java/res/drawable-ldrtl-xxxhdpi/btn_toolbar_reload.png
Binary files differ
diff --git a/chrome/browser/ui/android/toolbar/java/res/values/styles.xml b/chrome/browser/ui/android/toolbar/java/res/values/styles.xml
index c863a2e..2eb5bddd 100644
--- a/chrome/browser/ui/android/toolbar/java/res/values/styles.xml
+++ b/chrome/browser/ui/android/toolbar/java/res/values/styles.xml
@@ -36,5 +36,7 @@
         <item name="android:layout_width">@dimen/split_toolbar_button_width</item>
         <item name="android:background">@android:color/transparent</item>
     </style>
-    <style name="NavigationPopupDialog" parent="Widget.AppCompat.Light.ListPopupWindow" />
+   <style name="NavigationPopupDialog" parent="Widget.AppCompat.Light.ListPopupWindow">
+        <item name="android:popupElevation">0dp</item>
+    </style>
 </resources>
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/StartSurfaceToolbarCoordinator.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/StartSurfaceToolbarCoordinator.java
index 1b27e34..a5ede195 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/StartSurfaceToolbarCoordinator.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/StartSurfaceToolbarCoordinator.java
@@ -70,8 +70,7 @@
             boolean isTabToGtsAnimationEnabled, boolean isTabGroupsAndroidContinuationEnabled,
             BooleanSupplier isIncognitoModeEnabledSupplier,
             Callback<LoadUrlParams> logoClickedCallback, boolean isRefactorEnabled,
-            boolean shouldCreateLogoInToolbar, Callback<Boolean> finishedTransitionCallback,
-            ToolbarColorObserverManager toolbarColorObserverManager) {
+            boolean shouldCreateLogoInToolbar, Callback<Boolean> finishedTransitionCallback) {
         mStub = startSurfaceToolbarStub;
 
         mPropertyModel =
@@ -103,7 +102,7 @@
                 isTabToGtsFadeAnimationEnabled, isTabGroupsAndroidContinuationEnabled,
                 isIncognitoModeEnabledSupplier, logoClickedCallback, isRefactorEnabled,
                 StartSurfaceConfiguration.IS_DOODLE_SUPPORTED.getValue(), shouldCreateLogoInToolbar,
-                finishedTransitionCallback, toolbarColorObserverManager);
+                finishedTransitionCallback);
 
         mThemeColorProvider = provider;
         mMenuButtonCoordinator = menuButtonCoordinator;
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/StartSurfaceToolbarMediator.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/StartSurfaceToolbarMediator.java
index e87b9aa1..27c99fb 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/StartSurfaceToolbarMediator.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/StartSurfaceToolbarMediator.java
@@ -27,11 +27,9 @@
 import static org.chromium.chrome.browser.toolbar.top.StartSurfaceToolbarProperties.TRANSLATION_Y;
 
 import android.animation.Animator;
-import android.animation.ObjectAnimator;
 import android.content.Context;
 import android.view.View;
 
-import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
 import org.chromium.base.Callback;
@@ -50,7 +48,6 @@
 import org.chromium.chrome.browser.toolbar.ButtonDataProvider;
 import org.chromium.chrome.browser.toolbar.TabCountProvider;
 import org.chromium.chrome.browser.toolbar.menu_button.MenuButtonCoordinator;
-import org.chromium.chrome.browser.toolbar.top.TopToolbarCoordinator.ToolbarAlphaInOverviewObserver;
 import org.chromium.chrome.browser.user_education.IPHCommandBuilder;
 import org.chromium.chrome.browser.util.ChromeAccessibilityUtil;
 import org.chromium.chrome.features.start_surface.StartSurfaceState;
@@ -97,11 +94,9 @@
     private boolean mIsNativeInitializedForLogo;
     private LogoCoordinator mLogoCoordinator;
 
-    private ObjectAnimator mAlphaAnimator;
+    private Animator mAlphaAnimator;
     private Callback<Boolean> mFinishedTransitionCallback;
 
-    private @Nullable ToolbarAlphaInOverviewObserver mToolbarAlphaInOverviewObserver;
-
     StartSurfaceToolbarMediator(Context context, PropertyModel model,
             Callback<IPHCommandBuilder> showIdentityIPHCallback,
             boolean hideIncognitoSwitchWhenNoTabs, MenuButtonCoordinator menuButtonCoordinator,
@@ -112,8 +107,7 @@
             BooleanSupplier isIncognitoModeEnabledSupplier,
             Callback<LoadUrlParams> logoClickedCallback, boolean isRefactorEnabled,
             boolean shouldFetchDoodle, boolean shouldCreateLogoInToolbar,
-            Callback<Boolean> finishedTransitionCallback,
-            ToolbarAlphaInOverviewObserver toolbarAlphaInOverviewObserver) {
+            Callback<Boolean> finishedTransitionCallback) {
         mPropertyModel = model;
         mStartSurfaceState = StartSurfaceState.NOT_SHOWN;
         mShowIdentityIPHCallback = showIdentityIPHCallback;
@@ -131,7 +125,6 @@
         mShouldCreateLogoInToolbar = shouldCreateLogoInToolbar;
         mIsRefactorEnabled = isRefactorEnabled;
         mFinishedTransitionCallback = finishedTransitionCallback;
-        mToolbarAlphaInOverviewObserver = toolbarAlphaInOverviewObserver;
         mContext = context;
 
         mShouldShowTabSwitcherButtonOnHomepage = shouldShowTabSwitcherButtonOnHomepage;
@@ -177,9 +170,6 @@
             mCallbackController.destroy();
             mCallbackController = null;
         }
-        if (mToolbarAlphaInOverviewObserver != null) {
-            mToolbarAlphaInOverviewObserver = null;
-        }
         mIdentityDiscController.removeObserver(this);
     }
 
@@ -382,17 +372,6 @@
                 finishAlphaAnimator(shouldShowStartSurfaceToolbar);
             }
         });
-        // Notify the observer that the toolbar alpha value is changed and pass the rendering
-        // toolbar alpha value to the observer.
-        if (mToolbarAlphaInOverviewObserver != null) {
-            mAlphaAnimator.addUpdateListener((animation) -> {
-                Object alphaValue = animation.getAnimatedValue();
-                if (alphaValue != null && alphaValue instanceof Float) {
-                    mToolbarAlphaInOverviewObserver.onToolbarAlphaInOverviewChanged(
-                            (float) alphaValue);
-                }
-            });
-        }
         mAlphaAnimator.start();
     }
 
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/TabSwitcherModeTTCoordinator.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/TabSwitcherModeTTCoordinator.java
index ba35831..341a0232 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/TabSwitcherModeTTCoordinator.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/TabSwitcherModeTTCoordinator.java
@@ -46,8 +46,6 @@
     private TabSwitcherModeTopToolbar mTabSwitcherToolbar;
     private TabSwitcherModeTopToolbar mTabSwitcherFullscreenToolbar;
 
-    private ToolbarColorObserverManager mToolbarColorObserverManager;
-
     @Nullable
     private IncognitoTabModelObserver mIncognitoTabModelObserver;
 
@@ -60,8 +58,7 @@
     TabSwitcherModeTTCoordinator(ViewStub tabSwitcherToolbarStub,
             ViewStub tabSwitcherFullscreenToolbarStub, MenuButtonCoordinator menuButtonCoordinator,
             boolean isGridTabSwitcherEnabled, boolean isTabletGtsPolishEnabled,
-            boolean isTabToGtsAnimationEnabled, BooleanSupplier isIncognitoModeEnabledSupplier,
-            ToolbarColorObserverManager toolbarColorObserverManager) {
+            boolean isTabToGtsAnimationEnabled, BooleanSupplier isIncognitoModeEnabledSupplier) {
         mTabSwitcherToolbarStub = tabSwitcherToolbarStub;
         mTabSwitcherFullscreenToolbarStub = tabSwitcherFullscreenToolbarStub;
         mMenuButtonCoordinator = menuButtonCoordinator;
@@ -71,7 +68,6 @@
         mIsIncognitoModeEnabledSupplier = isIncognitoModeEnabledSupplier;
         mTopToolbarInteractabilityManager =
                 new TopToolbarInteractabilityManager(enabled -> setNewTabEnabled(enabled));
-        mToolbarColorObserverManager = toolbarColorObserverManager;
     }
 
     /**
@@ -239,8 +235,7 @@
      */
     private void initializeToolbar(TabSwitcherModeTopToolbar toolbar, boolean isFullscreenToolbar) {
         toolbar.initialize(mIsGridTabSwitcherEnabled, isFullscreenToolbar,
-                mIsTabToGtsAnimationEnabled, mIsIncognitoModeEnabledSupplier,
-                mToolbarColorObserverManager);
+                mIsTabToGtsAnimationEnabled, mIsIncognitoModeEnabledSupplier);
         mMenuButtonCoordinator.setMenuButton(toolbar.findViewById(R.id.menu_button_wrapper));
 
         // It's expected that these properties are set by the time the tab switcher is entered.
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/TabSwitcherModeTopToolbar.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/TabSwitcherModeTopToolbar.java
index a15dbb2..45d5117d 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/TabSwitcherModeTopToolbar.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/TabSwitcherModeTopToolbar.java
@@ -27,7 +27,6 @@
 import org.chromium.chrome.browser.toolbar.R;
 import org.chromium.chrome.browser.toolbar.TabCountProvider;
 import org.chromium.chrome.browser.toolbar.menu_button.MenuButton;
-import org.chromium.chrome.browser.toolbar.top.TopToolbarCoordinator.ToolbarAlphaInOverviewObserver;
 import org.chromium.chrome.browser.ui.theme.BrandedColorScheme;
 import org.chromium.components.browser_ui.styles.ChromeColors;
 import org.chromium.components.browser_ui.widget.animation.CancelAwareAnimatorListener;
@@ -72,8 +71,6 @@
     private boolean mIsFullscreenToolbar;
     private boolean mShowZoomingAnimation;
 
-    private @Nullable ToolbarAlphaInOverviewObserver mToolbarAlphaInOverviewObserver;
-
     public TabSwitcherModeTopToolbar(Context context, AttributeSet attrs) {
         super(context, attrs);
     }
@@ -97,13 +94,11 @@
     }
 
     void initialize(boolean isGridTabSwitcherEnabled, boolean isFullscreenToolbar,
-            boolean isTabToGtsAnimationEnabled, BooleanSupplier isIncognitoModeEnabledSupplier,
-            ToolbarColorObserverManager toolbarColorObserverManager) {
+            boolean isTabToGtsAnimationEnabled, BooleanSupplier isIncognitoModeEnabledSupplier) {
         mIsGridTabSwitcherEnabled = isGridTabSwitcherEnabled;
         mIsFullscreenToolbar = isFullscreenToolbar;
         mShowZoomingAnimation = isGridTabSwitcherEnabled && isTabToGtsAnimationEnabled;
         mIsIncognitoModeEnabledSupplier = isIncognitoModeEnabledSupplier;
-        mToolbarAlphaInOverviewObserver = toolbarColorObserverManager;
 
         mNewTabImageButton.setGridTabSwitcherEnabled(isGridTabSwitcherEnabled);
         mNewTabImageButton.setStartSurfaceEnabled(false);
@@ -140,9 +135,6 @@
             mIncognitoToggleTabLayout.destroy();
             mIncognitoToggleTabLayout = null;
         }
-        if (mToolbarAlphaInOverviewObserver != null) {
-            mToolbarAlphaInOverviewObserver = null;
-        }
     }
 
     /**
@@ -195,17 +187,7 @@
                 mVisiblityAnimator = null;
             }
         });
-        // Notify the observer that the toolbar alpha value is changed and pass the rendering
-        // toolbar alpha value to the observer.
-        if (mToolbarAlphaInOverviewObserver != null) {
-            mVisiblityAnimator.addUpdateListener((animation) -> {
-                Object alphaValue = animation.getAnimatedValue();
-                if (alphaValue != null && alphaValue instanceof Float) {
-                    mToolbarAlphaInOverviewObserver.onToolbarAlphaInOverviewChanged(
-                            (float) alphaValue);
-                }
-            });
-        }
+
         mVisiblityAnimator.start();
 
         if (DeviceClassManager.enableAccessibilityLayout(getContext())) mVisiblityAnimator.end();
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarColorObserverManager.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarColorObserverManager.java
deleted file mode 100644
index 679e5da..0000000
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarColorObserverManager.java
+++ /dev/null
@@ -1,81 +0,0 @@
-// Copyright 2022 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.toolbar.top;
-
-import android.content.Context;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import org.chromium.chrome.browser.tabmodel.IncognitoStateProvider;
-import org.chromium.chrome.browser.toolbar.top.TopToolbarCoordinator.ToolbarAlphaInOverviewObserver;
-import org.chromium.chrome.browser.toolbar.top.TopToolbarCoordinator.ToolbarColorObserver;
-import org.chromium.components.browser_ui.styles.ChromeColors;
-import org.chromium.ui.util.ColorUtils;
-
-/**
- * A class to receive toolbar color change updates from toolbar components and send the
- * rendering toolbar color to the ToolbarColorObserver.
- */
-class ToolbarColorObserverManager implements ToolbarAlphaInOverviewObserver, ToolbarColorObserver {
-    private @Nullable ToolbarColorObserver mToolbarColorObserver;
-
-    private Context mContext;
-    private IncognitoStateProvider mIncognitoStateProvider;
-    private float mToolbarAlphaValue;
-    private int mToolbarColor;
-
-    ToolbarColorObserverManager(Context context, ToolbarLayout toolbarLayout) {
-        mContext = context;
-        mToolbarAlphaValue = 0;
-        // Initialize mToolbarColor for first load of website.
-        if (toolbarLayout instanceof ToolbarPhone) {
-            mToolbarColor = ((ToolbarPhone) toolbarLayout).getToolbarBackgroundColor();
-        }
-    }
-
-    /**
-     * @param provider The provider used to determine incognito state.
-     */
-    void setIncognitoStateProvider(IncognitoStateProvider provider) {
-        mIncognitoStateProvider = provider;
-    }
-
-    /**
-     * Set Toolbar Color Observer for the toolbar color changes.
-     * @param toolbarColorObserver The observer to listen to toolbar color change.
-     */
-    void setToolbarColorObserver(@NonNull ToolbarColorObserver toolbarColorObserver) {
-        mToolbarColorObserver = toolbarColorObserver;
-    }
-
-    // TopToolbarCoordinator.ToolbarColorObserver implementation.
-    @Override
-    public void onToolbarColorChanged(int color) {
-        mToolbarColor = color;
-        notifyToolbarColorChanged();
-    }
-
-    // TopToolbarCoordinator.ToolbarAlphaInOverviewObserver implementation.
-    @Override
-    public void onToolbarAlphaInOverviewChanged(float fraction) {
-        mToolbarAlphaValue = fraction;
-        notifyToolbarColorChanged();
-    }
-
-    /**
-     * Notify the observer that the toolbar color is changed based on alpha value and toolbar color,
-     * and send the rendering toolbar color to the observer.
-     */
-    private void notifyToolbarColorChanged() {
-        if (mToolbarColorObserver != null && mIncognitoStateProvider != null) {
-            boolean isIncognito = mIncognitoStateProvider.isIncognitoSelected();
-            int overviewColor = ChromeColors.getPrimaryBackgroundColor(mContext, isIncognito);
-            int toolbarRenderingColor = ColorUtils.getColorWithOverlay(
-                    mToolbarColor, overviewColor, mToolbarAlphaValue);
-            mToolbarColorObserver.onToolbarColorChanged(toolbarRenderingColor);
-        }
-    }
-}
\ No newline at end of file
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhone.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhone.java
index 3458d6a2..1e0e2d6 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhone.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhone.java
@@ -2722,7 +2722,8 @@
     /**
      * Returns the toolbar's background color.
      */
-    public int getToolbarBackgroundColor() {
+    @VisibleForTesting
+    public int getToolbarBackgroundColorForTesting(Activity activity) {
         return mToolbarBackground.getColor();
     }
 
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/TopToolbarCoordinator.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/TopToolbarCoordinator.java
index b36e6bd..0da911c 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/TopToolbarCoordinator.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/TopToolbarCoordinator.java
@@ -79,14 +79,6 @@
         void onToolbarColorChanged(int color);
     }
 
-    /**
-     * Observes toolbar alpha change during overview mode fading animation.
-     */
-    public interface ToolbarAlphaInOverviewObserver {
-        /** @param fraction The toolbar alpha value. */
-        void onToolbarAlphaInOverviewChanged(float fraction);
-    }
-
     public static final int TAB_SWITCHER_MODE_NORMAL_ANIMATION_DURATION_MS = 200;
     public static final int TAB_SWITCHER_MODE_GTS_ANIMATION_DURATION_MS = 150;
 
@@ -116,8 +108,6 @@
     private TopToolbarOverlayCoordinator mOverlayCoordinator;
     private boolean mStartSurfaceToolbarVisible;
 
-    private ToolbarColorObserverManager mToolbarColorObserverManager;
-
     /**
      * Creates a new {@link TopToolbarCoordinator}.
      * @param controlContainer The {@link ToolbarControlContainer} for the containing activity.
@@ -193,9 +183,6 @@
         mResourceManagerSupplier = resourceManagerSupplier;
         mTabModelSelectorSupplier = tabModelSelectorSupplier;
         mIsStartSurfaceRefactorEnabled = isStartSurfaceRefactorEnabled;
-        mToolbarColorObserverManager =
-                new ToolbarColorObserverManager(mToolbarLayout.getContext(), mToolbarLayout);
-        mToolbarLayout.setToolbarColorObserver(mToolbarColorObserverManager);
 
         if (mToolbarLayout instanceof ToolbarPhone && isStartSurfaceEnabled) {
             mStartSurfaceToolbarCoordinator = new StartSurfaceToolbarCoordinator(toolbarStub,
@@ -204,13 +191,12 @@
                     isGridTabSwitcherEnabled, isTabToGtsAnimationEnabled,
                     isTabGroupsAndroidContinuationEnabled, isIncognitoModeEnabledSupplier,
                     startSurfaceLogoClickedCallback, mIsStartSurfaceRefactorEnabled,
-                    shouldCreateLogoInStartToolbar, this::onStartSurfaceToolbarTransitionFinished,
-                    mToolbarColorObserverManager);
+                    shouldCreateLogoInStartToolbar, this::onStartSurfaceToolbarTransitionFinished);
         } else if (mToolbarLayout instanceof ToolbarPhone || isTabletGridTabSwitcherEnabled()) {
             mTabSwitcherModeCoordinator = new TabSwitcherModeTTCoordinator(toolbarStub,
                     fullscreenToolbarStub, overviewModeMenuButtonCoordinator,
                     isGridTabSwitcherEnabled, isTabletGtsPolishEnabled, isTabToGtsAnimationEnabled,
-                    isIncognitoModeEnabledSupplier, mToolbarColorObserverManager);
+                    isIncognitoModeEnabledSupplier);
         }
         controlContainer.setPostInitializationDependencies(this, initializeWithIncognitoColors,
                 constraintsSupplier, toolbarDataProvider::getTab, compositorInMotionSupplier,
@@ -330,7 +316,7 @@
      * @param toolbarColorObserver The observer that observes toolbar color change.
      */
     public void setToolbarColorObserver(@NonNull ToolbarColorObserver toolbarColorObserver) {
-        mToolbarColorObserverManager.setToolbarColorObserver(toolbarColorObserver);
+        mToolbarLayout.setToolbarColorObserver(toolbarColorObserver);
     }
 
     /**
@@ -633,7 +619,6 @@
         } else if (mStartSurfaceToolbarCoordinator != null) {
             mStartSurfaceToolbarCoordinator.setIncognitoStateProvider(provider);
         }
-        mToolbarColorObserverManager.setIncognitoStateProvider(provider);
     }
 
     /**
diff --git a/chrome/browser/ui/ash/assistant/assistant_browsertest.cc b/chrome/browser/ui/ash/assistant/assistant_browsertest.cc
index 58dfce1..c47763f 100644
--- a/chrome/browser/ui/ash/assistant/assistant_browsertest.cc
+++ b/chrome/browser/ui/ash/assistant/assistant_browsertest.cc
@@ -54,8 +54,13 @@
                              public testing::WithParamInterface<bool> {
  public:
   AssistantBrowserTest() {
-    if (GetParam())
-      feature_list_.InitAndEnableFeature(features::kEnableLibAssistantDlc);
+    if (GetParam()) {
+      feature_list_.InitWithFeatures(
+          /*enabled_features=*/{features::kEnableLibAssistantDlc},
+          /*disabled_features=*/{features::kEnableLibAssistantSandbox});
+    } else {
+      feature_list_.InitAndDisableFeature(features::kEnableLibAssistantSandbox);
+    }
 
     // Do not log to file in test. Otherwise multiple tests may create/delete
     // the log file at the same time. See http://crbug.com/1307868.
diff --git a/chrome/browser/ui/ash/assistant/assistant_timers_browsertest.cc b/chrome/browser/ui/ash/assistant/assistant_timers_browsertest.cc
index 3e18ed6a..d9a9c22c 100644
--- a/chrome/browser/ui/ash/assistant/assistant_timers_browsertest.cc
+++ b/chrome/browser/ui/ash/assistant/assistant_timers_browsertest.cc
@@ -188,11 +188,17 @@
 
 // AssistantTimersBrowserTest --------------------------------------------------
 
-class AssistantTimersBrowserTest : public MixinBasedInProcessBrowserTest {
+class AssistantTimersBrowserTest : public MixinBasedInProcessBrowserTest,
+                                   public testing::WithParamInterface<bool> {
  public:
   AssistantTimersBrowserTest() {
-    // TODO(b/190633242): enable sandbox in browser tests.
-    feature_list_.InitAndDisableFeature(features::kEnableLibAssistantSandbox);
+    if (GetParam()) {
+      feature_list_.InitWithFeatures(
+          /*enabled_features=*/{features::kEnableLibAssistantDlc},
+          /*disabled_features=*/{features::kEnableLibAssistantSandbox});
+    } else {
+      feature_list_.InitAndDisableFeature(features::kEnableLibAssistantSandbox);
+    }
 
     // Do not log to file in test. Otherwise multiple tests may create/delete
     // the log file at the same time. See http://crbug.com/1307868.
@@ -226,7 +232,7 @@
 
 // Timer notifications should be dismissed when disabling Assistant in settings.
 // Flaky. See https://crbug.com/1196564.
-IN_PROC_BROWSER_TEST_F(
+IN_PROC_BROWSER_TEST_P(
     AssistantTimersBrowserTest,
     DISABLED_ShouldDismissTimerNotificationsWhenDisablingAssistant) {
   tester()->StartAssistantAndWaitForReady();
@@ -257,7 +263,7 @@
 // Pressing the "STOP" action button in a timer notification should result in
 // the timer being removed.
 // Flaky. See https://crbug.com/1196564.
-IN_PROC_BROWSER_TEST_F(AssistantTimersBrowserTest,
+IN_PROC_BROWSER_TEST_P(AssistantTimersBrowserTest,
                        DISABLED_ShouldRemoveTimerWhenStoppingViaNotification) {
   tester()->StartAssistantAndWaitForReady();
 
@@ -298,7 +304,7 @@
 }
 
 // Verifies that timer notifications are ticked at regular intervals.
-IN_PROC_BROWSER_TEST_F(AssistantTimersBrowserTest,
+IN_PROC_BROWSER_TEST_P(AssistantTimersBrowserTest,
                        ShouldTickNotificationsAtRegularIntervals) {
   // Observe notifications.
   MockMessageCenterObserver mock;
@@ -388,4 +394,8 @@
   notification_update_run_loop.Run();
 }
 
+INSTANTIATE_TEST_SUITE_P(/* no label */,
+                         AssistantTimersBrowserTest,
+                         /*values=*/testing::Bool());
+
 }  // namespace ash::assistant
diff --git a/chrome/browser/ui/extensions/settings_overridden_params_providers_unittest.cc b/chrome/browser/ui/extensions/settings_overridden_params_providers_unittest.cc
index 9aab079..4263f04 100644
--- a/chrome/browser/ui/extensions/settings_overridden_params_providers_unittest.cc
+++ b/chrome/browser/ui/extensions/settings_overridden_params_providers_unittest.cc
@@ -43,14 +43,13 @@
   // Adds a new extension that overrides the NTP.
   const extensions::Extension* AddExtensionControllingNewTab(
       const char* name = "ntp override") {
-    base::Value chrome_url_overrides = base::Value::FromUniquePtrValue(
-        extensions::DictionaryBuilder().Set("newtab", "newtab.html").Build());
+    base::Value::Dict chrome_url_overrides =
+        extensions::DictionaryBuilder().Set("newtab", "newtab.html").Build();
     scoped_refptr<const extensions::Extension> extension =
         extensions::ExtensionBuilder(name)
             .SetLocation(extensions::mojom::ManifestLocation::kInternal)
-            .SetManifestKey(
-                "chrome_url_overrides",
-                base::Value::ToUniquePtrValue(std::move(chrome_url_overrides)))
+            .SetManifestKey("chrome_url_overrides",
+                            std::move(chrome_url_overrides))
             .Build();
 
     service()->AddExtension(extension.get());
diff --git a/chrome/browser/ui/views/apps/app_info_dialog/app_info_permissions_panel_unittest.cc b/chrome/browser/ui/views/apps/app_info_dialog/app_info_permissions_panel_unittest.cc
index 9722007..cdffe58e 100644
--- a/chrome/browser/ui/views/apps/app_info_dialog/app_info_permissions_panel_unittest.cc
+++ b/chrome/browser/ui/views/apps/app_info_dialog/app_info_permissions_panel_unittest.cc
@@ -39,7 +39,7 @@
  protected:
   AppInfoPermissionsPanelTest() {}
 
-  std::unique_ptr<base::DictionaryValue> ValidAppManifest() {
+  base::Value::Dict ValidAppManifest() {
     return extensions::DictionaryBuilder()
         .Set("name", "Test App Name")
         .Set("version", "2.0")
diff --git a/chrome/browser/ui/views/sharing_hub/screenshot/screenshot_captured_bubble.cc b/chrome/browser/ui/views/sharing_hub/screenshot/screenshot_captured_bubble.cc
index e3e447a..ac6c7d2 100644
--- a/chrome/browser/ui/views/sharing_hub/screenshot/screenshot_captured_bubble.cc
+++ b/chrome/browser/ui/views/sharing_hub/screenshot/screenshot_captured_bubble.cc
@@ -26,13 +26,9 @@
 #include "chrome/browser/ui/browser_navigator.h"
 #include "chrome/browser/ui/browser_navigator_params.h"
 #include "chrome/browser/ui/color/chrome_color_id.h"
-#include "chrome/browser/ui/tab_contents/core_tab_helper.h"
 #include "chrome/browser/ui/views/chrome_layout_provider.h"
 #include "chrome/common/webui_url_constants.h"
 #include "chrome/grit/generated_resources.h"
-#include "components/lens/lens_entrypoints.h"
-#include "components/lens/lens_features.h"
-#include "components/lens/lens_rendering_environment.h"
 #include "content/public/browser/download_manager.h"
 #include "content/public/browser/download_request_utils.h"
 #include "content/public/browser/web_contents.h"
@@ -50,8 +46,6 @@
 #include "ui/views/layout/table_layout_view.h"
 #include "ui/views/view.h"
 
-using content::WebContents;
-
 namespace {
 
 // Rendered image size, pixels.
@@ -81,14 +75,6 @@
              ->IsImageEditorAvailable();
 }
 
-bool IsSearchImageEnabled() {
-#if !BUILDFLAG(IS_ANDROID) && BUILDFLAG(GOOGLE_CHROME_BRANDING)
-  return lens::features::IsLensInScreenshotSharingEnabled();
-#else
-  return false;
-#endif
-}
-
 ScreenshotCapturedBubble::ScreenshotCapturedBubble(
     views::View* anchor_view,
     content::WebContents* web_contents,
@@ -184,15 +170,6 @@
               IDS_BROWSER_SHARING_SCREENSHOT_DIALOG_EDIT_BUTTON_LABEL))
           .Build();
 
-  auto search_image_button =
-      views::Builder<views::MdTextButton>()
-          .SetCallback(base::BindRepeating(
-              &ScreenshotCapturedBubble::SearchImageButtonPressed,
-              weak_factory_.GetWeakPtr()))
-          .SetText(l10n_util::GetStringUTF16(
-              IDS_BROWSER_SHARING_SCREENSHOT_DIALOG_SEARCH_IMAGE_BUTTON_LABEL))
-          .Build();
-
   auto download_button =
       views::Builder<views::MdTextButton>()
           .SetCallback(base::BindRepeating(
@@ -205,34 +182,19 @@
 
   auto download_row = views::Builder<views::TableLayoutView>();
   if (IsEditorInstalled()) {
-    download_row.AddColumn(
-        /* h_align */ views::LayoutAlignment::kStart,
-        /* v_align */ views::LayoutAlignment::kCenter,
-        /* horizontal_resize */ 1.0,
-        /* size_type */ views::TableLayout::ColumnSize::kUsePreferred,
-        /* fixed_width */ 0, /* min_width */ 0);
-  }
-
-  if (IsSearchImageEnabled()) {
-    download_row.AddColumn(
-        /* h_align */ views::LayoutAlignment::kStart,
-        /* v_align */ views::LayoutAlignment::kCenter,
-        /* horizontal_resize */ 1.0,
-        /* size_type */ views::TableLayout::ColumnSize::kUsePreferred,
-        /* fixed_width */ 0, /* min_width */ 0);
-  }
-
-  if (IsEditorInstalled() || IsSearchImageEnabled()) {
-    const int kPaddingEditSearchDownloadButtonPx =
-        kImageWidthPx -
-        (IsEditorInstalled() ? edit_button->CalculatePreferredSize().width()
-                             : 0) -
-        (IsSearchImageEnabled()
-             ? search_image_button->CalculatePreferredSize().width()
-             : 0) -
+    const int kPaddingEditDownloadButtonPx =
+        kImageWidthPx - edit_button->CalculatePreferredSize().width() -
         download_button->CalculatePreferredSize().width();
-    download_row.AddPaddingColumn(views::TableLayout::kFixedSize,
-                                  kPaddingEditSearchDownloadButtonPx);
+
+    download_row
+        .AddColumn(
+            /* h_align */ views::LayoutAlignment::kStart,
+            /* v_align */ views::LayoutAlignment::kCenter,
+            /* horizontal_resize */ 1.0,
+            /* size_type */ views::TableLayout::ColumnSize::kUsePreferred,
+            /* fixed_width */ 0, /* min_width */ 0)
+        .AddPaddingColumn(views::TableLayout::kFixedSize,
+                          kPaddingEditDownloadButtonPx);
   }
 
   // Column for download button
@@ -246,11 +208,6 @@
         views::Builder<views::MdTextButton>(std::move(edit_button))
             .CopyAddressTo(&edit_button_));
   }
-  if (IsSearchImageEnabled()) {
-    download_row.AddChild(
-        views::Builder<views::MdTextButton>(std::move(search_image_button))
-            .CopyAddressTo(&search_image_button_));
-  }
   download_row.AddChild(
       views::Builder<views::MdTextButton>(std::move(download_button))
           .CopyAddressTo(&download_button_));
@@ -335,26 +292,6 @@
                      weak_factory_.GetWeakPtr()));
 }
 
-void ScreenshotCapturedBubble::SearchImageButtonPressed() {
-  // If EnablePersistentBubble() is true, we do not close the screenshot bubble
-  set_close_on_deactivate(!lens::features::EnablePersistentBubble());
-
-  CoreTabHelper::FromWebContents(web_contents_.get())
-      ->SearchWithLens(image_, GetImageSize(),
-                       lens::EntryPoint::CHROME_SCREENSHOT_SEARCH,
-                       /* is_region_search_request= */ false,
-                       /* is_side_panel_enabled_for_feature= */
-                       lens::features::UseSidePanelForScreenshotSharing());
-
-  // Need to manually close the screenshot bubble if side panel is enabled
-  if (lens::features::UseSidePanelForScreenshotSharing() &&
-      !lens::features::EnablePersistentBubble()) {
-    CloseBubble();
-  }
-
-  set_close_on_deactivate(true);
-}
-
 void ScreenshotCapturedBubble::NavigateToImageEditor(
     const base::FilePath& screenshot_file_path) {
   auto screenshot_data =
diff --git a/chrome/browser/ui/views/sharing_hub/screenshot/screenshot_captured_bubble.h b/chrome/browser/ui/views/sharing_hub/screenshot/screenshot_captured_bubble.h
index 1730914..f3659129 100644
--- a/chrome/browser/ui/views/sharing_hub/screenshot/screenshot_captured_bubble.h
+++ b/chrome/browser/ui/views/sharing_hub/screenshot/screenshot_captured_bubble.h
@@ -69,8 +69,6 @@
 
   void EditButtonPressed();
 
-  void SearchImageButtonPressed();
-
   gfx::Size GetImageSize();
 
   // Requests navigation to the image editor page.
@@ -92,7 +90,6 @@
   views::ImageView* image_view_ = nullptr;
   views::MdTextButton* download_button_ = nullptr;
   views::LabelButton* edit_button_ = nullptr;
-  views::LabelButton* search_image_button_ = nullptr;
 
   base::WeakPtrFactory<ScreenshotCapturedBubble> weak_factory_{this};
 };
diff --git a/chrome/browser/ui/webui/ash/drive_internals_ui.cc b/chrome/browser/ui/webui/ash/drive_internals_ui.cc
index 9ca3542..35605d00 100644
--- a/chrome/browser/ui/webui/ash/drive_internals_ui.cc
+++ b/chrome/browser/ui/webui/ash/drive_internals_ui.cc
@@ -623,23 +623,18 @@
   void OnSetupProgress(
       const drivefs::pinning::SetupProgress& progress) override {
     using drivefs::pinning::HumanReadableSize;
-    VLOG(1) << "Stage = " << progress.stage;
-    VLOG(1) << "Error = " << progress.error;
-    VLOG(1) << "Free space = "
-            << HumanReadableSize(progress.available_disk_space);
-    VLOG(1) << "Required space = "
-            << HumanReadableSize(progress.required_disk_space);
-    VLOG(1) << "Pinned space = "
-            << HumanReadableSize(progress.pinned_disk_space);
 
     base::Value::Dict setup_progress;
     setup_progress.Set("stage", ToString(progress.stage));
     setup_progress.Set("setupError", ToString(progress.error));
-    setup_progress.Set("availableDiskSpace",
-                       ToString(progress.available_disk_space));
-    setup_progress.Set("requiredDiskSpace",
-                       ToString(progress.required_disk_space));
-    setup_progress.Set("pinnedDiskSpace", ToString(progress.pinned_disk_space));
+    setup_progress.Set(
+        "availableDiskSpace",
+        ToString(HumanReadableSize(progress.available_disk_space)));
+    setup_progress.Set(
+        "requiredDiskSpace",
+        ToString(HumanReadableSize(progress.required_disk_space)));
+    setup_progress.Set("pinnedDiskSpace",
+                       ToString(HumanReadableSize(progress.pinned_disk_space)));
     MaybeCallJavascript("onBulkPinningProgress",
                         base::Value(std::move(setup_progress)));
   }
diff --git a/chrome/browser/ui/webui/settings/ash/device_storage_handler_unittest.cc b/chrome/browser/ui/webui/settings/ash/device_storage_handler_unittest.cc
index 17d9879f..b27fb12e 100644
--- a/chrome/browser/ui/webui/settings/ash/device_storage_handler_unittest.cc
+++ b/chrome/browser/ui/webui/settings/ash/device_storage_handler_unittest.cc
@@ -42,16 +42,6 @@
 
 namespace {
 
-class TestStorageHandler : public StorageHandler {
- public:
-  explicit TestStorageHandler(Profile* profile,
-                              content::WebUIDataSource* html_source)
-      : StorageHandler(profile, html_source) {}
-
-  // Pull WebUIMessageHandler::set_web_ui() into public so tests can call it.
-  using StorageHandler::set_web_ui;
-};
-
 class MockNewWindowDelegate : public testing::NiceMock<TestNewWindowDelegate> {
  public:
   // TestNewWindowDelegate:
@@ -94,7 +84,7 @@
     content::WebUIDataSource* html_source =
         content::WebUIDataSource::CreateAndAdd(profile_,
                                                chrome::kChromeUIOSSettingsHost);
-    auto handler = std::make_unique<TestStorageHandler>(profile_, html_source);
+    auto handler = std::make_unique<StorageHandler>(profile_, html_source);
     handler_ = handler.get();
     web_ui_ = std::make_unique<content::TestWebUI>();
     web_ui_->AddMessageHandler(std::move(handler));
@@ -214,7 +204,7 @@
     ASSERT_EQ(expected_size, stat.st_size);
   }
 
-  TestStorageHandler* handler_;
+  StorageHandler* handler_;
   std::unique_ptr<content::TestWebUI> web_ui_;
   content::BrowserTaskEnvironment task_environment_;
   std::unique_ptr<TestingProfileManager> profile_manager_;
diff --git a/chrome/browser/ui/webui/settings/ash/os_apps_page/app_notification_handler.cc b/chrome/browser/ui/webui/settings/ash/os_apps_page/app_notification_handler.cc
index abcd60a..df71a64 100644
--- a/chrome/browser/ui/webui/settings/ash/os_apps_page/app_notification_handler.cc
+++ b/chrome/browser/ui/webui/settings/ash/os_apps_page/app_notification_handler.cc
@@ -7,11 +7,13 @@
 #include <utility>
 
 #include "ash/public/cpp/message_center_ash.h"
+#include "ash/public/cpp/new_window_delegate.h"
 #include "base/logging.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/profiles/profile.h"
 #include "chrome/browser/ui/webui/settings/ash/os_apps_page/mojom/app_type_mojom_traits.h"
+#include "chrome/common/url_constants.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/cpp/permission.h"
@@ -105,6 +107,13 @@
   app_service_proxy_->SetPermission(app_id, std::move(permission));
 }
 
+void AppNotificationHandler::OpenBrowserNotificationSettings() {
+  ash::NewWindowDelegate::GetPrimary()->OpenUrl(
+      GURL(chrome::kAppNotificationsBrowserSettingsURL),
+      ash::NewWindowDelegate::OpenUrlFrom::kUserInteraction,
+      ash::NewWindowDelegate::Disposition::kSwitchToTab);
+}
+
 void AppNotificationHandler::GetApps(GetAppsCallback callback) {
   std::move(callback).Run(GetAppList());
 }
diff --git a/chrome/browser/ui/webui/settings/ash/os_apps_page/app_notification_handler.h b/chrome/browser/ui/webui/settings/ash/os_apps_page/app_notification_handler.h
index 1f7a9013..885b1ee 100644
--- a/chrome/browser/ui/webui/settings/ash/os_apps_page/app_notification_handler.h
+++ b/chrome/browser/ui/webui/settings/ash/os_apps_page/app_notification_handler.h
@@ -44,6 +44,7 @@
                                  apps::PermissionPtr permission) override;
   void GetApps(GetAppsCallback callback) override;
   void GetQuietMode(GetQuietModeCallback callback) override;
+  void OpenBrowserNotificationSettings() override;
 
   // apps::AppRegistryCache::Observer:
   void OnAppUpdate(const apps::AppUpdate& update) override;
diff --git a/chrome/browser/ui/webui/settings/ash/os_apps_page/app_notification_handler_unittest.cc b/chrome/browser/ui/webui/settings/ash/os_apps_page/app_notification_handler_unittest.cc
index 511d744f..91722b4 100644
--- a/chrome/browser/ui/webui/settings/ash/os_apps_page/app_notification_handler_unittest.cc
+++ b/chrome/browser/ui/webui/settings/ash/os_apps_page/app_notification_handler_unittest.cc
@@ -9,15 +9,18 @@
 #include <vector>
 
 #include "ash/public/cpp/message_center_ash.h"
+#include "ash/public/cpp/test/test_new_window_delegate.h"
 #include "base/logging.h"
 #include "chrome/browser/apps/app_service/app_service_proxy.h"
 #include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
 #include "chrome/browser/ui/webui/settings/ash/os_apps_page/mojom/app_notification_handler.mojom.h"
+#include "chrome/common/url_constants.h"
 #include "chrome/test/base/testing_profile.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/permission.h"
 #include "content/public/test/browser_task_environment.h"
+#include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/abseil-cpp/absl/types/variant.h"
 
@@ -87,6 +90,15 @@
       this};
 };
 
+class MockNewWindowDelegate : public testing::NiceMock<TestNewWindowDelegate> {
+ public:
+  // TestNewWindowDelegate:
+  MOCK_METHOD(void,
+              OpenUrl,
+              (const GURL& url, OpenUrlFrom from, Disposition disposition),
+              (override));
+};
+
 }  // namespace
 
 class AppNotificationHandlerTest : public testing::Test {
@@ -105,21 +117,34 @@
 
     observer_ = std::make_unique<AppNotificationHandlerTestObserver>();
     handler_->AddObserver(observer_->GenerateRemote());
+
+    auto instance = std::make_unique<MockNewWindowDelegate>();
+    auto primary = std::make_unique<MockNewWindowDelegate>();
+    new_window_delegate_primary_ = primary.get();
+    new_window_provider_ = std::make_unique<TestNewWindowDelegateProvider>(
+        std::move(instance), std::move(primary));
   }
 
   void TearDown() override {
+    new_window_provider_.reset();
     handler_.reset();
     app_service_proxy_.reset();
     MessageCenterAsh::SetForTesting(nullptr);
   }
 
  protected:
+  MockNewWindowDelegate* new_window_delegate_primary_;
+
   AppNotificationHandlerTestObserver* observer() { return observer_.get(); }
 
   void SetQuietModeState(bool quiet_mode_enabled) {
     handler_->SetQuietMode(quiet_mode_enabled);
   }
 
+  void OpenBrowserNotificationSettings() {
+    handler_->OpenBrowserNotificationSettings();
+  }
+
   void CreateAndStoreFakeApp(std::string fake_id,
                              apps::AppType app_type,
                              apps::PermissionType permission_type,
@@ -164,6 +189,7 @@
   std::unique_ptr<apps::AppServiceProxy> app_service_proxy_;
   FakeMessageCenterAsh message_center_ash_;
   std::unique_ptr<AppNotificationHandlerTestObserver> observer_;
+  std::unique_ptr<TestNewWindowDelegateProvider> new_window_provider_;
 };
 
 // Tests for update of in_quiet_mode_ variable by MessageCenterAsh observer
@@ -261,4 +287,13 @@
                                    ->notification_permission->value->value));
 }
 
+TEST_F(AppNotificationHandlerTest, TestOpenBrowserNotificationSettings) {
+  EXPECT_CALL(*new_window_delegate_primary_,
+              OpenUrl(GURL(chrome::kAppNotificationsBrowserSettingsURL),
+                      ash::NewWindowDelegate::OpenUrlFrom::kUserInteraction,
+                      ash::NewWindowDelegate::Disposition::kSwitchToTab));
+  base::Value::List empty_args;
+  OpenBrowserNotificationSettings();
+}
+
 }  // namespace ash::settings
diff --git a/chrome/browser/ui/webui/settings/ash/os_apps_page/mojom/app_notification_handler.mojom b/chrome/browser/ui/webui/settings/ash/os_apps_page/mojom/app_notification_handler.mojom
index a5cc55f..308caf7 100644
--- a/chrome/browser/ui/webui/settings/ash/os_apps_page/mojom/app_notification_handler.mojom
+++ b/chrome/browser/ui/webui/settings/ash/os_apps_page/mojom/app_notification_handler.mojom
@@ -57,6 +57,9 @@
 
   // Get the Quiet Mode state.
   GetQuietMode() => (bool enabled);
+
+  // Request the browser to open its notification settings.
+  OpenBrowserNotificationSettings();
 };
 
 // Frontend interface.
@@ -68,4 +71,4 @@
   OnNotificationAppChanged(App app);
   // Notifies client when the DoNotDisturb (QuietMode) state changes.
   OnQuietModeChanged(bool enabled);
-};
\ No newline at end of file
+};
diff --git a/chrome/browser/ui/webui/settings/ash/search_engines_handler_unittest.cc b/chrome/browser/ui/webui/settings/ash/search_engines_handler_unittest.cc
new file mode 100644
index 0000000..14ca9b0c
--- /dev/null
+++ b/chrome/browser/ui/webui/settings/ash/search_engines_handler_unittest.cc
@@ -0,0 +1,92 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/settings/search_engines_handler.h"
+
+#include "ash/public/cpp/test/test_new_window_delegate.h"
+#include "chrome/browser/search_engines/template_url_service_factory.h"
+#include "chrome/common/chrome_constants.h"
+#include "chrome/common/webui_url_constants.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "chrome/test/base/testing_profile_manager.h"
+#include "content/public/test/browser_task_environment.h"
+#include "content/public/test/test_web_ui.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace ash::settings {
+
+namespace {
+
+class MockNewWindowDelegate : public testing::NiceMock<TestNewWindowDelegate> {
+ public:
+  // TestNewWindowDelegate:
+  MOCK_METHOD(void,
+              OpenUrl,
+              (const GURL& url, OpenUrlFrom from, Disposition disposition),
+              (override));
+};
+
+class SearchEnginesHandlerTest : public testing::Test {
+ public:
+  SearchEnginesHandlerTest() = default;
+  SearchEnginesHandlerTest(const SearchEnginesHandlerTest&) = delete;
+  SearchEnginesHandlerTest& operator=(const SearchEnginesHandlerTest&) = delete;
+  ~SearchEnginesHandlerTest() override = default;
+
+  void SetUp() override {
+    // Initialize profile.
+    profile_manager_ = std::make_unique<TestingProfileManager>(
+        TestingBrowserProcess::GetGlobal());
+    ASSERT_TRUE(profile_manager_->SetUp());
+    Profile* profile = profile_manager_->CreateTestingProfile(
+        chrome::kInitialProfile,
+        {{TemplateURLServiceFactory::GetInstance(),
+          base::BindRepeating(&TemplateURLServiceFactory::BuildInstanceFor)}});
+
+    // Initialize handler and webui.
+    auto handler = std::make_unique<::settings::SearchEnginesHandler>(profile);
+    handler_ = handler.get();
+    web_ui_ = std::make_unique<content::TestWebUI>();
+    web_ui_->AddMessageHandler(std::move(handler));
+    handler_->AllowJavascriptForTesting();
+
+    // Initialize NewWindowDelegate things.
+    auto instance = std::make_unique<MockNewWindowDelegate>();
+    auto primary = std::make_unique<MockNewWindowDelegate>();
+    new_window_delegate_primary_ = primary.get();
+    new_window_provider_ = std::make_unique<TestNewWindowDelegateProvider>(
+        std::move(instance), std::move(primary));
+  }
+
+  void TearDown() override {
+    new_window_provider_.reset();
+    web_ui_.reset();
+  }
+
+ protected:
+  std::unique_ptr<content::TestWebUI> web_ui_;
+  MockNewWindowDelegate* new_window_delegate_primary_;
+
+ private:
+  content::BrowserTaskEnvironment task_environment_;
+  ::settings::SearchEnginesHandler* handler_;
+  std::unique_ptr<TestingProfileManager> profile_manager_;
+  std::unique_ptr<TestNewWindowDelegateProvider> new_window_provider_;
+};
+
+TEST_F(SearchEnginesHandlerTest, OpenBrowserSearchSettings) {
+  EXPECT_CALL(
+      *new_window_delegate_primary_,
+      OpenUrl(
+          GURL(chrome::kChromeUISettingsURL).Resolve(chrome::kSearchSubPage),
+          ash::NewWindowDelegate::OpenUrlFrom::kUserInteraction,
+          ash::NewWindowDelegate::Disposition::kSwitchToTab));
+  base::Value::List empty_args;
+  web_ui_->HandleReceivedMessage("openBrowserSearchSettings", empty_args);
+}
+
+}  // namespace
+
+}  // namespace ash::settings
diff --git a/chrome/browser/ui/webui/settings/search_engines_handler.cc b/chrome/browser/ui/webui/settings/search_engines_handler.cc
index 7f7577a..6e5b436 100644
--- a/chrome/browser/ui/webui/settings/search_engines_handler.cc
+++ b/chrome/browser/ui/webui/settings/search_engines_handler.cc
@@ -21,6 +21,7 @@
 #include "chrome/browser/ui/search_engines/template_url_table_model.h"
 #include "chrome/common/pref_names.h"
 #include "chrome/common/url_constants.h"
+#include "chrome/common/webui_url_constants.h"
 #include "chrome/grit/generated_resources.h"
 #include "components/prefs/pref_service.h"
 #include "components/search_engines/template_url.h"
@@ -31,6 +32,10 @@
 #include "extensions/browser/management_policy.h"
 #include "extensions/common/extension.h"
 
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+#include "ash/public/cpp/new_window_delegate.h"
+#endif
+
 namespace {
 // The following strings need to match with the IDs of the text input elements
 // at settings/search_engines_page/search_engine_edit_dialog.html.
@@ -92,6 +97,13 @@
       base::BindRepeating(
           &SearchEnginesHandler::HandleSearchEngineEditCompleted,
           base::Unretained(this)));
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  web_ui()->RegisterMessageCallback(
+      "openBrowserSearchSettings",
+      base::BindRepeating(
+          &SearchEnginesHandler::HandleOpenBrowserSearchSettings,
+          base::Unretained(this)));
+#endif
 }
 
 void SearchEnginesHandler::OnJavascriptAllowed() {
@@ -380,4 +392,14 @@
   }
 }
 
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+void SearchEnginesHandler::HandleOpenBrowserSearchSettings(
+    const base::Value::List& args) {
+  ash::NewWindowDelegate::GetPrimary()->OpenUrl(
+      GURL(chrome::kChromeUISettingsURL).Resolve(chrome::kSearchSubPage),
+      ash::NewWindowDelegate::OpenUrlFrom::kUserInteraction,
+      ash::NewWindowDelegate::Disposition::kSwitchToTab);
+}
+#endif
+
 }  // namespace settings
diff --git a/chrome/browser/ui/webui/settings/search_engines_handler.h b/chrome/browser/ui/webui/settings/search_engines_handler.h
index 155ba21..9f389106 100644
--- a/chrome/browser/ui/webui/settings/search_engines_handler.h
+++ b/chrome/browser/ui/webui/settings/search_engines_handler.h
@@ -86,6 +86,11 @@
   // Called from WebUI.
   void HandleSearchEngineEditCompleted(const base::Value::List& args);
 
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  // Request the browser to open its search settings.
+  void HandleOpenBrowserSearchSettings(const base::Value::List& args);
+#endif
+
   // Returns a dictionary to pass to WebUI representing the given search engine.
   base::Value::Dict CreateDictionaryForEngine(size_t index, bool is_default);
 
diff --git a/chrome/browser/ui/webui/side_panel/bookmarks/bookmarks.mojom b/chrome/browser/ui/webui/side_panel/bookmarks/bookmarks.mojom
index e5eec242..07033e0e 100644
--- a/chrome/browser/ui/webui/side_panel/bookmarks/bookmarks.mojom
+++ b/chrome/browser/ui/webui/side_panel/bookmarks/bookmarks.mojom
@@ -25,6 +25,22 @@
   // Bookmarks the current active tab in the given folder.
   BookmarkCurrentTabInFolder(int64 folder_id);
 
+  // Opens the bookmark specified by node_id in a new background tab, using the
+  // same logic as the native bookmarks context menu.
+  ExecuteOpenInNewTabCommand(int64 node_id, ActionSource source);
+
+  // Opens the bookmark specified by node_id in a new window, using the same
+  // logic as the native bookmarks context menu.
+  ExecuteOpenInNewWindowCommand(int64 node_id, ActionSource source);
+
+  // Opens the bookmark specified by node_id in a new incognito window, using
+  // the same logic as the native bookmarks context menu.
+  ExecuteOpenInIncognitoWindowCommand(int64 node_id, ActionSource source);
+
+  // Deletes bookmark specified by node_id, using the same logic as the native
+  // bookmarks context menu.
+  ExecuteDeleteCommand(int64 node_id, ActionSource source);
+
   // Opens the bookmark specified by node_id. Passes the parent folder
   // depth for metrics collection and the action source to identify
   // from which surface this request is made.
diff --git a/chrome/browser/ui/webui/side_panel/bookmarks/bookmarks_page_handler.cc b/chrome/browser/ui/webui/side_panel/bookmarks/bookmarks_page_handler.cc
index 23c328e8..1a414a9 100644
--- a/chrome/browser/ui/webui/side_panel/bookmarks/bookmarks_page_handler.cc
+++ b/chrome/browser/ui/webui/side_panel/bookmarks/bookmarks_page_handler.cc
@@ -3,12 +3,10 @@
 // found in the LICENSE file.
 
 #include "chrome/browser/ui/webui/side_panel/bookmarks/bookmarks_page_handler.h"
-#include <string>
 
 #include "base/memory/ptr_util.h"
 #include "base/metrics/user_metrics.h"
 #include "base/metrics/user_metrics_action.h"
-#include "base/strings/utf_string_conversions.h"
 #include "chrome/app/chrome_command_ids.h"
 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
 #include "chrome/browser/commerce/shopping_service_factory.h"
@@ -135,6 +133,27 @@
       shopping_list_controller_;
 };
 
+std::unique_ptr<BookmarkContextMenu> ContextMenuFromNode(
+    int64_t node_id,
+    base::WeakPtr<ui::MojoBubbleWebUIController::Embedder> embedder,
+    side_panel::mojom::ActionSource source) {
+  Browser* browser = chrome::FindLastActive();
+  if (!browser) {
+    return nullptr;
+  }
+
+  bookmarks::BookmarkModel* bookmark_model =
+      BookmarkModelFactory::GetForBrowserContext(browser->profile());
+  const bookmarks::BookmarkNode* bookmark =
+      bookmarks::GetBookmarkNodeByID(bookmark_model, node_id);
+  if (!bookmark) {
+    return nullptr;
+  }
+
+  return std::make_unique<BookmarkContextMenu>(browser, embedder, bookmark,
+                                               source);
+}
+
 }  // namespace
 
 BookmarksPageHandler::BookmarksPageHandler(
@@ -157,6 +176,54 @@
   chrome::BookmarkCurrentTabInFolder(browser, folder_id);
 }
 
+void BookmarksPageHandler::ExecuteOpenInNewTabCommand(
+    int64_t node_id,
+    side_panel::mojom::ActionSource source) {
+  auto embedder =
+      bookmarks_ui_ ? bookmarks_ui_->embedder() : reading_list_ui_->embedder();
+  std::unique_ptr<BookmarkContextMenu> contextMenu =
+      ContextMenuFromNode(node_id, embedder, source);
+  if (contextMenu) {
+    contextMenu->ExecuteCommand(IDC_BOOKMARK_BAR_OPEN_ALL, 0);
+  }
+}
+
+void BookmarksPageHandler::ExecuteOpenInNewWindowCommand(
+    int64_t node_id,
+    side_panel::mojom::ActionSource source) {
+  auto embedder =
+      bookmarks_ui_ ? bookmarks_ui_->embedder() : reading_list_ui_->embedder();
+  std::unique_ptr<BookmarkContextMenu> contextMenu =
+      ContextMenuFromNode(node_id, embedder, source);
+  if (contextMenu) {
+    contextMenu->ExecuteCommand(IDC_BOOKMARK_BAR_OPEN_ALL_NEW_WINDOW, 0);
+  }
+}
+
+void BookmarksPageHandler::ExecuteOpenInIncognitoWindowCommand(
+    int64_t node_id,
+    side_panel::mojom::ActionSource source) {
+  auto embedder =
+      bookmarks_ui_ ? bookmarks_ui_->embedder() : reading_list_ui_->embedder();
+  std::unique_ptr<BookmarkContextMenu> contextMenu =
+      ContextMenuFromNode(node_id, embedder, source);
+  if (contextMenu) {
+    contextMenu->ExecuteCommand(IDC_BOOKMARK_BAR_OPEN_ALL_INCOGNITO, 0);
+  }
+}
+
+void BookmarksPageHandler::ExecuteDeleteCommand(
+    int64_t node_id,
+    side_panel::mojom::ActionSource source) {
+  auto embedder =
+      bookmarks_ui_ ? bookmarks_ui_->embedder() : reading_list_ui_->embedder();
+  std::unique_ptr<BookmarkContextMenu> contextMenu =
+      ContextMenuFromNode(node_id, embedder, source);
+  if (contextMenu) {
+    contextMenu->ExecuteCommand(IDC_BOOKMARK_BAR_REMOVE, 0);
+  }
+}
+
 void BookmarksPageHandler::OpenBookmark(
     int64_t node_id,
     int32_t parent_folder_depth,
@@ -195,22 +262,16 @@
   if (!base::StringToInt64(id_string, &id))
     return;
 
-  Browser* browser = chrome::FindLastActive();
-  if (!browser)
-    return;
-
-  bookmarks::BookmarkModel* bookmark_model =
-      BookmarkModelFactory::GetForBrowserContext(browser->profile());
-  const bookmarks::BookmarkNode* bookmark =
-      bookmarks::GetBookmarkNodeByID(bookmark_model, id);
-  if (!bookmark)
-    return;
-
   auto embedder =
       bookmarks_ui_ ? bookmarks_ui_->embedder() : reading_list_ui_->embedder();
+
   if (embedder) {
-    embedder->ShowContextMenu(point, std::make_unique<BookmarkContextMenu>(
-                                         browser, embedder, bookmark, source));
+    std::unique_ptr<BookmarkContextMenu> contextMenu =
+        ContextMenuFromNode(id, embedder, source);
+    if (contextMenu) {
+      embedder->ShowContextMenu(point,
+                                ContextMenuFromNode(id, embedder, source));
+    }
   }
 }
 
diff --git a/chrome/browser/ui/webui/side_panel/bookmarks/bookmarks_page_handler.h b/chrome/browser/ui/webui/side_panel/bookmarks/bookmarks_page_handler.h
index 6779e16a..c635740 100644
--- a/chrome/browser/ui/webui/side_panel/bookmarks/bookmarks_page_handler.h
+++ b/chrome/browser/ui/webui/side_panel/bookmarks/bookmarks_page_handler.h
@@ -27,6 +27,17 @@
 
   // side_panel::mojom::BookmarksPageHandler:
   void BookmarkCurrentTabInFolder(int64_t folder_id) override;
+  void ExecuteOpenInNewTabCommand(
+      int64_t node_id,
+      side_panel::mojom::ActionSource source) override;
+  void ExecuteOpenInNewWindowCommand(
+      int64_t node_id,
+      side_panel::mojom::ActionSource source) override;
+  void ExecuteOpenInIncognitoWindowCommand(
+      int64_t node_id,
+      side_panel::mojom::ActionSource source) override;
+  void ExecuteDeleteCommand(int64_t node_id,
+                            side_panel::mojom::ActionSource source) override;
   void OpenBookmark(int64_t node_id,
                     int32_t parent_folder_depth,
                     ui::mojom::ClickModifiersPtr click_modifiers,
diff --git a/chrome/browser/ui/webui/side_panel/bookmarks/bookmarks_side_panel_ui.cc b/chrome/browser/ui/webui/side_panel/bookmarks/bookmarks_side_panel_ui.cc
index 352804ad..d2b1fef1 100644
--- a/chrome/browser/ui/webui/side_panel/bookmarks/bookmarks_side_panel_ui.cc
+++ b/chrome/browser/ui/webui/side_panel/bookmarks/bookmarks_side_panel_ui.cc
@@ -81,6 +81,8 @@
       {"clearSearch", IDS_BOOKMARK_MANAGER_CLEAR_SEARCH},
       {"selectedBookmarkCount", IDS_BOOKMARK_MANAGER_ITEMS_SELECTED},
       {"menuOpenNewTab", IDS_BOOKMARK_MANAGER_MENU_OPEN_IN_NEW_TAB},
+      {"menuOpenNewWindow", IDS_BOOKMARK_MANAGER_MENU_OPEN_IN_NEW_WINDOW},
+      {"menuOpenIncognito", IDS_BOOKMARK_MANAGER_MENU_OPEN_INCOGNITO},
       {"newFolderTitle", IDS_BOOKMARK_EDITOR_NEW_FOLDER_NAME},
   };
   for (const auto& str : kLocalizedStrings)
@@ -97,6 +99,7 @@
                      base::FeatureList::IsEnabled(features::kUnifiedSidePanel));
 
   source->AddBoolean("guestMode", profile->IsGuestSession());
+  source->AddBoolean("incognitoMode", profile->IsIncognitoProfile());
 
   bookmarks::BookmarkModel* bookmark_model =
       BookmarkModelFactory::GetForBrowserContext(profile);
diff --git a/chrome/browser/ui/webui/side_panel/reading_list/reading_list_ui.cc b/chrome/browser/ui/webui/side_panel/reading_list/reading_list_ui.cc
index fe11933..c94e7d01 100644
--- a/chrome/browser/ui/webui/side_panel/reading_list/reading_list_ui.cc
+++ b/chrome/browser/ui/webui/side_panel/reading_list/reading_list_ui.cc
@@ -99,6 +99,8 @@
       {"clearSearch", IDS_BOOKMARK_MANAGER_CLEAR_SEARCH},
       {"selectedBookmarkCount", IDS_BOOKMARK_MANAGER_ITEMS_SELECTED},
       {"menuOpenNewTab", IDS_BOOKMARK_MANAGER_MENU_OPEN_IN_NEW_TAB},
+      {"menuOpenNewWindow", IDS_BOOKMARK_MANAGER_MENU_OPEN_IN_NEW_WINDOW},
+      {"menuOpenIncognito", IDS_BOOKMARK_MANAGER_MENU_OPEN_INCOGNITO},
       {"newFolderTitle", IDS_BOOKMARK_EDITOR_NEW_FOLDER_NAME},
   };
   for (const auto& str : kLocalizedStrings)
@@ -129,6 +131,7 @@
                      base::FeatureList::IsEnabled(features::kUnifiedSidePanel));
 
   source->AddBoolean("guestMode", profile->IsGuestSession());
+  source->AddBoolean("incognitoMode", profile->IsIncognitoProfile());
 
   source->AddBoolean(
       "showPowerBookmarks",
diff --git a/chrome/browser/webauthn/android/java/src/org/chromium/chrome/browser/webauthn/CableAuthenticatorModuleProvider.java b/chrome/browser/webauthn/android/java/src/org/chromium/chrome/browser/webauthn/CableAuthenticatorModuleProvider.java
index b403cf1..a943886f1 100644
--- a/chrome/browser/webauthn/android/java/src/org/chromium/chrome/browser/webauthn/CableAuthenticatorModuleProvider.java
+++ b/chrome/browser/webauthn/android/java/src/org/chromium/chrome/browser/webauthn/CableAuthenticatorModuleProvider.java
@@ -12,7 +12,6 @@
 import android.content.Intent;
 import android.content.res.Resources;
 import android.graphics.drawable.Drawable;
-import android.os.Build;
 import android.os.Bundle;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -90,17 +89,6 @@
                 .setText(getResources().getString(
                         R.string.cablev2_error_code, INSTALL_FAILURE_ERROR_CODE));
 
-        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
-            // While the device will not be advertised in Sync if the Android
-            // version is too old, this case can occur in the QR flow because
-            // there's no version restriction on Play Services forwarding the
-            // `Intent`.
-            ((TextView) mErrorView.findViewById(R.id.error_description))
-                    .setText(getResources().getString(
-                            R.string.menu_update_unsupported_summary_default));
-            return mErrorView;
-        }
-
         ((TextView) mErrorView.findViewById(R.id.error_description))
                 .setText(getResources().getString(R.string.cablev2_error_generic));
 
@@ -242,8 +230,7 @@
     public static boolean canDeviceSupportCable() {
         // This function will be run on a background thread.
 
-        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N
-                || BluetoothAdapter.getDefaultAdapter() == null) {
+        if (BluetoothAdapter.getDefaultAdapter() == null) {
             return false;
         }
 
diff --git a/chrome/build/mac-arm.pgo.txt b/chrome/build/mac-arm.pgo.txt
index bde5211..4b49f4aa 100644
--- a/chrome/build/mac-arm.pgo.txt
+++ b/chrome/build/mac-arm.pgo.txt
@@ -1 +1 @@
-chrome-mac-arm-main-1671709962-5529a0e58e5c9b89902222777c2bda5684a66564.profdata
+chrome-mac-arm-main-1671731872-db8e3d4f4de670703661e481bb29570c48a76e6d.profdata
diff --git a/chrome/build/mac.pgo.txt b/chrome/build/mac.pgo.txt
index 024515f..4652a6b7 100644
--- a/chrome/build/mac.pgo.txt
+++ b/chrome/build/mac.pgo.txt
@@ -1 +1 @@
-chrome-mac-main-1671731872-e64a2792eafde6af466e698a236a817d2fcb14c8.profdata
+chrome-mac-main-1671753564-41b71bec5177319c3359b2eb986023a9c110138e.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index d1e38d61..3f55b21 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-main-1671721064-189024d4b81230d96d5ab1605f78c6009bc20cd6.profdata
+chrome-win32-main-1671742794-e0b6d08d0f339d051a2ee6141f4d9be7b903a76e.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index 106a9d9..05f1f97 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-main-1671709962-86e5c2f479991b3710dd2b796a8a5ecc2dbf3c7c.profdata
+chrome-win64-main-1671742794-968e29ba984d53c216ce69c46e2821893685bdb5.profdata
diff --git a/chrome/chrome_cleaner/components/system_restore_point_component.cc b/chrome/chrome_cleaner/components/system_restore_point_component.cc
index ec3ee9f..2626a15 100644
--- a/chrome/chrome_cleaner/components/system_restore_point_component.cc
+++ b/chrome/chrome_cleaner/components/system_restore_point_component.cc
@@ -10,7 +10,6 @@
 #include "base/files/file_path.h"
 #include "base/logging.h"
 #include "base/win/registry.h"
-#include "base/win/windows_version.h"
 
 namespace {
 
@@ -71,19 +70,16 @@
   if (!set_restore_point_info_fn_)
     return;
 
-  // On Windows8, a registry value needs to be created in order for restore
-  // points to be deterministically created. Attempt to create this value, but
-  // continue with the restore point anyway even if doing so fails. See
-  // http://msdn.microsoft.com/en-us/library/windows/desktop/aa378941.aspx for
-  // more information.
-  if (base::win::GetVersion() >= base::win::Version::WIN8) {
-    base::win::RegKey system_restore_key(HKEY_LOCAL_MACHINE, kSystemRestoreKey,
-                                         KEY_SET_VALUE | KEY_QUERY_VALUE);
-    if (system_restore_key.Valid() &&
-        !system_restore_key.HasValue(kSystemRestoreFrequencyWin8)) {
-      system_restore_key.WriteValue(kSystemRestoreFrequencyWin8,
-                                    static_cast<DWORD>(0));
-    }
+  // A registry value needs to be created in order for restore points to be
+  // deterministically created. Attempt to create this value, but continue with
+  // the restore point anyway even if doing so fails. For more info, see
+  // http://msdn.microsoft.com/en-us/library/windows/desktop/aa378941.aspx.
+  base::win::RegKey system_restore_key(HKEY_LOCAL_MACHINE, kSystemRestoreKey,
+                                       KEY_SET_VALUE | KEY_QUERY_VALUE);
+  if (system_restore_key.Valid() &&
+      !system_restore_key.HasValue(kSystemRestoreFrequencyWin8)) {
+    system_restore_key.WriteValue(kSystemRestoreFrequencyWin8,
+                                  static_cast<DWORD>(0));
   }
 
   // Take a system restore point before doing anything else.
diff --git a/chrome/chrome_cleaner/os/task_scheduler_unittest.cc b/chrome/chrome_cleaner/os/task_scheduler_unittest.cc
index 1fc8445c..fc0b1eb 100644
--- a/chrome/chrome_cleaner/os/task_scheduler_unittest.cc
+++ b/chrome/chrome_cleaner/os/task_scheduler_unittest.cc
@@ -27,10 +27,6 @@
 #include "chrome/chrome_cleaner/test/test_strings.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
-#if BUILDFLAG(IS_WIN)
-#include "base/win/windows_version.h"
-#endif
-
 namespace chrome_cleaner {
 
 namespace {
@@ -98,13 +94,6 @@
 }
 
 TEST_F(TaskSchedulerTests, RunAProgramNow) {
-#if BUILDFLAG(IS_WIN)
-    // TODO(crbug.com/1307401): Failing on Windows7.
-    if (base::win::GetVersion() <= base::win::Version::WIN7) {
-      return;
-    }
-#endif
-
   base::FilePath executable_path;
   ASSERT_TRUE(base::PathService::Get(base::DIR_EXE, &executable_path));
   base::CommandLine command_line(
diff --git a/chrome/chrome_cleaner/test/cleaner_test.cc b/chrome/chrome_cleaner/test/cleaner_test.cc
index b83122c..a61c626 100644
--- a/chrome/chrome_cleaner/test/cleaner_test.cc
+++ b/chrome/chrome_cleaner/test/cleaner_test.cc
@@ -22,7 +22,6 @@
 #include "base/task/thread_pool.h"
 #include "base/test/task_environment.h"
 #include "base/test/test_timeouts.h"
-#include "base/win/windows_version.h"
 #include "chrome/chrome_cleaner/buildflags.h"
 #include "chrome/chrome_cleaner/constants/chrome_cleaner_switches.h"
 #include "chrome/chrome_cleaner/ipc/chrome_prompt_test_util.h"
@@ -550,11 +549,6 @@
 }
 
 TEST_P(CleanerTest, NoUnsanitizedPaths) {
-  // Fails on Windows7
-  if (base::win::GetVersion() <= base::win::Version::WIN7) {
-    return;
-  }
-
   CreateRemovableUwS();
 
   base::CommandLine command_line = BuildCommandLine(kCleanerExecutable);
diff --git a/chrome/chrome_cleaner/test/test_util.cc b/chrome/chrome_cleaner/test/test_util.cc
index 5b768055..29172797 100644
--- a/chrome/chrome_cleaner/test/test_util.cc
+++ b/chrome/chrome_cleaner/test/test_util.cc
@@ -154,11 +154,6 @@
         chrome_cleaner::SandboxType::kNonSandboxed);
   }
 
-  // Some tests spawn sandbox targets using job objects. Windows 7 doesn't
-  // support nested job objects, so don't use them in the test suite. Otherwise
-  // all sandbox tests will fail as they try to create a second job object.
-  bool use_job_objects = base::win::GetVersion() >= base::win::Version::WIN8;
-
   // Some tests will fail if two tests try to launch test_process.exe
   // simultaneously, so run the tests serially. This will still shard them and
   // distribute the shards to different swarming bots, but tests will run
@@ -167,7 +162,7 @@
       argc, argv,
       /*parallel_jobs=*/1U,        // Like LaunchUnitTestsSerially
       /*default_batch_limit=*/10,  // Like LaunchUnitTestsSerially
-      use_job_objects, base::DoNothing(),
+      /*use_job_objects=*/true, base::DoNothing(),
       base::BindOnce(&base::TestSuite::Run, base::Unretained(&test_suite)));
 
   if (!IsSandboxedProcess())
diff --git a/chrome/chrome_paks.gni b/chrome/chrome_paks.gni
index 2772c35d..026ccb1 100644
--- a/chrome/chrome_paks.gni
+++ b/chrome/chrome_paks.gni
@@ -99,6 +99,7 @@
       "$root_gen_dir/chrome/segmentation_internals_resources.pak",
       "$root_gen_dir/components/autofill/core/browser/autofill_address_rewriter_resources.pak",
       "$root_gen_dir/components/components_resources.pak",
+      "$root_gen_dir/content/attribution_internals_resources.pak",
       "$root_gen_dir/content/content_resources.pak",
       "$root_gen_dir/content/indexed_db_resources.pak",
       "$root_gen_dir/content/quota_internals_resources.pak",
@@ -121,6 +122,7 @@
       "//components/optimization_guide/optimization_guide_internals/resources",
       "//components/resources",
       "//content:content_resources",
+      "//content/browser/resources/attribution_reporting:resources",
       "//content/browser/resources/indexed_db:resources",
       "//content/browser/resources/quota:resources",
       "//mojo/public/js:resources",
diff --git a/chrome/common/pepper_permission_util_unittest.cc b/chrome/common/pepper_permission_util_unittest.cc
index 4484faf..3de709d7 100644
--- a/chrome/common/pepper_permission_util_unittest.cc
+++ b/chrome/common/pepper_permission_util_unittest.cc
@@ -25,7 +25,7 @@
 scoped_refptr<const Extension> CreateExtensionImportingModule(
     const std::string& import_id,
     const std::string& id) {
-  std::unique_ptr<base::DictionaryValue> manifest =
+  base::Value::Dict manifest =
       DictionaryBuilder()
           .Set("name", "Has Dependent Modules")
           .Set("version", "1.0")
@@ -49,12 +49,11 @@
   ScopedCurrentChannel current_channel(version_info::Channel::UNKNOWN);
   ExtensionSet extensions;
   std::string allowed_id = crx_file::id_util::GenerateId("allowed_extension");
-  std::unique_ptr<base::DictionaryValue> manifest =
-      DictionaryBuilder()
-          .Set("name", "Allowed Extension")
-          .Set("version", "1.0")
-          .Set("manifest_version", 2)
-          .Build();
+  base::Value::Dict manifest = DictionaryBuilder()
+                                   .Set("name", "Allowed Extension")
+                                   .Set("version", "1.0")
+                                   .Set("manifest_version", 2)
+                                   .Build();
   scoped_refptr<const Extension> ext = ExtensionBuilder()
                                            .SetManifest(std::move(manifest))
                                            .SetID(allowed_id)
@@ -86,7 +85,7 @@
   std::string allowed_id = crx_file::id_util::GenerateId("extension_id");
   std::string bad_id = crx_file::id_util::GenerateId("bad_id");
 
-  std::unique_ptr<base::DictionaryValue> shared_module_manifest =
+  base::Value::Dict shared_module_manifest =
       DictionaryBuilder()
           .Set("name", "Allowed Shared Module")
           .Set("version", "1.0")
diff --git a/chrome/services/sharing/nearby/platform/wifi_lan_medium_unittest.cc b/chrome/services/sharing/nearby/platform/wifi_lan_medium_unittest.cc
index cc320f8d..17d8ba5c 100644
--- a/chrome/services/sharing/nearby/platform/wifi_lan_medium_unittest.cc
+++ b/chrome/services/sharing/nearby/platform/wifi_lan_medium_unittest.cc
@@ -9,6 +9,7 @@
 #include "base/task/thread_pool.h"
 #include "base/test/task_environment.h"
 #include "base/threading/thread_restrictions.h"
+#include "base/values.h"
 #include "chrome/services/sharing/nearby/platform/wifi_lan_server_socket.h"
 #include "chromeos/ash/components/login/login_state/login_state.h"
 #include "chromeos/ash/components/network/managed_network_configuration_handler.h"
@@ -237,8 +238,8 @@
       managed_network_config_handler_->SetPolicy(
           ::onc::ONC_SOURCE_DEVICE_POLICY,
           /*userhash=*/std::string(),
-          /*network_configs_onc=*/base::ListValue(),
-          /*global_network_config=*/base::DictionaryValue());
+          /*network_configs_onc=*/base::Value(base::Value::List()),
+          /*global_network_config=*/base::Value(base::Value::Dict()));
 
       base::RunLoop().RunUntilIdle();
     }
@@ -256,12 +257,12 @@
 
   void AddWifiService(bool add_ip_configs, const net::IPAddress& local_addr) {
     if (add_ip_configs) {
-      base::DictionaryValue ipv4;
-      ipv4.SetKey(shill::kAddressProperty, base::Value(local_addr.ToString()));
-      ipv4.SetKey(shill::kMethodProperty, base::Value(shill::kTypeIPv4));
+      base::Value::Dict ipv4;
+      ipv4.Set(shill::kAddressProperty, local_addr.ToString());
+      ipv4.Set(shill::kMethodProperty, shill::kTypeIPv4);
       cros_network_config_helper_->network_state_helper()
           .ip_config_test()
-          ->AddIPConfig(kIPv4ConfigPath, ipv4);
+          ->AddIPConfig(kIPv4ConfigPath, base::Value(std::move(ipv4)));
       base::RunLoop().RunUntilIdle();
     }
 
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 496fcc8..51cde89 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -3058,6 +3058,8 @@
         "../browser/extensions/crx_installer_browsertest.cc",
         "../browser/extensions/dynamic_origin_browsertest.cc",
         "../browser/extensions/error_console/error_console_browsertest.cc",
+        "../browser/extensions/error_console/error_console_test_observer.cc",
+        "../browser/extensions/error_console/error_console_test_observer.h",
         "../browser/extensions/events_apitest.cc",
         "../browser/extensions/extension_action_runner_browsertest.cc",
         "../browser/extensions/extension_bindings_apitest.cc",
@@ -4375,6 +4377,7 @@
         "//chromeos/ash/services/auth_factor_config",
         "//chromeos/ash/services/auth_factor_config",
         "//chromeos/ash/services/cros_healthd/public/cpp",
+        "//chromeos/ash/services/device_sync/public/cpp:test_support",
         "//chromeos/ash/services/multidevice_setup/public/cpp:test_support",
         "//chromeos/components/onc:test_support",
         "//chromeos/components/quick_answers/public/cpp:cpp",
@@ -7364,6 +7367,7 @@
       "../browser/ash/app_list/app_list_test_util.cc",
       "../browser/ash/app_list/app_list_test_util.h",
       "../browser/ash/app_list/app_service/app_service_app_model_builder_unittest.cc",
+      "../browser/ash/app_list/arc/arc_app_sync_metrics_helper_unittest.cc",
       "../browser/ash/app_list/arc/arc_app_test.cc",
       "../browser/ash/app_list/arc/arc_app_test.h",
       "../browser/ash/app_list/arc/arc_app_unittest.cc",
diff --git a/chrome/test/data/webui/chromeos/personalization_app/ambient_observer_test.ts b/chrome/test/data/webui/chromeos/personalization_app/ambient_observer_test.ts
index fb3c3b0f..9603dd878 100644
--- a/chrome/test/data/webui/chromeos/personalization_app/ambient_observer_test.ts
+++ b/chrome/test/data/webui/chromeos/personalization_app/ambient_observer_test.ts
@@ -6,7 +6,7 @@
 import 'chrome://webui-test/mojo_webui_test_support.js';
 
 import {AmbientActionName, AmbientModeAlbum, AmbientObserver, emptyState, SetAlbumsAction, TopicSource} from 'chrome://personalization/js/personalization_app.js';
-import {assertDeepEquals, assertEquals} from 'chrome://webui-test/chai_assert.js';
+import {assertDeepEquals, assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
 
 import {baseSetup} from './personalization_app_test_utils.js';
 import {TestAmbientProvider} from './test_ambient_interface_provider.js';
@@ -98,3 +98,75 @@
         'used updated regular album url');
   });
 });
+
+suite('GooglePhotosPreviewLoadPerformance', () => {
+  let ambientProvider: TestAmbientProvider;
+  let personalizationStore: TestPersonalizationStore;
+
+  setup(() => {
+    const mocks = baseSetup();
+    ambientProvider = mocks.ambientProvider;
+    personalizationStore = mocks.personalizationStore;
+    // Reset the value to always start true in test.
+    AmbientObserver.shouldLogGooglePhotosPreviewsLoadPerformance = true;
+    AmbientObserver.initAmbientObserverIfNeeded();
+  });
+
+  teardown(() => {
+    AmbientObserver.shutdown();
+  });
+
+  test('sets to false if ambient mode disabled', async () => {
+    ambientProvider.ambientObserverRemote!.onAmbientModeEnabledChanged(false);
+    personalizationStore.expectAction(
+        AmbientActionName.SET_AMBIENT_MODE_ENABLED);
+    await personalizationStore.waitForAction(
+        AmbientActionName.SET_AMBIENT_MODE_ENABLED);
+    assertFalse(AmbientObserver.shouldLogGooglePhotosPreviewsLoadPerformance);
+  });
+
+  test('sets to false if topic source is not kGooglePhotos', async () => {
+    ambientProvider.ambientObserverRemote!.onTopicSourceChanged(
+        TopicSource.kArtGallery);
+    personalizationStore.expectAction(AmbientActionName.SET_TOPIC_SOURCE);
+    await personalizationStore.waitForAction(
+        AmbientActionName.SET_TOPIC_SOURCE);
+    assertFalse(AmbientObserver.shouldLogGooglePhotosPreviewsLoadPerformance);
+  });
+
+  test('sets to false if already received preview images', async () => {
+    personalizationStore.data.ambient.googlePhotosAlbumsPreviews = [];
+    ambientProvider.ambientObserverRemote!.onGooglePhotosAlbumsPreviewsFetched(
+        []);
+    personalizationStore.expectAction(
+        AmbientActionName.SET_GOOGLE_PHOTOS_ALBUMS_PREVIEWS);
+    await personalizationStore.waitForAction(
+        AmbientActionName.SET_GOOGLE_PHOTOS_ALBUMS_PREVIEWS);
+    assertTrue(
+        AmbientObserver.shouldLogGooglePhotosPreviewsLoadPerformance,
+        'still true because no previews stored yet');
+
+    personalizationStore.data.ambient.googlePhotosAlbumsPreviews = [
+      {url: 'asdf'},
+    ];
+    ambientProvider.ambientObserverRemote!.onGooglePhotosAlbumsPreviewsFetched(
+        []);
+    personalizationStore.expectAction(
+        AmbientActionName.SET_GOOGLE_PHOTOS_ALBUMS_PREVIEWS);
+    await personalizationStore.waitForAction(
+        AmbientActionName.SET_GOOGLE_PHOTOS_ALBUMS_PREVIEWS);
+    assertFalse(AmbientObserver.shouldLogGooglePhotosPreviewsLoadPerformance);
+  });
+
+  test('sets to false after receiving preview images', async () => {
+    personalizationStore.data.ambient.googlePhotosAlbumsPreviews = [];
+    ambientProvider.ambientObserverRemote!.onGooglePhotosAlbumsPreviewsFetched([
+      {url: 'asdf'},
+    ]);
+    personalizationStore.expectAction(
+        AmbientActionName.SET_GOOGLE_PHOTOS_ALBUMS_PREVIEWS);
+    await personalizationStore.waitForAction(
+        AmbientActionName.SET_GOOGLE_PHOTOS_ALBUMS_PREVIEWS);
+    assertFalse(AmbientObserver.shouldLogGooglePhotosPreviewsLoadPerformance);
+  });
+});
diff --git a/chrome/test/data/webui/settings/chromeos/device_page_tests.js b/chrome/test/data/webui/settings/chromeos/device_page_tests.js
index b4066b39..7ca86db 100644
--- a/chrome/test/data/webui/settings/chromeos/device_page_tests.js
+++ b/chrome/test/data/webui/settings/chromeos/device_page_tests.js
@@ -721,6 +721,9 @@
         fakeCrosAudioConfig.defaultFakeSpeaker,
         fakeCrosAudioConfig.defaultFakeMicJack,
       ],
+
+      /** @type {!Array<!AudioDevice>} */
+      inputDevices: [],
     };
 
     /** @type {!AudioSystemProperties} */
@@ -735,6 +738,9 @@
         fakeCrosAudioConfig.defaultFakeSpeaker,
         fakeCrosAudioConfig.defaultFakeMicJack,
       ],
+
+      /** @type {!Array<!AudioDevice>} */
+      inputDevices: [],
     };
 
     /** @type {!AudioSystemProperties} */
@@ -749,6 +755,9 @@
         fakeCrosAudioConfig.defaultFakeSpeaker,
         fakeCrosAudioConfig.defaultFakeMicJack,
       ],
+
+      /** @type {!Array<!AudioDevice>} */
+      inputDevices: [],
     };
 
     /** @type {!AudioSystemProperties} */
@@ -763,6 +772,9 @@
         fakeCrosAudioConfig.defaultFakeSpeaker,
         fakeCrosAudioConfig.defaultFakeMicJack,
       ],
+
+      /** @type {!Array<!AudioDevice>} */
+      inputDevices: [],
     };
 
     /** @type {!AudioSystemProperties} */
@@ -774,6 +786,9 @@
 
       /** @type {!Array<!AudioDevice>} */
       outputDevices: [],
+
+      /** @type {!Array<!AudioDevice>} */
+      inputDevices: [],
     };
 
     /** @type {!AudioSystemProperties} */
@@ -788,6 +803,9 @@
         fakeCrosAudioConfig.fakeSpeakerActive,
         fakeCrosAudioConfig.fakeMicJackInactive,
       ],
+
+      /** @type {!Array<!AudioDevice>} */
+      inputDevices: [],
     };
 
     /**
@@ -819,6 +837,9 @@
       // FakeAudioConfig must be set before audio subpage is loaded.
       crosAudioConfig = new fakeCrosAudioConfig.FakeCrosAudioConfig();
       setCrosAudioConfigForTesting(crosAudioConfig);
+      // Ensure data reset to fresh state.
+      crosAudioConfig.setAudioSystemProperties(
+          {...fakeCrosAudioConfig.defaultFakeAudioSystemProperties});
       return showAndGetDeviceSubpage('audio', routes.AUDIO)
           .then(function(page) {
             audioPage = page;
@@ -887,9 +908,8 @@
       await flushTasks();
 
       assertEquals(
-          fakeCrosAudioConfig.defaultFakeAudioSystemProperties
-              .outputVolumePercent,
-          minOutputVolumePercent);
+          minOutputVolumePercent,
+          audioPage.audioSystemProperties_.outputVolumePercent);
 
       // Ensure value clamps to min.
       outputSlider.value = 101;
@@ -897,9 +917,8 @@
       await flushTasks();
 
       assertEquals(
-          fakeCrosAudioConfig.defaultFakeAudioSystemProperties
-              .outputVolumePercent,
-          maxOutputVolumePercent);
+          maxOutputVolumePercent,
+          audioPage.audioSystemProperties_.outputVolumePercent);
     });
 
     test('output mute state changes slider disabled state', async function() {
@@ -1073,8 +1092,7 @@
       const inputSlider = audioPage.shadowRoot.querySelector(sliderSelector);
       assertTrue(isVisible(inputSlider));
       assertEquals(
-          fakeCrosAudioConfig.defaultFakeAudioSystemProperties
-              .inputVolumePercent,
+          audioPage.audioSystemProperties_.inputVolumePercent,
           inputSlider.value);
 
       const minimumValue = 0;
@@ -1082,24 +1100,21 @@
 
       assertEquals(minimumValue, inputSlider.value);
       assertEquals(
-          fakeCrosAudioConfig.defaultFakeAudioSystemProperties
-              .inputVolumePercent,
+          audioPage.audioSystemProperties_.inputVolumePercent,
           inputSlider.value);
       const maximumValue = 100;
       await simulateSliderClicked(sliderSelector, maximumValue);
 
       assertEquals(maximumValue, inputSlider.value);
       assertEquals(
-          fakeCrosAudioConfig.defaultFakeAudioSystemProperties
-              .inputVolumePercent,
+          audioPage.audioSystemProperties_.inputVolumePercent,
           inputSlider.value);
       const middleValue = 50;
       await simulateSliderClicked(sliderSelector, middleValue);
 
       assertEquals(middleValue, inputSlider.value);
       assertEquals(
-          fakeCrosAudioConfig.defaultFakeAudioSystemProperties
-              .inputVolumePercent,
+          audioPage.audioSystemProperties_.inputVolumePercent,
           inputSlider.value);
 
       // Ensure value clamps to min.
@@ -1108,9 +1123,7 @@
       await flushTasks();
 
       assertEquals(
-          fakeCrosAudioConfig.defaultFakeAudioSystemProperties
-              .inputVolumePercent,
-          minimumValue);
+          audioPage.audioSystemProperties_.inputVolumePercent, minimumValue);
 
       // Ensure value clamps to min.
       inputSlider.value = 101;
@@ -1118,9 +1131,7 @@
       await flushTasks();
 
       assertEquals(
-          fakeCrosAudioConfig.defaultFakeAudioSystemProperties
-              .inputVolumePercent,
-          maximumValue);
+          audioPage.audioSystemProperties_.inputVolumePercent, maximumValue);
     });
   });
 
diff --git a/chrome/test/data/webui/side_panel/bookmarks/test_bookmarks_api_proxy.ts b/chrome/test/data/webui/side_panel/bookmarks/test_bookmarks_api_proxy.ts
index 1269de4..47adfa87 100644
--- a/chrome/test/data/webui/side_panel/bookmarks/test_bookmarks_api_proxy.ts
+++ b/chrome/test/data/webui/side_panel/bookmarks/test_bookmarks_api_proxy.ts
@@ -28,6 +28,10 @@
       'bookmarkCurrentTabInFolder',
       'openBookmark',
       'cutBookmark',
+      'contextMenuOpenBookmarkInNewTab',
+      'contextMenuOpenBookmarkInNewWindow',
+      'contextMenuOpenBookmarkInIncognitoWindow',
+      'contextMenuDelete',
       'copyBookmark',
       'createFolder',
       'deleteBookmarks',
@@ -72,6 +76,22 @@
     this.folders_ = folders;
   }
 
+  contextMenuOpenBookmarkInNewTab(id: string, source: ActionSource) {
+    this.methodCalled('contextMenuOpenBookmarkInNewTab', id, source);
+  }
+
+  contextMenuOpenBookmarkInNewWindow(id: string, source: ActionSource) {
+    this.methodCalled('contextMenuOpenBookmarkInNewWindow', id, source);
+  }
+
+  contextMenuOpenBookmarkInIncognitoWindow(id: string, source: ActionSource) {
+    this.methodCalled('contextMenuOpenBookmarkInIncognitoWindow', id, source);
+  }
+
+  contextMenuDelete(id: string, source: ActionSource) {
+    this.methodCalled('contextMenuDelete', id, source);
+  }
+
   copyBookmark(id: string): Promise<void> {
     this.methodCalled('copyBookmark', id);
     return Promise.resolve();
diff --git a/chromecast/cast_core/runtime/browser/runtime_application_service_impl.cc b/chromecast/cast_core/runtime/browser/runtime_application_service_impl.cc
index 1687dad..e21ed22 100644
--- a/chromecast/cast_core/runtime/browser/runtime_application_service_impl.cc
+++ b/chromecast/cast_core/runtime/browser/runtime_application_service_impl.cc
@@ -238,7 +238,7 @@
   runtime_application_->Launch(std::move(callback));
 }
 
-void RuntimeApplicationServiceImpl::LoadPage(const GURL& url) {
+void RuntimeApplicationServiceImpl::NavigateToPage(const GURL& url) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   auto* cast_web_contents = cast_web_view_->cast_web_contents();
diff --git a/chromecast/cast_core/runtime/browser/runtime_application_service_impl.h b/chromecast/cast_core/runtime/browser/runtime_application_service_impl.h
index d56217c..0441a83 100644
--- a/chromecast/cast_core/runtime/browser/runtime_application_service_impl.h
+++ b/chromecast/cast_core/runtime/browser/runtime_application_service_impl.h
@@ -75,7 +75,7 @@
 #if !BUILDFLAG(IS_CAST_DESKTOP_BUILD)
   cast_receiver::StreamingConfigManager* GetStreamingConfigManager() override;
 #endif
-  void LoadPage(const GURL& url) override;
+  void NavigateToPage(const GURL& url) override;
 
  private:
   // Gets the current |message_port_service_|, attempting to create it if it
diff --git a/chromeos/ash/components/network/cellular_connection_handler.cc b/chromeos/ash/components/network/cellular_connection_handler.cc
index 12aa36f..586f5b1 100644
--- a/chromeos/ash/components/network/cellular_connection_handler.cc
+++ b/chromeos/ash/components/network/cellular_connection_handler.cc
@@ -75,6 +75,9 @@
 }  // namespace
 
 // static
+const base::TimeDelta CellularConnectionHandler::kWaitingForAutoConnectTimeout =
+    base::Minutes(2);
+
 absl::optional<std::string> CellularConnectionHandler::ResultToErrorString(
     PrepareCellularConnectionResult result) {
   switch (result) {
@@ -216,7 +219,8 @@
 }
 
 void CellularConnectionHandler::CompleteConnectionAttempt(
-    PrepareCellularConnectionResult result) {
+    PrepareCellularConnectionResult result,
+    bool auto_connected) {
   DCHECK(state_ != ConnectionState::kIdle);
   DCHECK(!request_queue_.empty());
 
@@ -244,7 +248,7 @@
     std::move(metadata->error_callback)
         .Run(service_path, NetworkConnectionHandler::kErrorNotFound);
   } else {
-    std::move(metadata->success_callback).Run(service_path);
+    std::move(metadata->success_callback).Run(service_path, auto_connected);
   }
 
   ProcessRequestQueue();
@@ -322,14 +326,16 @@
     NET_LOG(ERROR) << "Could not find network for ICCID "
                    << *request_queue_.front()->iccid;
     CompleteConnectionAttempt(
-        PrepareCellularConnectionResult::kCouldNotFindNetworkWithIccid);
+        PrepareCellularConnectionResult::kCouldNotFindNetworkWithIccid,
+        /*auto_connected=*/false);
     return;
   }
 
   if (CanInitiateShillConnection(network_state)) {
     NET_LOG(USER) << "Cellular service with ICCID " << iccid
                   << " is connectable";
-    CompleteConnectionAttempt(PrepareCellularConnectionResult::kSuccess);
+    CompleteConnectionAttempt(PrepareCellularConnectionResult::kSuccess,
+                              /*auto_connected=*/false);
     return;
   }
 
@@ -362,7 +368,8 @@
 
   if (!inhibit_lock) {
     NET_LOG(ERROR) << "eSIM connection flow failed to inhibit scan";
-    CompleteConnectionAttempt(PrepareCellularConnectionResult::kInhibitFailed);
+    CompleteConnectionAttempt(PrepareCellularConnectionResult::kInhibitFailed,
+                              /*auto_connected=*/false);
     return;
   }
 
@@ -378,7 +385,8 @@
   if (!euicc_path) {
     NET_LOG(ERROR) << "eSIM connection flow could not find relevant EUICC";
     CompleteConnectionAttempt(
-        PrepareCellularConnectionResult::kCouldNotFindRelevantEuicc);
+        PrepareCellularConnectionResult::kCouldNotFindRelevantEuicc,
+        /*auto_connected=*/false);
     return;
   }
 
@@ -396,7 +404,8 @@
   if (!inhibit_lock) {
     NET_LOG(ERROR) << "eSIM connection flow failed to request profiles";
     CompleteConnectionAttempt(
-        PrepareCellularConnectionResult::kRefreshProfilesFailed);
+        PrepareCellularConnectionResult::kRefreshProfilesFailed,
+        /*auto_connected=*/false);
     return;
   }
 
@@ -412,7 +421,8 @@
   if (!profile_path) {
     NET_LOG(ERROR) << "eSIM connection flow could not find profile";
     CompleteConnectionAttempt(
-        PrepareCellularConnectionResult::kCouldNotFindRelevantESimProfile);
+        PrepareCellularConnectionResult::kCouldNotFindRelevantESimProfile,
+        /*auto_connected=*/false);
     return;
   }
 
@@ -436,10 +446,15 @@
   if (!success) {
     NET_LOG(ERROR) << "eSIM connection flow failed to enable profile";
     CompleteConnectionAttempt(
-        PrepareCellularConnectionResult::kEnableProfileFailed);
+        PrepareCellularConnectionResult::kEnableProfileFailed,
+        /*auto_connected=*/false);
     return;
   }
 
+  // kErrorAlreadyEnabled implies that the SIM profile was already enabled.
+  request_queue_.front()->did_connection_require_enabling_profile =
+      status == HermesResponseStatus::kSuccess;
+
   // Reset the inhibit_lock so that the device will be uninhibited
   // automatically.
   request_queue_.front()->inhibit_lock.reset();
@@ -452,12 +467,24 @@
     CheckForConnectable();
 }
 
+void CellularConnectionHandler::NetworkConnectionStateChanged(
+    const NetworkState* network) {
+  if (state_ == ConnectionState::kWaitingForShillAutoConnect) {
+    CheckForAutoConnected();
+  }
+}
+
 void CellularConnectionHandler::CheckForConnectable() {
   DCHECK_EQ(state_, ConnectionState::kWaitingForConnectable);
 
   const NetworkState* network_state = GetNetworkStateForCurrentOperation();
   if (network_state && CanInitiateShillConnection(network_state)) {
-    CompleteConnectionAttempt(PrepareCellularConnectionResult::kSuccess);
+    if (!request_queue_.front()->did_connection_require_enabling_profile) {
+      CompleteConnectionAttempt(PrepareCellularConnectionResult::kSuccess,
+                                /*auto_connected=*/false);
+    } else {
+      StartWaitingForShillAutoConnect();
+    }
     return;
   }
 
@@ -477,7 +504,46 @@
   NET_LOG(ERROR) << "Cellular connection timed out waiting for network to "
                     "become connectable";
   CompleteConnectionAttempt(
-      PrepareCellularConnectionResult::kTimeoutWaitingForConnectable);
+      PrepareCellularConnectionResult::kTimeoutWaitingForConnectable,
+      /*auto_connected=*/false);
+}
+
+void CellularConnectionHandler::StartWaitingForShillAutoConnect() {
+  // Stop the timer that wait for the network to become connectable.
+  if (timer_.IsRunning()) {
+    timer_.Stop();
+  }
+
+  TransitionToConnectionState(ConnectionState::kWaitingForShillAutoConnect);
+  CheckForAutoConnected();
+}
+
+void CellularConnectionHandler::CheckForAutoConnected() {
+  CHECK_EQ(state_, ConnectionState::kWaitingForShillAutoConnect);
+
+  const NetworkState* network_state = GetNetworkStateForCurrentOperation();
+  if (network_state && network_state->IsConnectedState()) {
+    CompleteConnectionAttempt(PrepareCellularConnectionResult::kSuccess,
+                              /*auto_connected=*/true);
+    return;
+  }
+
+  // If network hasn't autoconnected by Shill yet, start a timer and wait for
+  // the network to become connected.
+  if (!timer_.IsRunning()) {
+    timer_.Start(
+        FROM_HERE, kWaitingForAutoConnectTimeout,
+        base::BindOnce(&CellularConnectionHandler::OnWaitForAutoConnectTimeout,
+                       weak_ptr_factory_.GetWeakPtr()));
+  }
+}
+
+void CellularConnectionHandler::OnWaitForAutoConnectTimeout() {
+  DCHECK_EQ(state_, ConnectionState::kWaitingForShillAutoConnect);
+  NET_LOG(ERROR) << "Cellular connection timed out waiting for network to "
+                    "become auto connected";
+  CompleteConnectionAttempt(PrepareCellularConnectionResult::kSuccess,
+                            /*auto_connected=*/false);
 }
 
 std::ostream& operator<<(
@@ -503,6 +569,10 @@
     case CellularConnectionHandler::ConnectionState::kWaitingForConnectable:
       stream << "[Waiting for network to become connectable]";
       break;
+    case CellularConnectionHandler::ConnectionState::
+        kWaitingForShillAutoConnect:
+      stream << "[Waiting for network to become auto-connected]";
+      break;
   }
   return stream;
 }
diff --git a/chromeos/ash/components/network/cellular_connection_handler.h b/chromeos/ash/components/network/cellular_connection_handler.h
index f05d9dfa..733afb1 100644
--- a/chromeos/ash/components/network/cellular_connection_handler.h
+++ b/chromeos/ash/components/network/cellular_connection_handler.h
@@ -21,6 +21,10 @@
 
 namespace ash {
 
+namespace cellular_setup {
+class ESimTestBase;
+}
+
 class CellularESimProfileHandler;
 class CellularInhibitor;
 class NetworkState;
@@ -46,6 +50,7 @@
 //   (4) Enable the relevant profile.
 //   (5) Uninhibit cellular scans.
 //   (6) Wait until the associated NetworkState becomes connectable.
+//   (7) Wait until Shill auto connected if the sim slot is switched.
 //
 // Note that if this class receives multiple connection requests, it processes
 // them in FIFO order.
@@ -62,8 +67,10 @@
             CellularInhibitor* cellular_inhibitor,
             CellularESimProfileHandler* cellular_esim_profile_handler);
 
-  // Success callback which receives the network's service path as a parameter.
-  typedef base::OnceCallback<void(const std::string&)> SuccessCallback;
+  // Success callback which receives the network's service path as the first
+  // parameter and a boolean indicates whether the network is autoconnected
+  // as the second parameter.
+  typedef base::OnceCallback<void(const std::string&, bool)> SuccessCallback;
 
   // Error callback which receives the network's service path as the first
   // parameter and an error name as the second parameter. If no service path is
@@ -93,6 +100,11 @@
 
  private:
   friend class CellularConnectionHandlerTest;
+  friend class CellularESimInstallerTest;
+  friend class CellularPolicyHandlerTest;
+  friend class ManagedNetworkConfigurationHandlerTest;
+  friend class cellular_setup::ESimTestBase;
+
   FRIEND_TEST_ALL_PREFIXES(CellularConnectionHandlerTest, NoService);
   FRIEND_TEST_ALL_PREFIXES(CellularConnectionHandlerTest,
                            ServiceAlreadyConnectable);
@@ -104,7 +116,10 @@
                            TimeoutWaitingForConnectable_ESim);
   FRIEND_TEST_ALL_PREFIXES(CellularConnectionHandlerTest,
                            TimeoutWaitingForConnectable_PSim);
-  FRIEND_TEST_ALL_PREFIXES(CellularConnectionHandlerTest, Success);
+  FRIEND_TEST_ALL_PREFIXES(CellularConnectionHandlerTest,
+                           Success_AutoConnected);
+  FRIEND_TEST_ALL_PREFIXES(CellularConnectionHandlerTest,
+                           Success_TimeoutAutoConnected);
   FRIEND_TEST_ALL_PREFIXES(CellularConnectionHandlerTest,
                            Success_AlreadyEnabled);
   FRIEND_TEST_ALL_PREFIXES(CellularConnectionHandlerTest, ConnectToStub);
@@ -127,6 +142,9 @@
     absl::optional<dbus::ObjectPath> euicc_path;
     absl::optional<dbus::ObjectPath> profile_path;
     std::unique_ptr<CellularInhibitor::InhibitLock> inhibit_lock;
+    // A boolean indicating that if the connection switches the SIM profile and
+    // requires enabling the profile first.
+    bool did_connection_require_enabling_profile = false;
     SuccessCallback success_callback;
     ErrorCallback error_callback;
   };
@@ -137,7 +155,8 @@
     kInhibitingScans,
     kRequestingProfilesBeforeEnabling,
     kEnablingProfile,
-    kWaitingForConnectable
+    kWaitingForConnectable,
+    kWaitingForShillAutoConnect,
   };
   friend std::ostream& operator<<(std::ostream& stream,
                                   const ConnectionState& step);
@@ -155,6 +174,10 @@
     kTimeoutWaitingForConnectable = 7,
     kMaxValue = kTimeoutWaitingForConnectable
   };
+
+  // Timeout waiting for a cellular network to auto connect after switch
+  // profile.
+  static const base::TimeDelta kWaitingForAutoConnectTimeout;
   static absl::optional<std::string> ResultToErrorString(
       PrepareCellularConnectionResult result);
 
@@ -165,12 +188,15 @@
                                      const std::string& new_service_path,
                                      const std::string& old_guid,
                                      const std::string& new_guid) override;
+  void NetworkConnectionStateChanged(const NetworkState* network) override;
 
   void ProcessRequestQueue();
   void TransitionToConnectionState(ConnectionState state);
 
-  // Invokes the success or error callback, depending on |result|.
-  void CompleteConnectionAttempt(PrepareCellularConnectionResult result);
+  // Invokes the success or error callback, depending on |result| and
+  // |auto_connected|.
+  void CompleteConnectionAttempt(PrepareCellularConnectionResult result,
+                                 bool auto_connected);
 
   const NetworkState* GetNetworkStateForCurrentOperation() const;
   absl::optional<dbus::ObjectPath> GetEuiccPathForCurrentOperation() const;
@@ -193,6 +219,9 @@
   void HandleNetworkPropertiesUpdate();
   void CheckForConnectable();
   void OnWaitForConnectableTimeout();
+  void StartWaitingForShillAutoConnect();
+  void CheckForAutoConnected();
+  void OnWaitForAutoConnectTimeout();
 
   base::OneShotTimer timer_;
 
diff --git a/chromeos/ash/components/network/cellular_connection_handler_unittest.cc b/chromeos/ash/components/network/cellular_connection_handler_unittest.cc
index 49bd5a60..e97ec23a 100644
--- a/chromeos/ash/components/network/cellular_connection_handler_unittest.cc
+++ b/chromeos/ash/components/network/cellular_connection_handler_unittest.cc
@@ -114,6 +114,13 @@
     base::RunLoop().RunUntilIdle();
   }
 
+  void SetCellularServiceConnected(int profile_num) {
+    helper_.service_test()->SetServiceProperty(
+        CreateTestServicePath(profile_num), shill::kStateProperty,
+        base::Value(shill::kStateOnline));
+    base::RunLoop().RunUntilIdle();
+  }
+
   void ExpectServiceConnectable(int profile_num) {
     const NetworkState* network_state =
         helper_.network_state_handler()->GetNetworkState(
@@ -194,8 +201,10 @@
   }
 
   void ExpectSuccess(const std::string& expected_service_path,
-                     base::RunLoop* run_loop) {
+                     base::RunLoop* run_loop,
+                     bool auto_connected) {
     expected_service_path_ = expected_service_path;
+    expected_auto_connected_ = auto_connected;
     on_success_callback_ = run_loop->QuitClosure();
   }
 
@@ -236,8 +245,9 @@
   }
 
  private:
-  void OnSuccess(const std::string& service_path) {
+  void OnSuccess(const std::string& service_path, bool auto_connected) {
     EXPECT_EQ(expected_service_path_, service_path);
+    EXPECT_EQ(expected_auto_connected_, auto_connected);
     std::move(on_success_callback_).Run();
   }
 
@@ -260,6 +270,7 @@
   base::OnceClosure on_success_callback_;
   base::OnceClosure on_failure_callback_;
   std::string expected_service_path_;
+  bool expected_auto_connected_;
   std::string expected_error_name_;
 };
 
@@ -283,7 +294,8 @@
   SetServiceConnectable(/*profile_num=*/1);
 
   base::RunLoop run_loop;
-  ExpectSuccess(CreateTestServicePath(/*profile_num=*/1), &run_loop);
+  ExpectSuccess(CreateTestServicePath(/*profile_num=*/1), &run_loop,
+                /*auto_connected=*/false);
   CallPrepareExistingCellularNetworkForConnection(/*profile_num=*/1);
   run_loop.Run();
 
@@ -397,7 +409,7 @@
                    kTimeoutWaitingForConnectable);
 }
 
-TEST_F(CellularConnectionHandlerTest, Success) {
+TEST_F(CellularConnectionHandlerTest, Success_AutoConnected) {
   AddCellularDevice();
   AddEuicc(/*euicc_num=*/1);
   AddProfile(/*profile_num=*/1, /*euicc_num=*/1);
@@ -405,7 +417,29 @@
   SetServiceIccid(/*profile_num=*/1);
 
   base::RunLoop run_loop;
-  ExpectSuccess(CreateTestServicePath(/*profile_num=*/1), &run_loop);
+  ExpectSuccess(CreateTestServicePath(/*profile_num=*/1), &run_loop,
+                /*auto_connected=*/true);
+  CallPrepareExistingCellularNetworkForConnection(/*profile_num=*/1);
+  // Simulate the cellular network get connected after 10 seconds.
+  AdvanceClock(base::Seconds(10));
+  SetCellularServiceConnected(/*profile_num=*/1);
+  run_loop.Run();
+
+  ExpectServiceConnectable(/*profile_num=*/1);
+  ExpectResult(
+      CellularConnectionHandler::PrepareCellularConnectionResult::kSuccess);
+}
+
+TEST_F(CellularConnectionHandlerTest, Success_TimeoutAutoConnected) {
+  AddCellularDevice();
+  AddEuicc(/*euicc_num=*/1);
+  AddProfile(/*profile_num=*/1, /*euicc_num=*/1);
+  SetServiceEid(/*profile_num=*/1, /*euicc_num=*/1);
+  SetServiceIccid(/*profile_num=*/1);
+
+  base::RunLoop run_loop;
+  ExpectSuccess(CreateTestServicePath(/*profile_num=*/1), &run_loop,
+                /*auto_connected=*/false);
   CallPrepareExistingCellularNetworkForConnection(/*profile_num=*/1);
   run_loop.Run();
 
@@ -425,7 +459,8 @@
   SetServiceIccid(/*profile_num=*/1);
 
   base::RunLoop run_loop;
-  ExpectSuccess(CreateTestServicePath(/*profile_num=*/1), &run_loop);
+  ExpectSuccess(CreateTestServicePath(/*profile_num=*/1), &run_loop,
+                /*auto_connected=*/false);
   CallPrepareExistingCellularNetworkForConnection(/*profile_num=*/1);
   SetServiceConnectable(/*profile_num=*/1);
   run_loop.Run();
@@ -445,7 +480,8 @@
   base::RunLoop run_loop;
   // Expect that by the end, we will connect to a "real" (i.e., non-stub)
   // service path.
-  ExpectSuccess(CreateTestServicePath(/*profile_num=*/1), &run_loop);
+  ExpectSuccess(CreateTestServicePath(/*profile_num=*/1), &run_loop,
+                /*auto_connected=*/false);
   CallPrepareExistingCellularNetworkForConnection(/*profile_num=*/1);
   base::RunLoop().RunUntilIdle();
 
@@ -473,7 +509,8 @@
   SetServiceIccid(/*profile_num=*/2);
 
   base::RunLoop run_loop1;
-  ExpectSuccess(CreateTestServicePath(/*profile_num=*/1), &run_loop1);
+  ExpectSuccess(CreateTestServicePath(/*profile_num=*/1), &run_loop1,
+                /*auto_connected=*/false);
 
   // Start both operations.
   CallPrepareExistingCellularNetworkForConnection(/*profile_num=*/1);
@@ -484,7 +521,8 @@
   ExpectServiceConnectable(/*profile_num=*/1);
 
   base::RunLoop run_loop2;
-  ExpectSuccess(CreateTestServicePath(/*profile_num=*/2), &run_loop2);
+  ExpectSuccess(CreateTestServicePath(/*profile_num=*/2), &run_loop2,
+                /*auto_connected=*/false);
 
   // Verify that the second service becomes connectable.
   run_loop2.Run();
@@ -501,9 +539,10 @@
   AddProfile(/*profile_num=*/1, /*euicc_num=*/1);
 
   base::RunLoop run_loop;
-  ExpectSuccess(CreateTestServicePath(/*profile_num=*/1), &run_loop);
-  CallPrepareNewlyInstalledCellularNetworkForConnection(/*euicc_num=*/1,
-                                                        /*profile_num=*/1);
+  ExpectSuccess(CreateTestServicePath(/*profile_num=*/1), &run_loop,
+                /*auto_connected=*/false);
+  CallPrepareNewlyInstalledCellularNetworkForConnection(/*profile_num=*/1,
+                                                        /*euicc_num=*/1);
 
   // Verify that service corresponding to new profile becomes
   // connectable.
diff --git a/chromeos/ash/components/network/cellular_esim_installer.cc b/chromeos/ash/components/network/cellular_esim_installer.cc
index 0a87fed..7b027c0 100644
--- a/chromeos/ash/components/network/cellular_esim_installer.cc
+++ b/chromeos/ash/components/network/cellular_esim_installer.cc
@@ -298,7 +298,8 @@
 void CellularESimInstaller::OnPrepareCellularNetworkForConnectionSuccess(
     const dbus::ObjectPath& profile_path,
     InstallProfileFromActivationCodeCallback callback,
-    const std::string& service_path) {
+    const std::string& service_path,
+    bool auto_connected) {
   NET_LOG(EVENT) << "Successfully enabled installed profile on service path: "
                  << service_path;
   const NetworkState* network_state =
diff --git a/chromeos/ash/components/network/cellular_esim_installer.h b/chromeos/ash/components/network/cellular_esim_installer.h
index 68ff439..e60c328 100644
--- a/chromeos/ash/components/network/cellular_esim_installer.h
+++ b/chromeos/ash/components/network/cellular_esim_installer.h
@@ -96,6 +96,8 @@
                            InstallProfileConnectFailure);
   FRIEND_TEST_ALL_PREFIXES(CellularESimInstallerTest, InstallProfileSuccess);
   FRIEND_TEST_ALL_PREFIXES(CellularESimInstallerTest,
+                           InstallProfileAutoConnect);
+  FRIEND_TEST_ALL_PREFIXES(CellularESimInstallerTest,
                            InstallProfileViaQrCodeSuccess);
   FRIEND_TEST_ALL_PREFIXES(CellularESimInstallerTest,
                            InstallProfileAlreadyConnected);
@@ -153,7 +155,8 @@
   void OnPrepareCellularNetworkForConnectionSuccess(
       const dbus::ObjectPath& profile_path,
       InstallProfileFromActivationCodeCallback callback,
-      const std::string& service_path);
+      const std::string& service_path,
+      bool auto_connected);
   void OnPrepareCellularNetworkForConnectionFailure(
       const dbus::ObjectPath& profile_path,
       InstallProfileFromActivationCodeCallback callback,
diff --git a/chromeos/ash/components/network/cellular_esim_installer_unittest.cc b/chromeos/ash/components/network/cellular_esim_installer_unittest.cc
index 7960cee..752d9258 100644
--- a/chromeos/ash/components/network/cellular_esim_installer_unittest.cc
+++ b/chromeos/ash/components/network/cellular_esim_installer_unittest.cc
@@ -44,6 +44,7 @@
 
 const char kTestEuiccPath[] = "/org/chromium/Hermes/Euicc/0";
 const char kTestEid[] = "12345678901234567890123456789012";
+const char kTestCellularServicePath[] = "/service/cellular101";
 const char kInstallViaQrCodeHistogram[] =
     "Network.Cellular.ESim.InstallViaQrCode.Result";
 const char kESimInstallNonUserErrorSuccessRate[] =
@@ -151,7 +152,8 @@
       bool wait_for_connect,
       bool fail_connect,
       bool is_initial_install = true,
-      bool is_install_via_qr_code = false) {
+      bool is_install_via_qr_code = false,
+      bool auto_connected = false) {
     HermesResponseStatus out_install_result;
     absl::optional<dbus::ObjectPath> out_esim_profile_path;
     absl::optional<std::string> out_service_path;
@@ -174,15 +176,23 @@
     FastForwardProfileRefreshDelay();
 
     if (wait_for_connect) {
-      base::RunLoop().RunUntilIdle();
-      EXPECT_LE(1u, network_connection_handler_->connect_calls().size());
-      if (fail_connect) {
-        network_connection_handler_->connect_calls().back().InvokeErrorCallback(
-            "fake_error_name");
+      if (auto_connected) {
+        ShillServiceClient::Get()->GetTestInterface()->SetServiceProperty(
+            kTestCellularServicePath, shill::kStateProperty,
+            base::Value(shill::kStateOnline));
       } else {
-        network_connection_handler_->connect_calls()
-            .back()
-            .InvokeSuccessCallback();
+        FastForwardAutoConnectWaiting();
+        base::RunLoop().RunUntilIdle();
+        EXPECT_LE(1u, network_connection_handler_->connect_calls().size());
+        if (fail_connect) {
+          network_connection_handler_->connect_calls()
+              .back()
+              .InvokeErrorCallback("fake_error_name");
+        } else {
+          network_connection_handler_->connect_calls()
+              .back()
+              .InvokeSuccessCallback();
+        }
       }
     }
 
@@ -284,6 +294,11 @@
     task_environment_.FastForwardBy(2 * kProfileRefreshCallbackDelay);
   }
 
+  void FastForwardAutoConnectWaiting() {
+    task_environment_.FastForwardBy(
+        CellularConnectionHandler::kWaitingForAutoConnectTimeout);
+  }
+
   base::HistogramTester* HistogramTesterPtr() { return &histogram_tester_; }
 
  private:
@@ -430,7 +445,32 @@
       /*euicc_path=*/dbus::ObjectPath(kTestEuiccPath),
       /*new_shill_properties=*/base::Value(base::Value::Type::DICTIONARY),
       /*wait_for_connect=*/true, /*fail_connect=*/false,
-      /*is_initial_install=*/true, /*install_via_qr_code=*/true);
+      /*is_initial_install=*/true, /*is_install_via_qr_code=*/true);
+  CheckInstallSuccess(result_tuple);
+
+  HistogramTesterPtr()->ExpectTotalCount(kESimProfileDownloadLatencyHistogram,
+                                         1);
+  CheckESimInstallHistograms(
+      /*expected_count=*/1, HermesResponseStatus::kSuccess,
+      CellularESimInstaller::InstallESimProfileResult::kSuccess);
+  CheckDetailedESimInstallHistograms(
+      CellularESimInstaller::InstallESimProfileResult::kSuccess,
+      /*is_managed=*/false, /*is_retry=*/false,
+      /*is_install_via_qr_code=*/true);
+}
+
+TEST_F(CellularESimInstallerTest, InstallProfileAutoConnect) {
+  // Verify that install succeeds when valid activation code is passed.
+  InstallResultTuple result_tuple = InstallProfileFromActivationCode(
+      HermesEuiccClient::Get()
+          ->GetTestInterface()
+          ->GenerateFakeActivationCode(),
+      /*confirmation_code=*/std::string(),
+      /*euicc_path=*/dbus::ObjectPath(kTestEuiccPath),
+      /*new_shill_properties=*/base::Value(base::Value::Type::DICTIONARY),
+      /*wait_for_connect=*/true, /*fail_connect=*/false,
+      /*is_initial_install=*/true, /*is_install_via_qr_code=*/true,
+      /*auto_connected=*/true);
   CheckInstallSuccess(result_tuple);
 
   HistogramTesterPtr()->ExpectTotalCount(kESimProfileDownloadLatencyHistogram,
diff --git a/chromeos/ash/components/network/cellular_policy_handler.cc b/chromeos/ash/components/network/cellular_policy_handler.cc
index f46a7ce8..aafee1d 100644
--- a/chromeos/ash/components/network/cellular_policy_handler.cc
+++ b/chromeos/ash/components/network/cellular_policy_handler.cc
@@ -295,7 +295,8 @@
   HermesProfileClient::Properties* profile_properties =
       HermesProfileClient::Get()->GetProperties(*profile_path);
   managed_cellular_pref_handler_->AddIccidSmdpPair(
-      profile_properties->iccid().value(), current_request->smdp_address);
+      profile_properties->iccid().value(), current_request->smdp_address,
+      /*sync_stub_networks=*/false);
 
   managed_network_configuration_handler_->NotifyPolicyAppliedToNetwork(
       *service_path);
diff --git a/chromeos/ash/components/network/cellular_policy_handler_unittest.cc b/chromeos/ash/components/network/cellular_policy_handler_unittest.cc
index da5b2c7..4d211b2 100644
--- a/chromeos/ash/components/network/cellular_policy_handler_unittest.cc
+++ b/chromeos/ash/components/network/cellular_policy_handler_unittest.cc
@@ -42,7 +42,7 @@
 const char kTestEuiccPath2[] = "/org/chromium/Hermes/Euicc/1";
 const char kTestESimProfilePath[] =
     "/org/chromium/Hermes/Euicc/1/Profile/1000000000000000002";
-const char kTesServicePath[] = "/service/1";
+const char kTestServicePath[] = "/service/cellular102";
 const char kTestEid[] = "12345678901234567890123456789012";
 const char kTestEid2[] = "12345678901234567890123456789000";
 const char kCellularGuid[] = "cellular_guid";
@@ -177,7 +177,7 @@
         dbus::ObjectPath(kTestEuiccPath), kICCID, /*name=*/std::string(),
         /*nickname=*/std::string(),
         /*service_provider=*/std::string(), /*activation_code=*/std::string(),
-        kTesServicePath, hermes::profile::State::kInactive,
+        kTestServicePath, hermes::profile::State::kInactive,
         hermes::profile::ProfileClass::kOperational,
         HermesEuiccClient::TestInterface::AddCarrierProfileBehavior::
             kAddProfileWithoutService);
@@ -205,19 +205,24 @@
 
   void InstallESimPolicy(const std::string& onc_json,
                          const std::string& activation_code,
-                         bool expect_install_success) {
+                         bool expect_install_success,
+                         bool auto_connect = false) {
     base::Value policy = chromeos::onc::ReadDictionaryFromJson(onc_json);
     cellular_policy_handler_->InstallESim(activation_code, policy);
     FastForwardProfileRefreshDelay();
     base::RunLoop().RunUntilIdle();
 
-    if (expect_install_success) {
+    if (!expect_install_success) {
+      EXPECT_LE(0u, network_connection_handler_->connect_calls().size());
+      return;
+    }
+    FastForwardAutoConnectWaiting(auto_connect);
+    base::RunLoop().RunUntilIdle();
+    if (!auto_connect) {
       EXPECT_LE(1u, network_connection_handler_->connect_calls().size());
       network_connection_handler_->connect_calls()
           .back()
           .InvokeSuccessCallback();
-    } else {
-      EXPECT_LE(0u, network_connection_handler_->connect_calls().size());
     }
     base::RunLoop().RunUntilIdle();
   }
@@ -241,6 +246,19 @@
     FastForwardBy(2 * kProfileRefreshCallbackDelay);
   }
 
+  void FastForwardAutoConnectWaiting(bool auto_connect) {
+    if (auto_connect) {
+      task_environment_.FastForwardBy(base::Seconds(10));
+      ShillServiceClient::Get()->GetTestInterface()->SetServiceProperty(
+          kTestServicePath, shill::kStateProperty,
+          base::Value(shill::kStateOnline));
+      return;
+    }
+
+    task_environment_.FastForwardBy(
+        CellularConnectionHandler::kWaitingForAutoConnectTimeout);
+  }
+
   void FastForwardBy(base::TimeDelta delay) {
     task_environment_.FastForwardBy(delay);
   }
@@ -279,7 +297,8 @@
                     HermesEuiccClient::Get()
                         ->GetTestInterface()
                         ->GenerateFakeActivationCode(),
-                    /*expect_install_success=*/true);
+                    /*expect_install_success=*/true,
+                    /*auto_connect=*/true);
   CheckShillConfiguration(/*is_installed=*/true);
   CheckIccidSmdpPairInPref(/*is_installed=*/true);
 
@@ -318,6 +337,7 @@
   ShillDeviceClient::Get()->GetTestInterface()->AddDevice(
       "/device/cellular1", shill::kTypeCellular, "TestCellular");
   FastForwardProfileRefreshDelay();
+  FastForwardAutoConnectWaiting(/*auto_connect=*/false);
   base::RunLoop().RunUntilIdle();
   CheckShillConfiguration(/*is_installed=*/true);
 }
@@ -338,6 +358,7 @@
   CheckShillConfiguration(/*is_installed=*/false);
   SetupEuicc();
   FastForwardProfileRefreshDelay();
+  FastForwardAutoConnectWaiting(/*auto_connect=*/false);
   base::RunLoop().RunUntilIdle();
   CheckShillConfiguration(/*is_installed=*/true);
   CheckIccidSmdpPairInPref(/*is_installed=*/true);
diff --git a/chromeos/ash/components/network/managed_cellular_pref_handler.cc b/chromeos/ash/components/network/managed_cellular_pref_handler.cc
index da9a0f9..0e186db 100644
--- a/chromeos/ash/components/network/managed_cellular_pref_handler.cc
+++ b/chromeos/ash/components/network/managed_cellular_pref_handler.cc
@@ -50,7 +50,8 @@
 
 void ManagedCellularPrefHandler::AddIccidSmdpPair(
     const std::string& iccid,
-    const std::string& smdp_address) {
+    const std::string& smdp_address,
+    bool sync_stub_networks) {
   if (!device_prefs_) {
     NET_LOG(ERROR) << "Device pref not available yet.";
     return;
@@ -64,7 +65,10 @@
   ScopedDictPrefUpdate update(device_prefs_,
                               prefs::kManagedCellularIccidSmdpPair);
   update->SetByDottedPath(iccid, smdp_address);
-  network_state_handler_->SyncStubCellularNetworks();
+  if (sync_stub_networks) {
+    network_state_handler_->SyncStubCellularNetworks();
+  }
+
   NotifyManagedCellularPrefChanged();
 }
 
diff --git a/chromeos/ash/components/network/managed_cellular_pref_handler.h b/chromeos/ash/components/network/managed_cellular_pref_handler.h
index 40093037..f5f98f1 100644
--- a/chromeos/ash/components/network/managed_cellular_pref_handler.h
+++ b/chromeos/ash/components/network/managed_cellular_pref_handler.h
@@ -41,9 +41,11 @@
   void SetDevicePrefs(PrefService* device_prefs);
 
   // Add a new ICCID and SMDP+ address pair to device pref for a managed
-  // cellular network.
+  // cellular network. If |sync_stub_networks| is set true,
+  // NetworkStateHandler::SyncStubCellularNetworks() will be called.
   void AddIccidSmdpPair(const std::string& iccid,
-                        const std::string& smdp_address);
+                        const std::string& smdp_address,
+                        bool sync_stub_networks = true);
 
   // Remove the ICCID and SMDP+ address pair from the device pref with given
   // |iccid|.
diff --git a/chromeos/ash/components/network/managed_network_configuration_handler_unittest.cc b/chromeos/ash/components/network/managed_network_configuration_handler_unittest.cc
index f96001c..dff9fdc 100644
--- a/chromeos/ash/components/network/managed_network_configuration_handler_unittest.cc
+++ b/chromeos/ash/components/network/managed_network_configuration_handler_unittest.cc
@@ -358,6 +358,11 @@
     task_environment_.FastForwardBy(2 * kProfileRefreshCallbackDelay);
   }
 
+  void FastForwardAutoConnectWaiting() {
+    task_environment_.FastForwardBy(
+        CellularConnectionHandler::kWaitingForAutoConnectTimeout);
+  }
+
   ProhibitedTechnologiesHandler* prohibited_technologies_handler() {
     return prohibited_technologies_handler_.get();
   }
@@ -614,6 +619,7 @@
   SetPolicy(::onc::ONC_SOURCE_DEVICE_POLICY, std::string(),
             "policy/policy_cellular.onc");
   FastForwardProfileRefreshDelay();
+  FastForwardAutoConnectWaiting();
   base::RunLoop().RunUntilIdle();
 
   std::string service_path = GetShillServiceClient()->FindServiceMatchingGUID(
diff --git a/chromeos/ash/components/network/network_connection_handler_impl.cc b/chromeos/ash/components/network/network_connection_handler_impl.cc
index 8956505d..bbf149b 100644
--- a/chromeos/ash/components/network/network_connection_handler_impl.cc
+++ b/chromeos/ash/components/network/network_connection_handler_impl.cc
@@ -425,7 +425,8 @@
     // connection. Prepare the network for connection before proceeding.
     cellular_connection_handler_->PrepareExistingCellularNetworkForConnection(
         cellular_network_iccid,
-        base::BindOnce(&NetworkConnectionHandlerImpl::CallShillConnect,
+        base::BindOnce(&NetworkConnectionHandlerImpl::
+                           OnPrepareCellularNetworkForConnectionSuccess,
                        AsWeakPtr()),
         base::BindOnce(&NetworkConnectionHandlerImpl::
                            OnPrepareCellularNetworkForConnectionFailure,
@@ -447,6 +448,18 @@
                      AsWeakPtr(), check_error_state));
 }
 
+void NetworkConnectionHandlerImpl::OnPrepareCellularNetworkForConnectionSuccess(
+    const std::string& service_path,
+    bool auto_connected) {
+  // If the cellular network is auto-connected by Shill, there is no need to
+  // call Shill connect.
+  if (auto_connected) {
+    HandleShillConnectSuccess(service_path);
+    return;
+  }
+  CallShillConnect(service_path);
+}
+
 void NetworkConnectionHandlerImpl::DisconnectNetwork(
     const std::string& service_path,
     base::OnceClosure success_callback,
diff --git a/chromeos/ash/components/network/network_connection_handler_impl.h b/chromeos/ash/components/network/network_connection_handler_impl.h
index e51e684e..4785653 100644
--- a/chromeos/ash/components/network/network_connection_handler_impl.h
+++ b/chromeos/ash/components/network/network_connection_handler_impl.h
@@ -99,6 +99,12 @@
   ConnectRequest* GetPendingRequest(const std::string& service_path);
   bool HasPendingCellularRequest() const;
 
+  // Callback when PrepareExistingCellularNetworkForConnection succeeded.
+  void OnPrepareCellularNetworkForConnectionSuccess(
+      const std::string& service_path,
+      bool auto_connected);
+
+  // Callback when PrepareExistingCellularNetworkForConnection failed.
   void OnPrepareCellularNetworkForConnectionFailure(
       const std::string& service_path,
       const std::string& error_name);
diff --git a/chromeos/ash/components/network/network_connection_handler_impl_unittest.cc b/chromeos/ash/components/network/network_connection_handler_impl_unittest.cc
index 63b4dd6a..bb9eed2 100644
--- a/chromeos/ash/components/network/network_connection_handler_impl_unittest.cc
+++ b/chromeos/ash/components/network/network_connection_handler_impl_unittest.cc
@@ -63,6 +63,7 @@
 
 const char kTestCellularServicePath2[] = "cellular_service_path_2";
 const char kTestIccid2[] = "9876543210987654321";
+constexpr base::TimeDelta kCellularAutoConnectTimeout = base::Seconds(120);
 
 class TestNetworkConnectionObserver : public NetworkConnectionObserver {
  public:
@@ -701,7 +702,8 @@
 TEST_F(NetworkConnectionHandlerImplTest, IgnoreConnectInProgressError_Fails) {
   Init();
 
-  AddCellularServiceWithESimProfile();
+  AddNonConnectablePSimService();
+  SetCellularServiceConnectable();
   SetShillConnectError(shill::kErrorResultInProgress);
   Connect(kTestCellularServicePath);
   EXPECT_TRUE(GetResultAndReset().empty());
@@ -1169,7 +1171,8 @@
   EXPECT_EQ(kSuccessResult, GetResultAndReset());
 }
 
-TEST_F(NetworkConnectionHandlerImplTest, ESimProfile_EnableProfile) {
+TEST_F(NetworkConnectionHandlerImplTest,
+       ESimProfile_EnableProfileAndWaitForAutoconnect) {
   Init();
   AddCellularServiceWithESimProfile();
 
@@ -1177,6 +1180,23 @@
   // connection is initiated, we attempt to enable the profile via Hermes.
   Connect(kTestCellularServicePath);
   SetCellularServiceConnectable();
+  // Set cellular service to connected state.
+  SetCellularServiceState(shill::kStateOnline);
+  EXPECT_EQ(kSuccessResult, GetResultAndReset());
+}
+
+TEST_F(NetworkConnectionHandlerImplTest,
+       ESimProfile_EnableProfileAndAutoconnectTimeout) {
+  Init();
+  AddCellularServiceWithESimProfile();
+
+  // Do not set the service to be connectable before trying to connect. When a
+  // connection is initiated, we attempt to enable the profile via Hermes.
+  Connect(kTestCellularServicePath);
+  SetCellularServiceConnectable();
+  EXPECT_TRUE(GetResultAndReset().empty());
+
+  AdvanceClock(kCellularAutoConnectTimeout);
   EXPECT_EQ(kSuccessResult, GetResultAndReset());
 }
 
@@ -1191,6 +1211,8 @@
   // Now, create a non-stub service and make it connectable.
   AddNonConnectableESimService();
   SetCellularServiceConnectable();
+  // Set cellular service to connected state.
+  SetCellularServiceState(shill::kStateOnline);
 
   EXPECT_EQ(kSuccessResult, GetResultAndReset());
 
diff --git a/chromeos/ash/services/assistant/public/cpp/features.cc b/chromeos/ash/services/assistant/public/cpp/features.cc
index eedd46bc..7135da0 100644
--- a/chromeos/ash/services/assistant/public/cpp/features.cc
+++ b/chromeos/ash/services/assistant/public/cpp/features.cc
@@ -52,7 +52,7 @@
 
 BASE_FEATURE(kEnableLibAssistantSandbox,
              "LibAssistantSandbox",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 
 BASE_FEATURE(kEnableLibAssistantV2,
              "LibAssistantV2",
diff --git a/chromeos/ash/services/cellular_setup/esim_profile.cc b/chromeos/ash/services/cellular_setup/esim_profile.cc
index 7aba281..af0b11a 100644
--- a/chromeos/ash/services/cellular_setup/esim_profile.cc
+++ b/chromeos/ash/services/cellular_setup/esim_profile.cc
@@ -391,7 +391,8 @@
               weak_ptr_factory_.GetWeakPtr()));
 }
 
-void ESimProfile::OnNewProfileEnableSuccess(const std::string& service_path) {
+void ESimProfile::OnNewProfileEnableSuccess(const std::string& service_path,
+                                            bool auto_connected) {
   const NetworkState* network_state =
       esim_manager_->network_state_handler()->GetNetworkState(service_path);
   if (!network_state) {
diff --git a/chromeos/ash/services/cellular_setup/esim_profile.h b/chromeos/ash/services/cellular_setup/esim_profile.h
index ac54149..b4961d8 100644
--- a/chromeos/ash/services/cellular_setup/esim_profile.h
+++ b/chromeos/ash/services/cellular_setup/esim_profile.h
@@ -94,7 +94,8 @@
   void OnPendingProfileInstallResult(
       std::unique_ptr<CellularInhibitor::InhibitLock> inhibit_lock,
       HermesResponseStatus status);
-  void OnNewProfileEnableSuccess(const std::string& service_path);
+  void OnNewProfileEnableSuccess(const std::string& service_path,
+                                 bool auto_connected);
   void OnPrepareCellularNetworkForConnectionFailure(
       const std::string& service_path,
       const std::string& error_name);
diff --git a/chromeos/ash/services/cellular_setup/esim_profile_unittest.cc b/chromeos/ash/services/cellular_setup/esim_profile_unittest.cc
index bb1f777..2c13dd5 100644
--- a/chromeos/ash/services/cellular_setup/esim_profile_unittest.cc
+++ b/chromeos/ash/services/cellular_setup/esim_profile_unittest.cc
@@ -139,6 +139,7 @@
             }));
 
     FastForwardProfileRefreshDelay();
+    FastForwardAutoConnectWaiting();
 
     if (wait_for_connect) {
       base::RunLoop().RunUntilIdle();
diff --git a/chromeos/ash/services/cellular_setup/esim_test_base.cc b/chromeos/ash/services/cellular_setup/esim_test_base.cc
index 527d7ca..ffb9affb 100644
--- a/chromeos/ash/services/cellular_setup/esim_test_base.cc
+++ b/chromeos/ash/services/cellular_setup/esim_test_base.cc
@@ -139,4 +139,9 @@
   task_environment()->FastForwardBy(2 * kProfileRefreshCallbackDelay);
 }
 
+void ESimTestBase::FastForwardAutoConnectWaiting() {
+  task_environment_.FastForwardBy(
+      CellularConnectionHandler::kWaitingForAutoConnectTimeout);
+}
+
 }  // namespace ash::cellular_setup
diff --git a/chromeos/ash/services/cellular_setup/esim_test_base.h b/chromeos/ash/services/cellular_setup/esim_test_base.h
index f82f2b6..38aaad4 100644
--- a/chromeos/ash/services/cellular_setup/esim_test_base.h
+++ b/chromeos/ash/services/cellular_setup/esim_test_base.h
@@ -52,6 +52,7 @@
   ~ESimTestBase() override;
 
   void FastForwardProfileRefreshDelay();
+  void FastForwardAutoConnectWaiting();
 
   ESimManager* esim_manager() { return esim_manager_.get(); }
   ESimManagerTestObserver* observer() { return observer_.get(); }
diff --git a/chromeos/ash/services/cellular_setup/euicc_unittest.cc b/chromeos/ash/services/cellular_setup/euicc_unittest.cc
index a33856a9..feb005d 100644
--- a/chromeos/ash/services/cellular_setup/euicc_unittest.cc
+++ b/chromeos/ash/services/cellular_setup/euicc_unittest.cc
@@ -94,6 +94,7 @@
     FastForwardProfileRefreshDelay();
 
     if (wait_for_connect) {
+      FastForwardAutoConnectWaiting();
       base::RunLoop().RunUntilIdle();
       EXPECT_LE(1u, network_connection_handler()->connect_calls().size());
       if (fail_connect) {
diff --git a/chromeos/ash/services/device_sync/BUILD.gn b/chromeos/ash/services/device_sync/BUILD.gn
index 13d0ae6..5a3a817 100644
--- a/chromeos/ash/services/device_sync/BUILD.gn
+++ b/chromeos/ash/services/device_sync/BUILD.gn
@@ -11,7 +11,10 @@
 }
 
 static_library("group_private_key_and_better_together_metadata_status") {
-  sources = [ "group_private_key_and_better_together_metadata_status.h" ]
+  sources = [
+    "group_private_key_and_better_together_metadata_status.cc",
+    "group_private_key_and_better_together_metadata_status.h",
+  ]
 }
 
 static_library("device_sync") {
diff --git a/chromeos/ash/services/device_sync/group_private_key_and_better_together_metadata_status.cc b/chromeos/ash/services/device_sync/group_private_key_and_better_together_metadata_status.cc
new file mode 100644
index 0000000..e9944997
--- /dev/null
+++ b/chromeos/ash/services/device_sync/group_private_key_and_better_together_metadata_status.cc
@@ -0,0 +1,83 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromeos/ash/services/device_sync/group_private_key_and_better_together_metadata_status.h"
+
+namespace {
+
+constexpr char private_key_status_prefix[] =
+    "[DeviceSyncer group private key status: ";
+constexpr char better_together_metadata_status_prefix[] =
+    "[DeviceSyncer better together metadata status: ";
+
+}  // namespace
+
+namespace ash::device_sync {
+
+std::ostream& operator<<(std::ostream& stream,
+                         const GroupPrivateKeyStatus& status) {
+  switch (status) {
+    case GroupPrivateKeyStatus::
+        kStatusUnavailableBecauseDeviceSyncIsNotInitialized:
+      stream << private_key_status_prefix
+             << "Status unavailable because device sync is not initialized]";
+      break;
+    case GroupPrivateKeyStatus::kWaitingForGroupPrivateKey:
+      stream << private_key_status_prefix
+             << "Waiting to receive group private key]";
+      break;
+    case GroupPrivateKeyStatus::kNoEncryptedGroupPrivateKeyReceived:
+      stream << private_key_status_prefix
+             << "No encrypted group private key received]";
+      break;
+    case GroupPrivateKeyStatus::kEncryptedGroupPrivateKeyEmpty:
+      stream << private_key_status_prefix
+             << "Encrypted group private key empty]";
+      break;
+    case GroupPrivateKeyStatus::kLocalDeviceSyncBetterTogetherKeyMissing:
+      stream << private_key_status_prefix
+             << "Local device sync better together key missing]";
+      break;
+    case GroupPrivateKeyStatus::kGroupPrivateKeyDecryptionFailed:
+      stream << private_key_status_prefix
+             << "Group private key decryption failed]";
+      break;
+    case GroupPrivateKeyStatus::kGroupPrivateKeySuccessfullyDecrypted:
+      stream << private_key_status_prefix
+             << "Group private key successfully decrypted]";
+      break;
+  }
+
+  return stream;
+}
+
+std::ostream& operator<<(std::ostream& stream,
+                         const BetterTogetherMetadataStatus& status) {
+  switch (status) {
+    case BetterTogetherMetadataStatus::
+        kStatusUnavailableBecauseDeviceSyncIsNotInitialized:
+      stream << better_together_metadata_status_prefix
+             << "Status unavailable because device sync is not initialized]";
+      break;
+    case BetterTogetherMetadataStatus::kWaitingToProcessDeviceMetadata:
+      stream << better_together_metadata_status_prefix
+             << "Waiting to process device metadata]";
+      break;
+    case BetterTogetherMetadataStatus::kGroupPrivateKeyMissing:
+      stream << better_together_metadata_status_prefix
+             << "Group private key is missing]";
+      break;
+    case BetterTogetherMetadataStatus::kEncryptedMetadataEmpty:
+      stream << better_together_metadata_status_prefix
+             << "Encrypted metadata is empty]";
+      break;
+    case BetterTogetherMetadataStatus::kMetadataDecrypted:
+      stream << better_together_metadata_status_prefix << "Metadata decrypted]";
+      break;
+  }
+
+  return stream;
+}
+
+}  // namespace ash::device_sync
\ No newline at end of file
diff --git a/chromeos/ash/services/device_sync/group_private_key_and_better_together_metadata_status.h b/chromeos/ash/services/device_sync/group_private_key_and_better_together_metadata_status.h
index 0f0f838a..7986612 100644
--- a/chromeos/ash/services/device_sync/group_private_key_and_better_together_metadata_status.h
+++ b/chromeos/ash/services/device_sync/group_private_key_and_better_together_metadata_status.h
@@ -5,6 +5,8 @@
 #ifndef CHROMEOS_ASH_SERVICES_DEVICE_SYNC_GROUP_PRIVATE_KEY_AND_BETTER_TOGETHER_METADATA_STATUS_H_
 #define CHROMEOS_ASH_SERVICES_DEVICE_SYNC_GROUP_PRIVATE_KEY_AND_BETTER_TOGETHER_METADATA_STATUS_H_
 
+#include <ostream>
+
 namespace ash::device_sync {
 
 // The group private key and better together metadata status in the
@@ -63,6 +65,11 @@
   kMetadataDecrypted,
 };
 
+std::ostream& operator<<(std::ostream& stream,
+                         const GroupPrivateKeyStatus& state);
+std::ostream& operator<<(std::ostream& stream,
+                         const BetterTogetherMetadataStatus& state);
+
 }  // namespace ash::device_sync
 
 #endif  //  CHROMEOS_ASH_SERVICES_DEVICE_SYNC_GROUP_PRIVATE_KEY_AND_BETTER_TOGETHER_METADATA_STATUS_H_
diff --git a/chromeos/ash/services/device_sync/public/cpp/fake_device_sync_client.cc b/chromeos/ash/services/device_sync/public/cpp/fake_device_sync_client.cc
index edcc4c1b..747620b7 100644
--- a/chromeos/ash/services/device_sync/public/cpp/fake_device_sync_client.cc
+++ b/chromeos/ash/services/device_sync/public/cpp/fake_device_sync_client.cc
@@ -158,6 +158,15 @@
   return force_sync_now_callback_queue_.size();
 }
 
+int FakeDeviceSyncClient::GetBetterTogetherMetadataStatusCallbackQueueSize()
+    const {
+  return get_better_together_metadata_status_callback_queue_.size();
+}
+
+int FakeDeviceSyncClient::GetGroupPrivateKeyStatusCallbackQueueSize() const {
+  return get_group_private_key_status_callback_queue_.size();
+}
+
 int FakeDeviceSyncClient::GetSetSoftwareFeatureStateInputsQueueSize() const {
   return set_software_feature_state_inputs_queue_.size();
 }
@@ -191,6 +200,21 @@
   force_sync_now_callback_queue_.pop_front();
 }
 
+void FakeDeviceSyncClient::InvokePendingGetBetterTogetherMetadataStatusCallback(
+    BetterTogetherMetadataStatus status) {
+  DCHECK(get_better_together_metadata_status_callback_queue_.size() > 0);
+  std::move(get_better_together_metadata_status_callback_queue_.front())
+      .Run(status);
+  get_better_together_metadata_status_callback_queue_.pop_front();
+}
+
+void FakeDeviceSyncClient::InvokePendingGetGroupPrivateKeyStatusCallback(
+    GroupPrivateKeyStatus status) {
+  DCHECK(get_group_private_key_status_callback_queue_.size() > 0);
+  std::move(get_group_private_key_status_callback_queue_.front()).Run(status);
+  get_group_private_key_status_callback_queue_.pop_front();
+}
+
 void FakeDeviceSyncClient::InvokePendingSetSoftwareFeatureStateCallback(
     mojom::NetworkRequestResult result_code) {
   DCHECK(set_software_feature_state_inputs_queue_.size() > 0);
diff --git a/chromeos/ash/services/device_sync/public/cpp/fake_device_sync_client.h b/chromeos/ash/services/device_sync/public/cpp/fake_device_sync_client.h
index 8b80f617..404cc1f 100644
--- a/chromeos/ash/services/device_sync/public/cpp/fake_device_sync_client.h
+++ b/chromeos/ash/services/device_sync/public/cpp/fake_device_sync_client.h
@@ -14,6 +14,7 @@
 #include "chromeos/ash/components/multidevice/remote_device_ref.h"
 #include "chromeos/ash/components/multidevice/software_feature.h"
 #include "chromeos/ash/services/device_sync/feature_status_change.h"
+#include "chromeos/ash/services/device_sync/group_private_key_and_better_together_metadata_status.h"
 #include "chromeos/ash/services/device_sync/proto/cryptauth_common.pb.h"
 #include "chromeos/ash/services/device_sync/public/cpp/device_sync_client.h"
 #include "chromeos/ash/services/device_sync/public/mojom/device_sync.mojom.h"
@@ -111,6 +112,8 @@
 
   int GetForceEnrollmentNowCallbackQueueSize() const;
   int GetForceSyncNowCallbackQueueSize() const;
+  int GetBetterTogetherMetadataStatusCallbackQueueSize() const;
+  int GetGroupPrivateKeyStatusCallbackQueueSize() const;
   int GetSetSoftwareFeatureStateInputsQueueSize() const;
   int GetSetFeatureStatusInputsQueueSize() const;
   int GetFindEligibleDevicesInputsQueueSize() const;
@@ -119,6 +122,10 @@
 
   void InvokePendingForceEnrollmentNowCallback(bool success);
   void InvokePendingForceSyncNowCallback(bool success);
+  void InvokePendingGetBetterTogetherMetadataStatusCallback(
+      BetterTogetherMetadataStatus status);
+  void InvokePendingGetGroupPrivateKeyStatusCallback(
+      GroupPrivateKeyStatus status);
   void InvokePendingSetSoftwareFeatureStateCallback(
       mojom::NetworkRequestResult result_code);
   void InvokePendingSetFeatureStatusCallback(
diff --git a/chromeos/ash/services/libassistant/BUILD.gn b/chromeos/ash/services/libassistant/BUILD.gn
index ef7197a..a67bf43 100644
--- a/chromeos/ash/services/libassistant/BUILD.gn
+++ b/chromeos/ash/services/libassistant/BUILD.gn
@@ -84,6 +84,7 @@
     ":constants",
     ":loader",
     "//base",
+    "//chromeos/ash/services/assistant/public/cpp",
     "//chromeos/assistant/internal:internal",
     "//sandbox/linux:sandbox_services",
     "//sandbox/policy",
diff --git a/chromeos/ash/services/libassistant/libassistant_sandbox_hook.cc b/chromeos/ash/services/libassistant/libassistant_sandbox_hook.cc
index 54abe2dd..36063be 100644
--- a/chromeos/ash/services/libassistant/libassistant_sandbox_hook.cc
+++ b/chromeos/ash/services/libassistant/libassistant_sandbox_hook.cc
@@ -7,6 +7,7 @@
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
 #include "base/system/sys_info.h"
+#include "chromeos/ash/services/assistant/public/cpp/features.h"
 #include "chromeos/ash/services/libassistant/constants.h"
 #include "chromeos/ash/services/libassistant/libassistant_loader_impl.h"
 #include "chromeos/assistant/internal/libassistant_util.h"
@@ -66,7 +67,10 @@
 bool LibassistantPreSandboxHook(
     sandbox::policy::SandboxLinux::Options options) {
   // Load libassistant DLC before the sandbox initializes.
-  LibassistantLoaderImpl::GetInstance()->LoadBlocking(kLibAssistantDlcRootPath);
+  if (assistant::features::IsLibAssistantDlcEnabled()) {
+    LibassistantLoaderImpl::GetInstance()->LoadBlocking(
+        kLibAssistantDlcRootPath);
+  }
 
   auto* instance = sandbox::policy::SandboxLinux::GetInstance();
   instance->StartBrokerProcess(
diff --git a/components/autofill/core/browser/autofill_merge_unittest.cc b/components/autofill/core/browser/autofill_merge_unittest.cc
index 1685470..732c7737 100644
--- a/components/autofill/core/browser/autofill_merge_unittest.cc
+++ b/components/autofill/core/browser/autofill_merge_unittest.cc
@@ -286,16 +286,16 @@
         field->set_heuristic_type(GetActivePatternSource(), type);
       }
 
-      // Import the profile.
-      auto imported_data = form_data_importer_->ImportFormData(
+      // Extract the profile.
+      auto extracted_data = form_data_importer_->ExtractFormData(
           form_structure,
           /*profile_autofill_enabled=*/true,
           /*payment_methods_autofill_enabled=*/true);
       form_data_importer_->ProcessAddressProfileImportCandidates(
-          imported_data.address_profile_import_candidates,
+          extracted_data.address_profile_import_candidates,
           /*allow_prompt=*/true);
-      EXPECT_FALSE(imported_data.credit_card_import_candidate);
-      EXPECT_FALSE(imported_data.imported_upi_id.has_value());
+      EXPECT_FALSE(extracted_data.credit_card_import_candidate);
+      EXPECT_FALSE(extracted_data.extracted_upi_id.has_value());
 
       // Clear the |form| to start a new profile.
       form.fields.clear();
diff --git a/components/autofill/core/browser/form_data_importer.cc b/components/autofill/core/browser/form_data_importer.cc
index 3630f29..ab55ba3 100644
--- a/components/autofill/core/browser/form_data_importer.cc
+++ b/components/autofill/core/browser/form_data_importer.cc
@@ -122,16 +122,16 @@
 
 }  // namespace
 
-FormDataImporter::ImportFormDataResult::ImportFormDataResult() = default;
+FormDataImporter::ExtractedFormData::ExtractedFormData() = default;
 
-FormDataImporter::ImportFormDataResult::ImportFormDataResult(
-    const ImportFormDataResult& imported_form_data) = default;
+FormDataImporter::ExtractedFormData::ExtractedFormData(
+    const ExtractedFormData& extracted_form_data) = default;
 
-FormDataImporter::ImportFormDataResult&
-FormDataImporter::ImportFormDataResult::operator=(
-    const ImportFormDataResult& imported_form_data) = default;
+FormDataImporter::ExtractedFormData&
+FormDataImporter::ExtractedFormData::operator=(
+    const ExtractedFormData& extracted_form_data) = default;
 
-FormDataImporter::ImportFormDataResult::~ImportFormDataResult() = default;
+FormDataImporter::ExtractedFormData::~ExtractedFormData() = default;
 
 FormDataImporter::FormDataImporter(AutofillClient* client,
                                    payments::PaymentsClient* payments_client,
@@ -187,16 +187,16 @@
     const FormStructure& submitted_form,
     bool profile_autofill_enabled,
     bool payment_methods_autofill_enabled) {
-  ImportFormDataResult imported_data =
-      ImportFormData(submitted_form, profile_autofill_enabled,
-                     payment_methods_autofill_enabled);
+  ExtractedFormData extracted_data =
+      ExtractFormData(submitted_form, profile_autofill_enabled,
+                      payment_methods_autofill_enabled);
 
   // Create a vector of address profile import candidates.
   // This is used to make preliminarily imported profiles available
   // to the credit card import logic.
   std::vector<AutofillProfile> preliminary_imported_address_profiles;
   for (const auto& candidate :
-       imported_data.address_profile_import_candidates) {
+       extracted_data.address_profile_import_candidates) {
     if (candidate.all_requirements_fulfilled)
       preliminary_imported_address_profiles.push_back(candidate.profile);
   }
@@ -204,22 +204,22 @@
       preliminary_imported_address_profiles);
 
   bool cc_prompt_potentially_shown = ProcessCreditCardImportCandidate(
-      submitted_form, imported_data.credit_card_import_candidate,
-      imported_data.imported_upi_id, payment_methods_autofill_enabled,
+      submitted_form, extracted_data.credit_card_import_candidate,
+      extracted_data.extracted_upi_id, payment_methods_autofill_enabled,
       credit_card_save_manager_->IsCreditCardUploadEnabled());
   fetched_card_instrument_id_.reset();
 
   bool iban_prompt_potentially_shown = false;
-  if (imported_data.iban_import_candidate.has_value() &&
+  if (extracted_data.iban_import_candidate.has_value() &&
       payment_methods_autofill_enabled) {
     iban_prompt_potentially_shown =
-        ProcessIBANImportCandidate(*imported_data.iban_import_candidate);
+        ProcessIBANImportCandidate(*extracted_data.iban_import_candidate);
   }
 
   // If a prompt for credit cards or IBANs is potentially shown, do not allow
   // for a second address profile import dialog.
   ProcessAddressProfileImportCandidates(
-      imported_data.address_profile_import_candidates,
+      extracted_data.address_profile_import_candidates,
       !cc_prompt_potentially_shown && !iban_prompt_potentially_shown);
 }
 
@@ -273,38 +273,40 @@
   fetched_card_instrument_id_ = instrument_id;
 }
 
-FormDataImporter::ImportFormDataResult FormDataImporter::ImportFormData(
+FormDataImporter::ExtractedFormData FormDataImporter::ExtractFormData(
     const FormStructure& submitted_form,
     bool profile_autofill_enabled,
     bool payment_methods_autofill_enabled) {
-  ImportFormDataResult imported_form_data;
+  ExtractedFormData extracted_form_data;
   // We try the same `form` for both credit card and address import/update.
-  // - `ImportCreditCard()` may update an existing card, or fill
-  //   `credit_card_import_candidate` contained in `imported_form_data` with an
+  // - `ExtractCreditCard()` may update an existing card, or fill
+  //   `credit_card_import_candidate` contained in `extracted_form_data` with an
   //   extracted card.
-  // - `ImportAddressProfiles()` collects all importable profiles, but currently
+  // - `ExtractAddressProfiles()` collects all importable
+  // profiles, but currently
   //   at most one import prompt is shown.
-  // Reset `imported_credit_card_record_type_` every time we import data from
-  // form no matter whether `ImportCreditCard()` is called or not.
-  imported_credit_card_record_type_ = ImportedCreditCardRecordType::kNoCard;
+  // Reset `credit_card_import_type_` every time we extract
+  // data from form no matter whether `ExtractCreditCard()` is
+  // called or not.
+  credit_card_import_type_ = CreditCardImportType::kNoCard;
   if (payment_methods_autofill_enabled) {
-    imported_form_data.credit_card_import_candidate =
-        ImportCreditCard(submitted_form);
-    imported_form_data.imported_upi_id = ImportUpiId(submitted_form);
+    extracted_form_data.credit_card_import_candidate =
+        ExtractCreditCard(submitted_form);
+    extracted_form_data.extracted_upi_id = ExtractUpiId(submitted_form);
   }
 
 #if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
   if (base::FeatureList::IsEnabled(features::kAutofillFillIbanFields) &&
       payment_methods_autofill_enabled) {
-    imported_form_data.iban_import_candidate = ImportIBAN(submitted_form);
+    extracted_form_data.iban_import_candidate = ExtractIBAN(submitted_form);
   }
 #endif  // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
 
   size_t num_complete_address_profiles = 0;
   if (profile_autofill_enabled &&
       !base::FeatureList::IsEnabled(features::kAutofillDisableAddressImport)) {
-    num_complete_address_profiles = ImportAddressProfiles(
-        submitted_form, &imported_form_data.address_profile_import_candidates);
+    num_complete_address_profiles = ExtractAddressProfiles(
+        submitted_form, &extracted_form_data.address_profile_import_candidates);
   }
 
   if (profile_autofill_enabled && payment_methods_autofill_enabled &&
@@ -317,24 +319,24 @@
       form_associator_.TrackFormAssociations(
           origin, form_signature, FormAssociator::FormType::kAddressForm);
     }
-    if (imported_form_data.credit_card_import_candidate) {
+    if (extracted_form_data.credit_card_import_candidate) {
       form_associator_.TrackFormAssociations(
           origin, form_signature, FormAssociator::FormType::kCreditCardForm);
     }
   }
 
-  if (!imported_form_data.credit_card_import_candidate &&
-      !imported_form_data.imported_upi_id &&
+  if (!extracted_form_data.credit_card_import_candidate &&
+      !extracted_form_data.extracted_upi_id &&
       num_complete_address_profiles == 0 &&
-      (!imported_form_data.iban_import_candidate ||
-       imported_form_data.iban_import_candidate->record_type() !=
+      (!extracted_form_data.iban_import_candidate ||
+       extracted_form_data.iban_import_candidate->record_type() !=
            IBAN::NEW_IBAN)) {
     personal_data_manager_->MarkObserversInsufficientFormDataForImport();
   }
-  return imported_form_data;
+  return extracted_form_data;
 }
 
-size_t FormDataImporter::ImportAddressProfiles(
+size_t FormDataImporter::ExtractAddressProfiles(
     const FormStructure& form,
     std::vector<FormDataImporter::AddressProfileImportCandidate>*
         address_profile_import_candidates) {
@@ -371,25 +373,26 @@
       LOG_AF(import_log_buffer)
           << LogMessage::kImportAddressProfileFromFormSection << section
           << CTag{};
-      // Try to import an address profile from the form fields of this section.
+      // Try to extract an address profile from the form fields of this section.
       // Only allow for a prompt if no other complete profile was found so far.
-      if (ImportAddressProfileForSection(form, section,
-                                         address_profile_import_candidates,
-                                         &import_log_buffer))
+      if (ExtractAddressProfileFromSection(form, section,
+                                           address_profile_import_candidates,
+                                           &import_log_buffer)) {
         num_complete_profiles++;
+      }
       // And close the div of the section import log.
       LOG_AF(import_log_buffer) << CTag{"div"};
     }
-    // Run the import on the union of the section if the import was not
+    // Run the extract on the union of the section if the import was not
     // successful and if there is more than one section.
     if (num_complete_profiles > 0) {
       AutofillMetrics::LogAddressFormImportStatusMetric(
           AutofillMetrics::AddressProfileImportStatusMetric::REGULAR_IMPORT);
     } else if (sections.size() > 1) {
-      // Try to import by combining all sections.
-      if (ImportAddressProfileForSection(form, absl::nullopt,
-                                         address_profile_import_candidates,
-                                         &import_log_buffer)) {
+      // Try to extract by combining all sections.
+      if (ExtractAddressProfileFromSection(form, absl::nullopt,
+                                           address_profile_import_candidates,
+                                           &import_log_buffer)) {
         num_complete_profiles++;
         AutofillMetrics::LogAddressFormImportStatusMetric(
             AutofillMetrics::AddressProfileImportStatusMetric::
@@ -411,7 +414,7 @@
   return num_complete_profiles;
 }
 
-bool FormDataImporter::ImportAddressProfileForSection(
+bool FormDataImporter::ExtractAddressProfileFromSection(
     const FormStructure& form,
     const absl::optional<Section>& section,
     std::vector<FormDataImporter::AddressProfileImportCandidate>*
@@ -466,7 +469,7 @@
 
     AutofillType field_type = field->Type();
 
-    // Credit card fields are handled by ImportCreditCard().
+    // Credit card fields are handled by ExtractCreditCard().
     if (field_type.group() == FieldTypeGroup::kCreditCard)
       continue;
 
@@ -728,18 +731,17 @@
 bool FormDataImporter::ProcessCreditCardImportCandidate(
     const FormStructure& submitted_form,
     const absl::optional<CreditCard>& credit_card_import_candidate,
-    const absl::optional<std::string>& imported_upi_id,
+    const absl::optional<std::string>& extracted_upi_id,
     bool payment_methods_autofill_enabled,
     bool is_credit_card_upstream_enabled) {
 #if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
-  if (imported_upi_id && payment_methods_autofill_enabled &&
+  if (extracted_upi_id && payment_methods_autofill_enabled &&
       base::FeatureList::IsEnabled(features::kAutofillSaveAndFillVPA)) {
-    upi_vpa_save_manager_->OfferLocalSave(*imported_upi_id);
+    upi_vpa_save_manager_->OfferLocalSave(*extracted_upi_id);
   }
 #endif  // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
-  // If no card was successfully imported from the form, return.
-  if (imported_credit_card_record_type_ ==
-      ImportedCreditCardRecordType::kNoCard) {
+  // If no card was successfully extracted from the form, return.
+  if (credit_card_import_type_ == CreditCardImportType::kNoCard) {
     return false;
   }
   // Do not offer upload save for google domain.
@@ -757,12 +759,12 @@
   }
 
 #if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
-  // A credit card was successfully imported, but it's possible it is already a
+  // A credit card was successfully extracted, but it's possible it is already a
   // local or server card. First, check to see if we should offer local card
   // migration in this case, as local cards could go either way.
   if (local_card_migration_manager_ &&
       local_card_migration_manager_->ShouldOfferLocalCardMigration(
-          credit_card_import_candidate, imported_credit_card_record_type_)) {
+          credit_card_import_candidate, credit_card_import_type_)) {
     local_card_migration_manager_->AttemptToOfferLocalCardMigration(
         /*is_from_settings_page=*/false);
     return true;
@@ -780,25 +782,22 @@
   // We have a card to save; decide what type of save flow to display.
   if (is_credit_card_upstream_enabled) {
     // Attempt to offer upload save. Because we pass
-    // |credit_card_upstream_enabled| to ImportFormData, this block can be
-    // reached on observing either a new card or one already stored locally
-    // which doesn't match an existing server card. If Google Payments declines
-    // allowing upload, |credit_card_save_manager_| is tasked with deciding if
-    // we should fall back to local save or not.
-    DCHECK(imported_credit_card_record_type_ ==
-               ImportedCreditCardRecordType::kLocalCard ||
-           imported_credit_card_record_type_ ==
-               ImportedCreditCardRecordType::kNewCard);
+    // `credit_card_upstream_enabled` to ExtractFormImportCandidates, this block
+    // can be reached on observing either a new card or one already stored
+    // locally which doesn't match an existing server card. If Google Payments
+    // declines allowing upload, `credit_card_save_manager_` is tasked with
+    // deciding if we should fall back to local save or not.
+    DCHECK(credit_card_import_type_ == CreditCardImportType::kLocalCard ||
+           credit_card_import_type_ == CreditCardImportType::kNewCard);
     credit_card_save_manager_->AttemptToOfferCardUploadSave(
         submitted_form, from_dynamic_change_form_, has_non_focusable_field_,
         *credit_card_import_candidate,
-        /*uploading_local_card=*/imported_credit_card_record_type_ ==
-            ImportedCreditCardRecordType::kLocalCard);
+        /*uploading_local_card=*/credit_card_import_type_ ==
+            CreditCardImportType::kLocalCard);
     return true;
   };
   // If upload save is not allowed, new cards should be saved locally.
-  DCHECK(imported_credit_card_record_type_ ==
-         ImportedCreditCardRecordType::kNewCard);
+  DCHECK(credit_card_import_type_ == CreditCardImportType::kNewCard);
   if (credit_card_save_manager_->AttemptToOfferCardLocalSave(
           from_dynamic_change_form_, has_non_focusable_field_,
           *credit_card_import_candidate)) {
@@ -821,7 +820,7 @@
   return false;
 }
 
-absl::optional<CreditCard> FormDataImporter::ImportCreditCard(
+absl::optional<CreditCard> FormDataImporter::ExtractCreditCard(
     const FormStructure& form) {
   // The candidate for credit card import. There are many ways for the candidate
   // to be rejected as indicated by the `return absl::nullopt` statements below.
@@ -856,7 +855,7 @@
 
   // Can import one valid card per form. Start by treating it as kNewCard, but
   // overwrite this type if we discover it is already a local or server card.
-  imported_credit_card_record_type_ = ImportedCreditCardRecordType::kNewCard;
+  credit_card_import_type_ = CreditCardImportType::kNewCard;
 
   // Attempt to merge with an existing local credit card without presenting a
   // prompt.
@@ -867,8 +866,7 @@
     CreditCard maybe_updated_card = *local_card;
     if (maybe_updated_card.UpdateFromImportedCard(candidate, app_locale_)) {
       personal_data_manager_->UpdateCreditCard(maybe_updated_card);
-      imported_credit_card_record_type_ =
-          ImportedCreditCardRecordType::kLocalCard;
+      credit_card_import_type_ = CreditCardImportType::kLocalCard;
       if (!maybe_updated_card.nickname().empty()) {
         // The nickname may be shown in the upload save bubble.
         candidate.SetNickname(maybe_updated_card.nickname());
@@ -900,7 +898,7 @@
   if (candidate.expiration_month() == 0 || candidate.expiration_year() == 0)
     return absl::nullopt;
 
-  imported_credit_card_record_type_ = ImportedCreditCardRecordType::kServerCard;
+  credit_card_import_type_ = CreditCardImportType::kServerCard;
 
   if (candidate.expiration_month() == server_card->expiration_month() &&
       candidate.expiration_year() == server_card->expiration_year()) {
@@ -919,7 +917,7 @@
   return server_card;
 }
 
-absl::optional<IBAN> FormDataImporter::ImportIBAN(const FormStructure& form) {
+absl::optional<IBAN> FormDataImporter::ExtractIBAN(const FormStructure& form) {
   IBAN candidate_iban = ExtractIBANFromForm(form);
   if (candidate_iban.value().empty())
     return absl::nullopt;
@@ -1018,7 +1016,7 @@
   return candidate_iban;
 }
 
-absl::optional<std::string> FormDataImporter::ImportUpiId(
+absl::optional<std::string> FormDataImporter::ExtractUpiId(
     const FormStructure& form) {
   for (const auto& field : form) {
     if (IsUPIVirtualPaymentAddress(field->value))
@@ -1037,8 +1035,7 @@
     return false;
 
   // We do not want to offer upload save or local card save for server cards.
-  if (imported_credit_card_record_type_ ==
-      ImportedCreditCardRecordType::kServerCard) {
+  if (credit_card_import_type_ == CreditCardImportType::kServerCard) {
     return false;
   }
 
@@ -1046,8 +1043,7 @@
   // want to offer upload save as it is disabled and we do not want to offer
   // local card save as it is already saved as a local card.
   if (!is_credit_card_upload_enabled &&
-      imported_credit_card_record_type_ ==
-          ImportedCreditCardRecordType::kLocalCard) {
+      credit_card_import_type_ == CreditCardImportType::kLocalCard) {
     return false;
   }
 
diff --git a/components/autofill/core/browser/form_data_importer.h b/components/autofill/core/browser/form_data_importer.h
index 5e9a8d6..b070305f 100644
--- a/components/autofill/core/browser/form_data_importer.h
+++ b/components/autofill/core/browser/form_data_importer.h
@@ -37,8 +37,9 @@
 // Owned by `ChromeAutofillClient`.
 class FormDataImporter : public PersonalDataManagerObserver {
  public:
-  // Record type of the credit card imported from the form, if one exists.
-  enum ImportedCreditCardRecordType {
+  // Record type of the credit card import candidate extracted from the form, if
+  // one exists.
+  enum CreditCardImportType {
     // No card was successfully imported from the form.
     kNoCard,
     // The imported card is already stored locally on the device.
@@ -121,13 +122,12 @@
     return form_associator_.GetFormAssociations(form_signature);
   }
 
-  ImportedCreditCardRecordType imported_credit_card_record_type_for_testing()
-      const {
-    return imported_credit_card_record_type_;
+  CreditCardImportType credit_card_import_type_for_testing() const {
+    return credit_card_import_type_;
   }
-  void set_imported_credit_card_record_type_for_testing(
-      ImportedCreditCardRecordType imported_credit_card_record_type) {
-    imported_credit_card_record_type_ = imported_credit_card_record_type;
+  void set_credit_card_import_type_for_testing(
+      CreditCardImportType credit_card_import_type) {
+    credit_card_import_type_ = credit_card_import_type;
   }
 
  protected:
@@ -171,50 +171,50 @@
     ProfileImportMetadata import_metadata;
   };
 
-  // Defines data imported from the form.
-  struct ImportFormDataResult {
-    ImportFormDataResult();
-    ImportFormDataResult(const ImportFormDataResult& imported_form_data);
-    ImportFormDataResult& operator=(
-        const ImportFormDataResult& imported_form_data);
-    ~ImportFormDataResult();
+  // Defines data extracted from the form.
+  struct ExtractedFormData {
+    ExtractedFormData();
+    ExtractedFormData(const ExtractedFormData& extracted_form_data);
+    ExtractedFormData& operator=(const ExtractedFormData& extracted_form_data);
+    ~ExtractedFormData();
 
     // Credit card extracted from the form, which is a candidate for importing.
     // This credit card will be present after extraction if the form contained a
     // valid credit card, and the preconditions for extracting the credit card
-    // were met. See ImportCreditCard() for details on when
+    // were met. See `ExtractCreditCard()` for details on when
     // the preconditions are met for extracting a credit card from a form.
     absl::optional<CreditCard> credit_card_import_candidate;
-    // List of address profiles which are candidates for importing. The list is
-    // empty if none of the address profile fulfill import requirements.
+    // List of address profiles extracted from the form, which are candidates
+    // for importing. The list is empty if none of the address profile fulfill
+    // import requirements.
     std::vector<AddressProfileImportCandidate>
         address_profile_import_candidates;
     // IBAN extracted from the form, which is a candidate for importing. Present
     // if an IBAN is found in the form.
     absl::optional<IBAN> iban_import_candidate;
     // Present if a UPI (Unified Payment Interface) ID is found in the form.
-    absl::optional<std::string> imported_upi_id;
+    absl::optional<std::string> extracted_upi_id;
   };
 
-  // Scans the given `form` for importable Autofill data.
-  ImportFormDataResult ImportFormData(const FormStructure& form,
-                                      bool profile_autofill_enabled,
-                                      bool payment_methods_autofill_enabled);
+  // Scans the given `form` for extractable Autofill data.
+  ExtractedFormData ExtractFormData(const FormStructure& form,
+                                    bool profile_autofill_enabled,
+                                    bool payment_methods_autofill_enabled);
 
   // Attempts to construct AddressProfileImportCandidates by extracting values
-  // from the fields in the |form|'s sections. Extraction can fail if the
+  // from the fields in the `form`'s sections. Extraction can fail if the
   // fields' values don't pass validation. Apart from complete address profiles,
   // partial profiles for silent updates are extracted. All are stored in
-  // |imported_form_data|'s |address_profile_import_candidates|.
+  // `extracted_form_data`'s `address_profile_import_candidates`.
   // The function returns the number of _complete_ extracted profiles.
-  size_t ImportAddressProfiles(const FormStructure& form,
-                               std::vector<AddressProfileImportCandidate>*
-                                   address_profile_import_candidates);
+  size_t ExtractAddressProfiles(const FormStructure& form,
+                                std::vector<AddressProfileImportCandidate>*
+                                    address_profile_import_candidates);
 
   // Helper method for ImportAddressProfiles which only considers the fields for
-  // a specified |section|. If no section is passed, the import is performed on
+  // a specified `section`. If no section is passed, the import is performed on
   // the union of all sections.
-  bool ImportAddressProfileForSection(
+  bool ExtractAddressProfileFromSection(
       const FormStructure& form,
       const absl::optional<Section>& section,
       std::vector<AddressProfileImportCandidate>*
@@ -235,11 +235,11 @@
   // The function has two side-effects:
   // - all matching local cards are updated to include the information from the
   //   extracted card;
-  // - `imported_credit_card_record_type_` is set to
+  // - `credit_card_import_type_` is set to
   //   - SERVER_CARD if a server card matches;
   //   - LOCAL_CARD if a local and no server card matches;
   //   - NEW_CARD otherwise.
-  absl::optional<CreditCard> ImportCreditCard(const FormStructure& form);
+  absl::optional<CreditCard> ExtractCreditCard(const FormStructure& form);
 
   // Returns the extracted IBAN from the `form` if applicable.
   // This is the case if it is a new IBAN or a local IBAN.
@@ -248,17 +248,17 @@
   // - record_type of the returned IBAN is set to
   //   - LOCAL_IBAN if a local IBAN matches;
   //   - NEW_IBAN if no local IBAN matches
-  absl::optional<IBAN> ImportIBAN(const FormStructure& form);
+  absl::optional<IBAN> ExtractIBAN(const FormStructure& form);
 
   // Tries to initiate the saving of the `credit_card_import_candidate`
   // if applicable. `submitted_form` is the form from which the card was
-  // imported. If a UPI id was found it is stored in `imported_upi_id`.
+  // imported. If a UPI id was found it is stored in `extracted_upi_id`.
   // `is_credit_card_upstream_enabled` indicates if server card storage is
   // enabled. Returns true if a save is initiated.
   bool ProcessCreditCardImportCandidate(
       const FormStructure& submitted_form,
       const absl::optional<CreditCard>& credit_card_import_candidate,
-      const absl::optional<std::string>& imported_upi_id,
+      const absl::optional<std::string>& extracted_upi_id,
       bool payment_methods_autofill_enabled,
       bool is_credit_card_upstream_enabled);
 
@@ -274,9 +274,9 @@
   // Extracts the IBAN from the form structure.
   IBAN ExtractIBANFromForm(const FormStructure& form);
 
-  // Go through the |form| fields and find a UPI ID to import. The return value
+  // Go through the `form` fields and find a UPI ID to extract. The return value
   // will be empty if no UPI ID was found.
-  absl::optional<std::string> ImportUpiId(const FormStructure& form);
+  absl::optional<std::string> ExtractUpiId(const FormStructure& form);
 
   // Returns true if credit card upload or local save should be offered to user.
   // |credit_card_import_candidate| is the credit card imported from the form if
@@ -342,11 +342,11 @@
   // May be NULL.  NULL indicates OTR.
   raw_ptr<PersonalDataManager> personal_data_manager_;
 
-  // Represents the type of the imported credit card from the submitted form.
-  // It will be used to determine whether to offer upload save or card
-  // migration. Will be passed to `credit_card_save_manager_` for metrics.
-  ImportedCreditCardRecordType imported_credit_card_record_type_ =
-      ImportedCreditCardRecordType::kNoCard;
+  // Represents the type of the credit card import candidate from the submitted
+  // form. It will be used to determine whether to offer upload save or card
+  // migration. Will be passed to `credit_card_save_manager_` for metrics. If no
+  // credit card was found in the form, the type will be `kNoCard`.
+  CreditCardImportType credit_card_import_type_ = CreditCardImportType::kNoCard;
 
   std::string app_locale_;
 
diff --git a/components/autofill/core/browser/form_data_importer_unittest.cc b/components/autofill/core/browser/form_data_importer_unittest.cc
index ff9b84c..027b224 100644
--- a/components/autofill/core/browser/form_data_importer_unittest.cc
+++ b/components/autofill/core/browser/form_data_importer_unittest.cc
@@ -481,7 +481,7 @@
 
 class FormDataImporterTestBase {
  public:
-  using ImportFormDataResult = FormDataImporter::ImportFormDataResult;
+  using ExtractedFormData = FormDataImporter::ExtractedFormData;
   using AddressProfileImportCandidate =
       FormDataImporter::AddressProfileImportCandidate;
 
@@ -609,16 +609,16 @@
 
   // Helper methods that simply forward the call to the private member (to avoid
   // having to friend every test that needs to access the private
-  // PersonalDataManager::ImportAddressProfile or ImportCreditCard).
-  void ImportAddressProfiles(bool extraction_successful,
-                             const FormStructure& form,
-                             bool skip_waiting_on_pdm = false,
-                             bool allow_save_prompts = true) {
+  // PersonalDataManager::ImportAddressProfile or ExtractCreditCard).
+  void ExtractAddressProfiles(bool extraction_successful,
+                              const FormStructure& form,
+                              bool skip_waiting_on_pdm = false,
+                              bool allow_save_prompts = true) {
     std::vector<FormDataImporter::AddressProfileImportCandidate>
         address_profile_import_candidates;
 
     EXPECT_EQ(extraction_successful,
-              form_data_importer().ImportAddressProfiles(
+              form_data_importer().ExtractAddressProfiles(
                   form, &address_profile_import_candidates) > 0);
 
     if (!extraction_successful) {
@@ -645,70 +645,72 @@
   }
 
   // Verifies that the stored profiles in the PersonalDataManager equal
-  // |expected_profiles| with respect to |AutofillProfile::Compare|.
+  // `expected_profiles` with respect to `AutofillProfile::Compare`.
   // Note, that order is taken into account.
-  void VerifyExpectationForImportedAddressProfiles(
+  void VerifyExpectationForExtractedAddressProfiles(
       const std::vector<AutofillProfile>& expected_profiles) {
     EXPECT_THAT(personal_data_manager_->GetProfiles(),
                 UnorderedElementsCompareEqualArray(expected_profiles));
   }
 
-  // Convenience wrapper that calls |FormDataImporter::ImportFormData()| and
-  // subsequently processes the candidates for address profile import.
-  // Returns the result of |FormDataImporter::ImportFormData()|.
-  ImportFormDataResult ImportFormDataAndProcessAddressCandidates(
+  // Convenience wrapper that calls
+  // `FormDataImporter::ExtractFormData()` and subsequently
+  // processes the candidates for address profile import. Returns the result of
+  // `FormDataImporter::ExtractFormData()`.
+  ExtractedFormData ExtractFormDataAndProcessAddressCandidates(
       const FormStructure& form,
       bool profile_autofill_enabled,
       bool payment_methods_autofill_enabled) {
-    ImportFormDataResult imported_data = form_data_importer().ImportFormData(
+    ExtractedFormData extracted_data = form_data_importer().ExtractFormData(
         form, profile_autofill_enabled, payment_methods_autofill_enabled);
     form_data_importer().ProcessAddressProfileImportCandidates(
-        imported_data.address_profile_import_candidates);
-    return imported_data;
+        extracted_data.address_profile_import_candidates);
+    return extracted_data;
   }
 
-  // Convenience wrapper around `ImportFormDataAndProcessAddressCandidates()`.
-  void ImportFormDataAndProcessAddressCandidates(const FormStructure& form) {
-    std::ignore = ImportFormDataAndProcessAddressCandidates(
+  // Convenience wrapper around `ExtractFormDataAndProcessAddressCandidates()`.
+  void ExtractFormDataAndProcessAddressCandidates(const FormStructure& form) {
+    std::ignore = ExtractFormDataAndProcessAddressCandidates(
         form, /*profile_autofill_enabled=*/true,
         /*payment_methods_autofill_enabled=*/true);
   }
 
-  // Convenience wrapper that calls `FormDataImporter::ImportFormData()` and
-  // subsequently processes the candidates for IBAN import candidate.
-  // Returns the result of `FormDataImporter::ProcessIBANImportCandidate()`.
-  bool ImportFormDataAndProcessIBANCandidates(
+  // Convenience wrapper that calls
+  // `FormDataImporter::ExtractFormData()` and subsequently
+  // processes the candidates for IBAN import candidate. Returns the result of
+  // `FormDataImporter::ProcessIBANImportCandidate()`.
+  bool ExtractFormDataAndProcessIBANCandidates(
       const FormStructure& form,
       bool profile_autofill_enabled,
       bool payment_methods_autofill_enabled) {
-    ImportFormDataResult imported_data = form_data_importer().ImportFormData(
+    ExtractedFormData extracted_data = form_data_importer().ExtractFormData(
         form, profile_autofill_enabled, payment_methods_autofill_enabled);
-    return imported_data.iban_import_candidate &&
+    return extracted_data.iban_import_candidate &&
            form_data_importer().ProcessIBANImportCandidate(
-               imported_data.iban_import_candidate.value());
+               extracted_data.iban_import_candidate.value());
   }
 
-  void ImportAddressProfilesAndVerifyExpectation(
+  void ExtractAddressProfilesAndVerifyExpectation(
       const FormStructure& form,
       const std::vector<AutofillProfile>& expected_profiles) {
-    ImportAddressProfiles(/*extraction_successful=*/!expected_profiles.empty(),
-                          form);
-    VerifyExpectationForImportedAddressProfiles(expected_profiles);
+    ExtractAddressProfiles(
+        /*extraction_successful=*/!expected_profiles.empty(), form);
+    VerifyExpectationForExtractedAddressProfiles(expected_profiles);
   }
 
-  void ImportAddressProfileAndVerifyImportOfDefaultProfile(
+  void ExtractAddressProfileAndVerifyExtractionOfDefaultProfile(
       const FormStructure& form) {
-    ImportAddressProfilesAndVerifyExpectation(form,
-                                              {ConstructDefaultProfile()});
+    ExtractAddressProfilesAndVerifyExpectation(form,
+                                               {ConstructDefaultProfile()});
   }
 
   void ImportAddressProfileAndVerifyImportOfNoProfile(
       const FormStructure& form) {
-    ImportAddressProfilesAndVerifyExpectation(form, {});
+    ExtractAddressProfilesAndVerifyExpectation(form, {});
   }
 
-  absl::optional<CreditCard> ImportCreditCard(const FormStructure& form) {
-    return form_data_importer().ImportCreditCard(form);
+  absl::optional<CreditCard> ExtractCreditCard(const FormStructure& form) {
+    return form_data_importer().ExtractCreditCard(form);
   }
 
   void SubmitFormAndExpectImportedCardWithData(const FormData& form,
@@ -719,7 +721,7 @@
     FormStructure form_structure(form);
     form_structure.DetermineHeuristicTypes(nullptr, nullptr);
     absl::optional<CreditCard> credit_card_import_candidate =
-        ImportCreditCard(form_structure);
+        ExtractCreditCard(form_structure);
     EXPECT_TRUE(credit_card_import_candidate);
     personal_data_manager_->OnAcceptedLocalCreditCardSave(
         *credit_card_import_candidate);
@@ -769,7 +771,7 @@
           std::tuple<AutofillEnableSupportForApartmentNumbers,
                      AutofillFillIbanFields>> {
  public:
-  using ImportFormDataResult = FormDataImporter::ImportFormDataResult;
+  using ExtractedFormData = FormDataImporter::ExtractedFormData;
 
  private:
   void SetUp() override {
@@ -805,8 +807,8 @@
             ConstructFormStructureFromTypeValuePairs(
                 GetDefaultProfileTypeValuePairsWithOverriddenCountry(
                     form_country));
-        ImportAddressProfilesAndVerifyExpectation(*form_structure,
-                                                  expected_profiles);
+        ExtractAddressProfilesAndVerifyExpectation(*form_structure,
+                                                   expected_profiles);
       };
   // The German profile doesn't expect a state.
   AutofillProfile kDefaultGermanProfile =
@@ -861,8 +863,8 @@
     // "en_US" locale. Thus, parsing fails and the phone number is removed.
     base::HistogramTester histogram_tester;
     expected_profile.ClearFields({PHONE_HOME_WHOLE_NUMBER});
-    ImportAddressProfilesAndVerifyExpectation(*form_structure,
-                                              {expected_profile});
+    ExtractAddressProfilesAndVerifyExpectation(*form_structure,
+                                               {expected_profile});
     EXPECT_THAT(histogram_tester.GetAllSamples(kHistogramName),
                 testing::UnorderedElementsAre(base::Bucket(false, 1)));
   }
@@ -879,8 +881,8 @@
     // profile's country is "DE".
     EXPECT_TRUE(expected_profile.SetInfo(
         PHONE_HOME_WHOLE_NUMBER, base::UTF8ToUTF16(kNationalNumber), kLocale));
-    ImportAddressProfilesAndVerifyExpectation(*form_structure,
-                                              {expected_profile});
+    ExtractAddressProfilesAndVerifyExpectation(*form_structure,
+                                               {expected_profile});
     EXPECT_THAT(histogram_tester.GetAllSamples(kHistogramName),
                 testing::UnorderedElementsAre(base::Bucket(true, 1)));
   }
@@ -907,7 +909,7 @@
     base::test::ScopedFeatureList ignore_invalid_country_feature;
     ignore_invalid_country_feature.InitAndEnableFeature(
         features::kAutofillIgnoreInvalidCountryOnImport);
-    ImportAddressProfileAndVerifyImportOfDefaultProfile(*form_structure);
+    ExtractAddressProfileAndVerifyExtractionOfDefaultProfile(*form_structure);
   }
 }
 
@@ -920,8 +922,8 @@
 
   auto profile_without_number = ConstructDefaultProfile();
   profile_without_number.ClearFields({PHONE_HOME_WHOLE_NUMBER});
-  ImportAddressProfilesAndVerifyExpectation(*form_structure,
-                                            {profile_without_number});
+  ExtractAddressProfilesAndVerifyExpectation(*form_structure,
+                                             {profile_without_number});
 }
 
 // ImportAddressProfiles tests.
@@ -947,7 +949,7 @@
   form.fields.push_back(field);
   FormStructure form_structure(form);
   form_structure.DetermineHeuristicTypes(nullptr, nullptr);
-  ImportAddressProfiles(/*extraction_successful=*/true, form_structure);
+  ExtractAddressProfiles(/*extraction_successful=*/true, form_structure);
 
   const std::vector<AutofillProfile*>& results =
       personal_data_manager_->GetProfiles();
@@ -993,7 +995,7 @@
   form.fields.push_back(field);
   FormStructure form_structure(form);
   form_structure.DetermineHeuristicTypes(nullptr, nullptr);
-  ImportAddressProfiles(/*extraction_successful=*/true, form_structure);
+  ExtractAddressProfiles(/*extraction_successful=*/true, form_structure);
 
   const std::vector<AutofillProfile*>& results =
       personal_data_manager_->GetProfiles();
@@ -1047,7 +1049,7 @@
   form.fields.push_back(field);
   FormStructure form_structure(form);
   form_structure.DetermineHeuristicTypes(nullptr, nullptr);
-  ImportAddressProfiles(/*extraction_successful=*/true, form_structure);
+  ExtractAddressProfiles(/*extraction_successful=*/true, form_structure);
 
   const std::vector<AutofillProfile*>& results =
       personal_data_manager_->GetProfiles();
@@ -1093,7 +1095,7 @@
   form.fields.push_back(field);
   FormStructure form_structure(form);
   form_structure.DetermineHeuristicTypes(nullptr, nullptr);
-  ImportAddressProfiles(/*extraction_successful=*/true, form_structure);
+  ExtractAddressProfiles(/*extraction_successful=*/true, form_structure);
 
   const std::vector<AutofillProfile*>& results =
       personal_data_manager_->GetProfiles();
@@ -1136,7 +1138,7 @@
   form.fields.push_back(field);
   FormStructure form_structure(form);
   form_structure.DetermineHeuristicTypes(nullptr, nullptr);
-  ImportAddressProfiles(/*extraction_successful=*/true, form_structure);
+  ExtractAddressProfiles(/*extraction_successful=*/true, form_structure);
 
   const std::vector<AutofillProfile*>& results =
       personal_data_manager_->GetProfiles();
@@ -1154,21 +1156,21 @@
 TEST_P(FormDataImporterTest, ImportAddressProfiles) {
   std::unique_ptr<FormStructure> form_structure =
       ConstructDefaultProfileFormStructure();
-  ImportAddressProfileAndVerifyImportOfDefaultProfile(*form_structure);
+  ExtractAddressProfileAndVerifyExtractionOfDefaultProfile(*form_structure);
 }
 
 TEST_P(FormDataImporterTest, ImportSecondAddressProfiles) {
   std::unique_ptr<FormStructure> form_structure =
       ConstructSecondProfileFormStructure();
-  ImportAddressProfilesAndVerifyExpectation(*form_structure,
-                                            {ConstructSecondProfile()});
+  ExtractAddressProfilesAndVerifyExpectation(*form_structure,
+                                             {ConstructSecondProfile()});
 }
 
 TEST_P(FormDataImporterTest, ImportThirdAddressProfiles) {
   std::unique_ptr<FormStructure> form_structure =
       ConstructThirdProfileFormStructure();
-  ImportAddressProfilesAndVerifyExpectation(*form_structure,
-                                            {ConstructThirdProfile()});
+  ExtractAddressProfilesAndVerifyExpectation(*form_structure,
+                                             {ConstructThirdProfile()});
 }
 
 // Test that with dependent locality parsing enabled, dependent locality fields
@@ -1185,7 +1187,7 @@
                           "Bosques de las Lomas");
   std::unique_ptr<FormStructure> form_structure =
       ConstructFormStructureFromTypeValuePairs(mx_profile);
-  ImportAddressProfilesAndVerifyExpectation(
+  ExtractAddressProfilesAndVerifyExpectation(
       *form_structure, {ConstructProfileFromTypeValuePairs(mx_profile)});
 }
 
@@ -1194,10 +1196,10 @@
 TEST_P(FormDataImporterTest, ImportAddressProfiles_DontAllowPrompt) {
   std::unique_ptr<FormStructure> form_structure =
       ConstructDefaultProfileFormStructure();
-  ImportAddressProfiles(/*extraction_successful=*/true, *form_structure,
-                        /*skip_waiting_on_pdm=*/true,
-                        /*allow_save_prompts=*/false);
-  VerifyExpectationForImportedAddressProfiles({});
+  ExtractAddressProfiles(/*extraction_successful=*/true, *form_structure,
+                         /*skip_waiting_on_pdm=*/true,
+                         /*allow_save_prompts=*/false);
+  VerifyExpectationForExtractedAddressProfiles({});
 }
 
 TEST_P(FormDataImporterTest, ImportAddressProfileFromUnifiedSection) {
@@ -1208,7 +1210,7 @@
   form_structure->field(4)->section =
       Section::FromAutocomplete({.section = "another_section"});
 
-  ImportAddressProfileAndVerifyImportOfDefaultProfile(*form_structure);
+  ExtractAddressProfileAndVerifyExtractionOfDefaultProfile(*form_structure);
 }
 
 TEST_P(FormDataImporterTest, ImportAddressProfiles_BadEmail) {
@@ -1238,7 +1240,7 @@
            {ADDRESS_HOME_STATE, kDefaultState},
            {ADDRESS_HOME_ZIP, kDefaultZip}});
 
-  ImportAddressProfileAndVerifyImportOfDefaultProfile(*form_structure);
+  ExtractAddressProfileAndVerifyExtractionOfDefaultProfile(*form_structure);
 }
 
 // Tests two email fields containing different values blocks profile import.
@@ -1259,8 +1261,8 @@
   ImportAddressProfileAndVerifyImportOfNoProfile(*form_structure);
 }
 
-// Tests that multiple phone numbers do not block profile import and the first
-// one is saved.
+// Tests that multiple phone numbers do not block profile extraction and the
+// first one is saved.
 TEST_P(FormDataImporterTest, ImportAddressProfiles_MultiplePhoneNumbers) {
   base::test::ScopedFeatureList enable_import_when_multiple_phones_feature;
   enable_import_when_multiple_phones_feature.InitAndEnableFeature(
@@ -1279,7 +1281,7 @@
            {ADDRESS_HOME_STATE, kDefaultState},
            {ADDRESS_HOME_ZIP, kDefaultZip}});
 
-  ImportAddressProfileAndVerifyImportOfDefaultProfile(*form_structure);
+  ExtractAddressProfileAndVerifyExtractionOfDefaultProfile(*form_structure);
 }
 
 // Tests that multiple phone numbers do not block profile import and the first
@@ -1319,7 +1321,7 @@
   std::unique_ptr<FormStructure> form_structure =
       ConstructFormStructureFromFormData(form_data);
 
-  ImportAddressProfilesAndVerifyExpectation(
+  ExtractAddressProfilesAndVerifyExpectation(
       *form_structure,
       {ConstructProfileFromTypeValuePairs(
           {{NAME_FIRST, kDefaultFirstName},
@@ -1362,7 +1364,7 @@
 
   std::unique_ptr<FormStructure> form_structure =
       ConstructFormStructureFromTypeValuePairs(type_value_pairs);
-  ImportAddressProfilesAndVerifyExpectation(*form_structure, {profile});
+  ExtractAddressProfilesAndVerifyExpectation(*form_structure, {profile});
 }
 
 TEST_P(FormDataImporterTest, ImportAddressProfiles_MinimumAddressGB) {
@@ -1379,7 +1381,7 @@
 
   std::unique_ptr<FormStructure> form_structure =
       ConstructFormStructureFromTypeValuePairs(type_value_pairs);
-  ImportAddressProfilesAndVerifyExpectation(*form_structure, {profile});
+  ExtractAddressProfilesAndVerifyExpectation(*form_structure, {profile});
 }
 
 TEST_P(FormDataImporterTest, ImportAddressProfiles_MinimumAddressGI) {
@@ -1394,7 +1396,7 @@
 
   std::unique_ptr<FormStructure> form_structure =
       ConstructFormStructureFromTypeValuePairs(type_value_pairs);
-  ImportAddressProfilesAndVerifyExpectation(*form_structure, {profile});
+  ExtractAddressProfilesAndVerifyExpectation(*form_structure, {profile});
 }
 
 TEST_P(FormDataImporterTest,
@@ -1421,7 +1423,7 @@
 
   std::unique_ptr<FormStructure> form_structure =
       ConstructFormStructureFromFormData(form_data);
-  ImportAddressProfilesAndVerifyExpectation(
+  ExtractAddressProfilesAndVerifyExpectation(
       *form_structure,
       {ConstructProfileFromTypeValuePairs(
           {{NAME_FIRST, kDefaultFirstName},
@@ -1435,13 +1437,13 @@
            {ADDRESS_HOME_COUNTRY, kDefaultCountry}})});
 }
 
-// Test that even from unfocusable fields we import.
+// Test that even from unfocusable fields we extract.
 TEST_P(FormDataImporterTest, ImportAddressProfiles_UnFocussableFields) {
   std::unique_ptr<FormStructure> form_structure =
       ConstructDefaultProfileFormStructure();
   // Set the Address line field as unfocusable.
   form_structure->field(4)->is_focusable = false;
-  ImportAddressProfileAndVerifyImportOfDefaultProfile(*form_structure);
+  ExtractAddressProfileAndVerifyExtractionOfDefaultProfile(*form_structure);
 }
 
 TEST_P(FormDataImporterTest, ImportAddressProfiles_MultilineAddress) {
@@ -1460,7 +1462,7 @@
 
   std::unique_ptr<FormStructure> form_structure =
       ConstructFormStructureFromTypeValuePairs(type_value_pairs);
-  ImportAddressProfilesAndVerifyExpectation(*form_structure, {profile});
+  ExtractAddressProfilesAndVerifyExpectation(*form_structure, {profile});
 }
 
 TEST_P(FormDataImporterTest,
@@ -1469,8 +1471,8 @@
       ConstructDefaultProfileFormStructure();
 
   AutofillProfile default_profile = ConstructDefaultProfile();
-  ImportAddressProfilesAndVerifyExpectation(*default_form_structure,
-                                            {default_profile});
+  ExtractAddressProfilesAndVerifyExpectation(*default_form_structure,
+                                             {default_profile});
 
   // Now import a second profile from a different form submission.
   std::unique_ptr<FormStructure> alternative_form_structure =
@@ -1478,14 +1480,14 @@
   AutofillProfile alternative_profile = ConstructSecondProfile();
 
   // Verify that both profiles have been imported.
-  ImportAddressProfilesAndVerifyExpectation(
+  ExtractAddressProfilesAndVerifyExpectation(
       *alternative_form_structure, {alternative_profile, default_profile});
 }
 
 TEST_P(FormDataImporterTest, ImportAddressProfiles_TwoValidProfilesSameForm) {
   std::unique_ptr<FormStructure> form_structure =
       ConstructShippingAndBillingFormStructure();
-  ImportAddressProfilesAndVerifyExpectation(
+  ExtractAddressProfilesAndVerifyExpectation(
       *form_structure, {ConstructDefaultProfile(), ConstructSecondProfile()});
 }
 
@@ -1507,7 +1509,7 @@
 
   std::unique_ptr<FormStructure> form_structure =
       ConstructFormStructureFromFormData(form_data);
-  ImportAddressProfileAndVerifyImportOfDefaultProfile(*form_structure);
+  ExtractAddressProfileAndVerifyExtractionOfDefaultProfile(*form_structure);
 }
 
 // A maximum of two address profiles are imported per form.
@@ -1535,9 +1537,9 @@
   std::unique_ptr<FormStructure> form_structure =
       ConstructFormStructureFromTypeValuePairs(profile_type_value_pairs);
 
-  // Import from the form structure and verify that only the first two profiles
-  // are imported.
-  ImportAddressProfilesAndVerifyExpectation(
+  // Extract from the form structure and verify that only the first two profiles
+  // are extracted.
+  ExtractAddressProfilesAndVerifyExpectation(
       *form_structure, {ConstructDefaultProfile(), ConstructSecondProfile()});
 }
 
@@ -1556,8 +1558,8 @@
 
   std::unique_ptr<FormStructure> initial_form_structure =
       ConstructFormStructureFromTypeValuePairs(initial_type_value_pairs);
-  ImportAddressProfilesAndVerifyExpectation(*initial_form_structure,
-                                            {initial_profile});
+  ExtractAddressProfilesAndVerifyExpectation(*initial_form_structure,
+                                             {initial_profile});
 
   // Create a second form structure with an additional country and a differently
   // formatted phone number
@@ -1591,9 +1593,9 @@
       // Country information is added.
       {ADDRESS_HOME_COUNTRY, "US"}};
 
-  // Verify that importing the conflicting profile will result in an update of
+  // Verify that extracting the conflicting profile will result in an update of
   // the existing profile rather than creating a new one.
-  ImportAddressProfilesAndVerifyExpectation(
+  ExtractAddressProfilesAndVerifyExpectation(
       *conflicting_form_structure,
       {ConstructProfileFromTypeValuePairs(resulting_type_value_pairs)});
 }
@@ -1613,8 +1615,8 @@
 
   std::unique_ptr<FormStructure> initial_form_structure =
       ConstructFormStructureFromTypeValuePairs(initial_type_value_pairs);
-  ImportAddressProfilesAndVerifyExpectation(*initial_form_structure,
-                                            {initial_profile});
+  ExtractAddressProfilesAndVerifyExpectation(*initial_form_structure,
+                                             {initial_profile});
 
   // Create a superset that includes a new email address.
   TypeValuePairs superset_type_value_pairs = initial_type_value_pairs;
@@ -1629,10 +1631,10 @@
 
   std::unique_ptr<FormStructure> superset_form_structure =
       ConstructFormStructureFromTypeValuePairs(superset_type_value_pairs);
-  // Verify that importing the superset profile will result in an update of
+  // Verify that extracting the superset profile will result in an update of
   // the existing profile rather than creating a new one.
-  ImportAddressProfilesAndVerifyExpectation(*superset_form_structure,
-                                            {superset_profile});
+  ExtractAddressProfilesAndVerifyExpectation(*superset_form_structure,
+                                             {superset_profile});
 }
 
 TEST_P(FormDataImporterTest, ImportAddressProfiles_MissingInfoInNew) {
@@ -1661,15 +1663,15 @@
   // First import the superset profile.
   std::unique_ptr<FormStructure> superset_form_structure =
       ConstructFormStructureFromTypeValuePairs(superset_type_value_pairs);
-  ImportAddressProfilesAndVerifyExpectation(*superset_form_structure,
-                                            {superset_profile});
+  ExtractAddressProfilesAndVerifyExpectation(*superset_form_structure,
+                                             {superset_profile});
 
-  // Than import the subset profile and verify that the stored profile is still
+  // Than extract the subset profile and verify that the stored profile is still
   // the superset.
   std::unique_ptr<FormStructure> subset_form_structure =
       ConstructFormStructureFromTypeValuePairs(subset_type_value_pairs);
-  ImportAddressProfilesAndVerifyExpectation(*superset_form_structure,
-                                            {superset_profile});
+  ExtractAddressProfilesAndVerifyExpectation(*superset_form_structure,
+                                             {superset_profile});
 }
 
 TEST_P(FormDataImporterTest, ImportAddressProfiles_InsufficientAddress) {
@@ -1734,7 +1736,7 @@
 
   FormStructure form_structure(form);
   form_structure.DetermineHeuristicTypes(nullptr, nullptr);
-  ImportAddressProfiles(/*extraction_successful=*/true, form_structure);
+  ExtractAddressProfiles(/*extraction_successful=*/true, form_structure);
 
   // Expect that no new profile is saved.
   const std::vector<AutofillProfile*>& results =
@@ -1752,7 +1754,7 @@
   FormStructure form_structure2(form);
   form_structure2.DetermineHeuristicTypes(nullptr, nullptr);
 
-  ImportAddressProfiles(/*extraction_successful=*/true, form_structure2);
+  ExtractAddressProfiles(/*extraction_successful=*/true, form_structure2);
 
   // Expect that no new profile is saved.
   const std::vector<AutofillProfile*>& results2 =
@@ -1809,7 +1811,7 @@
 
   FormStructure form_structure(form);
   form_structure.DetermineHeuristicTypes(nullptr, nullptr);
-  ImportAddressProfiles(/*extraction_successful=*/true, form_structure);
+  ExtractAddressProfiles(/*extraction_successful=*/true, form_structure);
 
   // The form submission should result in a change of name structure.
   profile.SetRawInfoWithVerificationStatus(NAME_FIRST, u"Marion Mitchell",
@@ -1834,7 +1836,7 @@
   FormStructure form_structure2(form);
   form_structure2.DetermineHeuristicTypes(nullptr, nullptr);
 
-  ImportAddressProfiles(/*extraction_successful=*/true, form_structure2);
+  ExtractAddressProfiles(/*extraction_successful=*/true, form_structure2);
 
   // Expect that no new profile is saved.
   const std::vector<AutofillProfile*>& results2 =
@@ -1898,7 +1900,7 @@
 
   FormStructure form_structure(form);
   form_structure.DetermineHeuristicTypes(nullptr, nullptr);
-  ImportAddressProfiles(/*extraction_successful=*/true, form_structure);
+  ExtractAddressProfiles(/*extraction_successful=*/true, form_structure);
 
   // The form submission should result in a change of the address structure.
   profile.SetRawInfoWithVerificationStatus(
@@ -1946,7 +1948,7 @@
 
   FormStructure form_structure(form);
   form_structure.DetermineHeuristicTypes(nullptr, nullptr);
-  ImportAddressProfiles(/*extraction_successful=*/false, form_structure);
+  ExtractAddressProfiles(/*extraction_successful=*/false, form_structure);
 
   // Since no refresh is expected, reload the data from the database to make
   // sure no changes were written out.
@@ -1993,7 +1995,7 @@
   // the page language is not set.
   FormStructure form_structure(form);
   form_structure.DetermineHeuristicTypes(nullptr, nullptr);
-  ImportAddressProfiles(/*extraction_successful=*/false, form_structure);
+  ExtractAddressProfiles(/*extraction_successful=*/false, form_structure);
 
   ASSERT_EQ(0U, personal_data_manager_->GetProfiles().size());
   ASSERT_EQ(0U, personal_data_manager_->GetCreditCards().size());
@@ -2001,7 +2003,7 @@
   // Set the page language to match the localized country value and try again.
   autofill_client_->GetLanguageState()->SetSourceLanguage("de");
 
-  ImportAddressProfiles(/*extraction_successful=*/true, form_structure);
+  ExtractAddressProfiles(/*extraction_successful=*/true, form_structure);
 
   // There should be one imported address profile.
   ASSERT_EQ(1U, personal_data_manager_->GetProfiles().size());
@@ -2046,7 +2048,7 @@
 
   FormStructure form_structure(form);
   form_structure.DetermineHeuristicTypes(nullptr, nullptr);
-  ImportAddressProfiles(/*extraction_successful=*/true, form_structure);
+  ExtractAddressProfiles(/*extraction_successful=*/true, form_structure);
 
   AutofillProfile expected(base::GenerateGUID(), test::kEmptyOrigin);
   test::SetProfileInfo(&expected, "George", nullptr, "Washington",
@@ -2092,7 +2094,7 @@
 
   FormStructure form_structure(form);
   form_structure.DetermineHeuristicTypes(nullptr, nullptr);
-  ImportAddressProfiles(/*extraction_successful=*/false, form_structure);
+  ExtractAddressProfiles(/*extraction_successful=*/false, form_structure);
 
   // Since no refresh is expected, reload the data from the database to make
   // sure no changes were written out.
@@ -2102,15 +2104,15 @@
   ASSERT_EQ(0U, personal_data_manager_->GetCreditCards().size());
 }
 
-// ImportCreditCard tests.
+// ExtractCreditCard tests.
 
 // Tests that a valid credit card is extracted.
-TEST_P(FormDataImporterTest, ImportCreditCard_Valid) {
+TEST_P(FormDataImporterTest, ExtractCreditCard_Valid) {
   std::unique_ptr<FormStructure> form_structure =
       ConstructDefaultCreditCardFormStructure();
   base::HistogramTester histogram_tester;
   absl::optional<CreditCard> credit_card_import_candidate =
-      ImportCreditCard(*form_structure);
+      ExtractCreditCard(*form_structure);
   EXPECT_TRUE(credit_card_import_candidate);
   histogram_tester.ExpectUniqueSample(
       "Autofill.SubmittedCardState",
@@ -2128,7 +2130,7 @@
 }
 
 // Tests that an invalid credit card number is not extracted.
-TEST_P(FormDataImporterTest, ImportCreditCard_InvalidCardNumber) {
+TEST_P(FormDataImporterTest, ExtractCreditCard_InvalidCardNumber) {
   FormData form;
   form.url = GURL("https://www.foo.com");
 
@@ -2139,7 +2141,7 @@
   form_structure.DetermineHeuristicTypes(nullptr, nullptr);
   base::HistogramTester histogram_tester;
   absl::optional<CreditCard> credit_card_import_candidate =
-      ImportCreditCard(form_structure);
+      ExtractCreditCard(form_structure);
   EXPECT_FALSE(credit_card_import_candidate);
   histogram_tester.ExpectUniqueSample("Autofill.SubmittedCardState",
                                       AutofillMetrics::HAS_EXPIRATION_DATE_ONLY,
@@ -2155,7 +2157,7 @@
 // Tests that a credit card with an empty expiration can be extracted due to the
 // expiration date fix flow.
 TEST_P(FormDataImporterTest,
-       ImportCreditCard_InvalidExpiryDate_EditableExpirationExpOn) {
+       ExtractCreditCard_InvalidExpiryDate_EditableExpirationExpOn) {
   FormData form;
   form.url = GURL("https://www.foo.com");
 
@@ -2165,7 +2167,7 @@
   form_structure.DetermineHeuristicTypes(nullptr, nullptr);
   base::HistogramTester histogram_tester;
   absl::optional<CreditCard> credit_card_import_candidate =
-      ImportCreditCard(form_structure);
+      ExtractCreditCard(form_structure);
   EXPECT_TRUE(credit_card_import_candidate);
   histogram_tester.ExpectUniqueSample("Autofill.SubmittedCardState",
                                       AutofillMetrics::HAS_CARD_NUMBER_ONLY, 1);
@@ -2174,7 +2176,7 @@
 // Tests that an expired credit card can be extracted due to the expiration date
 // fix flow.
 TEST_P(FormDataImporterTest,
-       ImportCreditCard_ExpiredExpiryDate_EditableExpirationExpOn) {
+       ExtractCreditCard_ExpiredExpiryDate_EditableExpirationExpOn) {
   FormData form;
   form.url = GURL("https://www.foo.com");
 
@@ -2185,7 +2187,7 @@
   form_structure.DetermineHeuristicTypes(nullptr, nullptr);
   base::HistogramTester histogram_tester;
   absl::optional<CreditCard> credit_card_import_candidate =
-      ImportCreditCard(form_structure);
+      ExtractCreditCard(form_structure);
   EXPECT_TRUE(credit_card_import_candidate);
   histogram_tester.ExpectUniqueSample("Autofill.SubmittedCardState",
                                       AutofillMetrics::HAS_CARD_NUMBER_ONLY, 1);
@@ -2193,7 +2195,7 @@
 
 // Tests that a valid credit card is extracted when the option text for month
 // select can't be parsed but its value can.
-TEST_P(FormDataImporterTest, ImportCreditCard_MonthSelectInvalidText) {
+TEST_P(FormDataImporterTest, ExtractCreditCard_MonthSelectInvalidText) {
   // Add a single valid credit card form with an invalid option value.
   FormData form;
   form.url = GURL("https://www.foo.com");
@@ -2212,7 +2214,7 @@
   form_structure.DetermineHeuristicTypes(nullptr, nullptr);
   base::HistogramTester histogram_tester;
   absl::optional<CreditCard> credit_card_import_candidate =
-      ImportCreditCard(form_structure);
+      ExtractCreditCard(form_structure);
   EXPECT_TRUE(credit_card_import_candidate);
   histogram_tester.ExpectUniqueSample(
       "Autofill.SubmittedCardState",
@@ -2230,12 +2232,12 @@
               UnorderedElementsCompareEqual(expected));
 }
 
-TEST_P(FormDataImporterTest, ImportCreditCard_TwoValidCards) {
+TEST_P(FormDataImporterTest, ExtractCreditCard_TwoValidCards) {
   // Start with a single valid credit card form.
   std::unique_ptr<FormStructure> form_structure1 =
       ConstructDefaultCreditCardFormStructure();
   absl::optional<CreditCard> credit_card_import_candidate =
-      ImportCreditCard(*form_structure1);
+      ExtractCreditCard(*form_structure1);
   EXPECT_TRUE(credit_card_import_candidate);
   personal_data_manager_->OnAcceptedLocalCreditCardSave(
       *credit_card_import_candidate);
@@ -2258,7 +2260,7 @@
   form_structure2.DetermineHeuristicTypes(nullptr, nullptr);
 
   absl::optional<CreditCard> credit_card_import_candidate2 =
-      ImportCreditCard(form_structure2);
+      ExtractCreditCard(form_structure2);
   EXPECT_TRUE(credit_card_import_candidate2);
   personal_data_manager_->OnAcceptedLocalCreditCardSave(
       *credit_card_import_candidate2);
@@ -2276,7 +2278,7 @@
 }
 
 // This form has the expiration year as one field with MM/YY.
-TEST_P(FormDataImporterTest, ImportCreditCard_Month2DigitYearCombination) {
+TEST_P(FormDataImporterTest, ExtractCreditCard_Month2DigitYearCombination) {
   FormData form;
   form.url = GURL("https://www.foo.com");
 
@@ -2296,7 +2298,7 @@
 }
 
 // This form has the expiration year as one field with MM/YYYY.
-TEST_P(FormDataImporterTest, ImportCreditCard_Month4DigitYearCombination) {
+TEST_P(FormDataImporterTest, ExtractCreditCard_Month4DigitYearCombination) {
   FormData form;
   form.url = GURL("https://www.foo.com");
 
@@ -2316,7 +2318,7 @@
 }
 
 // This form has the expiration year as one field with M/YYYY.
-TEST_P(FormDataImporterTest, ImportCreditCard_1DigitMonth4DigitYear) {
+TEST_P(FormDataImporterTest, ExtractCreditCard_1DigitMonth4DigitYear) {
   FormData form;
   form.url = GURL("https://www.foo.com");
 
@@ -2336,7 +2338,7 @@
 }
 
 // This form has the expiration year as a 2-digit field.
-TEST_P(FormDataImporterTest, ImportCreditCard_2DigitYear) {
+TEST_P(FormDataImporterTest, ExtractCreditCard_2DigitYear) {
   FormData form;
   form.url = GURL("https://www.foo.com");
 
@@ -2360,7 +2362,7 @@
 // Tests that a credit card is extracted when the card matches a masked server
 // card.
 TEST_P(FormDataImporterTest,
-       ImportCreditCard_DuplicateServerCards_ExtractMaskedCard) {
+       ExtractCreditCard_DuplicateServerCards_ExtractMaskedCard) {
   // Add a masked server card.
   std::vector<CreditCard> server_cards;
   server_cards.push_back(CreditCard(CreditCard::MASKED_SERVER_CARD, "a123"));
@@ -2386,7 +2388,7 @@
   FormStructure form_structure(form);
   form_structure.DetermineHeuristicTypes(nullptr, nullptr);
   absl::optional<CreditCard> credit_card_import_candidate =
-      ImportCreditCard(form_structure);
+      ExtractCreditCard(form_structure);
   EXPECT_TRUE(credit_card_import_candidate);
   ASSERT_TRUE(credit_card_import_candidate.value().record_type() ==
               CreditCard::MASKED_SERVER_CARD);
@@ -2395,7 +2397,7 @@
 // Tests that a credit card is extracted when it matches a full server
 // card.
 TEST_P(FormDataImporterTest,
-       ImportCreditCard_DuplicateServerCards_ExtractFullCard) {
+       ExtractCreditCard_DuplicateServerCards_ExtractFullCard) {
   // Add a full server card.
   std::vector<CreditCard> server_cards;
   server_cards.push_back(CreditCard(CreditCard::FULL_SERVER_CARD, "c789"));
@@ -2420,13 +2422,13 @@
   FormStructure form_structure(form);
   form_structure.DetermineHeuristicTypes(nullptr, nullptr);
   absl::optional<CreditCard> credit_card_import_candidate =
-      ImportCreditCard(form_structure);
+      ExtractCreditCard(form_structure);
   EXPECT_TRUE(credit_card_import_candidate);
   EXPECT_EQ(credit_card_import_candidate.value().record_type(),
             CreditCard::RecordType::FULL_SERVER_CARD);
 }
 
-TEST_P(FormDataImporterTest, ImportCreditCard_SameCreditCardWithConflict) {
+TEST_P(FormDataImporterTest, ExtractCreditCard_SameCreditCardWithConflict) {
   // Start with a single valid credit card form.
   FormData form1;
   form1.url = GURL("https://wwww.foo.com");
@@ -2437,7 +2439,7 @@
   FormStructure form_structure1(form1);
   form_structure1.DetermineHeuristicTypes(nullptr, nullptr);
   absl::optional<CreditCard> credit_card_import_candidate =
-      ImportCreditCard(form_structure1);
+      ExtractCreditCard(form_structure1);
   EXPECT_TRUE(credit_card_import_candidate);
   personal_data_manager_->OnAcceptedLocalCreditCardSave(
       *credit_card_import_candidate);
@@ -2461,7 +2463,7 @@
   FormStructure form_structure2(form2);
   form_structure2.DetermineHeuristicTypes(nullptr, nullptr);
   absl::optional<CreditCard> credit_card_import_candidate2 =
-      ImportCreditCard(form_structure2);
+      ExtractCreditCard(form_structure2);
   EXPECT_TRUE(credit_card_import_candidate2);
 
   WaitForOnPersonalDataChanged();
@@ -2477,7 +2479,7 @@
   EXPECT_THAT(*results2[0], ComparesEqual(expected2));
 }
 
-TEST_P(FormDataImporterTest, ImportCreditCard_ShouldReturnLocalCard) {
+TEST_P(FormDataImporterTest, ExtractCreditCard_ShouldReturnLocalCard) {
   // Start with a single valid credit card form.
   FormData form1;
   form1.url = GURL("https://wwww.foo.com");
@@ -2488,7 +2490,7 @@
   FormStructure form_structure1(form1);
   form_structure1.DetermineHeuristicTypes(nullptr, nullptr);
   absl::optional<CreditCard> credit_card_import_candidate =
-      ImportCreditCard(form_structure1);
+      ExtractCreditCard(form_structure1);
   EXPECT_TRUE(credit_card_import_candidate);
   personal_data_manager_->OnAcceptedLocalCreditCardSave(
       *credit_card_import_candidate);
@@ -2512,7 +2514,7 @@
   FormStructure form_structure2(form2);
   form_structure2.DetermineHeuristicTypes(nullptr, nullptr);
   absl::optional<CreditCard> credit_card_import_candidate2 =
-      ImportCreditCard(form_structure2);
+      ExtractCreditCard(form_structure2);
   EXPECT_TRUE(credit_card_import_candidate2);
   // The local card is returned after an update.
   EXPECT_TRUE(credit_card_import_candidate2);
@@ -2530,7 +2532,7 @@
   EXPECT_THAT(*results2[0], ComparesEqual(expected2));
 }
 
-TEST_P(FormDataImporterTest, ImportCreditCard_EmptyCardWithConflict) {
+TEST_P(FormDataImporterTest, ExtractCreditCard_EmptyCardWithConflict) {
   // Start with a single valid credit card form.
   FormData form1;
   form1.url = GURL("https://wwww.foo.com");
@@ -2542,7 +2544,7 @@
   form_structure1.DetermineHeuristicTypes(nullptr, nullptr);
 
   absl::optional<CreditCard> credit_card_import_candidate =
-      ImportCreditCard(form_structure1);
+      ExtractCreditCard(form_structure1);
   EXPECT_TRUE(credit_card_import_candidate);
   personal_data_manager_->OnAcceptedLocalCreditCardSave(
       *credit_card_import_candidate);
@@ -2565,7 +2567,7 @@
   FormStructure form_structure2(form2);
   form_structure2.DetermineHeuristicTypes(nullptr, nullptr);
   absl::optional<CreditCard> credit_card_import_candidate2 =
-      ImportCreditCard(form_structure2);
+      ExtractCreditCard(form_structure2);
   EXPECT_FALSE(credit_card_import_candidate2);
 
   // Since no refresh is expected, reload the data from the database to make
@@ -2582,7 +2584,7 @@
   EXPECT_THAT(*results2[0], ComparesEqual(expected2));
 }
 
-TEST_P(FormDataImporterTest, ImportCreditCard_MissingInfoInNew) {
+TEST_P(FormDataImporterTest, ExtractCreditCard_MissingInfoInNew) {
   // Start with a single valid credit card form.
   FormData form1;
   form1.url = GURL("https://wwww.foo.com");
@@ -2593,7 +2595,7 @@
   FormStructure form_structure1(form1);
   form_structure1.DetermineHeuristicTypes(nullptr, nullptr);
   absl::optional<CreditCard> credit_card_import_candidate =
-      ImportCreditCard(form_structure1);
+      ExtractCreditCard(form_structure1);
   EXPECT_TRUE(credit_card_import_candidate);
   personal_data_manager_->OnAcceptedLocalCreditCardSave(
       *credit_card_import_candidate);
@@ -2617,7 +2619,7 @@
   FormStructure form_structure2(form2);
   form_structure2.DetermineHeuristicTypes(nullptr, nullptr);
   absl::optional<CreditCard> credit_card_import_candidate2 =
-      ImportCreditCard(form_structure2);
+      ExtractCreditCard(form_structure2);
   EXPECT_TRUE(credit_card_import_candidate2);
 
   // Since no refresh is expected, reload the data from the database to make
@@ -2644,7 +2646,7 @@
   FormStructure form_structure3(form3);
   form_structure3.DetermineHeuristicTypes(nullptr, nullptr);
   absl::optional<CreditCard> credit_card_import_candidate3 =
-      ImportCreditCard(form_structure3);
+      ExtractCreditCard(form_structure3);
   EXPECT_FALSE(credit_card_import_candidate3);
 
   // Since no refresh is expected, reload the data from the database to make
@@ -2661,7 +2663,7 @@
   EXPECT_THAT(*results3[0], ComparesEqual(expected3));
 }
 
-TEST_P(FormDataImporterTest, ImportCreditCard_MissingInfoInOld) {
+TEST_P(FormDataImporterTest, ExtractCreditCard_MissingInfoInOld) {
   // Start with a single valid credit card stored via the preferences.
   // Note the empty name.
   CreditCard saved_credit_card(base::GenerateGUID(), test::kEmptyOrigin);
@@ -2687,7 +2689,7 @@
   FormStructure form_structure(form);
   form_structure.DetermineHeuristicTypes(nullptr, nullptr);
   absl::optional<CreditCard> credit_card_import_candidate =
-      ImportCreditCard(form_structure);
+      ExtractCreditCard(form_structure);
   EXPECT_TRUE(credit_card_import_candidate);
 
   WaitForOnPersonalDataChanged();
@@ -2705,7 +2707,7 @@
 
 // We allow the user to store a credit card number with separators via the UI.
 // We should not try to re-aggregate the same card with the separators stripped.
-TEST_P(FormDataImporterTest, ImportCreditCard_SameCardWithSeparators) {
+TEST_P(FormDataImporterTest, ExtractCreditCard_SameCardWithSeparators) {
   // Start with a single valid credit card stored via the preferences.
   // Note the separators in the credit card number.
   CreditCard saved_credit_card(base::GenerateGUID(), test::kEmptyOrigin);
@@ -2730,7 +2732,7 @@
   FormStructure form_structure(form);
   form_structure.DetermineHeuristicTypes(nullptr, nullptr);
   absl::optional<CreditCard> credit_card_import_candidate =
-      ImportCreditCard(form_structure);
+      ExtractCreditCard(form_structure);
   EXPECT_TRUE(credit_card_import_candidate);
 
   // Since no refresh is expected, reload the data from the database to make
@@ -2747,7 +2749,7 @@
 // Ensure that if a verified credit card already exists, aggregated credit cards
 // cannot modify it in any way.
 TEST_P(FormDataImporterTest,
-       ImportCreditCard_ExistingVerifiedCardWithConflict) {
+       ExtractCreditCard_ExistingVerifiedCardWithConflict) {
   // Start with a verified credit card.
   CreditCard credit_card(base::GenerateGUID(), kSettingsOrigin);
   test::SetCreditCardInfo(&credit_card, "Biggie Smalls",
@@ -2772,7 +2774,7 @@
   FormStructure form_structure(form);
   form_structure.DetermineHeuristicTypes(nullptr, nullptr);
   absl::optional<CreditCard> credit_card_import_candidate =
-      ImportCreditCard(form_structure);
+      ExtractCreditCard(form_structure);
   EXPECT_TRUE(credit_card_import_candidate);
 
   // Since no refresh is expected, reload the data from the database to make
@@ -2786,10 +2788,11 @@
   EXPECT_THAT(*results[0], ComparesEqual(credit_card));
 }
 
-// Ensures that `FormDataImporterTest::imported_credit_card_record_type_` is set
-// and reset correctly.
+// Ensures that
+// `FormDataImporterTest::credit_card_import_type_` is set and
+// reset correctly.
 TEST_P(FormDataImporterTest,
-       ImportFormData_SecondImportResetsCreditCardRecordType) {
+       ExtractFormData_SecondImportResetsCreditCardRecordType) {
   // Start with a single valid credit card stored via the preferences.
   CreditCard saved_credit_card(base::GenerateGUID(), test::kEmptyOrigin);
   test::SetCreditCardInfo(&saved_credit_card, "Biggie Smalls",
@@ -2812,19 +2815,18 @@
 
   FormStructure form_structure(form);
   form_structure.DetermineHeuristicTypes(nullptr, nullptr);
-  auto imported_data = ImportFormDataAndProcessAddressCandidates(
+  auto extracted_data = ExtractFormDataAndProcessAddressCandidates(
       form_structure, /*profile_autofill_enabled=*/true,
       /*payment_methods_autofill_enabled=*/true);
-  ASSERT_TRUE(imported_data.credit_card_import_candidate);
-  // |imported_credit_card_record_type_| should be kLocalCard because upload was
-  // offered and the card is a local card already on the device.
-  ASSERT_TRUE(
-      form_data_importer().imported_credit_card_record_type_for_testing() ==
-      FormDataImporter::ImportedCreditCardRecordType::kLocalCard);
+  ASSERT_TRUE(extracted_data.credit_card_import_candidate);
+  // |credit_card_import_type_| should be kLocalCard because
+  // upload was offered and the card is a local card already on the device.
+  ASSERT_TRUE(form_data_importer().credit_card_import_type_for_testing() ==
+              FormDataImporter::CreditCardImportType::kLocalCard);
 
   // Second form is filled with a new card so
-  // `FormDataImporterTest::imported_credit_card_record_type_` should be reset.
-  // Simulate a form submission with a new card.
+  // `FormDataImporterTest::credit_card_import_type_` should be
+  // reset. Simulate a form submission with a new card.
   FormData form2;
   form2.url = GURL("https://wwww.foo.com");
 
@@ -2833,21 +2835,20 @@
 
   FormStructure form_structure2(form2);
   form_structure2.DetermineHeuristicTypes(nullptr, nullptr);
-  auto imported_data2 = ImportFormDataAndProcessAddressCandidates(
+  auto extracted_data2 = ExtractFormDataAndProcessAddressCandidates(
       form_structure2, /*profile_autofill_enabled=*/true,
       /*payment_methods_autofill_enabled=*/true);
-  ASSERT_TRUE(imported_data2.credit_card_import_candidate);
-  // |imported_credit_card_record_type_| should be kNewCard because the imported
-  // card is not already on the device.
-  ASSERT_TRUE(
-      form_data_importer().imported_credit_card_record_type_for_testing() ==
-      FormDataImporter::ImportedCreditCardRecordType::kNewCard);
+  ASSERT_TRUE(extracted_data2.credit_card_import_candidate);
+  // |credit_card_import_type_| should be kNewCard because the
+  // imported card is not already on the device.
+  ASSERT_TRUE(form_data_importer().credit_card_import_type_for_testing() ==
+              FormDataImporter::CreditCardImportType::kNewCard);
 
   // Third form is an address form and set `payment_methods_autofill_enabled` to
-  // be false so that the ImportCreditCard won't be called.
-  // `FormDataImporterTest::imported_credit_card_record_type_` should still be
-  // reset even if ImportCreditCard is not called. Simulate a form submission
-  // with no card.
+  // be false so that the ExtractCreditCard won't be called.
+  // `FormDataImporterTest::credit_card_import_type_` should
+  // still be reset even if ExtractCreditCard is not called. Simulate a form
+  // submission with no card.
   FormData form3;
   form3.url = GURL("https://wwww.foo.com");
 
@@ -2872,21 +2873,21 @@
   form3.fields.push_back(field);
   FormStructure form_structure3(form3);
   form_structure3.DetermineHeuristicTypes(nullptr, nullptr);
-  auto imported_data3 = ImportFormDataAndProcessAddressCandidates(
+  auto extracted_data3 = ExtractFormDataAndProcessAddressCandidates(
       form_structure3, /*profile_autofill_enabled=*/true,
       /*payment_methods_autofill_enabled=*/false);
-  // |imported_credit_card_record_type_| should be NO_CARD because no valid card
-  // was imported from the form.
-  EXPECT_NE(0u, imported_data3.address_profile_import_candidates.size());
-  ASSERT_TRUE(
-      form_data_importer().imported_credit_card_record_type_for_testing() ==
-      FormDataImporter::ImportedCreditCardRecordType::kNoCard);
+  // |credit_card_import_type_| should be NO_CARD because no
+  // valid card was imported from the form.
+  EXPECT_NE(0u, extracted_data3.address_profile_import_candidates.size());
+  ASSERT_TRUE(form_data_importer().credit_card_import_type_for_testing() ==
+              FormDataImporter::CreditCardImportType::kNoCard);
 }
 
-// Ensures that `FormDataImporterTest::imported_credit_card_record_type_` is set
+// Ensures that
+// `FormDataImporterTest::credit_card_import_type_` is set
 // correctly.
 TEST_P(FormDataImporterTest,
-       ImportFormData_ImportCreditCardRecordType_NewCard) {
+       ExtractFormData_ExtractCreditCardRecordType_NewCard) {
   // Simulate a form submission with a new credit card.
   FormData form;
   form.url = GURL("https://www.foo.com");
@@ -2896,20 +2897,19 @@
 
   FormStructure form_structure(form);
   form_structure.DetermineHeuristicTypes(nullptr, nullptr);
-  auto imported_data = ImportFormDataAndProcessAddressCandidates(
+  auto extracted_data = ExtractFormDataAndProcessAddressCandidates(
       form_structure, /*profile_autofill_enabled=*/true,
       /*payment_methods_autofill_enabled=*/true);
-  ASSERT_TRUE(imported_data.credit_card_import_candidate);
-  // |imported_credit_card_record_type_| should be kNewCard because the imported
-  // card is not already on the device.
-  ASSERT_TRUE(
-      form_data_importer().imported_credit_card_record_type_for_testing() ==
-      FormDataImporter::ImportedCreditCardRecordType::kNewCard);
+  ASSERT_TRUE(extracted_data.credit_card_import_candidate);
+  // |credit_card_import_type_| should be kNewCard because the
+  // imported card is not already on the device.
+  ASSERT_TRUE(form_data_importer().credit_card_import_type_for_testing() ==
+              FormDataImporter::CreditCardImportType::kNewCard);
 }
 
-// Ensures that `imported_credit_card_record_type_` is set correctly.
+// Ensures that `credit_card_import_type_` is set correctly.
 TEST_P(FormDataImporterTest,
-       ImportFormData_ImportCreditCardRecordType_LocalCard) {
+       ExtractFormData_ExtractCreditCardRecordType_LocalCard) {
   // Start with a single valid credit card stored via the preferences.
   CreditCard saved_credit_card(base::GenerateGUID(), test::kEmptyOrigin);
   test::SetCreditCardInfo(&saved_credit_card, "Biggie Smalls",
@@ -2932,21 +2932,21 @@
 
   FormStructure form_structure(form);
   form_structure.DetermineHeuristicTypes(nullptr, nullptr);
-  auto imported_data = ImportFormDataAndProcessAddressCandidates(
+  auto extracted_data = ExtractFormDataAndProcessAddressCandidates(
       form_structure, /*profile_autofill_enabled=*/true,
       /*payment_methods_autofill_enabled=*/true);
-  ASSERT_TRUE(imported_data.credit_card_import_candidate);
-  // |imported_credit_card_record_type_| should be kLocalCard because upload was
-  // offered and the card is a local card already on the device.
-  ASSERT_TRUE(
-      form_data_importer().imported_credit_card_record_type_for_testing() ==
-      FormDataImporter::ImportedCreditCardRecordType::kLocalCard);
+  ASSERT_TRUE(extracted_data.credit_card_import_candidate);
+  // |credit_card_import_type_| should be kLocalCard because
+  // upload was offered and the card is a local card already on the device.
+  ASSERT_TRUE(form_data_importer().credit_card_import_type_for_testing() ==
+              FormDataImporter::CreditCardImportType::kLocalCard);
 }
 
-// Ensures that `FormDataImporterTest::imported_credit_card_record_type_` is set
+// Ensures that
+// `FormDataImporterTest::credit_card_import_type_` is set
 // correctly.
 TEST_P(FormDataImporterTest,
-       ImportFormData_ImportCreditCardRecordType_MaskedServerCard) {
+       ExtractFormData_ExtractCreditCardRecordType_MaskedServerCard) {
   // Add a masked server card.
   std::vector<CreditCard> server_cards;
   server_cards.push_back(CreditCard(CreditCard::MASKED_SERVER_CARD, "a123"));
@@ -2969,20 +2969,20 @@
 
   FormStructure form_structure(form);
   form_structure.DetermineHeuristicTypes(nullptr, nullptr);
-  auto imported_data = ImportFormDataAndProcessAddressCandidates(
+  auto extracted_data = ExtractFormDataAndProcessAddressCandidates(
       form_structure, /*profile_autofill_enabled=*/true,
       /*payment_methods_autofill_enabled=*/true);
-  ASSERT_TRUE(imported_data.credit_card_import_candidate);
-  // |imported_credit_card_record_type_| should be SERVER_CARD.
-  ASSERT_TRUE(
-      form_data_importer().imported_credit_card_record_type_for_testing() ==
-      FormDataImporter::ImportedCreditCardRecordType::kServerCard);
+  ASSERT_TRUE(extracted_data.credit_card_import_candidate);
+  // |credit_card_import_type_| should be SERVER_CARD.
+  ASSERT_TRUE(form_data_importer().credit_card_import_type_for_testing() ==
+              FormDataImporter::CreditCardImportType::kServerCard);
 }
 
-// Ensures that `FormDataImporterTest::imported_credit_card_record_type_` is set
+// Ensures that
+// `FormDataImporterTest::credit_card_import_type_` is set
 // correctly.
 TEST_P(FormDataImporterTest,
-       ImportFormData_ImportCreditCardRecordType_FullServerCard) {
+       ExtractFormData_ExtractCreditCardRecordType_FullServerCard) {
   // Add a full server card.
   std::vector<CreditCard> server_cards;
   server_cards.push_back(CreditCard(CreditCard::FULL_SERVER_CARD, "c789"));
@@ -3005,20 +3005,20 @@
 
   FormStructure form_structure(form);
   form_structure.DetermineHeuristicTypes(nullptr, nullptr);
-  auto imported_data = ImportFormDataAndProcessAddressCandidates(
+  auto extracted_data = ExtractFormDataAndProcessAddressCandidates(
       form_structure, /*profile_autofill_enabled=*/true,
       /*payment_methods_autofill_enabled=*/true);
-  ASSERT_TRUE(imported_data.credit_card_import_candidate);
-  // |imported_credit_card_record_type_| should be SERVER_CARD.
-  ASSERT_TRUE(
-      form_data_importer().imported_credit_card_record_type_for_testing() ==
-      FormDataImporter::ImportedCreditCardRecordType::kServerCard);
+  ASSERT_TRUE(extracted_data.credit_card_import_candidate);
+  // |credit_card_import_type_| should be SERVER_CARD.
+  ASSERT_TRUE(form_data_importer().credit_card_import_type_for_testing() ==
+              FormDataImporter::CreditCardImportType::kServerCard);
 }
 
-// Ensures that `FormDataImporterTest::imported_credit_card_record_type_` is set
+// Ensures that
+// `FormDataImporterTest::credit_card_import_type_` is set
 // correctly.
 TEST_P(FormDataImporterTest,
-       ImportFormData_ImportCreditCardRecordType_NoCard_InvalidCardNumber) {
+       ExtractFormData_ExtractCreditCardRecordType_NoCard_InvalidCardNumber) {
   // Simulate a form submission using a credit card with an invalid card number.
   FormData form;
   form.url = GURL("https://www.foo.com");
@@ -3028,21 +3028,21 @@
 
   FormStructure form_structure(form);
   form_structure.DetermineHeuristicTypes(nullptr, nullptr);
-  auto imported_data = ImportFormDataAndProcessAddressCandidates(
+  auto extracted_data = ExtractFormDataAndProcessAddressCandidates(
       form_structure, /*profile_autofill_enabled=*/true,
       /*payment_methods_autofill_enabled=*/true);
-  ASSERT_FALSE(imported_data.credit_card_import_candidate);
-  // |imported_credit_card_record_type_| should be kNoCard because no valid card
-  // was successfully imported from the form.
-  ASSERT_TRUE(
-      form_data_importer().imported_credit_card_record_type_for_testing() ==
-      FormDataImporter::ImportedCreditCardRecordType::kNoCard);
+  ASSERT_FALSE(extracted_data.credit_card_import_candidate);
+  // |credit_card_import_type_| should be kNoCard because no
+  // valid card was successfully imported from the form.
+  ASSERT_TRUE(form_data_importer().credit_card_import_type_for_testing() ==
+              FormDataImporter::CreditCardImportType::kNoCard);
 }
 
-// Ensures that `FormDataImporterTest::imported_credit_card_record_type_` is set
+// Ensures that
+// `FormDataImporterTest::credit_card_import_type_` is set
 // correctly.
 TEST_P(FormDataImporterTest,
-       ImportFormData_ImportCreditCardRecordType_NoCard_VirtualCard) {
+       ExtractFormData_ExtractCreditCardRecordType_NoCard_VirtualCard) {
   // Simulate a form submission using a credit card that is known as a virtual
   // card.
   FormData form;
@@ -3052,22 +3052,22 @@
   FormStructure form_structure(form);
   form_structure.DetermineHeuristicTypes(nullptr, nullptr);
   form_data_importer().CacheFetchedVirtualCard(u"1111");
-  auto imported_data = ImportFormDataAndProcessAddressCandidates(
+  auto extracted_data = ExtractFormDataAndProcessAddressCandidates(
       form_structure, /*profile_autofill_enabled=*/true,
       /*payment_methods_autofill_enabled=*/true);
-  ASSERT_FALSE(imported_data.credit_card_import_candidate);
-  // |imported_credit_card_record_type_| should be kNoCard because the card
-  // imported from the form was a virtual card.
-  ASSERT_TRUE(
-      form_data_importer().imported_credit_card_record_type_for_testing() ==
-      FormDataImporter::ImportedCreditCardRecordType::kNoCard);
+  ASSERT_FALSE(extracted_data.credit_card_import_candidate);
+  // |credit_card_import_type_| should be kNoCard because the
+  // card imported from the form was a virtual card.
+  ASSERT_TRUE(form_data_importer().credit_card_import_type_for_testing() ==
+              FormDataImporter::CreditCardImportType::kNoCard);
 }
 
-// Ensures that `FormDataImporterTest::imported_credit_card_record_type_` is set
+// Ensures that
+// `FormDataImporterTest::credit_card_import_type_` is set
 // correctly.
 TEST_P(
     FormDataImporterTest,
-    ImportFormData_ImportCreditCardRecordType_NewCard_ExpiredCard_WithExpDateFixFlow) {
+    ExtractFormData_ExtractCreditCardRecordType_NewCard_ExpiredCard_WithExpDateFixFlow) {
   // Simulate a form submission with an expired credit card.
   FormData form;
   form.url = GURL("https://www.foo.com");
@@ -3077,21 +3077,21 @@
 
   FormStructure form_structure(form);
   form_structure.DetermineHeuristicTypes(nullptr, nullptr);
-  auto imported_data = ImportFormDataAndProcessAddressCandidates(
+  auto extracted_data = ExtractFormDataAndProcessAddressCandidates(
       form_structure, /*profile_autofill_enabled=*/true,
       /*payment_methods_autofill_enabled=*/true);
-  ASSERT_TRUE(imported_data.credit_card_import_candidate);
-  // |imported_credit_card_record_type_| should be kNewCard because card was
-  // successfully imported from the form via the expiration date fix flow.
-  ASSERT_TRUE(
-      form_data_importer().imported_credit_card_record_type_for_testing() ==
-      FormDataImporter::ImportedCreditCardRecordType::kNewCard);
+  ASSERT_TRUE(extracted_data.credit_card_import_candidate);
+  // |credit_card_import_type_| should be kNewCard because card
+  // was successfully imported from the form via the expiration date fix flow.
+  ASSERT_TRUE(form_data_importer().credit_card_import_type_for_testing() ==
+              FormDataImporter::CreditCardImportType::kNewCard);
 }
 
-// Ensures that `FormDataImporterTest::imported_credit_card_record_type_` is set
+// Ensures that
+// `FormDataImporterTest::credit_card_import_type_` is set
 // correctly.
 TEST_P(FormDataImporterTest,
-       ImportFormData_ImportCreditCardRecordType_NoCard_NoCardOnForm) {
+       ExtractFormData_ExtractCreditCardRecordType_NoCard_NoCardOnForm) {
   // Simulate a form submission with no credit card on form.
   FormData form;
   form.url = GURL("https://www.foo.com");
@@ -3118,22 +3118,21 @@
 
   FormStructure form_structure(form);
   form_structure.DetermineHeuristicTypes(nullptr, nullptr);
-  auto imported_data = ImportFormDataAndProcessAddressCandidates(
+  auto extracted_data = ExtractFormDataAndProcessAddressCandidates(
       form_structure, /*profile_autofill_enabled=*/true,
       /*payment_methods_autofill_enabled=*/true);
-  ASSERT_FALSE(imported_data.credit_card_import_candidate);
-  // |imported_credit_card_record_type_| should be kNoCard because the form
-  // doesn't have credit card section.
-  ASSERT_TRUE(
-      form_data_importer().imported_credit_card_record_type_for_testing() ==
-      FormDataImporter::ImportedCreditCardRecordType::kNoCard);
+  ASSERT_FALSE(extracted_data.credit_card_import_candidate);
+  // |credit_card_import_type_| should be kNoCard because the
+  // form doesn't have credit card section.
+  ASSERT_TRUE(form_data_importer().credit_card_import_type_for_testing() ==
+              FormDataImporter::CreditCardImportType::kNoCard);
 }
 
-// ImportFormData tests (both addresses and credit cards).
+// ExtractFormData tests (both addresses and credit cards).
 
 // Test that a form with both address and credit card sections imports the
 // address and the credit card.
-TEST_P(FormDataImporterTest, ImportFormData_OneAddressOneCreditCard) {
+TEST_P(FormDataImporterTest, ExtractFormData_OneAddressOneCreditCard) {
   FormData form;
   form.url = GURL("https://www.foo.com");
 
@@ -3166,12 +3165,12 @@
 
   FormStructure form_structure(form);
   form_structure.DetermineHeuristicTypes(nullptr, nullptr);
-  auto imported_data = ImportFormDataAndProcessAddressCandidates(
+  auto extracted_data = ExtractFormDataAndProcessAddressCandidates(
       form_structure, /*profile_autofill_enabled=*/true,
       /*payment_methods_autofill_enabled=*/true);
-  ASSERT_TRUE(imported_data.credit_card_import_candidate);
+  ASSERT_TRUE(extracted_data.credit_card_import_candidate);
   personal_data_manager_->OnAcceptedLocalCreditCardSave(
-      *imported_data.credit_card_import_candidate);
+      *extracted_data.credit_card_import_candidate);
 
   WaitForOnPersonalDataChanged();
 
@@ -3197,7 +3196,7 @@
 
 // Test that a form with two address sections and a credit card section does not
 // import the address but does import the credit card.
-TEST_P(FormDataImporterTest, ImportFormData_TwoAddressesOneCreditCard) {
+TEST_P(FormDataImporterTest, ExtractFormData_TwoAddressesOneCreditCard) {
   FormData form;
   form.url = GURL("https://www.foo.com");
 
@@ -3250,14 +3249,14 @@
   EXPECT_CALL(personal_data_observer_, OnPersonalDataChanged())
       .Times(testing::AnyNumber());
   // Still returns true because the credit card import was successful.
-  auto imported_data = ImportFormDataAndProcessAddressCandidates(
+  auto extracted_data = ExtractFormDataAndProcessAddressCandidates(
       form_structure, /*profile_autofill_enabled=*/true,
       /*payment_methods_autofill_enabled=*/true);
   run_loop.Run();
 
-  ASSERT_TRUE(imported_data.credit_card_import_candidate);
+  ASSERT_TRUE(extracted_data.credit_card_import_candidate);
   personal_data_manager_->OnAcceptedLocalCreditCardSave(
-      *imported_data.credit_card_import_candidate);
+      *extracted_data.credit_card_import_candidate);
 
   WaitForOnPersonalDataChanged();
 
@@ -3275,21 +3274,21 @@
 }
 
 #if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
-TEST_P(FormDataImporterTest, ImportFormData_ImportIbanRecordType_NoIban) {
+TEST_P(FormDataImporterTest, ExtractFormData_ImportIbanRecordType_NoIban) {
   // Simulate a form submission with no IBAN.
   FormData form;
   form.url = GURL("https://www.foo.com");
 
   FormStructure form_structure(form);
   form_structure.DetermineHeuristicTypes(nullptr, nullptr);
-  auto imported_data = ImportFormDataAndProcessAddressCandidates(
+  auto extracted_data = ExtractFormDataAndProcessAddressCandidates(
       form_structure, /*profile_autofill_enabled=*/true,
       /*payment_methods_autofill_enabled=*/true);
-  ASSERT_FALSE(imported_data.iban_import_candidate);
+  ASSERT_FALSE(extracted_data.iban_import_candidate);
 }
 
 TEST_P(FormDataImporterTest,
-       ImportFormData_ImportIbanRecordType_IbanAutofill_NewIban) {
+       ExtractFormData_ImportIbanRecordType_IbanAutofill_NewIban) {
   // Simulate a form submission with a new IBAN.
   FormData form;
   form.url = GURL("https://www.foo.com");
@@ -3298,19 +3297,19 @@
 
   FormStructure form_structure(form);
   form_structure.DetermineHeuristicTypes(nullptr, nullptr);
-  auto imported_data = ImportFormDataAndProcessAddressCandidates(
+  auto extracted_data = ExtractFormDataAndProcessAddressCandidates(
       form_structure, /*profile_autofill_enabled=*/true,
       /*payment_methods_autofill_enabled=*/true);
   if (base::FeatureList::IsEnabled(features::kAutofillFillIbanFields)) {
-    ASSERT_TRUE(imported_data.iban_import_candidate);
-    ASSERT_TRUE(imported_data.iban_import_candidate->record_type() ==
+    ASSERT_TRUE(extracted_data.iban_import_candidate);
+    ASSERT_TRUE(extracted_data.iban_import_candidate->record_type() ==
                 IBAN::NEW_IBAN);
   } else {
-    ASSERT_FALSE(imported_data.iban_import_candidate);
+    ASSERT_FALSE(extracted_data.iban_import_candidate);
   }
 }
 
-TEST_P(FormDataImporterTest, ImportFormData_ImportIbanRecordType_LocalIban) {
+TEST_P(FormDataImporterTest, ExtractFormData_ImportIbanRecordType_LocalIban) {
   IBAN iban;
   iban.set_value(u"IE12 BOFI 9000 0112 3456 78");
   personal_data_manager_->AddIBAN(iban);
@@ -3329,15 +3328,15 @@
 
   FormStructure form_structure(form);
   form_structure.DetermineHeuristicTypes(nullptr, nullptr);
-  auto imported_data = ImportFormDataAndProcessAddressCandidates(
+  auto extracted_data = ExtractFormDataAndProcessAddressCandidates(
       form_structure, /*profile_autofill_enabled=*/true,
       /*payment_methods_autofill_enabled=*/true);
   if (base::FeatureList::IsEnabled(features::kAutofillFillIbanFields)) {
-    ASSERT_TRUE(imported_data.iban_import_candidate);
-    ASSERT_TRUE(imported_data.iban_import_candidate->record_type() ==
+    ASSERT_TRUE(extracted_data.iban_import_candidate);
+    ASSERT_TRUE(extracted_data.iban_import_candidate->record_type() ==
                 IBAN::LOCAL_IBAN);
   } else {
-    ASSERT_FALSE(imported_data.iban_import_candidate);
+    ASSERT_FALSE(extracted_data.iban_import_candidate);
   }
 }
 
@@ -3345,7 +3344,7 @@
 
 // Test that a form with both address and credit card sections imports only the
 // the credit card if addresses are disabled.
-TEST_P(FormDataImporterTest, ImportFormData_AddressesDisabledOneCreditCard) {
+TEST_P(FormDataImporterTest, ExtractFormData_AddressesDisabledOneCreditCard) {
   FormData form;
   form.url = GURL("https://www.foo.com");
 
@@ -3376,12 +3375,12 @@
 
   FormStructure form_structure(form);
   form_structure.DetermineHeuristicTypes(nullptr, nullptr);
-  auto imported_data = ImportFormDataAndProcessAddressCandidates(
+  auto extracted_data = ExtractFormDataAndProcessAddressCandidates(
       form_structure, /*profile_autofill_enabled=*/false,
       /*payment_methods_autofill_enabled=*/true);
-  ASSERT_TRUE(imported_data.credit_card_import_candidate);
+  ASSERT_TRUE(extracted_data.credit_card_import_candidate);
   personal_data_manager_->OnAcceptedLocalCreditCardSave(
-      *imported_data.credit_card_import_candidate);
+      *extracted_data.credit_card_import_candidate);
 
   WaitForOnPersonalDataChanged();
 
@@ -3400,7 +3399,7 @@
 
 // Test that a form with both address and credit card sections imports only the
 // the address if credit cards are disabled.
-TEST_P(FormDataImporterTest, ImportFormData_OneAddressCreditCardDisabled) {
+TEST_P(FormDataImporterTest, ExtractFormData_OneAddressCreditCardDisabled) {
   FormData form;
   form.url = GURL("https://www.foo.com");
 
@@ -3433,10 +3432,10 @@
 
   FormStructure form_structure(form);
   form_structure.DetermineHeuristicTypes(nullptr, nullptr);
-  auto imported_data = ImportFormDataAndProcessAddressCandidates(
+  auto extracted_data = ExtractFormDataAndProcessAddressCandidates(
       form_structure, /*profile_autofill_enabled=*/true,
       /*payment_methods_autofill_enabled=*/false);
-  ASSERT_FALSE(imported_data.credit_card_import_candidate);
+  ASSERT_FALSE(extracted_data.credit_card_import_candidate);
 
   WaitForOnPersonalDataChanged();
 
@@ -3458,7 +3457,7 @@
 
 // Test that a form with both address and credit card sections imports nothing
 // if both addressed and credit cards are disabled.
-TEST_P(FormDataImporterTest, ImportFormData_AddressCreditCardDisabled) {
+TEST_P(FormDataImporterTest, ExtractFormData_AddressCreditCardDisabled) {
   FormData form;
   form.url = GURL("https://www.foo.com");
 
@@ -3489,10 +3488,10 @@
 
   FormStructure form_structure(form);
   form_structure.DetermineHeuristicTypes(nullptr, nullptr);
-  auto imported_data = ImportFormDataAndProcessAddressCandidates(
+  auto extracted_data = ExtractFormDataAndProcessAddressCandidates(
       form_structure, /*profile_autofill_enabled=*/false,
       /*payment_methods_autofill_enabled=*/false);
-  ASSERT_FALSE(imported_data.credit_card_import_candidate);
+  ASSERT_FALSE(extracted_data.credit_card_import_candidate);
 
   // Test that addresses were not saved.
   EXPECT_EQ(0U, personal_data_manager_->GetProfiles().size());
@@ -3541,15 +3540,15 @@
 
   FormStructure form_structure(form);
   form_structure.DetermineHeuristicTypes(nullptr, nullptr);
-  auto imported_data = ImportFormDataAndProcessAddressCandidates(
+  auto extracted_data = ExtractFormDataAndProcessAddressCandidates(
       form_structure, /*profile_autofill_enabled=*/true,
       /*payment_methods_autofill_enabled=*/true);
-  ASSERT_TRUE(imported_data.credit_card_import_candidate);
+  ASSERT_TRUE(extracted_data.credit_card_import_candidate);
 }
 
 // Tests that a credit card form that is hidden after receiving input still
 // imports the card.
-TEST_P(FormDataImporterTest, ImportFormData_HiddenCreditCardFormAfterEntered) {
+TEST_P(FormDataImporterTest, ExtractFormData_HiddenCreditCardFormAfterEntered) {
   FormData form;
   form.url = GURL("https://www.foo.com");
 
@@ -3576,12 +3575,12 @@
 
   FormStructure form_structure(form);
   form_structure.DetermineHeuristicTypes(nullptr, nullptr);
-  auto imported_data = ImportFormDataAndProcessAddressCandidates(
+  auto extracted_data = ExtractFormDataAndProcessAddressCandidates(
       form_structure, /*profile_autofill_enabled=*/true,
       /*payment_methods_autofill_enabled=*/true);
-  ASSERT_TRUE(imported_data.credit_card_import_candidate);
+  ASSERT_TRUE(extracted_data.credit_card_import_candidate);
   personal_data_manager_->OnAcceptedLocalCreditCardSave(
-      *imported_data.credit_card_import_candidate);
+      *extracted_data.credit_card_import_candidate);
 
   WaitForOnPersonalDataChanged();
 
@@ -3598,14 +3597,14 @@
 // Ensures that no UPI ID value is returned when there's a credit card and no
 // UPI ID.
 TEST_P(FormDataImporterTest,
-       ImportFormData_DontSetUpiIdWhenOnlyCreditCardExists) {
+       ExtractFormData_DontSetUpiIdWhenOnlyCreditCardExists) {
   std::unique_ptr<FormStructure> form_structure =
       ConstructDefaultCreditCardFormStructure();
-  auto imported_data = ImportFormDataAndProcessAddressCandidates(
+  auto extracted_data = ExtractFormDataAndProcessAddressCandidates(
       *form_structure, /*profile_autofill_enabled=*/true,
       /*payment_methods_autofill_enabled=*/true);
-  ASSERT_TRUE(imported_data.credit_card_import_candidate);
-  ASSERT_FALSE(imported_data.imported_upi_id.has_value());
+  ASSERT_TRUE(extracted_data.credit_card_import_candidate);
+  ASSERT_FALSE(extracted_data.extracted_upi_id.has_value());
 }
 
 TEST_P(FormDataImporterTest,
@@ -3662,13 +3661,13 @@
 
   FormStructure form_structure(form);
   form_structure.DetermineHeuristicTypes(nullptr, nullptr);
-  auto imported_data = ImportFormDataAndProcessAddressCandidates(
+  auto extracted_data = ExtractFormDataAndProcessAddressCandidates(
       form_structure, /*profile_autofill_enabled=*/true,
       /*payment_methods_autofill_enabled=*/true);
-  ASSERT_TRUE(imported_data.credit_card_import_candidate);
+  ASSERT_TRUE(extracted_data.credit_card_import_candidate);
   // Ensure that we imported the server version of the card, not the local
   // version.
-  ASSERT_TRUE(imported_data.credit_card_import_candidate->record_type() ==
+  ASSERT_TRUE(extracted_data.credit_card_import_candidate->record_type() ==
               CreditCard::FULL_SERVER_CARD);
 
   // Check that both of the local cards we have added were updated.
@@ -3716,10 +3715,10 @@
   base::HistogramTester histogram_tester;
   FormStructure form_structure(form);
   form_structure.DetermineHeuristicTypes(nullptr, nullptr);
-  auto imported_data = ImportFormDataAndProcessAddressCandidates(
+  auto extracted_data = ExtractFormDataAndProcessAddressCandidates(
       form_structure, /*profile_autofill_enabled=*/true,
       /*payment_methods_autofill_enabled=*/true);
-  ASSERT_TRUE(imported_data.credit_card_import_candidate);
+  ASSERT_TRUE(extracted_data.credit_card_import_candidate);
   histogram_tester.ExpectUniqueSample(
       "Autofill.SubmittedServerCardExpirationStatus",
       AutofillMetrics::FULL_SERVER_CARD_EXPIRATION_DATE_MATCHED, 1);
@@ -3760,10 +3759,10 @@
 
   FormStructure form_structure(form);
   form_structure.DetermineHeuristicTypes(nullptr, nullptr);
-  auto imported_data = ImportFormDataAndProcessAddressCandidates(
+  auto extracted_data = ExtractFormDataAndProcessAddressCandidates(
       form_structure, /*profile_autofill_enabled=*/true,
       /*payment_methods_autofill_enabled=*/true);
-  ASSERT_FALSE(imported_data.credit_card_import_candidate);
+  ASSERT_FALSE(extracted_data.credit_card_import_candidate);
 }
 
 // Ensure that we don't offer to save if we already have same card stored as a
@@ -3801,10 +3800,10 @@
 
   FormStructure form_structure(form);
   form_structure.DetermineHeuristicTypes(nullptr, nullptr);
-  auto imported_data = ImportFormDataAndProcessAddressCandidates(
+  auto extracted_data = ExtractFormDataAndProcessAddressCandidates(
       form_structure, /*profile_autofill_enabled=*/true,
       /*payment_methods_autofill_enabled=*/true);
-  ASSERT_FALSE(imported_data.credit_card_import_candidate);
+  ASSERT_FALSE(extracted_data.credit_card_import_candidate);
 }
 
 // Ensure that we still offer to save if we have different cards stored as a
@@ -3843,10 +3842,10 @@
 
   FormStructure form_structure(form);
   form_structure.DetermineHeuristicTypes(nullptr, nullptr);
-  auto imported_data = ImportFormDataAndProcessAddressCandidates(
+  auto extracted_data = ExtractFormDataAndProcessAddressCandidates(
       form_structure, /*profile_autofill_enabled=*/true,
       /*payment_methods_autofill_enabled=*/true);
-  ASSERT_TRUE(imported_data.credit_card_import_candidate);
+  ASSERT_TRUE(extracted_data.credit_card_import_candidate);
 }
 
 TEST_P(FormDataImporterTest,
@@ -3884,10 +3883,10 @@
   base::HistogramTester histogram_tester;
   FormStructure form_structure(form);
   form_structure.DetermineHeuristicTypes(nullptr, nullptr);
-  auto imported_data = ImportFormDataAndProcessAddressCandidates(
+  auto extracted_data = ExtractFormDataAndProcessAddressCandidates(
       form_structure, /*profile_autofill_enabled=*/true,
       /*payment_methods_autofill_enabled=*/true);
-  ASSERT_TRUE(imported_data.credit_card_import_candidate);
+  ASSERT_TRUE(extracted_data.credit_card_import_candidate);
   histogram_tester.ExpectUniqueSample(
       "Autofill.SubmittedServerCardExpirationStatus",
       AutofillMetrics::FULL_SERVER_CARD_EXPIRATION_DATE_DID_NOT_MATCH, 1);
@@ -3928,10 +3927,10 @@
   base::HistogramTester histogram_tester;
   FormStructure form_structure(form);
   form_structure.DetermineHeuristicTypes(nullptr, nullptr);
-  auto imported_data = ImportFormDataAndProcessAddressCandidates(
+  auto extracted_data = ExtractFormDataAndProcessAddressCandidates(
       form_structure, /*profile_autofill_enabled=*/true,
       /*payment_methods_autofill_enabled=*/true);
-  ASSERT_TRUE(imported_data.credit_card_import_candidate);
+  ASSERT_TRUE(extracted_data.credit_card_import_candidate);
   histogram_tester.ExpectUniqueSample(
       "Autofill.SubmittedServerCardExpirationStatus",
       AutofillMetrics::MASKED_SERVER_CARD_EXPIRATION_DATE_MATCHED, 1);
@@ -3973,16 +3972,16 @@
   base::HistogramTester histogram_tester;
   FormStructure form_structure(form);
   form_structure.DetermineHeuristicTypes(nullptr, nullptr);
-  auto imported_data = ImportFormDataAndProcessAddressCandidates(
+  auto extracted_data = ExtractFormDataAndProcessAddressCandidates(
       form_structure, /*profile_autofill_enabled=*/true,
       /*payment_methods_autofill_enabled=*/true);
-  ASSERT_TRUE(imported_data.credit_card_import_candidate);
+  ASSERT_TRUE(extracted_data.credit_card_import_candidate);
   histogram_tester.ExpectUniqueSample(
       "Autofill.SubmittedServerCardExpirationStatus",
       AutofillMetrics::MASKED_SERVER_CARD_EXPIRATION_DATE_DID_NOT_MATCH, 1);
 }
 
-TEST_P(FormDataImporterTest, ImportUpiId) {
+TEST_P(FormDataImporterTest, ExtractUpiId) {
   FormData form;
   form.url = GURL("https://www.foo.com");
 
@@ -3994,14 +3993,14 @@
   FormStructure form_structure(form);
   form_structure.DetermineHeuristicTypes(nullptr, nullptr);
 
-  auto imported_data = ImportFormDataAndProcessAddressCandidates(
+  auto extracted_data = ExtractFormDataAndProcessAddressCandidates(
       form_structure, /*profile_autofill_enabled=*/false,
       /*payment_methods_autofill_enabled=*/true);
-  ASSERT_TRUE(imported_data.imported_upi_id.has_value());
-  EXPECT_EQ(imported_data.imported_upi_id.value(), "user@indianbank");
+  ASSERT_TRUE(extracted_data.extracted_upi_id.has_value());
+  EXPECT_EQ(extracted_data.extracted_upi_id.value(), "user@indianbank");
 }
 
-TEST_P(FormDataImporterTest, ImportUpiIdDisabled) {
+TEST_P(FormDataImporterTest, ExtractUpiIdDisabled) {
   FormData form;
   form.url = GURL("https://www.foo.com");
 
@@ -4013,13 +4012,13 @@
   FormStructure form_structure(form);
   form_structure.DetermineHeuristicTypes(nullptr, nullptr);
 
-  auto imported_data = ImportFormDataAndProcessAddressCandidates(
+  auto extracted_data = ExtractFormDataAndProcessAddressCandidates(
       form_structure, /*profile_autofill_enabled=*/false,
       /*payment_methods_autofill_enabled=*/false);
-  ASSERT_FALSE(imported_data.imported_upi_id.has_value());
+  ASSERT_FALSE(extracted_data.extracted_upi_id.has_value());
 }
 
-TEST_P(FormDataImporterTest, ImportUpiIdIgnoreNonUpiId) {
+TEST_P(FormDataImporterTest, ExtractUpiIdIgnoreNonUpiId) {
   FormData form;
   form.url = GURL("https://www.foo.com");
 
@@ -4031,10 +4030,10 @@
   FormStructure form_structure(form);
   form_structure.DetermineHeuristicTypes(nullptr, nullptr);
 
-  auto imported_data = ImportFormDataAndProcessAddressCandidates(
+  auto extracted_data = ExtractFormDataAndProcessAddressCandidates(
       form_structure, /*profile_autofill_enabled=*/false,
       /*payment_methods_autofill_enabled=*/false);
-  ASSERT_FALSE(imported_data.imported_upi_id.has_value());
+  ASSERT_FALSE(extracted_data.extracted_upi_id.has_value());
 }
 
 TEST_P(FormDataImporterTest, SilentlyUpdateExistingProfileByIncompleteProfile) {
@@ -4078,7 +4077,7 @@
 
   FormStructure form_structure(form);
   form_structure.DetermineHeuristicTypes(nullptr, nullptr);
-  ImportAddressProfiles(/*extraction_successful=*/false, form_structure);
+  ExtractAddressProfiles(/*extraction_successful=*/false, form_structure);
 
   // Expect that no new profile is saved.
   const std::vector<AutofillProfile*>& results =
@@ -4134,9 +4133,9 @@
 
   FormStructure form_structure(form);
   form_structure.DetermineHeuristicTypes(nullptr, nullptr);
-  ImportAddressProfiles(/*extraction_successful=*/false, form_structure,
-                        /*skip_waiting_on_pdm=*/false,
-                        /*allow_save_prompts=*/false);
+  ExtractAddressProfiles(/*extraction_successful=*/false, form_structure,
+                         /*skip_waiting_on_pdm=*/false,
+                         /*allow_save_prompts=*/false);
 
   // Expect that no new profile is saved and the existing profile is updated.
   const std::vector<AutofillProfile*>& results =
@@ -4190,8 +4189,8 @@
 
   FormStructure form_structure(form);
   form_structure.DetermineHeuristicTypes(nullptr, nullptr);
-  ImportAddressProfiles(/*extraction_successful=*/false, form_structure,
-                        /*skip_waiting_on_pdm=*/true);
+  ExtractAddressProfiles(/*extraction_successful=*/false, form_structure,
+                         /*skip_waiting_on_pdm=*/true);
 
   // Expect that no new profile is saved.
   const std::vector<AutofillProfile*>& results =
@@ -4221,7 +4220,7 @@
       ConstructFormStructureFromTypeValuePairs(type_value_pairs);
   type_value_pairs.pop_back();  // Remove state manually for verification.
   base::HistogramTester histogram_tester;
-  ImportAddressProfilesAndVerifyExpectation(
+  ExtractAddressProfilesAndVerifyExpectation(
       *form_structure, {ConstructProfileFromTypeValuePairs(type_value_pairs)});
 
   // State was removed. Expect the metrics to behave accordingly.
@@ -4233,7 +4232,7 @@
       AutofillMetrics::SettingsVisibleFieldTypeForMetrics::kState, 1);
 }
 
-// Tests a 2-page multi-step import.
+// Tests a 2-page multi-step extraction.
 TEST_P(FormDataImporterTest, MultiStepImport) {
   base::test::ScopedFeatureList multistep_import_feature;
   multistep_import_feature.InitAndEnableFeature(
@@ -4241,10 +4240,10 @@
 
   std::unique_ptr<FormStructure> form_structure =
       ConstructSplitDefaultProfileFormStructure(/*part=*/1);
-  ImportAddressProfilesAndVerifyExpectation(*form_structure, {});
+  ExtractAddressProfilesAndVerifyExpectation(*form_structure, {});
 
   form_structure = ConstructSplitDefaultProfileFormStructure(/*part=*/2);
-  ImportAddressProfileAndVerifyImportOfDefaultProfile(*form_structure);
+  ExtractAddressProfileAndVerifyExtractionOfDefaultProfile(*form_structure);
 }
 
 // Tests that a complemented country is discarded in favour of an observed one.
@@ -4262,7 +4261,7 @@
       std::pair<ServerFieldType, std::string>(ADDRESS_HOME_COUNTRY, "US")));
   std::unique_ptr<FormStructure> form_structure =
       ConstructFormStructureFromTypeValuePairs(type_value_pairs);
-  ImportAddressProfilesAndVerifyExpectation(*form_structure, {});
+  ExtractAddressProfilesAndVerifyExpectation(*form_structure, {});
 
   // Now import a profile without a country. The country is thus be complemented
   // to the variation country "DE".
@@ -4272,7 +4271,7 @@
                               [](auto& pair) { return pair.first; }));
   form_structure = ConstructFormStructureFromTypeValuePairs(type_value_pairs);
 
-  ImportAddressProfileAndVerifyImportOfDefaultProfile(*form_structure);
+  ExtractAddressProfileAndVerifyExtractionOfDefaultProfile(*form_structure);
 }
 
 // Tests that when multi-step complements are enabled, complete profiles those
@@ -4284,23 +4283,23 @@
       features::kAutofillEnableMultiStepImports,
       {{features::kAutofillEnableMultiStepImportComplements.name, "true"}});
 
-  // Import the default profile without an email address.
+  // Extract the default profile without an email address.
   TypeValuePairs type_value_pairs = GetDefaultProfileTypeValuePairs();
   SetValueForType(type_value_pairs, EMAIL_ADDRESS, "");
   std::unique_ptr<FormStructure> form_structure =
       ConstructFormStructureFromTypeValuePairs(type_value_pairs);
-  // Using `ImportAddressProfileAndVerifyImportOfDefaultProfile()` doesn't
+  // Using `ExtractAddressProfileAndVerifyExtractionOfDefaultProfile()` doesn't
   // suffice, as the multi-step complement candidate is only added in the
   // "ProcessAddressCandidates" step.
-  ImportFormDataAndProcessAddressCandidates(*form_structure);
-  VerifyExpectationForImportedAddressProfiles(
+  ExtractFormDataAndProcessAddressCandidates(*form_structure);
+  VerifyExpectationForExtractedAddressProfiles(
       {ConstructProfileFromTypeValuePairs(type_value_pairs)});
 
   // Import the email address in a separate form. Without multi-step updates,
   // this information cannot be associated to a profile. The resulting profile
   // is the default one.
   form_structure = ConstructDefaultEmailFormStructure();
-  ImportAddressProfileAndVerifyImportOfDefaultProfile(*form_structure);
+  ExtractAddressProfileAndVerifyExtractionOfDefaultProfile(*form_structure);
 }
 
 // Tests that when an imported profile is modified through external means (e.g.
@@ -4312,13 +4311,13 @@
       features::kAutofillEnableMultiStepImports,
       {{features::kAutofillEnableMultiStepImportComplements.name, "true"}});
 
-  // Import the default profile without an email address.
+  // Extract the default profile without an email address.
   TypeValuePairs type_value_pairs = GetDefaultProfileTypeValuePairs();
   SetValueForType(type_value_pairs, EMAIL_ADDRESS, "");
   std::unique_ptr<FormStructure> form_structure =
       ConstructFormStructureFromTypeValuePairs(type_value_pairs);
-  ImportFormDataAndProcessAddressCandidates(*form_structure);
-  VerifyExpectationForImportedAddressProfiles(
+  ExtractFormDataAndProcessAddressCandidates(*form_structure);
+  VerifyExpectationForExtractedAddressProfiles(
       {ConstructProfileFromTypeValuePairs(type_value_pairs)});
 
   // Update the profile's ZIP through external means.
@@ -4333,8 +4332,8 @@
   AutofillProfile expected_profile = ConstructDefaultProfile();
   expected_profile.SetInfoWithVerificationStatus(
       ADDRESS_HOME_ZIP, u"12345", kLocale, VerificationStatus::kObserved);
-  ImportAddressProfilesAndVerifyExpectation(*form_structure,
-                                            {expected_profile});
+  ExtractAddressProfilesAndVerifyExpectation(*form_structure,
+                                             {expected_profile});
 }
 
 // Tests that when an imported profile is deleted through external means (e.g.
@@ -4346,13 +4345,13 @@
       features::kAutofillEnableMultiStepImports,
       {{features::kAutofillEnableMultiStepImportComplements.name, "true"}});
 
-  // Import the default profile without an email address.
+  // Extract the default profile without an email address.
   TypeValuePairs type_value_pairs = GetDefaultProfileTypeValuePairs();
   SetValueForType(type_value_pairs, EMAIL_ADDRESS, "");
   std::unique_ptr<FormStructure> form_structure =
       ConstructFormStructureFromTypeValuePairs(type_value_pairs);
-  ImportFormDataAndProcessAddressCandidates(*form_structure);
-  VerifyExpectationForImportedAddressProfiles(
+  ExtractFormDataAndProcessAddressCandidates(*form_structure);
+  VerifyExpectationForExtractedAddressProfiles(
       {ConstructProfileFromTypeValuePairs(type_value_pairs)});
 
   // Remove the profile through external means.
@@ -4376,7 +4375,7 @@
   form.url = GURL("https://www.foo.com");
   std::unique_ptr<FormStructure> form_structure =
       ConstructFormStructureFromFormData(form);
-  ImportAddressProfilesAndVerifyExpectation(*form_structure, {});
+  ExtractAddressProfilesAndVerifyExpectation(*form_structure, {});
 
   form = ConstructSplitDefaultFormData(/*part=*/2);
   form.url = GURL("https://wwww.bar.com");
@@ -4394,7 +4393,7 @@
 
   std::unique_ptr<FormStructure> form_structure =
       ConstructSplitDefaultProfileFormStructure(/*part=*/1);
-  ImportAddressProfilesAndVerifyExpectation(*form_structure, {});
+  ExtractAddressProfilesAndVerifyExpectation(*form_structure, {});
 
   test_clock.Advance(base::Minutes(31));
 
@@ -4411,7 +4410,7 @@
 
   std::unique_ptr<FormStructure> form_structure =
       ConstructSplitDefaultProfileFormStructure(/*part=*/1);
-  ImportAddressProfilesAndVerifyExpectation(*form_structure, {});
+  ExtractAddressProfilesAndVerifyExpectation(*form_structure, {});
 
   personal_data_manager_->OnURLsDeleted(
       /*history_service=*/nullptr,
@@ -4434,10 +4433,10 @@
   std::unique_ptr<FormStructure> form_structure =
       ConstructShippingAndBillingFormStructure();
   FormSignature form_signature = form_structure->form_signature();
-  // Don't use `ImportAddressProfileAndVerifyImportOfDefaultProfile()`, as this
-  // function assumes we know it's an address form already. Form associations
-  // are tracked in `ImportFormData()` instead.
-  ImportFormDataAndProcessAddressCandidates(*form_structure);
+  // Don't use `ExtractAddressProfileAndVerifyExtractionOfDefaultProfile()`, as
+  // this function assumes we know it's an address form already. Form
+  // associations are tracked in `ExtractFormData()` instead.
+  ExtractFormDataAndProcessAddressCandidates(*form_structure);
 
   absl::optional<FormStructure::FormAssociations> associations =
       form_data_importer().GetFormAssociations(form_signature);
@@ -4460,7 +4459,8 @@
       form_data_importer().ProcessIBANImportCandidate(iban_import_candidate));
 }
 
-TEST_P(FormDataImporterTest, ImportFormData_ProcessIBANImportCandidate_NoIban) {
+TEST_P(FormDataImporterTest,
+       ExtractFormData_ProcessIBANImportCandidate_NoIban) {
   // Simulate a form submission with a new IBAN.
   FormData form;
   form.url = GURL("https://www.foo.com");
@@ -4470,14 +4470,14 @@
   FormStructure form_structure(form);
   form_structure.DetermineHeuristicTypes(nullptr, nullptr);
 
-  ASSERT_FALSE(ImportFormDataAndProcessIBANCandidates(
+  ASSERT_FALSE(ExtractFormDataAndProcessIBANCandidates(
       form_structure, /*profile_autofill_enabled=*/true,
       /*payment_methods_autofill_enabled=*/true));
 }
 
 TEST_P(
     FormDataImporterTest,
-    ImportFormData_ProcessIBANImportCandidate_PaymentMethodsSettingDisabled) {
+    ExtractFormData_ProcessIBANImportCandidate_PaymentMethodsSettingDisabled) {
   // Simulate a form submission with a new IBAN.
   FormData form;
   form.url = GURL("https://www.foo.com");
@@ -4487,13 +4487,13 @@
   FormStructure form_structure(form);
   form_structure.DetermineHeuristicTypes(nullptr, nullptr);
 
-  ASSERT_FALSE(ImportFormDataAndProcessIBANCandidates(
+  ASSERT_FALSE(ExtractFormDataAndProcessIBANCandidates(
       form_structure, /*profile_autofill_enabled=*/true,
       /*payment_methods_autofill_enabled=*/false));
 }
 
 TEST_P(FormDataImporterTest,
-       ImportFormData_ProcessIBANImportCandidate_NewIban) {
+       ExtractFormData_ProcessIBANImportCandidate_NewIban) {
   // Simulate a form submission with a new IBAN.
   FormData form;
   form.url = GURL("https://www.foo.com");
@@ -4504,13 +4504,13 @@
   form_structure.DetermineHeuristicTypes(nullptr, nullptr);
 
   ASSERT_EQ(base::FeatureList::IsEnabled(features::kAutofillFillIbanFields),
-            ImportFormDataAndProcessIBANCandidates(
+            ExtractFormDataAndProcessIBANCandidates(
                 form_structure, /*profile_autofill_enabled=*/true,
                 /*payment_methods_autofill_enabled=*/true));
 }
 
 TEST_P(FormDataImporterTest,
-       ImportFormData_ProcessIBANImportCandidate_LocalIban) {
+       ExtractFormData_ProcessIBANImportCandidate_LocalIban) {
   IBAN iban;
   iban.set_value(base::UTF8ToUTF16(std::string(kIbanValue)));
   personal_data_manager_->AddIBAN(iban);
@@ -4525,13 +4525,13 @@
   FormStructure form_structure(form);
   form_structure.DetermineHeuristicTypes(nullptr, nullptr);
 
-  ASSERT_FALSE(ImportFormDataAndProcessIBANCandidates(
+  ASSERT_FALSE(ExtractFormDataAndProcessIBANCandidates(
       form_structure, /*profile_autofill_enabled=*/true,
       /*payment_methods_autofill_enabled=*/true));
 }
 
 TEST_P(FormDataImporterTest,
-       ImportFormData_ProcessIBANImportCandidate_MaxStrikes) {
+       ExtractFormData_ProcessIBANImportCandidate_MaxStrikes) {
   IBANSaveStrikeDatabase iban_save_strike_database =
       IBANSaveStrikeDatabase(autofill_client_->GetStrikeDatabase());
 
@@ -4547,7 +4547,7 @@
   FormStructure form_structure(form);
   form_structure.DetermineHeuristicTypes(nullptr, nullptr);
 
-  ASSERT_FALSE(ImportFormDataAndProcessIBANCandidates(
+  ASSERT_FALSE(ExtractFormDataAndProcessIBANCandidates(
       form_structure, /*profile_autofill_enabled=*/true,
       /*payment_methods_autofill_enabled=*/true));
 }
@@ -4569,16 +4569,16 @@
 TEST_F(FormDataImporterNonParameterizedTest,
        ProcessCreditCardImportCandidate_EmptyCreditCard) {
   absl::optional<CreditCard> credit_card_import_candidate;
-  absl::optional<std::string> imported_upi_id;
+  absl::optional<std::string> extracted_upi_id;
   std::unique_ptr<FormStructure> form_structure =
       ConstructDefaultCreditCardFormStructure();
 
-  // `form_data_importer()`'s `imported_credit_card_record_type_` is set to
-  // kLocalCard because we need to make sure we do not return early in the
+  // `form_data_importer()`'s `credit_card_import_type_` is set
+  // to kLocalCard because we need to make sure we do not return early in the
   // kNewCard case, and kLocalCard with upstream enabled but empty
   // |imported_credit_card| is the most likely scenario for a crash.
-  form_data_importer().set_imported_credit_card_record_type_for_testing(
-      FormDataImporter::ImportedCreditCardRecordType::kLocalCard);
+  form_data_importer().set_credit_card_import_type_for_testing(
+      FormDataImporter::CreditCardImportType::kLocalCard);
 
   // We need a sync service so that
   // LocalCardMigrationManager::ShouldOfferLocalCardMigration() does not crash.
@@ -4586,7 +4586,7 @@
   personal_data_manager_->OnSyncServiceInitialized(&sync_service);
 
   EXPECT_FALSE(form_data_importer().ProcessCreditCardImportCandidate(
-      *form_structure, credit_card_import_candidate, imported_upi_id,
+      *form_structure, credit_card_import_candidate, extracted_upi_id,
       /*payment_methods_autofill_enabled=*/true,
       /*is_credit_card_upstream_enabled=*/true));
   personal_data_manager_->OnSyncServiceInitialized(nullptr);
@@ -4602,12 +4602,12 @@
       CreditCard::VirtualCardEnrollmentState::UNENROLLED_AND_ELIGIBLE);
   absl::optional<CreditCard> credit_card_import_candidate =
       imported_credit_card;
-  absl::optional<std::string> imported_upi_id;
+  absl::optional<std::string> extracted_upi_id;
   std::unique_ptr<FormStructure> form_structure =
       ConstructDefaultCreditCardFormStructure();
 
-  form_data_importer().set_imported_credit_card_record_type_for_testing(
-      FormDataImporter::ImportedCreditCardRecordType::kServerCard);
+  form_data_importer().set_credit_card_import_type_for_testing(
+      FormDataImporter::CreditCardImportType::kServerCard);
   form_data_importer().SetFetchedCardInstrumentId(2222);
 
   // We need a sync service so that
@@ -4621,7 +4621,7 @@
                                     _, _, _, _))
       .Times(0);
   EXPECT_FALSE(form_data_importer().ProcessCreditCardImportCandidate(
-      *form_structure, credit_card_import_candidate, imported_upi_id,
+      *form_structure, credit_card_import_candidate, extracted_upi_id,
       /*payment_methods_autofill_enabled=*/true,
       /*is_credit_card_upstream_enabled=*/true));
 
@@ -4631,7 +4631,7 @@
                                     _, _, _, _))
       .Times(1);
   EXPECT_TRUE(form_data_importer().ProcessCreditCardImportCandidate(
-      *form_structure, credit_card_import_candidate, imported_upi_id,
+      *form_structure, credit_card_import_candidate, extracted_upi_id,
       /*payment_methods_autofill_enabled=*/true,
       /*is_credit_card_upstream_enabled=*/true));
 
@@ -4650,8 +4650,8 @@
   credit_card_import_candidate = test::GetCreditCard();
 
   // Should not offer save for local cards if upstream is not enabled.
-  form_data_importer().set_imported_credit_card_record_type_for_testing(
-      FormDataImporter::ImportedCreditCardRecordType::kLocalCard);
+  form_data_importer().set_credit_card_import_type_for_testing(
+      FormDataImporter::CreditCardImportType::kLocalCard);
   EXPECT_FALSE(form_data_importer().ShouldOfferUploadCardOrLocalCardSave(
       credit_card_import_candidate,
       /*is_credit_card_upload_enabled=*/false));
@@ -4662,16 +4662,16 @@
       /*is_credit_card_upload_enabled=*/true));
 
   // Should not offer save for server cards.
-  form_data_importer().set_imported_credit_card_record_type_for_testing(
-      FormDataImporter::ImportedCreditCardRecordType::kServerCard);
+  form_data_importer().set_credit_card_import_type_for_testing(
+      FormDataImporter::CreditCardImportType::kServerCard);
   EXPECT_FALSE(form_data_importer().ShouldOfferUploadCardOrLocalCardSave(
       credit_card_import_candidate,
       /*is_credit_card_upload_enabled=*/true));
 
   // Should always offer save for new cards; upload save if it is enabled, local
   // save otherwise.
-  form_data_importer().set_imported_credit_card_record_type_for_testing(
-      FormDataImporter::ImportedCreditCardRecordType::kNewCard);
+  form_data_importer().set_credit_card_import_type_for_testing(
+      FormDataImporter::CreditCardImportType::kNewCard);
   EXPECT_TRUE(form_data_importer().ShouldOfferUploadCardOrLocalCardSave(
       credit_card_import_candidate,
       /*is_credit_card_upload_enabled=*/true));
diff --git a/components/autofill/core/browser/payments/local_card_migration_manager.cc b/components/autofill/core/browser/payments/local_card_migration_manager.cc
index 544909fe..9bf5479 100644
--- a/components/autofill/core/browser/payments/local_card_migration_manager.cc
+++ b/components/autofill/core/browser/payments/local_card_migration_manager.cc
@@ -49,20 +49,20 @@
 
 bool LocalCardMigrationManager::ShouldOfferLocalCardMigration(
     const absl::optional<CreditCard>& credit_card_import_candidate,
-    int imported_credit_card_record_type) {
-  // Reset and store the imported credit card info for a later check of whether
-  // the imported card is supported.
-  imported_credit_card_number_.reset();
+    int credit_card_import_type) {
+  // Reset and store the extracted credit card info for a later check of whether
+  // the extracted card is supported.
+  extracted_credit_card_number_.reset();
   if (credit_card_import_candidate)
-    imported_credit_card_number_ = credit_card_import_candidate->number();
-  imported_credit_card_record_type_ = imported_credit_card_record_type;
+    extracted_credit_card_number_ = credit_card_import_candidate->number();
+  credit_card_import_type_ = credit_card_import_type;
   // Must be an existing card. New cards always get Upstream or local save.
-  switch (imported_credit_card_record_type_) {
-    case FormDataImporter::ImportedCreditCardRecordType::kLocalCard:
+  switch (credit_card_import_type_) {
+    case FormDataImporter::CreditCardImportType::kLocalCard:
       local_card_migration_origin_ =
           autofill_metrics::LocalCardMigrationOrigin::UseOfLocalCard;
       break;
-    case FormDataImporter::ImportedCreditCardRecordType::kServerCard:
+    case FormDataImporter::CreditCardImportType::kServerCard:
       local_card_migration_origin_ =
           autofill_metrics::LocalCardMigrationOrigin::UseOfServerCard;
       break;
@@ -82,12 +82,12 @@
 
   // Don't show the prompt if max strike count was reached.
   if (GetLocalCardMigrationStrikeDatabase()->ShouldBlockFeature()) {
-    switch (imported_credit_card_record_type_) {
-      case FormDataImporter::ImportedCreditCardRecordType::kLocalCard:
+    switch (credit_card_import_type_) {
+      case FormDataImporter::CreditCardImportType::kLocalCard:
         autofill_metrics::LogLocalCardMigrationNotOfferedDueToMaxStrikesMetric(
             AutofillMetrics::SaveTypeMetric::LOCAL);
         break;
-      case FormDataImporter::ImportedCreditCardRecordType::kServerCard:
+      case FormDataImporter::CreditCardImportType::kServerCard:
         autofill_metrics::LogLocalCardMigrationNotOfferedDueToMaxStrikesMetric(
             AutofillMetrics::SaveTypeMetric::SERVER);
         break;
@@ -105,15 +105,15 @@
   // of Upstream if there are other local cards to migrate as well. If the form
   // was submitted with a server card, offer migration if ANY local cards can be
   // migrated.
-  if ((imported_credit_card_record_type_ ==
-           FormDataImporter::ImportedCreditCardRecordType::kLocalCard &&
+  if ((credit_card_import_type_ ==
+           FormDataImporter::CreditCardImportType::kLocalCard &&
        migratable_credit_cards_.size() > 1) ||
-      (imported_credit_card_record_type_ ==
-           FormDataImporter::ImportedCreditCardRecordType::kServerCard &&
+      (credit_card_import_type_ ==
+           FormDataImporter::CreditCardImportType::kServerCard &&
        !migratable_credit_cards_.empty())) {
     return true;
-  } else if (imported_credit_card_record_type_ ==
-                 FormDataImporter::ImportedCreditCardRecordType::kLocalCard &&
+  } else if (credit_card_import_type_ ==
+                 FormDataImporter::CreditCardImportType::kLocalCard &&
              migratable_credit_cards_.size() == 1) {
     autofill_metrics::LogLocalCardMigrationDecisionMetric(
         autofill_metrics::LocalCardMigrationDecisionMetric::
@@ -231,15 +231,15 @@
       // Pops up a larger, modal dialog showing the local cards to be uploaded.
       ShowMainMigrationDialog();
     } else {
-      // Check if an imported local card is listed in
-      // |supported_card_bin_ranges|. Abort the migration when the user uses an
+      // Check if an extracted local card is listed in
+      // `supported_card_bin_ranges`. Abort the migration when the user uses an
       // unsupported local card.
       if (!supported_card_bin_ranges.empty() &&
-          imported_credit_card_record_type_ ==
-              FormDataImporter::ImportedCreditCardRecordType::kLocalCard &&
-          imported_credit_card_number_.has_value() &&
+          credit_card_import_type_ ==
+              FormDataImporter::CreditCardImportType::kLocalCard &&
+          extracted_credit_card_number_.has_value() &&
           !payments::IsCreditCardNumberSupported(
-              imported_credit_card_number_.value(),
+              extracted_credit_card_number_.value(),
               supported_card_bin_ranges)) {
         autofill_metrics::LogLocalCardMigrationDecisionMetric(
             autofill_metrics::LocalCardMigrationDecisionMetric::
diff --git a/components/autofill/core/browser/payments/local_card_migration_manager.h b/components/autofill/core/browser/payments/local_card_migration_manager.h
index 2a7c01d..82682125 100644
--- a/components/autofill/core/browser/payments/local_card_migration_manager.h
+++ b/components/autofill/core/browser/payments/local_card_migration_manager.h
@@ -97,13 +97,13 @@
 
   // Returns true if all of the conditions for allowing local credit card
   // migration are satisfied. Initializes the local card list for upload. Stores
-  // a local copy of |credit_card_import_candidate| and
-  // |imported_credit_card_record_type| locally for later check whether the
-  // imported card is supported. |credit_card_import_candidate| might be null if
-  // a user used server card.
+  // a local copy of `credit_card_import_candidate` and
+  // `credit_card_import_type` locally for later check whether
+  // the imported card is supported. `credit_card_import_candidate` might be
+  // null if a user used server card.
   bool ShouldOfferLocalCardMigration(
       const absl::optional<CreditCard>& credit_card_import_candidate,
-      int imported_credit_card_record_type);
+      int credit_card_import_type);
 
   // Called from FormDataImporter or settings page when all migration
   // requirements are met. Fetches legal documents and triggers the
@@ -232,10 +232,10 @@
   raw_ptr<PersonalDataManager> personal_data_manager_;
 
   // The imported credit card number from the form submission.
-  absl::optional<std::u16string> imported_credit_card_number_;
+  absl::optional<std::u16string> extracted_credit_card_number_;
 
   // The imported credit card record type from the form submission.
-  int imported_credit_card_record_type_;
+  int credit_card_import_type_;
 
   // Collected information about a pending migration request.
   payments::PaymentsClient::MigrationRequestDetails migration_request_;
diff --git a/components/browser_ui/media/android/java/src/org/chromium/components/browser_ui/media/MediaNotificationController.java b/components/browser_ui/media/android/java/src/org/chromium/components/browser_ui/media/MediaNotificationController.java
index 4166aaf9..e206f5d 100644
--- a/components/browser_ui/media/android/java/src/org/chromium/components/browser_ui/media/MediaNotificationController.java
+++ b/components/browser_ui/media/android/java/src/org/chromium/components/browser_ui/media/MediaNotificationController.java
@@ -9,7 +9,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
 import android.media.AudioManager;
 import android.os.Build;
 import android.os.Handler;
@@ -82,11 +81,6 @@
     public static final int MEDIA_ACTION_SEEK_FORWARD = 22;
     public static final int MEDIA_ACTION_SEEK_BACKWARD = 23;
 
-    // Overrides N detection. The production code will use |null|, which uses the Android version
-    // code. Otherwise, |isRunningAtLeastN()| will return whatever value is set.
-    @VisibleForTesting
-    public static Boolean sOverrideIsRunningNForTesting;
-
     // ListenerService running for the notification. Only non-null when showing.
     @VisibleForTesting
     public Service mService;
@@ -272,12 +266,6 @@
                         | IntentUtils.getPendingIntentMutabilityFlag(false));
     }
 
-    private static boolean isRunningAtLeastN() {
-        return (sOverrideIsRunningNForTesting != null)
-                ? sOverrideIsRunningNForTesting
-                : Build.VERSION.SDK_INT >= Build.VERSION_CODES.N;
-    }
-
     /**
      * The class containing all the information for adding a button in the notification for an
      * action.
@@ -755,17 +743,7 @@
         } else if (mMediaNotificationInfo.notificationLargeIcon != null
                 && !mMediaNotificationInfo.isPrivate) {
             builder.setLargeIcon(mMediaNotificationInfo.notificationLargeIcon);
-        } else if (!isRunningAtLeastN()) {
-            if (mDefaultNotificationLargeIcon == null
-                    && mMediaNotificationInfo.defaultNotificationLargeIcon != 0) {
-                mDefaultNotificationLargeIcon =
-                        MediaNotificationImageUtils.downscaleIconToIdealSize(
-                                BitmapFactory.decodeResource(getContext().getResources(),
-                                        mMediaNotificationInfo.defaultNotificationLargeIcon));
-            }
-            builder.setLargeIcon(mDefaultNotificationLargeIcon);
         }
-
         addNotificationButtons(builder);
     }
 
@@ -812,30 +790,17 @@
         if (mMediaNotificationInfo.isPrivate) {
             // Notifications in incognito shouldn't show what is playing to avoid leaking
             // information.
-            if (isRunningAtLeastN()) {
-                builder.setContentTitle(getContext().getResources().getString(
-                        R.string.media_notification_incognito));
-                builder.setSubText(
-                        getContext().getResources().getString(R.string.notification_incognito_tab));
-            } else {
-                // App name is automatically added to the title from Android N,
-                // but needs to be added explicitly for prior versions.
-                builder.setContentTitle(mDelegate.getAppName())
-                        .setContentText(getContext().getResources().getString(
-                                R.string.media_notification_incognito));
-            }
+            builder.setContentTitle(
+                    getContext().getResources().getString(R.string.media_notification_incognito));
+            builder.setSubText(
+                    getContext().getResources().getString(R.string.notification_incognito_tab));
             return;
         }
 
         builder.setContentTitle(mMediaNotificationInfo.metadata.getTitle());
         String artistAndAlbumText = getArtistAndAlbumText(mMediaNotificationInfo.metadata);
-        if (isRunningAtLeastN() || !artistAndAlbumText.isEmpty()) {
-            builder.setContentText(artistAndAlbumText);
-            builder.setSubText(mMediaNotificationInfo.origin);
-        } else {
-            // Leaving ContentText empty looks bad, so move origin up to the ContentText.
-            builder.setContentText(mMediaNotificationInfo.origin);
-        }
+        builder.setContentText(artistAndAlbumText);
+        builder.setSubText(mMediaNotificationInfo.origin);
     }
 
     private static String getArtistAndAlbumText(MediaMetadata metadata) {
diff --git a/components/cast_receiver/browser/embedder_application.cc b/components/cast_receiver/browser/embedder_application.cc
index ae022e9..b550b03 100644
--- a/components/cast_receiver/browser/embedder_application.cc
+++ b/components/cast_receiver/browser/embedder_application.cc
@@ -75,7 +75,7 @@
   return nullptr;
 }
 
-void EmbedderApplication::LoadPage(const GURL& gurl) {
+void EmbedderApplication::NavigateToPage(const GURL& gurl) {
   content::WebContents* web_contents = GetWebContents();
   DCHECK(web_contents);
   web_contents->GetController().LoadURL(gurl, content::Referrer(),
diff --git a/components/cast_receiver/browser/public/embedder_application.h b/components/cast_receiver/browser/public/embedder_application.h
index c1eb31d..3e4a8aab 100644
--- a/components/cast_receiver/browser/public/embedder_application.h
+++ b/components/cast_receiver/browser/public/embedder_application.h
@@ -86,7 +86,7 @@
   CreateWebUIControllerFactory(std::vector<std::string> hosts);
 
   // Loads |url| in the associated WebContents.
-  virtual void LoadPage(const GURL& url);
+  virtual void NavigateToPage(const GURL& url);
 };
 
 std::ostream& operator<<(std::ostream& os,
diff --git a/components/cast_receiver/browser/runtime_application_base.cc b/components/cast_receiver/browser/runtime_application_base.cc
index b6abca7..b9cda54 100644
--- a/components/cast_receiver/browser/runtime_application_base.cc
+++ b/components/cast_receiver/browser/runtime_application_base.cc
@@ -77,12 +77,12 @@
       *embedder_application().GetWebContents());
 }
 
-void RuntimeApplicationBase::LoadPage(const GURL& url) {
+void RuntimeApplicationBase::NavigateToPage(const GURL& url) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
-  embedder_application().LoadPage(url);
+  embedder_application().NavigateToPage(url);
 
-  SetWebVisibilityAndPaint(false);
+  SetWebVisibilityAndPaint(is_visible_);
 }
 
 void RuntimeApplicationBase::SetContentPermissions(
@@ -104,29 +104,19 @@
   }
 }
 
-void RuntimeApplicationBase::OnPageLoaded() {
+void RuntimeApplicationBase::OnPageNavigationComplete() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DVLOG(1) << "Page loaded: " << *this;
 
   auto* window_controls = embedder_application().GetContentWindowControls();
   DCHECK(window_controls);
   window_controls->AddVisibilityChangeObserver(*this);
-  if (is_touch_input_enabled_) {
-    window_controls->EnableTouchInput();
-  } else {
-    window_controls->DisableTouchInput();
-  }
-
-  // Create the window and show the web view.
-  if (is_visible_) {
-    DVLOG(1) << "Loading page in full screen: " << *this;
-    window_controls->ShowWindow();
-  } else {
-    DVLOG(1) << "Loading page in background: " << *this;
-    window_controls->HideWindow();
-  }
 
   embedder_application().NotifyApplicationStarted();
+
+  SetWebVisibilityAndPaint(is_visible_);
+  SetTouchInputEnabled(is_touch_input_enabled_);
+  SetMediaBlocking(is_media_load_blocked_, is_media_start_blocked_);
 }
 
 void RuntimeApplicationBase::SetUrlRewriteRules(
@@ -139,7 +129,12 @@
   url_rewrite::UrlRequestRewriteRulesManager&
       url_request_rewrite_rules_manager =
           GetApplicationControls().GetUrlRequestRewriteRulesManager();
-  url_request_rewrite_rules_manager.OnRulesUpdated(std::move(mojom_rules));
+  if (!url_request_rewrite_rules_manager.OnRulesUpdated(
+          std::move(mojom_rules))) {
+    LOG(ERROR) << "URL rewrite rules update failed.";
+    StopApplication(EmbedderApplication::ApplicationStopReason::kRuntimeError,
+                    net::Error::ERR_UNEXPECTED);
+  }
 }
 
 void RuntimeApplicationBase::SetMediaBlocking(bool load_blocked,
@@ -242,6 +237,15 @@
   if (is_visible) {
     web_contents->WasShown();
   } else {
+    // NOTE: Calling WasHidden() and later WasShown() does not behave properly
+    // on some platforms (e.g. Linux devices using X11 platform for Ozone). In
+    // such cases, the WasShown() call will execute, and the browser-side code
+    // associated with this call will run, but it will never reach the Renderer
+    // process, so the LayerTreeHost will never draw the surface assocaited with
+    // this WebContents.
+    DLOG(WARNING)
+        << "WebContents hidden. NOTE: Changing from hidden to visible does not "
+           "work in all cases, and such calls may not be respected.";
     web_contents->WasHidden();
   }
 
diff --git a/components/cast_receiver/browser/runtime_application_base.h b/components/cast_receiver/browser/runtime_application_base.h
index 5fd5211..feac3156 100644
--- a/components/cast_receiver/browser/runtime_application_base.h
+++ b/components/cast_receiver/browser/runtime_application_base.h
@@ -86,11 +86,12 @@
 
   ApplicationClient& application_client() { return *application_client_; }
 
-  // Loads the page at the given |url| in the associated WebContents.
-  void LoadPage(const GURL& url);
+  // Navigated to the page at the given |url| in the associated WebContents.
+  void NavigateToPage(const GURL& url);
 
-  // Called by the actual implementation as Cast application page has loaded.
-  void OnPageLoaded();
+  // Called by the actual implementation after the Cast application page has
+  // been navigated to, following a call to NavigateToPage().
+  void OnPageNavigationComplete();
 
   // Sets the permissions for the provided |web_contents| to that as configured
   // in |app_config_|.
diff --git a/components/cast_receiver/browser/streaming_runtime_application.cc b/components/cast_receiver/browser/streaming_runtime_application.cc
index 5e437b2..cc18fac5b 100644
--- a/components/cast_receiver/browser/streaming_runtime_application.cc
+++ b/components/cast_receiver/browser/streaming_runtime_application.cc
@@ -43,7 +43,7 @@
 
 void StreamingRuntimeApplication::OnStreamingSessionStarted() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  OnPageLoaded();
+  OnPageNavigationComplete();
 }
 
 void StreamingRuntimeApplication::OnError() {
@@ -86,7 +86,7 @@
   receiver_session_client_->LaunchStreamingReceiverAsync();
 
   // Application is initialized now - we can load the URL.
-  LoadPage(GURL(base::StringPrintf(
+  NavigateToPage(GURL(base::StringPrintf(
       kStreamingPageUrlTemplate,
       cast_streaming::GetCastStreamingMediaSourceUrl().spec().c_str())));
 
diff --git a/components/cast_receiver/browser/web_runtime_application.cc b/components/cast_receiver/browser/web_runtime_application.cc
index 3fba9a6..1ee9cf3 100644
--- a/components/cast_receiver/browser/web_runtime_application.cc
+++ b/components/cast_receiver/browser/web_runtime_application.cc
@@ -127,12 +127,12 @@
       embedder_application().GetWebContents());
 
   // Application is initialized now - we can load the URL.
-  LoadPage(app_url());
+  NavigateToPage(app_url());
 }
 
 void WebRuntimeApplication::OnPageLoadComplete() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  OnPageLoaded();
+  OnPageNavigationComplete();
 }
 
 void WebRuntimeApplication::OnError() {
diff --git a/components/exo/wayland/zaura_shell.cc b/components/exo/wayland/zaura_shell.cc
index 1007657..e6e970a 100644
--- a/components/exo/wayland/zaura_shell.cc
+++ b/components/exo/wayland/zaura_shell.cc
@@ -1082,6 +1082,7 @@
     1352584,
     1358908,
     1400226,
+    1402158,
 };
 
 // Implements aura shell interface and monitors workspace state needed
diff --git a/components/language/content/browser/geo_language_provider.cc b/components/language/content/browser/geo_language_provider.cc
index 9bb3678..40f83b9 100644
--- a/components/language/content/browser/geo_language_provider.cc
+++ b/components/language/content/browser/geo_language_provider.cc
@@ -218,11 +218,11 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(creation_sequence_checker_);
   languages_ = languages;
 
-  base::ListValue cache_list;
+  base::Value::List cache_list;
   for (const std::string& language : languages_) {
     cache_list.Append(language);
   }
-  prefs_->Set(kCachedGeoLanguagesPref, cache_list);
+  prefs_->SetList(kCachedGeoLanguagesPref, std::move(cache_list));
   prefs_->SetDouble(kTimeOfLastGeoLanguagesUpdatePref,
                     base::Time::Now().ToDoubleT());
 }
diff --git a/components/language/content/browser/geo_language_provider_unittest.cc b/components/language/content/browser/geo_language_provider_unittest.cc
index 154d0d5..5e2c785 100644
--- a/components/language/content/browser/geo_language_provider_unittest.cc
+++ b/components/language/content/browser/geo_language_provider_unittest.cc
@@ -65,11 +65,12 @@
 
   void SetUpCachedLanguages(const std::vector<std::string>& languages,
                             const double update_time) {
-    base::ListValue cache_list;
+    base::Value::List cache_list;
     for (const std::string& language : languages) {
       cache_list.Append(language);
     }
-    local_state_.Set(GeoLanguageProvider::kCachedGeoLanguagesPref, cache_list);
+    local_state_.SetList(GeoLanguageProvider::kCachedGeoLanguagesPref,
+                         std::move(cache_list));
     local_state_.SetDouble(
         GeoLanguageProvider::kTimeOfLastGeoLanguagesUpdatePref, update_time);
   }
diff --git a/components/lens/lens_entrypoints.h b/components/lens/lens_entrypoints.h
index a677f7f..88591a9 100644
--- a/components/lens/lens_entrypoints.h
+++ b/components/lens/lens_entrypoints.h
@@ -13,7 +13,6 @@
   CHROME_REGION_SEARCH_MENU_ITEM,
   CHROME_SEARCH_WITH_GOOGLE_LENS_CONTEXT_MENU_ITEM,
   CHROME_FULLSCREEN_SEARCH_MENU_ITEM,
-  CHROME_SCREENSHOT_SEARCH,
   UNKNOWN
 };
 
diff --git a/components/lens/lens_features.cc b/components/lens/lens_features.cc
index 94dc116..9e01cb7 100644
--- a/components/lens/lens_features.cc
+++ b/components/lens/lens_features.cc
@@ -22,10 +22,6 @@
              "LensSearchOptimizations",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
-BASE_FEATURE(kLensSearchImageInScreenshotSharing,
-             "LensSearchImageInScreenshotSharing",
-             base::FEATURE_DISABLED_BY_DEFAULT);
-
 BASE_FEATURE(kEnableLatencyLogging,
              "LensImageLatencyLogging",
              base::FEATURE_ENABLED_BY_DEFAULT);
@@ -70,13 +66,6 @@
 constexpr base::FeatureParam<int> kMaxPixelsForImageSearch{
     &kLensImageCompression, "dimensions-max-pixels", 1000};
 
-const base::FeatureParam<bool> kUseSidePanelForScreenshotSharing{
-    &kLensSearchImageInScreenshotSharing,
-    "use-side-panel-for-screenshot-sharing", false};
-
-const base::FeatureParam<bool> kEnablePersistentBubble{
-    &kLensSearchImageInScreenshotSharing, "enable-persistent-bubble", false};
-
 const base::FeatureParam<bool> kEnableLensFullscreenSearch{
     &kLensSearchOptimizations, "enable-lens-fullscreen-search", true};
 
@@ -147,23 +136,6 @@
   return IsLensSidePanelEnabled() && !IsLensFullscreenSearchEnabled();
 }
 
-bool IsLensInScreenshotSharingEnabled() {
-  return base::FeatureList::IsEnabled(kLensStandalone) &&
-         base::FeatureList::IsEnabled(kLensSearchImageInScreenshotSharing);
-}
-
-// Does not check if kLensSearchImageInScreenshotSharing is enabled because this
-// method is not called if kLensSearchImageInScreenshotSharing is false
-bool UseSidePanelForScreenshotSharing() {
-  return kUseSidePanelForScreenshotSharing.Get();
-}
-
-// Does not check if kLensSearchImageInScreenshotSharing is enabled because this
-// method is not called if kLensSearchImageInScreenshotSharing is false
-bool EnablePersistentBubble() {
-  return kEnablePersistentBubble.Get();
-}
-
 bool IsLensRegionSearchStaticPageEnabled() {
   return base::FeatureList::IsEnabled(kLensRegionSearchStaticPage);
 }
diff --git a/components/lens/lens_features.h b/components/lens/lens_features.h
index 43a0bc1..df6c086 100644
--- a/components/lens/lens_features.h
+++ b/components/lens/lens_features.h
@@ -27,11 +27,6 @@
 COMPONENT_EXPORT(LENS_FEATURES)
 BASE_DECLARE_FEATURE(kLensSearchOptimizations);
 
-// Enables Lens integration into the Chrome screenshot sharing feature by adding
-// a "Search Image" button.
-COMPONENT_EXPORT(LENS_FEATURES)
-BASE_DECLARE_FEATURE(kLensSearchImageInScreenshotSharing);
-
 // Enables Latency logging for the LensStandalone feature.
 COMPONENT_EXPORT(LENS_FEATURES)
 BASE_DECLARE_FEATURE(kEnableLatencyLogging);
@@ -40,11 +35,6 @@
 COMPONENT_EXPORT(LENS_FEATURES)
 BASE_DECLARE_FEATURE(kEnableRegionSearchKeyboardShortcut);
 
-// Enables the modification of the instruction chip UI that is presented when
-// region search is opened.
-COMPONENT_EXPORT(LENS_FEATURES)
-BASE_DECLARE_FEATURE(kLensInstructionChipImprovements);
-
 // Enables the image search side panel experience for third party default search
 // engines
 COMPONENT_EXPORT(LENS_FEATURES)
@@ -58,23 +48,6 @@
 COMPONENT_EXPORT(LENS_FEATURES)
 BASE_DECLARE_FEATURE(kLensImageFormatOptimizations);
 
-// Enables using `Google` as the visual search provider instead of `Google
-// Lens`.
-COMPONENT_EXPORT(LENS_FEATURES)
-extern const base::FeatureParam<bool> kUseGoogleAsVisualSearchProvider;
-
-// Enables alternate option 1 for the Region Search context menu item text.
-COMPONENT_EXPORT(LENS_FEATURES)
-extern const base::FeatureParam<bool> kRegionSearchUseMenuItemAltText1;
-
-// Enables alternate option 2 for the Region Search context menu item text.
-COMPONENT_EXPORT(LENS_FEATURES)
-extern const base::FeatureParam<bool> kRegionSearchUseMenuItemAltText2;
-
-// Enables alternate option 3 for the Region Search context menu item text.
-COMPONENT_EXPORT(LENS_FEATURES)
-extern const base::FeatureParam<bool> kRegionSearchUseMenuItemAltText3;
-
 // Enables UKM logging for the Lens Region Search feature.
 COMPONENT_EXPORT(LENS_FEATURES)
 extern const base::FeatureParam<bool> kEnableUKMLoggingForRegionSearch;
@@ -99,25 +72,6 @@
 COMPONENT_EXPORT(LENS_FEATURES)
 extern const base::FeatureParam<bool> kEnableFullscreenSearch;
 
-// Enables using side panel in the Chrome Screenshot sharing feature integration
-// instead of a new tab.
-COMPONENT_EXPORT(LENS_FEATURES)
-extern const base::FeatureParam<bool> kUseSidePanelForScreenshotSharing;
-
-// Forces the Chrome Screenshot sharing dialog bubble to stay open after the
-// user clicks the Search Image button.
-COMPONENT_EXPORT(LENS_FEATURES)
-extern const base::FeatureParam<bool> kEnablePersistentBubble;
-
-// Enables the use of the selection with image icon when using the instruction
-// chip improvements feature.
-COMPONENT_EXPORT(LENS_FEATURES)
-extern const base::FeatureParam<bool> kUseSelectionIconWithImage;
-
-// Enables the use of an alternative string for the instruction chip.
-COMPONENT_EXPORT(LENS_FEATURES)
-extern const base::FeatureParam<bool> kUseAltChipString;
-
 // Enables encoding to WebP for image search queries.
 COMPONENT_EXPORT(LENS_FEATURES)
 extern const base::FeatureParam<bool> kUseWebpInImageSearch;
@@ -183,27 +137,6 @@
 // Returns whether Lens fullscreen search is enabled.
 COMPONENT_EXPORT(LENS_FEATURES)
 extern bool IsLensFullscreenSearchEnabled();
-
-// Returns whether to use alternative option 1 for the Region Search context
-// menu item text.
-COMPONENT_EXPORT(LENS_FEATURES)
-extern bool UseRegionSearchMenuItemAltText1();
-
-// Returns whether to use alternative option 2 for the Region Search context
-// menu item text.
-COMPONENT_EXPORT(LENS_FEATURES)
-extern bool UseRegionSearchMenuItemAltText2();
-
-// Returns whether to use alternative option 3 for the Region Search context
-// menu item text.
-COMPONENT_EXPORT(LENS_FEATURES)
-extern bool UseRegionSearchMenuItemAltText3();
-
-// Returns whether to use `Google` as the visual search provider for all
-// relevant Lens context menu strings.
-COMPONENT_EXPORT(LENS_FEATURES)
-extern bool UseGoogleAsVisualSearchProvider();
-
 // Returns whether the Lens side panel is enabled for image search.
 COMPONENT_EXPORT(LENS_FEATURES)
 extern bool IsLensSidePanelEnabled();
@@ -217,29 +150,6 @@
 COMPONENT_EXPORT(LENS_FEATURES)
 extern bool IsLensInScreenshotSharingEnabled();
 
-// Returns whether the instruction chip improvement feature is enabled.
-COMPONENT_EXPORT(LENS_FEATURES)
-extern bool IsLensInstructionChipImprovementsEnabled();
-
-// Returns whether to use the Chrome Side Panel for the Lens integration in
-// Chrome Screenshot Sharing feature
-COMPONENT_EXPORT(LENS_FEATURES)
-extern bool UseSidePanelForScreenshotSharing();
-
-// Returns whether the Chrome Screenshot Sharing Bubble disappears after the
-// user clicks the Search Image button
-COMPONENT_EXPORT(LENS_FEATURES)
-extern bool EnablePersistentBubble();
-
-// Returns if we should use the selection with image icon instead of the default
-// when using the instruction chip improvements feature.
-COMPONENT_EXPORT(LENS_FEATURES)
-extern bool UseSelectionIconWithImage();
-
-// Returns whether we should use an alternative instruction chip string.
-COMPONENT_EXPORT(LENS_FEATURES)
-extern bool UseAltChipString();
-
 // Returns whether we should use a WebUI static page for region search.
 COMPONENT_EXPORT(LENS_FEATURES)
 extern bool IsLensRegionSearchStaticPageEnabled();
diff --git a/components/lens/lens_url_utils.cc b/components/lens/lens_url_utils.cc
index 5afd38e..89513f1 100644
--- a/components/lens/lens_url_utils.cc
+++ b/components/lens/lens_url_utils.cc
@@ -25,7 +25,6 @@
 constexpr char kChromeSearchWithGoogleLensContextMenuItem[] = "ccm";
 constexpr char kChromeOpenNewTabSidePanel[] = "cnts";
 constexpr char kChromeFullscreenSearchMenuItem[] = "cfs";
-constexpr char kChromeScreenshotSearch[] = "css";
 
 constexpr char kSurfaceQueryParameter[] = "s";
 constexpr char kStartTimeQueryParameter[] = "st";
@@ -68,10 +67,6 @@
       query_parameters.insert(
           {kEntryPointQueryParameter, kChromeFullscreenSearchMenuItem});
       break;
-    case lens::CHROME_SCREENSHOT_SEARCH:
-      query_parameters.insert(
-          {kEntryPointQueryParameter, kChromeScreenshotSearch});
-      break;
     default:
       // Empty strings are ignored when query parameters are built.
       break;
@@ -159,4 +154,4 @@
   return query_string;
 }
 
-}  // namespace lens
\ No newline at end of file
+}  // namespace lens
diff --git a/components/lens/lens_url_utils_unittest.cc b/components/lens/lens_url_utils_unittest.cc
index ccf686e5..a66ad70 100644
--- a/components/lens/lens_url_utils_unittest.cc
+++ b/components/lens/lens_url_utils_unittest.cc
@@ -70,14 +70,6 @@
   EXPECT_THAT(query_param, MatchesRegex("ep=cfs&re=avsf&s=&st=\\d+"));
 }
 
-TEST(LensUrlUtilsTest, GetScreenshotSearchQueryParameterTest) {
-  lens::EntryPoint lens_ep = lens::EntryPoint::CHROME_SCREENSHOT_SEARCH;
-  std::string query_param = lens::GetQueryParametersForLensRequest(
-      lens_ep, /* is_side_panel_request= */ false,
-      /* is_full_screen_region_search_request= */ false);
-  EXPECT_THAT(query_param, MatchesRegex("ep=css&re=df&s=&st=\\d+"));
-}
-
 TEST(LensUrlUtilsTest, GetUnknownEntryPointTest) {
   std::string query_param = lens::GetQueryParametersForLensRequest(
       lens::EntryPoint::UNKNOWN, /* is_side_panel_request= */ false,
@@ -163,16 +155,6 @@
   EXPECT_THAT(url.query(), MatchesRegex("ep=cfs&re=avsf&s=&st=\\d+"));
 }
 
-TEST(LensUrlUtilsTest, AppendScreenshotSearchQueryParameterTest) {
-  lens::EntryPoint lens_ep = lens::EntryPoint::CHROME_SCREENSHOT_SEARCH;
-  lens::RenderingEnvironment re =
-      lens::RenderingEnvironment::ONELENS_DESKTOP_WEB_FULLSCREEN;
-  GURL original_url = GURL("https://lens.google.com/");
-  GURL url = lens::AppendOrReplaceQueryParametersForLensRequest(
-      original_url, lens_ep, re, /* is_side_panel_request= */ false);
-  EXPECT_THAT(url.query(), MatchesRegex("ep=css&re=df&s=&st=\\d+"));
-}
-
 TEST(LensUrlUtilsTest, AppendUnknownEntryPointTest) {
   lens::RenderingEnvironment re =
       lens::RenderingEnvironment::ONELENS_DESKTOP_WEB_FULLSCREEN;
diff --git a/components/omnibox/browser/autocomplete_grouper_sections.cc b/components/omnibox/browser/autocomplete_grouper_sections.cc
index a3b2ef26..62558802 100644
--- a/components/omnibox/browser/autocomplete_grouper_sections.cc
+++ b/components/omnibox/browser/autocomplete_grouper_sections.cc
@@ -4,6 +4,9 @@
 
 #include "components/omnibox/browser/autocomplete_grouper_sections.h"
 
+#include <algorithm>
+#include <iterator>
+#include <limits>
 #include <memory>
 
 #include "base/ranges/algorithm.h"
@@ -17,11 +20,12 @@
 
 // static
 ACMatches Section::GroupMatches(PSections sections, ACMatches matches) {
-  int last_relevance = -1;
+  int last_relevance = std::numeric_limits<int>::max();
   for (const auto& match : matches) {
     DCHECK(match.suggestion_group_id.has_value());
-    DCHECK(last_relevance == -1 || match.relevance <= last_relevance);
-    last_relevance = match.relevance;
+    DCHECK(match.relevance <= last_relevance);
+    if (!match.allowed_to_be_default_match)
+      last_relevance = match.relevance;
     for (const auto& section : sections) {
       if (section->Add(match))
         break;
@@ -79,21 +83,32 @@
   groups_.push_back(std::make_unique<MultiGroup>(
       7,
       MultiGroup::GroupLimitsAndCounts{{omnibox::GROUP_OTHER_NAVS, {7, 0}}}));
+  // The default values above are reasonable placeholders. Iterate `matches` to
+  // determine the actual limits.
 
+  // Determine if `matches` contains any searches.
   bool has_search = base::ranges::any_of(
       matches, [&](const auto& match) { return groups_[1]->CanAdd(match); });
 
+  // Determine if the default match will be a search.
   auto default_match = base::ranges::find_if(
       matches, [&](const auto& match) { return groups_[0]->CanAdd(match); });
   bool default_is_search =
       default_match != matches.end() && groups_[1]->CanAdd(*default_match);
 
+  // Find the 1st nav's index.
+  size_t first_nav_index = std::distance(
+      matches.begin(), base::ranges::find_if(matches, [&](const auto& match) {
+        return groups_[2]->CanAdd(match);
+      }));
+
+  // Show at most 8 suggestions if doing so includes navs; otherwise show 9 or
+  // 10, if doing so doesn't include navs.
+  limit_ = std::clamp<size_t>(first_nav_index, 8, 10);
+  groups_[1]->limit_ = limit_ - 1;
+  groups_[2]->limit_ = limit_ - 1;
+
+  // Show at least 1 search, either in the default group or the search group.
   if (has_search && !default_is_search)
     groups_[2]->limit_--;
 }
-
-GroupBase* DesktopNonZpsSection::CanAdd(const AutocompleteMatch& match) {
-  if (groups_[2]->CanAdd(match))
-    limit_ = 8;
-  return Section::CanAdd(match);
-}
diff --git a/components/omnibox/browser/autocomplete_grouper_sections.h b/components/omnibox/browser/autocomplete_grouper_sections.h
index 6ac0c69..12edf5d 100644
--- a/components/omnibox/browser/autocomplete_grouper_sections.h
+++ b/components/omnibox/browser/autocomplete_grouper_sections.h
@@ -60,7 +60,6 @@
 class DesktopNonZpsSection : public Section {
  public:
   explicit DesktopNonZpsSection(const ACMatches& matches);
-  GroupBase* CanAdd(const AutocompleteMatch& match) override;
 };
 
 #endif  // COMPONENTS_OMNIBOX_BROWSER_AUTOCOMPLETE_GROUPER_SECTIONS_H_
diff --git a/components/omnibox/browser/autocomplete_match.cc b/components/omnibox/browser/autocomplete_match.cc
index 8b46e8e..d0926c1 100644
--- a/components/omnibox/browser/autocomplete_match.cc
+++ b/components/omnibox/browser/autocomplete_match.cc
@@ -31,6 +31,7 @@
 #include "components/search_engines/template_url_service.h"
 #include "inline_autocompletion_util.h"
 #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
+#include "third_party/omnibox_proto/groups.pb.h"
 #include "ui/gfx/vector_icon_types.h"
 #include "url/third_party/mozilla/url_parse.h"
 
@@ -807,6 +808,31 @@
 }
 
 // static
+omnibox::GroupId AutocompleteMatch::GetDefaultGroupId(Type type) {
+  if (type == AutocompleteMatchType::TILE_NAVSUGGEST ||
+      type == AutocompleteMatchType::TILE_SUGGESTION) {
+    return omnibox::GROUP_MOBILE_MOST_VISITED;
+  }
+
+  if (type == AutocompleteMatchType::CLIPBOARD_URL ||
+      type == AutocompleteMatchType::CLIPBOARD_TEXT ||
+      type == AutocompleteMatchType::CLIPBOARD_IMAGE) {
+    return omnibox::GROUP_MOBILE_CLIPBOARD;
+  }
+
+  if (IsStarterPackType(type))
+    return omnibox::GROUP_STARTER_PACK;
+
+  if (IsSearchType(type))
+    return omnibox::GROUP_SEARCH;
+
+  if (type == AutocompleteMatchType::HISTORY_CLUSTER)
+    return omnibox::GROUP_HISTORY_CLUSTER;
+
+  return omnibox::GROUP_OTHER_NAVS;
+}
+
+// static
 TemplateURL* AutocompleteMatch::GetTemplateURLWithKeyword(
     TemplateURLService* template_url_service,
     const std::u16string& keyword,
diff --git a/components/omnibox/browser/autocomplete_match.h b/components/omnibox/browser/autocomplete_match.h
index cf5d63f..f86ef7f 100644
--- a/components/omnibox/browser/autocomplete_match.h
+++ b/components/omnibox/browser/autocomplete_match.h
@@ -354,6 +354,10 @@
   // clipboard or query tile.
   static bool ShouldBeSkippedForGroupBySearchVsUrl(Type type);
 
+  // Return a group ID based on type. Should only be used as a fill in for
+  // matches that don't already have a group ID set by providers.
+  static omnibox::GroupId GetDefaultGroupId(Type type);
+
   // A static version GetTemplateURL() that takes the match's keyword and
   // match's hostname as parameters.  In short, returns the TemplateURL
   // associated with |keyword| if it exists; otherwise returns the TemplateURL
diff --git a/components/omnibox/browser/autocomplete_result.cc b/components/omnibox/browser/autocomplete_result.cc
index 9c0074c..ed74171 100644
--- a/components/omnibox/browser/autocomplete_result.cc
+++ b/components/omnibox/browser/autocomplete_result.cc
@@ -27,6 +27,7 @@
 #include "components/history_clusters/core/config.h"
 #include "components/omnibox/browser/actions/omnibox_pedal.h"
 #include "components/omnibox/browser/actions/omnibox_pedal_provider.h"
+#include "components/omnibox/browser/autocomplete_grouper_sections.h"
 #include "components/omnibox/browser/autocomplete_input.h"
 #include "components/omnibox/browser/autocomplete_match_type.h"
 #include "components/omnibox/browser/autocomplete_provider.h"
@@ -355,82 +356,101 @@
       matches_[0].ComputeStrippedDestinationURL(input, template_url_service);
   }
 
-  // TODO(manukh): Limiting (history clusters, zero suggest, max URL
-  //  suggestions, max suggestions, and max keyword suggestions) should be done
-  //  by the grouping framework.
-  // Limit history cluster suggestions to 1. This has to be done before limiting
-  // URL matches below so that a to-be-removed history cluster suggestion
-  // doesn't waste a URL slot.
-  bool history_cluster_included = false;
-  base::EraseIf(matches_, [&](const auto& match) {
-    // If not a history cluster match, don't erase it.
-    if (match.type != AutocompleteMatch::Type::HISTORY_CLUSTER)
-      return false;
-    // If not the 1st history cluster match, do erase it.
-    if (history_cluster_included)
-      return true;
-    // If the 1st history cluster match, don't erase it.
-    history_cluster_included = true;
-    return false;
-  });
-
-  // Limit URL matches per OmniboxMaxURLMatches.
-  size_t max_url_count = 0;
+  // If `kGroupingFramework` is enabled and the current input & platform are
+  // supported, delegate to the framework.
   const bool is_zero_suggest = input.IsZeroSuggest();
-  if (OmniboxFieldTrial::IsMaxURLMatchesFeatureEnabled() &&
-      (max_url_count = OmniboxFieldTrial::GetMaxURLMatches()) != 0)
-    LimitNumberOfURLsShown(GetMaxMatches(is_zero_suggest), max_url_count,
-                           comparing_object);
-
-  // Limit total matches accounting for suggestions score <= 0, sub matches, and
-  // feature configs such as OmniboxUIExperimentMaxAutocompleteMatches,
-  // OmniboxMaxZeroSuggestMatches, and OmniboxDynamicMaxAutocomplete.
-  const size_t num_matches =
-      CalculateNumMatches(is_zero_suggest, matches_, comparing_object);
-
-  // Group and trim suggestions to the given limit.
-  if (!is_zero_suggest) {
-    // Until limits are applied by the grouping framework, typed suggestions are
-    // trimmed then grouped.
-    // TODO(manukh): Limiting should be done by the grouping framework.
-    matches_.resize(num_matches);
-
-    // Group search suggestions above URL suggestions.
-    if (matches_.size() > 2 &&
-        !base::FeatureList::IsEnabled(omnibox::kAdaptiveSuggestionsCount)) {
-      // TODO(manukh): Grouping search v URL (actually
-      //  `GroupSuggestionsBySearchVsURL` now groups by other types as well)
-      //  should be done by the grouping framework.
-      GroupSuggestionsBySearchVsURL(std::next(matches_.begin()),
-                                    matches_.end());
-    }
-    GroupAndDemoteMatchesInGroups();
-
-  } else if (base::FeatureList::IsEnabled(omnibox::kKeepSecondaryZeroSuggest)) {
-    // Until limits are applied by the grouping framework, zero-prefix
-    // suggestions are grouped then trimmed.
-    // TODO(manukh): Limiting should be done by the grouping framework.
-    GroupAndDemoteMatchesInGroups();
-    size_t num_primary_suggestions = 0;
-    base::EraseIf(matches_, [&](const auto& match) {
-      if (!match.suggestion_group_id.has_value() ||
-          GetSideTypeForSuggestionGroup(match.suggestion_group_id.value()) ==
-              omnibox::GroupConfig_SideType_DEFAULT_PRIMARY) {
-        // Trim the primary suggestions to the given limit.
-        return ++num_primary_suggestions > num_matches;
-      } else {
-        // Keep the secondary suggestions for the NTP realbox.
-        // TODO(ender): Add appropriate page classification for Android.
-        return page_classification != OmniboxEventProto::NTP_REALBOX;
+  if (base::FeatureList::IsEnabled(omnibox::kGroupingFramework) &&
+      !is_zero_suggest && !is_android && !is_ios) {
+    // Grouping requires all matches have a group ID. To keep providers 'dumb',
+    // they only assign IDs when their ID isn't obvious from the match type.
+    // Most matches will instead set IDs here to keep providers 'dumb' and the
+    // type->group mapping consistent between providers.
+    base::ranges::for_each(matches_, [&](auto& match) {
+      if (!match.suggestion_group_id.has_value()) {
+        match.suggestion_group_id =
+            AutocompleteMatch::GetDefaultGroupId(match.type);
       }
+      DCHECK(match.suggestion_group_id.has_value());
     });
 
+    // Some providers give 0 relevance matches that are meant for deduping only
+    // but shouldn't be shown otherwise. Filter them out.
+    base::EraseIf(matches_,
+                  [&](const auto& match) { return match.relevance == 0; });
+
+    // If there's only 1 match, then grouping is a no-op.
+    if (matches_.size() > 2) {
+      PSections sections;
+      sections.push_back(std::make_unique<DesktopNonZpsSection>(matches_));
+      matches_ = Section::GroupMatches(std::move(sections), matches_);
+    }
+
   } else {
-    // Until limits are applied by the grouping framework, zero-prefix
-    // suggestions are grouped then trimmed.
-    // TODO(manukh): Limiting should be done by the grouping framework.
-    GroupAndDemoteMatchesInGroups();
-    matches_.resize(num_matches);
+    // Limit history cluster suggestions to 1. This has to be done before
+    // limiting URL matches below so that a to-be-removed history cluster
+    // suggestion doesn't waste a URL slot.
+    bool history_cluster_included = false;
+    base::EraseIf(matches_, [&](const auto& match) {
+      // If not a history cluster match, don't erase it.
+      if (match.type != AutocompleteMatch::Type::HISTORY_CLUSTER)
+        return false;
+      // If not the 1st history cluster match, do erase it.
+      if (history_cluster_included)
+        return true;
+      // If the 1st history cluster match, don't erase it.
+      history_cluster_included = true;
+      return false;
+    });
+
+    // Limit URL matches per OmniboxMaxURLMatches.
+    size_t max_url_count = 0;
+    if (OmniboxFieldTrial::IsMaxURLMatchesFeatureEnabled() &&
+        (max_url_count = OmniboxFieldTrial::GetMaxURLMatches()) != 0)
+      LimitNumberOfURLsShown(GetMaxMatches(is_zero_suggest), max_url_count,
+                             comparing_object);
+
+    // Limit total matches accounting for suggestions score <= 0, sub matches,
+    // and feature configs such as OmniboxUIExperimentMaxAutocompleteMatches,
+    // OmniboxMaxZeroSuggestMatches, and OmniboxDynamicMaxAutocomplete.
+    const size_t num_matches =
+        CalculateNumMatches(is_zero_suggest, matches_, comparing_object);
+
+    // Group and trim suggestions to the given limit.
+    if (!is_zero_suggest) {
+      // Typed suggestions are trimmed then grouped.
+      matches_.resize(num_matches);
+
+      // Group search suggestions above URL suggestions.
+      if (matches_.size() > 2 &&
+          !base::FeatureList::IsEnabled(omnibox::kAdaptiveSuggestionsCount)) {
+        GroupSuggestionsBySearchVsURL(std::next(matches_.begin()),
+                                      matches_.end());
+      }
+      GroupAndDemoteMatchesInGroups();
+
+    } else if (base::FeatureList::IsEnabled(
+                   omnibox::kKeepSecondaryZeroSuggest)) {
+      // Zero-prefix suggestions are grouped then trimmed.
+      GroupAndDemoteMatchesInGroups();
+      size_t num_primary_suggestions = 0;
+      base::EraseIf(matches_, [&](const auto& match) {
+        if (!match.suggestion_group_id.has_value() ||
+            GetSideTypeForSuggestionGroup(match.suggestion_group_id.value()) ==
+                omnibox::GroupConfig_SideType_DEFAULT_PRIMARY) {
+          // Trim the primary suggestions to the given limit.
+          return ++num_primary_suggestions > num_matches;
+        } else {
+          // Keep the secondary suggestions for the NTP realbox.
+          // TODO(ender): Add appropriate page classification for Android.
+          return page_classification != OmniboxEventProto::NTP_REALBOX;
+        }
+      });
+
+    } else {
+      // Zero-prefix suggestions are grouped then trimmed.
+      GroupAndDemoteMatchesInGroups();
+      matches_.resize(num_matches);
+    }
   }
 
 #if DCHECK_IS_ON()
diff --git a/components/omnibox/common/omnibox_features.cc b/components/omnibox/common/omnibox_features.cc
index ff997d2..ef33226 100644
--- a/components/omnibox/common/omnibox_features.cc
+++ b/components/omnibox/common/omnibox_features.cc
@@ -73,11 +73,10 @@
              "OmniboxDocumentProviderDedupingOptimization",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
-// Feature to tweak how the default suggestion is preserved. Feature params
-// control which tweaks specifically are enabled. Enabling this feature without
-// params is a no-op.
-BASE_FEATURE(kPreserveDefault,
-             "OmniboxPreserveDefault",
+// When enabled, uses the grouping framework (i.e.
+// autocomplete_grouper_sections.h) to limit and group (but not sort) matches.
+BASE_FEATURE(kGroupingFramework,
+             "OmniboxGroupingFramework",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
 // Demotes the relevance scores when comparing suggestions based on the
@@ -94,6 +93,20 @@
              "OmniboxRemoveExcessiveRecycledViewClearCalls",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
+// Feature to tweak how the default suggestion is preserved. Feature params
+// control which tweaks specifically are enabled. Enabling this feature without
+// params is a no-op.
+BASE_FEATURE(kPreserveDefault,
+             "OmniboxPreserveDefault",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
+// When disabled, when providers update their matches, the new set of matches
+// are sorted and culled, then merged with the old matches, then sorted and
+// culled again. When enabled, the first sort and cull is skipped.
+BASE_FEATURE(kSingleSortAndCullPass,
+             "OmniboxSingleSortAndCullPass",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 // Feature to enable memoizing URLs when replacing search terms in
 // `AutocompleteMatch::GURLToStrippedGURL()`.
 // TODO(manukh) Enabled by default on 10/20/22 m109. Clean up feature code
@@ -107,13 +120,6 @@
              "OmniboxUpdateResultDebounce",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
-// When disabled, when providers update their matches, the new set of matches
-// are sorted and culled, then merged with the old matches, then sorted and
-// culled again. When enabled, the first sort and cull is skipped.
-BASE_FEATURE(kSingleSortAndCullPass,
-             "OmniboxSingleSortAndCullPass",
-             base::FEATURE_DISABLED_BY_DEFAULT);
-
 // Feature used to cap max zero suggestions shown according to the param
 // OmniboxMaxZeroSuggestMatches. If omitted,
 // OmniboxUIExperimentMaxAutocompleteMatches will be used instead. If present,
diff --git a/components/omnibox/common/omnibox_features.h b/components/omnibox/common/omnibox_features.h
index efd9148..10350ef 100644
--- a/components/omnibox/common/omnibox_features.h
+++ b/components/omnibox/common/omnibox_features.h
@@ -21,12 +21,13 @@
 // `SortAndCull()`.
 BASE_DECLARE_FEATURE(kAutocompleteStability);
 BASE_DECLARE_FEATURE(kDocumentProviderDedupingOptimization);
+BASE_DECLARE_FEATURE(kGroupingFramework);
 BASE_DECLARE_FEATURE(kOmniboxDemoteByType);
 BASE_DECLARE_FEATURE(kOmniboxRemoveExcessiveRecycledViewClearCalls);
 BASE_DECLARE_FEATURE(kPreserveDefault);
+BASE_DECLARE_FEATURE(kSingleSortAndCullPass);
 BASE_DECLARE_FEATURE(kStrippedGurlOptimization);
 BASE_DECLARE_FEATURE(kUpdateResultDebounce);
-BASE_DECLARE_FEATURE(kSingleSortAndCullPass);
 
 // Features below this line should be sorted alphabetically by their comments.
 
diff --git a/components/page_load_metrics/browser/observers/ad_metrics/ads_page_load_metrics_observer.cc b/components/page_load_metrics/browser/observers/ad_metrics/ads_page_load_metrics_observer.cc
index a50b297..5c88d81c 100644
--- a/components/page_load_metrics/browser/observers/ad_metrics/ads_page_load_metrics_observer.cc
+++ b/components/page_load_metrics/browser/observers/ad_metrics/ads_page_load_metrics_observer.cc
@@ -79,6 +79,9 @@
 
 namespace {
 
+using RectId = PageAdDensityTracker::RectId;
+using RectType = PageAdDensityTracker::RectType;
+
 #define ADS_HISTOGRAM(suffix, hist_macro, visibility, value)        \
   switch (visibility) {                                             \
     case kNonVisible:                                               \
@@ -639,11 +642,19 @@
   FrameTreeData* ancestor_data = FindFrameData(frame_tree_node_id);
   if (ancestor_data &&
       frame_tree_node_id == ancestor_data->root_frame_tree_node_id()) {
-    page_ad_density_tracker_.RemoveRect(frame_tree_node_id);
+    RectId rect_id = RectId(RectType::kIFrame, frame_tree_node_id);
+
     // Only add frames if they are visible.
     if (!ancestor_data->is_display_none()) {
-      page_ad_density_tracker_.AddRect(frame_tree_node_id,
-                                       main_frame_intersection_rect);
+      page_ad_density_tracker_.RemoveRect(
+          rect_id,
+          /*recalculate_viewport_density=*/false);
+      page_ad_density_tracker_.AddRect(rect_id, main_frame_intersection_rect,
+                                       /*recalculate_density=*/true);
+    } else {
+      page_ad_density_tracker_.RemoveRect(
+          rect_id,
+          /*recalculate_viewport_density=*/true);
     }
   }
 
@@ -656,6 +667,12 @@
       main_frame_viewport_rect);
 }
 
+void AdsPageLoadMetricsObserver::OnMainFrameImageAdRectsChanged(
+    const base::flat_map<int, gfx::Rect>& main_frame_image_ad_rects) {
+  page_ad_density_tracker_.UpdateMainFrameImageAdRects(
+      main_frame_image_ad_rects);
+}
+
 // TODO(https://crbug.com/1142669): Evaluate imposing width requirements
 // for ad density violations.
 void AdsPageLoadMetricsObserver::CheckForAdDensityViolation() {
@@ -850,8 +867,8 @@
 
   auto* ukm_recorder = ukm::UkmRecorder::Get();
 
-  // AdPageLoadCustomSampling2 is recorded on all pages
-  ukm::builders::AdPageLoadCustomSampling2 custom_sampling_builder(source_id);
+  // AdPageLoadCustomSampling3 is recorded on all pages
+  ukm::builders::AdPageLoadCustomSampling3 custom_sampling_builder(source_id);
 
   page_ad_density_tracker_.Finalize();
 
@@ -1450,8 +1467,10 @@
   if (record_metrics)
     RecordPerFrameMetrics(*frame_data, GetDelegate().GetPageUkmSourceId());
 
-  if (update_density_tracker)
-    page_ad_density_tracker_.RemoveRect(id);
+  if (update_density_tracker) {
+    page_ad_density_tracker_.RemoveRect(RectId(RectType::kIFrame, id),
+                                        /*recalculate_viewport_density=*/true);
+  }
 }
 
 }  // namespace page_load_metrics
diff --git a/components/page_load_metrics/browser/observers/ad_metrics/ads_page_load_metrics_observer.h b/components/page_load_metrics/browser/observers/ad_metrics/ads_page_load_metrics_observer.h
index b194d64..3b59a045 100644
--- a/components/page_load_metrics/browser/observers/ad_metrics/ads_page_load_metrics_observer.h
+++ b/components/page_load_metrics/browser/observers/ad_metrics/ads_page_load_metrics_observer.h
@@ -137,6 +137,8 @@
       const gfx::Rect& main_frame_intersection_rect) override;
   void OnMainFrameViewportRectChanged(
       const gfx::Rect& main_frame_viewport_rect) override;
+  void OnMainFrameImageAdRectsChanged(
+      const base::flat_map<int, gfx::Rect>& main_frame_image_ad_rects) override;
   void OnSubFrameDeleted(int frame_tree_node_id) override;
   void OnV8MemoryChanged(
       const std::vector<MemoryUpdate>& memory_updates) override;
diff --git a/components/page_load_metrics/browser/observers/ad_metrics/ads_page_load_metrics_observer_unittest.cc b/components/page_load_metrics/browser/observers/ad_metrics/ads_page_load_metrics_observer_unittest.cc
index 1d37a29..27f5f9e 100644
--- a/components/page_load_metrics/browser/observers/ad_metrics/ads_page_load_metrics_observer_unittest.cc
+++ b/components/page_load_metrics/browser/observers/ad_metrics/ads_page_load_metrics_observer_unittest.cc
@@ -1712,24 +1712,24 @@
   NavigateFrame(kNonAdUrl, main_frame);
 
   auto entries = ukm_recorder.GetEntriesByName(
-      ukm::builders::AdPageLoadCustomSampling2::kEntryName);
+      ukm::builders::AdPageLoadCustomSampling3::kEntryName);
   EXPECT_EQ(1u, entries.size());
 
   ukm_recorder.ExpectEntryMetric(
       entries.front(),
-      ukm::builders::AdPageLoadCustomSampling2::kAverageViewportAdDensityName,
+      ukm::builders::AdPageLoadCustomSampling3::kAverageViewportAdDensityName,
       20);
   ukm_recorder.ExpectEntryMetric(
       entries.front(),
-      ukm::builders::AdPageLoadCustomSampling2::kVarianceViewportAdDensityName,
+      ukm::builders::AdPageLoadCustomSampling3::kVarianceViewportAdDensityName,
       /*ukm::GetExponentialBucketMin(300, 1.3)=*/248);
   ukm_recorder.ExpectEntryMetric(
       entries.front(),
-      ukm::builders::AdPageLoadCustomSampling2::kSkewnessViewportAdDensityName,
+      ukm::builders::AdPageLoadCustomSampling3::kSkewnessViewportAdDensityName,
       /*ukm::GetExponentialBucketMin(std::llround(1.1547), 1.3)=*/1);
   ukm_recorder.ExpectEntryMetric(
       entries.front(),
-      ukm::builders::AdPageLoadCustomSampling2::kKurtosisViewportAdDensityName,
+      ukm::builders::AdPageLoadCustomSampling3::kKurtosisViewportAdDensityName,
       /*-ukm::GetExponentialBucketMin(-std::llround(-0.666667), 1.3)=*/-1);
 }
 
@@ -1740,7 +1740,7 @@
 
   RenderFrameHost* main_frame = NavigateMainFrame(kNonAdUrl);
 
-  // No ad resource so that only AdPageLoadCustomSampling2 is recorded in the
+  // No ad resource so that only AdPageLoadCustomSampling3 is recorded in the
   // end.
 
   AdvancePageDuration(base::Seconds(1));
@@ -1748,23 +1748,23 @@
   NavigateFrame(kNonAdUrl, main_frame);
 
   auto entries = ukm_recorder.GetEntriesByName(
-      ukm::builders::AdPageLoadCustomSampling2::kEntryName);
+      ukm::builders::AdPageLoadCustomSampling3::kEntryName);
   EXPECT_EQ(1u, entries.size());
   ukm_recorder.ExpectEntryMetric(
       entries.front(),
-      ukm::builders::AdPageLoadCustomSampling2::kAverageViewportAdDensityName,
+      ukm::builders::AdPageLoadCustomSampling3::kAverageViewportAdDensityName,
       0);
   ukm_recorder.ExpectEntryMetric(
       entries.front(),
-      ukm::builders::AdPageLoadCustomSampling2::kVarianceViewportAdDensityName,
+      ukm::builders::AdPageLoadCustomSampling3::kVarianceViewportAdDensityName,
       0);
   ukm_recorder.ExpectEntryMetric(
       entries.front(),
-      ukm::builders::AdPageLoadCustomSampling2::kSkewnessViewportAdDensityName,
+      ukm::builders::AdPageLoadCustomSampling3::kSkewnessViewportAdDensityName,
       0);
   ukm_recorder.ExpectEntryMetric(
       entries.front(),
-      ukm::builders::AdPageLoadCustomSampling2::kKurtosisViewportAdDensityName,
+      ukm::builders::AdPageLoadCustomSampling3::kKurtosisViewportAdDensityName,
       -3);
 }
 
diff --git a/components/page_load_metrics/browser/observers/ad_metrics/page_ad_density_tracker.cc b/components/page_load_metrics/browser/observers/ad_metrics/page_ad_density_tracker.cc
index f7ac4e7..2ae612b 100644
--- a/components/page_load_metrics/browser/observers/ad_metrics/page_ad_density_tracker.cc
+++ b/components/page_load_metrics/browser/observers/ad_metrics/page_ad_density_tracker.cc
@@ -14,6 +14,8 @@
 
 namespace {
 
+using RectId = PageAdDensityTracker::RectId;
+
 int CalculateIntersectedLength(int start1, int end1, int start2, int end2) {
   DCHECK_LE(start1, end1);
   DCHECK_LE(start2, end2);
@@ -34,7 +36,7 @@
   // An event to process corresponding to the left or right point of each
   // line segment.
   struct SegmentEvent {
-    SegmentEvent(int segment_id, int pos, bool is_segment_start)
+    SegmentEvent(RectId segment_id, int pos, bool is_segment_start)
         : segment_id(segment_id),
           pos(pos),
           is_segment_start(is_segment_start) {}
@@ -44,7 +46,7 @@
     bool operator<(const SegmentEvent& rhs) const {
       if (pos == rhs.pos) {
         // We do not have 0-length segment.
-        DCHECK_NE(segment_id, rhs.segment_id);
+        DCHECK(segment_id != rhs.segment_id);
 
         return segment_id < rhs.segment_id;
       } else {
@@ -52,7 +54,7 @@
       }
     }
 
-    int segment_id;
+    RectId segment_id;
     int pos;
     bool is_segment_start;
   };
@@ -82,7 +84,7 @@
 
   // Add a line segment to the set of active line segments, the segment
   // corresponds to the bottom or top of a rect.
-  void AddSegment(int segment_id, int start, int end) {
+  void AddSegment(RectId segment_id, int start, int end) {
     DCHECK_LE(start, end);
 
     int clipped_start = std::max(bound_start_, start);
@@ -106,7 +108,7 @@
   }
 
   // Remove a segment from the set of active line segmnets.
-  void RemoveSegment(int segment_id) {
+  void RemoveSegment(RectId segment_id) {
     auto it = segment_event_iterators_.find(segment_id);
     if (it == segment_event_iterators_.end())
       return;
@@ -154,12 +156,17 @@
   std::set<SegmentEvent> active_segments_;
 
   // Map from the segment_id passed by user to the Segment struct.
-  std::unordered_map<int, SegmentEventSetIterators> segment_event_iterators_;
+  std::map<RectId, SegmentEventSetIterators> segment_event_iterators_;
 };
 
 }  // namespace
 
-PageAdDensityTracker::RectEvent::RectEvent(int id,
+PageAdDensityTracker::RectId::RectId(RectType rect_type, int id)
+    : rect_type(rect_type), id(id) {}
+
+PageAdDensityTracker::RectId::RectId(const RectId& other) = default;
+
+PageAdDensityTracker::RectEvent::RectEvent(RectId id,
                                            bool is_bottom,
                                            const gfx::Rect& rect)
     : rect_id(id), is_bottom(is_bottom), rect(rect) {}
@@ -199,7 +206,9 @@
   return last_viewport_ad_density_by_area_;
 }
 
-void PageAdDensityTracker::AddRect(int rect_id, const gfx::Rect& rect) {
+void PageAdDensityTracker::AddRect(RectId rect_id,
+                                   const gfx::Rect& rect,
+                                   bool recalculate_density) {
   // Check that we do not already have rect events for the rect.
   DCHECK(rect_events_iterators_.find(rect_id) == rect_events_iterators_.end());
 
@@ -220,15 +229,18 @@
   rect_events_iterators_.emplace(rect_id,
                                  RectEventSetIterators(top_it, bottom_it));
 
-  // TODO(https://crbug.com/1068586): Improve performance by adding additional
-  // throttling to only calculate when max density can decrease (frame deleted
-  // or moved).
-  CalculatePageAdDensity();
+  if (recalculate_density) {
+    // TODO(https://crbug.com/1068586): Improve performance by adding additional
+    // throttling to only calculate when max density can decrease (frame deleted
+    // or moved).
+    CalculatePageAdDensity();
 
-  CalculateViewportAdDensity();
+    CalculateViewportAdDensity();
+  }
 }
 
-void PageAdDensityTracker::RemoveRect(int rect_id) {
+void PageAdDensityTracker::RemoveRect(RectId rect_id,
+                                      bool recalculate_viewport_density) {
   auto it = rect_events_iterators_.find(rect_id);
 
   if (it == rect_events_iterators_.end())
@@ -238,6 +250,10 @@
   rect_events_.erase(set_its.top_it);
   rect_events_.erase(set_its.bottom_it);
   rect_events_iterators_.erase(it);
+
+  if (recalculate_viewport_density) {
+    CalculateViewportAdDensity();
+  }
 }
 
 void PageAdDensityTracker::UpdateMainFrameRect(const gfx::Rect& rect) {
@@ -256,6 +272,22 @@
   CalculateViewportAdDensity();
 }
 
+void PageAdDensityTracker::UpdateMainFrameImageAdRects(
+    const base::flat_map<int, gfx::Rect>& main_frame_image_ad_rects) {
+  for (auto const& [element_id, rect] : main_frame_image_ad_rects) {
+    RectId rect_id = RectId(RectType::kElement, element_id);
+
+    RemoveRect(rect_id, /*recalculate_viewport_density=*/false);
+
+    if (!rect.IsEmpty()) {
+      AddRect(rect_id, rect, /*recalculate_density=*/false);
+    }
+  }
+
+  CalculatePageAdDensity();
+  CalculateViewportAdDensity();
+}
+
 void PageAdDensityTracker::Finalize() {
   DCHECK(!finalize_called_);
 
@@ -410,6 +442,22 @@
   return result;
 }
 
+bool PageAdDensityTracker::RectId::operator<(const RectId& rhs) const {
+  if (rect_type == rhs.rect_type) {
+    return id < rhs.id;
+  }
+
+  return rect_type < rhs.rect_type;
+}
+
+bool PageAdDensityTracker::RectId::operator==(const RectId& rhs) const {
+  return rect_type == rhs.rect_type && id == rhs.id;
+}
+
+bool PageAdDensityTracker::RectId::operator!=(const RectId& rhs) const {
+  return !(*this == rhs);
+}
+
 bool PageAdDensityTracker::RectEvent::operator<(const RectEvent& rhs) const {
   int lhs_y = is_bottom ? rect.bottom() : rect.y();
   int rhs_y = rhs.is_bottom ? rhs.rect.bottom() : rhs.rect.y();
@@ -417,7 +465,7 @@
   // Tiebreak with |rect_id|.
   if (lhs_y == rhs_y) {
     // We do not have 0-length Rect.
-    DCHECK_NE(rect_id, rhs.rect_id);
+    DCHECK(rect_id != rhs.rect_id);
     return rect_id < rhs.rect_id;
   } else {
     return lhs_y > rhs_y;
diff --git a/components/page_load_metrics/browser/observers/ad_metrics/page_ad_density_tracker.h b/components/page_load_metrics/browser/observers/ad_metrics/page_ad_density_tracker.h
index d32e67a..945efd05 100644
--- a/components/page_load_metrics/browser/observers/ad_metrics/page_ad_density_tracker.h
+++ b/components/page_load_metrics/browser/observers/ad_metrics/page_ad_density_tracker.h
@@ -5,9 +5,10 @@
 #ifndef COMPONENTS_PAGE_LOAD_METRICS_BROWSER_OBSERVERS_AD_METRICS_PAGE_AD_DENSITY_TRACKER_H_
 #define COMPONENTS_PAGE_LOAD_METRICS_BROWSER_OBSERVERS_AD_METRICS_PAGE_AD_DENSITY_TRACKER_H_
 
+#include <map>
 #include <set>
-#include <unordered_map>
 
+#include <base/containers/flat_map.h>
 #include "base/memory/raw_ptr.h"
 #include "base/time/tick_clock.h"
 #include "base/time/time.h"
@@ -28,6 +29,24 @@
 //       current viewport ad density using CalculateViewportAdDensity.
 class PageAdDensityTracker {
  public:
+  enum class RectType { kIFrame, kElement };
+
+  struct RectId {
+    RectId(RectType rect_type, int id);
+    RectId(const RectId& other);
+
+    RectType rect_type;
+
+    // For iframe, the id comes from the frame tree node id. For other elements
+    // (e.g. main frame ad rectangles), the id comes from the node id from the
+    // renderer.
+    int id;
+
+    bool operator<(const RectId& rhs) const;
+    bool operator==(const RectId& rhs) const;
+    bool operator!=(const RectId& rhs) const;
+  };
+
   struct AdDensityCalculationResult {
     absl::optional<int> ad_density_by_height;
     absl::optional<int> ad_density_by_area;
@@ -39,12 +58,15 @@
   PageAdDensityTracker(const PageAdDensityTracker&) = delete;
   PageAdDensityTracker& operator=(const PageAdDensityTracker&) = delete;
 
-  // Operations to track sub frame rects in the page density calcluation.
-  void AddRect(int rect_id, const gfx::Rect& rect);
+  // Operations to track sub frame rects in the page density calcluation. If
+  // `recalculate_density` is true, the max page ad density and the viewport ad
+  // density will be recalculated in the end.
+  void AddRect(RectId rect_id, const gfx::Rect& rect, bool recalculate_density);
 
   // Removes a rect from the tracker if it is currently being tracked.
-  // Otherwise RemoveRect is a no op.
-  void RemoveRect(int rect_id);
+  // Otherwise RemoveRect is a no op. If `recalculate_viewport_density` is true,
+  // the viewport ad density will be recalculated in the end.
+  void RemoveRect(RectId rect_id, bool recalculate_viewport_density);
 
   // Operations to track the main frame dimensions. The main frame rect has to
   // be set to calculate the page ad density.
@@ -54,6 +76,10 @@
   // rect has to be set to calculate the viewport ad density.
   void UpdateMainFrameViewportRect(const gfx::Rect& rect);
 
+  // Operations to track the main frame ad rectangles' position and dimensions.
+  void UpdateMainFrameImageAdRects(
+      const base::flat_map<int, gfx::Rect>& main_frame_image_ad_rects);
+
   // Returns the density by height, as a value from 0-100. If the density
   // calculation fails (i.e. no main frame size), this returns -1. Percentage
   // density by height is calculated as the the combined height of ads divided
@@ -83,12 +109,12 @@
  private:
   // An event to process corresponding to the top or bottom of each rect.
   struct RectEvent {
-    RectEvent(int id, bool is_bottom, const gfx::Rect& rect);
+    RectEvent(RectId id, bool is_bottom, const gfx::Rect& rect);
     RectEvent(const RectEvent& other);
 
     // A unique identifier set when adding and removing rect events
     // corresponding to a single rect.
-    int rect_id;
+    RectId rect_id;
     bool is_bottom;
     gfx::Rect rect;
 
@@ -127,7 +153,7 @@
 
   // Map from rect_id to iterators of rect events in rect_events_. This allows
   // efficient removal according to rect_id.
-  std::unordered_map<int, RectEventSetIterators> rect_events_iterators_;
+  std::map<RectId, RectEventSetIterators> rect_events_iterators_;
 
   // Percentage of page ad density as a value from 0-100. These only have
   // a value of -1 when ad density has not yet been calculated successfully.
diff --git a/components/page_load_metrics/browser/observers/ad_metrics/page_ad_density_tracker_unittest.cc b/components/page_load_metrics/browser/observers/ad_metrics/page_ad_density_tracker_unittest.cc
index 7e73072..4376d35 100644
--- a/components/page_load_metrics/browser/observers/ad_metrics/page_ad_density_tracker_unittest.cc
+++ b/components/page_load_metrics/browser/observers/ad_metrics/page_ad_density_tracker_unittest.cc
@@ -11,12 +11,23 @@
 
 namespace page_load_metrics {
 
+namespace {
+
+using RectId = PageAdDensityTracker::RectId;
+using RectType = PageAdDensityTracker::RectType;
+
+const RectId kRectId1 = RectId(RectType::kIFrame, 1);
+const RectId kRectId2 = RectId(RectType::kElement, 1);
+const RectId kRectId3 = RectId(RectType::kElement, 2);
+
+}  // namespace
+
 // Only for test purpose.
 class PageAdDensityTrackerTestPeer {
  public:
   static bool RectExistsAndHasCorrectTopIterator(
       const PageAdDensityTracker& tracker,
-      int rect_id) {
+      RectId rect_id) {
     auto it = tracker.rect_events_iterators_.find(rect_id);
     // Rect not exists.
     if (it == tracker.rect_events_iterators_.end())
@@ -27,7 +38,7 @@
 
   static bool RectExistsAndHasCorrectBottomIterator(
       const PageAdDensityTracker& tracker,
-      int rect_id) {
+      RectId rect_id) {
     auto it = tracker.rect_events_iterators_.find(rect_id);
     // Rect not exists.
     if (it == tracker.rect_events_iterators_.end())
@@ -44,20 +55,23 @@
   EXPECT_EQ(tracker.MaxPageAdDensityByArea(), -1);
 
   tracker.UpdateMainFrameRect(gfx::Rect(0, 0, 100, 100));
-  tracker.AddRect(1 /* rect_id */, gfx::Rect(0, 0, 100, 10));
+  tracker.AddRect(kRectId1, gfx::Rect(0, 0, 100, 10),
+                  /*recalculate_density=*/true);
   EXPECT_EQ(tracker.MaxPageAdDensityByArea(), 10);
   EXPECT_EQ(tracker.MaxPageAdDensityByHeight(), 10);
 
-  tracker.AddRect(2 /* rect_id */, gfx::Rect(50, 0, 100, 20));
+  tracker.AddRect(kRectId2, gfx::Rect(50, 0, 100, 20),
+                  /*recalculate_density=*/true);
   EXPECT_EQ(tracker.MaxPageAdDensityByArea(), 15);
   EXPECT_EQ(tracker.MaxPageAdDensityByHeight(), 20);
 
-  tracker.AddRect(3 /* rect_id */, gfx::Rect(50, 50, 50, 50));
+  tracker.AddRect(kRectId3, gfx::Rect(50, 50, 50, 50),
+                  /*recalculate_density=*/true);
   EXPECT_EQ(tracker.MaxPageAdDensityByArea(), 40);
   EXPECT_EQ(tracker.MaxPageAdDensityByHeight(), 70);
 
   // Removing a rect should not change the maximum ad density.
-  tracker.RemoveRect(3 /* rect_id */);
+  tracker.RemoveRect(kRectId3, /*recalculate_viewport_density=*/false);
   EXPECT_EQ(tracker.MaxPageAdDensityByArea(), 40);
   EXPECT_EQ(tracker.MaxPageAdDensityByHeight(), 70);
 }
@@ -67,9 +81,10 @@
 TEST(PageAdDensityTrackerTest, RemoveRectTwice_SecondRemoveIgnored) {
   PageAdDensityTracker tracker;
 
-  tracker.AddRect(1 /* rect_id */, gfx::Rect(0, 0, 100, 10));
-  tracker.RemoveRect(1 /* rect_id */);
-  tracker.RemoveRect(1 /* rect_id */);
+  tracker.AddRect(kRectId1, gfx::Rect(0, 0, 100, 10),
+                  /*recalculate_density=*/true);
+  tracker.RemoveRect(kRectId1, /*recalculate_viewport_density=*/false);
+  tracker.RemoveRect(kRectId1, /*recalculate_viewport_density=*/false);
 }
 
 // Ensures that two rects with the same dimensions hash to different
@@ -79,12 +94,14 @@
 
   tracker.UpdateMainFrameRect(gfx::Rect(0, 0, 100, 100));
 
-  tracker.AddRect(1 /* rect_id */, gfx::Rect(0, 0, 100, 10));
-  tracker.AddRect(2 /* rect_id */, gfx::Rect(0, 0, 100, 10));
+  tracker.AddRect(kRectId1, gfx::Rect(0, 0, 100, 10),
+                  /*recalculate_density=*/true);
+  tracker.AddRect(kRectId2, gfx::Rect(0, 0, 100, 10),
+                  /*recalculate_density=*/true);
   EXPECT_EQ(tracker.MaxPageAdDensityByArea(), 10);
 
-  tracker.RemoveRect(1 /* rect_id */);
-  tracker.RemoveRect(2 /* rect_id */);
+  tracker.RemoveRect(kRectId1, /*recalculate_viewport_density=*/false);
+  tracker.RemoveRect(kRectId2, /*recalculate_viewport_density=*/false);
   EXPECT_EQ(tracker.MaxPageAdDensityByArea(), 10);
 }
 
@@ -92,13 +109,17 @@
 TEST(PageAdDensityTrackerTest, TwoRectsOverflowTotalAreaAndHeight) {
   PageAdDensityTracker tracker;
 
-  tracker.AddRect(1 /* rect_id */, gfx::Rect(std::numeric_limits<int>::min(), 0,
-                                             std::numeric_limits<int>::max(),
-                                             std::numeric_limits<int>::max()));
-  tracker.AddRect(2 /* rect_id */, gfx::Rect(std::numeric_limits<int>::min(),
-                                             std::numeric_limits<int>::max(),
-                                             std::numeric_limits<int>::max(),
-                                             std::numeric_limits<int>::max()));
+  tracker.AddRect(kRectId1,
+                  gfx::Rect(std::numeric_limits<int>::min(), 0,
+                            std::numeric_limits<int>::max(),
+                            std::numeric_limits<int>::max()),
+                  /*recalculate_density=*/true);
+  tracker.AddRect(kRectId2,
+                  gfx::Rect(std::numeric_limits<int>::min(),
+                            std::numeric_limits<int>::max(),
+                            std::numeric_limits<int>::max(),
+                            std::numeric_limits<int>::max()),
+                  /*recalculate_density=*/true);
 
   // Update main frame rect to force a calculation.
   tracker.UpdateMainFrameRect(gfx::Rect(0, 0, 100, 100));
@@ -112,9 +133,10 @@
 TEST(PageAdDensityTrackerTest, OverflowTotalAreaAndHeight) {
   PageAdDensityTracker tracker;
 
-  tracker.AddRect(1 /* rect_id */,
+  tracker.AddRect(kRectId1,
                   gfx::Rect(0, 0, std::numeric_limits<int>::max(),
-                            std::numeric_limits<int>::max()));
+                            std::numeric_limits<int>::max()),
+                  /*recalculate_density=*/true);
 
   // Update main frame rect to force a calculation.
   tracker.UpdateMainFrameRect(gfx::Rect(0, 0, std::numeric_limits<int>::max(),
@@ -132,7 +154,8 @@
 
   tracker.UpdateMainFrameRect(gfx::Rect(0, 0, 100, 100));
 
-  tracker.AddRect(1 /* rect_id */, gfx::Rect(-1, -1, 1, 1));
+  tracker.AddRect(kRectId1, gfx::Rect(-1, -1, 1, 1),
+                  /*recalculate_density=*/true);
 
   EXPECT_EQ(tracker.MaxPageAdDensityByArea(), 0);
   EXPECT_EQ(tracker.MaxPageAdDensityByHeight(), 0);
@@ -142,9 +165,10 @@
 TEST(PageAdDensityTrackerTest, ViewportAdDensity_OverflowViewportArea) {
   PageAdDensityTracker tracker;
 
-  tracker.AddRect(1 /* rect_id */,
+  tracker.AddRect(kRectId1,
                   gfx::Rect(0, 0, std::numeric_limits<int>::max(),
-                            std::numeric_limits<int>::max()));
+                            std::numeric_limits<int>::max()),
+                  /*recalculate_density=*/true);
 
   tracker.UpdateMainFrameViewportRect(gfx::Rect(
       0, 0, std::numeric_limits<int>::max(), std::numeric_limits<int>::max()));
@@ -157,7 +181,8 @@
   PageAdDensityTracker tracker;
 
   tracker.UpdateMainFrameViewportRect(gfx::Rect(0, 0, 100, 100));
-  tracker.AddRect(1 /* rect_id */, gfx::Rect(0, 0, 100, 100));
+  tracker.AddRect(kRectId1, gfx::Rect(0, 0, 100, 100),
+                  /*recalculate_density=*/true);
 
   EXPECT_EQ(tracker.ViewportAdDensityByArea(), 100);
 }
@@ -166,7 +191,8 @@
   PageAdDensityTracker tracker;
 
   tracker.UpdateMainFrameViewportRect(gfx::Rect(0, 0, 100, 100));
-  tracker.AddRect(1 /* rect_id */, gfx::Rect(0, 0, 50, 100));
+  tracker.AddRect(kRectId1, gfx::Rect(0, 0, 50, 100),
+                  /*recalculate_density=*/true);
 
   EXPECT_EQ(tracker.ViewportAdDensityByArea(), 50);
 }
@@ -175,7 +201,8 @@
   PageAdDensityTracker tracker;
 
   tracker.UpdateMainFrameViewportRect(gfx::Rect(0, 0, 100, 100));
-  tracker.AddRect(1 /* rect_id */, gfx::Rect(100, 0, 100, 100));
+  tracker.AddRect(kRectId1, gfx::Rect(100, 0, 100, 100),
+                  /*recalculate_density=*/true);
 
   EXPECT_EQ(tracker.ViewportAdDensityByArea(), 0);
 }
@@ -184,7 +211,8 @@
   PageAdDensityTracker tracker;
 
   tracker.UpdateMainFrameViewportRect(gfx::Rect(0, 0, 100, 100));
-  tracker.AddRect(1 /* rect_id */, gfx::Rect(50, 50, 100, 100));
+  tracker.AddRect(kRectId1, gfx::Rect(50, 50, 100, 100),
+                  /*recalculate_density=*/true);
 
   EXPECT_EQ(tracker.ViewportAdDensityByArea(), 25);
 }
@@ -193,8 +221,10 @@
   PageAdDensityTracker tracker;
 
   tracker.UpdateMainFrameViewportRect(gfx::Rect(0, 0, 100, 100));
-  tracker.AddRect(1 /* rect_id */, gfx::Rect(30, 70, 100, 100));
-  tracker.AddRect(2 /* rect_id */, gfx::Rect(70, 30, 100, 100));
+  tracker.AddRect(kRectId1, gfx::Rect(30, 70, 100, 100),
+                  /*recalculate_density=*/true);
+  tracker.AddRect(kRectId2, gfx::Rect(70, 30, 100, 100),
+                  /*recalculate_density=*/true);
 
   EXPECT_EQ(tracker.ViewportAdDensityByArea(),
             33);  // ((30 * 70 * 2) - 30 * 30) / 10000 * 100
@@ -207,7 +237,8 @@
   PageAdDensityTracker tracker;
 
   tracker.UpdateMainFrameViewportRect(gfx::Rect(0, 0, 100, 100));
-  tracker.AddRect(1 /* rect_id */, gfx::Rect(0, 0, 50, 50));
+  tracker.AddRect(kRectId1, gfx::Rect(0, 0, 50, 50),
+                  /*recalculate_density=*/true);
 
   tracker.Finalize();
   EXPECT_DOUBLE_EQ(tracker.GetAdDensityByAreaStats().mean, 0);
@@ -218,7 +249,8 @@
       base::test::TaskEnvironment::TimeSource::MOCK_TIME);
   PageAdDensityTracker tracker;
 
-  tracker.AddRect(1 /* rect_id */, gfx::Rect(0, 0, 50, 50));
+  tracker.AddRect(kRectId1, gfx::Rect(0, 0, 50, 50),
+                  /*recalculate_density=*/true);
   task_environment.FastForwardBy(base::Seconds(1));
 
   tracker.Finalize();
@@ -246,7 +278,8 @@
   task_environment.FastForwardBy(base::Seconds(1));
 
   tracker.UpdateMainFrameViewportRect(gfx::Rect(0, 0, 50, 50));
-  tracker.AddRect(1 /* rect_id */, gfx::Rect(0, 0, 50, 50));
+  tracker.AddRect(kRectId1, gfx::Rect(0, 0, 50, 50),
+                  /*recalculate_density=*/true);
 
   EXPECT_EQ(tracker.ViewportAdDensityByArea(), 100);
 
@@ -263,7 +296,8 @@
   task_environment.FastForwardBy(base::Seconds(1));
 
   tracker.UpdateMainFrameViewportRect(gfx::Rect(0, 0, 50, 50));
-  tracker.AddRect(1 /* rect_id */, gfx::Rect(0, 0, 50, 50));
+  tracker.AddRect(kRectId1, gfx::Rect(0, 0, 50, 50),
+                  /*recalculate_density=*/true);
 
   task_environment.FastForwardBy(base::Seconds(1));
 
@@ -280,7 +314,8 @@
   PageAdDensityTracker tracker;
 
   tracker.UpdateMainFrameViewportRect(gfx::Rect(0, 0, 50, 100));
-  tracker.AddRect(1 /* rect_id */, gfx::Rect(0, 0, 50, 50));
+  tracker.AddRect(kRectId1, gfx::Rect(0, 0, 50, 50),
+                  /*recalculate_density=*/true);
 
   task_environment.FastForwardBy(base::Seconds(1));
 
@@ -297,7 +332,8 @@
       base::test::TaskEnvironment::TimeSource::MOCK_TIME);
   PageAdDensityTracker tracker;
 
-  tracker.AddRect(1 /* rect_id */, gfx::Rect(0, 0, 50, 50));
+  tracker.AddRect(kRectId1, gfx::Rect(0, 0, 50, 50),
+                  /*recalculate_density=*/true);
   tracker.UpdateMainFrameViewportRect(gfx::Rect(0, 0, 50, 50));
 
   task_environment.FastForwardBy(base::Seconds(1));
@@ -314,13 +350,15 @@
       base::test::TaskEnvironment::TimeSource::MOCK_TIME);
   PageAdDensityTracker tracker;
 
-  tracker.AddRect(1 /* rect_id */, gfx::Rect(0, 0, 50, 50));
+  tracker.AddRect(kRectId1, gfx::Rect(0, 0, 50, 50),
+                  /*recalculate_density=*/true);
   tracker.UpdateMainFrameViewportRect(gfx::Rect(0, 0, 50, 100));
 
   task_environment.FastForwardBy(base::Seconds(1));
 
-  tracker.RemoveRect(1 /* rect_id */);
-  tracker.AddRect(1 /* rect_id */, gfx::Rect(0, 0, 50, 100));
+  tracker.RemoveRect(kRectId1, /*recalculate_viewport_density=*/false);
+  tracker.AddRect(kRectId1, gfx::Rect(0, 0, 50, 100),
+                  /*recalculate_density=*/true);
 
   task_environment.FastForwardBy(base::Seconds(1));
   tracker.Finalize();
@@ -328,12 +366,103 @@
 }
 
 TEST(PageAdDensityTrackerTest,
+     AverageViewportAdDensity_RectRemovedAndRecalculateDensity) {
+  base::test::SingleThreadTaskEnvironment task_environment(
+      base::test::TaskEnvironment::TimeSource::MOCK_TIME);
+  PageAdDensityTracker tracker;
+
+  tracker.AddRect(kRectId1, gfx::Rect(0, 0, 50, 50),
+                  /*recalculate_density=*/true);
+  tracker.UpdateMainFrameViewportRect(gfx::Rect(0, 0, 50, 100));
+
+  task_environment.FastForwardBy(base::Seconds(1));
+
+  tracker.RemoveRect(kRectId1, /*recalculate_viewport_density=*/true);
+
+  task_environment.FastForwardBy(base::Seconds(1));
+  tracker.Finalize();
+
+  EXPECT_DOUBLE_EQ(tracker.GetAdDensityByAreaStats().mean, 25);
+}
+
+TEST(PageAdDensityTrackerTest, AverageViewportAdDensity_ImageAdRects_Simple) {
+  base::test::SingleThreadTaskEnvironment task_environment(
+      base::test::TaskEnvironment::TimeSource::MOCK_TIME);
+  PageAdDensityTracker tracker;
+
+  tracker.UpdateMainFrameViewportRect(gfx::Rect(0, 0, 100, 100));
+
+  base::flat_map<int, gfx::Rect> rects;
+  rects.emplace(1, gfx::Rect(0, 0, 50, 50));
+  rects.emplace(2, gfx::Rect(0, 50, 100, 50));
+
+  tracker.UpdateMainFrameImageAdRects(rects);
+
+  task_environment.FastForwardBy(base::Seconds(1));
+  tracker.Finalize();
+
+  EXPECT_DOUBLE_EQ(tracker.GetAdDensityByAreaStats().mean, 75);
+}
+
+TEST(PageAdDensityTrackerTest, AverageViewportAdDensity_ImageAdRects_Removal) {
+  base::test::SingleThreadTaskEnvironment task_environment(
+      base::test::TaskEnvironment::TimeSource::MOCK_TIME);
+  PageAdDensityTracker tracker;
+
+  tracker.UpdateMainFrameViewportRect(gfx::Rect(0, 0, 100, 100));
+
+  {
+    base::flat_map<int, gfx::Rect> rects;
+    rects.emplace(1, gfx::Rect(0, 0, 50, 50));
+    rects.emplace(2, gfx::Rect(0, 50, 100, 50));
+
+    tracker.UpdateMainFrameImageAdRects(rects);
+  }
+  task_environment.FastForwardBy(base::Seconds(1));
+
+  {
+    base::flat_map<int, gfx::Rect> rects;
+    rects.emplace(2, gfx::Rect());
+
+    tracker.UpdateMainFrameImageAdRects(rects);
+  }
+  task_environment.FastForwardBy(base::Seconds(1));
+
+  tracker.Finalize();
+
+  EXPECT_DOUBLE_EQ(tracker.GetAdDensityByAreaStats().mean, 50);
+}
+
+TEST(PageAdDensityTrackerTest,
+     AverageViewportAdDensity_ImageAdRects_MixedWithIframeRects) {
+  base::test::SingleThreadTaskEnvironment task_environment(
+      base::test::TaskEnvironment::TimeSource::MOCK_TIME);
+  PageAdDensityTracker tracker;
+
+  tracker.UpdateMainFrameViewportRect(gfx::Rect(0, 0, 100, 100));
+
+  tracker.AddRect(RectId(RectType::kIFrame, /*id=*/1), gfx::Rect(0, 0, 50, 50),
+                  /*recalculate_density=*/true);
+  base::flat_map<int, gfx::Rect> rects;
+  rects.emplace(1, gfx::Rect(0, 50, 100, 50));
+
+  tracker.UpdateMainFrameImageAdRects(rects);
+
+  task_environment.FastForwardBy(base::Seconds(1));
+
+  tracker.Finalize();
+
+  EXPECT_DOUBLE_EQ(tracker.GetAdDensityByAreaStats().mean, 75);
+}
+
+TEST(PageAdDensityTrackerTest,
      AverageViewportAdDensity_MultipleUnequalTimePeriods) {
   base::test::SingleThreadTaskEnvironment task_environment(
       base::test::TaskEnvironment::TimeSource::MOCK_TIME);
   PageAdDensityTracker tracker;
 
-  tracker.AddRect(1 /* rect_id */, gfx::Rect(0, 0, 50, 50));
+  tracker.AddRect(kRectId1, gfx::Rect(0, 0, 50, 50),
+                  /*recalculate_density=*/true);
   tracker.UpdateMainFrameViewportRect(gfx::Rect(0, 0, 50, 100));
 
   task_environment.FastForwardBy(base::Seconds(1));
@@ -359,14 +488,17 @@
   tracker.UpdateMainFrameViewportRect(gfx::Rect(50, 0, 50, 100));
 
   // Rect(1) is not within the viewport.
-  tracker.AddRect(1 /* rect_id */, gfx::Rect(0, 0, 50, 50));
+  tracker.AddRect(kRectId1, gfx::Rect(0, 0, 50, 50),
+                  /*recalculate_density=*/true);
 
   // Rect(2) occupy 1/4 of the viewport.
-  tracker.AddRect(2 /* rect_id */, gfx::Rect(25, 0, 50, 50));
+  tracker.AddRect(kRectId2, gfx::Rect(25, 0, 50, 50),
+                  /*recalculate_density=*/true);
 
   // Rect(3) occupy 1/4 of the viewport; 1/8 of the viewport is occupied by both
   // Rect(2) and Rect(3)
-  tracker.AddRect(3 /* rect_id */, gfx::Rect(25, 25, 50, 50));
+  tracker.AddRect(kRectId3, gfx::Rect(25, 25, 50, 50),
+                  /*recalculate_density=*/true);
 
   task_environment.FastForwardBy(base::Seconds(1));
   tracker.Finalize();
@@ -375,15 +507,15 @@
 
 TEST(PageAdDensityTrackerTest, RectEvent_CheckTopAndBottomIterator) {
   PageAdDensityTracker tracker;
-  int rect_id = 1;
-  tracker.AddRect(rect_id, gfx::Rect(0, 0, 50, 10));
+  tracker.AddRect(kRectId1, gfx::Rect(0, 0, 50, 10),
+                  /*recalculate_density=*/true);
 
   EXPECT_TRUE(PageAdDensityTrackerTestPeer::RectExistsAndHasCorrectTopIterator(
-      tracker, rect_id));
+      tracker, kRectId1));
   EXPECT_TRUE(
       PageAdDensityTrackerTestPeer::RectExistsAndHasCorrectBottomIterator(
-          tracker, rect_id));
-  tracker.RemoveRect(rect_id);
+          tracker, kRectId1));
+  tracker.RemoveRect(kRectId1, /*recalculate_viewport_density=*/true);
 }
 
 }  // namespace page_load_metrics
diff --git a/components/page_load_metrics/browser/observers/assert_page_load_metrics_observer.h b/components/page_load_metrics/browser/observers/assert_page_load_metrics_observer.h
index f019932..f040619 100644
--- a/components/page_load_metrics/browser/observers/assert_page_load_metrics_observer.h
+++ b/components/page_load_metrics/browser/observers/assert_page_load_metrics_observer.h
@@ -170,6 +170,8 @@
       const gfx::Rect& main_frame_intersection_rect) override {}
   void OnMainFrameViewportRectChanged(
       const gfx::Rect& main_frame_viewport_rect) override {}
+  void OnMainFrameImageAdRectsChanged(const base::flat_map<int, gfx::Rect>&
+                                          main_frame_image_ad_rects) override {}
   void OnLoadedResource(const page_load_metrics::ExtraRequestCompleteInfo&
                             extra_request_complete_info) override {}
   void FrameReceivedUserActivation(
diff --git a/components/page_load_metrics/browser/page_load_metrics_forward_observer.cc b/components/page_load_metrics/browser/page_load_metrics_forward_observer.cc
index 341a47b..fd715f8 100644
--- a/components/page_load_metrics/browser/page_load_metrics_forward_observer.cc
+++ b/components/page_load_metrics/browser/page_load_metrics_forward_observer.cc
@@ -281,6 +281,14 @@
   parent_observer_->OnMainFrameViewportRectChanged(main_frame_viewport_rect);
 }
 
+void PageLoadMetricsForwardObserver::OnMainFrameImageAdRectsChanged(
+    const base::flat_map<int, gfx::Rect>& main_frame_image_ad_rects) {
+  if (!parent_observer_) {
+    return;
+  }
+  parent_observer_->OnMainFrameImageAdRectsChanged(main_frame_image_ad_rects);
+}
+
 // Don't need to forward FlushMetricsOnAppEnterBackground and OnComplete as they
 // are dispatched to all trackers.
 PageLoadMetricsObserverInterface::ObservePolicy
diff --git a/components/page_load_metrics/browser/page_load_metrics_forward_observer.h b/components/page_load_metrics/browser/page_load_metrics_forward_observer.h
index 36b45cf..b330026 100644
--- a/components/page_load_metrics/browser/page_load_metrics_forward_observer.h
+++ b/components/page_load_metrics/browser/page_load_metrics_forward_observer.h
@@ -121,6 +121,8 @@
       const gfx::Rect& main_frame_intersection_rect) override;
   void OnMainFrameViewportRectChanged(
       const gfx::Rect& main_frame_viewport_rect) override;
+  void OnMainFrameImageAdRectsChanged(
+      const base::flat_map<int, gfx::Rect>& main_frame_image_ad_rects) override;
   ObservePolicy FlushMetricsOnAppEnterBackground(
       const mojom::PageLoadTiming& timing) override;
   void OnComplete(const mojom::PageLoadTiming& timing) override;
diff --git a/components/page_load_metrics/browser/page_load_metrics_observer.h b/components/page_load_metrics/browser/page_load_metrics_observer.h
index 4e6925c1..595be88 100644
--- a/components/page_load_metrics/browser/page_load_metrics_observer.h
+++ b/components/page_load_metrics/browser/page_load_metrics_observer.h
@@ -200,6 +200,8 @@
       const gfx::Rect& main_frame_intersection_rect) override {}
   void OnMainFrameViewportRectChanged(
       const gfx::Rect& main_frame_viewport_rect) override {}
+  void OnMainFrameImageAdRectsChanged(const base::flat_map<int, gfx::Rect>&
+                                          main_frame_image_ad_rects) override {}
   ObservePolicy FlushMetricsOnAppEnterBackground(
       const mojom::PageLoadTiming& timing) override;
   void OnComplete(const mojom::PageLoadTiming& timing) override {}
diff --git a/components/page_load_metrics/browser/page_load_metrics_observer_interface.h b/components/page_load_metrics/browser/page_load_metrics_observer_interface.h
index ecebf43..b7532a3 100644
--- a/components/page_load_metrics/browser/page_load_metrics_observer_interface.h
+++ b/components/page_load_metrics/browser/page_load_metrics_observer_interface.h
@@ -462,6 +462,11 @@
   virtual void OnMainFrameViewportRectChanged(
       const gfx::Rect& main_frame_viewport_rect) = 0;
 
+  // Called when an image ad rectangle changed. An empty `image_ad_rect` is used
+  // to signal the removal of the rectangle. Only invoked on the main frame.
+  virtual void OnMainFrameImageAdRectsChanged(
+      const base::flat_map<int, gfx::Rect>& main_frame_image_ad_rects) = 0;
+
   // Invoked when the UMA metrics subsystem is persisting metrics as the
   // application goes into the background, on platforms where the browser
   // process may be killed after backgrounding (Android). Implementers should
diff --git a/components/page_load_metrics/browser/page_load_metrics_test_waiter.cc b/components/page_load_metrics/browser/page_load_metrics_test_waiter.cc
index 7fc1b31..7dd3935 100644
--- a/components/page_load_metrics/browser/page_load_metrics_test_waiter.cc
+++ b/components/page_load_metrics/browser/page_load_metrics_test_waiter.cc
@@ -82,6 +82,8 @@
       const gfx::Rect& main_frame_intersection_rect) override;
   void OnMainFrameViewportRectChanged(
       const gfx::Rect& main_frame_viewport_rect) override;
+  void OnMainFrameImageAdRectsChanged(
+      const base::flat_map<int, gfx::Rect>& main_frame_image_ad_rects) override;
   void OnV8MemoryChanged(
       const std::vector<MemoryUpdate>& memory_updates) override;
   void OnPageRenderDataUpdate(const mojom::FrameRenderDataUpdate& render_data,
@@ -181,6 +183,10 @@
   expected_.did_set_main_frame_intersection_ = true;
 }
 
+void PageLoadMetricsTestWaiter::SetMainFrameImageAdRectsExpectation() {
+  expected_.did_observed_main_frame_image_ad_rects_ = true;
+}
+
 void PageLoadMetricsTestWaiter::AddMainFrameViewportRectExpectation(
     const gfx::Rect& rect) {
   expected_.main_frame_viewport_rect_ = rect;
@@ -255,6 +261,17 @@
        static_cast<blink::UseCounterFeature::EnumValue>(feature)});
 }
 
+bool PageLoadMetricsTestWaiter::DidObserveMainFrameImageAdRect(
+    const gfx::Rect& rect) const {
+  for (auto& [id, observed_rect] : main_frame_image_ad_rects_) {
+    if (observed_rect == rect) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
 void PageLoadMetricsTestWaiter::Wait() {
   if (!ExpectationsSatisfied()) {
     run_loop_ = std::make_unique<base::RunLoop>();
@@ -389,6 +406,23 @@
     run_loop_->Quit();
 }
 
+void PageLoadMetricsTestWaiter::OnMainFrameImageAdRectsChanged(
+    const base::flat_map<int, gfx::Rect>& main_frame_image_ad_rects) {
+  if (main_frame_image_ad_rects.empty()) {
+    return;
+  }
+
+  observed_.did_observed_main_frame_image_ad_rects_ = true;
+
+  for (auto& [id, rect] : main_frame_image_ad_rects) {
+    main_frame_image_ad_rects_[id] = rect;
+  }
+
+  if (ExpectationsSatisfied() && run_loop_) {
+    run_loop_->Quit();
+  }
+}
+
 void PageLoadMetricsTestWaiter::OnDidFinishSubFrameNavigation(
     content::NavigationHandle* navigation_handle) {
   observed_.subframe_navigation_ = true;
@@ -605,6 +639,15 @@
              expected_.main_frame_viewport_rect_;
 }
 
+bool PageLoadMetricsTestWaiter::MainFrameImageAdRectsExpectationsSatisfied()
+    const {
+  if (!expected_.did_observed_main_frame_image_ad_rects_) {
+    return true;
+  }
+
+  return observed_.did_observed_main_frame_image_ad_rects_;
+}
+
 bool PageLoadMetricsTestWaiter::MemoryUpdateExpectationsSatisfied() const {
   return IsSubset(expected_.memory_update_frame_ids_,
                   observed_.memory_update_frame_ids_);
@@ -638,6 +681,7 @@
          CpuTimeExpectationsSatisfied() &&
          MainFrameIntersectionExpectationsSatisfied() &&
          MainFrameViewportRectExpectationsSatisfied() &&
+         MainFrameImageAdRectsExpectationsSatisfied() &&
          MemoryUpdateExpectationsSatisfied() &&
          TotalInputDelayExpectationsSatisfied() &&
          LayoutShiftExpectationsSatisfied() &&
@@ -758,6 +802,13 @@
   }
 }
 
+void WaiterMetricsObserver::OnMainFrameImageAdRectsChanged(
+    const base::flat_map<int, gfx::Rect>& main_frame_image_ad_rects) {
+  if (waiter_) {
+    waiter_->OnMainFrameImageAdRectsChanged(main_frame_image_ad_rects);
+  }
+}
+
 void WaiterMetricsObserver::OnDidFinishSubFrameNavigation(
     content::NavigationHandle* navigation_handle) {
   if (waiter_)
diff --git a/components/page_load_metrics/browser/page_load_metrics_test_waiter.h b/components/page_load_metrics/browser/page_load_metrics_test_waiter.h
index 458daac..a1d37419 100644
--- a/components/page_load_metrics/browser/page_load_metrics_test_waiter.h
+++ b/components/page_load_metrics/browser/page_load_metrics_test_waiter.h
@@ -81,6 +81,10 @@
   // TODO(skobes): Unify this API with AddMainFrameIntersectionExpectation.
   void SetMainFrameIntersectionExpectation();
 
+  // Indicates that we expect at least one notification for the
+  // main frame image ad rectangles update, with any rect allowed.
+  void SetMainFrameImageAdRectsExpectation();
+
   // Add a main frame viewport intersection expectation. Expects that the
   // mainframe receives its viewport rectangle in the main frame document's
   // coornidate. Subsequent calls overwrite unmet expectations.
@@ -126,6 +130,9 @@
   // Whether the given WebFeature was observed in the page.
   bool DidObserveWebFeature(blink::mojom::WebFeature feature) const;
 
+  // Whether the given image ad rect was observed in the page.
+  bool DidObserveMainFrameImageAdRect(const gfx::Rect& rect) const;
+
   // Waits for PageLoadMetrics events that match the fields set by the add
   // expectation methods. All matching fields must be set to end this wait.
   // All expectations are reset when the wait ends.
@@ -276,6 +283,9 @@
   void OnMainFrameViewportRectChanged(
       const gfx::Rect& main_frame_viewport_rect);
 
+  void OnMainFrameImageAdRectsChanged(
+      const base::flat_map<int, gfx::Rect>& main_frame_image_ad_rects);
+
   void OnDidFinishSubFrameNavigation(
       content::NavigationHandle* navigation_handle);
 
@@ -298,6 +308,7 @@
   bool SubframeDataExpectationsSatisfied() const;
   bool MainFrameIntersectionExpectationsSatisfied() const;
   bool MainFrameViewportRectExpectationsSatisfied() const;
+  bool MainFrameImageAdRectsExpectationsSatisfied() const;
   bool MemoryUpdateExpectationsSatisfied() const;
   bool TotalInputDelayExpectationsSatisfied() const;
   bool LayoutShiftExpectationsSatisfied() const;
@@ -321,6 +332,7 @@
     bool subframe_data_ = false;
     std::set<gfx::Size, FrameSizeComparator> frame_sizes_;
     bool did_set_main_frame_intersection_ = false;
+    bool did_observed_main_frame_image_ad_rects_ = false;
     std::vector<gfx::Rect> main_frame_intersections_;
     absl::optional<gfx::Rect> main_frame_viewport_rect_;
     std::unordered_set<content::GlobalRenderFrameHostId,
@@ -334,6 +346,10 @@
   int current_complete_resources_ = 0;
   int64_t current_network_bytes_ = 0;
 
+  // The last observed main frame image ad rectangle for each image id. This
+  // doesn't get reset in `ResetExpectations`.
+  base::flat_map<int, gfx::Rect> main_frame_image_ad_rects_;
+
   // Network body bytes are only counted for complete resources.
   int64_t current_network_body_bytes_ = 0;
   int expected_minimum_complete_resources_ = 0;
diff --git a/components/page_load_metrics/browser/page_load_metrics_update_dispatcher.cc b/components/page_load_metrics/browser/page_load_metrics_update_dispatcher.cc
index 29a59fb..312b902 100644
--- a/components/page_load_metrics/browser/page_load_metrics_update_dispatcher.cc
+++ b/components/page_load_metrics/browser/page_load_metrics_update_dispatcher.cc
@@ -752,6 +752,9 @@
     MaybeUpdateMainFrameIntersectionRect(render_frame_host,
                                          main_frame_metadata_);
     MaybeUpdateMainFrameViewportRect(main_frame_metadata_);
+
+    client_->OnMainFrameImageAdRectsChanged(
+        main_frame_metadata_->main_frame_image_ad_rects);
   }
 }
 
diff --git a/components/page_load_metrics/browser/page_load_metrics_update_dispatcher.h b/components/page_load_metrics/browser/page_load_metrics_update_dispatcher.h
index a2ef17d6..507c9fa5 100644
--- a/components/page_load_metrics/browser/page_load_metrics_update_dispatcher.h
+++ b/components/page_load_metrics/browser/page_load_metrics_update_dispatcher.h
@@ -149,6 +149,8 @@
         const gfx::Rect& main_frame_intersection_rect) = 0;
     virtual void OnMainFrameViewportRectChanged(
         const gfx::Rect& main_frame_viewport_rect) = 0;
+    virtual void OnMainFrameImageAdRectsChanged(
+        const base::flat_map<int, gfx::Rect>& main_frame_image_ad_rects) = 0;
     virtual void SetUpSharedMemoryForSmoothness(
         base::ReadOnlySharedMemoryRegion shared_memory) = 0;
   };
diff --git a/components/page_load_metrics/browser/page_load_tracker.cc b/components/page_load_metrics/browser/page_load_tracker.cc
index dda95f9..4243062 100644
--- a/components/page_load_metrics/browser/page_load_tracker.cc
+++ b/components/page_load_metrics/browser/page_load_tracker.cc
@@ -984,6 +984,13 @@
   }
 }
 
+void PageLoadTracker::OnMainFrameImageAdRectsChanged(
+    const base::flat_map<int, gfx::Rect>& main_frame_image_ad_rects) {
+  for (const auto& observer : observers_) {
+    observer->OnMainFrameImageAdRectsChanged(main_frame_image_ad_rects);
+  }
+}
+
 content::WebContents* PageLoadTracker::GetWebContents() const {
   return web_contents_;
 }
diff --git a/components/page_load_metrics/browser/page_load_tracker.h b/components/page_load_metrics/browser/page_load_tracker.h
index 65528a5..79ecae83 100644
--- a/components/page_load_metrics/browser/page_load_tracker.h
+++ b/components/page_load_metrics/browser/page_load_tracker.h
@@ -231,6 +231,8 @@
       const gfx::Rect& main_frame_intersection_rect) override;
   void OnMainFrameViewportRectChanged(
       const gfx::Rect& main_frame_viewport_rect) override;
+  void OnMainFrameImageAdRectsChanged(
+      const base::flat_map<int, gfx::Rect>& main_frame_image_ad_rects) override;
   void SetUpSharedMemoryForSmoothness(
       base::ReadOnlySharedMemoryRegion shared_memory) override;
 
diff --git a/components/page_load_metrics/common/page_load_metrics.mojom b/components/page_load_metrics/common/page_load_metrics.mojom
index 92ee6c0..8dbfa57 100644
--- a/components/page_load_metrics/common/page_load_metrics.mojom
+++ b/components/page_load_metrics/common/page_load_metrics.mojom
@@ -212,6 +212,12 @@
   // computed, and for any subsequent changes, and is null otherwise (i.e.
   // hasn't changed).
   gfx.mojom.Rect? main_frame_viewport_rect;
+
+  // The image ad rectangles within the main frame. Empty rectangles are used to
+  // signal the removal of the rectangle. The map keys are the element ids. This
+  // is only populated for the main frame, for the first time
+  // each rectangle is initially computed and for any subsequent changes.
+  map<int32, gfx.mojom.Rect> main_frame_image_ad_rects;
 };
 
 struct SubresourceLoadMetrics {
diff --git a/components/page_load_metrics/renderer/metrics_render_frame_observer.cc b/components/page_load_metrics/renderer/metrics_render_frame_observer.cc
index 5078d57b..3cfd00c 100644
--- a/components/page_load_metrics/renderer/metrics_render_frame_observer.cc
+++ b/components/page_load_metrics/renderer/metrics_render_frame_observer.cc
@@ -388,6 +388,15 @@
   }
 }
 
+void MetricsRenderFrameObserver::OnMainFrameImageAdRectangleChanged(
+    int element_id,
+    const gfx::Rect& image_ad_rect) {
+  if (page_timing_metrics_sender_) {
+    page_timing_metrics_sender_->OnMainFrameImageAdRectangleChanged(
+        element_id, image_ad_rect);
+  }
+}
+
 void MetricsRenderFrameObserver::OnFrameDetached() {
   WillDetach();
 }
diff --git a/components/page_load_metrics/renderer/metrics_render_frame_observer.h b/components/page_load_metrics/renderer/metrics_render_frame_observer.h
index 4f73dd4..1997b2b8 100644
--- a/components/page_load_metrics/renderer/metrics_render_frame_observer.h
+++ b/components/page_load_metrics/renderer/metrics_render_frame_observer.h
@@ -107,6 +107,9 @@
       const gfx::Rect& main_frame_intersection_rect) override;
   void OnMainFrameViewportRectangleChanged(
       const gfx::Rect& main_frame_viewport_rect) override;
+  void OnMainFrameImageAdRectangleChanged(
+      int element_id,
+      const gfx::Rect& image_ad_rect) override;
 
   // blink::WebLocalFrameObserver implementation
   void OnFrameDetached() override;
diff --git a/components/page_load_metrics/renderer/page_timing_metrics_sender.cc b/components/page_load_metrics/renderer/page_timing_metrics_sender.cc
index c5e680c..6f7ef33d 100644
--- a/components/page_load_metrics/renderer/page_timing_metrics_sender.cc
+++ b/components/page_load_metrics/renderer/page_timing_metrics_sender.cc
@@ -226,6 +226,13 @@
   EnsureSendTimer();
 }
 
+void PageTimingMetricsSender::OnMainFrameImageAdRectangleChanged(
+    int element_id,
+    const gfx::Rect& image_ad_rect) {
+  metadata_->main_frame_image_ad_rects[element_id] = image_ad_rect;
+  EnsureSendTimer();
+}
+
 void PageTimingMetricsSender::UpdateResourceMetadata(
     int resource_id,
     bool reported_as_ad_resource,
@@ -336,6 +343,7 @@
   new_features_.clear();
   metadata_->main_frame_intersection_rect.reset();
   metadata_->main_frame_viewport_rect.reset();
+  metadata_->main_frame_image_ad_rects.clear();
   last_cpu_timing_->task_time = base::TimeDelta();
   modified_resources_.clear();
   render_data_.new_layout_shifts.clear();
diff --git a/components/page_load_metrics/renderer/page_timing_metrics_sender.h b/components/page_load_metrics/renderer/page_timing_metrics_sender.h
index 5c60099..289e7a6 100644
--- a/components/page_load_metrics/renderer/page_timing_metrics_sender.h
+++ b/components/page_load_metrics/renderer/page_timing_metrics_sender.h
@@ -73,6 +73,8 @@
       const gfx::Rect& main_frame_intersection_rect);
   void OnMainFrameViewportRectangleChanged(
       const gfx::Rect& main_frame_viewport_rect);
+  void OnMainFrameImageAdRectangleChanged(int element_id,
+                                          const gfx::Rect& image_ad_rect);
 
   void DidObserveInputDelay(base::TimeDelta input_delay);
   void DidObserveUserInteraction(base::TimeDelta max_event_duration,
diff --git a/components/segmentation_platform/internal/execution/model_executor_impl.cc b/components/segmentation_platform/internal/execution/model_executor_impl.cc
index f341f91..5c93907 100644
--- a/components/segmentation_platform/internal/execution/model_executor_impl.cc
+++ b/components/segmentation_platform/internal/execution/model_executor_impl.cc
@@ -186,7 +186,7 @@
       clock_->Now() - state->model_execution_start_time);
   // TODO(ritikagup): Change the use of this according to MultiOutputModel.
   if (result.has_value()) {
-    VLOG(0) << "Segmentation model result: " << result.value().at(0)
+    VLOG(1) << "Segmentation model result: " << result.value().at(0)
             << " for segment "
             << proto::SegmentId_Name(state->segment_info.segment_id());
     const proto::SegmentationModelMetadata& model_metadata =
diff --git a/components/services/storage/shared_storage/async_shared_storage_database_impl_unittest.cc b/components/services/storage/shared_storage/async_shared_storage_database_impl_unittest.cc
index 2bc8a07..c4ec814 100644
--- a/components/services/storage/shared_storage/async_shared_storage_database_impl_unittest.cc
+++ b/components/services/storage/shared_storage/async_shared_storage_database_impl_unittest.cc
@@ -2025,12 +2025,10 @@
     origins.push_back(info->storage_key.origin());
   EXPECT_THAT(origins, ElementsAre(kOrigin2, kOrigin4));
 
-  // `kOrigin1` is still in `per_origin_mapping` even though it has no entries,
-  // as we didn't override its creation time to be old enough to have expired.
   origins.clear();
   for (const auto& info : infos6)
     origins.push_back(info->storage_key.origin());
-  EXPECT_THAT(origins, ElementsAre(kOrigin1, kOrigin2, kOrigin4));
+  EXPECT_THAT(origins, ElementsAre(kOrigin2, kOrigin4));
 
   // Database is still intact after trimming memory.
   EXPECT_EQ(OperationResult::kSuccess, result14);
diff --git a/components/services/storage/shared_storage/shared_storage_database.cc b/components/services/storage/shared_storage/shared_storage_database.cc
index 8b0f0211..5a88449 100644
--- a/components/services/storage/shared_storage/shared_storage_database.cc
+++ b/components/services/storage/shared_storage/shared_storage_database.cc
@@ -766,57 +766,43 @@
   if (!transaction.Begin())
     return OperationResult::kSqlError;
 
+  static constexpr char kUpdateLengthsSql[] =
+      "UPDATE per_origin_mapping SET length = length - counts.num_expired "
+      "FROM "
+      "    (SELECT context_origin, COUNT(context_origin) AS num_expired "
+      "    FROM values_mapping WHERE last_used_time<? "
+      "    GROUP BY context_origin) "
+      "AS counts "
+      "WHERE per_origin_mapping.context_origin = counts.context_origin";
+
+  sql::Statement update_statement(
+      db_.GetCachedStatement(SQL_FROM_HERE, kUpdateLengthsSql));
+  base::Time cutoff_time = clock_->Now() - staleness_threshold_;
+  update_statement.BindTime(0, cutoff_time);
+
+  if (!update_statement.Run()) {
+    return OperationResult::kSqlError;
+  }
+
   static constexpr char kDeleteEntriesSql[] =
       "DELETE FROM values_mapping WHERE last_used_time<?";
   sql::Statement entries_statement(
       db_.GetCachedStatement(SQL_FROM_HERE, kDeleteEntriesSql));
-  entries_statement.BindTime(0, clock_->Now() - staleness_threshold_);
+  entries_statement.BindTime(0, cutoff_time);
 
   // Delete expired entries.
   if (!entries_statement.Run())
     return OperationResult::kSqlError;
 
-  static constexpr char kSelectSql[] =
-      "SELECT context_origin,creation_time,length FROM per_origin_mapping "
-      "ORDER BY creation_time";
-  sql::Statement select_statement(
-      db_.GetCachedStatement(SQL_FROM_HERE, kSelectSql));
+  static constexpr char kDeleteOriginsSql[] =
+      "DELETE FROM per_origin_mapping WHERE length<=0";
+  sql::Statement origins_statement(
+      db_.GetCachedStatement(SQL_FROM_HERE, kDeleteOriginsSql));
 
-  // Update `per_origin_mapping` to agree with the correct post-purge `length`s.
-  // Also, for any origins that are now empty and whose `creation_time`s are
-  // prior to the lookback window, remove them from `per_origin_mapping`
-  // entirely.
-  while (select_statement.Step()) {
-    std::string origin = select_statement.ColumnString(0);
-    base::Time creation_time = select_statement.ColumnTime(1);
-    int64_t reported_length = select_statement.ColumnInt64(2);
-
-    // We want the count of all entries for this origin. There are unlikely to
-    // be any expired ones, since we just purged them, but one or more
-    // additional unpurged entries could have become expired in the meantime.
-    int64_t actual_length =
-        NumEntriesManualCount(origin, /*include_expired=*/true);
-
-    if (actual_length == -1)
-      return OperationResult::kSqlError;
-
-    if (actual_length == 0 &&
-        creation_time < clock_->Now() - staleness_threshold_) {
-      if (!DeleteFromPerOriginMapping(origin))
-        return OperationResult::kSqlError;
-      continue;
-    }
-
-    if (actual_length == reported_length) {
-      continue;
-    }
-
-    if (!UpdateLength(origin, actual_length - reported_length))
-      return OperationResult::kSqlError;
-  }
-
-  if (!select_statement.Succeeded())
+  // Delete empty origins.
+  if (!origins_statement.Run()) {
     return OperationResult::kSqlError;
+  }
 
   static constexpr char kDeleteWithdrawalsSql[] =
       "DELETE FROM budget_mapping WHERE time_stamp<?";
@@ -1416,19 +1402,14 @@
 }
 
 int64_t SharedStorageDatabase::NumEntriesManualCount(
-    const std::string& context_origin,
-    bool include_expired) {
-  const char* kCountSql =
-      (include_expired)
-          ? "SELECT COUNT(*) FROM values_mapping WHERE context_origin=?"
-          : "SELECT COUNT(*) FROM values_mapping "
-            "WHERE context_origin=? AND last_used_time>=?";
+    const std::string& context_origin) {
+  static constexpr char kCountSql[] =
+      "SELECT COUNT(*) FROM values_mapping "
+      "WHERE context_origin=? AND last_used_time>=?";
 
-  sql::Statement statement(db_.GetUniqueStatement(kCountSql));
+  sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, kCountSql));
   statement.BindString(0, context_origin);
-
-  if (!include_expired)
-    statement.BindTime(1, clock_->Now() - staleness_threshold_);
+  statement.BindTime(1, clock_->Now() - staleness_threshold_);
 
   int64_t length = 0;
   if (statement.Step())
diff --git a/components/services/storage/shared_storage/shared_storage_database.h b/components/services/storage/shared_storage/shared_storage_database.h
index cce3ae0b..f361b19 100644
--- a/components/services/storage/shared_storage/shared_storage_database.h
+++ b/components/services/storage/shared_storage/shared_storage_database.h
@@ -473,12 +473,8 @@
 
   // Returns the number of entries for `context_origin`, as determined by a
   // manual "COUNT(*)" query, rather than relying on the `length` recorded in
-  // `per_origin_mapping`. If `include_expired`, then the count is scoped to all
-  // entries; otherwise it is only scoped to entries within the lookback window
-  // determined by `staleness_threshold_`. Returns -1 if there is a database
-  // error.
-  [[nodiscard]] int64_t NumEntriesManualCount(const std::string& context_origin,
-                                              bool include_expired = false)
+  // `per_origin_mapping`. Returns -1 if there is a database error.
+  [[nodiscard]] int64_t NumEntriesManualCount(const std::string& context_origin)
       VALID_CONTEXT_REQUIRED(sequence_checker_);
 
   // Returns whether an entry exists for `context_origin` and `key`.
diff --git a/components/value_store/value_store_change.cc b/components/value_store/value_store_change.cc
index 086d7fbc..da85bac8 100644
--- a/components/value_store/value_store_change.cc
+++ b/components/value_store/value_store_change.cc
@@ -13,18 +13,18 @@
 namespace value_store {
 
 base::Value ValueStoreChange::ToValue(ValueStoreChangeList changes) {
-  base::Value changes_value(base::Value::Type::DICTIONARY);
+  base::Value::Dict changes_dict;
   for (auto& change : changes) {
-    base::Value change_value(base::Value::Type::DICTIONARY);
+    base::Value::Dict change_dict;
     if (change.old_value) {
-      change_value.SetKey("oldValue", std::move(*change.old_value));
+      change_dict.Set("oldValue", std::move(*change.old_value));
     }
     if (change.new_value) {
-      change_value.SetKey("newValue", std::move(*change.new_value));
+      change_dict.Set("newValue", std::move(*change.new_value));
     }
-    changes_value.SetKey(change.key, std::move(change_value));
+    changes_dict.Set(change.key, std::move(change_dict));
   }
-  return changes_value;
+  return base::Value(std::move(changes_dict));
 }
 
 ValueStoreChange::ValueStoreChange(const std::string& key,
diff --git a/components/value_store/value_store_change_unittest.cc b/components/value_store/value_store_change_unittest.cc
index 7195a3a..219b0c5 100644
--- a/components/value_store/value_store_change_unittest.cc
+++ b/components/value_store/value_store_change_unittest.cc
@@ -15,13 +15,13 @@
   changes.push_back(ValueStoreChange("foo", absl::nullopt, base::Value("bar")));
   changes.push_back(ValueStoreChange("baz", base::Value("qux"), absl::nullopt));
 
-  base::Value expected(base::Value::Type::DICTIONARY);
-  base::Value expected_foo(base::Value::Type::DICTIONARY);
-  base::Value expected_baz(base::Value::Type::DICTIONARY);
-  expected_foo.SetStringKey("newValue", "bar");
-  expected_baz.SetStringKey("oldValue", "qux");
-  expected.SetKey("foo", std::move(expected_foo));
-  expected.SetKey("baz", std::move(expected_baz));
+  base::Value::Dict expected;
+  base::Value::Dict expected_foo;
+  base::Value::Dict expected_baz;
+  expected_foo.Set("newValue", "bar");
+  expected_baz.Set("oldValue", "qux");
+  expected.Set("foo", std::move(expected_foo));
+  expected.Set("baz", std::move(expected_baz));
 
   EXPECT_EQ(expected, ValueStoreChange::ToValue(std::move(changes)));
 }
diff --git a/components/value_store/value_store_test_suite.cc b/components/value_store/value_store_test_suite.cc
index 164198f3..c4e23b3f 100644
--- a/components/value_store/value_store_test_suite.cc
+++ b/components/value_store/value_store_test_suite.cc
@@ -77,7 +77,7 @@
 TEST_P(ValueStoreTestSuite, NonexistentKeysReturnOk) {
   auto result = storage_->Get("key");
   ASSERT_TRUE(result.status().ok());
-  EXPECT_EQ(result.settings(), base::Value(base::Value::Type::DICTIONARY));
+  EXPECT_EQ(result.settings(), base::Value::Dict());
 }
 
 TEST_P(ValueStoreTestSuite, SetProducesMatchingChanges) {
diff --git a/content/BUILD.gn b/content/BUILD.gn
index c96243b..3a8b6b03 100644
--- a/content/BUILD.gn
+++ b/content/BUILD.gn
@@ -117,12 +117,8 @@
     "dev_ui_content_resources.pak",
   ]
   deps = [
-    "//components/attribution_reporting:mojom_js__generator",
     "//content/browser/aggregation_service:mojo_bindings_js__generator",
-    "//content/browser/attribution_reporting:internals_mojo_bindings_js__generator",
-    "//content/browser/attribution_reporting:mojo_bindings_js__generator",
     "//content/browser/resources/aggregation_service:build_ts",
-    "//content/browser/resources/attribution_reporting:build_ts",
     "//content/browser/resources/gpu:html_wrapper_files",
     "//content/browser/resources/process:build_ts",
     "//storage/browser/quota:mojo_bindings_js__generator",
diff --git a/content/browser/accessibility/accessibility_action_browsertest.cc b/content/browser/accessibility/accessibility_action_browsertest.cc
index 1855eac..149d49c 100644
--- a/content/browser/accessibility/accessibility_action_browsertest.cc
+++ b/content/browser/accessibility/accessibility_action_browsertest.cc
@@ -1239,7 +1239,7 @@
 
   ASSERT_EQ(1U, target->PlatformChildCount());
   BrowserAccessibility* popup_web_area = target->PlatformGetChild(0);
-  EXPECT_EQ(ax::mojom::Role::kRootWebArea, popup_web_area->GetRole());
+  EXPECT_EQ(ax::mojom::Role::kGroup, popup_web_area->GetRole());
 
   BrowserAccessibility* listbox = FindNode(ax::mojom::Role::kListBox, "");
   ASSERT_TRUE(listbox);
@@ -1274,7 +1274,7 @@
 
   ASSERT_EQ(1U, target->PlatformChildCount());
   BrowserAccessibility* popup_web_area = target->PlatformGetChild(0);
-  EXPECT_EQ(ax::mojom::Role::kRootWebArea, popup_web_area->GetRole());
+  EXPECT_EQ(ax::mojom::Role::kGroup, popup_web_area->GetRole());
 }
 #endif  // BUILDFLAG(OS_WIN) || BUILDFLAG(OS_CHROMEOS) || BUILDFLAG(USE_ATK)
 }  // namespace content
diff --git a/content/browser/accessibility/browser_accessibility_manager.cc b/content/browser/accessibility/browser_accessibility_manager.cc
index 4ddf7e4..448e573 100644
--- a/content/browser/accessibility/browser_accessibility_manager.cc
+++ b/content/browser/accessibility/browser_accessibility_manager.cc
@@ -327,7 +327,7 @@
   if (popup_root_ids_.size() == 1) {
     BrowserAccessibility* node = GetFromID(*popup_root_ids_.begin());
     if (node) {
-      DCHECK(node->GetRole() == ax::mojom::Role::kRootWebArea);
+      DCHECK(node->GetRole() == ax::mojom::Role::kGroup);
       return node;
     }
   }
@@ -1463,8 +1463,7 @@
 
   id_wrapper_map_[node->id()] = BrowserAccessibility::Create(this, node);
 
-  if (tree->root() != node &&
-      node->GetRole() == ax::mojom::Role::kRootWebArea) {
+  if (node->HasIntAttribute(ax::mojom::IntAttribute::kPopupForId)) {
     popup_root_ids_.insert(node->id());
   }
 }
@@ -1495,20 +1494,6 @@
   wrapper->SetNode(*node);
 }
 
-void BrowserAccessibilityManager::OnRoleChanged(ui::AXTree* tree,
-                                                ui::AXNode* node,
-                                                ax::mojom::Role old_role,
-                                                ax::mojom::Role new_role) {
-  DCHECK(node);
-  if (tree->root() == node)
-    return;
-  if (new_role == ax::mojom::Role::kRootWebArea) {
-    popup_root_ids_.insert(node->id());
-  } else if (old_role == ax::mojom::Role::kRootWebArea) {
-    popup_root_ids_.erase(node->id());
-  }
-}
-
 void BrowserAccessibilityManager::OnAtomicUpdateFinished(
     ui::AXTree* tree,
     bool root_changed,
diff --git a/content/browser/accessibility/browser_accessibility_manager.h b/content/browser/accessibility/browser_accessibility_manager.h
index e0061fc..45cb1a0 100644
--- a/content/browser/accessibility/browser_accessibility_manager.h
+++ b/content/browser/accessibility/browser_accessibility_manager.h
@@ -410,10 +410,6 @@
   void OnNodeCreated(ui::AXTree* tree, ui::AXNode* node) override;
   void OnNodeDeleted(ui::AXTree* tree, int32_t node_id) override;
   void OnNodeReparented(ui::AXTree* tree, ui::AXNode* node) override;
-  void OnRoleChanged(ui::AXTree* tree,
-                     ui::AXNode* node,
-                     ax::mojom::Role old_role,
-                     ax::mojom::Role new_role) override;
   void OnAtomicUpdateFinished(
       ui::AXTree* tree,
       bool root_changed,
diff --git a/content/browser/accessibility/browser_accessibility_manager_unittest.cc b/content/browser/accessibility/browser_accessibility_manager_unittest.cc
index b0c8691..60ef227 100644
--- a/content/browser/accessibility/browser_accessibility_manager_unittest.cc
+++ b/content/browser/accessibility/browser_accessibility_manager_unittest.cc
@@ -1525,8 +1525,9 @@
 
   ui::AXNodeData child_tree_root;
   child_tree_root.id = 3;
-  child_tree_root.role = ax::mojom::Role::kRootWebArea;
-  root.child_ids.push_back(3);
+  child_tree_root.role = ax::mojom::Role::kGroup;
+  child_tree_root.AddIntAttribute(ax::mojom::IntAttribute::kPopupForId, 2);
+  popup_button.child_ids.push_back(3);
 
   std::unique_ptr<BrowserAccessibilityManager> manager(
       BrowserAccessibilityManager::Create(
@@ -1536,37 +1537,15 @@
   ASSERT_NE(manager->GetPopupRoot(), nullptr);
   EXPECT_EQ(manager->GetPopupRoot()->GetId(), 3);
 
-  // Update tree to change the role of the nested child root, add new child root
-  // in same update.
-  child_tree_root.role = ax::mojom::Role::kGroup;
-  ui::AXNodeData second_child_tree_root;
-  second_child_tree_root.id = 4;
-  second_child_tree_root.role = ax::mojom::Role::kRootWebArea;
-  root.child_ids.push_back(4);
-
-  manager->Initialize(MakeAXTreeUpdateForTesting(root, child_tree_root,
-                                                 second_child_tree_root));
-
-  ASSERT_NE(manager->GetPopupRoot(), nullptr);
-  EXPECT_EQ(manager->GetPopupRoot()->GetId(), 4);
-
-  // Update tree to change the role of the nested child root, so that there is
-  // no longer any nested child root.
-  second_child_tree_root.role = ax::mojom::Role::kGroup;
-  manager->Initialize(MakeAXTreeUpdateForTesting(second_child_tree_root));
-  EXPECT_EQ(manager->GetPopupRoot(), nullptr);
-
   // Test deleting child root.
 
-  // First, ensure a child root exists.
-  second_child_tree_root.role = ax::mojom::Role::kRootWebArea;
-  manager->Initialize(MakeAXTreeUpdateForTesting(second_child_tree_root));
-  ASSERT_NE(manager->GetPopupRoot(), nullptr);
-  EXPECT_EQ(manager->GetPopupRoot()->GetId(), 4);
-
   // Now remove the child root from the tree.
-  root.child_ids = {2, 3};
-  manager->Initialize(MakeAXTreeUpdateForTesting(root));
+  popup_button.child_ids = {};
+  ui::AXTreeUpdate update = MakeAXTreeUpdateForTesting(popup_button);
+  AXEventNotificationDetails events;
+  events.updates = {update};
+  ASSERT_TRUE(manager->OnAccessibilityEvents(events));
+
   EXPECT_EQ(manager->GetPopupRoot(), nullptr);
 }
 
diff --git a/content/browser/accessibility/cross_platform_accessibility_browsertest.cc b/content/browser/accessibility/cross_platform_accessibility_browsertest.cc
index 98cca66..7d5aaca2 100644
--- a/content/browser/accessibility/cross_platform_accessibility_browsertest.cc
+++ b/content/browser/accessibility/cross_platform_accessibility_browsertest.cc
@@ -1588,7 +1588,7 @@
     const BrowserAccessibility* popup_area =
         manager->GetFromID(controls_ids[1]);
     ASSERT_NE(nullptr, popup_area);
-    EXPECT_EQ(ax::mojom::Role::kRootWebArea, popup_area->GetRole());
+    EXPECT_EQ(ax::mojom::Role::kGroup, popup_area->GetRole());
 
 #if !BUILDFLAG(IS_CASTOS) && !BUILDFLAG(IS_CAST_ANDROID)
     // Ensure that the bounding box of the popup area is at least 100
@@ -1653,7 +1653,7 @@
     const BrowserAccessibility* popup_area =
         manager->GetFromID(controls_ids[0]);
     ASSERT_NE(nullptr, popup_area);
-    EXPECT_EQ(ax::mojom::Role::kRootWebArea, popup_area->GetRole());
+    EXPECT_EQ(ax::mojom::Role::kGroup, popup_area->GetRole());
   }
 }
 
diff --git a/content/browser/attribution_reporting/attribution_internals_browsertest.cc b/content/browser/attribution_reporting/attribution_internals_browsertest.cc
index 3c9b50f1..2f7d6d7 100644
--- a/content/browser/attribution_reporting/attribution_internals_browsertest.cc
+++ b/content/browser/attribution_reporting/attribution_internals_browsertest.cc
@@ -323,9 +323,11 @@
           table.children[1].children[14]?.innerText === '' &&
           table.children[4].children[14]?.innerText === 'Cleared (was 987)' &&
           table.children[0].children[15]?.innerText === '' &&
-          table.children[1].children[15]?.innerText === '13, 17' &&
+          table.children[1].children[15]?.children[0]?.children[0]?.innerText === '13' &&
+          table.children[1].children[15]?.children[0]?.children[1]?.innerText === '17' &&
           table.children[0].children[16]?.innerText === '' &&
-          table.children[1].children[16]?.innerText === '14, 18' &&
+          table.children[1].children[16]?.children[0]?.children[0]?.innerText === '14' &&
+          table.children[1].children[16]?.children[0]?.children[1]?.innerText === '18' &&
           table.children[0].children[1]?.innerText === 'Unattributable: noised' &&
           table.children[1].children[1]?.innerText === 'Attributable' &&
           table.children[2].children[1]?.innerText === 'Attributable: reached event-level attribution limit' &&
diff --git a/content/browser/attribution_reporting/attribution_internals_ui.cc b/content/browser/attribution_reporting/attribution_internals_ui.cc
index 1e2d6f8..be5c463 100644
--- a/content/browser/attribution_reporting/attribution_internals_ui.cc
+++ b/content/browser/attribution_reporting/attribution_internals_ui.cc
@@ -4,8 +4,10 @@
 
 #include "content/browser/attribution_reporting/attribution_internals_ui.h"
 
+#include "base/containers/span.h"
 #include "content/browser/attribution_reporting/attribution_internals_handler_impl.h"
-#include "content/grit/dev_ui_content_resources.h"
+#include "content/grit/attribution_internals_resources.h"
+#include "content/grit/attribution_internals_resources_map.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/browser/web_ui.h"
@@ -25,23 +27,12 @@
       web_ui->GetWebContents()->GetBrowserContext(),
       kChromeUIAttributionInternalsHost);
 
-  source->AddResourcePath("attribution_internals.mojom-webui.js",
-                          IDR_ATTRIBUTION_INTERNALS_MOJOM_JS);
-  source->AddResourcePath("attribution_internals.js",
-                          IDR_ATTRIBUTION_INTERNALS_JS);
-  source->AddResourcePath("attribution_internals_table.js",
-                          IDR_ATTRIBUTION_INTERNALS_TABLE_JS);
-  source->AddResourcePath("attribution_internals_table.html.js",
-                          IDR_ATTRIBUTION_INTERNALS_TABLE_HTML_JS);
-  source->AddResourcePath("attribution_reporting.mojom-webui.js",
-                          IDR_ATTRIBUTION_REPORTING_MOJOM_JS);
-  source->AddResourcePath("source_registration_error.mojom-webui.js",
-                          IDR_SOURCE_REGISTRATION_ERROR_MOJOM_JS);
-  source->AddResourcePath("table_model.js",
-                          IDR_ATTRIBUTION_INTERNALS_TABLE_MODEL_JS);
-  source->AddResourcePath("attribution_internals.css",
-                          IDR_ATTRIBUTION_INTERNALS_CSS);
-  source->SetDefaultResource(IDR_ATTRIBUTION_INTERNALS_HTML);
+  source->AddResourcePaths(base::make_span(kAttributionInternalsResources,
+                                           kAttributionInternalsResourcesSize));
+
+  source->SetDefaultResource(
+      IDR_ATTRIBUTION_INTERNALS_ATTRIBUTION_INTERNALS_HTML);
+
   source->OverrideContentSecurityPolicy(
       network::mojom::CSPDirectiveName::TrustedTypes,
       "trusted-types static-types;");
diff --git a/content/browser/interest_group/interest_group_browsertest.cc b/content/browser/interest_group/interest_group_browsertest.cc
index 3829d37..46ce25c 100644
--- a/content/browser/interest_group/interest_group_browsertest.cc
+++ b/content/browser/interest_group/interest_group_browsertest.cc
@@ -83,6 +83,7 @@
 namespace {
 
 using ::testing::Eq;
+using ::testing::HasSubstr;
 using ::testing::Optional;
 
 // Returned by test Javascript code when join or leave promises complete without
@@ -1194,21 +1195,21 @@
   bool ReplaceInURNInJS(
       const GURL& urn_url,
       const base::flat_map<std::string, std::string> replacements,
-      const absl::optional<ToRenderFrameHost> execution_target =
-          absl::nullopt) {
+      std::string* error_out = nullptr) {
     base::Value::Dict replacement_value;
     for (const auto& replacement : replacements)
       replacement_value.Set(replacement.first, replacement.second);
-    EvalJsResult result =
-        EvalJs(execution_target ? *execution_target : shell(),
-               JsReplace(R"(
+    EvalJsResult result = EvalJs(
+        shell(), JsReplace(R"(
     (async function() {
       await navigator.deprecatedReplaceInURN($1, $2);
       return 'done';
     })())",
-                         urn_url, base::Value(std::move(replacement_value))));
-    EXPECT_EQ("", result.error);
-    return "done" == result;
+                           urn_url, base::Value(std::move(replacement_value))));
+    if (error_out != nullptr) {
+      *error_out = result.error;
+    }
+    return result.error == "" && result == "done";
   }
 
   void AttachInterestGroupObserver() {
@@ -4107,6 +4108,33 @@
   NavigateIframeAndCheckURL(web_contents(), urn_url, expected_ad_url);
 }
 
+IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTest,
+                       ReplaceURLFailsOnBadReplacementInput) {
+  GURL test_url = https_server_->GetURL("a.test", "/page_with_iframe.html");
+  ASSERT_TRUE(NavigateToURL(shell(), test_url));
+
+  GURL urn_url = GURL("urn:uuid:84a8bf15-8539-432d-bb9f-4eb20eaf400b");
+  std::string error;
+  EXPECT_FALSE(ReplaceInURNInJS(
+      urn_url, {{"${INTEREST_GROUP_NAME}", "render_cars"}, {"%echo%%", "echo"}},
+      &error));
+  EXPECT_THAT(error, HasSubstr("Replacements must be of the form "));
+}
+
+IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTest,
+                       ReplaceURLFailsOnMalformedURN) {
+  GURL test_url = https_server_->GetURL("a.test", "/page_with_iframe.html");
+  ASSERT_TRUE(NavigateToURL(shell(), test_url));
+
+  GURL urn_url = GURL("http://test.com");
+  std::string error;
+  EXPECT_FALSE(ReplaceInURNInJS(
+      urn_url,
+      {{"${INTEREST_GROUP_NAME}", "render_cars"}, {"%%echo%%", "echo"}},
+      &error));
+  EXPECT_THAT(error, HasSubstr("Passed URL must be a valid URN URL."));
+}
+
 IN_PROC_BROWSER_TEST_F(
     InterestGroupBrowserTest,
     RunAdAuctionPerBuyerSignalsAndPerBuyerTimeoutsOriginNotInBuyers) {
@@ -4838,7 +4866,8 @@
       << "URL is not valid: " << urn_url_string.ExtractString();
   EXPECT_EQ(url::kUrnScheme, urn_url.scheme_piece());
 
-  ReplaceInURNInJS(urn_url, {{"%%LOADING_MODE%%", "fenced-frame"}});
+  EXPECT_TRUE(
+      ReplaceInURNInJS(urn_url, {{"%%LOADING_MODE%%", "fenced-frame"}}));
 
   NavigateFencedFrameAndWait(urn_url, expected_ad_url, shell());
 }
diff --git a/content/browser/media/capture/screen_capture_kit_device_mac.mm b/content/browser/media/capture/screen_capture_kit_device_mac.mm
index 476a8e6..dc28dc53 100644
--- a/content/browser/media/capture/screen_capture_kit_device_mac.mm
+++ b/content/browser/media/capture/screen_capture_kit_device_mac.mm
@@ -66,16 +66,24 @@
     if (attachment) {
       CFDictionaryRef contentRectValue = base::mac::CFCast<CFDictionaryRef>(
           CFDictionaryGetValue(attachment, SCStreamFrameInfoContentRect));
+      CFNumberRef scaleFactorValue = base::mac::CFCast<CFNumberRef>(
+          CFDictionaryGetValue(attachment, SCStreamFrameInfoScaleFactor));
       CFNumberRef contentScaleValue = base::mac::CFCast<CFNumberRef>(
           CFDictionaryGetValue(attachment, SCStreamFrameInfoContentScale));
-      if (contentRectValue && contentScaleValue) {
+
+      if (contentRectValue && scaleFactorValue && contentScaleValue) {
         CGRect contentRect = {};
         bool succeed = CGRectMakeWithDictionaryRepresentation(contentRectValue,
                                                               &contentRect);
+        float scaleFactor = 1.0f;
+        succeed &= CFNumberGetValue(scaleFactorValue, kCFNumberFloatType,
+                                    &scaleFactor);
         float contentScale = 1.0f;
         succeed &= CFNumberGetValue(contentScaleValue, kCFNumberFloatType,
                                     &contentScale);
         if (succeed) {
+          contentRect.size.width *= scaleFactor;
+          contentRect.size.height *= scaleFactor;
           visibleRect.emplace(contentRect);
           contentSize.emplace(round(contentRect.size.width / contentScale),
                               round(contentRect.size.height / contentScale));
@@ -267,8 +275,11 @@
       }
     } else {
       // No current request for new capture format. Check to see if content_size
-      // has changed and requires an updated configuration.
-      if (content_size &&
+      // has changed and requires an updated configuration. We only track the
+      // content size for window capturing since the resolution does not
+      // normally change during a session and because the content scale is wrong
+      // for retina displays.
+      if (source_.type == DesktopMediaID::TYPE_WINDOW && content_size &&
           (stream_config_content_size_.width() != content_size->width() ||
            stream_config_content_size_.height() != content_size->height())) {
         DVLOG(3) << "Content size changed to " << content_size->width() << " x "
diff --git a/content/browser/resources/BUILD.gn b/content/browser/resources/BUILD.gn
index 30b024e..462de0d 100644
--- a/content/browser/resources/BUILD.gn
+++ b/content/browser/resources/BUILD.gn
@@ -4,6 +4,7 @@
 
 group("resources") {
   public_deps = [
+    "attribution_reporting:resources",
     "indexed_db:resources",
     "quota:resources",
   ]
diff --git a/content/browser/resources/attribution_reporting/BUILD.gn b/content/browser/resources/attribution_reporting/BUILD.gn
index 61040daf..6f1bec62 100644
--- a/content/browser/resources/attribution_reporting/BUILD.gn
+++ b/content/browser/resources/attribution_reporting/BUILD.gn
@@ -2,53 +2,44 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-import("//tools/polymer/html_to_wrapper.gni")
-import("//tools/typescript/ts_library.gni")
+import("//chrome/browser/resources/tools/build_webui.gni")
 
-html_to_wrapper("html_wrapper_files") {
-  in_files = [ "attribution_internals_table.html" ]
-  template = "native"
-}
+build_webui("build") {
+  grd_prefix = "attribution_internals"
 
-# Copy (via creating sym links) all the other files into the same folder for
-# ts_library.
-copy("copy_files") {
-  deps = [
-    "//components/attribution_reporting:mojom_js__generator",
-    "//content/browser/attribution_reporting:internals_mojo_bindings_js__generator",
-    "//content/browser/attribution_reporting:mojo_bindings_js__generator",
+  static_files = [
+    "attribution_internals.html",
+    "attribution_internals.css",
   ]
-  sources = [
-    "$root_gen_dir/mojom-webui/components/attribution_reporting/source_registration_error.mojom-webui.js",
-    "$root_gen_dir/mojom-webui/content/browser/attribution_reporting/attribution_internals.mojom-webui.js",
-    "$root_gen_dir/mojom-webui/content/browser/attribution_reporting/attribution_reporting.mojom-webui.js",
+
+  non_web_component_files = [
     "attribution_internals.ts",
-    "attribution_internals_table.ts",
     "table_model.ts",
   ]
-  outputs = [ "$target_gen_dir/{{source_file_part}}" ]
-}
 
-ts_library("build_ts") {
-  root_dir = target_gen_dir
-  out_dir = "$target_gen_dir/tsc"
-  tsconfig_base = "tsconfig_base.json"
-  in_files = [
-    "attribution_internals.ts",
-    "attribution_internals_table.ts",
-    "attribution_internals_table.html.ts",
-    "table_model.ts",
-    "attribution_internals.mojom-webui.js",
-    "attribution_reporting.mojom-webui.js",
-    "source_registration_error.mojom-webui.js",
-  ]
-  deps = [
+  web_component_files = [ "attribution_internals_table.ts" ]
+
+  ts_deps = [
     "//ui/webui/resources:library",
     "//ui/webui/resources/mojo:library",
   ]
-  definitions = [ "//tools/typescript/definitions/chrome_send.d.ts" ]
-  extra_deps = [
-    ":copy_files",
-    ":html_wrapper_files",
+
+  mojom_folder = "$root_gen_dir/mojom-webui"
+  attribution_reporting_component_folder = "components/attribution_reporting"
+  attribution_reporting_content_folder = "content/browser/attribution_reporting"
+
+  mojo_files = [
+    "$mojom_folder/$attribution_reporting_component_folder/source_registration_error.mojom-webui.js",
+    "$mojom_folder/$attribution_reporting_content_folder/attribution_internals.mojom-webui.js",
+    "$mojom_folder/$attribution_reporting_content_folder/attribution_reporting.mojom-webui.js",
   ]
+
+  mojo_files_deps = [
+    "//$attribution_reporting_component_folder:mojom_js__generator",
+    "//$attribution_reporting_content_folder:internals_mojo_bindings_js__generator",
+    "//$attribution_reporting_content_folder:mojo_bindings_js__generator",
+  ]
+
+  grit_output_dir = "$root_gen_dir/content"
+  html_to_wrapper_template = "native"
 }
diff --git a/content/browser/resources/attribution_reporting/attribution_internals.ts b/content/browser/resources/attribution_reporting/attribution_internals.ts
index 1317345..36074e0 100644
--- a/content/browser/resources/attribution_reporting/attribution_internals.ts
+++ b/content/browser/resources/attribution_reporting/attribution_internals.ts
@@ -79,6 +79,29 @@
   }
 }
 
+class ListColumn<T, V> extends ValueColumn<T, V[]> {
+  constructor(header: string, getValue: (p: T) => V[]) {
+    super(header, getValue, /*comparable=*/ false);
+  }
+
+  override render(td: HTMLElement, row: T) {
+    const values = this.getValue(row);
+    if (values.length === 0) {
+      return;
+    }
+
+    const ul = td.ownerDocument.createElement('ul');
+
+    values.forEach(value => {
+      const li = td.ownerDocument.createElement('li');
+      li.innerText = `${value}`;
+      ul.appendChild(li);
+    });
+
+    td.appendChild(ul);
+  }
+}
+
 function renderDL<T>(td: HTMLElement, row: T, cols: Array<Column<T>>) {
   const dl = td.ownerDocument.createElement('dl');
 
@@ -236,11 +259,11 @@
   filterData: string;
   aggregationKeys: string;
   debugKey: string;
-  dedupKeys: string;
+  dedupKeys: bigint[];
   priority: bigint;
   status: string;
   aggregatableBudgetConsumed: bigint;
-  aggregatableDedupKeys: string;
+  aggregatableDedupKeys: bigint[];
   debugReportingEnabled: string;
 
   constructor(mojo: WebUISource) {
@@ -267,9 +290,9 @@
       this.debugKey = '';
     }
 
-    this.dedupKeys = mojo.dedupKeys.join(', ');
+    this.dedupKeys = mojo.dedupKeys;
     this.aggregatableBudgetConsumed = mojo.aggregatableBudgetConsumed;
-    this.aggregatableDedupKeys = mojo.aggregatableDedupKeys.join(', ');
+    this.aggregatableDedupKeys = mojo.aggregatableDedupKeys;
     this.status = attributabilityToText(mojo.attributability);
     this.debugReportingEnabled = sourceDebugReportingToText(mojo.debugReportingEnabled);
   }
@@ -307,8 +330,8 @@
               'Aggregatable Budget Consumed',
               (e) => `${e.aggregatableBudgetConsumed} / ${BUDGET_PER_SOURCE}`),
           new ValueColumn<Source, string>('Debug Key', (e) => e.debugKey),
-          new ValueColumn<Source, string>('Dedup Keys', (e) => e.dedupKeys),
-          new ValueColumn<Source, string>(
+          new ListColumn<Source, bigint>('Dedup Keys', (e) => e.dedupKeys),
+          new ListColumn<Source, bigint>(
               'Aggregatable Dedup Keys', (e) => e.aggregatableDedupKeys),
           new ValueColumn<Source, string>(
               'Verbose Debug Reporting', (e) => e.debugReportingEnabled),
diff --git a/content/dev_ui_content_resources.grd b/content/dev_ui_content_resources.grd
index 59fed7d..e84b586 100644
--- a/content/dev_ui_content_resources.grd
+++ b/content/dev_ui_content_resources.grd
@@ -22,14 +22,6 @@
       <include name="IDR_AGGREGATION_SERVICE_INTERNALS_TABLE_HTML_JS" file="${root_gen_dir}/content/browser/resources/aggregation_service/tsc/aggregation_service_internals_table.html.js" use_base_dir="false" type="BINDATA" />
       <include name="IDR_AGGREGATION_SERVICE_INTERNALS_CSS" file="browser/resources/aggregation_service/aggregation_service_internals.css" type="BINDATA" />
       <include name="IDR_AGGREGATION_SERVICE_INTERNALS_MOJOM_JS" file="${root_gen_dir}/mojom-webui/content/browser/aggregation_service/aggregation_service_internals.mojom-webui.js" use_base_dir="false" type="BINDATA" />
-      <include name="IDR_ATTRIBUTION_INTERNALS_HTML" file="browser/resources/attribution_reporting/attribution_internals.html" type="BINDATA" />
-      <include name="IDR_ATTRIBUTION_INTERNALS_JS" file="${root_gen_dir}/content/browser/resources/attribution_reporting/tsc/attribution_internals.js" use_base_dir="false" type="BINDATA" />
-      <include name="IDR_ATTRIBUTION_INTERNALS_TABLE_MODEL_JS" file="${root_gen_dir}/content/browser/resources/attribution_reporting/tsc/table_model.js" use_base_dir="false" type="BINDATA" />
-      <include name="IDR_ATTRIBUTION_INTERNALS_TABLE_JS" file="${root_gen_dir}/content/browser/resources/attribution_reporting/tsc/attribution_internals_table.js" use_base_dir="false" type="BINDATA" />
-      <include name="IDR_ATTRIBUTION_INTERNALS_TABLE_HTML_JS" file="${root_gen_dir}/content/browser/resources/attribution_reporting/tsc/attribution_internals_table.html.js" use_base_dir="false" type="BINDATA" />
-      <include name="IDR_ATTRIBUTION_INTERNALS_CSS" file="browser/resources/attribution_reporting/attribution_internals.css" type="BINDATA" />
-      <include name="IDR_ATTRIBUTION_INTERNALS_MOJOM_JS" file="${root_gen_dir}/mojom-webui/content/browser/attribution_reporting/attribution_internals.mojom-webui.js" use_base_dir="false" type="BINDATA" />
-      <include name="IDR_ATTRIBUTION_REPORTING_MOJOM_JS" file="${root_gen_dir}/mojom-webui/content/browser/attribution_reporting/attribution_reporting.mojom-webui.js" use_base_dir="false" type="BINDATA" />
       <include name="IDR_GPU_BROWSER_BRIDGE_JS" file="browser/resources/gpu/browser_bridge.js" type="BINDATA" />
       <include name="IDR_GPU_INTERNALS_HTML" file="browser/resources/gpu/gpu_internals.html" type="BINDATA" />
       <include name="IDR_GPU_INTERNALS_JS" file="browser/resources/gpu/gpu_internals.js" type="BINDATA" />
@@ -50,7 +42,6 @@
       <include name="IDR_SERVICE_WORKER_INTERNALS_HTML" file="browser/resources/service_worker/serviceworker_internals.html" flattenhtml="true" allowexternalscript="true" type="BINDATA" />
       <include name="IDR_SERVICE_WORKER_INTERNALS_JS" file="browser/resources/service_worker/serviceworker_internals.js" type="BINDATA" />
       <include name="IDR_SERVICE_WORKER_INTERNALS_CSS" file="browser/resources/service_worker/serviceworker_internals.css" type="BINDATA" />
-      <include name="IDR_SOURCE_REGISTRATION_ERROR_MOJOM_JS" file="${root_gen_dir}/mojom-webui/components/attribution_reporting/source_registration_error.mojom-webui.js" use_base_dir="false" type="BINDATA" />
     </includes>
   </release>
 </grit>
diff --git a/content/public/android/BUILD.gn b/content/public/android/BUILD.gn
index 6eb90ad..d439be4 100644
--- a/content/public/android/BUILD.gn
+++ b/content/public/android/BUILD.gn
@@ -28,15 +28,10 @@
     "//ui/android:ui_java_resources",
   ]
   sources = [
-    "java/res/drawable-hdpi/ic_menu_share_holo_light.png",
     "java/res/drawable-hdpi/ic_search.png",
-    "java/res/drawable-mdpi/ic_menu_share_holo_light.png",
     "java/res/drawable-mdpi/ic_search.png",
-    "java/res/drawable-xhdpi/ic_menu_share_holo_light.png",
     "java/res/drawable-xhdpi/ic_search.png",
-    "java/res/drawable-xxhdpi/ic_menu_share_holo_light.png",
     "java/res/drawable-xxhdpi/ic_search.png",
-    "java/res/drawable-xxxhdpi/ic_menu_share_holo_light.png",
     "java/res/drawable-xxxhdpi/ic_search.png",
     "java/res/drawable/floating_popup_background.xml",
     "java/res/layout-land/date_time_picker_dialog.xml",
@@ -48,11 +43,10 @@
     "java/res/layout/text_edit_suggestion_list_footer.xml",
     "java/res/layout/two_field_date_picker.xml",
     "java/res/menu/select_action_menu.xml",
-    "java/res/values-v17/styles.xml",
-    "java/res/values-v21/styles.xml",
     "java/res/values/attrs.xml",
     "java/res/values/dimens.xml",
     "java/res/values/strings.xml",
+    "java/res/values/styles.xml",
   ]
 }
 
diff --git a/content/public/android/java/res/drawable-hdpi/ic_menu_share_holo_light.png b/content/public/android/java/res/drawable-hdpi/ic_menu_share_holo_light.png
deleted file mode 100644
index 5c7406e..0000000
--- a/content/public/android/java/res/drawable-hdpi/ic_menu_share_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/content/public/android/java/res/drawable-mdpi/ic_menu_share_holo_light.png b/content/public/android/java/res/drawable-mdpi/ic_menu_share_holo_light.png
deleted file mode 100644
index 8053569..0000000
--- a/content/public/android/java/res/drawable-mdpi/ic_menu_share_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/content/public/android/java/res/drawable-xhdpi/ic_menu_share_holo_light.png b/content/public/android/java/res/drawable-xhdpi/ic_menu_share_holo_light.png
deleted file mode 100644
index c8f19ee..0000000
--- a/content/public/android/java/res/drawable-xhdpi/ic_menu_share_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/content/public/android/java/res/drawable-xxhdpi/ic_menu_share_holo_light.png b/content/public/android/java/res/drawable-xxhdpi/ic_menu_share_holo_light.png
deleted file mode 100644
index af433b4..0000000
--- a/content/public/android/java/res/drawable-xxhdpi/ic_menu_share_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/content/public/android/java/res/drawable-xxxhdpi/ic_menu_share_holo_light.png b/content/public/android/java/res/drawable-xxxhdpi/ic_menu_share_holo_light.png
deleted file mode 100644
index 796c843..0000000
--- a/content/public/android/java/res/drawable-xxxhdpi/ic_menu_share_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/content/public/android/java/res/values-v21/styles.xml b/content/public/android/java/res/values-v21/styles.xml
deleted file mode 100644
index 05bd8e3..0000000
--- a/content/public/android/java/res/values-v21/styles.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-Copyright 2014 The Chromium Authors
-Use of this source code is governed by a BSD-style license that can be
-found in the LICENSE file.
--->
-<resources>
-    <style name="SelectActionMenuShare">
-        <item name="android:icon">?android:attr/actionModeShareDrawable</item>
-    </style>
-    <style name="SelectActionMenuWebSearch">
-        <item name="android:icon">?android:attr/actionModeWebSearchDrawable</item>
-    </style>
-    <style name="TextAppearance.TextSuggestionButton">
-        <item name="android:drawablePadding">8dp</item>
-        <item name="android:gravity">start|center_vertical</item>
-        <item name="android:layout_gravity">start|center_vertical</item>
-        <item name="android:layout_height">48dp</item>
-        <item name="android:layout_width">match_parent</item>
-        <item name="android:paddingBottom">8dp</item>
-        <item name="android:paddingEnd">16dp</item>
-        <item name="android:paddingStart">16dp</item>
-        <item name="android:paddingTop">8dp</item>
-        <item name="android:singleLine">true</item>
-        <!-- v17 hard codes modern_blue_600 -->
-        <item name="android:textAppearance">@style/TextAppearance.TextSuggestionButtonText</item>
-    </style>
-
-    <style name="TextAppearance.TextSuggestionButtonText" parent="@style/TextAppearance.Button.Text.Blue">
-        <item name="android:textColor">?android:attr/colorAccent</item>
-    </style>
-</resources>
diff --git a/content/public/android/java/res/values-v17/styles.xml b/content/public/android/java/res/values/styles.xml
similarity index 78%
rename from content/public/android/java/res/values-v17/styles.xml
rename to content/public/android/java/res/values/styles.xml
index fcccab94..800d8857 100644
--- a/content/public/android/java/res/values-v17/styles.xml
+++ b/content/public/android/java/res/values/styles.xml
@@ -11,17 +11,16 @@
         <item name="select_dialog_multichoice">@android:layout/select_dialog_multichoice</item>
     </style>
     <style name="SelectActionMenuShare">
-        <item name="android:icon">@drawable/ic_menu_share_holo_light</item>
+        <item name="android:icon">?android:attr/actionModeShareDrawable</item>
     </style>
     <style name="SelectActionMenuWebSearch">
-        <item name="android:icon">@drawable/ic_search</item>
+        <item name="android:icon">?android:attr/actionModeWebSearchDrawable</item>
     </style>
     <style name="TextAppearance.SuggestionPrefixOrSuffix">
       <item name="android:textColor">?android:attr/textColorSecondary</item>
     </style>
     <style name="TextAppearance.TextSuggestionButton">
         <item name="android:drawablePadding">8dp</item>
-        <!-- v21 uses sans-serif-medium -->
         <item name="android:gravity">start|center_vertical</item>
         <item name="android:layout_gravity">start|center_vertical</item>
         <item name="android:layout_height">48dp</item>
@@ -31,7 +30,9 @@
         <item name="android:paddingStart">16dp</item>
         <item name="android:paddingTop">8dp</item>
         <item name="android:singleLine">true</item>
-        <!-- v21 uses android:attr/colorAccent -->
-        <item name="android:textAppearance">@style/TextAppearance.Button.Text.Blue</item>
+        <item name="android:textAppearance">@style/TextAppearance.TextSuggestionButtonText</item>
+    </style>
+    <style name="TextAppearance.TextSuggestionButtonText" parent="@style/TextAppearance.Button.Text.Blue">
+        <item name="android:textColor">?android:attr/colorAccent</item>
     </style>
 </resources>
diff --git a/content/public/renderer/render_frame_observer.h b/content/public/renderer/render_frame_observer.h
index b132d649..7eaed5e 100644
--- a/content/public/renderer/render_frame_observer.h
+++ b/content/public/renderer/render_frame_observer.h
@@ -307,6 +307,12 @@
   virtual void OnMainFrameViewportRectangleChanged(
       const gfx::Rect& main_frame_viewport_rect) {}
 
+  // Called when an image ad rectangle changed. An empty `image_ad_rect` is used
+  // to signal the removal of the rectangle. Only invoked on the main frame.
+  virtual void OnMainFrameImageAdRectangleChanged(
+      int element_id,
+      const gfx::Rect& image_ad_rect) {}
+
   // Overlay-popup-ad violates The Better Ads Standards
   // (https://www.betterads.org/standards/). This method will be called when an
   // overlay-popup-ad is detected, to let the embedder
diff --git a/content/renderer/BUILD.gn b/content/renderer/BUILD.gn
index a1f691c..2a2410d 100644
--- a/content/renderer/BUILD.gn
+++ b/content/renderer/BUILD.gn
@@ -72,10 +72,6 @@
     "media/audio_decoder.h",
     "media/batching_media_log.cc",
     "media/batching_media_log.h",
-    "media/codec_factory.cc",
-    "media/codec_factory.h",
-    "media/codec_factory_mojo.cc",
-    "media/codec_factory_mojo.h",
     "media/gpu/gpu_video_accelerator_factories_impl.cc",
     "media/gpu/gpu_video_accelerator_factories_impl.h",
     "media/inspector_media_event_handler.cc",
@@ -344,11 +340,7 @@
   }
 
   if (is_fuchsia) {
-    sources += [
-      "media/codec_factory_fuchsia.cc",
-      "media/codec_factory_fuchsia.h",
-      "renderer_main_platform_delegate_fuchsia.cc",
-    ]
+    sources += [ "renderer_main_platform_delegate_fuchsia.cc" ]
     public_deps += [ "//third_party/fuchsia-sdk/sdk/fidl/fuchsia.mediacodec" ]
 
     deps += [
diff --git a/content/renderer/media/codec_factory.cc b/content/renderer/media/codec_factory.cc
deleted file mode 100644
index 65939e76..0000000
--- a/content/renderer/media/codec_factory.cc
+++ /dev/null
@@ -1,224 +0,0 @@
-// Copyright 2022 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "content/renderer/media/codec_factory.h"
-
-#include <memory>
-#include <optional>
-
-#include "base/functional/callback_forward.h"
-#include "base/memory/ptr_util.h"
-#include "base/notreached.h"
-#include "media/base/bind_to_current_loop.h"
-#include "media/base/decoder.h"
-#include "media/base/media_log.h"
-#include "media/base/overlay_info.h"
-#include "media/base/supported_video_decoder_config.h"
-#include "media/mojo/clients/mojo_video_encode_accelerator.h"
-#include "media/video/gpu_video_accelerator_factories.h"
-
-namespace content {
-
-CodecFactory::CodecFactory(
-    scoped_refptr<base::SequencedTaskRunner> media_task_runner,
-    scoped_refptr<viz::ContextProviderCommandBuffer> context_provider,
-    bool video_decode_accelerator_enabled,
-    bool video_encode_accelerator_enabled,
-    mojo::PendingRemote<media::mojom::VideoEncodeAcceleratorProvider>
-        pending_vea_provider_remote)
-    : media_task_runner_(std::move(media_task_runner)),
-      context_provider_(std::move(context_provider)),
-      video_decode_accelerator_enabled_(video_decode_accelerator_enabled),
-      video_encode_accelerator_enabled_(video_encode_accelerator_enabled) {
-  media_task_runner_->PostTask(
-      FROM_HERE,
-      base::BindOnce(&CodecFactory::BindOnTaskRunner, base::Unretained(this),
-                     std::move(pending_vea_provider_remote)));
-}
-CodecFactory::~CodecFactory() = default;
-
-std::unique_ptr<media::VideoEncodeAccelerator>
-CodecFactory::CreateVideoEncodeAccelerator() {
-  DCHECK(video_encode_accelerator_enabled_);
-  DCHECK(media_task_runner_->RunsTasksInCurrentSequence());
-  DCHECK(vea_provider_.is_bound());
-
-  base::AutoLock lock(supported_profiles_lock_);
-  // When |supported_vea_profiles_| is empty, no hw encoder is available or
-  // we have not yet gotten the supported profiles.
-  if (!supported_vea_profiles_) {
-    DVLOG(2) << "VEA's profiles have not yet been gotten";
-  } else if (supported_vea_profiles_->empty()) {
-    // There is no profile supported by VEA.
-    return nullptr;
-  }
-
-  mojo::PendingRemote<media::mojom::VideoEncodeAccelerator> vea;
-  vea_provider_->CreateVideoEncodeAccelerator(
-      vea.InitWithNewPipeAndPassReceiver());
-
-  if (!vea) {
-    return nullptr;
-  }
-
-  return base::WrapUnique<media::VideoEncodeAccelerator>(
-      new media::MojoVideoEncodeAccelerator(std::move(vea)));
-}
-
-media::VideoDecoderType CodecFactory::GetVideoDecoderType() {
-  base::AutoLock lock(supported_profiles_lock_);
-  return video_decoder_type_;
-}
-
-absl::optional<media::SupportedVideoDecoderConfigs>
-CodecFactory::GetSupportedVideoDecoderConfigs() {
-  base::AutoLock lock(supported_profiles_lock_);
-  return supported_decoder_configs_;
-}
-
-absl::optional<media::VideoEncodeAccelerator::SupportedProfiles>
-CodecFactory::GetVideoEncodeAcceleratorSupportedProfiles() {
-  base::AutoLock lock(supported_profiles_lock_);
-  return supported_vea_profiles_;
-}
-
-bool CodecFactory::IsDecoderSupportKnown() {
-  base::AutoLock lock(supported_profiles_lock_);
-  return decoder_support_notifier_.is_notified();
-}
-
-bool CodecFactory::IsEncoderSupportKnown() {
-  base::AutoLock lock(supported_profiles_lock_);
-  return encoder_support_notifier_.is_notified();
-}
-
-void CodecFactory::NotifyDecoderSupportKnown(base::OnceClosure callback) {
-  base::AutoLock lock(supported_profiles_lock_);
-  decoder_support_notifier_.Register(
-      media::BindToCurrentLoop(std::move(callback)));
-}
-
-void CodecFactory::NotifyEncoderSupportKnown(base::OnceClosure callback) {
-  base::AutoLock lock(supported_profiles_lock_);
-  encoder_support_notifier_.Register(
-      media::BindToCurrentLoop(std::move(callback)));
-}
-
-CodecFactory::Notifier::Notifier() = default;
-CodecFactory::Notifier::~Notifier() = default;
-
-void CodecFactory::Notifier::Register(base::OnceClosure callback) {
-  if (is_notified_) {
-    std::move(callback).Run();
-    return;
-  }
-  callbacks_.push_back(std::move(callback));
-}
-
-void CodecFactory::Notifier::Notify() {
-  DCHECK(!is_notified_);
-  is_notified_ = true;
-  while (!callbacks_.empty()) {
-    std::move(callbacks_.back()).Run();
-    callbacks_.pop_back();
-  }
-}
-
-void CodecFactory::OnDecoderSupportFailed() {
-  base::AutoLock lock(supported_profiles_lock_);
-  if (decoder_support_notifier_.is_notified()) {
-    return;
-  }
-  supported_decoder_configs_ = media::SupportedVideoDecoderConfigs();
-  decoder_support_notifier_.Notify();
-}
-
-void CodecFactory::OnGetSupportedDecoderConfigs() {
-  base::AutoLock lock(supported_profiles_lock_);
-  decoder_support_notifier_.Notify();
-}
-
-void CodecFactory::OnEncoderSupportFailed() {
-  base::AutoLock lock(supported_profiles_lock_);
-  if (encoder_support_notifier_.is_notified()) {
-    return;
-  }
-  supported_vea_profiles_ = media::VideoEncodeAccelerator::SupportedProfiles();
-  encoder_support_notifier_.Notify();
-}
-
-void CodecFactory::OnGetVideoEncodeAcceleratorSupportedProfiles(
-    const media::VideoEncodeAccelerator::SupportedProfiles&
-        supported_profiles) {
-  base::AutoLock lock(supported_profiles_lock_);
-  supported_vea_profiles_ = supported_profiles;
-  encoder_support_notifier_.Notify();
-}
-
-void CodecFactory::BindOnTaskRunner(
-    mojo::PendingRemote<media::mojom::VideoEncodeAcceleratorProvider>
-        pending_vea_provider_remote) {
-  DCHECK(media_task_runner_->RunsTasksInCurrentSequence());
-  DCHECK(context_provider_);
-
-  vea_provider_.Bind(std::move(pending_vea_provider_remote));
-
-  if (context_provider_->BindToCurrentSequence() !=
-      gpu::ContextResult::kSuccess) {
-    OnDecoderSupportFailed();
-    OnEncoderSupportFailed();
-    return;
-  }
-
-  if (video_encode_accelerator_enabled_) {
-    // The remote might be disconnected if the encoding process crashes, for
-    // example a GPU driver failure. Set a disconnect handler to watch these
-    // types of failures and treat them as if there are no supported encoder
-    // profiles.
-    // Unretained is safe since CodecFactory is never destroyed.
-    // It lives until the process shuts down.
-    vea_provider_.set_disconnect_handler(base::BindOnce(
-        &CodecFactory::OnEncoderSupportFailed, base::Unretained(this)));
-    vea_provider_->GetVideoEncodeAcceleratorSupportedProfiles(base::BindOnce(
-        &CodecFactory::OnGetVideoEncodeAcceleratorSupportedProfiles,
-        base::Unretained(this)));
-  } else {
-    OnEncoderSupportFailed();
-  }
-
-  if (!video_decode_accelerator_enabled_) {
-    OnDecoderSupportFailed();
-  }
-}
-
-CodecFactoryDefault::CodecFactoryDefault(
-    scoped_refptr<base::SequencedTaskRunner> task_runner,
-    scoped_refptr<viz::ContextProviderCommandBuffer> context_provider,
-    bool video_decode_accelerator_enabled,
-    bool video_encode_accelerator_enabled,
-    mojo::PendingRemote<media::mojom::VideoEncodeAcceleratorProvider>
-        pending_vea_provider_remote)
-    : CodecFactory(std::move(task_runner),
-                   std::move(context_provider),
-                   video_decode_accelerator_enabled,
-                   video_encode_accelerator_enabled,
-                   std::move(pending_vea_provider_remote)) {
-  // There is no decoder provider.
-  OnDecoderSupportFailed();
-}
-
-CodecFactoryDefault::~CodecFactoryDefault() = default;
-
-std::unique_ptr<media::VideoDecoder> CodecFactoryDefault::CreateVideoDecoder(
-    media::GpuVideoAcceleratorFactories* gpu_factories,
-    media::MediaLog* media_log,
-    media::RequestOverlayInfoCB request_overlay_info_cb,
-    const gfx::ColorSpace& rendering_color_space) {
-  NOTIMPLEMENTED()
-      << "CodecFactoryDefault does not have a provider to create a "
-         "hardware video decoder.";
-  return nullptr;
-}
-
-}  // namespace content
diff --git a/content/renderer/media/codec_factory.h b/content/renderer/media/codec_factory.h
deleted file mode 100644
index 55e1374..0000000
--- a/content/renderer/media/codec_factory.h
+++ /dev/null
@@ -1,196 +0,0 @@
-// Copyright 2022 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CONTENT_RENDERER_MEDIA_CODEC_FACTORY_H_
-#define CONTENT_RENDERER_MEDIA_CODEC_FACTORY_H_
-
-#include <memory>
-
-#include "base/functional/callback_forward.h"
-#include "content/common/content_export.h"
-#include "media/base/decoder.h"
-#include "media/base/media_log.h"
-#include "media/base/overlay_info.h"
-#include "media/base/supported_video_decoder_config.h"
-#include "media/base/video_decoder.h"
-#include "media/mojo/mojom/video_encode_accelerator.mojom.h"
-#include "media/video/gpu_video_accelerator_factories.h"
-#include "mojo/public/cpp/bindings/pending_remote.h"
-#include "mojo/public/cpp/bindings/remote.h"
-#include "services/viz/public/cpp/gpu/context_provider_command_buffer.h"
-
-namespace content {
-
-// Assists to GpuVideoAcceleratorFactoriesImpl on hardware decoder and encoder
-// functionalities.
-//
-// It is a base class that handles the encoder resources
-// via media::mojom::VideoEncodeAcceleratorProvider. Its derived classes need
-// to implement how to connect to hardware decoder resources.
-class CONTENT_EXPORT CodecFactory {
- public:
-  // `media_task_runner` - task runner for running multi-media operations.
-  // `context_provider` - context provider for creating a video decoder.
-  // `video_decode_accelerator_enabled` - whether the video decode accelerator
-  //    is enabled.
-  // `video_encode_accelerator_enabled` - whether the video encode accelerator
-  //    is enabled.
-  // `pending_vea_provider_remote` - bound pending
-  //    media::mojom::VideoEncodeAcceleratorProvider remote.
-  CodecFactory(
-      scoped_refptr<base::SequencedTaskRunner> media_task_runner,
-      scoped_refptr<viz::ContextProviderCommandBuffer> context_provider,
-      bool video_decode_accelerator_enabled,
-      bool video_encode_accelerator_enabled,
-      mojo::PendingRemote<media::mojom::VideoEncodeAcceleratorProvider>
-          pending_vea_provider_remote);
-
-  CodecFactory(const CodecFactory&) = delete;
-  CodecFactory& operator=(const CodecFactory&) = delete;
-  virtual ~CodecFactory();
-
-  // `gpu_factories` - pointer to the GpuVideoAcceleratorFactories that
-  //    owns |this|.
-  // `media_log` - process-wide pointer to log to chrome://media-internals log.
-  // `request_overlay_info_cb` - callback that gets the overlay information.
-  // `rendering_color_space` - color space for the purpose of color conversion.
-  //
-  // Derived class should construct its own type of video decoder.
-  virtual std::unique_ptr<media::VideoDecoder> CreateVideoDecoder(
-      media::GpuVideoAcceleratorFactories* gpu_factories,
-      media::MediaLog* media_log,
-      media::RequestOverlayInfoCB request_overlay_info_cb,
-      const gfx::ColorSpace& rendering_color_space) = 0;
-
-  std::unique_ptr<media::VideoEncodeAccelerator> CreateVideoEncodeAccelerator();
-
-  // Returns VideoDecoderType::kUnknown in cases where IsDecoderSupportKnown()
-  // is false.
-  // Otherwise, it returns the type of decoder that provided the
-  // configs for the config support check.
-  media::VideoDecoderType GetVideoDecoderType();
-
-  // Returns a nullopt if we have not yet gotten the configs.
-  // Returns an optional that contains an empty vector if we have gotten the
-  // result and there are no supported configs.
-  absl::optional<media::SupportedVideoDecoderConfigs>
-  GetSupportedVideoDecoderConfigs();
-
-  // Returns a nullopt if we have not yet gotten the profiles.
-  // Returns an optional that contains an empty vector if we have gotten the
-  // result and there are no supported profiles.
-  absl::optional<media::VideoEncodeAccelerator::SupportedProfiles>
-  GetVideoEncodeAcceleratorSupportedProfiles();
-
-  // Returns true if media::SupportedVideoDecoderConfigs are populated.
-  bool IsDecoderSupportKnown();
-
-  // Returns true if media::VideoEncodeAccelerator::SupportedProfiles are
-  // populated.
-  bool IsEncoderSupportKnown();
-
-  // If the current decoder support is not yet known, use this to register a
-  // callback that is notified once the support is known. At that point, calling
-  // GetSupportedVideoDecoderConfigs will give the set of supported decoder
-  // configs.
-  //
-  // There is no way to unsubscribe a callback, it is recommended to use a
-  // WeakPtr if you need this feature.
-  void NotifyDecoderSupportKnown(base::OnceClosure callback);
-
-  // If the current encoder support is not yet known, use this to register a
-  // callback that is notified once the support is known. At that point, calling
-  // GetVideoEncodeAcceleratorSupportedProfiles will give the set of supported
-  // encoder profiles.
-  //
-  // There is no way to unsubscribe a callback, it is recommended to use a
-  // WeakPtr if you need this feature.
-  void NotifyEncoderSupportKnown(base::OnceClosure callback);
-
- protected:
-  class Notifier {
-   public:
-    Notifier();
-    ~Notifier();
-
-    void Register(base::OnceClosure callback);
-    void Notify();
-
-    bool is_notified() { return is_notified_; }
-
-   private:
-    bool is_notified_ = false;
-    std::vector<base::OnceClosure> callbacks_;
-  };
-
-  void OnDecoderSupportFailed();
-  void OnGetSupportedDecoderConfigs();
-
-  void OnEncoderSupportFailed();
-  void OnGetVideoEncodeAcceleratorSupportedProfiles(
-      const media::VideoEncodeAccelerator::SupportedProfiles&
-          supported_profiles);
-
-  // Task runner on the Media thread for running multi-media operations
-  // (e.g., creating a video decoder).
-  // In Fuchsia, it needs to be started with the IO message pump for FIDL calls.
-  scoped_refptr<base::SequencedTaskRunner> media_task_runner_;
-
-  // Shared pointer to a shared context provider. All access should happen only
-  // on the media thread.
-  const scoped_refptr<viz::ContextProviderCommandBuffer> context_provider_;
-
-  // Whether video acceleration encoding/decoding should be enabled.
-  const bool video_decode_accelerator_enabled_;
-  const bool video_encode_accelerator_enabled_;
-
-  base::Lock supported_profiles_lock_;
-
-  // If the Optional is empty, then we have not yet gotten the configs.
-  // If the Optional contains an empty vector, then we have gotten the result
-  // and there are no supported configs.
-  absl::optional<media::SupportedVideoDecoderConfigs> supported_decoder_configs_
-      GUARDED_BY(supported_profiles_lock_);
-  absl::optional<media::VideoEncodeAccelerator::SupportedProfiles>
-      supported_vea_profiles_ GUARDED_BY(supported_profiles_lock_);
-
-  media::VideoDecoderType video_decoder_type_
-      GUARDED_BY(supported_profiles_lock_) = media::VideoDecoderType::kUnknown;
-
-  Notifier decoder_support_notifier_ GUARDED_BY(supported_profiles_lock_);
-  Notifier encoder_support_notifier_ GUARDED_BY(supported_profiles_lock_);
-
- private:
-  void BindOnTaskRunner(
-      mojo::PendingRemote<media::mojom::VideoEncodeAcceleratorProvider>
-          pending_vea_provider_remote);
-
-  mojo::Remote<media::mojom::VideoEncodeAcceleratorProvider> vea_provider_;
-};
-
-// CodecFactoryDefault is the default derived class, which has no
-// decoder provider. It does not have any supported video decoder configs and
-// returns a null pointer when creating a hardware video decoder.
-class CodecFactoryDefault final : public CodecFactory {
- public:
-  CodecFactoryDefault(
-      scoped_refptr<base::SequencedTaskRunner> task_runner,
-      scoped_refptr<viz::ContextProviderCommandBuffer> context_provider,
-      bool video_decode_accelerator_enabled,
-      bool video_encode_accelerator_enabled,
-      mojo::PendingRemote<media::mojom::VideoEncodeAcceleratorProvider>
-          pending_vea_provider_remote);
-  ~CodecFactoryDefault() override;
-
-  // Returns nullptr since there is no decoder provider.
-  std::unique_ptr<media::VideoDecoder> CreateVideoDecoder(
-      media::GpuVideoAcceleratorFactories* gpu_factories,
-      media::MediaLog* media_log,
-      media::RequestOverlayInfoCB request_overlay_info_cb,
-      const gfx::ColorSpace& rendering_color_space) override;
-};
-
-}  // namespace content
-
-#endif  // CONTENT_RENDERER_MEDIA_CODEC_FACTORY_H_
diff --git a/content/renderer/media/codec_factory_fuchsia.cc b/content/renderer/media/codec_factory_fuchsia.cc
deleted file mode 100644
index c8e6d2b..0000000
--- a/content/renderer/media/codec_factory_fuchsia.cc
+++ /dev/null
@@ -1,92 +0,0 @@
-// Copyright 2022 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "content/renderer/media/codec_factory_fuchsia.h"
-
-#include "base/functional/bind.h"
-#include "base/location.h"
-#include "base/synchronization/lock.h"
-#include "content/renderer/media/codec_factory.h"
-#include "media/base/decoder.h"
-#include "media/base/overlay_info.h"
-#include "media/base/supported_video_decoder_config.h"
-#include "media/base/video_decoder.h"
-#include "media/fuchsia/mojom/fuchsia_media.mojom.h"
-#include "media/fuchsia/video/fuchsia_video_decoder.h"
-#include "media/video/gpu_video_accelerator_factories.h"
-
-namespace content {
-
-CodecFactoryFuchsia::CodecFactoryFuchsia(
-    scoped_refptr<base::SequencedTaskRunner> media_task_runner,
-    scoped_refptr<viz::ContextProviderCommandBuffer> context_provider,
-    bool video_decode_accelerator_enabled,
-    bool video_encode_accelerator_enabled,
-    mojo::PendingRemote<media::mojom::VideoEncodeAcceleratorProvider>
-        pending_vea_provider_remote,
-    mojo::PendingRemote<media::mojom::FuchsiaMediaCodecProvider>
-        pending_media_codec_provider_remote)
-    : CodecFactory(std::move(media_task_runner),
-                   std::move(context_provider),
-                   video_decode_accelerator_enabled,
-                   video_encode_accelerator_enabled,
-                   std::move(pending_vea_provider_remote)) {
-  media_task_runner_->PostTask(
-      FROM_HERE,
-      base::BindOnce(&CodecFactoryFuchsia::BindOnTaskRunner,
-                     base::Unretained(this),
-                     std::move(pending_media_codec_provider_remote)));
-}
-CodecFactoryFuchsia::~CodecFactoryFuchsia() = default;
-
-std::unique_ptr<media::VideoDecoder> CodecFactoryFuchsia::CreateVideoDecoder(
-    media::GpuVideoAcceleratorFactories* gpu_factories,
-    media::MediaLog* media_log,
-    media::RequestOverlayInfoCB request_overlay_info_cb,
-    const gfx::ColorSpace& rendering_color_space) {
-  DCHECK(video_decode_accelerator_enabled_);
-
-  return std::make_unique<media::FuchsiaVideoDecoder>(context_provider_,
-                                                      media_codec_provider_,
-                                                      /*allow_overlays=*/true);
-}
-
-void CodecFactoryFuchsia::BindOnTaskRunner(
-    mojo::PendingRemote<media::mojom::FuchsiaMediaCodecProvider>
-        media_codec_provider_remote) {
-  DCHECK(media_task_runner_->RunsTasksInCurrentSequence());
-
-  if (!video_decode_accelerator_enabled_) {
-    OnDecoderSupportFailed();
-    return;
-  }
-
-  media_codec_provider_.Bind(std::move(media_codec_provider_remote),
-                             media_task_runner_);
-  // The remote might be disconnected if the decoding process crashes, for
-  // example a decoder driver failure. Set a disconnect handler to watch these
-  // types of failures and treat them as if there are no supported decoder
-  // configs.
-  // Unretained is safe since CodecFactory is never destroyed.
-  // It lives until the process shuts down.
-  media_codec_provider_.set_disconnect_handler(
-      base::BindOnce(&CodecFactoryFuchsia::OnDecoderSupportFailed,
-                     base::Unretained(this)),
-      media_task_runner_);
-  media_codec_provider_->GetSupportedVideoDecoderConfigs(
-      base::BindOnce(&CodecFactoryFuchsia::OnGetSupportedDecoderConfigs,
-                     base::Unretained(this)));
-}
-
-void CodecFactoryFuchsia::OnGetSupportedDecoderConfigs(
-    const media::SupportedVideoDecoderConfigs& supported_configs) {
-  {
-    base::AutoLock lock(supported_profiles_lock_);
-    supported_decoder_configs_.emplace(supported_configs);
-    video_decoder_type_ = media::VideoDecoderType::kFuchsia;
-  }
-  CodecFactory::OnGetSupportedDecoderConfigs();
-}
-
-}  // namespace content
diff --git a/content/renderer/media/codec_factory_fuchsia.h b/content/renderer/media/codec_factory_fuchsia.h
deleted file mode 100644
index 743c203..0000000
--- a/content/renderer/media/codec_factory_fuchsia.h
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright 2022 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CONTENT_RENDERER_MEDIA_CODEC_FACTORY_FUCHSIA_H_
-#define CONTENT_RENDERER_MEDIA_CODEC_FACTORY_FUCHSIA_H_
-
-#include "content/common/content_export.h"
-#include "content/renderer/media/codec_factory.h"
-#include "media/base/overlay_info.h"
-#include "media/base/video_decoder.h"
-#include "media/fuchsia/mojom/fuchsia_media.mojom.h"
-#include "media/video/gpu_video_accelerator_factories.h"
-#include "mojo/public/cpp/bindings/shared_remote.h"
-
-namespace content {
-
-// CodecFactoryFuchsia gets hardware decoder resources
-// via media::mojom::FuchsiaMediaCodecProvider.
-//
-// Codec-related services on Fuchsia are used directly from the renderer process
-// after the browser process provides a connection to the FIDL protocol via
-// media::mojom::FuchsiaMediaCodecProvider. This can improve performance by
-// avoiding the need to hop through the browser process.
-class CONTENT_EXPORT CodecFactoryFuchsia final : public CodecFactory {
- public:
-  CodecFactoryFuchsia(
-      scoped_refptr<base::SequencedTaskRunner> media_task_runner,
-      scoped_refptr<viz::ContextProviderCommandBuffer> context_provider,
-      bool video_decode_accelerator_enabled,
-      bool video_encode_accelerator_enabled,
-      mojo::PendingRemote<media::mojom::VideoEncodeAcceleratorProvider>
-          pending_vea_provider_remote,
-      mojo::PendingRemote<media::mojom::FuchsiaMediaCodecProvider>
-          pending_media_codec_provider_remote);
-  ~CodecFactoryFuchsia() override;
-
-  std::unique_ptr<media::VideoDecoder> CreateVideoDecoder(
-      media::GpuVideoAcceleratorFactories* gpu_factories,
-      media::MediaLog* media_log,
-      media::RequestOverlayInfoCB request_overlay_info_cb,
-      const gfx::ColorSpace& rendering_color_space) override;
-
- private:
-  void BindOnTaskRunner(
-      mojo::PendingRemote<media::mojom::FuchsiaMediaCodecProvider>
-          media_codec_provider_remote);
-  void OnGetSupportedDecoderConfigs(
-      const media::SupportedVideoDecoderConfigs& supported_configs);
-
-  mojo::SharedRemote<media::mojom::FuchsiaMediaCodecProvider>
-      media_codec_provider_;
-};
-
-}  // namespace content
-
-#endif  // CONTENT_RENDERER_MEDIA_CODEC_FACTORY_FUCHSIA_H_
diff --git a/content/renderer/media/codec_factory_mojo.cc b/content/renderer/media/codec_factory_mojo.cc
deleted file mode 100644
index 411d3ab..0000000
--- a/content/renderer/media/codec_factory_mojo.cc
+++ /dev/null
@@ -1,98 +0,0 @@
-// Copyright 2022 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "content/renderer/media/codec_factory_mojo.h"
-
-#include "base/functional/bind.h"
-#include "base/location.h"
-#include "base/synchronization/lock.h"
-#include "content/renderer/media/codec_factory.h"
-#include "media/base/overlay_info.h"
-#include "media/mojo/clients/mojo_video_decoder.h"
-#include "media/mojo/mojom/interface_factory.mojom.h"
-#include "media/video/gpu_video_accelerator_factories.h"
-#include "mojo/public/cpp/bindings/pending_remote.h"
-
-namespace content {
-
-CodecFactoryMojo::CodecFactoryMojo(
-    scoped_refptr<base::SequencedTaskRunner> media_task_runner,
-    scoped_refptr<viz::ContextProviderCommandBuffer> context_provider,
-    bool video_decode_accelerator_enabled,
-    bool video_encode_accelerator_enabled,
-    mojo::PendingRemote<media::mojom::VideoEncodeAcceleratorProvider>
-        pending_vea_provider_remote,
-    mojo::PendingRemote<media::mojom::InterfaceFactory>
-        pending_interface_factory_remote)
-    : CodecFactory(std::move(media_task_runner),
-                   std::move(context_provider),
-                   video_decode_accelerator_enabled,
-                   video_encode_accelerator_enabled,
-                   std::move(pending_vea_provider_remote)) {
-  media_task_runner_->PostTask(
-      FROM_HERE, base::BindOnce(&CodecFactoryMojo::BindOnTaskRunner,
-                                base::Unretained(this),
-                                std::move(pending_interface_factory_remote)));
-}
-CodecFactoryMojo::~CodecFactoryMojo() = default;
-
-std::unique_ptr<media::VideoDecoder> CodecFactoryMojo::CreateVideoDecoder(
-    media::GpuVideoAcceleratorFactories* gpu_factories,
-    media::MediaLog* media_log,
-    media::RequestOverlayInfoCB request_overlay_info_cb,
-    const gfx::ColorSpace& rendering_color_space) {
-  DCHECK(video_decode_accelerator_enabled_);
-  DCHECK(interface_factory_.is_bound());
-
-  mojo::PendingRemote<media::mojom::VideoDecoder> video_decoder;
-  interface_factory_->CreateVideoDecoder(
-      video_decoder.InitWithNewPipeAndPassReceiver(), /*dst_video_decoder=*/{});
-  return std::make_unique<media::MojoVideoDecoder>(
-      media_task_runner_, gpu_factories, media_log, std::move(video_decoder),
-      std::move(request_overlay_info_cb), rendering_color_space);
-}
-
-void CodecFactoryMojo::BindOnTaskRunner(
-    mojo::PendingRemote<media::mojom::InterfaceFactory>
-        interface_factory_remote) {
-  DCHECK(media_task_runner_->RunsTasksInCurrentSequence());
-  DCHECK(context_provider_);
-
-  interface_factory_.Bind(std::move(interface_factory_remote));
-
-  if (!video_decode_accelerator_enabled_) {
-    OnDecoderSupportFailed();
-    return;
-  }
-
-  // Note: This is a bit of a hack, since we don't specify the implementation
-  // before asking for the map of supported configs.  We do this because it
-  // (a) saves an ipc call, and (b) makes the return of those configs atomic.
-  interface_factory_->CreateVideoDecoder(
-      video_decoder_.BindNewPipeAndPassReceiver(), /*dst_video_decoder=*/{});
-  // The remote might be disconnected if the decoding process crashes, for
-  // example a GPU driver failure. Set a disconnect handler to watch these
-  // types of failures and treat them as if there are no supported decoder
-  // configs.
-  // Unretained is safe since CodecFactory is never destroyed.
-  // It lives until the process shuts down.
-  video_decoder_.set_disconnect_handler(base::BindOnce(
-      &CodecFactoryMojo::OnDecoderSupportFailed, base::Unretained(this)));
-  video_decoder_->GetSupportedConfigs(base::BindOnce(
-      &CodecFactoryMojo::OnGetSupportedDecoderConfigs, base::Unretained(this)));
-}
-
-void CodecFactoryMojo::OnGetSupportedDecoderConfigs(
-    const media::SupportedVideoDecoderConfigs& supported_configs,
-    media::VideoDecoderType decoder_type) {
-  {
-    base::AutoLock lock(supported_profiles_lock_);
-    video_decoder_.reset();
-    supported_decoder_configs_.emplace(supported_configs);
-    video_decoder_type_ = decoder_type;
-  }
-  CodecFactory::OnGetSupportedDecoderConfigs();
-}
-
-}  // namespace content
diff --git a/content/renderer/media/codec_factory_mojo.h b/content/renderer/media/codec_factory_mojo.h
deleted file mode 100644
index ff371a6..0000000
--- a/content/renderer/media/codec_factory_mojo.h
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright 2022 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CONTENT_RENDERER_MEDIA_CODEC_FACTORY_MOJO_H_
-#define CONTENT_RENDERER_MEDIA_CODEC_FACTORY_MOJO_H_
-
-#include <memory>
-
-#include "content/common/content_export.h"
-#include "content/renderer/media/codec_factory.h"
-#include "media/base/decoder.h"
-#include "media/base/overlay_info.h"
-#include "media/base/video_decoder.h"
-#include "media/mojo/mojom/interface_factory.mojom.h"
-#include "media/video/gpu_video_accelerator_factories.h"
-#include "mojo/public/cpp/bindings/remote.h"
-
-namespace content {
-
-// CodecFactoryMojo gets hardware decoder resources
-// via media::mojom::InterfaceFactory. Use it when mojo-based video decoder is
-// enabled.
-class CONTENT_EXPORT CodecFactoryMojo final : public CodecFactory {
- public:
-  CodecFactoryMojo(
-      scoped_refptr<base::SequencedTaskRunner> media_task_runner,
-      scoped_refptr<viz::ContextProviderCommandBuffer> context_provider,
-      bool video_decode_accelerator_enabled,
-      bool video_encode_accelerator_enabled,
-      mojo::PendingRemote<media::mojom::VideoEncodeAcceleratorProvider>
-          pending_vea_provider_remote,
-      mojo::PendingRemote<media::mojom::InterfaceFactory>
-          pending_interface_factory_remote);
-  ~CodecFactoryMojo() override;
-
-  std::unique_ptr<media::VideoDecoder> CreateVideoDecoder(
-      media::GpuVideoAcceleratorFactories* gpu_factories,
-      media::MediaLog* media_log,
-      media::RequestOverlayInfoCB request_overlay_info_cb,
-      const gfx::ColorSpace& rendering_color_space) override;
-
- private:
-  void BindOnTaskRunner(
-      mojo::PendingRemote<media::mojom::InterfaceFactory> interface_factory);
-  void OnGetSupportedDecoderConfigs(
-      const media::SupportedVideoDecoderConfigs& supported_configs,
-      media::VideoDecoderType decoder_type);
-
-  mojo::Remote<media::mojom::InterfaceFactory> interface_factory_;
-  mojo::Remote<media::mojom::VideoDecoder> video_decoder_;
-};
-
-}  // namespace content
-
-#endif  // CONTENT_RENDERER_MEDIA_CODEC_FACTORY_MOJO_H_
diff --git a/content/renderer/media/gpu/gpu_video_accelerator_factories_impl.cc b/content/renderer/media/gpu/gpu_video_accelerator_factories_impl.cc
index c6022f1..d2bfea3 100644
--- a/content/renderer/media/gpu/gpu_video_accelerator_factories_impl.cc
+++ b/content/renderer/media/gpu/gpu_video_accelerator_factories_impl.cc
@@ -6,10 +6,8 @@
 
 #include <GLES2/gl2.h>
 #include <GLES2/gl2ext.h>
-#include <memory>
-#include <optional>
 
-#include "base/functional/bind.h"
+#include "base/bind.h"
 #include "base/memory/ptr_util.h"
 #include "base/memory/unsafe_shared_memory_region.h"
 #include "base/metrics/histogram_macros.h"
@@ -17,15 +15,22 @@
 #include "base/unguessable_token.h"
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
-#include "content/renderer/media/codec_factory.h"
+#include "components/viz/common/gpu/context_provider.h"
+#include "content/child/child_thread_impl.h"
+#include "content/public/common/content_features.h"
 #include "content/renderer/render_thread_impl.h"
+#include "gpu/command_buffer/client/context_support.h"
+#include "gpu/command_buffer/client/gles2_interface.h"
 #include "gpu/command_buffer/client/gpu_memory_buffer_manager.h"
 #include "gpu/command_buffer/common/gpu_memory_buffer_support.h"
 #include "gpu/ipc/client/command_buffer_proxy_impl.h"
 #include "gpu/ipc/client/gpu_channel_host.h"
-#include "media/base/decoder.h"
-#include "media/base/supported_video_decoder_config.h"
+#include "gpu/ipc/common/gpu_memory_buffer_support.h"
+#include "media/base/bind_to_current_loop.h"
+#include "media/gpu/gpu_video_accelerator_util.h"
 #include "media/mojo/buildflags.h"
+#include "media/mojo/clients/mojo_video_decoder.h"
+#include "media/mojo/clients/mojo_video_encode_accelerator.h"
 #include "media/video/video_encode_accelerator.h"
 #include "services/viz/public/cpp/gpu/context_provider_command_buffer.h"
 #include "third_party/skia/include/core/SkTypes.h"
@@ -48,6 +53,26 @@
 
 }  // namespace
 
+GpuVideoAcceleratorFactoriesImpl::Notifier::Notifier() = default;
+GpuVideoAcceleratorFactoriesImpl::Notifier::~Notifier() = default;
+
+void GpuVideoAcceleratorFactoriesImpl::Notifier::Register(
+    base::OnceClosure callback) {
+  if (is_notified_) {
+    std::move(callback).Run();
+    return;
+  }
+  callbacks_.push_back(std::move(callback));
+}
+
+void GpuVideoAcceleratorFactoriesImpl::Notifier::Notify() {
+  DCHECK(!is_notified_);
+  is_notified_ = true;
+  for (auto& callback : callbacks_)
+    std::move(callback).Run();
+  callbacks_.clear();
+}
+
 // static
 std::unique_ptr<GpuVideoAcceleratorFactoriesImpl>
 GpuVideoAcceleratorFactoriesImpl::Create(
@@ -55,40 +80,22 @@
     const scoped_refptr<base::SequencedTaskRunner>& main_thread_task_runner,
     const scoped_refptr<base::SequencedTaskRunner>& task_runner,
     const scoped_refptr<viz::ContextProviderCommandBuffer>& context_provider,
-    std::unique_ptr<CodecFactory> codec_factory,
     bool enable_video_gpu_memory_buffers,
     bool enable_media_stream_gpu_memory_buffers,
     bool enable_video_decode_accelerator,
-    bool enable_video_encode_accelerator) {
+    bool enable_video_encode_accelerator,
+    mojo::PendingRemote<media::mojom::InterfaceFactory>
+        interface_factory_remote,
+    mojo::PendingRemote<media::mojom::VideoEncodeAcceleratorProvider>
+        vea_provider_remote) {
   RecordContextProviderPhaseUmaEnum(
       ContextProviderPhase::CONTEXT_PROVIDER_ACQUIRED);
   return base::WrapUnique(new GpuVideoAcceleratorFactoriesImpl(
       std::move(gpu_channel_host), main_thread_task_runner, task_runner,
-      std::move(context_provider), std::move(codec_factory),
-      RenderThreadImpl::current()->GetGpuMemoryBufferManager(),
-      enable_video_gpu_memory_buffers, enable_media_stream_gpu_memory_buffers,
-      enable_video_decode_accelerator, enable_video_encode_accelerator));
-}
-
-// static
-std::unique_ptr<GpuVideoAcceleratorFactoriesImpl>
-GpuVideoAcceleratorFactoriesImpl::CreateForTesting(
-    scoped_refptr<gpu::GpuChannelHost> gpu_channel_host,
-    const scoped_refptr<base::SequencedTaskRunner>& main_thread_task_runner,
-    const scoped_refptr<base::SequencedTaskRunner>& task_runner,
-    const scoped_refptr<viz::ContextProviderCommandBuffer>& context_provider,
-    std::unique_ptr<CodecFactory> codec_factory,
-    gpu::GpuMemoryBufferManager* gpu_memory_buffer_manager,
-    bool enable_video_gpu_memory_buffers,
-    bool enable_media_stream_gpu_memory_buffers,
-    bool enable_video_decode_accelerator,
-    bool enable_video_encode_accelerator) {
-  return base::WrapUnique(new GpuVideoAcceleratorFactoriesImpl(
-      std::move(gpu_channel_host), main_thread_task_runner, task_runner,
-      std::move(context_provider), std::move(codec_factory),
-      std::move(gpu_memory_buffer_manager), enable_video_gpu_memory_buffers,
+      context_provider, enable_video_gpu_memory_buffers,
       enable_media_stream_gpu_memory_buffers, enable_video_decode_accelerator,
-      enable_video_encode_accelerator));
+      enable_video_encode_accelerator, std::move(interface_factory_remote),
+      std::move(vea_provider_remote)));
 }
 
 GpuVideoAcceleratorFactoriesImpl::GpuVideoAcceleratorFactoriesImpl(
@@ -96,40 +103,53 @@
     const scoped_refptr<base::SequencedTaskRunner>& main_thread_task_runner,
     const scoped_refptr<base::SequencedTaskRunner>& task_runner,
     const scoped_refptr<viz::ContextProviderCommandBuffer>& context_provider,
-    std::unique_ptr<CodecFactory> codec_factory,
-    gpu::GpuMemoryBufferManager* gpu_memory_buffer_manager,
     bool enable_video_gpu_memory_buffers,
     bool enable_media_stream_gpu_memory_buffers,
     bool enable_video_decode_accelerator,
-    bool enable_video_encode_accelerator)
+    bool enable_video_encode_accelerator,
+    mojo::PendingRemote<media::mojom::InterfaceFactory>
+        interface_factory_remote,
+    mojo::PendingRemote<media::mojom::VideoEncodeAcceleratorProvider>
+        vea_provider_remote)
     : main_thread_task_runner_(main_thread_task_runner),
       task_runner_(task_runner),
       gpu_channel_host_(std::move(gpu_channel_host)),
-      codec_factory_(std::move(codec_factory)),
       context_provider_(context_provider),
       enable_video_gpu_memory_buffers_(enable_video_gpu_memory_buffers),
       enable_media_stream_gpu_memory_buffers_(
           enable_media_stream_gpu_memory_buffers),
       video_decode_accelerator_enabled_(enable_video_decode_accelerator),
       video_encode_accelerator_enabled_(enable_video_encode_accelerator),
-      gpu_memory_buffer_manager_(gpu_memory_buffer_manager) {
+      gpu_memory_buffer_manager_(
+          RenderThreadImpl::current()->GetGpuMemoryBufferManager()) {
   DCHECK(main_thread_task_runner_);
   DCHECK(gpu_channel_host_);
 
   task_runner_->PostTask(
       FROM_HERE,
       base::BindOnce(&GpuVideoAcceleratorFactoriesImpl::BindOnTaskRunner,
-                     base::Unretained(this)));
+                     base::Unretained(this),
+                     std::move(interface_factory_remote),
+                     std::move(vea_provider_remote)));
 }
 
 GpuVideoAcceleratorFactoriesImpl::~GpuVideoAcceleratorFactoriesImpl() {}
 
-void GpuVideoAcceleratorFactoriesImpl::BindOnTaskRunner() {
+void GpuVideoAcceleratorFactoriesImpl::BindOnTaskRunner(
+    mojo::PendingRemote<media::mojom::InterfaceFactory>
+        interface_factory_remote,
+    mojo::PendingRemote<media::mojom::VideoEncodeAcceleratorProvider>
+        vea_provider_remote) {
   DCHECK(task_runner_->RunsTasksInCurrentSequence());
   DCHECK(context_provider_);
 
+  interface_factory_.Bind(std::move(interface_factory_remote));
+  vea_provider_.Bind(std::move(vea_provider_remote));
+
   if (context_provider_->BindToCurrentSequence() !=
       gpu::ContextResult::kSuccess) {
+    OnDecoderSupportFailed();
+    OnEncoderSupportFailed();
     OnContextLost();
     return;
   }
@@ -140,24 +160,98 @@
   context_provider_->GetCommandBufferProxy()->GetGpuChannel().GetChannelToken(
       base::BindOnce(&GpuVideoAcceleratorFactoriesImpl::OnChannelTokenReady,
                      base::Unretained(this)));
+
+  if (video_encode_accelerator_enabled_) {
+    vea_provider_.set_disconnect_handler(base::BindOnce(
+        &GpuVideoAcceleratorFactoriesImpl::OnEncoderSupportFailed,
+        base::Unretained(this)));
+    vea_provider_->GetVideoEncodeAcceleratorSupportedProfiles(
+        base::BindOnce(&GpuVideoAcceleratorFactoriesImpl::
+                           OnGetVideoEncodeAcceleratorSupportedProfiles,
+                       base::Unretained(this)));
+  } else {
+    OnEncoderSupportFailed();
+  }
+
+#if BUILDFLAG(ENABLE_MOJO_VIDEO_DECODER)
+  if (video_decode_accelerator_enabled_) {
+    // Note: This is a bit of a hack, since we don't specify the implementation
+    // before asking for the map of supported configs.  We do this because it
+    // (a) saves an ipc call, and (b) makes the return of those configs atomic.
+    interface_factory_->CreateVideoDecoder(
+        video_decoder_.BindNewPipeAndPassReceiver(), /*dst_video_decoder=*/{});
+    video_decoder_.set_disconnect_handler(base::BindOnce(
+        &GpuVideoAcceleratorFactoriesImpl::OnDecoderSupportFailed,
+        base::Unretained(this)));
+    video_decoder_->GetSupportedConfigs(base::BindOnce(
+        &GpuVideoAcceleratorFactoriesImpl::OnSupportedDecoderConfigs,
+        base::Unretained(this)));
+  } else {
+    OnDecoderSupportFailed();
+  }
+#else
+  OnDecoderSupportFailed();
+#endif  // BUILDFLAG(ENABLE_MOJO_VIDEO_DECODER)
 }
 
 bool GpuVideoAcceleratorFactoriesImpl::IsDecoderSupportKnown() {
-  return codec_factory_->IsDecoderSupportKnown();
+  base::AutoLock lock(supported_profiles_lock_);
+  return decoder_support_notifier_.is_notified();
 }
 
 void GpuVideoAcceleratorFactoriesImpl::NotifyDecoderSupportKnown(
     base::OnceClosure callback) {
-  codec_factory_->NotifyDecoderSupportKnown(std::move(callback));
+  base::AutoLock lock(supported_profiles_lock_);
+  decoder_support_notifier_.Register(
+      media::BindToCurrentLoop(std::move(callback)));
+}
+
+void GpuVideoAcceleratorFactoriesImpl::OnSupportedDecoderConfigs(
+    const media::SupportedVideoDecoderConfigs& supported_configs,
+    media::VideoDecoderType decoder_type) {
+  base::AutoLock lock(supported_profiles_lock_);
+  video_decoder_.reset();
+  supported_decoder_configs_ = supported_configs;
+  video_decoder_type_ = decoder_type;
+  decoder_support_notifier_.Notify();
+}
+
+void GpuVideoAcceleratorFactoriesImpl::OnDecoderSupportFailed() {
+  base::AutoLock lock(supported_profiles_lock_);
+  video_decoder_.reset();
+  if (decoder_support_notifier_.is_notified())
+    return;
+  supported_decoder_configs_ = media::SupportedVideoDecoderConfigs();
+  decoder_support_notifier_.Notify();
 }
 
 bool GpuVideoAcceleratorFactoriesImpl::IsEncoderSupportKnown() {
-  return codec_factory_->IsEncoderSupportKnown();
+  base::AutoLock lock(supported_profiles_lock_);
+  return encoder_support_notifier_.is_notified();
 }
 
 void GpuVideoAcceleratorFactoriesImpl::NotifyEncoderSupportKnown(
     base::OnceClosure callback) {
-  codec_factory_->NotifyEncoderSupportKnown(std::move(callback));
+  base::AutoLock lock(supported_profiles_lock_);
+  encoder_support_notifier_.Register(
+      media::BindToCurrentLoop(std::move(callback)));
+}
+
+void GpuVideoAcceleratorFactoriesImpl::
+    OnGetVideoEncodeAcceleratorSupportedProfiles(
+        const media::VideoEncodeAccelerator::SupportedProfiles&
+            supported_profiles) {
+  base::AutoLock lock(supported_profiles_lock_);
+  supported_vea_profiles_ = supported_profiles;
+  encoder_support_notifier_.Notify();
+}
+
+void GpuVideoAcceleratorFactoriesImpl::OnEncoderSupportFailed() {
+  base::AutoLock lock(supported_profiles_lock_);
+  if (encoder_support_notifier_.is_notified())
+    return;
+  supported_vea_profiles_ = media::VideoEncodeAccelerator::SupportedProfiles();
+  encoder_support_notifier_.Notify();
 }
 
 bool GpuVideoAcceleratorFactoriesImpl::CheckContextLost() {
@@ -236,14 +330,16 @@
     return Supported::kFalse;
   }
 
-  auto supported_decoder_configs =
-      codec_factory_->GetSupportedVideoDecoderConfigs();
-  if (!supported_decoder_configs) {
+  base::AutoLock lock(supported_profiles_lock_);
+
+  // If GetSupportedConfigs() has not completed (or was never started), report
+  // that all configs are supported. Clients will find out that configs are not
+  // supported when VideoDecoder::Initialize() fails.
+  if (!supported_decoder_configs_)
     return Supported::kUnknown;
-  }
 
   // Iterate over the supported configs.
-  for (const auto& supported : *supported_decoder_configs) {
+  for (const auto& supported : *supported_decoder_configs_) {
     if (supported.Matches(config))
       return Supported::kTrue;
   }
@@ -251,7 +347,8 @@
 }
 
 media::VideoDecoderType GpuVideoAcceleratorFactoriesImpl::GetDecoderType() {
-  return codec_factory_->GetVideoDecoderType();
+  base::AutoLock lock(supported_profiles_lock_);
+  return video_decoder_type_;
 }
 
 std::unique_ptr<media::VideoDecoder>
@@ -260,21 +357,50 @@
     media::RequestOverlayInfoCB request_overlay_info_cb) {
   DCHECK(video_decode_accelerator_enabled_);
   DCHECK(task_runner_->RunsTasksInCurrentSequence());
+  DCHECK(interface_factory_.is_bound());
+
+#if BUILDFLAG(ENABLE_MOJO_VIDEO_DECODER)
   if (CheckContextLost())
     return nullptr;
 
-  return codec_factory_->CreateVideoDecoder(
-      this, media_log, request_overlay_info_cb, rendering_color_space_);
+  mojo::PendingRemote<media::mojom::VideoDecoder> video_decoder;
+  interface_factory_->CreateVideoDecoder(
+      video_decoder.InitWithNewPipeAndPassReceiver(), /*dst_video_decoder=*/{});
+  return std::make_unique<media::MojoVideoDecoder>(
+      task_runner_, this, media_log, std::move(video_decoder),
+      std::move(request_overlay_info_cb), rendering_color_space_);
+#else
+  return nullptr;
+#endif  // BUILDFLAG(ENABLE_MOJO_VIDEO_DECODER)
 }
 
 std::unique_ptr<media::VideoEncodeAccelerator>
 GpuVideoAcceleratorFactoriesImpl::CreateVideoEncodeAccelerator() {
   DCHECK(video_encode_accelerator_enabled_);
   DCHECK(task_runner_->RunsTasksInCurrentSequence());
+  DCHECK(vea_provider_.is_bound());
   if (CheckContextLost())
     return nullptr;
 
-  return codec_factory_->CreateVideoEncodeAccelerator();
+  base::AutoLock lock(supported_profiles_lock_);
+  // When |supported_vea_profiles_| is empty, no hw encoder is available or
+  // we have not yet gotten the supported profiles.
+  if (!supported_vea_profiles_) {
+    DVLOG(2) << "VEA's profiles have not yet been gotten";
+  } else if (supported_vea_profiles_->empty()) {
+    // There is no profile supported by VEA.
+    return nullptr;
+  }
+
+  mojo::PendingRemote<media::mojom::VideoEncodeAccelerator> vea;
+  vea_provider_->CreateVideoEncodeAccelerator(
+      vea.InitWithNewPipeAndPassReceiver());
+
+  if (!vea)
+    return nullptr;
+
+  return std::unique_ptr<media::VideoEncodeAccelerator>(
+      new media::MojoVideoEncodeAccelerator(std::move(vea)));
 }
 
 std::unique_ptr<gfx::GpuMemoryBuffer>
@@ -396,7 +522,8 @@
 
 absl::optional<media::VideoEncodeAccelerator::SupportedProfiles>
 GpuVideoAcceleratorFactoriesImpl::GetVideoEncodeAcceleratorSupportedProfiles() {
-  return codec_factory_->GetVideoEncodeAcceleratorSupportedProfiles();
+  base::AutoLock lock(supported_profiles_lock_);
+  return supported_vea_profiles_;
 }
 
 viz::RasterContextProvider*
diff --git a/content/renderer/media/gpu/gpu_video_accelerator_factories_impl.h b/content/renderer/media/gpu/gpu_video_accelerator_factories_impl.h
index bf8aa6e..99bd984 100644
--- a/content/renderer/media/gpu/gpu_video_accelerator_factories_impl.h
+++ b/content/renderer/media/gpu/gpu_video_accelerator_factories_impl.h
@@ -12,19 +12,20 @@
 #include <vector>
 
 #include "base/callback_list.h"
-#include "base/memory/scoped_refptr.h"
+#include "base/memory/ref_counted.h"
+#include "base/synchronization/lock.h"
+#include "base/synchronization/waitable_event.h"
 #include "base/unguessable_token.h"
-#include "build/build_config.h"
 #include "components/viz/common/gpu/context_lost_observer.h"
-#include "content/common/content_export.h"
-#include "content/renderer/media/codec_factory.h"
-#include "media/mojo/buildflags.h"
+#include "media/base/supported_video_decoder_config.h"
+#include "media/mojo/mojom/interface_factory.mojom.h"
+#include "media/mojo/mojom/video_decoder.mojom.h"
+#include "media/mojo/mojom/video_encode_accelerator.mojom.h"
 #include "media/video/gpu_video_accelerator_factories.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/remote.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
-
-#if BUILDFLAG(IS_FUCHSIA)
-#include <fuchsia/mediacodec/cpp/fidl.h>
-#endif
+#include "ui/gfx/geometry/size.h"
 
 namespace base {
 class SequencedTaskRunner;
@@ -51,7 +52,7 @@
 // the |task_runner_|, as provided during construction.
 // |context_provider| should not support locking and will be bound to
 // |task_runner_| where all the operations on the context should also happen.
-class CONTENT_EXPORT GpuVideoAcceleratorFactoriesImpl
+class GpuVideoAcceleratorFactoriesImpl
     : public media::GpuVideoAcceleratorFactories,
       public viz::ContextLostObserver {
  public:
@@ -62,22 +63,14 @@
       const scoped_refptr<base::SequencedTaskRunner>& main_thread_task_runner,
       const scoped_refptr<base::SequencedTaskRunner>& task_runner,
       const scoped_refptr<viz::ContextProviderCommandBuffer>& context_provider,
-      std::unique_ptr<CodecFactory> codec_factory,
       bool enable_video_gpu_memory_buffers,
       bool enable_media_stream_gpu_memory_buffers,
       bool enable_video_decode_accelerator,
-      bool enable_video_encode_accelerator);
-  static std::unique_ptr<GpuVideoAcceleratorFactoriesImpl> CreateForTesting(
-      scoped_refptr<gpu::GpuChannelHost> gpu_channel_host,
-      const scoped_refptr<base::SequencedTaskRunner>& main_thread_task_runner,
-      const scoped_refptr<base::SequencedTaskRunner>& task_runner,
-      const scoped_refptr<viz::ContextProviderCommandBuffer>& context_provider,
-      std::unique_ptr<CodecFactory> codec_factory,
-      gpu::GpuMemoryBufferManager* gpu_memory_buffer_manager,
-      bool enable_video_gpu_memory_buffers,
-      bool enable_media_stream_gpu_memory_buffers,
-      bool enable_video_decode_accelerator,
-      bool enable_video_encode_accelerator);
+      bool enable_video_encode_accelerator,
+      mojo::PendingRemote<media::mojom::InterfaceFactory>
+          interface_factory_remote,
+      mojo::PendingRemote<media::mojom::VideoEncodeAcceleratorProvider>
+          vea_provider_remote);
 
   // media::GpuVideoAcceleratorFactories implementation.
   bool IsGpuVideoDecodeAcceleratorEnabled() override;
@@ -147,32 +140,60 @@
   ~GpuVideoAcceleratorFactoriesImpl() override;
 
  private:
+  class Notifier {
+   public:
+    Notifier();
+    ~Notifier();
+
+    void Register(base::OnceClosure callback);
+    void Notify();
+
+    bool is_notified() { return is_notified_; }
+
+   private:
+    bool is_notified_ = false;
+    std::vector<base::OnceClosure> callbacks_;
+  };
+
   GpuVideoAcceleratorFactoriesImpl(
       scoped_refptr<gpu::GpuChannelHost> gpu_channel_host,
       const scoped_refptr<base::SequencedTaskRunner>& main_thread_task_runner,
       const scoped_refptr<base::SequencedTaskRunner>& task_runner,
       const scoped_refptr<viz::ContextProviderCommandBuffer>& context_provider,
-      std::unique_ptr<CodecFactory> codec_factory,
-      gpu::GpuMemoryBufferManager* gpu_memory_buffer_manager,
       bool enable_gpu_memory_buffer_video_frames_for_video,
       bool enable_gpu_memory_buffer_video_frames_for_media_stream,
       bool enable_video_decode_accelerator,
-      bool enable_video_encode_accelerator);
+      bool enable_video_encode_accelerator,
+      mojo::PendingRemote<media::mojom::InterfaceFactory>
+          interface_factory_remote,
+      mojo::PendingRemote<media::mojom::VideoEncodeAcceleratorProvider>
+          vea_provider_remote);
 
-  void BindOnTaskRunner();
+  void BindOnTaskRunner(
+      mojo::PendingRemote<media::mojom::InterfaceFactory>
+          interface_factory_remote,
+      mojo::PendingRemote<media::mojom::VideoEncodeAcceleratorProvider>
+          vea_provider_remote);
 
   // viz::ContextLostObserver implementation.
   void OnContextLost() override;
   void SetContextProviderLostOnMainThread();
 
+  void OnSupportedDecoderConfigs(
+      const media::SupportedVideoDecoderConfigs& supported_configs,
+      media::VideoDecoderType decoder_type);
+  void OnDecoderSupportFailed();
+
+  void OnGetVideoEncodeAcceleratorSupportedProfiles(
+      const media::VideoEncodeAccelerator::SupportedProfiles&
+          supported_profiles);
+  void OnEncoderSupportFailed();
   void OnChannelTokenReady(const base::UnguessableToken& token);
 
   const scoped_refptr<base::SequencedTaskRunner> main_thread_task_runner_;
   const scoped_refptr<base::SequencedTaskRunner> task_runner_;
   const scoped_refptr<gpu::GpuChannelHost> gpu_channel_host_;
 
-  const std::unique_ptr<CodecFactory> codec_factory_;
-
   // Shared pointer to a shared context provider. It is initially set on main
   // thread, but all subsequent access and destruction should happen only on the
   // media thread.
@@ -197,6 +218,27 @@
   gfx::ColorSpace rendering_color_space_;
 
   gpu::GpuMemoryBufferManager* const gpu_memory_buffer_manager_;
+
+  mojo::Remote<media::mojom::InterfaceFactory> interface_factory_;
+  mojo::Remote<media::mojom::VideoEncodeAcceleratorProvider> vea_provider_;
+
+  // SupportedDecoderConfigs state.
+  mojo::Remote<media::mojom::VideoDecoder> video_decoder_;
+
+  base::Lock supported_profiles_lock_;
+
+  // If the Optional is empty, then we have not yet gotten the configs.  If the
+  // Optional contains an empty vector, then we have gotten the result and there
+  // are no supported configs.
+  absl::optional<media::SupportedVideoDecoderConfigs> supported_decoder_configs_
+      GUARDED_BY(supported_profiles_lock_);
+  media::VideoDecoderType video_decoder_type_
+      GUARDED_BY(supported_profiles_lock_) = media::VideoDecoderType::kUnknown;
+  Notifier decoder_support_notifier_ GUARDED_BY(supported_profiles_lock_);
+
+  absl::optional<media::VideoEncodeAccelerator::SupportedProfiles>
+      supported_vea_profiles_ GUARDED_BY(supported_profiles_lock_);
+  Notifier encoder_support_notifier_ GUARDED_BY(supported_profiles_lock_);
 };
 
 }  // namespace content
diff --git a/content/renderer/media/gpu/gpu_video_accelerator_factories_impl_unittest.cc b/content/renderer/media/gpu/gpu_video_accelerator_factories_impl_unittest.cc
deleted file mode 100644
index 5b70f84..0000000
--- a/content/renderer/media/gpu/gpu_video_accelerator_factories_impl_unittest.cc
+++ /dev/null
@@ -1,685 +0,0 @@
-// Copyright 2022 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "content/renderer/media/gpu/gpu_video_accelerator_factories_impl.h"
-
-#include <GLES2/gl2.h>
-#include <cstddef>
-#include <memory>
-
-#include "base/memory/scoped_refptr.h"
-#include "base/test/gtest_util.h"
-#include "base/test/task_environment.h"
-#include "base/test/test_future.h"
-#include "build/build_config.h"
-#include "components/viz/common/gpu/context_cache_controller.h"
-#include "components/viz/common/gpu/context_lost_observer.h"
-#include "components/viz/test/test_gpu_memory_buffer_manager.h"
-#include "content/public/common/gpu_stream_constants.h"
-#include "content/renderer/media/codec_factory.h"
-#include "gpu/command_buffer/client/gles2_interface_stub.h"
-#include "gpu/command_buffer/common/capabilities.h"
-#include "gpu/command_buffer/common/context_creation_attribs.h"
-#include "gpu/command_buffer/common/context_result.h"
-#include "gpu/config/gpu_feature_info.h"
-#include "gpu/ipc/client/command_buffer_proxy_impl.h"
-#include "gpu/ipc/client/gpu_channel_host.h"
-#include "gpu/ipc/common/gpu_channel.mojom.h"
-#include "gpu/ipc/common/mock_gpu_channel.h"
-#include "media/base/decoder.h"
-#include "media/base/media_util.h"
-#include "media/base/supported_video_decoder_config.h"
-#include "media/base/video_codecs.h"
-#include "media/base/video_decoder_config.h"
-#include "media/mojo/buildflags.h"
-#include "media/mojo/mojom/video_encode_accelerator.mojom.h"
-#include "mojo/public/cpp/bindings/pending_remote.h"
-#include "mojo/public/cpp/bindings/receiver.h"
-#include "services/viz/public/cpp/gpu/context_provider_command_buffer.h"
-#include "testing/gmock/include/gmock/gmock.h"
-#include "testing/gtest/include/gtest/gtest-death-test.h"
-#include "testing/gtest/include/gtest/gtest.h"
-#include "third_party/skia/include/core/SkImage.h"
-
-#if BUILDFLAG(ENABLE_MOJO_VIDEO_DECODER)
-#include "content/renderer/media/codec_factory_mojo.h"
-#include "media/filters/fake_video_decoder.h"
-#include "media/mojo/mojom/interface_factory.mojom.h"
-#include "media/mojo/services/mojo_cdm_service_context.h"
-#include "media/mojo/services/mojo_media_client.h"
-#include "media/mojo/services/mojo_video_decoder_service.h"
-#include "mojo/public/cpp/bindings/unique_receiver_set.h"
-#endif
-
-#if BUILDFLAG(IS_FUCHSIA)
-#include "content/renderer/media/codec_factory_fuchsia.h"
-#include "media/fuchsia/mojom/fuchsia_media.mojom.h"
-#endif
-
-using ::testing::_;
-using ::testing::Invoke;
-using ::testing::NiceMock;
-using ::testing::Return;
-
-namespace content {
-
-namespace {
-
-constexpr gfx::Size kCodedSize(320, 240);
-constexpr gfx::Rect kVisibleRect(320, 240);
-constexpr gfx::Size kNaturalSize(320, 240);
-
-const media::SupportedVideoDecoderConfig kH264MaxSupportedVideoDecoderConfig =
-    media::SupportedVideoDecoderConfig(
-        media::VideoCodecProfile::H264PROFILE_MIN,
-        media::VideoCodecProfile::H264PROFILE_MAX,
-        media::kDefaultSwDecodeSizeMin,
-        media::kDefaultSwDecodeSizeMax,
-        true,
-        false);
-
-const media::VideoDecoderConfig kH264BaseConfig(
-    media::VideoCodec::kH264,
-    media::H264PROFILE_MIN,
-    media::VideoDecoderConfig::AlphaMode::kIsOpaque,
-    media::VideoColorSpace(),
-    media::kNoTransformation,
-    kCodedSize,
-    kVisibleRect,
-    kNaturalSize,
-    media::EmptyExtraData(),
-    media::EncryptionScheme::kUnencrypted);
-
-const media::VideoDecoderConfig kVP9BaseConfig(
-    media::VideoCodec::kVP9,
-    media::VP9PROFILE_MIN,
-    media::VideoDecoderConfig::AlphaMode::kIsOpaque,
-    media::VideoColorSpace(),
-    media::kNoTransformation,
-    kCodedSize,
-    kVisibleRect,
-    kNaturalSize,
-    media::EmptyExtraData(),
-    media::EncryptionScheme::kUnencrypted);
-
-}  // namespace
-
-class TestGpuChannelHost : public gpu::GpuChannelHost {
- public:
-  explicit TestGpuChannelHost(gpu::mojom::GpuChannel& gpu_channel)
-      : GpuChannelHost(0 /* channel_id */,
-                       gpu::GPUInfo(),
-                       gpu::GpuFeatureInfo(),
-                       mojo::ScopedMessagePipeHandle(
-                           mojo::MessagePipeHandle(mojo::kInvalidHandleValue))),
-        gpu_channel_(gpu_channel) {}
-
-  gpu::mojom::GpuChannel& GetGpuChannel() override { return *gpu_channel_; }
-
- protected:
-  ~TestGpuChannelHost() override = default;
-  const raw_ref<gpu::mojom::GpuChannel> gpu_channel_;
-};
-
-class MockOverlayInfoCbHandler {
- public:
-  MOCK_METHOD2(Call, void(bool, media::ProvideOverlayInfoCB));
-};
-
-class MockContextProviderCommandBuffer
-    : public viz::ContextProviderCommandBuffer {
- public:
-  MockContextProviderCommandBuffer(
-      scoped_refptr<gpu::GpuChannelHost> channel,
-      gpu::GpuMemoryBufferManager* gpu_memory_buffer_manager)
-      : viz::ContextProviderCommandBuffer(
-            std::move(channel),
-            gpu_memory_buffer_manager,
-            content::kGpuStreamIdDefault,
-            content::kGpuStreamPriorityDefault,
-            gpu::kNullSurfaceHandle,
-            GURL(),
-            false,
-            false,
-            true,
-            gpu::SharedMemoryLimits(),
-            gpu::ContextCreationAttribs(),
-            viz::command_buffer_metrics::ContextType::FOR_TESTING) {}
-
-  MOCK_METHOD(gpu::CommandBufferProxyImpl*,
-              GetCommandBufferProxy,
-              (),
-              (override));
-
-  // ContextProvider / RasterContextProvider implementation.
-  MOCK_METHOD(gpu::ContextResult, BindToCurrentSequence, (), (override));
-  MOCK_METHOD(gpu::gles2::GLES2Interface*, ContextGL, (), (override));
-  MOCK_METHOD(gpu::raster::RasterInterface*, RasterInterface, (), (override));
-  MOCK_METHOD(gpu::ContextSupport*, ContextSupport, (), (override));
-  MOCK_METHOD(class GrDirectContext*, GrContext, (), (override));
-  MOCK_METHOD(gpu::SharedImageInterface*, SharedImageInterface, (), (override));
-  MOCK_METHOD(viz::ContextCacheController*, CacheController, (), (override));
-  MOCK_METHOD(base::Lock*, GetLock, (), (override));
-  MOCK_METHOD(gpu::Capabilities&, ContextCapabilities, (), (const, override));
-  MOCK_METHOD(gpu::GpuFeatureInfo&, GetGpuFeatureInfo, (), (const, override));
-  MOCK_METHOD(void, AddObserver, (viz::ContextLostObserver*), (override));
-  MOCK_METHOD(void, RemoveObserver, (viz::ContextLostObserver*), (override));
-
-  // base::trace_event::MemoryDumpProvider implementation.
-  MOCK_METHOD(bool,
-              OnMemoryDump,
-              (const base::trace_event::MemoryDumpArgs&,
-               base::trace_event::ProcessMemoryDump*),
-              (override));
-
- protected:
-  ~MockContextProviderCommandBuffer() override = default;
-};
-
-class MockGLESInterface : public gpu::gles2::GLES2InterfaceStub {
- public:
-  MOCK_METHOD(GLenum, GetGraphicsResetStatusKHR, ());
-};
-
-class FakeVEAProviderImpl
-    : public media::mojom::VideoEncodeAcceleratorProvider {
- public:
-  ~FakeVEAProviderImpl() override = default;
-
-  void Bind(mojo::PendingReceiver<media::mojom::VideoEncodeAcceleratorProvider>
-                receiver) {
-    receiver_.Bind(std::move(receiver));
-  }
-
-  void SetVideoEncodeAcceleratorSupportedProfiles(
-      std::vector<media::VideoEncodeAccelerator::SupportedProfile>
-          supported_profile) {
-    supported_profile_ = supported_profile;
-  }
-  // media::mojom::VideoEncodeAcceleratorProvider impl.
-  void CreateVideoEncodeAccelerator(
-      mojo::PendingReceiver<media::mojom::VideoEncodeAccelerator> receiver)
-      override {}
-  void GetVideoEncodeAcceleratorSupportedProfiles(
-      GetVideoEncodeAcceleratorSupportedProfilesCallback callback) override {
-    std::move(callback).Run(supported_profile_);
-  }
-
- private:
-  mojo::Receiver<media::mojom::VideoEncodeAcceleratorProvider> receiver_{this};
-  std::vector<media::VideoEncodeAccelerator::SupportedProfile>
-      supported_profile_;
-};
-
-#if BUILDFLAG(ENABLE_MOJO_VIDEO_DECODER)
-// Client to MojoVideoDecoderService vended by FakeInterfaceFactory. Creates a
-// FakeGpuVideoDecoder when requested.
-class FakeMojoMediaClient : public media::MojoMediaClient {
- public:
-  void SetSupportedVideoDecoderConfigs(
-      media::SupportedVideoDecoderConfigs configs) {
-    supported_video_decoder_configs_ = configs;
-  }
-
-  // MojoMediaClient implementation.
-  std::unique_ptr<media::VideoDecoder> CreateVideoDecoder(
-      scoped_refptr<base::SingleThreadTaskRunner> task_runner,
-      media::MediaLog* media_log,
-      media::mojom::CommandBufferIdPtr command_buffer_id,
-      media::RequestOverlayInfoCB request_overlay_info_cb,
-      const gfx::ColorSpace& target_color_space,
-      mojo::PendingRemote<media::stable::mojom::StableVideoDecoder>
-          oop_video_decoder) override {
-    return std::make_unique<media::FakeVideoDecoder>(
-        0 /* decoder_id */, 0 /* decoding_delay */,
-        13 /* max_parallel_decoding_requests */, media::BytesDecodedCB());
-  }
-  media::SupportedVideoDecoderConfigs GetSupportedVideoDecoderConfigs()
-      override {
-    return supported_video_decoder_configs_;
-  }
-  media::VideoDecoderType GetDecoderImplementationType() override {
-    return media::VideoDecoderType::kTesting;
-  }
-
- private:
-  media::SupportedVideoDecoderConfigs supported_video_decoder_configs_;
-};
-
-// Other end of remote InterfaceFactory requested by VideoDecoderBroker. Used
-// to create our (fake) media::mojom::VideoDecoder.
-class FakeInterfaceFactory : public media::mojom::InterfaceFactory {
- public:
-  FakeInterfaceFactory() = default;
-  ~FakeInterfaceFactory() override = default;
-
-  void Bind(mojo::PendingReceiver<media::mojom::InterfaceFactory> receiver) {
-    receiver_.Bind(std::move(receiver));
-    receiver_.set_disconnect_handler(base::BindOnce(
-        &FakeInterfaceFactory::OnConnectionError, base::Unretained(this)));
-  }
-
-  void SetSupportedVideoDecoderConfigs(
-      media::SupportedVideoDecoderConfigs configs) {
-    mojo_media_client_.SetSupportedVideoDecoderConfigs(configs);
-  }
-
-  // Implement this one interface from mojom::InterfaceFactory. Using the real
-  // MojoVideoDecoderService allows us to reuse buffer conversion code. The
-  // FakeMojoMediaClient will create a FakeGpuVideoDecoder.
-  void CreateVideoDecoder(
-      mojo::PendingReceiver<media::mojom::VideoDecoder> receiver,
-      mojo::PendingRemote<media::stable::mojom::StableVideoDecoder>
-          dst_video_decoder) override {
-    video_decoder_receivers_.Add(
-        std::make_unique<media::MojoVideoDecoderService>(
-            &mojo_media_client_, &cdm_service_context_,
-            mojo::PendingRemote<media::stable::mojom::StableVideoDecoder>()),
-        std::move(receiver));
-  }
-
-  // Stub out other mojom::InterfaceFactory interfaces.
-  void CreateAudioDecoder(
-      mojo::PendingReceiver<media::mojom::AudioDecoder> receiver) override {}
-  void CreateAudioEncoder(
-      mojo::PendingReceiver<media::mojom::AudioEncoder> receiver) override {}
-  void CreateDefaultRenderer(
-      const std::string& audio_device_id,
-      mojo::PendingReceiver<media::mojom::Renderer> receiver) override {}
-#if BUILDFLAG(ENABLE_CAST_RENDERER)
-  void CreateCastRenderer(
-      const base::UnguessableToken& overlay_plane_id,
-      mojo::PendingReceiver<media::mojom::Renderer> receiver) override {}
-#endif
-#if BUILDFLAG(IS_ANDROID)
-  void CreateFlingingRenderer(
-      const std::string& presentation_id,
-      mojo::PendingRemote<media::mojom::FlingingRendererClientExtension>
-          client_extension,
-      mojo::PendingReceiver<media::mojom::Renderer> receiver) override {}
-  void CreateMediaPlayerRenderer(
-      mojo::PendingRemote<media::mojom::MediaPlayerRendererClientExtension>
-          client_extension_remote,
-      mojo::PendingReceiver<media::mojom::Renderer> receiver,
-      mojo::PendingReceiver<media::mojom::MediaPlayerRendererExtension>
-          renderer_extension_receiver) override {}
-#endif  // BUILDFLAG(IS_ANDROID)
-#if BUILDFLAG(IS_WIN)
-  void CreateMediaFoundationRenderer(
-      mojo::PendingRemote<media::mojom::MediaLog> media_log_remote,
-      mojo::PendingReceiver<media::mojom::Renderer> receiver,
-      mojo::PendingReceiver<media::mojom::MediaFoundationRendererExtension>
-          renderer_extension_receiver,
-      mojo::PendingRemote<media::mojom::MediaFoundationRendererClientExtension>
-          client_extension_remote) override {}
-#endif  // BUILDFLAG(IS_WIN)
-  void CreateCdm(const media::CdmConfig& cdm_config,
-                 CreateCdmCallback callback) override {}
-
- private:
-  void OnConnectionError() { receiver_.reset(); }
-
-  media::MojoCdmServiceContext cdm_service_context_;
-  FakeMojoMediaClient mojo_media_client_;
-  mojo::Receiver<media::mojom::InterfaceFactory> receiver_{this};
-  mojo::UniqueReceiverSet<media::mojom::VideoDecoder> video_decoder_receivers_;
-};
-#endif  // BUILDFLAG(ENABLE_MOJO_VIDEO_DECODER)
-
-#if BUILDFLAG(IS_FUCHSIA)
-class FakeFuchsiaMediaCodecProvide
-    : public media::mojom::FuchsiaMediaCodecProvider {
- public:
-  ~FakeFuchsiaMediaCodecProvide() override = default;
-
-  void Bind(
-      mojo::PendingReceiver<media::mojom::FuchsiaMediaCodecProvider> receiver) {
-    receiver_.Bind(std::move(receiver));
-  }
-
-  void SetSupportedVideoDecoderConfigs(
-      media::SupportedVideoDecoderConfigs configs) {
-    supported_video_decoder_configs_ = configs;
-  }
-
-  // media::mojom::FuchsiaMediaCodecProvider implementation.
-  void CreateVideoDecoder(
-      media::VideoCodec codec,
-      media::mojom::VideoDecoderSecureMemoryMode secure_mode,
-      fidl::InterfaceRequest<fuchsia::media::StreamProcessor>
-          stream_processor_request) final {
-    ADD_FAILURE() << "Not implemented.";
-  }
-
-  void GetSupportedVideoDecoderConfigs(
-      GetSupportedVideoDecoderConfigsCallback callback) final {
-    std::move(callback).Run(supported_video_decoder_configs_);
-  }
-
- private:
-  media::SupportedVideoDecoderConfigs supported_video_decoder_configs_;
-  mojo::Receiver<media::mojom::FuchsiaMediaCodecProvider> receiver_{this};
-};
-#endif  // BUILDFLAG(IS_FUCHSIA)
-
-class GpuVideoAcceleratorFactoriesImplTest : public testing::Test {
- public:
-  GpuVideoAcceleratorFactoriesImplTest()
-      : gpu_channel_host_(
-            base::MakeRefCounted<TestGpuChannelHost>(mock_gpu_channel_)),
-        mock_context_provider_(
-            base::MakeRefCounted<NiceMock<MockContextProviderCommandBuffer>>(
-                gpu_channel_host_,
-                &gpu_memory_buffer_manager_)) {}
-  ~GpuVideoAcceleratorFactoriesImplTest() override = default;
-
-  void SetUp() override {
-    MockGpuChannel();
-    MockContextProvider();
-  }
-
-  void TearDown() override {
-    task_environment_.RunUntilIdle();
-    ASSERT_TRUE(testing::Mock::VerifyAndClear(&mock_context_provider_));
-    ASSERT_TRUE(testing::Mock::VerifyAndClear(&mock_context_gl_));
-    ASSERT_TRUE(testing::Mock::VerifyAndClear(&mock_gpu_channel_));
-    delete gpu_command_buffer_proxy_;
-    mock_context_provider_.reset();
-    gpu_channel_host_.reset();
-  }
-
-  void MockGpuChannel() {
-    // Simulate success, since we're not actually talking to the service
-    // in this test suite.
-    ON_CALL(mock_gpu_channel_, CreateCommandBuffer(_, _, _, _, _, _, _))
-        .WillByDefault(Invoke(
-            [&](gpu::mojom::CreateCommandBufferParamsPtr params,
-                int32_t routing_id, base::UnsafeSharedMemoryRegion shared_state,
-                mojo::PendingAssociatedReceiver<gpu::mojom::CommandBuffer>
-                    receiver,
-                mojo::PendingAssociatedRemote<gpu::mojom::CommandBufferClient>
-                    client,
-                gpu::ContextResult* result,
-                gpu::Capabilities* capabilities) -> bool {
-              // There's no real GpuChannel pipe for this endpoint to use, so
-              // associate it with a dedicated pipe for these tests. This
-              // allows the CommandBufferProxyImpl to make calls on its
-              // CommandBuffer endpoint.
-              receiver.EnableUnassociatedUsage();
-              *result = gpu::ContextResult::kSuccess;
-              return true;
-            }));
-  }
-
-  void MockContextProvider() {
-    ON_CALL(*mock_context_provider_, BindToCurrentSequence())
-        .WillByDefault(Return(gpu::ContextResult::kSuccess));
-    ON_CALL(mock_context_gl_, GetGraphicsResetStatusKHR())
-        .WillByDefault(Return(GL_NO_ERROR));
-    ON_CALL(*mock_context_provider_, ContextGL())
-        .WillByDefault(Return(&mock_context_gl_));
-
-    gpu_command_buffer_proxy_ = new gpu::CommandBufferProxyImpl(
-        gpu_channel_host_, &gpu_memory_buffer_manager_,
-        content::kGpuStreamIdDefault,
-        task_environment_.GetMainThreadTaskRunner());
-    gpu_command_buffer_proxy_->Initialize(
-        gpu::kNullSurfaceHandle, nullptr, content::kGpuStreamPriorityDefault,
-        gpu::ContextCreationAttribs(), GURL());
-    ON_CALL(*mock_context_provider_, GetCommandBufferProxy())
-        .WillByDefault(Return(gpu_command_buffer_proxy_));
-  }
-
-  std::unique_ptr<CodecFactory> CreateCodecFactory(
-      scoped_refptr<viz::ContextProviderCommandBuffer> context_provider,
-      bool enable_video_decode_accelerator,
-      bool enable_video_encode_accelerator) {
-    mojo::PendingRemote<media::mojom::VideoEncodeAcceleratorProvider>
-        vea_provider;
-    fake_vea_provider_.Bind(vea_provider.InitWithNewPipeAndPassReceiver());
-
-#if BUILDFLAG(ENABLE_MOJO_VIDEO_DECODER)
-    mojo::PendingRemote<media::mojom::InterfaceFactory> interface_factory;
-    fake_media_codec_provider_.Bind(
-        interface_factory.InitWithNewPipeAndPassReceiver());
-    return std::make_unique<CodecFactoryMojo>(
-        task_environment_.GetMainThreadTaskRunner(),
-        std::move(context_provider), enable_video_decode_accelerator,
-        enable_video_encode_accelerator, std::move(vea_provider),
-        std::move(interface_factory));
-#elif BUILDFLAG(IS_FUCHSIA)
-    mojo::PendingRemote<media::mojom::FuchsiaMediaCodecProvider>
-        media_codec_provider;
-    fake_media_codec_provider_.Bind(
-        media_codec_provider.InitWithNewPipeAndPassReceiver());
-    return std::make_unique<CodecFactoryFuchsia>(
-        task_environment_.GetMainThreadTaskRunner(),
-        std::move(context_provider), enable_video_decode_accelerator,
-        enable_video_encode_accelerator, std::move(vea_provider),
-        std::move(media_codec_provider));
-#else
-    return std::make_unique<CodecFactoryDefault>(
-        task_environment_.GetMainThreadTaskRunner(),
-        std::move(context_provider), enable_video_decode_accelerator,
-        enable_video_encode_accelerator, std::move(vea_provider));
-#endif
-  }
-
-  std::unique_ptr<GpuVideoAcceleratorFactoriesImpl>
-  CreateGpuVideoAcceleratorFactories(bool enable_video_decode_accelerator,
-                                     bool enable_video_encode_accelerator) {
-    std::unique_ptr<CodecFactory> codec_factory = CreateCodecFactory(
-        mock_context_provider_, enable_video_decode_accelerator,
-        enable_video_encode_accelerator);
-    auto gpu_factories = GpuVideoAcceleratorFactoriesImpl::CreateForTesting(
-        gpu_channel_host_, task_environment_.GetMainThreadTaskRunner(),
-        task_environment_.GetMainThreadTaskRunner(), mock_context_provider_,
-        std::move(codec_factory), &gpu_memory_buffer_manager_,
-        true, /* enable_video_gpu_memory_buffers */
-        true, /* enable_media_stream_gpu_memory_buffers */
-        enable_video_decode_accelerator, enable_video_encode_accelerator);
-
-    // Wait until all async IO messages (e.g. Mojo and FIDL) to be delieved
-    // and handled.
-    task_environment_.RunUntilIdle();
-
-    return gpu_factories;
-  }
-
- protected:
-  base::test::SingleThreadTaskEnvironment task_environment_{
-      base::test::SingleThreadTaskEnvironment::MainThreadType::IO};
-
-  NiceMock<gpu::MockGpuChannel> mock_gpu_channel_;
-  NiceMock<MockGLESInterface> mock_context_gl_;
-  viz::TestGpuMemoryBufferManager gpu_memory_buffer_manager_;
-  scoped_refptr<TestGpuChannelHost> gpu_channel_host_;
-  scoped_refptr<MockContextProviderCommandBuffer> mock_context_provider_;
-  gpu::CommandBufferProxyImpl* gpu_command_buffer_proxy_;
-
-  FakeVEAProviderImpl fake_vea_provider_;
-
-#if BUILDFLAG(ENABLE_MOJO_VIDEO_DECODER)
-  FakeInterfaceFactory fake_media_codec_provider_;
-#elif BUILDFLAG(IS_FUCHSIA)
-  FakeFuchsiaMediaCodecProvide fake_media_codec_provider_;
-#endif
-};
-
-TEST_F(GpuVideoAcceleratorFactoriesImplTest, VideoDecoderAcceleratorDisabled) {
-  auto gpu_video_accelerator_factories =
-      CreateGpuVideoAcceleratorFactories(false, false);
-
-  EXPECT_FALSE(
-      gpu_video_accelerator_factories->IsGpuVideoDecodeAcceleratorEnabled());
-  EXPECT_TRUE(gpu_video_accelerator_factories->IsDecoderSupportKnown());
-  EXPECT_EQ(gpu_video_accelerator_factories->IsDecoderConfigSupported(
-                kH264BaseConfig),
-            media::GpuVideoAcceleratorFactories::Supported::kFalse);
-  EXPECT_EQ(gpu_video_accelerator_factories->GetDecoderType(),
-            media::VideoDecoderType::kUnknown);
-
-  ASSERT_TRUE(
-      testing::Mock::VerifyAndClearExpectations(&mock_context_provider_));
-}
-
-TEST_F(GpuVideoAcceleratorFactoriesImplTest, VideoEncoderAcceleratorDisabled) {
-  auto gpu_video_accelerator_factories =
-      CreateGpuVideoAcceleratorFactories(false, false);
-
-  EXPECT_FALSE(
-      gpu_video_accelerator_factories->IsGpuVideoEncodeAcceleratorEnabled());
-  EXPECT_TRUE(gpu_video_accelerator_factories->IsEncoderSupportKnown());
-}
-
-TEST_F(GpuVideoAcceleratorFactoriesImplTest, EncoderConfigsIsSupported) {
-  fake_vea_provider_.SetVideoEncodeAcceleratorSupportedProfiles(
-      {media::VideoEncodeAccelerator::SupportedProfile(
-          media::VideoCodecProfile::VP9PROFILE_MAX, kCodedSize)});
-  auto gpu_video_accelerator_factories =
-      CreateGpuVideoAcceleratorFactories(false, true);
-
-  EXPECT_TRUE(
-      gpu_video_accelerator_factories->IsGpuVideoEncodeAcceleratorEnabled());
-  EXPECT_TRUE(gpu_video_accelerator_factories->IsEncoderSupportKnown());
-  base::test::TestFuture<void> future;
-  gpu_video_accelerator_factories->NotifyEncoderSupportKnown(
-      future.GetCallback());
-  EXPECT_TRUE(future.Wait());
-  auto supported_profiles = gpu_video_accelerator_factories
-                                ->GetVideoEncodeAcceleratorSupportedProfiles();
-  EXPECT_TRUE(supported_profiles.has_value());
-  EXPECT_EQ(supported_profiles->size(), static_cast<size_t>(1));
-}
-
-TEST_F(GpuVideoAcceleratorFactoriesImplTest, EncoderConfigsIsNotSupported) {
-  fake_vea_provider_.SetVideoEncodeAcceleratorSupportedProfiles({});
-  auto gpu_video_accelerator_factories =
-      CreateGpuVideoAcceleratorFactories(false, true);
-
-  EXPECT_TRUE(
-      gpu_video_accelerator_factories->IsGpuVideoEncodeAcceleratorEnabled());
-  EXPECT_TRUE(gpu_video_accelerator_factories->IsEncoderSupportKnown());
-  base::test::TestFuture<void> future;
-  gpu_video_accelerator_factories->NotifyEncoderSupportKnown(
-      future.GetCallback());
-  EXPECT_TRUE(future.Wait());
-  auto supported_profiles = gpu_video_accelerator_factories
-                                ->GetVideoEncodeAcceleratorSupportedProfiles();
-  EXPECT_TRUE(supported_profiles.has_value());
-  EXPECT_TRUE(supported_profiles->empty());
-}
-
-TEST_F(GpuVideoAcceleratorFactoriesImplTest, CreateVideoEncodeAccelerator) {
-  fake_vea_provider_.SetVideoEncodeAcceleratorSupportedProfiles(
-      {media::VideoEncodeAccelerator::SupportedProfile(
-          media::VideoCodecProfile::VP9PROFILE_MAX, kCodedSize)});
-  auto gpu_video_accelerator_factories =
-      CreateGpuVideoAcceleratorFactories(false, true);
-
-  EXPECT_NE(gpu_video_accelerator_factories->CreateVideoEncodeAccelerator(),
-            nullptr);
-}
-
-#ifdef GTEST_HAS_DEATH_TEST
-using GpuVideoAcceleratorFactoriesImplDeathTest =
-    GpuVideoAcceleratorFactoriesImplTest;
-
-TEST_F(GpuVideoAcceleratorFactoriesImplDeathTest,
-       CreateVideoEncodeAcceleratorFailed) {
-  auto gpu_video_accelerator_factories =
-      CreateGpuVideoAcceleratorFactories(false, false);
-
-  EXPECT_DCHECK_DEATH({
-    EXPECT_EQ(gpu_video_accelerator_factories->CreateVideoEncodeAccelerator(),
-              nullptr);
-  });
-}
-
-TEST_F(GpuVideoAcceleratorFactoriesImplDeathTest, CreateVideoDecoderFailed) {
-  testing::StrictMock<MockOverlayInfoCbHandler> cb_handler;
-  media::RequestOverlayInfoCB mock_cb = base::BindRepeating(
-      &MockOverlayInfoCbHandler::Call, base::Unretained(&cb_handler));
-  auto gpu_video_accelerator_factories =
-      CreateGpuVideoAcceleratorFactories(false, false);
-
-  EXPECT_DCHECK_DEATH({
-    EXPECT_EQ(gpu_video_accelerator_factories->CreateVideoDecoder(
-                  nullptr, std::move(mock_cb)),
-              nullptr);
-  });
-}
-#endif  // GTEST_HAS_DEATH_TEST
-
-#if BUILDFLAG(ENABLE_MOJO_VIDEO_DECODER) || BUILDFLAG(IS_FUCHSIA)
-TEST_F(GpuVideoAcceleratorFactoriesImplTest, DecoderConfigIsSupported) {
-  fake_media_codec_provider_.SetSupportedVideoDecoderConfigs(
-      {kH264MaxSupportedVideoDecoderConfig});
-
-  auto gpu_video_accelerator_factories =
-      CreateGpuVideoAcceleratorFactories(true, false);
-
-  EXPECT_TRUE(
-      gpu_video_accelerator_factories->IsGpuVideoDecodeAcceleratorEnabled());
-  EXPECT_TRUE(gpu_video_accelerator_factories->IsDecoderSupportKnown());
-  base::test::TestFuture<void> future;
-  gpu_video_accelerator_factories->NotifyDecoderSupportKnown(
-      future.GetCallback());
-  EXPECT_TRUE(future.Wait());
-  EXPECT_EQ(gpu_video_accelerator_factories->IsDecoderConfigSupported(
-                kH264BaseConfig),
-            media::GpuVideoAcceleratorFactories::Supported::kTrue);
-  EXPECT_NE(gpu_video_accelerator_factories->GetDecoderType(),
-            media::VideoDecoderType::kUnknown);
-}
-
-TEST_F(GpuVideoAcceleratorFactoriesImplTest, DecoderConfigIsNotSupported) {
-  fake_media_codec_provider_.SetSupportedVideoDecoderConfigs(
-      {kH264MaxSupportedVideoDecoderConfig});
-
-  auto gpu_video_accelerator_factories =
-      CreateGpuVideoAcceleratorFactories(true, false);
-
-  EXPECT_TRUE(
-      gpu_video_accelerator_factories->IsGpuVideoDecodeAcceleratorEnabled());
-  EXPECT_TRUE(gpu_video_accelerator_factories->IsDecoderSupportKnown());
-  base::test::TestFuture<void> future;
-  gpu_video_accelerator_factories->NotifyDecoderSupportKnown(
-      future.GetCallback());
-  EXPECT_TRUE(future.Wait());
-  EXPECT_EQ(
-      gpu_video_accelerator_factories->IsDecoderConfigSupported(kVP9BaseConfig),
-      media::GpuVideoAcceleratorFactories::Supported::kFalse);
-}
-
-TEST_F(GpuVideoAcceleratorFactoriesImplTest, CreateVideoDecoder) {
-  testing::StrictMock<MockOverlayInfoCbHandler> cb_handler;
-  media::RequestOverlayInfoCB mock_cb = base::BindRepeating(
-      &MockOverlayInfoCbHandler::Call, base::Unretained(&cb_handler));
-  auto gpu_video_accelerator_factories =
-      CreateGpuVideoAcceleratorFactories(true, false);
-
-  EXPECT_NE(gpu_video_accelerator_factories->CreateVideoDecoder(
-                nullptr, std::move(mock_cb)),
-            nullptr);
-}
-#else
-TEST_F(GpuVideoAcceleratorFactoriesImplTest, DefaultCodecFactory) {
-  auto gpu_video_accelerator_factories =
-      CreateGpuVideoAcceleratorFactories(false, false);
-
-  EXPECT_FALSE(
-      gpu_video_accelerator_factories->IsGpuVideoDecodeAcceleratorEnabled());
-  EXPECT_TRUE(gpu_video_accelerator_factories->IsDecoderSupportKnown());
-  base::test::TestFuture<void> future;
-  gpu_video_accelerator_factories->NotifyDecoderSupportKnown(
-      future.GetCallback());
-  EXPECT_TRUE(future.Wait());
-  EXPECT_EQ(gpu_video_accelerator_factories->IsDecoderConfigSupported(
-                kH264BaseConfig),
-            media::GpuVideoAcceleratorFactories::Supported::kFalse);
-}
-#endif  // BUILDFLAG(ENABLE_MOJO_VIDEO_DECODER) || BUILDFLAG(IS_FUCHSIA)
-
-}  // namespace content
diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc
index b2d9c16d..9e09c53 100644
--- a/content/renderer/render_frame_impl.cc
+++ b/content/renderer/render_frame_impl.cc
@@ -4152,6 +4152,14 @@
   }
 }
 
+void RenderFrameImpl::OnMainFrameImageAdRectangleChanged(
+    int element_id,
+    const gfx::Rect& image_ad_rect) {
+  for (auto& observer : observers_) {
+    observer.OnMainFrameImageAdRectangleChanged(element_id, image_ad_rect);
+  }
+}
+
 void RenderFrameImpl::OnOverlayPopupAdDetected() {
   for (auto& observer : observers_) {
     observer.OnOverlayPopupAdDetected();
diff --git a/content/renderer/render_frame_impl.h b/content/renderer/render_frame_impl.h
index a42b940..3b55829 100644
--- a/content/renderer/render_frame_impl.h
+++ b/content/renderer/render_frame_impl.h
@@ -575,6 +575,9 @@
       const gfx::Rect& main_frame_intersection_rect) override;
   void OnMainFrameViewportRectangleChanged(
       const gfx::Rect& main_frame_viewport_rect) override;
+  void OnMainFrameImageAdRectangleChanged(
+      int element_id,
+      const gfx::Rect& image_ad_rect) override;
   void WillSendRequest(blink::WebURLRequest& request,
                        ForRedirect for_redirect) override;
   void OnOverlayPopupAdDetected() override;
diff --git a/content/renderer/render_thread_impl.cc b/content/renderer/render_thread_impl.cc
index 8820b82..1ab2dbae 100644
--- a/content/renderer/render_thread_impl.cc
+++ b/content/renderer/render_thread_impl.cc
@@ -75,7 +75,6 @@
 #include "content/common/main_frame_counter.h"
 #include "content/common/process_visibility_tracker.h"
 #include "content/common/pseudonymization_salt.h"
-#include "content/public/common/content_client.h"
 #include "content/public/common/content_constants.h"
 #include "content/public/common/content_features.h"
 #include "content/public/common/content_paths.h"
@@ -88,7 +87,6 @@
 #include "content/renderer/agent_scheduling_group.h"
 #include "content/renderer/browser_exposed_renderer_interfaces.h"
 #include "content/renderer/effective_connection_type_helper.h"
-#include "content/renderer/media/codec_factory.h"
 #include "content/renderer/media/gpu/gpu_video_accelerator_factories_impl.h"
 #include "content/renderer/media/media_factory.h"
 #include "content/renderer/media/render_media_client.h"
@@ -122,7 +120,6 @@
 #include "media/video/gpu_video_accelerator_factories.h"
 #include "mojo/public/cpp/bindings/binder_map.h"
 #include "mojo/public/cpp/bindings/callback_helpers.h"
-#include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/self_owned_receiver.h"
 #include "mojo/public/cpp/system/message_pipe.h"
 #include "net/base/net_errors.h"
@@ -204,16 +201,6 @@
 #include <malloc.h>
 #endif
 
-#if BUILDFLAG(ENABLE_MOJO_VIDEO_DECODER)
-#include "content/renderer/media/codec_factory_mojo.h"
-#include "media/mojo/mojom/interface_factory.mojom.h"
-#endif
-
-#if BUILDFLAG(IS_FUCHSIA)
-#include "content/renderer/media/codec_factory_fuchsia.h"
-#include "media/fuchsia/mojom/fuchsia_media.mojom.h"
-#endif
-
 #if BUILDFLAG(CLANG_PROFILING_INSIDE_SANDBOX)
 #include "base/test/clang_profiling.h"
 #endif
@@ -1060,6 +1047,7 @@
        gpu::kGpuFeatureStatusEnabled);
 
   const bool enable_video_encode_accelerator =
+
 #if BUILDFLAG(IS_LINUX)
       base::FeatureList::IsEnabled(media::kVaapiVideoEncodeLinux) &&
 #else
@@ -1088,17 +1076,31 @@
        gpu_channel_host->gpu_info().overlay_info.supports_overlays);
 #endif  // BUILDFLAG(IS_WIN)
 
-  auto codec_factory = CreateMediaCodecFactory(media_context_provider,
-                                               enable_video_decode_accelerator,
-                                               enable_video_encode_accelerator);
+  mojo::PendingRemote<media::mojom::InterfaceFactory> interface_factory;
+  BindHostReceiver(interface_factory.InitWithNewPipeAndPassReceiver());
+
+  mojo::PendingRemote<media::mojom::VideoEncodeAcceleratorProvider>
+      vea_provider;
+
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
+  if (base::FeatureList::IsEnabled(media::kUseOutOfProcessVideoEncoding)) {
+    BindHostReceiver(vea_provider.InitWithNewPipeAndPassReceiver());
+  } else {
+    gpu_->CreateVideoEncodeAcceleratorProvider(
+        vea_provider.InitWithNewPipeAndPassReceiver());
+  }
+#else
+  gpu_->CreateVideoEncodeAcceleratorProvider(
+      vea_provider.InitWithNewPipeAndPassReceiver());
+#endif
+
   gpu_factories_.push_back(GpuVideoAcceleratorFactoriesImpl::Create(
       std::move(gpu_channel_host),
       base::SingleThreadTaskRunner::GetCurrentDefault(),
       GetMediaSequencedTaskRunner(), std::move(media_context_provider),
-      std::move(codec_factory), enable_video_gpu_memory_buffers,
-      enable_media_stream_gpu_memory_buffers, enable_video_decode_accelerator,
-      enable_video_encode_accelerator));
-
+      enable_video_gpu_memory_buffers, enable_media_stream_gpu_memory_buffers,
+      enable_video_decode_accelerator, enable_video_encode_accelerator,
+      std::move(interface_factory), std::move(vea_provider)));
   gpu_factories_.back()->SetRenderingColorSpace(rendering_color_space_);
   return gpu_factories_.back().get();
 }
@@ -1837,45 +1839,4 @@
   attribution_os_support_ = attribution_os_support;
 }
 
-std::unique_ptr<CodecFactory> RenderThreadImpl::CreateMediaCodecFactory(
-    scoped_refptr<viz::ContextProviderCommandBuffer> context_provider,
-    bool enable_video_decode_accelerator,
-    bool enable_video_encode_accelerator) {
-  mojo::PendingRemote<media::mojom::VideoEncodeAcceleratorProvider>
-      vea_provider;
-#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
-  if (base::FeatureList::IsEnabled(media::kUseOutOfProcessVideoEncoding)) {
-    BindHostReceiver(vea_provider.InitWithNewPipeAndPassReceiver());
-  } else {
-    gpu_->CreateVideoEncodeAcceleratorProvider(
-        vea_provider.InitWithNewPipeAndPassReceiver());
-  }
-#else
-  gpu_->CreateVideoEncodeAcceleratorProvider(
-      vea_provider.InitWithNewPipeAndPassReceiver());
-#endif
-
-#if BUILDFLAG(ENABLE_MOJO_VIDEO_DECODER)
-  mojo::PendingRemote<media::mojom::InterfaceFactory> interface_factory;
-  BindHostReceiver(interface_factory.InitWithNewPipeAndPassReceiver());
-  return std::make_unique<CodecFactoryMojo>(
-      GetMediaSequencedTaskRunner(), context_provider,
-      enable_video_decode_accelerator, enable_video_encode_accelerator,
-      std::move(vea_provider), std::move(interface_factory));
-#elif BUILDFLAG(IS_FUCHSIA)
-  mojo::PendingRemote<media::mojom::FuchsiaMediaCodecProvider>
-      media_codec_provider;
-  BindHostReceiver(media_codec_provider.InitWithNewPipeAndPassReceiver());
-  return std::make_unique<CodecFactoryFuchsia>(
-      GetMediaSequencedTaskRunner(), context_provider,
-      enable_video_decode_accelerator, enable_video_encode_accelerator,
-      std::move(vea_provider), std::move(media_codec_provider));
-#else
-  return std::make_unique<CodecFactoryDefault>(
-      GetMediaSequencedTaskRunner(), context_provider,
-      enable_video_decode_accelerator, enable_video_encode_accelerator,
-      std::move(vea_provider));
-#endif
-}
-
 }  // namespace content
diff --git a/content/renderer/render_thread_impl.h b/content/renderer/render_thread_impl.h
index d4cbe57..d3dc1320 100644
--- a/content/renderer/render_thread_impl.h
+++ b/content/renderer/render_thread_impl.h
@@ -40,7 +40,6 @@
 #include "content/common/shared_storage_worklet_service.mojom.h"
 #include "content/public/renderer/render_thread.h"
 #include "content/renderer/discardable_memory_utils.h"
-#include "content/renderer/media/codec_factory.h"
 #include "gpu/ipc/client/gpu_channel_host.h"
 #include "ipc/ipc_sync_channel.h"
 #include "media/media_buildflags.h"
@@ -468,11 +467,6 @@
   void OnRendererInterfaceReceiver(
       mojo::PendingAssociatedReceiver<mojom::Renderer> receiver);
 
-  std::unique_ptr<CodecFactory> CreateMediaCodecFactory(
-      scoped_refptr<viz::ContextProviderCommandBuffer> context_provider,
-      bool enable_video_decode_accelerator,
-      bool enable_video_encode_accelerator);
-
   scoped_refptr<discardable_memory::ClientDiscardableSharedMemoryManager>
       discardable_memory_allocator_;
 
diff --git a/content/shell/BUILD.gn b/content/shell/BUILD.gn
index 94f0429..3b7d600 100644
--- a/content/shell/BUILD.gn
+++ b/content/shell/BUILD.gn
@@ -430,6 +430,7 @@
 
   sources = [
     "$root_gen_dir/base/tracing/protos/tracing_proto_resources.pak",
+    "$root_gen_dir/content/attribution_internals_resources.pak",
     "$root_gen_dir/content/browser/resources/media/media_internals_resources.pak",
     "$root_gen_dir/content/browser/webrtc/resources/webrtc_internals_resources.pak",
     "$root_gen_dir/content/content_resources.pak",
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index 928f2a63..02e3f16 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -2585,7 +2585,6 @@
     "../renderer/accessibility/ax_image_stopwords_unittest.cc",
     "../renderer/content_security_policy_util_unittest.cc",
     "../renderer/media/batching_media_log_unittest.cc",
-    "../renderer/media/gpu/gpu_video_accelerator_factories_impl_unittest.cc",
     "../renderer/media/inspector_media_event_handler_unittest.cc",
     "../renderer/media/renderer_webaudiodevice_impl_unittest.cc",
     "../renderer/render_thread_impl_unittest.cc",
@@ -2755,7 +2754,6 @@
     "//gin",
     "//gpu",
     "//gpu:test_support",
-    "//gpu/ipc/common:test_support",
     "//gpu/ipc/host",
     "//gpu/ipc/service",
     "//ipc:test_support",
@@ -2787,7 +2785,6 @@
     "//services/video_capture/public/cpp:mocks",
     "//services/video_capture/public/mojom",
     "//services/video_capture/public/mojom:constants",
-    "//services/viz/public/cpp/gpu",
     "//skia",
     "//sql",
     "//sql:test_support",
diff --git a/content/test/data/accessibility/html/input-time-with-popup-open-expected-blink.txt b/content/test/data/accessibility/html/input-time-with-popup-open-expected-blink.txt
index 486280b..268a2e9b 100644
--- a/content/test/data/accessibility/html/input-time-with-popup-open-expected-blink.txt
+++ b/content/test/data/accessibility/html/input-time-with-popup-open-expected-blink.txt
@@ -1,7 +1,7 @@
 rootWebArea
 ++genericContainer ignored
 ++++genericContainer
-++++++inputTime inputType='time' value='13:50:02.922'
+++++++inputTime inputType='time' value='13:50:02.922' controlsIds=group
 ++++++++genericContainer
 ++++++++++genericContainer
 ++++++++++++spinButton name='Hours' placeholder='--' value='01' valueForRange=1.00 minValueForRange=1.00 maxValueForRange=12.00
@@ -28,7 +28,7 @@
 ++++++++++++++staticText name='PM'
 ++++++++++++++++inlineTextBox name='PM'
 ++++++++popUpButton name='Show time picker'
-++++++++rootWebArea
+++++++++group
 ++++++++++genericContainer ignored
 ++++++++++++genericContainer ignored
 ++++++++++++++genericContainer
diff --git a/content/test/data/accessibility/html/input-time-with-popup-open-expected-fuchsia.txt b/content/test/data/accessibility/html/input-time-with-popup-open-expected-fuchsia.txt
index 98b4b473..82e1b365 100644
--- a/content/test/data/accessibility/html/input-time-with-popup-open-expected-fuchsia.txt
+++ b/content/test/data/accessibility/html/input-time-with-popup-open-expected-fuchsia.txt
@@ -28,7 +28,7 @@
 ++++++++++++++STATIC_TEXT label='PM'
 ++++++++++++++++UNKNOWN label='PM'
 ++++++++UNKNOWN focusable label='Show time picker' actions='{DEFAULT}'
-++++++++UNKNOWN focusable
+++++++++UNKNOWN
 ++++++++++UNKNOWN hidden
 ++++++++++++UNKNOWN hidden
 ++++++++++++++UNKNOWN
diff --git a/content/test/data/accessibility/html/input-time-with-popup-open-expected-mac.txt b/content/test/data/accessibility/html/input-time-with-popup-open-expected-mac.txt
index 5bb2b4c..623f670b 100644
--- a/content/test/data/accessibility/html/input-time-with-popup-open-expected-mac.txt
+++ b/content/test/data/accessibility/html/input-time-with-popup-open-expected-mac.txt
@@ -13,7 +13,7 @@
 ++++++++++AXStaticText AXRoleDescription='text' AXValue=' '
 ++++++++++AXIncrementor AXDescription='AM/PM' AXRoleDescription='stepper' AXValue=2
 ++++++AXPopUpButton AXDescription='Show time picker' AXRoleDescription='pop up button'
-++++++AXWebArea AXRoleDescription='HTML content'
+++++++AXGroup AXSubrole=AXApplicationGroup AXRoleDescription='group'
 ++++++++AXGroup AXRoleDescription='group'
 ++++++++++AXGroup AXRoleDescription='group'
 ++++++++++++AXList AXDescription='Hours' AXRoleDescription='list box'
diff --git a/content/test/data/accessibility/html/input-time-with-popup-open-expected-uia-win.txt b/content/test/data/accessibility/html/input-time-with-popup-open-expected-uia-win.txt
index 1f399105..c63684a 100644
--- a/content/test/data/accessibility/html/input-time-with-popup-open-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/input-time-with-popup-open-expected-uia-win.txt
@@ -1,6 +1,6 @@
 Document
 ++Group IsControlElement=false
-++++Group LocalizedControlType='time picker' Value.Value='13:50:02.922'
+++++Group LocalizedControlType='time picker' ControllerFor='{group}' Value.Value='13:50:02.922'
 ++++++Group IsControlElement=false
 ++++++++Group IsControlElement=false
 ++++++++++Spinner Name='Hours' RangeValue.IsReadOnly=false RangeValue.LargeChange=0.00 RangeValue.SmallChange=0.00 RangeValue.Maximum=12.00 RangeValue.Minimum=1.00 RangeValue.Value=1.00 Value.Value='01'
@@ -14,7 +14,7 @@
 ++++++++++Spinner Name='AM/PM' RangeValue.IsReadOnly=false RangeValue.LargeChange=0.00 RangeValue.SmallChange=0.00 RangeValue.Maximum=2.00 RangeValue.Minimum=1.00 RangeValue.Value=2.00 Value.Value='PM'
 ++++++Button Name='Show time picker' ExpandCollapse.ExpandCollapseState='Collapsed'
 ++++++Pane Name='Chrome Legacy Window' IsControlElement=false
-++++++++Document
+++++++++Group IsControlElement=false
 ++++++++++Group IsControlElement=false
 ++++++++++++Group
 ++++++++++++++List Name='Hours' Selection.CanSelectMultiple=false Selection.IsSelectionRequired=false
diff --git a/content/test/data/accessibility/html/input-time-with-popup-open-expected-win.txt b/content/test/data/accessibility/html/input-time-with-popup-open-expected-win.txt
index 51e4e77..a10d029 100644
--- a/content/test/data/accessibility/html/input-time-with-popup-open-expected-win.txt
+++ b/content/test/data/accessibility/html/input-time-with-popup-open-expected-win.txt
@@ -13,7 +13,7 @@
 ++++++++++ROLE_SYSTEM_STATICTEXT name=' ' ia2_hypertext=' '
 ++++++++++ROLE_SYSTEM_SPINBUTTON name='AM/PM' value='PM' FOCUSABLE
 ++++++ROLE_SYSTEM_BUTTONMENU name='Show time picker' FOCUSABLE HASPOPUP
-++++++ROLE_SYSTEM_DOCUMENT READONLY FOCUSABLE ia2_hypertext='<obj0>'
+++++++ROLE_SYSTEM_GROUPING ia2_hypertext='<obj0>'
 ++++++++IA2_ROLE_SECTION ia2_hypertext='<obj0>'
 ++++++++++IA2_ROLE_SECTION ia2_hypertext='<obj0><obj1><obj2><obj3><obj4>'
 ++++++++++++ROLE_SYSTEM_LIST name='Hours' FOCUSABLE ia2_hypertext='<obj0><obj1><obj2><obj3><obj4><obj5><obj6><obj7><obj8><obj9><obj10><obj11>'
diff --git a/docs/autofill/chrome_payments_flows.md b/docs/autofill/chrome_payments_flows.md
index c5c4c04..99cc163 100644
--- a/docs/autofill/chrome_payments_flows.md
+++ b/docs/autofill/chrome_payments_flows.md
@@ -83,17 +83,17 @@
 
 1.  The user submits a form, triggering
     [AutofillManager::OnFormSubmitted](https://cs.chromium.org/chromium/src/components/autofill/core/browser/autofill_manager.cc?l=368&rcl=ab8d0ea46daf7673a53524a3708f0ffd1ea9ee2d).
-    If the form was autofillable, FormDataImporter::ImportFormData [is
+    If the form was autofillable, FormDataImporter::ExtractFormData [is
     called](https://cs.chromium.org/chromium/src/components/autofill/core/browser/autofill_manager.cc?l=392-393&rcl=ab8d0ea46daf7673a53524a3708f0ffd1ea9ee2d)
-2.  An inner [FormDataImporter::ImportFormData
+2.  An inner [FormDataImporter::ExtractFormData
     helper](https://cs.chromium.org/chromium/src/components/autofill/core/browser/form_data_importer.cc?l=169&rcl=ab8d0ea46daf7673a53524a3708f0ffd1ea9ee2d)
-    is called, which begins the process of importing both credit card and
+    is called, which begins the process of extracting both credit card and
     address profile information
-3.  [FormDataImporter::ImportCreditCard](https://cs.chromium.org/chromium/src/components/autofill/core/browser/form_data_importer.cc?l=307&rcl=ab8d0ea46daf7673a53524a3708f0ffd1ea9ee2d)
+3.  [FormDataImporter::ExtractCreditCard](https://cs.chromium.org/chromium/src/components/autofill/core/browser/form_data_importer.cc?l=307&rcl=ab8d0ea46daf7673a53524a3708f0ffd1ea9ee2d)
     is called which tries to detect if a new credit card was entered on the
-    form, storing it in `|imported_credit_card|` if so
-4.  [FormDataImporter::ImportAddressProfiles](https://cs.chromium.org/chromium/src/components/autofill/core/browser/form_data_importer.cc?l=196&rcl=ab8d0ea46daf7673a53524a3708f0ffd1ea9ee2d)
-    is called, which tries to [import one address profile per form
+    form, storing it in `imported_credit_card` if so
+4.  [FormDataImporter::ExtractAddressProfiles](https://cs.chromium.org/chromium/src/components/autofill/core/browser/form_data_importer.cc?l=196&rcl=ab8d0ea46daf7673a53524a3708f0ffd1ea9ee2d)
+    is called, which tries to [extract one address profile per form
     section](https://cs.chromium.org/chromium/src/components/autofill/core/browser/form_data_importer.cc?l=222&rcl=ab8d0ea46daf7673a53524a3708f0ffd1ea9ee2d)
     (maximum of 2)
 5.  If the submitted form [included a credit
@@ -198,4 +198,3 @@
     [CreditCardSaveManager::OnDidUploadCard](https://cs.chromium.org/chromium/src/components/autofill/core/browser/credit_card_save_manager.cc?l=215&rcl=6ad45bcd758ad6eaba1da3a71b909f7ca7b46217),
     which, on a success, saves the credit card as a FULL_SERVER_CARD (so it
     doesn’t need to be unmasked on next use on the same device)
-
diff --git a/docs/infra/trybot_usage.md b/docs/infra/trybot_usage.md
index da8aa39..bf8871e 100644
--- a/docs/infra/trybot_usage.md
+++ b/docs/infra/trybot_usage.md
@@ -9,8 +9,8 @@
 
 -   Trybots include all platforms for which we currently build Chromium, though
     they may not support all configurations built on CI.
--   The commit queue runs a subset of available trybots. See [here][1] for more
-    information.
+-   The commit queue (CQ) runs a subset of available trybots. See [here][1] for
+    more information.
 -   trybots can be manually invoked via `git cl try` or the "Choose Trybots" UI
     in gerrit.
 -   Any committer can use the trybots.
diff --git a/extensions/browser/api/declarative_net_request/ruleset_manager.h b/extensions/browser/api/declarative_net_request/ruleset_manager.h
index ae949f3..5cde9ad1 100644
--- a/extensions/browser/api/declarative_net_request/ruleset_manager.h
+++ b/extensions/browser/api/declarative_net_request/ruleset_manager.h
@@ -100,6 +100,8 @@
   void OnRenderFrameDeleted(content::RenderFrameHost* host);
   void OnDidFinishNavigation(content::NavigationHandle* navigation_handle);
 
+  bool has_rulesets() const { return !rulesets_.empty(); }
+
   // Returns the number of CompositeMatchers currently being managed.
   size_t GetMatcherCountForTest() const { return rulesets_.size(); }
 
diff --git a/extensions/browser/api/networking_private/networking_private_chromeos_unittest.cc b/extensions/browser/api/networking_private/networking_private_chromeos_unittest.cc
index 69d7c5a9..fd60b0e 100644
--- a/extensions/browser/api/networking_private/networking_private_chromeos_unittest.cc
+++ b/extensions/browser/api/networking_private/networking_private_chromeos_unittest.cc
@@ -215,15 +215,14 @@
                       base::Value("test_min"));
     SetDeviceProperty(kCellularDevicePath, shill::kModelIdProperty,
                       base::Value("test_model_id"));
-    std::unique_ptr<base::Value> apn =
-        DictionaryBuilder()
-            .Set(shill::kApnProperty, "test-apn")
-            .Set(shill::kApnUsernameProperty, "test-user")
-            .Set(shill::kApnPasswordProperty, "test-password")
-            .Set(shill::kApnAuthenticationProperty, "chap")
-            .Build();
+    base::Value apn(DictionaryBuilder()
+                        .Set(shill::kApnProperty, "test-apn")
+                        .Set(shill::kApnUsernameProperty, "test-user")
+                        .Set(shill::kApnPasswordProperty, "test-password")
+                        .Set(shill::kApnAuthenticationProperty, "chap")
+                        .Build());
     base::Value apn_list(base::Value::Type::LIST);
-    apn_list.Append(apn->Clone());
+    apn_list.GetList().Append(apn.Clone());
     SetDeviceProperty(kCellularDevicePath, shill::kCellularApnListProperty,
                       apn_list);
 
@@ -246,9 +245,9 @@
                                        shill::kRoamingStateProperty,
                                        base::Value(shill::kRoamingStateHome));
     service_test()->SetServiceProperty(kCellularServicePath,
-                                       shill::kCellularApnProperty, *apn);
+                                       shill::kCellularApnProperty, apn);
     service_test()->SetServiceProperty(
-        kCellularServicePath, shill::kCellularLastGoodApnProperty, *apn);
+        kCellularServicePath, shill::kCellularLastGoodApnProperty, apn);
 
     profile_test()->AddService(kUserProfilePath, kCellularServicePath);
 
@@ -1076,7 +1075,7 @@
 
   ASSERT_TRUE(result);
 
-  std::unique_ptr<base::Value> expected_result =
+  base::Value::Dict expected_result =
       DictionaryBuilder()
           .Set("Cellular",
                DictionaryBuilder()
@@ -1103,7 +1102,7 @@
           .Set("Type", "Cellular")
           .Build();
 
-  EXPECT_EQ(*expected_result, *result);
+  EXPECT_EQ(base::Value(std::move(expected_result)), *result);
 }
 
 TEST_F(NetworkingPrivateApiTest, GetCellularPropertiesFromWebUi) {
@@ -1119,14 +1118,13 @@
 
   ASSERT_TRUE(result);
 
-  std::unique_ptr<base::Value> expected_apn =
-      DictionaryBuilder()
-          .Set("AccessPointName", "test-apn")
-          .Set("Username", "test-user")
-          .Set("Password", "test-password")
-          .Set("Authentication", "chap")
-          .Build();
-  std::unique_ptr<base::Value> expected_result =
+  base::Value::Dict expected_apn = DictionaryBuilder()
+                                       .Set("AccessPointName", "test-apn")
+                                       .Set("Username", "test-user")
+                                       .Set("Password", "test-password")
+                                       .Set("Authentication", "chap")
+                                       .Build();
+  base::Value::Dict expected_result =
       DictionaryBuilder()
           .Set("Cellular",
                DictionaryBuilder()
@@ -1149,14 +1147,10 @@
                    .Set("NetworkTechnology", "GSM")
                    .Set("RoamingState", "Home")
                    .Set("Scanning", false)
-                   .Set("APNList", ListBuilder()
-                                       .Append(base::Value::ToUniquePtrValue(
-                                           expected_apn->Clone()))
-                                       .Build())
-                   .Set("APN",
-                        base::Value::ToUniquePtrValue(expected_apn->Clone()))
-                   .Set("LastGoodAPN",
-                        base::Value::ToUniquePtrValue(expected_apn->Clone()))
+                   .Set("APNList",
+                        ListBuilder().Append(expected_apn.Clone()).Build())
+                   .Set("APN", expected_apn.Clone())
+                   .Set("LastGoodAPN", expected_apn.Clone())
                    .Build())
           .Set("ConnectionState", "Connected")
           .Set("GUID", "cellular_guid")
@@ -1168,7 +1162,7 @@
           .Set("Type", "Cellular")
           .Build();
 
-  EXPECT_EQ(*expected_result, *result);
+  EXPECT_EQ(base::Value(std::move(expected_result)), *result);
 }
 
 TEST_F(NetworkingPrivateApiTest, ForgetSharedNetwork) {
diff --git a/extensions/browser/api/web_request/web_request_api.cc b/extensions/browser/api/web_request/web_request_api.cc
index 3fbe300d..c800df6 100644
--- a/extensions/browser/api/web_request/web_request_api.cc
+++ b/extensions/browser/api/web_request/web_request_api.cc
@@ -1166,6 +1166,9 @@
         CreateEventDetails(*request, extra_info_spec));
     event_details->SetRequestBody(request);
 
+    request_time_tracker_->LogBeforeRequestDispatchTime(request->id,
+                                                        base::TimeTicks::Now());
+
     initialize_blocked_requests |= DispatchEvent(
         browser_context, request, listeners, std::move(event_details));
   }
@@ -1181,50 +1184,76 @@
   // currently only depend on the request url, initiator and resource type,
   // which should stay the same during the diffierent network request stages. A
   // redirect should cause another OnBeforeRequest call.
-  const std::vector<DNRRequestAction>& actions =
+  declarative_net_request::RulesetManager* ruleset_manager =
       declarative_net_request::RulesMonitorService::Get(browser_context)
-          ->ruleset_manager()
-          ->EvaluateRequest(*request, is_incognito_context);
-  for (const auto& action : actions) {
-    switch (action.type) {
-      case DNRRequestAction::Type::BLOCK:
-        ClearPendingCallbacks(*request);
-        DCHECK_EQ(1u, actions.size());
-        OnDNRActionMatched(browser_context, *request, action);
-        RecordNetworkRequestBlocked(request->ukm_source_id,
-                                    action.extension_id);
-        return net::ERR_BLOCKED_BY_CLIENT;
-      case DNRRequestAction::Type::COLLAPSE:
-        ClearPendingCallbacks(*request);
-        DCHECK_EQ(1u, actions.size());
-        OnDNRActionMatched(browser_context, *request, action);
-        *should_collapse_initiator = true;
-        RecordNetworkRequestBlocked(request->ukm_source_id,
-                                    action.extension_id);
-        return net::ERR_BLOCKED_BY_CLIENT;
-      case DNRRequestAction::Type::ALLOW:
-      case DNRRequestAction::Type::ALLOW_ALL_REQUESTS:
-        DCHECK_EQ(1u, actions.size());
-        OnDNRActionMatched(browser_context, *request, action);
-        break;
-      case DNRRequestAction::Type::REDIRECT:
-      case DNRRequestAction::Type::UPGRADE:
-        ClearPendingCallbacks(*request);
-        DCHECK_EQ(1u, actions.size());
-        DCHECK(action.redirect_url);
-        OnDNRActionMatched(browser_context, *request, action);
-        *new_url = action.redirect_url.value();
-        return net::OK;
-      case DNRRequestAction::Type::MODIFY_HEADERS:
-        // Unlike other actions, allow web request extensions to intercept the
-        // request here. The headers will be modified during subsequent request
-        // stages.
-        DCHECK(
-            base::ranges::all_of(*request->dnr_actions, [](const auto& action) {
-              return action.type == DNRRequestAction::Type::MODIFY_HEADERS;
-            }));
-        break;
+          ->ruleset_manager();
+
+  if (ruleset_manager->has_rulesets()) {
+    request_time_tracker_->LogBeforeRequestDNRStartTime(request->id,
+                                                        base::TimeTicks::Now());
+
+    auto record_completion_time = [](ExtensionWebRequestTimeTracker* tracker,
+                                     int64_t request_id) {
+      tracker->LogBeforeRequestDNRCompletionTime(request_id,
+                                                 base::TimeTicks::Now());
+    };
+
+    const std::vector<DNRRequestAction>& actions =
+        ruleset_manager->EvaluateRequest(*request, is_incognito_context);
+    base::ScopedClosureRunner scoped_timer;
+    if (!actions.empty()) {
+      // We only record completion time if there's at least one relevant rule.
+      // Otherwise, we'd record evaluation for every request even if the user
+      // only had a single rule added.
+      scoped_timer = base::ScopedClosureRunner(base::BindOnce(
+          record_completion_time, request_time_tracker_.get(), request->id));
     }
+
+    for (const auto& action : actions) {
+      switch (action.type) {
+        case DNRRequestAction::Type::BLOCK:
+          ClearPendingCallbacks(*request);
+          DCHECK_EQ(1u, actions.size());
+          OnDNRActionMatched(browser_context, *request, action);
+          RecordNetworkRequestBlocked(request->ukm_source_id,
+                                      action.extension_id);
+          return net::ERR_BLOCKED_BY_CLIENT;
+        case DNRRequestAction::Type::COLLAPSE:
+          ClearPendingCallbacks(*request);
+          DCHECK_EQ(1u, actions.size());
+          OnDNRActionMatched(browser_context, *request, action);
+          *should_collapse_initiator = true;
+          RecordNetworkRequestBlocked(request->ukm_source_id,
+                                      action.extension_id);
+          return net::ERR_BLOCKED_BY_CLIENT;
+        case DNRRequestAction::Type::ALLOW:
+        case DNRRequestAction::Type::ALLOW_ALL_REQUESTS:
+          DCHECK_EQ(1u, actions.size());
+          OnDNRActionMatched(browser_context, *request, action);
+          break;
+        case DNRRequestAction::Type::REDIRECT:
+        case DNRRequestAction::Type::UPGRADE:
+          ClearPendingCallbacks(*request);
+          DCHECK_EQ(1u, actions.size());
+          DCHECK(action.redirect_url);
+          OnDNRActionMatched(browser_context, *request, action);
+          *new_url = action.redirect_url.value();
+          return net::OK;
+        case DNRRequestAction::Type::MODIFY_HEADERS:
+          // Unlike other actions, allow web request extensions to intercept
+          // the request here. The headers will be modified during subsequent
+          // request stages.
+          DCHECK(base::ranges::all_of(
+              *request->dnr_actions, [](const auto& action) {
+                return action.type == DNRRequestAction::Type::MODIFY_HEADERS;
+              }));
+          break;
+      }
+    }
+  } else {
+    // Later methods require `dnr_actions` to be populated; give it an empty
+    // set.
+    request->dnr_actions = std::vector<DNRRequestAction>();
   }
 
   if (!initialize_blocked_requests)
@@ -2489,6 +2518,8 @@
 
   // Ensure that the response is for the event we are blocked on.
   DCHECK_EQ(blocked_request.event, GetEventTypeFromEventName(event_name));
+  // Cache the event type; we use it below.
+  EventTypes request_event = blocked_request.event;
 
   int num_handlers_blocking = --blocked_request.num_handlers_blocking;
   CHECK_GE(num_handlers_blocking, 0);
@@ -2505,8 +2536,16 @@
     blocked_request.response_deltas.push_back(std::move(delta));
   }
 
-  if (num_handlers_blocking == 0)
+  if (num_handlers_blocking == 0) {
     ExecuteDeltas(browser_context, blocked_request.request, true);
+    // Note: `blocked_request` can be deleted here, depending on the outcome
+    // of ExecuteDeltas(). Use the cached `request_event` and `request_id`
+    // instead of using `blocked_request`.
+    if (request_event == kOnBeforeRequest) {
+      request_time_tracker_->LogBeforeRequestCompletionTime(
+          request_id, base::TimeTicks::Now());
+    }
+  }
 }
 
 void ExtensionWebRequestEventRouter::SendMessages(
diff --git a/extensions/browser/api/web_request/web_request_time_tracker.cc b/extensions/browser/api/web_request/web_request_time_tracker.cc
index 2627b19..a39c6fc 100644
--- a/extensions/browser/api/web_request/web_request_time_tracker.cc
+++ b/extensions/browser/api/web_request/web_request_time_tracker.cc
@@ -28,6 +28,43 @@
   log.has_extra_headers_listener = has_extra_headers_listener;
 }
 
+void ExtensionWebRequestTimeTracker::LogBeforeRequestDispatchTime(
+    int64_t request_id,
+    base::TimeTicks dispatch_time) {
+  auto iter = request_time_logs_.find(request_id);
+  DCHECK(iter != request_time_logs_.end());
+  iter->second.before_request_listener_dispatch_time = dispatch_time;
+}
+
+void ExtensionWebRequestTimeTracker::LogBeforeRequestCompletionTime(
+    int64_t request_id,
+    base::TimeTicks completion_time) {
+  auto iter = request_time_logs_.find(request_id);
+  if (iter == request_time_logs_.end()) {
+    // This probably *shouldn't* happen, but there's enough subtlety in handling
+    // network requests that we handle it gracefully.
+    return;
+  }
+
+  iter->second.before_request_listener_completion_time = completion_time;
+}
+
+void ExtensionWebRequestTimeTracker::LogBeforeRequestDNRStartTime(
+    int64_t request_id,
+    base::TimeTicks start_time) {
+  auto iter = request_time_logs_.find(request_id);
+  DCHECK(iter != request_time_logs_.end());
+  iter->second.before_request_dnr_start_time = start_time;
+}
+
+void ExtensionWebRequestTimeTracker::LogBeforeRequestDNRCompletionTime(
+    int64_t request_id,
+    base::TimeTicks completion_time) {
+  auto iter = request_time_logs_.find(request_id);
+  DCHECK(iter != request_time_logs_.end());
+  iter->second.before_request_dnr_completion_time = completion_time;
+}
+
 void ExtensionWebRequestTimeTracker::LogRequestEndTime(
     int64_t request_id,
     const base::TimeTicks& end_time) {
@@ -70,6 +107,47 @@
         base::ClampRound(log.block_duration / request_duration * 100);
     UMA_HISTOGRAM_PERCENTAGE("Extensions.NetworkDelayPercentage", percentage);
   }
+
+  // Record the time spent in listeners in onBeforeRequest. Only do this if
+  // we have a time for both the dispatch and completion time (we may not,
+  // if the request were canceled).
+  if (!log.before_request_listener_dispatch_time.is_null() &&
+      !log.before_request_listener_completion_time.is_null()) {
+    base::TimeDelta listener_time =
+        log.before_request_listener_completion_time -
+        log.before_request_listener_dispatch_time;
+    // Because the DNR actions are calculated right after the event is
+    // dispatched, we separate these into different metrics (so that we can
+    // differentiate between times that include declarativeNetRequest rule
+    // matching and those that don't).
+    if (log.before_request_dnr_start_time.is_null()) {
+      UMA_HISTOGRAM_TIMES(
+          "Extensions.WebRequest.BeforeRequestListenerEvaluationTime."
+          "WebRequestOnly",
+          listener_time);
+    } else {  // Both webRequest and DNR handlers.
+      UMA_HISTOGRAM_TIMES(
+          "Extensions.WebRequest.BeforeRequestListenerEvaluationTime."
+          "WebRequestAndDeclarativeNetRequest",
+          listener_time);
+    }
+  }
+
+  if (!log.before_request_dnr_completion_time.is_null()) {
+    // Since declarativeNetRequest handlers are evaluated synchronously in the
+    // same method, if there's a completion time, there should always be a
+    // start time. (The inverse is not true, since we only log completion time
+    // if there was at least one relevant action.)
+    DCHECK(!log.before_request_dnr_start_time.is_null());
+    // DeclarativeNetRequest handlers also aren't really affected by webRequest
+    // listeners, so no need to split up the time depending on whether there
+    // were webRequest listeners.
+    UMA_HISTOGRAM_TIMES(
+        "Extensions.WebRequest."
+        "BeforeRequestDeclarativeNetRequestEvaluationTime",
+        log.before_request_dnr_completion_time -
+            log.before_request_dnr_start_time);
+  }
 }
 
 void ExtensionWebRequestTimeTracker::IncrementTotalBlockTime(
diff --git a/extensions/browser/api/web_request/web_request_time_tracker.h b/extensions/browser/api/web_request/web_request_time_tracker.h
index b8c544e..e3728b90 100644
--- a/extensions/browser/api/web_request/web_request_time_tracker.h
+++ b/extensions/browser/api/web_request/web_request_time_tracker.h
@@ -35,6 +35,27 @@
                            bool has_listener,
                            bool has_extra_headers_listener);
 
+  // Records the time at which an onBeforeRequest event was dispatched to
+  // listeners.
+  void LogBeforeRequestDispatchTime(int64_t request_id,
+                                    base::TimeTicks dispatch_time);
+
+  // Records the time at which an onBeforeRequest event received a response
+  // from all blocking listeners and the responses have been handled. Only
+  // called if there was at least one blocking listener.
+  void LogBeforeRequestCompletionTime(int64_t request_id,
+                                      base::TimeTicks completion_time);
+
+  // Records the time at which Chrome started to evaluate declarativeNetRequest
+  // rules at the beginning of a request.
+  void LogBeforeRequestDNRStartTime(int64_t request_id,
+                                    base::TimeTicks start_time);
+
+  // Records the time at which Chrome has completed handling
+  // declarativeNetRequest rules. Only called if at least one rule was applied.
+  void LogBeforeRequestDNRCompletionTime(int64_t request_id,
+                                         base::TimeTicks completion_time);
+
   // Records the time that a request either completed or encountered an error.
   void LogRequestEndTime(int64_t request_id, const base::TimeTicks& end_time);
 
@@ -55,7 +76,13 @@
   // Timing information for a single request.
   struct RequestTimeLog {
     base::TimeTicks request_start_time;
+    base::TimeTicks before_request_listener_dispatch_time;
+    base::TimeTicks before_request_dnr_start_time;
+    base::TimeTicks before_request_dnr_completion_time;
+    base::TimeTicks before_request_listener_completion_time;
+
     base::TimeDelta block_duration;
+
     bool has_listener = false;
     bool has_extra_headers_listener = false;
 
diff --git a/extensions/browser/content_verifier/test_utils.cc b/extensions/browser/content_verifier/test_utils.cc
index a026351..5ec68db 100644
--- a/extensions/browser/content_verifier/test_utils.cc
+++ b/extensions/browser/content_verifier/test_utils.cc
@@ -488,18 +488,22 @@
                      .Build());
   }
 
-  return DictionaryBuilder()
-      .Set("item_id", extension_id_)
-      .Set("item_version", "1.0")
-      .Set("content_hashes", ListBuilder()
-                                 .Append(DictionaryBuilder()
-                                             .Set("format", "treehash")
-                                             .Set("block_size", block_size)
-                                             .Set("hash_block_size", block_size)
-                                             .Set("files", files.Build())
-                                             .Build())
-                                 .Build())
-      .Build();
+  base::Value::Dict result =
+      DictionaryBuilder()
+          .Set("item_id", extension_id_)
+          .Set("item_version", "1.0")
+          .Set("content_hashes",
+               ListBuilder()
+                   .Append(DictionaryBuilder()
+                               .Set("format", "treehash")
+                               .Set("block_size", block_size)
+                               .Set("hash_block_size", block_size)
+                               .Set("files", files.Build())
+                               .Build())
+                   .Build())
+          .Build();
+
+  return std::make_unique<base::Value>(std::move(result));
 }
 
 // Other stuff ----------------------------------------------------------------
diff --git a/extensions/common/extension_builder_unittest.cc b/extensions/common/extension_builder_unittest.cc
index 62d7094..2168bcb 100644
--- a/extensions/common/extension_builder_unittest.cc
+++ b/extensions/common/extension_builder_unittest.cc
@@ -137,7 +137,7 @@
 TEST(ExtensionBuilderTest, MergeManifest) {
   DictionaryBuilder connectable;
   connectable.Set("matches", ListBuilder().Append("*://example.com/*").Build());
-  std::unique_ptr<base::DictionaryValue> connectable_value =
+  base::Value::Dict connectable_value =
       DictionaryBuilder()
           .Set("externally_connectable", connectable.Build())
           .Build();
diff --git a/extensions/common/permissions/usb_device_permission_unittest.cc b/extensions/common/permissions/usb_device_permission_unittest.cc
index b35f9d4..56eddf2 100644
--- a/extensions/common/permissions/usb_device_permission_unittest.cc
+++ b/extensions/common/permissions/usb_device_permission_unittest.cc
@@ -63,7 +63,7 @@
 }
 
 scoped_refptr<const Extension> CreateTestApp(
-    std::unique_ptr<base::Value> usb_device_permission) {
+    base::Value usb_device_permission) {
   return ExtensionBuilder()
       .SetManifest(
           DictionaryBuilder()
@@ -107,14 +107,13 @@
 }
 
 TEST(USBDevicePermissionTest, CheckVendorAndProductId) {
-  std::unique_ptr<base::Value> permission_data_value =
-      DictionaryBuilder()
-          .Set("vendorId", 0x02ad)
-          .Set("productId", 0x138c)
-          .Build();
+  base::Value permission_data_value(DictionaryBuilder()
+                                        .Set("vendorId", 0x02ad)
+                                        .Set("productId", 0x138c)
+                                        .Build());
 
   UsbDevicePermissionData permission_data;
-  ASSERT_TRUE(permission_data.FromValue(permission_data_value.get()));
+  ASSERT_TRUE(permission_data.FromValue(&permission_data_value));
 
   scoped_refptr<const Extension> app =
       CreateTestApp(std::move(permission_data_value));
@@ -147,14 +146,13 @@
 }
 
 TEST(USBDevicePermissionTest, CheckInterfaceId) {
-  std::unique_ptr<base::Value> permission_data_value =
-      DictionaryBuilder()
-          .Set("vendorId", 0x02ad)
-          .Set("productId", 0x138c)
-          .Set("interfaceId", 3)
-          .Build();
+  base::Value permission_data_value(DictionaryBuilder()
+                                        .Set("vendorId", 0x02ad)
+                                        .Set("productId", 0x138c)
+                                        .Set("interfaceId", 3)
+                                        .Build());
   UsbDevicePermissionData permission_data;
-  ASSERT_TRUE(permission_data.FromValue(permission_data_value.get()));
+  ASSERT_TRUE(permission_data.FromValue(&permission_data_value));
 
   scoped_refptr<const Extension> app =
       CreateTestApp(std::move(permission_data_value));
@@ -185,10 +183,10 @@
 }
 
 TEST(USBDevicePermissionTest, InterfaceClass) {
-  std::unique_ptr<base::Value> permission_data_value =
-      DictionaryBuilder().Set("interfaceClass", 3).Build();
+  base::Value permission_data_value(
+      DictionaryBuilder().Set("interfaceClass", 3).Build());
   UsbDevicePermissionData permission_data;
-  EXPECT_TRUE(permission_data.FromValue(permission_data_value.get()));
+  EXPECT_TRUE(permission_data.FromValue(&permission_data_value));
 
   scoped_refptr<const Extension> app =
       CreateTestApp(std::move(permission_data_value));
@@ -224,13 +222,12 @@
 }
 
 TEST(USBDevicePermissionTest, InterfaceClassWithVendorId) {
-  std::unique_ptr<base::Value> permission_data_value =
-      DictionaryBuilder()
-          .Set("vendorId", 0x02ad)
-          .Set("interfaceClass", 3)
-          .Build();
+  base::Value permission_data_value(DictionaryBuilder()
+                                        .Set("vendorId", 0x02ad)
+                                        .Set("interfaceClass", 3)
+                                        .Build());
   UsbDevicePermissionData permission_data;
-  EXPECT_TRUE(permission_data.FromValue(permission_data_value.get()));
+  EXPECT_TRUE(permission_data.FromValue(&permission_data_value));
 
   scoped_refptr<const Extension> app =
       CreateTestApp(std::move(permission_data_value));
@@ -274,13 +271,12 @@
 }
 
 TEST(USBDevicePermissionTest, CheckHidUsbAgainstInterfaceClass) {
-  std::unique_ptr<base::Value> permission_data_value =
-      DictionaryBuilder()
-          .Set("vendorId", 0x02ad)
-          .Set("interfaceClass", 3)
-          .Build();
+  base::Value permission_data_value(DictionaryBuilder()
+                                        .Set("vendorId", 0x02ad)
+                                        .Set("interfaceClass", 3)
+                                        .Build());
   UsbDevicePermissionData permission_data;
-  EXPECT_TRUE(permission_data.FromValue(permission_data_value.get()));
+  EXPECT_TRUE(permission_data.FromValue(&permission_data_value));
 
   scoped_refptr<const Extension> app =
       CreateTestApp(std::move(permission_data_value));
@@ -321,13 +317,12 @@
 }
 
 TEST(USBDevicePermissionTest, CheckHidUsbAgainstDeviceIds) {
-  std::unique_ptr<base::Value> permission_data_value =
-      DictionaryBuilder()
-          .Set("vendorId", 0x02ad)
-          .Set("productId", 0x138c)
-          .Build();
+  base::Value permission_data_value(DictionaryBuilder()
+                                        .Set("vendorId", 0x02ad)
+                                        .Set("productId", 0x138c)
+                                        .Build());
   UsbDevicePermissionData permission_data;
-  EXPECT_TRUE(permission_data.FromValue(permission_data_value.get()));
+  EXPECT_TRUE(permission_data.FromValue(&permission_data_value));
 
   scoped_refptr<const Extension> app =
       CreateTestApp(std::move(permission_data_value));
@@ -350,13 +345,12 @@
 }
 
 TEST(USBDevicePermissionTest, CheckDeviceAgainstDeviceIds) {
-  std::unique_ptr<base::Value> permission_data_value =
-      DictionaryBuilder()
-          .Set("vendorId", 0x02ad)
-          .Set("productId", 0x138c)
-          .Build();
+  base::Value permission_data_value(DictionaryBuilder()
+                                        .Set("vendorId", 0x02ad)
+                                        .Set("productId", 0x138c)
+                                        .Build());
   UsbDevicePermissionData permission_data;
-  EXPECT_TRUE(permission_data.FromValue(permission_data_value.get()));
+  EXPECT_TRUE(permission_data.FromValue(&permission_data_value));
 
   scoped_refptr<const Extension> app =
       CreateTestApp(std::move(permission_data_value));
@@ -383,10 +377,10 @@
 }
 
 TEST(USBDevicePermissionTest, CheckDeviceAgainstDeviceClass) {
-  std::unique_ptr<base::Value> permission_data_value =
-      DictionaryBuilder().Set("interfaceClass", 0x9).Build();
+  base::Value permission_data_value(
+      DictionaryBuilder().Set("interfaceClass", 0x9).Build());
   UsbDevicePermissionData permission_data;
-  EXPECT_TRUE(permission_data.FromValue(permission_data_value.get()));
+  EXPECT_TRUE(permission_data.FromValue(&permission_data_value));
 
   scoped_refptr<const Extension> app =
       CreateTestApp(std::move(permission_data_value));
@@ -432,10 +426,10 @@
 }
 
 TEST(USBDevicePermissionTest, IgnoreNullDeviceClass) {
-  std::unique_ptr<base::Value> permission_data_value =
-      DictionaryBuilder().Set("interfaceClass", 0).Build();
+  base::Value permission_data_value(
+      DictionaryBuilder().Set("interfaceClass", 0).Build());
   UsbDevicePermissionData permission_data;
-  EXPECT_TRUE(permission_data.FromValue(permission_data_value.get()));
+  EXPECT_TRUE(permission_data.FromValue(&permission_data_value));
 
   scoped_refptr<const Extension> app =
       CreateTestApp(std::move(permission_data_value));
@@ -457,10 +451,10 @@
 }
 
 TEST(USBDevicePermissionTest, CheckDeviceAgainstInterfaceClass) {
-  std::unique_ptr<base::Value> permission_data_value =
-      DictionaryBuilder().Set("interfaceClass", 0x3).Build();
+  base::Value permission_data_value(
+      DictionaryBuilder().Set("interfaceClass", 0x3).Build());
   UsbDevicePermissionData permission_data;
-  EXPECT_TRUE(permission_data.FromValue(permission_data_value.get()));
+  EXPECT_TRUE(permission_data.FromValue(&permission_data_value));
 
   scoped_refptr<const Extension> app =
       CreateTestApp(std::move(permission_data_value));
@@ -543,14 +537,13 @@
 }
 
 TEST(USBDevicePermissionTest, CheckDeviceAndInterfaceId) {
-  std::unique_ptr<base::Value> permission_data_value =
-      DictionaryBuilder()
-          .Set("vendorId", 0x02ad)
-          .Set("productId", 0x138c)
-          .Set("interfaceId", 3)
-          .Build();
+  base::Value permission_data_value(DictionaryBuilder()
+                                        .Set("vendorId", 0x02ad)
+                                        .Set("productId", 0x138c)
+                                        .Set("interfaceId", 3)
+                                        .Build());
   UsbDevicePermissionData permission_data;
-  EXPECT_TRUE(permission_data.FromValue(permission_data_value.get()));
+  EXPECT_TRUE(permission_data.FromValue(&permission_data_value));
 
   scoped_refptr<const Extension> app =
       CreateTestApp(std::move(permission_data_value));
@@ -578,13 +571,12 @@
 
 TEST(USBDevicePermissionTest,
      CheckDeviceAndInterfaceIDAgainstMissingInterfaceId) {
-  std::unique_ptr<base::Value> permission_data_value =
-      DictionaryBuilder()
-          .Set("vendorId", 0x02ad)
-          .Set("productId", 0x138c)
-          .Build();
+  base::Value permission_data_value(DictionaryBuilder()
+                                        .Set("vendorId", 0x02ad)
+                                        .Set("productId", 0x138c)
+                                        .Build());
   UsbDevicePermissionData permission_data;
-  EXPECT_TRUE(permission_data.FromValue(permission_data_value.get()));
+  EXPECT_TRUE(permission_data.FromValue(&permission_data_value));
 
   scoped_refptr<const Extension> app =
       CreateTestApp(std::move(permission_data_value));
@@ -601,39 +593,39 @@
 }
 
 TEST(USBDevicePermissionTest, InvalidPermission_NoVendorId) {
-  std::unique_ptr<base::Value> permission_data_value =
-      DictionaryBuilder()
-          .Set("productId", 0x138c)
-          .Set("interfaceClass", 3)
-          .Build();
+  base::Value permission_data_value(DictionaryBuilder()
+                                        .Set("productId", 0x138c)
+                                        .Set("interfaceClass", 3)
+                                        .Build());
   UsbDevicePermissionData permission_data;
-  ASSERT_FALSE(permission_data.FromValue(permission_data_value.get()));
+  ASSERT_FALSE(permission_data.FromValue(&permission_data_value));
 }
 
 TEST(USBDevicePermissionTest, InvalidPermission_OnlyVendorId) {
-  std::unique_ptr<base::Value> permission_data_value =
-      DictionaryBuilder().Set("vendorId", 0x02ad).Build();
+  base::Value permission_data_value(
+      DictionaryBuilder().Set("vendorId", 0x02ad).Build());
   UsbDevicePermissionData permission_data;
-  ASSERT_FALSE(permission_data.FromValue(permission_data_value.get()));
+  ASSERT_FALSE(permission_data.FromValue(&permission_data_value));
 }
 
 TEST(USBDevicePermissionTest, InvalidPermission_NoProductIdWithInterfaceId) {
-  std::unique_ptr<base::Value> permission_data_value =
-      DictionaryBuilder().Set("vendorId", 0x02ad).Set("interfaceId", 3).Build();
+  base::Value permission_data_value(DictionaryBuilder()
+                                        .Set("vendorId", 0x02ad)
+                                        .Set("interfaceId", 3)
+                                        .Build());
   UsbDevicePermissionData permission_data;
-  ASSERT_FALSE(permission_data.FromValue(permission_data_value.get()));
+  ASSERT_FALSE(permission_data.FromValue(&permission_data_value));
 }
 
 TEST(USBDevicePermissionTest, RejectInterfaceIdIfInterfaceClassPresent) {
-  std::unique_ptr<base::Value> permission_data_value =
-      DictionaryBuilder()
-          .Set("vendorId", 0x02ad)
-          .Set("productId", 0x128c)
-          .Set("interfaceId", 3)
-          .Set("interfaceClass", 7)
-          .Build();
+  base::Value permission_data_value(DictionaryBuilder()
+                                        .Set("vendorId", 0x02ad)
+                                        .Set("productId", 0x128c)
+                                        .Set("interfaceId", 3)
+                                        .Set("interfaceClass", 7)
+                                        .Build());
   UsbDevicePermissionData permission_data;
-  ASSERT_FALSE(permission_data.FromValue(permission_data_value.get()));
+  ASSERT_FALSE(permission_data.FromValue(&permission_data_value));
 }
 
 }  // namespace extensions
diff --git a/extensions/common/value_builder.cc b/extensions/common/value_builder.cc
index bb41c44b..5bf412e 100644
--- a/extensions/common/value_builder.cc
+++ b/extensions/common/value_builder.cc
@@ -13,22 +13,17 @@
 
 // DictionaryBuilder
 
-DictionaryBuilder::DictionaryBuilder() : dict_(new base::DictionaryValue) {}
-
-DictionaryBuilder::DictionaryBuilder(const base::DictionaryValue& init)
-    : dict_(base::DictionaryValue::From(
-          base::Value::ToUniquePtrValue(init.Clone()))) {}
+DictionaryBuilder::DictionaryBuilder() = default;
 
 DictionaryBuilder::DictionaryBuilder(const base::Value::Dict& init)
-    : dict_(base::DictionaryValue::From(
-          base::Value::ToUniquePtrValue(base::Value(init.Clone())))) {}
+    : dict_(init.Clone()) {}
 
 DictionaryBuilder::~DictionaryBuilder() = default;
 
 std::string DictionaryBuilder::ToJSON() const {
   std::string json;
   base::JSONWriter::WriteWithOptions(
-      *dict_, base::JSONWriter::OPTIONS_PRETTY_PRINT, &json);
+      dict_, base::JSONWriter::OPTIONS_PRETTY_PRINT, &json);
   return json;
 }
 
diff --git a/extensions/common/value_builder.h b/extensions/common/value_builder.h
index 84ed0d53..30fd7d2 100644
--- a/extensions/common/value_builder.h
+++ b/extensions/common/value_builder.h
@@ -45,7 +45,6 @@
 class DictionaryBuilder {
  public:
   DictionaryBuilder();
-  explicit DictionaryBuilder(const base::DictionaryValue& init);
   explicit DictionaryBuilder(const base::Value::Dict& init);
 
   DictionaryBuilder(const DictionaryBuilder&) = delete;
@@ -53,23 +52,22 @@
 
   ~DictionaryBuilder();
 
-  // Can only be called once, after which it's invalid to use the builder.
-  base::Value::Dict BuildDict() {
-    base::Value::Dict result = std::move(*dict_).TakeDict();
-    dict_.reset();
+  // Deprecated: Use Build instead.
+  base::Value::Dict BuildDict() { return Build(); }
+
+  base::Value::Dict Build() {
+    base::Value::Dict result = std::move(dict_);
+    dict_ = base::Value::Dict();
     return result;
   }
 
-  // DEPRECATED version of BuildDict().
-  std::unique_ptr<base::DictionaryValue> Build() { return std::move(dict_); }
-
   // Immediately serializes the current state to JSON. Can be called as many
   // times as you like.
   std::string ToJSON() const;
 
   template <typename T>
   DictionaryBuilder& Set(base::StringPiece key, T in_value) {
-    dict_->GetDict().Set(key, std::move(in_value));
+    dict_.Set(key, std::move(in_value));
     return *this;
   }
 
@@ -80,12 +78,12 @@
   // a base::Value (or one of its subclasses).
   template <typename T>
   DictionaryBuilder& Set(base::StringPiece key, std::unique_ptr<T> in_value) {
-    dict_->SetKey(key, std::move(*in_value));
+    dict_.Set(key, std::move(*in_value));
     return *this;
   }
 
  private:
-  std::unique_ptr<base::DictionaryValue> dict_;
+  base::Value::Dict dict_;
 };
 
 class ListBuilder {
diff --git a/extensions/renderer/api/messaging/native_renderer_messaging_service_unittest.cc b/extensions/renderer/api/messaging/native_renderer_messaging_service_unittest.cc
index 196171b..acaba87 100644
--- a/extensions/renderer/api/messaging/native_renderer_messaging_service_unittest.cc
+++ b/extensions/renderer/api/messaging/native_renderer_messaging_service_unittest.cc
@@ -151,8 +151,7 @@
   tab_connection_info.frame_id = 0;
   const int tab_id = 10;
   GURL source_url("http://example.com");
-  tab_connection_info.tab =
-      DictionaryBuilder().Set("tabId", tab_id).BuildDict();
+  tab_connection_info.tab = DictionaryBuilder().Set("tabId", tab_id).Build();
   ExtensionMsg_ExternalConnectionInfo external_connection_info;
   external_connection_info.target_id = extension()->id();
   external_connection_info.source_endpoint =
@@ -191,8 +190,8 @@
           .Set("tab", DictionaryBuilder().Set("tabId", tab_id).BuildDict())
           .Set("url", source_url.spec())
           .Set("id", extension()->id())
-          .BuildDict();
-  EXPECT_EQ(ValueToString(expected_sender),
+          .Build();
+  EXPECT_EQ(ValueToString(base::Value(std::move(expected_sender))),
             GetStringPropertyFromObject(context->Global(), context, "sender"));
 }
 
@@ -497,8 +496,7 @@
   tab_connection_info.frame_id = 0;
   const int tab_id = 10;
   GURL source_url("http://example.com");
-  tab_connection_info.tab =
-      DictionaryBuilder().Set("tabId", tab_id).BuildDict();
+  tab_connection_info.tab = DictionaryBuilder().Set("tabId", tab_id).Build();
   ExtensionMsg_ExternalConnectionInfo external_connection_info;
   external_connection_info.target_id = extension()->id();
   external_connection_info.source_endpoint =
@@ -570,8 +568,7 @@
     tab_connection_info.frame_id = 0;
     const int tab_id = 10;
     GURL source_url("http://example.com");
-    tab_connection_info.tab =
-        DictionaryBuilder().Set("tabId", tab_id).BuildDict();
+    tab_connection_info.tab = DictionaryBuilder().Set("tabId", tab_id).Build();
 
     ExtensionMsg_ExternalConnectionInfo external_connection_info;
     external_connection_info.target_id = extension()->id();
diff --git a/extensions/renderer/module_system_test.cc b/extensions/renderer/module_system_test.cc
index 266e7d0..b6beff81 100644
--- a/extensions/renderer/module_system_test.cc
+++ b/extensions/renderer/module_system_test.cc
@@ -316,7 +316,7 @@
                                    .Set("name", "test")
                                    .Set("version", "1.0")
                                    .Set("manifest_version", 2)
-                                   .BuildDict();
+                                   .Build();
   return ExtensionBuilder().SetManifest(std::move(manifest)).Build();
 }
 
diff --git a/gpu/config/gpu_driver_bug_list.json b/gpu/config/gpu_driver_bug_list.json
index 1a8315a..c7ae55f 100644
--- a/gpu/config/gpu_driver_bug_list.json
+++ b/gpu/config/gpu_driver_bug_list.json
@@ -2066,34 +2066,6 @@
       ]
     },
     {
-      "id": 232,
-      "description": "Delayed copy NV12 crashes on Intel on Windows <= 8.1.",
-      "cr_bugs": [727216],
-      "os": {
-        "type": "win",
-        "version": {
-          "op": "<=",
-          "value": "8.1"
-        }
-      },
-      "vendor_id": "0x8086",
-      "features": [
-        "disable_delayed_copy_nv12"
-      ]
-    },
-    {
-      "id": 233,
-      "description": "Delayed copy NV12 displays incorrect colors on NVIDIA drivers.",
-      "cr_bugs": [728670],
-      "os": {
-        "type": "win"
-      },
-      "vendor_id": "0x10de",
-      "features": [
-        "disable_delayed_copy_nv12"
-      ]
-    },
-    {
       "id": 235,
       "description": "Avoid waiting on a egl fence before pageflipping and rely on implicit sync.",
       "cr_bugs": [721463],
@@ -2881,18 +2853,6 @@
       ]
     },
     {
-      "id": 308,
-      "cr_bugs": [983787],
-      "description": "Delayed copy NV12 causes crashes on GPU main thread when DXVA video decoder runs on another thread",
-      "os": {
-        "type": "win"
-      },
-      "vendor_id": "0x1002",
-      "features": [
-        "disable_delayed_copy_nv12"
-      ]
-    },
-    {
       "id": 309,
       "cr_bugs": [993233],
       "description": "Don't use video processor scaling on non-Intel, non-NVIDIA GPUs.",
diff --git a/gpu/config/gpu_workaround_list.txt b/gpu/config/gpu_workaround_list.txt
index 272a105..36324c9 100644
--- a/gpu/config/gpu_workaround_list.txt
+++ b/gpu/config/gpu_workaround_list.txt
@@ -31,7 +31,6 @@
 disable_d3d11
 disable_d3d11_video_decoder
 disable_decode_swap_chain
-disable_delayed_copy_nv12
 disable_depth_texture
 disable_direct_composition
 disable_direct_composition_sw_video_overlays
diff --git a/headless/test/data/protocol/sanity/file-input-directory-upload.js b/headless/test/data/protocol/sanity/file-input-directory-upload.js
index b0805e2..d875a2d8 100644
--- a/headless/test/data/protocol/sanity/file-input-directory-upload.js
+++ b/headless/test/data/protocol/sanity/file-input-directory-upload.js
@@ -11,11 +11,7 @@
   const {result} = await dp.Runtime.evaluate({
      expression: `document.getElementById('file')`
   });
-  dp.DOM.setFileInputFiles({
-    objectId: result.result.objectId,
-    files: [dataPath]
-  });
-  const value = await session.evaluateAsync(`new Promise(resolve => {
+  const valuePromise = session.evaluateAsync(`new Promise(resolve => {
     const file = document.getElementById('file');
     async function readFile(f) {
       return f.name + ': ' + await f.text() + "------------------";
@@ -25,6 +21,10 @@
       resolve(contents.join('\\n'));
     });
   })`);
-  testRunner.log(value);
+  dp.DOM.setFileInputFiles({
+    objectId: result.result.objectId,
+    files: [dataPath]
+  });
+  testRunner.log(await valuePromise);
   testRunner.completeTest();
 })
diff --git a/headless/test/headless_protocol_browsertest.cc b/headless/test/headless_protocol_browsertest.cc
index 3d211b7..f528bb0 100644
--- a/headless/test/headless_protocol_browsertest.cc
+++ b/headless/test/headless_protocol_browsertest.cc
@@ -429,7 +429,7 @@
 
 // TODO(crbug.com/1399463)  Re-enable after resolving flaky failures.
 HEADLESS_PROTOCOL_TEST_WITH_DATA_PATH(
-    DISABLED_FileInputDirectoryUpload,
+    FileInputDirectoryUpload,
     "sanity/file-input-directory-upload.js",
     "sanity/resources/file-input-directory-upload")
 
diff --git a/ios/chrome/browser/ui/settings/password/password_details/BUILD.gn b/ios/chrome/browser/ui/settings/password/password_details/BUILD.gn
index 0636231..f00a80d 100644
--- a/ios/chrome/browser/ui/settings/password/password_details/BUILD.gn
+++ b/ios/chrome/browser/ui/settings/password/password_details/BUILD.gn
@@ -81,7 +81,6 @@
     "//ios/chrome/browser/ui/keyboard",
     "//ios/chrome/browser/ui/list_model:list_model",
     "//ios/chrome/browser/ui/settings:settings_root",
-    "//ios/chrome/browser/ui/settings:settings_root_constants",
     "//ios/chrome/browser/ui/settings/autofill",
     "//ios/chrome/browser/ui/settings/cells",
     "//ios/chrome/browser/ui/settings/password:password_constants",
diff --git a/ios/chrome/browser/ui/settings/password/password_details/password_details_coordinator.h b/ios/chrome/browser/ui/settings/password/password_details/password_details_coordinator.h
index 6c0b403..42bd0d76 100644
--- a/ios/chrome/browser/ui/settings/password/password_details/password_details_coordinator.h
+++ b/ios/chrome/browser/ui/settings/password/password_details/password_details_coordinator.h
@@ -48,6 +48,11 @@
 // Displays the password data in edit mode without requiring any authentication.
 - (void)showPasswordDetailsInEditModeWithoutAuthentication;
 
+// Remove the credential from the cache and reload password details view
+// controller after a change was made.
+- (void)removeCredentialFromCacheAndRefreshTableView:
+    (const password_manager::CredentialUIEntry&)credential;
+
 // Delegate.
 @property(nonatomic, weak) id<PasswordDetailsCoordinatorDelegate> delegate;
 
diff --git a/ios/chrome/browser/ui/settings/password/password_details/password_details_coordinator.mm b/ios/chrome/browser/ui/settings/password/password_details/password_details_coordinator.mm
index 0de726eb..0201365a 100644
--- a/ios/chrome/browser/ui/settings/password/password_details/password_details_coordinator.mm
+++ b/ios/chrome/browser/ui/settings/password/password_details/password_details_coordinator.mm
@@ -269,6 +269,15 @@
   [self.viewController showEditViewWithoutAuthentication];
 }
 
+- (void)removeCredentialFromCacheAndRefreshTableView:
+    (const password_manager::CredentialUIEntry&)credential {
+  // Remove credential from the credentials cache of the password details
+  // manager.
+  [self.mediator removeCredential:credential];
+
+  [self.mediator didFinishEditingPasswordDetails];
+}
+
 - (void)onPasswordCopiedByUser {
   if (IsCredentialProviderExtensionPromoEnabled()) {
     DCHECK(_credentialProviderPromoHandler);
@@ -337,8 +346,10 @@
 // Notifies delegate about password deletion and records metric if needed.
 - (void)passwordDeletionConfirmedForCompromised:(BOOL)compromised
                                        forIndex:(int)index {
-  [self.delegate passwordDetailsCoordinator:self
-                           deleteCredential:self.mediator.credentials[index]];
+  [self.delegate
+      passwordDetailsCoordinator:self
+                deleteCredential:self.mediator.credentials[index]
+               shouldDismissView:(self.mediator.credentials.size() - 1 == 0)];
   if (compromised) {
     base::UmaHistogramEnumeration(
         "PasswordManager.BulkCheck.UserAction",
diff --git a/ios/chrome/browser/ui/settings/password/password_details/password_details_coordinator_delegate.h b/ios/chrome/browser/ui/settings/password/password_details/password_details_coordinator_delegate.h
index ffea15f..ccc6011 100644
--- a/ios/chrome/browser/ui/settings/password/password_details/password_details_coordinator_delegate.h
+++ b/ios/chrome/browser/ui/settings/password/password_details/password_details_coordinator_delegate.h
@@ -18,11 +18,14 @@
 - (void)passwordDetailsCoordinatorDidRemove:
     (PasswordDetailsCoordinator*)coordinator;
 
-// Called when user deleted password. This action should be handled
-// outside to update the list of passwords immediately.
+// Called when user deleted password. This action should be handled outside to
+// update the list of passwords immediately. Callers should pass YES for
+// `shouldDismiss` if this is the last password on the page, to ensure the view
+// controller gets dismissed.
 - (void)passwordDetailsCoordinator:(PasswordDetailsCoordinator*)coordinator
                   deleteCredential:
-                      (const password_manager::CredentialUIEntry&)credential;
+                      (const password_manager::CredentialUIEntry&)credential
+                 shouldDismissView:(BOOL)shouldDismiss;
 
 @end
 
diff --git a/ios/chrome/browser/ui/settings/password/password_details/password_details_mediator.h b/ios/chrome/browser/ui/settings/password/password_details/password_details_mediator.h
index 7e53d80..404edc1 100644
--- a/ios/chrome/browser/ui/settings/password/password_details/password_details_mediator.h
+++ b/ios/chrome/browser/ui/settings/password/password_details/password_details_mediator.h
@@ -43,6 +43,9 @@
 // Disconnects the mediator from all observers.
 - (void)disconnect;
 
+// Remove credential from credentials cache.
+- (void)removeCredential:(const password_manager::CredentialUIEntry&)credential;
+
 @end
 
 #endif  // IOS_CHROME_BROWSER_UI_SETTINGS_PASSWORD_PASSWORD_DETAILS_PASSWORD_DETAILS_MEDIATOR_H_
diff --git a/ios/chrome/browser/ui/settings/password/password_details/password_details_mediator.mm b/ios/chrome/browser/ui/settings/password/password_details/password_details_mediator.mm
index a8c5822..6c7367623 100644
--- a/ios/chrome/browser/ui/settings/password/password_details/password_details_mediator.mm
+++ b/ios/chrome/browser/ui/settings/password/password_details/password_details_mediator.mm
@@ -103,19 +103,11 @@
   _manager->RemoveObserver(_passwordCheckObserver.get());
 }
 
-// Update the usernames by domain dictionary by removing the old username and
-// adding the new one if it has changed.
-- (void)updateOldUsernameInDict:(NSString*)oldUsername
-                  toNewUsername:(NSString*)newUsername
-                withSignonRealm:(NSString*)signonRealm {
-  if ([oldUsername isEqualToString:newUsername]) {
-    return;
-  }
-
-  NSMutableSet* set = [_usernamesWithSameDomainDict objectForKey:signonRealm];
-  if (set) {
-    [set removeObject:oldUsername];
-    [set addObject:newUsername];
+- (void)removeCredential:
+    (const password_manager::CredentialUIEntry&)credential {
+  auto it = base::ranges::find(_credentials, credential);
+  if (it != _credentials.end()) {
+    _credentials.erase(it);
   }
 }
 
@@ -239,4 +231,20 @@
   [self.consumer setPasswords:passwords andTitle:_displayName];
 }
 
+// Update the usernames by domain dictionary by removing the old username and
+// adding the new one if it has changed.
+- (void)updateOldUsernameInDict:(NSString*)oldUsername
+                  toNewUsername:(NSString*)newUsername
+                withSignonRealm:(NSString*)signonRealm {
+  if ([oldUsername isEqualToString:newUsername]) {
+    return;
+  }
+
+  NSMutableSet* set = [_usernamesWithSameDomainDict objectForKey:signonRealm];
+  if (set) {
+    [set removeObject:oldUsername];
+    [set addObject:newUsername];
+  }
+}
+
 @end
diff --git a/ios/chrome/browser/ui/settings/password/password_details/password_details_table_view_controller.mm b/ios/chrome/browser/ui/settings/password/password_details/password_details_table_view_controller.mm
index 5c93f0a8..7540fa0 100644
--- a/ios/chrome/browser/ui/settings/password/password_details/password_details_table_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/password/password_details/password_details_table_view_controller.mm
@@ -24,7 +24,6 @@
 #import "ios/chrome/browser/ui/settings/password/password_details/password_details_table_view_constants.h"
 #import "ios/chrome/browser/ui/settings/password/password_details/password_details_table_view_controller_delegate.h"
 #import "ios/chrome/browser/ui/settings/password/passwords_table_view_constants.h"
-#import "ios/chrome/browser/ui/settings/settings_root_table_constants.h"
 #import "ios/chrome/browser/ui/table_view/cells/table_view_text_button_item.h"
 #import "ios/chrome/browser/ui/table_view/cells/table_view_text_edit_item.h"
 #import "ios/chrome/browser/ui/table_view/cells/table_view_text_edit_item_delegate.h"
@@ -357,7 +356,8 @@
   return item;
 }
 
-- (TableViewTextButtonItem*)deleteButtonItem {
+- (TableViewTextButtonItem*)deleteButtonItemForPasswordDetails:
+    (PasswordDetails*)passwordDetails {
   TableViewTextButtonItem* item =
       [[TableViewTextButtonItem alloc] initWithType:ItemTypeDeleteButton];
   item.buttonText = l10n_util::GetNSString(IDS_IOS_SETTINGS_TOOLBAR_DELETE);
@@ -365,7 +365,9 @@
   item.disableButtonIntrinsicWidth = YES;
   item.buttonTextColor = [UIColor colorNamed:kRedColor];
   item.buttonBackgroundColor = [UIColor clearColor];
-  item.buttonAccessibilityIdentifier = kSettingsToolbarDeleteButtonId;
+  item.buttonAccessibilityIdentifier = [NSString
+      stringWithFormat:@"%@%@%@", kDeleteButtonForPasswordDetailsId,
+                       passwordDetails.username, passwordDetails.password];
   return item;
 }
 
@@ -978,7 +980,7 @@
   }
 
   if (IsPasswordGroupingEnabled() && self.tableView.editing) {
-    [model addItem:[self deleteButtonItem]
+    [model addItem:[self deleteButtonItemForPasswordDetails:passwordDetails]
         toSectionWithIdentifier:sectionForPassword];
   }
   [self.passwordDetailsInfoItems addObject:passwordItem];
diff --git a/ios/chrome/browser/ui/settings/password/password_issues_coordinator.mm b/ios/chrome/browser/ui/settings/password/password_issues_coordinator.mm
index eee1763b..1efb27e 100644
--- a/ios/chrome/browser/ui/settings/password/password_issues_coordinator.mm
+++ b/ios/chrome/browser/ui/settings/password/password_issues_coordinator.mm
@@ -138,7 +138,8 @@
 
 - (void)passwordDetailsCoordinator:(PasswordDetailsCoordinator*)coordinator
                   deleteCredential:
-                      (const password_manager::CredentialUIEntry&)credential {
+                      (const password_manager::CredentialUIEntry&)credential
+                 shouldDismissView:(BOOL)shouldDismiss {
   if (![self.delegate willHandlePasswordDeletion:credential]) {
     [self.mediator deleteCredential:credential];
   }
diff --git a/ios/chrome/browser/ui/settings/password/password_manager_egtest.mm b/ios/chrome/browser/ui/settings/password/password_manager_egtest.mm
index 2c442ce..ce5edf5 100644
--- a/ios/chrome/browser/ui/settings/password/password_manager_egtest.mm
+++ b/ios/chrome/browser/ui/settings/password/password_manager_egtest.mm
@@ -192,6 +192,17 @@
                     grey_interactable(), nullptr);
 }
 
+// Matcher for the Delete button at with accessibility identifier containing
+// `username` and `password` in Password Details view.
+id<GREYMatcher> DeleteButtonForUsernameAndPassword(NSString* username,
+                                                   NSString* password) {
+  return grey_allOf(
+      grey_accessibilityID([NSString
+          stringWithFormat:@"%@%@%@", kDeleteButtonForPasswordDetailsId,
+                           username, password]),
+      grey_interactable(), nullptr);
+}
+
 // Matcher for the Delete button in Password Details view.
 id<GREYMatcher> DeleteButton() {
   return grey_allOf(
@@ -2558,6 +2569,96 @@
       assertWithMatcher:grey_notNil()];
 }
 
+// Tests that the detail view is dismissed when the last password is deleted,
+// but stays if there are still passwords on the page.
+- (void)testPasswordsDeletionNavigation {
+  if (![self groupingEnabled]) {
+    EARL_GREY_TEST_SKIPPED(@"This test is only for grouped passwords.");
+  }
+
+  // Save forms with the same origin to be deleted later.
+  GREYAssert([PasswordSettingsAppInterface
+                 saveExamplePassword:@"password1"
+                            userName:@"user1"
+                              origin:@"https://example1.com"],
+             @"Stored form was not found in the PasswordStore results.");
+  GREYAssert([PasswordSettingsAppInterface
+                 saveExamplePassword:@"password2"
+                            userName:@"user2"
+                              origin:@"https://example1.com"],
+             @"Stored form was not found in the PasswordStore results.");
+  GREYAssert([PasswordSettingsAppInterface
+                 saveExamplePassword:@"password3"
+                            userName:@"user3"
+                              origin:@"https://example3.com"],
+             @"Stored form was not found in the PasswordStore results.");
+
+  OpenPasswordManager();
+
+  [GetInteractionForPasswordEntry(@"example1.com, 2 accounts")
+      assertWithMatcher:grey_notNil()];
+  [GetInteractionForPasswordEntry(@"example1.com, 2 accounts")
+      performAction:grey_tap()];
+
+  [PasswordSettingsAppInterface setUpMockReauthenticationModule];
+  [PasswordSettingsAppInterface mockReauthenticationModuleExpectedResult:
+                                    ReauthenticationResult::kSuccess];
+
+  [[EarlGrey selectElementWithMatcher:NavigationBarEditButton()]
+      performAction:grey_tap()];
+
+  // Delete first password.
+  [[EarlGrey selectElementWithMatcher:DeleteButtonForUsernameAndPassword(
+                                          @"user1", @"password1")]
+      performAction:grey_tap()];
+
+  [[EarlGrey selectElementWithMatcher:DeleteConfirmationButton()]
+      performAction:grey_tap()];
+
+  // Check that the current view is still the password details since there is
+  // still one more password left on the view.
+  ConditionBlock condition = ^{
+    NSError* error = nil;
+    [[EarlGrey selectElementWithMatcher:grey_accessibilityID(
+                                            kPasswordDetailsTableViewId)]
+        assertWithMatcher:grey_notNil()
+                    error:&error];
+    return error == nil;
+  };
+
+  GREYAssert(base::test::ios::WaitUntilConditionOrTimeout(
+                 base::test::ios::kWaitForUIElementTimeout, condition),
+             @"Waiting for the view to load");
+
+  // Delete last password.
+  [[EarlGrey selectElementWithMatcher:DeleteButtonForUsernameAndPassword(
+                                          @"user2", @"password2")]
+      performAction:grey_tap()];
+
+  [[EarlGrey selectElementWithMatcher:DeleteConfirmationButton()]
+      performAction:grey_tap()];
+
+  // Check that the current view is now the password manager since we deleted
+  // the last password.
+  condition = ^{
+    NSError* error = nil;
+    [[EarlGrey
+        selectElementWithMatcher:grey_accessibilityID(kPasswordsTableViewId)]
+        assertWithMatcher:grey_notNil()
+                    error:&error];
+    return error == nil;
+  };
+
+  GREYAssert(base::test::ios::WaitUntilConditionOrTimeout(
+                 base::test::ios::kWaitForUIElementTimeout, condition),
+             @"Waiting for the view to load");
+
+  [[EarlGrey selectElementWithMatcher:SettingsMenuBackButton()]
+      performAction:grey_tap()];
+  [[EarlGrey selectElementWithMatcher:SettingsDoneButton()]
+      performAction:grey_tap()];
+}
+
 @end
 
 // Rerun all the tests in this file but with kPasswordsGrouping disabled. This
diff --git a/ios/chrome/browser/ui/settings/password/passwords_coordinator.mm b/ios/chrome/browser/ui/settings/password/passwords_coordinator.mm
index bc534bc..4a6bbd75 100644
--- a/ios/chrome/browser/ui/settings/password/passwords_coordinator.mm
+++ b/ios/chrome/browser/ui/settings/password/passwords_coordinator.mm
@@ -272,10 +272,17 @@
 
 - (void)passwordDetailsCoordinator:(PasswordDetailsCoordinator*)coordinator
                   deleteCredential:
-                      (const password_manager::CredentialUIEntry&)credential {
+                      (const password_manager::CredentialUIEntry&)credential
+                 shouldDismissView:(BOOL)shouldDismiss {
   DCHECK_EQ(self.passwordDetailsCoordinator, coordinator);
   [self.mediator deleteCredential:credential];
-  [self.baseNavigationController popViewControllerAnimated:YES];
+
+  if (shouldDismiss) {
+    [self.baseNavigationController popViewControllerAnimated:YES];
+  } else {
+    [self.passwordDetailsCoordinator
+        removeCredentialFromCacheAndRefreshTableView:credential];
+  }
 }
 
 #pragma mark AddPasswordDetailsCoordinatorDelegate
diff --git a/ios/chrome/browser/ui/settings/password/passwords_table_view_constants.h b/ios/chrome/browser/ui/settings/password/passwords_table_view_constants.h
index f96c42e..eae40600 100644
--- a/ios/chrome/browser/ui/settings/password/passwords_table_view_constants.h
+++ b/ios/chrome/browser/ui/settings/password/passwords_table_view_constants.h
@@ -40,6 +40,9 @@
 // displayed in the table.
 extern NSString* const kAddPasswordButtonId;
 
+// Delete button accessibility identifier for Password Details.
+extern NSString* const kDeleteButtonForPasswordDetailsId;
+
 // Sections of the password settings
 typedef NS_ENUM(NSInteger, PasswordSectionIdentifier) {
   SectionIdentifierSavePasswordsSwitch = kSectionIdentifierEnumZero,
diff --git a/ios/chrome/browser/ui/settings/password/passwords_table_view_constants.mm b/ios/chrome/browser/ui/settings/password/passwords_table_view_constants.mm
index d4fc352..e2bcfbd 100644
--- a/ios/chrome/browser/ui/settings/password/passwords_table_view_constants.mm
+++ b/ios/chrome/browser/ui/settings/password/passwords_table_view_constants.mm
@@ -39,3 +39,6 @@
 NSString* const kAddPasswordButtonId = @"addPasswordItem";
 
 NSString* const kPasswordIssuesTableViewId = @"kPasswordIssuesTableViewId";
+
+NSString* const kDeleteButtonForPasswordDetailsId =
+    @"kDeleteButtonForPasswordDetailsId";
diff --git a/media/base/media_switches.cc b/media/base/media_switches.cc
index 3ac3d244..213ed63 100644
--- a/media/base/media_switches.cc
+++ b/media/base/media_switches.cc
@@ -986,12 +986,6 @@
 #endif  // BUILDFLAG(USE_CHROMEOS_MEDIA_ACCELERATION)
 
 #if BUILDFLAG(IS_WIN)
-// Does NV12->NV12 video copy on the main thread right before the texture's
-// used by GL.
-BASE_FEATURE(kDelayCopyNV12Textures,
-             "DelayCopyNV12Textures",
-             base::FEATURE_ENABLED_BY_DEFAULT);
-
 // Enables DirectShow GetPhotoState implementation
 // Created to act as a kill switch by disabling it, in the case of the
 // resurgence of https://crbug.com/722038
diff --git a/media/base/media_switches.h b/media/base/media_switches.h
index f59a686..b24cd08 100644
--- a/media/base/media_switches.h
+++ b/media/base/media_switches.h
@@ -308,7 +308,6 @@
 #endif  // BUILDFLAG(USE_CHROMEOS_MEDIA_ACCELERATION)
 
 #if BUILDFLAG(IS_WIN)
-MEDIA_EXPORT BASE_DECLARE_FEATURE(kDelayCopyNV12Textures);
 MEDIA_EXPORT BASE_DECLARE_FEATURE(kDirectShowGetPhotoState);
 MEDIA_EXPORT BASE_DECLARE_FEATURE(kIncludeIRCamerasInDeviceEnumeration);
 MEDIA_EXPORT BASE_DECLARE_FEATURE(kMediaFoundationVideoCapture);
diff --git a/media/formats/webm/webm_video_client.cc b/media/formats/webm/webm_video_client.cc
index b9add5a..9342e6e 100644
--- a/media/formats/webm/webm_video_client.cc
+++ b/media/formats/webm/webm_video_client.cc
@@ -5,6 +5,7 @@
 #include "media/formats/webm/webm_video_client.h"
 
 #include "media/base/video_decoder_config.h"
+#include "media/formats/mp4/box_definitions.h"
 #include "media/formats/webm/webm_constants.h"
 #include "media/media_buildflags.h"
 
@@ -28,6 +29,22 @@
       static_cast<size_t>(VP9PROFILE_PROFILE0) + data[2]);
 }
 
+#if BUILDFLAG(ENABLE_AV1_DECODER)
+media::VideoCodecProfile GetAV1CodecProfile(const std::vector<uint8_t>& data) {
+  if (data.empty()) {
+    return AV1PROFILE_PROFILE_MAIN;
+  }
+
+  mp4::AV1CodecConfigurationRecord av1_config;
+  if (av1_config.Parse(data.data(), data.size())) {
+    return av1_config.profile;
+  }
+
+  DLOG(WARNING) << "Failed to parser AV1 extra data for profile.";
+  return AV1PROFILE_PROFILE_MAIN;
+}
+#endif  // BUILDFLAG(ENABLE_AV1_DECODER)
+
 // Values for "StereoMode" are spec'd here:
 // https://www.matroska.org/technical/elements.html#StereoMode
 bool IsValidStereoMode(int64_t stereo_mode_code) {
@@ -92,11 +109,8 @@
                            config->hdr_metadata().has_value() || !is_8bit);
 #if BUILDFLAG(ENABLE_AV1_DECODER)
   } else if (codec_id == "V_AV1") {
-    // TODO(dalecurtis): AV1 profiles in WebM are not finalized, this needs
-    // updating to read the actual profile and configuration before enabling for
-    // release. http://crbug.com/784993
     video_codec = VideoCodec::kAV1;
-    profile = AV1PROFILE_PROFILE_MAIN;
+    profile = GetAV1CodecProfile(codec_private);
 #endif
   } else {
     MEDIA_LOG(ERROR, media_log_) << "Unsupported video codec_id " << codec_id;
diff --git a/media/formats/webm/webm_video_client_unittest.cc b/media/formats/webm/webm_video_client_unittest.cc
index dd91182..a8de5a6 100644
--- a/media/formats/webm/webm_video_client_unittest.cc
+++ b/media/formats/webm/webm_video_client_unittest.cc
@@ -162,6 +162,31 @@
       << expected_config.AsHumanReadableString() << ")";
 }
 
+#if BUILDFLAG(ENABLE_AV1_DECODER)
+TEST_F(WebMVideoClientTest, InitializeConfigAV1Profile) {
+  const std::string codec_id = "V_AV1";
+  const auto expected_profile = AV1PROFILE_PROFILE_HIGH;
+  const std::vector<uint8_t> codec_private{0x81, 0x20, 0x00, 0x00, 0x0a, 0x0a,
+                                           0x20, 0x00, 0x00, 0x03, 0xbf, 0x7f,
+                                           0x7b, 0xff, 0xf3, 0x04};
+
+  VideoDecoderConfig config;
+  EXPECT_TRUE(webm_video_client_.InitializeConfig(codec_id, codec_private,
+                                                  EncryptionScheme(), &config));
+
+  VideoDecoderConfig expected_config(
+      VideoCodec::kAV1, expected_profile,
+      VideoDecoderConfig::AlphaMode::kIsOpaque, VideoColorSpace::REC709(),
+      kNoTransformation, kCodedSize, gfx::Rect(kCodedSize), kCodedSize,
+      codec_private, EncryptionScheme::kUnencrypted);
+
+  EXPECT_TRUE(config.Matches(expected_config))
+      << "Config (" << config.AsHumanReadableString()
+      << ") does not match expected ("
+      << expected_config.AsHumanReadableString() << ")";
+}
+#endif
+
 TEST_F(WebMVideoClientTest, InvalidStereoMode) {
   EXPECT_MEDIA_LOG(UnexpectedStereoMode());
   OnUInt(kWebMIdStereoMode, 15);
diff --git a/media/gpu/android/media_codec_video_decoder.cc b/media/gpu/android/media_codec_video_decoder.cc
index ffecc9f..855013e 100644
--- a/media/gpu/android/media_codec_video_decoder.cc
+++ b/media/gpu/android/media_codec_video_decoder.cc
@@ -142,21 +142,25 @@
 
   if (device_info->IsAv1DecoderAvailable()) {
     if (device_info->IsDecoderKnownUnaccelerated(VideoCodec::kAV1)) {
-      supported_configs.emplace_back(AV1PROFILE_MIN, AV1PROFILE_MAX,
-                                     gfx::Size(0, 0), gfx::Size(3840, 2160),
+      supported_configs.emplace_back(AV1PROFILE_PROFILE_MAIN,
+                                     AV1PROFILE_PROFILE_MAIN, gfx::Size(0, 0),
+                                     gfx::Size(3840, 2160),
                                      true,   // allow_encrypted
                                      true);  // require_encrypted
-      supported_configs.emplace_back(AV1PROFILE_MIN, AV1PROFILE_MAX,
-                                     gfx::Size(0, 0), gfx::Size(2160, 3840),
+      supported_configs.emplace_back(AV1PROFILE_PROFILE_MAIN,
+                                     AV1PROFILE_PROFILE_MAIN, gfx::Size(0, 0),
+                                     gfx::Size(2160, 3840),
                                      true,   // allow_encrypted
                                      true);  // require_encrypted
     } else {
-      supported_configs.emplace_back(AV1PROFILE_MIN, AV1PROFILE_MAX,
-                                     gfx::Size(0, 0), gfx::Size(3840, 2160),
+      supported_configs.emplace_back(AV1PROFILE_PROFILE_MAIN,
+                                     AV1PROFILE_PROFILE_MAIN, gfx::Size(0, 0),
+                                     gfx::Size(3840, 2160),
                                      true,    // allow_encrypted
                                      false);  // require_encrypted
-      supported_configs.emplace_back(AV1PROFILE_MIN, AV1PROFILE_MAX,
-                                     gfx::Size(0, 0), gfx::Size(2160, 3840),
+      supported_configs.emplace_back(AV1PROFILE_PROFILE_MAIN,
+                                     AV1PROFILE_PROFILE_MAIN, gfx::Size(0, 0),
+                                     gfx::Size(2160, 3840),
                                      true,    // allow_encrypted
                                      false);  // require_encrypted
     }
diff --git a/media/gpu/vaapi/vaapi_dmabuf_video_frame_mapper.cc b/media/gpu/vaapi/vaapi_dmabuf_video_frame_mapper.cc
index 06815fded..0158342 100644
--- a/media/gpu/vaapi/vaapi_dmabuf_video_frame_mapper.cc
+++ b/media/gpu/vaapi/vaapi_dmabuf_video_frame_mapper.cc
@@ -181,8 +181,8 @@
     return nullptr;
   }
 
-  if (!(permissions & PROT_READ && permissions & PROT_WRITE)) {
-    LOG(ERROR) << "VAAPI DMA Buffer must be mapped read/write.";
+  if (!(permissions & PROT_READ)) {
+    LOG(ERROR) << "VAAPI DMA Buffer must be mapped with read permissions.";
     return nullptr;
   }
 
diff --git a/media/gpu/windows/dxva_picture_buffer_win.cc b/media/gpu/windows/dxva_picture_buffer_win.cc
index 9ff15bc..b49270a 100644
--- a/media/gpu/windows/dxva_picture_buffer_win.cc
+++ b/media/gpu/windows/dxva_picture_buffer_win.cc
@@ -39,15 +39,6 @@
 
       return picture_buffer;
     }
-    case DXVAVideoDecodeAccelerator::PictureBufferMechanism::
-        DELAYED_COPY_TO_NV12: {
-      auto picture_buffer =
-          std::make_unique<EGLStreamDelayedCopyPictureBuffer>(buffer);
-      if (!picture_buffer->Initialize(decoder))
-        return nullptr;
-
-      return picture_buffer;
-    }
     case DXVAVideoDecodeAccelerator::PictureBufferMechanism::COPY_TO_NV12: {
       auto picture_buffer =
           std::make_unique<EGLStreamCopyPictureBuffer>(buffer);
@@ -476,121 +467,6 @@
   return true;
 }
 
-EGLStreamDelayedCopyPictureBuffer::EGLStreamDelayedCopyPictureBuffer(
-    const PictureBuffer& buffer)
-    : DXVAPictureBuffer(buffer), stream_(nullptr) {}
-
-EGLStreamDelayedCopyPictureBuffer::~EGLStreamDelayedCopyPictureBuffer() {
-  // stream_ will be deleted by gl_image_.
-}
-
-bool EGLStreamDelayedCopyPictureBuffer::Initialize(
-    const DXVAVideoDecodeAccelerator& decoder) {
-  RETURN_ON_FAILURE(picture_buffer_.service_texture_ids().size() >= 2,
-                    "Not enough texture ids provided", false);
-
-  EGLDisplay egl_display = gl::GLSurfaceEGL::GetGLDisplayEGL()->GetDisplay();
-  const EGLint stream_attributes[] = {
-      EGL_CONSUMER_LATENCY_USEC_KHR,
-      0,
-      EGL_CONSUMER_ACQUIRE_TIMEOUT_USEC_KHR,
-      0,
-      EGL_NONE,
-  };
-  stream_ = eglCreateStreamKHR(egl_display, stream_attributes);
-  RETURN_ON_FAILURE(!!stream_, "Could not create stream", false);
-  gl::ScopedActiveTexture texture0(GL_TEXTURE0);
-  gl::ScopedTextureBinder texture0_binder(
-      GL_TEXTURE_EXTERNAL_OES, picture_buffer_.service_texture_ids()[0]);
-  gl::ScopedActiveTexture texture1(GL_TEXTURE1);
-  gl::ScopedTextureBinder texture1_binder(
-      GL_TEXTURE_EXTERNAL_OES, picture_buffer_.service_texture_ids()[1]);
-
-  EGLAttrib consumer_attributes[] = {
-      EGL_COLOR_BUFFER_TYPE,
-      EGL_YUV_BUFFER_EXT,
-      EGL_YUV_NUMBER_OF_PLANES_EXT,
-      2,
-      EGL_YUV_PLANE0_TEXTURE_UNIT_NV,
-      0,
-      EGL_YUV_PLANE1_TEXTURE_UNIT_NV,
-      1,
-      EGL_NONE,
-  };
-  EGLBoolean result = eglStreamConsumerGLTextureExternalAttribsNV(
-      egl_display, stream_, consumer_attributes);
-  RETURN_ON_FAILURE(result, "Could not set stream consumer", false);
-
-  EGLAttrib producer_attributes[] = {
-      EGL_NONE,
-  };
-
-  result = eglCreateStreamProducerD3DTextureANGLE(egl_display, stream_,
-                                                  producer_attributes);
-  RETURN_ON_FAILURE(result, "Could not create stream producer", false);
-  scoped_refptr<CopyingGLImageEGLStream> copying_image_ =
-      base::MakeRefCounted<CopyingGLImageEGLStream>(
-          ComD3D11Device(decoder.D3D11Device()), size(), stream_);
-  gl_image_ = copying_image_;
-  return copying_image_->Initialize();
-}
-
-bool EGLStreamDelayedCopyPictureBuffer::ReusePictureBuffer() {
-  DCHECK_NE(UNUSED, state_);
-
-  static_cast<CopyingGLImageEGLStream*>(gl_image_.get())->UnbindFromTexture();
-  if (current_d3d_sample_) {
-    dx11_decoding_texture_.Reset();
-    current_d3d_sample_.Reset();
-  }
-  state_ = UNUSED;
-  return true;
-}
-
-bool EGLStreamDelayedCopyPictureBuffer::BindSampleToTexture(
-    DXVAVideoDecodeAccelerator* decoder,
-    Microsoft::WRL::ComPtr<IMFSample> sample) {
-  DCHECK_EQ(BOUND, state_);
-  state_ = IN_CLIENT;
-
-  current_d3d_sample_ = sample;
-
-  Microsoft::WRL::ComPtr<IMFMediaBuffer> output_buffer;
-  HRESULT hr = current_d3d_sample_->GetBufferByIndex(0, &output_buffer);
-  RETURN_ON_HR_FAILURE(hr, "Failed to get buffer from output sample", false);
-
-  Microsoft::WRL::ComPtr<IMFDXGIBuffer> dxgi_buffer;
-  hr = output_buffer.As(&dxgi_buffer);
-  RETURN_ON_HR_FAILURE(hr, "Failed to get DXGIBuffer from output sample",
-                       false);
-  hr = dxgi_buffer->GetResource(IID_PPV_ARGS(&dx11_decoding_texture_));
-  RETURN_ON_HR_FAILURE(hr, "Failed to get texture from output sample", false);
-  UINT subresource;
-  dxgi_buffer->GetSubresourceIndex(&subresource);
-  if (!decoder->InitializeID3D11VideoProcessor(size().width(), size().height(),
-                                               color_space_))
-    return false;
-
-  DCHECK(decoder->d3d11_processor_);
-  DCHECK(decoder->enumerator_);
-
-  CopyingGLImageEGLStream* copying_gl_image =
-      static_cast<CopyingGLImageEGLStream*>(gl_image_.get());
-  DCHECK(copying_gl_image);
-
-  copying_gl_image->SetTexture(dx11_decoding_texture_, subresource);
-  return copying_gl_image->InitializeVideoProcessor(decoder->d3d11_processor_,
-                                                    decoder->enumerator_);
-}
-
-bool EGLStreamDelayedCopyPictureBuffer::AllowOverlay() const {
-  return true;
-}
-
-bool EGLStreamDelayedCopyPictureBuffer::CanBindSamples() const {
-  return true;
-}
-
 EGLStreamCopyPictureBuffer::EGLStreamCopyPictureBuffer(
     const PictureBuffer& buffer)
     : DXVAPictureBuffer(buffer), stream_(nullptr) {}
diff --git a/media/gpu/windows/dxva_picture_buffer_win.h b/media/gpu/windows/dxva_picture_buffer_win.h
index e3c5783..fed9960 100644
--- a/media/gpu/windows/dxva_picture_buffer_win.h
+++ b/media/gpu/windows/dxva_picture_buffer_win.h
@@ -204,26 +204,6 @@
   ComD3D11Texture2D dx11_decoding_texture_;
 };
 
-// Shares the decoded texture with ANGLE without copying by using an EGL stream.
-class EGLStreamDelayedCopyPictureBuffer : public DXVAPictureBuffer {
- public:
-  explicit EGLStreamDelayedCopyPictureBuffer(const PictureBuffer& buffer);
-  ~EGLStreamDelayedCopyPictureBuffer() override;
-
-  bool Initialize(const DXVAVideoDecodeAccelerator& decoder);
-  bool ReusePictureBuffer() override;
-  bool BindSampleToTexture(DXVAVideoDecodeAccelerator* decoder,
-                           Microsoft::WRL::ComPtr<IMFSample> sample) override;
-  bool AllowOverlay() const override;
-  bool CanBindSamples() const override;
-
- private:
-  EGLStreamKHR stream_;
-
-  Microsoft::WRL::ComPtr<IMFSample> current_d3d_sample_;
-  ComD3D11Texture2D dx11_decoding_texture_;
-};
-
 // Creates an NV12 texture and copies to it, then shares that with ANGLE.
 class EGLStreamCopyPictureBuffer : public DXVAPictureBuffer {
  public:
diff --git a/media/gpu/windows/dxva_video_decode_accelerator_win.cc b/media/gpu/windows/dxva_video_decode_accelerator_win.cc
index 05efd05..1c714c6 100644
--- a/media/gpu/windows/dxva_video_decode_accelerator_win.cc
+++ b/media/gpu/windows/dxva_video_decode_accelerator_win.cc
@@ -668,9 +668,6 @@
                                          : kNumPictureBuffers),
       support_copy_nv12_textures_(gpu_preferences.enable_nv12_dxgi_video &&
                                   !workarounds.disable_nv12_dxgi_video),
-      support_delayed_copy_nv12_textures_(
-          base::FeatureList::IsEnabled(kDelayCopyNV12Textures) &&
-          !workarounds.disable_delayed_copy_nv12),
       use_dx11_(false),
       use_keyed_mutex_(false),
       using_angle_device_(false),
@@ -1232,16 +1229,6 @@
     RETURN_AND_NOTIFY_ON_FAILURE(it->second->ReusePictureBuffer(),
                                  "Failed to reuse picture buffer",
                                  PLATFORM_FAILURE, );
-    if (bind_image_cb_ && (GetPictureBufferMechanism() ==
-                           PictureBufferMechanism::DELAYED_COPY_TO_NV12)) {
-      // Unbind the image to ensure it will be copied again the next time it's
-      // needed.
-      for (uint32_t client_id :
-           it->second->picture_buffer().client_texture_ids()) {
-        bind_image_cb_.Run(client_id, GetTextureTarget(),
-                           it->second->gl_image());
-      }
-    }
 
     ProcessPendingSamples();
     if (pending_flush_) {
@@ -2987,9 +2974,7 @@
   // If we're copying textures or just not using color space information, set
   // the same color space on input and output.
   if ((!use_color_info_ && !use_fp16_) ||
-      GetPictureBufferMechanism() == PictureBufferMechanism::COPY_TO_NV12 ||
-      GetPictureBufferMechanism() ==
-          PictureBufferMechanism::DELAYED_COPY_TO_NV12) {
+      GetPictureBufferMechanism() == PictureBufferMechanism::COPY_TO_NV12) {
     const auto d3d11_color_space =
         gfx::ColorSpaceWin::GetD3D11ColorSpace(color_space);
     video_context_->VideoProcessorSetOutputColorSpace(d3d11_processor_.Get(),
@@ -3196,7 +3181,6 @@
 uint32_t DXVAVideoDecodeAccelerator::GetTextureTarget() const {
   switch (GetPictureBufferMechanism()) {
     case PictureBufferMechanism::BIND:
-    case PictureBufferMechanism::DELAYED_COPY_TO_NV12:
     case PictureBufferMechanism::COPY_TO_NV12:
       return GL_TEXTURE_EXTERNAL_OES;
     case PictureBufferMechanism::COPY_TO_RGB:
@@ -3330,8 +3314,6 @@
   // It works fine dealing P010/P016 with BIND mode.
   if (support_share_nv12_textures_ || decoder_output_p010_or_p016_)
     return PictureBufferMechanism::BIND;
-  if (support_delayed_copy_nv12_textures_ && support_copy_nv12_textures_)
-    return PictureBufferMechanism::DELAYED_COPY_TO_NV12;
   if (support_copy_nv12_textures_)
     return PictureBufferMechanism::COPY_TO_NV12;
   return PictureBufferMechanism::COPY_TO_RGB;
@@ -3340,7 +3322,6 @@
 bool DXVAVideoDecodeAccelerator::ShouldUseANGLEDevice() const {
   switch (GetPictureBufferMechanism()) {
     case PictureBufferMechanism::BIND:
-    case PictureBufferMechanism::DELAYED_COPY_TO_NV12:
       return true;
     case PictureBufferMechanism::COPY_TO_NV12:
     case PictureBufferMechanism::COPY_TO_RGB:
diff --git a/media/gpu/windows/dxva_video_decode_accelerator_win.h b/media/gpu/windows/dxva_video_decode_accelerator_win.h
index 78c5d8c7..3c6ea509 100644
--- a/media/gpu/windows/dxva_video_decode_accelerator_win.h
+++ b/media/gpu/windows/dxva_video_decode_accelerator_win.h
@@ -149,8 +149,8 @@
 
     // Bind the resulting GLImage to the NV12 texture. If the texture's used
     // in a an overlay than use it directly, otherwise copy it to another NV12
-    // texture when necessary.
-    DELAYED_COPY_TO_NV12 = 2,
+    // texture when necessary -- no longer used
+    // DELAYED_COPY_TO_NV12 = 2,
 
     // Bind the NV12 decoder texture directly to the texture used in ANGLE.
     BIND = 3,
@@ -553,9 +553,6 @@
   // ANGLE.
   bool support_copy_nv12_textures_;
 
-  // Supports copying NV12 textures on the main thread to use in ANGLE.
-  bool support_delayed_copy_nv12_textures_;
-
   // Copy video to FP16 scRGB textures.
   bool use_fp16_ = false;
 
diff --git a/media/gpu/windows/gl_image_egl_stream.cc b/media/gpu/windows/gl_image_egl_stream.cc
index 85eb127..6346f39 100644
--- a/media/gpu/windows/gl_image_egl_stream.cc
+++ b/media/gpu/windows/gl_image_egl_stream.cc
@@ -64,132 +64,4 @@
   }
 }
 
-CopyingGLImageEGLStream::CopyingGLImageEGLStream(
-    const Microsoft::WRL::ComPtr<ID3D11Device>& d3d11_device,
-    const gfx::Size& size,
-    EGLStreamKHR stream)
-    : GLImageEGLStream(size, stream), d3d11_device_(d3d11_device) {}
-
-bool CopyingGLImageEGLStream::Initialize() {
-  D3D11_TEXTURE2D_DESC desc;
-  desc.Width = size_.width();
-  desc.Height = size_.height();
-  desc.MipLevels = 1;
-  desc.ArraySize = 1;
-  desc.Format = DXGI_FORMAT_NV12;
-  desc.SampleDesc.Count = 1;
-  desc.SampleDesc.Quality = 0;
-  desc.Usage = D3D11_USAGE_DEFAULT;
-  desc.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET;
-  desc.CPUAccessFlags = 0;
-  desc.MiscFlags = 0;
-
-  HRESULT hr =
-      d3d11_device_->CreateTexture2D(&desc, nullptr, &decoder_copy_texture_);
-  if (FAILED(hr)) {
-    DLOG(ERROR) << "CreateTexture2D failed: " << std::hex << hr;
-    return false;
-  }
-  EGLDisplay egl_display = gl::GLSurfaceEGL::GetGLDisplayEGL()->GetDisplay();
-
-  EGLAttrib frame_attributes[] = {
-      EGL_D3D_TEXTURE_SUBRESOURCE_ID_ANGLE,
-      0,
-      EGL_NONE,
-  };
-
-  EGLBoolean result = eglStreamPostD3DTextureANGLE(
-      egl_display, stream_, static_cast<void*>(decoder_copy_texture_.Get()),
-      frame_attributes);
-  if (!result) {
-    DLOG(ERROR) << "eglStreamPostD3DTextureANGLE failed";
-    return false;
-  }
-  result = eglStreamConsumerAcquireKHR(egl_display, stream_);
-  if (!result) {
-    DLOG(ERROR) << "eglStreamConsumerAcquireKHR failed";
-    return false;
-  }
-
-  d3d11_device_.As(&video_device_);
-  Microsoft::WRL::ComPtr<ID3D11DeviceContext> context;
-  d3d11_device_->GetImmediateContext(&context);
-  context.As(&video_context_);
-
-#if DCHECK_IS_ON()
-  Microsoft::WRL::ComPtr<ID3D10Multithread> multithread;
-  d3d11_device_.As(&multithread);
-  DCHECK(multithread->GetMultithreadProtected());
-#endif  // DCHECK_IS_ON()
-
-  return true;
-}
-
-bool CopyingGLImageEGLStream::InitializeVideoProcessor(
-    const Microsoft::WRL::ComPtr<ID3D11VideoProcessor>& video_processor,
-    const Microsoft::WRL::ComPtr<ID3D11VideoProcessorEnumerator>& enumerator) {
-  output_view_.Reset();
-
-  Microsoft::WRL::ComPtr<ID3D11Device> processor_device;
-  video_processor->GetDevice(&processor_device);
-  DCHECK_EQ(d3d11_device_.Get(), processor_device.Get());
-
-  d3d11_processor_ = video_processor;
-  enumerator_ = enumerator;
-  D3D11_VIDEO_PROCESSOR_OUTPUT_VIEW_DESC output_view_desc = {
-      D3D11_VPOV_DIMENSION_TEXTURE2D};
-  output_view_desc.Texture2D.MipSlice = 0;
-  Microsoft::WRL::ComPtr<ID3D11VideoProcessorOutputView> output_view;
-  HRESULT hr = video_device_->CreateVideoProcessorOutputView(
-      decoder_copy_texture_.Get(), enumerator_.Get(), &output_view_desc,
-      &output_view_);
-  if (FAILED(hr)) {
-    DLOG(ERROR) << "Failed to get output view";
-    return false;
-  }
-  return true;
-}
-
-void CopyingGLImageEGLStream::UnbindFromTexture() {
-  copied_ = false;
-}
-
-bool CopyingGLImageEGLStream::BindTexImage(unsigned target) {
-  if (copied_) {
-    return true;
-  }
-
-  DCHECK(video_device_);
-  Microsoft::WRL::ComPtr<ID3D11Device> texture_device;
-  texture_->GetDevice(&texture_device);
-  DCHECK_EQ(d3d11_device_.Get(), texture_device.Get());
-
-  D3D11_VIDEO_PROCESSOR_INPUT_VIEW_DESC input_view_desc = {0};
-  input_view_desc.ViewDimension = D3D11_VPIV_DIMENSION_TEXTURE2D;
-  input_view_desc.Texture2D.ArraySlice = (UINT)level_;
-  input_view_desc.Texture2D.MipSlice = 0;
-  Microsoft::WRL::ComPtr<ID3D11VideoProcessorInputView> input_view;
-  HRESULT hr = video_device_->CreateVideoProcessorInputView(
-      texture_.Get(), enumerator_.Get(), &input_view_desc, &input_view);
-  if (FAILED(hr)) {
-    DLOG(ERROR) << "Failed to create video processor input view.";
-    return false;
-  }
-
-  D3D11_VIDEO_PROCESSOR_STREAM streams = {0};
-  streams.Enable = TRUE;
-  streams.pInputSurface = input_view.Get();
-
-  hr = video_context_->VideoProcessorBlt(d3d11_processor_.Get(),
-                                         output_view_.Get(), 0, 1, &streams);
-  if (FAILED(hr)) {
-    DLOG(ERROR) << "Failed to process video";
-    return false;
-  }
-  copied_ = true;
-  return true;
-}
-
-CopyingGLImageEGLStream::~CopyingGLImageEGLStream() = default;
-
 }  // namespace media
diff --git a/media/gpu/windows/gl_image_egl_stream.h b/media/gpu/windows/gl_image_egl_stream.h
index 4b9404e..61e2c5ae 100644
--- a/media/gpu/windows/gl_image_egl_stream.h
+++ b/media/gpu/windows/gl_image_egl_stream.h
@@ -48,37 +48,6 @@
   size_t level_ = 0;
 };
 
-// This copies to a new texture on bind.
-class CopyingGLImageEGLStream : public GLImageEGLStream {
- public:
-  CopyingGLImageEGLStream(
-      const Microsoft::WRL::ComPtr<ID3D11Device>& d3d11_device,
-      const gfx::Size& size,
-      EGLStreamKHR stream);
-
-  bool Initialize();
-  bool InitializeVideoProcessor(
-      const Microsoft::WRL::ComPtr<ID3D11VideoProcessor>& video_processor,
-      const Microsoft::WRL::ComPtr<ID3D11VideoProcessorEnumerator>& enumerator);
-  void UnbindFromTexture();
-
-  // GLImage implementation.
-  bool BindTexImage(unsigned target) override;
-
- private:
-  ~CopyingGLImageEGLStream() override;
-
-  bool copied_ = false;
-
-  Microsoft::WRL::ComPtr<ID3D11VideoDevice> video_device_;
-  Microsoft::WRL::ComPtr<ID3D11VideoContext> video_context_;
-  Microsoft::WRL::ComPtr<ID3D11VideoProcessor> d3d11_processor_;
-  Microsoft::WRL::ComPtr<ID3D11VideoProcessorEnumerator> enumerator_;
-  Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device_;
-  Microsoft::WRL::ComPtr<ID3D11Texture2D> decoder_copy_texture_;
-  Microsoft::WRL::ComPtr<ID3D11VideoProcessorOutputView> output_view_;
-};
-
 }  // namespace media
 
 #endif  // MEDIA_GPU_WINDOWS_GL_IMAGE_EGL_STREAM_H_
diff --git a/media/video/gpu_video_accelerator_factories.h b/media/video/gpu_video_accelerator_factories.h
index beb43bd2..eb08a3bd 100644
--- a/media/video/gpu_video_accelerator_factories.h
+++ b/media/video/gpu_video_accelerator_factories.h
@@ -160,6 +160,9 @@
   // May be called on any thread.
   virtual void NotifyEncoderSupportKnown(base::OnceClosure callback) = 0;
 
+  // Caller owns returned pointer, but should call Destroy() on it (instead of
+  // directly deleting) for proper destruction, as per the
+  // VideoEncodeAccelerator interface.
   virtual std::unique_ptr<VideoEncodeAccelerator>
   CreateVideoEncodeAccelerator() = 0;
 
diff --git a/media/video/video_encode_accelerator.h b/media/video/video_encode_accelerator.h
index f37a1d50..61a515a0 100644
--- a/media/video/video_encode_accelerator.h
+++ b/media/video/video_encode_accelerator.h
@@ -456,7 +456,7 @@
   virtual bool IsGpuFrameResizeSupported();
 
  protected:
-  // Do not delete directly; use Destroy() or own it with a unique_ptr, which
+  // Do not delete directly; use Destroy() or own it with a scoped_ptr, which
   // will Destroy() it properly by default.
   virtual ~VideoEncodeAccelerator();
 };
diff --git a/net/cert/pki/parsed_certificate.h b/net/cert/pki/parsed_certificate.h
index fe4414e3..5fba2b80 100644
--- a/net/cert/pki/parsed_certificate.h
+++ b/net/cert/pki/parsed_certificate.h
@@ -302,7 +302,8 @@
   std::vector<std::string_view> ca_issuers_uris_;
   std::vector<std::string_view> ocsp_uris_;
 
-  // Policies extension.
+  // Policies extension. This list will already have been checked for
+  // duplicates.
   bool has_policy_oids_ = false;
   std::vector<der::Input> policy_oids_;
 
diff --git a/net/cert/pki/verify_certificate_chain.cc b/net/cert/pki/verify_certificate_chain.cc
index d84ed652..1957a9a 100644
--- a/net/cert/pki/verify_certificate_chain.cc
+++ b/net/cert/pki/verify_certificate_chain.cc
@@ -5,6 +5,7 @@
 #include "net/cert/pki/verify_certificate_chain.h"
 
 #include <algorithm>
+#include <cassert>
 
 #include "net/cert/pki/cert_error_params.h"
 #include "net/cert/pki/cert_errors.h"
@@ -16,7 +17,6 @@
 #include "net/cert/pki/trust_store.h"
 #include "net/cert/pki/verify_signed_data.h"
 #include "net/der/input.h"
-#include "net/der/parser.h"
 
 namespace net {
 
@@ -359,196 +359,265 @@
   }
 }
 
-// Returns |true| if |policies| contains the OID |search_oid|.
-bool SetContains(const std::set<der::Input>& policies,
-                 const der::Input& search_oid) {
-  return policies.count(search_oid) > 0;
-}
-
 // Representation of RFC 5280's "valid_policy_tree", used to keep track of the
-// valid policies and policy re-mappings.
+// valid policies and policy re-mappings. This structure is defined in
+// section 6.1.2.
 //
-// ValidPolicyTree differs slightly from RFC 5280's description in that:
+// ValidPolicyGraph differs from RFC 5280's description in that:
 //
 //  (1) It does not track "qualifier_set". This is not needed as it is not
 //      output by this implementation.
 //
-//  (2) It only stores the most recent level of the policy tree rather than
-//      the full tree of nodes.
-class ValidPolicyTree {
+//  (2) It builds a directed acyclic graph, rather than a tree. When a given
+//      policy matches multiple parents, RFC 5280 makes a separate node for
+//      each parent. This representation condenses them into one node with
+//      multiple parents.
+//
+//  (3) It does not track "expected_policy_set" or anyPolicy nodes directly.
+//      Rather it maintains, only for the most recent level, whether there is an
+//      anyPolicy node and an inverted map of all "expected_policy_set" values.
+//
+//  (4) Some pruning steps are deferred to when policies are evaluated, as a
+//      reachability pass.
+class ValidPolicyGraph {
  public:
-  ValidPolicyTree() = default;
+  ValidPolicyGraph() = default;
 
-  ValidPolicyTree(const ValidPolicyTree&) = delete;
-  ValidPolicyTree& operator=(const ValidPolicyTree&) = delete;
+  ValidPolicyGraph(const ValidPolicyGraph&) = delete;
+  ValidPolicyGraph& operator=(const ValidPolicyGraph&) = delete;
 
+  // A Node is an entry in the policy graph. It contains information about some
+  // policy asserted by a certificate in the chain. The policy OID itself is
+  // omitted because it is the key in the Level map.
   struct Node {
-    // |root_policy| is equivalent to |valid_policy|, but in the domain of the
-    // caller.
+    // The list of "valid_policy" values for all nodes which are a parent of
+    // this node, other than anyPolicy. If empty, this node has a single parent,
+    // anyPolicy.
     //
-    // The reason for this distinction is the Policy Mappings extension.
+    // Nodes whose parent is anyPolicy are root policies, and may be returned
+    // in the authorities-constrained-policy-set. Nodes with a concrete policy
+    // as a parent are derived from that policy in the issuer certificate,
+    // possibly with a policy mapping applied.
     //
-    // So whereas |valid_policy| is in the remapped domain defined by the
-    // issuing certificate, |root_policy| is in the fixed domain of the caller.
-    //
-    // OIDs in "user_initial_policy_set" and "user_constrained_policy_set" are
-    // directly comparable to |root_policy| values, but not necessarily to
-    // |valid_policy|.
-    //
-    // In terms of the valid policy tree, |root_policy| can be found by
-    // starting at the node's root ancestor, and finding the first node with a
-    // valid_policy other than anyPolicy. This is effectively the same process
-    // as used during policy tree intersection in RFC 5280 6.1.5.g.iii.1
-    der::Input root_policy;
+    // Note it is not possible for a policy to have both anyPolicy and a
+    // concrete policy as a parent. Section 6.1.3, step d.1.ii only runs if
+    // there was no match in step d.1.i.
+    std::vector<der::Input> parent_policies;
 
-    // The same as RFC 5280's "valid_policy" variable.
-    der::Input valid_policy;
+    // Whether this node matches a policy mapping in the certificate. If true,
+    // its "expected_policy_set" comes from the policy mappings extension. If
+    // false, its "expected_policy_set" is itself.
+    bool mapped = false;
 
-    // The same as RFC 5280s "expected_policy_set" variable.
-    std::set<der::Input> expected_policy_set;
-
-    // Note that RFC 5280's "qualifier_set" is omitted.
+    // Whether this node is reachable from some valid policy in the end-entity
+    // certificate. Computed during GetValidRootPolicySet().
+    bool reachable = false;
   };
 
-  // Level represents all the nodes at depth "i" in the valid_policy_tree.
-  using Level = std::vector<Node>;
+  // The policy graph is organized into "levels", each corresponding to a
+  // certificate in the chain. We maintain a map from "valid_policy" to the
+  // corresponding Node. This is the set of policies asserted by this
+  // certificate. The special anyPolicy OID is handled separately below.
+  using Level = std::map<der::Input, Node>;
 
-  // Initializes the ValidPolicyTree for the given "user_initial_policy_set".
+  // Additional per-level information that only needs to be maintained for the
+  // bottom-most level.
+  struct LevelDetails {
+    // Maintains the "expected_policy_set" values for nodes in a level of the
+    // graph, but the map is inverted from RFC 5280's formulation. For a given
+    // policy OID P, other than anyPolicy, this map gives the set of nodes where
+    // P appears in the node's "expected_policy_set". anyPolicy is handled
+    // separately below.
+    std::map<der::Input, std::vector<der::Input>> expected_policy_map;
+
+    // Whether there is a node at this level whose "valid_policy" is anyPolicy.
+    //
+    // Note anyPolicy's "expected_policy_set" always {anyPolicy}, and anyPolicy
+    // will never appear in the "expected_policy_set" of any other policy. That
+    // means this field also captures how anyPolicy appears in
+    // "expected_policy_set".
+    bool has_any_policy = false;
+  };
+
+  // Initializes the ValidPolicyGraph for the given "user_initial_policy_set".
   //
-  // In RFC 5280, the valid_policy_tree is initialized to a root node at depth
+  // In RFC 5280, the valid_policy_tree is initialized to a root node at level
   // 0 of "anyPolicy"; the intersection with the "user_initial_policy_set" is
   // done at the end (Wrap Up) as described in section 6.1.5 step g.
   //
   // Whereas in this implementation, the restriction on policies is added here,
-  // and intersecting the valid policy tree during Wrap Up is no longer needed.
+  // and intersecting during Wrap Up is no longer needed.
   //
   // The final "user_constrained_policy_set" obtained will be the same. The
   // advantages of this approach is simpler code.
-  void Init(const std::set<der::Input>& user_initial_policy_set) {
-    Clear();
-    for (const der::Input& policy_oid : user_initial_policy_set)
-      AddRootNode(policy_oid);
+  //
+  // TODO(davidben): It is not quite the same in some edge cases around
+  // anyPolicy. Switch this to match RFC 5280's formulation.
+  void Init(const std::set<der::Input>& user_constained_policy_set) {
+    SetNull();
+    StartLevel();
+    for (der::Input policy : user_constained_policy_set) {
+      if (policy == der::Input(kAnyPolicyOid)) {
+        AddAnyPolicyNode();
+      } else {
+        AddNode(policy, {});
+      }
+    }
   }
 
-  // Returns the current level (i.e. all nodes at depth i in the valid
-  // policy tree).
-  const Level& current_level() const { return current_level_; }
-  Level& current_level() { return current_level_; }
-
   // In RFC 5280 valid_policy_tree may be set to null. That is represented here
   // by emptiness.
-  bool IsNull() const { return current_level_.empty(); }
-  void SetNull() { Clear(); }
+  bool IsNull() const {
+    return !current_level_.has_any_policy &&
+           (levels_.empty() || levels_.back().empty());
+  }
+  void SetNull() {
+    levels_.clear();
+    current_level_ = LevelDetails{};
+  }
 
-  // This implementation keeps only the last level of the valid policy
-  // tree. Calling StartLevel() returns the nodes for the previous
-  // level, and starts a new level.
-  Level StartLevel() {
-    Level prev_level;
-    std::swap(prev_level, current_level_);
+  // Completes the previous level, returning a corresponding LevelDetails
+  // structure, and starts a new level.
+  LevelDetails StartLevel() {
+    // Finish building expected_policy_map for the previous level.
+    if (!levels_.empty()) {
+      for (const auto& [policy, node] : levels_.back()) {
+        if (!node.mapped) {
+          current_level_.expected_policy_map[policy].push_back(policy);
+        }
+      }
+    }
+
+    LevelDetails prev_level = std::move(current_level_);
+    levels_.emplace_back();
+    current_level_ = LevelDetails{};
     return prev_level;
   }
 
   // Gets the set of policies (in terms of root authority's policy domain) that
-  // are valid at the curent level of the policy tree.
+  // are valid at the bottom level of the policy graph. This is what X.509 calls
+  // "user-constrained-policy-set".
   //
-  // For example:
-  //
-  //  * If the valid policy tree was initialized with anyPolicy, then this
-  //    function returns what X.509 calls "authorities-constrained-policy-set".
-  //
-  //  * If the valid policy tree was instead initialized with the
-  //    "user-initial-policy_set", then this function returns what X.509
-  //    calls "user-constrained-policy-set"
-  //    ("authorities-constrained-policy-set" intersected with the
-  //    "user-initial-policy-set").
+  // This method may only be called once, after the policy graph is constructed.
   void GetValidRootPolicySet(std::set<der::Input>* policy_set) {
     policy_set->clear();
-    for (const Node& node : current_level_)
-      policy_set->insert(node.root_policy);
+    if (levels_.empty()) {
+      return;
+    }
 
-    // If the result includes anyPolicy, simplify it to a set of size 1.
-    if (policy_set->size() > 1 &&
-        SetContains(*policy_set, der::Input(kAnyPolicyOid))) {
+    if (current_level_.has_any_policy) {
       *policy_set = {der::Input(kAnyPolicyOid)};
+      return;
     }
-  }
 
-  // Adds a node |n| to the current level which is a child of |parent|
-  // such that:
-  //   * n.valid_policy = policy_oid
-  //   * n.expected_policy_set = {policy_oid}
-  void AddNode(const Node& parent, const der::Input& policy_oid) {
-    AddNodeWithExpectedPolicySet(parent, policy_oid, {policy_oid});
-  }
-
-  // Adds a node |n| to the current level which is a child of |parent|
-  // such that:
-  //   * n.valid_policy = policy_oid
-  //   * n.expected_policy_set = expected_policy_set
-  void AddNodeWithExpectedPolicySet(
-      const Node& parent,
-      const der::Input& policy_oid,
-      const std::set<der::Input>& expected_policy_set) {
-    Node new_node;
-    new_node.valid_policy = policy_oid;
-    new_node.expected_policy_set = expected_policy_set;
-
-    // Consider the root policy as the first policy other than anyPolicy (or
-    // anyPolicy if it hasn't been restricted yet).
-    new_node.root_policy = (parent.root_policy == der::Input(kAnyPolicyOid))
-                               ? policy_oid
-                               : parent.root_policy;
-
-    current_level_.push_back(std::move(new_node));
-  }
-
-  // Returns the first node having valid_policy == anyPolicy in |level|, or
-  // nullptr if there is none.
-  static const Node* FindAnyPolicyNode(const Level& level) {
-    for (const Node& node : level) {
-      if (node.valid_policy == der::Input(kAnyPolicyOid))
-        return &node;
+    // The root's policy domain is determined by nodes with anyPolicy as a
+    // parent. However, we must limit to those which are reachable from the
+    // end-entity certificate because we defer some pruning steps.
+    for (auto& [policy, node] : levels_.back()) {
+      node.reachable = true;
     }
-    return nullptr;
-  }
-
-  // Deletes all nodes |n| in |level| where |n.valid_policy| matches the given
-  // |valid_policy|. This may re-order the nodes in |level|.
-  static void DeleteNodesMatchingValidPolicy(const der::Input& valid_policy,
-                                             Level* level) {
-    // This works by swapping nodes to the end of the vector, and then doing a
-    // single resize to delete them all.
-    auto cur = level->begin();
-    auto end = level->end();
-    while (cur != end) {
-      bool should_delete_node = cur->valid_policy == valid_policy;
-      if (should_delete_node) {
-        end = std::prev(end);
-        if (cur != end)
-          std::iter_swap(cur, end);
-      } else {
-        ++cur;
+    for (size_t i = levels_.size() - 1; i < levels_.size(); i--) {
+      for (auto& [policy, node] : levels_[i]) {
+        if (!node.reachable) {
+          continue;
+        }
+        if (node.parent_policies.empty()) {
+          // |node|'s parent is anyPolicy, so this is in the root policy domain.
+          policy_set->insert(policy);
+        } else if (i > 0) {
+          // Otherwise, continue searching the previous level.
+          for (der::Input parent : node.parent_policies) {
+            auto iter = levels_[i - 1].find(parent);
+            if (iter != levels_[i - 1].end()) {
+              iter->second.reachable = true;
+            }
+          }
+        }
       }
     }
-    level->erase(end, level->end());
+  }
+
+  // Adds a node with policy anyPolicy to the current level.
+  void AddAnyPolicyNode() {
+    assert(!levels_.empty());
+    current_level_.has_any_policy = true;
+  }
+
+  // Adds a node to the current level which is a child of |parent_policies| with
+  // the specified policy.
+  void AddNode(der::Input policy, std::vector<der::Input> parent_policies) {
+    assert(policy != der::Input(kAnyPolicyOid));
+    AddNodeReturningIterator(policy, std::move(parent_policies));
+  }
+
+  // Adds a node to the current level which is a child of anyPolicy with the
+  // specified policy.
+  void AddNodeWithParentAnyPolicy(der::Input policy) {
+    // An empty parent set represents a node parented by anyPolicy.
+    AddNode(policy, {});
+  }
+
+  // Maps |issuer_policy| to |subject_policy|, as in RFC 5280, section 6.1.4,
+  // step b.1.
+  void AddPolicyMapping(der::Input issuer_policy, der::Input subject_policy) {
+    assert(issuer_policy != der::Input(kAnyPolicyOid));
+    assert(subject_policy != der::Input(kAnyPolicyOid));
+    if (levels_.empty()) {
+      return;
+    }
+
+    // The mapping only applies if |issuer_policy| exists in the current level.
+    auto issuer_policy_iter = levels_.back().find(issuer_policy);
+    if (issuer_policy_iter == levels_.back().end()) {
+      // If there is no match, it can instead match anyPolicy.
+      if (!current_level_.has_any_policy) {
+        return;
+      }
+
+      // From RFC 5280, section 6.1.4, step b.1:
+      //
+      //    If no node of depth i in the valid_policy_tree has a
+      //    valid_policy of ID-P but there is a node of depth i with a
+      //    valid_policy of anyPolicy, then generate a child node of
+      //    the node of depth i-1 that has a valid_policy of anyPolicy
+      //    as follows: [...]
+      //
+      // The anyPolicy node of depth i-1 is referring to the parent of the
+      // anyPolicy node of depth i. The parent of anyPolicy is always anyPolicy.
+      issuer_policy_iter = AddNodeReturningIterator(issuer_policy, {});
+    }
+
+    // Unmapped nodes have a singleton "expected_policy_set" containing their
+    // valid_policy. Track whether nodes have been mapped so this can be filled
+    // in at StartLevel().
+    issuer_policy_iter->second.mapped = true;
+
+    // Add |subject_policy| to |issuer_policy|'s "expected_policy_set".
+    current_level_.expected_policy_map[subject_policy].push_back(issuer_policy);
+  }
+
+  // Removes the node with the specified policy from the current level.
+  void DeleteNode(der::Input policy) {
+    if (!levels_.empty()) {
+      levels_.back().erase(policy);
+    }
   }
 
  private:
-  // Deletes all nodes in the valid policy tree.
-  void Clear() { current_level_.clear(); }
-
-  // Adds a node to the current level for OID |policy_oid|. The current level
-  // is assumed to be the root level.
-  void AddRootNode(const der::Input& policy_oid) {
-    Node new_node;
-    new_node.root_policy = policy_oid;
-    new_node.valid_policy = policy_oid;
-    new_node.expected_policy_set = {policy_oid};
-    current_level_.push_back(std::move(new_node));
+  Level::iterator AddNodeReturningIterator(
+      der::Input policy,
+      std::vector<der::Input> parent_policies) {
+    assert(policy != der::Input(kAnyPolicyOid));
+    auto [iter, inserted] = levels_.back().insert(
+        std::pair{policy, Node{std::move(parent_policies)}});
+    assert(inserted);
+    return iter;
   }
 
-  Level current_level_;
+  // The list of levels, starting from the root.
+  std::vector<Level> levels_;
+  // Additional information about the current level.
+  LevelDetails current_level_;
 };
 
 // Class that encapsulates the state variables used by certificate path
@@ -623,7 +692,7 @@
   bssl::UniquePtr<EVP_PKEY> ParseAndCheckPublicKey(const der::Input& spki,
                                                    CertErrors* errors);
 
-  ValidPolicyTree valid_policy_tree_;
+  ValidPolicyGraph valid_policy_graph_;
 
   // Will contain a NameConstraints for each previous cert in the chain which
   // had nameConstraints. This corresponds to the permitted_subtrees and
@@ -719,12 +788,9 @@
   //       certificate and the valid_policy_tree is not NULL, process
   //       the policy information by performing the following steps in
   //       order:
-  if (cert.has_policy_oids() && !valid_policy_tree_.IsNull()) {
-    ValidPolicyTree::Level previous_level = valid_policy_tree_.StartLevel();
-
-    // Identify if there was a node with valid_policy == anyPolicy at depth i-1.
-    const ValidPolicyTree::Node* any_policy_node_prev_level =
-        ValidPolicyTree::FindAnyPolicyNode(previous_level);
+  if (cert.has_policy_oids() && !valid_policy_graph_.IsNull()) {
+    ValidPolicyGraph::LevelDetails previous_level =
+        valid_policy_graph_.StartLevel();
 
     //     (1)  For each policy P not equal to anyPolicy in the
     //          certificate policies extension, let P-OID denote the OID
@@ -742,22 +808,20 @@
       //              child node as follows: set the valid_policy to P-OID,
       //              set the qualifier_set to P-Q, and set the
       //              expected_policy_set to {P-OID}.
-      bool found_match = false;
-      for (const ValidPolicyTree::Node& prev_node : previous_level) {
-        if (SetContains(prev_node.expected_policy_set, p_oid)) {
-          valid_policy_tree_.AddNode(prev_node, p_oid);
-          found_match = true;
-        }
+      auto iter = previous_level.expected_policy_map.find(p_oid);
+      if (iter != previous_level.expected_policy_map.end()) {
+        valid_policy_graph_.AddNode(
+            p_oid, /*parent_policies=*/std::move(iter->second));
+        previous_level.expected_policy_map.erase(iter);
+      } else if (previous_level.has_any_policy) {
+        //      (ii)  If there was no match in step (i) and the
+        //            valid_policy_tree includes a node of depth i-1 with
+        //            the valid_policy anyPolicy, generate a child node with
+        //            the following values: set the valid_policy to P-OID,
+        //            set the qualifier_set to P-Q, and set the
+        //            expected_policy_set to  {P-OID}.
+        valid_policy_graph_.AddNodeWithParentAnyPolicy(p_oid);
       }
-
-      //        (ii)  If there was no match in step (i) and the
-      //              valid_policy_tree includes a node of depth i-1 with
-      //              the valid_policy anyPolicy, generate a child node with
-      //              the following values: set the valid_policy to P-OID,
-      //              set the qualifier_set to P-Q, and set the
-      //              expected_policy_set to  {P-OID}.
-      if (!found_match && any_policy_node_prev_level)
-        valid_policy_tree_.AddNode(*any_policy_node_prev_level, p_oid);
     }
 
     //     (2)  If the certificate policies extension includes the policy
@@ -775,20 +839,12 @@
     //          this node.
     if (cert_has_any_policy && ((inhibit_any_policy_ > 0) ||
                                 (!is_target_cert && IsSelfIssued(cert)))) {
-      // Keep track of the existing policies at depth i.
-      std::set<der::Input> child_node_policies;
-      for (const ValidPolicyTree::Node& node :
-           valid_policy_tree_.current_level())
-        child_node_policies.insert(node.valid_policy);
-
-      for (const ValidPolicyTree::Node& prev_node : previous_level) {
-        for (const der::Input& expected_policy :
-             prev_node.expected_policy_set) {
-          if (!SetContains(child_node_policies, expected_policy)) {
-            child_node_policies.insert(expected_policy);
-            valid_policy_tree_.AddNode(prev_node, expected_policy);
-          }
-        }
+      for (auto& [p_oid, parent_policies] :
+           previous_level.expected_policy_map) {
+        valid_policy_graph_.AddNode(p_oid, std::move(parent_policies));
+      }
+      if (previous_level.has_any_policy) {
+        valid_policy_graph_.AddAnyPolicyNode();
       }
     }
 
@@ -797,19 +853,18 @@
     //          this step until there are no nodes of depth i-1 or less
     //          without children.
     //
-    // Nothing needs to be done for this step, since this implementation only
-    // stores the nodes at depth i, and the entire level has already been
-    // calculated.
+    // This implementation does this as part of GetValidRootPolicySet(). Only
+    // the current level needs to be pruned to compute the policy graph.
   }
 
   //  (e)  If the certificate policies extension is not present, set the
   //       valid_policy_tree to NULL.
   if (!cert.has_policy_oids())
-    valid_policy_tree_.SetNull();
+    valid_policy_graph_.SetNull();
 
   //  (f)  Verify that either explicit_policy is greater than 0 or the
   //       valid_policy_tree is not equal to NULL;
-  if (!((explicit_policy_ > 0) || !valid_policy_tree_.IsNull()))
+  if (!((explicit_policy_ > 0) || !valid_policy_graph_.IsNull()))
     errors->AddError(cert_errors::kNoValidPolicy);
 }
 
@@ -827,10 +882,11 @@
     if (mapping.issuer_domain_policy == der::Input(kAnyPolicyOid) ||
         mapping.subject_domain_policy == der::Input(kAnyPolicyOid)) {
       // Because this implementation continues processing certificates after
-      // this error, clear the valid policy tree to ensure the
+      // this error, clear the valid policy graph to ensure the
       // "user_constrained_policy_set" output upon failure is empty.
-      valid_policy_tree_.SetNull();
+      valid_policy_graph_.SetNull();
       errors->AddError(cert_errors::kPolicyMappingAnyPolicy);
+      return;
     }
   }
 
@@ -860,32 +916,9 @@
   //               equivalent to ID-P by the policy mappings extension.
   //
   if (policy_mapping_ > 0) {
-    const ValidPolicyTree::Node* any_policy_node =
-        ValidPolicyTree::FindAnyPolicyNode(valid_policy_tree_.current_level());
-
-    // Group mappings by issuer domain policy.
-    std::map<der::Input, std::set<der::Input>> mappings;
     for (const ParsedPolicyMapping& mapping : cert.policy_mappings()) {
-      mappings[mapping.issuer_domain_policy].insert(
-          mapping.subject_domain_policy);
-    }
-
-    for (const auto& it : mappings) {
-      const der::Input& issuer_domain_policy = it.first;
-      const std::set<der::Input>& subject_domain_policies = it.second;
-      bool found_node = false;
-
-      for (ValidPolicyTree::Node& node : valid_policy_tree_.current_level()) {
-        if (node.valid_policy == issuer_domain_policy) {
-          node.expected_policy_set = subject_domain_policies;
-          found_node = true;
-        }
-      }
-
-      if (!found_node && any_policy_node) {
-        valid_policy_tree_.AddNodeWithExpectedPolicySet(
-            *any_policy_node, issuer_domain_policy, subject_domain_policies);
-      }
+      valid_policy_graph_.AddPolicyMapping(mapping.issuer_domain_policy,
+                                           mapping.subject_domain_policy);
     }
   }
 
@@ -905,8 +938,7 @@
   //               depth i-1 or less without children.
   if (policy_mapping_ == 0) {
     for (const ParsedPolicyMapping& mapping : cert.policy_mappings()) {
-      ValidPolicyTree::DeleteNodesMatchingValidPolicy(
-          mapping.issuer_domain_policy, &valid_policy_tree_.current_level());
+      valid_policy_graph_.DeleteNode(mapping.issuer_domain_policy);
     }
   }
 }
@@ -1181,7 +1213,7 @@
   //    If either (1) the value of explicit_policy variable is greater than
   //    zero or (2) the valid_policy_tree is not NULL, then path processing
   //   has succeeded.
-  if (!(explicit_policy_ > 0 || !valid_policy_tree_.IsNull())) {
+  if (!(explicit_policy_ > 0 || !valid_policy_graph_.IsNull())) {
     errors->AddError(cert_errors::kNoValidPolicy);
   }
 
@@ -1348,7 +1380,7 @@
   // if n is used in place of n+1).
   const size_t n = certs.size() - 1;
 
-  valid_policy_tree_.Init(user_initial_policy_set);
+  valid_policy_graph_.Init(user_initial_policy_set);
 
   // RFC 5280 section section 6.1.2:
   //
@@ -1443,9 +1475,9 @@
   }
 
   if (user_constrained_policy_set) {
-    // valid_policy_tree_ already contains the intersection of valid policies
+    // valid_policy_graph_ already contains the intersection of valid policies
     // with user_initial_policy_set.
-    valid_policy_tree_.GetValidRootPolicySet(user_constrained_policy_set);
+    valid_policy_graph_.GetValidRootPolicySet(user_constrained_policy_set);
   }
 
   // TODO(eroman): RFC 5280 forbids duplicate certificates per section 6.1:
diff --git a/net/data/url_request_unittest/expect-ct-header-multiple.html b/net/data/url_request_unittest/expect-ct-header-multiple.html
deleted file mode 100644
index e69de29..0000000
--- a/net/data/url_request_unittest/expect-ct-header-multiple.html
+++ /dev/null
diff --git a/net/data/url_request_unittest/expect-ct-header-preload.html b/net/data/url_request_unittest/expect-ct-header-preload.html
deleted file mode 100644
index e69de29..0000000
--- a/net/data/url_request_unittest/expect-ct-header-preload.html
+++ /dev/null
diff --git a/net/data/url_request_unittest/expect-ct-header.html b/net/data/url_request_unittest/expect-ct-header.html
deleted file mode 100644
index e69de29..0000000
--- a/net/data/url_request_unittest/expect-ct-header.html
+++ /dev/null
diff --git a/pdf/BUILD.gn b/pdf/BUILD.gn
index a0b4646..7a03d252 100644
--- a/pdf/BUILD.gn
+++ b/pdf/BUILD.gn
@@ -300,7 +300,7 @@
   }
 
   if (is_linux || is_chromeos) {
-    # TODO(crbug.com/1302059): After PPAPI deprecation, there will only be one
+    # TODO(crbug.com/702990): After PPAPI deprecation, there will only be one
     # caller left. Move inside the file with the caller.
     static_library("font_table_linux") {
       sources = [ "font_table_linux.cc" ]
diff --git a/pdf/paint_manager.cc b/pdf/paint_manager.cc
index d17c80f9..667b9f9 100644
--- a/pdf/paint_manager.cc
+++ b/pdf/paint_manager.cc
@@ -305,7 +305,7 @@
                                    SkSamplingOptions(), /*paint=*/nullptr);
   client_->UpdateSnapshot(std::move(snapshot));
 
-  // TODO(crbug.com/1302059): Complete flush synchronously.
+  // TODO(crbug.com/1403311): Complete flush synchronously.
   base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE, base::BindOnce(&PaintManager::OnFlushComplete,
                                 weak_factory_.GetWeakPtr()));
diff --git a/pdf/pdf_view_web_plugin.cc b/pdf/pdf_view_web_plugin.cc
index aa185962..f6ea2bb4 100644
--- a/pdf/pdf_view_web_plugin.cc
+++ b/pdf/pdf_view_web_plugin.cc
@@ -455,9 +455,6 @@
   if (!total_translate_.IsZero())
     canvas->translate(total_translate_.x(), total_translate_.y());
 
-  if (device_to_css_scale_ != 1.0f)
-    canvas->scale(device_to_css_scale_, device_to_css_scale_);
-
   // Position layer at plugin origin before layer scaling.
   if (!plugin_rect_.origin().IsOrigin())
     canvas->translate(plugin_rect_.x(), plugin_rect_.y());
@@ -544,18 +541,8 @@
 blink::WebInputEventResult PdfViewWebPlugin::HandleInputEvent(
     const blink::WebCoalescedInputEvent& event,
     ui::Cursor* cursor) {
-  // TODO(crbug.com/1302059): The input events received by the Pepper plugin
-  // already have the viewport-to-DIP scale applied. The scaling done here
-  // should be moved into `HandleWebInputEvent()` once the Pepper plugin is
-  // removed.
-  std::unique_ptr<blink::WebInputEvent> scaled_event =
-      ui::ScaleWebInputEvent(event.Event(), viewport_to_dip_scale_);
-
-  const blink::WebInputEvent& event_to_handle =
-      scaled_event ? *scaled_event : event.Event();
-
   const blink::WebInputEventResult result =
-      HandleWebInputEvent(event_to_handle)
+      HandleWebInputEvent(event.Event())
           ? blink::WebInputEventResult::kHandledApplication
           : blink::WebInputEventResult::kNotHandled;
 
@@ -924,8 +911,7 @@
 }
 
 void PdfViewWebPlugin::CaretChanged(const gfx::Rect& caret_rect) {
-  caret_rect_ = gfx::ScaleToEnclosingRect(
-      caret_rect + available_area_.OffsetFromOrigin(), device_to_css_scale_);
+  caret_rect_ = caret_rect + available_area_.OffsetFromOrigin();
 }
 
 void PdfViewWebPlugin::GetDocumentPassword(
@@ -1659,9 +1645,7 @@
   message.Set("token", token);
   client_->PostMessage(std::move(message));
 
-  // TODO(crbug.com/1302059): Is there a good reason to null-terminate here?
-  pdf_service_->SaveUrlAs(GURL(url_.c_str()),
-                          network::mojom::ReferrerPolicy::kDefault);
+  pdf_service_->SaveUrlAs(GURL(url_), network::mojom::ReferrerPolicy::kDefault);
 }
 
 void PdfViewWebPlugin::InvalidatePluginContainer() {
@@ -1888,7 +1872,6 @@
   }
 
   viewport_to_dip_scale_ = scale;
-  device_to_css_scale_ = 1.0f;
   UpdateScaledValues();
 }
 
@@ -2002,10 +1985,12 @@
     return false;
 
   // `engine_` expects input events in device coordinates.
+  float viewport_to_device_scale = viewport_to_dip_scale_ * device_scale_;
   std::unique_ptr<blink::WebInputEvent> transformed_event =
       ui::TranslateAndScaleWebInputEvent(
-          event, gfx::Vector2dF(-available_area_.x() / device_scale_, 0),
-          device_scale_);
+          event,
+          gfx::Vector2dF(-available_area_.x() / viewport_to_device_scale, 0),
+          viewport_to_device_scale);
 
   const blink::WebInputEvent& event_to_handle =
       transformed_event ? *transformed_event : event;
diff --git a/pdf/pdf_view_web_plugin.h b/pdf/pdf_view_web_plugin.h
index 6b50cc5ee..025fe199 100644
--- a/pdf/pdf_view_web_plugin.h
+++ b/pdf/pdf_view_web_plugin.h
@@ -655,9 +655,6 @@
   // The viewport coordinates to DIP (device-independent pixel) ratio.
   float viewport_to_dip_scale_ = 1.0f;
 
-  // The device pixel to CSS pixel ratio.
-  float device_to_css_scale_ = 1.0f;
-
   // Combined translate from snapshot to device to CSS pixels.
   gfx::Vector2dF total_translate_;
 
diff --git a/services/viz/public/cpp/gpu/context_provider_command_buffer.h b/services/viz/public/cpp/gpu/context_provider_command_buffer.h
index e434c1b..04de90c5 100644
--- a/services/viz/public/cpp/gpu/context_provider_command_buffer.h
+++ b/services/viz/public/cpp/gpu/context_provider_command_buffer.h
@@ -84,8 +84,7 @@
       command_buffer_metrics::ContextType type,
       base::SharedMemoryMapper* buffer_mapper = nullptr);
 
-  // Virtual for testing.
-  virtual gpu::CommandBufferProxyImpl* GetCommandBufferProxy();
+  gpu::CommandBufferProxyImpl* GetCommandBufferProxy();
   // Gives the GL internal format that should be used for calling CopyTexImage2D
   // on the default framebuffer.
   uint32_t GetCopyTextureInternalFormat();
diff --git a/third_party/blink/public/web/web_local_frame_client.h b/third_party/blink/public/web/web_local_frame_client.h
index f6be297..c27c7cd3 100644
--- a/third_party/blink/public/web/web_local_frame_client.h
+++ b/third_party/blink/public/web/web_local_frame_client.h
@@ -509,6 +509,12 @@
   virtual void OnMainFrameViewportRectangleChanged(
       const gfx::Rect& main_frame_viewport_rect) {}
 
+  // Called when an image ad rectangle changed. An empty `image_ad_rect` is used
+  // to signal the removal of the rectangle. Only invoked on the main frame.
+  virtual void OnMainFrameImageAdRectangleChanged(
+      int element_id,
+      const gfx::Rect& image_ad_rect) {}
+
   // Called when an overlay interstitial pop up ad is detected.
   virtual void OnOverlayPopupAdDetected() {}
 
diff --git a/third_party/blink/renderer/build/scripts/core/css/properties/templates/style_builder_functions.tmpl b/third_party/blink/renderer/build/scripts/core/css/properties/templates/style_builder_functions.tmpl
index ab72616..6819ca3 100644
--- a/third_party/blink/renderer/build/scripts/core/css/properties/templates/style_builder_functions.tmpl
+++ b/third_party/blink/renderer/build/scripts/core/css/properties/templates/style_builder_functions.tmpl
@@ -38,11 +38,7 @@
 {% endmacro %}
 
 {% macro style_access(property) %}
-  {%- if property.writable %}
-state.Style()->
-  {%- else %}
 state.StyleBuilder().
-  {%- endif %}
 {% endmacro %}
 
 {% macro set_value(property) %}
diff --git a/third_party/blink/renderer/build/scripts/core/style/computed_style_fields.py b/third_party/blink/renderer/build/scripts/core/style/computed_style_fields.py
index b568d2af..8d941c64 100644
--- a/third_party/blink/renderer/build/scripts/core/style/computed_style_fields.py
+++ b/third_party/blink/renderer/build/scripts/core/style/computed_style_fields.py
@@ -136,15 +136,14 @@
         default_value: Default value for this field when it is first initialized
     """
 
-    def __init__(self, field_role, name_for_methods, writable, property_name,
-                 type_name, wrapper_pointer_name, field_template, size,
-                 default_value, derived_from, custom_copy, custom_compare,
-                 mutable, getter_method_name, setter_method_name,
-                 initial_method_name, computed_style_custom_functions,
+    def __init__(self, field_role, name_for_methods, property_name, type_name,
+                 wrapper_pointer_name, field_template, size, default_value,
+                 derived_from, custom_copy, custom_compare, mutable,
+                 getter_method_name, setter_method_name, initial_method_name,
+                 computed_style_custom_functions,
                  computed_style_protected_functions, **kwargs):
         name_source = NameStyleConverter(name_for_methods)
         self.name = name_source.to_class_data_member()
-        self.writable = writable
         self.property_name = property_name
         self.type_name = type_name
         self.wrapper_pointer_name = wrapper_pointer_name
diff --git a/third_party/blink/renderer/build/scripts/core/style/make_computed_style_base.py b/third_party/blink/renderer/build/scripts/core/style/make_computed_style_base.py
index bd791e1..fb9f6ae 100755
--- a/third_party/blink/renderer/build/scripts/core/style/make_computed_style_base.py
+++ b/third_party/blink/renderer/build/scripts/core/style/make_computed_style_base.py
@@ -170,10 +170,9 @@
 
 
 def _mark_builder_flags(group):
-    """Mark all fields as builder fields, and set writable."""
+    """Mark all fields as builder fields."""
     for field in group.fields:
         field.builder = True
-        field.writable = True
     for subgroup in group.subgroups:
         _mark_builder_flags(subgroup)
 
@@ -321,7 +320,6 @@
         'property',
         name_for_methods,
         property_name=property_.name.original,
-        writable=property_.writable,
         inherited=property_.inherited,
         independent=property_.independent,
         semi_independent_variable=property_.semi_independent_variable,
@@ -357,7 +355,6 @@
         'inherited_flag',
         name_for_methods,
         property_name=property_.name.original,
-        writable=False,
         type_name='bool',
         wrapper_pointer_name=None,
         field_template='primitive',
diff --git a/third_party/blink/renderer/build/scripts/templates/fields/base.tmpl b/third_party/blink/renderer/build/scripts/templates/fields/base.tmpl
index 9ae3448..c272a5d 100644
--- a/third_party/blink/renderer/build/scripts/templates/fields/base.tmpl
+++ b/third_party/blink/renderer/build/scripts/templates/fields/base.tmpl
@@ -9,7 +9,7 @@
 {%- endmacro %}
 
 {% macro decl_setter_method(field, visibility) -%}
-{% if field.setter_visibility == visibility and field.writable %}
+{% if field.setter_visibility == visibility and field.builder %}
 void {{setter_method_name(field)}}({{const_ref(field)}} v) {
   {{set_if_changed(field, encode(field, "v"))|indent(2)}}
 }
@@ -17,7 +17,7 @@
 {%- endmacro %}
 
 {% macro decl_move_method(field, visibility) -%}
-{% if field.setter_visibility == visibility and field.writable %}
+{% if field.setter_visibility == visibility and field.builder %}
 void {{setter_method_name(field)}}({{rvalue_ref(field)}} v) {
   {{move_if_changed(field, encode(field, "v"))|indent(2)}}
 }
@@ -25,7 +25,7 @@
 {%- endmacro %}
 
 {% macro decl_resetter_method(field, visibility) -%}
-{% if field.resetter_visibility == visibility and field.writable %}
+{% if field.resetter_visibility == visibility and field.builder %}
 inline void {{resetter_method_name(field)}}() {
   {{setter_expression(field)}} = {{encode(field, field.default_value)}};
 }
@@ -37,7 +37,7 @@
   Internal suffix.
 #}
 {% macro decl_mutable_method(field) -%}
-{% if field.writable %}
+{% if field.builder %}
 {{nonconst_ref(field)}} {{mutable_method_name(field)}}() {
   return {{decode(field, setter_expression(field))}};
 }
diff --git a/third_party/blink/renderer/build/scripts/templates/fields/monotonic_flag.tmpl b/third_party/blink/renderer/build/scripts/templates/fields/monotonic_flag.tmpl
index 768dbd35..0ec4651 100644
--- a/third_party/blink/renderer/build/scripts/templates/fields/monotonic_flag.tmpl
+++ b/third_party/blink/renderer/build/scripts/templates/fields/monotonic_flag.tmpl
@@ -4,7 +4,7 @@
 
 {% macro decl_public_methods(field) %}
 {{base.decl_public_getter_method(field)}}
-{% if field.writable or field.mutable %}
+{% if field.builder or field.mutable %}
 void {{field.setter_method_name}}() {{print_if(field.mutable, "const ")}}{
   {{set_if_changed(field, encode(field, "true"))|indent(2)}}
 }
diff --git a/third_party/blink/renderer/build/scripts/templates/fields/pointer.tmpl b/third_party/blink/renderer/build/scripts/templates/fields/pointer.tmpl
index b9083be593..0f7bf297 100644
--- a/third_party/blink/renderer/build/scripts/templates/fields/pointer.tmpl
+++ b/third_party/blink/renderer/build/scripts/templates/fields/pointer.tmpl
@@ -6,7 +6,7 @@
   return {{decode(field, getter_expression(field))}}.get();
 }
 
-{% if field.writable %}
+{% if field.builder %}
 void {{field.setter_method_name}}({{field.wrapper_pointer_name}}<{{field.type_name}}> v) {
 {% if field.group.parent %}
   {{setter_expression(field)}} = std::move(v);
diff --git a/third_party/blink/renderer/core/css/css_properties.json5 b/third_party/blink/renderer/core/css/css_properties.json5
index aac58b6..9128c5f 100644
--- a/third_party/blink/renderer/core/css/css_properties.json5
+++ b/third_party/blink/renderer/core/css/css_properties.json5
@@ -71,16 +71,6 @@
       ],
     },
 
-    // Properties which are writable have setters on ComputedStyle.
-    // All other properties must use the ComputedStyleBuilder for setting
-    // the values of properties.
-    //
-    // TODO(crbug.com/1377295): Eventually nothing should be writable.
-    writable: {
-      default: false,
-      valid_type: "bool",
-    },
-
     // Affects how the style building functions are generated.
     //
     // Several property groups (e.g. color properties) deviate from the default
diff --git a/third_party/blink/renderer/core/frame/frame_client.h b/third_party/blink/renderer/core/frame/frame_client.h
index beb8795d..ec6ca356 100644
--- a/third_party/blink/renderer/core/frame/frame_client.h
+++ b/third_party/blink/renderer/core/frame/frame_client.h
@@ -7,6 +7,7 @@
 
 #include "base/unguessable_token.h"
 #include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/core/dom/dom_node_ids.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
 
 namespace gfx {
@@ -44,6 +45,12 @@
   virtual void OnMainFrameViewportRectangleChanged(
       const gfx::Rect& main_frame_viewport_rect) {}
 
+  // Called when an image ad rectangle changed. An empty `image_ad_rect` is used
+  // to signal the removal of the rectangle. Only invoked on the main frame.
+  virtual void OnMainFrameImageAdRectangleChanged(
+      DOMNodeId element_id,
+      const gfx::Rect& image_ad_rect) {}
+
   virtual ~FrameClient() = default;
 
   virtual void Trace(Visitor* visitor) const {}
diff --git a/third_party/blink/renderer/core/frame/local_frame_client_impl.cc b/third_party/blink/renderer/core/frame/local_frame_client_impl.cc
index 50f14f38..dccf5ca 100644
--- a/third_party/blink/renderer/core/frame/local_frame_client_impl.cc
+++ b/third_party/blink/renderer/core/frame/local_frame_client_impl.cc
@@ -1070,6 +1070,14 @@
       main_frame_viewport_rect);
 }
 
+void LocalFrameClientImpl::OnMainFrameImageAdRectangleChanged(
+    DOMNodeId element_id,
+    const gfx::Rect& image_ad_rect) {
+  DCHECK(web_frame_->Client());
+  web_frame_->Client()->OnMainFrameImageAdRectangleChanged(element_id,
+                                                           image_ad_rect);
+}
+
 void LocalFrameClientImpl::OnOverlayPopupAdDetected() {
   DCHECK(web_frame_->Client());
   web_frame_->Client()->OnOverlayPopupAdDetected();
diff --git a/third_party/blink/renderer/core/frame/local_frame_client_impl.h b/third_party/blink/renderer/core/frame/local_frame_client_impl.h
index befd55a8b9..9592a957 100644
--- a/third_party/blink/renderer/core/frame/local_frame_client_impl.h
+++ b/third_party/blink/renderer/core/frame/local_frame_client_impl.h
@@ -254,6 +254,10 @@
   void OnMainFrameViewportRectangleChanged(
       const gfx::Rect& main_frame_viewport_rect) override;
 
+  void OnMainFrameImageAdRectangleChanged(
+      DOMNodeId element_id,
+      const gfx::Rect& image_ad_rect) override;
+
   void OnOverlayPopupAdDetected() override;
 
   void OnLargeStickyAdDetected() override;
diff --git a/third_party/blink/renderer/core/frame/local_frame_view.cc b/third_party/blink/renderer/core/frame/local_frame_view.cc
index d0b84cae..75c6ba11 100644
--- a/third_party/blink/renderer/core/frame/local_frame_view.cc
+++ b/third_party/blink/renderer/core/frame/local_frame_view.cc
@@ -3067,6 +3067,8 @@
 
   SCOPED_UMA_AND_UKM_TIMER(GetUkmAggregator(),
                            LocalFrameUkmAggregator::kCompositingCommit);
+  DEVTOOLS_TIMELINE_TRACE_EVENT("Layerize", inspector_layerize_event::Data,
+                                frame_.Get());
 
   // Skip updating property trees, pushing cc::Layers, and issuing raster
   // invalidations if possible.
diff --git a/third_party/blink/renderer/core/html/html_image_element.cc b/third_party/blink/renderer/core/html/html_image_element.cc
index 92cddb8..c9d34fb2 100644
--- a/third_party/blink/renderer/core/html/html_image_element.cc
+++ b/third_party/blink/renderer/core/html/html_image_element.cc
@@ -39,6 +39,7 @@
 #include "third_party/blink/renderer/core/frame/attribution_src_loader.h"
 #include "third_party/blink/renderer/core/frame/deprecation/deprecation.h"
 #include "third_party/blink/renderer/core/frame/local_dom_window.h"
+#include "third_party/blink/renderer/core/frame/local_frame_client.h"
 #include "third_party/blink/renderer/core/html/canvas/html_canvas_element.h"
 #include "third_party/blink/renderer/core/html/cross_origin_attribute.h"
 #include "third_party/blink/renderer/core/html/forms/form_associated.h"
@@ -516,6 +517,13 @@
 }
 
 void HTMLImageElement::RemovedFrom(ContainerNode& insertion_point) {
+  if (InActiveDocument() && !last_reported_ad_rect_.IsEmpty()) {
+    gfx::Rect empty_rect;
+    GetDocument().GetFrame()->Client()->OnMainFrameImageAdRectangleChanged(
+        DOMNodeIds::IdForNode(this), empty_rect);
+    last_reported_ad_rect_ = empty_rect;
+  }
+
   if (!form_ || NodeTraversal::HighestAncestorOrSelf(*form_.Get()) !=
                     NodeTraversal::HighestAncestorOrSelf(*this))
     ResetFormOwner();
@@ -662,6 +670,48 @@
   return html_names::kSrcAttr;
 }
 
+void HTMLImageElement::SetIsAdRelated() {
+  if (!is_ad_related_ && GetDocument().View()) {
+    GetDocument().View()->RegisterForLifecycleNotifications(this);
+  }
+
+  is_ad_related_ = true;
+}
+
+void HTMLImageElement::DidFinishLifecycleUpdate(
+    const LocalFrameView& local_frame_view) {
+  DCHECK(is_ad_related_);
+
+  // Scope to the outermost frame to avoid counting image ads that are (likely)
+  // already in ad iframes.
+  LocalFrame* frame = GetDocument().GetFrame();
+  if (!frame || !frame->View() || !frame->IsOutermostMainFrame()) {
+    return;
+  }
+
+  gfx::Rect rect_to_report;
+  if (LayoutObject* r = GetLayoutObject()) {
+    gfx::Rect rect_in_viewport = r->AbsoluteBoundingBoxRect();
+
+    // Exclude image ads that are invisible or too small (e.g. tracking pixels).
+    if (rect_in_viewport.width() > 1 && rect_in_viewport.height() > 1) {
+      if (!image_ad_use_counter_recorded_) {
+        UseCounter::Count(GetDocument(), WebFeature::kImageAd);
+        image_ad_use_counter_recorded_ = true;
+      }
+
+      rect_to_report =
+          rect_in_viewport + frame->View()->LayoutViewport()->ScrollOffsetInt();
+    }
+  }
+
+  if (last_reported_ad_rect_ != rect_to_report) {
+    frame->Client()->OnMainFrameImageAdRectangleChanged(
+        DOMNodeIds::IdForNode(this), rect_to_report);
+    last_reported_ad_rect_ = rect_to_report;
+  }
+}
+
 bool HTMLImageElement::draggable() const {
   // Image elements are draggable by default.
   return !EqualIgnoringASCIICase(FastGetAttribute(html_names::kDraggableAttr),
diff --git a/third_party/blink/renderer/core/html/html_image_element.h b/third_party/blink/renderer/core/html/html_image_element.h
index 2c04b64..26f7485 100644
--- a/third_party/blink/renderer/core/html/html_image_element.h
+++ b/third_party/blink/renderer/core/html/html_image_element.h
@@ -29,6 +29,7 @@
 #include "third_party/blink/renderer/bindings/core/v8/active_script_wrappable.h"
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/dom/create_element_flags.h"
+#include "third_party/blink/renderer/core/frame/local_frame_view.h"
 #include "third_party/blink/renderer/core/html/canvas/image_element_base.h"
 #include "third_party/blink/renderer/core/html/forms/form_associated.h"
 #include "third_party/blink/renderer/core/html/html_element.h"
@@ -52,7 +53,8 @@
     : public HTMLElement,
       public ImageElementBase,
       public ActiveScriptWrappable<HTMLImageElement>,
-      public FormAssociated {
+      public FormAssociated,
+      public LocalFrameView::LifecycleNotificationObserver {
   DEFINE_WRAPPERTYPEINFO();
 
  public:
@@ -185,7 +187,7 @@
   }
 
   // Keeps track of whether the image comes from an ad.
-  void SetIsAdRelated() { is_ad_related_ = true; }
+  void SetIsAdRelated();
   bool IsAdRelated() const override { return is_ad_related_; }
 
   // Keeps track whether this image is an LCP element.
@@ -269,6 +271,9 @@
   void NotifyViewportChanged();
   void CreateMediaQueryListIfDoesNotExist();
 
+  // LocalFrameView::LifecycleNotificationObserver
+  void DidFinishLifecycleUpdate(const LocalFrameView&) override;
+
   Member<HTMLImageLoader> image_loader_;
   Member<ViewportChangeListener> listener_;
   Member<HTMLFormElement> form_;
@@ -294,6 +299,14 @@
 
   std::unique_ptr<LazyLoadImageObserver::VisibleLoadTimeMetrics>
       visible_load_time_metrics_;
+
+  bool image_ad_use_counter_recorded_ = false;
+
+  // The last rectangle reported to the the `PageTimingMetricsSender`.
+  // `last_reported_ad_rect_` is empty if there's no report before, or if the
+  // last report was used to signal the removal of this element (i.e. both cases
+  // will be handled the same way).
+  gfx::Rect last_reported_ad_rect_;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/html/html_image_element_test.cc b/third_party/blink/renderer/core/html/html_image_element_test.cc
index b2cc608..d70557fe 100644
--- a/third_party/blink/renderer/core/html/html_image_element_test.cc
+++ b/third_party/blink/renderer/core/html/html_image_element_test.cc
@@ -9,21 +9,54 @@
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/renderer/core/css/css_property_value_set.h"
 #include "third_party/blink/renderer/core/css/parser/css_parser.h"
+#include "third_party/blink/renderer/core/css/resolver/style_resolver.h"
 #include "third_party/blink/renderer/core/dom/document.h"
 #include "third_party/blink/renderer/core/execution_context/security_context.h"
 #include "third_party/blink/renderer/core/frame/local_frame_view.h"
+#include "third_party/blink/renderer/core/frame/settings.h"
+#include "third_party/blink/renderer/core/loader/empty_clients.h"
 #include "third_party/blink/renderer/core/testing/page_test_base.h"
 
 namespace blink {
 
-class HTMLImageElementTest : public PageTestBase {
+namespace {
+
+class TestFrameClient : public EmptyLocalFrameClient {
+ public:
+  void OnMainFrameImageAdRectangleChanged(
+      DOMNodeId element_id,
+      const gfx::Rect& image_ad_rect) override {
+    observed_image_ad_rects_.emplace_back(element_id, image_ad_rect);
+  }
+
+  const std::vector<std::pair<DOMNodeId, gfx::Rect>>& observed_image_ad_rects()
+      const {
+    return observed_image_ad_rects_;
+  }
+
+ private:
+  std::vector<std::pair<DOMNodeId, gfx::Rect>> observed_image_ad_rects_;
+};
+
+}  // namespace
+
+class HTMLImageElementTest : public PageTestBase,
+                             private ScopedLayoutNGForTest {
  protected:
   static constexpr int kViewportWidth = 500;
   static constexpr int kViewportHeight = 600;
 
+  HTMLImageElementTest() : ScopedLayoutNGForTest(false) {}
+
   void SetUp() override {
-    PageTestBase::SetUp(gfx::Size(kViewportWidth, kViewportHeight));
+    test_frame_client_ = MakeGarbageCollected<TestFrameClient>();
+
+    PageTestBase::SetupPageWithClients(
+        nullptr, test_frame_client_.Get(), nullptr,
+        gfx::Size(kViewportWidth, kViewportHeight));
   }
+
+  Persistent<TestFrameClient> test_frame_client_;
 };
 
 // Instantiate class constants. Not needed after C++17.
@@ -107,4 +140,94 @@
   }
 }
 
+TEST_F(HTMLImageElementTest, ImageAdRectangleUpdate) {
+  GetDocument().GetSettings()->SetScriptEnabled(true);
+
+  SetBodyInnerHTML(R"HTML(
+    <img id="target"
+         style="position:absolute;top:5px;left:5px;width:10px;height:10px;">
+    </img>
+
+    <p style="position:absolute;top:10000px;">abc</p>
+  )HTML");
+
+  HTMLImageElement* image = To<HTMLImageElement>(GetElementById("target"));
+  image->SetIsAdRelated();
+
+  EXPECT_TRUE(test_frame_client_->observed_image_ad_rects().empty());
+
+  UpdateAllLifecyclePhasesForTest();
+
+  EXPECT_EQ(test_frame_client_->observed_image_ad_rects().size(), 1u);
+  DOMNodeId id = test_frame_client_->observed_image_ad_rects()[0].first;
+  EXPECT_EQ(test_frame_client_->observed_image_ad_rects()[0].second,
+            gfx::Rect(5, 5, 10, 10));
+
+  // Scrolling won't trigger another notification, as the rectangle hasn't
+  // changed relative to the page.
+  {
+    auto* script = GetDocument().CreateRawElement(html_names::kScriptTag);
+    script->setTextContent(R"JS(
+      window.scroll(0, 100);
+    )JS");
+    GetDocument().body()->appendChild(script);
+    UpdateAllLifecyclePhasesForTest();
+  }
+
+  EXPECT_EQ(test_frame_client_->observed_image_ad_rects().size(), 1u);
+
+  // Update the size to 1x1. A new notification is expected to signal the
+  // removal of the element.
+  {
+    auto* script = GetDocument().CreateRawElement(html_names::kScriptTag);
+    script->setTextContent(R"JS(
+      var image = document.getElementById('target');
+      image.style.width = '1px';
+      image.style.height = '1px';
+    )JS");
+    GetDocument().body()->appendChild(script);
+    UpdateAllLifecyclePhasesForTest();
+  }
+
+  EXPECT_EQ(test_frame_client_->observed_image_ad_rects().size(), 2u);
+  EXPECT_EQ(test_frame_client_->observed_image_ad_rects()[1].first, id);
+  EXPECT_EQ(test_frame_client_->observed_image_ad_rects()[1].second,
+            gfx::Rect());
+
+  // Update the size to 30x30. A new notification is expected to signal the new
+  // rectangle.
+  {
+    auto* script = GetDocument().CreateRawElement(html_names::kScriptTag);
+    script->setTextContent(R"JS(
+      var image = document.getElementById('target');
+      image.style.width = '30px';
+      image.style.height = '30px';
+    )JS");
+    GetDocument().body()->appendChild(script);
+    UpdateAllLifecyclePhasesForTest();
+  }
+
+  EXPECT_EQ(test_frame_client_->observed_image_ad_rects().size(), 3u);
+  EXPECT_EQ(test_frame_client_->observed_image_ad_rects()[2].first, id);
+  EXPECT_EQ(test_frame_client_->observed_image_ad_rects()[2].second,
+            gfx::Rect(5, 5, 30, 30));
+
+  // Remove the element. A new notification is expected to signal the removal of
+  // the element.
+  {
+    auto* script = GetDocument().CreateRawElement(html_names::kScriptTag);
+    script->setTextContent(R"JS(
+      var image = document.getElementById('target');
+      image.remove()
+    )JS");
+    GetDocument().body()->appendChild(script);
+    UpdateAllLifecyclePhasesForTest();
+  }
+
+  EXPECT_EQ(test_frame_client_->observed_image_ad_rects().size(), 4u);
+  EXPECT_EQ(test_frame_client_->observed_image_ad_rects()[3].first, id);
+  EXPECT_EQ(test_frame_client_->observed_image_ad_rects()[3].second,
+            gfx::Rect());
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/inspector/inspector_trace_events.cc b/third_party/blink/renderer/core/inspector/inspector_trace_events.cc
index 83aa91c..fca8e6b 100644
--- a/third_party/blink/renderer/core/inspector/inspector_trace_events.cc
+++ b/third_party/blink/renderer/core/inspector/inspector_trace_events.cc
@@ -1162,6 +1162,13 @@
   FillCommonFrameData(dict, frame);
 }
 
+void inspector_layerize_event::Data(perfetto::TracedValue context,
+                                    LocalFrame* frame) {
+  auto dict = std::move(context).WriteDictionary();
+  FrameEventData(dict, frame);
+  dict.Add("frame", IdentifiersFactory::FrameId(frame));
+}
+
 void inspector_mark_load_event::Data(perfetto::TracedValue context,
                                      LocalFrame* frame) {
   auto dict = std::move(context).WriteDictionary();
diff --git a/third_party/blink/renderer/core/inspector/inspector_trace_events.h b/third_party/blink/renderer/core/inspector/inspector_trace_events.h
index be5b59d..09bad868 100644
--- a/third_party/blink/renderer/core/inspector/inspector_trace_events.h
+++ b/third_party/blink/renderer/core/inspector/inspector_trace_events.h
@@ -462,6 +462,10 @@
 void Data(perfetto::TracedValue context, LocalFrame*);
 }
 
+namespace inspector_layerize_event {
+void Data(perfetto::TracedValue context, LocalFrame*);
+}
+
 namespace inspector_mark_load_event {
 void Data(perfetto::TracedValue context, LocalFrame*);
 }
diff --git a/third_party/blink/renderer/core/layout/layout_image.cc b/third_party/blink/renderer/core/layout/layout_image.cc
index 1f7791a..12b53da 100644
--- a/third_party/blink/renderer/core/layout/layout_image.cc
+++ b/third_party/blink/renderer/core/layout/layout_image.cc
@@ -463,17 +463,6 @@
     media_element_parser_helpers::CheckUnsizedMediaViolation(
         this, image_element->IsDefaultIntrinsicSize());
     image_element->SetAutoSizesUsecounter();
-
-    // Scope to the outermost frame to avoid counting image ads that are
-    // (likely) already in ad iframes. Exclude image ads that are invisible or
-    // too small (e.g. tracking pixels).
-    if (!image_ad_use_counter_recorded_ && image_element->IsAdRelated() &&
-        GetDocument().IsInOutermostMainFrame() &&
-        image_element->LayoutBoxWidth() > 1 &&
-        image_element->LayoutBoxHeight() > 1) {
-      UseCounter::Count(GetDocument(), WebFeature::kImageAd);
-      image_ad_use_counter_recorded_ = true;
-    }
   } else if (auto* video_element = DynamicTo<HTMLVideoElement>(node)) {
     media_element_parser_helpers::CheckUnsizedMediaViolation(
         this, video_element->IsDefaultIntrinsicSize());
diff --git a/third_party/blink/renderer/core/layout/layout_image.h b/third_party/blink/renderer/core/layout/layout_image.h
index 517f9fb7..be4e212 100644
--- a/third_party/blink/renderer/core/layout/layout_image.h
+++ b/third_party/blink/renderer/core/layout/layout_image.h
@@ -179,8 +179,6 @@
 
   // This field stores whether this image is generated with 'content'.
   bool is_generated_content_ = false;
-
-  bool image_ad_use_counter_recorded_ = false;
 };
 
 template <>
diff --git a/third_party/blink/renderer/core/testing/page_test_base.cc b/third_party/blink/renderer/core/testing/page_test_base.cc
index 3722a3a..315fad14 100644
--- a/third_party/blink/renderer/core/testing/page_test_base.cc
+++ b/third_party/blink/renderer/core/testing/page_test_base.cc
@@ -147,7 +147,8 @@
 void PageTestBase::SetupPageWithClients(
     ChromeClient* chrome_client,
     LocalFrameClient* local_frame_client,
-    FrameSettingOverrideFunction setting_overrider) {
+    FrameSettingOverrideFunction setting_overrider,
+    gfx::Size size) {
   DCHECK(!dummy_page_holder_) << "Page should be set up only once";
   auto setter = base::BindLambdaForTesting([&](Settings& settings) {
     if (setting_overrider)
@@ -155,9 +156,9 @@
     if (enable_compositing_)
       settings.SetAcceleratedCompositingEnabled(true);
   });
-  dummy_page_holder_ = std::make_unique<DummyPageHolder>(
-      gfx::Size(800, 600), chrome_client, local_frame_client, std::move(setter),
-      GetTickClock());
+  dummy_page_holder_ =
+      std::make_unique<DummyPageHolder>(size, chrome_client, local_frame_client,
+                                        std::move(setter), GetTickClock());
 
   // Use no-quirks (ake "strict") mode by default.
   GetDocument().SetCompatibilityMode(Document::kNoQuirksMode);
diff --git a/third_party/blink/renderer/core/testing/page_test_base.h b/third_party/blink/renderer/core/testing/page_test_base.h
index 52b8d4e1..48a515e 100644
--- a/third_party/blink/renderer/core/testing/page_test_base.h
+++ b/third_party/blink/renderer/core/testing/page_test_base.h
@@ -67,7 +67,8 @@
   void SetUp(gfx::Size);
   void SetupPageWithClients(ChromeClient* = nullptr,
                             LocalFrameClient* = nullptr,
-                            FrameSettingOverrideFunction = nullptr);
+                            FrameSettingOverrideFunction = nullptr,
+                            gfx::Size size = gfx::Size(800, 600));
   // TODO(shanmuga.m@samsung.com): These two function to be unified.
   void SetBodyContent(const std::string&);
   void SetBodyInnerHTML(const String&);
diff --git a/third_party/blink/renderer/modules/accessibility/ax_layout_object.cc b/third_party/blink/renderer/modules/accessibility/ax_layout_object.cc
index 3e18eef..9e85b9f2 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_layout_object.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_layout_object.cc
@@ -128,8 +128,9 @@
 }
 
 ScrollableArea* AXLayoutObject::GetScrollableAreaIfScrollable() const {
-  if (IsWebArea())
+  if (IsA<Document>(GetNode())) {
     return DocumentFrameView()->LayoutViewport();
+  }
 
   if (auto* box = DynamicTo<LayoutBox>(GetLayoutObject())) {
     PaintLayerScrollableArea* scrollable_area = box->GetScrollableArea();
@@ -224,8 +225,10 @@
   if (IsA<HTMLCanvasElement>(node))
     return ax::mojom::blink::Role::kCanvas;
 
-  if (IsA<LayoutView>(*layout_object_))
-    return ax::mojom::blink::Role::kRootWebArea;
+  if (IsA<LayoutView>(*layout_object_)) {
+    return ParentObject() ? ax::mojom::blink::Role::kGroup
+                          : ax::mojom::blink::Role::kRootWebArea;
+  }
 
   if (node && node->IsSVGElement()) {
     if (layout_object_->IsSVGImage())
@@ -429,8 +432,9 @@
   // node of the main web area, so force that node to always be unignored.
   // The web area for a <select>'s' popup document is ignored, because the
   // popup object hierarchy is constructed without the document root.
-  if (IsWebArea())
+  if (IsA<Document>(GetNode())) {
     return CachedParentObject() && CachedParentObject()->IsMenuList();
+  }
 
   const Node* node = GetNode();
   if (IsA<HTMLHtmlElement>(node))
@@ -1151,8 +1155,9 @@
 
 AXObject* AXLayoutObject::AccessibilityHitTest(const gfx::Point& point) const {
   // Must be called for the document's root or a popup's root.
-  if (RoleValue() != ax::mojom::blink::Role::kRootWebArea || !layout_object_)
+  if (!IsA<Document>(GetNode()) || !layout_object_) {
     return nullptr;
+  }
 
   // Must be called with lifecycle >= pre-paint clean
   DCHECK_GE(GetDocument()->Lifecycle().GetState(),
diff --git a/third_party/blink/renderer/modules/accessibility/ax_node_object.cc b/third_party/blink/renderer/modules/accessibility/ax_node_object.cc
index 6039892..bf596cd 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_node_object.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_node_object.cc
@@ -756,8 +756,9 @@
 
   // All nodes must have an unignored parent within their tree under
   // the root node of the web area, so force that node to always be unignored.
-  if (IsWebArea())
+  if (IsA<Document>(GetNode())) {
     return false;
+  }
 
   DCHECK_NE(role_, ax::mojom::blink::Role::kUnknown);
   // Use AXLayoutObject::ComputeAccessibilityIsIgnored().
@@ -2550,7 +2551,7 @@
   if (!layout_object)
     return Color::kTransparent.Rgb();
 
-  if (IsWebArea()) {
+  if (IsA<Document>(GetNode())) {
     LocalFrameView* view = DocumentFrameView();
     if (view)
       return view->BaseBackgroundColor().Rgb();
@@ -2899,8 +2900,10 @@
   if (IsLink())  // <area>, <link>, <html:a> or <svg:a>
     return GetElement()->HrefURL();
 
-  if (IsWebArea() && GetDocument())
+  if (IsWebArea()) {
+    DCHECK(GetDocument());
     return GetDocument()->Url();
+  }
 
   auto* html_image_element = DynamicTo<HTMLImageElement>(GetNode());
   if (IsImage() && html_image_element) {
@@ -2920,18 +2923,28 @@
 
 AXObject* AXNodeObject::ChooserPopup() const {
   // When color & date chooser popups are visible, they can be found in the tree
-  // as a WebArea child of the <input> control itself.
+  // as a group child of the <input> control itself.
   switch (native_role_) {
     case ax::mojom::blink::Role::kColorWell:
+    case ax::mojom::blink::Role::kComboBoxSelect:
     case ax::mojom::blink::Role::kDate:
-    case ax::mojom::blink::Role::kDateTime: {
+    case ax::mojom::blink::Role::kDateTime:
+    case ax::mojom::blink::Role::kInputTime:
+    case ax::mojom::blink::Role::kTextFieldWithComboBox: {
       for (const auto& child : ChildrenIncludingIgnored()) {
-        if (child->IsWebArea())
+        if (IsA<Document>(child->GetNode())) {
           return child;
+        }
       }
       return nullptr;
     }
     default:
+#if DCHECK_IS_ON()
+      for (const auto& child : ChildrenIncludingIgnored()) {
+        DCHECK(!IsA<Document>(child->GetNode()))
+            << "Chooser popup exists for " << native_role_;
+      }
+#endif
       return nullptr;
   }
 }
@@ -4022,7 +4035,6 @@
   auto* html_input_element = DynamicTo<HTMLInputElement>(GetNode());
   if (html_input_element) {
     AddChildAndCheckIncluded(html_input_element->PopupRootAXObject());
-    return;
   }
 }
 
@@ -4498,7 +4510,7 @@
 
   // If it's the root, get the computed language for the document element,
   // because the root LayoutObject doesn't have the right value.
-  if (RoleValue() == ax::mojom::blink::Role::kRootWebArea) {
+  if (IsWebArea()) {
     Element* document_element = GetDocument()->documentElement();
     if (!document_element)
       return g_empty_atom;
@@ -5315,8 +5327,7 @@
   }
 
   // Document.
-  if (IsWebArea()) {
-    Document* document = GetDocument();
+  if (Document* document = DynamicTo<Document>(GetNode())) {
     if (document) {
       name_from = ax::mojom::blink::NameFrom::kAttribute;
       if (name_sources) {
diff --git a/third_party/blink/renderer/modules/accessibility/ax_object.cc b/third_party/blink/renderer/modules/accessibility/ax_object.cc
index 59d9dc4..bdaa23da 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_object.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_object.cc
@@ -860,7 +860,7 @@
   DCHECK(node->isConnected())
       << "Should not call with disconnected node: " << node;
 
-  // A WebArea's parent should be the page popup owner, if any, otherwise null.
+  // A document's parent should be the page popup owner, if any, otherwise null.
   if (auto* document = DynamicTo<Document>(node)) {
     LocalFrame* frame = document->GetFrame();
     DCHECK(frame);
@@ -1500,7 +1500,7 @@
 void AXObject::SerializeHTMLTagAndClass(ui::AXNodeData* node_data) {
   Element* element = GetElement();
   if (!element) {
-    if (ui::IsPlatformDocument(RoleValue())) {
+    if (IsA<Document>(GetNode())) {
       TruncateAndAddStringAttribute(
           node_data, ax::mojom::blink::StringAttribute::kHtmlTag, "#document");
     }
@@ -1762,8 +1762,17 @@
     }
   }
 
-  if (ui::IsPlatformDocument(node_data->role) && !IsLoaded())
-    node_data->AddBoolAttribute(ax::mojom::blink::BoolAttribute::kBusy, true);
+  if (IsA<Document>(GetNode())) {
+    if (!IsLoaded()) {
+      node_data->AddBoolAttribute(ax::mojom::blink::BoolAttribute::kBusy, true);
+    }
+    if (AXObject* parent = ParentObject()) {
+      DCHECK(parent->ChooserPopup() == this)
+          << "ChooserPopup missing for: " << parent->ToString(true);
+      node_data->AddIntAttribute(ax::mojom::blink::IntAttribute::kPopupForId,
+                                 parent->AXObjectID());
+    }
+  }
 
   if (node_data->role == ax::mojom::blink::Role::kColorWell) {
     node_data->AddIntAttribute(ax::mojom::blink::IntAttribute::kColorValue,
@@ -3048,8 +3057,9 @@
 
 bool AXObject::ComputeIsAriaHidden(IgnoredReasons* ignored_reasons) const {
   // The root node of a document or popup document cannot be aria-hidden.
-  if (IsWebArea())
+  if (IsA<Document>(GetNode())) {
     return false;
+  }
 
   // aria-hidden:true works a bit like display:none.
   // * aria-hidden=true affects entire subtree.
@@ -5792,13 +5802,14 @@
   }
 
   if (clips_children) {
-    if (IsWebArea())
+    if (IsA<Document>(GetNode())) {
       *clips_children = true;
-    else
+    } else {
       *clips_children = layout_object->HasNonVisibleOverflow();
+    }
   }
 
-  if (IsWebArea()) {
+  if (IsA<Document>(GetNode())) {
     if (LocalFrameView* view = layout_object->GetFrame()->View()) {
       out_bounds_in_container.set_size(gfx::SizeF(view->Size()));
 
@@ -5844,8 +5855,9 @@
           if (layout_object->IsAbsolutePositioned()) {
             // If it's absolutely positioned, the container must be the
             // nearest positioned container, or the root.
-            if (container->IsWebArea())
+            if (IsA<LayoutView>(layout_object)) {
               break;
+            }
             if (container_layout_object->IsPositioned())
               break;
           } else {
diff --git a/third_party/blink/renderer/platform/audio/audio_destination.cc b/third_party/blink/renderer/platform/audio/audio_destination.cc
index cc607b84..2523346 100644
--- a/third_party/blink/renderer/platform/audio/audio_destination.cc
+++ b/third_party/blink/renderer/platform/audio/audio_destination.cc
@@ -242,14 +242,6 @@
   return web_audio_device_->FramesPerBuffer();
 }
 
-size_t AudioDestination::HardwareBufferSize() {
-  return Platform::Current()->AudioHardwareBufferSize();
-}
-
-float AudioDestination::HardwareSampleRate() {
-  return static_cast<float>(Platform::Current()->AudioHardwareSampleRate());
-}
-
 uint32_t AudioDestination::MaxChannelCount() {
   return Platform::Current()->AudioHardwareOutputChannels();
 }
@@ -275,8 +267,19 @@
     const WebAudioLatencyHint& latency_hint,
     absl::optional<float> context_sample_rate,
     unsigned render_quantum_frames)
-    : number_of_output_channels_(number_of_output_channels),
+    : web_audio_device_(
+          Platform::Current()->CreateAudioDevice(sink_descriptor,
+                                                 number_of_output_channels,
+                                                 latency_hint,
+                                                 this)),
+      callback_buffer_size_(
+          web_audio_device_ ? web_audio_device_->FramesPerBuffer() : 0),
+      number_of_output_channels_(number_of_output_channels),
       render_quantum_frames_(render_quantum_frames),
+      context_sample_rate_(
+          context_sample_rate.has_value()
+              ? context_sample_rate.value()
+              : (web_audio_device_ ? web_audio_device_->SampleRate() : 0)),
       fifo_(std::make_unique<PushPullFIFO>(number_of_output_channels,
                                            kFIFOSize,
                                            render_quantum_frames)),
@@ -286,16 +289,13 @@
       render_bus_(
           AudioBus::Create(number_of_output_channels, render_quantum_frames)),
       callback_(callback) {
+  CHECK(web_audio_device_);
+
   SendLogMessage(String::Format("%s({output_channels=%u})", __func__,
                                 number_of_output_channels));
   SendLogMessage(
       String::Format("%s => (FIFO size=%u bytes)", __func__, fifo_->length()));
 
-  web_audio_device_ = Platform::Current()->CreateAudioDevice(
-      sink_descriptor, number_of_output_channels, latency_hint, this);
-  DCHECK(web_audio_device_);
-
-  callback_buffer_size_ = web_audio_device_->FramesPerBuffer();
   SendLogMessage(String::Format("%s => (device callback buffer size=%u frames)",
                                 __func__, callback_buffer_size_));
   SendLogMessage(String::Format("%s => (device sample rate=%.0f Hz)", __func__,
@@ -319,16 +319,13 @@
     fifo_->Push(render_bus_.get());
   }
 
-  if (!CheckBufferSize(render_quantum_frames)) {
-    NOTREACHED();
-  }
+  // Check if the requested buffer size is too large.
+  DCHECK_LE(callback_buffer_size_ + render_quantum_frames, kFIFOSize);
 
-  double scale_factor = 1;
+  double scale_factor = 1.0;
 
-  if (context_sample_rate.has_value() &&
-      context_sample_rate.value() != web_audio_device_->SampleRate()) {
-    scale_factor =
-        context_sample_rate.value() / web_audio_device_->SampleRate();
+  if (context_sample_rate_ != web_audio_device_->SampleRate()) {
+    scale_factor = context_sample_rate_ / web_audio_device_->SampleRate();
     SendLogMessage(String::Format("%s => (resampling from %0.f Hz to %0.f Hz)",
                                   __func__, context_sample_rate.value(),
                                   web_audio_device_->SampleRate()));
@@ -343,14 +340,24 @@
       resampler_bus_->SetChannelData(i, render_bus_->Channel(i)->MutableData());
     }
     resampler_bus_->set_frames(render_bus_->length());
-    context_sample_rate_ = context_sample_rate.value();
   } else {
-    context_sample_rate_ = web_audio_device_->SampleRate();
     SendLogMessage(String::Format(
         "%s => (no resampling: context sample rate set to %0.f Hz)", __func__,
         context_sample_rate_));
   }
 
+  // Record the sizes if we successfully created an output device.
+  // Histogram for audioHardwareBufferSize
+  base::UmaHistogramSparse(
+      "WebAudio.AudioDestination.HardwareBufferSize",
+      static_cast<int>(Platform::Current()->AudioHardwareBufferSize()));
+
+  // Histogram for the actual callback size used.  Typically, this is the same
+  // as audioHardwareBufferSize, but can be adjusted depending on some
+  // heuristics below.
+  base::UmaHistogramSparse("WebAudio.AudioDestination.CallbackBufferSize",
+                           callback_buffer_size_);
+
   base::UmaHistogramSparse("WebAudio.AudioContext.HardwareSampleRate",
                            web_audio_device_->SampleRate());
 
@@ -366,7 +373,7 @@
     // the most common ratios to be the set 0.5, 44100/48000, and 48000/44100.
     // Other values are possible but seem unlikely.
     base::UmaHistogramSparse("WebAudio.AudioContextOptions.sampleRateRatio",
-                             static_cast<int32_t>(100 * scale_factor + 0.5));
+                             static_cast<int32_t>(100.0 * scale_factor + 0.5));
   }
 }
 
@@ -451,25 +458,6 @@
                    metric_reporter_.GetMetric());
 }
 
-bool AudioDestination::CheckBufferSize(unsigned render_quantum_frames) {
-  // Record the sizes if we successfully created an output device.
-  // Histogram for audioHardwareBufferSize
-  base::UmaHistogramSparse("WebAudio.AudioDestination.HardwareBufferSize",
-                           static_cast<int>(HardwareBufferSize()));
-
-  // Histogram for the actual callback size used.  Typically, this is the same
-  // as audioHardwareBufferSize, but can be adjusted depending on some
-  // heuristics below.
-  base::UmaHistogramSparse("WebAudio.AudioDestination.CallbackBufferSize",
-                           callback_buffer_size_);
-
-  // Check if the requested buffer size is too large.
-  const bool is_buffer_size_valid =
-      callback_buffer_size_ + render_quantum_frames <= kFIFOSize;
-  DCHECK_LE(callback_buffer_size_ + render_quantum_frames, kFIFOSize);
-  return is_buffer_size_valid;
-}
-
 void AudioDestination::SendLogMessage(const String& message) const {
   WebRtcLogMessage(String::Format("[WA]AD::%s [state=%s]",
                                   message.Utf8().c_str(),
diff --git a/third_party/blink/renderer/platform/audio/audio_destination.h b/third_party/blink/renderer/platform/audio/audio_destination.h
index 76df5cd..347cae2 100644
--- a/third_party/blink/renderer/platform/audio/audio_destination.h
+++ b/third_party/blink/renderer/platform/audio/audio_destination.h
@@ -104,27 +104,23 @@
   void StartWithWorkletTaskRunner(
       scoped_refptr<base::SingleThreadTaskRunner> worklet_task_runner);
 
-  // Getters must be accessed from the main thread.
-  uint32_t CallbackBufferSize() const;
-
   bool IsPlaying();
 
   // This is the context sample rate, not the device one.
   double SampleRate() const;
 
+  uint32_t CallbackBufferSize() const;
+
   // Returns the audio buffer size in frames used by the underlying audio
   // hardware.
   int FramesPerBuffer() const;
 
   // The information from the actual audio hardware. (via Platform::Current)
-  static size_t HardwareBufferSize();
-  static float HardwareSampleRate();
   static uint32_t MaxChannelCount();
 
   // Sets the detect silence flag for `web_audio_device_`.
   void SetDetectSilence(bool detect_silence);
 
-  // This should only be called from the audio thread.
   unsigned RenderQuantumFrames() const;
 
  private:
@@ -148,22 +144,19 @@
   // Provide input to the resampler (if used).
   void ProvideResamplerInput(int resampler_frame_delay, AudioBus* dest);
 
-  // Check if the buffer size chosen by the WebAudioDevice is too large.
-  bool CheckBufferSize(unsigned render_quantum_frames);
-
   void SendLogMessage(const String& message) const;
 
   // Accessed by the main thread.
   std::unique_ptr<WebAudioDevice> web_audio_device_;
 
-  uint32_t callback_buffer_size_;
+  const uint32_t callback_buffer_size_;
 
   const unsigned number_of_output_channels_;
 
   const unsigned render_quantum_frames_;
 
   // The sample rate used for rendering the Web Audio graph.
-  float context_sample_rate_;
+  const float context_sample_rate_;
 
   // Can be accessed by both threads: resolves the buffer size mismatch between
   // the WebAudio engine and the callback function from the actual audio device.
diff --git a/third_party/blink/tools/blinkpy/web_tests/results.html b/third_party/blink/tools/blinkpy/web_tests/results.html
index a0970fb..40809236 100644
--- a/third_party/blink/tools/blinkpy/web_tests/results.html
+++ b/third_party/blink/tools/blinkpy/web_tests/results.html
@@ -548,7 +548,7 @@
     '-expected.png': 'expected_image',
     '-diff.png': 'image_diff',
     '-actual.txt': 'actual_text',
-    '-expected.txt': 'expected_txt',
+    '-expected.txt': 'expected_text',
     '-diff.txt': 'text_diff',
     '-pretty-diff.html': 'pretty_text_diff',
     '-actual.wav': 'actual_audio',
@@ -585,7 +585,7 @@
       return this.dir + this.resultPrefix + resultName;
     }
     let artifacts = await this.getArtifacts();
-    return artifacts[PathParser.resultNameToArtifactName[resultName]];
+    return artifacts[PathParser.resultNameToArtifactName[resultName]] || 'about:blank';
   }
 
   resultFilename(resultName) {
@@ -2944,6 +2944,19 @@
 
   let script = document.createElement('script');
   script.src = jsonpUrl.substring(slashPos + 1);
+  script.onerror = () => {
+    let message = `Fail to load ${jsonpUrl}.<br>` +
+                  'This may be because the web test step has not finished yet';
+    if (location.search) {
+      let oldResults =
+          `https://test-results.appspot.com/data/layout_results/${jsonpUrl.substring(0, slashPos)}/layout-test-results/results.html`;
+      message += ', or the results are in the old format which can be' +
+                 ` accessed through <a href="${oldResults}">this link</a>.`;
+    } else {
+      message += '.';
+    }
+    document.write(message);
+  };
   document.body.appendChild(script);
 })();
 </script>
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 36e06c76..175a91a8 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -6306,7 +6306,8 @@
 # Sheriff 2022-10-03
 crbug.com/1368767 wpt_internal/webmidi/requestmidiaccess-basic.https.html [ Failure Pass ]
 
-crbug.com/1394227 [ Linux ] external/wpt/element-timing/image-src-change.html [ Failure Pass ]
+# TODO(crbug.com/1403318): Re-enable this test
+crbug.com/1394227 [ Linux ] external/wpt/element-timing/image-src-change.html [ Failure Skip ]
 crbug.com/1394227 [ Mac ] external/wpt/element-timing/image-src-change.html [ Failure Pass ]
 
 # These tests are optional and not reliably supported in Chrome.
diff --git a/third_party/blink/web_tests/VirtualTestSuites b/third_party/blink/web_tests/VirtualTestSuites
index fe15e91e..259311d 100644
--- a/third_party/blink/web_tests/VirtualTestSuites
+++ b/third_party/blink/web_tests/VirtualTestSuites
@@ -1685,7 +1685,17 @@
     "prefix": "js-resizable-arraybuffer",
     "platforms": ["Linux", "Mac", "Win"],
     "bases": [
-      "external/wpt/html/infrastructure/safe-passing-of-structured-data/cross-origin-transfer-resizable-arraybuffer.html"
+      "external/wpt/html/infrastructure/safe-passing-of-structured-data/cross-origin-transfer-resizable-arraybuffer.html",
+      "external/wpt/html/infrastructure/safe-passing-of-structured-data/messagechannel.any.html",
+      "external/wpt/html/infrastructure/safe-passing-of-structured-data/messagechannel.any.serviceworker.html",
+      "external/wpt/html/infrastructure/safe-passing-of-structured-data/messagechannel.any.sharedworker.html",
+      "external/wpt/html/infrastructure/safe-passing-of-structured-data/messagechannel.any.worker.html",
+      "external/wpt/html/infrastructure/safe-passing-of-structured-data/window-postmessage.window.html",
+      "external/wpt/html/webappapis/structured-clone/structured-clone.any.html",
+      "external/wpt/html/webappapis/structured-clone/structured-clone.any.worker.html",
+      "external/wpt/webidl/ecmascript-binding/allow-resizable.html",
+      "external/wpt/workers/semantics/structured-clone/dedicated.html",
+      "external/wpt/workers/semantics/structured-clone/shared.html"
     ],
     "exclusive_tests": [
       "external/wpt/html/infrastructure/safe-passing-of-structured-data/cross-origin-transfer-resizable-arraybuffer.html"
diff --git a/third_party/blink/web_tests/external/wpt/common/sab.js b/third_party/blink/web_tests/external/wpt/common/sab.js
index 47d1297..a3ea610e 100644
--- a/third_party/blink/web_tests/external/wpt/common/sab.js
+++ b/third_party/blink/web_tests/external/wpt/common/sab.js
@@ -6,14 +6,14 @@
   } catch(e) {
     sabConstructor = null;
   }
-  return (type, length) => {
+  return (type, length, opts) => {
     if (type === "ArrayBuffer") {
-      return new ArrayBuffer(length);
+      return new ArrayBuffer(length, opts);
     } else if (type === "SharedArrayBuffer") {
       if (sabConstructor && sabConstructor.name !== "SharedArrayBuffer") {
         throw new Error("WebAssembly.Memory does not support shared:true");
       }
-      return new sabConstructor(length);
+      return new sabConstructor(length, opts);
     } else {
       throw new Error("type has to be ArrayBuffer or SharedArrayBuffer");
     }
diff --git a/third_party/blink/web_tests/external/wpt/html/infrastructure/safe-passing-of-structured-data/messagechannel.any-expected.txt b/third_party/blink/web_tests/external/wpt/html/infrastructure/safe-passing-of-structured-data/messagechannel.any-expected.txt
new file mode 100644
index 0000000..09b51b19
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/infrastructure/safe-passing-of-structured-data/messagechannel.any-expected.txt
@@ -0,0 +1,154 @@
+This is a testharness.js-based test.
+Found 150 tests; 139 PASS, 11 FAIL, 0 TIMEOUT, 0 NOTRUN.
+PASS primitive undefined
+PASS primitive null
+PASS primitive true
+PASS primitive false
+PASS primitive string, empty string
+PASS primitive string, lone high surrogate
+PASS primitive string, lone low surrogate
+PASS primitive string, NUL
+PASS primitive string, astral character
+PASS primitive number, 0.2
+PASS primitive number, 0
+PASS primitive number, -0
+PASS primitive number, NaN
+PASS primitive number, Infinity
+PASS primitive number, -Infinity
+PASS primitive number, 9007199254740992
+PASS primitive number, -9007199254740992
+PASS primitive number, 9007199254740994
+PASS primitive number, -9007199254740994
+PASS primitive BigInt, 0n
+PASS primitive BigInt, -0n
+PASS primitive BigInt, -9007199254740994000n
+PASS primitive BigInt, -9007199254740994000900719925474099400090071992547409940009007199254740994000n
+PASS Array primitives
+PASS Object primitives
+PASS Boolean true
+PASS Boolean false
+PASS Array Boolean objects
+PASS Object Boolean objects
+PASS String empty string
+PASS String lone high surrogate
+PASS String lone low surrogate
+PASS String NUL
+PASS String astral character
+PASS Array String objects
+PASS Object String objects
+PASS Number 0.2
+PASS Number 0
+PASS Number -0
+PASS Number NaN
+PASS Number Infinity
+PASS Number -Infinity
+PASS Number 9007199254740992
+PASS Number -9007199254740992
+PASS Number 9007199254740994
+PASS Number -9007199254740994
+PASS BigInt -9007199254740994n
+PASS Array Number objects
+PASS Object Number objects
+PASS Date 0
+PASS Date -0
+PASS Date -8.64e15
+PASS Date 8.64e15
+PASS Array Date objects
+PASS Object Date objects
+PASS RegExp flags and lastIndex
+PASS RegExp sticky flag
+PASS RegExp unicode flag
+PASS RegExp empty
+PASS RegExp slash
+PASS RegExp new line
+PASS Array RegExp object, RegExp flags and lastIndex
+PASS Array RegExp object, RegExp sticky flag
+PASS Array RegExp object, RegExp unicode flag
+PASS Array RegExp object, RegExp empty
+PASS Array RegExp object, RegExp slash
+PASS Array RegExp object, RegExp new line
+PASS Object RegExp object, RegExp flags and lastIndex
+PASS Object RegExp object, RegExp sticky flag
+PASS Object RegExp object, RegExp unicode flag
+PASS Object RegExp object, RegExp empty
+PASS Object RegExp object, RegExp slash
+PASS Object RegExp object, RegExp new line
+PASS Empty Error object
+PASS Error object
+PASS EvalError object
+PASS RangeError object
+PASS ReferenceError object
+PASS SyntaxError object
+PASS TypeError object
+PASS URIError object
+PASS Blob basic
+PASS Blob unpaired high surrogate (invalid utf-8)
+PASS Blob unpaired low surrogate (invalid utf-8)
+PASS Blob paired surrogates (invalid utf-8)
+PASS Blob empty
+PASS Blob NUL
+PASS Array Blob object, Blob basic
+PASS Array Blob object, Blob unpaired high surrogate (invalid utf-8)
+PASS Array Blob object, Blob unpaired low surrogate (invalid utf-8)
+PASS Array Blob object, Blob paired surrogates (invalid utf-8)
+PASS Array Blob object, Blob empty
+PASS Array Blob object, Blob NUL
+PASS Array Blob object, two Blobs
+PASS Object Blob object, Blob basic
+PASS Object Blob object, Blob unpaired high surrogate (invalid utf-8)
+PASS Object Blob object, Blob unpaired low surrogate (invalid utf-8)
+PASS Object Blob object, Blob paired surrogates (invalid utf-8)
+PASS Object Blob object, Blob empty
+PASS Object Blob object, Blob NUL
+PASS File basic
+PASS FileList empty
+PASS Array FileList object, FileList empty
+PASS Object FileList object, FileList empty
+PASS ImageData 1x1 transparent black
+PASS ImageData 1x1 non-transparent non-black
+PASS Array ImageData object, ImageData 1x1 transparent black
+PASS Array ImageData object, ImageData 1x1 non-transparent non-black
+PASS Object ImageData object, ImageData 1x1 transparent black
+PASS Object ImageData object, ImageData 1x1 non-transparent non-black
+PASS Array sparse
+PASS Array with non-index property
+PASS Object with index property and length
+PASS Array with circular reference
+PASS Object with circular reference
+PASS Array with identical property values
+PASS Object with identical property values
+PASS Object with property on prototype
+PASS Object with non-enumerable property
+PASS Object with non-writable property
+PASS Object with non-configurable property
+PASS Object with a getter that throws
+PASS ImageBitmap 1x1 transparent black
+PASS ImageBitmap 1x1 non-transparent non-black
+PASS Array ImageBitmap object, ImageBitmap 1x1 transparent black
+PASS Array ImageBitmap object, ImageBitmap 1x1 transparent non-black
+PASS Object ImageBitmap object, ImageBitmap 1x1 transparent black
+PASS Object ImageBitmap object, ImageBitmap 1x1 transparent non-black
+PASS ObjectPrototype must lose its exotic-ness when cloned
+PASS Serializing a non-serializable platform object fails
+PASS An object whose interface is deleted from the global must still deserialize
+PASS A subclass instance will deserialize as its closest serializable superclass
+FAIL Resizable ArrayBuffer assert_true: expected true got undefined
+FAIL Growable SharedArrayBuffer assert_true: expected true got undefined
+FAIL Length-tracking TypedArray assert_true: expected true got undefined
+FAIL Length-tracking DataView assert_true: expected true got undefined
+FAIL Serializing OOB TypedArray throws promise_test: Unhandled rejection with value: object "TypeError: ab.resize is not a function"
+FAIL Serializing OOB DataView throws promise_test: Unhandled rejection with value: object "TypeError: ab.resize is not a function"
+PASS ArrayBuffer
+PASS MessagePort
+PASS A detached ArrayBuffer cannot be transferred
+PASS A detached platform object cannot be transferred
+PASS Transferring a non-transferable platform object fails
+PASS An object whose interface is deleted from the global object must still be received
+PASS A subclass instance will be received as its closest transferable superclass
+FAIL Resizable ArrayBuffer is transferable assert_equals: expected (number) 1024 but got (undefined) undefined
+FAIL Length-tracking TypedArray is transferable assert_equals: expected (number) 1024 but got (undefined) undefined
+FAIL Length-tracking DataView is transferable assert_equals: expected (number) 1024 but got (undefined) undefined
+FAIL Transferring OOB TypedArray throws promise_test: Unhandled rejection with value: object "TypeError: ab.resize is not a function"
+FAIL Transferring OOB DataView throws promise_test: Unhandled rejection with value: object "TypeError: ab.resize is not a function"
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/html/infrastructure/safe-passing-of-structured-data/messagechannel.any.js b/third_party/blink/web_tests/external/wpt/html/infrastructure/safe-passing-of-structured-data/messagechannel.any.js
index 1814df3..6ba17f7 100644
--- a/third_party/blink/web_tests/external/wpt/html/infrastructure/safe-passing-of-structured-data/messagechannel.any.js
+++ b/third_party/blink/web_tests/external/wpt/html/infrastructure/safe-passing-of-structured-data/messagechannel.any.js
@@ -1,4 +1,5 @@
 // META: global=window,worker
+// META: script=/common/sab.js
 // META: script=/html/webappapis/structured-clone/structured-clone-battery-of-tests.js
 // META: script=/html/webappapis/structured-clone/structured-clone-battery-of-tests-with-transferables.js
 // META: script=/html/webappapis/structured-clone/structured-clone-battery-of-tests-harness.js
diff --git a/third_party/blink/web_tests/external/wpt/html/infrastructure/safe-passing-of-structured-data/messagechannel.any.serviceworker-expected.txt b/third_party/blink/web_tests/external/wpt/html/infrastructure/safe-passing-of-structured-data/messagechannel.any.serviceworker-expected.txt
new file mode 100644
index 0000000..acb3995
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/infrastructure/safe-passing-of-structured-data/messagechannel.any.serviceworker-expected.txt
@@ -0,0 +1,139 @@
+This is a testharness.js-based test.
+Found 135 tests; 124 PASS, 11 FAIL, 0 TIMEOUT, 0 NOTRUN.
+PASS primitive undefined
+PASS primitive null
+PASS primitive true
+PASS primitive false
+PASS primitive string, empty string
+PASS primitive string, lone high surrogate
+PASS primitive string, lone low surrogate
+PASS primitive string, NUL
+PASS primitive string, astral character
+PASS primitive number, 0.2
+PASS primitive number, 0
+PASS primitive number, -0
+PASS primitive number, NaN
+PASS primitive number, Infinity
+PASS primitive number, -Infinity
+PASS primitive number, 9007199254740992
+PASS primitive number, -9007199254740992
+PASS primitive number, 9007199254740994
+PASS primitive number, -9007199254740994
+PASS primitive BigInt, 0n
+PASS primitive BigInt, -0n
+PASS primitive BigInt, -9007199254740994000n
+PASS primitive BigInt, -9007199254740994000900719925474099400090071992547409940009007199254740994000n
+PASS Array primitives
+PASS Object primitives
+PASS Boolean true
+PASS Boolean false
+PASS Array Boolean objects
+PASS Object Boolean objects
+PASS String empty string
+PASS String lone high surrogate
+PASS String lone low surrogate
+PASS String NUL
+PASS String astral character
+PASS Array String objects
+PASS Object String objects
+PASS Number 0.2
+PASS Number 0
+PASS Number -0
+PASS Number NaN
+PASS Number Infinity
+PASS Number -Infinity
+PASS Number 9007199254740992
+PASS Number -9007199254740992
+PASS Number 9007199254740994
+PASS Number -9007199254740994
+PASS BigInt -9007199254740994n
+PASS Array Number objects
+PASS Object Number objects
+PASS Date 0
+PASS Date -0
+PASS Date -8.64e15
+PASS Date 8.64e15
+PASS Array Date objects
+PASS Object Date objects
+PASS RegExp flags and lastIndex
+PASS RegExp sticky flag
+PASS RegExp unicode flag
+PASS RegExp empty
+PASS RegExp slash
+PASS RegExp new line
+PASS Array RegExp object, RegExp flags and lastIndex
+PASS Array RegExp object, RegExp sticky flag
+PASS Array RegExp object, RegExp unicode flag
+PASS Array RegExp object, RegExp empty
+PASS Array RegExp object, RegExp slash
+PASS Array RegExp object, RegExp new line
+PASS Object RegExp object, RegExp flags and lastIndex
+PASS Object RegExp object, RegExp sticky flag
+PASS Object RegExp object, RegExp unicode flag
+PASS Object RegExp object, RegExp empty
+PASS Object RegExp object, RegExp slash
+PASS Object RegExp object, RegExp new line
+PASS Empty Error object
+PASS Error object
+PASS EvalError object
+PASS RangeError object
+PASS ReferenceError object
+PASS SyntaxError object
+PASS TypeError object
+PASS URIError object
+PASS Blob basic
+PASS Blob unpaired high surrogate (invalid utf-8)
+PASS Blob unpaired low surrogate (invalid utf-8)
+PASS Blob paired surrogates (invalid utf-8)
+PASS Blob empty
+PASS Blob NUL
+PASS Array Blob object, Blob basic
+PASS Array Blob object, Blob unpaired high surrogate (invalid utf-8)
+PASS Array Blob object, Blob unpaired low surrogate (invalid utf-8)
+PASS Array Blob object, Blob paired surrogates (invalid utf-8)
+PASS Array Blob object, Blob empty
+PASS Array Blob object, Blob NUL
+PASS Array Blob object, two Blobs
+PASS Object Blob object, Blob basic
+PASS Object Blob object, Blob unpaired high surrogate (invalid utf-8)
+PASS Object Blob object, Blob unpaired low surrogate (invalid utf-8)
+PASS Object Blob object, Blob paired surrogates (invalid utf-8)
+PASS Object Blob object, Blob empty
+PASS Object Blob object, Blob NUL
+PASS File basic
+PASS Array sparse
+PASS Array with non-index property
+PASS Object with index property and length
+PASS Array with circular reference
+PASS Object with circular reference
+PASS Array with identical property values
+PASS Object with identical property values
+PASS Object with property on prototype
+PASS Object with non-enumerable property
+PASS Object with non-writable property
+PASS Object with non-configurable property
+PASS Object with a getter that throws
+PASS ObjectPrototype must lose its exotic-ness when cloned
+PASS Serializing a non-serializable platform object fails
+PASS An object whose interface is deleted from the global must still deserialize
+PASS A subclass instance will deserialize as its closest serializable superclass
+FAIL Resizable ArrayBuffer assert_true: expected true got undefined
+FAIL Growable SharedArrayBuffer assert_true: expected true got undefined
+FAIL Length-tracking TypedArray assert_true: expected true got undefined
+FAIL Length-tracking DataView assert_true: expected true got undefined
+FAIL Serializing OOB TypedArray throws promise_test: Unhandled rejection with value: object "TypeError: ab.resize is not a function"
+FAIL Serializing OOB DataView throws promise_test: Unhandled rejection with value: object "TypeError: ab.resize is not a function"
+PASS ArrayBuffer
+PASS MessagePort
+PASS A detached ArrayBuffer cannot be transferred
+PASS A detached platform object cannot be transferred
+PASS Transferring a non-transferable platform object fails
+PASS An object whose interface is deleted from the global object must still be received
+PASS A subclass instance will be received as its closest transferable superclass
+FAIL Resizable ArrayBuffer is transferable assert_equals: expected (number) 1024 but got (undefined) undefined
+FAIL Length-tracking TypedArray is transferable assert_equals: expected (number) 1024 but got (undefined) undefined
+FAIL Length-tracking DataView is transferable assert_equals: expected (number) 1024 but got (undefined) undefined
+FAIL Transferring OOB TypedArray throws promise_test: Unhandled rejection with value: object "TypeError: ab.resize is not a function"
+FAIL Transferring OOB DataView throws promise_test: Unhandled rejection with value: object "TypeError: ab.resize is not a function"
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/html/infrastructure/safe-passing-of-structured-data/messagechannel.any.sharedworker-expected.txt b/third_party/blink/web_tests/external/wpt/html/infrastructure/safe-passing-of-structured-data/messagechannel.any.sharedworker-expected.txt
new file mode 100644
index 0000000..acb3995
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/infrastructure/safe-passing-of-structured-data/messagechannel.any.sharedworker-expected.txt
@@ -0,0 +1,139 @@
+This is a testharness.js-based test.
+Found 135 tests; 124 PASS, 11 FAIL, 0 TIMEOUT, 0 NOTRUN.
+PASS primitive undefined
+PASS primitive null
+PASS primitive true
+PASS primitive false
+PASS primitive string, empty string
+PASS primitive string, lone high surrogate
+PASS primitive string, lone low surrogate
+PASS primitive string, NUL
+PASS primitive string, astral character
+PASS primitive number, 0.2
+PASS primitive number, 0
+PASS primitive number, -0
+PASS primitive number, NaN
+PASS primitive number, Infinity
+PASS primitive number, -Infinity
+PASS primitive number, 9007199254740992
+PASS primitive number, -9007199254740992
+PASS primitive number, 9007199254740994
+PASS primitive number, -9007199254740994
+PASS primitive BigInt, 0n
+PASS primitive BigInt, -0n
+PASS primitive BigInt, -9007199254740994000n
+PASS primitive BigInt, -9007199254740994000900719925474099400090071992547409940009007199254740994000n
+PASS Array primitives
+PASS Object primitives
+PASS Boolean true
+PASS Boolean false
+PASS Array Boolean objects
+PASS Object Boolean objects
+PASS String empty string
+PASS String lone high surrogate
+PASS String lone low surrogate
+PASS String NUL
+PASS String astral character
+PASS Array String objects
+PASS Object String objects
+PASS Number 0.2
+PASS Number 0
+PASS Number -0
+PASS Number NaN
+PASS Number Infinity
+PASS Number -Infinity
+PASS Number 9007199254740992
+PASS Number -9007199254740992
+PASS Number 9007199254740994
+PASS Number -9007199254740994
+PASS BigInt -9007199254740994n
+PASS Array Number objects
+PASS Object Number objects
+PASS Date 0
+PASS Date -0
+PASS Date -8.64e15
+PASS Date 8.64e15
+PASS Array Date objects
+PASS Object Date objects
+PASS RegExp flags and lastIndex
+PASS RegExp sticky flag
+PASS RegExp unicode flag
+PASS RegExp empty
+PASS RegExp slash
+PASS RegExp new line
+PASS Array RegExp object, RegExp flags and lastIndex
+PASS Array RegExp object, RegExp sticky flag
+PASS Array RegExp object, RegExp unicode flag
+PASS Array RegExp object, RegExp empty
+PASS Array RegExp object, RegExp slash
+PASS Array RegExp object, RegExp new line
+PASS Object RegExp object, RegExp flags and lastIndex
+PASS Object RegExp object, RegExp sticky flag
+PASS Object RegExp object, RegExp unicode flag
+PASS Object RegExp object, RegExp empty
+PASS Object RegExp object, RegExp slash
+PASS Object RegExp object, RegExp new line
+PASS Empty Error object
+PASS Error object
+PASS EvalError object
+PASS RangeError object
+PASS ReferenceError object
+PASS SyntaxError object
+PASS TypeError object
+PASS URIError object
+PASS Blob basic
+PASS Blob unpaired high surrogate (invalid utf-8)
+PASS Blob unpaired low surrogate (invalid utf-8)
+PASS Blob paired surrogates (invalid utf-8)
+PASS Blob empty
+PASS Blob NUL
+PASS Array Blob object, Blob basic
+PASS Array Blob object, Blob unpaired high surrogate (invalid utf-8)
+PASS Array Blob object, Blob unpaired low surrogate (invalid utf-8)
+PASS Array Blob object, Blob paired surrogates (invalid utf-8)
+PASS Array Blob object, Blob empty
+PASS Array Blob object, Blob NUL
+PASS Array Blob object, two Blobs
+PASS Object Blob object, Blob basic
+PASS Object Blob object, Blob unpaired high surrogate (invalid utf-8)
+PASS Object Blob object, Blob unpaired low surrogate (invalid utf-8)
+PASS Object Blob object, Blob paired surrogates (invalid utf-8)
+PASS Object Blob object, Blob empty
+PASS Object Blob object, Blob NUL
+PASS File basic
+PASS Array sparse
+PASS Array with non-index property
+PASS Object with index property and length
+PASS Array with circular reference
+PASS Object with circular reference
+PASS Array with identical property values
+PASS Object with identical property values
+PASS Object with property on prototype
+PASS Object with non-enumerable property
+PASS Object with non-writable property
+PASS Object with non-configurable property
+PASS Object with a getter that throws
+PASS ObjectPrototype must lose its exotic-ness when cloned
+PASS Serializing a non-serializable platform object fails
+PASS An object whose interface is deleted from the global must still deserialize
+PASS A subclass instance will deserialize as its closest serializable superclass
+FAIL Resizable ArrayBuffer assert_true: expected true got undefined
+FAIL Growable SharedArrayBuffer assert_true: expected true got undefined
+FAIL Length-tracking TypedArray assert_true: expected true got undefined
+FAIL Length-tracking DataView assert_true: expected true got undefined
+FAIL Serializing OOB TypedArray throws promise_test: Unhandled rejection with value: object "TypeError: ab.resize is not a function"
+FAIL Serializing OOB DataView throws promise_test: Unhandled rejection with value: object "TypeError: ab.resize is not a function"
+PASS ArrayBuffer
+PASS MessagePort
+PASS A detached ArrayBuffer cannot be transferred
+PASS A detached platform object cannot be transferred
+PASS Transferring a non-transferable platform object fails
+PASS An object whose interface is deleted from the global object must still be received
+PASS A subclass instance will be received as its closest transferable superclass
+FAIL Resizable ArrayBuffer is transferable assert_equals: expected (number) 1024 but got (undefined) undefined
+FAIL Length-tracking TypedArray is transferable assert_equals: expected (number) 1024 but got (undefined) undefined
+FAIL Length-tracking DataView is transferable assert_equals: expected (number) 1024 but got (undefined) undefined
+FAIL Transferring OOB TypedArray throws promise_test: Unhandled rejection with value: object "TypeError: ab.resize is not a function"
+FAIL Transferring OOB DataView throws promise_test: Unhandled rejection with value: object "TypeError: ab.resize is not a function"
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/html/infrastructure/safe-passing-of-structured-data/messagechannel.any.worker-expected.txt b/third_party/blink/web_tests/external/wpt/html/infrastructure/safe-passing-of-structured-data/messagechannel.any.worker-expected.txt
new file mode 100644
index 0000000..acb3995
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/infrastructure/safe-passing-of-structured-data/messagechannel.any.worker-expected.txt
@@ -0,0 +1,139 @@
+This is a testharness.js-based test.
+Found 135 tests; 124 PASS, 11 FAIL, 0 TIMEOUT, 0 NOTRUN.
+PASS primitive undefined
+PASS primitive null
+PASS primitive true
+PASS primitive false
+PASS primitive string, empty string
+PASS primitive string, lone high surrogate
+PASS primitive string, lone low surrogate
+PASS primitive string, NUL
+PASS primitive string, astral character
+PASS primitive number, 0.2
+PASS primitive number, 0
+PASS primitive number, -0
+PASS primitive number, NaN
+PASS primitive number, Infinity
+PASS primitive number, -Infinity
+PASS primitive number, 9007199254740992
+PASS primitive number, -9007199254740992
+PASS primitive number, 9007199254740994
+PASS primitive number, -9007199254740994
+PASS primitive BigInt, 0n
+PASS primitive BigInt, -0n
+PASS primitive BigInt, -9007199254740994000n
+PASS primitive BigInt, -9007199254740994000900719925474099400090071992547409940009007199254740994000n
+PASS Array primitives
+PASS Object primitives
+PASS Boolean true
+PASS Boolean false
+PASS Array Boolean objects
+PASS Object Boolean objects
+PASS String empty string
+PASS String lone high surrogate
+PASS String lone low surrogate
+PASS String NUL
+PASS String astral character
+PASS Array String objects
+PASS Object String objects
+PASS Number 0.2
+PASS Number 0
+PASS Number -0
+PASS Number NaN
+PASS Number Infinity
+PASS Number -Infinity
+PASS Number 9007199254740992
+PASS Number -9007199254740992
+PASS Number 9007199254740994
+PASS Number -9007199254740994
+PASS BigInt -9007199254740994n
+PASS Array Number objects
+PASS Object Number objects
+PASS Date 0
+PASS Date -0
+PASS Date -8.64e15
+PASS Date 8.64e15
+PASS Array Date objects
+PASS Object Date objects
+PASS RegExp flags and lastIndex
+PASS RegExp sticky flag
+PASS RegExp unicode flag
+PASS RegExp empty
+PASS RegExp slash
+PASS RegExp new line
+PASS Array RegExp object, RegExp flags and lastIndex
+PASS Array RegExp object, RegExp sticky flag
+PASS Array RegExp object, RegExp unicode flag
+PASS Array RegExp object, RegExp empty
+PASS Array RegExp object, RegExp slash
+PASS Array RegExp object, RegExp new line
+PASS Object RegExp object, RegExp flags and lastIndex
+PASS Object RegExp object, RegExp sticky flag
+PASS Object RegExp object, RegExp unicode flag
+PASS Object RegExp object, RegExp empty
+PASS Object RegExp object, RegExp slash
+PASS Object RegExp object, RegExp new line
+PASS Empty Error object
+PASS Error object
+PASS EvalError object
+PASS RangeError object
+PASS ReferenceError object
+PASS SyntaxError object
+PASS TypeError object
+PASS URIError object
+PASS Blob basic
+PASS Blob unpaired high surrogate (invalid utf-8)
+PASS Blob unpaired low surrogate (invalid utf-8)
+PASS Blob paired surrogates (invalid utf-8)
+PASS Blob empty
+PASS Blob NUL
+PASS Array Blob object, Blob basic
+PASS Array Blob object, Blob unpaired high surrogate (invalid utf-8)
+PASS Array Blob object, Blob unpaired low surrogate (invalid utf-8)
+PASS Array Blob object, Blob paired surrogates (invalid utf-8)
+PASS Array Blob object, Blob empty
+PASS Array Blob object, Blob NUL
+PASS Array Blob object, two Blobs
+PASS Object Blob object, Blob basic
+PASS Object Blob object, Blob unpaired high surrogate (invalid utf-8)
+PASS Object Blob object, Blob unpaired low surrogate (invalid utf-8)
+PASS Object Blob object, Blob paired surrogates (invalid utf-8)
+PASS Object Blob object, Blob empty
+PASS Object Blob object, Blob NUL
+PASS File basic
+PASS Array sparse
+PASS Array with non-index property
+PASS Object with index property and length
+PASS Array with circular reference
+PASS Object with circular reference
+PASS Array with identical property values
+PASS Object with identical property values
+PASS Object with property on prototype
+PASS Object with non-enumerable property
+PASS Object with non-writable property
+PASS Object with non-configurable property
+PASS Object with a getter that throws
+PASS ObjectPrototype must lose its exotic-ness when cloned
+PASS Serializing a non-serializable platform object fails
+PASS An object whose interface is deleted from the global must still deserialize
+PASS A subclass instance will deserialize as its closest serializable superclass
+FAIL Resizable ArrayBuffer assert_true: expected true got undefined
+FAIL Growable SharedArrayBuffer assert_true: expected true got undefined
+FAIL Length-tracking TypedArray assert_true: expected true got undefined
+FAIL Length-tracking DataView assert_true: expected true got undefined
+FAIL Serializing OOB TypedArray throws promise_test: Unhandled rejection with value: object "TypeError: ab.resize is not a function"
+FAIL Serializing OOB DataView throws promise_test: Unhandled rejection with value: object "TypeError: ab.resize is not a function"
+PASS ArrayBuffer
+PASS MessagePort
+PASS A detached ArrayBuffer cannot be transferred
+PASS A detached platform object cannot be transferred
+PASS Transferring a non-transferable platform object fails
+PASS An object whose interface is deleted from the global object must still be received
+PASS A subclass instance will be received as its closest transferable superclass
+FAIL Resizable ArrayBuffer is transferable assert_equals: expected (number) 1024 but got (undefined) undefined
+FAIL Length-tracking TypedArray is transferable assert_equals: expected (number) 1024 but got (undefined) undefined
+FAIL Length-tracking DataView is transferable assert_equals: expected (number) 1024 but got (undefined) undefined
+FAIL Transferring OOB TypedArray throws promise_test: Unhandled rejection with value: object "TypeError: ab.resize is not a function"
+FAIL Transferring OOB DataView throws promise_test: Unhandled rejection with value: object "TypeError: ab.resize is not a function"
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/html/infrastructure/safe-passing-of-structured-data/window-postmessage.window-expected.txt b/third_party/blink/web_tests/external/wpt/html/infrastructure/safe-passing-of-structured-data/window-postmessage.window-expected.txt
new file mode 100644
index 0000000..09b51b19
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/infrastructure/safe-passing-of-structured-data/window-postmessage.window-expected.txt
@@ -0,0 +1,154 @@
+This is a testharness.js-based test.
+Found 150 tests; 139 PASS, 11 FAIL, 0 TIMEOUT, 0 NOTRUN.
+PASS primitive undefined
+PASS primitive null
+PASS primitive true
+PASS primitive false
+PASS primitive string, empty string
+PASS primitive string, lone high surrogate
+PASS primitive string, lone low surrogate
+PASS primitive string, NUL
+PASS primitive string, astral character
+PASS primitive number, 0.2
+PASS primitive number, 0
+PASS primitive number, -0
+PASS primitive number, NaN
+PASS primitive number, Infinity
+PASS primitive number, -Infinity
+PASS primitive number, 9007199254740992
+PASS primitive number, -9007199254740992
+PASS primitive number, 9007199254740994
+PASS primitive number, -9007199254740994
+PASS primitive BigInt, 0n
+PASS primitive BigInt, -0n
+PASS primitive BigInt, -9007199254740994000n
+PASS primitive BigInt, -9007199254740994000900719925474099400090071992547409940009007199254740994000n
+PASS Array primitives
+PASS Object primitives
+PASS Boolean true
+PASS Boolean false
+PASS Array Boolean objects
+PASS Object Boolean objects
+PASS String empty string
+PASS String lone high surrogate
+PASS String lone low surrogate
+PASS String NUL
+PASS String astral character
+PASS Array String objects
+PASS Object String objects
+PASS Number 0.2
+PASS Number 0
+PASS Number -0
+PASS Number NaN
+PASS Number Infinity
+PASS Number -Infinity
+PASS Number 9007199254740992
+PASS Number -9007199254740992
+PASS Number 9007199254740994
+PASS Number -9007199254740994
+PASS BigInt -9007199254740994n
+PASS Array Number objects
+PASS Object Number objects
+PASS Date 0
+PASS Date -0
+PASS Date -8.64e15
+PASS Date 8.64e15
+PASS Array Date objects
+PASS Object Date objects
+PASS RegExp flags and lastIndex
+PASS RegExp sticky flag
+PASS RegExp unicode flag
+PASS RegExp empty
+PASS RegExp slash
+PASS RegExp new line
+PASS Array RegExp object, RegExp flags and lastIndex
+PASS Array RegExp object, RegExp sticky flag
+PASS Array RegExp object, RegExp unicode flag
+PASS Array RegExp object, RegExp empty
+PASS Array RegExp object, RegExp slash
+PASS Array RegExp object, RegExp new line
+PASS Object RegExp object, RegExp flags and lastIndex
+PASS Object RegExp object, RegExp sticky flag
+PASS Object RegExp object, RegExp unicode flag
+PASS Object RegExp object, RegExp empty
+PASS Object RegExp object, RegExp slash
+PASS Object RegExp object, RegExp new line
+PASS Empty Error object
+PASS Error object
+PASS EvalError object
+PASS RangeError object
+PASS ReferenceError object
+PASS SyntaxError object
+PASS TypeError object
+PASS URIError object
+PASS Blob basic
+PASS Blob unpaired high surrogate (invalid utf-8)
+PASS Blob unpaired low surrogate (invalid utf-8)
+PASS Blob paired surrogates (invalid utf-8)
+PASS Blob empty
+PASS Blob NUL
+PASS Array Blob object, Blob basic
+PASS Array Blob object, Blob unpaired high surrogate (invalid utf-8)
+PASS Array Blob object, Blob unpaired low surrogate (invalid utf-8)
+PASS Array Blob object, Blob paired surrogates (invalid utf-8)
+PASS Array Blob object, Blob empty
+PASS Array Blob object, Blob NUL
+PASS Array Blob object, two Blobs
+PASS Object Blob object, Blob basic
+PASS Object Blob object, Blob unpaired high surrogate (invalid utf-8)
+PASS Object Blob object, Blob unpaired low surrogate (invalid utf-8)
+PASS Object Blob object, Blob paired surrogates (invalid utf-8)
+PASS Object Blob object, Blob empty
+PASS Object Blob object, Blob NUL
+PASS File basic
+PASS FileList empty
+PASS Array FileList object, FileList empty
+PASS Object FileList object, FileList empty
+PASS ImageData 1x1 transparent black
+PASS ImageData 1x1 non-transparent non-black
+PASS Array ImageData object, ImageData 1x1 transparent black
+PASS Array ImageData object, ImageData 1x1 non-transparent non-black
+PASS Object ImageData object, ImageData 1x1 transparent black
+PASS Object ImageData object, ImageData 1x1 non-transparent non-black
+PASS Array sparse
+PASS Array with non-index property
+PASS Object with index property and length
+PASS Array with circular reference
+PASS Object with circular reference
+PASS Array with identical property values
+PASS Object with identical property values
+PASS Object with property on prototype
+PASS Object with non-enumerable property
+PASS Object with non-writable property
+PASS Object with non-configurable property
+PASS Object with a getter that throws
+PASS ImageBitmap 1x1 transparent black
+PASS ImageBitmap 1x1 non-transparent non-black
+PASS Array ImageBitmap object, ImageBitmap 1x1 transparent black
+PASS Array ImageBitmap object, ImageBitmap 1x1 transparent non-black
+PASS Object ImageBitmap object, ImageBitmap 1x1 transparent black
+PASS Object ImageBitmap object, ImageBitmap 1x1 transparent non-black
+PASS ObjectPrototype must lose its exotic-ness when cloned
+PASS Serializing a non-serializable platform object fails
+PASS An object whose interface is deleted from the global must still deserialize
+PASS A subclass instance will deserialize as its closest serializable superclass
+FAIL Resizable ArrayBuffer assert_true: expected true got undefined
+FAIL Growable SharedArrayBuffer assert_true: expected true got undefined
+FAIL Length-tracking TypedArray assert_true: expected true got undefined
+FAIL Length-tracking DataView assert_true: expected true got undefined
+FAIL Serializing OOB TypedArray throws promise_test: Unhandled rejection with value: object "TypeError: ab.resize is not a function"
+FAIL Serializing OOB DataView throws promise_test: Unhandled rejection with value: object "TypeError: ab.resize is not a function"
+PASS ArrayBuffer
+PASS MessagePort
+PASS A detached ArrayBuffer cannot be transferred
+PASS A detached platform object cannot be transferred
+PASS Transferring a non-transferable platform object fails
+PASS An object whose interface is deleted from the global object must still be received
+PASS A subclass instance will be received as its closest transferable superclass
+FAIL Resizable ArrayBuffer is transferable assert_equals: expected (number) 1024 but got (undefined) undefined
+FAIL Length-tracking TypedArray is transferable assert_equals: expected (number) 1024 but got (undefined) undefined
+FAIL Length-tracking DataView is transferable assert_equals: expected (number) 1024 but got (undefined) undefined
+FAIL Transferring OOB TypedArray throws promise_test: Unhandled rejection with value: object "TypeError: ab.resize is not a function"
+FAIL Transferring OOB DataView throws promise_test: Unhandled rejection with value: object "TypeError: ab.resize is not a function"
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/html/infrastructure/safe-passing-of-structured-data/window-postmessage.window.js b/third_party/blink/web_tests/external/wpt/html/infrastructure/safe-passing-of-structured-data/window-postmessage.window.js
index ebbda99..2a46d79 100644
--- a/third_party/blink/web_tests/external/wpt/html/infrastructure/safe-passing-of-structured-data/window-postmessage.window.js
+++ b/third_party/blink/web_tests/external/wpt/html/infrastructure/safe-passing-of-structured-data/window-postmessage.window.js
@@ -1,3 +1,4 @@
+// META: script=/common/sab.js
 // META: script=/html/webappapis/structured-clone/structured-clone-battery-of-tests.js
 // META: script=/html/webappapis/structured-clone/structured-clone-battery-of-tests-with-transferables.js
 // META: script=/html/webappapis/structured-clone/structured-clone-battery-of-tests-harness.js
diff --git a/third_party/blink/web_tests/external/wpt/html/webappapis/structured-clone/structured-clone-battery-of-tests-with-transferables.js b/third_party/blink/web_tests/external/wpt/html/webappapis/structured-clone/structured-clone-battery-of-tests-with-transferables.js
index 65e4085..23cf4f6 100644
--- a/third_party/blink/web_tests/external/wpt/html/webappapis/structured-clone/structured-clone-battery-of-tests-with-transferables.js
+++ b/third_party/blink/web_tests/external/wpt/html/webappapis/structured-clone/structured-clone-battery-of-tests-with-transferables.js
@@ -97,3 +97,73 @@
     assert_equals(Object.getPrototypeOf(transfer), ReadableStream.prototype);
   }
 });
+
+structuredCloneBatteryOfTests.push({
+  description: 'Resizable ArrayBuffer is transferable',
+  async f(runner) {
+    const buffer = new ArrayBuffer(16, { maxByteLength: 1024 });
+    const copy = await runner.structuredClone(buffer, [buffer]);
+    assert_equals(buffer.byteLength, 0);
+    assert_equals(copy.byteLength, 16);
+    assert_equals(copy.maxByteLength, 1024);
+    assert_true(copy.resizable);
+  }
+});
+
+structuredCloneBatteryOfTests.push({
+  description: 'Length-tracking TypedArray is transferable',
+  async f(runner) {
+    const ab = new ArrayBuffer(16, { maxByteLength: 1024 });
+    const ta = new Uint8Array(ab);
+    const copy = await runner.structuredClone(ta, [ab]);
+    assert_equals(ab.byteLength, 0);
+    assert_equals(copy.buffer.byteLength, 16);
+    assert_equals(copy.buffer.maxByteLength, 1024);
+    assert_true(copy.buffer.resizable);
+    copy.buffer.resize(32);
+    assert_equals(copy.byteLength, 32);
+  }
+});
+
+structuredCloneBatteryOfTests.push({
+  description: 'Length-tracking DataView is transferable',
+  async f(runner) {
+    const ab = new ArrayBuffer(16, { maxByteLength: 1024 });
+    const dv = new DataView(ab);
+    const copy = await runner.structuredClone(dv, [ab]);
+    assert_equals(ab.byteLength, 0);
+    assert_equals(copy.buffer.byteLength, 16);
+    assert_equals(copy.buffer.maxByteLength, 1024);
+    assert_true(copy.buffer.resizable);
+    copy.buffer.resize(32);
+    assert_equals(copy.byteLength, 32);
+  }
+});
+
+structuredCloneBatteryOfTests.push({
+  description: 'Transferring OOB TypedArray throws',
+  async f(runner, t) {
+    const ab = new ArrayBuffer(16, { maxByteLength: 1024 });
+    const ta = new Uint8Array(ab, 8);
+    ab.resize(0);
+    await promise_rejects_dom(
+      t,
+      "DataCloneError",
+      runner.structuredClone(ta, [ab])
+    );
+  }
+});
+
+structuredCloneBatteryOfTests.push({
+  description: 'Transferring OOB DataView throws',
+  async f(runner, t) {
+    const ab = new ArrayBuffer(16, { maxByteLength: 1024 });
+    const dv = new DataView(ab, 8);
+    ab.resize(0);
+    await promise_rejects_dom(
+      t,
+      "DataCloneError",
+      runner.structuredClone(dv, [ab])
+    );
+  }
+});
diff --git a/third_party/blink/web_tests/external/wpt/html/webappapis/structured-clone/structured-clone-battery-of-tests.js b/third_party/blink/web_tests/external/wpt/html/webappapis/structured-clone/structured-clone-battery-of-tests.js
index 580a81a..923ac9d 100644
--- a/third_party/blink/web_tests/external/wpt/html/webappapis/structured-clone/structured-clone-battery-of-tests.js
+++ b/third_party/blink/web_tests/external/wpt/html/webappapis/structured-clone/structured-clone-battery-of-tests.js
@@ -379,6 +379,14 @@
 check('Array FileList object, FileList empty', () => ([func_FileList_empty()]), compare_Array(enumerate_props(compare_FileList)), true);
 check('Object FileList object, FileList empty', () => ({'x':func_FileList_empty()}), compare_Object(enumerate_props(compare_FileList)), true);
 
+function compare_ArrayBuffer(actual, input) {
+  assert_true(actual instanceof ArrayBuffer, 'instanceof ArrayBuffer');
+  assert_equals(actual.byteLength, input.byteLength, 'byteLength');
+  assert_equals(actual.maxByteLength, input.maxByteLength, 'maxByteLength');
+  assert_equals(actual.resizable, input.resizable, 'resizable');
+  assert_equals(actual.growable, input.growable, 'growable');
+}
+
 function compare_ArrayBufferView(view) {
   const Type = self[view];
   return function(actual, input) {
@@ -386,6 +394,8 @@
       assert_unreached(actual);
     assert_true(actual instanceof Type, 'instanceof '+view);
     assert_equals(actual.length, input.length, 'length');
+    assert_equals(actual.byteLength, input.byteLength, 'byteLength');
+    assert_equals(actual.byteOffset, input.byteOffset, 'byteOffset');
     assert_not_equals(actual.buffer, input.buffer, 'buffer');
     for (let i = 0; i < actual.length; ++i) {
       assert_equals(actual[i], input[i], 'actual['+i+']');
@@ -667,3 +677,77 @@
     assert_equals(Object.getPrototypeOf(copy), File.prototype);
   }
 );
+
+check(
+  'Resizable ArrayBuffer',
+  () => {
+    const ab = new ArrayBuffer(16, { maxByteLength: 1024 });
+    assert_true(ab.resizable);
+    return ab;
+  },
+  compare_ArrayBuffer);
+
+structuredCloneBatteryOfTests.push({
+  description: 'Growable SharedArrayBuffer',
+  async f(runner) {
+    const sab = createBuffer('SharedArrayBuffer', 16, { maxByteLength: 1024 });
+    assert_true(sab.growable);
+    try {
+      const copy = await runner.structuredClone(sab);
+      compare_ArrayBuffer(sab, copy);
+    } catch (e) {
+      // If we're cross-origin isolated, cloning SABs should not fail.
+      if (e instanceof DOMException && e.code === DOMException.DATA_CLONE_ERR) {
+        assert_false(self.crossOriginIsolated);
+      } else {
+        throw e;
+      }
+    }
+  }
+});
+
+check(
+  'Length-tracking TypedArray',
+  () => {
+    const ab = new ArrayBuffer(16, { maxByteLength: 1024 });
+    assert_true(ab.resizable);
+    return new Uint8Array(ab);
+  },
+  compare_ArrayBufferView('Uint8Array'));
+
+check(
+  'Length-tracking DataView',
+  () => {
+    const ab = new ArrayBuffer(16, { maxByteLength: 1024 });
+    assert_true(ab.resizable);
+    return new DataView(ab);
+  },
+  compare_ArrayBufferView('DataView'));
+
+structuredCloneBatteryOfTests.push({
+  description: 'Serializing OOB TypedArray throws',
+  async f(runner, t) {
+    const ab = new ArrayBuffer(16, { maxByteLength: 1024 });
+    const ta = new Uint8Array(ab, 8);
+    ab.resize(0);
+    await promise_rejects_dom(
+      t,
+      "DataCloneError",
+      runner.structuredClone(ta)
+    );
+  }
+});
+
+structuredCloneBatteryOfTests.push({
+  description: 'Serializing OOB DataView throws',
+  async f(runner, t) {
+    const ab = new ArrayBuffer(16, { maxByteLength: 1024 });
+    const dv = new DataView(ab, 8);
+    ab.resize(0);
+    await promise_rejects_dom(
+      t,
+      "DataCloneError",
+      runner.structuredClone(dv)
+    );
+  }
+});
diff --git a/third_party/blink/web_tests/external/wpt/html/webappapis/structured-clone/structured-clone.any-expected.txt b/third_party/blink/web_tests/external/wpt/html/webappapis/structured-clone/structured-clone.any-expected.txt
new file mode 100644
index 0000000..09b51b19
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/webappapis/structured-clone/structured-clone.any-expected.txt
@@ -0,0 +1,154 @@
+This is a testharness.js-based test.
+Found 150 tests; 139 PASS, 11 FAIL, 0 TIMEOUT, 0 NOTRUN.
+PASS primitive undefined
+PASS primitive null
+PASS primitive true
+PASS primitive false
+PASS primitive string, empty string
+PASS primitive string, lone high surrogate
+PASS primitive string, lone low surrogate
+PASS primitive string, NUL
+PASS primitive string, astral character
+PASS primitive number, 0.2
+PASS primitive number, 0
+PASS primitive number, -0
+PASS primitive number, NaN
+PASS primitive number, Infinity
+PASS primitive number, -Infinity
+PASS primitive number, 9007199254740992
+PASS primitive number, -9007199254740992
+PASS primitive number, 9007199254740994
+PASS primitive number, -9007199254740994
+PASS primitive BigInt, 0n
+PASS primitive BigInt, -0n
+PASS primitive BigInt, -9007199254740994000n
+PASS primitive BigInt, -9007199254740994000900719925474099400090071992547409940009007199254740994000n
+PASS Array primitives
+PASS Object primitives
+PASS Boolean true
+PASS Boolean false
+PASS Array Boolean objects
+PASS Object Boolean objects
+PASS String empty string
+PASS String lone high surrogate
+PASS String lone low surrogate
+PASS String NUL
+PASS String astral character
+PASS Array String objects
+PASS Object String objects
+PASS Number 0.2
+PASS Number 0
+PASS Number -0
+PASS Number NaN
+PASS Number Infinity
+PASS Number -Infinity
+PASS Number 9007199254740992
+PASS Number -9007199254740992
+PASS Number 9007199254740994
+PASS Number -9007199254740994
+PASS BigInt -9007199254740994n
+PASS Array Number objects
+PASS Object Number objects
+PASS Date 0
+PASS Date -0
+PASS Date -8.64e15
+PASS Date 8.64e15
+PASS Array Date objects
+PASS Object Date objects
+PASS RegExp flags and lastIndex
+PASS RegExp sticky flag
+PASS RegExp unicode flag
+PASS RegExp empty
+PASS RegExp slash
+PASS RegExp new line
+PASS Array RegExp object, RegExp flags and lastIndex
+PASS Array RegExp object, RegExp sticky flag
+PASS Array RegExp object, RegExp unicode flag
+PASS Array RegExp object, RegExp empty
+PASS Array RegExp object, RegExp slash
+PASS Array RegExp object, RegExp new line
+PASS Object RegExp object, RegExp flags and lastIndex
+PASS Object RegExp object, RegExp sticky flag
+PASS Object RegExp object, RegExp unicode flag
+PASS Object RegExp object, RegExp empty
+PASS Object RegExp object, RegExp slash
+PASS Object RegExp object, RegExp new line
+PASS Empty Error object
+PASS Error object
+PASS EvalError object
+PASS RangeError object
+PASS ReferenceError object
+PASS SyntaxError object
+PASS TypeError object
+PASS URIError object
+PASS Blob basic
+PASS Blob unpaired high surrogate (invalid utf-8)
+PASS Blob unpaired low surrogate (invalid utf-8)
+PASS Blob paired surrogates (invalid utf-8)
+PASS Blob empty
+PASS Blob NUL
+PASS Array Blob object, Blob basic
+PASS Array Blob object, Blob unpaired high surrogate (invalid utf-8)
+PASS Array Blob object, Blob unpaired low surrogate (invalid utf-8)
+PASS Array Blob object, Blob paired surrogates (invalid utf-8)
+PASS Array Blob object, Blob empty
+PASS Array Blob object, Blob NUL
+PASS Array Blob object, two Blobs
+PASS Object Blob object, Blob basic
+PASS Object Blob object, Blob unpaired high surrogate (invalid utf-8)
+PASS Object Blob object, Blob unpaired low surrogate (invalid utf-8)
+PASS Object Blob object, Blob paired surrogates (invalid utf-8)
+PASS Object Blob object, Blob empty
+PASS Object Blob object, Blob NUL
+PASS File basic
+PASS FileList empty
+PASS Array FileList object, FileList empty
+PASS Object FileList object, FileList empty
+PASS ImageData 1x1 transparent black
+PASS ImageData 1x1 non-transparent non-black
+PASS Array ImageData object, ImageData 1x1 transparent black
+PASS Array ImageData object, ImageData 1x1 non-transparent non-black
+PASS Object ImageData object, ImageData 1x1 transparent black
+PASS Object ImageData object, ImageData 1x1 non-transparent non-black
+PASS Array sparse
+PASS Array with non-index property
+PASS Object with index property and length
+PASS Array with circular reference
+PASS Object with circular reference
+PASS Array with identical property values
+PASS Object with identical property values
+PASS Object with property on prototype
+PASS Object with non-enumerable property
+PASS Object with non-writable property
+PASS Object with non-configurable property
+PASS Object with a getter that throws
+PASS ImageBitmap 1x1 transparent black
+PASS ImageBitmap 1x1 non-transparent non-black
+PASS Array ImageBitmap object, ImageBitmap 1x1 transparent black
+PASS Array ImageBitmap object, ImageBitmap 1x1 transparent non-black
+PASS Object ImageBitmap object, ImageBitmap 1x1 transparent black
+PASS Object ImageBitmap object, ImageBitmap 1x1 transparent non-black
+PASS ObjectPrototype must lose its exotic-ness when cloned
+PASS Serializing a non-serializable platform object fails
+PASS An object whose interface is deleted from the global must still deserialize
+PASS A subclass instance will deserialize as its closest serializable superclass
+FAIL Resizable ArrayBuffer assert_true: expected true got undefined
+FAIL Growable SharedArrayBuffer assert_true: expected true got undefined
+FAIL Length-tracking TypedArray assert_true: expected true got undefined
+FAIL Length-tracking DataView assert_true: expected true got undefined
+FAIL Serializing OOB TypedArray throws promise_test: Unhandled rejection with value: object "TypeError: ab.resize is not a function"
+FAIL Serializing OOB DataView throws promise_test: Unhandled rejection with value: object "TypeError: ab.resize is not a function"
+PASS ArrayBuffer
+PASS MessagePort
+PASS A detached ArrayBuffer cannot be transferred
+PASS A detached platform object cannot be transferred
+PASS Transferring a non-transferable platform object fails
+PASS An object whose interface is deleted from the global object must still be received
+PASS A subclass instance will be received as its closest transferable superclass
+FAIL Resizable ArrayBuffer is transferable assert_equals: expected (number) 1024 but got (undefined) undefined
+FAIL Length-tracking TypedArray is transferable assert_equals: expected (number) 1024 but got (undefined) undefined
+FAIL Length-tracking DataView is transferable assert_equals: expected (number) 1024 but got (undefined) undefined
+FAIL Transferring OOB TypedArray throws promise_test: Unhandled rejection with value: object "TypeError: ab.resize is not a function"
+FAIL Transferring OOB DataView throws promise_test: Unhandled rejection with value: object "TypeError: ab.resize is not a function"
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/html/webappapis/structured-clone/structured-clone.any.js b/third_party/blink/web_tests/external/wpt/html/webappapis/structured-clone/structured-clone.any.js
index 90ba5df..1358a71 100644
--- a/third_party/blink/web_tests/external/wpt/html/webappapis/structured-clone/structured-clone.any.js
+++ b/third_party/blink/web_tests/external/wpt/html/webappapis/structured-clone/structured-clone.any.js
@@ -1,4 +1,5 @@
 // META: title=structuredClone() tests
+// META: script=/common/sab.js
 // META: script=/html/webappapis/structured-clone/structured-clone-battery-of-tests.js
 // META: script=/html/webappapis/structured-clone/structured-clone-battery-of-tests-with-transferables.js
 // META: script=/html/webappapis/structured-clone/structured-clone-battery-of-tests-harness.js
diff --git a/third_party/blink/web_tests/external/wpt/html/webappapis/structured-clone/structured-clone.any.worker-expected.txt b/third_party/blink/web_tests/external/wpt/html/webappapis/structured-clone/structured-clone.any.worker-expected.txt
new file mode 100644
index 0000000..acb3995
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/webappapis/structured-clone/structured-clone.any.worker-expected.txt
@@ -0,0 +1,139 @@
+This is a testharness.js-based test.
+Found 135 tests; 124 PASS, 11 FAIL, 0 TIMEOUT, 0 NOTRUN.
+PASS primitive undefined
+PASS primitive null
+PASS primitive true
+PASS primitive false
+PASS primitive string, empty string
+PASS primitive string, lone high surrogate
+PASS primitive string, lone low surrogate
+PASS primitive string, NUL
+PASS primitive string, astral character
+PASS primitive number, 0.2
+PASS primitive number, 0
+PASS primitive number, -0
+PASS primitive number, NaN
+PASS primitive number, Infinity
+PASS primitive number, -Infinity
+PASS primitive number, 9007199254740992
+PASS primitive number, -9007199254740992
+PASS primitive number, 9007199254740994
+PASS primitive number, -9007199254740994
+PASS primitive BigInt, 0n
+PASS primitive BigInt, -0n
+PASS primitive BigInt, -9007199254740994000n
+PASS primitive BigInt, -9007199254740994000900719925474099400090071992547409940009007199254740994000n
+PASS Array primitives
+PASS Object primitives
+PASS Boolean true
+PASS Boolean false
+PASS Array Boolean objects
+PASS Object Boolean objects
+PASS String empty string
+PASS String lone high surrogate
+PASS String lone low surrogate
+PASS String NUL
+PASS String astral character
+PASS Array String objects
+PASS Object String objects
+PASS Number 0.2
+PASS Number 0
+PASS Number -0
+PASS Number NaN
+PASS Number Infinity
+PASS Number -Infinity
+PASS Number 9007199254740992
+PASS Number -9007199254740992
+PASS Number 9007199254740994
+PASS Number -9007199254740994
+PASS BigInt -9007199254740994n
+PASS Array Number objects
+PASS Object Number objects
+PASS Date 0
+PASS Date -0
+PASS Date -8.64e15
+PASS Date 8.64e15
+PASS Array Date objects
+PASS Object Date objects
+PASS RegExp flags and lastIndex
+PASS RegExp sticky flag
+PASS RegExp unicode flag
+PASS RegExp empty
+PASS RegExp slash
+PASS RegExp new line
+PASS Array RegExp object, RegExp flags and lastIndex
+PASS Array RegExp object, RegExp sticky flag
+PASS Array RegExp object, RegExp unicode flag
+PASS Array RegExp object, RegExp empty
+PASS Array RegExp object, RegExp slash
+PASS Array RegExp object, RegExp new line
+PASS Object RegExp object, RegExp flags and lastIndex
+PASS Object RegExp object, RegExp sticky flag
+PASS Object RegExp object, RegExp unicode flag
+PASS Object RegExp object, RegExp empty
+PASS Object RegExp object, RegExp slash
+PASS Object RegExp object, RegExp new line
+PASS Empty Error object
+PASS Error object
+PASS EvalError object
+PASS RangeError object
+PASS ReferenceError object
+PASS SyntaxError object
+PASS TypeError object
+PASS URIError object
+PASS Blob basic
+PASS Blob unpaired high surrogate (invalid utf-8)
+PASS Blob unpaired low surrogate (invalid utf-8)
+PASS Blob paired surrogates (invalid utf-8)
+PASS Blob empty
+PASS Blob NUL
+PASS Array Blob object, Blob basic
+PASS Array Blob object, Blob unpaired high surrogate (invalid utf-8)
+PASS Array Blob object, Blob unpaired low surrogate (invalid utf-8)
+PASS Array Blob object, Blob paired surrogates (invalid utf-8)
+PASS Array Blob object, Blob empty
+PASS Array Blob object, Blob NUL
+PASS Array Blob object, two Blobs
+PASS Object Blob object, Blob basic
+PASS Object Blob object, Blob unpaired high surrogate (invalid utf-8)
+PASS Object Blob object, Blob unpaired low surrogate (invalid utf-8)
+PASS Object Blob object, Blob paired surrogates (invalid utf-8)
+PASS Object Blob object, Blob empty
+PASS Object Blob object, Blob NUL
+PASS File basic
+PASS Array sparse
+PASS Array with non-index property
+PASS Object with index property and length
+PASS Array with circular reference
+PASS Object with circular reference
+PASS Array with identical property values
+PASS Object with identical property values
+PASS Object with property on prototype
+PASS Object with non-enumerable property
+PASS Object with non-writable property
+PASS Object with non-configurable property
+PASS Object with a getter that throws
+PASS ObjectPrototype must lose its exotic-ness when cloned
+PASS Serializing a non-serializable platform object fails
+PASS An object whose interface is deleted from the global must still deserialize
+PASS A subclass instance will deserialize as its closest serializable superclass
+FAIL Resizable ArrayBuffer assert_true: expected true got undefined
+FAIL Growable SharedArrayBuffer assert_true: expected true got undefined
+FAIL Length-tracking TypedArray assert_true: expected true got undefined
+FAIL Length-tracking DataView assert_true: expected true got undefined
+FAIL Serializing OOB TypedArray throws promise_test: Unhandled rejection with value: object "TypeError: ab.resize is not a function"
+FAIL Serializing OOB DataView throws promise_test: Unhandled rejection with value: object "TypeError: ab.resize is not a function"
+PASS ArrayBuffer
+PASS MessagePort
+PASS A detached ArrayBuffer cannot be transferred
+PASS A detached platform object cannot be transferred
+PASS Transferring a non-transferable platform object fails
+PASS An object whose interface is deleted from the global object must still be received
+PASS A subclass instance will be received as its closest transferable superclass
+FAIL Resizable ArrayBuffer is transferable assert_equals: expected (number) 1024 but got (undefined) undefined
+FAIL Length-tracking TypedArray is transferable assert_equals: expected (number) 1024 but got (undefined) undefined
+FAIL Length-tracking DataView is transferable assert_equals: expected (number) 1024 but got (undefined) undefined
+FAIL Transferring OOB TypedArray throws promise_test: Unhandled rejection with value: object "TypeError: ab.resize is not a function"
+FAIL Transferring OOB DataView throws promise_test: Unhandled rejection with value: object "TypeError: ab.resize is not a function"
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/webidl/ecmascript-binding/allow-resizable-expected.txt b/third_party/blink/web_tests/external/wpt/webidl/ecmascript-binding/allow-resizable-expected.txt
new file mode 100644
index 0000000..a3f42be
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/webidl/ecmascript-binding/allow-resizable-expected.txt
@@ -0,0 +1,7 @@
+This is a testharness.js-based test.
+FAIL APIs without [AllowResizable] throw when passed resizable ArrayBuffers assert_throws_js: function "() => {
+    new Response(new Uint8Array(rab));
+  }" did not throw
+FAIL APIs with [AllowShared] but without [AllowResizable] throw when passed growable SharedArrayBuffers Failed to execute 'encodeInto' on 'TextEncoder': The provided Uint8Array value must not be shared.
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/webidl/ecmascript-binding/allow-resizable.html b/third_party/blink/web_tests/external/wpt/webidl/ecmascript-binding/allow-resizable.html
new file mode 100644
index 0000000..be9df55
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/webidl/ecmascript-binding/allow-resizable.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/sab.js"></script>
+<script type="module">
+test(t => {
+  // Fixed-length ABs should not throw
+  const ab = new ArrayBuffer(16);
+  new Response(new Uint8Array(ab));
+
+  const rab = new ArrayBuffer(16, { maxByteLength: 1024 });
+  // Response doesn't have [AllowResizable] or [AllowShared]
+  assert_throws_js(TypeError, () => {
+    new Response(new Uint8Array(rab));
+  });
+}, "APIs without [AllowResizable] throw when passed resizable ArrayBuffers");
+
+test(t => {
+  const enc = new TextEncoder();
+
+  // Fixed-length SABs should not throw
+  const sab = createBuffer('SharedArrayBuffer', 16, { maxByteLength: 1024 });
+  enc.encodeInto("foobar", new Uint8Array(sab));
+
+  const gsab = createBuffer('SharedArrayBuffer', 16, { maxByteLength: 1024 });
+  // TextEncoder.encodeInto doesn't have [AllowResizable] but has [AllowShared]
+  assert_throws_js(TypeError, () => {
+    enc.encodeInto("foobar", new Uint8Array(gsab));
+  });
+}, "APIs with [AllowShared] but without [AllowResizable] throw when passed growable SharedArrayBuffers");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/workers/semantics/structured-clone/dedicated-expected.txt b/third_party/blink/web_tests/external/wpt/workers/semantics/structured-clone/dedicated-expected.txt
new file mode 100644
index 0000000..09b51b19
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/workers/semantics/structured-clone/dedicated-expected.txt
@@ -0,0 +1,154 @@
+This is a testharness.js-based test.
+Found 150 tests; 139 PASS, 11 FAIL, 0 TIMEOUT, 0 NOTRUN.
+PASS primitive undefined
+PASS primitive null
+PASS primitive true
+PASS primitive false
+PASS primitive string, empty string
+PASS primitive string, lone high surrogate
+PASS primitive string, lone low surrogate
+PASS primitive string, NUL
+PASS primitive string, astral character
+PASS primitive number, 0.2
+PASS primitive number, 0
+PASS primitive number, -0
+PASS primitive number, NaN
+PASS primitive number, Infinity
+PASS primitive number, -Infinity
+PASS primitive number, 9007199254740992
+PASS primitive number, -9007199254740992
+PASS primitive number, 9007199254740994
+PASS primitive number, -9007199254740994
+PASS primitive BigInt, 0n
+PASS primitive BigInt, -0n
+PASS primitive BigInt, -9007199254740994000n
+PASS primitive BigInt, -9007199254740994000900719925474099400090071992547409940009007199254740994000n
+PASS Array primitives
+PASS Object primitives
+PASS Boolean true
+PASS Boolean false
+PASS Array Boolean objects
+PASS Object Boolean objects
+PASS String empty string
+PASS String lone high surrogate
+PASS String lone low surrogate
+PASS String NUL
+PASS String astral character
+PASS Array String objects
+PASS Object String objects
+PASS Number 0.2
+PASS Number 0
+PASS Number -0
+PASS Number NaN
+PASS Number Infinity
+PASS Number -Infinity
+PASS Number 9007199254740992
+PASS Number -9007199254740992
+PASS Number 9007199254740994
+PASS Number -9007199254740994
+PASS BigInt -9007199254740994n
+PASS Array Number objects
+PASS Object Number objects
+PASS Date 0
+PASS Date -0
+PASS Date -8.64e15
+PASS Date 8.64e15
+PASS Array Date objects
+PASS Object Date objects
+PASS RegExp flags and lastIndex
+PASS RegExp sticky flag
+PASS RegExp unicode flag
+PASS RegExp empty
+PASS RegExp slash
+PASS RegExp new line
+PASS Array RegExp object, RegExp flags and lastIndex
+PASS Array RegExp object, RegExp sticky flag
+PASS Array RegExp object, RegExp unicode flag
+PASS Array RegExp object, RegExp empty
+PASS Array RegExp object, RegExp slash
+PASS Array RegExp object, RegExp new line
+PASS Object RegExp object, RegExp flags and lastIndex
+PASS Object RegExp object, RegExp sticky flag
+PASS Object RegExp object, RegExp unicode flag
+PASS Object RegExp object, RegExp empty
+PASS Object RegExp object, RegExp slash
+PASS Object RegExp object, RegExp new line
+PASS Empty Error object
+PASS Error object
+PASS EvalError object
+PASS RangeError object
+PASS ReferenceError object
+PASS SyntaxError object
+PASS TypeError object
+PASS URIError object
+PASS Blob basic
+PASS Blob unpaired high surrogate (invalid utf-8)
+PASS Blob unpaired low surrogate (invalid utf-8)
+PASS Blob paired surrogates (invalid utf-8)
+PASS Blob empty
+PASS Blob NUL
+PASS Array Blob object, Blob basic
+PASS Array Blob object, Blob unpaired high surrogate (invalid utf-8)
+PASS Array Blob object, Blob unpaired low surrogate (invalid utf-8)
+PASS Array Blob object, Blob paired surrogates (invalid utf-8)
+PASS Array Blob object, Blob empty
+PASS Array Blob object, Blob NUL
+PASS Array Blob object, two Blobs
+PASS Object Blob object, Blob basic
+PASS Object Blob object, Blob unpaired high surrogate (invalid utf-8)
+PASS Object Blob object, Blob unpaired low surrogate (invalid utf-8)
+PASS Object Blob object, Blob paired surrogates (invalid utf-8)
+PASS Object Blob object, Blob empty
+PASS Object Blob object, Blob NUL
+PASS File basic
+PASS FileList empty
+PASS Array FileList object, FileList empty
+PASS Object FileList object, FileList empty
+PASS ImageData 1x1 transparent black
+PASS ImageData 1x1 non-transparent non-black
+PASS Array ImageData object, ImageData 1x1 transparent black
+PASS Array ImageData object, ImageData 1x1 non-transparent non-black
+PASS Object ImageData object, ImageData 1x1 transparent black
+PASS Object ImageData object, ImageData 1x1 non-transparent non-black
+PASS Array sparse
+PASS Array with non-index property
+PASS Object with index property and length
+PASS Array with circular reference
+PASS Object with circular reference
+PASS Array with identical property values
+PASS Object with identical property values
+PASS Object with property on prototype
+PASS Object with non-enumerable property
+PASS Object with non-writable property
+PASS Object with non-configurable property
+PASS Object with a getter that throws
+PASS ImageBitmap 1x1 transparent black
+PASS ImageBitmap 1x1 non-transparent non-black
+PASS Array ImageBitmap object, ImageBitmap 1x1 transparent black
+PASS Array ImageBitmap object, ImageBitmap 1x1 transparent non-black
+PASS Object ImageBitmap object, ImageBitmap 1x1 transparent black
+PASS Object ImageBitmap object, ImageBitmap 1x1 transparent non-black
+PASS ObjectPrototype must lose its exotic-ness when cloned
+PASS Serializing a non-serializable platform object fails
+PASS An object whose interface is deleted from the global must still deserialize
+PASS A subclass instance will deserialize as its closest serializable superclass
+FAIL Resizable ArrayBuffer assert_true: expected true got undefined
+FAIL Growable SharedArrayBuffer assert_true: expected true got undefined
+FAIL Length-tracking TypedArray assert_true: expected true got undefined
+FAIL Length-tracking DataView assert_true: expected true got undefined
+FAIL Serializing OOB TypedArray throws promise_test: Unhandled rejection with value: object "TypeError: ab.resize is not a function"
+FAIL Serializing OOB DataView throws promise_test: Unhandled rejection with value: object "TypeError: ab.resize is not a function"
+PASS ArrayBuffer
+PASS MessagePort
+PASS A detached ArrayBuffer cannot be transferred
+PASS A detached platform object cannot be transferred
+PASS Transferring a non-transferable platform object fails
+PASS An object whose interface is deleted from the global object must still be received
+PASS A subclass instance will be received as its closest transferable superclass
+FAIL Resizable ArrayBuffer is transferable assert_equals: expected (number) 1024 but got (undefined) undefined
+FAIL Length-tracking TypedArray is transferable assert_equals: expected (number) 1024 but got (undefined) undefined
+FAIL Length-tracking DataView is transferable assert_equals: expected (number) 1024 but got (undefined) undefined
+FAIL Transferring OOB TypedArray throws promise_test: Unhandled rejection with value: object "TypeError: ab.resize is not a function"
+FAIL Transferring OOB DataView throws promise_test: Unhandled rejection with value: object "TypeError: ab.resize is not a function"
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/workers/semantics/structured-clone/dedicated.html b/third_party/blink/web_tests/external/wpt/workers/semantics/structured-clone/dedicated.html
index 2f1732c..16b6be5 100644
--- a/third_party/blink/web_tests/external/wpt/workers/semantics/structured-clone/dedicated.html
+++ b/third_party/blink/web_tests/external/wpt/workers/semantics/structured-clone/dedicated.html
@@ -2,6 +2,7 @@
 <title>structured clone to dedicated worker</title>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
+<script src=/common/sab.js></script>
 <script src=/html/webappapis/structured-clone/structured-clone-battery-of-tests.js></script>
 <script src=/html/webappapis/structured-clone/structured-clone-battery-of-tests-with-transferables.js></script>
 <script src=/html/webappapis/structured-clone/structured-clone-battery-of-tests-harness.js></script>
diff --git a/third_party/blink/web_tests/external/wpt/workers/semantics/structured-clone/shared-expected.txt b/third_party/blink/web_tests/external/wpt/workers/semantics/structured-clone/shared-expected.txt
new file mode 100644
index 0000000..09b51b19
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/workers/semantics/structured-clone/shared-expected.txt
@@ -0,0 +1,154 @@
+This is a testharness.js-based test.
+Found 150 tests; 139 PASS, 11 FAIL, 0 TIMEOUT, 0 NOTRUN.
+PASS primitive undefined
+PASS primitive null
+PASS primitive true
+PASS primitive false
+PASS primitive string, empty string
+PASS primitive string, lone high surrogate
+PASS primitive string, lone low surrogate
+PASS primitive string, NUL
+PASS primitive string, astral character
+PASS primitive number, 0.2
+PASS primitive number, 0
+PASS primitive number, -0
+PASS primitive number, NaN
+PASS primitive number, Infinity
+PASS primitive number, -Infinity
+PASS primitive number, 9007199254740992
+PASS primitive number, -9007199254740992
+PASS primitive number, 9007199254740994
+PASS primitive number, -9007199254740994
+PASS primitive BigInt, 0n
+PASS primitive BigInt, -0n
+PASS primitive BigInt, -9007199254740994000n
+PASS primitive BigInt, -9007199254740994000900719925474099400090071992547409940009007199254740994000n
+PASS Array primitives
+PASS Object primitives
+PASS Boolean true
+PASS Boolean false
+PASS Array Boolean objects
+PASS Object Boolean objects
+PASS String empty string
+PASS String lone high surrogate
+PASS String lone low surrogate
+PASS String NUL
+PASS String astral character
+PASS Array String objects
+PASS Object String objects
+PASS Number 0.2
+PASS Number 0
+PASS Number -0
+PASS Number NaN
+PASS Number Infinity
+PASS Number -Infinity
+PASS Number 9007199254740992
+PASS Number -9007199254740992
+PASS Number 9007199254740994
+PASS Number -9007199254740994
+PASS BigInt -9007199254740994n
+PASS Array Number objects
+PASS Object Number objects
+PASS Date 0
+PASS Date -0
+PASS Date -8.64e15
+PASS Date 8.64e15
+PASS Array Date objects
+PASS Object Date objects
+PASS RegExp flags and lastIndex
+PASS RegExp sticky flag
+PASS RegExp unicode flag
+PASS RegExp empty
+PASS RegExp slash
+PASS RegExp new line
+PASS Array RegExp object, RegExp flags and lastIndex
+PASS Array RegExp object, RegExp sticky flag
+PASS Array RegExp object, RegExp unicode flag
+PASS Array RegExp object, RegExp empty
+PASS Array RegExp object, RegExp slash
+PASS Array RegExp object, RegExp new line
+PASS Object RegExp object, RegExp flags and lastIndex
+PASS Object RegExp object, RegExp sticky flag
+PASS Object RegExp object, RegExp unicode flag
+PASS Object RegExp object, RegExp empty
+PASS Object RegExp object, RegExp slash
+PASS Object RegExp object, RegExp new line
+PASS Empty Error object
+PASS Error object
+PASS EvalError object
+PASS RangeError object
+PASS ReferenceError object
+PASS SyntaxError object
+PASS TypeError object
+PASS URIError object
+PASS Blob basic
+PASS Blob unpaired high surrogate (invalid utf-8)
+PASS Blob unpaired low surrogate (invalid utf-8)
+PASS Blob paired surrogates (invalid utf-8)
+PASS Blob empty
+PASS Blob NUL
+PASS Array Blob object, Blob basic
+PASS Array Blob object, Blob unpaired high surrogate (invalid utf-8)
+PASS Array Blob object, Blob unpaired low surrogate (invalid utf-8)
+PASS Array Blob object, Blob paired surrogates (invalid utf-8)
+PASS Array Blob object, Blob empty
+PASS Array Blob object, Blob NUL
+PASS Array Blob object, two Blobs
+PASS Object Blob object, Blob basic
+PASS Object Blob object, Blob unpaired high surrogate (invalid utf-8)
+PASS Object Blob object, Blob unpaired low surrogate (invalid utf-8)
+PASS Object Blob object, Blob paired surrogates (invalid utf-8)
+PASS Object Blob object, Blob empty
+PASS Object Blob object, Blob NUL
+PASS File basic
+PASS FileList empty
+PASS Array FileList object, FileList empty
+PASS Object FileList object, FileList empty
+PASS ImageData 1x1 transparent black
+PASS ImageData 1x1 non-transparent non-black
+PASS Array ImageData object, ImageData 1x1 transparent black
+PASS Array ImageData object, ImageData 1x1 non-transparent non-black
+PASS Object ImageData object, ImageData 1x1 transparent black
+PASS Object ImageData object, ImageData 1x1 non-transparent non-black
+PASS Array sparse
+PASS Array with non-index property
+PASS Object with index property and length
+PASS Array with circular reference
+PASS Object with circular reference
+PASS Array with identical property values
+PASS Object with identical property values
+PASS Object with property on prototype
+PASS Object with non-enumerable property
+PASS Object with non-writable property
+PASS Object with non-configurable property
+PASS Object with a getter that throws
+PASS ImageBitmap 1x1 transparent black
+PASS ImageBitmap 1x1 non-transparent non-black
+PASS Array ImageBitmap object, ImageBitmap 1x1 transparent black
+PASS Array ImageBitmap object, ImageBitmap 1x1 transparent non-black
+PASS Object ImageBitmap object, ImageBitmap 1x1 transparent black
+PASS Object ImageBitmap object, ImageBitmap 1x1 transparent non-black
+PASS ObjectPrototype must lose its exotic-ness when cloned
+PASS Serializing a non-serializable platform object fails
+PASS An object whose interface is deleted from the global must still deserialize
+PASS A subclass instance will deserialize as its closest serializable superclass
+FAIL Resizable ArrayBuffer assert_true: expected true got undefined
+FAIL Growable SharedArrayBuffer assert_true: expected true got undefined
+FAIL Length-tracking TypedArray assert_true: expected true got undefined
+FAIL Length-tracking DataView assert_true: expected true got undefined
+FAIL Serializing OOB TypedArray throws promise_test: Unhandled rejection with value: object "TypeError: ab.resize is not a function"
+FAIL Serializing OOB DataView throws promise_test: Unhandled rejection with value: object "TypeError: ab.resize is not a function"
+PASS ArrayBuffer
+PASS MessagePort
+PASS A detached ArrayBuffer cannot be transferred
+PASS A detached platform object cannot be transferred
+PASS Transferring a non-transferable platform object fails
+PASS An object whose interface is deleted from the global object must still be received
+PASS A subclass instance will be received as its closest transferable superclass
+FAIL Resizable ArrayBuffer is transferable assert_equals: expected (number) 1024 but got (undefined) undefined
+FAIL Length-tracking TypedArray is transferable assert_equals: expected (number) 1024 but got (undefined) undefined
+FAIL Length-tracking DataView is transferable assert_equals: expected (number) 1024 but got (undefined) undefined
+FAIL Transferring OOB TypedArray throws promise_test: Unhandled rejection with value: object "TypeError: ab.resize is not a function"
+FAIL Transferring OOB DataView throws promise_test: Unhandled rejection with value: object "TypeError: ab.resize is not a function"
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/workers/semantics/structured-clone/shared.html b/third_party/blink/web_tests/external/wpt/workers/semantics/structured-clone/shared.html
index 793da8f..eb85499f 100644
--- a/third_party/blink/web_tests/external/wpt/workers/semantics/structured-clone/shared.html
+++ b/third_party/blink/web_tests/external/wpt/workers/semantics/structured-clone/shared.html
@@ -2,6 +2,7 @@
 <title>structured clone to shared worker</title>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
+<script src=/common/sab.js></script>
 <script src=/html/webappapis/structured-clone/structured-clone-battery-of-tests.js></script>
 <script src=/html/webappapis/structured-clone/structured-clone-battery-of-tests-with-transferables.js></script>
 <script src=/html/webappapis/structured-clone/structured-clone-battery-of-tests-harness.js></script>
diff --git a/third_party/blink/web_tests/virtual/js-resizable-arraybuffer/external/wpt/html/infrastructure/safe-passing-of-structured-data/messagechannel.any-expected.txt b/third_party/blink/web_tests/virtual/js-resizable-arraybuffer/external/wpt/html/infrastructure/safe-passing-of-structured-data/messagechannel.any-expected.txt
new file mode 100644
index 0000000..5ccf001
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/js-resizable-arraybuffer/external/wpt/html/infrastructure/safe-passing-of-structured-data/messagechannel.any-expected.txt
@@ -0,0 +1,153 @@
+This is a testharness.js-based test.
+PASS primitive undefined
+PASS primitive null
+PASS primitive true
+PASS primitive false
+PASS primitive string, empty string
+PASS primitive string, lone high surrogate
+PASS primitive string, lone low surrogate
+PASS primitive string, NUL
+PASS primitive string, astral character
+PASS primitive number, 0.2
+PASS primitive number, 0
+PASS primitive number, -0
+PASS primitive number, NaN
+PASS primitive number, Infinity
+PASS primitive number, -Infinity
+PASS primitive number, 9007199254740992
+PASS primitive number, -9007199254740992
+PASS primitive number, 9007199254740994
+PASS primitive number, -9007199254740994
+PASS primitive BigInt, 0n
+PASS primitive BigInt, -0n
+PASS primitive BigInt, -9007199254740994000n
+PASS primitive BigInt, -9007199254740994000900719925474099400090071992547409940009007199254740994000n
+PASS Array primitives
+PASS Object primitives
+PASS Boolean true
+PASS Boolean false
+PASS Array Boolean objects
+PASS Object Boolean objects
+PASS String empty string
+PASS String lone high surrogate
+PASS String lone low surrogate
+PASS String NUL
+PASS String astral character
+PASS Array String objects
+PASS Object String objects
+PASS Number 0.2
+PASS Number 0
+PASS Number -0
+PASS Number NaN
+PASS Number Infinity
+PASS Number -Infinity
+PASS Number 9007199254740992
+PASS Number -9007199254740992
+PASS Number 9007199254740994
+PASS Number -9007199254740994
+PASS BigInt -9007199254740994n
+PASS Array Number objects
+PASS Object Number objects
+PASS Date 0
+PASS Date -0
+PASS Date -8.64e15
+PASS Date 8.64e15
+PASS Array Date objects
+PASS Object Date objects
+PASS RegExp flags and lastIndex
+PASS RegExp sticky flag
+PASS RegExp unicode flag
+PASS RegExp empty
+PASS RegExp slash
+PASS RegExp new line
+PASS Array RegExp object, RegExp flags and lastIndex
+PASS Array RegExp object, RegExp sticky flag
+PASS Array RegExp object, RegExp unicode flag
+PASS Array RegExp object, RegExp empty
+PASS Array RegExp object, RegExp slash
+PASS Array RegExp object, RegExp new line
+PASS Object RegExp object, RegExp flags and lastIndex
+PASS Object RegExp object, RegExp sticky flag
+PASS Object RegExp object, RegExp unicode flag
+PASS Object RegExp object, RegExp empty
+PASS Object RegExp object, RegExp slash
+PASS Object RegExp object, RegExp new line
+PASS Empty Error object
+PASS Error object
+PASS EvalError object
+PASS RangeError object
+PASS ReferenceError object
+PASS SyntaxError object
+PASS TypeError object
+PASS URIError object
+PASS Blob basic
+PASS Blob unpaired high surrogate (invalid utf-8)
+PASS Blob unpaired low surrogate (invalid utf-8)
+PASS Blob paired surrogates (invalid utf-8)
+PASS Blob empty
+PASS Blob NUL
+PASS Array Blob object, Blob basic
+PASS Array Blob object, Blob unpaired high surrogate (invalid utf-8)
+PASS Array Blob object, Blob unpaired low surrogate (invalid utf-8)
+PASS Array Blob object, Blob paired surrogates (invalid utf-8)
+PASS Array Blob object, Blob empty
+PASS Array Blob object, Blob NUL
+PASS Array Blob object, two Blobs
+PASS Object Blob object, Blob basic
+PASS Object Blob object, Blob unpaired high surrogate (invalid utf-8)
+PASS Object Blob object, Blob unpaired low surrogate (invalid utf-8)
+PASS Object Blob object, Blob paired surrogates (invalid utf-8)
+PASS Object Blob object, Blob empty
+PASS Object Blob object, Blob NUL
+PASS File basic
+PASS FileList empty
+PASS Array FileList object, FileList empty
+PASS Object FileList object, FileList empty
+PASS ImageData 1x1 transparent black
+PASS ImageData 1x1 non-transparent non-black
+PASS Array ImageData object, ImageData 1x1 transparent black
+PASS Array ImageData object, ImageData 1x1 non-transparent non-black
+PASS Object ImageData object, ImageData 1x1 transparent black
+PASS Object ImageData object, ImageData 1x1 non-transparent non-black
+PASS Array sparse
+PASS Array with non-index property
+PASS Object with index property and length
+PASS Array with circular reference
+PASS Object with circular reference
+PASS Array with identical property values
+PASS Object with identical property values
+PASS Object with property on prototype
+PASS Object with non-enumerable property
+PASS Object with non-writable property
+PASS Object with non-configurable property
+PASS Object with a getter that throws
+PASS ImageBitmap 1x1 transparent black
+PASS ImageBitmap 1x1 non-transparent non-black
+PASS Array ImageBitmap object, ImageBitmap 1x1 transparent black
+PASS Array ImageBitmap object, ImageBitmap 1x1 transparent non-black
+PASS Object ImageBitmap object, ImageBitmap 1x1 transparent black
+PASS Object ImageBitmap object, ImageBitmap 1x1 transparent non-black
+PASS ObjectPrototype must lose its exotic-ness when cloned
+PASS Serializing a non-serializable platform object fails
+PASS An object whose interface is deleted from the global must still deserialize
+PASS A subclass instance will deserialize as its closest serializable superclass
+PASS Resizable ArrayBuffer
+PASS Growable SharedArrayBuffer
+PASS Length-tracking TypedArray
+PASS Length-tracking DataView
+PASS Serializing OOB TypedArray throws
+PASS Serializing OOB DataView throws
+PASS ArrayBuffer
+PASS MessagePort
+PASS A detached ArrayBuffer cannot be transferred
+PASS A detached platform object cannot be transferred
+PASS Transferring a non-transferable platform object fails
+PASS An object whose interface is deleted from the global object must still be received
+PASS A subclass instance will be received as its closest transferable superclass
+PASS Resizable ArrayBuffer is transferable
+PASS Length-tracking TypedArray is transferable
+PASS Length-tracking DataView is transferable
+PASS Transferring OOB TypedArray throws
+PASS Transferring OOB DataView throws
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/virtual/js-resizable-arraybuffer/external/wpt/html/infrastructure/safe-passing-of-structured-data/messagechannel.any.serviceworker-expected.txt b/third_party/blink/web_tests/virtual/js-resizable-arraybuffer/external/wpt/html/infrastructure/safe-passing-of-structured-data/messagechannel.any.serviceworker-expected.txt
new file mode 100644
index 0000000..77eb14338
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/js-resizable-arraybuffer/external/wpt/html/infrastructure/safe-passing-of-structured-data/messagechannel.any.serviceworker-expected.txt
@@ -0,0 +1,138 @@
+This is a testharness.js-based test.
+PASS primitive undefined
+PASS primitive null
+PASS primitive true
+PASS primitive false
+PASS primitive string, empty string
+PASS primitive string, lone high surrogate
+PASS primitive string, lone low surrogate
+PASS primitive string, NUL
+PASS primitive string, astral character
+PASS primitive number, 0.2
+PASS primitive number, 0
+PASS primitive number, -0
+PASS primitive number, NaN
+PASS primitive number, Infinity
+PASS primitive number, -Infinity
+PASS primitive number, 9007199254740992
+PASS primitive number, -9007199254740992
+PASS primitive number, 9007199254740994
+PASS primitive number, -9007199254740994
+PASS primitive BigInt, 0n
+PASS primitive BigInt, -0n
+PASS primitive BigInt, -9007199254740994000n
+PASS primitive BigInt, -9007199254740994000900719925474099400090071992547409940009007199254740994000n
+PASS Array primitives
+PASS Object primitives
+PASS Boolean true
+PASS Boolean false
+PASS Array Boolean objects
+PASS Object Boolean objects
+PASS String empty string
+PASS String lone high surrogate
+PASS String lone low surrogate
+PASS String NUL
+PASS String astral character
+PASS Array String objects
+PASS Object String objects
+PASS Number 0.2
+PASS Number 0
+PASS Number -0
+PASS Number NaN
+PASS Number Infinity
+PASS Number -Infinity
+PASS Number 9007199254740992
+PASS Number -9007199254740992
+PASS Number 9007199254740994
+PASS Number -9007199254740994
+PASS BigInt -9007199254740994n
+PASS Array Number objects
+PASS Object Number objects
+PASS Date 0
+PASS Date -0
+PASS Date -8.64e15
+PASS Date 8.64e15
+PASS Array Date objects
+PASS Object Date objects
+PASS RegExp flags and lastIndex
+PASS RegExp sticky flag
+PASS RegExp unicode flag
+PASS RegExp empty
+PASS RegExp slash
+PASS RegExp new line
+PASS Array RegExp object, RegExp flags and lastIndex
+PASS Array RegExp object, RegExp sticky flag
+PASS Array RegExp object, RegExp unicode flag
+PASS Array RegExp object, RegExp empty
+PASS Array RegExp object, RegExp slash
+PASS Array RegExp object, RegExp new line
+PASS Object RegExp object, RegExp flags and lastIndex
+PASS Object RegExp object, RegExp sticky flag
+PASS Object RegExp object, RegExp unicode flag
+PASS Object RegExp object, RegExp empty
+PASS Object RegExp object, RegExp slash
+PASS Object RegExp object, RegExp new line
+PASS Empty Error object
+PASS Error object
+PASS EvalError object
+PASS RangeError object
+PASS ReferenceError object
+PASS SyntaxError object
+PASS TypeError object
+PASS URIError object
+PASS Blob basic
+PASS Blob unpaired high surrogate (invalid utf-8)
+PASS Blob unpaired low surrogate (invalid utf-8)
+PASS Blob paired surrogates (invalid utf-8)
+PASS Blob empty
+PASS Blob NUL
+PASS Array Blob object, Blob basic
+PASS Array Blob object, Blob unpaired high surrogate (invalid utf-8)
+PASS Array Blob object, Blob unpaired low surrogate (invalid utf-8)
+PASS Array Blob object, Blob paired surrogates (invalid utf-8)
+PASS Array Blob object, Blob empty
+PASS Array Blob object, Blob NUL
+PASS Array Blob object, two Blobs
+PASS Object Blob object, Blob basic
+PASS Object Blob object, Blob unpaired high surrogate (invalid utf-8)
+PASS Object Blob object, Blob unpaired low surrogate (invalid utf-8)
+PASS Object Blob object, Blob paired surrogates (invalid utf-8)
+PASS Object Blob object, Blob empty
+PASS Object Blob object, Blob NUL
+PASS File basic
+PASS Array sparse
+PASS Array with non-index property
+PASS Object with index property and length
+PASS Array with circular reference
+PASS Object with circular reference
+PASS Array with identical property values
+PASS Object with identical property values
+PASS Object with property on prototype
+PASS Object with non-enumerable property
+PASS Object with non-writable property
+PASS Object with non-configurable property
+PASS Object with a getter that throws
+PASS ObjectPrototype must lose its exotic-ness when cloned
+PASS Serializing a non-serializable platform object fails
+PASS An object whose interface is deleted from the global must still deserialize
+PASS A subclass instance will deserialize as its closest serializable superclass
+PASS Resizable ArrayBuffer
+PASS Growable SharedArrayBuffer
+PASS Length-tracking TypedArray
+PASS Length-tracking DataView
+PASS Serializing OOB TypedArray throws
+PASS Serializing OOB DataView throws
+PASS ArrayBuffer
+PASS MessagePort
+PASS A detached ArrayBuffer cannot be transferred
+PASS A detached platform object cannot be transferred
+PASS Transferring a non-transferable platform object fails
+PASS An object whose interface is deleted from the global object must still be received
+PASS A subclass instance will be received as its closest transferable superclass
+PASS Resizable ArrayBuffer is transferable
+PASS Length-tracking TypedArray is transferable
+PASS Length-tracking DataView is transferable
+PASS Transferring OOB TypedArray throws
+PASS Transferring OOB DataView throws
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/virtual/js-resizable-arraybuffer/external/wpt/html/infrastructure/safe-passing-of-structured-data/messagechannel.any.sharedworker-expected.txt b/third_party/blink/web_tests/virtual/js-resizable-arraybuffer/external/wpt/html/infrastructure/safe-passing-of-structured-data/messagechannel.any.sharedworker-expected.txt
new file mode 100644
index 0000000..77eb14338
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/js-resizable-arraybuffer/external/wpt/html/infrastructure/safe-passing-of-structured-data/messagechannel.any.sharedworker-expected.txt
@@ -0,0 +1,138 @@
+This is a testharness.js-based test.
+PASS primitive undefined
+PASS primitive null
+PASS primitive true
+PASS primitive false
+PASS primitive string, empty string
+PASS primitive string, lone high surrogate
+PASS primitive string, lone low surrogate
+PASS primitive string, NUL
+PASS primitive string, astral character
+PASS primitive number, 0.2
+PASS primitive number, 0
+PASS primitive number, -0
+PASS primitive number, NaN
+PASS primitive number, Infinity
+PASS primitive number, -Infinity
+PASS primitive number, 9007199254740992
+PASS primitive number, -9007199254740992
+PASS primitive number, 9007199254740994
+PASS primitive number, -9007199254740994
+PASS primitive BigInt, 0n
+PASS primitive BigInt, -0n
+PASS primitive BigInt, -9007199254740994000n
+PASS primitive BigInt, -9007199254740994000900719925474099400090071992547409940009007199254740994000n
+PASS Array primitives
+PASS Object primitives
+PASS Boolean true
+PASS Boolean false
+PASS Array Boolean objects
+PASS Object Boolean objects
+PASS String empty string
+PASS String lone high surrogate
+PASS String lone low surrogate
+PASS String NUL
+PASS String astral character
+PASS Array String objects
+PASS Object String objects
+PASS Number 0.2
+PASS Number 0
+PASS Number -0
+PASS Number NaN
+PASS Number Infinity
+PASS Number -Infinity
+PASS Number 9007199254740992
+PASS Number -9007199254740992
+PASS Number 9007199254740994
+PASS Number -9007199254740994
+PASS BigInt -9007199254740994n
+PASS Array Number objects
+PASS Object Number objects
+PASS Date 0
+PASS Date -0
+PASS Date -8.64e15
+PASS Date 8.64e15
+PASS Array Date objects
+PASS Object Date objects
+PASS RegExp flags and lastIndex
+PASS RegExp sticky flag
+PASS RegExp unicode flag
+PASS RegExp empty
+PASS RegExp slash
+PASS RegExp new line
+PASS Array RegExp object, RegExp flags and lastIndex
+PASS Array RegExp object, RegExp sticky flag
+PASS Array RegExp object, RegExp unicode flag
+PASS Array RegExp object, RegExp empty
+PASS Array RegExp object, RegExp slash
+PASS Array RegExp object, RegExp new line
+PASS Object RegExp object, RegExp flags and lastIndex
+PASS Object RegExp object, RegExp sticky flag
+PASS Object RegExp object, RegExp unicode flag
+PASS Object RegExp object, RegExp empty
+PASS Object RegExp object, RegExp slash
+PASS Object RegExp object, RegExp new line
+PASS Empty Error object
+PASS Error object
+PASS EvalError object
+PASS RangeError object
+PASS ReferenceError object
+PASS SyntaxError object
+PASS TypeError object
+PASS URIError object
+PASS Blob basic
+PASS Blob unpaired high surrogate (invalid utf-8)
+PASS Blob unpaired low surrogate (invalid utf-8)
+PASS Blob paired surrogates (invalid utf-8)
+PASS Blob empty
+PASS Blob NUL
+PASS Array Blob object, Blob basic
+PASS Array Blob object, Blob unpaired high surrogate (invalid utf-8)
+PASS Array Blob object, Blob unpaired low surrogate (invalid utf-8)
+PASS Array Blob object, Blob paired surrogates (invalid utf-8)
+PASS Array Blob object, Blob empty
+PASS Array Blob object, Blob NUL
+PASS Array Blob object, two Blobs
+PASS Object Blob object, Blob basic
+PASS Object Blob object, Blob unpaired high surrogate (invalid utf-8)
+PASS Object Blob object, Blob unpaired low surrogate (invalid utf-8)
+PASS Object Blob object, Blob paired surrogates (invalid utf-8)
+PASS Object Blob object, Blob empty
+PASS Object Blob object, Blob NUL
+PASS File basic
+PASS Array sparse
+PASS Array with non-index property
+PASS Object with index property and length
+PASS Array with circular reference
+PASS Object with circular reference
+PASS Array with identical property values
+PASS Object with identical property values
+PASS Object with property on prototype
+PASS Object with non-enumerable property
+PASS Object with non-writable property
+PASS Object with non-configurable property
+PASS Object with a getter that throws
+PASS ObjectPrototype must lose its exotic-ness when cloned
+PASS Serializing a non-serializable platform object fails
+PASS An object whose interface is deleted from the global must still deserialize
+PASS A subclass instance will deserialize as its closest serializable superclass
+PASS Resizable ArrayBuffer
+PASS Growable SharedArrayBuffer
+PASS Length-tracking TypedArray
+PASS Length-tracking DataView
+PASS Serializing OOB TypedArray throws
+PASS Serializing OOB DataView throws
+PASS ArrayBuffer
+PASS MessagePort
+PASS A detached ArrayBuffer cannot be transferred
+PASS A detached platform object cannot be transferred
+PASS Transferring a non-transferable platform object fails
+PASS An object whose interface is deleted from the global object must still be received
+PASS A subclass instance will be received as its closest transferable superclass
+PASS Resizable ArrayBuffer is transferable
+PASS Length-tracking TypedArray is transferable
+PASS Length-tracking DataView is transferable
+PASS Transferring OOB TypedArray throws
+PASS Transferring OOB DataView throws
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/virtual/js-resizable-arraybuffer/external/wpt/html/infrastructure/safe-passing-of-structured-data/messagechannel.any.worker-expected.txt b/third_party/blink/web_tests/virtual/js-resizable-arraybuffer/external/wpt/html/infrastructure/safe-passing-of-structured-data/messagechannel.any.worker-expected.txt
new file mode 100644
index 0000000..77eb14338
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/js-resizable-arraybuffer/external/wpt/html/infrastructure/safe-passing-of-structured-data/messagechannel.any.worker-expected.txt
@@ -0,0 +1,138 @@
+This is a testharness.js-based test.
+PASS primitive undefined
+PASS primitive null
+PASS primitive true
+PASS primitive false
+PASS primitive string, empty string
+PASS primitive string, lone high surrogate
+PASS primitive string, lone low surrogate
+PASS primitive string, NUL
+PASS primitive string, astral character
+PASS primitive number, 0.2
+PASS primitive number, 0
+PASS primitive number, -0
+PASS primitive number, NaN
+PASS primitive number, Infinity
+PASS primitive number, -Infinity
+PASS primitive number, 9007199254740992
+PASS primitive number, -9007199254740992
+PASS primitive number, 9007199254740994
+PASS primitive number, -9007199254740994
+PASS primitive BigInt, 0n
+PASS primitive BigInt, -0n
+PASS primitive BigInt, -9007199254740994000n
+PASS primitive BigInt, -9007199254740994000900719925474099400090071992547409940009007199254740994000n
+PASS Array primitives
+PASS Object primitives
+PASS Boolean true
+PASS Boolean false
+PASS Array Boolean objects
+PASS Object Boolean objects
+PASS String empty string
+PASS String lone high surrogate
+PASS String lone low surrogate
+PASS String NUL
+PASS String astral character
+PASS Array String objects
+PASS Object String objects
+PASS Number 0.2
+PASS Number 0
+PASS Number -0
+PASS Number NaN
+PASS Number Infinity
+PASS Number -Infinity
+PASS Number 9007199254740992
+PASS Number -9007199254740992
+PASS Number 9007199254740994
+PASS Number -9007199254740994
+PASS BigInt -9007199254740994n
+PASS Array Number objects
+PASS Object Number objects
+PASS Date 0
+PASS Date -0
+PASS Date -8.64e15
+PASS Date 8.64e15
+PASS Array Date objects
+PASS Object Date objects
+PASS RegExp flags and lastIndex
+PASS RegExp sticky flag
+PASS RegExp unicode flag
+PASS RegExp empty
+PASS RegExp slash
+PASS RegExp new line
+PASS Array RegExp object, RegExp flags and lastIndex
+PASS Array RegExp object, RegExp sticky flag
+PASS Array RegExp object, RegExp unicode flag
+PASS Array RegExp object, RegExp empty
+PASS Array RegExp object, RegExp slash
+PASS Array RegExp object, RegExp new line
+PASS Object RegExp object, RegExp flags and lastIndex
+PASS Object RegExp object, RegExp sticky flag
+PASS Object RegExp object, RegExp unicode flag
+PASS Object RegExp object, RegExp empty
+PASS Object RegExp object, RegExp slash
+PASS Object RegExp object, RegExp new line
+PASS Empty Error object
+PASS Error object
+PASS EvalError object
+PASS RangeError object
+PASS ReferenceError object
+PASS SyntaxError object
+PASS TypeError object
+PASS URIError object
+PASS Blob basic
+PASS Blob unpaired high surrogate (invalid utf-8)
+PASS Blob unpaired low surrogate (invalid utf-8)
+PASS Blob paired surrogates (invalid utf-8)
+PASS Blob empty
+PASS Blob NUL
+PASS Array Blob object, Blob basic
+PASS Array Blob object, Blob unpaired high surrogate (invalid utf-8)
+PASS Array Blob object, Blob unpaired low surrogate (invalid utf-8)
+PASS Array Blob object, Blob paired surrogates (invalid utf-8)
+PASS Array Blob object, Blob empty
+PASS Array Blob object, Blob NUL
+PASS Array Blob object, two Blobs
+PASS Object Blob object, Blob basic
+PASS Object Blob object, Blob unpaired high surrogate (invalid utf-8)
+PASS Object Blob object, Blob unpaired low surrogate (invalid utf-8)
+PASS Object Blob object, Blob paired surrogates (invalid utf-8)
+PASS Object Blob object, Blob empty
+PASS Object Blob object, Blob NUL
+PASS File basic
+PASS Array sparse
+PASS Array with non-index property
+PASS Object with index property and length
+PASS Array with circular reference
+PASS Object with circular reference
+PASS Array with identical property values
+PASS Object with identical property values
+PASS Object with property on prototype
+PASS Object with non-enumerable property
+PASS Object with non-writable property
+PASS Object with non-configurable property
+PASS Object with a getter that throws
+PASS ObjectPrototype must lose its exotic-ness when cloned
+PASS Serializing a non-serializable platform object fails
+PASS An object whose interface is deleted from the global must still deserialize
+PASS A subclass instance will deserialize as its closest serializable superclass
+PASS Resizable ArrayBuffer
+PASS Growable SharedArrayBuffer
+PASS Length-tracking TypedArray
+PASS Length-tracking DataView
+PASS Serializing OOB TypedArray throws
+PASS Serializing OOB DataView throws
+PASS ArrayBuffer
+PASS MessagePort
+PASS A detached ArrayBuffer cannot be transferred
+PASS A detached platform object cannot be transferred
+PASS Transferring a non-transferable platform object fails
+PASS An object whose interface is deleted from the global object must still be received
+PASS A subclass instance will be received as its closest transferable superclass
+PASS Resizable ArrayBuffer is transferable
+PASS Length-tracking TypedArray is transferable
+PASS Length-tracking DataView is transferable
+PASS Transferring OOB TypedArray throws
+PASS Transferring OOB DataView throws
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/virtual/js-resizable-arraybuffer/external/wpt/html/infrastructure/safe-passing-of-structured-data/window-postmessage.window-expected.txt b/third_party/blink/web_tests/virtual/js-resizable-arraybuffer/external/wpt/html/infrastructure/safe-passing-of-structured-data/window-postmessage.window-expected.txt
new file mode 100644
index 0000000..5ccf001
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/js-resizable-arraybuffer/external/wpt/html/infrastructure/safe-passing-of-structured-data/window-postmessage.window-expected.txt
@@ -0,0 +1,153 @@
+This is a testharness.js-based test.
+PASS primitive undefined
+PASS primitive null
+PASS primitive true
+PASS primitive false
+PASS primitive string, empty string
+PASS primitive string, lone high surrogate
+PASS primitive string, lone low surrogate
+PASS primitive string, NUL
+PASS primitive string, astral character
+PASS primitive number, 0.2
+PASS primitive number, 0
+PASS primitive number, -0
+PASS primitive number, NaN
+PASS primitive number, Infinity
+PASS primitive number, -Infinity
+PASS primitive number, 9007199254740992
+PASS primitive number, -9007199254740992
+PASS primitive number, 9007199254740994
+PASS primitive number, -9007199254740994
+PASS primitive BigInt, 0n
+PASS primitive BigInt, -0n
+PASS primitive BigInt, -9007199254740994000n
+PASS primitive BigInt, -9007199254740994000900719925474099400090071992547409940009007199254740994000n
+PASS Array primitives
+PASS Object primitives
+PASS Boolean true
+PASS Boolean false
+PASS Array Boolean objects
+PASS Object Boolean objects
+PASS String empty string
+PASS String lone high surrogate
+PASS String lone low surrogate
+PASS String NUL
+PASS String astral character
+PASS Array String objects
+PASS Object String objects
+PASS Number 0.2
+PASS Number 0
+PASS Number -0
+PASS Number NaN
+PASS Number Infinity
+PASS Number -Infinity
+PASS Number 9007199254740992
+PASS Number -9007199254740992
+PASS Number 9007199254740994
+PASS Number -9007199254740994
+PASS BigInt -9007199254740994n
+PASS Array Number objects
+PASS Object Number objects
+PASS Date 0
+PASS Date -0
+PASS Date -8.64e15
+PASS Date 8.64e15
+PASS Array Date objects
+PASS Object Date objects
+PASS RegExp flags and lastIndex
+PASS RegExp sticky flag
+PASS RegExp unicode flag
+PASS RegExp empty
+PASS RegExp slash
+PASS RegExp new line
+PASS Array RegExp object, RegExp flags and lastIndex
+PASS Array RegExp object, RegExp sticky flag
+PASS Array RegExp object, RegExp unicode flag
+PASS Array RegExp object, RegExp empty
+PASS Array RegExp object, RegExp slash
+PASS Array RegExp object, RegExp new line
+PASS Object RegExp object, RegExp flags and lastIndex
+PASS Object RegExp object, RegExp sticky flag
+PASS Object RegExp object, RegExp unicode flag
+PASS Object RegExp object, RegExp empty
+PASS Object RegExp object, RegExp slash
+PASS Object RegExp object, RegExp new line
+PASS Empty Error object
+PASS Error object
+PASS EvalError object
+PASS RangeError object
+PASS ReferenceError object
+PASS SyntaxError object
+PASS TypeError object
+PASS URIError object
+PASS Blob basic
+PASS Blob unpaired high surrogate (invalid utf-8)
+PASS Blob unpaired low surrogate (invalid utf-8)
+PASS Blob paired surrogates (invalid utf-8)
+PASS Blob empty
+PASS Blob NUL
+PASS Array Blob object, Blob basic
+PASS Array Blob object, Blob unpaired high surrogate (invalid utf-8)
+PASS Array Blob object, Blob unpaired low surrogate (invalid utf-8)
+PASS Array Blob object, Blob paired surrogates (invalid utf-8)
+PASS Array Blob object, Blob empty
+PASS Array Blob object, Blob NUL
+PASS Array Blob object, two Blobs
+PASS Object Blob object, Blob basic
+PASS Object Blob object, Blob unpaired high surrogate (invalid utf-8)
+PASS Object Blob object, Blob unpaired low surrogate (invalid utf-8)
+PASS Object Blob object, Blob paired surrogates (invalid utf-8)
+PASS Object Blob object, Blob empty
+PASS Object Blob object, Blob NUL
+PASS File basic
+PASS FileList empty
+PASS Array FileList object, FileList empty
+PASS Object FileList object, FileList empty
+PASS ImageData 1x1 transparent black
+PASS ImageData 1x1 non-transparent non-black
+PASS Array ImageData object, ImageData 1x1 transparent black
+PASS Array ImageData object, ImageData 1x1 non-transparent non-black
+PASS Object ImageData object, ImageData 1x1 transparent black
+PASS Object ImageData object, ImageData 1x1 non-transparent non-black
+PASS Array sparse
+PASS Array with non-index property
+PASS Object with index property and length
+PASS Array with circular reference
+PASS Object with circular reference
+PASS Array with identical property values
+PASS Object with identical property values
+PASS Object with property on prototype
+PASS Object with non-enumerable property
+PASS Object with non-writable property
+PASS Object with non-configurable property
+PASS Object with a getter that throws
+PASS ImageBitmap 1x1 transparent black
+PASS ImageBitmap 1x1 non-transparent non-black
+PASS Array ImageBitmap object, ImageBitmap 1x1 transparent black
+PASS Array ImageBitmap object, ImageBitmap 1x1 transparent non-black
+PASS Object ImageBitmap object, ImageBitmap 1x1 transparent black
+PASS Object ImageBitmap object, ImageBitmap 1x1 transparent non-black
+PASS ObjectPrototype must lose its exotic-ness when cloned
+PASS Serializing a non-serializable platform object fails
+PASS An object whose interface is deleted from the global must still deserialize
+PASS A subclass instance will deserialize as its closest serializable superclass
+PASS Resizable ArrayBuffer
+PASS Growable SharedArrayBuffer
+PASS Length-tracking TypedArray
+PASS Length-tracking DataView
+PASS Serializing OOB TypedArray throws
+PASS Serializing OOB DataView throws
+PASS ArrayBuffer
+PASS MessagePort
+PASS A detached ArrayBuffer cannot be transferred
+PASS A detached platform object cannot be transferred
+PASS Transferring a non-transferable platform object fails
+PASS An object whose interface is deleted from the global object must still be received
+PASS A subclass instance will be received as its closest transferable superclass
+PASS Resizable ArrayBuffer is transferable
+PASS Length-tracking TypedArray is transferable
+PASS Length-tracking DataView is transferable
+PASS Transferring OOB TypedArray throws
+PASS Transferring OOB DataView throws
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/virtual/js-resizable-arraybuffer/external/wpt/html/webappapis/structured-clone/structured-clone.any-expected.txt b/third_party/blink/web_tests/virtual/js-resizable-arraybuffer/external/wpt/html/webappapis/structured-clone/structured-clone.any-expected.txt
new file mode 100644
index 0000000..5ccf001
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/js-resizable-arraybuffer/external/wpt/html/webappapis/structured-clone/structured-clone.any-expected.txt
@@ -0,0 +1,153 @@
+This is a testharness.js-based test.
+PASS primitive undefined
+PASS primitive null
+PASS primitive true
+PASS primitive false
+PASS primitive string, empty string
+PASS primitive string, lone high surrogate
+PASS primitive string, lone low surrogate
+PASS primitive string, NUL
+PASS primitive string, astral character
+PASS primitive number, 0.2
+PASS primitive number, 0
+PASS primitive number, -0
+PASS primitive number, NaN
+PASS primitive number, Infinity
+PASS primitive number, -Infinity
+PASS primitive number, 9007199254740992
+PASS primitive number, -9007199254740992
+PASS primitive number, 9007199254740994
+PASS primitive number, -9007199254740994
+PASS primitive BigInt, 0n
+PASS primitive BigInt, -0n
+PASS primitive BigInt, -9007199254740994000n
+PASS primitive BigInt, -9007199254740994000900719925474099400090071992547409940009007199254740994000n
+PASS Array primitives
+PASS Object primitives
+PASS Boolean true
+PASS Boolean false
+PASS Array Boolean objects
+PASS Object Boolean objects
+PASS String empty string
+PASS String lone high surrogate
+PASS String lone low surrogate
+PASS String NUL
+PASS String astral character
+PASS Array String objects
+PASS Object String objects
+PASS Number 0.2
+PASS Number 0
+PASS Number -0
+PASS Number NaN
+PASS Number Infinity
+PASS Number -Infinity
+PASS Number 9007199254740992
+PASS Number -9007199254740992
+PASS Number 9007199254740994
+PASS Number -9007199254740994
+PASS BigInt -9007199254740994n
+PASS Array Number objects
+PASS Object Number objects
+PASS Date 0
+PASS Date -0
+PASS Date -8.64e15
+PASS Date 8.64e15
+PASS Array Date objects
+PASS Object Date objects
+PASS RegExp flags and lastIndex
+PASS RegExp sticky flag
+PASS RegExp unicode flag
+PASS RegExp empty
+PASS RegExp slash
+PASS RegExp new line
+PASS Array RegExp object, RegExp flags and lastIndex
+PASS Array RegExp object, RegExp sticky flag
+PASS Array RegExp object, RegExp unicode flag
+PASS Array RegExp object, RegExp empty
+PASS Array RegExp object, RegExp slash
+PASS Array RegExp object, RegExp new line
+PASS Object RegExp object, RegExp flags and lastIndex
+PASS Object RegExp object, RegExp sticky flag
+PASS Object RegExp object, RegExp unicode flag
+PASS Object RegExp object, RegExp empty
+PASS Object RegExp object, RegExp slash
+PASS Object RegExp object, RegExp new line
+PASS Empty Error object
+PASS Error object
+PASS EvalError object
+PASS RangeError object
+PASS ReferenceError object
+PASS SyntaxError object
+PASS TypeError object
+PASS URIError object
+PASS Blob basic
+PASS Blob unpaired high surrogate (invalid utf-8)
+PASS Blob unpaired low surrogate (invalid utf-8)
+PASS Blob paired surrogates (invalid utf-8)
+PASS Blob empty
+PASS Blob NUL
+PASS Array Blob object, Blob basic
+PASS Array Blob object, Blob unpaired high surrogate (invalid utf-8)
+PASS Array Blob object, Blob unpaired low surrogate (invalid utf-8)
+PASS Array Blob object, Blob paired surrogates (invalid utf-8)
+PASS Array Blob object, Blob empty
+PASS Array Blob object, Blob NUL
+PASS Array Blob object, two Blobs
+PASS Object Blob object, Blob basic
+PASS Object Blob object, Blob unpaired high surrogate (invalid utf-8)
+PASS Object Blob object, Blob unpaired low surrogate (invalid utf-8)
+PASS Object Blob object, Blob paired surrogates (invalid utf-8)
+PASS Object Blob object, Blob empty
+PASS Object Blob object, Blob NUL
+PASS File basic
+PASS FileList empty
+PASS Array FileList object, FileList empty
+PASS Object FileList object, FileList empty
+PASS ImageData 1x1 transparent black
+PASS ImageData 1x1 non-transparent non-black
+PASS Array ImageData object, ImageData 1x1 transparent black
+PASS Array ImageData object, ImageData 1x1 non-transparent non-black
+PASS Object ImageData object, ImageData 1x1 transparent black
+PASS Object ImageData object, ImageData 1x1 non-transparent non-black
+PASS Array sparse
+PASS Array with non-index property
+PASS Object with index property and length
+PASS Array with circular reference
+PASS Object with circular reference
+PASS Array with identical property values
+PASS Object with identical property values
+PASS Object with property on prototype
+PASS Object with non-enumerable property
+PASS Object with non-writable property
+PASS Object with non-configurable property
+PASS Object with a getter that throws
+PASS ImageBitmap 1x1 transparent black
+PASS ImageBitmap 1x1 non-transparent non-black
+PASS Array ImageBitmap object, ImageBitmap 1x1 transparent black
+PASS Array ImageBitmap object, ImageBitmap 1x1 transparent non-black
+PASS Object ImageBitmap object, ImageBitmap 1x1 transparent black
+PASS Object ImageBitmap object, ImageBitmap 1x1 transparent non-black
+PASS ObjectPrototype must lose its exotic-ness when cloned
+PASS Serializing a non-serializable platform object fails
+PASS An object whose interface is deleted from the global must still deserialize
+PASS A subclass instance will deserialize as its closest serializable superclass
+PASS Resizable ArrayBuffer
+PASS Growable SharedArrayBuffer
+PASS Length-tracking TypedArray
+PASS Length-tracking DataView
+PASS Serializing OOB TypedArray throws
+PASS Serializing OOB DataView throws
+PASS ArrayBuffer
+PASS MessagePort
+PASS A detached ArrayBuffer cannot be transferred
+PASS A detached platform object cannot be transferred
+PASS Transferring a non-transferable platform object fails
+PASS An object whose interface is deleted from the global object must still be received
+PASS A subclass instance will be received as its closest transferable superclass
+PASS Resizable ArrayBuffer is transferable
+PASS Length-tracking TypedArray is transferable
+PASS Length-tracking DataView is transferable
+PASS Transferring OOB TypedArray throws
+PASS Transferring OOB DataView throws
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/virtual/js-resizable-arraybuffer/external/wpt/html/webappapis/structured-clone/structured-clone.any.worker-expected.txt b/third_party/blink/web_tests/virtual/js-resizable-arraybuffer/external/wpt/html/webappapis/structured-clone/structured-clone.any.worker-expected.txt
new file mode 100644
index 0000000..77eb14338
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/js-resizable-arraybuffer/external/wpt/html/webappapis/structured-clone/structured-clone.any.worker-expected.txt
@@ -0,0 +1,138 @@
+This is a testharness.js-based test.
+PASS primitive undefined
+PASS primitive null
+PASS primitive true
+PASS primitive false
+PASS primitive string, empty string
+PASS primitive string, lone high surrogate
+PASS primitive string, lone low surrogate
+PASS primitive string, NUL
+PASS primitive string, astral character
+PASS primitive number, 0.2
+PASS primitive number, 0
+PASS primitive number, -0
+PASS primitive number, NaN
+PASS primitive number, Infinity
+PASS primitive number, -Infinity
+PASS primitive number, 9007199254740992
+PASS primitive number, -9007199254740992
+PASS primitive number, 9007199254740994
+PASS primitive number, -9007199254740994
+PASS primitive BigInt, 0n
+PASS primitive BigInt, -0n
+PASS primitive BigInt, -9007199254740994000n
+PASS primitive BigInt, -9007199254740994000900719925474099400090071992547409940009007199254740994000n
+PASS Array primitives
+PASS Object primitives
+PASS Boolean true
+PASS Boolean false
+PASS Array Boolean objects
+PASS Object Boolean objects
+PASS String empty string
+PASS String lone high surrogate
+PASS String lone low surrogate
+PASS String NUL
+PASS String astral character
+PASS Array String objects
+PASS Object String objects
+PASS Number 0.2
+PASS Number 0
+PASS Number -0
+PASS Number NaN
+PASS Number Infinity
+PASS Number -Infinity
+PASS Number 9007199254740992
+PASS Number -9007199254740992
+PASS Number 9007199254740994
+PASS Number -9007199254740994
+PASS BigInt -9007199254740994n
+PASS Array Number objects
+PASS Object Number objects
+PASS Date 0
+PASS Date -0
+PASS Date -8.64e15
+PASS Date 8.64e15
+PASS Array Date objects
+PASS Object Date objects
+PASS RegExp flags and lastIndex
+PASS RegExp sticky flag
+PASS RegExp unicode flag
+PASS RegExp empty
+PASS RegExp slash
+PASS RegExp new line
+PASS Array RegExp object, RegExp flags and lastIndex
+PASS Array RegExp object, RegExp sticky flag
+PASS Array RegExp object, RegExp unicode flag
+PASS Array RegExp object, RegExp empty
+PASS Array RegExp object, RegExp slash
+PASS Array RegExp object, RegExp new line
+PASS Object RegExp object, RegExp flags and lastIndex
+PASS Object RegExp object, RegExp sticky flag
+PASS Object RegExp object, RegExp unicode flag
+PASS Object RegExp object, RegExp empty
+PASS Object RegExp object, RegExp slash
+PASS Object RegExp object, RegExp new line
+PASS Empty Error object
+PASS Error object
+PASS EvalError object
+PASS RangeError object
+PASS ReferenceError object
+PASS SyntaxError object
+PASS TypeError object
+PASS URIError object
+PASS Blob basic
+PASS Blob unpaired high surrogate (invalid utf-8)
+PASS Blob unpaired low surrogate (invalid utf-8)
+PASS Blob paired surrogates (invalid utf-8)
+PASS Blob empty
+PASS Blob NUL
+PASS Array Blob object, Blob basic
+PASS Array Blob object, Blob unpaired high surrogate (invalid utf-8)
+PASS Array Blob object, Blob unpaired low surrogate (invalid utf-8)
+PASS Array Blob object, Blob paired surrogates (invalid utf-8)
+PASS Array Blob object, Blob empty
+PASS Array Blob object, Blob NUL
+PASS Array Blob object, two Blobs
+PASS Object Blob object, Blob basic
+PASS Object Blob object, Blob unpaired high surrogate (invalid utf-8)
+PASS Object Blob object, Blob unpaired low surrogate (invalid utf-8)
+PASS Object Blob object, Blob paired surrogates (invalid utf-8)
+PASS Object Blob object, Blob empty
+PASS Object Blob object, Blob NUL
+PASS File basic
+PASS Array sparse
+PASS Array with non-index property
+PASS Object with index property and length
+PASS Array with circular reference
+PASS Object with circular reference
+PASS Array with identical property values
+PASS Object with identical property values
+PASS Object with property on prototype
+PASS Object with non-enumerable property
+PASS Object with non-writable property
+PASS Object with non-configurable property
+PASS Object with a getter that throws
+PASS ObjectPrototype must lose its exotic-ness when cloned
+PASS Serializing a non-serializable platform object fails
+PASS An object whose interface is deleted from the global must still deserialize
+PASS A subclass instance will deserialize as its closest serializable superclass
+PASS Resizable ArrayBuffer
+PASS Growable SharedArrayBuffer
+PASS Length-tracking TypedArray
+PASS Length-tracking DataView
+PASS Serializing OOB TypedArray throws
+PASS Serializing OOB DataView throws
+PASS ArrayBuffer
+PASS MessagePort
+PASS A detached ArrayBuffer cannot be transferred
+PASS A detached platform object cannot be transferred
+PASS Transferring a non-transferable platform object fails
+PASS An object whose interface is deleted from the global object must still be received
+PASS A subclass instance will be received as its closest transferable superclass
+PASS Resizable ArrayBuffer is transferable
+PASS Length-tracking TypedArray is transferable
+PASS Length-tracking DataView is transferable
+PASS Transferring OOB TypedArray throws
+PASS Transferring OOB DataView throws
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/virtual/js-resizable-arraybuffer/external/wpt/webidl/ecmascript-binding/README.txt b/third_party/blink/web_tests/virtual/js-resizable-arraybuffer/external/wpt/webidl/ecmascript-binding/README.txt
new file mode 100644
index 0000000..3eaae58
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/js-resizable-arraybuffer/external/wpt/webidl/ecmascript-binding/README.txt
@@ -0,0 +1 @@
+This virtual test suite will be removed once resizable ArrayBuffers ship in V8.
diff --git a/third_party/blink/web_tests/virtual/js-resizable-arraybuffer/external/wpt/webidl/ecmascript-binding/allow-resizable-expected.txt b/third_party/blink/web_tests/virtual/js-resizable-arraybuffer/external/wpt/webidl/ecmascript-binding/allow-resizable-expected.txt
new file mode 100644
index 0000000..3db7e63
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/js-resizable-arraybuffer/external/wpt/webidl/ecmascript-binding/allow-resizable-expected.txt
@@ -0,0 +1,5 @@
+This is a testharness.js-based test.
+PASS APIs without [AllowResizable] throw when passed resizable ArrayBuffers
+FAIL APIs with [AllowShared] but without [AllowResizable] throw when passed growable SharedArrayBuffers Failed to execute 'encodeInto' on 'TextEncoder': The provided Uint8Array value must not be shared.
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/virtual/js-resizable-arraybuffer/external/wpt/workers/semantics/structured-clone/README.txt b/third_party/blink/web_tests/virtual/js-resizable-arraybuffer/external/wpt/workers/semantics/structured-clone/README.txt
new file mode 100644
index 0000000..3eaae58
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/js-resizable-arraybuffer/external/wpt/workers/semantics/structured-clone/README.txt
@@ -0,0 +1 @@
+This virtual test suite will be removed once resizable ArrayBuffers ship in V8.
diff --git a/third_party/blink/web_tests/virtual/js-resizable-arraybuffer/external/wpt/workers/semantics/structured-clone/dedicated-expected.txt b/third_party/blink/web_tests/virtual/js-resizable-arraybuffer/external/wpt/workers/semantics/structured-clone/dedicated-expected.txt
new file mode 100644
index 0000000..5ccf001
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/js-resizable-arraybuffer/external/wpt/workers/semantics/structured-clone/dedicated-expected.txt
@@ -0,0 +1,153 @@
+This is a testharness.js-based test.
+PASS primitive undefined
+PASS primitive null
+PASS primitive true
+PASS primitive false
+PASS primitive string, empty string
+PASS primitive string, lone high surrogate
+PASS primitive string, lone low surrogate
+PASS primitive string, NUL
+PASS primitive string, astral character
+PASS primitive number, 0.2
+PASS primitive number, 0
+PASS primitive number, -0
+PASS primitive number, NaN
+PASS primitive number, Infinity
+PASS primitive number, -Infinity
+PASS primitive number, 9007199254740992
+PASS primitive number, -9007199254740992
+PASS primitive number, 9007199254740994
+PASS primitive number, -9007199254740994
+PASS primitive BigInt, 0n
+PASS primitive BigInt, -0n
+PASS primitive BigInt, -9007199254740994000n
+PASS primitive BigInt, -9007199254740994000900719925474099400090071992547409940009007199254740994000n
+PASS Array primitives
+PASS Object primitives
+PASS Boolean true
+PASS Boolean false
+PASS Array Boolean objects
+PASS Object Boolean objects
+PASS String empty string
+PASS String lone high surrogate
+PASS String lone low surrogate
+PASS String NUL
+PASS String astral character
+PASS Array String objects
+PASS Object String objects
+PASS Number 0.2
+PASS Number 0
+PASS Number -0
+PASS Number NaN
+PASS Number Infinity
+PASS Number -Infinity
+PASS Number 9007199254740992
+PASS Number -9007199254740992
+PASS Number 9007199254740994
+PASS Number -9007199254740994
+PASS BigInt -9007199254740994n
+PASS Array Number objects
+PASS Object Number objects
+PASS Date 0
+PASS Date -0
+PASS Date -8.64e15
+PASS Date 8.64e15
+PASS Array Date objects
+PASS Object Date objects
+PASS RegExp flags and lastIndex
+PASS RegExp sticky flag
+PASS RegExp unicode flag
+PASS RegExp empty
+PASS RegExp slash
+PASS RegExp new line
+PASS Array RegExp object, RegExp flags and lastIndex
+PASS Array RegExp object, RegExp sticky flag
+PASS Array RegExp object, RegExp unicode flag
+PASS Array RegExp object, RegExp empty
+PASS Array RegExp object, RegExp slash
+PASS Array RegExp object, RegExp new line
+PASS Object RegExp object, RegExp flags and lastIndex
+PASS Object RegExp object, RegExp sticky flag
+PASS Object RegExp object, RegExp unicode flag
+PASS Object RegExp object, RegExp empty
+PASS Object RegExp object, RegExp slash
+PASS Object RegExp object, RegExp new line
+PASS Empty Error object
+PASS Error object
+PASS EvalError object
+PASS RangeError object
+PASS ReferenceError object
+PASS SyntaxError object
+PASS TypeError object
+PASS URIError object
+PASS Blob basic
+PASS Blob unpaired high surrogate (invalid utf-8)
+PASS Blob unpaired low surrogate (invalid utf-8)
+PASS Blob paired surrogates (invalid utf-8)
+PASS Blob empty
+PASS Blob NUL
+PASS Array Blob object, Blob basic
+PASS Array Blob object, Blob unpaired high surrogate (invalid utf-8)
+PASS Array Blob object, Blob unpaired low surrogate (invalid utf-8)
+PASS Array Blob object, Blob paired surrogates (invalid utf-8)
+PASS Array Blob object, Blob empty
+PASS Array Blob object, Blob NUL
+PASS Array Blob object, two Blobs
+PASS Object Blob object, Blob basic
+PASS Object Blob object, Blob unpaired high surrogate (invalid utf-8)
+PASS Object Blob object, Blob unpaired low surrogate (invalid utf-8)
+PASS Object Blob object, Blob paired surrogates (invalid utf-8)
+PASS Object Blob object, Blob empty
+PASS Object Blob object, Blob NUL
+PASS File basic
+PASS FileList empty
+PASS Array FileList object, FileList empty
+PASS Object FileList object, FileList empty
+PASS ImageData 1x1 transparent black
+PASS ImageData 1x1 non-transparent non-black
+PASS Array ImageData object, ImageData 1x1 transparent black
+PASS Array ImageData object, ImageData 1x1 non-transparent non-black
+PASS Object ImageData object, ImageData 1x1 transparent black
+PASS Object ImageData object, ImageData 1x1 non-transparent non-black
+PASS Array sparse
+PASS Array with non-index property
+PASS Object with index property and length
+PASS Array with circular reference
+PASS Object with circular reference
+PASS Array with identical property values
+PASS Object with identical property values
+PASS Object with property on prototype
+PASS Object with non-enumerable property
+PASS Object with non-writable property
+PASS Object with non-configurable property
+PASS Object with a getter that throws
+PASS ImageBitmap 1x1 transparent black
+PASS ImageBitmap 1x1 non-transparent non-black
+PASS Array ImageBitmap object, ImageBitmap 1x1 transparent black
+PASS Array ImageBitmap object, ImageBitmap 1x1 transparent non-black
+PASS Object ImageBitmap object, ImageBitmap 1x1 transparent black
+PASS Object ImageBitmap object, ImageBitmap 1x1 transparent non-black
+PASS ObjectPrototype must lose its exotic-ness when cloned
+PASS Serializing a non-serializable platform object fails
+PASS An object whose interface is deleted from the global must still deserialize
+PASS A subclass instance will deserialize as its closest serializable superclass
+PASS Resizable ArrayBuffer
+PASS Growable SharedArrayBuffer
+PASS Length-tracking TypedArray
+PASS Length-tracking DataView
+PASS Serializing OOB TypedArray throws
+PASS Serializing OOB DataView throws
+PASS ArrayBuffer
+PASS MessagePort
+PASS A detached ArrayBuffer cannot be transferred
+PASS A detached platform object cannot be transferred
+PASS Transferring a non-transferable platform object fails
+PASS An object whose interface is deleted from the global object must still be received
+PASS A subclass instance will be received as its closest transferable superclass
+PASS Resizable ArrayBuffer is transferable
+PASS Length-tracking TypedArray is transferable
+PASS Length-tracking DataView is transferable
+PASS Transferring OOB TypedArray throws
+PASS Transferring OOB DataView throws
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/virtual/js-resizable-arraybuffer/external/wpt/workers/semantics/structured-clone/shared-expected.txt b/third_party/blink/web_tests/virtual/js-resizable-arraybuffer/external/wpt/workers/semantics/structured-clone/shared-expected.txt
new file mode 100644
index 0000000..5ccf001
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/js-resizable-arraybuffer/external/wpt/workers/semantics/structured-clone/shared-expected.txt
@@ -0,0 +1,153 @@
+This is a testharness.js-based test.
+PASS primitive undefined
+PASS primitive null
+PASS primitive true
+PASS primitive false
+PASS primitive string, empty string
+PASS primitive string, lone high surrogate
+PASS primitive string, lone low surrogate
+PASS primitive string, NUL
+PASS primitive string, astral character
+PASS primitive number, 0.2
+PASS primitive number, 0
+PASS primitive number, -0
+PASS primitive number, NaN
+PASS primitive number, Infinity
+PASS primitive number, -Infinity
+PASS primitive number, 9007199254740992
+PASS primitive number, -9007199254740992
+PASS primitive number, 9007199254740994
+PASS primitive number, -9007199254740994
+PASS primitive BigInt, 0n
+PASS primitive BigInt, -0n
+PASS primitive BigInt, -9007199254740994000n
+PASS primitive BigInt, -9007199254740994000900719925474099400090071992547409940009007199254740994000n
+PASS Array primitives
+PASS Object primitives
+PASS Boolean true
+PASS Boolean false
+PASS Array Boolean objects
+PASS Object Boolean objects
+PASS String empty string
+PASS String lone high surrogate
+PASS String lone low surrogate
+PASS String NUL
+PASS String astral character
+PASS Array String objects
+PASS Object String objects
+PASS Number 0.2
+PASS Number 0
+PASS Number -0
+PASS Number NaN
+PASS Number Infinity
+PASS Number -Infinity
+PASS Number 9007199254740992
+PASS Number -9007199254740992
+PASS Number 9007199254740994
+PASS Number -9007199254740994
+PASS BigInt -9007199254740994n
+PASS Array Number objects
+PASS Object Number objects
+PASS Date 0
+PASS Date -0
+PASS Date -8.64e15
+PASS Date 8.64e15
+PASS Array Date objects
+PASS Object Date objects
+PASS RegExp flags and lastIndex
+PASS RegExp sticky flag
+PASS RegExp unicode flag
+PASS RegExp empty
+PASS RegExp slash
+PASS RegExp new line
+PASS Array RegExp object, RegExp flags and lastIndex
+PASS Array RegExp object, RegExp sticky flag
+PASS Array RegExp object, RegExp unicode flag
+PASS Array RegExp object, RegExp empty
+PASS Array RegExp object, RegExp slash
+PASS Array RegExp object, RegExp new line
+PASS Object RegExp object, RegExp flags and lastIndex
+PASS Object RegExp object, RegExp sticky flag
+PASS Object RegExp object, RegExp unicode flag
+PASS Object RegExp object, RegExp empty
+PASS Object RegExp object, RegExp slash
+PASS Object RegExp object, RegExp new line
+PASS Empty Error object
+PASS Error object
+PASS EvalError object
+PASS RangeError object
+PASS ReferenceError object
+PASS SyntaxError object
+PASS TypeError object
+PASS URIError object
+PASS Blob basic
+PASS Blob unpaired high surrogate (invalid utf-8)
+PASS Blob unpaired low surrogate (invalid utf-8)
+PASS Blob paired surrogates (invalid utf-8)
+PASS Blob empty
+PASS Blob NUL
+PASS Array Blob object, Blob basic
+PASS Array Blob object, Blob unpaired high surrogate (invalid utf-8)
+PASS Array Blob object, Blob unpaired low surrogate (invalid utf-8)
+PASS Array Blob object, Blob paired surrogates (invalid utf-8)
+PASS Array Blob object, Blob empty
+PASS Array Blob object, Blob NUL
+PASS Array Blob object, two Blobs
+PASS Object Blob object, Blob basic
+PASS Object Blob object, Blob unpaired high surrogate (invalid utf-8)
+PASS Object Blob object, Blob unpaired low surrogate (invalid utf-8)
+PASS Object Blob object, Blob paired surrogates (invalid utf-8)
+PASS Object Blob object, Blob empty
+PASS Object Blob object, Blob NUL
+PASS File basic
+PASS FileList empty
+PASS Array FileList object, FileList empty
+PASS Object FileList object, FileList empty
+PASS ImageData 1x1 transparent black
+PASS ImageData 1x1 non-transparent non-black
+PASS Array ImageData object, ImageData 1x1 transparent black
+PASS Array ImageData object, ImageData 1x1 non-transparent non-black
+PASS Object ImageData object, ImageData 1x1 transparent black
+PASS Object ImageData object, ImageData 1x1 non-transparent non-black
+PASS Array sparse
+PASS Array with non-index property
+PASS Object with index property and length
+PASS Array with circular reference
+PASS Object with circular reference
+PASS Array with identical property values
+PASS Object with identical property values
+PASS Object with property on prototype
+PASS Object with non-enumerable property
+PASS Object with non-writable property
+PASS Object with non-configurable property
+PASS Object with a getter that throws
+PASS ImageBitmap 1x1 transparent black
+PASS ImageBitmap 1x1 non-transparent non-black
+PASS Array ImageBitmap object, ImageBitmap 1x1 transparent black
+PASS Array ImageBitmap object, ImageBitmap 1x1 transparent non-black
+PASS Object ImageBitmap object, ImageBitmap 1x1 transparent black
+PASS Object ImageBitmap object, ImageBitmap 1x1 transparent non-black
+PASS ObjectPrototype must lose its exotic-ness when cloned
+PASS Serializing a non-serializable platform object fails
+PASS An object whose interface is deleted from the global must still deserialize
+PASS A subclass instance will deserialize as its closest serializable superclass
+PASS Resizable ArrayBuffer
+PASS Growable SharedArrayBuffer
+PASS Length-tracking TypedArray
+PASS Length-tracking DataView
+PASS Serializing OOB TypedArray throws
+PASS Serializing OOB DataView throws
+PASS ArrayBuffer
+PASS MessagePort
+PASS A detached ArrayBuffer cannot be transferred
+PASS A detached platform object cannot be transferred
+PASS Transferring a non-transferable platform object fails
+PASS An object whose interface is deleted from the global object must still be received
+PASS A subclass instance will be received as its closest transferable superclass
+PASS Resizable ArrayBuffer is transferable
+PASS Length-tracking TypedArray is transferable
+PASS Length-tracking DataView is transferable
+PASS Transferring OOB TypedArray throws
+PASS Transferring OOB DataView throws
+Harness: the test ran to completion.
+
diff --git a/third_party/wpt_tools/README.chromium b/third_party/wpt_tools/README.chromium
index cb933a2..55308376 100644
--- a/third_party/wpt_tools/README.chromium
+++ b/third_party/wpt_tools/README.chromium
@@ -1,7 +1,7 @@
 Name: web-platform-tests - Test Suites for Web Platform specifications
 Short Name: wpt
 URL: https://github.com/web-platform-tests/wpt/
-Version: 4f5b32625ce798e1404df06ddd75677e01c7fdbb
+Version: c30037b8fc2efe7fc7a4c0bba20d274fdbde5537
 License: LICENSES FOR W3C TEST SUITES (https://www.w3.org/Consortium/Legal/2008/03-bsd-license.html)
 License File: NOT_SHIPPED
 Security Critical: no
diff --git a/third_party/wpt_tools/wpt/tools/webdriver/webdriver/bidi/error.py b/third_party/wpt_tools/wpt/tools/webdriver/webdriver/bidi/error.py
index bb81b426c..9e8737e 100644
--- a/third_party/wpt_tools/wpt/tools/webdriver/webdriver/bidi/error.py
+++ b/third_party/wpt_tools/wpt/tools/webdriver/webdriver/bidi/error.py
@@ -11,10 +11,9 @@
     # TODO: Match on error and let it be a class variables only.
     error_code = None  # type: ClassVar[str]
 
-    def __init__(self, error: str, message: str, stacktrace: Optional[str]):
+    def __init__(self, message: str, stacktrace: Optional[str] = None):
         super()
 
-        self.error = error
         self.message = message
         self.stacktrace = stacktrace
 
@@ -24,9 +23,9 @@
 
     def __str__(self):
         """Return the string representation of the object."""
-        message = f"{self.error} ({self.message})"
+        message = f"{self.error_code} ({self.message})"
 
-        if self.stacktrace:
+        if self.stacktrace is not None:
             message += f"\n\nRemote-end stacktrace:\n\n{self.stacktrace}"
 
         return message
@@ -54,7 +53,7 @@
     Defaults to ``UnknownErrorException`` if `error` is unknown.
     """
     cls = get(error)
-    return cls(error, message, stacktrace)
+    return cls(message, stacktrace)
 
 
 def get(error_code: str) -> Type[BidiException]:
diff --git a/third_party/wpt_tools/wpt/tools/webdriver/webdriver/bidi/modules/script.py b/third_party/wpt_tools/wpt/tools/webdriver/webdriver/bidi/modules/script.py
index 8bdbd30a..d9af11a 100644
--- a/third_party/wpt_tools/wpt/tools/webdriver/webdriver/bidi/modules/script.py
+++ b/third_party/wpt_tools/wpt/tools/webdriver/webdriver/bidi/modules/script.py
@@ -1,6 +1,7 @@
 from enum import Enum
-from typing import Any, Optional, Mapping, List, MutableMapping, Union, Dict
+from typing import Any, Dict, List, Mapping, MutableMapping, Optional, Union
 
+from ..error import UnknownErrorException
 from ._module import BidiModule, command
 
 
@@ -69,9 +70,14 @@
 
     @call_function.result
     def _call_function(self, result: Mapping[str, Any]) -> Any:
-        if "result" not in result:
+        assert "type" in result
+
+        if result["type"] == "success":
+            return result["result"]
+        elif result["type"] == "exception":
             raise ScriptEvaluateResultException(result)
-        return result["result"]
+        else:
+            raise UnknownErrorException(f"""Invalid type '{result["type"]}' in response""")
 
     @command
     def disown(self, handles: List[str], target: Target) -> Mapping[str, Any]:
@@ -98,9 +104,14 @@
 
     @evaluate.result
     def _evaluate(self, result: Mapping[str, Any]) -> Any:
-        if "result" not in result:
+        assert "type" in result
+
+        if result["type"] == "success":
+            return result["result"]
+        elif result["type"] == "exception":
             raise ScriptEvaluateResultException(result)
-        return result["result"]
+        else:
+            raise UnknownErrorException(f"""Invalid type '{result["type"]}' in response""")
 
     @command
     def get_realms(
diff --git a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/executors/executorservo.py b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/executors/executorservo.py
index 0df90990..89aaf003 100644
--- a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/executors/executorservo.py
+++ b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/executors/executorservo.py
@@ -180,16 +180,18 @@
 
     def __init__(self, logger, browser, server_config, binary=None, timeout_multiplier=1,
                  screenshot_cache=None, debug_info=None, pause_after_test=False,
-                 **kwargs):
+                 reftest_screenshot="unexpected", **kwargs):
         ProcessTestExecutor.__init__(self,
                                      logger,
                                      browser,
                                      server_config,
                                      timeout_multiplier=timeout_multiplier,
-                                     debug_info=debug_info)
+                                     debug_info=debug_info,
+                                     reftest_screenshot=reftest_screenshot)
 
         self.protocol = ConnectionlessProtocol(self, browser)
         self.screenshot_cache = screenshot_cache
+        self.reftest_screenshot = reftest_screenshot
         self.implementation = RefTestImplementation(self)
         self.tempdir = tempfile.mkdtemp()
         self.hosts_path = write_hosts_file(server_config)
diff --git a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/stability.py b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/stability.py
index f1724d35..9ac6249c 100644
--- a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/stability.py
+++ b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/stability.py
@@ -357,7 +357,7 @@
                     output_results=True, **kwargs):
     kwargs_extras = [{}]
     if chaos_mode and kwargs["product"] == "firefox":
-        kwargs_extras.append({"chaos_mode_flags": "0xfb"})
+        kwargs_extras.append({"chaos_mode_flags": int("0xfb", base=16)})
 
     steps = get_steps(logger, repeat_loop, repeat_restart, kwargs_extras)
 
diff --git a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/testrunner.py b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/testrunner.py
index 61af18c..82ffc9b 100644
--- a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/testrunner.py
+++ b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/testrunner.py
@@ -333,9 +333,8 @@
         self.logger = None
 
         self.test_count = 0
-        self.unexpected_count = 0
-        self.unexpected_pass_count = 0
         self.unexpected_tests = set()
+        self.unexpected_pass_tests = set()
 
         # This may not really be what we want
         self.daemon = True
@@ -651,21 +650,22 @@
         # Write the result of each subtest
         file_result, test_results = results
         subtest_unexpected = False
+        subtest_all_pass_or_expected = True
         for result in test_results:
             if test.disabled(result.name):
                 continue
             expected = test.expected(result.name)
             known_intermittent = test.known_intermittent(result.name)
             is_unexpected = expected != result.status and result.status not in known_intermittent
+            is_expected_notrun = (expected == "NOTRUN" or "NOTRUN" in known_intermittent)
 
             if is_unexpected:
-                self.unexpected_count += 1
-                self.logger.debug("Unexpected count in this thread %i" % self.unexpected_count)
                 subtest_unexpected = True
 
-            is_unexpected_pass = is_unexpected and result.status == "PASS"
-            if is_unexpected_pass:
-                self.unexpected_pass_count += 1
+                if result.status != "PASS" and not is_expected_notrun:
+                    # Any result against an expected "NOTRUN" should be treated
+                    # as unexpected pass.
+                    subtest_all_pass_or_expected = False
 
             self.logger.test_status(test.id,
                                     result.name,
@@ -698,17 +698,19 @@
 
         self.test_count += 1
         is_unexpected = expected != status and status not in known_intermittent
-        if is_unexpected:
-            self.unexpected_count += 1
-            self.logger.debug("Unexpected count in this thread %i" % self.unexpected_count)
-
-        is_unexpected_pass = is_unexpected and status == "OK"
-        if is_unexpected_pass:
-            self.unexpected_pass_count += 1
 
         if is_unexpected or subtest_unexpected:
             self.unexpected_tests.add(test.id)
 
+        # A result is unexpected pass if the test or any subtest run
+        # unexpectedly, and the overall status is OK (for test harness test), or
+        # PASS (for reftest), and all unexpected results for subtests (if any) are
+        # unexpected pass.
+        is_unexpected_pass = ((is_unexpected or subtest_unexpected) and
+                              status in ["OK", "PASS"] and subtest_all_pass_or_expected)
+        if is_unexpected_pass:
+            self.unexpected_pass_tests.add(test.id)
+
         if "assertion_count" in file_result.extra:
             assertion_count = file_result.extra["assertion_count"]
             if assertion_count is not None and assertion_count > 0:
@@ -975,11 +977,8 @@
     def test_count(self):
         return sum(manager.test_count for manager in self.pool)
 
-    def unexpected_count(self):
-        return sum(manager.unexpected_count for manager in self.pool)
-
-    def unexpected_pass_count(self):
-        return sum(manager.unexpected_pass_count for manager in self.pool)
-
     def unexpected_tests(self):
         return set().union(*(manager.unexpected_tests for manager in self.pool))
+
+    def unexpected_pass_tests(self):
+        return set().union(*(manager.unexpected_pass_tests for manager in self.pool))
diff --git a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/wptcommandline.py b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/wptcommandline.py
index eec0130..2fe6586 100644
--- a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/wptcommandline.py
+++ b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/wptcommandline.py
@@ -184,11 +184,9 @@
     debugging_group.add_argument("--repeat-until-unexpected", action="store_true", default=None,
                                  help="Run tests in a loop until one returns an unexpected result")
     debugging_group.add_argument('--retry-unexpected', type=int, default=0,
-                                 help=('Maximum number of times to retry '
-                                       'each test that consistently runs '
-                                       'unexpectedly in the initial repeat '
-                                       'loop. A retried test takes any '
-                                       'expected status as its final result.'))
+                                 help=('Maximum number of times to retry unexpected tests. '
+                                       'A test is retried until it gets one of the expected status, '
+                                       'or until it exhausts the maximum number of retries.'))
     debugging_group.add_argument('--pause-after-test', action="store_true", default=None,
                                  help="Halt the test runner after each test (this happens by default if only a single test is run)")
     debugging_group.add_argument('--no-pause-after-test', dest="pause_after_test", action="store_false",
diff --git a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/wptrunner.py b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/wptrunner.py
index e879925..548d5bf 100644
--- a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/wptrunner.py
+++ b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/wptrunner.py
@@ -158,7 +158,7 @@
 def run_test_iteration(test_status, test_loader, test_source_kwargs, test_source_cls, run_info,
                        recording, test_environment, product, run_test_kwargs):
     """Runs the entire test suite.
-    This is called for each repeat or retry run requested."""
+    This is called for each repeat run requested."""
     tests_by_type = defaultdict(list)
     for test_type in test_loader.test_types:
         tests_by_type[test_type].extend(test_loader.tests[test_type])
@@ -170,12 +170,7 @@
         logger.critical("Loading tests failed")
         return False
 
-    if test_status.retries_remaining:
-        for test_type, tests in dict(test_groups).items():
-            test_groups[test_type] = [test for test in tests
-                                      if test in test_status.unexpected_tests]
-
-    logger.suite_start(test_groups,
+    logger.suite_start(tests_by_type,
                        name='web-platform-test',
                        run_info=run_info,
                        extra={"run_by_dir": run_test_kwargs["run_by_dir"]})
@@ -226,38 +221,56 @@
         else:
             tests_to_run[test_type] = test_loader.tests[test_type]
 
-        if test_status.retries_remaining:
-            tests_to_run[test_type] = [test for test in tests_to_run[test_type]
-                                       if test.id in test_status.unexpected_tests]
-
+    unexpected_tests = set()
+    unexpected_pass_tests = set()
     recording.pause()
-    with ManagerGroup("web-platform-tests",
-                      run_test_kwargs["processes"],
-                      test_source_cls,
-                      test_source_kwargs,
-                      test_implementation_by_type,
-                      run_test_kwargs["rerun"],
-                      run_test_kwargs["pause_after_test"],
-                      run_test_kwargs["pause_on_unexpected"],
-                      run_test_kwargs["restart_on_unexpected"],
-                      run_test_kwargs["debug_info"],
-                      not run_test_kwargs["no_capture_stdio"],
-                      run_test_kwargs["restart_on_new_group"],
-                      recording=recording) as manager_group:
-        try:
-            manager_group.run(tests_to_run)
-        except KeyboardInterrupt:
-            logger.critical("Main thread got signal")
-            manager_group.stop()
-            raise
-        test_status.total_tests += manager_group.test_count()
-        test_status.unexpected += manager_group.unexpected_count()
-        test_status.unexpected_pass += manager_group.unexpected_pass_count()
+    retry_counts = run_test_kwargs["retry_unexpected"]
+    for i in range(retry_counts + 1):
+        if i > 0:
+            if not run_test_kwargs["fail_on_unexpected_pass"]:
+                unexpected_fail_tests = unexpected_tests - unexpected_pass_tests
+            else:
+                unexpected_fail_tests = unexpected_tests
+            if len(unexpected_fail_tests) == 0:
+                break
+            for test_type, tests in tests_to_run.items():
+                tests_to_run[test_type] = [test for test in tests
+                                           if test.id in unexpected_fail_tests]
 
-    if test_status.repeated_runs == 1:
-        test_status.unexpected_tests = manager_group.unexpected_tests()
-    else:
-        test_status.unexpected_tests &= manager_group.unexpected_tests()
+            logger.suite_end()
+            logger.suite_start(tests_to_run,
+                               name='web-platform-test',
+                               run_info=run_info,
+                               extra={"run_by_dir": run_test_kwargs["run_by_dir"]})
+
+        with ManagerGroup("web-platform-tests",
+                          run_test_kwargs["processes"],
+                          test_source_cls,
+                          test_source_kwargs,
+                          test_implementation_by_type,
+                          run_test_kwargs["rerun"],
+                          run_test_kwargs["pause_after_test"],
+                          run_test_kwargs["pause_on_unexpected"],
+                          run_test_kwargs["restart_on_unexpected"],
+                          run_test_kwargs["debug_info"],
+                          not run_test_kwargs["no_capture_stdio"],
+                          run_test_kwargs["restart_on_new_group"],
+                          recording=recording) as manager_group:
+            try:
+                manager_group.run(tests_to_run)
+            except KeyboardInterrupt:
+                logger.critical("Main thread got signal")
+                manager_group.stop()
+                raise
+
+            test_status.total_tests += manager_group.test_count()
+            unexpected_tests = manager_group.unexpected_tests()
+            unexpected_pass_tests = manager_group.unexpected_pass_tests()
+
+    test_status.unexpected += len(unexpected_tests)
+    test_status.unexpected_pass += len(unexpected_pass_tests)
+
+    logger.suite_end()
 
     return True
 
@@ -299,8 +312,6 @@
         self.repeated_runs = 0
         self.expected_repeated_runs = 0
         self.all_skipped = False
-        self.unexpected_tests = set()
-        self.retries_remaining = 0
 
 
 def run_tests(config, test_paths, product, **kwargs):
@@ -433,7 +444,6 @@
                 recording.set(["after-end"])
                 logger.info(f"Got {test_status.unexpected} unexpected results, "
                     f"with {test_status.unexpected_pass} unexpected passes")
-                logger.suite_end()
 
                 # Note this iteration's runtime
                 iteration_runtime = datetime.now() - iteration_start
@@ -447,44 +457,10 @@
                     test_status.all_skipped = True
                     break
 
-            if not test_status.all_skipped and kwargs["retry_unexpected"] > 0:
-                retry_success = retry_unexpected_tests(test_status, test_loader,
-                                                       test_source_kwargs,
-                                                       test_source_cls, run_info,
-                                                       recording, test_environment,
-                                                       product, kwargs)
-                if not retry_success:
-                    return False, test_status
-
     # Return the evaluation of the runs and the number of repeated iterations that were run.
     return evaluate_runs(test_status, kwargs), test_status
 
 
-def retry_unexpected_tests(test_status, test_loader, test_source_kwargs,
-                           test_source_cls, run_info, recording,
-                           test_environment, product, kwargs):
-    kwargs["rerun"] = 1
-    max_retries = kwargs["retry_unexpected"]
-    test_status.retries_remaining = max_retries
-    while (test_status.retries_remaining > 0 and not
-           evaluate_runs(test_status, kwargs)):
-        logger.info(f"Retry {max_retries - test_status.retries_remaining + 1}")
-        test_status.total_tests = 0
-        test_status.skipped = 0
-        test_status.unexpected = 0
-        test_status.unexpected_pass = 0
-        iter_success = run_test_iteration(test_status, test_loader,
-                                          test_source_kwargs, test_source_cls,
-                                          run_info, recording, test_environment,
-                                          product, kwargs)
-        if not iter_success:
-            return False
-        recording.set(["after-end"])
-        logger.suite_end()
-        test_status.retries_remaining -= 1
-    return True
-
-
 def check_stability(**kwargs):
     from . import stability
     if kwargs["stability"]:
diff --git a/tools/gritsettings/resource_ids.spec b/tools/gritsettings/resource_ids.spec
index e2a6ff6..dc85d1f 100644
--- a/tools/gritsettings/resource_ids.spec
+++ b/tools/gritsettings/resource_ids.spec
@@ -288,7 +288,7 @@
     "includes": [1925],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/side_panel/customize_chrome/resources.grd": {
-    "META": {"sizes": {"includes": [30],}},
+    "META": {"sizes": {"includes": [35],}},
     "includes": [1930],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/side_panel/side_panel_resources.grd": {
@@ -448,6 +448,10 @@
   "components/resources/dev_ui_components_resources.grd": {
     "includes": [2560],
   },
+  "<(SHARED_INTERMEDIATE_DIR)/content/browser/resources/attribution_reporting/resources.grd": {
+    "META": {"sizes": {"includes": [20]}},
+    "includes": [2565],
+  },
   "<(SHARED_INTERMEDIATE_DIR)/content/browser/resources/indexed_db/resources.grd": {
     "META": {"sizes": {"includes": [20]}},
     "includes": [2570],
diff --git a/tools/ipc_fuzzer/BUILD.gn b/tools/ipc_fuzzer/BUILD.gn
index 1901b2e..21ca130 100644
--- a/tools/ipc_fuzzer/BUILD.gn
+++ b/tools/ipc_fuzzer/BUILD.gn
@@ -13,10 +13,7 @@
   if (is_win) {
     cflags = [ "/wd4366" ]
   }
-  defines = [
-    "ENABLE_IPC_FUZZER",
-    "USE_CUPS",
-  ]
+  defines = [ "ENABLE_IPC_FUZZER" ]
   include_dirs = [ "." ]
 }
 
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 477ec05..3ebd28e 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -63166,6 +63166,7 @@
       label="OmniboxUIExperimentHideSuggestionUrlTrivialSubdomains:disabled"/>
   <int value="1387356699" label="PolicyAtomicGroup:enabled"/>
   <int value="1388726032" label="ChromeOSAmbientModeThrottleAnimation:enabled"/>
+  <int value="1389697465" label="OmniboxGroupingFramework:disabled"/>
   <int value="1389729816" label="data-reduction-proxy-lo-fi"/>
   <int value="1391313384" label="Hotspot:disabled"/>
   <int value="1392836587" label="DesktopPWAsElidedExtensionsMenu:disabled"/>
@@ -63647,6 +63648,7 @@
   <int value="1687272287" label="HandwritingLibraryDlc:disabled"/>
   <int value="1687544136" label="AndroidManagedByMenuItem:enabled"/>
   <int value="1687768359" label="ServiceWorkerBypassFetchHandler:disabled"/>
+  <int value="1687987218" label="OmniboxGroupingFramework:enabled"/>
   <int value="1688075820" label="OmniboxExperimentalKeywordMode:disabled"/>
   <int value="1689001971" label="ProjectorLocalPlayback:enabled"/>
   <int value="1689123607" label="enable-app-link"/>
@@ -74846,6 +74848,27 @@
   <int value="36" label="WorkerScriptLoader (Proxy)"/>
 </enum>
 
+<enum name="OobeMultideviceScreenSkippedReason">
+  <int value="0" label="Public session or ephermeral login"/>
+  <int value="1" label="Host phone already set"/>
+  <int value="2" label="Device sync finished and no eligible host phone found"/>
+  <int value="3" label="Setup client not initialized"/>
+  <int value="4"
+      label="Device sync not initialized during better together metadata
+             status fetch"/>
+  <int value="5"
+      label="Device sync not initialized during group private key status
+             fetch"/>
+  <int value="6" label="Encrypted metadata empty"/>
+  <int value="7" label="Waiting to receive group private key"/>
+  <int value="8" label="No encrypted group private key received"/>
+  <int value="9" label="Encrypted group private key empty"/>
+  <int value="10" label="Local device sync better together key missing"/>
+  <int value="11" label="Group private key decryption failed"/>
+  <int value="12" label="Screen destroyed before reason could be determined"/>
+  <int value="13" label="Unknown"/>
+</enum>
+
 <enum name="OobeUserClickTarget">
   <int value="0" label="Shut down button"/>
   <int value="1" label="Browse as guest button"/>
@@ -80184,10 +80207,10 @@
   <int value="1" label="COPY_TO_NV12">
     Copy to another NV12 texture that can be used in ANGLE.
   </int>
-  <int value="2" label="DELAYED_COPY_TO_NV12">
+  <int value="2" label="DELAYED_COPY_TO_NV12 (Deprecated)">
     Bind the resulting GLImage to the NV12 texture. If the texture's used in a
     an overlay than use it directly, otherwise copy it to another NV12 texture
-    when necessary.
+    when necessary. This enum is deprecated. See crbug.com/1401462.
   </int>
   <int value="3" label="BIND">
     Bind the NV12 decoder texture directly to the texture used in ANGLE.
diff --git a/tools/metrics/histograms/metadata/arc/histograms.xml b/tools/metrics/histograms/metadata/arc/histograms.xml
index 95adb14..dd99621 100644
--- a/tools/metrics/histograms/metadata/arc/histograms.xml
+++ b/tools/metrics/histograms/metadata/arc/histograms.xml
@@ -114,6 +114,13 @@
   <variant name=".Roblox" summary="Online game: Roblox"/>
 </variants>
 
+<variants name="ArcSyncAppTypes">
+  <variant name="Expected" summary="Apps expected to be synced"/>
+  <variant name="Installed" summary="Apps installed"/>
+  <variant name="NotInstalled"
+      summary="Apps that were expected but not installed"/>
+</variants>
+
 <variants name="ArcUserTypes">
   <variant name=".ActiveDirectory"
       summary="User with active directory account"/>
@@ -532,6 +539,31 @@
   </summary>
 </histogram>
 
+<histogram name="Arc.AppSync.InitialSession.Latency" units="seconds"
+    expires_after="2023-06-20">
+  <owner>batoon@google.com</owner>
+  <owner>arc-core@google.com</owner>
+  <summary>
+    Records the time from the start of app sync until the last finished
+    installation before the session ended. This is not the latency for total app
+    sync, since total app sync may occur over multiple sessions. Metrics are
+    emitted when a user logs off IFF it is the first session after opting in
+    during the sync consent screen.
+  </summary>
+</histogram>
+
+<histogram name="Arc.AppSync.InitialSession.NumApps{ArcSyncAppTypes}"
+    units="apps" expires_after="2023-06-20">
+  <owner>batoon@google.com</owner>
+  <owner>arc-core@google.com</owner>
+  <summary>
+    Records the count of {ArcSyncAppTypes} apps during the initial session for
+    app sync. Metrics are emitted when a user logs off IFF it is the first
+    session after opting in during the sync consent screen.
+  </summary>
+  <token key="ArcSyncAppTypes" variants="ArcSyncAppTypes"/>
+</histogram>
+
 <histogram name="Arc.AppUninstallReason" enum="UninstallCounterReasonEnum"
     expires_after="2023-06-04">
   <owner>thanhdng@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/ash/histograms.xml b/tools/metrics/histograms/metadata/ash/histograms.xml
index e529bb9..c5103e3 100644
--- a/tools/metrics/histograms/metadata/ash/histograms.xml
+++ b/tools/metrics/histograms/metadata/ash/histograms.xml
@@ -3703,6 +3703,21 @@
   </summary>
 </histogram>
 
+<histogram name="Ash.Personalization.Ambient.GooglePhotosPreviewsLoadTime"
+    units="ms" expires_after="2023-12-01">
+  <owner>cowmoo@google.com</owner>
+  <owner>assistive-eng@google.com</owner>
+  <summary>
+    Emitted when a request to load ambient mode google photos albums preview
+    images completes, but only upon first page load. This roughly corresponds to
+    user perceived time it takes to load the ambient preview section of
+    Personalization App. Does not emit if a user opens Personalization App onto
+    a page that does not load google photos albums previews. Does not emit
+    unless the user already has ambient mode enabled and Google Photos selected
+    as the source upon first opening Personalization App.
+  </summary>
+</histogram>
+
 <histogram name="Ash.Personalization.AmbientMode.AnimationTheme"
     enum="AmbientModeAnimationTheme" expires_after="2023-06-18">
   <owner>jasontt@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/extensions/histograms.xml b/tools/metrics/histograms/metadata/extensions/histograms.xml
index fa5dfb3..f08a5a84 100644
--- a/tools/metrics/histograms/metadata/extensions/histograms.xml
+++ b/tools/metrics/histograms/metadata/extensions/histograms.xml
@@ -3141,6 +3141,36 @@
   </summary>
 </histogram>
 
+<histogram
+    name="Extensions.WebRequest.BeforeRequestDeclarativeNetRequestEvaluationTime"
+    units="ms" expires_after="2023-12-01">
+  <owner>rdevlin.cronin@chromium.org</owner>
+  <owner>src/extensions/OWNERS</owner>
+  <summary>
+    The total amount of time taken to evaluate declarativeNetRequest rules in
+    the onBeforeRequest stage, measured in milliseconds. Recorded once per
+    request if and only if at least one declarativeNetRequest rule is present.
+  </summary>
+</histogram>
+
+<histogram
+    name="Extensions.WebRequest.BeforeRequestListenerEvaluationTime.{HandlerTypes}"
+    units="ms" expires_after="2023-12-01">
+  <owner>rdevlin.cronin@chromium.org</owner>
+  <owner>src/extensions/OWNERS</owner>
+  <summary>
+    The total amount of time, measured in milliseconds, between when an event is
+    dispatched to webRequest listeners and when all responses are received and
+    handled for an onBeforeRequest event. Emitted when there are {HandlerTypes}
+    for the request. Recorded once per request if the request completes.
+  </summary>
+  <token key="HandlerTypes">
+    <variant name="WebRequestAndDeclarativeNetRequest"
+        summary="both webRequest listeners and declarativeNetRequest rules"/>
+    <variant name="WebRequestOnly" summary="only webRequest listeners"/>
+  </token>
+</histogram>
+
 <histogram name="Extensions.WebRequest.EventListenerFlag"
     enum="WebRequestEventListenerFlag" expires_after="never">
 <!-- expires-never: For monitoring Web Request API usage statistics. -->
diff --git a/tools/metrics/histograms/metadata/fastpair/histograms.xml b/tools/metrics/histograms/metadata/fastpair/histograms.xml
index d9854a6..c1bf962 100644
--- a/tools/metrics/histograms/metadata/fastpair/histograms.xml
+++ b/tools/metrics/histograms/metadata/fastpair/histograms.xml
@@ -34,6 +34,30 @@
   </summary>
 </histogram>
 
+<histogram name="FastPair.Handshake.AttemptCount" units="count"
+    expires_after="2023-06-01">
+  <owner>akingsb@google.com</owner>
+  <owner>jackshira@google.com</owner>
+  <owner>chromeos-cross-device-eng@google.com</owner>
+  <summary>
+    Records the number of attempts needed to successfully create a handshake.
+    Currently the highest allowed attempt number is 3. Emitted following a
+    successful handshake. No metric is emitted on failure.
+  </summary>
+</histogram>
+
+<histogram name="FastPair.Handshake.EffectiveSuccessRate" enum="BooleanSuccess"
+    expires_after="2023-06-01">
+  <owner>akingsb@google.com</owner>
+  <owner>jackshira@google.com</owner>
+  <owner>chromeos-cross-device-eng@google.com</owner>
+  <summary>
+    Records the effective success rate of creating a handshake during the Fast
+    Pair pairing protocol. Emitted following the attempted creation of the
+    handshake and all retries.
+  </summary>
+</histogram>
+
 <histogram name="FastPair.InitialPairing"
     enum="FastPairInitialSuccessFunnelEvent" expires_after="2023-06-01">
   <owner>jackshira@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/oobe/histograms.xml b/tools/metrics/histograms/metadata/oobe/histograms.xml
index 7819143..563ce4d 100644
--- a/tools/metrics/histograms/metadata/oobe/histograms.xml
+++ b/tools/metrics/histograms/metadata/oobe/histograms.xml
@@ -616,6 +616,18 @@
   <token key="OOBEScreenName_ExitReason" variants="OOBEScreenName_ExitReason"/>
 </histogram>
 
+<histogram name="OOBE.StepShownStatus.Multidevice-setup-screen.Skipped"
+    enum="OobeMultideviceScreenSkippedReason" expires_after="2023-11-01">
+  <owner>bhartmire@google.com</owner>
+  <owner>chromeos-cross-device-eng@google.com</owner>
+  <summary>
+    The reason why the Multidevice setup screen is skipped in OOBE. Recorded
+    when determining that the screen should be skipped. This metric breaks down
+    the &quot;Not shown&quot; bucket of
+    &quot;OOBE.StepShownStatus.Multidevice-setup-screen&quot;.
+  </summary>
+</histogram>
+
 <histogram name="OOBE.StepShownStatus.{OOBELegacyScreenName}"
     enum="BooleanShown" expires_after="never">
 <!-- expires-never: Core metric for monitoring OOBE flow regressions. -->
diff --git a/tools/metrics/ukm/ukm.xml b/tools/metrics/ukm/ukm.xml
index 131d14429..b275854 100644
--- a/tools/metrics/ukm/ukm.xml
+++ b/tools/metrics/ukm/ukm.xml
@@ -513,6 +513,9 @@
 </event>
 
 <event name="AdPageLoadCustomSampling2" singular="True">
+  <obsolete>
+    Removed 2022/12 in favor of AdPageLoadCustomSampling3
+  </obsolete>
   <owner>yaoxia@chromium.org</owner>
   <owner>johnidel@chromium.org</owner>
   <owner>jkarlin@chromium.org</owner>
@@ -563,6 +566,58 @@
   </metric>
 </event>
 
+<event name="AdPageLoadCustomSampling3" singular="True">
+  <owner>yaoxia@chromium.org</owner>
+  <owner>johnidel@chromium.org</owner>
+  <owner>jkarlin@chromium.org</owner>
+  <summary>
+    Recorded when the page is being destroyed/navigated away or when the app
+    enters the background on mobile. This is separate from the `AdPageLoad`
+    event to allow it to be recorded on all pages (i.e. not only pages that have
+    loaded non-zero ad byte), and to allow a potentially different sampling.
+    Both iframe and img ads are covered.
+  </summary>
+  <metric name="AverageViewportAdDensity">
+    <summary>
+      The estimated average viewport ad density. Each density value is
+      calculated as the area of (iframe and img) ads within the viewport * 100 /
+      viewport area, where each overlapping area is counted once. And this
+      returns the average of the densities accumulated over the page load time
+      rounded to the nearest integer.
+    </summary>
+  </metric>
+  <metric name="KurtosisViewportAdDensity">
+    <summary>
+      The estimated kurtosis of the viewport ad density. Each density value is
+      calculated as the area of (iframe and img) ads within the viewport * 100 /
+      viewport area, where each overlapping area is counted once. And this
+      returns the kurtosis of the densities accumulated over the page load time
+      rounded to the nearest exponential bucket integer with bucket spacing 1.3.
+      For negative values, the bucketing will be taken on its magnitude part.
+    </summary>
+  </metric>
+  <metric name="SkewnessViewportAdDensity">
+    <summary>
+      The estimated skewness of the viewport ad density. Each density value is
+      calculated as the area of (iframe and img) ads within the viewport * 100 /
+      viewport area, where each overlapping area is counted once. And this
+      returns the skewness of the densities accumulated over the page load time
+      rounded to the nearest exponential bucket integer with bucket spacing 1.3.
+      For negative values, the bucketing will be taken on its magnitude part.
+    </summary>
+  </metric>
+  <metric name="VarianceViewportAdDensity">
+    <summary>
+      The estimated variance of the viewport ad density. Each density value is
+      calculated as the area of (iframe and img) ads within the viewport * 100 /
+      viewport area, where each overlapping area is counted once. And this
+      returns the variance of the densities accumulated over the page load time
+      rounded to the nearest exponential bucket integer with bucket spacing 1.3.
+      For negative values, the bucketing will be taken on its magnitude part.
+    </summary>
+  </metric>
+</event>
+
 <event name="AdsIntervention.LastIntervention" singular="True">
   <owner>yaoxia@google.com</owner>
   <owner>chrome-ads-core@google.com</owner>
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json
index 396de62..82e24c29 100644
--- a/tools/perf/core/perfetto_binary_roller/binary_deps.json
+++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -6,15 +6,15 @@
         },
         "win": {
             "hash": "0befcfb83f92421c85513c0d86328dd1fbffd5ba",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/b177a9b77d364faf675687204c9e48b8fd4d813c/trace_processor_shell.exe"
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/2e920f3a489071ad7c98f6c84eba83f259f107a2/trace_processor_shell.exe"
         },
         "linux_arm": {
             "hash": "6373f26144aad58f230d11d6a91efda5a09c9873",
             "full_remote_path": "perfetto-luci-artifacts/v31.0/linux-arm/trace_processor_shell"
         },
         "mac": {
-            "hash": "642e3e46788e22f52302b04aea6422f7810718e1",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/mac/b177a9b77d364faf675687204c9e48b8fd4d813c/trace_processor_shell"
+            "hash": "9ea16498678b255c71f01a9228448e6b812c6c2f",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/mac/2e920f3a489071ad7c98f6c84eba83f259f107a2/trace_processor_shell"
         },
         "mac_arm64": {
             "hash": "5f47ee79e59d00bf3889d30ca52315522c158040",
@@ -22,7 +22,7 @@
         },
         "linux": {
             "hash": "e84516277d57cf556b47707d89511acc85bd8aeb",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/b177a9b77d364faf675687204c9e48b8fd4d813c/trace_processor_shell"
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/2e920f3a489071ad7c98f6c84eba83f259f107a2/trace_processor_shell"
         }
     },
     "power_profile.sql": {
diff --git a/ui/accessibility/ax_tree.cc b/ui/accessibility/ax_tree.cc
index 8a9ea10..fe1993b 100644
--- a/ui/accessibility/ax_tree.cc
+++ b/ui/accessibility/ax_tree.cc
@@ -2245,31 +2245,30 @@
                                          node->id()));
         } else {
           // --- Begin temporary change ---
-          // TODO(crbug.com/1156601) Revert this once we have the crash data we
-          // need (crrev.com/c/2892259)
-          // Diagnose strange errors "Node 1 reparented from 0 to 2", which
-          // sounds like the root node is getting the <html> element as a parent
-          // -- in the normal case, the root is 1 and <html> is 2.
+          // TODO(crbug.com/1156601, crbug.com/1402673) Revert this once we have
+          // the crash data we need (crrev.com/c/2892259) Diagnose strange
+          // errors:
+          // Node did not have a previous parent, but reparenting error
+          // triggered:
+          // * New parent = id=3 rootWebArea FOCUSABLE
+          // * Child = id=1 rootWebArea (0, 0)-(0, 0) busy=true
           std::ostringstream error;
           error << "Node did not have a previous parent, but "
                    "reparenting error triggered:"
-                << "\n* Child = " << *child << "\n* New parent = " << *node
                 << "\n* root_will_be_created = "
                 << update_state->root_will_be_created
                 << "\n* pending_root_id = "
                 << (update_state->pending_root_id
                         ? *update_state->pending_root_id
                         : kInvalidAXNodeID)
-                << "\nTree update: "
-                << update_state->pending_tree_update->ToString(
-                       /*verbose*/ false);
-
-          // Add a crash key so we can figure out why this is happening.
-          static crash_reporter::CrashKeyString<256> ax_tree_error(
-              "ax_reparenting_error");
-          ax_tree_error.Set(error.str());
-          LOG(ERROR) << error.str();
-          CHECK(false);
+                << "\n* new parent = " << *node << "\n* Old parent = "
+                << (child->parent()
+                        ? child->parent()->data().ToString(/*verbose*/ false)
+                        : "-")
+                << "\n* child = " << *child;
+          // Crash with crash keys for debugging.
+          RecordError(*update_state, error.str());
+          CHECK(false) << error.str();
           // --- End temporary change ---
         }
         success = false;
diff --git a/ui/accessibility/ax_tree_serializer.h b/ui/accessibility/ax_tree_serializer.h
index d16eca17..b8324f1 100644
--- a/ui/accessibility/ax_tree_serializer.h
+++ b/ui/accessibility/ax_tree_serializer.h
@@ -614,6 +614,8 @@
     client_id_map_[client_node->id] = client_node;
   }
 
+  DCHECK_EQ(tree_->GetId(tree_->GetRoot()), client_root_->id);
+
   // We're about to serialize it, so mark it as valid.
   client_node->invalid = false;
   client_node->ignored = tree_->IsIgnored(node);
@@ -716,8 +718,25 @@
     AXNodeData* serialized_node = &out_update->nodes[serialized_node_index];
 
     tree_->SerializeNode(node, serialized_node);
-    if (serialized_node->id == client_root_->id)
+    if (serialized_node->id == client_root_->id) {
       out_update->root_id = serialized_node->id;
+      CHECK(!client_root_->parent) << "The root cannot have a parent:";
+      // << "\n* Root: "
+      // << tree_->GetDebugString(tree_->GetFromId(out_update->root_id))
+      // << "\n* Root's parent: "
+      // << tree_->GetDebugString(tree_->GetFromId(client_root_->parent->id));
+
+    } else {
+      DCHECK(serialized_node->role != ax::mojom::Role::kRootWebArea)
+          << "A kRootWebArea role was used on an object that is not the root: "
+          << "\n* Actual root: " << tree_->GetDebugString(tree_->GetRoot())
+          << "\n* Illegal node with root web area role: "
+          << tree_->GetDebugString(tree_->GetFromId(serialized_node->id))
+          << "\n* Parent of illegal node: "
+          << (client_node->parent ? tree_->GetDebugString(tree_->GetFromId(
+                                        client_node->parent->id))
+                                  : "");
+    }
   }
 
   // Iterate over the children, serialize them, and update the ClientTreeNode
diff --git a/ui/android/BUILD.gn b/ui/android/BUILD.gn
index 8f6206c..69585cd 100644
--- a/ui/android/BUILD.gn
+++ b/ui/android/BUILD.gn
@@ -157,10 +157,6 @@
     "java/res/drawable-mdpi/ic_expand_less_black_24dp.png",
     "java/res/drawable-mdpi/ic_expand_more_black_24dp.png",
     "java/res/drawable-mdpi/popup_bg.9.png",
-    "java/res/drawable-v21/transition_expand_less_expand_more_black_24dp.xml",
-    "java/res/drawable-v21/transition_expand_more_expand_less_black_24dp.xml",
-    "java/res/drawable-v23/dialog_bg_baseline.xml",
-    "java/res/drawable-v23/menu_bg_baseline.xml",
     "java/res/drawable-xhdpi/btn_close.png",
     "java/res/drawable-xhdpi/ic_expand_less_black_24dp.png",
     "java/res/drawable-xhdpi/ic_expand_more_black_24dp.png",
@@ -174,11 +170,15 @@
     "java/res/drawable-xxxhdpi/ic_expand_more_black_24dp.png",
     "java/res/drawable-xxxhdpi/popup_bg.9.png",
     "java/res/drawable/custom_toast_background.xml",
+    "java/res/drawable/dialog_bg_baseline.xml",
     "java/res/drawable/drag_handlebar.xml",
     "java/res/drawable/drag_shadow_background.xml",
     "java/res/drawable/ic_apps_blue_24dp.xml",
     "java/res/drawable/ic_expand_more_horizontal_black_24dp.xml",
     "java/res/drawable/ic_globe_24dp.xml",
+    "java/res/drawable/menu_bg_baseline.xml",
+    "java/res/drawable/transition_expand_less_expand_more_black_24dp.xml",
+    "java/res/drawable/transition_expand_more_expand_less_black_24dp.xml",
     "java/res/font/accent_font.xml",
     "java/res/layout/custom_toast_layout.xml",
     "java/res/layout/dropdown_item.xml",
@@ -189,9 +189,7 @@
     "java/res/values-night/dimens.xml",
     "java/res/values-sw600dp/dimens.xml",
     "java/res/values-sw600dp/values.xml",
-    "java/res/values-sw720dp-v17/values.xml",
-    "java/res/values-v17/styles.xml",
-    "java/res/values-v21/styles.xml",
+    "java/res/values-sw720dp/values.xml",
     "java/res/values-v31/colors.xml",
     "java/res/values/attrs.xml",
     "java/res/values/color_palette.xml",
diff --git a/ui/android/java/res/drawable-v23/dialog_bg_baseline.xml b/ui/android/java/res/drawable/dialog_bg_baseline.xml
similarity index 100%
rename from ui/android/java/res/drawable-v23/dialog_bg_baseline.xml
rename to ui/android/java/res/drawable/dialog_bg_baseline.xml
diff --git a/ui/android/java/res/drawable-v23/menu_bg_baseline.xml b/ui/android/java/res/drawable/menu_bg_baseline.xml
similarity index 100%
rename from ui/android/java/res/drawable-v23/menu_bg_baseline.xml
rename to ui/android/java/res/drawable/menu_bg_baseline.xml
diff --git a/ui/android/java/res/drawable-v21/transition_expand_less_expand_more_black_24dp.xml b/ui/android/java/res/drawable/transition_expand_less_expand_more_black_24dp.xml
similarity index 100%
rename from ui/android/java/res/drawable-v21/transition_expand_less_expand_more_black_24dp.xml
rename to ui/android/java/res/drawable/transition_expand_less_expand_more_black_24dp.xml
diff --git a/ui/android/java/res/drawable-v21/transition_expand_more_expand_less_black_24dp.xml b/ui/android/java/res/drawable/transition_expand_more_expand_less_black_24dp.xml
similarity index 100%
rename from ui/android/java/res/drawable-v21/transition_expand_more_expand_less_black_24dp.xml
rename to ui/android/java/res/drawable/transition_expand_more_expand_less_black_24dp.xml
diff --git a/ui/android/java/res/values-sw720dp-v17/values.xml b/ui/android/java/res/values-sw720dp/values.xml
similarity index 100%
rename from ui/android/java/res/values-sw720dp-v17/values.xml
rename to ui/android/java/res/values-sw720dp/values.xml
diff --git a/ui/android/java/res/values-v17/styles.xml b/ui/android/java/res/values-v17/styles.xml
deleted file mode 100644
index b4f57c3..0000000
--- a/ui/android/java/res/values-v17/styles.xml
+++ /dev/null
@@ -1,327 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!--
-Copyright 2013 The Chromium Authors
-Use of this source code is governed by a BSD-style license that can be
-found in the LICENSE file.
--->
-
-<resources xmlns:tools="http://schemas.android.com/tools">
-    <style name="DropdownPopupWindow" parent="@android:style/Widget.ListPopupWindow">
-        <item name="android:popupBackground">@drawable/dialog_bg_baseline</item>
-    </style>
-
-    <!-- Buttons -->
-    <style name="FilledButtonThemeOverlay">
-        <item name="android:buttonStyle">@style/FilledButton</item>
-    </style>
-    <style name="FilledButtonThemeOverlay.Flat" tools:ignore="UnusedResources">
-        <item name="android:buttonStyle">@style/FilledButton.Flat</item>
-    </style>
-    <style name="TextButtonThemeOverlay" tools:ignore="UnusedResources">
-        <item name="android:buttonStyle">@style/TextButton</item>
-    </style>
-
-    <style name="ButtonCompatBase">
-        <item name="android:minWidth">@dimen/button_min_width</item>
-        <item name="android:minHeight">@dimen/min_touch_target_size</item>
-        <item name="android:paddingStart">20dp</item>
-        <item name="android:paddingEnd">20dp</item>
-        <item name="android:paddingTop">5dp</item>
-        <item name="android:paddingBottom">5dp</item>
-        <item name="android:focusable">true</item>
-        <item name="android:clickable">true</item>
-        <item name="android:gravity">center_vertical|center_horizontal</item>
-        <item name="verticalInset">@dimen/button_bg_vertical_inset</item>
-    </style>
-    <style name="FilledButton" parent="ButtonCompatBase" tools:ignore="UnusedResources">
-        <item name="android:paddingStart">24dp</item>
-        <item name="android:paddingEnd">24dp</item>
-        <item name="android:textAppearance">@style/TextAppearance.Button.Text.Filled</item>
-        <item name="buttonTextColor">?attr/globalFilledButtonTextColor</item>
-        <item name="buttonColor">?attr/globalFilledButtonBgColor</item>
-        <item name="rippleColor">@color/filled_button_ripple_color</item>
-        <item name="buttonRaised">true</item>
-    </style>
-    <style name="FilledButton.Flat" tools:ignore="UnusedResources">
-        <item name="buttonRaised">false</item>
-    </style>
-    <style name="TextButton" parent="ButtonCompatBase" tools:ignore="UnusedResources">
-        <item name="android:paddingStart">8dp</item>
-        <item name="android:paddingEnd">8dp</item>
-        <item name="android:textAppearance">@style/TextAppearance.Button.Text.Blue</item>
-        <item name="buttonTextColor">?attr/globalTextButtonTextColor</item>
-        <item name="buttonColor">@android:color/transparent</item>
-        <!--
-          If ?attr/globalTextButtonRippleColor isn't defined in the theme, ButtonCompat will fall
-          back to a blue ripple color for buttons with transparent background and a white one for
-          the buttons with a solid background.
-        -->
-        <item name="rippleColor">?attr/globalTextButtonRippleColor</item>
-        <item name="buttonRaised">false</item>
-    </style>
-    <style name="OutlinedButton" parent="TextButton" tools:ignore="UnusedResources">
-        <item name="borderWidth">@dimen/button_outlined_border_width</item>
-        <item name="borderColor">?attr/globalOutlinedButtonBorderColor</item>
-    </style>
-
-    <!-- Used by Chrome and Content -->
-    <style name="TextAppearance" parent="android:TextAppearance" tools:ignore="UnusedResources" />
-    <style name="TextAppearance.RobotoMediumStyle">
-        <item name="android:fontFamily">sans-serif</item>
-        <item name="android:textStyle">bold</item>
-    </style>
-    <!-- This style is overridden downstream to set accent_font_medium as the font family. -->
-    <style name="TextAppearance.AccentMediumStyle" parent="TextAppearance.RobotoMediumStyle" />
-
-    <!-- Base Text Styles -->
-    <!--
-      Define incomplete base text styles. The styles in this section are used
-      to create other text styles below and should not be used directly to style
-      text as they are missing textColor attributes
-    -->
-    <style name="TextAppearance.Headline">
-        <item name="android:fontFamily">@font/accent_font</item>
-        <item name="android:textSize">@dimen/headline_size</item>
-    </style>
-    <style name="TextAppearance.HeadlineThick" parent="TextAppearance.AccentMediumStyle">
-        <item name="android:textSize">@dimen/headline_size</item>
-    </style>
-    <style name="TextAppearance.TextLarge">
-        <item name="android:textSize">@dimen/text_size_large</item>
-    </style>
-    <style name="TextAppearance.TextMediumThick" parent="TextAppearance.RobotoMediumStyle">
-        <item name="android:textSize">@dimen/text_size_medium</item>
-    </style>
-    <style name="TextAppearance.TextAccentMediumThick" parent="TextAppearance.AccentMediumStyle">
-        <item name="android:textSize">@dimen/text_size_medium</item>
-    </style>
-
-    <style name="TextAppearance.TextMedium">
-        <item name="android:textSize">@dimen/text_size_medium</item>
-    </style>
-
-    <style name="TextAppearance.TextSmall">
-        <item name="android:textSize">@dimen/text_size_small</item>
-    </style>
-
-    <!-- Non Adaptive Text Styles -->
-    <!--  Light version  -->
-    <style name="TextAppearance.Headline.Primary.Baseline.Light" tools:ignore="UnusedResources">
-        <item name="android:textColor">@color/default_text_color_light_list</item>
-    </style>
-    <style name="TextAppearance.HeadlineThick.Primary.Baseline.Light" tools:ignore="UnusedResources">
-        <item name="android:textColor">@color/default_text_color_light_list</item>
-    </style>
-    <style name="TextAppearance.TextLarge.Primary.Baseline.Light" tools:ignore="UnusedResources">
-        <item name="android:textColor">@color/default_text_color_light_list</item>
-    </style>
-    <style name="TextAppearance.TextMediumThick.Primary.Baseline.Light" tools:ignore="UnusedResources">
-        <item name="android:textColor">@color/default_text_color_light_list</item>
-    </style>
-    <style name="TextAppearance.TextMedium.Primary.Baseline.Light" tools:ignore="UnusedResources">
-        <item name="android:textColor">@color/default_text_color_light_list</item>
-    </style>
-    <style name="TextAppearance.TextSmall.Primary.Baseline.Light" tools:ignore="UnusedResources">
-        <item name="android:textColor">@color/default_text_color_light_list</item>
-    </style>
-    <style name="TextAppearance.TextMediumThick.Secondary.Baseline.Light"
-        parent="TextAppearance.TextMediumThick" tools:ignore="UnusedResources">
-        <item name="android:textColor">@color/default_text_color_secondary_light_list</item>
-    </style>
-
-    <style name="TextAppearance.TextLarge.Secondary.Baseline.Light"
-        parent="TextAppearance.TextLarge" tools:ignore="UnusedResources">
-        <item name="android:textColor">@color/default_text_color_secondary_light_list</item>
-    </style>
-    <style name="TextAppearance.TextMedium.Secondary.Baseline.Light" tools:ignore="UnusedResources">
-        <item name="android:textColor">@color/default_text_color_secondary_light_list</item>
-    </style>
-
-    <style name="TextAppearance.TextLarge.Disabled.Baseline.Light" parent="TextAppearance.TextLarge"
-        tools:ignore="UnusedResources">
-        <item name="android:textColor">@color/default_text_color_disabled_light</item>
-    </style>
-    <style name="TextAppearance.TextSmall.Disabled.Baseline.Light" parent="TextAppearance.TextSmall"
-        tools:ignore="UnusedResources">
-        <item name="android:textColor">@color/default_text_color_disabled_light</item>
-    </style>
-
-    <style name="TextAppearance.WhiteLink" tools:ignore="UnusedResources">
-        <item name="android:textColor">@android:color/white</item>
-        <item name="android:textSize">@dimen/text_size_medium</item>
-        <item name="android:textStyle">bold</item>
-    </style>
-
-    <style name="TextAppearance.TextSmallThick.Secondary.Baseline.Light"
-        parent="TextAppearance.TextSmall">
-        <item name="android:textColor">@color/default_text_color_secondary_light_list</item>
-        <item name="android:textStyle">bold</item>
-    </style>
-
-    <!--  Dark version  -->
-    <style name="TextAppearance.Headline.Primary.Baseline.Dark" tools:ignore="UnusedResources">
-        <item name="android:textColor">@color/default_text_color_dark</item>
-    </style>
-    <style name="TextAppearance.TextLarge.Primary.Baseline.Dark" tools:ignore="UnusedResources">
-        <item name="android:textColor">@color/default_text_color_dark</item>
-    </style>
-    <style name="TextAppearance.TextMedium.Primary.Baseline.Dark" tools:ignore="UnusedResources">
-        <item name="android:textColor">@color/default_text_color_dark</item>
-    </style>
-    <style name="TextAppearance.TextSmall.Primary.Baseline.Dark" tools:ignore="UnusedResources">
-        <item name="android:textColor">@color/default_text_color_dark</item>
-    </style>
-
-    <style name="TextAppearance.TextLarge.Secondary.Baseline.Dark"
-        parent="TextAppearance.TextLarge" tools:ignore="UnusedResources">
-        <item name="android:textColor">@color/default_text_color_secondary_dark</item>
-    </style>
-    <style name="TextAppearance.TextMedium.Secondary.Baseline.Dark" tools:ignore="UnusedResources">
-        <item name="android:textColor">@color/default_text_color_secondary_dark</item>
-    </style>
-    <style name="TextAppearance.TextSmall.Secondary.Baseline.Dark" tools:ignore="UnusedResources">
-        <item name="android:textColor">@color/default_text_color_secondary_dark</item>
-    </style>
-    <style name="TextAppearance.TextMediumThick.Secondary.Baseline.Dark"
-        parent="TextAppearance.TextMediumThick" tools:ignore="UnusedResources">
-        <item name="android:textColor">@color/default_text_color_secondary_dark</item>
-    </style>
-
-    <style name="TextAppearance.TextLarge.Disabled.Baseline.Dark" parent="TextAppearance.TextLarge"
-        tools:ignore="UnusedResources">
-        <item name="android:textColor">@color/default_text_color_disabled_dark</item>
-    </style>
-    <style name="TextAppearance.TextMedium.Disabled.Baseline.Dark" parent="TextAppearance.TextMedium"
-        tools:ignore="UnusedResources">
-        <item name="android:textColor">@color/default_text_color_disabled_dark</item>
-    </style>
-    <style name="TextAppearance.TextSmall.Disabled.Baseline.Dark" parent="TextAppearance.TextSmall"
-        tools:ignore="UnusedResources">
-        <item name="android:textColor">@color/default_text_color_disabled_dark</item>
-    </style>
-
-    <!-- Blue And Button Text Styles -->
-    <style name="TextAppearance.TextMediumThick.Green">
-        <item name="android:textColor">@color/default_green</item>
-    </style>
-    <style name="TextAppearance.TextMediumThick.Green.Dark">
-        <item name="android:textColor">@color/default_green_dark</item>
-    </style>
-
-    <style name="TextAppearance.Button.Text.Blue" parent="TextAppearance.TextAccentMediumThick"
-        tools:ignore="UnusedResources">
-        <item name="android:textColor">@color/blue_when_enabled_list</item>
-    </style>
-    <style name="TextAppearance.Button.Text.Blue.Dark" parent="TextAppearance.TextAccentMediumThick"
-        tools:ignore="UnusedResources">
-        <item name="android:textColor">@color/blue_when_enabled_dark</item>
-    </style>
-    <style name="TextAppearance.Button.Text.Filled" parent="TextAppearance.TextAccentMediumThick">
-        <item name="android:textColor">@color/default_text_color_on_accent1_baseline_list</item>
-    </style>
-    <style name="TextAppearance.Button.Text.Inverse" parent="TextAppearance.TextAccentMediumThick"
-        tools:ignore="UnusedResources">
-        <item name="android:textColor">@color/default_text_color_on_accent1_baseline_list</item>
-    </style>
-    <style name="TextAppearance.MenuChip.Text.Blue" parent="TextAppearance.Button.Text.Blue">
-       <item name="android:textSize">@dimen/text_size_small</item>
-    </style>
-
-    <!-- Blue Non Adaptive button text styles -->
-    <style name="TextAppearance.Button.Text.Filled.Baseline.Dark" parent="TextAppearance.TextAccentMediumThick">
-        <item name="android:textColor">@color/default_text_color_on_accent1_dark</item>
-    </style>
-
-    <!-- Blue Non Adaptive Text Styles -->
-    <style name="TextAppearance.TextMedium.Blue.Baseline.Light" parent="TextAppearance.TextMedium">
-        <item name="android:textColor">@color/default_icon_color_blue_light</item>
-    </style>
-    <style name="TextAppearance.TextMediumThick.Blue.Baseline.Light"
-        parent="TextAppearance.TextMediumThick" tools:ignore="UnusedResources">
-        <item name="android:textColor">@color/default_icon_color_blue_light</item>
-    </style>
-    <style name="TextAppearance.TextLarge.Blue.Baseline.Dark" parent="TextAppearance.TextLarge"
-        tools:ignore="UnusedResources">
-        <item name="android:textColor">@color/default_icon_color_blue_dark</item>
-    </style>
-    <style name="TextAppearance.TextMedium.Blue.Baseline.Dark" parent="TextAppearance.TextMedium"
-        tools:ignore="UnusedResources">
-        <item name="android:textColor">@color/default_icon_color_blue_dark</item>
-    </style>
-    <style name="TextAppearance.TextSmall.Blue.Baseline.Dark" parent="TextAppearance.TextSmall"
-        tools:ignore="UnusedResources">
-        <item name="android:textColor">@color/default_icon_color_blue_dark</item>
-    </style>
-    <style name="TextAppearance.TextMediumThick.Blue.Baseline.Dark"
-        parent="TextAppearance.TextMediumThick" tools:ignore="UnusedResources">
-        <item name="android:textColor">@color/default_icon_color_blue_dark</item>
-    </style>
-
-    <!-- Baseline or non-dynamic text styles -->
-
-    <!-- Primary text styles -->
-    <style name="TextAppearance.Headline.Primary.Baseline" parent="TextAppearance.Headline">
-        <item name="android:textColor">@color/default_text_color_list_baseline</item>
-    </style>
-    <style name="TextAppearance.HeadlineThick.Primary.Baseline" parent="TextAppearance.HeadlineThick">
-        <item name="android:textColor">@color/default_text_color_list_baseline</item>
-    </style>
-
-    <style name="TextAppearance.TextLarge.Primary.Baseline" parent="TextAppearance.TextLarge">
-        <item name="android:textColor">@color/default_text_color_list_baseline</item>
-    </style>
-
-    <style name="TextAppearance.TextMediumThick.Primary.Baseline" parent="TextAppearance.TextMediumThick">
-        <item name="android:textColor">@color/default_text_color_list_baseline</item>
-    </style>
-
-    <style name="TextAppearance.TextMedium.Primary.Baseline" parent="TextAppearance.TextMedium">
-        <item name="android:textColor">@color/default_text_color_list_baseline</item>
-    </style>
-
-    <style name="TextAppearance.TextSmall.Primary.Baseline" parent="TextAppearance.TextSmall">
-        <item name="android:textColor">@color/default_text_color_list_baseline</item>
-    </style>
-
-    <!-- Secondary text styles -->
-    <style name="TextAppearance.TextMedium.Secondary.Baseline" parent="TextAppearance.TextMedium">
-        <item name="android:textColor">@color/default_text_color_secondary_list_baseline</item>
-    </style>
-
-    <style name="TextAppearance.TextSmall.Secondary.Baseline" parent="TextAppearance.TextSmall">
-        <item name="android:textColor">@color/default_text_color_secondary_list_baseline</item>
-    </style>
-
-    <!-- Error Text Styles -->
-    <style name="TextAppearance.ErrorCaption" tools:ignore="UnusedResources">
-        <item name="android:textColor">@color/default_text_color_error</item>
-        <item name="android:textSize">@dimen/text_size_small</item>
-    </style>
-
-    <!-- Toast UI -->
-    <style name="TextAppearance.Toast" parent="TextAppearance.TextSmall">
-        <item name="android:textColor">@color/default_text_color_light</item>
-    </style>
-
-    <!-- Dividers -->
-    <style name="HorizontalDivider"
-           tools:ignore="UnusedResources">
-        <item name="android:layout_width">match_parent</item>
-        <item name="android:layout_height">@dimen/divider_height</item>
-        <item name="android:background">?android:attr/listDivider</item>
-        <item name="android:importantForAccessibility">no</item>
-    </style>
-    <style name="VerticalDivider"
-           tools:ignore="UnusedResources">
-        <item name="android:layout_width">@dimen/divider_height</item>
-        <item name="android:layout_height">match_parent</item>
-        <item name="android:background">?android:attr/listDivider</item>
-        <item name="android:importantForAccessibility">no</item>
-    </style>
-
-    <style name="ThemeOverlay.UI.SelectionHandle" parent="">
-        <item name="colorControlActivated">@color/default_control_color_active_baseline</item>
-    </style>
-
-</resources>
diff --git a/ui/android/java/res/values-v21/styles.xml b/ui/android/java/res/values-v21/styles.xml
deleted file mode 100644
index 94f4f41..0000000
--- a/ui/android/java/res/values-v21/styles.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-Copyright 2015 The Chromium Authors
-Use of this source code is governed by a BSD-style license that can be
-found in the LICENSE file.
--->
-<resources xmlns:tools="http://schemas.android.com/tools">
-    <!-- Used by Chrome and Content -->
-    <style name="TextAppearance.RobotoMediumStyle" tools:ignore="UnusedResources">
-        <item name="android:fontFamily">sans-serif-medium</item>
-    </style>
-</resources>
diff --git a/ui/android/java/res/values/styles.xml b/ui/android/java/res/values/styles.xml
index 37042d7..fea730ee 100644
--- a/ui/android/java/res/values/styles.xml
+++ b/ui/android/java/res/values/styles.xml
@@ -1,11 +1,329 @@
 <?xml version="1.0" encoding="utf-8"?>
+
 <!--
-Copyright 2022 The Chromium Authors
+Copyright 2013 The Chromium Authors
 Use of this source code is governed by a BSD-style license that can be
 found in the LICENSE file.
 -->
 
 <resources xmlns:tools="http://schemas.android.com/tools">
+    <style name="DropdownPopupWindow" parent="@android:style/Widget.ListPopupWindow">
+        <item name="android:popupBackground">@drawable/dialog_bg_baseline</item>
+    </style>
+
+    <!-- Buttons -->
+    <style name="FilledButtonThemeOverlay">
+        <item name="android:buttonStyle">@style/FilledButton</item>
+    </style>
+    <style name="FilledButtonThemeOverlay.Flat" tools:ignore="UnusedResources">
+        <item name="android:buttonStyle">@style/FilledButton.Flat</item>
+    </style>
+    <style name="TextButtonThemeOverlay" tools:ignore="UnusedResources">
+        <item name="android:buttonStyle">@style/TextButton</item>
+    </style>
+
+    <style name="ButtonCompatBase">
+        <item name="android:minWidth">@dimen/button_min_width</item>
+        <item name="android:minHeight">@dimen/min_touch_target_size</item>
+        <item name="android:paddingStart">20dp</item>
+        <item name="android:paddingEnd">20dp</item>
+        <item name="android:paddingTop">5dp</item>
+        <item name="android:paddingBottom">5dp</item>
+        <item name="android:focusable">true</item>
+        <item name="android:clickable">true</item>
+        <item name="android:gravity">center_vertical|center_horizontal</item>
+        <item name="verticalInset">@dimen/button_bg_vertical_inset</item>
+    </style>
+    <style name="FilledButton" parent="ButtonCompatBase" tools:ignore="UnusedResources">
+        <item name="android:paddingStart">24dp</item>
+        <item name="android:paddingEnd">24dp</item>
+        <item name="android:textAppearance">@style/TextAppearance.Button.Text.Filled</item>
+        <item name="buttonTextColor">?attr/globalFilledButtonTextColor</item>
+        <item name="buttonColor">?attr/globalFilledButtonBgColor</item>
+        <item name="rippleColor">@color/filled_button_ripple_color</item>
+        <item name="buttonRaised">true</item>
+    </style>
+    <style name="FilledButton.Flat" tools:ignore="UnusedResources">
+        <item name="buttonRaised">false</item>
+    </style>
+    <style name="TextButton" parent="ButtonCompatBase" tools:ignore="UnusedResources">
+        <item name="android:paddingStart">8dp</item>
+        <item name="android:paddingEnd">8dp</item>
+        <item name="android:textAppearance">@style/TextAppearance.Button.Text.Blue</item>
+        <item name="buttonTextColor">?attr/globalTextButtonTextColor</item>
+        <item name="buttonColor">@android:color/transparent</item>
+        <!--
+          If ?attr/globalTextButtonRippleColor isn't defined in the theme, ButtonCompat will fall
+          back to a blue ripple color for buttons with transparent background and a white one for
+          the buttons with a solid background.
+        -->
+        <item name="rippleColor">?attr/globalTextButtonRippleColor</item>
+        <item name="buttonRaised">false</item>
+    </style>
+    <style name="OutlinedButton" parent="TextButton" tools:ignore="UnusedResources">
+        <item name="borderWidth">@dimen/button_outlined_border_width</item>
+        <item name="borderColor">?attr/globalOutlinedButtonBorderColor</item>
+    </style>
+
+    <!-- Used by Chrome and Content -->
+    <style name="TextAppearance" parent="android:TextAppearance" tools:ignore="UnusedResources" />
+    <!-- Used by Chrome and Content -->
+    <style name="TextAppearance.RobotoMediumStyle" tools:ignore="UnusedResources">
+        <item name="android:fontFamily">sans-serif-medium</item>
+    </style>
+    <!-- This style is overridden downstream to set accent_font_medium as the font family. -->
+    <style name="TextAppearance.AccentMediumStyle" parent="TextAppearance.RobotoMediumStyle" />
+
+    <!-- Base Text Styles -->
+    <!--
+      Define incomplete base text styles. The styles in this section are used
+      to create other text styles below and should not be used directly to style
+      text as they are missing textColor attributes
+    -->
+    <style name="TextAppearance.Headline">
+        <item name="android:fontFamily">@font/accent_font</item>
+        <item name="android:textSize">@dimen/headline_size</item>
+    </style>
+    <style name="TextAppearance.HeadlineThick" parent="TextAppearance.AccentMediumStyle">
+        <item name="android:textSize">@dimen/headline_size</item>
+    </style>
+    <style name="TextAppearance.TextLarge">
+        <item name="android:textSize">@dimen/text_size_large</item>
+    </style>
+    <style name="TextAppearance.TextMediumThick" parent="TextAppearance.RobotoMediumStyle">
+        <item name="android:textSize">@dimen/text_size_medium</item>
+    </style>
+    <style name="TextAppearance.TextAccentMediumThick" parent="TextAppearance.AccentMediumStyle">
+        <item name="android:textSize">@dimen/text_size_medium</item>
+    </style>
+
+    <style name="TextAppearance.TextMedium">
+        <item name="android:textSize">@dimen/text_size_medium</item>
+    </style>
+
+    <style name="TextAppearance.TextSmall">
+        <item name="android:textSize">@dimen/text_size_small</item>
+    </style>
+
+    <!-- Non Adaptive Text Styles -->
+    <!--  Light version  -->
+    <style name="TextAppearance.Headline.Primary.Baseline.Light" tools:ignore="UnusedResources">
+        <item name="android:textColor">@color/default_text_color_light_list</item>
+    </style>
+    <style name="TextAppearance.HeadlineThick.Primary.Baseline.Light" tools:ignore="UnusedResources">
+        <item name="android:textColor">@color/default_text_color_light_list</item>
+    </style>
+    <style name="TextAppearance.TextLarge.Primary.Baseline.Light" tools:ignore="UnusedResources">
+        <item name="android:textColor">@color/default_text_color_light_list</item>
+    </style>
+    <style name="TextAppearance.TextMediumThick.Primary.Baseline.Light" tools:ignore="UnusedResources">
+        <item name="android:textColor">@color/default_text_color_light_list</item>
+    </style>
+    <style name="TextAppearance.TextMedium.Primary.Baseline.Light" tools:ignore="UnusedResources">
+        <item name="android:textColor">@color/default_text_color_light_list</item>
+    </style>
+    <style name="TextAppearance.TextSmall.Primary.Baseline.Light" tools:ignore="UnusedResources">
+        <item name="android:textColor">@color/default_text_color_light_list</item>
+    </style>
+    <style name="TextAppearance.TextMediumThick.Secondary.Baseline.Light"
+        parent="TextAppearance.TextMediumThick" tools:ignore="UnusedResources">
+        <item name="android:textColor">@color/default_text_color_secondary_light_list</item>
+    </style>
+
+    <style name="TextAppearance.TextLarge.Secondary.Baseline.Light"
+        parent="TextAppearance.TextLarge" tools:ignore="UnusedResources">
+        <item name="android:textColor">@color/default_text_color_secondary_light_list</item>
+    </style>
+    <style name="TextAppearance.TextMedium.Secondary.Baseline.Light" tools:ignore="UnusedResources">
+        <item name="android:textColor">@color/default_text_color_secondary_light_list</item>
+    </style>
+
+    <style name="TextAppearance.TextLarge.Disabled.Baseline.Light" parent="TextAppearance.TextLarge"
+        tools:ignore="UnusedResources">
+        <item name="android:textColor">@color/default_text_color_disabled_light</item>
+    </style>
+    <style name="TextAppearance.TextSmall.Disabled.Baseline.Light" parent="TextAppearance.TextSmall"
+        tools:ignore="UnusedResources">
+        <item name="android:textColor">@color/default_text_color_disabled_light</item>
+    </style>
+
+    <style name="TextAppearance.WhiteLink" tools:ignore="UnusedResources">
+        <item name="android:textColor">@android:color/white</item>
+        <item name="android:textSize">@dimen/text_size_medium</item>
+        <item name="android:textStyle">bold</item>
+    </style>
+
+    <style name="TextAppearance.TextSmallThick.Secondary.Baseline.Light"
+        parent="TextAppearance.TextSmall">
+        <item name="android:textColor">@color/default_text_color_secondary_light_list</item>
+        <item name="android:textStyle">bold</item>
+    </style>
+
+    <!--  Dark version  -->
+    <style name="TextAppearance.Headline.Primary.Baseline.Dark" tools:ignore="UnusedResources">
+        <item name="android:textColor">@color/default_text_color_dark</item>
+    </style>
+    <style name="TextAppearance.TextLarge.Primary.Baseline.Dark" tools:ignore="UnusedResources">
+        <item name="android:textColor">@color/default_text_color_dark</item>
+    </style>
+    <style name="TextAppearance.TextMedium.Primary.Baseline.Dark" tools:ignore="UnusedResources">
+        <item name="android:textColor">@color/default_text_color_dark</item>
+    </style>
+    <style name="TextAppearance.TextSmall.Primary.Baseline.Dark" tools:ignore="UnusedResources">
+        <item name="android:textColor">@color/default_text_color_dark</item>
+    </style>
+
+    <style name="TextAppearance.TextLarge.Secondary.Baseline.Dark"
+        parent="TextAppearance.TextLarge" tools:ignore="UnusedResources">
+        <item name="android:textColor">@color/default_text_color_secondary_dark</item>
+    </style>
+    <style name="TextAppearance.TextMedium.Secondary.Baseline.Dark" tools:ignore="UnusedResources">
+        <item name="android:textColor">@color/default_text_color_secondary_dark</item>
+    </style>
+    <style name="TextAppearance.TextSmall.Secondary.Baseline.Dark" tools:ignore="UnusedResources">
+        <item name="android:textColor">@color/default_text_color_secondary_dark</item>
+    </style>
+    <style name="TextAppearance.TextMediumThick.Secondary.Baseline.Dark"
+        parent="TextAppearance.TextMediumThick" tools:ignore="UnusedResources">
+        <item name="android:textColor">@color/default_text_color_secondary_dark</item>
+    </style>
+
+    <style name="TextAppearance.TextLarge.Disabled.Baseline.Dark" parent="TextAppearance.TextLarge"
+        tools:ignore="UnusedResources">
+        <item name="android:textColor">@color/default_text_color_disabled_dark</item>
+    </style>
+    <style name="TextAppearance.TextMedium.Disabled.Baseline.Dark" parent="TextAppearance.TextMedium"
+        tools:ignore="UnusedResources">
+        <item name="android:textColor">@color/default_text_color_disabled_dark</item>
+    </style>
+    <style name="TextAppearance.TextSmall.Disabled.Baseline.Dark" parent="TextAppearance.TextSmall"
+        tools:ignore="UnusedResources">
+        <item name="android:textColor">@color/default_text_color_disabled_dark</item>
+    </style>
+
+    <!-- Blue And Button Text Styles -->
+    <style name="TextAppearance.TextMediumThick.Green">
+        <item name="android:textColor">@color/default_green</item>
+    </style>
+    <style name="TextAppearance.TextMediumThick.Green.Dark">
+        <item name="android:textColor">@color/default_green_dark</item>
+    </style>
+
+    <style name="TextAppearance.Button.Text.Blue" parent="TextAppearance.TextAccentMediumThick"
+        tools:ignore="UnusedResources">
+        <item name="android:textColor">@color/blue_when_enabled_list</item>
+    </style>
+    <style name="TextAppearance.Button.Text.Blue.Dark" parent="TextAppearance.TextAccentMediumThick"
+        tools:ignore="UnusedResources">
+        <item name="android:textColor">@color/blue_when_enabled_dark</item>
+    </style>
+    <style name="TextAppearance.Button.Text.Filled" parent="TextAppearance.TextAccentMediumThick">
+        <item name="android:textColor">@color/default_text_color_on_accent1_baseline_list</item>
+    </style>
+    <style name="TextAppearance.Button.Text.Inverse" parent="TextAppearance.TextAccentMediumThick"
+        tools:ignore="UnusedResources">
+        <item name="android:textColor">@color/default_text_color_on_accent1_baseline_list</item>
+    </style>
+    <style name="TextAppearance.MenuChip.Text.Blue" parent="TextAppearance.Button.Text.Blue">
+       <item name="android:textSize">@dimen/text_size_small</item>
+    </style>
+
+    <!-- Blue Non Adaptive button text styles -->
+    <style name="TextAppearance.Button.Text.Filled.Baseline.Dark" parent="TextAppearance.TextAccentMediumThick">
+        <item name="android:textColor">@color/default_text_color_on_accent1_dark</item>
+    </style>
+
+    <!-- Blue Non Adaptive Text Styles -->
+    <style name="TextAppearance.TextMedium.Blue.Baseline.Light" parent="TextAppearance.TextMedium">
+        <item name="android:textColor">@color/default_icon_color_blue_light</item>
+    </style>
+    <style name="TextAppearance.TextMediumThick.Blue.Baseline.Light"
+        parent="TextAppearance.TextMediumThick" tools:ignore="UnusedResources">
+        <item name="android:textColor">@color/default_icon_color_blue_light</item>
+    </style>
+    <style name="TextAppearance.TextLarge.Blue.Baseline.Dark" parent="TextAppearance.TextLarge"
+        tools:ignore="UnusedResources">
+        <item name="android:textColor">@color/default_icon_color_blue_dark</item>
+    </style>
+    <style name="TextAppearance.TextMedium.Blue.Baseline.Dark" parent="TextAppearance.TextMedium"
+        tools:ignore="UnusedResources">
+        <item name="android:textColor">@color/default_icon_color_blue_dark</item>
+    </style>
+    <style name="TextAppearance.TextSmall.Blue.Baseline.Dark" parent="TextAppearance.TextSmall"
+        tools:ignore="UnusedResources">
+        <item name="android:textColor">@color/default_icon_color_blue_dark</item>
+    </style>
+    <style name="TextAppearance.TextMediumThick.Blue.Baseline.Dark"
+        parent="TextAppearance.TextMediumThick" tools:ignore="UnusedResources">
+        <item name="android:textColor">@color/default_icon_color_blue_dark</item>
+    </style>
+
+    <!-- Baseline or non-dynamic text styles -->
+
+    <!-- Primary text styles -->
+    <style name="TextAppearance.Headline.Primary.Baseline" parent="TextAppearance.Headline">
+        <item name="android:textColor">@color/default_text_color_list_baseline</item>
+    </style>
+    <style name="TextAppearance.HeadlineThick.Primary.Baseline" parent="TextAppearance.HeadlineThick">
+        <item name="android:textColor">@color/default_text_color_list_baseline</item>
+    </style>
+
+    <style name="TextAppearance.TextLarge.Primary.Baseline" parent="TextAppearance.TextLarge">
+        <item name="android:textColor">@color/default_text_color_list_baseline</item>
+    </style>
+
+    <style name="TextAppearance.TextMediumThick.Primary.Baseline" parent="TextAppearance.TextMediumThick">
+        <item name="android:textColor">@color/default_text_color_list_baseline</item>
+    </style>
+
+    <style name="TextAppearance.TextMedium.Primary.Baseline" parent="TextAppearance.TextMedium">
+        <item name="android:textColor">@color/default_text_color_list_baseline</item>
+    </style>
+
+    <style name="TextAppearance.TextSmall.Primary.Baseline" parent="TextAppearance.TextSmall">
+        <item name="android:textColor">@color/default_text_color_list_baseline</item>
+    </style>
+
+    <!-- Secondary text styles -->
+    <style name="TextAppearance.TextMedium.Secondary.Baseline" parent="TextAppearance.TextMedium">
+        <item name="android:textColor">@color/default_text_color_secondary_list_baseline</item>
+    </style>
+
+    <style name="TextAppearance.TextSmall.Secondary.Baseline" parent="TextAppearance.TextSmall">
+        <item name="android:textColor">@color/default_text_color_secondary_list_baseline</item>
+    </style>
+
+    <!-- Error Text Styles -->
+    <style name="TextAppearance.ErrorCaption" tools:ignore="UnusedResources">
+        <item name="android:textColor">@color/default_text_color_error</item>
+        <item name="android:textSize">@dimen/text_size_small</item>
+    </style>
+
+    <!-- Toast UI -->
+    <style name="TextAppearance.Toast" parent="TextAppearance.TextSmall">
+        <item name="android:textColor">@color/default_text_color_light</item>
+    </style>
+
+    <!-- Dividers -->
+    <style name="HorizontalDivider"
+           tools:ignore="UnusedResources">
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_height">@dimen/divider_height</item>
+        <item name="android:background">?android:attr/listDivider</item>
+        <item name="android:importantForAccessibility">no</item>
+    </style>
+    <style name="VerticalDivider"
+           tools:ignore="UnusedResources">
+        <item name="android:layout_width">@dimen/divider_height</item>
+        <item name="android:layout_height">match_parent</item>
+        <item name="android:background">?android:attr/listDivider</item>
+        <item name="android:importantForAccessibility">no</item>
+    </style>
+
+    <style name="ThemeOverlay.UI.SelectionHandle" parent="">
+        <item name="colorControlActivated">@color/default_control_color_active_baseline</item>
+    </style>
+
     <!-- AnchoredPopupAnimationStyle -->
     <style name="AnchoredPopupAnimEndTop">
         <item name="android:windowEnterAnimation">@anim/menu_enter</item>
diff --git a/ui/ozone/platform/flatland/ozone_platform_flatland.cc b/ui/ozone/platform/flatland/ozone_platform_flatland.cc
index 37112a2..a5226787 100644
--- a/ui/ozone/platform/flatland/ozone_platform_flatland.cc
+++ b/ui/ozone/platform/flatland/ozone_platform_flatland.cc
@@ -109,9 +109,6 @@
               std::move(parent_token));
     }
 
-    // TODO(fxbug.dev/93998): Add a hook for the RootPresenter equivalent of
-    // Flatland to ui::fuchsia::InitializeViewTokenAndPresentView() create a
-    // window.
     CHECK(properties.view_creation_token.value.is_valid());
     return std::make_unique<FlatlandWindow>(window_manager_.get(), delegate,
                                             std::move(properties));
diff --git a/ui/ozone/platform/wayland/ozone_platform_wayland.cc b/ui/ozone/platform/wayland/ozone_platform_wayland.cc
index b3190c6b..5366707 100644
--- a/ui/ozone/platform/wayland/ozone_platform_wayland.cc
+++ b/ui/ozone/platform/wayland/ozone_platform_wayland.cc
@@ -350,7 +350,7 @@
             (wl::get_version_of_object(
                  connection_->zaura_shell()->wl_object()) >=
              ZAURA_SURFACE_SHOW_TOOLTIP_SINCE_VERSION) &&
-            connection_->zaura_shell()->HasBugFix(1400226);
+            connection_->zaura_shell()->HasBugFix(1402158);
       }
 
       if (surface_factory_) {
diff --git a/ui/views/corewm/tooltip_aura.cc b/ui/views/corewm/tooltip_aura.cc
index 971d26c..033cf28 100644
--- a/ui/views/corewm/tooltip_aura.cc
+++ b/ui/views/corewm/tooltip_aura.cc
@@ -16,6 +16,7 @@
 #include "ui/accessibility/ax_enums.mojom.h"
 #include "ui/accessibility/ax_node_data.h"
 #include "ui/aura/client/aura_constants.h"
+#include "ui/aura/client/screen_position_client.h"
 #include "ui/aura/window.h"
 #include "ui/aura/window_tree_host.h"
 #include "ui/base/metadata/metadata_header_macros.h"
@@ -313,8 +314,13 @@
   tooltip_window_ = window;
 
   auto new_tooltip_view = std::make_unique<TooltipView>();
-  gfx::Point anchor_point =
-      position + window->GetBoundsInScreen().OffsetFromOrigin();
+
+  // Convert `position` to screen coordinates.
+  gfx::Point anchor_point = position;
+  aura::client::ScreenPositionClient* screen_position_client =
+      aura::client::GetScreenPositionClient(window->GetRootWindow());
+  screen_position_client->ConvertPointToScreen(window, &anchor_point);
+
   new_tooltip_view->SetMaxWidth(GetMaxWidth(anchor_point));
   new_tooltip_view->SetText(tooltip_text);
   ui::OwnedWindowAnchor anchor;