diff --git a/.gn b/.gn
index 1d802d41..1abcb11 100644
--- a/.gn
+++ b/.gn
@@ -94,7 +94,6 @@
   "//extensions/browser/api/bluetooth_socket:*",  # 12 errors
   "//extensions/browser/api/cast_channel:*",  # 3 errors
   "//extensions/browser/api/cec_private:*",  # 4 errors
-  "//extensions/browser/api/clipboard:*",  # 3 errors
   "//extensions/browser/api/declarative:*",  # 20 errors
   "//extensions/browser/api/declarative_content:*",  # 2 errors
   "//extensions/browser/api/declarative_net_request:*",  # 18 errors
diff --git a/DEPS b/DEPS
index ceb547c7..be26caae 100644
--- a/DEPS
+++ b/DEPS
@@ -199,11 +199,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': 'd60c919fc866fdb53ea532428a1a8b58b2309dc1',
+  'skia_revision': 'db2dad5c644f5a57059ef57483d52090f0e3c0c9',
   # 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': 'edfb3b4d60eb7b778b92afc495b0cf11afa7a66a',
+  'v8_revision': 'd87c3006482190728da53f87875b2a528dfb7ef3',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling swarming_client
   # and whatever else without interference from each other.
@@ -211,11 +211,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ANGLE
   # and whatever else without interference from each other.
-  'angle_revision': 'd0a38010a14cf01b1a7b32143315108bd3334f46',
+  'angle_revision': 'f99ccb082ba59aab0fba2b8d04625bc2364fc01f',
   # 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': '071cf0cff810a95284f8635bbff20d138567fdd8',
+  'swiftshader_revision': '937395c1ed5d20af5a5fd999d7e2deddf1b78fe3',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
@@ -266,7 +266,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': 'd9d7213a6c08cd636ae27cea934259f7fa03ee65',
+  'catapult_revision': 'e174329bab1589f8df20965fcb9dde4428673a64',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -274,7 +274,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': '61704b4db5d783813e19262bad9ef6e36c8d7ce5',
+  'devtools_frontend_revision': 'b43fbe8a06ea52e110218c82644181ed12917400',
   # 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.
@@ -318,7 +318,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'quiche_revision': '528c36b3cfd0110e56b1158f8f58d833817991e0',
+  'quiche_revision': '63bdfe586f8ceddf5e0823b75b1c595cbd380b2e',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ios_webkit
   # and whatever else without interference from each other.
@@ -893,7 +893,7 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + 'e856b6bba8266df0634a365b0a51b6097bb01d4b',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '60b433cf0b29e12218abbdf36a8d166fc59e7209',
 
   'src/third_party/devtools-frontend/src':
     Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'),
@@ -961,7 +961,7 @@
     Var('chromium_git') + '/external/github.com/google/emoji-segmenter.git' + '@' + Var('emoji_segmenter_revision'),
 
   'src/third_party/libgav1/src':
-    Var('chromium_git') + '/codecs/libgav1.git' + '@' + 'a9449e612bc251b4271bbe1e3a0d12e9809bf74c',
+    Var('chromium_git') + '/codecs/libgav1.git' + '@' + 'a5ee0e00923c355ef3aad2b2829365a9fde84430',
 
   'src/third_party/google_toolbox_for_mac/src': {
       'url': Var('chromium_git') + '/external/github.com/google/google-toolbox-for-mac.git' + '@' + Var('google_toolbox_for_mac_revision'),
@@ -1485,7 +1485,7 @@
   'src/third_party/usrsctp/usrsctplib':
     Var('chromium_git') + '/external/github.com/sctplab/usrsctp' + '@' + '4191ca1784d8774dbf62d48ab9426c7311a91bc5',
 
-  'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@725a542f7776a62b8389c8489d7e41885ac115be',
+  'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@ffb3b84133b03dfc4b83a8430a418f4fa94308d8',
 
   'src/third_party/vulkan_memory_allocator':
     Var('chromium_git') + '/external/github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git' + '@' + '6c656df63da5995a932aafd45b32af1974e497d9',
@@ -1518,7 +1518,7 @@
     Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + '3c2fe3888658d82b47ca831d59a2e07579619c2d',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + 'ec9b281bbc14fe9c3cd2b27241059f52b7eb654a',
+    Var('webrtc_git') + '/src.git' + '@' + 'cdb3bc3b93dae5237610d0e45bccd900812a0cfe',
 
   'src/third_party/libgifcodec':
      Var('skia_git') + '/libgifcodec' + '@'+  Var('libgifcodec_revision'),
@@ -1556,7 +1556,7 @@
       'packages': [
         {
           'package': 'skia/tools/goldctl/linux-amd64',
-          'version': 'C_u9-R-I-yQyomb3Y-XIH8Ad3Ya4ZWi2MpPiFTtfHz4C',
+          'version': 'SydJmfq0VW7jq5fTDGtIQ-7WoB0vNc-LJAi3Xd4fKvQC',
         },
       ],
       'dep_type': 'cipd',
@@ -1566,7 +1566,7 @@
       'packages': [
         {
           'package': 'skia/tools/goldctl/windows-amd64',
-          'version': 'FzQDQOcAASIc0LaD9YBjqH2JPm0HbYCE2x0WXw2xpz0C',
+          'version': 'mvme2We4n3wONUhzQu6ATl9Ow4Fb4li6ET-Yhv7Ph3EC',
         },
       ],
       'dep_type': 'cipd',
@@ -1576,7 +1576,7 @@
       'packages': [
         {
           'package': 'skia/tools/goldctl/mac-amd64',
-          'version': 'H_4GgcW8CtnxDnWd7RX17C39CAYzfHUaFR6drL3wWAcC',
+          'version': 'oxKWG6EzN6FHwQ-4J6gUYMHuNuHyARpVl4EvirOsXSgC',
         },
       ],
       'dep_type': 'cipd',
@@ -1590,7 +1590,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@7a13b94a1d4c836e869dc10733e068b2573a9aed',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@8e33046d8bd53448771287085284e477d29aef1d',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/AwMetricsIntegrationTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/AwMetricsIntegrationTest.java
index 0fd60c6..6870d28c 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/AwMetricsIntegrationTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/AwMetricsIntegrationTest.java
@@ -6,6 +6,7 @@
 
 import static org.chromium.android_webview.test.OnlyRunIn.ProcessMode.MULTI_PROCESS;
 
+import android.os.Build;
 import android.support.test.InstrumentationRegistry;
 
 import androidx.test.filters.MediumTest;
@@ -22,6 +23,7 @@
 import org.chromium.android_webview.metrics.AwMetricsServiceClient;
 import org.chromium.base.Callback;
 import org.chromium.base.ThreadUtils;
+import org.chromium.base.compat.ApiHelperForM;
 import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.CommandLineFlags;
@@ -167,6 +169,11 @@
         // some reason).
         Assert.assertTrue(
                 "Should have some application_locale", systemProfile.hasApplicationLocale());
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            Assert.assertEquals(
+                    ApiHelperForM.isProcess64Bit(), systemProfile.getAppVersion().contains("-64"));
+        }
     }
 
     @Test
diff --git a/android_webview/tools/system_webview_shell/test/data/webexposed/not-webview-exposed.txt b/android_webview/tools/system_webview_shell/test/data/webexposed/not-webview-exposed.txt
index 706879c..8bc05281 100644
--- a/android_webview/tools/system_webview_shell/test/data/webexposed/not-webview-exposed.txt
+++ b/android_webview/tools/system_webview_shell/test/data/webexposed/not-webview-exposed.txt
@@ -62,12 +62,13 @@
 
 # permissions API (crbug.com/490120), presentation API (crbug.com/521319),
 # share API (crbug.com/765923), custom scheme handlers (crbug.com/589502),
-# media session API (crbug.com/925997), and WebXr API (crbug.com/1012899)
-# are not supported in webview.
+# media session API (crbug.com/925997), Web Serial API (crbug.com/1164036),
+# and WebXr API (crbug.com/1012899) are not supported in webview.
 interface Navigator
     getter mediaSession                # crbug.com/925997
     getter permissions                 # crbug.com/490120
     getter presentation                # crbug.com/521319
+    getter serial                      # crbug.com/1164036
     getter xr                          # crbug.com/1012899
     method registerProtocolHandler     # crbug.com/589502
     method unregisterProtocolHandler   # crbug.com/589502
@@ -172,6 +173,11 @@
 interface XRAnchor
 interface XRAnchorSet
 
+# Web Serial API is not implemented on Android. If Bluetooth Classic support
+# were added this could change. https://crbug.com/1164036
+interface Serial : EventTarget
+interface SerialPort : EventTarget
+
 [GLOBAL OBJECT]
     method openDatabase
     attribute eventSender                    # test only
diff --git a/ash/accelerators/accelerator_controller_impl.cc b/ash/accelerators/accelerator_controller_impl.cc
index a3d3026..d9cca591 100644
--- a/ash/accelerators/accelerator_controller_impl.cc
+++ b/ash/accelerators/accelerator_controller_impl.cc
@@ -98,6 +98,7 @@
 #include "chromeos/dbus/power/power_manager_client.h"
 #include "chromeos/ui/vector_icons/vector_icons.h"
 #include "components/user_manager/user_type.h"
+#include "ui/aura/client/aura_constants.h"
 #include "ui/base/accelerators/accelerator.h"
 #include "ui/base/accelerators/accelerator_manager.h"
 #include "ui/base/l10n/l10n_util.h"
diff --git a/ash/accelerometer/accelerometer_file_reader.cc b/ash/accelerometer/accelerometer_file_reader.cc
index 7c47c13..79feba2f 100644
--- a/ash/accelerometer/accelerometer_file_reader.cc
+++ b/ash/accelerometer/accelerometer_file_reader.cc
@@ -20,7 +20,6 @@
 #include "base/location.h"
 #include "base/memory/singleton.h"
 #include "base/numerics/math_constants.h"
-#include "base/observer_list_threadsafe.h"
 #include "base/sequenced_task_runner.h"
 #include "base/stl_util.h"
 #include "base/strings/string_number_conversions.h"
@@ -142,8 +141,7 @@
 
 }  // namespace
 
-AccelerometerFileReader::AccelerometerFileReader()
-    : ui_task_runner_(base::SequencedTaskRunnerHandle::Get()) {}
+AccelerometerFileReader::AccelerometerFileReader() = default;
 
 void AccelerometerFileReader::PrepareAndInitialize() {
   DCHECK(base::CurrentUIThread::IsSet());
@@ -165,62 +163,11 @@
   TryScheduleInitialize();
 }
 
-void AccelerometerFileReader::AddObserver(
-    AccelerometerReader::Observer* observer) {
-  DCHECK(base::CurrentUIThread::IsSet());
-  DCHECK(blocking_task_runner_);
-  observers_.AddObserver(observer);
-
-  if (initialization_state_ != State::SUCCESS)
-    return;
-
-  blocking_task_runner_->PostTask(
-      FROM_HERE, base::BindOnce(&AccelerometerFileReader::ReadSample, this));
-}
-
-void AccelerometerFileReader::RemoveObserver(
-    AccelerometerReader::Observer* observer) {
-  DCHECK(base::CurrentUIThread::IsSet());
-  observers_.RemoveObserver(observer);
-}
-
-void AccelerometerFileReader::StartListenToTabletModeController() {
-  DCHECK(base::CurrentUIThread::IsSet());
-  Shell::Get()->tablet_mode_controller()->AddObserver(this);
-}
-
-void AccelerometerFileReader::StopListenToTabletModeController() {
-  DCHECK(base::CurrentUIThread::IsSet());
-  Shell::Get()->tablet_mode_controller()->RemoveObserver(this);
-}
-
-void AccelerometerFileReader::SetEmitEvents(bool emit_events) {
-  DCHECK(base::CurrentUIThread::IsSet());
-  emit_events_ = emit_events;
-}
-
-void AccelerometerFileReader::EnableAccelerometerReading() {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  if (read_refresh_timer_.IsRunning())
-    return;
-
-  read_refresh_timer_.Start(FROM_HERE, kDelayBetweenReads, this,
-                            &AccelerometerFileReader::ReadSample);
-}
-
-void AccelerometerFileReader::DisableAccelerometerReading() {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  if (!read_refresh_timer_.IsRunning())
-    return;
-
-  read_refresh_timer_.Stop();
-}
-
 void AccelerometerFileReader::TriggerRead() {
   DCHECK(base::CurrentUIThread::IsSet());
   switch (initialization_state_) {
     case State::SUCCESS:
-      if (ec_lid_angle_driver_status_ == ECLidAngleDriverStatus::SUPPORTED) {
+      if (GetECLidAngleDriverStatus() == ECLidAngleDriverStatus::SUPPORTED) {
         blocking_task_runner_->PostTask(
             FROM_HERE,
             base::BindOnce(&AccelerometerFileReader::EnableAccelerometerReading,
@@ -242,7 +189,7 @@
 void AccelerometerFileReader::CancelRead() {
   DCHECK(base::CurrentUIThread::IsSet());
   if (initialization_state_ == State::SUCCESS &&
-      ec_lid_angle_driver_status_ == ECLidAngleDriverStatus::SUPPORTED) {
+      GetECLidAngleDriverStatus() == ECLidAngleDriverStatus::SUPPORTED) {
     blocking_task_runner_->PostTask(
         FROM_HERE,
         base::BindOnce(&AccelerometerFileReader::DisableAccelerometerReading,
@@ -250,30 +197,6 @@
   }
 }
 
-void AccelerometerFileReader::OnTabletPhysicalStateChanged() {
-  DCHECK(base::CurrentUIThread::IsSet());
-
-  // When CrOS EC lid angle driver is not present, accelerometer read is always
-  // ON and can't be tuned. Thus AccelerometerFileReader no longer listens to
-  // tablet mode event.
-  auto* tablet_mode_controller = Shell::Get()->tablet_mode_controller();
-  if (ec_lid_angle_driver_status_ == ECLidAngleDriverStatus::NOT_SUPPORTED) {
-    tablet_mode_controller->RemoveObserver(this);
-    return;
-  }
-
-  // Auto rotation is turned on when the device is physically used as a tablet
-  // (i.e. flipped or detached), regardless of the UI state (i.e. whether tablet
-  // mode is turned on or off).
-  const bool is_auto_rotation_on =
-      tablet_mode_controller->is_in_tablet_physical_state();
-
-  if (is_auto_rotation_on)
-    TriggerRead();
-  else
-    CancelRead();
-}
-
 AccelerometerFileReader::InitializationResult::InitializationResult()
     : initialization_state(State::INITIALIZING),
       ec_lid_angle_driver_status(ECLidAngleDriverStatus::UNKNOWN) {}
@@ -465,9 +388,9 @@
     case State::SUCCESS:
       DCHECK_NE(result.ec_lid_angle_driver_status,
                 ECLidAngleDriverStatus::UNKNOWN);
-      ec_lid_angle_driver_status_ = result.ec_lid_angle_driver_status;
+      SetECLidAngleDriverStatus(result.ec_lid_angle_driver_status);
 
-      if (ec_lid_angle_driver_status_ ==
+      if (GetECLidAngleDriverStatus() ==
           ECLidAngleDriverStatus::NOT_SUPPORTED) {
         // If ChromeOS lid angle driver is not present, start accelerometer read
         // and read is always on.
@@ -593,6 +516,20 @@
   return true;
 }
 
+void AccelerometerFileReader::EnableAccelerometerReading() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (read_refresh_timer_.IsRunning())
+    return;
+
+  read_refresh_timer_.Start(FROM_HERE, kDelayBetweenReads, this,
+                            &AccelerometerFileReader::ReadSample);
+}
+
+void AccelerometerFileReader::DisableAccelerometerReading() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  read_refresh_timer_.Stop();
+}
+
 void AccelerometerFileReader::ReadSample() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
@@ -640,19 +577,8 @@
 
   ui_task_runner_->PostTask(
       FROM_HERE,
-      base::BindOnce(&AccelerometerFileReader::NotifyObserversWithUpdate, this,
+      base::BindOnce(&AccelerometerFileReader::NotifyAccelerometerUpdated, this,
                      update));
 }
 
-void AccelerometerFileReader::NotifyObserversWithUpdate(
-    const AccelerometerUpdate& update) {
-  DCHECK(base::CurrentUIThread::IsSet());
-
-  if (!emit_events_)
-    return;
-
-  for (auto& observer : observers_)
-    observer.OnAccelerometerUpdated(update);
-}
-
 }  // namespace ash
diff --git a/ash/accelerometer/accelerometer_file_reader.h b/ash/accelerometer/accelerometer_file_reader.h
index 9400055..4f6503e6 100644
--- a/ash/accelerometer/accelerometer_file_reader.h
+++ b/ash/accelerometer/accelerometer_file_reader.h
@@ -9,9 +9,7 @@
 #include <vector>
 
 #include "ash/accelerometer/accelerometer_reader.h"
-#include "ash/public/cpp/tablet_mode_observer.h"
 #include "base/files/file_util.h"
-#include "base/observer_list.h"
 #include "base/sequence_checker.h"
 #include "base/timer/timer.h"
 
@@ -20,8 +18,7 @@
 // Work that runs on a base::TaskRunner. It determines the accelerometer
 // configuration, and reads the data. Upon a successful read it will notify
 // all observers.
-class AccelerometerFileReader : public AccelerometerProviderInterface,
-                                public TabletModeObserver {
+class AccelerometerFileReader : public AccelerometerProviderInterface {
  public:
   AccelerometerFileReader();
   AccelerometerFileReader(const AccelerometerFileReader&) = delete;
@@ -29,24 +26,8 @@
 
   // AccelerometerProviderInterface:
   void PrepareAndInitialize() override;
-  void AddObserver(AccelerometerReader::Observer* observer) override;
-  void RemoveObserver(AccelerometerReader::Observer* observer) override;
-  void StartListenToTabletModeController() override;
-  void StopListenToTabletModeController() override;
-  void SetEmitEvents(bool emit_events) override;
-
-  // Controls accelerometer reading.
-  void EnableAccelerometerReading();
-  void DisableAccelerometerReading();
-
-  // With ChromeOS EC lid angle driver present, it's triggered when the device
-  // is physically used as a tablet (even thought its UI might be in clamshell
-  // mode), cancelled otherwise.
-  void TriggerRead();
-  void CancelRead();
-
-  // TabletModeObserver:
-  void OnTabletPhysicalStateChanged() override;
+  void TriggerRead() override;
+  void CancelRead() override;
 
  private:
   struct InitializationResult {
@@ -97,11 +78,11 @@
 
   ~AccelerometerFileReader() override;
 
-  // Post a task to initialize on |task_runner_| and process the result on the
-  // UI thread. May be called multiple times in the retries.
+  // Post a task to initialize on |blocking_task_runner_| and process the result
+  // on the UI thread. May be called multiple times in the retries.
   void TryScheduleInitialize();
 
-  // Detects the accelerometer configuration in |task_runner_|.
+  // Detects the accelerometer configuration in |blocking_task_runner_|.
   // If an accelerometer is available, it triggers reads.
   // This function MAY be called more than once.
   // This function contains the actual initialization code to be run by the
@@ -130,12 +111,14 @@
   bool InitializeLegacyAccelerometers(const base::FilePath& iio_path,
                                       const base::FilePath& name);
 
-  // Attempts to read the accelerometer data in |task_runner_|. Upon a success,
-  // converts the raw reading to an AccelerometerUpdate and notifies observers.
-  void ReadSample();
-  void NotifyObserversWithUpdate(const AccelerometerUpdate& update);
+  // Controls accelerometer reading.
+  void EnableAccelerometerReading();
+  void DisableAccelerometerReading();
 
-  bool emit_events_ = true;
+  // Attempts to read the accelerometer data in |blocking_task_runner_|. Upon a
+  // success, converts the raw reading to an AccelerometerUpdate and notifies
+  // observers.
+  void ReadSample();
 
   // The time at which initialization re-tries should stop.
   base::TimeTicks initialization_timeout_;
@@ -149,15 +132,9 @@
   base::RepeatingTimer read_refresh_timer_
       GUARDED_BY_CONTEXT(sequence_checker_);
 
-  // The observers to notify of accelerometer updates.
-  base::ObserverList<AccelerometerReader::Observer>::Unchecked observers_;
-
   // The task runner to use for blocking tasks.
   scoped_refptr<base::SequencedTaskRunner> blocking_task_runner_;
 
-  // The task runner of the UI thread.
-  scoped_refptr<base::SequencedTaskRunner> ui_task_runner_;
-
   SEQUENCE_CHECKER(sequence_checker_);
 };
 
diff --git a/ash/accelerometer/accelerometer_provider_mojo.cc b/ash/accelerometer/accelerometer_provider_mojo.cc
index 371fe425..95155bc 100644
--- a/ash/accelerometer/accelerometer_provider_mojo.cc
+++ b/ash/accelerometer/accelerometer_provider_mojo.cc
@@ -11,7 +11,6 @@
 #include "ash/shell.h"
 #include "ash/wm/tablet_mode/tablet_mode_controller.h"
 #include "base/bind.h"
-#include "base/observer_list_threadsafe.h"
 #include "base/ranges/algorithm.h"
 #include "base/stl_util.h"
 #include "base/strings/string_number_conversions.h"
@@ -33,89 +32,20 @@
 
 void AccelerometerProviderMojo::PrepareAndInitialize() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  // This function should only be called once.
-  DCHECK(!task_runner_);
 
-  task_runner_ = base::SequencedTaskRunnerHandle::Get();
-
-  task_runner_->PostTask(
-      FROM_HERE,
-      base::BindOnce(&AccelerometerProviderMojo::RegisterSensorClient,
-                     base::Unretained(this)));
+  RegisterSensorClient();
 }
 
-void AccelerometerProviderMojo::AddObserver(
-    AccelerometerReader::Observer* observer) {
+void AccelerometerProviderMojo::TriggerRead() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  DCHECK(task_runner_);
-
-  observers_.AddObserver(observer);
-
-  one_time_read_ = true;
-
-  if (accelerometer_read_on_)
-    return;
-
-  for (auto& accelerometer : accelerometers_) {
-    if (!accelerometer.second.samples_observer.get())
-      continue;
-
-    accelerometer.second.samples_observer->SetEnabled(true);
-  }
+  if (GetECLidAngleDriverStatus() == ECLidAngleDriverStatus::SUPPORTED)
+    EnableAccelerometerReading();
 }
 
-void AccelerometerProviderMojo::RemoveObserver(
-    AccelerometerReader::Observer* observer) {
+void AccelerometerProviderMojo::CancelRead() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  DCHECK(task_runner_);
-
-  observers_.RemoveObserver(observer);
-}
-
-void AccelerometerProviderMojo::StartListenToTabletModeController() {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  Shell::Get()->tablet_mode_controller()->AddObserver(this);
-}
-
-void AccelerometerProviderMojo::StopListenToTabletModeController() {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  Shell::Get()->tablet_mode_controller()->RemoveObserver(this);
-}
-
-void AccelerometerProviderMojo::SetEmitEvents(bool emit_events) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  emit_events_ = emit_events;
-}
-
-void AccelerometerProviderMojo::OnTabletPhysicalStateChanged() {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-
-  // Wait until the existence of the driver is determined.
-  if (ec_lid_angle_driver_status_ == ECLidAngleDriverStatus::UNKNOWN) {
-    pending_on_tablet_physical_state_changed_ = true;
-    return;
-  }
-
-  // When CrOS EC lid angle driver is not present, accelerometer read is always
-  // ON and can't be tuned. Thus AccelerometerProviderMojo no longer listens
-  // to tablet mode event.
-  auto* tablet_mode_controller = Shell::Get()->tablet_mode_controller();
-  if (ec_lid_angle_driver_status_ == ECLidAngleDriverStatus::NOT_SUPPORTED) {
-    tablet_mode_controller->RemoveObserver(this);
-    return;
-  }
-
-  // Auto rotation is turned on when the device is physically used as a tablet
-  // (i.e. flipped or detached), regardless of the UI state (i.e. whether tablet
-  // mode is turned on or off).
-  const bool is_auto_rotation_on =
-      tablet_mode_controller->is_in_tablet_physical_state();
-
-  task_runner_->PostNonNestableTask(
-      FROM_HERE,
-      is_auto_rotation_on
-          ? base::BindOnce(&AccelerometerProviderMojo::TriggerRead, this)
-          : base::BindOnce(&AccelerometerProviderMojo::CancelRead, this));
+  if (GetECLidAngleDriverStatus() == ECLidAngleDriverStatus::SUPPORTED)
+    DisableAccelerometerReading();
 }
 
 void AccelerometerProviderMojo::SetUpChannel(
@@ -127,39 +57,35 @@
     return;
   }
 
-  sensor_service_remote_.Bind(std::move(pending_remote), task_runner_);
-  sensor_service_remote_.set_disconnect_handler(
-      base::BindOnce(&AccelerometerProviderMojo::OnSensorServiceDisconnect,
-                     base::Unretained(this)));
-  if (ec_lid_angle_driver_status_ == ECLidAngleDriverStatus::UNKNOWN) {
+  sensor_service_remote_.Bind(std::move(pending_remote));
+  sensor_service_remote_.set_disconnect_handler(base::BindOnce(
+      &AccelerometerProviderMojo::OnSensorServiceDisconnect, this));
+  if (GetECLidAngleDriverStatus() == ECLidAngleDriverStatus::UNKNOWN) {
     sensor_service_remote_->GetDeviceIds(
         chromeos::sensors::mojom::DeviceType::ANGL,
         base::BindOnce(&AccelerometerProviderMojo::GetLidAngleIdsCallback,
-                       base::Unretained(this)));
+                       this));
   }
 
   sensor_service_remote_->GetDeviceIds(
       chromeos::sensors::mojom::DeviceType::ACCEL,
       base::BindOnce(&AccelerometerProviderMojo::GetAccelerometerIdsCallback,
-                     base::Unretained(this)));
-}
-
-void AccelerometerProviderMojo::TriggerRead() {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  if (ec_lid_angle_driver_status_ == ECLidAngleDriverStatus::SUPPORTED)
-    EnableAccelerometerReading();
-}
-
-void AccelerometerProviderMojo::CancelRead() {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  if (ec_lid_angle_driver_status_ == ECLidAngleDriverStatus::SUPPORTED)
-    DisableAccelerometerReading();
+                     this));
 }
 
 State AccelerometerProviderMojo::GetInitializationStateForTesting() const {
   return initialization_state_;
 }
 
+bool AccelerometerProviderMojo::ShouldDelayOnTabletPhysicalStateChanged() {
+  if (GetECLidAngleDriverStatus() == ECLidAngleDriverStatus::UNKNOWN) {
+    pending_on_tablet_physical_state_changed_ = true;
+    return true;
+  }
+
+  return false;
+}
+
 AccelerometerProviderMojo::AccelerometerData::AccelerometerData() = default;
 AccelerometerProviderMojo::AccelerometerData::~AccelerometerData() = default;
 
@@ -171,9 +97,8 @@
   chromeos::sensors::SensorHalDispatcher::GetInstance()->RegisterClient(
       sensor_hal_client_.BindNewPipeAndPassRemote());
 
-  sensor_hal_client_.set_disconnect_handler(
-      base::BindOnce(&AccelerometerProviderMojo::OnSensorHalClientFailure,
-                     base::Unretained(this)));
+  sensor_hal_client_.set_disconnect_handler(base::BindOnce(
+      &AccelerometerProviderMojo::OnSensorHalClientFailure, this));
 }
 
 void AccelerometerProviderMojo::OnSensorHalClientFailure() {
@@ -184,10 +109,9 @@
   ResetSensorService();
   sensor_hal_client_.reset();
 
-  task_runner_->PostDelayedTask(
+  ui_task_runner_->PostDelayedTask(
       FROM_HERE,
-      base::BindOnce(&AccelerometerProviderMojo::RegisterSensorClient,
-                     base::Unretained(this)),
+      base::BindOnce(&AccelerometerProviderMojo::RegisterSensorClient, this),
       kDelayReconnect);
 }
 
@@ -212,12 +136,12 @@
 void AccelerometerProviderMojo::GetLidAngleIdsCallback(
     const std::vector<int32_t>& lid_angle_ids) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  DCHECK_EQ(ec_lid_angle_driver_status_, ECLidAngleDriverStatus::UNKNOWN);
+  DCHECK_EQ(GetECLidAngleDriverStatus(), ECLidAngleDriverStatus::UNKNOWN);
 
   if (!lid_angle_ids.empty()) {
-    ec_lid_angle_driver_status_ = ECLidAngleDriverStatus::SUPPORTED;
+    SetECLidAngleDriverStatus(ECLidAngleDriverStatus::SUPPORTED);
   } else {
-    ec_lid_angle_driver_status_ = ECLidAngleDriverStatus::NOT_SUPPORTED;
+    SetECLidAngleDriverStatus(ECLidAngleDriverStatus::NOT_SUPPORTED);
     EnableAccelerometerReading();
   }
 
@@ -261,8 +185,7 @@
   sensor_service_remote_->GetDevice(
       id, accelerometer.remote.BindNewPipeAndPassReceiver());
   accelerometer.remote.set_disconnect_handler(base::BindOnce(
-      &AccelerometerProviderMojo::OnAccelerometerRemoteDisconnect,
-      base::Unretained(this), id));
+      &AccelerometerProviderMojo::OnAccelerometerRemoteDisconnect, this, id));
 
   std::vector<std::string> attr_names;
   if (!accelerometer.location.has_value())
@@ -273,8 +196,8 @@
   if (!attr_names.empty()) {
     accelerometer.remote->GetAttributes(
         attr_names,
-        base::BindOnce(&AccelerometerProviderMojo::GetAttributesCallback,
-                       base::Unretained(this), id));
+        base::BindOnce(&AccelerometerProviderMojo::GetAttributesCallback, this,
+                       id));
   } else {
     // Create the observer directly if the attributes have already been
     // retrieved.
@@ -378,7 +301,7 @@
 
 void AccelerometerProviderMojo::CheckInitialization() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  DCHECK_NE(ec_lid_angle_driver_status_, ECLidAngleDriverStatus::UNKNOWN);
+  DCHECK_NE(GetECLidAngleDriverStatus(), ECLidAngleDriverStatus::UNKNOWN);
 
   if (initialization_state_ != State::INITIALIZING)
     return;
@@ -390,7 +313,7 @@
         continue;
 
       if (accelerometer.second.location == ACCELEROMETER_SOURCE_SCREEN ||
-          ec_lid_angle_driver_status_ ==
+          GetECLidAngleDriverStatus() ==
               ECLidAngleDriverStatus::NOT_SUPPORTED) {
         // This ignored accelerometer is essential.
         FailedToInitialize();
@@ -431,7 +354,7 @@
   DCHECK(accelerometer.scale.has_value() && accelerometer.location.has_value());
 
   if (accelerometer.location == ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD &&
-      ec_lid_angle_driver_status_ == ECLidAngleDriverStatus::SUPPORTED) {
+      GetECLidAngleDriverStatus() == ECLidAngleDriverStatus::SUPPORTED) {
     // Skipping as it's only needed if lid-angle is not supported.
     // |GetLidAngleIdsCallback| will call this function with this |id| again if
     // it's found not supported.
@@ -442,16 +365,15 @@
       std::make_unique<AccelerometerSamplesObserver>(
           id, std::move(accelerometer.remote), accelerometer.scale.value(),
           base::BindRepeating(
-              &AccelerometerProviderMojo::OnSampleUpdatedCallback,
-              base::Unretained(this)));
+              &AccelerometerProviderMojo::OnSampleUpdatedCallback, this));
 
-  if (accelerometer_read_on_ || one_time_read_)
+  if (accelerometer_read_on_)
     accelerometer.samples_observer->SetEnabled(true);
 }
 
 void AccelerometerProviderMojo::EnableAccelerometerReading() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  DCHECK_NE(ec_lid_angle_driver_status_, ECLidAngleDriverStatus::UNKNOWN);
+  DCHECK_NE(GetECLidAngleDriverStatus(), ECLidAngleDriverStatus::UNKNOWN);
   if (accelerometer_read_on_)
     return;
 
@@ -466,17 +388,12 @@
 
 void AccelerometerProviderMojo::DisableAccelerometerReading() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  DCHECK_EQ(ec_lid_angle_driver_status_, ECLidAngleDriverStatus::SUPPORTED);
+  DCHECK_EQ(GetECLidAngleDriverStatus(), ECLidAngleDriverStatus::SUPPORTED);
   if (!accelerometer_read_on_)
     return;
 
   accelerometer_read_on_ = false;
 
-  // Allow one more read and let |OnSampleUpdatedCallback| disable the
-  // observers.
-  if (one_time_read_)
-    return;
-
   for (auto& accelerometer : accelerometers_) {
     if (!accelerometer.second.samples_observer.get())
       continue;
@@ -495,17 +412,14 @@
   DCHECK(accelerometer.location.has_value());
 
   bool need_two_accelerometers =
-      (ec_lid_angle_driver_status_ == ECLidAngleDriverStatus::NOT_SUPPORTED &&
+      (GetECLidAngleDriverStatus() == ECLidAngleDriverStatus::NOT_SUPPORTED &&
        has_accelerometer_base_);
 
-  if (!one_time_read_ && !accelerometer_read_on_) {
+  if (!accelerometer_read_on_) {
     // This sample is not needed.
     return;
   }
 
-  if (!emit_events_)
-    return;
-
   update_.Set(accelerometers_[iio_device_id].location.value(), sample[0],
               sample[1], sample[2]);
 
@@ -516,22 +430,8 @@
     return;
   }
 
-  for (auto& observer : observers_)
-    observer.OnAccelerometerUpdated(update_);
-
+  NotifyAccelerometerUpdated(update_);
   update_.Reset();
-
-  one_time_read_ = false;
-  if (accelerometer_read_on_)
-    return;
-
-  // This was a one time read. Disable observers.
-  for (auto& accelerometer : accelerometers_) {
-    if (!accelerometer.second.samples_observer.get())
-      continue;
-
-    accelerometer.second.samples_observer->SetEnabled(false);
-  }
 }
 
 void AccelerometerProviderMojo::FailedToInitialize() {
diff --git a/ash/accelerometer/accelerometer_provider_mojo.h b/ash/accelerometer/accelerometer_provider_mojo.h
index 5184d66..3767075 100644
--- a/ash/accelerometer/accelerometer_provider_mojo.h
+++ b/ash/accelerometer/accelerometer_provider_mojo.h
@@ -15,11 +15,8 @@
 #include "ash/accelerometer/accelerometer_reader.h"
 #include "ash/accelerometer/accelerometer_samples_observer.h"
 #include "ash/ash_export.h"
-#include "ash/public/cpp/tablet_mode_observer.h"
-#include "base/observer_list.h"
 #include "base/optional.h"
 #include "base/sequence_checker.h"
-#include "base/sequenced_task_runner.h"
 #include "mojo/public/cpp/bindings/receiver.h"
 #include "mojo/public/cpp/bindings/remote.h"
 
@@ -31,7 +28,6 @@
 // observers.
 class ASH_EXPORT AccelerometerProviderMojo
     : public AccelerometerProviderInterface,
-      public TabletModeObserver,
       public chromeos::sensors::mojom::SensorHalClient {
  public:
   AccelerometerProviderMojo();
@@ -41,27 +37,19 @@
 
   // AccelerometerProviderInterface:
   void PrepareAndInitialize() override;
-  void AddObserver(AccelerometerReader::Observer* observer) override;
-  void RemoveObserver(AccelerometerReader::Observer* observer) override;
-  void StartListenToTabletModeController() override;
-  void StopListenToTabletModeController() override;
-  void SetEmitEvents(bool emit_events) override;
-
-  // TabletModeObserver:
-  void OnTabletPhysicalStateChanged() override;
+  void TriggerRead() override;
+  void CancelRead() override;
 
   // chromeos::sensors::mojom::SensorHalClient:
   void SetUpChannel(mojo::PendingRemote<chromeos::sensors::mojom::SensorService>
                         pending_remote) override;
 
-  // With ChromeOS EC lid angle driver present, it's triggered when the device
-  // is physically used as a tablet (even thought its UI might be in clamshell
-  // mode), cancelled otherwise.
-  void TriggerRead();
-  void CancelRead();
-
   State GetInitializationStateForTesting() const;
 
+ protected:
+  // AccelerometerProviderInterface:
+  bool ShouldDelayOnTabletPhysicalStateChanged() override;
+
  private:
   struct AccelerometerData {
     AccelerometerData();
@@ -117,15 +105,13 @@
   void EnableAccelerometerReading();
   void DisableAccelerometerReading();
 
-  // Called by |observers_|, containing a sample of the accelerometer.
+  // Called by |AccelerometerData::samples_observer| stored in the
+  // |accelerometers_| map, containing a sample of the accelerometer.
   void OnSampleUpdatedCallback(int iio_device_id, std::vector<float> sample);
 
   // Sets FAILED to |initialization_state_| due to an error.
   void FailedToInitialize();
 
-  // The task runner to use for blocking tasks.
-  scoped_refptr<base::SequencedTaskRunner> task_runner_;
-
   // The Mojo channel connecting to Sensor Hal Dispatcher.
   mojo::Receiver<chromeos::sensors::mojom::SensorHalClient> sensor_hal_client_{
       this};
@@ -148,20 +134,9 @@
   // |ec_lid_angle_driver_status_| is set.
   bool pending_on_tablet_physical_state_changed_ = false;
 
-  // One time read upon |AddObserver|.
-  // Some observers need to know ECLidAngleDriverStatus, and it's guaranteed to
-  // be set before reading samples. When adding an observer, trigger at least
-  // one sample to notify observers that ECLidAngleDriverStatus has been set.
-  bool one_time_read_ = false;
-
   // True if periodical accelerometer read is on.
   bool accelerometer_read_on_ = false;
 
-  bool emit_events_ = true;
-
-  // The observers to notify of accelerometer updates.
-  base::ObserverList<AccelerometerReader::Observer>::Unchecked observers_;
-
   // The last seen accelerometer data.
   AccelerometerUpdate update_;
 
diff --git a/ash/accelerometer/accelerometer_provider_mojo_unittest.cc b/ash/accelerometer/accelerometer_provider_mojo_unittest.cc
index 58ac3b4..46e39740 100644
--- a/ash/accelerometer/accelerometer_provider_mojo_unittest.cc
+++ b/ash/accelerometer/accelerometer_provider_mojo_unittest.cc
@@ -32,6 +32,10 @@
 
 class FakeObserver : public AccelerometerReader::Observer {
  public:
+  void OnECLidAngleDriverStatusChanged(bool is_supported) override {
+    CHECK(!is_supported_.has_value());
+    is_supported_ = is_supported;
+  }
   void OnAccelerometerUpdated(const AccelerometerUpdate& update) override {
     for (uint32_t index = 0; index < ACCELEROMETER_SOURCE_COUNT; ++index) {
       auto source = static_cast<AccelerometerSource>(index);
@@ -46,6 +50,7 @@
     update_ = update;
   }
 
+  base::Optional<bool> is_supported_;
   AccelerometerUpdate update_;
 };
 
@@ -103,7 +108,8 @@
   std::unique_ptr<chromeos::sensors::FakeSensorHalServer> sensor_hal_server_;
   scoped_refptr<AccelerometerProviderMojo> provider_;
 
-  base::test::SingleThreadTaskEnvironment task_environment;
+  base::test::SingleThreadTaskEnvironment task_environment{
+      base::test::TaskEnvironment::MainThreadType::UI};
 };
 
 TEST_F(AccelerometerProviderMojoTest, CheckNoScale) {
@@ -117,6 +123,8 @@
   // Wait until initialization failed.
   base::RunLoop().RunUntilIdle();
 
+  EXPECT_TRUE(observer_.is_supported_.has_value());
+  EXPECT_FALSE(observer_.is_supported_.value());
   EXPECT_EQ(provider_->GetInitializationStateForTesting(), State::FAILED);
 }
 
@@ -131,6 +139,8 @@
   // Wait until initialization failed.
   base::RunLoop().RunUntilIdle();
 
+  EXPECT_TRUE(observer_.is_supported_.has_value());
+  EXPECT_FALSE(observer_.is_supported_.value());
   EXPECT_EQ(provider_->GetInitializationStateForTesting(), State::SUCCESS);
 }
 
@@ -141,6 +151,8 @@
   // Wait until a sample is received.
   base::RunLoop().RunUntilIdle();
 
+  EXPECT_TRUE(observer_.is_supported_.has_value());
+  EXPECT_FALSE(observer_.is_supported_.value());
   EXPECT_EQ(provider_->GetInitializationStateForTesting(), State::SUCCESS);
 
   EXPECT_TRUE(observer_.update_.has(ACCELEROMETER_SOURCE_SCREEN));
@@ -159,6 +171,8 @@
   // Wait until samples are received.
   base::RunLoop().RunUntilIdle();
 
+  EXPECT_TRUE(observer_.is_supported_.has_value());
+  EXPECT_FALSE(observer_.is_supported_.value());
   EXPECT_EQ(provider_->GetInitializationStateForTesting(), State::SUCCESS);
 
   EXPECT_TRUE(observer_.update_.has(ACCELEROMETER_SOURCE_SCREEN));
@@ -190,11 +204,13 @@
   chromeos::sensors::SensorHalDispatcher::GetInstance()->RegisterServer(
       sensor_hal_server_->PassRemote());
 
-  // Wait until all setups are finished and the one time read is done.
+  // Wait until all setups are finished and no samples updated.
   base::RunLoop().RunUntilIdle();
 
+  EXPECT_TRUE(observer_.is_supported_.has_value());
+  EXPECT_TRUE(observer_.is_supported_.value());
   EXPECT_EQ(provider_->GetInitializationStateForTesting(), State::SUCCESS);
-  EXPECT_TRUE(observer_.update_.has(ACCELEROMETER_SOURCE_SCREEN));
+  EXPECT_FALSE(observer_.update_.has(ACCELEROMETER_SOURCE_SCREEN));
   EXPECT_FALSE(observer_.update_.has(ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD));
 
   observer_.update_.Reset();
diff --git a/ash/accelerometer/accelerometer_reader.cc b/ash/accelerometer/accelerometer_reader.cc
index 95b90d5..a654401 100644
--- a/ash/accelerometer/accelerometer_reader.cc
+++ b/ash/accelerometer/accelerometer_reader.cc
@@ -8,9 +8,12 @@
 
 #include "ash/accelerometer/accelerometer_file_reader.h"
 #include "ash/accelerometer/accelerometer_provider_mojo.h"
+#include "ash/shell.h"
+#include "ash/wm/tablet_mode/tablet_mode_controller.h"
 #include "base/no_destructor.h"
 #include "base/posix/eintr_wrapper.h"
 #include "base/sequenced_task_runner.h"
+#include "base/task/current_thread.h"
 #include "base/threading/sequenced_task_runner_handle.h"
 
 namespace ash {
@@ -52,10 +55,6 @@
   accelerometer_provider_->SetEmitEvents(enabled);
 }
 
-ECLidAngleDriverStatus AccelerometerReader::GetECLidAngleDriverStatus() const {
-  return accelerometer_provider_->GetECLidAngleDriverStatus();
-}
-
 void AccelerometerReader::SetECLidAngleDriverStatusForTesting(
     ECLidAngleDriverStatus ec_lid_angle_driver_status) {
   accelerometer_provider_->SetECLidAngleDriverStatusForTesting(  // IN-TEST
@@ -78,14 +77,110 @@
 
 AccelerometerReader::~AccelerometerReader() = default;
 
-ECLidAngleDriverStatus
-AccelerometerProviderInterface::GetECLidAngleDriverStatus() const {
-  return ec_lid_angle_driver_status_;
+void AccelerometerProviderInterface::OnTabletPhysicalStateChanged() {
+  DCHECK(base::CurrentUIThread::IsSet());
+
+  if (ShouldDelayOnTabletPhysicalStateChanged())
+    return;
+
+  // When CrOS EC lid angle driver is not present, accelerometer read is always
+  // ON and can't be tuned. Thus this object no longer listens to tablet mode
+  // event.
+  auto* tablet_mode_controller = Shell::Get()->tablet_mode_controller();
+  if (ec_lid_angle_driver_status_ == ECLidAngleDriverStatus::NOT_SUPPORTED) {
+    tablet_mode_controller->RemoveObserver(this);
+    return;
+  }
+
+  // Auto rotation is turned on when the device is physically used as a tablet
+  // (i.e. flipped or detached), regardless of the UI state (i.e. whether tablet
+  // mode is turned on or off).
+  if (tablet_mode_controller->is_in_tablet_physical_state())
+    TriggerRead();
+  else
+    CancelRead();
+}
+
+void AccelerometerProviderInterface::AddObserver(
+    AccelerometerReader::Observer* observer) {
+  DCHECK(base::CurrentUIThread::IsSet());
+
+  if (ec_lid_angle_driver_status_ != ECLidAngleDriverStatus::UNKNOWN) {
+    observer->OnECLidAngleDriverStatusChanged(
+        ec_lid_angle_driver_status_ == ECLidAngleDriverStatus::SUPPORTED);
+  }
+
+  observers_.AddObserver(observer);
+}
+
+void AccelerometerProviderInterface::RemoveObserver(
+    AccelerometerReader::Observer* observer) {
+  DCHECK(base::CurrentUIThread::IsSet());
+  observers_.RemoveObserver(observer);
+}
+
+void AccelerometerProviderInterface::StartListenToTabletModeController() {
+  Shell::Get()->tablet_mode_controller()->AddObserver(this);
+}
+
+void AccelerometerProviderInterface::StopListenToTabletModeController() {
+  Shell::Get()->tablet_mode_controller()->RemoveObserver(this);
+}
+
+void AccelerometerProviderInterface::SetEmitEvents(bool emit_events) {
+  DCHECK(base::CurrentUIThread::IsSet());
+  emit_events_ = emit_events;
 }
 
 void AccelerometerProviderInterface::SetECLidAngleDriverStatusForTesting(
-    ECLidAngleDriverStatus ec_lid_angle_driver_status) {
-  ec_lid_angle_driver_status_ = ec_lid_angle_driver_status;
+    ECLidAngleDriverStatus status) {
+  SetECLidAngleDriverStatus(status);
+}
+
+AccelerometerProviderInterface::AccelerometerProviderInterface()
+    : ui_task_runner_(base::SequencedTaskRunnerHandle::Get()) {
+  DCHECK(base::CurrentUIThread::IsSet());
+}
+
+AccelerometerProviderInterface::~AccelerometerProviderInterface() = default;
+
+bool AccelerometerProviderInterface::ShouldDelayOnTabletPhysicalStateChanged() {
+  return false;
+}
+
+void AccelerometerProviderInterface::SetECLidAngleDriverStatus(
+    ECLidAngleDriverStatus status) {
+  DCHECK(base::CurrentUIThread::IsSet());
+  DCHECK_NE(status, ECLidAngleDriverStatus::UNKNOWN);
+
+  if (status == ec_lid_angle_driver_status_)
+    return;
+
+  ec_lid_angle_driver_status_ = status;
+
+  for (auto& observer : observers_) {
+    observer.OnECLidAngleDriverStatusChanged(ec_lid_angle_driver_status_ ==
+                                             ECLidAngleDriverStatus::SUPPORTED);
+  }
+}
+
+ECLidAngleDriverStatus
+AccelerometerProviderInterface::GetECLidAngleDriverStatus() const {
+  DCHECK(base::CurrentUIThread::IsSet());
+
+  return ec_lid_angle_driver_status_;
+}
+
+void AccelerometerProviderInterface::NotifyAccelerometerUpdated(
+    const AccelerometerUpdate& update) {
+  DCHECK(base::CurrentUIThread::IsSet());
+  DCHECK_NE(ec_lid_angle_driver_status_, ECLidAngleDriverStatus::UNKNOWN);
+
+  if (!emit_events_)
+    return;
+
+  for (auto& observer : observers_)
+    observer.OnAccelerometerUpdated(update);
 }
 
 }  // namespace ash
diff --git a/ash/accelerometer/accelerometer_reader.h b/ash/accelerometer/accelerometer_reader.h
index e039cb70..3c3be67 100644
--- a/ash/accelerometer/accelerometer_reader.h
+++ b/ash/accelerometer/accelerometer_reader.h
@@ -7,8 +7,10 @@
 
 #include "ash/accelerometer/accelerometer_types.h"
 #include "ash/ash_export.h"
+#include "ash/public/cpp/tablet_mode_observer.h"
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
+#include "base/observer_list.h"
 
 namespace base {
 template <typename T>
@@ -39,6 +41,11 @@
   // An interface to receive data from the AccelerometerReader.
   class Observer {
    public:
+    // Called only once, when
+    // |AcceleromterProviderInterface::ec_lid_angle_driver_status_| is set to
+    // either SUPPORTED or NOT_SUPPORTED.
+    // It's also guaranteed to be called before |OnAccelerometerUpdated|.
+    virtual void OnECLidAngleDriverStatusChanged(bool is_supported) = 0;
     virtual void OnAccelerometerUpdated(const AccelerometerUpdate& update) = 0;
 
    protected:
@@ -62,9 +69,6 @@
   // be able to control the accelerometer feature.
   void SetEnabled(bool enabled);
 
-  // Return the state of the driver being supported or not.
-  ECLidAngleDriverStatus GetECLidAngleDriverStatus() const;
-
   void SetECLidAngleDriverStatusForTesting(
       ECLidAngleDriverStatus ec_lid_angle_driver_status);
 
@@ -83,41 +87,71 @@
   scoped_refptr<AccelerometerProviderInterface> accelerometer_provider_;
 };
 
-class AccelerometerProviderInterface
-    : public base::RefCountedThreadSafe<AccelerometerProviderInterface> {
+class ASH_EXPORT AccelerometerProviderInterface
+    : public base::RefCountedThreadSafe<AccelerometerProviderInterface>,
+      public TabletModeObserver {
  public:
   // Prepare and start async initialization.
   virtual void PrepareAndInitialize() = 0;
+  // With ChromeOS EC lid angle driver present, it's triggered when the device
+  // is physically used as a tablet (even thought its UI might be in clamshell
+  // mode), cancelled otherwise.
+  virtual void TriggerRead() = 0;
+  virtual void CancelRead() = 0;
+
+  // TabletModeObserver:
+  void OnTabletPhysicalStateChanged() override;
 
   // Add/Remove observers.
-  virtual void AddObserver(AccelerometerReader::Observer* observer) = 0;
-  virtual void RemoveObserver(AccelerometerReader::Observer* observer) = 0;
+  void AddObserver(AccelerometerReader::Observer* observer);
+  void RemoveObserver(AccelerometerReader::Observer* observer);
 
   // Start/Stop listening to tablet mode controller.
-  virtual void StartListenToTabletModeController() = 0;
-  virtual void StopListenToTabletModeController() = 0;
+  void StartListenToTabletModeController();
+  void StopListenToTabletModeController();
 
   // Set emitting events (samples) to observers or not.
-  virtual void SetEmitEvents(bool emit_events) = 0;
+  void SetEmitEvents(bool emit_events);
 
-  // Return the state of the driver being supported or not.
-  ECLidAngleDriverStatus GetECLidAngleDriverStatus() const;
-
-  void SetECLidAngleDriverStatusForTesting(
-      ECLidAngleDriverStatus ec_lid_angle_driver_status);
+  void SetECLidAngleDriverStatusForTesting(ECLidAngleDriverStatus status);
 
  protected:
-  virtual ~AccelerometerProviderInterface() = default;
+  AccelerometerProviderInterface();
+  ~AccelerometerProviderInterface() override;
+
+  // Used in |OnTabletPhysicalStateChanged()|. As there might be
+  // initialization steps, each implementation can override this function to
+  // determine if this class is ready to process the state changed.
+  // If returns true, |OnTabletPhysicalStateChanged()| will be skipped, and it's
+  // the implementation's responsibility to call it again when the class is
+  // ready. If returns false, |OnTabletPhysicalStateChanged()| will be processed
+  // as usual.
+  // Default to return false.
+  virtual bool ShouldDelayOnTabletPhysicalStateChanged();
+
+  void SetECLidAngleDriverStatus(ECLidAngleDriverStatus status);
+  ECLidAngleDriverStatus GetECLidAngleDriverStatus() const;
+
+  void NotifyAccelerometerUpdated(const AccelerometerUpdate& update);
+
+  // Set in the constructor.
+  scoped_refptr<base::SequencedTaskRunner> ui_task_runner_;
 
   // The current initialization state of reader.
   State initialization_state_ = State::INITIALIZING;
 
+ private:
   // State of ChromeOS EC lid angle driver, if SUPPORTED, it means EC can handle
   // lid angle calculation.
   ECLidAngleDriverStatus ec_lid_angle_driver_status_ =
       ECLidAngleDriverStatus::UNKNOWN;
 
- private:
+  bool emit_events_ = true;
+
+  // The observers to notify of accelerometer updates.
+  // Bound to the UI thread.
+  base::ObserverList<AccelerometerReader::Observer>::Unchecked observers_;
+
   friend class base::RefCountedThreadSafe<AccelerometerProviderInterface>;
 };
 
diff --git a/ash/display/screen_orientation_controller.h b/ash/display/screen_orientation_controller.h
index 00d613f..74b503e6 100644
--- a/ash/display/screen_orientation_controller.h
+++ b/ash/display/screen_orientation_controller.h
@@ -152,6 +152,7 @@
   void OnWindowVisibilityChanged(aura::Window* window, bool visible) override;
 
   // AccelerometerReader::Observer:
+  void OnECLidAngleDriverStatusChanged(bool is_supported) override {}
   void OnAccelerometerUpdated(const AccelerometerUpdate& update) override;
 
   // WindowTreeHostManager::Observer:
diff --git a/ash/keyboard/ui/keyboard_ui_controller.cc b/ash/keyboard/ui/keyboard_ui_controller.cc
index 8e31eacc..7cff1ae 100644
--- a/ash/keyboard/ui/keyboard_ui_controller.cc
+++ b/ash/keyboard/ui/keyboard_ui_controller.cc
@@ -295,7 +295,10 @@
       parent_container_->RemoveChild(keyboard_window);
     }
   }
-  parent_container_->GetRootWindow()->RemoveObserver(this);
+  aura::Window* root_window = parent_container_->GetRootWindow();
+  if (root_window) {
+    root_window->RemoveObserver(this);
+  }
   parent_container_ = nullptr;
 }
 
diff --git a/ash/public/cpp/BUILD.gn b/ash/public/cpp/BUILD.gn
index 54d2eaa2..84b4d6d 100644
--- a/ash/public/cpp/BUILD.gn
+++ b/ash/public/cpp/BUILD.gn
@@ -287,6 +287,8 @@
     "update_types.h",
     "view_shadow.cc",
     "view_shadow.h",
+    "views_text_services_context_menu_impl.cc",
+    "views_text_services_context_menu_impl.h",
     "vm_camera_mic_constants.cc",
     "vm_camera_mic_constants.h",
     "wallpaper_controller.cc",
diff --git a/ash/public/cpp/test/shell_test_api.h b/ash/public/cpp/test/shell_test_api.h
index c4f43123..a0d6891 100644
--- a/ash/public/cpp/test/shell_test_api.h
+++ b/ash/public/cpp/test/shell_test_api.h
@@ -30,7 +30,6 @@
 class PowerPrefs;
 class ScreenPositionController;
 class Shell;
-class SystemGestureEventFilter;
 class WorkspaceController;
 
 // Accesses private data from a Shell for testing.
@@ -45,7 +44,6 @@
   static void SetTabletControllerUseScreenshotForTest(bool use_screenshot);
 
   MessageCenterController* message_center_controller();
-  SystemGestureEventFilter* system_gesture_event_filter();
   WorkspaceController* workspace_controller();
   ScreenPositionController* screen_position_controller();
   NativeCursorManagerAsh* native_cursor_manager_ash();
@@ -79,9 +77,6 @@
   // fullscreen button.
   void ToggleFullscreen();
 
-  // Returns true if it is in overview selecting mode.
-  bool IsOverviewSelecting();
-
   // Used to emulate display change when run in a desktop environment instead
   // of on a device.
   void AddRemoveDisplay();
@@ -121,10 +116,6 @@
   // It returns nullptr when app-list is not shown.
   PaginationModel* GetAppListPaginationModel();
 
-  // Returns the list of windows used in overview item. Returns empty
-  // if not in the overview mode.
-  std::vector<aura::Window*> GetItemWindowListInOverviewGrids();
-
  private:
   Shell* shell_;  // not owned
 
diff --git a/ash/public/cpp/views_text_services_context_menu_impl.cc b/ash/public/cpp/views_text_services_context_menu_impl.cc
new file mode 100644
index 0000000..42f1396a
--- /dev/null
+++ b/ash/public/cpp/views_text_services_context_menu_impl.cc
@@ -0,0 +1,98 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/public/cpp/views_text_services_context_menu_impl.h"
+
+#include "ash/public/cpp/clipboard_history_controller.h"
+#include "chromeos/constants/chromeos_features.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/pointer/touch_editing_controller.h"
+#include "ui/strings/grit/ui_strings.h"
+#include "ui/views/controls/textfield/textfield.h"
+
+namespace ash {
+
+ViewsTextServicesContextMenuImpl::ViewsTextServicesContextMenuImpl(
+    ui::SimpleMenuModel* menu,
+    views::Textfield* client)
+    : views::ViewsTextServicesContextMenuBase(menu, client) {
+  if (chromeos::features::IsClipboardHistoryEnabled())
+    AddClipboardHistoryMenuOption(menu);
+}
+
+ViewsTextServicesContextMenuImpl::~ViewsTextServicesContextMenuImpl() = default;
+
+bool ViewsTextServicesContextMenuImpl::GetAcceleratorForCommandId(
+    int command_id,
+    ui::Accelerator* accelerator) const {
+  if (command_id == IDS_APP_SHOW_CLIPBOARD_HISTORY) {
+    *accelerator = ui::Accelerator(ui::VKEY_V, ui::EF_COMMAND_DOWN);
+    return true;
+  }
+
+  return ViewsTextServicesContextMenuBase::GetAcceleratorForCommandId(
+      command_id, accelerator);
+}
+
+bool ViewsTextServicesContextMenuImpl::IsCommandIdChecked(
+    int command_id) const {
+  if (command_id == IDS_APP_SHOW_CLIPBOARD_HISTORY)
+    return true;
+
+  return ViewsTextServicesContextMenuBase::IsCommandIdChecked(command_id);
+}
+
+bool ViewsTextServicesContextMenuImpl::IsCommandIdEnabled(
+    int command_id) const {
+  if (command_id == IDS_APP_SHOW_CLIPBOARD_HISTORY)
+    return ClipboardHistoryController::Get()->CanShowMenu();
+
+  return ViewsTextServicesContextMenuBase::IsCommandIdEnabled(command_id);
+}
+
+void ViewsTextServicesContextMenuImpl::ExecuteCommand(int command_id,
+                                                      int event_flags) {
+  if (command_id == IDS_APP_SHOW_CLIPBOARD_HISTORY) {
+    auto* clipboard_history_controller = ClipboardHistoryController::Get();
+
+    // Calculate the menu source type from `event_flags`.
+    ui::MenuSourceType source_type;
+    if (event_flags & ui::EF_LEFT_MOUSE_BUTTON)
+      source_type = ui::MENU_SOURCE_MOUSE;
+    else if (event_flags & ui::EF_FROM_TOUCH)
+      source_type = ui::MENU_SOURCE_TOUCH;
+    else
+      source_type = ui::MENU_SOURCE_KEYBOARD;
+
+    clipboard_history_controller->ShowMenu(client()->GetCaretBounds(),
+                                           source_type);
+    return;
+  }
+
+  ViewsTextServicesContextMenuBase::ExecuteCommand(command_id, event_flags);
+}
+
+bool ViewsTextServicesContextMenuImpl::SupportsCommand(int command_id) const {
+  if (command_id == IDS_APP_SHOW_CLIPBOARD_HISTORY)
+    return true;
+
+  return ViewsTextServicesContextMenuBase::SupportsCommand(command_id);
+}
+
+void ViewsTextServicesContextMenuImpl::AddClipboardHistoryMenuOption(
+    ui::SimpleMenuModel* menu) {
+  const int index_of_paste =
+      menu->GetIndexOfCommandId(ui::TouchEditable::kPaste);
+
+  // Only add the clipboard history menu option when having the menu option
+  // for paste.
+  if (index_of_paste == -1)
+    return;
+
+  const int target_index = index_of_paste + 1;
+  menu->InsertItemAt(target_index, IDS_APP_SHOW_CLIPBOARD_HISTORY,
+                     l10n_util::GetStringUTF16(IDS_APP_SHOW_CLIPBOARD_HISTORY));
+}
+
+}  // namespace ash
diff --git a/ash/public/cpp/views_text_services_context_menu_impl.h b/ash/public/cpp/views_text_services_context_menu_impl.h
new file mode 100644
index 0000000..820de19e
--- /dev/null
+++ b/ash/public/cpp/views_text_services_context_menu_impl.h
@@ -0,0 +1,46 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ASH_PUBLIC_CPP_VIEWS_TEXT_SERVICES_CONTEXT_MENU_IMPL_H_
+#define ASH_PUBLIC_CPP_VIEWS_TEXT_SERVICES_CONTEXT_MENU_IMPL_H_
+
+#include "ash/public/cpp/ash_public_export.h"
+#include "ui/views/controls/views_text_services_context_menu_base.h"
+
+namespace views {
+class Textfield;
+}
+
+namespace ash {
+
+// This class supports the text context menu with the exclusive functions under
+// the CrOS environment.
+class ASH_PUBLIC_EXPORT ViewsTextServicesContextMenuImpl
+    : public views::ViewsTextServicesContextMenuBase {
+ public:
+  ViewsTextServicesContextMenuImpl(ui::SimpleMenuModel* menu,
+                                   views::Textfield* client);
+  ViewsTextServicesContextMenuImpl(const ViewsTextServicesContextMenuImpl&) =
+      delete;
+  ViewsTextServicesContextMenuImpl& operator=(
+      const ViewsTextServicesContextMenuImpl&) = delete;
+  ~ViewsTextServicesContextMenuImpl() override;
+
+  // ViewsTextServicesContextMenuBase:
+  bool GetAcceleratorForCommandId(int command_id,
+                                  ui::Accelerator* accelerator) const override;
+  bool IsCommandIdChecked(int command_id) const override;
+  bool IsCommandIdEnabled(int command_id) const override;
+  void ExecuteCommand(int command_id, int event_flags) override;
+  bool SupportsCommand(int command_id) const override;
+
+ private:
+  // Adds the menu option which shows the clipboard history menu after
+  // activation.
+  void AddClipboardHistoryMenuOption(ui::SimpleMenuModel* menu);
+};
+
+}  // namespace ash
+
+#endif  // ASH_PUBLIC_CPP_VIEWS_TEXT_SERVICES_CONTEXT_MENU_IMPL_H_
diff --git a/ash/shell.cc b/ash/shell.cc
index 764e276..11fb4272 100644
--- a/ash/shell.cc
+++ b/ash/shell.cc
@@ -84,6 +84,7 @@
 #include "ash/public/cpp/shelf_config.h"
 #include "ash/public/cpp/shelf_model.h"
 #include "ash/public/cpp/shell_window_ids.h"
+#include "ash/public/cpp/views_text_services_context_menu_impl.h"
 #include "ash/quick_answers/quick_answers_controller_impl.h"
 #include "ash/root_window_controller.h"
 #include "ash/screenshot_delegate.h"
@@ -210,6 +211,7 @@
 #include "ui/gfx/image/image_skia.h"
 #include "ui/message_center/message_center.h"
 #include "ui/ozone/public/ozone_platform.h"
+#include "ui/views/controls/views_text_services_context_menu_chromeos.h"
 #include "ui/views/corewm/tooltip_aura.h"
 #include "ui/views/corewm/tooltip_controller.h"
 #include "ui/views/focus/focus_manager_factory.h"
@@ -640,6 +642,10 @@
   RemovePreTargetHandler(modality_filter_.get());
   RemovePreTargetHandler(tooltip_controller_.get());
 
+  // Resets the text context menu implementation factory.
+  views::ViewsTextServicesContextMenuChromeos::SetImplFactory(
+      base::NullCallback());
+
   event_rewriter_controller_.reset();
 
   screen_orientation_controller_.reset();
@@ -1253,6 +1259,16 @@
         std::make_unique<DisplayAlignmentController>();
   }
 
+  // Injects the factory which fulfills the implementation of the text context
+  // menu exclusive to CrOS.
+  views::ViewsTextServicesContextMenuChromeos::SetImplFactory(
+      base::BindRepeating(
+          [](ui::SimpleMenuModel* menu_model, views::Textfield* textfield)
+              -> std::unique_ptr<views::ViewsTextServicesContextMenu> {
+            return std::make_unique<ViewsTextServicesContextMenuImpl>(
+                menu_model, textfield);
+          }));
+
   for (auto& observer : shell_observers_)
     observer.OnShellInitialized();
 
diff --git a/ash/shell_test_api.cc b/ash/shell_test_api.cc
index 082f30f..d2b28d1 100644
--- a/ash/shell_test_api.cc
+++ b/ash/shell_test_api.cc
@@ -118,10 +118,6 @@
   return shell_->message_center_controller_.get();
 }
 
-SystemGestureEventFilter* ShellTestApi::system_gesture_event_filter() {
-  return shell_->system_gesture_filter_.get();
-}
-
 WorkspaceController* ShellTestApi::workspace_controller() {
   // TODO(afakhry): Split this into two, one for root, and one for context.
   return GetActiveWorkspaceController(shell_->GetPrimaryRootWindow());
@@ -185,10 +181,6 @@
   accelerators::ToggleFullscreen();
 }
 
-bool ShellTestApi::IsOverviewSelecting() {
-  return shell_->overview_controller()->InOverviewSession();
-}
-
 void ShellTestApi::AddRemoveDisplay() {
   shell_->display_manager()->AddRemoveDisplay();
 }
@@ -260,10 +252,4 @@
   return view->GetAppsPaginationModel();
 }
 
-std::vector<aura::Window*> ShellTestApi::GetItemWindowListInOverviewGrids() {
-  return Shell::Get()
-      ->overview_controller()
-      ->GetItemWindowListInOverviewGridsForTest();
-}
-
 }  // namespace ash
diff --git a/ash/system/holding_space/holding_space_color_provider_impl.cc b/ash/system/holding_space/holding_space_color_provider_impl.cc
index d9c5f02..0c097b6 100644
--- a/ash/system/holding_space/holding_space_color_provider_impl.cc
+++ b/ash/system/holding_space/holding_space_color_provider_impl.cc
@@ -5,6 +5,7 @@
 #include "ash/system/holding_space/holding_space_color_provider_impl.h"
 
 #include "ash/style/ash_color_provider.h"
+#include "ui/gfx/color_palette.h"
 
 namespace ash {
 
@@ -18,8 +19,7 @@
 }
 
 SkColor HoldingSpaceColorProviderImpl::GetFileIconColor() const {
-  return AshColorProvider::Get()->GetContentLayerColor(
-      AshColorProvider::ContentLayerType::kIconColorPrimary);
+  return gfx::kGoogleGrey700;
 }
 
 }  // namespace ash
diff --git a/ash/system/holding_space/holding_space_item_chip_view.cc b/ash/system/holding_space/holding_space_item_chip_view.cc
index 5b4608a..3f06e6b7 100644
--- a/ash/system/holding_space/holding_space_item_chip_view.cc
+++ b/ash/system/holding_space/holding_space_item_chip_view.cc
@@ -12,6 +12,7 @@
 #include "ash/system/holding_space/holding_space_util.h"
 #include "ui/compositor/paint_recorder.h"
 #include "ui/gfx/skia_paint_util.h"
+#include "ui/views/background.h"
 #include "ui/views/controls/label.h"
 #include "ui/views/layout/box_layout.h"
 #include "ui/views/layout/fill_layout.h"
@@ -87,6 +88,15 @@
 
   image_ = AddChildView(std::make_unique<RoundedImageView>(
       kHoldingSpaceChipIconSize / 2, RoundedImageView::Alignment::kLeading));
+  image_->SetBackground(views::CreateRoundedRectBackground(
+      SK_ColorWHITE, kHoldingSpaceChipIconSize / 2));
+
+  // Subscribe to be notified of changes to `item_`'s image.
+  image_subscription_ =
+      item->image().AddImageSkiaChangedCallback(base::BindRepeating(
+          &HoldingSpaceItemChipView::UpdateImage, base::Unretained(this)));
+
+  UpdateImage();
 
   label_and_pin_button_container_ =
       AddChildView(std::make_unique<views::View>());
@@ -107,13 +117,6 @@
   label_->layer()->SetFillsBoundsOpaquely(false);
   label_->layer()->SetMaskLayer(label_mask_layer_owner_->layer());
 
-  // Subscribe to be notified of changes to `item_`'s image.
-  image_subscription_ =
-      item->image().AddImageSkiaChangedCallback(base::BindRepeating(
-          &HoldingSpaceItemChipView::UpdateImage, base::Unretained(this)));
-
-  UpdateImage();
-
   views::View* pin_button_container =
       label_and_pin_button_container_->AddChildView(
           std::make_unique<views::View>());
diff --git a/ash/system/network/network_state_list_detailed_view.cc b/ash/system/network/network_state_list_detailed_view.cc
index 029baeae..7652df3 100644
--- a/ash/system/network/network_state_list_detailed_view.cc
+++ b/ash/system/network/network_state_list_detailed_view.cc
@@ -323,10 +323,17 @@
   Shell::Get()->metrics()->RecordUserMetricsAction(
       list_type_ == LIST_TYPE_VPN ? UMA_STATUS_AREA_VPN_SETTINGS_OPENED
                                   : UMA_STATUS_AREA_NETWORK_SETTINGS_OPENED);
+
+  SystemTrayClient* system_tray_client =
+      Shell::Get()->system_tray_model()->client();
+
+  if (system_tray_client) {
+    system_tray_client->ShowNetworkSettings(
+        model_->default_network() ? model_->default_network()->guid
+                                  : std::string());
+  }
+
   CloseBubble();  // Deletes |this|.
-  Shell::Get()->system_tray_model()->client()->ShowNetworkSettings(
-      model_->default_network() ? model_->default_network()->guid
-                                : std::string());
 }
 
 void NetworkStateListDetailedView::UpdateHeaderButtons() {
diff --git a/ash/system/power/power_button_controller.h b/ash/system/power/power_button_controller.h
index ee660aa..703562d 100644
--- a/ash/system/power/power_button_controller.h
+++ b/ash/system/power/power_button_controller.h
@@ -138,6 +138,7 @@
   // TODO(minch): Remove this if/when all applicable devices expose a tablet
   // mode switch: https://crbug.com/798646.
   // AccelerometerReader::Observer:
+  void OnECLidAngleDriverStatusChanged(bool is_supported) override {}
   void OnAccelerometerUpdated(const AccelerometerUpdate& update) override;
 
   // BacklightsForcedOffSetter::Observer:
diff --git a/ash/wm/cursor_manager_test_api.cc b/ash/wm/cursor_manager_test_api.cc
index c835ff86..adddf42 100644
--- a/ash/wm/cursor_manager_test_api.cc
+++ b/ash/wm/cursor_manager_test_api.cc
@@ -32,8 +32,4 @@
   return ShellTestApi().native_cursor_manager_ash()->GetRotation();
 }
 
-float CursorManagerTestApi::GetCurrentCursorScale() const {
-  return ShellTestApi().native_cursor_manager_ash()->GetScale();
-}
-
 }  // namespace ash
diff --git a/ash/wm/cursor_manager_test_api.h b/ash/wm/cursor_manager_test_api.h
index b2d267e5..338a7c55 100644
--- a/ash/wm/cursor_manager_test_api.h
+++ b/ash/wm/cursor_manager_test_api.h
@@ -29,7 +29,6 @@
   ui::CursorSize GetCurrentCursorSize() const;
   gfx::NativeCursor GetCurrentCursor() const;
   display::Display::Rotation GetCurrentCursorRotation() const;
-  float GetCurrentCursorScale() const;
 
  private:
   ::wm::CursorManager* cursor_manager_;
diff --git a/ash/wm/default_state.cc b/ash/wm/default_state.cc
index 15bb88f..45d04e1b 100644
--- a/ash/wm/default_state.cc
+++ b/ash/wm/default_state.cc
@@ -152,9 +152,13 @@
       gfx::Rect bounds = window->bounds();
       // When window is added to a workspace, |bounds| may be not the original
       // not-changed-by-user bounds, for example a resized bounds truncated by
-      // available workarea.
-      if (window_state->pre_added_to_workspace_window_bounds())
+      // available workarea. If the window is visible on all desks, its
+      // bounds are global across workspaces so don't restore to pre-added
+      // bounds.
+      if (window_state->pre_added_to_workspace_window_bounds() &&
+          !window->GetProperty(aura::client::kVisibleOnAllWorkspacesKey)) {
         bounds = *window_state->pre_added_to_workspace_window_bounds();
+      }
 
       // Don't adjust window bounds if the bounds are empty as this
       // happens when a new views::Widget is created.
diff --git a/ash/wm/desks/desk.cc b/ash/wm/desks/desk.cc
index df0ba9b..81f07429 100644
--- a/ash/wm/desks/desk.cc
+++ b/ash/wm/desks/desk.cc
@@ -1,4 +1,4 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
+// Copyright 2018 The Chromium Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -383,10 +383,13 @@
     MoveWindowToDeskInternal(transient_root, target_desk, target_root);
 
     // Unminimize the window so that it shows up in the mini_view after it had
-    // been dragged and moved to another desk.
+    // been dragged and moved to another desk. Don't unminimize if the window is
+    // visible on all desks since it's being moved during desk activation.
     auto* window_state = WindowState::Get(transient_root);
-    if (window_state->IsMinimized())
+    if (window_state->IsMinimized() &&
+        !window->GetProperty(aura::client::kVisibleOnAllWorkspacesKey)) {
       window_state->Unminimize();
+    }
   }
 
   NotifyContentChanged();
diff --git a/ash/wm/desks/desk_animation_impl.cc b/ash/wm/desks/desk_animation_impl.cc
index b0cf8b79..7dca6a2 100644
--- a/ash/wm/desks/desk_animation_impl.cc
+++ b/ash/wm/desks/desk_animation_impl.cc
@@ -116,7 +116,6 @@
   // List of animators that need a screenshot. It should be either empty or
   // match the size of |desk_switch_animators_| as all the animations should be
   // in sync.
-  // TODO(sammiequon): Verify all the animations are in sync.
   std::vector<RootWindowDeskSwitchAnimator*> pending_animators;
   for (const auto& animator : desk_switch_animators_) {
     if (animator->ReplaceAnimation(new_ending_desk_index))
diff --git a/ash/wm/desks/desks_animations.cc b/ash/wm/desks/desks_animations.cc
index 67b1422f..b266911 100644
--- a/ash/wm/desks/desks_animations.cc
+++ b/ash/wm/desks/desks_animations.cc
@@ -12,12 +12,14 @@
 #include "ash/wm/overview/overview_controller.h"
 #include "ash/wm/window_transient_descendant_iterator.h"
 #include "base/time/time.h"
+#include "ui/aura/client/aura_constants.h"
 #include "ui/aura/window.h"
 #include "ui/compositor/layer.h"
 #include "ui/compositor/layer_animation_sequence.h"
 #include "ui/compositor/layer_tree_owner.h"
 #include "ui/compositor/scoped_layer_animation_settings.h"
 #include "ui/gfx/transform.h"
+#include "ui/views/widget/widget.h"
 #include "ui/wm/core/window_util.h"
 
 namespace ash {
@@ -116,6 +118,7 @@
 
 void PerformWindowMoveToDeskAnimation(aura::Window* window, bool going_left) {
   DCHECK(!Shell::Get()->overview_controller()->InOverviewSession());
+  DCHECK(!window->GetProperty(aura::client::kVisibleOnAllWorkspacesKey));
 
   // The entire transient window tree should appear to animate together towards
   // the target desk.
diff --git a/ash/wm/desks/desks_animations.h b/ash/wm/desks/desks_animations.h
index 7b4cb50..7ee200f1 100644
--- a/ash/wm/desks/desks_animations.h
+++ b/ash/wm/desks/desks_animations.h
@@ -23,7 +23,8 @@
 // tree offscreen in the direction of the target desk indicated by |going_left|.
 // After this function, |window| can be moved safely immediately to the target
 // desk without having to wait for the animation to finish, since we're
-// animating a completely separate layer tree.
+// animating a completely separate layer tree. |window| cannot be visible on all
+// desks.
 // Note: This animation should not be performed on windows in overview.
 void PerformWindowMoveToDeskAnimation(aura::Window* window, bool going_left);
 
diff --git a/ash/wm/desks/desks_controller.cc b/ash/wm/desks/desks_controller.cc
index 83c5500..d9f2eb8 100644
--- a/ash/wm/desks/desks_controller.cc
+++ b/ash/wm/desks/desks_controller.cc
@@ -40,6 +40,7 @@
 #include "base/notreached.h"
 #include "base/numerics/ranges.h"
 #include "base/timer/timer.h"
+#include "ui/aura/client/aura_constants.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/wm/public/activation_client.h"
 
@@ -491,6 +492,17 @@
   if (!base::Contains(active_desk_->windows(), window))
     return false;
 
+  if (window->GetProperty(aura::client::kVisibleOnAllWorkspacesKey)) {
+    if (source == DesksMoveWindowFromActiveDeskSource::kDragAndDrop) {
+      // Since a visible on all desks window is on all desks, prevent users from
+      // moving them manually in overview.
+      // TODO(chinsenj): Add a UX indication for users.
+      return false;
+    } else if (source == DesksMoveWindowFromActiveDeskSource::kShortcut) {
+      window->SetProperty(aura::client::kVisibleOnAllWorkspacesKey, false);
+    }
+  }
+
   base::AutoReset<bool> in_progress(&are_desks_being_modified_, true);
 
   auto* overview_controller = Shell::Get()->overview_controller();
@@ -534,6 +546,15 @@
   return true;
 }
 
+void DesksController::AddVisibleOnAllDesksWindow(aura::Window* window) {
+  const bool added = visible_on_all_desks_windows_.emplace(window).second;
+  DCHECK(added);
+}
+
+void DesksController::RemoveVisibleOnAllDesksWindow(aura::Window* window) {
+  visible_on_all_desks_windows_.erase(window);
+}
+
 void DesksController::RevertDeskNameToDefault(Desk* desk) {
   DCHECK(HasDesk(desk));
   desk->SetName(GetDeskDefaultName(GetDeskIndex(desk)), /*set_by_user=*/false);
@@ -595,6 +616,9 @@
   if (desk_index < 0 || desk_index >= static_cast<int>(desks_.size()))
     return;
 
+  if (window->GetProperty(aura::client::kVisibleOnAllWorkspacesKey))
+    window->SetProperty(aura::client::kVisibleOnAllWorkspacesKey, false);
+
   const int active_desk_index = GetDeskIndex(active_desk_);
   if (desk_index == active_desk_index)
     return;
@@ -700,7 +724,9 @@
   // `old_active` desk do not activate other windows on the same desk. See
   // `ash::AshFocusRules::GetNextActivatableWindow()`.
   Desk* old_active = active_desk_;
+  MoveVisibleOnAllDesksWindowsFromActiveDeskTo(const_cast<Desk*>(desk));
   active_desk_ = const_cast<Desk*>(desk);
+  RestackAssignedWindowsOnActiveDesk();
 
   // There should always be an active desk at any time.
   DCHECK(old_active);
@@ -821,6 +847,10 @@
     // Desk activation should not change overview mode state.
     DCHECK_EQ(in_overview, overview_controller->InOverviewSession());
 
+    // Now that |target_desk| is activated, we can restack the visible on all
+    // desks windows that were moved from the old active desk.
+    RestackAssignedWindowsOnActiveDesk();
+
     // Now that the windows from the removed and target desks merged, add them
     // all to the grid in the order of the new MRU.
     if (in_overview)
@@ -872,6 +902,58 @@
   DCHECK_LE(available_container_ids_.size(), desks_util::GetMaxNumberOfDesks());
 }
 
+void DesksController::MoveVisibleOnAllDesksWindowsFromActiveDeskTo(
+    Desk* new_desk) {
+  // Ignore activations in the MRU tracker until we finish moving all visible on
+  // all desks windows so we maintain global MRU order that is used later
+  // for stacking visible on all desks windows.
+  auto* mru_tracker = Shell::Get()->mru_window_tracker();
+  mru_tracker->SetIgnoreActivations(true);
+
+  for (auto* visible_on_all_desks_window : visible_on_all_desks_windows_) {
+    MoveWindowFromActiveDeskTo(
+        visible_on_all_desks_window, new_desk,
+        visible_on_all_desks_window->GetRootWindow(),
+        DesksMoveWindowFromActiveDeskSource::kVisibleOnAllDesks);
+  }
+
+  mru_tracker->SetIgnoreActivations(false);
+}
+
+void DesksController::RestackAssignedWindowsOnActiveDesk() {
+  auto mru_windows =
+      Shell::Get()->mru_window_tracker()->BuildMruWindowList(kActiveDesk);
+  for (auto* visible_on_all_desks_window : visible_on_all_desks_windows_) {
+    auto visible_on_all_desks_window_iter = std::find(
+        mru_windows.begin(), mru_windows.end(), visible_on_all_desks_window);
+    DCHECK(visible_on_all_desks_window_iter != mru_windows.end());
+    auto* desk_container =
+        visible_on_all_desks_window->GetRootWindow()->GetChildById(
+            active_desk_->container_id());
+    DCHECK_EQ(desk_container, visible_on_all_desks_window->parent());
+
+    // Search through the MRU list for the next element that shares the same
+    // parent. This will be used to stack |visible_on_all_desks_window| in
+    // the active desk so its stacking respects global MRU order.
+    auto closest_window_below_iter =
+        std::next(visible_on_all_desks_window_iter);
+    while (closest_window_below_iter != mru_windows.end() &&
+           (*closest_window_below_iter)->parent() !=
+               visible_on_all_desks_window->parent()) {
+      closest_window_below_iter = std::next(closest_window_below_iter);
+    }
+
+    if (closest_window_below_iter == mru_windows.end()) {
+      // There was no element in the MRU list that was used after
+      // |visible_on_all_desks_window| so stack it at the bottom.
+      desk_container->StackChildAtBottom(visible_on_all_desks_window);
+    } else {
+      desk_container->StackChildAbove(visible_on_all_desks_window,
+                                      *closest_window_below_iter);
+    }
+  }
+}
+
 const Desk* DesksController::FindDeskOfWindow(aura::Window* window) const {
   DCHECK(window);
 
diff --git a/ash/wm/desks/desks_controller.h b/ash/wm/desks/desks_controller.h
index add52fc..1ac014c8 100644
--- a/ash/wm/desks/desks_controller.h
+++ b/ash/wm/desks/desks_controller.h
@@ -16,6 +16,7 @@
 #include "ash/wm/desks/desks_histogram_enums.h"
 #include "ash/wm/desks/root_window_desk_switch_animator.h"
 #include "base/containers/flat_map.h"
+#include "base/containers/flat_set.h"
 #include "base/macros.h"
 #include "base/observer_list.h"
 #include "components/account_id/account_id.h"
@@ -76,6 +77,10 @@
 
   const Desk* active_desk() const { return active_desk_; }
 
+  const base::flat_set<aura::Window*>& visible_on_all_desks_windows() const {
+    return visible_on_all_desks_windows_;
+  }
+
   DeskAnimationBase* animation() const { return animation_.get(); }
 
   // Returns the current |active_desk()| or the soon-to-be active desk if a desk
@@ -145,15 +150,23 @@
   // |target_root| is provided if |window| is desired to be moved to another
   // desk on another display, otherwise, you can just provide
   // |window->GetRootWindow()| if the window should stay on the same display.
-  // If |window| is minimized, it will be unminimized after it's moved to
-  // |target_desk|.
-  // Returns true on success, false otherwise (e.g. if |window| doesn't belong
-  // to the active desk).
+  // If |window| is minimized and isn't visible on all desks, it will be
+  // unminimized after it's moved to |target_desk|. Returns true on success,
+  // false otherwise (e.g. if |window| doesn't belong to the active desk or
+  // |window| is visible on all desks and user is manually moving it). If
+  // |window| is visible on all desks and |source| is kShortcut, it will be made
+  // not visible on all desks.
   bool MoveWindowFromActiveDeskTo(aura::Window* window,
                                   Desk* target_desk,
                                   aura::Window* target_root,
                                   DesksMoveWindowFromActiveDeskSource source);
 
+  // Adds |window| to |visible_on_all_desks_windows_|.
+  void AddVisibleOnAllDesksWindow(aura::Window* window);
+
+  // Removes |window| if it is in |visible_on_all_desks_windows_|.
+  void RemoveVisibleOnAllDesksWindow(aura::Window* window);
+
   // Reverts the name of the given |desk| to the default value (i.e. "Desk 1",
   // "Desk 2", ... etc.) according to its position in the |desks_| list, as if
   // it was never modified by users.
@@ -218,6 +231,15 @@
   // Removes `desk` without animation.
   void RemoveDeskInternal(const Desk* desk, DesksCreationRemovalSource source);
 
+  // Moves all the windows that are visible on all desks that currently
+  // reside on |active_desk_| to |new_desk|.
+  void MoveVisibleOnAllDesksWindowsFromActiveDeskTo(Desk* new_desk);
+
+  // Iterates through the visible on all desks windows on the active desk
+  // and restacks them based on their position in the global MRU tracker. This
+  // should be called after desk activation.
+  void RestackAssignedWindowsOnActiveDesk();
+
   // Returns the desk to which |window| belongs or nullptr if it doesn't belong
   // to any desk.
   const Desk* FindDeskOfWindow(aura::Window* window) const;
@@ -243,6 +265,9 @@
   // Stores the per-user last active desk index.
   base::flat_map<AccountId, int> user_to_active_desk_index_;
 
+  // Stores the visible on all desks windows.
+  base::flat_set<aura::Window*> visible_on_all_desks_windows_;
+
   // True when desks addition, removal, or activation change are in progress.
   // This can be checked when overview mode is active to avoid exiting overview
   // mode as a result of desks modifications.
diff --git a/ash/wm/desks/desks_histogram_enums.h b/ash/wm/desks/desks_histogram_enums.h
index 4cfaea15..703f655 100644
--- a/ash/wm/desks/desks_histogram_enums.h
+++ b/ash/wm/desks/desks_histogram_enums.h
@@ -25,7 +25,8 @@
   kDragAndDrop = 0,
   kShortcut = 1,
   kSendToDesk = 2,
-  kMaxValue = kSendToDesk,
+  kVisibleOnAllDesks = 3,
+  kMaxValue = kVisibleOnAllDesks,
 };
 
 // These values are logged to UMA. Entries should not be renumbered and
diff --git a/ash/wm/desks/desks_unittests.cc b/ash/wm/desks/desks_unittests.cc
index 6b584c45..5adfe22c 100644
--- a/ash/wm/desks/desks_unittests.cc
+++ b/ash/wm/desks/desks_unittests.cc
@@ -3832,6 +3832,238 @@
   EXPECT_FALSE(display_bounds.Contains(new_desk_button->GetBoundsInScreen()));
 }
 
+// Tests that the bounds of a window that is visible on all desks is shared
+// across desks.
+TEST_F(DesksBentoTest, VisibleOnAllDesksGlobalBounds) {
+  auto* controller = DesksController::Get();
+  NewDesk();
+  const Desk* desk_1 = controller->desks()[0].get();
+  const Desk* desk_2 = controller->desks()[1].get();
+  auto* root = Shell::GetPrimaryRootWindow();
+  const gfx::Rect window_initial_bounds(1, 1, 200, 200);
+  const gfx::Rect window_moved_bounds(200, 200, 250, 250);
+
+  auto window = CreateAppWindow(window_initial_bounds);
+  auto* widget = views::Widget::GetWidgetForNativeWindow(window.get());
+  ASSERT_EQ(window_initial_bounds, window->bounds());
+
+  // Assign |window| to all desks. It shouldn't change bounds.
+  widget->SetVisibleOnAllWorkspaces(true);
+  ASSERT_TRUE(window->GetProperty(aura::client::kVisibleOnAllWorkspacesKey));
+  EXPECT_EQ(window_initial_bounds, window->bounds());
+  EXPECT_EQ(1u, controller->visible_on_all_desks_windows().size());
+
+  // Move to desk 2. The only window on the new desk should be |window|
+  // and it should have the same bounds.
+  ActivateDesk(desk_2);
+  auto desk_2_children = desk_2->GetDeskContainerForRoot(root)->children();
+  EXPECT_EQ(1u, desk_2_children.size());
+  EXPECT_EQ(window.get(), desk_2_children[0]);
+  EXPECT_EQ(window_initial_bounds, window->bounds());
+
+  // Change |window|'s bounds and move to desk 1. It should retain its moved
+  // bounds.
+  window->SetBounds(window_moved_bounds);
+  EXPECT_EQ(window_moved_bounds, window->bounds());
+  ActivateDesk(desk_1);
+  auto desk_1_children = desk_1->GetDeskContainerForRoot(root)->children();
+  EXPECT_EQ(1u, desk_1_children.size());
+  EXPECT_EQ(window.get(), desk_1_children[0]);
+  EXPECT_EQ(window_moved_bounds, window->bounds());
+}
+
+// Tests that the z-ordering of windows that are visible on all desks respects
+// its global MRU ordering.
+TEST_F(DesksBentoTest, VisibleOnAllDesksGlobalZOrder) {
+  auto* controller = DesksController::Get();
+  NewDesk();
+  const Desk* desk_1 = controller->desks()[0].get();
+  const Desk* desk_2 = controller->desks()[1].get();
+  auto* root = Shell::GetPrimaryRootWindow();
+
+  auto win0 = CreateAppWindow(gfx::Rect(0, 0, 100, 100));
+  auto win1 = CreateAppWindow(gfx::Rect(1, 1, 150, 150));
+  auto win2 = CreateAppWindow(gfx::Rect(2, 2, 200, 200));
+  auto* widget0 = views::Widget::GetWidgetForNativeWindow(win0.get());
+  auto* widget1 = views::Widget::GetWidgetForNativeWindow(win1.get());
+  auto* widget2 = views::Widget::GetWidgetForNativeWindow(win2.get());
+  ASSERT_TRUE(IsStackedBelow(win0.get(), win1.get()));
+  ASSERT_TRUE(IsStackedBelow(win1.get(), win2.get()));
+
+  // Assign |win1| to all desks. It shouldn't change stacking order.
+  widget1->SetVisibleOnAllWorkspaces(true);
+  ASSERT_TRUE(win1->GetProperty(aura::client::kVisibleOnAllWorkspacesKey));
+  EXPECT_TRUE(IsStackedBelow(win0.get(), win1.get()));
+  EXPECT_TRUE(IsStackedBelow(win1.get(), win2.get()));
+  EXPECT_EQ(1u, controller->visible_on_all_desks_windows().size());
+
+  // Move to desk 2. The only window on the new desk should be |win1|.
+  ActivateDesk(desk_2);
+  auto desk_2_children = desk_2->GetDeskContainerForRoot(root)->children();
+  EXPECT_EQ(1u, desk_2_children.size());
+  EXPECT_EQ(win1.get(), desk_2_children[0]);
+
+  // Move to desk 1. Since |win1|  was activated last, |win1| should be on top
+  // of the stacking order.
+  ActivateDesk(desk_1);
+  auto desk_1_children = desk_1->GetDeskContainerForRoot(root)->children();
+  EXPECT_EQ(3u, desk_1_children.size());
+  EXPECT_TRUE(IsStackedBelow(win0.get(), win2.get()));
+  EXPECT_TRUE(IsStackedBelow(win2.get(), win1.get()));
+
+  // Assign all the other windows and rearrange the order by activating the
+  // windows.
+  widget0->SetVisibleOnAllWorkspaces(true);
+  widget2->SetVisibleOnAllWorkspaces(true);
+  ASSERT_TRUE(win0->GetProperty(aura::client::kVisibleOnAllWorkspacesKey));
+  ASSERT_TRUE(win1->GetProperty(aura::client::kVisibleOnAllWorkspacesKey));
+  wm::ActivateWindow(win2.get());
+  wm::ActivateWindow(win1.get());
+  wm::ActivateWindow(win0.get());
+  EXPECT_TRUE(IsStackedBelow(win2.get(), win1.get()));
+  EXPECT_TRUE(IsStackedBelow(win1.get(), win0.get()));
+  EXPECT_EQ(3u, controller->visible_on_all_desks_windows().size());
+
+  // Move to desk 2. All the windows should move to the new desk and maintain
+  // their order.
+  ActivateDesk(desk_2);
+  desk_2_children = desk_2->GetDeskContainerForRoot(root)->children();
+  EXPECT_EQ(3u, desk_2_children.size());
+  EXPECT_TRUE(IsStackedBelow(win2.get(), win1.get()));
+  EXPECT_TRUE(IsStackedBelow(win1.get(), win0.get()));
+}
+
+// Tests the behavior of windows that are visible on all desks when the active
+// desk is removed.
+TEST_F(DesksBentoTest, VisibleOnAllDesksActiveDeskRemoval) {
+  auto* controller = DesksController::Get();
+  NewDesk();
+  const Desk* desk_1 = controller->desks()[0].get();
+  const Desk* desk_2 = controller->desks()[1].get();
+  auto* root = Shell::GetPrimaryRootWindow();
+
+  auto win0 = CreateAppWindow(gfx::Rect(0, 0, 100, 100));
+  auto win1 = CreateAppWindow(gfx::Rect(1, 1, 150, 150));
+  auto* widget0 = views::Widget::GetWidgetForNativeWindow(win0.get());
+  auto* widget1 = views::Widget::GetWidgetForNativeWindow(win1.get());
+
+  // Assign |win0| and |win1| to all desks.
+  widget0->SetVisibleOnAllWorkspaces(true);
+  widget1->SetVisibleOnAllWorkspaces(true);
+  ASSERT_TRUE(win0->GetProperty(aura::client::kVisibleOnAllWorkspacesKey));
+  ASSERT_TRUE(win1->GetProperty(aura::client::kVisibleOnAllWorkspacesKey));
+
+  // Remove the active desk. The visible on all desks windows should be on
+  // |desk_2|.
+  controller->RemoveDesk(desk_1, DesksCreationRemovalSource::kKeyboard);
+  auto desk_2_children = desk_2->GetDeskContainerForRoot(root)->children();
+  EXPECT_EQ(2u, desk_2_children.size());
+  EXPECT_TRUE(IsStackedBelow(win0.get(), win1.get()));
+  EXPECT_EQ(2u, controller->visible_on_all_desks_windows().size());
+}
+
+// Tests the behavior of a minimized window that is visible on all desks.
+TEST_F(DesksBentoTest, VisibleOnAllDesksMinimizedWindow) {
+  auto* controller = DesksController::Get();
+  NewDesk();
+  const Desk* desk_2 = controller->desks()[1].get();
+  auto* root = Shell::GetPrimaryRootWindow();
+  auto window = CreateAppWindow(gfx::Rect(0, 0, 100, 100));
+  auto* widget = views::Widget::GetWidgetForNativeWindow(window.get());
+
+  // Minimize |window| and then assign it to all desks. This shouldn't
+  // unminimize it.
+  auto* window_state = WindowState::Get(window.get());
+  window_state->Minimize();
+  ASSERT_TRUE(window_state->IsMinimized());
+  widget->SetVisibleOnAllWorkspaces(true);
+  ASSERT_TRUE(window->GetProperty(aura::client::kVisibleOnAllWorkspacesKey));
+  EXPECT_TRUE(window_state->IsMinimized());
+
+  // Switch desks. |window| should be on the newly active desk and should still
+  // be minimized.
+  ActivateDesk(desk_2);
+  auto desk_2_children = desk_2->GetDeskContainerForRoot(root)->children();
+  EXPECT_EQ(1u, desk_2_children.size());
+  EXPECT_EQ(window.get(), desk_2_children[0]);
+  EXPECT_TRUE(window_state->IsMinimized());
+}
+
+// Tests the behavior of a window that is visible on all desks when a user tries
+// to move it to another desk using drag and drop (overview mode).
+TEST_F(DesksBentoTest, VisibleOnAllDesksMoveWindowToDeskViaDragAndDrop) {
+  auto* controller = DesksController::Get();
+  auto* root = Shell::GetPrimaryRootWindow();
+  NewDesk();
+  const Desk* desk_1 = controller->desks()[0].get();
+  const Desk* desk_2 = controller->desks()[1].get();
+
+  auto window = CreateAppWindow(gfx::Rect(0, 0, 100, 100));
+  auto* widget = views::Widget::GetWidgetForNativeWindow(window.get());
+
+  // Assign |window| to all desks.
+  widget->SetVisibleOnAllWorkspaces(true);
+  ASSERT_TRUE(window->GetProperty(aura::client::kVisibleOnAllWorkspacesKey));
+
+  // Try to move |window| to |desk_2| via drag and drop. It should not be moved.
+  EXPECT_FALSE(controller->MoveWindowFromActiveDeskTo(
+      window.get(), const_cast<Desk*>(desk_2), root,
+      DesksMoveWindowFromActiveDeskSource::kDragAndDrop));
+  EXPECT_TRUE(desks_util::BelongsToActiveDesk(window.get()));
+  EXPECT_EQ(1u, controller->visible_on_all_desks_windows().size());
+  EXPECT_TRUE(window->GetProperty(aura::client::kVisibleOnAllWorkspacesKey));
+  EXPECT_TRUE(base::Contains(desk_1->windows(), window.get()));
+}
+
+// Tests the behavior of a window that is visible on all desks when a user tries
+// to move it to another desk using keyboard shorcuts.
+TEST_F(DesksBentoTest, VisibleOnAllDesksMoveWindowToDeskViaShortcuts) {
+  auto* controller = DesksController::Get();
+  auto* root = Shell::GetPrimaryRootWindow();
+  NewDesk();
+  const Desk* desk_2 = controller->desks()[1].get();
+
+  auto window = CreateAppWindow(gfx::Rect(0, 0, 100, 100));
+  auto* widget = views::Widget::GetWidgetForNativeWindow(window.get());
+
+  // Assign |window| to all desks.
+  widget->SetVisibleOnAllWorkspaces(true);
+  ASSERT_TRUE(window->GetProperty(aura::client::kVisibleOnAllWorkspacesKey));
+
+  // Move |window| to |desk_2| via keyboard shortcut. It should be on |desk_2|
+  // and should no longer be visible on all desks.
+  EXPECT_TRUE(controller->MoveWindowFromActiveDeskTo(
+      window.get(), const_cast<Desk*>(desk_2), root,
+      DesksMoveWindowFromActiveDeskSource::kShortcut));
+  EXPECT_FALSE(desks_util::BelongsToActiveDesk(window.get()));
+  EXPECT_EQ(0u, controller->visible_on_all_desks_windows().size());
+  EXPECT_FALSE(window->GetProperty(aura::client::kVisibleOnAllWorkspacesKey));
+  EXPECT_TRUE(base::Contains(desk_2->windows(), window.get()));
+}
+
+// Tests the behavior of a window that is visible on all desks when a user tries
+// to move it using the context menu.
+TEST_F(DesksBentoTest, VisibleOnAllDesksMoveWindowToDeskViaContextMenu) {
+  auto* controller = DesksController::Get();
+  NewDesk();
+  const Desk* desk_2 = controller->desks()[1].get();
+
+  auto window = CreateAppWindow(gfx::Rect(0, 0, 100, 100));
+  auto* widget = views::Widget::GetWidgetForNativeWindow(window.get());
+
+  // Assign |window| to all desks.
+  widget->SetVisibleOnAllWorkspaces(true);
+  ASSERT_TRUE(window->GetProperty(aura::client::kVisibleOnAllWorkspacesKey));
+
+  // Move |window| to |desk_2| via keyboard shortcut. It should be on |desk_2|
+  // and should no longer be visible on all desks.
+  controller->SendToDeskAtIndex(window.get(), controller->GetDeskIndex(desk_2));
+  EXPECT_FALSE(desks_util::BelongsToActiveDesk(window.get()));
+  EXPECT_EQ(0u, controller->visible_on_all_desks_windows().size());
+  EXPECT_FALSE(window->GetProperty(aura::client::kVisibleOnAllWorkspacesKey));
+  EXPECT_TRUE(base::Contains(desk_2->windows(), window.get()));
+}
+
 // TODO(afakhry): Add more tests:
 // - Always on top windows are not tracked by any desk.
 // - Reusing containers when desks are removed and created.
diff --git a/ash/wm/overview/overview_controller.cc b/ash/wm/overview/overview_controller.cc
index 211b6f40..2bb68d7c 100644
--- a/ash/wm/overview/overview_controller.cc
+++ b/ash/wm/overview/overview_controller.cc
@@ -251,17 +251,6 @@
   return windows;
 }
 
-std::vector<aura::Window*>
-OverviewController::GetItemWindowListInOverviewGridsForTest() {
-  std::vector<aura::Window*> windows;
-  for (const std::unique_ptr<OverviewGrid>& grid :
-       overview_session_->grid_list()) {
-    for (const auto& overview_item : grid->window_list())
-      windows.push_back(overview_item->item_widget()->GetNativeWindow());
-  }
-  return windows;
-}
-
 void OverviewController::ToggleOverview(OverviewEnterExitType type) {
   // Hide the virtual keyboard as it obstructs the overview mode.
   // Don't need to hide if it's the a11y keyboard, as overview mode
diff --git a/ash/wm/overview/overview_controller.h b/ash/wm/overview/overview_controller.h
index 138ae9d..0200cf9e 100644
--- a/ash/wm/overview/overview_controller.h
+++ b/ash/wm/overview/overview_controller.h
@@ -102,7 +102,6 @@
   // Gets the windows list that are shown in the overview windows grids if the
   // overview mode is active for testing.
   std::vector<aura::Window*> GetWindowsListInOverviewGridsForTest();
-  std::vector<aura::Window*> GetItemWindowListInOverviewGridsForTest();
 
  private:
   friend class OverviewSessionTest;
diff --git a/ash/wm/overview/overview_session_unittest.cc b/ash/wm/overview/overview_session_unittest.cc
index be06bf31..04e15e5 100644
--- a/ash/wm/overview/overview_session_unittest.cc
+++ b/ash/wm/overview/overview_session_unittest.cc
@@ -155,8 +155,6 @@
 
 }  // namespace
 
-// TODO(bruthig): Move all non-simple method definitions out of class
-// declaration.
 class OverviewSessionTest : public AshTestBase {
  public:
   OverviewSessionTest() = default;
diff --git a/ash/wm/overview/overview_test_util.cc b/ash/wm/overview/overview_test_util.cc
index ba645bc3..3d01d395 100644
--- a/ash/wm/overview/overview_test_util.cc
+++ b/ash/wm/overview/overview_test_util.cc
@@ -35,8 +35,6 @@
 
 }  // namespace
 
-// TODO(sammiequon): Consider adding an overload for this function to trigger
-// the key event |count| times.
 void SendKey(ui::KeyboardCode key, int flags) {
   ui::test::EventGenerator generator(Shell::GetPrimaryRootWindow());
   generator.PressKey(key, flags);
diff --git a/ash/wm/tablet_mode/DIR_METADATA b/ash/wm/tablet_mode/DIR_METADATA
index 3ab7038a..8e8a6a7 100644
--- a/ash/wm/tablet_mode/DIR_METADATA
+++ b/ash/wm/tablet_mode/DIR_METADATA
@@ -1,3 +1,3 @@
 monorail {
-  component: "UI>Shell>TouchView"
+  component: "UI>Shell>TabletMode"
 }
diff --git a/ash/wm/tablet_mode/tablet_mode_controller.cc b/ash/wm/tablet_mode/tablet_mode_controller.cc
index 8e6ad19a..403250bb 100644
--- a/ash/wm/tablet_mode/tablet_mode_controller.cc
+++ b/ash/wm/tablet_mode/tablet_mode_controller.cc
@@ -627,27 +627,27 @@
   }
 }
 
+void TabletModeController::OnECLidAngleDriverStatusChanged(bool is_supported) {
+  is_ec_lid_angle_driver_supported_ = is_supported;
+
+  if (!is_supported)
+    return;
+
+  // When ChromeOS EC lid angle driver is supported, EC can handle lid angle
+  // calculation, thus Chrome side lid angle calculation is disabled. In this
+  // case, TabletModeController no longer listens to accelerometer samples.
+
+  // Reset lid angle that might be calculated before lid angle driver is
+  // read.
+  lid_angle_ = 0.f;
+  can_detect_lid_angle_ = false;
+  if (record_lid_angle_timer_.IsRunning())
+    record_lid_angle_timer_.Stop();
+  AccelerometerReader::GetInstance()->RemoveObserver(this);
+}
+
 void TabletModeController::OnAccelerometerUpdated(
     const AccelerometerUpdate& update) {
-  if (ec_lid_angle_driver_status_ == ECLidAngleDriverStatus::UNKNOWN) {
-    ec_lid_angle_driver_status_ =
-        AccelerometerReader::GetInstance()->GetECLidAngleDriverStatus();
-  }
-
-  // When ChromeOS EC lid angle driver is present, EC can handle lid angle
-  // calculation, thus Chrome side lid angle calculation is disabled. In this
-  // case, TabletModeController no longer listens to accelerometer events.
-  if (ec_lid_angle_driver_status_ == ECLidAngleDriverStatus::SUPPORTED) {
-    // Reset lid angle that might be calculated before lid angle driver is
-    // read.
-    lid_angle_ = 0.f;
-    can_detect_lid_angle_ = false;
-    if (record_lid_angle_timer_.IsRunning())
-      record_lid_angle_timer_.Stop();
-    AccelerometerReader::GetInstance()->RemoveObserver(this);
-    return;
-  }
-
   have_seen_accelerometer_data_ = true;
   can_detect_lid_angle_ = update.has(ACCELEROMETER_SOURCE_SCREEN) &&
                           update.has(ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD);
@@ -1300,7 +1300,7 @@
 
   const bool can_enter_tablet_mode =
       IsBoardTypeMarkedAsTabletCapable() && HasActiveInternalDisplay() &&
-      (ec_lid_angle_driver_status_ == ECLidAngleDriverStatus::SUPPORTED ||
+      (is_ec_lid_angle_driver_supported_.value_or(false) ||
        have_seen_accelerometer_data_);
 
   return !has_internal_pointing_device_ && can_enter_tablet_mode &&
diff --git a/ash/wm/tablet_mode/tablet_mode_controller.h b/ash/wm/tablet_mode/tablet_mode_controller.h
index 056908d..a1e37ab1 100644
--- a/ash/wm/tablet_mode/tablet_mode_controller.h
+++ b/ash/wm/tablet_mode/tablet_mode_controller.h
@@ -147,6 +147,7 @@
   void OnChromeTerminating() override;
 
   // AccelerometerReader::Observer:
+  void OnECLidAngleDriverStatusChanged(bool is_supported) override;
   void OnAccelerometerUpdated(const AccelerometerUpdate& update) override;
 
   // chromeos::PowerManagerClient::Observer:
@@ -351,15 +352,17 @@
   // internal keyboard and touchpad.
   std::unique_ptr<InternalInputDevicesEventBlocker> event_blocker_;
 
-  // Whether we have ever seen accelerometer data. When ChromeOS EC lid angle is
-  // present, convertible device cannot see accelerometer data.
+  // Whether we have ever seen accelerometer data. When ChromeOS EC lid angle
+  // driver is supported, convertible device cannot see accelerometer data.
   bool have_seen_accelerometer_data_ = false;
 
   // If ECLidAngleDriverStatus is supported, Chrome does not calculate lid angle
-  // itself, but will reply on the tablet-mode flag that EC sends to decide if
+  // itself, but will rely on the tablet-mode flag that EC sends to decide if
   // the device should in tablet mode.
-  ECLidAngleDriverStatus ec_lid_angle_driver_status_ =
-      ECLidAngleDriverStatus::UNKNOWN;
+  // As it's set in |OnECLidAngleDriverStatusChanged|, which is a callback by
+  // AccelerometerReader, we make it optional to indicate a lack of value until
+  // the accelerometer reader is initialized.
+  base::Optional<bool> is_ec_lid_angle_driver_supported_;
 
   // Whether the lid angle can be detected by browser. If it's true, the device
   // is a convertible device (both screen acclerometer and keyboard acclerometer
diff --git a/ash/wm/window_properties.cc b/ash/wm/window_properties.cc
index 52630ab..fc3e42ca 100644
--- a/ash/wm/window_properties.cc
+++ b/ash/wm/window_properties.cc
@@ -5,7 +5,6 @@
 #include "ash/wm/window_properties.h"
 
 #include "ash/wm/window_state.h"
-#include "ui/gfx/geometry/rect.h"
 
 DEFINE_EXPORTED_UI_CLASS_PROPERTY_TYPE(ASH_EXPORT, ash::WindowState*)
 
@@ -15,8 +14,6 @@
 
 DEFINE_UI_CLASS_PROPERTY_KEY(bool, kLockedToRootKey, false)
 
-DEFINE_UI_CLASS_PROPERTY_KEY(bool, kWindowIsJanky, false)
-
 DEFINE_OWNED_UI_CLASS_PROPERTY_KEY(WindowState, kWindowStateKey, nullptr)
 
 }  // namespace ash
diff --git a/ash/wm/window_properties.h b/ash/wm/window_properties.h
index 8d4305da..b0c9278 100644
--- a/ash/wm/window_properties.h
+++ b/ash/wm/window_properties.h
@@ -31,10 +31,6 @@
 // bounds outside of its root window is set.
 ASH_EXPORT extern const aura::WindowProperty<bool>* const kLockedToRootKey;
 
-// Set to true if the window server tells us the window is janky (see
-// WindowManagerDelegate::OnWmClientJankinessChanged()).
-ASH_EXPORT extern const aura::WindowProperty<bool>* const kWindowIsJanky;
-
 // A property key to store WindowState in the window. The window state
 // is owned by the window.
 ASH_EXPORT extern const aura::WindowProperty<WindowState*>* const
diff --git a/ash/wm/workspace/multi_window_resize_controller_unittest.cc b/ash/wm/workspace/multi_window_resize_controller_unittest.cc
index 291d48c..2bd5fca 100644
--- a/ash/wm/workspace/multi_window_resize_controller_unittest.cc
+++ b/ash/wm/workspace/multi_window_resize_controller_unittest.cc
@@ -77,8 +77,6 @@
 
   bool HasPendingShow() { return resize_controller_->show_timer_.IsRunning(); }
 
-  void Hide() { resize_controller_->Hide(); }
-
   bool HasTarget(aura::Window* window) {
     if (!resize_controller_->windows_.is_valid())
       return false;
diff --git a/ash/wm/workspace/workspace_layout_manager.cc b/ash/wm/workspace/workspace_layout_manager.cc
index 483572b4..a9725b6 100644
--- a/ash/wm/workspace/workspace_layout_manager.cc
+++ b/ash/wm/workspace/workspace_layout_manager.cc
@@ -19,6 +19,8 @@
 #include "ash/shelf/shelf.h"
 #include "ash/shell.h"
 #include "ash/wm/always_on_top_controller.h"
+#include "ash/wm/desks/desk.h"
+#include "ash/wm/desks/desks_controller.h"
 #include "ash/wm/desks/desks_util.h"
 #include "ash/wm/fullscreen_window_finder.h"
 #include "ash/wm/screen_pinning_controller.h"
@@ -315,6 +317,19 @@
   } else if (key == kWindowBackdropKey) {
     // kWindowBackdropKey is not supposed to be cleared.
     DCHECK(window->GetProperty(kWindowBackdropKey));
+  } else if (key == aura::client::kVisibleOnAllWorkspacesKey) {
+    auto* desks_controller = Shell::Get()->desks_controller();
+
+    if (window->type() != aura::client::WindowType::WINDOW_TYPE_NORMAL ||
+        window->GetProperty(aura::client::kZOrderingKey) !=
+            ui::ZOrderLevel::kNormal) {
+      return;
+    }
+
+    if (window->GetProperty(aura::client::kVisibleOnAllWorkspacesKey))
+      desks_controller->AddVisibleOnAllDesksWindow(window);
+    else
+      desks_controller->RemoveVisibleOnAllDesksWindow(window);
   }
 }
 
diff --git a/ash/wm/workspace_controller_test_api.cc b/ash/wm/workspace_controller_test_api.cc
index 5a6f64e..eb2aac8 100644
--- a/ash/wm/workspace_controller_test_api.cc
+++ b/ash/wm/workspace_controller_test_api.cc
@@ -22,11 +22,6 @@
   return controller_->event_handler_.get();
 }
 
-MultiWindowResizeController*
-WorkspaceControllerTestApi::GetMultiWindowResizeController() {
-  return WorkspaceEventHandlerTestHelper(GetEventHandler()).resize_controller();
-}
-
 aura::Window* WorkspaceControllerTestApi::GetBackdropWindow() {
   return controller_->layout_manager_->backdrop_controller_->backdrop_window_;
 }
diff --git a/ash/wm/workspace_controller_test_api.h b/ash/wm/workspace_controller_test_api.h
index 96777dae..f98441e 100644
--- a/ash/wm/workspace_controller_test_api.h
+++ b/ash/wm/workspace_controller_test_api.h
@@ -6,26 +6,28 @@
 #define ASH_WM_WORKSPACE_CONTROLLER_TEST_API_H_
 
 #include "ash/ash_export.h"
-#include "ash/wm/workspace_controller.h"
-#include "base/macros.h"
+
+namespace aura {
+class Window;
+}
 
 namespace ash {
-class MultiWindowResizeController;
+class WorkspaceController;
 class WorkspaceEventHandler;
 
 class ASH_EXPORT WorkspaceControllerTestApi {
  public:
   explicit WorkspaceControllerTestApi(WorkspaceController* controller);
+  WorkspaceControllerTestApi(const WorkspaceControllerTestApi&) = delete;
+  WorkspaceControllerTestApi& operator=(const WorkspaceControllerTestApi&) =
+      delete;
   ~WorkspaceControllerTestApi();
 
   WorkspaceEventHandler* GetEventHandler();
-  MultiWindowResizeController* GetMultiWindowResizeController();
   aura::Window* GetBackdropWindow();
 
  private:
   WorkspaceController* controller_;
-
-  DISALLOW_COPY_AND_ASSIGN(WorkspaceControllerTestApi);
 };
 
 }  // namespace ash
diff --git a/base/allocator/allocator_shim.h b/base/allocator/allocator_shim.h
index 6c1bf3b..2cdcfad 100644
--- a/base/allocator/allocator_shim.h
+++ b/base/allocator/allocator_shim.h
@@ -146,7 +146,7 @@
 // in malloc(), which we really don't want.
 BASE_EXPORT void RemoveAllocatorDispatchForTesting(AllocatorDispatch* dispatch);
 
-#if defined(OS_WIN)
+#if BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) && defined(OS_WIN)
 // Configures the allocator for the caller's allocation domain. Allocations that
 // take place prior to this configuration step will succeed, but will not
 // benefit from its one-time mitigations. As such, this function must be called
diff --git a/base/allocator/allocator_shim_default_dispatch_to_partition_alloc.cc b/base/allocator/allocator_shim_default_dispatch_to_partition_alloc.cc
index 2b9ab96..20c07457 100644
--- a/base/allocator/allocator_shim_default_dispatch_to_partition_alloc.cc
+++ b/base/allocator/allocator_shim_default_dispatch_to_partition_alloc.cc
@@ -5,6 +5,7 @@
 #include "base/allocator/allocator_shim_default_dispatch_to_partition_alloc.h"
 
 #include "base/allocator/allocator_shim_internals.h"
+#include "base/allocator/buildflags.h"
 #include "base/allocator/partition_allocator/memory_reclaimer.h"
 #include "base/allocator/partition_allocator/partition_alloc.h"
 #include "base/allocator/partition_allocator/partition_alloc_constants.h"
@@ -117,6 +118,7 @@
 }
 
 #if defined(OS_WIN) && defined(ARCH_CPU_X86)
+#if BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
 bool IsRunning32bitEmulatedOnArm64() {
   using IsWow64Process2Function = decltype(&IsWow64Process2);
 
@@ -135,6 +137,7 @@
     return true;
   return false;
 }
+#endif  // BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
 
 // The number of bytes to add to every allocation. Ordinarily zero, but set to 8
 // when emulating an x86 on ARM64 to avoid a bug in the Windows x86 emulator.
diff --git a/base/metrics/field_trial_param_associator.cc b/base/metrics/field_trial_param_associator.cc
index ea76b79..8414b52 100644
--- a/base/metrics/field_trial_param_associator.cc
+++ b/base/metrics/field_trial_param_associator.cc
@@ -62,7 +62,7 @@
     FieldTrialParams* params) {
   AutoLock scoped_lock(lock_);
 
-  const FieldTrialKey key(trial_name, group_name);
+  const FieldTrialRefKey key(trial_name, group_name);
   if (!Contains(field_trial_params_, key))
     return false;
 
@@ -82,7 +82,7 @@
     const std::string& trial_name,
     const std::string& group_name) {
   AutoLock scoped_lock(lock_);
-  const FieldTrialKey key(trial_name, group_name);
+  const FieldTrialRefKey key(trial_name, group_name);
   field_trial_params_.erase(key);
 }
 
diff --git a/base/metrics/field_trial_param_associator.h b/base/metrics/field_trial_param_associator.h
index 17c2b3c7..27c5904 100644
--- a/base/metrics/field_trial_param_associator.h
+++ b/base/metrics/field_trial_param_associator.h
@@ -62,6 +62,8 @@
 
   // (field_trial_name, field_trial_group)
   typedef std::pair<std::string, std::string> FieldTrialKey;
+  // The following type can be used for lookups without needing to copy strings.
+  typedef std::pair<const std::string&, const std::string&> FieldTrialRefKey;
 
   Lock lock_;
   std::map<FieldTrialKey, FieldTrialParams> field_trial_params_;
diff --git a/base/threading/thread_restrictions.h b/base/threading/thread_restrictions.h
index cc1ca9ba..116eafeff 100644
--- a/base/threading/thread_restrictions.h
+++ b/base/threading/thread_restrictions.h
@@ -11,6 +11,7 @@
 #include "base/location.h"
 #include "base/macros.h"
 #include "base/threading/hang_watcher.h"
+#include "build/build_config.h"
 
 // -----------------------------------------------------------------------------
 // Usage documentation
@@ -228,6 +229,9 @@
 }
 namespace printing {
 class LocalPrinterHandlerDefault;
+#if defined(OS_MAC)
+class PrintBackendServiceImpl;
+#endif
 class PrintJobWorker;
 class PrinterQuery;
 }
@@ -399,6 +403,9 @@
   friend class module_installer::ScopedAllowModulePakLoad;
   friend class mojo::CoreLibraryInitializer;
   friend class printing::LocalPrinterHandlerDefault;
+#if defined(OS_MAC)
+  friend class printing::PrintBackendServiceImpl;
+#endif
   friend class printing::PrintJobWorker;
   friend class resource_coordinator::TabManagerDelegate;  // crbug.com/778703
   friend class web::WebSubThread;
diff --git a/build/chromeos/test_runner.py b/build/chromeos/test_runner.py
index 72aaac8..1628d3f 100755
--- a/build/chromeos/test_runner.py
+++ b/build/chromeos/test_runner.py
@@ -264,6 +264,13 @@
     self._tests = args.tests
     self._conditional = args.conditional
     self._should_strip = args.strip_chrome
+    self._deploy_lacros = args.deploy_lacros
+
+    if self._deploy_lacros and self._should_strip:
+      raise TestFormatError(
+          '--strip-chrome is only applicable to ash-chrome because '
+          'lacros-chrome deployment uses --nostrip by default, so it cannot '
+          'be specificed with --deploy-lacros.')
 
     if not self._llvm_profile_var and not self._logs_dir:
       # The host-side Tast bin returns 0 when tests fail, so we need to capture
@@ -290,11 +297,12 @@
           if not arg.startswith('--gtest_repeat')
       ]
 
+    # Lacros deployment mounts itself by default.
+    self._test_cmd.extend(
+        ['--deploy-lacros'] if self._deploy_lacros else ['--deploy', '--mount'])
     self._test_cmd += [
-        '--deploy',
-        '--mount',
         '--build-dir',
-        os.path.relpath(self._path_to_outdir, CHROMIUM_SRC_PATH),
+        os.path.relpath(self._path_to_outdir, CHROMIUM_SRC_PATH)
     ] + self._additional_args
 
     # Coverage tests require some special pre-test setup, so use an
@@ -329,10 +337,6 @@
           './' + os.path.relpath(self._on_device_script, self._path_to_outdir)
       ]
     else:
-      # Mounting the browser gives it enough disk space to not need stripping,
-      # but only for browsers not instrumented with code coverage.
-      if not self._should_strip:
-        self._test_cmd.append('--nostrip')
       # Capture tast's results in the logs dir as well.
       if self._logs_dir:
         self._test_cmd += [
@@ -356,6 +360,12 @@
       for v in self._tast_vars or []:
         self._test_cmd.extend(['--tast-var', v])
 
+      # Mounting ash-chrome gives it enough disk space to not need stripping,
+      # but only for one not instrumented with code coverage.
+      # Lacros uses --nostrip by default, so there is no need to specify.
+      if not self._deploy_lacros and not self._should_strip:
+        self._test_cmd.append('--nostrip')
+
   def post_run(self, return_code):
     # If we don't need to parse the host-side Tast tool's results, fall back to
     # the parent method's default behavior.
@@ -705,7 +715,7 @@
   if args.deploy_chrome:
     cros_run_test_cmd += [
         '--deploy',
-        # Mounting the browser gives it enough disk space to not need stripping.
+        # Mounting ash-chrome gives it enough disk space to not need stripping.
         '--mount',
         '--nostrip',
         '--build-dir',
@@ -822,8 +832,9 @@
   host_cmd_parser.add_argument(
       '--deploy-chrome',
       action='store_true',
-      help='Will deploy a locally built Chrome binary to the device before '
+      help='Will deploy a locally built ash-chrome binary to the device before '
       'running the host-cmd.')
+
   # GTest args.
   # TODO(bpastene): Rename 'vm-test' arg to 'gtest'.
   gtest_parser = subparsers.add_parser(
@@ -890,7 +901,11 @@
   tast_test_parser.add_argument(
       '--strip-chrome',
       action='store_true',
-      help='Strips symbols from the browser before deploying to the device.')
+      help='Strips symbols from ash-chrome before deploying to the device.')
+  tast_test_parser.add_argument(
+      '--deploy-lacros',
+      action='store_true',
+      help='Deploy a lacros-chrome instead of ash-chrome.')
   tast_test_parser.add_argument(
       '--tast-var',
       action='append',
diff --git a/build/chromeos/test_runner_test.py b/build/chromeos/test_runner_test.py
index cc5b192c..03cc0dc 100755
--- a/build/chromeos/test_runner_test.py
+++ b/build/chromeos/test_runner_test.py
@@ -34,7 +34,12 @@
         test_runner.result_sink, 'TryInitClient', return_value=None)
     self.mock_rdb.start()
 
-    self.common_tast_args = [
+  def tearDown(self):
+    shutil.rmtree(self._tmp_dir, ignore_errors=True)
+    self.mock_rdb.stop()
+
+  def get_common_tast_args(self, use_vm):
+    return [
         'script_name',
         'tast',
         '--suite-name=chrome_all_tast_tests',
@@ -42,8 +47,11 @@
         '--flash',
         '--path-to-outdir=out_eve/Release',
         '--logs-dir=%s' % self._tmp_dir,
+        '--use-vm' if use_vm else '--device=localhost:2222',
     ]
-    self.common_tast_expectations = [
+
+  def get_common_tast_expectations(self, use_vm, is_lacros=False):
+    expectation = [
         test_runner.CROS_RUN_TEST_PATH,
         '--board',
         'eve',
@@ -51,9 +59,6 @@
         test_runner.DEFAULT_CROS_CACHE,
         '--results-dest-dir',
         '%s/system_logs' % self._tmp_dir,
-        '--mount',
-        '--deploy',
-        '--nostrip',
         '--flash',
         '--build-dir',
         'out_eve/Release',
@@ -62,10 +67,18 @@
         '--tast-total-shards=1',
         '--tast-shard-index=0',
     ]
+    expectation.extend(['--start', '--copy-on-write']
+                       if use_vm else ['--device', 'localhost:2222'])
+    for p in test_runner.SYSTEM_LOG_LOCATIONS:
+      expectation.extend(['--results-src', p])
 
-  def tearDown(self):
-    shutil.rmtree(self._tmp_dir, ignore_errors=True)
-    self.mock_rdb.stop()
+    if not is_lacros:
+      expectation += [
+          '--mount',
+          '--deploy',
+          '--nostrip',
+      ]
+    return expectation
 
   @parameterized.expand([
       [True],
@@ -121,22 +134,17 @@
     with open(os.path.join(self._tmp_dir, 'streamed_results.jsonl'), 'w') as f:
       json.dump(_TAST_TEST_RESULTS_JSON, f)
 
-    args = self.common_tast_args + [
+    args = self.get_common_tast_args(use_vm) + [
         '-t=ui.ChromeLogin',
-        '--use-vm' if use_vm else '--device=localhost:2222',
     ]
     with mock.patch.object(sys, 'argv', args),\
          mock.patch.object(test_runner.subprocess, 'Popen') as mock_popen:
       mock_popen.return_value.returncode = 0
 
       test_runner.main()
-      expected_cmd = self.common_tast_expectations + [
+      expected_cmd = self.get_common_tast_expectations(use_vm) + [
           '--tast', 'ui.ChromeLogin'
       ]
-      expected_cmd.extend(['--start', '--copy-on-write']
-                          if use_vm else ['--device', 'localhost:2222'])
-      for p in test_runner.SYSTEM_LOG_LOCATIONS:
-        expected_cmd.extend(['--results-src', p])
 
       self.assertItemsEqual(expected_cmd, mock_popen.call_args[0][0])
 
@@ -149,25 +157,47 @@
     with open(os.path.join(self._tmp_dir, 'streamed_results.jsonl'), 'w') as f:
       json.dump(_TAST_TEST_RESULTS_JSON, f)
 
-    args = self.common_tast_args + [
+    args = self.get_common_tast_args(use_vm) + [
         '--attr-expr=( "group:mainline" && "dep:chrome" && !informational)',
-        '--use-vm' if use_vm else '--device=localhost:2222',
     ]
     with mock.patch.object(sys, 'argv', args),\
          mock.patch.object(test_runner.subprocess, 'Popen') as mock_popen:
       mock_popen.return_value.returncode = 0
 
       test_runner.main()
-      expected_cmd = self.common_tast_expectations + [
+      expected_cmd = self.get_common_tast_expectations(use_vm) + [
           '--tast=( "group:mainline" && "dep:chrome" && !informational)',
       ]
-      expected_cmd.extend(['--start', '--copy-on-write']
-                          if use_vm else ['--device', 'localhost:2222'])
-      for p in test_runner.SYSTEM_LOG_LOCATIONS:
-        expected_cmd.extend(['--results-src', p])
 
       self.assertItemsEqual(expected_cmd, mock_popen.call_args[0][0])
 
+  @parameterized.expand([
+      [True],
+      [False],
+  ])
+  def test_tast_lacros(self, use_vm):
+    """Tests running a tast tests for Lacros."""
+    with open(os.path.join(self._tmp_dir, 'streamed_results.jsonl'), 'w') as f:
+      json.dump(_TAST_TEST_RESULTS_JSON, f)
+
+    args = self.get_common_tast_args(use_vm) + [
+        '-t=lacros.Basic',
+        '--deploy-lacros',
+    ]
+
+    with mock.patch.object(sys, 'argv', args),\
+         mock.patch.object(test_runner.subprocess, 'Popen') as mock_popen:
+      mock_popen.return_value.returncode = 0
+
+      test_runner.main()
+      expected_cmd = self.get_common_tast_expectations(
+          use_vm, is_lacros=True) + [
+              '--tast',
+              'lacros.Basic',
+              '--deploy-lacros',
+          ]
+
+      self.assertItemsEqual(expected_cmd, mock_popen.call_args[0][0])
 
   @parameterized.expand([
       [True],
@@ -178,22 +208,17 @@
     with open(os.path.join(self._tmp_dir, 'streamed_results.jsonl'), 'w') as f:
       json.dump(_TAST_TEST_RESULTS_JSON, f)
 
-    args = self.common_tast_args + [
+    args = self.get_common_tast_args(use_vm) + [
         '-t=ui.ChromeLogin',
         '--tast-var=key=value',
-        '--use-vm' if use_vm else '--device=localhost:2222',
     ]
     with mock.patch.object(sys, 'argv', args),\
          mock.patch.object(test_runner.subprocess, 'Popen') as mock_popen:
       mock_popen.return_value.returncode = 0
       test_runner.main()
-      expected_cmd = self.common_tast_expectations + [
+      expected_cmd = self.get_common_tast_expectations(use_vm) + [
           '--tast', 'ui.ChromeLogin', '--tast-var', 'key=value'
       ]
-      expected_cmd.extend(['--start', '--copy-on-write']
-                          if use_vm else ['--device', 'localhost:2222'])
-      for p in test_runner.SYSTEM_LOG_LOCATIONS:
-        expected_cmd.extend(['--results-src', p])
 
       self.assertItemsEqual(expected_cmd, mock_popen.call_args[0][0])
 
diff --git a/build/config/BUILDCONFIG.gn b/build/config/BUILDCONFIG.gn
index d8eb6546..1cff8218 100644
--- a/build/config/BUILDCONFIG.gn
+++ b/build/config/BUILDCONFIG.gn
@@ -162,6 +162,17 @@
   is_component_build = is_debug && current_os != "ios"
 }
 
+declare_args() {
+  # Regression Test Selection (RTS).
+  # https://chromium.googlesource.com/chromium/src/+/master/docs/testing/regression-test-selection.md
+  #
+  # Exclude unittest/browsertest files listed in this file.
+  #
+  # Entries should be newline separated.
+  # Each entry much be an source-absolute path starting with //.
+  rts_exclude_file = ""
+}
+
 assert(!(is_debug && is_official_build), "Can't do official debug builds")
 
 # ==============================================================================
@@ -575,3 +586,32 @@
     configs = default_compiler_configs
   }
 }
+
+# ==============================================================================
+# Regression Test Selection (RTS)
+# https://chromium.googlesource.com/chromium/src/+/master/docs/testing/regression-test-selection.md
+# ==============================================================================
+if (rts_exclude_file != "") {
+  rts_exclusions = read_file(rts_exclude_file, "list lines")
+
+  # Many tests are included in source_sets
+  template("source_set") {
+    _target_name = target_name
+    target("source_set", _target_name) {
+      forward_variables_from(invoker, "*", TESTONLY_AND_VISIBILITY)
+      forward_variables_from(invoker, TESTONLY_AND_VISIBILITY)
+
+      if (defined(sources) && sources != []) {
+        # Normalize paths
+        abs_paths = get_path_info(sources, "abspath")
+
+        # Filter
+        filtered_sources = filter_exclude(abs_paths, rts_exclusions)
+
+        # Do the replacement
+        sources = []
+        sources = filtered_sources
+      }
+    }
+  }
+}
diff --git a/cc/animation/animation_unittest.cc b/cc/animation/animation_unittest.cc
index 360400e..15fe896 100644
--- a/cc/animation/animation_unittest.cc
+++ b/cc/animation/animation_unittest.cc
@@ -456,30 +456,33 @@
                          animation_->id(), element_id_.ToString().c_str()),
       animation_->ToString());
 
-  animation_->AddKeyframeModel(
-      KeyframeModel::Create(std::make_unique<FakeFloatAnimationCurve>(15), 42,
-                            73, TargetProperty::OPACITY));
+  animation_->AddKeyframeModel(KeyframeModel::Create(
+      std::make_unique<FakeFloatAnimationCurve>(15), 42, 73,
+      KeyframeModel::TargetPropertyId(TargetProperty::OPACITY)));
   EXPECT_EQ(
       base::StringPrintf("Animation{id=%d, element_id=%s, "
                          "keyframe_models=[KeyframeModel{id=42, "
-                         "group=73, target_property_id=1, "
-                         "run_state=WAITING_FOR_TARGET_AVAILABILITY}]}",
+                         "group=73, target_property_type=1, "
+                         "custom_property_name=, native_property_type=1, "
+                         "run_state=WAITING_FOR_TARGET_AVAILABILITY, "
+                         "element_id=(0)}]}",
                          animation_->id(), element_id_.ToString().c_str()),
       animation_->ToString());
 
-  animation_->AddKeyframeModel(
-      KeyframeModel::Create(std::make_unique<FakeFloatAnimationCurve>(18), 45,
-                            76, TargetProperty::BOUNDS));
-  EXPECT_EQ(
-      base::StringPrintf(
-          "Animation{id=%d, element_id=%s, "
-          "keyframe_models=[KeyframeModel{id=42, "
-          "group=73, target_property_id=1, "
-          "run_state=WAITING_FOR_TARGET_AVAILABILITY}, KeyframeModel{id=45, "
-          "group=76, "
-          "target_property_id=5, run_state=WAITING_FOR_TARGET_AVAILABILITY}]}",
-          animation_->id(), element_id_.ToString().c_str()),
-      animation_->ToString());
+  animation_->AddKeyframeModel(KeyframeModel::Create(
+      std::make_unique<FakeFloatAnimationCurve>(18), 45, 76,
+      KeyframeModel::TargetPropertyId(TargetProperty::BOUNDS)));
+  EXPECT_EQ(base::StringPrintf(
+                "Animation{id=%d, element_id=%s, "
+                "keyframe_models=[KeyframeModel{id=42, "
+                "group=73, target_property_type=1, custom_property_name=, "
+                "native_property_type=1, "
+                "run_state=WAITING_FOR_TARGET_AVAILABILITY, element_id=(0)}, "
+                "KeyframeModel{id=45, group=76, target_property_type=5, "
+                "custom_property_name=, native_property_type=1, "
+                "run_state=WAITING_FOR_TARGET_AVAILABILITY, element_id=(0)}]}",
+                animation_->id(), element_id_.ToString().c_str()),
+            animation_->ToString());
 }
 
 }  // namespace
diff --git a/cc/animation/element_animations.cc b/cc/animation/element_animations.cc
index 7c3ef87..2427573 100644
--- a/cc/animation/element_animations.cc
+++ b/cc/animation/element_animations.cc
@@ -223,7 +223,7 @@
     float value,
     int target_property_id,
     KeyframeModel* keyframe_model) {
-  switch (keyframe_model->target_property_id()) {
+  switch (keyframe_model->target_property_type()) {
     case TargetProperty::CSS_CUSTOM_PROPERTY:
     case TargetProperty::NATIVE_PROPERTY:
       // Custom properties are only tracked on the pending tree, where they may
@@ -251,7 +251,7 @@
     const FilterOperations& filters,
     int target_property_id,
     KeyframeModel* keyframe_model) {
-  switch (keyframe_model->target_property_id()) {
+  switch (keyframe_model->target_property_type()) {
     case TargetProperty::BACKDROP_FILTER:
       if (KeyframeModelAffectsActiveElements(keyframe_model))
         OnBackdropFilterAnimated(ElementListType::ACTIVE, filters,
@@ -275,7 +275,7 @@
     SkColor value,
     int target_property_id,
     KeyframeModel* keyframe_model) {
-  DCHECK_EQ(keyframe_model->target_property_id(),
+  DCHECK_EQ(keyframe_model->target_property_type(),
             TargetProperty::CSS_CUSTOM_PROPERTY);
   OnCustomPropertyAnimated(PaintWorkletInput::PropertyValue(value),
                            keyframe_model, target_property_id);
diff --git a/cc/animation/element_animations_unittest.cc b/cc/animation/element_animations_unittest.cc
index 9e1fa83..3486db3 100644
--- a/cc/animation/element_animations_unittest.cc
+++ b/cc/animation/element_animations_unittest.cc
@@ -4,6 +4,9 @@
 
 #include "cc/animation/element_animations.h"
 
+#include <limits>
+#include <utility>
+
 #include "base/memory/ptr_util.h"
 #include "cc/animation/animation.h"
 #include "cc/animation/animation_delegate.h"
@@ -269,7 +272,8 @@
   curve_fixed->SetInitialValue(initial_value);
   const int animation1_id = 1;
   std::unique_ptr<KeyframeModel> animation_fixed(KeyframeModel::Create(
-      std::move(curve_fixed), animation1_id, 0, TargetProperty::SCROLL_OFFSET));
+      std::move(curve_fixed), animation1_id, 0,
+      KeyframeModel::TargetPropertyId(TargetProperty::SCROLL_OFFSET)));
   animation_->AddKeyframeModel(std::move(animation_fixed));
   PushProperties();
   EXPECT_VECTOR2DF_EQ(initial_value, animation_impl_->keyframe_effect()
@@ -285,7 +289,8 @@
           target_value));
   const int animation2_id = 2;
   std::unique_ptr<KeyframeModel> keyframe_model(KeyframeModel::Create(
-      std::move(curve), animation2_id, 1, TargetProperty::SCROLL_OFFSET));
+      std::move(curve), animation2_id, 1,
+      KeyframeModel::TargetPropertyId(TargetProperty::SCROLL_OFFSET)));
   animation_->AddKeyframeModel(std::move(keyframe_model));
   PushProperties();
   EXPECT_VECTOR2DF_EQ(provider_initial_value,
@@ -751,7 +756,8 @@
     std::unique_ptr<AnimationCurve> curve,
     int group_id,
     TargetProperty::Type property) {
-  return KeyframeModel::Create(std::move(curve), 0, group_id, property);
+  return KeyframeModel::Create(std::move(curve), 0, group_id,
+                               KeyframeModel::TargetPropertyId(property));
 }
 
 TEST_F(ElementAnimationsTest, TrivialTransition) {
@@ -807,8 +813,9 @@
   curve->AddKeyframe(FilterKeyframe::Create(base::TimeDelta::FromSecondsD(1.0),
                                             end_filters, nullptr));
 
-  std::unique_ptr<KeyframeModel> keyframe_model(
-      KeyframeModel::Create(std::move(curve), 1, 0, TargetProperty::FILTER));
+  std::unique_ptr<KeyframeModel> keyframe_model(KeyframeModel::Create(
+      std::move(curve), 1, 0,
+      KeyframeModel::TargetPropertyId(TargetProperty::FILTER)));
   animation_->AddKeyframeModel(std::move(keyframe_model));
 
   animation_->Tick(kInitialTickTime);
@@ -850,7 +857,8 @@
                                             end_filters, nullptr));
 
   std::unique_ptr<KeyframeModel> keyframe_model(KeyframeModel::Create(
-      std::move(curve), 1, 0, TargetProperty::BACKDROP_FILTER));
+      std::move(curve), 1, 0,
+      KeyframeModel::TargetPropertyId(TargetProperty::BACKDROP_FILTER)));
   animation_->AddKeyframeModel(std::move(keyframe_model));
 
   animation_->Tick(kInitialTickTime);
@@ -889,7 +897,8 @@
           target_value));
 
   std::unique_ptr<KeyframeModel> keyframe_model(KeyframeModel::Create(
-      std::move(curve), 1, 0, TargetProperty::SCROLL_OFFSET));
+      std::move(curve), 1, 0,
+      KeyframeModel::TargetPropertyId(TargetProperty::SCROLL_OFFSET)));
   keyframe_model->set_needs_synchronized_start_time(true);
   animation_->AddKeyframeModel(std::move(keyframe_model));
 
@@ -961,7 +970,8 @@
   double duration_in_seconds = curve->Duration().InSecondsF();
 
   std::unique_ptr<KeyframeModel> keyframe_model(KeyframeModel::Create(
-      std::move(curve), 1, 0, TargetProperty::SCROLL_OFFSET));
+      std::move(curve), 1, 0,
+      KeyframeModel::TargetPropertyId(TargetProperty::SCROLL_OFFSET)));
   keyframe_model->SetIsImplOnly();
   animation_impl_->AddKeyframeModel(std::move(keyframe_model));
 
@@ -1060,7 +1070,8 @@
           target_value));
 
   std::unique_ptr<KeyframeModel> keyframe_model(KeyframeModel::Create(
-      std::move(curve), 1, 0, TargetProperty::SCROLL_OFFSET));
+      std::move(curve), 1, 0,
+      KeyframeModel::TargetPropertyId(TargetProperty::SCROLL_OFFSET)));
   keyframe_model->set_needs_synchronized_start_time(true);
   animation_->AddKeyframeModel(std::move(keyframe_model));
 
@@ -1139,7 +1150,8 @@
 
   int keyframe_model_id = 1;
   std::unique_ptr<KeyframeModel> keyframe_model(KeyframeModel::Create(
-      std::move(curve), keyframe_model_id, 0, TargetProperty::SCROLL_OFFSET));
+      std::move(curve), keyframe_model_id, 0,
+      KeyframeModel::TargetPropertyId(TargetProperty::SCROLL_OFFSET)));
   keyframe_model->set_needs_synchronized_start_time(true);
   animation_->AddKeyframeModel(std::move(keyframe_model));
   PushProperties();
@@ -1166,8 +1178,9 @@
   // Now, test the 2-argument version of RemoveKeyframeModel.
   curve = ScrollOffsetAnimationCurveFactory::CreateEaseInOutAnimationForTesting(
       target_value);
-  keyframe_model = KeyframeModel::Create(std::move(curve), keyframe_model_id, 0,
-                                         TargetProperty::SCROLL_OFFSET);
+  keyframe_model = KeyframeModel::Create(
+      std::move(curve), keyframe_model_id, 0,
+      KeyframeModel::TargetPropertyId(TargetProperty::SCROLL_OFFSET));
   keyframe_model->set_needs_synchronized_start_time(true);
   animation_->AddKeyframeModel(std::move(keyframe_model));
   PushProperties();
@@ -1261,7 +1274,8 @@
   curve->SetInitialValue(initial_value);
   TimeDelta duration = curve->Duration();
   std::unique_ptr<KeyframeModel> to_add(KeyframeModel::Create(
-      std::move(curve), 1, 0, TargetProperty::SCROLL_OFFSET));
+      std::move(curve), 1, 0,
+      KeyframeModel::TargetPropertyId(TargetProperty::SCROLL_OFFSET)));
   to_add->SetIsImplOnly();
   animation_impl_->AddKeyframeModel(std::move(to_add));
 
@@ -1379,10 +1393,12 @@
   int animation2_id = 2;
   animation_->AddKeyframeModel(KeyframeModel::Create(
       std::unique_ptr<AnimationCurve>(new FakeFloatTransition(1.0, 0.f, 1.f)),
-      animation1_id, 1, TargetProperty::OPACITY));
+      animation1_id, 1,
+      KeyframeModel::TargetPropertyId(TargetProperty::OPACITY)));
   animation_->AddKeyframeModel(KeyframeModel::Create(
       std::unique_ptr<AnimationCurve>(new FakeFloatTransition(1.0, 1.f, 0.5f)),
-      animation2_id, 2, TargetProperty::OPACITY));
+      animation2_id, 2,
+      KeyframeModel::TargetPropertyId(TargetProperty::OPACITY)));
 
   animation_->Tick(kInitialTickTime);
 
@@ -1679,13 +1695,14 @@
   const int keyframe_model_id = 2;
   animation_->AddKeyframeModel(KeyframeModel::Create(
       std::unique_ptr<AnimationCurve>(new FakeTransformTransition(1)), 1, 1,
-      TargetProperty::TRANSFORM));
+      KeyframeModel::TargetPropertyId(TargetProperty::TRANSFORM)));
   animation_->AddKeyframeModel(KeyframeModel::Create(
       std::unique_ptr<AnimationCurve>(new FakeFloatTransition(2.0, 0.f, 1.f)),
-      keyframe_model_id, 1, TargetProperty::OPACITY));
+      keyframe_model_id, 1,
+      KeyframeModel::TargetPropertyId(TargetProperty::OPACITY)));
   animation_->AddKeyframeModel(KeyframeModel::Create(
       std::unique_ptr<AnimationCurve>(new FakeFloatTransition(1.0, 1.f, 0.75f)),
-      3, 2, TargetProperty::OPACITY));
+      3, 2, KeyframeModel::TargetPropertyId(TargetProperty::OPACITY)));
 
   animation_->Tick(kInitialTickTime);
   animation_->UpdateState(true, events.get());
@@ -1873,19 +1890,19 @@
   // state.
   animation_->AddKeyframeModel(KeyframeModel::Create(
       std::unique_ptr<AnimationCurve>(new FakeTransformTransition(1.0)), 1, 1,
-      TargetProperty::TRANSFORM));
+      KeyframeModel::TargetPropertyId(TargetProperty::TRANSFORM)));
   animation_->AddKeyframeModel(KeyframeModel::Create(
       std::unique_ptr<AnimationCurve>(new FakeFloatTransition(1.0, 0.f, 1.f)),
-      2, 2, TargetProperty::OPACITY));
+      2, 2, KeyframeModel::TargetPropertyId(TargetProperty::OPACITY)));
   animation_->AddKeyframeModel(KeyframeModel::Create(
       std::unique_ptr<AnimationCurve>(new FakeTransformTransition(1.0)), 3, 3,
-      TargetProperty::TRANSFORM));
+      KeyframeModel::TargetPropertyId(TargetProperty::TRANSFORM)));
   animation_->AddKeyframeModel(KeyframeModel::Create(
       std::unique_ptr<AnimationCurve>(new FakeTransformTransition(2.0)), 4, 4,
-      TargetProperty::TRANSFORM));
+      KeyframeModel::TargetPropertyId(TargetProperty::TRANSFORM)));
   animation_->AddKeyframeModel(KeyframeModel::Create(
       std::unique_ptr<AnimationCurve>(new FakeFloatTransition(1.0, 0.f, 1.f)),
-      5, 5, TargetProperty::OPACITY));
+      5, 5, KeyframeModel::TargetPropertyId(TargetProperty::OPACITY)));
 
   animation_->Tick(kInitialTickTime);
   animation_->UpdateState(true, nullptr);
@@ -2047,7 +2064,8 @@
           target_value));
   curve->SetInitialValue(initial_value);
   std::unique_ptr<KeyframeModel> keyframe_model(KeyframeModel::Create(
-      std::move(curve), keyframe_model_id, 0, TargetProperty::SCROLL_OFFSET));
+      std::move(curve), keyframe_model_id, 0,
+      KeyframeModel::TargetPropertyId(TargetProperty::SCROLL_OFFSET)));
   keyframe_model->set_start_time(TicksFromSecondsF(123));
   keyframe_model->SetIsImplOnly();
   animation_impl_->AddKeyframeModel(std::move(keyframe_model));
@@ -2113,13 +2131,13 @@
   // Add two animations with the same group id but different durations.
   std::unique_ptr<KeyframeModel> first_keyframe_model(KeyframeModel::Create(
       std::unique_ptr<AnimationCurve>(new FakeTransformTransition(2.0)), 1,
-      group_id, TargetProperty::TRANSFORM));
+      group_id, KeyframeModel::TargetPropertyId(TargetProperty::TRANSFORM)));
   first_keyframe_model->set_is_controlling_instance_for_test(true);
   animation_impl_->AddKeyframeModel(std::move(first_keyframe_model));
 
   std::unique_ptr<KeyframeModel> second_keyframe_model(KeyframeModel::Create(
       std::unique_ptr<AnimationCurve>(new FakeFloatTransition(1.0, 0.f, 1.f)),
-      2, group_id, TargetProperty::OPACITY));
+      2, group_id, KeyframeModel::TargetPropertyId(TargetProperty::OPACITY)));
   second_keyframe_model->set_is_controlling_instance_for_test(true);
   animation_impl_->AddKeyframeModel(std::move(second_keyframe_model));
 
@@ -2242,7 +2260,8 @@
       base::TimeDelta::FromSecondsD(1.0), operations1, nullptr));
 
   std::unique_ptr<KeyframeModel> keyframe_model(KeyframeModel::Create(
-      std::move(curve1), 2, 2, TargetProperty::TRANSFORM));
+      std::move(curve1), 2, 2,
+      KeyframeModel::TargetPropertyId(TargetProperty::TRANSFORM)));
   animation_impl_->AddKeyframeModel(std::move(keyframe_model));
 
   // The only transform animation we've added is a translation.
@@ -2273,7 +2292,8 @@
   curve1->AddKeyframe(TransformKeyframe::Create(
       base::TimeDelta::FromSecondsD(1.0), operations1b, nullptr));
   std::unique_ptr<KeyframeModel> keyframe_model(KeyframeModel::Create(
-      std::move(curve1), 1, 1, TargetProperty::TRANSFORM));
+      std::move(curve1), 1, 1,
+      KeyframeModel::TargetPropertyId(TargetProperty::TRANSFORM)));
   keyframe_model->set_affects_active_elements(false);
   animation_impl_->AddKeyframeModel(std::move(keyframe_model));
 
@@ -2311,8 +2331,9 @@
       base::TimeDelta::FromSecondsD(1.0), operations2b, nullptr));
 
   animation_impl_->RemoveKeyframeModel(1);
-  keyframe_model =
-      KeyframeModel::Create(std::move(curve2), 2, 2, TargetProperty::TRANSFORM);
+  keyframe_model = KeyframeModel::Create(
+      std::move(curve2), 2, 2,
+      KeyframeModel::TargetPropertyId(TargetProperty::TRANSFORM));
 
   // Reverse Direction
   keyframe_model->set_direction(KeyframeModel::Direction::REVERSE);
@@ -2331,8 +2352,9 @@
   curve3->AddKeyframe(TransformKeyframe::Create(
       base::TimeDelta::FromSecondsD(1.0), operations3b, nullptr));
 
-  keyframe_model =
-      KeyframeModel::Create(std::move(curve3), 3, 3, TargetProperty::TRANSFORM);
+  keyframe_model = KeyframeModel::Create(
+      std::move(curve3), 3, 3,
+      KeyframeModel::TargetPropertyId(TargetProperty::TRANSFORM));
   keyframe_model->set_affects_active_elements(false);
   animation_impl_->AddKeyframeModel(std::move(keyframe_model));
 
@@ -2386,7 +2408,8 @@
       base::TimeDelta::FromSecondsD(1.0), operations2, nullptr));
 
   std::unique_ptr<KeyframeModel> keyframe_model_owned(KeyframeModel::Create(
-      std::move(curve1), 1, 1, TargetProperty::TRANSFORM));
+      std::move(curve1), 1, 1,
+      KeyframeModel::TargetPropertyId(TargetProperty::TRANSFORM)));
   KeyframeModel* keyframe_model = keyframe_model_owned.get();
   animation_impl_->AddKeyframeModel(std::move(keyframe_model_owned));
 
@@ -3883,10 +3906,10 @@
 
   animation_->AddKeyframeModel(KeyframeModel::Create(
       std::unique_ptr<AnimationCurve>(new FakeTransformTransition(1.0)), 1, 1,
-      TargetProperty::TRANSFORM));
+      KeyframeModel::TargetPropertyId(TargetProperty::TRANSFORM)));
   animation_->AddKeyframeModel(KeyframeModel::Create(
       std::unique_ptr<AnimationCurve>(new FakeFloatTransition(2.0, 0.f, 1.f)),
-      2, 2, TargetProperty::OPACITY));
+      2, 2, KeyframeModel::TargetPropertyId(TargetProperty::OPACITY)));
 
   // Finish the first keyframe model.
   animation_->Tick(kInitialTickTime);
diff --git a/cc/animation/keyframe_effect.cc b/cc/animation/keyframe_effect.cc
index e6979e5..dc9b8d6 100644
--- a/cc/animation/keyframe_effect.cc
+++ b/cc/animation/keyframe_effect.cc
@@ -137,32 +137,32 @@
     case AnimationCurve::TRANSFORM:
       target->NotifyClientTransformOperationsAnimated(
           curve->ToTransformAnimationCurve()->GetValue(trimmed),
-          keyframe_model->target_property_id(), keyframe_model);
+          keyframe_model->target_property_type(), keyframe_model);
       break;
     case AnimationCurve::FLOAT:
       target->NotifyClientFloatAnimated(
           curve->ToFloatAnimationCurve()->GetValue(trimmed),
-          keyframe_model->target_property_id(), keyframe_model);
+          keyframe_model->target_property_type(), keyframe_model);
       break;
     case AnimationCurve::FILTER:
       target->NotifyClientFilterAnimated(
           curve->ToFilterAnimationCurve()->GetValue(trimmed),
-          keyframe_model->target_property_id(), keyframe_model);
+          keyframe_model->target_property_type(), keyframe_model);
       break;
     case AnimationCurve::COLOR:
       target->NotifyClientColorAnimated(
           curve->ToColorAnimationCurve()->GetValue(trimmed),
-          keyframe_model->target_property_id(), keyframe_model);
+          keyframe_model->target_property_type(), keyframe_model);
       break;
     case AnimationCurve::SCROLL_OFFSET:
       target->NotifyClientScrollOffsetAnimated(
           curve->ToScrollOffsetAnimationCurve()->GetValue(trimmed),
-          keyframe_model->target_property_id(), keyframe_model);
+          keyframe_model->target_property_type(), keyframe_model);
       break;
     case AnimationCurve::SIZE:
       target->NotifyClientSizeAnimated(
           curve->ToSizeAnimationCurve()->GetValue(trimmed),
-          keyframe_model->target_property_id(), keyframe_model);
+          keyframe_model->target_property_type(), keyframe_model);
       break;
   }
 }
@@ -243,29 +243,30 @@
 
 void KeyframeEffect::AddKeyframeModel(
     std::unique_ptr<KeyframeModel> keyframe_model) {
-  DCHECK(keyframe_model->target_property_id() !=
+  DCHECK(keyframe_model->target_property_type() !=
              TargetProperty::SCROLL_OFFSET ||
          (animation_->animation_host()->SupportsScrollAnimations()));
   DCHECK(!keyframe_model->is_impl_only() ||
-         keyframe_model->target_property_id() == TargetProperty::SCROLL_OFFSET);
+         keyframe_model->target_property_type() ==
+             TargetProperty::SCROLL_OFFSET);
   // This is to make sure that keyframe models in the same group, i.e., start
   // together, don't animate the same property.
   DCHECK(std::none_of(
       keyframe_models_.begin(), keyframe_models_.end(),
       [&](const auto& existing_keyframe_model) {
-        return keyframe_model->target_property_id() ==
-                   existing_keyframe_model->target_property_id() &&
+        return keyframe_model->target_property_type() ==
+                   existing_keyframe_model->target_property_type() &&
                keyframe_model->group() == existing_keyframe_model->group();
       }));
 
-  if (keyframe_model->target_property_id() == TargetProperty::SCROLL_OFFSET) {
+  if (keyframe_model->target_property_type() == TargetProperty::SCROLL_OFFSET) {
     // We should never have more than one scroll offset animation queued on the
     // same scrolling element as this would result in multiple automated
     // scrolls.
     DCHECK(std::none_of(
         keyframe_models_.begin(), keyframe_models_.end(),
         [&](const auto& existing_keyframe_model) {
-          return existing_keyframe_model->target_property_id() ==
+          return existing_keyframe_model->target_property_type() ==
                      TargetProperty::SCROLL_OFFSET &&
                  !existing_keyframe_model->is_finished() &&
                  (!existing_keyframe_model->is_controlling_instance() ||
@@ -309,7 +310,7 @@
       });
   for (auto it = keyframe_models_to_remove; it != keyframe_models_.end();
        ++it) {
-    if ((*it)->target_property_id() == TargetProperty::SCROLL_OFFSET) {
+    if ((*it)->target_property_type() == TargetProperty::SCROLL_OFFSET) {
       if (has_bound_element_animations())
         scroll_offset_animation_was_interrupted_ = true;
     } else if (!(*it)->is_finished()) {
@@ -352,7 +353,7 @@
 
   bool aborted_keyframe_model = false;
   for (auto& keyframe_model : keyframe_models_) {
-    if (keyframe_model->target_property_id() == target_property &&
+    if (keyframe_model->target_property_type() == target_property &&
         !keyframe_model->is_finished()) {
       // Currently only impl-only scroll offset KeyframeModels can be completed
       // on the main thread.
@@ -477,7 +478,7 @@
 
 bool KeyframeEffect::AffectsCustomProperty() const {
   for (const auto& it : keyframe_models_)
-    if (it->target_property_id() == TargetProperty::CSS_CUSTOM_PROPERTY)
+    if (it->target_property_type() == TargetProperty::CSS_CUSTOM_PROPERTY)
       return true;
   return false;
 }
@@ -493,7 +494,7 @@
 bool KeyframeEffect::AnimationsPreserveAxisAlignment() const {
   for (const auto& keyframe_model : keyframe_models_) {
     if (keyframe_model->is_finished() ||
-        keyframe_model->target_property_id() != TargetProperty::TRANSFORM)
+        keyframe_model->target_property_type() != TargetProperty::TRANSFORM)
       continue;
 
     const TransformAnimationCurve* transform_animation_curve =
@@ -513,7 +514,7 @@
   bool starting_scale_valid = true;
   for (const auto& keyframe_model : keyframe_models_) {
     if (keyframe_model->is_finished() ||
-        keyframe_model->target_property_id() != TargetProperty::TRANSFORM)
+        keyframe_model->target_property_type() != TargetProperty::TRANSFORM)
       continue;
 
     if ((list_type == ElementListType::ACTIVE &&
@@ -573,7 +574,7 @@
     ElementListType list_type) const {
   for (const auto& keyframe_model : keyframe_models_) {
     if (!keyframe_model->is_finished() &&
-        keyframe_model->target_property_id() == target_property) {
+        keyframe_model->target_property_type() == target_property) {
       if ((list_type == ElementListType::ACTIVE &&
            keyframe_model->affects_active_elements()) ||
           (list_type == ElementListType::PENDING &&
@@ -590,7 +591,7 @@
   for (const auto& keyframe_model : keyframe_models_) {
     if (!keyframe_model->is_finished() &&
         keyframe_model->InEffect(last_tick_time_.value_or(base::TimeTicks())) &&
-        keyframe_model->target_property_id() == target_property) {
+        keyframe_model->target_property_type() == target_property) {
       if ((list_type == ElementListType::ACTIVE &&
            keyframe_model->affects_active_elements()) ||
           (list_type == ElementListType::PENDING &&
@@ -605,7 +606,7 @@
     TargetProperty::Type target_property) const {
   for (size_t i = 0; i < keyframe_models_.size(); ++i) {
     size_t index = keyframe_models_.size() - i - 1;
-    if (keyframe_models_[index]->target_property_id() == target_property)
+    if (keyframe_models_[index]->target_property_type() == target_property)
       return keyframe_models_[index].get();
   }
   return nullptr;
@@ -631,7 +632,7 @@
           keyframe_model->InEffect(last_tick_time_.value_or(base::TimeTicks()));
       bool active = keyframe_model->affects_active_elements();
       bool pending = keyframe_model->affects_pending_elements();
-      int property = keyframe_model->target_property_id();
+      int property = keyframe_model->target_property_type();
 
       if (pending)
         pending_state->potentially_animating[property] = true;
@@ -706,7 +707,8 @@
     if (keyframe_effect_impl->GetKeyframeModelById(keyframe_model->id()))
       continue;
 
-    if (keyframe_model->target_property_id() == TargetProperty::SCROLL_OFFSET &&
+    if (keyframe_model->target_property_type() ==
+            TargetProperty::SCROLL_OFFSET &&
         !keyframe_model->curve()
              ->ToScrollOffsetAnimationCurve()
              ->HasSetInitialValue()) {
@@ -841,7 +843,7 @@
     auto& keyframe_model = keyframe_models_[i];
     if (keyframe_model->run_state() == KeyframeModel::STARTING ||
         keyframe_model->run_state() == KeyframeModel::RUNNING) {
-      int property = keyframe_model->target_property_id();
+      int property = keyframe_model->target_property_type();
       if (keyframe_model->affects_active_elements()) {
         blocked_properties_for_active_elements[property] = true;
       }
@@ -871,12 +873,13 @@
       bool affects_pending_elements =
           keyframe_model_waiting_for_target->affects_pending_elements();
       enqueued_properties[keyframe_model_waiting_for_target
-                              ->target_property_id()] = true;
+                              ->target_property_type()] = true;
       for (size_t j = keyframe_model_index + 1; j < keyframe_models_.size();
            ++j) {
         if (keyframe_model_waiting_for_target->group() ==
             keyframe_models_[j]->group()) {
-          enqueued_properties[keyframe_models_[j]->target_property_id()] = true;
+          enqueued_properties[keyframe_models_[j]->target_property_type()] =
+              true;
           affects_active_elements |=
               keyframe_models_[j]->affects_active_elements();
           affects_pending_elements |=
@@ -1095,7 +1098,7 @@
                        {animation_->animation_timeline()->id(),
                         animation_->id(), keyframe_model.id()},
                        keyframe_model.group(),
-                       keyframe_model.target_property_id(), monotonic_time);
+                       keyframe_model.target_property_type(), monotonic_time);
   event.is_impl_only = keyframe_model.is_impl_only();
   if (!event.is_impl_only) {
     events->events_.push_back(event);
@@ -1109,7 +1112,8 @@
     AnimationEvents* events,
     const KeyframeModel& keyframe_model,
     base::TimeTicks monotonic_time) {
-  DCHECK_EQ(keyframe_model.target_property_id(), TargetProperty::SCROLL_OFFSET);
+  DCHECK_EQ(keyframe_model.target_property_type(),
+            TargetProperty::SCROLL_OFFSET);
   if (!events)
     return;
 
@@ -1117,7 +1121,7 @@
                                 {animation_->animation_timeline()->id(),
                                  animation_->id(), keyframe_model.id()},
                                 keyframe_model.group(),
-                                keyframe_model.target_property_id(),
+                                keyframe_model.target_property_type(),
                                 monotonic_time);
   takeover_event.animation_start_time = keyframe_model.start_time();
   const ScrollOffsetAnimationCurve* scroll_offset_animation_curve =
@@ -1130,7 +1134,7 @@
                                 {animation_->animation_timeline()->id(),
                                  animation_->id(), keyframe_model.id()},
                                 keyframe_model.group(),
-                                keyframe_model.target_property_id(),
+                                keyframe_model.target_property_type(),
                                 monotonic_time);
   // Notify the compositor that the animation is finished.
   finished_event.is_impl_only = true;
diff --git a/cc/animation/keyframe_model.cc b/cc/animation/keyframe_model.cc
index b30d719..c718ea9 100644
--- a/cc/animation/keyframe_model.cc
+++ b/cc/animation/keyframe_model.cc
@@ -48,6 +48,30 @@
 
 namespace cc {
 
+KeyframeModel::TargetPropertyId::TargetPropertyId(int target_property_type)
+    : target_property_type_(target_property_type),
+      custom_property_name_(""),
+      native_property_type_(PaintWorkletInput::NativePropertyType::kInvalid) {}
+
+KeyframeModel::TargetPropertyId::TargetPropertyId(
+    int target_property_type,
+    const std::string& custom_property_name)
+    : target_property_type_(target_property_type),
+      custom_property_name_(custom_property_name),
+      native_property_type_(PaintWorkletInput::NativePropertyType::kInvalid) {}
+
+KeyframeModel::TargetPropertyId::TargetPropertyId(
+    int target_property_type,
+    PaintWorkletInput::NativePropertyType native_property_type)
+    : target_property_type_(target_property_type),
+      custom_property_name_(""),
+      native_property_type_(native_property_type) {}
+
+KeyframeModel::TargetPropertyId::TargetPropertyId(
+    const TargetPropertyId& other) = default;
+
+KeyframeModel::TargetPropertyId::~TargetPropertyId() = default;
+
 std::string KeyframeModel::ToString(RunState state) {
   return s_runStateNames[state];
 }
@@ -56,12 +80,9 @@
     std::unique_ptr<AnimationCurve> curve,
     int keyframe_model_id,
     int group_id,
-    int target_property_id,
-    const std::string& custom_property_name,
-    PaintWorkletInput::NativePropertyType native_property_type) {
-  return base::WrapUnique(new KeyframeModel(
-      std::move(curve), keyframe_model_id, group_id, target_property_id,
-      custom_property_name, native_property_type));
+    TargetPropertyId target_property_id) {
+  return base::WrapUnique(new KeyframeModel(std::move(curve), keyframe_model_id,
+                                            group_id, target_property_id));
 }
 
 std::unique_ptr<KeyframeModel> KeyframeModel::CreateImplInstance(
@@ -70,8 +91,7 @@
   // creating multiple controlling instances.
   DCHECK(!is_controlling_instance_);
   std::unique_ptr<KeyframeModel> to_return(
-      new KeyframeModel(curve_->Clone(), id_, group_, target_property_id_,
-                        custom_property_name_, native_property_type_));
+      new KeyframeModel(curve_->Clone(), id_, group_, target_property_id_));
   to_return->element_id_ = element_id_;
   to_return->run_state_ = initial_run_state;
   to_return->iterations_ = iterations_;
@@ -88,19 +108,14 @@
   return to_return;
 }
 
-KeyframeModel::KeyframeModel(
-    std::unique_ptr<AnimationCurve> curve,
-    int keyframe_model_id,
-    int group_id,
-    int target_property_id,
-    const std::string& custom_property_name,
-    PaintWorkletInput::NativePropertyType native_property_type)
+KeyframeModel::KeyframeModel(std::unique_ptr<AnimationCurve> curve,
+                             int keyframe_model_id,
+                             int group_id,
+                             TargetPropertyId target_property_id)
     : curve_(std::move(curve)),
       id_(keyframe_model_id),
       group_(group_id),
       target_property_id_(target_property_id),
-      custom_property_name_(custom_property_name),
-      native_property_type_(native_property_type),
       run_state_(WAITING_FOR_TARGET_AVAILABILITY),
       iterations_(1),
       iteration_start_(0),
@@ -112,10 +127,7 @@
       is_controlling_instance_(false),
       is_impl_only_(false),
       affects_active_elements_(true),
-      affects_pending_elements_(true) {
-  DCHECK(custom_property_name_.empty() ||
-         target_property_id_ == TargetProperty::CSS_CUSTOM_PROPERTY);
-}
+      affects_pending_elements_(true) {}
 
 KeyframeModel::~KeyframeModel() {
   if (run_state_ == RUNNING || run_state_ == PAUSED)
@@ -126,7 +138,8 @@
                                 base::TimeTicks monotonic_time) {
   char name_buffer[256];
   base::snprintf(name_buffer, sizeof(name_buffer), "%s-%d-%d",
-                 s_curveTypeNames[curve_->Type()], target_property_id_, group_);
+                 s_curveTypeNames[curve_->Type()],
+                 target_property_id_.target_property_type(), group_);
 
   bool is_waiting_to_start =
       run_state_ == WAITING_FOR_TARGET_AVAILABILITY || run_state_ == STARTING;
@@ -354,10 +367,14 @@
 
 std::string KeyframeModel::ToString() const {
   return base::StringPrintf(
-      "KeyframeModel{id=%d, group=%d, target_property_id=%d, "
-      "run_state=%s}",
-      id_, group_, target_property_id_,
-      KeyframeModel::ToString(run_state_).c_str());
+      "KeyframeModel{id=%d, group=%d, target_property_type=%d, "
+      "custom_property_name=%s, native_property_type=%d, run_state=%s, "
+      "element_id=%s}",
+      id_, group_, target_property_id_.target_property_type(),
+      target_property_id_.custom_property_name().c_str(),
+      static_cast<int>(target_property_id_.native_property_type()),
+      KeyframeModel::ToString(run_state_).c_str(),
+      element_id_.ToString().c_str());
 }
 
 void KeyframeModel::SetIsImplOnly() {
diff --git a/cc/animation/keyframe_model.h b/cc/animation/keyframe_model.h
index 23fd3d0..ad88bc07 100644
--- a/cc/animation/keyframe_model.h
+++ b/cc/animation/keyframe_model.h
@@ -56,17 +56,44 @@
 
   enum class Phase { BEFORE, ACTIVE, AFTER };
 
-  // The |custom_property_name| has a default value of an empty string,
-  // indicating that the animated property is a native property. When it is an
-  // animated custom property, it should be the property name.
+  class CC_ANIMATION_EXPORT TargetPropertyId {
+   public:
+    // For a property that is neither TargetProperty::CSS_CUSTOM_PROPERTY nor
+    // TargetProperty::NATIVE_PROPERTY.
+    explicit TargetPropertyId(int target_property_type);
+    // For TargetProperty::CSS_CUSTOM_PROPERTY, the string is the custom
+    // property name.
+    TargetPropertyId(int target_property_type,
+                     const std::string& custom_property_name);
+    // For TargetProperty::NATIVE_PROPERTY.
+    TargetPropertyId(
+        int target_property_type,
+        PaintWorkletInput::NativePropertyType native_property_type);
+    TargetPropertyId(const TargetPropertyId&);
+    ~TargetPropertyId();
+
+    int target_property_type() const { return target_property_type_; }
+    const std::string& custom_property_name() const {
+      return custom_property_name_;
+    }
+    PaintWorkletInput::NativePropertyType native_property_type() const {
+      return native_property_type_;
+    }
+
+   private:
+    int target_property_type_;
+    // Name of the animated custom property. Empty if it is an animated native
+    // property.
+    std::string custom_property_name_;
+    // Type of the animated native property.
+    PaintWorkletInput::NativePropertyType native_property_type_;
+  };
+
   static std::unique_ptr<KeyframeModel> Create(
       std::unique_ptr<AnimationCurve> curve,
       int keyframe_model_id,
       int group_id,
-      int target_property_id,
-      const std::string& custom_property_name = "",
-      PaintWorkletInput::NativePropertyType native_property_type =
-          PaintWorkletInput::NativePropertyType::kInvalid);
+      TargetPropertyId target_property_id);
 
   std::unique_ptr<KeyframeModel> CreateImplInstance(
       RunState initial_run_state) const;
@@ -78,7 +105,9 @@
 
   int id() const { return id_; }
   int group() const { return group_; }
-  int target_property_id() const { return target_property_id_; }
+  int target_property_type() const {
+    return target_property_id_.target_property_type();
+  }
 
   ElementId element_id() const { return element_id_; }
   void set_element_id(ElementId element_id) { element_id_ = element_id; }
@@ -180,11 +209,11 @@
   bool affects_pending_elements() const { return affects_pending_elements_; }
 
   const std::string& custom_property_name() const {
-    return custom_property_name_;
+    return target_property_id_.custom_property_name();
   }
 
   PaintWorkletInput::NativePropertyType native_property_type() const {
-    return native_property_type_;
+    return target_property_id_.native_property_type();
   }
 
   KeyframeModel::Phase CalculatePhaseForTesting(
@@ -194,9 +223,7 @@
   KeyframeModel(std::unique_ptr<AnimationCurve> curve,
                 int keyframe_model_id,
                 int group_id,
-                int target_property_id,
-                const std::string& custom_property_name,
-                PaintWorkletInput::NativePropertyType native_property_type);
+                TargetPropertyId target_property_id);
 
   // Return local time for this keyframe model given the absolute monotonic
   // time.
@@ -240,17 +267,7 @@
   // properties until all KeyframeModels in the group have finished animating.
   int group_;
 
-  // TODO(crbug.com/1156631): wrap the following 3 attributes into a class.
-  // If this is CSS_CUSTOM_PROPERTY then |custom_property_name_| should be
-  // non-empty which identifies which custom property it is. If this is
-  // NATIVE_PROPERTY then |native_property_type_| should be set to targeted
-  // property.
-  int target_property_id_;
-  // Name of the animated custom property. Empty if it is an animated native
-  // property.
-  std::string custom_property_name_;
-  // Type of the animated native property.
-  PaintWorkletInput::NativePropertyType native_property_type_;
+  TargetPropertyId target_property_id_;
 
   // If specified, overrides the ElementId to apply this KeyframeModel's effect
   // value on.
diff --git a/cc/animation/keyframe_model_unittest.cc b/cc/animation/keyframe_model_unittest.cc
index 2d4e94b..1e01ad2a 100644
--- a/cc/animation/keyframe_model_unittest.cc
+++ b/cc/animation/keyframe_model_unittest.cc
@@ -4,6 +4,8 @@
 
 #include "cc/animation/keyframe_model.h"
 
+#include <limits>
+
 #include "base/strings/stringprintf.h"
 #include "cc/test/animation_test_common.h"
 #include "cc/trees/target_property.h"
@@ -22,9 +24,9 @@
 std::unique_ptr<KeyframeModel> CreateKeyframeModel(double iterations,
                                                    double duration,
                                                    double playback_rate) {
-  std::unique_ptr<KeyframeModel> to_return(
-      KeyframeModel::Create(std::make_unique<FakeFloatAnimationCurve>(duration),
-                            0, 1, TargetProperty::OPACITY));
+  std::unique_ptr<KeyframeModel> to_return(KeyframeModel::Create(
+      std::make_unique<FakeFloatAnimationCurve>(duration), 0, 1,
+      KeyframeModel::TargetPropertyId(TargetProperty::OPACITY)));
   to_return->set_iterations(iterations);
   to_return->set_playback_rate(playback_rate);
   return to_return;
@@ -1381,27 +1383,29 @@
 }
 
 TEST(KeyframeModelTest, ToString) {
-  std::unique_ptr<KeyframeModel> keyframe_model =
-      KeyframeModel::Create(std::make_unique<FakeFloatAnimationCurve>(15), 42,
-                            73, TargetProperty::OPACITY);
-  EXPECT_EQ(
-      base::StringPrintf("KeyframeModel{id=%d, group=73, target_property_id=1, "
-                         "run_state=WAITING_FOR_TARGET_AVAILABILITY}",
-                         keyframe_model->id()),
-      keyframe_model->ToString());
+  std::unique_ptr<KeyframeModel> keyframe_model = KeyframeModel::Create(
+      std::make_unique<FakeFloatAnimationCurve>(15), 42, 73,
+      KeyframeModel::TargetPropertyId(TargetProperty::OPACITY));
+  EXPECT_EQ(base::StringPrintf(
+                "KeyframeModel{id=%d, group=73, target_property_type=1, "
+                "custom_property_name=, native_property_type=1, "
+                "run_state=WAITING_FOR_TARGET_AVAILABILITY, element_id=(0)}",
+                keyframe_model->id()),
+            keyframe_model->ToString());
 }
 
 TEST(KeyframeModelTest, CustomPropertyKeyframe) {
   std::unique_ptr<KeyframeModel> keyframe_model =
       KeyframeModel::Create(std::make_unique<FakeFloatAnimationCurve>(1), 1, 1,
-                            TargetProperty::CSS_CUSTOM_PROPERTY, "foo");
+                            KeyframeModel::TargetPropertyId(
+                                TargetProperty::CSS_CUSTOM_PROPERTY, "foo"));
   EXPECT_EQ(keyframe_model->custom_property_name(), "foo");
 }
 
 TEST(KeyframeModelTest, NonCustomPropertyKeyframe) {
-  std::unique_ptr<KeyframeModel> keyframe_model =
-      KeyframeModel::Create(std::make_unique<FakeFloatAnimationCurve>(1), 1, 1,
-                            TargetProperty::TRANSFORM);
+  std::unique_ptr<KeyframeModel> keyframe_model = KeyframeModel::Create(
+      std::make_unique<FakeFloatAnimationCurve>(1), 1, 1,
+      KeyframeModel::TargetPropertyId(TargetProperty::TRANSFORM));
   EXPECT_EQ(keyframe_model->custom_property_name(), "");
 }
 
diff --git a/cc/animation/scroll_offset_animations_impl.cc b/cc/animation/scroll_offset_animations_impl.cc
index 4989921..d6c44889 100644
--- a/cc/animation/scroll_offset_animations_impl.cc
+++ b/cc/animation/scroll_offset_animations_impl.cc
@@ -4,6 +4,9 @@
 
 #include "cc/animation/scroll_offset_animations_impl.h"
 
+#include <memory>
+#include <utility>
+
 #include "base/trace_event/trace_event.h"
 #include "base/trace_event/traced_value.h"
 #include "cc/animation/animation.h"
@@ -76,7 +79,8 @@
 
   std::unique_ptr<KeyframeModel> keyframe_model = KeyframeModel::Create(
       std::move(curve), AnimationIdProvider::NextKeyframeModelId(),
-      AnimationIdProvider::NextGroupId(), TargetProperty::SCROLL_OFFSET);
+      AnimationIdProvider::NextGroupId(),
+      KeyframeModel::TargetPropertyId(TargetProperty::SCROLL_OFFSET));
   keyframe_model->set_time_offset(animation_start_offset);
   keyframe_model->SetIsImplOnly();
 
@@ -171,7 +175,8 @@
 
   std::unique_ptr<KeyframeModel> new_keyframe_model = KeyframeModel::Create(
       std::move(new_curve), AnimationIdProvider::NextKeyframeModelId(),
-      AnimationIdProvider::NextGroupId(), TargetProperty::SCROLL_OFFSET);
+      AnimationIdProvider::NextGroupId(),
+      KeyframeModel::TargetPropertyId(TargetProperty::SCROLL_OFFSET));
   new_keyframe_model->set_start_time(keyframe_model->start_time());
   new_keyframe_model->SetIsImplOnly();
   new_keyframe_model->set_affects_active_elements(false);
diff --git a/cc/test/animation_test_common.cc b/cc/test/animation_test_common.cc
index 562dcdbe..981cc6c 100644
--- a/cc/test/animation_test_common.cc
+++ b/cc/test/animation_test_common.cc
@@ -54,7 +54,7 @@
 
   std::unique_ptr<KeyframeModel> keyframe_model(KeyframeModel::Create(
       std::move(curve), id, AnimationIdProvider::NextGroupId(),
-      TargetProperty::OPACITY));
+      KeyframeModel::TargetPropertyId(TargetProperty::OPACITY)));
   keyframe_model->set_needs_synchronized_start_time(true);
 
   target->AddKeyframeModel(std::move(keyframe_model));
@@ -80,7 +80,7 @@
 
   std::unique_ptr<KeyframeModel> keyframe_model(KeyframeModel::Create(
       std::move(curve), id, AnimationIdProvider::NextGroupId(),
-      TargetProperty::TRANSFORM));
+      KeyframeModel::TargetPropertyId(TargetProperty::TRANSFORM)));
   keyframe_model->set_needs_synchronized_start_time(true);
 
   target->AddKeyframeModel(std::move(keyframe_model));
@@ -125,7 +125,7 @@
 
   std::unique_ptr<KeyframeModel> keyframe_model(KeyframeModel::Create(
       std::move(curve), id, AnimationIdProvider::NextGroupId(),
-      TargetProperty::FILTER));
+      KeyframeModel::TargetPropertyId(TargetProperty::FILTER)));
   keyframe_model->set_needs_synchronized_start_time(true);
 
   target->AddKeyframeModel(std::move(keyframe_model));
@@ -155,7 +155,7 @@
 
   std::unique_ptr<KeyframeModel> keyframe_model(KeyframeModel::Create(
       std::move(curve), id, AnimationIdProvider::NextGroupId(),
-      TargetProperty::BACKDROP_FILTER));
+      KeyframeModel::TargetPropertyId(TargetProperty::BACKDROP_FILTER)));
   keyframe_model->set_needs_synchronized_start_time(true);
 
   target->AddKeyframeModel(std::move(keyframe_model));
@@ -252,7 +252,7 @@
 
   std::unique_ptr<KeyframeModel> keyframe_model(KeyframeModel::Create(
       std::move(curve), id, AnimationIdProvider::NextGroupId(),
-      TargetProperty::SCROLL_OFFSET));
+      KeyframeModel::TargetPropertyId(TargetProperty::SCROLL_OFFSET)));
   keyframe_model->SetIsImplOnly();
 
   animation->AddKeyframeModel(std::move(keyframe_model));
@@ -320,7 +320,7 @@
 
   std::unique_ptr<KeyframeModel> keyframe_model(KeyframeModel::Create(
       std::move(curve), id, AnimationIdProvider::NextGroupId(),
-      TargetProperty::OPACITY));
+      KeyframeModel::TargetPropertyId(TargetProperty::OPACITY)));
   keyframe_model->set_needs_synchronized_start_time(true);
 
   animation->AddKeyframeModel(std::move(keyframe_model));
diff --git a/cc/trees/draw_properties_unittest.cc b/cc/trees/draw_properties_unittest.cc
index 7eaca3e3..93183a35 100644
--- a/cc/trees/draw_properties_unittest.cc
+++ b/cc/trees/draw_properties_unittest.cc
@@ -1747,7 +1747,7 @@
   // Add a transform animation with a start delay to |grand_child|.
   std::unique_ptr<KeyframeModel> keyframe_model = KeyframeModel::Create(
       std::unique_ptr<AnimationCurve>(new FakeTransformTransition(1.0)), 0, 1,
-      TargetProperty::TRANSFORM);
+      KeyframeModel::TargetPropertyId(TargetProperty::TRANSFORM));
   keyframe_model->set_fill_mode(KeyframeModel::FillMode::NONE);
   keyframe_model->set_time_offset(base::TimeDelta::FromMilliseconds(-1000));
   AddKeyframeModelToElementWithAnimation(
@@ -6410,7 +6410,8 @@
   int keyframe_model_id = 0;
   std::unique_ptr<KeyframeModel> keyframe_model = KeyframeModel::Create(
       std::unique_ptr<AnimationCurve>(new FakeTransformTransition(1.0)),
-      keyframe_model_id, 1, TargetProperty::TRANSFORM);
+      keyframe_model_id, 1,
+      KeyframeModel::TargetPropertyId(TargetProperty::TRANSFORM));
   keyframe_model->set_fill_mode(KeyframeModel::FillMode::NONE);
   keyframe_model->set_time_offset(base::TimeDelta::FromMilliseconds(-1000));
   AddKeyframeModelToElementWithAnimation(child->element_id(), timeline(),
@@ -6437,7 +6438,8 @@
   keyframe_model_id = 1;
   keyframe_model = KeyframeModel::Create(
       std::unique_ptr<AnimationCurve>(new FakeFloatTransition(1.0, 0.f, 1.f)),
-      keyframe_model_id, 1, TargetProperty::OPACITY);
+      keyframe_model_id, 1,
+      KeyframeModel::TargetPropertyId(TargetProperty::OPACITY));
   keyframe_model->set_fill_mode(KeyframeModel::FillMode::NONE);
   keyframe_model->set_time_offset(base::TimeDelta::FromMilliseconds(-1000));
   AddKeyframeModelToElementWithExistingKeyframeEffect(
@@ -6555,8 +6557,9 @@
       TransformKeyframe::Create(base::TimeDelta(), start, nullptr));
   curve->AddKeyframe(TransformKeyframe::Create(
       base::TimeDelta::FromSecondsD(1.0), operation, nullptr));
-  std::unique_ptr<KeyframeModel> transform_animation(
-      KeyframeModel::Create(std::move(curve), 3, 3, TargetProperty::TRANSFORM));
+  std::unique_ptr<KeyframeModel> transform_animation(KeyframeModel::Create(
+      std::move(curve), 3, 3,
+      KeyframeModel::TargetPropertyId(TargetProperty::TRANSFORM)));
   scoped_refptr<Animation> animation(Animation::Create(1));
   timeline()->AttachAnimation(animation);
   animation->AttachElement(parent->element_id());
@@ -6587,8 +6590,9 @@
       TransformKeyframe::Create(base::TimeDelta(), start, nullptr));
   curve->AddKeyframe(TransformKeyframe::Create(
       base::TimeDelta::FromSecondsD(1.0), operation, nullptr));
-  std::unique_ptr<KeyframeModel> transform_animation(
-      KeyframeModel::Create(std::move(curve), 3, 3, TargetProperty::TRANSFORM));
+  std::unique_ptr<KeyframeModel> transform_animation(KeyframeModel::Create(
+      std::move(curve), 3, 3,
+      KeyframeModel::TargetPropertyId(TargetProperty::TRANSFORM)));
   scoped_refptr<Animation> animation(Animation::Create(1));
   timeline_impl()->AttachAnimation(animation);
   animation->AddKeyframeModel(std::move(transform_animation));
@@ -6699,8 +6703,9 @@
       FloatKeyframe::Create(base::TimeDelta(), 0.9f, std::move(func)));
   curve->AddKeyframe(
       FloatKeyframe::Create(base::TimeDelta::FromSecondsD(1.0), 0.3f, nullptr));
-  std::unique_ptr<KeyframeModel> keyframe_model(
-      KeyframeModel::Create(std::move(curve), 3, 3, TargetProperty::OPACITY));
+  std::unique_ptr<KeyframeModel> keyframe_model(KeyframeModel::Create(
+      std::move(curve), 3, 3,
+      KeyframeModel::TargetPropertyId(TargetProperty::OPACITY)));
   scoped_refptr<Animation> animation(Animation::Create(1));
   timeline()->AttachAnimation(animation);
   animation->AddKeyframeModel(std::move(keyframe_model));
@@ -7505,7 +7510,8 @@
   int keyframe_model_id = 0;
   std::unique_ptr<KeyframeModel> keyframe_model = KeyframeModel::Create(
       std::unique_ptr<AnimationCurve>(new FakeFloatTransition(1.0, 0.f, 1.f)),
-      keyframe_model_id, 1, TargetProperty::OPACITY);
+      keyframe_model_id, 1,
+      KeyframeModel::TargetPropertyId(TargetProperty::OPACITY));
   keyframe_model->set_fill_mode(KeyframeModel::FillMode::NONE);
   keyframe_model->set_time_offset(base::TimeDelta::FromMilliseconds(-1000));
   KeyframeModel* keyframe_model_ptr = keyframe_model.get();
@@ -7561,8 +7567,9 @@
       TransformKeyframe::Create(base::TimeDelta(), start, nullptr));
   curve->AddKeyframe(TransformKeyframe::Create(
       base::TimeDelta::FromSecondsD(1.0), operation, nullptr));
-  std::unique_ptr<KeyframeModel> keyframe_model(
-      KeyframeModel::Create(std::move(curve), 3, 3, TargetProperty::TRANSFORM));
+  std::unique_ptr<KeyframeModel> keyframe_model(KeyframeModel::Create(
+      std::move(curve), 3, 3,
+      KeyframeModel::TargetPropertyId(TargetProperty::TRANSFORM)));
   keyframe_model->set_fill_mode(KeyframeModel::FillMode::NONE);
   keyframe_model->set_time_offset(base::TimeDelta::FromMilliseconds(-1000));
   KeyframeModel* keyframe_model_ptr = keyframe_model.get();
diff --git a/cc/trees/layer_tree_host_unittest_animation.cc b/cc/trees/layer_tree_host_unittest_animation.cc
index a3a4c36..0c19447 100644
--- a/cc/trees/layer_tree_host_unittest_animation.cc
+++ b/cc/trees/layer_tree_host_unittest_animation.cc
@@ -556,7 +556,8 @@
       // Any valid AnimationCurve will do here.
       std::unique_ptr<AnimationCurve> curve(new FakeFloatAnimationCurve());
       std::unique_ptr<KeyframeModel> keyframe_model(KeyframeModel::Create(
-          std::move(curve), 1, 1, TargetProperty::OPACITY));
+          std::move(curve), 1, 1,
+          KeyframeModel::TargetPropertyId(TargetProperty::OPACITY)));
       animation_->AddKeyframeModel(std::move(keyframe_model));
 
       // We add the animation *before* attaching the layer to the tree.
@@ -794,7 +795,8 @@
                 CreateEaseInOutAnimationForTesting(
                     gfx::ScrollOffset(500.f, 550.f)));
         std::unique_ptr<KeyframeModel> keyframe_model(KeyframeModel::Create(
-            std::move(curve), 1, 0, TargetProperty::SCROLL_OFFSET));
+            std::move(curve), 1, 0,
+            KeyframeModel::TargetPropertyId(TargetProperty::SCROLL_OFFSET)));
         keyframe_model->set_needs_synchronized_start_time(true);
         bool impl_scrolling_supported = proxy()->SupportsImplScrolling();
         if (impl_scrolling_supported)
@@ -1006,7 +1008,8 @@
         ScrollOffsetAnimationCurveFactory::CreateEaseInOutAnimationForTesting(
             gfx::ScrollOffset(6500.f, 7500.f)));
     std::unique_ptr<KeyframeModel> keyframe_model(KeyframeModel::Create(
-        std::move(curve), 1, 0, TargetProperty::SCROLL_OFFSET));
+        std::move(curve), 1, 0,
+        KeyframeModel::TargetPropertyId(TargetProperty::SCROLL_OFFSET)));
     keyframe_model->set_needs_synchronized_start_time(true);
 
     AttachAnimationsToTimeline();
@@ -1086,7 +1089,8 @@
         ScrollOffsetAnimationCurveFactory::CreateEaseInOutAnimationForTesting(
             gfx::ScrollOffset(6500.f, 7500.f)));
     std::unique_ptr<KeyframeModel> keyframe_model(KeyframeModel::Create(
-        std::move(curve), 1, 0, TargetProperty::SCROLL_OFFSET));
+        std::move(curve), 1, 0,
+        KeyframeModel::TargetPropertyId(TargetProperty::SCROLL_OFFSET)));
     keyframe_model->set_needs_synchronized_start_time(true);
 
     AttachAnimationsToTimeline();
@@ -1211,7 +1215,8 @@
         ScrollOffsetAnimationCurveFactory::CreateEaseInOutAnimationForTesting(
             final_position_));
     std::unique_ptr<KeyframeModel> keyframe_model(KeyframeModel::Create(
-        std::move(curve), 1, 0, TargetProperty::SCROLL_OFFSET));
+        std::move(curve), 1, 0,
+        KeyframeModel::TargetPropertyId(TargetProperty::SCROLL_OFFSET)));
     keyframe_model->set_needs_synchronized_start_time(true);
 
     AttachAnimationsToTimeline();
@@ -2167,7 +2172,8 @@
         ScrollOffsetAnimationCurveFactory::CreateEaseInOutAnimationForTesting(
             gfx::ScrollOffset(500.f, 550.f)));
     std::unique_ptr<KeyframeModel> keyframe_model(KeyframeModel::Create(
-        std::move(curve), 1, 0, TargetProperty::SCROLL_OFFSET));
+        std::move(curve), 1, 0,
+        KeyframeModel::TargetPropertyId(TargetProperty::SCROLL_OFFSET)));
     keyframe_model->set_needs_synchronized_start_time(true);
     ASSERT_TRUE(proxy()->SupportsImplScrolling());
     animation_child_->AddKeyframeModel(std::move(keyframe_model));
diff --git a/cc/trees/property_tree_builder_unittest.cc b/cc/trees/property_tree_builder_unittest.cc
index c824607..c517b917 100644
--- a/cc/trees/property_tree_builder_unittest.cc
+++ b/cc/trees/property_tree_builder_unittest.cc
@@ -552,8 +552,9 @@
       FilterKeyframe::Create(base::TimeDelta(), start_filters, nullptr));
   curve->AddKeyframe(FilterKeyframe::Create(
       base::TimeDelta::FromMilliseconds(100), end_filters, nullptr));
-  std::unique_ptr<KeyframeModel> keyframe_model =
-      KeyframeModel::Create(std::move(curve), 0, 1, TargetProperty::FILTER);
+  std::unique_ptr<KeyframeModel> keyframe_model = KeyframeModel::Create(
+      std::move(curve), 0, 1,
+      KeyframeModel::TargetPropertyId(TargetProperty::FILTER));
   keyframe_model->set_fill_mode(KeyframeModel::FillMode::NONE);
   keyframe_model->set_time_offset(base::TimeDelta::FromMilliseconds(-1000));
 
diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
index 170aeec..0827f3d 100644
--- a/chrome/android/BUILD.gn
+++ b/chrome/android/BUILD.gn
@@ -2344,7 +2344,6 @@
                              "include_64_bit_webview",
                              "is_64_bit_browser",
                              "is_base_module",
-                             "module_descs",
                              "resource_ids_provider_dep",
                              "static_library_provider",
                              "static_library_synchronized_proguard",
@@ -2996,9 +2995,6 @@
     target_type = "android_app_bundle_module"
     is_base_module = true
     version_code = _version_code
-    if (enable_chrome_module) {
-      module_descs = _module_descs
-    }
 
     if (!_is_trichrome ||
         !defined(invoker.static_library_synchronized_proguard) ||
@@ -3230,7 +3226,6 @@
     # Files under a feature's public/ dir are included in chrome_java's source
     # files, so include these files in chrome_jni_headers.
     "feed/core/java/src/org/chromium/chrome/browser/feed/v2/FeedImageFetchClient.java",
-    "feed/core/java/src/org/chromium/chrome/browser/feed/v2/FeedPersistentKeyValueCache.java",
     "feed/core/java/src/org/chromium/chrome/browser/feed/v2/FeedServiceBridge.java",
     "feed/core/java/src/org/chromium/chrome/browser/feed/v2/FeedStreamSurface.java",
     "java/src/org/chromium/chrome/browser/AfterStartupTaskUtils.java",
@@ -3268,7 +3263,6 @@
     "java/src/org/chromium/chrome/browser/background_sync/PeriodicBackgroundSyncChromeWakeUpTask.java",
     "java/src/org/chromium/chrome/browser/background_task_scheduler/ChromeBackgroundTaskFactory.java",
     "java/src/org/chromium/chrome/browser/background_task_scheduler/ProxyNativeTask.java",
-    "java/src/org/chromium/chrome/browser/banners/AppBannerManagerHelper.java",
     "java/src/org/chromium/chrome/browser/bookmarks/BookmarkBridge.java",
     "java/src/org/chromium/chrome/browser/browserservices/QualityEnforcer.java",
     "java/src/org/chromium/chrome/browser/browserservices/permissiondelegation/InstalledWebappBridge.java",
diff --git a/chrome/android/chrome_java_sources.gni b/chrome/android/chrome_java_sources.gni
index c9ba4690..c5fd07d 100644
--- a/chrome/android/chrome_java_sources.gni
+++ b/chrome/android/chrome_java_sources.gni
@@ -141,7 +141,6 @@
   "java/src/org/chromium/chrome/browser/background_task_scheduler/ChromeBackgroundTaskFactory.java",
   "java/src/org/chromium/chrome/browser/background_task_scheduler/ChromeNativeBackgroundTaskDelegate.java",
   "java/src/org/chromium/chrome/browser/background_task_scheduler/ProxyNativeTask.java",
-  "java/src/org/chromium/chrome/browser/banners/AppBannerManagerHelper.java",
   "java/src/org/chromium/chrome/browser/bookmarks/BookmarkActionBar.java",
   "java/src/org/chromium/chrome/browser/bookmarks/BookmarkActivity.java",
   "java/src/org/chromium/chrome/browser/bookmarks/BookmarkAddActivity.java",
@@ -1194,6 +1193,7 @@
   "java/src/org/chromium/chrome/browser/rappor/RapporServiceBridge.java",
   "java/src/org/chromium/chrome/browser/read_later/ReadLaterIPHController.java",
   "java/src/org/chromium/chrome/browser/read_later/ReadingListBridge.java",
+  "java/src/org/chromium/chrome/browser/read_later/ReadingListUtils.java",
   "java/src/org/chromium/chrome/browser/reengagement/ReengagementNotificationController.java",
   "java/src/org/chromium/chrome/browser/resources/ResourceMapper.java",
   "java/src/org/chromium/chrome/browser/rlz/RevenueStats.java",
diff --git a/chrome/android/chrome_junit_test_java_sources.gni b/chrome/android/chrome_junit_test_java_sources.gni
index 67c9128..d862b63a 100644
--- a/chrome/android/chrome_junit_test_java_sources.gni
+++ b/chrome/android/chrome_junit_test_java_sources.gni
@@ -198,6 +198,7 @@
   "junit/src/org/chromium/chrome/browser/payments/handler/toolbar/PaymentHandlerToolbarMediatorTest.java",
   "junit/src/org/chromium/chrome/browser/policy/EnterpriseInfoTest.java",
   "junit/src/org/chromium/chrome/browser/privacy/settings/PrivacyPreferencesManagerImplTest.java",
+  "junit/src/org/chromium/chrome/browser/read_later/ReadingListUtilsUnitTest.java",
   "junit/src/org/chromium/chrome/browser/reengagement/ReengagementNotificationControllerTest.java",
   "junit/src/org/chromium/chrome/browser/safe_browsing/SafeBrowsingReferringAppBridgeTest.java",
   "junit/src/org/chromium/chrome/browser/search_engines/SearchEngineChoiceMetricsTest.java",
diff --git a/chrome/android/chrome_public_apk_tmpl.gni b/chrome/android/chrome_public_apk_tmpl.gni
index 8c6cd1c..e95b16f 100644
--- a/chrome/android/chrome_public_apk_tmpl.gni
+++ b/chrome/android/chrome_public_apk_tmpl.gni
@@ -464,9 +464,6 @@
       _enable_chrome_module =
           _is_bundle_module && invoker.is_base_module && enable_chrome_module
       if (_enable_chrome_module) {
-        # TODO(crbug.com/1150459): Remove this once internal repo is updated.
-        not_needed(invoker, [ "module_descs" ])
-
         # If the manifest is being verified, add the chrome module's manifest.
         if (defined(invoker.expected_android_manifest)) {
           _bundle_target_gen_dir =
diff --git a/chrome/android/features/dev_ui/dev_ui_module.gni b/chrome/android/features/dev_ui/dev_ui_module.gni
index 69ddc274..8c098e7 100644
--- a/chrome/android/features/dev_ui/dev_ui_module.gni
+++ b/chrome/android/features/dev_ui/dev_ui_module.gni
@@ -19,5 +19,4 @@
   # DevUI DFM does not need to call Module.getImpl(), and manages loading on
   # install and on use. Therefore disable auto-load on Module.getImpl().
   load_native_on_get_impl = false
-  supports_isolated_split = true
 }
diff --git a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceLayoutPerfTest.java b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceLayoutPerfTest.java
index 83a3c141..6113db3 100644
--- a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceLayoutPerfTest.java
+++ b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceLayoutPerfTest.java
@@ -154,7 +154,10 @@
     @Test
     @EnormousTest
     @CommandLineFlags.Add({BASE_PARAMS + "/downsampling-scale/1"})
-    public void testTabToGridFromLiveTabWith10TabsNoDownsample() throws InterruptedException {
+    @DisableIf.Build(message = "Flaky on Android P, see https://crbug.com/1161731",
+            sdk_is_greater_than = VERSION_CODES.O_MR1, sdk_is_less_than = VERSION_CODES.Q)
+    public void
+    testTabToGridFromLiveTabWith10TabsNoDownsample() throws InterruptedException {
         prepareTabs(10, NTP_URL);
         reportTabToGridPerf(mUrl, "Tab-to-Grid from live tab with 10 tabs (no downsample)");
     }
@@ -162,7 +165,10 @@
     @Test
     @EnormousTest
     @CommandLineFlags.Add({BASE_PARAMS + "/max-duty-cycle/1"})
-    public void testTabToGridFromLiveTabWith10TabsNoRateLimit() throws InterruptedException {
+    @DisableIf.Build(message = "Flaky on Android P, see https://crbug.com/1161731",
+            sdk_is_greater_than = VERSION_CODES.O_MR1, sdk_is_less_than = VERSION_CODES.Q)
+    public void
+    testTabToGridFromLiveTabWith10TabsNoRateLimit() throws InterruptedException {
         prepareTabs(10, NTP_URL);
         reportTabToGridPerf(mUrl, "Tab-to-Grid from live tab with 10 tabs (no rate-limit)");
     }
diff --git a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/v2/FeedPersistentKeyValueCache.java b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/v2/FeedPersistentKeyValueCache.java
deleted file mode 100644
index c93ba50..0000000
--- a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/v2/FeedPersistentKeyValueCache.java
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.feed.v2;
-
-import androidx.annotation.Nullable;
-
-import org.chromium.base.Callback;
-import org.chromium.base.ThreadUtils;
-import org.chromium.base.annotations.JNINamespace;
-import org.chromium.base.annotations.NativeMethods;
-import org.chromium.chrome.browser.xsurface.PersistentKeyValueCache;
-
-/**
- * Implementation of xsurface's PersistentKeyValueCache.
- */
-@JNINamespace("feed")
-public class FeedPersistentKeyValueCache implements PersistentKeyValueCache {
-    @Override
-    public void lookup(byte[] key, ValueConsumer consumer) {
-        assert ThreadUtils.runningOnUiThread();
-        FeedPersistentKeyValueCacheJni.get().lookup(key, new Callback<byte[]>() {
-            @Override
-            public void onResult(byte[] result) {
-                consumer.run(result);
-            }
-        });
-    }
-
-    @Override
-    public void put(byte[] key, byte[] value, @Nullable Runnable onComplete) {
-        assert ThreadUtils.runningOnUiThread();
-        FeedPersistentKeyValueCacheJni.get().put(key, value, onComplete);
-    }
-
-    @Override
-    public void evict(byte[] key, @Nullable Runnable onComplete) {
-        assert ThreadUtils.runningOnUiThread();
-        FeedPersistentKeyValueCacheJni.get().evict(key, onComplete);
-    }
-
-    @NativeMethods
-    interface Natives {
-        void lookup(byte[] key, Object consumer);
-        void put(byte[] key, byte[] value, Runnable onComplete);
-        void evict(byte[] key, Runnable onComplete);
-    }
-}
diff --git a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/v2/FeedProcessScopeDependencyProvider.java b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/v2/FeedProcessScopeDependencyProvider.java
index 8def531..c767a3c2 100644
--- a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/v2/FeedProcessScopeDependencyProvider.java
+++ b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/v2/FeedProcessScopeDependencyProvider.java
@@ -21,7 +21,6 @@
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.signin.services.IdentityServicesProvider;
 import org.chromium.chrome.browser.xsurface.ImageFetchClient;
-import org.chromium.chrome.browser.xsurface.PersistentKeyValueCache;
 import org.chromium.chrome.browser.xsurface.ProcessScopeDependencyProvider;
 import org.chromium.components.signin.base.CoreAccountInfo;
 import org.chromium.components.signin.identitymanager.ConsentLevel;
@@ -35,7 +34,6 @@
 
     private Context mContext;
     private ImageFetchClient mImageFetchClient;
-    private FeedPersistentKeyValueCache mPersistentKeyValueCache;
     private LibraryResolver mLibraryResolver;
 
     @VisibleForTesting
@@ -44,7 +42,6 @@
     FeedProcessScopeDependencyProvider() {
         mContext = createFeedContext(ContextUtils.getApplicationContext());
         mImageFetchClient = new FeedImageFetchClient();
-        mPersistentKeyValueCache = new FeedPersistentKeyValueCache();
         if (BundleUtils.isIsolatedSplitInstalled(mContext, FEED_SPLIT_NAME)) {
             mLibraryResolver = (libName) -> {
                 return BundleUtils.getNativeLibraryPath(libName, FEED_SPLIT_NAME);
@@ -88,11 +85,6 @@
     }
 
     @Override
-    public PersistentKeyValueCache getPersistentKeyValueCache() {
-        return mPersistentKeyValueCache;
-    }
-
-    @Override
     public void logError(String tag, String format, Object... args) {
         Log.e(tag, format, args);
     }
diff --git a/chrome/android/feed/core/javatests/src/org/chromium/chrome/browser/feed/v2/FeedProcessScopeDependencyProviderTest.java b/chrome/android/feed/core/javatests/src/org/chromium/chrome/browser/feed/v2/FeedProcessScopeDependencyProviderTest.java
deleted file mode 100644
index e0ffb18..0000000
--- a/chrome/android/feed/core/javatests/src/org/chromium/chrome/browser/feed/v2/FeedProcessScopeDependencyProviderTest.java
+++ /dev/null
@@ -1,113 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.feed.v2;
-
-import androidx.test.filters.MediumTest;
-
-import org.hamcrest.Matchers;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import org.chromium.base.test.util.CommandLineFlags;
-import org.chromium.base.test.util.Criteria;
-import org.chromium.base.test.util.CriteriaHelper;
-import org.chromium.base.test.util.Feature;
-import org.chromium.chrome.browser.flags.ChromeFeatureList;
-import org.chromium.chrome.browser.flags.ChromeSwitches;
-import org.chromium.chrome.browser.xsurface.PersistentKeyValueCache;
-import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
-import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
-import org.chromium.chrome.test.util.browser.Features;
-import org.chromium.content_public.browser.test.util.TestThreadUtils;
-
-import java.util.ArrayList;
-
-/**
- * Tests for FeedProcessScopeDependencyProvider.
- */
-@RunWith(ChromeJUnit4ClassRunner.class)
-@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
-@Features.EnableFeatures(ChromeFeatureList.INTEREST_FEED_V2)
-public class FeedProcessScopeDependencyProviderTest {
-    static final byte[] VALUE_1 = "one".getBytes();
-    static final byte[] VALUE_2 = "two".getBytes();
-
-    String toString(byte[] array) {
-        if (array == null) return "null";
-        return new String(array);
-    }
-
-    @Rule
-    private final ChromeTabbedActivityTestRule mActivityTestRule =
-            new ChromeTabbedActivityTestRule();
-
-    @Before
-    public void setUp() throws Exception {
-        mActivityTestRule.startMainActivityWithURL("about:blank");
-    }
-
-    @Test
-    @MediumTest
-    @Feature({"Feed"})
-    public void testPersistentKeyValueCachePutAndLookup() {
-        FeedProcessScopeDependencyProvider dependencyProvider =
-                new FeedProcessScopeDependencyProvider();
-        ArrayList<String> calls = new ArrayList<String>();
-
-        TestThreadUtils.runOnUiThreadBlocking(() -> {
-            PersistentKeyValueCache cache = dependencyProvider.getPersistentKeyValueCache();
-            cache.put(VALUE_1, VALUE_2, () -> calls.add("put"));
-            cache.lookup(VALUE_1, (byte[] v) -> calls.add("lookup1 " + toString(v)));
-            cache.lookup(VALUE_2, (byte[] v) -> calls.add("lookup2 " + toString(v)));
-        });
-        CriteriaHelper.pollUiThread(() -> {
-            Criteria.checkThat(
-                    "Calls match", calls, Matchers.contains("put", "lookup1 two", "lookup2 null"));
-        });
-    }
-
-    @Test
-    @MediumTest
-    @Feature({"Feed"})
-    public void testPersistentKeyValueCacheEvict() {
-        FeedProcessScopeDependencyProvider dependencyProvider =
-                new FeedProcessScopeDependencyProvider();
-        ArrayList<String> calls = new ArrayList<String>();
-
-        TestThreadUtils.runOnUiThreadBlocking(() -> {
-            PersistentKeyValueCache cache = dependencyProvider.getPersistentKeyValueCache();
-            cache.put(VALUE_1, VALUE_2, () -> calls.add("put"));
-            cache.evict(VALUE_1, () -> calls.add("evict"));
-            cache.lookup(VALUE_1, (byte[] v) -> calls.add("lookup " + toString(v)));
-        });
-
-        CriteriaHelper.pollUiThread(() -> {
-            Criteria.checkThat(
-                    "Calls match", calls, Matchers.contains("put", "evict", "lookup null"));
-        });
-    }
-
-    @Test
-    @MediumTest
-    @Feature({"Feed"})
-    public void testPersistentKeyValueCacheNullRunnables() {
-        // Verify put() and evict() accept null runnables.
-        FeedProcessScopeDependencyProvider dependencyProvider =
-                new FeedProcessScopeDependencyProvider();
-        ArrayList<String> calls = new ArrayList<String>();
-
-        TestThreadUtils.runOnUiThreadBlocking(() -> {
-            PersistentKeyValueCache cache = dependencyProvider.getPersistentKeyValueCache();
-            cache.put(VALUE_1, VALUE_2, null);
-            cache.evict(VALUE_1, null);
-            cache.put(VALUE_1, VALUE_2, () -> calls.add("put"));
-        });
-
-        CriteriaHelper.pollUiThread(
-                () -> { Criteria.checkThat("Calls match", calls, Matchers.contains("put")); });
-    }
-}
diff --git a/chrome/android/feed/feed_java_sources.gni b/chrome/android/feed/feed_java_sources.gni
index b38f40e..fd330a0 100644
--- a/chrome/android/feed/feed_java_sources.gni
+++ b/chrome/android/feed/feed_java_sources.gni
@@ -47,7 +47,6 @@
   "//chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/v2/CardMenuBottomSheetContent.java",
   "//chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/v2/FeedImageFetchClient.java",
   "//chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/v2/FeedListContentManager.java",
-  "//chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/v2/FeedPersistentKeyValueCache.java",
   "//chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/v2/FeedProcessScopeDependencyProvider.java",
   "//chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/v2/FeedServiceBridge.java",
   "//chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/v2/FeedSliceViewTracker.java",
@@ -735,7 +734,6 @@
 
 if (enable_feed_v2) {
   feed_test_java_sources += [
-    "//chrome/android/feed/core/javatests/src/org/chromium/chrome/browser/feed/v2/FeedProcessScopeDependencyProviderTest.java",
     "//chrome/android/feed/core/javatests/src/org/chromium/chrome/browser/feed/v2/FeedV2NewTabPageTest.java",
     "//chrome/android/feed/core/javatests/src/org/chromium/chrome/browser/feed/v2/FeedV2TestHelper.java",
     "//chrome/android/feed/core/javatests/src/org/chromium/chrome/browser/feed/v2/TestFeedServer.java",
@@ -745,10 +743,9 @@
 feed_test_deps = []
 if (enable_feed_v1 || enable_feed_v2) {
   feed_test_deps += feed_deps + [
-                      "//chrome/browser/privacy:java",
                       "//chrome/browser/user_education:java",
-                      "//chrome/browser/xsurface:java",
-                      "//third_party/android_deps:guava_android_java",
                       "//third_party/google-truth:google_truth_java",
+                      "//third_party/android_deps:guava_android_java",
+                      "//chrome/browser/privacy:java",
                     ]
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/appmenu/AppMenuPropertiesDelegateImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/app/appmenu/AppMenuPropertiesDelegateImpl.java
index c489e46..df62afdc 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/app/appmenu/AppMenuPropertiesDelegateImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/app/appmenu/AppMenuPropertiesDelegateImpl.java
@@ -49,6 +49,7 @@
 import org.chromium.chrome.browser.multiwindow.MultiWindowModeStateDispatcher;
 import org.chromium.chrome.browser.omaha.UpdateMenuItemHelper;
 import org.chromium.chrome.browser.profiles.Profile;
+import org.chromium.chrome.browser.read_later.ReadingListUtils;
 import org.chromium.chrome.browser.share.ShareHelper;
 import org.chromium.chrome.browser.share.ShareUtils;
 import org.chromium.chrome.browser.tab.Tab;
@@ -448,6 +449,7 @@
                         addToMenuItem.getSubMenu().findItem(R.id.add_to_reading_list_menu_id);
                 addToReadingListMenuItem.setVisible(
                         CachedFeatureFlags.isEnabled(ChromeFeatureList.READ_LATER));
+                addToReadingListMenuItem.setEnabled(ReadingListUtils.isReadingListSupported(url));
 
                 MenuItem addToDownloadsMenuItem =
                         addToMenuItem.getSubMenu().findItem(R.id.add_to_downloads_menu_id);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/banners/AppBannerManagerHelper.java b/chrome/android/java/src/org/chromium/chrome/browser/banners/AppBannerManagerHelper.java
deleted file mode 100644
index 15d027b..0000000
--- a/chrome/android/java/src/org/chromium/chrome/browser/banners/AppBannerManagerHelper.java
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.banners;
-
-import androidx.annotation.VisibleForTesting;
-
-import org.chromium.base.annotations.CalledByNative;
-import org.chromium.base.annotations.JNINamespace;
-import org.chromium.components.webapps.WebappsUtils;
-
-/**
- * This class is a helper class for the C++ layer. It responds whether the
- * feature is supported (and facilitates testing). It was split out of
- * AppBannerManager during a modularization effort because ShortcutHelper hadn't
- * been modularized. TODO(estade): That's now done, so this functionality can move back into
- * AppBannerManager.
- */
-@JNINamespace("webapps")
-public class AppBannerManagerHelper {
-    /** Whether add to home screen is permitted by the system. */
-    private static Boolean sIsSupported;
-
-    /**
-     * Checks if the add to home screen intent is supported.
-     * @return true if add to home screen is supported, false otherwise.
-     */
-    public static boolean isSupported() {
-        if (sIsSupported == null) {
-            sIsSupported = WebappsUtils.isAddToHomeIntentSupported();
-        }
-        return sIsSupported;
-    }
-
-    /**
-     * Checks if app banners are enabled for the tab which this manager is attached to.
-     * @return true if app banners can be shown for this tab, false otherwise.
-     */
-    @CalledByNative
-    private static boolean isEnabledForTab() {
-        return isSupported();
-    }
-
-    /** Overrides whether the system supports add to home screen. Used in testing. */
-    @VisibleForTesting
-    public static void setIsSupported(boolean state) {
-        sIsSupported = state;
-    }
-}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkBridge.java b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkBridge.java
index 04c22be..c1fd6ad 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkBridge.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkBridge.java
@@ -840,10 +840,9 @@
      * bookmark ID will be returned.
      * @param title The title to be used for the reading list item.
      * @param url The URL of the reading list item.
-     * @return The bookmark ID created after saving the article to the reading list, or null on
-     *         error.
+     * @return The bookmark ID created after saving the article to the reading list.
      */
-    public @Nullable BookmarkId addToReadingList(String title, String url) {
+    public BookmarkId addToReadingList(String title, String url) {
         ThreadUtils.assertOnUiThread();
         assert title != null;
         assert url != null;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/bottomsheet/BookmarkBottomSheetCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/bottomsheet/BookmarkBottomSheetCoordinator.java
index bbe0181..6be8b9a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/bottomsheet/BookmarkBottomSheetCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/bottomsheet/BookmarkBottomSheetCoordinator.java
@@ -5,6 +5,10 @@
 package org.chromium.chrome.browser.bookmarks.bottomsheet;
 
 import android.content.Context;
+import android.text.SpannableString;
+import android.text.style.ForegroundColorSpan;
+import android.text.style.RelativeSizeSpan;
+import android.text.style.SuperscriptSpan;
 import android.view.LayoutInflater;
 import android.view.View;
 
@@ -15,22 +19,29 @@
 import androidx.recyclerview.widget.LinearLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
 
+import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.base.Callback;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.bookmarks.BookmarkBridge.BookmarkItem;
 import org.chromium.chrome.browser.bookmarks.BookmarkModel;
 import org.chromium.chrome.browser.bookmarks.BookmarkUtils;
 import org.chromium.chrome.browser.bookmarks.bottomsheet.BookmarkBottomSheetItemProperties.ItemType;
+import org.chromium.chrome.browser.feature_engagement.TrackerFactory;
+import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.components.bookmarks.BookmarkId;
 import org.chromium.components.bookmarks.BookmarkType;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetController.StateChangeReason;
 import org.chromium.components.browser_ui.bottomsheet.EmptyBottomSheetObserver;
+import org.chromium.components.feature_engagement.FeatureConstants;
+import org.chromium.components.feature_engagement.Tracker;
 import org.chromium.ui.modelutil.LayoutViewBuilder;
 import org.chromium.ui.modelutil.MVCListAdapter.ListItem;
 import org.chromium.ui.modelutil.MVCListAdapter.ModelList;
 import org.chromium.ui.modelutil.PropertyModel;
 import org.chromium.ui.modelutil.SimpleRecyclerViewAdapter;
+import org.chromium.ui.text.SpanApplier;
+import org.chromium.ui.text.SpanApplier.SpanInfo;
 
 import java.util.List;
 
@@ -110,7 +121,7 @@
         int type = bookmarkItem.getId().getType();
         PropertyModel model =
                 new PropertyModel.Builder(BookmarkBottomSheetItemProperties.ALL_KEYS)
-                        .with(BookmarkBottomSheetItemProperties.TITLE, bookmarkItem.getTitle())
+                        .with(BookmarkBottomSheetItemProperties.TITLE, getTitle(bookmarkItem))
                         .with(BookmarkBottomSheetItemProperties.SUBTITLE, getSubtitle(bookmarkItem))
                         .with(BookmarkBottomSheetItemProperties.ICON_DRAWABLE_AND_COLOR,
                                 new Pair<>(BookmarkUtils.getFolderIcon(mContext, type),
@@ -121,7 +132,36 @@
         return model;
     }
 
-    private @Nullable String getSubtitle(@NonNull final BookmarkItem bookmarkItem) {
+    private CharSequence getTitle(@NonNull final BookmarkItem bookmarkItem) {
+        if (bookmarkItem.getId().getType() == BookmarkType.READING_LIST) {
+            Tracker tracker =
+                    TrackerFactory.getTrackerForProfile(Profile.getLastUsedRegularProfile());
+            boolean showNew = tracker.isInitialized()
+                    && tracker.shouldTriggerHelpUI(
+                            FeatureConstants.READ_LATER_BOTTOM_SHEET_FEATURE);
+            SpannableString spannableString = new SpannableString(
+                    mContext.getResources().getString(R.string.reading_list_title_new));
+
+            // Maybe show a "New" text after the reading list title.
+            if (showNew) {
+                tracker.dismissed(FeatureConstants.READ_LATER_BOTTOM_SHEET_FEATURE);
+                spannableString = SpanApplier.applySpans(spannableString.toString(),
+                        new SpanInfo("<new>", "</new>", new RelativeSizeSpan(0.75f),
+                                new SuperscriptSpan(),
+                                new ForegroundColorSpan(
+                                        ApiCompatibilityUtils.getColor(mContext.getResources(),
+                                                R.color.default_text_color_blue))));
+            } else {
+                spannableString = new SpannableString(SpanApplier.removeSpanText(
+                        spannableString.toString(), new SpanInfo("<new>", "</new>")));
+            }
+            return spannableString;
+        }
+
+        return bookmarkItem.getTitle();
+    }
+
+    private @Nullable CharSequence getSubtitle(@NonNull final BookmarkItem bookmarkItem) {
         switch (bookmarkItem.getId().getType()) {
             case BookmarkType.NORMAL:
                 int totalCount = mBookmarkModel.getTotalBookmarkCount(bookmarkItem.getId());
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/bottomsheet/BookmarkBottomSheetFolderRow.java b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/bottomsheet/BookmarkBottomSheetFolderRow.java
index 68b6757..377d148 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/bottomsheet/BookmarkBottomSheetFolderRow.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/bottomsheet/BookmarkBottomSheetFolderRow.java
@@ -33,11 +33,11 @@
         super(context, attrs);
     }
 
-    void setTitle(@NonNull String title) {
+    void setTitle(@NonNull CharSequence title) {
         mTitleView.setText(title);
     }
 
-    void setSubtitle(@Nullable String subtitle) {
+    void setSubtitle(@Nullable CharSequence subtitle) {
         mDescriptionView.setText(subtitle == null ? "" : subtitle);
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/bottomsheet/BookmarkBottomSheetItemProperties.java b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/bottomsheet/BookmarkBottomSheetItemProperties.java
index f8a5455..3591d9c7 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/bottomsheet/BookmarkBottomSheetItemProperties.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/bottomsheet/BookmarkBottomSheetItemProperties.java
@@ -32,13 +32,13 @@
     /**
      * The title of the bottom sheet item. e.g. Mobile bookmarks, reading list.
      */
-    static final PropertyModel.ReadableObjectPropertyKey<String> TITLE =
+    static final PropertyModel.ReadableObjectPropertyKey<CharSequence> TITLE =
             new PropertyModel.ReadableObjectPropertyKey<>();
 
     /**
      * The subtitle of the bottom sheet item. e.g. 4 bookmarks, 8 unread pages.
      */
-    static final PropertyModel.ReadableObjectPropertyKey<String> SUBTITLE =
+    static final PropertyModel.ReadableObjectPropertyKey<CharSequence> SUBTITLE =
             new PropertyModel.ReadableObjectPropertyKey<>();
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/firstrun/FreIntentCreator.java b/chrome/android/java/src/org/chromium/chrome/browser/firstrun/FreIntentCreator.java
index 305b266..1a96be98 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/firstrun/FreIntentCreator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/firstrun/FreIntentCreator.java
@@ -9,6 +9,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.TypedArray;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.SystemClock;
 import android.text.TextUtils;
@@ -142,6 +143,9 @@
             Intent intentToLaunchAfterFreComplete, boolean requiresBroadcast) {
         final PendingIntent pendingIntent;
         int pendingIntentFlags = PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_ONE_SHOT;
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            pendingIntentFlags |= PendingIntent.FLAG_IMMUTABLE;
+        }
         if (requiresBroadcast) {
             pendingIntent = PendingIntent.getBroadcast(
                     context, 0, intentToLaunchAfterFreComplete, pendingIntentFlags);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/read_later/ReadingListUtils.java b/chrome/android/java/src/org/chromium/chrome/browser/read_later/ReadingListUtils.java
new file mode 100644
index 0000000..a86177a
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/read_later/ReadingListUtils.java
@@ -0,0 +1,26 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.read_later;
+
+import android.text.TextUtils;
+
+import org.chromium.components.embedder_support.util.UrlConstants;
+
+/**
+ * Utility functions for reading list feature.
+ */
+public final class ReadingListUtils {
+    /**
+     * @return Whether the URL can be added as reading list article.
+     */
+    public static boolean isReadingListSupported(String url) {
+        if (TextUtils.isEmpty(url)) return false;
+
+        // This should match ReadingListModel::IsUrlSupported(), having a separate function since
+        // the UI may not load native library.
+        return url.startsWith(UrlConstants.HTTP_URL_PREFIX)
+                || url.startsWith(UrlConstants.HTTPS_URL_PREFIX);
+    }
+}
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkBridgeTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkBridgeTest.java
index 742dbf0e..9442632 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkBridgeTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkBridgeTest.java
@@ -18,13 +18,10 @@
 import org.chromium.base.test.util.Batch;
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.browser.bookmarks.BookmarkBridge.BookmarkItem;
-import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.test.ChromeBrowserTestRule;
 import org.chromium.chrome.test.util.BookmarkTestUtil;
-import org.chromium.chrome.test.util.browser.Features;
 import org.chromium.components.bookmarks.BookmarkId;
-import org.chromium.components.bookmarks.BookmarkType;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
 
 import java.util.ArrayList;
@@ -317,21 +314,4 @@
                         + "over partner bookmarks",
                 expectedSearchResults, searchResults);
     }
-
-    @Test
-    @SmallTest
-    @UiThreadTest
-    @Features.EnableFeatures({ChromeFeatureList.READ_LATER})
-    public void testAddToReadingList() {
-        Assert.assertNull("Should return null for non http/https URLs.",
-                mBookmarkBridge.addToReadingList("a", "chrome://flags"));
-        BookmarkId readingListId = mBookmarkBridge.addToReadingList("a", "https://www.google.com/");
-        Assert.assertNotNull(readingListId);
-        Assert.assertEquals(BookmarkType.READING_LIST, readingListId.getType());
-        BookmarkItem readingListItem =
-                mBookmarkBridge.getReadingListItem("https://www.google.com/");
-        Assert.assertNotNull(readingListItem);
-        Assert.assertEquals("https://www.google.com/", readingListItem.getUrl());
-        Assert.assertEquals("a", readingListItem.getTitle());
-    }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkTest.java
index b30f151..f9f04fe0 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkTest.java
@@ -1706,7 +1706,7 @@
                 mActivityTestRule.getActivity(), R.id.bookmark_this_page_id);
 
         // Click the reading list folder in the bottom sheet, and wait for reading list item added.
-        onView(withText("Reading list")).check(matches(isDisplayed())).perform(click());
+        onView(withText("Reading list ")).check(matches(isDisplayed())).perform(click());
         CriteriaHelper.pollUiThread(() -> mBookmarkModel.getReadingListItem(mTestPage) != null);
 
         TestThreadUtils.runOnUiThreadBlocking(() -> {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/bottomsheet/BookmarkBottomSheetTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/bottomsheet/BookmarkBottomSheetTest.java
index 1aa488f..7fc769fa 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/bottomsheet/BookmarkBottomSheetTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/bottomsheet/BookmarkBottomSheetTest.java
@@ -11,6 +11,10 @@
 import static androidx.test.espresso.matcher.ViewMatchers.withId;
 import static androidx.test.espresso.matcher.ViewMatchers.withText;
 
+import static org.mockito.Mockito.when;
+
+import static org.chromium.components.feature_engagement.FeatureConstants.READ_LATER_BOTTOM_SHEET_FEATURE;
+
 import android.view.View;
 
 import androidx.recyclerview.widget.RecyclerView;
@@ -22,6 +26,7 @@
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 
@@ -30,6 +35,7 @@
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.bookmarks.BookmarkBridge.BookmarkItem;
 import org.chromium.chrome.browser.bookmarks.BookmarkModel;
+import org.chromium.chrome.browser.feature_engagement.TrackerFactory;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.browser.profiles.Profile;
@@ -41,6 +47,7 @@
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetContent;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetController.SheetState;
+import org.chromium.components.feature_engagement.Tracker;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
 
 import java.util.ArrayList;
@@ -60,19 +67,27 @@
     private static final String TITLE = "bookmark title";
     private static final String TEST_URL_A = "http://a.com";
     private static final String TEST_URL_B = "http://b.com";
+    private static final String READING_LIST_TITLE = "Reading list ";
+    private static final String READING_LIST_TITLE_NEW = "Reading list New";
 
     private BookmarkBottomSheetCoordinator mBottomSheetCoordinator;
     private BottomSheetController mBottomSheetController;
     private BookmarkModel mBookmarkModel;
     private BookmarkItem mItemClicked;
     private boolean mCallbackInvoked;
+    @Mock
+    private Tracker mTracker;
 
     @Before
     public void setUp() {
+        when(mTracker.isInitialized()).thenReturn(true);
+        when(mTracker.shouldTriggerHelpUI(READ_LATER_BOTTOM_SHEET_FEATURE)).thenReturn(false);
+
         mActivityTestRule.startMainActivityOnBlankPage();
         TestThreadUtils.runOnUiThreadBlocking(() -> {
             mBookmarkModel = new BookmarkModel(Profile.fromWebContents(
                     mActivityTestRule.getActivity().getActivityTab().getWebContents()));
+            TrackerFactory.setTrackerForTests(mTracker);
         });
 
         mBottomSheetController = mActivityTestRule.getActivity()
@@ -88,6 +103,11 @@
         mItemClicked = null;
     }
 
+    private void setShouldShowNew(boolean shouldShowNew) {
+        when(mTracker.shouldTriggerHelpUI(READ_LATER_BOTTOM_SHEET_FEATURE))
+                .thenReturn(shouldShowNew);
+    }
+
     private void bottomSheetCallback(BookmarkItem item) {
         mItemClicked = item;
         mCallbackInvoked = true;
@@ -144,7 +164,7 @@
     public void testBottomSheetShowWithoutBookmarks() throws InterruptedException {
         showBottomSheet();
         onView(withId(R.id.sheet_title)).check(matches(isDisplayed()));
-        onView(withText("Reading list")).check(matches(isDisplayed()));
+        onView(withText(READING_LIST_TITLE)).check(matches(isDisplayed()));
         assertViewHolderHasString(0, "Save this page for later and get a reminder");
         assertNoOverflowMenu(0, "No overflow menu for reading list folder.");
 
@@ -155,6 +175,14 @@
 
     @Test
     @MediumTest
+    public void testBottomSheetNewIPH() {
+        setShouldShowNew(true);
+        showBottomSheet();
+        onView(withText(READING_LIST_TITLE_NEW)).check(matches(isDisplayed()));
+    }
+
+    @Test
+    @MediumTest
     public void testBottomSheetShowWithOneBookmark() {
         // Add 1 bookmark and 1 unread page.
         TestThreadUtils.runOnUiThreadBlocking(() -> {
@@ -163,7 +191,7 @@
         });
 
         showBottomSheet();
-        onView(withText("Reading list")).check(matches(isDisplayed()));
+        onView(withText(READING_LIST_TITLE)).check(matches(isDisplayed()));
         assertViewHolderHasString(0, "1 unread page");
 
         onView(withText("Mobile bookmarks")).check(matches(isDisplayed()));
@@ -182,7 +210,7 @@
         });
 
         showBottomSheet();
-        onView(withText("Reading list")).check(matches(isDisplayed()));
+        onView(withText(READING_LIST_TITLE)).check(matches(isDisplayed()));
         assertViewHolderHasString(0, "2 unread pages");
 
         onView(withText("Mobile bookmarks")).check(matches(isDisplayed()));
@@ -194,7 +222,7 @@
     public void testBottomSheetClickThrough() {
         showBottomSheet();
         onView(withText("Mobile bookmarks")).check(matches(isDisplayed()));
-        onView(withText("Reading list")).check(matches(isDisplayed())).perform(click());
+        onView(withText(READING_LIST_TITLE)).check(matches(isDisplayed())).perform(click());
 
         waitForBookmarkClicked();
         Assert.assertEquals(BookmarkType.READING_LIST, mItemClicked.getId().getType());
@@ -205,7 +233,7 @@
     @MediumTest
     public void testBottomSheetCloseInvokeCallback() {
         showBottomSheet();
-        onView(withText("Reading list")).check(matches(isDisplayed()));
+        onView(withText(READING_LIST_TITLE)).check(matches(isDisplayed()));
         hideBottomSheet();
         Assert.assertNull(mItemClicked);
         Assert.assertTrue(mCallbackInvoked);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/contextmenu/RevampedContextMenuTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/contextmenu/RevampedContextMenuTest.java
index ba40386..51d2cc5 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/contextmenu/RevampedContextMenuTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/contextmenu/RevampedContextMenuTest.java
@@ -411,6 +411,26 @@
     @MediumTest
     @Feature({"Browser"})
     @Features.EnableFeatures({ChromeFeatureList.CONTEXT_MENU_GOOGLE_LENS_CHIP})
+    public void testLensChipNotShowingIfNotEnabled() throws Throwable {
+        // Required to avoid runtime error.
+        Looper.prepare();
+
+        Tab tab = mDownloadTestRule.getActivity().getActivityTab();
+        hardcodeTestImageForSharing(TEST_JPG_IMAGE_FILE_EXTENSION);
+
+        RevampedContextMenuCoordinator menuCoordinator =
+                RevampedContextMenuUtils.openContextMenu(tab, "testImage");
+        // Needs to run on UI thread so creation happens on same thread as dismissal.
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            Assert.assertNull("Chip popoup was initialized.",
+                    menuCoordinator.getCurrentPopupWindowForTesting());
+        });
+    }
+
+    @Test
+    @MediumTest
+    @Feature({"Browser"})
+    @Features.EnableFeatures({ChromeFeatureList.CONTEXT_MENU_GOOGLE_LENS_CHIP})
     public void testSelectLensChip() throws Throwable {
         // Required to avoid runtime error.
         Looper.prepare();
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/app/appmenu/AppMenuPropertiesDelegateUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/app/appmenu/AppMenuPropertiesDelegateUnitTest.java
index be40f40..c1b6828 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/app/appmenu/AppMenuPropertiesDelegateUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/app/appmenu/AppMenuPropertiesDelegateUnitTest.java
@@ -11,6 +11,7 @@
 
 import android.content.Context;
 import android.view.Menu;
+import android.view.MenuItem;
 import android.view.SubMenu;
 import android.view.View;
 import android.widget.PopupMenu;
@@ -70,7 +71,10 @@
 import org.chromium.ui.modaldialog.ModalDialogManager;
 
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 /**
  * Unit tests for {@link AppMenuPropertiesDelegateImpl}.
@@ -575,6 +579,8 @@
         assertMenuItemsAreEqual(menu, expectedItems);
         assertActionBarItemsAreEqual(menu, expectedActionBarItems);
         assertAddToItemsAreEqual(menu, expectedAddToItems);
+        assertAddToItemsEnableState(
+                menu, new HashSet<>(Arrays.asList(R.id.add_to_reading_list_menu_id)), null);
     }
 
     @Test
@@ -915,6 +921,20 @@
                 Matchers.containsInAnyOrder(expectedItems));
     }
 
+    private void assertAddToItemsEnableState(
+            Menu menu, Set<Integer> enabledItems, Set<Integer> disabledItems) {
+        SubMenu addToSubMenu = menu.findItem(R.id.add_to_menu_id).getSubMenu();
+        for (int i = 0; i < addToSubMenu.size(); i++) {
+            MenuItem item = addToSubMenu.getItem(i);
+            if (enabledItems != null && enabledItems.contains(item.getItemId())) {
+                Assert.assertTrue(item.isEnabled());
+            }
+            if (disabledItems != null && disabledItems.contains(item.getItemId())) {
+                Assert.assertFalse(item.isEnabled());
+            }
+        }
+    }
+
     private String getMenuTitles(Menu menu) {
         StringBuilder items = new StringBuilder();
         for (int i = 0; i < menu.size(); i++) {
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/read_later/ReadingListUtilsUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/read_later/ReadingListUtilsUnitTest.java
new file mode 100644
index 0000000..b380f57a
--- /dev/null
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/read_later/ReadingListUtilsUnitTest.java
@@ -0,0 +1,31 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.read_later;
+
+import android.support.test.filters.SmallTest;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+import org.chromium.base.test.BaseRobolectricTestRunner;
+
+@RunWith(BaseRobolectricTestRunner.class)
+@Config(manifest = Config.NONE)
+/**
+ * Unit test for {@link ReadingListUtils}.
+ */
+public class ReadingListUtilsUnitTest {
+    @Test
+    @SmallTest
+    public void testIsReadingListSupport() {
+        Assert.assertFalse(ReadingListUtils.isReadingListSupported(null));
+        Assert.assertFalse(ReadingListUtils.isReadingListSupported(""));
+        Assert.assertFalse(ReadingListUtils.isReadingListSupported("chrome://flags"));
+        Assert.assertTrue(ReadingListUtils.isReadingListSupported("http://www.example.com"));
+        Assert.assertTrue(ReadingListUtils.isReadingListSupported("https://www.example.com"));
+    }
+}
diff --git a/chrome/android/modules/chrome_feature_modules.gni b/chrome/android/modules/chrome_feature_modules.gni
index e9fa100..cae0d233 100644
--- a/chrome/android/modules/chrome_feature_modules.gni
+++ b/chrome/android/modules/chrome_feature_modules.gni
@@ -40,8 +40,6 @@
 #     library files going into module if the module is executed in 64 bit.
 #   pak_deps: (Optional) Grit or repack targets of PAKs going into module.
 #   paks: (Optional) PAKs going into module.
-#   supports_isolated_split: (Optional) Whether this module can be in its own
-#     isolated split.
 # Each new module needs to add a desc to one of the lists below.
 
 # Modules shipped in Chrome Modern (Android L+).
diff --git a/chrome/android/modules/extra_icu/extra_icu_module.gni b/chrome/android/modules/extra_icu/extra_icu_module.gni
index f6bf8ddbb..65a0c94b 100644
--- a/chrome/android/modules/extra_icu/extra_icu_module.gni
+++ b/chrome/android/modules/extra_icu/extra_icu_module.gni
@@ -10,5 +10,4 @@
     "//chrome/android/modules/extra_icu/internal:java",
     "//third_party/icu:icu_extra_assets",
   ]
-  supports_isolated_split = true
 }
diff --git a/chrome/android/modules/stack_unwinder/stack_unwinder_module.gni b/chrome/android/modules/stack_unwinder/stack_unwinder_module.gni
index 3eed55d..ea292d5 100644
--- a/chrome/android/modules/stack_unwinder/stack_unwinder_module.gni
+++ b/chrome/android/modules/stack_unwinder/stack_unwinder_module.gni
@@ -10,5 +10,4 @@
   java_deps = [ "//chrome/android/modules/stack_unwinder/internal:java" ]
   native_deps = [ "//chrome/android/modules/stack_unwinder/internal:native" ]
   load_native_on_get_impl = false
-  supports_isolated_split = true
 }
diff --git a/chrome/android/modules/test_dummy/test_dummy_module.gni b/chrome/android/modules/test_dummy/test_dummy_module.gni
index 3ca44b39..c28e917 100644
--- a/chrome/android/modules/test_dummy/test_dummy_module.gni
+++ b/chrome/android/modules/test_dummy/test_dummy_module.gni
@@ -19,5 +19,4 @@
   paks = [ "$root_gen_dir/chrome/test_dummy_resources.pak" ]
   pak_deps = [ "//chrome/browser/test_dummy/internal:resources_native" ]
   load_native_on_get_impl = true
-  supports_isolated_split = true
 }
diff --git a/chrome/app/chrome_command_ids.h b/chrome/app/chrome_command_ids.h
index 54f336b..0a636d2 100644
--- a/chrome/app/chrome_command_ids.h
+++ b/chrome/app/chrome_command_ids.h
@@ -92,15 +92,16 @@
 #define IDC_VISIT_DESKTOP_OF_LRU_USER_LAST IDC_VISIT_DESKTOP_OF_LRU_USER_5
 
 // Assign to desk commands
-#define IDC_ASSIGN_TO_DESKS_MENU 34084
-#define IDC_SEND_TO_DESK_1 34085
-#define IDC_SEND_TO_DESK_2 34086
-#define IDC_SEND_TO_DESK_3 34087
-#define IDC_SEND_TO_DESK_4 34088
-#define IDC_SEND_TO_DESK_5 34089
-#define IDC_SEND_TO_DESK_6 34090
-#define IDC_SEND_TO_DESK_7 34091
-#define IDC_SEND_TO_DESK_8 34092
+#define IDC_ASSIGN_TO_DESKS_MENU 34090
+#define IDC_SEND_TO_DESK_1 34091
+#define IDC_SEND_TO_DESK_2 34092
+#define IDC_SEND_TO_DESK_3 34093
+#define IDC_SEND_TO_DESK_4 34094
+#define IDC_SEND_TO_DESK_5 34095
+#define IDC_SEND_TO_DESK_6 34096
+#define IDC_SEND_TO_DESK_7 34097
+#define IDC_SEND_TO_DESK_8 34098
+#define IDC_TOGGLE_ASSIGN_TO_ALL_DESKS 34099
 #endif
 
 // Page-related commands
diff --git a/chrome/app/chrome_main.cc b/chrome/app/chrome_main.cc
index d353778d..8b473c6 100644
--- a/chrome/app/chrome_main.cc
+++ b/chrome/app/chrome_main.cc
@@ -68,7 +68,7 @@
 #endif
 
 #if defined(OS_WIN)
-#if BUILDFLAG(USE_ALLOCATOR_SHIM)
+#if BUILDFLAG(USE_ALLOCATOR_SHIM) && BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
   // Call this early on in order to configure heap workarounds. This must be
   // called from chrome.dll. This may be a NOP on some platforms.
   base::allocator::ConfigurePartitionAlloc();
diff --git a/chrome/app/chromeos_strings.grdp b/chrome/app/chromeos_strings.grdp
index 476e5ca7e..7b663f10 100644
--- a/chrome/app/chromeos_strings.grdp
+++ b/chrome/app/chromeos_strings.grdp
@@ -351,6 +351,15 @@
   <message name="IDS_CELLULAR_SETUP_ESTABLISH_NETWORK_CONNECTION" desc="Message, informing user that a network connection is being established during cellular setup">
     Establishing network connection ...
   </message>
+  <message name="IDS_CELLULAR_SETUP_EID_POPUP_TITLE" desc="Label shown when vewing EID and QR code popup describing device EID">
+    Your device EID
+  </message>
+  <message name="IDS_CELLULAR_SETUP_EID_POPUP_DESCRIPTION" desc="Label shown when veiwing EID and QR code popup describing what an EID number is used for">
+    A customer service rep can use the EID number to help you activate service.
+  </message>
+  <message name="IDS_CELLULAR_SETUP_CLOSE_EID_POPUP_BUTTON_LABEL" desc="A11y and tooltip label for EID and QR code popup close button when viewing EID and QR code popup">
+    Close EID and QR code popup
+  </message>
 
   <!-- Upgrade notifications -->
   <message name="IDS_RELAUNCH_REQUIRED_TITLE_DAYS" desc="The title of a dialog that tells users the device must be restarted within two or more days.">
@@ -1632,9 +1641,18 @@
   <message name="IDS_NETWORK_LIST_ITEM_LABEL_ESIM_PENDING_PROFILE_WITH_PROVIDER_NAME" desc="A11y label for an eSIM profile that's ready to be installed, shown in a list in settings and oobe. Includes the provider name.  Clicking the profile installs the eSIM profile so that the cellular network is available for connecting.">
     Download mobile profile, Network <ph name="NETWORK_INDEX">$1<ex>1</ex></ph> of <ph name="NETWORK_COUNT">$2<ex>10</ex></ph>, <ph name="NETWORK_NAME">$3<ex>Verizon Wireless</ex></ph>, <ph name="NETWORK_PROVIDER_NAME">$4<ex>Verizon LTE</ex></ph>
   </message>
+  <message name="IDS_NETWORK_LIST_ITEM_LABEL_ESIM_PENDING_PROFILE_INSTALLING" desc="A11y label for an eSIM profile that is currently installing, shown in a list in settings and oobe.">
+    Installing mobile profile, Network <ph name="NETWORK_INDEX">$1<ex>1</ex></ph> of <ph name="NETWORK_COUNT">$2<ex>10</ex></ph>, <ph name="NETWORK_NAME">$3<ex>Verizon Wireless</ex></ph>
+  </message>
+  <message name="IDS_NETWORK_LIST_ITEM_LABEL_ESIM_PENDING_PROFILE_WITH_PROVIDER_NAME_INSTALLING" desc="A11y label for an eSIM profile that is currently installing, shown in a list in settings and oobe. Includes the provider name.">
+    Installing mobile profile, Network <ph name="NETWORK_INDEX">$1<ex>1</ex></ph> of <ph name="NETWORK_COUNT">$2<ex>10</ex></ph>, <ph name="NETWORK_NAME">$3<ex>Verizon Wireless</ex></ph>, <ph name="NETWORK_PROVIDER_NAME">$4<ex>Verizon LTE</ex></ph>
+  </message>
   <message name="IDS_NETWORK_LIST_ITEM_DOWNLOAD" desc="Text for the button displayed for pending eSIM profiles that installs the profile when pressed.">
     Download
   </message>
+  <message name="IDS_NETWORK_LIST_ITEM_ADDING_PROFILE" desc="Loading text indicating that an eSIM profile is currently installing.">
+    Adding Profile...
+  </message>
   <message name="IDS_WIFI_NETWORK_STATUS_SECURED" desc="a11y label used when a Wi-Fi network is secured.">
     Secured
   </message>
diff --git a/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_INTERNET_CLOSE_EID_POPUP_BUTTON_LABEL.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_CELLULAR_SETUP_CLOSE_EID_POPUP_BUTTON_LABEL.png.sha1
similarity index 100%
rename from chrome/app/os_settings_strings_grdp/IDS_SETTINGS_INTERNET_CLOSE_EID_POPUP_BUTTON_LABEL.png.sha1
rename to chrome/app/chromeos_strings_grdp/IDS_CELLULAR_SETUP_CLOSE_EID_POPUP_BUTTON_LABEL.png.sha1
diff --git a/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_INTERNET_EID_POPUP_DESCRIPTION.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_CELLULAR_SETUP_EID_POPUP_DESCRIPTION.png.sha1
similarity index 100%
rename from chrome/app/os_settings_strings_grdp/IDS_SETTINGS_INTERNET_EID_POPUP_DESCRIPTION.png.sha1
rename to chrome/app/chromeos_strings_grdp/IDS_CELLULAR_SETUP_EID_POPUP_DESCRIPTION.png.sha1
diff --git a/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_INTERNET_EID_POPUP_TITLE.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_CELLULAR_SETUP_EID_POPUP_TITLE.png.sha1
similarity index 100%
rename from chrome/app/os_settings_strings_grdp/IDS_SETTINGS_INTERNET_EID_POPUP_TITLE.png.sha1
rename to chrome/app/chromeos_strings_grdp/IDS_CELLULAR_SETUP_EID_POPUP_TITLE.png.sha1
diff --git a/chrome/app/chromeos_strings_grdp/IDS_NETWORK_LIST_ITEM_ADDING_PROFILE.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_NETWORK_LIST_ITEM_ADDING_PROFILE.png.sha1
new file mode 100644
index 0000000..dac87ddb
--- /dev/null
+++ b/chrome/app/chromeos_strings_grdp/IDS_NETWORK_LIST_ITEM_ADDING_PROFILE.png.sha1
@@ -0,0 +1 @@
+185afb896fdb3fe2f34d173871deaba7e1037e09
\ No newline at end of file
diff --git a/chrome/app/chromeos_strings_grdp/IDS_NETWORK_LIST_ITEM_LABEL_ESIM_PENDING_PROFILE_INSTALLING.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_NETWORK_LIST_ITEM_LABEL_ESIM_PENDING_PROFILE_INSTALLING.png.sha1
new file mode 100644
index 0000000..29d7e0c
--- /dev/null
+++ b/chrome/app/chromeos_strings_grdp/IDS_NETWORK_LIST_ITEM_LABEL_ESIM_PENDING_PROFILE_INSTALLING.png.sha1
@@ -0,0 +1 @@
+747335c6e34eecfa25233f914c1a5338c518f981
\ No newline at end of file
diff --git a/chrome/app/chromeos_strings_grdp/IDS_NETWORK_LIST_ITEM_LABEL_ESIM_PENDING_PROFILE_WITH_PROVIDER_NAME_INSTALLING.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_NETWORK_LIST_ITEM_LABEL_ESIM_PENDING_PROFILE_WITH_PROVIDER_NAME_INSTALLING.png.sha1
new file mode 100644
index 0000000..70cd3809
--- /dev/null
+++ b/chrome/app/chromeos_strings_grdp/IDS_NETWORK_LIST_ITEM_LABEL_ESIM_PENDING_PROFILE_WITH_PROVIDER_NAME_INSTALLING.png.sha1
@@ -0,0 +1 @@
+087aebeabd87d98b9c132dd4246801c35d60c2f8
\ No newline at end of file
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index 484f830..2c075d95 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -1161,6 +1161,9 @@
         <message name="IDS_ASSIGN_TO_DESKS_MENU" desc="The text label of the Assign to Desks submenu">
           Assign to desk
         </message>
+        <message name="IDS_ASSIGN_TO_ALL_DESKS" desc="The text label of the Assign to All Desks menu item">
+          All desks
+        </message>
       </if>
 
       <message name="IDS_ACCNAME_ZOOM_PLUS2" desc="The accessible description of the Make Text Larger menu item in the merged menu">
@@ -5985,7 +5988,7 @@
         Reopen a tab if you accidentally closed it
       </message>
       <message name="IDS_DESKTOP_PWA_INSTALL_PROMO" desc="Text shown on promotional UI appearing next to the PWA install icon">
-        To get back here quickly, install <ph name="APP_NAME">$1<ex>Google Maps</ex></ph>
+        To get back here quickly, install <ph name="APP_NAME">$1<ex>Google Maps</ex></ph> by clicking the install button
       </message>
       <message name="IDS_REOPEN_TAB_PROMO_SCREENREADER" desc="Text announced encouraging users to reopen a tab with the given shortcut">
         <ph name="SHORTCUT">$1<ex>CTRL+SHIFT+T</ex></ph> can reopen accidentally closed tabs
diff --git a/chrome/app/generated_resources_grd/IDS_ASSIGN_TO_ALL_DESKS.png.sha1 b/chrome/app/generated_resources_grd/IDS_ASSIGN_TO_ALL_DESKS.png.sha1
new file mode 100644
index 0000000..3e52249f
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_ASSIGN_TO_ALL_DESKS.png.sha1
@@ -0,0 +1 @@
+9b0aad1bc68655b35dd4aec3e1547b5896010d9b
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_DESKTOP_PWA_INSTALL_PROMO.png.sha1 b/chrome/app/generated_resources_grd/IDS_DESKTOP_PWA_INSTALL_PROMO.png.sha1
index f46e5869..6d25783 100644
--- a/chrome/app/generated_resources_grd/IDS_DESKTOP_PWA_INSTALL_PROMO.png.sha1
+++ b/chrome/app/generated_resources_grd/IDS_DESKTOP_PWA_INSTALL_PROMO.png.sha1
@@ -1 +1 @@
-34a9e96d0617141c5d1e542741566260cb823b75
\ No newline at end of file
+aa0c0ad4cc0e1bf32e30103572a06666c14b01c3
\ No newline at end of file
diff --git a/chrome/app/os_settings_strings.grdp b/chrome/app/os_settings_strings.grdp
index 606aa240..68b872e 100644
--- a/chrome/app/os_settings_strings.grdp
+++ b/chrome/app/os_settings_strings.grdp
@@ -2400,15 +2400,6 @@
   <message name="IDS_SETTINGS_INTERNET_TETHER_NOT_SETUP_WITH_LEARN_MORE_LINK" desc="Text shown when viewing the Mobile data page and no instant tether network is available">
     No device detected <ph name="BEGIN_LINK">&lt;a target="_blank" href="$1"&gt;</ph>Learn more<ph name="END_LINK">&lt;/a&gt;</ph>
   </message>
-  <message name="IDS_SETTINGS_INTERNET_EID_POPUP_TITLE" desc="Label shown when vewing EID and QR code popup describing device EID">
-    Your device EID
-  </message>
-  <message name="IDS_SETTINGS_INTERNET_EID_POPUP_DESCRIPTION" desc="Label shown when veiwing EID and QR code popup describing what an EID number is used for">
-    A customer service rep can use the EID number to help you activate service.
-  </message>
-  <message name="IDS_SETTINGS_INTERNET_CLOSE_EID_POPUP_BUTTON_LABEL" desc="A11y and tooltip label for EID and QR code popup close button when viewing EID and QR code popup">
-    Close EID and QR code popup
-  </message>
   <message name="IDS_SETTINGS_INTERNET_SHOW_EID_POPUP_BUTTON_LABEL" desc="A11y and tooltop label for EID and QR code popup show button when viewing cellular network list">
     Show device EID and QR code popup
   </message>
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index e1a76b7..c50fad4 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -980,8 +980,6 @@
     "optimization_guide/optimization_guide_navigation_data.h",
     "optimization_guide/optimization_guide_permissions_util.cc",
     "optimization_guide/optimization_guide_permissions_util.h",
-    "optimization_guide/optimization_guide_session_statistic.cc",
-    "optimization_guide/optimization_guide_session_statistic.h",
     "optimization_guide/optimization_guide_top_host_provider.cc",
     "optimization_guide/optimization_guide_top_host_provider.h",
     "optimization_guide/optimization_guide_web_contents_observer.cc",
@@ -993,10 +991,6 @@
     "optimization_guide/prediction/prediction_model_download_manager.cc",
     "optimization_guide/prediction/prediction_model_download_manager.h",
     "optimization_guide/prediction/prediction_model_download_observer.h",
-    "optimization_guide/prediction/prediction_model_fetcher.cc",
-    "optimization_guide/prediction/prediction_model_fetcher.h",
-    "optimization_guide/prediction/prediction_model_file.cc",
-    "optimization_guide/prediction/prediction_model_file.h",
     "page_load_metrics/observers/aborts_page_load_metrics_observer.cc",
     "page_load_metrics/observers/aborts_page_load_metrics_observer.h",
     "page_load_metrics/observers/ad_metrics/ads_page_load_metrics_observer.cc",
@@ -1324,6 +1318,8 @@
     "prefetch/no_state_prefetch/prerender_link_manager_factory.h",
     "prefetch/no_state_prefetch/prerender_manager_factory.cc",
     "prefetch/no_state_prefetch/prerender_manager_factory.h",
+    "prefetch/pref_names.cc",
+    "prefetch/pref_names.h",
     "prefetch/prefetch_proxy/prefetch_proxy_features.cc",
     "prefetch/prefetch_proxy/prefetch_proxy_features.h",
     "prefetch/prefetch_proxy/prefetch_proxy_from_string_url_loader.cc",
@@ -2718,7 +2714,6 @@
       "android/feed/v2/background_refresh_task.cc",
       "android/feed/v2/background_refresh_task.h",
       "android/feed/v2/feed_image_fetch_client.cc",
-      "android/feed/v2/feed_persistent_key_value_cache.cc",
       "android/feed/v2/feed_service_bridge.cc",
       "android/feed/v2/feed_service_bridge.h",
       "android/feed/v2/feed_service_factory.cc",
@@ -3941,6 +3936,8 @@
       "send_tab_to_self/send_tab_to_self_desktop_util.h",
       "serial/chrome_serial_delegate.cc",
       "serial/chrome_serial_delegate.h",
+      "serial/serial_blocklist.cc",
+      "serial/serial_blocklist.h",
       "serial/serial_chooser_context.cc",
       "serial/serial_chooser_context.h",
       "serial/serial_chooser_context_factory.cc",
@@ -4516,7 +4513,7 @@
       "//chrome/browser/nearby_sharing/scheduling",
       "//chrome/browser/ui/webui/nearby_share:mojom",
       "//chrome/browser/ui/webui/nearby_share/public/mojom",
-      "//chrome/browser/webshare/chromeos:storage",
+      "//chrome/browser/webshare:storage",
       "//chromeos/components/account_manager",
       "//chromeos/components/cdm_factory_daemon:cdm_factory_daemon_browser",
       "//chromeos/components/quick_answers",
@@ -4615,6 +4612,8 @@
       "lacros/cert_db_initializer_impl.h",
       "lacros/client_cert_store_lacros.cc",
       "lacros/client_cert_store_lacros.h",
+      "lacros/crosapi_pref_observer.cc",
+      "lacros/crosapi_pref_observer.h",
       "lacros/feedback_util.cc",
       "lacros/feedback_util.h",
       "lacros/immersive_context_lacros.cc",
diff --git a/chrome/browser/android/bookmarks/bookmark_bridge.cc b/chrome/browser/android/bookmarks/bookmark_bridge.cc
index 048b4ce7..52ef5096 100644
--- a/chrome/browser/android/bookmarks/bookmark_bridge.cc
+++ b/chrome/browser/android/bookmarks/bookmark_bridge.cc
@@ -828,9 +828,8 @@
   const BookmarkNode* node = reading_list_manager_->Add(
       GURL(base::android::ConvertJavaStringToUTF16(env, j_url)),
       base::android::ConvertJavaStringToUTF8(env, j_title));
-  return node ? JavaBookmarkIdCreateBookmarkId(env, node->id(),
-                                               GetBookmarkType(node))
-              : ScopedJavaLocalRef<jobject>();
+  DCHECK(node);
+  return JavaBookmarkIdCreateBookmarkId(env, node->id(), GetBookmarkType(node));
 }
 
 ScopedJavaLocalRef<jobject> BookmarkBridge::GetReadingListItem(
diff --git a/chrome/browser/android/feed/v2/feed_persistent_key_value_cache.cc b/chrome/browser/android/feed/v2/feed_persistent_key_value_cache.cc
deleted file mode 100644
index ca8b363a9..0000000
--- a/chrome/browser/android/feed/v2/feed_persistent_key_value_cache.cc
+++ /dev/null
@@ -1,111 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include <memory>
-
-#include "base/android/callback_android.h"
-#include "base/android/jni_android.h"
-#include "base/android/jni_array.h"
-#include "chrome/android/chrome_jni_headers/FeedPersistentKeyValueCache_jni.h"
-#include "chrome/browser/android/feed/v2/feed_service_factory.h"
-#include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/profiles/profile_manager.h"
-#include "components/feed/core/v2/public/feed_service.h"
-#include "components/feed/core/v2/public/feed_stream_api.h"
-#include "components/feed/core/v2/public/persistent_key_value_store.h"
-
-namespace feed {
-namespace {
-using base::android::JavaParamRef;
-
-std::string JavaByteArrayToString(
-    JNIEnv* env,
-    const base::android::JavaRef<jbyteArray>& byte_array) {
-  std::string result;
-  base::android::JavaByteArrayToString(env, byte_array, &result);
-  return result;
-}
-
-void OnLookupFinished(JNIEnv* env,
-                      base::android::ScopedJavaGlobalRef<jobject> callback,
-                      PersistentKeyValueStore::Result result) {
-  base::android::ScopedJavaLocalRef<jbyteArray> j_result;
-  if (result.get_result) {
-    j_result = base::android::ToJavaByteArray(env, *result.get_result);
-  }
-  base::android::RunObjectCallbackAndroid(callback, j_result);
-}
-
-void CallRunnable(base::android::ScopedJavaGlobalRef<jobject> runnable,
-                  PersistentKeyValueStore::Result result) {
-  if (runnable)
-    base::android::RunRunnableAndroid(runnable);
-}
-
-PersistentKeyValueStore* GetStore() {
-  Profile* profile = ProfileManager::GetLastUsedProfile();
-  if (!profile)
-    return nullptr;
-
-  FeedService* feed_service = FeedServiceFactory::GetForBrowserContext(profile);
-  if (!feed_service)
-    return nullptr;
-
-  return feed_service->GetStream()->GetPersistentKeyValueStore();
-}
-
-}  // namespace
-
-void JNI_FeedPersistentKeyValueCache_Lookup(
-    JNIEnv* env,
-    const JavaParamRef<jbyteArray>& j_key,
-    const JavaParamRef<jobject>& j_response_callback) {
-  base::android::ScopedJavaGlobalRef<jobject> callback(j_response_callback);
-
-  PersistentKeyValueStore* store = GetStore();
-  if (!store) {
-    OnLookupFinished(env, std::move(callback), {});
-    return;
-  }
-  return store->Get(
-      JavaByteArrayToString(env, j_key),
-      base::BindOnce(&OnLookupFinished, env, std::move(callback)));
-}
-
-void JNI_FeedPersistentKeyValueCache_Put(
-    JNIEnv* env,
-    const JavaParamRef<jbyteArray>& j_key,
-    const JavaParamRef<jbyteArray>& j_value,
-    const JavaParamRef<jobject>& j_runnable) {
-  base::android::ScopedJavaGlobalRef<jobject> callback(j_runnable);
-
-  PersistentKeyValueStore* store = GetStore();
-  if (!store) {
-    base::android::RunRunnableAndroid(j_runnable);
-    return;
-  }
-  return store->Put(
-      JavaByteArrayToString(env, j_key), JavaByteArrayToString(env, j_value),
-      base::BindOnce(&CallRunnable,
-                     base::android::ScopedJavaGlobalRef<jobject>(j_runnable)));
-}
-
-void JNI_FeedPersistentKeyValueCache_Evict(
-    JNIEnv* env,
-    const JavaParamRef<jbyteArray>& j_key,
-    const JavaParamRef<jobject>& j_runnable) {
-  base::android::ScopedJavaGlobalRef<jobject> callback(j_runnable);
-
-  PersistentKeyValueStore* store = GetStore();
-  if (!store) {
-    base::android::RunRunnableAndroid(j_runnable);
-    return;
-  }
-  return store->Delete(
-      JavaByteArrayToString(env, j_key),
-      base::BindOnce(&CallRunnable,
-                     base::android::ScopedJavaGlobalRef<jobject>(j_runnable)));
-}
-
-}  // namespace feed
diff --git a/chrome/browser/android/feed/v2/feed_service_factory.cc b/chrome/browser/android/feed/v2/feed_service_factory.cc
index a19b022..472d37b 100644
--- a/chrome/browser/android/feed/v2/feed_service_factory.cc
+++ b/chrome/browser/android/feed/v2/feed_service_factory.cc
@@ -23,7 +23,6 @@
 #include "chrome/common/chrome_version.h"
 #include "components/background_task_scheduler/background_task_scheduler_factory.h"
 #include "components/feed/buildflags.h"
-#include "components/feed/core/proto/v2/keyvalue_store.pb.h"
 #include "components/feed/core/proto/v2/store.pb.h"
 #include "components/feed/core/v2/public/feed_service.h"
 #include "components/keyed_service/content/browser_context_dependency_manager.h"
@@ -130,9 +129,6 @@
       storage_partition->GetProtoDatabaseProvider()->GetDB<feedstore::Record>(
           leveldb_proto::ProtoDbType::FEED_STREAM_DATABASE,
           feed_dir.AppendASCII("streamdb"), background_task_runner),
-      storage_partition->GetProtoDatabaseProvider()->GetDB<feedkvstore::Entry>(
-          leveldb_proto::ProtoDbType::FEED_KEY_VALUE_DATABASE,
-          feed_dir.AppendASCII("keyvaldb"), background_task_runner),
       identity_manager,
       HistoryServiceFactory::GetForProfile(profile,
                                            ServiceAccessType::IMPLICIT_ACCESS),
diff --git a/chrome/browser/apps/app_service/app_launch_params.h b/chrome/browser/apps/app_service/app_launch_params.h
index 417b33b..49b918cb 100644
--- a/chrome/browser/apps/app_service/app_launch_params.h
+++ b/chrome/browser/apps/app_service/app_launch_params.h
@@ -62,6 +62,9 @@
   // If non-empty, use override_app_name in place of generating one normally.
   std::string override_app_name;
 
+  // The id from the restore data to restore the browser window.
+  int32_t restore_id = 0;
+
   // If non-empty, information from the command line may be passed on to the
   // application.
   base::CommandLine command_line;
diff --git a/chrome/browser/banners/android/java/src/org/chromium/chrome/browser/banners/AppBannerManager.java b/chrome/browser/banners/android/java/src/org/chromium/chrome/browser/banners/AppBannerManager.java
index 548806b..782fede 100644
--- a/chrome/browser/banners/android/java/src/org/chromium/chrome/browser/banners/AppBannerManager.java
+++ b/chrome/browser/banners/android/java/src/org/chromium/chrome/browser/banners/AppBannerManager.java
@@ -15,6 +15,7 @@
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.base.annotations.JNINamespace;
 import org.chromium.base.annotations.NativeMethods;
+import org.chromium.components.webapps.WebappsUtils;
 import org.chromium.content_public.browser.WebContents;
 
 /**
@@ -56,6 +57,27 @@
     /** Pointer to the native side AppBannerManager. */
     private long mNativePointer;
 
+    /** Whether add to home screen is permitted by the system. */
+    private static Boolean sIsSupported;
+
+    /**
+     * Checks if the add to home screen intent is supported.
+     * @return true if add to home screen is supported, false otherwise.
+     */
+    @CalledByNative
+    private static boolean isSupported() {
+        if (sIsSupported == null) {
+            sIsSupported = WebappsUtils.isAddToHomeIntentSupported();
+        }
+        return sIsSupported;
+    }
+
+    /** Overrides whether the system supports add to home screen. Used in testing. */
+    @VisibleForTesting
+    public static void setIsSupported(boolean state) {
+        sIsSupported = state;
+    }
+
     /**
      * Sets the delegate that provides information about a given package.
      * @param delegate Delegate to use.  Previously set ones are destroyed.
diff --git a/chrome/browser/banners/android/java/src/org/chromium/chrome/browser/banners/AppBannerManagerTest.java b/chrome/browser/banners/android/java/src/org/chromium/chrome/browser/banners/AppBannerManagerTest.java
index 1008b68..8deaf41 100644
--- a/chrome/browser/banners/android/java/src/org/chromium/chrome/browser/banners/AppBannerManagerTest.java
+++ b/chrome/browser/banners/android/java/src/org/chromium/chrome/browser/banners/AppBannerManagerTest.java
@@ -226,7 +226,7 @@
 
     @Before
     public void setUp() throws Exception {
-        AppBannerManagerHelper.setIsSupported(true);
+        AppBannerManager.setIsSupported(true);
         ShortcutHelper.setDelegateForTests(new ShortcutHelper.Delegate() {
             @Override
             public void addShortcutToHomescreen(
@@ -793,7 +793,7 @@
     @Test
     @MediumTest
     @Feature({"AppBanners"})
-    @CommandLineFlags.Add("enable-features=" + ChromeFeatureList.INSTALLABLE_AMBIENT_BADGE_INFOBAR)
+    @CommandLineFlags.Add("enable-features=" + FeatureConstants.PWA_INSTALL_AVAILABLE_FEATURE)
     public void testInProductHelp() throws Exception {
         // Visit a site that is a PWA. The ambient badge should show.
         String webBannerUrl = WebappTestPage.getServiceWorkerUrl(mTestServer);
diff --git a/chrome/browser/banners/app_banner_manager_android.cc b/chrome/browser/banners/app_banner_manager_android.cc
index 592a9b7e..9fa23ab4 100644
--- a/chrome/browser/banners/app_banner_manager_android.cc
+++ b/chrome/browser/banners/app_banner_manager_android.cc
@@ -13,7 +13,6 @@
 #include "base/strings/string16.h"
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
-#include "chrome/android/chrome_jni_headers/AppBannerManagerHelper_jni.h"
 #include "chrome/browser/android/webapk/webapk_metrics.h"
 #include "chrome/browser/android/webapk/webapk_ukm_recorder.h"
 #include "chrome/browser/android/webapps/add_to_homescreen_coordinator.h"
@@ -117,7 +116,7 @@
 
 void AppBannerManagerAndroid::RequestAppBanner(const GURL& validated_url) {
   JNIEnv* env = base::android::AttachCurrentThread();
-  if (!Java_AppBannerManagerHelper_isEnabledForTab(env) ||
+  if (!Java_AppBannerManager_isSupported(env) ||
       !WebappsClient::Get()->CanShowAppBanners(web_contents())) {
     return;
   }
@@ -452,6 +451,12 @@
 }
 
 bool AppBannerManagerAndroid::MaybeShowInProductHelp() const {
+  if (!base::FeatureList::IsEnabled(
+          feature_engagement::kIPHPwaInstallAvailableFeature)) {
+    DVLOG(2) << "Feature not enabled";
+    return false;
+  }
+
   if (!web_contents()) {
     DVLOG(2) << "IPH for PWA aborted: null WebContents";
     return false;
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index f590c2f..f0d4bf1 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -1075,6 +1075,8 @@
     "crosapi/message_center_ash.h",
     "crosapi/metrics_reporting_ash.cc",
     "crosapi/metrics_reporting_ash.h",
+    "crosapi/prefs_ash.cc",
+    "crosapi/prefs_ash.h",
     "crosapi/screen_manager_ash.cc",
     "crosapi/screen_manager_ash.h",
     "crosapi/select_file_ash.cc",
@@ -1456,6 +1458,8 @@
     "first_run/drive_first_run_controller.h",
     "first_run/first_run.cc",
     "first_run/first_run.h",
+    "full_restore/app_launch_handler.cc",
+    "full_restore/app_launch_handler.h",
     "full_restore/full_restore_prefs.cc",
     "full_restore/full_restore_prefs.h",
     "full_restore/full_restore_service.cc",
@@ -3502,6 +3506,7 @@
     "crosapi/browser_util_unittest.cc",
     "crosapi/message_center_ash_unittest.cc",
     "crosapi/metrics_reporting_ash_unittest.cc",
+    "crosapi/prefs_ash_unittest.cc",
     "crosapi/test_mojo_connection_manager_unittest.cc",
     "crostini/ansible/ansible_management_service_unittest.cc",
     "crostini/crostini_disk_unittest.cc",
diff --git a/chrome/browser/chromeos/crosapi/ash_chrome_service_impl.cc b/chrome/browser/chromeos/crosapi/ash_chrome_service_impl.cc
index c775548..41f78ad 100644
--- a/chrome/browser/chromeos/crosapi/ash_chrome_service_impl.cc
+++ b/chrome/browser/chromeos/crosapi/ash_chrome_service_impl.cc
@@ -20,6 +20,7 @@
 #include "chrome/browser/chromeos/crosapi/keystore_service_ash.h"
 #include "chrome/browser/chromeos/crosapi/message_center_ash.h"
 #include "chrome/browser/chromeos/crosapi/metrics_reporting_ash.h"
+#include "chrome/browser/chromeos/crosapi/prefs_ash.h"
 #include "chrome/browser/chromeos/crosapi/screen_manager_ash.h"
 #include "chrome/browser/chromeos/crosapi/select_file_ash.h"
 #include "chrome/browser/chromeos/crosapi/test_controller_ash.h"
@@ -45,6 +46,9 @@
     : receiver_(this, std::move(pending_receiver)),
       metrics_reporting_ash_(std::make_unique<MetricsReportingAsh>(
           g_browser_process->local_state())),
+      prefs_ash_(std::make_unique<PrefsAsh>(
+          g_browser_process->local_state(),
+          ProfileManager::GetPrimaryUserProfile()->GetPrefs())),
       screen_manager_ash_(std::make_unique<ScreenManagerAsh>()),
       cert_database_ash_(std::make_unique<CertDatabaseAsh>()),
       test_controller_ash_(std::make_unique<TestControllerAsh>()),
@@ -175,6 +179,11 @@
   clipboard_ash_->BindReceiver(std::move(receiver));
 }
 
+void AshChromeServiceImpl::BindPrefs(
+    mojo::PendingReceiver<mojom::Prefs> receiver) {
+  prefs_ash_->BindReceiver(std::move(receiver));
+}
+
 void AshChromeServiceImpl::OnLacrosStartup(mojom::LacrosInfoPtr lacros_info) {
   BrowserManager::Get()->set_lacros_version(lacros_info->lacros_version);
 }
diff --git a/chrome/browser/chromeos/crosapi/ash_chrome_service_impl.h b/chrome/browser/chromeos/crosapi/ash_chrome_service_impl.h
index 594d52f..2c18931a 100644
--- a/chrome/browser/chromeos/crosapi/ash_chrome_service_impl.h
+++ b/chrome/browser/chromeos/crosapi/ash_chrome_service_impl.h
@@ -21,6 +21,7 @@
 class KeystoreServiceAsh;
 class MessageCenterAsh;
 class MetricsReportingAsh;
+class PrefsAsh;
 class ScreenManagerAsh;
 class SelectFileAsh;
 class TestControllerAsh;
@@ -47,6 +48,7 @@
       mojo::PendingReceiver<mojom::MessageCenter> receiver) override;
   void BindMetricsReporting(
       mojo::PendingReceiver<mojom::MetricsReporting> receiver) override;
+  void BindPrefs(mojo::PendingReceiver<mojom::Prefs> receiver) override;
   void BindScreenManager(
       mojo::PendingReceiver<mojom::ScreenManager> receiver) override;
   void BindSelectFile(
@@ -75,6 +77,7 @@
   std::unique_ptr<KeystoreServiceAsh> keystore_service_ash_;
   std::unique_ptr<MessageCenterAsh> message_center_ash_;
   std::unique_ptr<MetricsReportingAsh> metrics_reporting_ash_;
+  std::unique_ptr<PrefsAsh> prefs_ash_;
   std::unique_ptr<ScreenManagerAsh> screen_manager_ash_;
   std::unique_ptr<SelectFileAsh> select_file_ash_;
   std::unique_ptr<FeedbackAsh> feedback_ash_;
diff --git a/chrome/browser/chromeos/crosapi/browser_util.cc b/chrome/browser/chromeos/crosapi/browser_util.cc
index 2815d6c..afaf136 100644
--- a/chrome/browser/chromeos/crosapi/browser_util.cc
+++ b/chrome/browser/chromeos/crosapi/browser_util.cc
@@ -198,7 +198,7 @@
 
 base::flat_map<base::Token, uint32_t> GetInterfaceVersions() {
   static_assert(
-      crosapi::mojom::AshChromeService::Version_ == 10,
+      crosapi::mojom::AshChromeService::Version_ == 11,
       "if you add a new crosapi, please add it to the version map here");
   InterfaceVersions versions;
   AddVersion<crosapi::mojom::AccountManager>(&versions);
@@ -210,6 +210,7 @@
   AddVersion<crosapi::mojom::KeystoreService>(&versions);
   AddVersion<crosapi::mojom::MessageCenter>(&versions);
   AddVersion<crosapi::mojom::MetricsReporting>(&versions);
+  AddVersion<crosapi::mojom::Prefs>(&versions);
   AddVersion<crosapi::mojom::ScreenManager>(&versions);
   AddVersion<crosapi::mojom::SnapshotCapturer>(&versions);
   AddVersion<crosapi::mojom::TestController>(&versions);
diff --git a/chrome/browser/chromeos/crosapi/prefs_ash.cc b/chrome/browser/chromeos/crosapi/prefs_ash.cc
new file mode 100644
index 0000000..d76410e
--- /dev/null
+++ b/chrome/browser/chromeos/crosapi/prefs_ash.cc
@@ -0,0 +1,110 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/chromeos/crosapi/prefs_ash.h"
+
+#include <utility>
+
+#include "ash/public/cpp/ash_pref_names.h"
+#include "base/bind.h"
+#include "base/check.h"
+#include "chromeos/crosapi/mojom/prefs.mojom.h"
+#include "components/metrics/metrics_pref_names.h"
+#include "components/prefs/pref_service.h"
+
+namespace crosapi {
+
+PrefsAsh::PrefsAsh(PrefService* local_state, PrefService* profile_prefs)
+    : local_state_(local_state), profile_prefs_(profile_prefs) {
+  DCHECK(local_state_);
+  DCHECK(profile_prefs_);
+  local_state_registrar_.Init(local_state_);
+  profile_prefs_registrar_.Init(profile_prefs_);
+}
+
+PrefsAsh::~PrefsAsh() = default;
+
+void PrefsAsh::BindReceiver(mojo::PendingReceiver<mojom::Prefs> receiver) {
+  receivers_.Add(this, std::move(receiver));
+}
+
+void PrefsAsh::GetPref(mojom::PrefPath path, GetPrefCallback callback) {
+  auto state = GetState(path);
+  const base::Value* value =
+      state ? state->pref_service->Get(state->path) : nullptr;
+  std::move(callback).Run(value ? base::Optional<base::Value>(value->Clone())
+                                : base::nullopt);
+}
+
+void PrefsAsh::SetPref(mojom::PrefPath path,
+                       base::Value value,
+                       SetPrefCallback callback) {
+  auto state = GetState(path);
+  if (state) {
+    state->pref_service->Set(state->path, value);
+  }
+  std::move(callback).Run();
+}
+
+void PrefsAsh::AddObserver(mojom::PrefPath path,
+                           mojo::PendingRemote<mojom::PrefObserver> observer) {
+  auto state = GetState(path);
+  const base::Value* value =
+      state ? state->pref_service->Get(state->path) : nullptr;
+  if (!value) {
+    return;
+  }
+
+  // Fire the observer with the initial value.
+  mojo::Remote<mojom::PrefObserver> remote(std::move(observer));
+  remote->OnPrefChanged(value->Clone());
+
+  if (!state->registrar->IsObserved(state->path)) {
+    // Unretained() is safe since PrefChangeRegistrar and RemoteSet within
+    // observers_ are owned by this and wont invoke if PrefsAsh is destroyed.
+    state->registrar->Add(state->path,
+                          base::BindRepeating(&PrefsAsh::OnPrefChanged,
+                                              base::Unretained(this), path));
+    observers_[path].set_disconnect_handler(base::BindRepeating(
+        &PrefsAsh::OnDisconnect, base::Unretained(this), path));
+  }
+  observers_[path].Add(std::move(remote));
+}
+
+base::Optional<PrefsAsh::State> PrefsAsh::GetState(mojom::PrefPath path) {
+  switch (path) {
+    case mojom::PrefPath::kMetricsReportingEnabled:
+      return State{local_state_, &local_state_registrar_,
+                   metrics::prefs::kMetricsReportingEnabled};
+    case mojom::PrefPath::kAccessibilitySpokenFeedbackEnabled:
+      return State{profile_prefs_, &profile_prefs_registrar_,
+                   ash::prefs::kAccessibilitySpokenFeedbackEnabled};
+    default:
+      LOG(WARNING) << "Unknown pref path: " << path;
+      return base::nullopt;
+  }
+}
+
+void PrefsAsh::OnPrefChanged(mojom::PrefPath path) {
+  auto state = GetState(path);
+  const base::Value* value =
+      state ? state->pref_service->Get(state->path) : nullptr;
+  if (value) {
+    for (auto& observer : observers_[path]) {
+      observer->OnPrefChanged(value->Clone());
+    }
+  }
+}
+
+void PrefsAsh::OnDisconnect(mojom::PrefPath path, mojo::RemoteSetElementId id) {
+  const auto& it = observers_.find(path);
+  if (it != observers_.end() && it->second.empty()) {
+    if (auto state = GetState(path)) {
+      state->registrar->Remove(state->path);
+    }
+    observers_.erase(it);
+  }
+}
+
+}  // namespace crosapi
diff --git a/chrome/browser/chromeos/crosapi/prefs_ash.h b/chrome/browser/chromeos/crosapi/prefs_ash.h
new file mode 100644
index 0000000..82374ee
--- /dev/null
+++ b/chrome/browser/chromeos/crosapi/prefs_ash.h
@@ -0,0 +1,73 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_CHROMEOS_CROSAPI_PREFS_ASH_H_
+#define CHROME_BROWSER_CHROMEOS_CROSAPI_PREFS_ASH_H_
+
+#include <map>
+#include <memory>
+#include <utility>
+
+#include "base/gtest_prod_util.h"
+#include "base/optional.h"
+#include "chromeos/crosapi/mojom/prefs.mojom.h"
+#include "components/prefs/pref_change_registrar.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/receiver_set.h"
+#include "mojo/public/cpp/bindings/remote_set.h"
+
+class PrefService;
+class PrefChangeRegistrar;
+
+namespace crosapi {
+
+// The ash-chrome implementation of the Prefs crosapi interface.
+// This class must only be used from the main thread.
+class PrefsAsh : public mojom::Prefs {
+ public:
+  PrefsAsh(PrefService* local_state, PrefService* profile_prefs);
+  PrefsAsh(const PrefsAsh&) = delete;
+  PrefsAsh& operator=(const PrefsAsh&) = delete;
+  ~PrefsAsh() override;
+
+  void BindReceiver(mojo::PendingReceiver<mojom::Prefs> receiver);
+
+  // crosapi::mojom::Prefs:
+  void GetPref(mojom::PrefPath path, GetPrefCallback callback) override;
+  void SetPref(mojom::PrefPath path,
+               base::Value value,
+               SetPrefCallback callback) override;
+  void AddObserver(mojom::PrefPath path,
+                   mojo::PendingRemote<mojom::PrefObserver> observer) override;
+
+ private:
+  FRIEND_TEST_ALL_PREFIXES(PrefsAshTest, LocalStatePrefs);
+
+  struct State {
+    PrefService* pref_service;
+    PrefChangeRegistrar* registrar;
+    std::string path;
+  };
+  base::Optional<State> GetState(mojom::PrefPath path);
+
+  void OnPrefChanged(mojom::PrefPath path);
+  void OnDisconnect(mojom::PrefPath path, mojo::RemoteSetElementId id);
+
+  // In production, owned by g_browser_process, which outlives this object.
+  PrefService* const local_state_;
+  // From GetPrimaryUserProfile()->GetPrefs(), which outlives this object.
+  PrefService* const profile_prefs_;
+  PrefChangeRegistrar local_state_registrar_;
+  PrefChangeRegistrar profile_prefs_registrar_;
+
+  // This class supports any number of connections.
+  mojo::ReceiverSet<mojom::Prefs> receivers_;
+
+  // This class supports any number of observers.
+  std::map<mojom::PrefPath, mojo::RemoteSet<mojom::PrefObserver>> observers_;
+};
+
+}  // namespace crosapi
+
+#endif  // CHROME_BROWSER_CHROMEOS_CROSAPI_PREFS_ASH_H_
diff --git a/chrome/browser/chromeos/crosapi/prefs_ash_unittest.cc b/chrome/browser/chromeos/crosapi/prefs_ash_unittest.cc
new file mode 100644
index 0000000..826b608
--- /dev/null
+++ b/chrome/browser/chromeos/crosapi/prefs_ash_unittest.cc
@@ -0,0 +1,173 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/chromeos/crosapi/prefs_ash.h"
+
+#include <memory>
+
+#include "ash/public/cpp/ash_pref_names.h"
+#include "base/optional.h"
+#include "base/test/bind.h"
+#include "chrome/test/base/scoped_testing_local_state.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "chrome/test/base/testing_profile.h"
+#include "components/metrics/metrics_pref_names.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/testing_pref_service.h"
+#include "content/public/test/browser_task_environment.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace crosapi {
+namespace {
+
+class TestObserver : public mojom::PrefObserver {
+ public:
+  TestObserver() = default;
+  TestObserver(const TestObserver&) = delete;
+  TestObserver& operator=(const TestObserver&) = delete;
+  ~TestObserver() override = default;
+
+  // crosapi::mojom::PrefObserver:
+  void OnPrefChanged(base::Value value) override { value_ = std::move(value); }
+
+  base::Optional<base::Value> value_;
+  mojo::Receiver<mojom::PrefObserver> receiver_{this};
+};
+
+}  // namespace
+
+class PrefsAshTest : public testing::Test {
+ public:
+  PrefsAshTest()
+      : scoped_testing_local_state_(TestingBrowserProcess::GetGlobal()),
+        local_state_(scoped_testing_local_state_.Get()),
+        profile_prefs_(profile_.GetPrefs()) {}
+  PrefsAshTest(const PrefsAshTest&) = delete;
+  PrefsAshTest& operator=(const PrefsAshTest&) = delete;
+  ~PrefsAshTest() override = default;
+
+  content::BrowserTaskEnvironment task_environment_;
+  ScopedTestingLocalState scoped_testing_local_state_;
+  TestingProfile profile_;
+  PrefService* const local_state_;
+  PrefService* const profile_prefs_;
+};
+
+TEST_F(PrefsAshTest, LocalStatePrefs) {
+  local_state_->SetBoolean(metrics::prefs::kMetricsReportingEnabled, false);
+  PrefsAsh prefs_ash(local_state_, profile_prefs_);
+  mojo::Remote<mojom::Prefs> prefs_remote;
+  prefs_ash.BindReceiver(prefs_remote.BindNewPipeAndPassReceiver());
+  mojom::PrefPath path = mojom::PrefPath::kMetricsReportingEnabled;
+
+  // Get returns value.
+  base::Value get_value;
+  prefs_remote->GetPref(
+      path, base::BindLambdaForTesting([&](base::Optional<base::Value> value) {
+        get_value = std::move(*value);
+      }));
+  prefs_remote.FlushForTesting();
+  EXPECT_FALSE(get_value.GetBool());
+
+  // Set updates value.
+  prefs_remote->SetPref(path, base::Value(true), base::DoNothing());
+  prefs_remote.FlushForTesting();
+  EXPECT_TRUE(
+      local_state_->GetBoolean(metrics::prefs::kMetricsReportingEnabled));
+
+  // Adding an observer results in it being fired with the current state.
+  EXPECT_FALSE(prefs_ash.local_state_registrar_.IsObserved(
+      metrics::prefs::kMetricsReportingEnabled));
+  auto observer1 = std::make_unique<TestObserver>();
+  prefs_remote->AddObserver(path,
+                            observer1->receiver_.BindNewPipeAndPassRemote());
+  prefs_remote.FlushForTesting();
+  EXPECT_TRUE(observer1->value_->GetBool());
+  EXPECT_TRUE(prefs_ash.local_state_registrar_.IsObserved(
+      metrics::prefs::kMetricsReportingEnabled));
+  EXPECT_EQ(1, prefs_ash.observers_[path].size());
+
+  // Multiple observers is ok.
+  auto observer2 = std::make_unique<TestObserver>();
+  prefs_remote->AddObserver(path,
+                            observer2->receiver_.BindNewPipeAndPassRemote());
+  prefs_remote.FlushForTesting();
+  EXPECT_TRUE(observer2->value_->GetBool());
+  EXPECT_EQ(2, prefs_ash.observers_[path].size());
+
+  // Observer should be notified when value changes.
+  local_state_->SetBoolean(metrics::prefs::kMetricsReportingEnabled, false);
+  task_environment_.RunUntilIdle();
+  EXPECT_FALSE(observer1->value_->GetBool());
+  EXPECT_FALSE(observer2->value_->GetBool());
+
+  // Disconnect should remove PrefChangeRegistrar.
+  observer1.reset();
+  prefs_remote.FlushForTesting();
+  EXPECT_EQ(1, prefs_ash.observers_[path].size());
+  observer2.reset();
+  prefs_remote.FlushForTesting();
+  EXPECT_EQ(0, prefs_ash.observers_[path].size());
+  EXPECT_FALSE(prefs_ash.local_state_registrar_.IsObserved(
+      metrics::prefs::kMetricsReportingEnabled));
+}
+
+TEST_F(PrefsAshTest, ProfilePrefs) {
+  profile_prefs_->SetBoolean(ash::prefs::kAccessibilitySpokenFeedbackEnabled,
+                             false);
+  PrefsAsh prefs_ash(local_state_, profile_prefs_);
+  mojo::Remote<mojom::Prefs> prefs_remote;
+  prefs_ash.BindReceiver(prefs_remote.BindNewPipeAndPassReceiver());
+  mojom::PrefPath path = mojom::PrefPath::kAccessibilitySpokenFeedbackEnabled;
+
+  // Get returns value.
+  base::Value get_value;
+  prefs_remote->GetPref(
+      path, base::BindLambdaForTesting([&](base::Optional<base::Value> value) {
+        get_value = std::move(*value);
+      }));
+  prefs_remote.FlushForTesting();
+  EXPECT_FALSE(get_value.GetBool());
+
+  // Set updates value.
+  prefs_remote->SetPref(path, base::Value(true), base::DoNothing());
+  prefs_remote.FlushForTesting();
+  EXPECT_TRUE(profile_prefs_->GetBoolean(
+      ash::prefs::kAccessibilitySpokenFeedbackEnabled));
+
+  // Adding an observer results in it being fired with the current state.
+  TestObserver observer;
+  prefs_remote->AddObserver(path,
+                            observer.receiver_.BindNewPipeAndPassRemote());
+  prefs_remote.FlushForTesting();
+  EXPECT_TRUE(observer.value_->GetBool());
+}
+
+TEST_F(PrefsAshTest, GetUnknown) {
+  PrefsAsh prefs_ash(local_state_, profile_prefs_);
+  mojo::Remote<mojom::Prefs> prefs_remote;
+  prefs_ash.BindReceiver(prefs_remote.BindNewPipeAndPassReceiver());
+  mojom::PrefPath path = mojom::PrefPath::kUnknown;
+
+  // Get for an unknown value returns base::nullopt.
+  bool has_value = true;
+  prefs_remote->GetPref(
+      path, base::BindLambdaForTesting([&](base::Optional<base::Value> value) {
+        has_value = value.has_value();
+      }));
+  prefs_remote.FlushForTesting();
+  EXPECT_FALSE(has_value);
+
+  // Set or AddObserver for an unknown value does nothing.
+  prefs_remote->SetPref(path, base::Value(false), base::DoNothing());
+  TestObserver observer;
+  prefs_remote->AddObserver(path,
+                            observer.receiver_.BindNewPipeAndPassRemote());
+  prefs_remote.FlushForTesting();
+  EXPECT_FALSE(observer.value_.has_value());
+}
+
+}  // namespace crosapi
diff --git a/chrome/browser/chromeos/full_restore/app_launch_handler.cc b/chrome/browser/chromeos/full_restore/app_launch_handler.cc
new file mode 100644
index 0000000..375b9b8
--- /dev/null
+++ b/chrome/browser/chromeos/full_restore/app_launch_handler.cc
@@ -0,0 +1,188 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/chromeos/full_restore/app_launch_handler.h"
+
+#include <set>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "chrome/browser/apps/app_service/app_service_proxy.h"
+#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
+#include "chrome/browser/apps/app_service/browser_app_launcher.h"
+#include "chrome/browser/profiles/profile.h"
+#include "components/full_restore/full_restore_read_handler.h"
+#include "components/full_restore/full_restore_save_handler.h"
+#include "components/full_restore/restore_data.h"
+#include "components/services/app_service/public/cpp/app_update.h"
+#include "components/services/app_service/public/mojom/types.mojom.h"
+#include "ui/base/window_open_disposition.h"
+
+namespace chromeos {
+namespace full_restore {
+
+AppLaunchHandler::AppLaunchHandler(Profile* profile) : profile_(profile) {
+  // FullRestoreReadHandler reads the full restore data from the full restore
+  // data file on a background task runner.
+  ::full_restore::FullRestoreReadHandler::GetInstance()->ReadFromFile(
+      profile_->GetPath(), base::BindOnce(&AppLaunchHandler::OnGetRestoreData,
+                                          weak_ptr_factory_.GetWeakPtr()));
+}
+
+AppLaunchHandler::~AppLaunchHandler() = default;
+
+void AppLaunchHandler::OnAppUpdate(const apps::AppUpdate& update) {
+  // If the restore flag |should_restore_| is false, or the restore data has not
+  // been read yet, or the app is not ready, don't launch the app for the
+  // restoration.
+  if (!should_restore_ || !restore_data_ || !update.ReadinessChanged() ||
+      update.Readiness() != apps::mojom::Readiness::kReady) {
+    return;
+  }
+
+  // If there is no restore data or the launch list for the app is empty, don't
+  // launch the app.
+  const auto& app_id_to_launch_list = restore_data_->app_id_to_launch_list();
+  if (app_id_to_launch_list.find(update.AppId()) ==
+      app_id_to_launch_list.end()) {
+    return;
+  }
+
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, base::BindOnce(&AppLaunchHandler::LaunchApp,
+                                weak_ptr_factory_.GetWeakPtr(),
+                                update.AppType(), update.AppId()));
+}
+
+void AppLaunchHandler::OnAppRegistryCacheWillBeDestroyed(
+    apps::AppRegistryCache* cache) {
+  apps::AppRegistryCache::Observer::Observe(nullptr);
+}
+
+void AppLaunchHandler::SetShouldRestore() {
+  should_restore_ = true;
+  MaybePostRestore();
+}
+
+void AppLaunchHandler::OnGetRestoreData(
+    std::unique_ptr<::full_restore::RestoreData> restore_data) {
+  restore_data_ = std::move(restore_data);
+
+  // After reading the restore data, the restore data can be cleared from the
+  // restore file to save the new restore data.
+  ::full_restore::FullRestoreSaveHandler::GetInstance()->Flush(
+      profile_->GetPath());
+
+  MaybePostRestore();
+}
+
+void AppLaunchHandler::MaybePostRestore() {
+  // If the restore flag |should_restore_| is not true, or reading the restore
+  // data hasn't finished, don't restore.
+  if (!should_restore_ || !restore_data_)
+    return;
+
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, base::BindOnce(&AppLaunchHandler::MaybeRestore,
+                                weak_ptr_factory_.GetWeakPtr()));
+}
+
+void AppLaunchHandler::MaybeRestore() {
+  // If there is no launch list from the restore data, we don't need to handle
+  // the restoration.
+  const auto& launch_list = restore_data_->app_id_to_launch_list();
+  if (launch_list.empty())
+    return;
+
+  // Observe AppRegistryCache to get the notification when the app is ready.
+  DCHECK(
+      apps::AppServiceProxyFactory::IsAppServiceAvailableForProfile(profile_));
+  auto* cache = &apps::AppServiceProxyFactory::GetForProfile(profile_)
+                     ->AppRegistryCache();
+  Observe(cache);
+
+  // Add the app to |app_ids| if there is a launch list from the restore data
+  // for the app.
+  std::set<std::string> app_ids;
+  cache->ForEachApp([&app_ids, &launch_list](const apps::AppUpdate& update) {
+    if (update.Readiness() == apps::mojom::Readiness::kReady &&
+        launch_list.find(update.AppId()) != launch_list.end()) {
+      app_ids.insert(update.AppId());
+    }
+  });
+
+  for (const auto& app_id : app_ids)
+    LaunchApp(cache->GetAppType(app_id), app_id);
+}
+
+void AppLaunchHandler::LaunchApp(apps::mojom::AppType app_type,
+                                 const std::string& app_id) {
+  DCHECK(restore_data_);
+
+  // For the Chrome browser, the browser session restore is used to restore the
+  // web pages, so we don't need to launch the app.
+  if (app_id == extension_misc::kChromeAppId) {
+    restore_data_->RemoveApp(app_id);
+    return;
+  }
+
+  const auto it = restore_data_->app_id_to_launch_list().find(app_id);
+  if (it == restore_data_->app_id_to_launch_list().end() ||
+      it->second.empty()) {
+    restore_data_->RemoveApp(app_id);
+    return;
+  }
+
+  switch (app_type) {
+    case apps::mojom::AppType::kArc:
+      // TODO(crbug.com/1146900): Handle ARC apps
+      break;
+    case apps::mojom::AppType::kExtension:
+    case apps::mojom::AppType::kWeb:
+      LaunchWebAppOrExtension(app_id, it->second);
+      break;
+    case apps::mojom::AppType::kBuiltIn:
+    case apps::mojom::AppType::kCrostini:
+    case apps::mojom::AppType::kPluginVm:
+    case apps::mojom::AppType::kUnknown:
+    case apps::mojom::AppType::kMacOs:
+    case apps::mojom::AppType::kLacros:
+    case apps::mojom::AppType::kRemote:
+    case apps::mojom::AppType::kBorealis:
+      NOTREACHED();
+      break;
+  }
+  restore_data_->RemoveApp(app_id);
+}
+
+void AppLaunchHandler::LaunchWebAppOrExtension(
+    const std::string& app_id,
+    const ::full_restore::RestoreData::LaunchList& launch_list) {
+  auto* launcher = apps::AppServiceProxyFactory::GetForProfile(profile_)
+                       ->BrowserAppLauncher();
+  if (!launcher)
+    return;
+
+  for (const auto& it : launch_list) {
+    DCHECK(it.second->container.has_value());
+    DCHECK(it.second->disposition.has_value());
+    DCHECK(it.second->display_id.has_value());
+    apps::mojom::IntentPtr intent;
+    apps::AppLaunchParams params(
+        app_id,
+        static_cast<apps::mojom::LaunchContainer>(it.second->container.value()),
+        static_cast<WindowOpenDisposition>(it.second->disposition.value()),
+        it.second->display_id.value(),
+        it.second->file_paths.has_value() ? it.second->file_paths.value()
+                                          : std::vector<base::FilePath>{},
+        it.second->intent.has_value() ? it.second->intent.value() : intent);
+    params.restore_id = it.first;
+    launcher->LaunchAppWithParams(std::move(params));
+  }
+}
+
+}  // namespace full_restore
+}  // namespace chromeos
diff --git a/chrome/browser/chromeos/full_restore/app_launch_handler.h b/chrome/browser/chromeos/full_restore/app_launch_handler.h
new file mode 100644
index 0000000..8211dea2
--- /dev/null
+++ b/chrome/browser/chromeos/full_restore/app_launch_handler.h
@@ -0,0 +1,78 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_CHROMEOS_FULL_RESTORE_APP_LAUNCH_HANDLER_H_
+#define CHROME_BROWSER_CHROMEOS_FULL_RESTORE_APP_LAUNCH_HANDLER_H_
+
+#include "base/memory/weak_ptr.h"
+#include "components/full_restore/restore_data.h"
+#include "components/services/app_service/public/cpp/app_registry_cache.h"
+#include "components/services/app_service/public/mojom/types.mojom.h"
+
+namespace apps {
+class AppUpdate;
+}
+
+class Profile;
+
+namespace chromeos {
+namespace full_restore {
+
+// The AppLaunchHandler class calls FullRestoreReadHandler to read the full
+// restore data from the full restore data file on a background task runner, and
+// restore apps and web pages based on the user preference or the user's choice.
+//
+// The apps can be re-launched for the restoration when:
+// 1. There is the restore data for the app.
+// 2. The user preference sets always restore or the user selects 'Restore' from
+// the notification dialog.
+// 3. The app is ready.
+//
+// TODO(crbug.com/1146900): Implement ARC apps launching.
+class AppLaunchHandler : public apps::AppRegistryCache::Observer {
+ public:
+  explicit AppLaunchHandler(Profile* profile);
+  AppLaunchHandler(const AppLaunchHandler&) = delete;
+  AppLaunchHandler& operator=(const AppLaunchHandler&) = delete;
+  ~AppLaunchHandler() override;
+
+  // apps::AppRegistryCache::Observer:
+  void OnAppUpdate(const apps::AppUpdate& update) override;
+  void OnAppRegistryCacheWillBeDestroyed(
+      apps::AppRegistryCache* cache) override;
+
+  // If the user preference sets always restore or the user selects 'Restore'
+  // from the notification dialog, sets the restore flag |should_restore_| as
+  // true to allow the restoration.
+  void SetShouldRestore();
+
+ private:
+  void OnGetRestoreData(
+      std::unique_ptr<::full_restore::RestoreData> restore_data);
+
+  void MaybePostRestore();
+
+  // If there is the restore data, and the restore flag |should_restore_| is
+  // true, launches apps based on the restore data when apps are ready.
+  void MaybeRestore();
+
+  void LaunchApp(apps::mojom::AppType app_type, const std::string& app_id);
+
+  void LaunchWebAppOrExtension(
+      const std::string& app_id,
+      const ::full_restore::RestoreData::LaunchList& launch_list);
+
+  Profile* profile_ = nullptr;
+
+  bool should_restore_ = false;
+
+  std::unique_ptr<::full_restore::RestoreData> restore_data_;
+
+  base::WeakPtrFactory<AppLaunchHandler> weak_ptr_factory_{this};
+};
+
+}  // namespace full_restore
+}  // namespace chromeos
+
+#endif  // CHROME_BROWSER_CHROMEOS_FULL_RESTORE_APP_LAUNCH_HANDLER_H_
diff --git a/chrome/browser/chromeos/full_restore/full_restore_service.cc b/chrome/browser/chromeos/full_restore/full_restore_service.cc
index a6b8ab78..8e86c53 100644
--- a/chrome/browser/chromeos/full_restore/full_restore_service.cc
+++ b/chrome/browser/chromeos/full_restore/full_restore_service.cc
@@ -7,6 +7,7 @@
 #include "ash/public/cpp/notification_utils.h"
 #include "base/strings/string_util.h"
 #include "chrome/app/vector_icons/vector_icons.h"
+#include "chrome/browser/chromeos/full_restore/app_launch_handler.h"
 #include "chrome/browser/chromeos/full_restore/full_restore_prefs.h"
 #include "chrome/browser/chromeos/full_restore/full_restore_service_factory.h"
 #include "chrome/browser/chromeos/full_restore/new_user_restore_pref_handler.h"
@@ -29,7 +30,9 @@
 const char kRestoreForCrashNotificationId[] = "restore_for_crash_notification";
 const char kRestoreNotificationId[] = "restore_notification";
 
-FullRestoreService::FullRestoreService(Profile* profile) : profile_(profile) {
+FullRestoreService::FullRestoreService(Profile* profile)
+    : profile_(profile),
+      app_launch_handler_(std::make_unique<AppLaunchHandler>(profile_)) {
   // If the system crashed before reboot, show the restore notification.
   if (profile->GetLastSessionExitType() == Profile::EXIT_CRASHED) {
     ShowRestoreNotification(kRestoreForCrashNotificationId);
@@ -140,8 +143,7 @@
         user->GetAccountId(), true);
   }
 
-  // TODO(crbug.com/909794): Implement the restoration. And move the heavy load
-  // out of this KeyedService class.
+  app_launch_handler_->SetShouldRestore();
 }
 
 }  // namespace full_restore
diff --git a/chrome/browser/chromeos/full_restore/full_restore_service.h b/chrome/browser/chromeos/full_restore/full_restore_service.h
index 59bd750..fcbec4f 100644
--- a/chrome/browser/chromeos/full_restore/full_restore_service.h
+++ b/chrome/browser/chromeos/full_restore/full_restore_service.h
@@ -17,6 +17,7 @@
 namespace chromeos {
 namespace full_restore {
 
+class AppLaunchHandler;
 class NewUserRestorePrefHandler;
 
 extern const char kRestoreForCrashNotificationId[];
@@ -65,6 +66,10 @@
 
   std::unique_ptr<NewUserRestorePrefHandler> new_user_pref_handler_;
 
+  // |app_launch_handler_| is responsible for launching apps based on the
+  // restore data.
+  std::unique_ptr<AppLaunchHandler> app_launch_handler_;
+
   base::WeakPtrFactory<FullRestoreService> weak_ptr_factory_{this};
 };
 
diff --git a/chrome/browser/chromeos/lock_screen_apps/toast_dialog_view.cc b/chrome/browser/chromeos/lock_screen_apps/toast_dialog_view.cc
index 7eb0c21f..e458f342 100644
--- a/chrome/browser/chromeos/lock_screen_apps/toast_dialog_view.cc
+++ b/chrome/browser/chromeos/lock_screen_apps/toast_dialog_view.cc
@@ -40,10 +40,13 @@
 }  // namespace
 
 ToastDialogView::ToastDialogView(const base::string16& app_name,
-                                 base::OnceClosure dismissed_callback)
-    : app_name_(app_name) {
-  DialogDelegate::SetButtons(ui::DIALOG_BUTTON_NONE);
-  DialogDelegate::SetCloseCallback(std::move(dismissed_callback));
+                                 base::OnceClosure dismissed_callback) {
+  SetButtons(ui::DIALOG_BUTTON_NONE);
+  SetCloseCallback(std::move(dismissed_callback));
+  SetModalType(ui::MODAL_TYPE_NONE);
+  SetShowCloseButton(true);
+  SetTitle(l10n_util::GetStringFUTF16(
+      IDS_LOCK_SCREEN_NOTE_APP_TOAST_DIALOG_TITLE, app_name));
 
   chrome::RecordDialogCreation(
       chrome::DialogIdentifier::LOCK_SCREEN_NOTE_APP_TOAST);
@@ -59,7 +62,7 @@
 
   SetLayoutManager(std::make_unique<views::FillLayout>());
   auto* label = new views::Label(l10n_util::GetStringFUTF16(
-      IDS_LOCK_SCREEN_NOTE_APP_TOAST_DIALOG_MESSAGE, app_name_));
+      IDS_LOCK_SCREEN_NOTE_APP_TOAST_DIALOG_MESSAGE, app_name));
   label->SetMultiLine(true);
   label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
   label->SetEnabledColor(SkColorSetARGB(138, 0, 0, 0));
@@ -75,15 +78,6 @@
 
 ToastDialogView::~ToastDialogView() = default;
 
-ui::ModalType ToastDialogView::GetModalType() const {
-  return ui::MODAL_TYPE_NONE;
-}
-
-base::string16 ToastDialogView::GetWindowTitle() const {
-  return l10n_util::GetStringFUTF16(IDS_LOCK_SCREEN_NOTE_APP_TOAST_DIALOG_TITLE,
-                                    app_name_);
-}
-
 void ToastDialogView::AddedToWidget() {
   std::unique_ptr<views::Label> title =
       views::BubbleFrameView::CreateDefaultTitleLabel(GetWindowTitle());
@@ -92,10 +86,6 @@
   GetBubbleFrameView()->SetTitleView(std::move(title));
 }
 
-bool ToastDialogView::ShouldShowCloseButton() const {
-  return true;
-}
-
 void ToastDialogView::OnBeforeBubbleWidgetInit(
     views::Widget::InitParams* params,
     views::Widget* widget) const {
diff --git a/chrome/browser/chromeos/lock_screen_apps/toast_dialog_view.h b/chrome/browser/chromeos/lock_screen_apps/toast_dialog_view.h
index 6b52794e..efc63e17 100644
--- a/chrome/browser/chromeos/lock_screen_apps/toast_dialog_view.h
+++ b/chrome/browser/chromeos/lock_screen_apps/toast_dialog_view.h
@@ -22,17 +22,11 @@
   ~ToastDialogView() override;
 
   // views::BubbleDialogDelegateView:
-  ui::ModalType GetModalType() const override;
-  base::string16 GetWindowTitle() const override;
   void AddedToWidget() override;
-  bool ShouldShowCloseButton() const override;
   void OnBeforeBubbleWidgetInit(views::Widget::InitParams* params,
                                 views::Widget* widget) const override;
 
  private:
-  // The name of the app for which the dialog is shown.
-  const base::string16 app_name_;
-
   // Callback to be called when the user closes the dialog.
   base::OnceClosure dismissed_callback_;
 
diff --git a/chrome/browser/chromeos/login/easy_unlock/easy_unlock_screenlock_state_handler.cc b/chrome/browser/chromeos/login/easy_unlock/easy_unlock_screenlock_state_handler.cc
index 43b29ca..492bf7a 100644
--- a/chrome/browser/chromeos/login/easy_unlock/easy_unlock_screenlock_state_handler.cc
+++ b/chrome/browser/chromeos/login/easy_unlock/easy_unlock_screenlock_state_handler.cc
@@ -322,6 +322,8 @@
   }
 
   bool autoshow = true;
+  // TODO(crbug.com/1152491): Only call into SetHasShownLoginDisabledMessage()
+  // if this is a signin screen, not lock screen, context.
   if (hardlock_state_ == LOGIN_DISABLED) {
     // If Signin with Smart Lock is disabled, only automatically show the
     // tooltip if it hasn't been shown yet. See https://crbug.com/848893 for
diff --git a/chrome/browser/chromeos/printing/printer_configurer.cc b/chrome/browser/chromeos/printing/printer_configurer.cc
index 3f0062d..ad392f0 100644
--- a/chrome/browser/chromeos/printing/printer_configurer.cc
+++ b/chrome/browser/chromeos/printing/printer_configurer.cc
@@ -103,17 +103,6 @@
   }
 }
 
-// Records whether a |printer| contains a valid PpdReference defined as having
-// either autoconf or a ppd reference set.
-void RecordValidPpdReference(const Printer& printer) {
-  const auto& ppd_ref = printer.ppd_reference();
-  // A PpdReference is valid if exactly one field is set in PpdReference.
-  int refs = ppd_ref.autoconf ? 1 : 0;
-  refs += !ppd_ref.user_supplied_ppd_url.empty() ? 1 : 0;
-  refs += !ppd_ref.effective_make_and_model.empty() ? 1 : 0;
-  base::UmaHistogramBoolean("Printing.CUPS.ValidPpdReference", refs == 1);
-}
-
 // Configures printers by downloading PPDs then adding them to CUPS through
 // debugd.  This class must be used on the UI thread.
 class PrinterConfigurerImpl : public PrinterConfigurer {
@@ -129,8 +118,6 @@
     DCHECK(!printer.id().empty());
     DCHECK(printer.HasUri());
     PRINTER_LOG(USER) << printer.make_and_model() << " Printer setup requested";
-    // Record if autoconf and a PPD are set.  crbug.com/814374.
-    RecordValidPpdReference(printer);
 
     if (!printer.IsIppEverywhere()) {
       PRINTER_LOG(DEBUG) << printer.make_and_model() << " Lookup PPD";
diff --git a/chrome/browser/error_reporting/BUILD.gn b/chrome/browser/error_reporting/BUILD.gn
index 101eb5e2..15cff0c9 100644
--- a/chrome/browser/error_reporting/BUILD.gn
+++ b/chrome/browser/error_reporting/BUILD.gn
@@ -14,10 +14,12 @@
   deps = [
     "//base",
     "//build:chromeos_buildflags",
+    "//chrome/common:constants",
     "//components/crash/content/browser/error_reporting",
     "//components/crash/core/app",
     "//components/feedback",
     "//components/startup_metric_utils/browser",
+    "//components/upload_list",
     "//content/public/browser",
     "//net",
     "//services/network:network_service",
@@ -34,6 +36,9 @@
   deps = [
     ":error_reporting",
     "//base",
+    "//base/test:test_support",
+    "//build:chromeos_buildflags",
+    "//chrome/common:constants",
     "//components/crash/content/browser/error_reporting:mock_crash_endpoint",
   ]
 }
@@ -49,6 +54,7 @@
     "//components/crash/content/browser/error_reporting",
     "//components/crash/content/browser/error_reporting:mock_crash_endpoint",
     "//components/crash/core/app",
+    "//components/upload_list",
     "//content/test:test_support",
     "//net:test_support",
     "//testing/gtest",
diff --git a/chrome/browser/error_reporting/chrome_js_error_report_processor.cc b/chrome/browser/error_reporting/chrome_js_error_report_processor.cc
index 9e2f876..ff6454ce 100644
--- a/chrome/browser/error_reporting/chrome_js_error_report_processor.cc
+++ b/chrome/browser/error_reporting/chrome_js_error_report_processor.cc
@@ -10,8 +10,11 @@
 
 #include "base/callback.h"
 #include "base/callback_helpers.h"
+#include "base/files/file.h"
+#include "base/files/file_path.h"
 #include "base/logging.h"
 #include "base/memory/scoped_refptr.h"
+#include "base/path_service.h"
 #include "base/strings/strcat.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/stringprintf.h"
@@ -20,11 +23,13 @@
 #include "base/task/thread_pool.h"
 #include "base/time/default_clock.h"
 #include "build/build_config.h"
-#include "build/chromeos_buildflags.h"
+#include "chrome/common/chrome_paths.h"
 #include "components/crash/content/browser/error_reporting/javascript_error_report.h"
 #include "components/crash/core/app/client_upload_info.h"
+#include "components/crash/core/app/crashpad.h"
 #include "components/feedback/redaction_tool.h"
 #include "components/startup_metric_utils/browser/startup_metric_utils.h"
+#include "components/upload_list/crash_upload_list.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
@@ -100,17 +105,58 @@
     : clock_(base::DefaultClock::GetInstance()) {}
 ChromeJsErrorReportProcessor::~ChromeJsErrorReportProcessor() = default;
 
+#if !BUILDFLAG(IS_CHROMEOS_ASH) && !BUILDFLAG(IS_CHROMEOS_LACROS)
+void ChromeJsErrorReportProcessor::UpdateReportDatabase(
+    std::string remote_report_id,
+    base::Time report_time) {
+  // Uploads.log format is "seconds_since_epoch,crash_id\n"
+  base::FilePath crash_dir_path;
+  if (!base::PathService::Get(chrome::DIR_CRASH_DUMPS, &crash_dir_path)) {
+    VLOG(1) << "Nowhere to write uploads.log";
+    return;
+  }
+  base::FilePath upload_log_path =
+      crash_dir_path.AppendASCII(CrashUploadList::kReporterLogFilename);
+  base::File upload_log(upload_log_path,
+                        base::File::FLAG_OPEN_ALWAYS | base::File::FLAG_APPEND);
+  if (!upload_log.IsValid()) {
+    VLOG(1) << "Could not open upload.log: "
+            << base::File::ErrorToString(upload_log.error_details());
+    return;
+  }
+  std::string line = base::StrCat({base::NumberToString(report_time.ToTimeT()),
+                                   ",", remote_report_id, "\n"});
+  // WriteAtCurrentPos because O_APPEND.
+  if (upload_log.WriteAtCurrentPos(line.c_str(), line.length()) !=
+      static_cast<int>(line.length())) {
+    VLOG(1) << "Could not write to upload.log";
+    return;
+  }
+}
+#endif  // !BUILDFLAG(IS_CHROMEOS_ASH) && !BUILDFLAG(IS_CHROMEOS_LACROS)
+
 void ChromeJsErrorReportProcessor::OnRequestComplete(
     std::unique_ptr<network::SimpleURLLoader> url_loader,
     base::ScopedClosureRunner callback_runner,
+    base::Time report_time,
     std::unique_ptr<std::string> response_body) {
   if (response_body) {
-    // TODO(iby): Update the crash log (uploads.log)
     VLOG(1) << "Uploaded crash report. ID: " << *response_body;
+    // On Chrome OS, we use a different format than other platforms. Since we
+    // will soon not call this function at all on Chrome OS (crbug.com/986166),
+    // don't bother writing code to write to that format.
+#if !BUILDFLAG(IS_CHROMEOS_ASH) && !BUILDFLAG(IS_CHROMEOS_LACROS)
+    base::ThreadPool::PostTaskAndReply(
+        FROM_HERE, {base::MayBlock()},
+        base::BindOnce(&ChromeJsErrorReportProcessor::UpdateReportDatabase,
+                       this, *response_body, report_time),
+        callback_runner.Release());
+#endif  // !BUILDFLAG(IS_CHROMEOS_ASH) && !BUILDFLAG(IS_CHROMEOS_LACROS)
   } else {
     LOG(ERROR) << "Failed to upload crash report";
   }
-  // callback_runner will implicitly run the callback when we reach this line.
+  // callback_runner may implicitly run the callback when we reach this line if
+  // we didn't add a task to update the report database.
 }
 
 // Returns the redacted, fixed-up error report if the user consented to have it
@@ -166,6 +212,7 @@
     const GURL& url,
     const std::string& body,
     base::ScopedClosureRunner callback_runner,
+    base::Time report_time,
     network::SharedURLLoaderFactory* loader_factory) {
   auto resource_request = std::make_unique<network::ResourceRequest>();
   resource_request->method = "POST";
@@ -220,7 +267,8 @@
   loader->DownloadToString(
       loader_factory,
       base::BindOnce(&ChromeJsErrorReportProcessor::OnRequestComplete, this,
-                     std::move(url_loader), std::move(callback_runner)),
+                     std::move(url_loader), std::move(callback_runner),
+                     report_time),
       kCrashEndpointResponseMaxSizeInBytes);
 }
 
@@ -229,6 +277,7 @@
     base::ScopedClosureRunner callback_runner,
     scoped_refptr<network::SharedURLLoaderFactory> loader_factory,
     base::TimeDelta browser_process_uptime,
+    base::Time report_time,
     base::Optional<JavaScriptErrorReport> error_report) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   if (!error_report) {
@@ -292,7 +341,8 @@
     body = std::move(*error_report->stack_trace);
   }
 
-  SendReport(url, body, std::move(callback_runner), loader_factory.get());
+  SendReport(url, body, std::move(callback_runner), report_time,
+             loader_factory.get());
 }
 
 void ChromeJsErrorReportProcessor::CheckAndUpdateRecentErrorReports(
@@ -417,7 +467,8 @@
                      std::move(error_report)),
       base::BindOnce(&ChromeJsErrorReportProcessor::OnConsentCheckCompleted,
                      this, std::move(callback_runner),
-                     std::move(loader_factory), browser_process_uptime));
+                     std::move(loader_factory), browser_process_uptime,
+                     clock_->Now()));
 }
 
 std::string ChromeJsErrorReportProcessor::GetCrashEndpoint() {
diff --git a/chrome/browser/error_reporting/chrome_js_error_report_processor.h b/chrome/browser/error_reporting/chrome_js_error_report_processor.h
index bf1cb2d9..1ac534e 100644
--- a/chrome/browser/error_reporting/chrome_js_error_report_processor.h
+++ b/chrome/browser/error_reporting/chrome_js_error_report_processor.h
@@ -15,6 +15,7 @@
 #include "base/containers/flat_map.h"
 #include "base/time/clock.h"
 #include "base/time/time.h"
+#include "build/chromeos_buildflags.h"
 #include "components/crash/content/browser/error_reporting/js_error_report_processor.h"
 
 namespace content {
@@ -68,11 +69,20 @@
                             int32_t& os_minor_version,
                             int32_t& os_bugfix_version);
 
+#if !BUILDFLAG(IS_CHROMEOS_ASH) && !BUILDFLAG(IS_CHROMEOS_LACROS)
+  // Update the uploads.log file with a record of this error report. This
+  // ensures that the error appears on chrome://crashes and is listed in the
+  // feedback reports.
+  virtual void UpdateReportDatabase(std::string remote_report_id,
+                                    base::Time report_time);
+#endif  // !BUILDFLAG(IS_CHROMEOS_ASH) && !BUILDFLAG(IS_CHROMEOS_LACROS)
+
  private:
   struct PlatformInfo;
 
   void OnRequestComplete(std::unique_ptr<network::SimpleURLLoader> url_loader,
                          base::ScopedClosureRunner callback_runner,
+                         base::Time report_time,
                          std::unique_ptr<std::string> response_body);
 
   base::Optional<JavaScriptErrorReport> CheckConsentAndRedact(
@@ -83,12 +93,14 @@
   void SendReport(const GURL& url,
                   const std::string& body,
                   base::ScopedClosureRunner callback_runner,
+                  base::Time report_time,
                   network::SharedURLLoaderFactory* loader_factory);
 
   void OnConsentCheckCompleted(
       base::ScopedClosureRunner callback_runner,
       scoped_refptr<network::SharedURLLoaderFactory> loader_factory,
       base::TimeDelta browser_process_uptime,
+      base::Time report_time,
       base::Optional<JavaScriptErrorReport> error_report);
 
   // To avoid spamming the error collection system, do not send duplicate
diff --git a/chrome/browser/error_reporting/chrome_js_error_report_processor_unittest.cc b/chrome/browser/error_reporting/chrome_js_error_report_processor_unittest.cc
index 0be985020..6df0d6d9 100644
--- a/chrome/browser/error_reporting/chrome_js_error_report_processor_unittest.cc
+++ b/chrome/browser/error_reporting/chrome_js_error_report_processor_unittest.cc
@@ -9,12 +9,18 @@
 
 #include "base/callback.h"
 #include "base/callback_helpers.h"
+#include "base/strings/strcat.h"
+#include "base/strings/utf_string_conversions.h"
 #include "base/test/simple_test_clock.h"
 #include "build/build_config.h"
+#include "chrome/browser/crash_upload_list/crash_upload_list.h"
 #include "chrome/browser/error_reporting/mock_chrome_js_error_report_processor.h"
+#include "chrome/common/chrome_paths.h"
 #include "chrome/test/base/testing_profile.h"
 #include "components/crash/content/browser/error_reporting/javascript_error_report.h"
 #include "components/crash/content/browser/error_reporting/mock_crash_endpoint.h"
+#include "components/crash/core/app/crashpad.h"
+#include "components/upload_list/upload_list.h"
 #include "content/public/test/browser_task_environment.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -38,7 +44,7 @@
 
   void SetUp() override {
     // Set clock to something arbitrary which is not the null value.
-    test_clock_.SetNow(base::Time::FromTimeT(1586581472));
+    test_clock_.SetNow(base::Time::FromTimeT(kFakeNow));
     test_server_ = std::make_unique<net::test_server::EmbeddedTestServer>();
     endpoint_ = std::make_unique<MockCrashEndpoint>(test_server_.get());
     processor_->SetCrashEndpoint(endpoint_->GetCrashEndpointURL());
@@ -73,6 +79,7 @@
   bool finish_callback_was_called_ = false;
   scoped_refptr<MockChromeJsErrorReportProcessor> processor_;
 
+  static constexpr time_t kFakeNow = 1586581472;
   static constexpr char kFirstMessage[] = "An Error Is Me";
   static constexpr char kFirstMessageQuery[] =
       "error_message=An%20Error%20Is%20Me";
@@ -85,6 +92,7 @@
   static constexpr char kSecondProduct[] = "Chrome_Linux";
 };
 
+constexpr time_t ChromeJsErrorReportProcessorTest::kFakeNow;
 constexpr char ChromeJsErrorReportProcessorTest::kFirstMessage[];
 constexpr char ChromeJsErrorReportProcessorTest::kFirstMessageQuery[];
 constexpr char ChromeJsErrorReportProcessorTest::kSecondMessage[];
@@ -421,3 +429,82 @@
   SendErrorReport(std::move(report3));
   EXPECT_EQ(endpoint_->report_count(), 3);
 }
+
+#if !BUILDFLAG(IS_CHROMEOS_ASH) && !BUILDFLAG(IS_CHROMEOS_LACROS)
+static std::string UploadInfoStateToString(
+    UploadList::UploadInfo::State state) {
+  switch (state) {
+    case UploadList::UploadInfo::State::NotUploaded:
+      return "NotUploaded";
+    case UploadList::UploadInfo::State::Pending:
+      return "Pending";
+    case UploadList::UploadInfo::State::Pending_UserRequested:
+      return "Pending_UserRequested";
+    case UploadList::UploadInfo::State::Uploaded:
+      return "Uploaded";
+    default:
+      return base::StrCat({"Unknown upload state ",
+                           base::NumberToString(static_cast<int>(state))});
+  }
+}
+
+static std::string UploadInfoVectorToString(
+    const std::vector<UploadList::UploadInfo>& uploads) {
+  std::string result = "[";
+  bool first = true;
+  for (const UploadList::UploadInfo& upload : uploads) {
+    if (first) {
+      first = false;
+    } else {
+      result += ", ";
+    }
+    base::StrAppend(&result,
+                    {"{state ", UploadInfoStateToString(upload.state),
+                     ", upload_id ", upload.upload_id, ", upload_time ",
+                     base::NumberToString(upload.upload_time.ToTimeT()),
+                     ", local_id ", upload.local_id, ", capture_time ",
+                     base::NumberToString(upload.capture_time.ToTimeT()),
+                     ", source ", upload.source, ", file size ",
+                     base::UTF16ToUTF8(upload.file_size), "}"});
+  }
+  result += "]";
+  return result;
+}
+
+TEST_F(ChromeJsErrorReportProcessorTest, UpdatesUploadsLog) {
+  if (crash_reporter::IsCrashpadEnabled()) {
+    // TODO(crbug.com/1162356): Combine uploads.log with Crashpad database when
+    // getting list of crashes.
+    GTEST_SKIP();
+  }
+
+  base::ScopedPathOverride crash_dir_override(chrome::DIR_CRASH_DUMPS);
+  processor_->set_update_report_database(true);
+
+  constexpr char kCrashId[] = "123abc456def";
+  endpoint_->set_response(net::HTTP_OK, kCrashId);
+
+  SendErrorReport(MakeErrorReport(kFirstMessage));
+  EXPECT_EQ(endpoint_->report_count(), 1);
+
+  auto upload_list = CreateCrashUploadList();
+  base::RunLoop run_loop;
+  upload_list->Load(run_loop.QuitClosure());
+  run_loop.Run();
+  std::vector<UploadList::UploadInfo> uploads;
+  upload_list->GetUploads(50, &uploads);
+  EXPECT_EQ(uploads.size(), 1U) << UploadInfoVectorToString(uploads);
+
+  bool found = false;
+  for (const UploadList::UploadInfo& upload : uploads) {
+    if (upload.state == UploadList::UploadInfo::State::Uploaded &&
+        upload.upload_id == kCrashId) {
+      EXPECT_FALSE(found) << "Found twice";
+      found = true;
+      EXPECT_EQ(upload.upload_time.ToTimeT(), kFakeNow);
+    }
+  }
+  EXPECT_TRUE(found) << "Didn't find upload record in "
+                     << UploadInfoVectorToString(uploads);
+}
+#endif  // !BUILDFLAG(IS_CHROMEOS_ASH) && !BUILDFLAG(IS_CHROMEOS_LACROS)
diff --git a/chrome/browser/error_reporting/mock_chrome_js_error_report_processor.cc b/chrome/browser/error_reporting/mock_chrome_js_error_report_processor.cc
index 0f155c82..69ecc89c 100644
--- a/chrome/browser/error_reporting/mock_chrome_js_error_report_processor.cc
+++ b/chrome/browser/error_reporting/mock_chrome_js_error_report_processor.cc
@@ -9,6 +9,7 @@
 #include "components/crash/content/browser/error_reporting/mock_crash_endpoint.h"
 
 MockChromeJsErrorReportProcessor::MockChromeJsErrorReportProcessor() = default;
+
 MockChromeJsErrorReportProcessor::~MockChromeJsErrorReportProcessor() = default;
 
 void MockChromeJsErrorReportProcessor::SetAsDefault() {
@@ -50,6 +51,17 @@
   os_bugfix_version = 1;
 }
 
+#if !BUILDFLAG(IS_CHROMEOS_ASH) && !BUILDFLAG(IS_CHROMEOS_LACROS)
+void MockChromeJsErrorReportProcessor::UpdateReportDatabase(
+    std::string remote_report_id,
+    base::Time report_time) {
+  if (update_report_database_) {
+    ChromeJsErrorReportProcessor::UpdateReportDatabase(
+        std::move(remote_report_id), report_time);
+  }
+}
+#endif  //  !BUILDFLAG(IS_CHROMEOS_ASH) && !BUILDFLAG(IS_CHROMEOS_LACROS)
+
 ScopedMockChromeJsErrorReportProcessor::ScopedMockChromeJsErrorReportProcessor(
     const MockCrashEndpoint& endpoint)
     : processor_(base::MakeRefCounted<MockChromeJsErrorReportProcessor>()),
diff --git a/chrome/browser/error_reporting/mock_chrome_js_error_report_processor.h b/chrome/browser/error_reporting/mock_chrome_js_error_report_processor.h
index 0925989a..ca84262 100644
--- a/chrome/browser/error_reporting/mock_chrome_js_error_report_processor.h
+++ b/chrome/browser/error_reporting/mock_chrome_js_error_report_processor.h
@@ -7,9 +7,12 @@
 
 #include <stdint.h>
 
+#include <memory>
 #include <string>
 
 #include "base/memory/scoped_refptr.h"
+#include "base/test/scoped_path_override.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/error_reporting/chrome_js_error_report_processor.h"
 
 class MockCrashEndpoint;
@@ -31,6 +34,17 @@
   // return the given (other) JsErrorReportProcessor.
   static void SetDefaultTo(scoped_refptr<JsErrorReportProcessor> new_default);
 
+#if !BUILDFLAG(IS_CHROMEOS_ASH) && !BUILDFLAG(IS_CHROMEOS_LACROS)
+  // By default, a MockChromeJsErrorReportProcessor will suppress the updating
+  // of the crash database (a.k.a. uploads.log) to avoid contaminating the real
+  // database with test uploads. Set |update_report_database| to true to have
+  // ChromeJsErrorReportProcessor::UpdateReportDatabase called like it normally
+  // would be.
+  void set_update_report_database(bool update_report_database) {
+    update_report_database_ = update_report_database;
+  }
+#endif
+
  protected:
   std::string GetCrashEndpoint() override;
   std::string GetCrashEndpointStaging() override;
@@ -40,10 +54,18 @@
                     int32_t& os_minor_version,
                     int32_t& os_bugfix_version) override;
 
+#if !BUILDFLAG(IS_CHROMEOS_ASH) && !BUILDFLAG(IS_CHROMEOS_LACROS)
+  void UpdateReportDatabase(std::string remote_report_id,
+                            base::Time report_time) override;
+#endif
+
  private:
   ~MockChromeJsErrorReportProcessor() override;
   std::string crash_endpoint_;
   std::string crash_endpoint_staging_;
+#if !BUILDFLAG(IS_CHROMEOS_ASH) && !BUILDFLAG(IS_CHROMEOS_LACROS)
+  bool update_report_database_ = false;
+#endif
 };
 
 // Wrapper for MockChromeJsErrorReportProcessor. Will automatically create, set
@@ -63,6 +85,8 @@
   // JsErrorReportProcessor::Get() will return nullptr.
   ~ScopedMockChromeJsErrorReportProcessor();
 
+  MockChromeJsErrorReportProcessor& processor() const { return *processor_; }
+
  private:
   scoped_refptr<MockChromeJsErrorReportProcessor> processor_;
   scoped_refptr<JsErrorReportProcessor> previous_;
diff --git a/chrome/browser/extensions/extension_webkit_preferences.cc b/chrome/browser/extensions/extension_webkit_preferences.cc
index 4155812..4a11471 100644
--- a/chrome/browser/extensions/extension_webkit_preferences.cc
+++ b/chrome/browser/extensions/extension_webkit_preferences.cc
@@ -34,6 +34,8 @@
     webkit_prefs->local_storage_enabled = false;
     webkit_prefs->sync_xhr_in_documents_enabled = false;
     webkit_prefs->cookie_enabled = false;
+    webkit_prefs->target_blank_implies_no_opener_enabled_will_be_removed =
+        false;
   }
 
   // Enable WebGL features that regular pages can't access, since they add
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index aa12241..84cee16 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -450,7 +450,7 @@
   {
     "name": "calculate-native-win-occlusion",
     "owners": [ "davidbienvenu", "fdoray" ],
-    "expiry_milestone": 88
+    "expiry_milestone": 92
   },
   {
     "name": "camera-system-web-app",
diff --git a/chrome/browser/lacros/crosapi_pref_observer.cc b/chrome/browser/lacros/crosapi_pref_observer.cc
new file mode 100644
index 0000000..3fc83f1d
--- /dev/null
+++ b/chrome/browser/lacros/crosapi_pref_observer.cc
@@ -0,0 +1,26 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/lacros/crosapi_pref_observer.h"
+
+#include "base/callback.h"
+#include "chromeos/lacros/lacros_chrome_service_impl.h"
+
+CrosapiPrefObserver::CrosapiPrefObserver(crosapi::mojom::PrefPath path,
+                                         PrefChangedCallback callback)
+    : callback_(std::move(callback)) {
+  auto* lacros_service = chromeos::LacrosChromeServiceImpl::Get();
+  if (!lacros_service->IsPrefsAvailable()) {
+    LOG(WARNING) << "crosapi: Prefs API not available";
+    return;
+  }
+  lacros_service->prefs_remote()->AddObserver(
+      path, receiver_.BindNewPipeAndPassRemote());
+}
+
+CrosapiPrefObserver::~CrosapiPrefObserver() = default;
+
+void CrosapiPrefObserver::OnPrefChanged(base::Value value) {
+  callback_.Run(std::move(value));
+}
diff --git a/chrome/browser/lacros/crosapi_pref_observer.h b/chrome/browser/lacros/crosapi_pref_observer.h
new file mode 100644
index 0000000..79a8a41
--- /dev/null
+++ b/chrome/browser/lacros/crosapi_pref_observer.h
@@ -0,0 +1,38 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_LACROS_CROSAPI_PREF_OBSERVER_H_
+#define CHROME_BROWSER_LACROS_CROSAPI_PREF_OBSERVER_H_
+
+#include "base/callback_forward.h"
+#include "base/gtest_prod_util.h"
+#include "base/values.h"
+#include "chromeos/crosapi/mojom/prefs.mojom.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+
+// Helper to simplify the crosapi::mojom::PrefObserver API.
+// Observes ash-chrome for changes in specified pref.
+class CrosapiPrefObserver : public crosapi::mojom::PrefObserver {
+ public:
+  using PrefChangedCallback = base::RepeatingCallback<void(base::Value value)>;
+
+  CrosapiPrefObserver(crosapi::mojom::PrefPath path,
+                      PrefChangedCallback callback);
+  CrosapiPrefObserver(const CrosapiPrefObserver&) = delete;
+  CrosapiPrefObserver& operator=(const CrosapiPrefObserver&) = delete;
+  ~CrosapiPrefObserver() override;
+
+ private:
+  FRIEND_TEST_ALL_PREFIXES(CrosapiPrefObserverLacrosBrowserTest, Basics);
+
+  // crosapi::mojom::PrefObserver:
+  void OnPrefChanged(base::Value value) override;
+
+  PrefChangedCallback callback_;
+
+  // Receives mojo messages from ash.
+  mojo::Receiver<crosapi::mojom::PrefObserver> receiver_{this};
+};
+
+#endif  // CHROME_BROWSER_LACROS_CROSAPI_PREF_OBSERVER_H_
diff --git a/chrome/browser/lacros/crosapi_pref_observer_lacros_browsertest.cc b/chrome/browser/lacros/crosapi_pref_observer_lacros_browsertest.cc
new file mode 100644
index 0000000..801294a
--- /dev/null
+++ b/chrome/browser/lacros/crosapi_pref_observer_lacros_browsertest.cc
@@ -0,0 +1,46 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/run_loop.h"
+#include "base/test/bind.h"
+#include "base/values.h"
+#include "chrome/browser/lacros/crosapi_pref_observer.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "content/public/test/browser_test.h"
+
+using CrosapiPrefObserverLacrosBrowserTest = InProcessBrowserTest;
+
+// Tests multiple observers reading ash::kAccessibilitySpokenFeedbackEnabled.
+// TODO(crbug.com/1157314): Not safe to run with other test since this assumes
+// the pref is false and does not change during test.
+IN_PROC_BROWSER_TEST_F(CrosapiPrefObserverLacrosBrowserTest, Basics) {
+  // Register an observer.
+  bool value1 = true;
+  base::RunLoop run_loop1;
+  CrosapiPrefObserver observer1(
+      crosapi::mojom::PrefPath::kAccessibilitySpokenFeedbackEnabled,
+      base::BindLambdaForTesting([&](base::Value value) {
+        value1 = value.GetBool();
+        run_loop1.Quit();
+      }));
+  run_loop1.Run();
+  EXPECT_FALSE(value1);
+
+  // Additional observers are OK.
+  bool value2 = true;
+  base::RunLoop run_loop2;
+  CrosapiPrefObserver observer2(
+      crosapi::mojom::PrefPath::kAccessibilitySpokenFeedbackEnabled,
+      base::BindLambdaForTesting([&](base::Value value) {
+        value2 = value.GetBool();
+        run_loop2.Quit();
+      }));
+  run_loop2.Run();
+  EXPECT_FALSE(value2);
+
+  // OnPrefChanged should fire callback.
+  observer1.OnPrefChanged(base::Value(true));
+  EXPECT_TRUE(value1);
+  EXPECT_FALSE(value2);
+}
diff --git a/chrome/browser/lifetime/browser_close_manager_browsertest.cc b/chrome/browser/lifetime/browser_close_manager_browsertest.cc
index 6a199dc0..c964d00 100644
--- a/chrome/browser/lifetime/browser_close_manager_browsertest.cc
+++ b/chrome/browser/lifetime/browser_close_manager_browsertest.cc
@@ -398,11 +398,16 @@
 
 // Test that the tab closed after the aborted shutdown attempt is not re-opened
 // when restoring the session.
-// Flaky on Windows trybots, see https://crbug.com/737860.
 // Flaky on chromium.chromeos, chromium.linux, and chromium.mac bots. See
-// https://crbug.com/1145235.
+// https://crbug.com/1145235. It was flaky on Windows, but  crrev.com/c/2559156,
+// which added retries to ReplaceFile, should fix the Windows flakiness.
+#if defined(OS_WIN)
+#define MAYBE_TestSessionRestore TestSessionRestore
+#else
+#define MAYBE_TestSessionRestore DISABLED_TestSessionRestore
+#endif
 IN_PROC_BROWSER_TEST_F(BrowserCloseManagerBrowserTest,
-                       DISABLED_TestSessionRestore) {
+                       MAYBE_TestSessionRestore) {
   // The testing framework launches Chrome with about:blank as args.
   EXPECT_EQ(2, browser()->tab_strip_model()->count());
   EXPECT_EQ(GURL(chrome::kChromeUIVersionURL),
diff --git a/chrome/browser/optimization_guide/optimization_guide_keyed_service.cc b/chrome/browser/optimization_guide/optimization_guide_keyed_service.cc
index 73d21d4..f5df625 100644
--- a/chrome/browser/optimization_guide/optimization_guide_keyed_service.cc
+++ b/chrome/browser/optimization_guide/optimization_guide_keyed_service.cc
@@ -17,7 +17,6 @@
 #include "chrome/browser/optimization_guide/optimization_guide_hints_manager.h"
 #include "chrome/browser/optimization_guide/optimization_guide_keyed_service_factory.h"
 #include "chrome/browser/optimization_guide/optimization_guide_navigation_data.h"
-#include "chrome/browser/optimization_guide/optimization_guide_session_statistic.h"
 #include "chrome/browser/optimization_guide/optimization_guide_top_host_provider.h"
 #include "chrome/browser/optimization_guide/prediction/prediction_manager.h"
 #include "chrome/browser/profiles/profile.h"
diff --git a/chrome/browser/optimization_guide/prediction/prediction_manager.cc b/chrome/browser/optimization_guide/prediction/prediction_manager.cc
index ed87a42..5f0d201 100644
--- a/chrome/browser/optimization_guide/prediction/prediction_manager.cc
+++ b/chrome/browser/optimization_guide/prediction/prediction_manager.cc
@@ -13,6 +13,7 @@
 #include "base/metrics/histogram_functions.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/metrics/histogram_macros_local.h"
+#include "base/path_service.h"
 #include "base/rand_util.h"
 #include "base/sequence_checker.h"
 #include "base/sequenced_task_runner.h"
@@ -21,14 +22,14 @@
 #include "base/task/thread_pool.h"
 #include "base/time/default_clock.h"
 #include "chrome/browser/browser_process.h"
+#include "chrome/browser/download/download_service_factory.h"
 #include "chrome/browser/engagement/site_engagement_service.h"
 #include "chrome/browser/optimization_guide/optimization_guide_navigation_data.h"
 #include "chrome/browser/optimization_guide/optimization_guide_permissions_util.h"
-#include "chrome/browser/optimization_guide/optimization_guide_session_statistic.h"
 #include "chrome/browser/optimization_guide/prediction/prediction_model_download_manager.h"
-#include "chrome/browser/optimization_guide/prediction/prediction_model_fetcher.h"
-#include "chrome/browser/optimization_guide/prediction/prediction_model_file.h"
 #include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/profiles/profile_key.h"
+#include "chrome/common/chrome_paths.h"
 #include "components/optimization_guide/content/optimization_guide_decider.h"
 #include "components/optimization_guide/core/optimization_guide_constants.h"
 #include "components/optimization_guide/core/optimization_guide_enums.h"
@@ -38,11 +39,14 @@
 #include "components/optimization_guide/core/optimization_guide_switches.h"
 #include "components/optimization_guide/core/optimization_guide_util.h"
 #include "components/optimization_guide/core/prediction_model.h"
+#include "components/optimization_guide/core/prediction_model_fetcher.h"
+#include "components/optimization_guide/core/prediction_model_file.h"
 #include "components/optimization_guide/core/store_update_data.h"
 #include "components/optimization_guide/core/top_host_provider.h"
 #include "components/optimization_guide/proto/models.pb.h"
 #include "components/prefs/pref_service.h"
 #include "content/public/browser/navigation_handle.h"
+#include "content/public/browser/network_service_instance.h"
 #include "content/public/browser/web_contents.h"
 #include "mojo/public/cpp/bindings/remote.h"
 
@@ -228,13 +232,7 @@
     Profile* profile)
     : host_model_features_cache_(
           std::max(features::MaxHostModelFeaturesCacheSize(), size_t(1))),
-      prediction_model_download_manager_(
-          features::IsModelDownloadingEnabled()
-              ? std::make_unique<PredictionModelDownloadManager>(
-                    profile,
-                    base::ThreadPool::CreateSequencedTaskRunner(
-                        {base::MayBlock(), base::TaskPriority::BEST_EFFORT}))
-              : nullptr),
+      prediction_model_download_manager_(nullptr),
       top_host_provider_(top_host_provider),
       model_and_features_store_(model_and_features_store),
       url_loader_factory_(url_loader_factory),
@@ -242,8 +240,20 @@
       profile_(profile),
       clock_(base::DefaultClock::GetInstance()) {
   DCHECK(model_and_features_store_);
-  if (prediction_model_download_manager_)
+
+  if (features::IsModelDownloadingEnabled()) {
+    base::FilePath models_dir;
+    base::PathService::Get(chrome::DIR_OPTIMIZATION_GUIDE_PREDICTION_MODELS,
+                           &models_dir);
+    prediction_model_download_manager_ =
+        std::make_unique<PredictionModelDownloadManager>(
+            DownloadServiceFactory::GetForKey(profile->GetProfileKey()),
+            models_dir,
+            base::ThreadPool::CreateSequencedTaskRunner(
+                {base::MayBlock(), base::TaskPriority::BEST_EFFORT}));
     prediction_model_download_manager_->AddObserver(this);
+  }
+
   Initialize();
 }
 
@@ -645,7 +655,8 @@
   if (!prediction_model_fetcher_) {
     prediction_model_fetcher_ = std::make_unique<PredictionModelFetcher>(
         url_loader_factory_,
-        features::GetOptimizationGuideServiceGetModelsURL());
+        features::GetOptimizationGuideServiceGetModelsURL(),
+        content::GetNetworkConnectionTracker());
   }
 
   std::vector<proto::ModelInfo> models_info = std::vector<proto::ModelInfo>();
diff --git a/chrome/browser/optimization_guide/prediction/prediction_manager.h b/chrome/browser/optimization_guide/prediction/prediction_manager.h
index 43aca7b..49b1f73 100644
--- a/chrome/browser/optimization_guide/prediction/prediction_manager.h
+++ b/chrome/browser/optimization_guide/prediction/prediction_manager.h
@@ -18,9 +18,9 @@
 #include "base/sequence_checker.h"
 #include "base/time/clock.h"
 #include "base/timer/timer.h"
-#include "chrome/browser/optimization_guide/optimization_guide_session_statistic.h"
 #include "chrome/browser/optimization_guide/prediction/prediction_model_download_observer.h"
 #include "components/optimization_guide/core/optimization_guide_enums.h"
+#include "components/optimization_guide/core/optimization_guide_session_statistic.h"
 #include "components/optimization_guide/proto/models.pb.h"
 #include "services/network/public/cpp/network_quality_tracker.h"
 #include "url/origin.h"
diff --git a/chrome/browser/optimization_guide/prediction/prediction_manager_browsertest.cc b/chrome/browser/optimization_guide/prediction/prediction_manager_browsertest.cc
index eaa05a1..5b72b41 100644
--- a/chrome/browser/optimization_guide/prediction/prediction_manager_browsertest.cc
+++ b/chrome/browser/optimization_guide/prediction/prediction_manager_browsertest.cc
@@ -15,7 +15,6 @@
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/optimization_guide/optimization_guide_keyed_service.h"
 #include "chrome/browser/optimization_guide/optimization_guide_keyed_service_factory.h"
-#include "chrome/browser/optimization_guide/optimization_guide_session_statistic.h"
 #include "chrome/browser/optimization_guide/prediction/prediction_manager.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_key.h"
@@ -26,6 +25,7 @@
 #include "components/metrics/content/subprocess_metrics_provider.h"
 #include "components/optimization_guide/core/optimization_guide_constants.h"
 #include "components/optimization_guide/core/optimization_guide_features.h"
+#include "components/optimization_guide/core/optimization_guide_session_statistic.h"
 #include "components/optimization_guide/core/optimization_guide_store.h"
 #include "components/optimization_guide/core/optimization_guide_switches.h"
 #include "components/optimization_guide/core/optimization_guide_test_util.h"
diff --git a/chrome/browser/optimization_guide/prediction/prediction_manager_unittest.cc b/chrome/browser/optimization_guide/prediction/prediction_manager_unittest.cc
index 5f9847dc..f1dd17a 100644
--- a/chrome/browser/optimization_guide/prediction/prediction_manager_unittest.cc
+++ b/chrome/browser/optimization_guide/prediction/prediction_manager_unittest.cc
@@ -18,7 +18,6 @@
 #include "chrome/browser/optimization_guide/optimization_guide_navigation_data.h"
 #include "chrome/browser/optimization_guide/optimization_guide_web_contents_observer.h"
 #include "chrome/browser/optimization_guide/prediction/prediction_model_download_manager.h"
-#include "chrome/browser/optimization_guide/prediction/prediction_model_fetcher.h"
 #include "chrome/services/machine_learning/public/cpp/test_support/fake_service_connection.h"
 #include "chrome/services/machine_learning/public/mojom/decision_tree.mojom.h"
 #include "chrome/test/base/testing_profile.h"
@@ -30,6 +29,7 @@
 #include "components/optimization_guide/core/optimization_guide_switches.h"
 #include "components/optimization_guide/core/optimization_guide_util.h"
 #include "components/optimization_guide/core/prediction_model.h"
+#include "components/optimization_guide/core/prediction_model_fetcher.h"
 #include "components/optimization_guide/core/proto_database_provider_test_base.h"
 #include "components/optimization_guide/core/top_host_provider.h"
 #include "components/optimization_guide/proto/hint_cache.pb.h"
@@ -41,6 +41,7 @@
 #include "content/public/test/web_contents_tester.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 #include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
+#include "services/network/test/test_network_connection_tracker.h"
 #include "services/network/test/test_url_loader_factory.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/base/page_transition_types.h"
@@ -193,9 +194,10 @@
     : public PredictionModelDownloadManager {
  public:
   FakePredictionModelDownloadManager(
-      Profile* profile,
       scoped_refptr<base::SequencedTaskRunner> task_runner)
-      : PredictionModelDownloadManager(profile, task_runner) {}
+      : PredictionModelDownloadManager(/*download_service=*/nullptr,
+                                       base::FilePath(),
+                                       task_runner) {}
   ~FakePredictionModelDownloadManager() override = default;
 
   void StartDownload(const GURL& url) override {
@@ -230,10 +232,12 @@
  public:
   TestPredictionModelFetcher(
       scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
-      GURL optimization_guide_service_get_models_url,
+      const GURL& optimization_guide_service_get_models_url,
+      network::NetworkConnectionTracker* network_connection_tracker,
       PredictionModelFetcherEndState fetch_state)
       : PredictionModelFetcher(url_loader_factory,
-                               optimization_guide_service_get_models_url),
+                               optimization_guide_service_get_models_url,
+                               network_connection_tracker),
         fetch_state_(fetch_state) {}
 
   bool FetchOptimizationGuideServiceModels(
@@ -539,7 +543,8 @@
       PredictionModelFetcherEndState end_state) {
     std::unique_ptr<TestPredictionModelFetcher> prediction_model_fetcher =
         std::make_unique<TestPredictionModelFetcher>(
-            url_loader_factory_, GURL("https://hintsserver.com"), end_state);
+            url_loader_factory_, GURL("https://hintsserver.com"),
+            network::TestNetworkConnectionTracker::GetInstance(), end_state);
     return prediction_model_fetcher;
   }
 
@@ -1178,7 +1183,7 @@
           PredictionModelFetcherEndState::kFetchSuccessWithModelDownloadUrls));
   prediction_manager()->SetPredictionModelDownloadManagerForTesting(
       std::make_unique<FakePredictionModelDownloadManager>(
-          profile(), task_environment()->GetMainThreadTaskRunner()));
+          task_environment()->GetMainThreadTaskRunner()));
   prediction_model_download_manager()->SetAvailableForDownloads(false);
 
   prediction_manager()->RegisterOptimizationTargets(
@@ -1208,7 +1213,7 @@
           PredictionModelFetcherEndState::kFetchSuccessWithModelDownloadUrls));
   prediction_manager()->SetPredictionModelDownloadManagerForTesting(
       std::make_unique<FakePredictionModelDownloadManager>(
-          profile(), task_environment()->GetMainThreadTaskRunner()));
+          task_environment()->GetMainThreadTaskRunner()));
 
   prediction_manager()->RegisterOptimizationTargets(
       {proto::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD});
diff --git a/chrome/browser/optimization_guide/prediction/prediction_model_download_manager.cc b/chrome/browser/optimization_guide/prediction/prediction_model_download_manager.cc
index ad08f8b10..c1c8d854 100644
--- a/chrome/browser/optimization_guide/prediction/prediction_model_download_manager.cc
+++ b/chrome/browser/optimization_guide/prediction/prediction_model_download_manager.cc
@@ -8,18 +8,13 @@
 #include "base/files/file_util.h"
 #include "base/guid.h"
 #include "base/metrics/histogram_functions.h"
-#include "base/path_service.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/task/post_task.h"
 #include "base/task/thread_pool.h"
 #include "build/build_config.h"
-#include "chrome/browser/download/download_service_factory.h"
 #include "chrome/browser/optimization_guide/prediction/prediction_model_download_observer.h"
-#include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/profiles/profile_key.h"
-#include "chrome/common/chrome_paths.h"
 #include "components/crx_file/crx_verifier.h"
 #include "components/download/public/background_service/download_service.h"
 #include "components/optimization_guide/core/optimization_guide_enums.h"
@@ -28,8 +23,6 @@
 #include "components/optimization_guide/core/optimization_guide_util.h"
 #include "components/services/unzip/content/unzip_service.h"
 #include "components/services/unzip/public/cpp/unzip.h"
-#include "content/public/browser/browser_task_traits.h"
-#include "content/public/browser/browser_thread.h"
 #include "net/traffic_annotation/network_traffic_annotation.h"
 
 namespace optimization_guide {
@@ -76,11 +69,9 @@
          base_name_value == kModelInfoFileName;
 }
 
-base::FilePath GetFilePathForModelInfo(const proto::ModelInfo& model_info) {
-  base::FilePath models_dir;
-  base::PathService::Get(chrome::DIR_OPTIMIZATION_GUIDE_PREDICTION_MODELS,
-                         &models_dir);
-  return models_dir.AppendASCII(base::StringPrintf(
+base::FilePath GetFilePathForModelInfo(const base::FilePath& dir,
+                                       const proto::ModelInfo& model_info) {
+  return dir.AppendASCII(base::StringPrintf(
       "%s_%s.tflite",
       proto::OptimizationTarget_Name(model_info.optimization_target()).c_str(),
       base::NumberToString(model_info.version()).c_str()));
@@ -96,10 +87,11 @@
 }  // namespace
 
 PredictionModelDownloadManager::PredictionModelDownloadManager(
-    Profile* profile,
+    download::DownloadService* download_service,
+    const base::FilePath& models_dir,
     scoped_refptr<base::SequencedTaskRunner> background_task_runner)
-    : download_service_(
-          DownloadServiceFactory::GetForKey(profile->GetProfileKey())),
+    : download_service_(download_service),
+      models_dir_(models_dir),
       is_available_for_downloads_(true),
       api_key_(features::GetOptimizationGuideServiceAPIKey()),
       background_task_runner_(background_task_runner) {}
@@ -152,14 +144,14 @@
 
 void PredictionModelDownloadManager::AddObserver(
     PredictionModelDownloadObserver* observer) {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   observers_.AddObserver(observer);
 }
 
 void PredictionModelDownloadManager::RemoveObserver(
     PredictionModelDownloadObserver* observer) {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   observers_.RemoveObserver(observer);
 }
@@ -250,7 +242,7 @@
 void PredictionModelDownloadManager::StartUnzipping(
     const base::Optional<std::pair<base::FilePath, base::FilePath>>&
         unzip_paths) {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   if (!unzip_paths)
     return;
@@ -267,7 +259,7 @@
     const base::FilePath& original_file_path,
     const base::FilePath& unzipped_dir_path,
     bool success) {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   // Clean up original download file when this function finishes.
   background_task_runner_->PostTask(
@@ -320,7 +312,7 @@
 
   // Move model file away from temp directory.
   base::FilePath temp_model_path = unzipped_dir_path.Append(kModelFileName);
-  base::FilePath model_path = GetFilePathForModelInfo(model_info);
+  base::FilePath model_path = GetFilePathForModelInfo(models_dir_, model_info);
   base::File::Error file_error;
   if (!base::ReplaceFile(temp_model_path, model_path, &file_error)) {
     if (file_error == base::File::FILE_ERROR_NOT_FOUND) {
@@ -343,7 +335,7 @@
 
 void PredictionModelDownloadManager::NotifyModelReady(
     const base::Optional<proto::PredictionModel>& model) {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   if (!model)
     return;
diff --git a/chrome/browser/optimization_guide/prediction/prediction_model_download_manager.h b/chrome/browser/optimization_guide/prediction/prediction_model_download_manager.h
index e846615..405c92a 100644
--- a/chrome/browser/optimization_guide/prediction/prediction_model_download_manager.h
+++ b/chrome/browser/optimization_guide/prediction/prediction_model_download_manager.h
@@ -13,8 +13,6 @@
 #include "base/observer_list.h"
 #include "components/download/public/background_service/download_params.h"
 
-class Profile;
-
 namespace download {
 class DownloadService;
 }  // namespace download
@@ -32,7 +30,8 @@
 class PredictionModelDownloadManager {
  public:
   PredictionModelDownloadManager(
-      Profile* profile,
+      download::DownloadService* download_service,
+      const base::FilePath& models_dir,
       scoped_refptr<base::SequencedTaskRunner> background_task_runner);
   virtual ~PredictionModelDownloadManager();
   PredictionModelDownloadManager(const PredictionModelDownloadManager&) =
@@ -124,6 +123,9 @@
   // Guaranteed to outlive |this|.
   download::DownloadService* download_service_;
 
+  // The directory to store verified models in.
+  base::FilePath models_dir_;
+
   // Whether the download service is available.
   bool is_available_for_downloads_;
 
@@ -139,6 +141,10 @@
   // Background thread where download file processing should be performed.
   scoped_refptr<base::SequencedTaskRunner> background_task_runner_;
 
+  // Sequence checker used to verify all public API methods are called on the
+  // UI thread.
+  SEQUENCE_CHECKER(sequence_checker_);
+
   // Used to get weak ptr to self on the UI thread.
   base::WeakPtrFactory<PredictionModelDownloadManager> ui_weak_ptr_factory_{
       this};
diff --git a/chrome/browser/optimization_guide/prediction/prediction_model_download_manager_unittest.cc b/chrome/browser/optimization_guide/prediction/prediction_model_download_manager_unittest.cc
index 32df535..b89fcc4 100644
--- a/chrome/browser/optimization_guide/prediction/prediction_model_download_manager_unittest.cc
+++ b/chrome/browser/optimization_guide/prediction/prediction_model_download_manager_unittest.cc
@@ -8,17 +8,14 @@
 #include "base/files/scoped_temp_dir.h"
 #include "base/optional.h"
 #include "base/path_service.h"
+#include "base/sequence_checker.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/task/thread_pool/thread_pool_instance.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
-#include "base/test/scoped_path_override.h"
+#include "base/test/task_environment.h"
 #include "build/build_config.h"
-#include "chrome/browser/download/download_service_factory.h"
 #include "chrome/browser/optimization_guide/prediction/prediction_model_download_observer.h"
-#include "chrome/browser/profiles/profile_key.h"
-#include "chrome/common/chrome_paths.h"
-#include "chrome/test/base/chrome_render_view_host_test_harness.h"
 #include "components/download/public/background_service/test/mock_download_service.h"
 #include "components/optimization_guide/core/optimization_guide_enums.h"
 #include "components/optimization_guide/core/optimization_guide_features.h"
@@ -62,41 +59,26 @@
   kUnverifiedFile,
 };
 
-class PredictionModelDownloadManagerTest
-    : public ChromeRenderViewHostTestHarness {
+class PredictionModelDownloadManagerTest : public testing::Test {
  public:
   PredictionModelDownloadManagerTest() = default;
   ~PredictionModelDownloadManagerTest() override = default;
 
   void SetUp() override {
-    ChromeRenderViewHostTestHarness::SetUp();
-
     ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
-    mock_download_service_ = static_cast<download::test::MockDownloadService*>(
-        DownloadServiceFactory::GetInstance()->SetTestingFactoryAndUse(
-            profile()->GetProfileKey(),
-            base::BindRepeating([](SimpleFactoryKey* key)
-                                    -> std::unique_ptr<KeyedService> {
-              return std::make_unique<download::test::MockDownloadService>();
-            })));
+    mock_download_service_ =
+        std::make_unique<download::test::MockDownloadService>();
     download_manager_ = std::make_unique<PredictionModelDownloadManager>(
-        profile(), task_environment()->GetMainThreadTaskRunner());
+        mock_download_service_.get(), temp_dir_.GetPath(),
+        task_environment_.GetMainThreadTaskRunner());
 
     unzip::SetUnzipperLaunchOverrideForTesting(
         base::BindRepeating(&unzip::LaunchInProcessUnzipper));
-
-    path_override_ = std::make_unique<base::ScopedPathOverride>(
-        chrome::DIR_OPTIMIZATION_GUIDE_PREDICTION_MODELS, temp_dir_.GetPath(),
-        /*is_absolute=*/true,
-        /*create=*/false);
   }
 
   void TearDown() override {
     download_manager_.reset();
     mock_download_service_ = nullptr;
-    path_override_.reset();
-
-    ChromeRenderViewHostTestHarness::TearDown();
   }
 
   PredictionModelDownloadManager* download_manager() {
@@ -104,7 +86,7 @@
   }
 
   download::test::MockDownloadService* download_service() {
-    return mock_download_service_;
+    return mock_download_service_.get();
   }
 
  protected:
@@ -137,7 +119,7 @@
   }
 
   void RunUntilIdle() {
-    task_environment()->RunUntilIdle();
+    task_environment_.RunUntilIdle();
 
     // Wait for all delayed tasks to finish.
     base::RunLoop run_loop;
@@ -221,9 +203,9 @@
         zip::Zip(zip_dir, GetFilePathForDownloadFileStatus(status), true));
   }
 
+  base::test::TaskEnvironment task_environment_;
   base::ScopedTempDir temp_dir_;
-  std::unique_ptr<base::ScopedPathOverride> path_override_;
-  download::test::MockDownloadService* mock_download_service_;
+  std::unique_ptr<download::test::MockDownloadService> mock_download_service_;
   std::unique_ptr<PredictionModelDownloadManager> download_manager_;
 };
 
diff --git a/chrome/browser/page_load_metrics/observers/translate_page_load_metrics_observer.cc b/chrome/browser/page_load_metrics/observers/translate_page_load_metrics_observer.cc
index 481bb7a6..1a94f922 100644
--- a/chrome/browser/page_load_metrics/observers/translate_page_load_metrics_observer.cc
+++ b/chrome/browser/page_load_metrics/observers/translate_page_load_metrics_observer.cc
@@ -43,6 +43,14 @@
 }
 
 page_load_metrics::PageLoadMetricsObserver::ObservePolicy
+TranslatePageLoadMetricsObserver::OnCommit(
+    content::NavigationHandle* navigation_handle,
+    ukm::SourceId source_id) {
+  translate_metrics_logger_->SetUkmSourceId(source_id);
+  return CONTINUE_OBSERVING;
+}
+
+page_load_metrics::PageLoadMetricsObserver::ObservePolicy
 TranslatePageLoadMetricsObserver::OnHidden(
     const page_load_metrics::mojom::PageLoadTiming& timing) {
   translate_metrics_logger_->OnForegroundChange(false);
diff --git a/chrome/browser/page_load_metrics/observers/translate_page_load_metrics_observer.h b/chrome/browser/page_load_metrics/observers/translate_page_load_metrics_observer.h
index c4902b6..608e954 100644
--- a/chrome/browser/page_load_metrics/observers/translate_page_load_metrics_observer.h
+++ b/chrome/browser/page_load_metrics/observers/translate_page_load_metrics_observer.h
@@ -41,6 +41,8 @@
   ObservePolicy OnStart(content::NavigationHandle* navigation_handle,
                         const GURL& currently_committed_url,
                         bool started_in_foreground) override;
+  ObservePolicy OnCommit(content::NavigationHandle* navigation_handle,
+                         ukm::SourceId source_id) override;
   ObservePolicy OnHidden(
       const page_load_metrics::mojom::PageLoadTiming& timing) override;
   ObservePolicy OnShown() override;
diff --git a/chrome/browser/page_load_metrics/observers/translate_page_load_metrics_observer_unittest.cc b/chrome/browser/page_load_metrics/observers/translate_page_load_metrics_observer_unittest.cc
index c6a623e..1c94ce8 100644
--- a/chrome/browser/page_load_metrics/observers/translate_page_load_metrics_observer_unittest.cc
+++ b/chrome/browser/page_load_metrics/observers/translate_page_load_metrics_observer_unittest.cc
@@ -34,6 +34,10 @@
     mock_translate_metrics_logger_->RecordMetrics(is_final);
   }
 
+  void SetUkmSourceId(ukm::SourceId ukm_source_id) override {
+    mock_translate_metrics_logger_->SetUkmSourceId(ukm_source_id);
+  }
+
   void LogRankerMetrics(translate::RankerDecision ranker_decision,
                         uint32_t ranker_version) override {
     mock_translate_metrics_logger_->LogRankerMetrics(ranker_decision,
diff --git a/chrome/browser/policy/messaging_layer/encryption/decryption.cc b/chrome/browser/policy/messaging_layer/encryption/decryption.cc
index 0f0ffc6..40cf237 100644
--- a/chrome/browser/policy/messaging_layer/encryption/decryption.cc
+++ b/chrome/browser/policy/messaging_layer/encryption/decryption.cc
@@ -152,8 +152,8 @@
                        " actual=", base::NumberToString(public_key.size())}));
             } else {
               // Assign a random number to be public key id for testing purposes
-              // only (in production it will be Java Fingerprint2011 which is
-              // 'long').
+              // only (in production it will be retrieved from the server as
+              // 'int32').
               Encryptor::PublicKeyId public_key_id;
               base::RandBytes(&public_key_id, sizeof(public_key_id));
               if (!decryptor->keys_.emplace(public_key_id, key_info).second) {
diff --git a/chrome/browser/policy/messaging_layer/public/report_client.cc b/chrome/browser/policy/messaging_layer/public/report_client.cc
index e87d29d..70d7589 100644
--- a/chrome/browser/policy/messaging_layer/public/report_client.cc
+++ b/chrome/browser/policy/messaging_layer/public/report_client.cc
@@ -10,6 +10,7 @@
 #include "base/bind.h"
 #include "base/callback.h"
 #include "base/containers/queue.h"
+#include "base/feature_list.h"
 #include "base/memory/ptr_util.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/memory/singleton.h"
@@ -507,6 +508,15 @@
 void ReportingClient::CreateReportQueue(
     std::unique_ptr<ReportQueueConfiguration> config,
     CreateReportQueueCallback create_cb) {
+  if (!IsEncryptedReportingPipelineEnabled()) {
+    Status not_enabled = Status(
+        error::FAILED_PRECONDITION,
+        "The Encrypted Reporting Pipeline is not enabled. Please enable it on "
+        "the command line using --enable-features=EncryptedReportingPipeline");
+    VLOG(1) << not_enabled;
+    std::move(create_cb).Run(not_enabled);
+    return;
+  }
   auto* instance = GetInstance();
   instance->create_request_queue_->Push(
       CreateReportQueueRequest(std::move(config), std::move(create_cb)),
@@ -514,6 +524,15 @@
                      base::Unretained(instance)));
 }
 
+// static
+bool ReportingClient::IsEncryptedReportingPipelineEnabled() {
+  return base::FeatureList::IsEnabled(kEncryptedReportingPipeline);
+}
+
+// static
+const base::Feature ReportingClient::kEncryptedReportingPipeline{
+    "EncryptedReportingPipeline", base::FEATURE_DISABLED_BY_DEFAULT};
+
 void ReportingClient::OnPushComplete() {
   init_state_tracker_->GetInitState(
       base::BindOnce(&ReportingClient::OnInitState, base::Unretained(this)));
diff --git a/chrome/browser/policy/messaging_layer/public/report_client.h b/chrome/browser/policy/messaging_layer/public/report_client.h
index 1aac6d9..aae3452 100644
--- a/chrome/browser/policy/messaging_layer/public/report_client.h
+++ b/chrome/browser/policy/messaging_layer/public/report_client.h
@@ -9,6 +9,7 @@
 #include <utility>
 
 #include "base/containers/queue.h"
+#include "base/feature_list.h"
 #include "base/memory/singleton.h"
 #include "chrome/browser/policy/messaging_layer/public/report_queue.h"
 #include "chrome/browser/policy/messaging_layer/public/report_queue_configuration.h"
@@ -24,6 +25,9 @@
 // ReportingClient acts a single point for creating |reporting::ReportQueue|s.
 // It ensures that all ReportQueues are created with the same storage settings.
 //
+// In order to utilize the ReportingClient the EncryptedReportingPipeline
+// feature must be turned on using --enable-features=EncryptedReportingPipeline.
+//
 // Example Usage:
 // void SendMessage(google::protobuf::ImportantMessage important_message,
 //                  reporting::ReportQueue::EnqueueCallback done_cb) {
@@ -211,6 +215,9 @@
       std::unique_ptr<ReportQueueConfiguration> config,
       CreateReportQueueCallback create_cb);
 
+  static bool IsEncryptedReportingPipelineEnabled();
+  static const base::Feature kEncryptedReportingPipeline;
+
  private:
   class Uploader;
 
diff --git a/chrome/browser/policy/messaging_layer/public/report_client_unittest.cc b/chrome/browser/policy/messaging_layer/public/report_client_unittest.cc
index fce944d..e68fca6 100644
--- a/chrome/browser/policy/messaging_layer/public/report_client_unittest.cc
+++ b/chrome/browser/policy/messaging_layer/public/report_client_unittest.cc
@@ -7,6 +7,7 @@
 #include "base/memory/singleton.h"
 #include "base/synchronization/waitable_event.h"
 #include "base/task/post_task.h"
+#include "base/test/scoped_feature_list.h"
 #include "base/test/task_environment.h"
 #include "chrome/browser/policy/messaging_layer/public/report_queue.h"
 #include "chrome/browser/policy/messaging_layer/public/report_queue_configuration.h"
@@ -94,6 +95,9 @@
         policy::DMToken::CreateValidTokenForTesting("FAKE_DM_TOKEN").value());
     test_reporting_ =
         std::make_unique<ReportingClient::TestEnvironment>(client_.get());
+
+    scoped_feature_list_.InitAndEnableFeature(
+        ReportingClient::kEncryptedReportingPipeline);
   }
 
   void TearDown() override {
@@ -115,6 +119,9 @@
   const Destination destination_ = Destination::UPLOAD_EVENTS;
   ReportQueueConfiguration::PolicyCheckCallback policy_checker_callback_ =
       base::BindRepeating([]() { return Status::StatusOK(); });
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
 };
 
 // Tests that a ReportQueue can be created using the ReportingClient.
diff --git a/chrome/browser/prefetch/pref_names.cc b/chrome/browser/prefetch/pref_names.cc
new file mode 100644
index 0000000..ede6c972
--- /dev/null
+++ b/chrome/browser/prefetch/pref_names.cc
@@ -0,0 +1,26 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/prefetch/pref_names.h"
+
+namespace prefetch {
+namespace prefs {
+
+// This pref contains a dictionary value whose keys are string representations
+// of a URL. The values are a tuple (as a List Value) where the first value is a
+// string representation of a URL and a a base::Time. This pref is limited to 10
+// entries in the dictionary.
+// The two URLs are not the same URL.
+const char kCachePrefPath[] = "prefetch.search_prefetch.cache";
+
+// This pref contains a dictionary value whose keys are string representations
+// of a url::Origin and values are a base::Time. The recorded base::Time is the
+// time at which prefetch requests to the corresponding origin can resume, (any
+// base::Time that is in the past can be removed). Entries to the dictionary are
+// created when a prefetch request gets a 503 response with Retry-After header.
+const char kRetryAfterPrefPath[] =
+    "chrome.prefetch_proxy.origin_decider.retry_after";
+
+}  // namespace prefs
+}  // namespace prefetch
diff --git a/chrome/browser/prefetch/pref_names.h b/chrome/browser/prefetch/pref_names.h
new file mode 100644
index 0000000..553d176
--- /dev/null
+++ b/chrome/browser/prefetch/pref_names.h
@@ -0,0 +1,18 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_PREFETCH_PREF_NAMES_H_
+#define CHROME_BROWSER_PREFETCH_PREF_NAMES_H_
+
+namespace prefetch {
+namespace prefs {
+
+extern const char kCachePrefPath[];
+
+extern const char kRetryAfterPrefPath[];
+
+}  // namespace prefs
+}  // namespace prefetch
+
+#endif  // CHROME_BROWSER_PREFETCH_PREF_NAMES_H_
diff --git a/chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_origin_decider.cc b/chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_origin_decider.cc
index 04c55f6..59e714e9 100644
--- a/chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_origin_decider.cc
+++ b/chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_origin_decider.cc
@@ -9,26 +9,17 @@
 
 #include "base/metrics/histogram_macros.h"
 #include "base/util/values/values_util.h"
+#include "chrome/browser/prefetch/pref_names.h"
 #include "chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_params.h"
 #include "chrome/browser/profiles/profile.h"
 #include "components/prefs/pref_registry_simple.h"
 #include "components/prefs/pref_service.h"
 
-namespace {
-// This pref contains a dictionary value whose keys are string representations
-// of a url::Origin and values are a base::Time. The recorded base::Time is the
-// time at which prefetch requests to the corresponding origin can resume, (any
-// base::Time that is in the past can be removed). Entries to the dictionary are
-// created when a prefetch request gets a 503 response with Retry-After header.
-const char kRetryAfterPrefPath[] =
-    "chrome.prefetch_proxy.origin_decider.retry_after";
-}  // namespace
-
 // static
 void PrefetchProxyOriginDecider::RegisterPrefs(PrefRegistrySimple* registry) {
   // Some loss in this pref (especially following a browser crash) is well
   // tolerated and helps ensure the pref service isn't slammed.
-  registry->RegisterDictionaryPref(kRetryAfterPrefPath,
+  registry->RegisterDictionaryPref(prefetch::prefs::kRetryAfterPrefPath,
                                    PrefRegistry::LOSSY_PREF);
 }
 
@@ -90,7 +81,7 @@
   origin_retry_afters_.clear();
 
   const base::DictionaryValue* dictionary =
-      pref_service_->GetDictionary(kRetryAfterPrefPath);
+      pref_service_->GetDictionary(prefetch::prefs::kRetryAfterPrefPath);
   DCHECK(dictionary);
 
   for (const auto& element : *dictionary) {
@@ -122,7 +113,7 @@
     base::Value value = util::TimeToValue(element.second);
     dictionary.SetKey(std::move(key), std::move(value));
   }
-  pref_service_->Set(kRetryAfterPrefPath, dictionary);
+  pref_service_->Set(prefetch::prefs::kRetryAfterPrefPath, dictionary);
 }
 
 bool PrefetchProxyOriginDecider::ClearPastEntries() {
diff --git a/chrome/browser/prefetch/search_prefetch/search_prefetch_service.cc b/chrome/browser/prefetch/search_prefetch/search_prefetch_service.cc
index ec5feba..8574f467 100644
--- a/chrome/browser/prefetch/search_prefetch/search_prefetch_service.cc
+++ b/chrome/browser/prefetch/search_prefetch/search_prefetch_service.cc
@@ -8,8 +8,11 @@
 #include "base/callback.h"
 #include "base/location.h"
 #include "base/metrics/histogram_macros.h"
+#include "base/util/values/values_util.h"
+#include "base/values.h"
 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
 #include "chrome/browser/net/prediction_options.h"
+#include "chrome/browser/prefetch/pref_names.h"
 #include "chrome/browser/prefetch/search_prefetch/back_forward_search_prefetch_url_loader.h"
 #include "chrome/browser/prefetch/search_prefetch/field_trial_settings.h"
 #include "chrome/browser/prefetch/search_prefetch/full_body_search_prefetch_request.h"
@@ -25,6 +28,7 @@
 #include "components/omnibox/browser/base_search_provider.h"
 #include "components/omnibox/browser/omnibox_event_global_tracker.h"
 #include "components/omnibox/browser/omnibox_log.h"
+#include "components/prefs/pref_registry_simple.h"
 #include "components/prefs/pref_service.h"
 #include "components/search_engines/template_url_service.h"
 #include "url/origin.h"
@@ -75,9 +79,20 @@
 
 }  // namespace
 
+// static
+void SearchPrefetchService::RegisterProfilePrefs(PrefRegistrySimple* registry) {
+  // Some loss in this pref (especially following a browser crash) is well
+  // tolerated and helps ensure the pref service isn't slammed.
+  registry->RegisterDictionaryPref(prefetch::prefs::kCachePrefPath,
+                                   PrefRegistry::LOSSY_PREF);
+}
+
 SearchPrefetchService::SearchPrefetchService(Profile* profile)
     : profile_(profile) {
   DCHECK(!profile_->IsOffTheRecord());
+
+  if (LoadFromPrefs())
+    SaveToPrefs();
 }
 
 SearchPrefetchService::~SearchPrefetchService() = default;
@@ -328,6 +343,7 @@
   prefetches_.clear();
   prefetch_expiry_timers_.clear();
   prefetch_cache_.clear();
+  SaveToPrefs();
 }
 
 void SearchPrefetchService::DeletePrefetch(base::string16 search_terms) {
@@ -438,19 +454,28 @@
 }
 
 void SearchPrefetchService::ClearCacheEntry(const GURL& navigation_url) {
+  if (prefetch_cache_.find(navigation_url) == prefetch_cache_.end()) {
+    return;
+  }
+
   prefetch_cache_.erase(navigation_url);
+  SaveToPrefs();
 }
 
 void SearchPrefetchService::UpdateServeTime(const GURL& navigation_url) {
   if (prefetch_cache_.find(navigation_url) == prefetch_cache_.end())
     return;
+
   prefetch_cache_[navigation_url].second = base::Time::Now();
+  SaveToPrefs();
 }
 
 void SearchPrefetchService::AddCacheEntry(const GURL& navigation_url,
                                           const GURL& prefetch_url) {
-  // TODO(ryansturm): Add prefs support to handle session restore.
-  // https://crbug.com/1162121.
+  if (navigation_url == prefetch_url) {
+    return;
+  }
+
   prefetch_cache_.emplace(navigation_url,
                           std::make_pair(prefetch_url, base::Time::Now()));
 
@@ -468,4 +493,100 @@
     }
   }
   ClearCacheEntry(url_to_remove);
+  SaveToPrefs();
+}
+
+bool SearchPrefetchService::LoadFromPrefs() {
+  prefetch_cache_.clear();
+  const base::DictionaryValue* dictionary =
+      profile_->GetPrefs()->GetDictionary(prefetch::prefs::kCachePrefPath);
+  DCHECK(dictionary);
+
+  auto* template_url_service =
+      TemplateURLServiceFactory::GetForProfile(profile_);
+  if (!template_url_service ||
+      !template_url_service->GetDefaultSearchProvider()) {
+    return dictionary->size() > 0;
+  }
+
+  for (const auto& element : *dictionary) {
+    GURL navigation_url(element.first);
+    if (!navigation_url.is_valid()) {
+      continue;
+    }
+
+    if (!element.second) {
+      continue;
+    }
+
+    base::Value::ConstListView const prefetch_url_and_time =
+        base::Value::AsListValue(*element.second).GetList();
+
+    if (prefetch_url_and_time.size() != 2 ||
+        !prefetch_url_and_time[0].is_string() ||
+        !prefetch_url_and_time[1].is_string()) {
+      continue;
+    }
+
+    std::string prefetch_url;
+    if (!prefetch_url_and_time[0].GetAsString(&prefetch_url)) {
+      continue;
+    }
+
+    // Make sure we are only mapping same origin in case of corrupted prefs.
+    if (url::Origin::Create(navigation_url) !=
+        url::Origin::Create(GURL(prefetch_url))) {
+      continue;
+    }
+
+    // Don't redirect same URL.
+    if (navigation_url == prefetch_url) {
+      continue;
+    }
+
+    // Make sure the navigation URL is still a search URL.
+    base::string16 search_terms;
+    template_url_service->GetDefaultSearchProvider()->ExtractSearchTermsFromURL(
+        navigation_url, template_url_service->search_terms_data(),
+        &search_terms);
+
+    if (search_terms.size() == 0) {
+      continue;
+    }
+
+    base::Optional<base::Time> last_update =
+        util::ValueToTime(prefetch_url_and_time[1]);
+    if (!last_update) {
+      continue;
+    }
+
+    // This time isn't valid.
+    if (last_update.value() > base::Time::Now()) {
+      continue;
+    }
+
+    prefetch_cache_.emplace(
+        navigation_url,
+        std::make_pair(GURL(prefetch_url), last_update.value()));
+  }
+  return dictionary->size() > prefetch_cache_.size();
+}
+
+void SearchPrefetchService::SaveToPrefs() const {
+  base::DictionaryValue dictionary;
+  for (const auto& element : prefetch_cache_) {
+    std::string navigation_url = element.first.spec();
+    std::string prefetch_url = element.second.first.spec();
+    auto time =
+        std::make_unique<base::Value>(util::TimeToValue(element.second.second));
+    base::ListValue value;
+    value.AppendString(prefetch_url);
+    value.Append(std::move(time));
+    dictionary.SetKey(std::move(navigation_url), std::move(value));
+  }
+  profile_->GetPrefs()->Set(prefetch::prefs::kCachePrefPath, dictionary);
+}
+
+bool SearchPrefetchService::LoadFromPrefsForTesting() {
+  return LoadFromPrefs();
 }
diff --git a/chrome/browser/prefetch/search_prefetch/search_prefetch_service.h b/chrome/browser/prefetch/search_prefetch/search_prefetch_service.h
index 5038f7b..093d4588 100644
--- a/chrome/browser/prefetch/search_prefetch/search_prefetch_service.h
+++ b/chrome/browser/prefetch/search_prefetch/search_prefetch_service.h
@@ -22,6 +22,7 @@
 
 class AutocompleteController;
 struct OmniboxLog;
+class PrefRegistrySimple;
 class Profile;
 class SearchPrefetchURLLoader;
 
@@ -117,6 +118,11 @@
   base::Optional<SearchPrefetchStatus> GetSearchPrefetchStatusForTesting(
       base::string16 search_terms);
 
+  // Calls |LoadFromPrefs()|.
+  bool LoadFromPrefsForTesting();
+
+  static void RegisterProfilePrefs(PrefRegistrySimple* registry);
+
  private:
   // Records a cache entry for a navigation that is being served.
   void AddCacheEntry(const GURL& navigation_url, const GURL& prefetch_url);
@@ -132,6 +138,14 @@
   // closes.
   void OnURLOpenedFromOmnibox(OmniboxLog* log);
 
+  // These methods serialize and deserialize |prefetch_cache_| to
+  // |profile_| pref service in a dictionary value.
+  //
+  // Returns true iff loading the prefs removed at least one entry, so the pref
+  // should be saved.
+  bool LoadFromPrefs();
+  void SaveToPrefs() const;
+
   // Prefetches that are started are stored using search terms as a key. Only
   // one prefetch should be started for a given search term until the old
   // prefetch expires.
diff --git a/chrome/browser/prefetch/search_prefetch/search_prefetch_service_browsertest.cc b/chrome/browser/prefetch/search_prefetch/search_prefetch_service_browsertest.cc
index 2611411..b1ea39aa 100644
--- a/chrome/browser/prefetch/search_prefetch/search_prefetch_service_browsertest.cc
+++ b/chrome/browser/prefetch/search_prefetch/search_prefetch_service_browsertest.cc
@@ -1261,6 +1261,72 @@
 }
 
 IN_PROC_BROWSER_TEST_P(SearchPrefetchServiceEnabledBrowserTest,
+                       BackPrefetchServedAfterPrefs) {
+  // This test prefetches and serves two SRP responses. It then navigates back
+  // then forward, the back navigation should not be cached, due to cache limit
+  // size of 1, the second navigation should be cached.
+
+  base::HistogramTester histogram_tester;
+  auto* search_prefetch_service =
+      SearchPrefetchServiceFactory::GetForProfile(browser()->profile());
+  EXPECT_NE(nullptr, search_prefetch_service);
+
+  std::string search_terms = "prefetch_content";
+  GURL prefetch_url = GetSearchServerQueryURL(search_terms + "&pf=cs");
+  EXPECT_TRUE(search_prefetch_service->MaybePrefetchURL(prefetch_url));
+  WaitUntilStatusChangesTo(base::ASCIIToUTF16(search_terms),
+                           SearchPrefetchStatus::kComplete);
+  ui_test_utils::NavigateToURL(browser(),
+                               GetSearchServerQueryURL(search_terms));
+
+  // The prefetch should be served, and only 1 request should be issued.
+  EXPECT_EQ(1u, search_server_requests().size());
+  auto inner_html = GetDocumentInnerHTML();
+  EXPECT_FALSE(base::Contains(inner_html, "regular"));
+  EXPECT_TRUE(base::Contains(inner_html, "prefetch"));
+
+  search_terms = "prefetch_content_2";
+  prefetch_url = GetSearchServerQueryURL(search_terms + "&pf=cs");
+  EXPECT_TRUE(search_prefetch_service->MaybePrefetchURL(prefetch_url));
+  WaitUntilStatusChangesTo(base::ASCIIToUTF16(search_terms),
+                           SearchPrefetchStatus::kComplete);
+  ui_test_utils::NavigateToURL(browser(),
+                               GetSearchServerQueryURL(search_terms));
+
+  // The prefetch should be served, and only 1 request (now the second total
+  // request) should be issued.
+  EXPECT_EQ(2u, search_server_requests().size());
+  inner_html = GetDocumentInnerHTML();
+  EXPECT_FALSE(base::Contains(inner_html, "regular"));
+  EXPECT_TRUE(base::Contains(inner_html, "prefetch"));
+
+  content::TestNavigationObserver back_load_observer(GetWebContents());
+  GetWebContents()->GetController().GoBack();
+  back_load_observer.Wait();
+
+  // There should not be a cached prefetch request, so there should be a network
+  // request.
+  EXPECT_EQ(3u, search_server_requests().size());
+  inner_html = GetDocumentInnerHTML();
+  EXPECT_TRUE(base::Contains(inner_html, "regular"));
+  EXPECT_FALSE(base::Contains(inner_html, "prefetch"));
+
+  // Reload the map from prefs.
+  EXPECT_FALSE(search_prefetch_service->LoadFromPrefsForTesting());
+
+  content::TestNavigationObserver forward_load_observer(GetWebContents());
+  GetWebContents()->GetController().GoForward();
+  forward_load_observer.Wait();
+
+  // There should be a cached prefetch request, so there should not be a new
+  // network request.
+  EXPECT_EQ(3u, search_server_requests().size());
+  inner_html = GetDocumentInnerHTML();
+  EXPECT_FALSE(base::Contains(inner_html, "regular"));
+  EXPECT_TRUE(base::Contains(inner_html, "prefetch"));
+}
+
+IN_PROC_BROWSER_TEST_P(SearchPrefetchServiceEnabledBrowserTest,
                        EvictedCacheFallsback) {
   // This test prefetches and serves a SRP responses. It then navigates to a
   // different URL. Then it clears cache as if it was evicted. Then it navigates
diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc
index 637d7c0..b73a41e2 100644
--- a/chrome/browser/prefs/browser_prefs.cc
+++ b/chrome/browser/prefs/browser_prefs.cc
@@ -54,6 +54,7 @@
 #include "chrome/browser/permissions/quiet_notification_permission_ui_state.h"
 #include "chrome/browser/policy/developer_tools_policy_handler.h"
 #include "chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_origin_decider.h"
+#include "chrome/browser/prefetch/search_prefetch/search_prefetch_service.h"
 #include "chrome/browser/prefs/chrome_pref_service_factory.h"
 #include "chrome/browser/prefs/incognito_mode_prefs.h"
 #include "chrome/browser/prefs/origin_trial_prefs.h"
@@ -883,6 +884,7 @@
   QuietNotificationPermissionUiState::RegisterProfilePrefs(registry);
   RegisterBrowserUserPrefs(registry);
   safe_browsing::RegisterProfilePrefs(registry);
+  SearchPrefetchService::RegisterProfilePrefs(registry);
   blocked_content::SafeBrowsingTriggeredPopupBlocker::RegisterProfilePrefs(
       registry);
   security_interstitials::InsecureFormBlockingPage::RegisterProfilePrefs(
diff --git a/chrome/browser/printing/print_backend_browsertest.cc b/chrome/browser/printing/print_backend_browsertest.cc
index 4b34cf945..a9b3a0f1 100644
--- a/chrome/browser/printing/print_backend_browsertest.cc
+++ b/chrome/browser/printing/print_backend_browsertest.cc
@@ -110,6 +110,21 @@
     CheckForQuit();
   }
 
+  void OnDidFetchCapabilities(
+      base::Optional<PrinterBasicInfo>* capture_printer_info,
+      base::Optional<PrinterSemanticCapsAndDefaults::Papers>*
+          capture_user_defined_papers,
+      base::Optional<PrinterSemanticCapsAndDefaults>* capture_printer_caps,
+      const base::Optional<PrinterBasicInfo>& printer_info,
+      const base::Optional<PrinterSemanticCapsAndDefaults::Papers>&
+          user_defined_papers,
+      const base::Optional<PrinterSemanticCapsAndDefaults>& printer_caps) {
+    *capture_printer_info = printer_info;
+    *capture_user_defined_papers = user_defined_papers;
+    *capture_printer_caps = printer_caps;
+    CheckForQuit();
+  }
+
   // The following are helper functions for having a wait loop in the test and
   // exit when expected messages are received.  Expect to only have to wait for
   // one message.
@@ -209,4 +224,37 @@
   EXPECT_FALSE(printer_caps.has_value());
 }
 
+IN_PROC_BROWSER_TEST_F(PrintBackendBrowserTest, FetchCapabilities) {
+  base::Optional<PrinterBasicInfo> printer_info;
+  base::Optional<PrinterSemanticCapsAndDefaults::Papers> user_defined_papers;
+  base::Optional<PrinterSemanticCapsAndDefaults> printer_caps;
+
+  DoInitAndSetupTestData();
+
+  // Safe to use base::Unretained(this) since waiting locally on the callback
+  // forces a shorter lifetime than `this`.
+  GetPrintBackendService()->FetchCapabilities(
+      kDefaultPrinterName,
+      base::BindOnce(&PrintBackendBrowserTest::OnDidFetchCapabilities,
+                     base::Unretained(this), &printer_info,
+                     &user_defined_papers, &printer_caps));
+  WaitUntilCallbackReceived();
+  EXPECT_TRUE(printer_info.has_value());
+  EXPECT_TRUE(user_defined_papers.has_value());
+  EXPECT_TRUE(printer_caps.has_value());
+  EXPECT_TRUE(printer_info->is_default);
+  EXPECT_EQ(printer_caps->copies_max, kCopiesMax);
+
+  // Requesting for an invalid printer should not return capabilities.
+  GetPrintBackendService()->FetchCapabilities(
+      kInvalidPrinterName,
+      base::BindOnce(&PrintBackendBrowserTest::OnDidFetchCapabilities,
+                     base::Unretained(this), &printer_info,
+                     &user_defined_papers, &printer_caps));
+  WaitUntilCallbackReceived();
+  EXPECT_FALSE(printer_info.has_value());
+  EXPECT_FALSE(user_defined_papers.has_value());
+  EXPECT_FALSE(printer_caps.has_value());
+}
+
 }  // namespace printing
diff --git a/chrome/browser/reading_list/android/reading_list_manager.h b/chrome/browser/reading_list/android/reading_list_manager.h
index 28b589a3..efa7e53 100644
--- a/chrome/browser/reading_list/android/reading_list_manager.h
+++ b/chrome/browser/reading_list/android/reading_list_manager.h
@@ -47,7 +47,7 @@
   // Adds a reading list article to the unread section, and return the bookmark
   // node representation. The bookmark node is owned by this class. If there is
   // a duplicate URL, a new bookmark node will be created, and the old bookmark
-  // node pointer will be invalidated. May return nullptr on error.
+  // node pointer will be invalidated.
   virtual const bookmarks::BookmarkNode* Add(const GURL& url,
                                              const std::string& title) = 0;
 
diff --git a/chrome/browser/reading_list/android/reading_list_manager_impl.cc b/chrome/browser/reading_list/android/reading_list_manager_impl.cc
index b2bf08d..98b8bcc 100644
--- a/chrome/browser/reading_list/android/reading_list_manager_impl.cc
+++ b/chrome/browser/reading_list/android/reading_list_manager_impl.cc
@@ -132,13 +132,13 @@
 const BookmarkNode* ReadingListManagerImpl::Add(const GURL& url,
                                                 const std::string& title) {
   DCHECK(reading_list_model_->loaded());
-  if (!reading_list_model_->IsUrlSupported(url))
-    return nullptr;
 
   // Add or swap the reading list entry.
   const auto& new_entry = reading_list_model_->AddEntry(
       url, title, reading_list::ADDED_VIA_CURRENT_APP);
   const auto* node = FindBookmarkByURL(new_entry.URL());
+  DCHECK(node)
+      << "Bookmark node should have been create in ReadingListDidAddEntry().";
   return node;
 }
 
diff --git a/chrome/browser/reading_list/android/reading_list_manager_impl_unittest.cc b/chrome/browser/reading_list/android/reading_list_manager_impl_unittest.cc
index 57670f5..1ca6a98 100644
--- a/chrome/browser/reading_list/android/reading_list_manager_impl_unittest.cc
+++ b/chrome/browser/reading_list/android/reading_list_manager_impl_unittest.cc
@@ -32,7 +32,6 @@
 constexpr char kReadStatusKey[] = "read_status";
 constexpr char kReadStatusRead[] = "true";
 constexpr char kReadStatusUnread[] = "false";
-constexpr char kInvalidUTF8[] = "\xc3\x28";
 
 class MockObserver : public ReadingListManager::Observer {
  public:
@@ -229,31 +228,6 @@
   EXPECT_EQ(url, new_node->url());
 }
 
-// If Add() with an invalid title, nullptr will be returned.
-TEST_F(ReadingListManagerImplTest, AddInvalidTitle) {
-  GURL url(kURL);
-
-  // Use an invalid UTF8 string.
-  base::string16 dummy;
-  EXPECT_FALSE(
-      base::UTF8ToUTF16(kInvalidUTF8, base::size(kInvalidUTF8), &dummy));
-  const auto* new_node = Add(url, std::string(kInvalidUTF8));
-  EXPECT_EQ(nullptr, new_node)
-      << "Should return nullptr when failed to parse the title.";
-}
-
-// If Add() with an invalid URL, nullptr will be returned.
-TEST_F(ReadingListManagerImplTest, AddInvalidURL) {
-  GURL invalid_url("chrome://flags");
-  EXPECT_FALSE(reading_list_model()->IsUrlSupported(invalid_url));
-
-  // Use an invalid URL, the observer method ReadingListDidAddEntry() won't be
-  // invoked.
-  const auto* new_node = manager()->Add(invalid_url, kTitle);
-  EXPECT_EQ(nullptr, new_node)
-      << "Should return nullptr when the URL scheme is not supported.";
-}
-
 // Verifes SetReadStatus()/GetReadStatus() API.
 TEST_F(ReadingListManagerImplTest, ReadStatus) {
   GURL url(kURL);
diff --git a/chrome/browser/resources/chromeos/accessibility/BUILD.gn b/chrome/browser/resources/chromeos/accessibility/BUILD.gn
index e220a7c..f4d49f2 100644
--- a/chrome/browser/resources/chromeos/accessibility/BUILD.gn
+++ b/chrome/browser/resources/chromeos/accessibility/BUILD.gn
@@ -147,11 +147,6 @@
   sources = [
     "braille_ime/braille_ime_unittest.js",
     "common/rect_util_unittest.js",
-    "select_to_speak/node_utils_unittest.js",
-    "select_to_speak/paragraph_utils_unittest.js",
-    "select_to_speak/select_to_speak_unittest.js",
-    "select_to_speak/sentence_utils_unittest.js",
-    "select_to_speak/word_utils_unittest.js",
   ]
   gen_include_files = [ "common/closure_shim.js" ]
   extra_js_files = [
@@ -163,7 +158,6 @@
     "common/tree_walker.js",
     "select_to_speak/paragraph_utils.js",
     "select_to_speak/select_to_speak.js",
-    "select_to_speak/test_support.js",
     "select_to_speak/word_utils.js",
     "select_to_speak/node_utils.js",
     "select_to_speak/sentence_utils.js",
diff --git a/chrome/browser/resources/chromeos/accessibility/common/testing/e2e_test_base.js b/chrome/browser/resources/chromeos/accessibility/common/testing/e2e_test_base.js
index a835bec..f8c710f7 100644
--- a/chrome/browser/resources/chromeos/accessibility/common/testing/e2e_test_base.js
+++ b/chrome/browser/resources/chromeos/accessibility/common/testing/e2e_test_base.js
@@ -167,3 +167,18 @@
  * No UI in the background context.
  */
 E2ETestBase.prototype.runAccessibilityChecks = false;
+
+/**
+ * Similar to |TEST_F|. Generates a test for the given |testFixture|,
+ * |testName|, and |testFunction|.
+ * Used this variant when an |isAsync| fixture wants to temporarily mix in an
+ * sync test.
+ * @param {string} testFixture Fixture name.
+ * @param {string} testName Test name.
+ * @param {function} testFunction The test impl.
+ */
+function SYNC_TEST_F(testFixture, testName, testFunction) {
+  TEST_F(testFixture, testName, function() {
+    this.newCallback(testFunction)();
+  });
+}
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/BUILD.gn b/chrome/browser/resources/chromeos/accessibility/select_to_speak/BUILD.gn
index 75750e9..5a312ef 100644
--- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/BUILD.gn
+++ b/chrome/browser/resources/chromeos/accessibility/select_to_speak/BUILD.gn
@@ -26,6 +26,7 @@
   mode = "copy"
   dest_dir = select_to_speak_out_dir
   sources = [
+    "background.html",
     "checked.png",
     "earcons/null_selection.ogg",
     "input_handler.js",
@@ -37,6 +38,7 @@
     "prefs_manager.js",
     "select_to_speak-2x.svg",
     "select_to_speak.js",
+    "select_to_speak_constants.js",
     "select_to_speak_main.js",
     "select_to_speak_options.js",
     "sentence_utils.js",
@@ -81,6 +83,15 @@
     "select_to_speak_navigation_control_test.js",
     "select_to_speak_prefs_test.js",
   ]
+
+  # These are unit tests run under an extension environment to get ES6 module support.
+  sources += [
+    "node_utils_unittest.js",
+    "paragraph_utils_unittest.js",
+    "select_to_speak_unittest.js",
+    "sentence_utils_unittest.js",
+    "word_utils_unittest.js",
+  ]
   gen_include_files = [
     "../common/testing/callback_helper.js",
     "../common/testing/e2e_test_base.js",
@@ -113,6 +124,7 @@
     ":paragraph_utils",
     ":prefs_manager",
     ":select_to_speak",
+    ":select_to_speak_constants",
     ":select_to_speak_options",
     ":sentence_utils",
     ":word_utils",
@@ -197,3 +209,6 @@
   deps = [ ":prefs_manager" ]
   externs_list = [ "$externs_path/metrics_private.js" ]
 }
+
+js_library("select_to_speak_constants") {
+}
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/background.html b/chrome/browser/resources/chromeos/accessibility/select_to_speak/background.html
new file mode 100644
index 0000000..cad25b8
--- /dev/null
+++ b/chrome/browser/resources/chromeos/accessibility/select_to_speak/background.html
@@ -0,0 +1,13 @@
+<!-- Global scripts. -->
+<script src="/common/closure_shim.js"></script>
+<script src="/common/constants.js"></script>
+<script src="/common/array_util.js"></script>
+<script src="/common/automation_predicate.js"></script>
+<script src="/common/automation_util.js"></script>
+<script src="/common/key_code.js"></script>
+<script src="/common/instance_checker.js"></script>
+<script src="/common/rect_util.js"></script>
+<script src="/common/tree_walker.js"></script>
+
+<!-- Module entrypoint. -->
+<script type="module" src="/select_to_speak/select_to_speak_main.js"></script>
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/input_handler.js b/chrome/browser/resources/chromeos/accessibility/select_to_speak/input_handler.js
index 7ed45f1..d1a4ced 100644
--- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/input_handler.js
+++ b/chrome/browser/resources/chromeos/accessibility/select_to_speak/input_handler.js
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import {SelectToSpeakConstants} from './select_to_speak_constants.js';
+
 const SelectToSpeakState = chrome.accessibilityPrivate.SelectToSpeakState;
 
 /**
@@ -29,7 +31,7 @@
 /**
  * Class to handle user-input, from mouse, keyboard, and copy-paste events.
  */
-class InputHandler {
+export class InputHandler {
   /**
    * @param {SelectToSpeakCallbacks} callbacks
    */
@@ -233,7 +235,7 @@
     }
     this.onMouseMove_(evt);
     this.trackingMouse_ = false;
-    if (!this.keysCurrentlyDown_.has(SelectToSpeak.SEARCH_KEY_CODE)) {
+    if (!this.keysCurrentlyDown_.has(SelectToSpeakConstants.SEARCH_KEY_CODE)) {
       // This is only needed to cancel something started with the search key.
       this.didTrackMouse_ = false;
     }
@@ -257,11 +259,11 @@
     this.keysCurrentlyDown_.add(evt.keyCode);
     this.keysPressedTogether_.add(evt.keyCode);
     if (this.keysPressedTogether_.size === 1 &&
-        evt.keyCode === SelectToSpeak.SEARCH_KEY_CODE) {
+        evt.keyCode === SelectToSpeakConstants.SEARCH_KEY_CODE) {
       this.isSearchKeyDown_ = true;
     } else if (
         this.keysCurrentlyDown_.size === 2 &&
-        evt.keyCode === SelectToSpeak.READ_SELECTION_KEY_CODE &&
+        evt.keyCode === SelectToSpeakConstants.READ_SELECTION_KEY_CODE &&
         !this.trackingMouse_) {
       // Only go into selection mode if we aren't already tracking the mouse.
       this.isSelectionKeyDown_ = true;
@@ -276,14 +278,15 @@
    * @param {!Event} evt
    */
   onKeyUp_(evt) {
-    if (evt.keyCode === SelectToSpeak.READ_SELECTION_KEY_CODE) {
+    if (evt.keyCode === SelectToSpeakConstants.READ_SELECTION_KEY_CODE) {
       if (this.isSelectionKeyDown_ && this.keysPressedTogether_.size === 2 &&
           this.keysPressedTogether_.has(evt.keyCode) &&
-          this.keysPressedTogether_.has(SelectToSpeak.SEARCH_KEY_CODE)) {
+          this.keysPressedTogether_.has(
+              SelectToSpeakConstants.SEARCH_KEY_CODE)) {
         this.callbacks_.onKeystrokeSelection();
       }
       this.isSelectionKeyDown_ = false;
-    } else if (evt.keyCode === SelectToSpeak.SEARCH_KEY_CODE) {
+    } else if (evt.keyCode === SelectToSpeakConstants.SEARCH_KEY_CODE) {
       this.isSearchKeyDown_ = false;
 
       // If we were in the middle of tracking the mouse, cancel it.
@@ -296,8 +299,8 @@
     // Stop speech when the user taps and releases Control or Search
     // without using the mouse or pressing any other keys along the way.
     if (!this.didTrackMouse_ &&
-        (evt.keyCode === SelectToSpeak.SEARCH_KEY_CODE ||
-         evt.keyCode === SelectToSpeak.CONTROL_KEY_CODE) &&
+        (evt.keyCode === SelectToSpeakConstants.SEARCH_KEY_CODE ||
+         evt.keyCode === SelectToSpeakConstants.CONTROL_KEY_CODE) &&
         this.keysPressedTogether_.has(evt.keyCode) &&
         this.keysPressedTogether_.size === 1) {
       this.trackingMouse_ = false;
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/metrics_utils.js b/chrome/browser/resources/chromeos/accessibility/select_to_speak/metrics_utils.js
index 963b853..8ddacaae 100644
--- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/metrics_utils.js
+++ b/chrome/browser/resources/chromeos/accessibility/select_to_speak/metrics_utils.js
@@ -2,9 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import {PrefsManager} from './prefs_manager.js';
+
 // Utilities for UMA metrics.
 
-class MetricsUtils {
+export class MetricsUtils {
   constructor() {}
 
   /**
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/node_utils.js b/chrome/browser/resources/chromeos/accessibility/select_to_speak/node_utils.js
index 204efc6..891e436 100644
--- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/node_utils.js
+++ b/chrome/browser/resources/chromeos/accessibility/select_to_speak/node_utils.js
@@ -2,9 +2,14 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import {ParagraphUtils} from './paragraph_utils.js';
+
+const AutomationNode = chrome.automation.AutomationNode;
+const RoleType = chrome.automation.RoleType;
+
 // Utilities for automation nodes in Select-to-Speak.
 
-class NodeUtils {
+export class NodeUtils {
   constructor() {}
 
   /**
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/node_utils_unittest.js b/chrome/browser/resources/chromeos/accessibility/select_to_speak/node_utils_unittest.js
index d57859a7..11af58c 100644
--- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/node_utils_unittest.js
+++ b/chrome/browser/resources/chromeos/accessibility/select_to_speak/node_utils_unittest.js
@@ -2,75 +2,80 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+GEN_INCLUDE(['select_to_speak_e2e_test_base.js']);
+
 /**
  * Test fixture for node_utils.js.
  */
-SelectToSpeakNodeUtilsUnitTest = class extends testing.Test {};
+SelectToSpeakNodeUtilsUnitTest = class extends SelectToSpeakE2ETest {
+  /** @override */
+  setUp() {
+    var runTest = this.deferRunTest(WhenTestDone.EXPECT);
+    (async function() {
+      let module = await import('/select_to_speak/node_utils.js');
+      window.NodeUtils = module.NodeUtils;
 
-/** @override */
-SelectToSpeakNodeUtilsUnitTest.prototype.extraLibraries = [
-  'test_support.js',
-  'paragraph_utils.js',
-  'node_utils.js',
-  'word_utils.js',
-  '../common/closure_shim.js',
-  '../common/constants.js',
-  '../common/automation_predicate.js',
-  '../common/automation_util.js',
-  '../common/rect_util.js',
-  '../common/tree_walker.js',
-];
+      module = await import('/select_to_speak/paragraph_utils.js');
+      window.ParagraphUtils = module.ParagraphUtils;
 
+      module = await import('/select_to_speak/word_utils.js');
+      window.WordUtils = module.WordUtils;
 
-TEST_F('SelectToSpeakNodeUtilsUnitTest', 'GetNodeVisibilityState', function() {
-  const nodeWithoutRoot1 = {root: null};
-  const nodeWithoutRoot2 = {root: null, state: {invisible: true}};
-  assertEquals(
-      NodeUtils.getNodeState(nodeWithoutRoot1),
-      NodeUtils.NodeState.NODE_STATE_INVALID);
-  assertEquals(
-      NodeUtils.getNodeState(nodeWithoutRoot2),
-      NodeUtils.NodeState.NODE_STATE_INVALID);
+      runTest();
+    })();
+  }
+};
 
-  const invisibleNode1 = {
-    root: {},
-    parent: {role: ''},
-    state: {invisible: true}
-  };
-  // Currently nodes aren't actually marked 'invisible', so we need to navigate
-  // up their tree.
-  const invisibleNode2 = {
-    root: {},
-    parent: {role: 'window', state: {invisible: true}},
-    state: {}
-  };
-  const invisibleNode3 = {root: {}, parent: invisibleNode2, state: {}};
-  const invisibleNode4 = {root: {}, parent: invisibleNode3, state: {}};
-  assertEquals(
-      NodeUtils.getNodeState(invisibleNode1),
-      NodeUtils.NodeState.NODE_STATE_INVISIBLE);
-  assertEquals(
-      NodeUtils.getNodeState(invisibleNode2),
-      NodeUtils.NodeState.NODE_STATE_INVISIBLE);
-  assertEquals(
-      NodeUtils.getNodeState(invisibleNode3),
-      NodeUtils.NodeState.NODE_STATE_INVISIBLE);
+SYNC_TEST_F(
+    'SelectToSpeakNodeUtilsUnitTest', 'GetNodeVisibilityState', function() {
+      const nodeWithoutRoot1 = {root: null};
+      const nodeWithoutRoot2 = {root: null, state: {invisible: true}};
+      assertEquals(
+          NodeUtils.getNodeState(nodeWithoutRoot1),
+          NodeUtils.NodeState.NODE_STATE_INVALID);
+      assertEquals(
+          NodeUtils.getNodeState(nodeWithoutRoot2),
+          NodeUtils.NodeState.NODE_STATE_INVALID);
 
-  const normalNode1 = {
-    root: {},
-    parent: {role: 'window', state: {}},
-    state: {}
-  };
-  const normalNode2 = {root: {}, parent: {normalNode1}, state: {}};
-  assertEquals(
-      NodeUtils.getNodeState(normalNode1),
-      NodeUtils.NodeState.NODE_STATE_NORMAL);
-  assertEquals(
-      NodeUtils.getNodeState(normalNode2),
-      NodeUtils.NodeState.NODE_STATE_NORMAL);
-});
+      const invisibleNode1 = {
+        root: {},
+        parent: {role: ''},
+        state: {invisible: true}
+      };
+      // Currently nodes aren't actually marked 'invisible', so we need to
+      // navigate up their tree.
+      const invisibleNode2 = {
+        root: {},
+        parent: {role: 'window', state: {invisible: true}},
+        state: {}
+      };
+      const invisibleNode3 = {root: {}, parent: invisibleNode2, state: {}};
+      const invisibleNode4 = {root: {}, parent: invisibleNode3, state: {}};
+      assertEquals(
+          NodeUtils.getNodeState(invisibleNode1),
+          NodeUtils.NodeState.NODE_STATE_INVISIBLE);
+      assertEquals(
+          NodeUtils.getNodeState(invisibleNode2),
+          NodeUtils.NodeState.NODE_STATE_INVISIBLE);
+      assertEquals(
+          NodeUtils.getNodeState(invisibleNode3),
+          NodeUtils.NodeState.NODE_STATE_INVISIBLE);
 
-TEST_F(
+      const normalNode1 = {
+        root: {},
+        parent: {role: 'window', state: {}},
+        state: {}
+      };
+      const normalNode2 = {root: {}, parent: {normalNode1}, state: {}};
+      assertEquals(
+          NodeUtils.getNodeState(normalNode1),
+          NodeUtils.NodeState.NODE_STATE_NORMAL);
+      assertEquals(
+          NodeUtils.getNodeState(normalNode2),
+          NodeUtils.NodeState.NODE_STATE_NORMAL);
+    });
+
+SYNC_TEST_F(
     'SelectToSpeakNodeUtilsUnitTest', 'GetNodeVisibilityStateWithRootWebArea',
     function() {
       // Currently nodes aren't actually marked 'invisible', so we need to
@@ -121,7 +126,7 @@
           NodeUtils.NodeState.NODE_STATE_NORMAL);
     });
 
-TEST_F('SelectToSpeakNodeUtilsUnitTest', 'findAllMatching', function() {
+SYNC_TEST_F('SelectToSpeakNodeUtilsUnitTest', 'findAllMatching', function() {
   const rect = {left: 0, top: 0, width: 100, height: 100};
   const rootNode = {
     root: {},
@@ -255,7 +260,7 @@
   assertEquals(container2, result[0]);
 });
 
-TEST_F(
+SYNC_TEST_F(
     'SelectToSpeakNodeUtilsUnitTest', 'findAllMatchingWithInputs', function() {
       const rect = {left: 0, top: 0, width: 100, height: 100};
       const rootNode = {
@@ -281,7 +286,7 @@
       assertEquals(checkbox, result[0]);
     });
 
-TEST_F(
+SYNC_TEST_F(
     'SelectToSpeakNodeUtilsUnitTest',
     'getDeepEquivalentForSelectionDeprecatedNoChildren', function() {
       const node = {name: 'Hello, world', children: []};
@@ -294,7 +299,7 @@
       assertEquals(6, result.offset);
     });
 
-TEST_F(
+SYNC_TEST_F(
     'SelectToSpeakNodeUtilsUnitTest',
     'getDeepEquivalentForSelectionDeprecatedSimpleChildren', function() {
       const child1 =
@@ -330,7 +335,7 @@
       assertEquals(3, result.offset);
     });
 
-TEST_F(
+SYNC_TEST_F(
     'SelectToSpeakNodeUtilsUnitTest',
     'getDeepEquivalentForSelectionDeprecatedComplexChildren', function() {
       const child1 =
@@ -403,7 +408,7 @@
       assertEquals(0, result.offset);
     });
 
-TEST_F(
+SYNC_TEST_F(
     'SelectToSpeakNodeUtilsUnitTest', 'sortSvgNodesByReadingOrder', function() {
       const svgRootNode = {role: 'svgRoot'};
       const gNode1 = {
@@ -442,7 +447,7 @@
       assertEquals(nodes[2].name, 'three');
     });
 
-TEST_F(
+SYNC_TEST_F(
     'SelectToSpeakNodeUtilsUnitTest', 'sortNodesByReadingOrderMultipleSVGs',
     function() {
       const textNode1 = {role: 'staticText', name: 'Text Node 1'};
@@ -490,7 +495,7 @@
       assertEquals(nodes[6].name, 'Text Node 3');
     });
 
-TEST_F('SelectToSpeakNodeUtilsUnitTest', 'getNextParagraph', function() {
+SYNC_TEST_F('SelectToSpeakNodeUtilsUnitTest', 'getNextParagraph', function() {
   const root = createMockNode({role: 'rootWebArea'});
   const paragraph1 =
       createMockNode({role: 'paragraph', display: 'block', parent: root, root});
@@ -551,7 +556,7 @@
   assertEquals(result.length, 0);
 });
 
-TEST_F(
+SYNC_TEST_F(
     'SelectToSpeakNodeUtilsUnitTest', 'getNextParagraphContainedWithinRoot',
     function() {
       const desktop = createMockNode({role: 'desktop'});
@@ -584,7 +589,7 @@
       assertEquals(result.length, 0);
     });
 
-TEST_F(
+SYNC_TEST_F(
     'SelectToSpeakNodeUtilsUnitTest', 'getNextParagraphThroughIframe',
     function() {
       const desktop = createMockNode({role: 'desktop'});
@@ -623,7 +628,7 @@
       assertEquals(result[0], text3);
     });
 
-TEST_F(
+SYNC_TEST_F(
     'SelectToSpeakNodeUtilsUnitTest', 'getNextParagraphNonBlockNodes',
     function() {
       /**
@@ -663,7 +668,7 @@
       assertEquals(result[0], text2);
     });
 
-TEST_F(
+SYNC_TEST_F(
     'SelectToSpeakNodeUtilsUnitTest', 'getNextParagraphNestedBlocks',
     function() {
       const root = createMockNode({role: 'rootWebArea'});
@@ -695,142 +700,146 @@
       assertEquals(result[0], text1);
     });
 
-TEST_F('SelectToSpeakNodeUtilsUnitTest', 'getNextParagraphAndroid', function() {
-  const root = createMockNode({role: 'application'});
-  const container1 =
-      createMockNode({role: 'genericContainer', parent: root, root});
-  const text1 = createMockNode(
-      {role: 'staticText', parent: container1, root, name: 'Line 1'});
-  const text2 = createMockNode(
-      {role: 'staticText', parent: container1, root, name: 'Line 2'});
-  const container2 =
-      createMockNode({role: 'genericContainer', parent: root, root});
-  const text3 = createMockNode(
-      {role: 'staticText', parent: container2, root, name: 'Line 3'});
+SYNC_TEST_F(
+    'SelectToSpeakNodeUtilsUnitTest', 'getNextParagraphAndroid', function() {
+      const root = createMockNode({role: 'application'});
+      const container1 =
+          createMockNode({role: 'genericContainer', parent: root, root});
+      const text1 = createMockNode(
+          {role: 'staticText', parent: container1, root, name: 'Line 1'});
+      const text2 = createMockNode(
+          {role: 'staticText', parent: container1, root, name: 'Line 2'});
+      const container2 =
+          createMockNode({role: 'genericContainer', parent: root, root});
+      const text3 = createMockNode(
+          {role: 'staticText', parent: container2, root, name: 'Line 3'});
 
-  // Without paragraphs, navigate node to node.
-  let result = NodeUtils.getNextParagraph(text1, constants.Dir.FORWARD);
-  assertEquals(result.length, 1);
-  assertEquals(result[0], text2);
+      // Without paragraphs, navigate node to node.
+      let result = NodeUtils.getNextParagraph(text1, constants.Dir.FORWARD);
+      assertEquals(result.length, 1);
+      assertEquals(result[0], text2);
 
-  result = NodeUtils.getNextParagraph(text2, constants.Dir.FORWARD);
-  assertEquals(result.length, 1);
-  assertEquals(result[0], text3);
+      result = NodeUtils.getNextParagraph(text2, constants.Dir.FORWARD);
+      assertEquals(result.length, 1);
+      assertEquals(result[0], text3);
 
-  result = NodeUtils.getNextParagraph(container1, constants.Dir.FORWARD);
-  assertEquals(result.length, 1);
-  assertEquals(result[0], text3);
+      result = NodeUtils.getNextParagraph(container1, constants.Dir.FORWARD);
+      assertEquals(result.length, 1);
+      assertEquals(result[0], text3);
 
-  result = NodeUtils.getNextParagraph(text3, constants.Dir.FORWARD);
-  assertEquals(result.length, 0);
+      result = NodeUtils.getNextParagraph(text3, constants.Dir.FORWARD);
+      assertEquals(result.length, 0);
 
-  result = NodeUtils.getNextParagraph(container2, constants.Dir.FORWARD);
-  assertEquals(result.length, 0);
+      result = NodeUtils.getNextParagraph(container2, constants.Dir.FORWARD);
+      assertEquals(result.length, 0);
 
-  result = NodeUtils.getNextParagraph(text3, constants.Dir.BACKWARD);
-  assertEquals(result.length, 1);
-  assertEquals(result[0], text2);
+      result = NodeUtils.getNextParagraph(text3, constants.Dir.BACKWARD);
+      assertEquals(result.length, 1);
+      assertEquals(result[0], text2);
 
-  result = NodeUtils.getNextParagraph(container2, constants.Dir.BACKWARD);
-  assertEquals(result.length, 1);
-  assertEquals(result[0], text2);
+      result = NodeUtils.getNextParagraph(container2, constants.Dir.BACKWARD);
+      assertEquals(result.length, 1);
+      assertEquals(result[0], text2);
 
-  result = NodeUtils.getNextParagraph(text2, constants.Dir.BACKWARD);
-  assertEquals(result.length, 1);
-  assertEquals(result[0], text1);
+      result = NodeUtils.getNextParagraph(text2, constants.Dir.BACKWARD);
+      assertEquals(result.length, 1);
+      assertEquals(result[0], text1);
 
-  result = NodeUtils.getNextParagraph(text1, constants.Dir.BACKWARD);
-  assertEquals(result.length, 0);
+      result = NodeUtils.getNextParagraph(text1, constants.Dir.BACKWARD);
+      assertEquals(result.length, 0);
 
-  result = NodeUtils.getNextParagraph(container1, constants.Dir.BACKWARD);
-  assertEquals(result.length, 0);
-});
+      result = NodeUtils.getNextParagraph(container1, constants.Dir.BACKWARD);
+      assertEquals(result.length, 0);
+    });
 
-TEST_F('SelectToSpeakNodeUtilsUnitTest', 'getNextNodesInParagraph', function() {
-  const root = createMockNode({role: 'rootWebArea'});
-  createMockNode({role: 'paragraph', display: 'block', parent: root, root});
-  const paragraph2 =
+SYNC_TEST_F(
+    'SelectToSpeakNodeUtilsUnitTest', 'getNextNodesInParagraph', function() {
+      const root = createMockNode({role: 'rootWebArea'});
       createMockNode({role: 'paragraph', display: 'block', parent: root, root});
-  const text1 = createMockNode(
-      {role: 'staticText', parent: paragraph2, root, name: 'Line 1'});
-  const text2 = createMockNode(
-      {role: 'staticText', parent: paragraph2, root, name: 'Line 2'});
-  const text3 = createMockNode(
-      {role: 'staticText', parent: paragraph2, root, name: 'Line 3'});
-  createMockNode({role: 'paragraph', display: 'block', parent: root, root});
-
-  let result = NodeUtils.getNextNodesInParagraph(text2, constants.Dir.FORWARD);
-  assertEquals(result.length, 1);
-  assertEquals(result[0], text3);
-
-  result = NodeUtils.getNextNodesInParagraph(text1, constants.Dir.FORWARD);
-  assertEquals(result.length, 2);
-  assertEquals(result[0], text2);
-  assertEquals(result[1], text3);
-
-  result = NodeUtils.getNextNodesInParagraph(text3, constants.Dir.FORWARD);
-  assertEquals(result.length, 0);
-
-  result = NodeUtils.getNextNodesInParagraph(text3, constants.Dir.BACKWARD);
-  assertEquals(result.length, 2);
-  assertEquals(result[0], text1);
-  assertEquals(result[1], text2);
-
-  result = NodeUtils.getNextNodesInParagraph(text2, constants.Dir.BACKWARD);
-  assertEquals(result.length, 1);
-  assertEquals(result[0], text1);
-
-  result = NodeUtils.getNextNodesInParagraph(text1, constants.Dir.BACKWARD);
-  assertEquals(result.length, 0);
-});
-
-TEST_F('SelectToSpeakNodeUtilsUnitTest', 'getAllNodesInParagraph', function() {
-  const root = createMockNode({role: 'rootWebArea'});
-  const paragraph1 =
+      const paragraph2 = createMockNode(
+          {role: 'paragraph', display: 'block', parent: root, root});
+      const text1 = createMockNode(
+          {role: 'staticText', parent: paragraph2, root, name: 'Line 1'});
+      const text2 = createMockNode(
+          {role: 'staticText', parent: paragraph2, root, name: 'Line 2'});
+      const text3 = createMockNode(
+          {role: 'staticText', parent: paragraph2, root, name: 'Line 3'});
       createMockNode({role: 'paragraph', display: 'block', parent: root, root});
-  const text1 = createMockNode(
-      {role: 'staticText', parent: paragraph1, root, name: 'Line 1'});
-  const text2 = createMockNode(
-      {role: 'staticText', parent: paragraph1, root, name: 'Line 2'});
-  const paragraph2 =
-      createMockNode({role: 'paragraph', display: 'block', parent: root, root});
-  const text3 = createMockNode(
-      {role: 'staticText', parent: paragraph2, root, name: 'Line 3'});
-  const text4 = createMockNode(
-      {role: 'staticText', parent: paragraph2, root, name: 'Line 4'});
-  const text5 = createMockNode(
-      {role: 'staticText', parent: paragraph2, root, name: 'Line 5'});
 
-  let result = NodeUtils.getAllNodesInParagraph(text1);
-  assertEquals(result.length, 2);
-  assertEquals(result[0], text1);
-  assertEquals(result[1], text2);
+      let result =
+          NodeUtils.getNextNodesInParagraph(text2, constants.Dir.FORWARD);
+      assertEquals(result.length, 1);
+      assertEquals(result[0], text3);
 
-  result = NodeUtils.getAllNodesInParagraph(text2);
-  assertEquals(result.length, 2);
-  assertEquals(result[0], text1);
-  assertEquals(result[1], text2);
+      result = NodeUtils.getNextNodesInParagraph(text1, constants.Dir.FORWARD);
+      assertEquals(result.length, 2);
+      assertEquals(result[0], text2);
+      assertEquals(result[1], text3);
 
-  result = NodeUtils.getAllNodesInParagraph(text3);
-  assertEquals(result.length, 3);
-  assertEquals(result[0], text3);
-  assertEquals(result[1], text4);
-  assertEquals(result[2], text5);
+      result = NodeUtils.getNextNodesInParagraph(text3, constants.Dir.FORWARD);
+      assertEquals(result.length, 0);
 
-  result = NodeUtils.getAllNodesInParagraph(text4);
-  assertEquals(result.length, 3);
-  assertEquals(result[0], text3);
-  assertEquals(result[1], text4);
-  assertEquals(result[2], text5);
+      result = NodeUtils.getNextNodesInParagraph(text3, constants.Dir.BACKWARD);
+      assertEquals(result.length, 2);
+      assertEquals(result[0], text1);
+      assertEquals(result[1], text2);
 
-  result = NodeUtils.getAllNodesInParagraph(text5);
-  assertEquals(result.length, 3);
-  assertEquals(result[0], text3);
-  assertEquals(result[1], text4);
-  assertEquals(result[2], text5);
-});
+      result = NodeUtils.getNextNodesInParagraph(text2, constants.Dir.BACKWARD);
+      assertEquals(result.length, 1);
+      assertEquals(result[0], text1);
 
-TEST_F(
+      result = NodeUtils.getNextNodesInParagraph(text1, constants.Dir.BACKWARD);
+      assertEquals(result.length, 0);
+    });
+
+SYNC_TEST_F(
+    'SelectToSpeakNodeUtilsUnitTest', 'getAllNodesInParagraph', function() {
+      const root = createMockNode({role: 'rootWebArea'});
+      const paragraph1 = createMockNode(
+          {role: 'paragraph', display: 'block', parent: root, root});
+      const text1 = createMockNode(
+          {role: 'staticText', parent: paragraph1, root, name: 'Line 1'});
+      const text2 = createMockNode(
+          {role: 'staticText', parent: paragraph1, root, name: 'Line 2'});
+      const paragraph2 = createMockNode(
+          {role: 'paragraph', display: 'block', parent: root, root});
+      const text3 = createMockNode(
+          {role: 'staticText', parent: paragraph2, root, name: 'Line 3'});
+      const text4 = createMockNode(
+          {role: 'staticText', parent: paragraph2, root, name: 'Line 4'});
+      const text5 = createMockNode(
+          {role: 'staticText', parent: paragraph2, root, name: 'Line 5'});
+
+      let result = NodeUtils.getAllNodesInParagraph(text1);
+      assertEquals(result.length, 2);
+      assertEquals(result[0], text1);
+      assertEquals(result[1], text2);
+
+      result = NodeUtils.getAllNodesInParagraph(text2);
+      assertEquals(result.length, 2);
+      assertEquals(result[0], text1);
+      assertEquals(result[1], text2);
+
+      result = NodeUtils.getAllNodesInParagraph(text3);
+      assertEquals(result.length, 3);
+      assertEquals(result[0], text3);
+      assertEquals(result[1], text4);
+      assertEquals(result[2], text5);
+
+      result = NodeUtils.getAllNodesInParagraph(text4);
+      assertEquals(result.length, 3);
+      assertEquals(result[0], text3);
+      assertEquals(result[1], text4);
+      assertEquals(result[2], text5);
+
+      result = NodeUtils.getAllNodesInParagraph(text5);
+      assertEquals(result.length, 3);
+      assertEquals(result[0], text3);
+      assertEquals(result[1], text4);
+      assertEquals(result[2], text5);
+    });
+
+SYNC_TEST_F(
     'SelectToSpeakNodeUtilsUnitTest',
     'getNextNodesInParagraphFromNodeGroup_forward', function() {
       // The nodeGroup has four inline text nodes and one static text node.
@@ -868,7 +877,7 @@
       assertEquals(result.nodes.length, 0);
     });
 
-TEST_F(
+SYNC_TEST_F(
     'SelectToSpeakNodeUtilsUnitTest',
     'getNextNodesInParagraphFromNodeGroup_backward', function() {
       // The nodeGroup has four inline text nodes and one static text node.
@@ -909,7 +918,7 @@
       assertEquals(result.nodes.length, 0);
     });
 
-TEST_F(
+SYNC_TEST_F(
     'SelectToSpeakNodeUtilsUnitTest',
     'getNextNodesInParagraphFromNodeGroup_forwardWithEmptyTail', function() {
       // The nodeGroup consists of three inline text nodes: "Hello", "world ",
@@ -935,7 +944,7 @@
       assertEquals(result.nodes[0].name, 'world ');
     });
 
-TEST_F(
+SYNC_TEST_F(
     'SelectToSpeakNodeUtilsUnitTest',
     'getNextNodesInParagraphFromNodeGroup_backwardWithEmptyHeads', function() {
       // The nodeGroup consists of three inline text nodes: " ", " Hello",
@@ -961,7 +970,7 @@
       assertEquals(result.nodes[0].name, ' Hello');
     });
 
-TEST_F(
+SYNC_TEST_F(
     'SelectToSpeakNodeUtilsUnitTest',
     'getNextNodesInParagraphFromNodeGroup_forwardFromPartialParagraph',
     function() {
@@ -982,7 +991,7 @@
       assertEquals(result.offset, 0);
     });
 
-TEST_F(
+SYNC_TEST_F(
     'SelectToSpeakNodeUtilsUnitTest',
     'getNextNodesInParagraphFromNodeGroup_backwardFromPartialParagraph',
     function() {
@@ -1154,4 +1163,4 @@
 
   return ParagraphUtils.buildNodeGroup(
       [text2], 0, false /* do not split on language */);
-}
\ No newline at end of file
+}
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/paragraph_utils.js b/chrome/browser/resources/chromeos/accessibility/select_to_speak/paragraph_utils.js
index fe2f669..60fc5417 100644
--- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/paragraph_utils.js
+++ b/chrome/browser/resources/chromeos/accessibility/select_to_speak/paragraph_utils.js
@@ -2,10 +2,12 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import {WordUtils} from './word_utils.js';
+
 var AutomationNode = chrome.automation.AutomationNode;
 var RoleType = chrome.automation.RoleType;
 
-class ParagraphUtils {
+export class ParagraphUtils {
   constructor() {}
 
   /**
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/paragraph_utils_overflow_test.js b/chrome/browser/resources/chromeos/accessibility/select_to_speak/paragraph_utils_overflow_test.js
index ed78f27b..9f36d50 100644
--- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/paragraph_utils_overflow_test.js
+++ b/chrome/browser/resources/chromeos/accessibility/select_to_speak/paragraph_utils_overflow_test.js
@@ -8,6 +8,17 @@
  * Browser tests for select-to-speak's feature to filter out overflow text.
  */
 SelectToSpeakParagraphOverflowTest = class extends SelectToSpeakE2ETest {
+  /** @override */
+  setUp() {
+    var runTest = this.deferRunTest(WhenTestDone.EXPECT);
+    (async function() {
+      const module = await import('/select_to_speak/paragraph_utils.js');
+      window.ParagraphUtils = module.ParagraphUtils;
+
+      runTest();
+    })();
+  }
+
   generateHorizentalOverflowText(text) {
     return (
         '<div style="width: 50px; overflow: hidden">' +
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/paragraph_utils_unittest.js b/chrome/browser/resources/chromeos/accessibility/select_to_speak/paragraph_utils_unittest.js
index 82c46834..ac492404 100644
--- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/paragraph_utils_unittest.js
+++ b/chrome/browser/resources/chromeos/accessibility/select_to_speak/paragraph_utils_unittest.js
@@ -2,46 +2,56 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+GEN_INCLUDE(['select_to_speak_e2e_test_base.js']);
+
 /**
  * Test fixture for paragraph_utils.js.
  */
-SelectToSpeakParagraphUnitTest = class extends testing.Test {};
+SelectToSpeakParagraphUnitTest = class extends SelectToSpeakE2ETest {
+  /** @override */
+  setUp() {
+    var runTest = this.deferRunTest(WhenTestDone.EXPECT);
+    (async function() {
+      const module = await import('/select_to_speak/paragraph_utils.js');
+      window.ParagraphUtils = module.ParagraphUtils;
 
-/** @override */
-SelectToSpeakParagraphUnitTest.prototype.extraLibraries =
-    ['test_support.js', 'paragraph_utils.js'];
+      runTest();
+    })();
+  }
+};
 
+SYNC_TEST_F(
+    'SelectToSpeakParagraphUnitTest', 'GetFirstBlockAncestor', function() {
+      const root = {role: 'rootWebArea'};
+      const paragraph = {role: 'paragraph', parent: root, root};
+      const text1 =
+          {role: 'staticText', parent: paragraph, display: 'block', root};
+      const text2 = {role: 'staticText', parent: root, root};
+      const text3 = {role: 'inlineTextBox', parent: text1, root};
+      const div =
+          {role: 'genericContainer', parent: paragraph, display: 'block', root};
+      const text4 = {role: 'staticText', parent: div, root};
+      assertEquals(paragraph, ParagraphUtils.getFirstBlockAncestor(text1));
+      assertEquals(root, ParagraphUtils.getFirstBlockAncestor(text2));
+      assertEquals(paragraph, ParagraphUtils.getFirstBlockAncestor(text3));
+      assertEquals(div, ParagraphUtils.getFirstBlockAncestor(text4));
+    });
 
-TEST_F('SelectToSpeakParagraphUnitTest', 'GetFirstBlockAncestor', function() {
-  const root = {role: 'rootWebArea'};
-  const paragraph = {role: 'paragraph', parent: root, root};
-  const text1 = {role: 'staticText', parent: paragraph, display: 'block', root};
-  const text2 = {role: 'staticText', parent: root, root};
-  const text3 = {role: 'inlineTextBox', parent: text1, root};
-  const div =
-      {role: 'genericContainer', parent: paragraph, display: 'block', root};
-  const text4 = {role: 'staticText', parent: div, root};
-  assertEquals(paragraph, ParagraphUtils.getFirstBlockAncestor(text1));
-  assertEquals(root, ParagraphUtils.getFirstBlockAncestor(text2));
-  assertEquals(paragraph, ParagraphUtils.getFirstBlockAncestor(text3));
-  assertEquals(div, ParagraphUtils.getFirstBlockAncestor(text4));
-});
+SYNC_TEST_F(
+    'SelectToSpeakParagraphUnitTest', 'SVGRootIsBlockAncestor', function() {
+      const root = {role: 'rootWebArea'};
+      const svgRoot = {role: 'svgRoot', parent: root, root};
+      const text1 = {role: 'staticText', parent: svgRoot, root};
+      const inline1 = {role: 'inlineTextBox', parent: text1, root};
+      const text2 = {role: 'staticText', parent: svgRoot, root};
+      const inline2 = {role: 'inlineTextBox', parent: text2, root};
+      assertEquals(svgRoot, ParagraphUtils.getFirstBlockAncestor(text1));
+      assertEquals(svgRoot, ParagraphUtils.getFirstBlockAncestor(inline1));
+      assertEquals(svgRoot, ParagraphUtils.getFirstBlockAncestor(inline2));
+      assertTrue(ParagraphUtils.inSameParagraph(inline1, inline2));
+    });
 
-TEST_F('SelectToSpeakParagraphUnitTest', 'SVGRootIsBlockAncestor', function() {
-  const root = {role: 'rootWebArea'};
-  const svgRoot = {role: 'svgRoot', parent: root, root};
-  const text1 = {role: 'staticText', parent: svgRoot, root};
-  const inline1 = {role: 'inlineTextBox', parent: text1, root};
-  const text2 = {role: 'staticText', parent: svgRoot, root};
-  const inline2 = {role: 'inlineTextBox', parent: text2, root};
-  assertEquals(svgRoot, ParagraphUtils.getFirstBlockAncestor(text1));
-  assertEquals(svgRoot, ParagraphUtils.getFirstBlockAncestor(inline1));
-  assertEquals(svgRoot, ParagraphUtils.getFirstBlockAncestor(inline2));
-  assertTrue(ParagraphUtils.inSameParagraph(inline1, inline2));
-});
-
-
-TEST_F('SelectToSpeakParagraphUnitTest', 'InSameParagraph', function() {
+SYNC_TEST_F('SelectToSpeakParagraphUnitTest', 'InSameParagraph', function() {
   const root = {role: 'rootWebArea'};
   const paragraph1 =
       {role: 'paragraph', display: 'block', parent: 'rootWebArea', root};
@@ -54,7 +64,7 @@
   assertFalse(ParagraphUtils.inSameParagraph(text1, text3));
 });
 
-TEST_F(
+SYNC_TEST_F(
     'SelectToSpeakParagraphUnitTest', 'BlockDivBreaksSameParagraph',
     function() {
       const root = {role: 'rootWebArea'};
@@ -70,7 +80,7 @@
       assertTrue(ParagraphUtils.inSameParagraph(text3, text4));
     });
 
-TEST_F('SelectToSpeakParagraphUnitTest', 'IsWhitespace', function() {
+SYNC_TEST_F('SelectToSpeakParagraphUnitTest', 'IsWhitespace', function() {
   assertTrue(ParagraphUtils.isWhitespace(''));
   assertTrue(ParagraphUtils.isWhitespace(' '));
   assertTrue(ParagraphUtils.isWhitespace(' \n \t '));
@@ -79,7 +89,7 @@
   assertFalse(ParagraphUtils.isWhitespace(' cats '));
 });
 
-TEST_F('SelectToSpeakParagraphUnitTest', 'GetNodeName', function() {
+SYNC_TEST_F('SelectToSpeakParagraphUnitTest', 'GetNodeName', function() {
   assertEquals(
       ParagraphUtils.getNodeName({role: 'staticText', name: 'cat'}), 'cat');
   assertEquals(
@@ -115,7 +125,7 @@
       'partially selected');
 });
 
-TEST_F(
+SYNC_TEST_F(
     'SelectToSpeakParagraphUnitTest', 'GetStartCharIndexInParent', function() {
       const staticText = {
         role: 'staticText',
@@ -145,7 +155,7 @@
       assertEquals(ParagraphUtils.getStartCharIndexInParent(inline3), 17);
     });
 
-TEST_F(
+SYNC_TEST_F(
     'SelectToSpeakParagraphUnitTest', 'FindInlineTextNodeByCharIndex',
     function() {
       const staticText = {
@@ -180,7 +190,7 @@
           null);
     });
 
-TEST_F(
+SYNC_TEST_F(
     'SelectToSpeakParagraphUnitTest', 'FindInlineTextNodeIndexByCharIndex',
     function() {
       const staticText = {
@@ -221,7 +231,7 @@
           -1);
     });
 
-TEST_F(
+SYNC_TEST_F(
     'SelectToSpeakParagraphUnitTest', 'BuildNodeGroupStopsAtNewParagraph',
     function() {
       const root = {role: 'rootWebArea'};
@@ -247,7 +257,7 @@
       assertEquals(paragraph1, result.blockParent);
     });
 
-TEST_F(
+SYNC_TEST_F(
     'SelectToSpeakParagraphUnitTest', 'BuildNodeGroupAcrossParagraphs',
     function() {
       const root = {role: 'rootWebArea'};
@@ -274,7 +284,7 @@
       assertEquals(text3, result.nodes[2].node);
     });
 
-TEST_F(
+SYNC_TEST_F(
     'SelectToSpeakParagraphUnitTest', 'BuildNodeGroupStopsAtLanguageBoundary',
     function() {
       const splitOnLanguage = true;
@@ -325,7 +335,7 @@
       assertEquals('fr-FR', result2.detectedLanguage);
     });
 
-TEST_F(
+SYNC_TEST_F(
     'SelectToSpeakParagraphUnitTest',
     'BuildNodeGroupStopsAtLanguageBoundaryAllUndefined', function() {
       const splitOnLanguage = true;
@@ -349,7 +359,7 @@
       assertEquals(undefined, result.detectedLanguage);
     });
 
-TEST_F(
+SYNC_TEST_F(
     'SelectToSpeakParagraphUnitTest',
     'BuildNodeGroupStopsAtLanguageBoundaryLastNode', function() {
       const splitOnLanguage = true;
@@ -379,7 +389,7 @@
       assertEquals('fr-FR', result.detectedLanguage);
     });
 
-TEST_F(
+SYNC_TEST_F(
     'SelectToSpeakParagraphUnitTest', 'BuildNodeGroupSplitOnLanguageDisabled',
     function() {
       // Test behaviour with splitOnLanguage disabled. This is to show that we
@@ -413,7 +423,7 @@
       assertEquals(undefined, result.detectedLanguage);
     });
 
-TEST_F(
+SYNC_TEST_F(
     'SelectToSpeakParagraphUnitTest',
     'BuildNodeGroupStopsAtLanguageBoundarySomeUndefined', function() {
       const splitOnLanguage = true;
@@ -453,7 +463,7 @@
       assertEquals('en-US', result.detectedLanguage);
     });
 
-TEST_F(
+SYNC_TEST_F(
     'SelectToSpeakParagraphUnitTest', 'BuildNodeGroupIncludesLinks',
     function() {
       const root = {role: 'rootWebArea'};
@@ -478,7 +488,7 @@
       assertEquals(paragraph1, result.blockParent);
     });
 
-TEST_F(
+SYNC_TEST_F(
     'SelectToSpeakParagraphUnitTest', 'BuildNodeGroupNativeTextBox',
     function() {
       const root = {role: 'desktop'};
@@ -499,20 +509,23 @@
       assertEquals('Address and search bar ', result.text);
     });
 
-TEST_F('SelectToSpeakParagraphUnitTest', 'BuildNodeGroupWithSvg', function() {
-  const root = {role: 'rootWebArea'};
-  const svgRoot = {role: 'svgRoot', parent: root, root};
-  const text1 = {role: 'staticText', parent: svgRoot, root, name: 'Hello,'};
-  const inline1 = {role: 'inlineTextBox', parent: text1, root, name: 'Hello,'};
-  const text2 = {role: 'staticText', parent: svgRoot, root, name: 'world!'};
-  const inline2 = {role: 'inlineTextBox', parent: text2, root, name: 'world!'};
+SYNC_TEST_F(
+    'SelectToSpeakParagraphUnitTest', 'BuildNodeGroupWithSvg', function() {
+      const root = {role: 'rootWebArea'};
+      const svgRoot = {role: 'svgRoot', parent: root, root};
+      const text1 = {role: 'staticText', parent: svgRoot, root, name: 'Hello,'};
+      const inline1 =
+          {role: 'inlineTextBox', parent: text1, root, name: 'Hello,'};
+      const text2 = {role: 'staticText', parent: svgRoot, root, name: 'world!'};
+      const inline2 =
+          {role: 'inlineTextBox', parent: text2, root, name: 'world!'};
 
-  const result = ParagraphUtils.buildNodeGroup(
-      [inline1, inline2], 0, {splitOnLanguage: false});
-  assertEquals('Hello, world! ', result.text);
-});
+      const result = ParagraphUtils.buildNodeGroup(
+          [inline1, inline2], 0, {splitOnLanguage: false});
+      assertEquals('Hello, world! ', result.text);
+    });
 
-TEST_F(
+SYNC_TEST_F(
     'SelectToSpeakParagraphUnitTest', 'findNodeFromNodeGroupByCharIndex',
     function() {
       // The array has four inline text nodes and one static text node.
@@ -564,7 +577,7 @@
       assertEquals(result.node, null);
     });
 
-TEST_F(
+SYNC_TEST_F(
     'SelectToSpeakParagraphUnitTest', 'BuildSingleNodeGroupWithOffset',
     function() {
       // The array has four inline text nodes and one static text node.
@@ -675,4 +688,4 @@
   };
 
   return [inlineText1, inlineText2, inlineText3, inlineText4, text3];
-}
\ No newline at end of file
+}
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/prefs_manager.js b/chrome/browser/resources/chromeos/accessibility/select_to_speak/prefs_manager.js
index 009063d..b49af183 100644
--- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/prefs_manager.js
+++ b/chrome/browser/resources/chromeos/accessibility/select_to_speak/prefs_manager.js
@@ -5,7 +5,7 @@
 /**
  * Manages getting and storing user preferences.
  */
-class PrefsManager {
+export class PrefsManager {
   constructor() {
     /** @private {?string} */
     this.voiceNameFromPrefs_ = null;
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak.js b/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak.js
index 50f0d2d..66db632 100644
--- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak.js
+++ b/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak.js
@@ -2,18 +2,29 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-var AutomationEvent = chrome.automation.AutomationEvent;
-var EventType = chrome.automation.EventType;
-var RoleType = chrome.automation.RoleType;
+import {InputHandler} from './input_handler.js';
+import {MetricsUtils} from './metrics_utils.js';
+import {NodeUtils} from './node_utils.js';
+import {ParagraphUtils} from './paragraph_utils.js';
+import {PrefsManager} from './prefs_manager.js';
+import {SelectToSpeakConstants} from './select_to_speak_constants.js';
+import {SentenceUtils} from './sentence_utils.js';
+import {WordUtils} from './word_utils.js';
+
+const AutomationNode = chrome.automation.AutomationNode;
+const AutomationEvent = chrome.automation.AutomationEvent;
+const EventType = chrome.automation.EventType;
+const RoleType = chrome.automation.RoleType;
 const AccessibilityFeature = chrome.accessibilityPrivate.AccessibilityFeature;
 const SelectToSpeakPanelAction =
     chrome.accessibilityPrivate.SelectToSpeakPanelAction;
 const FocusRingStackingOrder =
     chrome.accessibilityPrivate.FocusRingStackingOrder;
+const SelectToSpeakState = chrome.accessibilityPrivate.SelectToSpeakState;
 
 // This must be the same as in ash/system/accessibility/select_to_speak_tray.cc:
 // ash::kSelectToSpeakTrayClassName.
-const SELECT_TO_SPEAK_TRAY_CLASS_NAME =
+export const SELECT_TO_SPEAK_TRAY_CLASS_NAME =
     'tray/TrayBackgroundView/SelectToSpeakTray';
 
 // This must match the name of view class that implements the menu view:
@@ -56,7 +67,7 @@
  * @return {?AutomationNode} The root node of the GSuite app, or null if none is
  *     found.
  */
-function getGSuiteAppRoot(node) {
+export function getGSuiteAppRoot(node) {
   while (node !== undefined && node.root !== undefined) {
     if (node.root.url !== undefined && GSUITE_APP_REGEXP.exec(node.root.url)) {
       return node.root;
@@ -66,7 +77,7 @@
   return null;
 }
 
-class SelectToSpeak {
+export class SelectToSpeak {
   constructor() {
     /**
      * The current state of the SelectToSpeak extension, from
@@ -331,8 +342,9 @@
         return;
       }
       if (this.shouldShowNavigationControls_() && nodes.length > 0 &&
-          (rect.width <= SelectToSpeak.PARAGRAPH_SELECTION_MAX_SIZE ||
-           rect.height <= SelectToSpeak.PARAGRAPH_SELECTION_MAX_SIZE)) {
+          (rect.width <= SelectToSpeakConstants.PARAGRAPH_SELECTION_MAX_SIZE ||
+           rect.height <=
+               SelectToSpeakConstants.PARAGRAPH_SELECTION_MAX_SIZE)) {
         // If this is a single click (zero sized selection) on a text node, then
         // expand to entire paragraph.
         nodes = NodeUtils.getAllNodesInParagraph(nodes[0]);
@@ -1653,7 +1665,7 @@
     }
     this.intervalRef_ = setInterval(
         this.testCurrentNode_.bind(this),
-        SelectToSpeak.NODE_STATE_TEST_INTERVAL_MS);
+        SelectToSpeakConstants.NODE_STATE_TEST_INTERVAL_MS);
   }
 
   /**
@@ -2067,30 +2079,3 @@
     this.inputHandler_.onMouseUp_(event);
   }
 }
-
-/** @const {number} */
-SelectToSpeak.SEARCH_KEY_CODE = KeyCode.SEARCH;
-
-/** @const {number} */
-SelectToSpeak.CONTROL_KEY_CODE = KeyCode.CONTROL;
-
-/** @const {number} */
-SelectToSpeak.READ_SELECTION_KEY_CODE = KeyCode.S;
-
-/**
- * How often (in ms) to check that the currently spoken node is
- * still valid and in the same position. Decreasing this will make
- * STS seem more reactive to page changes but decreasing it too much
- * could cause performance issues.
- * @const {number}
- */
-SelectToSpeak.NODE_STATE_TEST_INTERVAL_MS = 500;
-
-/**
- * Max size in pixels for a region selection to be considered a paragraph
- * selection vs a selection of specific nodes. Generally paragraph
- * selection is a single click (size 0), though allow for a little
- * jitter.
- * @const {number}
- */
-SelectToSpeak.PARAGRAPH_SELECTION_MAX_SIZE = 5;
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_constants.js b/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_constants.js
new file mode 100644
index 0000000..db268cb
--- /dev/null
+++ b/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_constants.js
@@ -0,0 +1,32 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+export class SelectToSpeakConstants {}
+
+/** @const {number} */
+SelectToSpeakConstants.SEARCH_KEY_CODE = KeyCode.SEARCH;
+
+/** @const {number} */
+SelectToSpeakConstants.CONTROL_KEY_CODE = KeyCode.CONTROL;
+
+/** @const {number} */
+SelectToSpeakConstants.READ_SELECTION_KEY_CODE = KeyCode.S;
+
+/**
+ * How often (in ms) to check that the currently spoken node is
+ * still valid and in the same position. Decreasing this will make
+ * STS seem more reactive to page changes but decreasing it too much
+ * could cause performance issues.
+ * @const {number}
+ */
+SelectToSpeakConstants.NODE_STATE_TEST_INTERVAL_MS = 500;
+
+/**
+ * Max size in pixels for a region selection to be considered a paragraph
+ * selection vs a selection of specific nodes. Generally paragraph
+ * selection is a single click (size 0), though allow for a little
+ * jitter.
+ * @const {number}
+ */
+SelectToSpeakConstants.PARAGRAPH_SELECTION_MAX_SIZE = 5;
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_e2e_test_base.js b/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_e2e_test_base.js
index 92e614c4..179af3e 100644
--- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_e2e_test_base.js
+++ b/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_e2e_test_base.js
@@ -67,13 +67,14 @@
     assertFalse(this.mockTts.currentlySpeaking());
     assertEquals(this.mockTts.pendingUtterances().length, 0);
     selectToSpeak.fireMockKeyDownEvent(
-        {keyCode: SelectToSpeak.SEARCH_KEY_CODE});
+        {keyCode: SelectToSpeakConstants.SEARCH_KEY_CODE});
     selectToSpeak.fireMockKeyDownEvent(
-        {keyCode: SelectToSpeak.READ_SELECTION_KEY_CODE});
+        {keyCode: SelectToSpeakConstants.READ_SELECTION_KEY_CODE});
     assertTrue(selectToSpeak.inputHandler_.isSelectionKeyDown_);
     selectToSpeak.fireMockKeyUpEvent(
-        {keyCode: SelectToSpeak.READ_SELECTION_KEY_CODE});
-    selectToSpeak.fireMockKeyUpEvent({keyCode: SelectToSpeak.SEARCH_KEY_CODE});
+        {keyCode: SelectToSpeakConstants.READ_SELECTION_KEY_CODE});
+    selectToSpeak.fireMockKeyUpEvent(
+        {keyCode: SelectToSpeakConstants.SEARCH_KEY_CODE});
   }
 
   /**
@@ -83,10 +84,11 @@
    */
   triggerReadMouseSelectedText(downEvent, upEvent) {
     selectToSpeak.fireMockKeyDownEvent(
-        {keyCode: SelectToSpeak.SEARCH_KEY_CODE});
+        {keyCode: SelectToSpeakConstants.SEARCH_KEY_CODE});
     selectToSpeak.fireMockMouseDownEvent(downEvent);
     selectToSpeak.fireMockMouseUpEvent(upEvent);
-    selectToSpeak.fireMockKeyUpEvent({keyCode: SelectToSpeak.SEARCH_KEY_CODE});
+    selectToSpeak.fireMockKeyUpEvent(
+        {keyCode: SelectToSpeakConstants.SEARCH_KEY_CODE});
   }
 
   /**
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_keystroke_selection_test.js b/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_keystroke_selection_test.js
index c3dd794..4dee458 100644
--- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_keystroke_selection_test.js
+++ b/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_keystroke_selection_test.js
@@ -16,6 +16,20 @@
     chrome.tts = this.mockTts;
   }
 
+  /** @override */
+  setUp() {
+    var runTest = this.deferRunTest(WhenTestDone.EXPECT);
+    (async function() {
+      let module = await import('/select_to_speak/select_to_speak_main.js');
+      window.selectToSpeak = module.selectToSpeak;
+
+      module = await import('/select_to_speak/select_to_speak_constants.js');
+      window.SelectToSpeakConstants = module.SelectToSpeakConstants;
+
+      runTest();
+    })();
+  }
+
   /**
    * Function to load a simple webpage, select some of the single text
    * node, and trigger Select-to-Speak to read that partial node. Tests
@@ -690,4 +704,4 @@
             this.assertEqualsCollapseWhitespace(
                 this.mockTts.pendingUtterances()[0], 'My cat is Grumpy!');
           });
-    });
\ No newline at end of file
+    });
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_main.js b/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_main.js
index cec34e8..6f2df8f59 100644
--- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_main.js
+++ b/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_main.js
@@ -2,5 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import {SelectToSpeak} from './select_to_speak.js';
+
 InstanceChecker.closeExtraInstances();
-const selectToSpeak = new SelectToSpeak();
+export const selectToSpeak = new SelectToSpeak();
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_mouse_selection_test.js b/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_mouse_selection_test.js
index 25937af..f2426e2 100644
--- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_mouse_selection_test.js
+++ b/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_mouse_selection_test.js
@@ -16,11 +16,33 @@
     chrome.tts = this.mockTts;
   }
 
+  /** @override */
+  setUp() {
+    var runTest = this.deferRunTest(WhenTestDone.EXPECT);
+
+    window.EventType = chrome.automation.EventType;
+    window.SelectToSpeakState = chrome.accessibilityPrivate.SelectToSpeakState;
+
+    (async function() {
+      let module = await import('/select_to_speak/select_to_speak_main.js');
+      window.selectToSpeak = module.selectToSpeak;
+
+      module = await import('/select_to_speak/select_to_speak.js');
+      window.SELECT_TO_SPEAK_TRAY_CLASS_NAME =
+          module.SELECT_TO_SPEAK_TRAY_CLASS_NAME;
+
+      module = await import('/select_to_speak/select_to_speak_constants.js');
+      window.SelectToSpeakConstants = module.SelectToSpeakConstants;
+
+      runTest();
+    })();
+  }
+
   tapTrayButton(desktop, callback) {
     const button = desktop.find({
-      roleType: 'button',
       attributes: {className: SELECT_TO_SPEAK_TRAY_CLASS_NAME}
     });
+
     callback = this.newCallback(callback);
     selectToSpeak.onStateChangeRequestedCallbackForTest_ =
         this.newCallback(() => {
@@ -231,7 +253,6 @@
         this.tapTrayButton(desktop, () => {
           assertEquals(selectToSpeak.state_, SelectToSpeakState.SELECTING);
           const button = desktop.find({
-            roleType: 'button',
             attributes: {className: SELECT_TO_SPEAK_TRAY_CLASS_NAME}
           });
 
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_navigation_control_test.js b/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_navigation_control_test.js
index 8df3661..8ca7a00e 100644
--- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_navigation_control_test.js
+++ b/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_navigation_control_test.js
@@ -16,6 +16,28 @@
   }
 
   /** @override */
+  setUp() {
+    var runTest = this.deferRunTest(WhenTestDone.EXPECT);
+
+    window.EventType = chrome.automation.EventType;
+    window.SelectToSpeakState = chrome.accessibilityPrivate.SelectToSpeakState;
+
+    (async function() {
+      let module = await import('/select_to_speak/select_to_speak_main.js');
+      window.selectToSpeak = module.selectToSpeak;
+
+      module = await import('/select_to_speak/select_to_speak.js');
+      window.SELECT_TO_SPEAK_TRAY_CLASS_NAME =
+          module.SELECT_TO_SPEAK_TRAY_CLASS_NAME;
+
+      module = await import('/select_to_speak/select_to_speak_constants.js');
+      window.SelectToSpeakConstants = module.SelectToSpeakConstants;
+
+      runTest();
+    })();
+  }
+
+  /** @override */
   get featureList() {
     return {enabled: ['features::kSelectToSpeakNavigationControl']};
   }
@@ -637,4 +659,4 @@
         // Perform resize.
         resizeButton.doDefault();
       });
-});
\ No newline at end of file
+});
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_options.js b/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_options.js
index a7ea4ef..378c980 100644
--- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_options.js
+++ b/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_options.js
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import {PrefsManager} from './prefs_manager.js';
+
 class SelectToSpeakOptionsPage {
   constructor() {
     this.init_();
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_prefs_test.js b/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_prefs_test.js
index 1ebdae7..b2a2c80 100644
--- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_prefs_test.js
+++ b/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_prefs_test.js
@@ -31,8 +31,23 @@
         return msgid;
       }
     };
+  }
 
-    this.resetStorage();
+  /** @override */
+  setUp() {
+    var runTest = this.deferRunTest(WhenTestDone.EXPECT);
+
+    (async () => {
+      let module = await import('/select_to_speak/select_to_speak_main.js');
+      window.selectToSpeak = module.selectToSpeak;
+
+      module = await import('/select_to_speak/select_to_speak_constants.js');
+      window.SelectToSpeakConstants = module.SelectToSpeakConstants;
+
+      this.resetStorage();
+
+      runTest();
+    })();
   }
 
   resetStorage() {
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_unittest.js b/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_unittest.js
index 1671ea4..ba1da17 100644
--- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_unittest.js
+++ b/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_unittest.js
@@ -2,19 +2,25 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+GEN_INCLUDE(['select_to_speak_e2e_test_base.js']);
+
 /**
  * Test fixture for select_to_speak.js.
  */
-SelectToSpeakUnitTest = class extends testing.Test {};
+SelectToSpeakUnitTest = class extends SelectToSpeakE2ETest {
+  /** @override */
+  setUp() {
+    var runTest = this.deferRunTest(WhenTestDone.EXPECT);
+    (async function() {
+      const module = await import('/select_to_speak/select_to_speak.js');
+      window.getGSuiteAppRoot = module.getGSuiteAppRoot;
 
-/** @override */
-SelectToSpeakUnitTest.prototype.extraLibraries = [
-  '../common/closure_shim.js', '../common/key_code.js', 'test_support.js',
-  'select_to_speak.js'
-];
+      runTest();
+    })();
+  }
+};
 
-
-TEST_F('SelectToSpeakUnitTest', 'getGSuiteAppRoot', function() {
+SYNC_TEST_F('SelectToSpeakUnitTest', 'getGSuiteAppRoot', function() {
   const root = {url: 'https://docs.google.com/presentation/p/cats_r_awesome'};
   const div1 = {root};
   const frame1 = {url: 'about:blank', parent: div1};
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/sentence_utils.js b/chrome/browser/resources/chromeos/accessibility/select_to_speak/sentence_utils.js
index 36cf91e2..8637d2f 100644
--- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/sentence_utils.js
+++ b/chrome/browser/resources/chromeos/accessibility/select_to_speak/sentence_utils.js
@@ -2,10 +2,14 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import {ParagraphUtils} from './paragraph_utils.js';
+
+const RoleType = chrome.automation.RoleType;
+
 /**
  * Utilities for processing sentences within strings and node groups.
  */
-class SentenceUtils {
+export class SentenceUtils {
   constructor() {}
 
   /**
@@ -80,7 +84,7 @@
       return null;
     }
     // Check if this nodeGroupItem has a non-empty static text node.
-    if (!nodeGroupItem.node.role === RoleType.STATIC_TEXT ||
+    if (nodeGroupItem.node.role !== RoleType.STATIC_TEXT ||
         nodeGroupItem.node.name.length === 0) {
       return null;
     }
@@ -157,7 +161,7 @@
     for (let i = 0; i < nodeGroup.nodes.length; i++) {
       const nodeGroupItem = nodeGroup.nodes[i];
       // Check if this nodeGroupItem has a non-empty static text node.
-      if (!nodeGroupItem.node.role === RoleType.STATIC_TEXT ||
+      if (nodeGroupItem.node.role !== RoleType.STATIC_TEXT ||
           nodeGroupItem.node.name.length === 0) {
         continue;
       }
@@ -186,4 +190,4 @@
     // We did not find a sentence start equal to |currentCharIndex|.
     return false;
   }
-}
\ No newline at end of file
+}
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/sentence_utils_unittest.js b/chrome/browser/resources/chromeos/accessibility/select_to_speak/sentence_utils_unittest.js
index c689f8d4..a86e3fa 100644
--- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/sentence_utils_unittest.js
+++ b/chrome/browser/resources/chromeos/accessibility/select_to_speak/sentence_utils_unittest.js
@@ -2,25 +2,28 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+GEN_INCLUDE(['select_to_speak_e2e_test_base.js']);
+
 /**
  * Test fixture for sentence_utils.js.
  */
-SelectToSpeakSentenceUtilsUnitTest = class extends testing.Test {};
+SelectToSpeakSentenceUtilsUnitTest = class extends SelectToSpeakE2ETest {
+  /** @override */
+  setUp() {
+    var runTest = this.deferRunTest(WhenTestDone.EXPECT);
+    (async function() {
+      const module = await import('/select_to_speak/sentence_utils.js');
+      window.SentenceUtils = module.SentenceUtils;
 
-/** @override */
-SelectToSpeakSentenceUtilsUnitTest.prototype.extraLibraries = [
-  'test_support.js',
-  'sentence_utils.js',
-  'paragraph_utils.js',
-  '../common/closure_shim.js',
-  '../common/constants.js',
-];
+      runTest();
+    })();
+  }
+};
 
-TEST_F(
+SYNC_TEST_F(
     'SelectToSpeakSentenceUtilsUnitTest', 'getNextSentenceStart', function() {
       // The text of the test node group is "Hello. New. World."
       const nodeGroup = getTestNodeGroupWithOneNode();
-
       assertEquals(
           7,
           SentenceUtils.getSentenceStart(
@@ -48,7 +51,7 @@
               constants.Dir.FORWARD /* direction */));
     });
 
-TEST_F(
+SYNC_TEST_F(
     'SelectToSpeakSentenceUtilsUnitTest', 'getPrevSentenceStart', function() {
       // The text of the test node group is "Hello. New. World."
       const nodeGroup = getTestNodeGroupWithOneNode();
@@ -80,7 +83,7 @@
               constants.Dir.BACKWARD /* direction */));
     });
 
-TEST_F(
+SYNC_TEST_F(
     'SelectToSpeakSentenceUtilsUnitTest', 'getNextSentenceStartMultiNodes',
     function() {
       // The text of the test node group is "Hello. New. Beautiful. World." The
@@ -114,7 +117,7 @@
               constants.Dir.FORWARD /* direction */));
     });
 
-TEST_F(
+SYNC_TEST_F(
     'SelectToSpeakSentenceUtilsUnitTest', 'getPrevSentenceStartMultiNodes',
     function() {
       // The text of the test node group is "Hello. New. Beautiful. World." The
@@ -148,7 +151,7 @@
               constants.Dir.BACKWARD /* direction */));
     });
 
-TEST_F(
+SYNC_TEST_F(
     'SelectToSpeakSentenceUtilsUnitTest',
     'getNextSentenceStartSentenceSpanningAcrossMultiNodes', function() {
       // The text of the test node group is "Hello world. New world." The
@@ -177,7 +180,7 @@
               constants.Dir.FORWARD /* direction */));
     });
 
-TEST_F(
+SYNC_TEST_F(
     'SelectToSpeakSentenceUtilsUnitTest',
     'getPrevSentenceStartSentenceSpanningAcrossMultiNodes', function() {
       // The text of the test node group is "Hello world. New world." The
@@ -211,34 +214,34 @@
               constants.Dir.BACKWARD /* direction */));
     });
 
+SYNC_TEST_F(
+    'SelectToSpeakSentenceUtilsUnitTest', 'isSentenceStart', function() {
+      // The text of the test node group is "Hello. New. World."
+      const nodeGroup = getTestNodeGroupWithOneNode();
 
-TEST_F('SelectToSpeakSentenceUtilsUnitTest', 'isSentenceStart', function() {
-  // The text of the test node group is "Hello. New. World."
-  const nodeGroup = getTestNodeGroupWithOneNode();
+      assertEquals(
+          true,
+          SentenceUtils.isSentenceStart(
+              nodeGroup /* nodeGroup */, 0 /* startCharIndex */));
+      assertEquals(
+          false,
+          SentenceUtils.isSentenceStart(
+              nodeGroup /* nodeGroup */, 3 /* startCharIndex */));
+      assertEquals(
+          true,
+          SentenceUtils.isSentenceStart(
+              nodeGroup /* nodeGroup */, 7 /* startCharIndex */));
+      assertEquals(
+          false,
+          SentenceUtils.isSentenceStart(
+              nodeGroup /* nodeGroup */, 11 /* startCharIndex */));
+      assertEquals(
+          true,
+          SentenceUtils.isSentenceStart(
+              nodeGroup /* nodeGroup */, 12 /* startCharIndex */));
+    });
 
-  assertEquals(
-      true,
-      SentenceUtils.isSentenceStart(
-          nodeGroup /* nodeGroup */, 0 /* startCharIndex */));
-  assertEquals(
-      false,
-      SentenceUtils.isSentenceStart(
-          nodeGroup /* nodeGroup */, 3 /* startCharIndex */));
-  assertEquals(
-      true,
-      SentenceUtils.isSentenceStart(
-          nodeGroup /* nodeGroup */, 7 /* startCharIndex */));
-  assertEquals(
-      false,
-      SentenceUtils.isSentenceStart(
-          nodeGroup /* nodeGroup */, 11 /* startCharIndex */));
-  assertEquals(
-      true,
-      SentenceUtils.isSentenceStart(
-          nodeGroup /* nodeGroup */, 12 /* startCharIndex */));
-});
-
-TEST_F(
+SYNC_TEST_F(
     'SelectToSpeakSentenceUtilsUnitTest', 'isSentenceStartMultiNodes',
     function() {
       // The text of the test node group is "Hello. New. Beautiful. World." The
@@ -275,7 +278,11 @@
               nodeGroup /* nodeGroup */, 23 /* startCharIndex */));
     });
 function getTestNodeGroupWithOneNode() {
-  const staticText = {sentenceStarts: [0, 7, 12], name: 'Hello. New. World.'};
+  const staticText = {
+    sentenceStarts: [0, 7, 12],
+    name: 'Hello. New. World.',
+    role: 'staticText'
+  };
   const node = {node: staticText, startChar: 0};
   return {nodes: [node], text: 'Hello. New. World.'};
 }
@@ -313,4 +320,4 @@
   const node3 = {node: staticText3, startChar: 16};
 
   return {nodes: [node1, node2, node3], text: 'Hello world. New world.'};
-}
\ No newline at end of file
+}
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/test_support.js b/chrome/browser/resources/chromeos/accessibility/select_to_speak/test_support.js
deleted file mode 100644
index 4ac8144..0000000
--- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/test_support.js
+++ /dev/null
@@ -1,78 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * Stubs out extension API functions so that SelectToSpeakUnitTest
- * can load.
- */
-
-chrome.automation = {};
-
-/**
- * Stub
- */
-chrome.automation.getDesktop = function() {};
-
-/**
- * Set necessary constants.
- */
-chrome.automation.RoleType = {
-  CHECK_BOX: 'checkBox',
-  INLINE_TEXT_BOX: 'inlineTextBox',
-  MENU_ITEM_CHECK_BOX: 'menuItemCheckBox',
-  MENU_ITEM_RADIO: 'menuItemRadio',
-  PARAGRAPH: 'paragraph',
-  RADIO_BUTTON: 'radioButton',
-  ROOT_WEB_AREA: 'rootWebArea',
-  STATIC_TEXT: 'staticText',
-  SVG_ROOT: 'svgRoot',
-  TEXT_FIELD: 'textField',
-  WINDOW: 'window'
-};
-
-chrome.automation.StateType = {
-  INVISIBLE: 'invisible'
-};
-
-chrome.metricsPrivate = {
-  recordUserAction() {},
-  recordValue() {},
-  MetricTypeType: {HISTOGRAM_LINEAR: 1}
-};
-
-chrome.commandLinePrivate = {
-  hasSwitch() {}
-};
-
-chrome.accessibilityPrivate = {};
-
-chrome.accessibilityPrivate.SelectToSpeakState = {
-  INACTIVE: 'inactive',
-  SELECTING: 'selecting',
-  SPEAKING: 'speaking'
-};
-
-chrome.i18n = {
-  getMessage(key) {
-    if (key === 'select_to_speak_checkbox_checked') {
-      return 'checked';
-    }
-    if (key === 'select_to_speak_checkbox_unchecked') {
-      return 'unchecked';
-    }
-    if (key === 'select_to_speak_checkbox_mixed') {
-      return 'partially checked';
-    }
-    if (key === 'select_to_speak_radiobutton_selected') {
-      return 'selected';
-    }
-    if (key === 'select_to_speak_radiobutton_unselected') {
-      return 'unselected';
-    }
-    if (key === 'select_to_speak_radiobutton_mixed') {
-      return 'partially selected';
-    }
-    return '';
-  }
-};
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/word_utils.js b/chrome/browser/resources/chromeos/accessibility/select_to_speak/word_utils.js
index fe4f86c1..a46c7ec4 100644
--- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/word_utils.js
+++ b/chrome/browser/resources/chromeos/accessibility/select_to_speak/word_utils.js
@@ -2,9 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import {ParagraphUtils} from './paragraph_utils.js';
+
 // Utilities for processing words within strings and nodes.
 
-class WordUtils {
+export class WordUtils {
   constructor() {}
 
   /**
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/word_utils_unittest.js b/chrome/browser/resources/chromeos/accessibility/select_to_speak/word_utils_unittest.js
index 76e34082..7affeb6 100644
--- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/word_utils_unittest.js
+++ b/chrome/browser/resources/chromeos/accessibility/select_to_speak/word_utils_unittest.js
@@ -2,20 +2,24 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+GEN_INCLUDE(['select_to_speak_e2e_test_base.js']);
+
 /**
  * Test fixture for word_utils.js.
  */
-SelectToSpeakWordUtilsUnitTest = class extends testing.Test {};
+SelectToSpeakWordUtilsUnitTest = class extends SelectToSpeakE2ETest {
+  setUp() {
+    var runTest = this.deferRunTest(WhenTestDone.EXPECT);
+    (async function() {
+      const module = await import('/select_to_speak/word_utils.js');
+      window.WordUtils = module.WordUtils;
 
-/** @override */
-SelectToSpeakWordUtilsUnitTest.prototype.extraLibraries = [
-  'test_support.js',
-  'paragraph_utils.js',
-  'word_utils.js',
-];
+      runTest();
+    })();
+  }
+};
 
-
-TEST_F(
+SYNC_TEST_F(
     'SelectToSpeakWordUtilsUnitTest', 'getNextWordStartWithoutWordStarts',
     function() {
       const node = {node: {}};
@@ -26,7 +30,7 @@
       assertEquals(7, WordUtils.getNextWordStart('kitty "cat"', 5, node));
     });
 
-TEST_F(
+SYNC_TEST_F(
     'SelectToSpeakWordUtilsUnitTest', 'getNextWordEndWithoutWordEnds',
     function() {
       const node = {node: {}};
@@ -36,7 +40,7 @@
       assertEquals(9, WordUtils.getNextWordEnd('kitty cat', 7, node));
     });
 
-TEST_F('SelectToSpeakWordUtilsUnitTest', 'getNextWordStart', function() {
+SYNC_TEST_F('SelectToSpeakWordUtilsUnitTest', 'getNextWordStart', function() {
   const inlineText = {wordStarts: [0, 6], name: 'kitty cat'};
   const staticText = {children: [inlineText], name: 'kitty cat'};
   const node = {node: staticText, startChar: 0, hasInlineText: true};
@@ -54,7 +58,7 @@
       10, WordUtils.getNextWordStart('once upon a kitty cat', 10, node));
 });
 
-TEST_F(
+SYNC_TEST_F(
     'SelectToSpeakWordUtilsUnitTest', 'getNextWordStartIgnoresStartCharOffset',
     function() {
       const inlineText = {
@@ -78,7 +82,7 @@
           10, WordUtils.getNextWordStart('once upon a kitty cat', 10, node));
     });
 
-TEST_F(
+SYNC_TEST_F(
     'SelectToSpeakWordUtilsUnitTest', 'getNextWordStartMultipleChildren',
     function() {
       const inlineText1 = {
@@ -104,7 +108,7 @@
           13, WordUtils.getNextWordStart('kitty cat is cute', 11, node));
     });
 
-TEST_F('SelectToSpeakWordUtilsUnitTest', 'getNextWordEnd', function() {
+SYNC_TEST_F('SelectToSpeakWordUtilsUnitTest', 'getNextWordEnd', function() {
   const inlineText = {wordEnds: [5, 9], name: 'kitty cat'};
   const staticText = {children: [inlineText], name: 'kitty cat'};
   const node = {node: staticText, startChar: 0, hasInlineText: true};
@@ -122,7 +126,7 @@
   assertEquals(5, WordUtils.getNextWordEnd('kitty cat', 4, node));
 });
 
-TEST_F(
+SYNC_TEST_F(
     'SelectToSpeakWordUtilsUnitTest', 'getNextWordEndMultipleChildren',
     function() {
       const inlineText1 = {
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak_manifest.json.jinja2 b/chrome/browser/resources/chromeos/accessibility/select_to_speak_manifest.json.jinja2
index cca4540..9583ec1b 100644
--- a/chrome/browser/resources/chromeos/accessibility/select_to_speak_manifest.json.jinja2
+++ b/chrome/browser/resources/chromeos/accessibility/select_to_speak_manifest.json.jinja2
@@ -10,25 +10,7 @@
   "incognito": "split",
 {% endif %}
   "background": {
-    "scripts": [
-      "common/closure_shim.js",
-      "common/constants.js",
-      "common/key_code.js",
-      "common/automation_predicate.js",
-      "common/instance_checker.js",
-      "common/rect_util.js",
-      "common/tree_walker.js",
-      "common/automation_util.js",
-      "select_to_speak/input_handler.js",
-      "select_to_speak/metrics_utils.js",
-      "select_to_speak/node_utils.js",
-      "select_to_speak/paragraph_utils.js",
-      "select_to_speak/prefs_manager.js",
-      "select_to_speak/word_utils.js",
-      "select_to_speak/sentence_utils.js",
-      "select_to_speak/select_to_speak.js",
-      "select_to_speak/select_to_speak_main.js"
-    ]
+    "page": "select_to_speak/background.html"
   },
   "permissions": [
     "accessibilityPrivate",
diff --git a/chrome/browser/resources/pdf/gesture_detector.js b/chrome/browser/resources/pdf/gesture_detector.js
index 827725f..5c08d70 100644
--- a/chrome/browser/resources/pdf/gesture_detector.js
+++ b/chrome/browser/resources/pdf/gesture_detector.js
@@ -102,7 +102,7 @@
 
     this.pinchStartEvent_ = event;
     this.lastEvent_ = event;
-    this.notify_('pinchstart', {center: GestureDetector.center_(event)});
+    this.notify_('pinchstart', {center: center(event)});
   }
 
   /**
@@ -120,27 +120,23 @@
     // Check if the pinch ends with the current event.
     if (event.touches.length < 2 ||
         lastEvent.touches.length !== event.touches.length) {
-      const startScaleRatio =
-          GestureDetector.pinchScaleRatio_(lastEvent, this.pinchStartEvent_);
-      const center = GestureDetector.center_(lastEvent);
+      const startScaleRatio = pinchScaleRatio(lastEvent, this.pinchStartEvent_);
       this.pinchStartEvent_ = null;
       this.lastEvent_ = null;
       this.notify_('pinchend', {
         startScaleRatio: startScaleRatio,
-        center: center,
+        center: center(lastEvent),
       });
       return;
     }
 
-    const scaleRatio = GestureDetector.pinchScaleRatio_(event, lastEvent);
-    const startScaleRatio =
-        GestureDetector.pinchScaleRatio_(event, this.pinchStartEvent_);
-    const center = GestureDetector.center_(event);
+    const scaleRatio = pinchScaleRatio(event, lastEvent);
+    const startScaleRatio = pinchScaleRatio(event, this.pinchStartEvent_);
     this.notify_('pinchupdate', {
       scaleRatio: scaleRatio,
       direction: scaleRatio > 1.0 ? 'in' : 'out',
       startScaleRatio: startScaleRatio,
-      center: center,
+      center: center(event),
     });
 
     this.lastEvent_ = event;
@@ -222,50 +218,47 @@
       e.preventDefault();
     }
   }
+}
 
-  /**
-   * Computes the change in scale between this touch event
-   * and a previous one.
-   * @param {!TouchEvent} event Latest touch event on the element.
-   * @param {!TouchEvent} prevEvent A previous touch event on the element.
-   * @return {?number} The ratio of the scale of this event and the
-   *     scale of the previous one.
-   * @private
-   */
-  static pinchScaleRatio_(event, prevEvent) {
-    const distance1 = GestureDetector.distance_(prevEvent);
-    const distance2 = GestureDetector.distance_(event);
-    return distance1 === 0 ? null : distance2 / distance1;
-  }
+/**
+ * Computes the change in scale between this touch event
+ * and a previous one.
+ * @param {!TouchEvent} event Latest touch event on the element.
+ * @param {!TouchEvent} prevEvent A previous touch event on the element.
+ * @return {?number} The ratio of the scale of this event and the
+ *     scale of the previous one.
+ */
+function pinchScaleRatio(event, prevEvent) {
+  const distance1 = distance(prevEvent);
+  const distance2 = distance(event);
+  return distance1 === 0 ? null : distance2 / distance1;
+}
 
-  /**
-   * Computes the distance between fingers.
-   * @param {!TouchEvent} event Touch event with at least 2 touch points.
-   * @return {number} Distance between touch[0] and touch[1].
-   * @private
-   */
-  static distance_(event) {
-    const touch1 = event.touches[0];
-    const touch2 = event.touches[1];
-    const dx = touch1.clientX - touch2.clientX;
-    const dy = touch1.clientY - touch2.clientY;
-    return Math.sqrt(dx * dx + dy * dy);
-  }
+/**
+ * Computes the distance between fingers.
+ * @param {!TouchEvent} event Touch event with at least 2 touch points.
+ * @return {number} Distance between touch[0] and touch[1].
+ */
+function distance(event) {
+  const touch1 = event.touches[0];
+  const touch2 = event.touches[1];
+  const dx = touch1.clientX - touch2.clientX;
+  const dy = touch1.clientY - touch2.clientY;
+  return Math.sqrt(dx * dx + dy * dy);
+}
 
-  /**
-   * Computes the midpoint between fingers.
-   * @param {!TouchEvent} event Touch event with at least 2 touch points.
-   * @return {!Point} Midpoint between touch[0] and touch[1].
-   * @private
-   */
-  static center_(event) {
-    const touch1 = event.touches[0];
-    const touch2 = event.touches[1];
-    return {
-      x: (touch1.clientX + touch2.clientX) / 2,
-      y: (touch1.clientY + touch2.clientY) / 2
-    };
-  }
+/**
+ * Computes the midpoint between fingers.
+ * @param {!TouchEvent} event Touch event with at least 2 touch points.
+ * @return {!Point} Midpoint between touch[0] and touch[1].
+ */
+function center(event) {
+  const touch1 = event.touches[0];
+  const touch2 = event.touches[1];
+  return {
+    x: (touch1.clientX + touch2.clientX) / 2,
+    y: (touch1.clientY + touch2.clientY) / 2
+  };
 }
 
 // Export on |window| such that scripts injected from pdf_extension_test.cc can
diff --git a/chrome/browser/resources/pdf/metrics.js b/chrome/browser/resources/pdf/metrics.js
index 949adcfb..83f8fa5 100644
--- a/chrome/browser/resources/pdf/metrics.js
+++ b/chrome/browser/resources/pdf/metrics.js
@@ -47,8 +47,8 @@
     if (!chrome.metricsPrivate) {
       return;
     }
-    if (!PDFMetrics.actionsMetric_) {
-      PDFMetrics.actionsMetric_ = {
+    if (!actionsMetric) {
+      actionsMetric = {
         'metricName': 'PDF.Actions',
         'type': chrome.metricsPrivate.MetricTypeType.HISTOGRAM_LOG,
         'min': 1,
@@ -56,28 +56,27 @@
         'buckets': UserAction.NUMBER_OF_ACTIONS + 1
       };
     }
-    chrome.metricsPrivate.recordValue(PDFMetrics.actionsMetric_, action);
-    if (PDFMetrics.firstMap_.has(action)) {
-      const firstAction = PDFMetrics.firstMap_.get(action);
-      if (!PDFMetrics.firstActionRecorded_.has(firstAction)) {
-        chrome.metricsPrivate.recordValue(
-            PDFMetrics.actionsMetric_, firstAction);
-        PDFMetrics.firstActionRecorded_.add(firstAction);
+    chrome.metricsPrivate.recordValue(actionsMetric, action);
+    if (firstMap.has(action)) {
+      const firstAction = firstMap.get(action);
+      if (!firstActionRecorded.has(firstAction)) {
+        chrome.metricsPrivate.recordValue(actionsMetric, firstAction);
+        firstActionRecorded.add(firstAction);
       }
     }
   }
 
   static resetForTesting() {
-    PDFMetrics.firstActionRecorded_.clear();
-    PDFMetrics.actionsMetric_ = null;
+    firstActionRecorded.clear();
+    actionsMetric = null;
   }
 }
 
-/** @private {?chrome.metricsPrivate.MetricType} */
-PDFMetrics.actionsMetric_ = null;
+/** @type {?chrome.metricsPrivate.MetricType} */
+let actionsMetric = null;
 
-/** @private {Set} */
-PDFMetrics.firstActionRecorded_ = new Set();
+/** @type {!Set<!UserAction>} */
+const firstActionRecorded = new Set();
 
 // Keep in sync with enums.xml.
 // Do not change the numeric values or reuse them since these numbers are
@@ -218,8 +217,8 @@
 
 // Map from UserAction to the 'FIRST' action. These metrics are recorded
 // by PDFMetrics.log the first time each corresponding action occurs.
-/** @private Map<number, number> */
-PDFMetrics.firstMap_ = new Map([
+/** @type {!Map<!UserAction, !UserAction>} */
+const firstMap = new Map([
   [
     UserAction.ROTATE,
     UserAction.ROTATE_FIRST,
diff --git a/chrome/browser/resources/settings/chromeos/BUILD.gn b/chrome/browser/resources/settings/chromeos/BUILD.gn
index 4f98df83..a047a3a 100644
--- a/chrome/browser/resources/settings/chromeos/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/BUILD.gn
@@ -315,7 +315,6 @@
     "chromeos/internet_page/esim_remove_profile_dialog.m.js",
     "chromeos/internet_page/esim_rename_dialog.m.js",
     "chromeos/internet_page/cellular_networks_list.m.js",
-    "chromeos/internet_page/cellular_eid_popup.m.js",
     "chromeos/internet_page/cellular_setup_dialog.m.js",
     "chromeos/internet_page/cellular_setup_settings_delegate.m.js",
     "chromeos/internet_page/internet_config.m.js",
@@ -603,8 +602,6 @@
     "chromeos/internet_page/esim_rename_dialog.html",
     "chromeos/internet_page/cellular_networks_list.js",
     "chromeos/internet_page/cellular_networks_list.html",
-    "chromeos/internet_page/cellular_eid_popup.html",
-    "chromeos/internet_page/cellular_eid_popup.js",
     "chromeos/internet_page/cellular_setup_dialog.html",
     "chromeos/internet_page/cellular_setup_dialog.js",
     "chromeos/internet_page/cellular_setup_settings_delegate.html",
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/internet_page/BUILD.gn
index 028b257..168fadf 100644
--- a/chrome/browser/resources/settings/chromeos/internet_page/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/internet_page/BUILD.gn
@@ -8,7 +8,6 @@
 
 js_type_check("closure_compile") {
   deps = [
-    ":cellular_eid_popup",
     ":cellular_networks_list",
     ":cellular_setup_dialog",
     ":esim_remove_profile_dialog",
@@ -129,7 +128,6 @@
 
 js_library("internet_subpage") {
   deps = [
-    ":cellular_eid_popup",
     ":cellular_networks_list",
     ":internet_page_browser_proxy",
     "..:deep_linking_behavior",
@@ -211,8 +209,9 @@
 
 js_library("cellular_networks_list") {
   deps = [
-    ":cellular_eid_popup",
+    "//ui/webui/resources/cr_components/chromeos/cellular_setup:cellular_eid_popup",
     "//ui/webui/resources/cr_components/chromeos/cellular_setup:cellular_types",
+    "//ui/webui/resources/cr_components/chromeos/cellular_setup:esim_manager_listener_behavior",
     "//ui/webui/resources/cr_components/chromeos/cellular_setup:esim_manager_utils",
     "//ui/webui/resources/cr_components/chromeos/network:mojo_interface_provider",
     "//ui/webui/resources/cr_components/chromeos/network:network_list_types",
@@ -222,10 +221,6 @@
   ]
 }
 
-js_library("cellular_eid_popup") {
-  deps = [ "//ui/webui/resources/js:i18n_behavior" ]
-}
-
 js_library("esim_rename_dialog") {
   deps = [
     "//ui/webui/resources/cr_components/chromeos/cellular_setup:mojo_interface_provider",
@@ -250,7 +245,6 @@
 js_type_check("closure_compile_module") {
   is_polymer3 = true
   deps = [
-    ":cellular_eid_popup.m",
     ":cellular_networks_list.m",
     ":cellular_setup_dialog.m",
     ":cellular_setup_settings_delegate.m",
@@ -536,6 +530,7 @@
     "//chrome/browser/resources/settings/chromeos/localized_link:localized_link.m",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
     "//ui/webui/resources/cr_components/chromeos/cellular_setup:cellular_types.m",
+    "//ui/webui/resources/cr_components/chromeos/cellular_setup:esim_manager_listener_behavior.m",
     "//ui/webui/resources/cr_components/chromeos/cellular_setup:esim_manager_utils.m",
     "//ui/webui/resources/cr_components/chromeos/network:network_list_types.m",
     "//ui/webui/resources/cr_elements/cr_icon_button:cr_icon_button.m",
@@ -544,12 +539,6 @@
   extra_deps = [ ":cellular_networks_list_module" ]
 }
 
-js_library("cellular_eid_popup.m") {
-  sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/internet_page/cellular_eid_popup.m.js" ]
-  deps = [ "//ui/webui/resources/js:i18n_behavior.m" ]
-  extra_deps = [ ":cellular_eid_popup_module" ]
-}
-
 js_library("esim_rename_dialog.m") {
   sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/internet_page/esim_rename_dialog.m.js" ]
   deps = [
@@ -578,7 +567,6 @@
 
 group("polymer3_elements") {
   public_deps = [
-    ":cellular_eid_popup_module",
     ":cellular_networks_list_module",
     ":cellular_setup_dialog_module",
     ":esim_remove_profile_dialog_module",
@@ -701,14 +689,6 @@
   namespace_rewrites = os_settings_namespace_rewrites
 }
 
-polymer_modulizer("cellular_eid_popup") {
-  js_file = "cellular_eid_popup.js"
-  html_file = "cellular_eid_popup.html"
-  html_type = "dom-module"
-  auto_imports = os_settings_auto_imports
-  namespace_rewrites = os_settings_namespace_rewrites
-}
-
 polymer_modulizer("esim_rename_dialog") {
   js_file = "esim_rename_dialog.js"
   html_file = "esim_rename_dialog.html"
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/cellular_networks_list.html b/chrome/browser/resources/settings/chromeos/internet_page/cellular_networks_list.html
index 9fc2dfd..8386804 100644
--- a/chrome/browser/resources/settings/chromeos/internet_page/cellular_networks_list.html
+++ b/chrome/browser/resources/settings/chromeos/internet_page/cellular_networks_list.html
@@ -1,7 +1,9 @@
 <link rel="import" href="chrome://resources/html/polymer.html">
 
 <link rel="import" href="chrome://resources/cr_components/chromeos/cellular_setup/mojo_interface_provider.html">
+<link rel="import" href="chrome://resources/cr_components/chromeos/cellular_setup/esim_manager_listener_behavior.html">
 <link rel="import" href="chrome://resources/cr_components/chromeos/cellular_setup/esim_manager_utils.html">
+<link rel="import" href="chrome://resources/cr_components/chromeos/cellular_setup/cellular_eid_popup.html">
 <link rel="import" href="chrome://resources/cr_components/chromeos/network/network_list_types.html">
 <link rel="import" href="chrome://resources/cr_components/chromeos/network/onc_mojo.html">
 <link rel="import" href="chrome://resources/cr_components/chromeos/network/mojo_interface_provider.html">
@@ -13,7 +15,6 @@
 <link rel="import" href="chrome://resources/cr_elements/shared_style_css.html">
 <link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
 <link rel="import" href="../../chromeos/os_settings_icons_css.html">
-<link rel="import" href="./cellular_eid_popup.html">
 
 <dom-module id="cellular-networks-list">
   <template>
@@ -67,43 +68,45 @@
       }
 
     </style>
-    <div class="cellular-network-list-header esim-list-header flex">
-      <div class="esim-list-title">$i18n{cellularNetworkEsimLabel}</div>
-      <div class="flex-column">
-        <cr-icon-button
-            id="eidPopupButton"
-            iron-icon="cr:info-outline"
-            title="$i18n{showEidPopupButtonLabel}"
-            aria-label="$i18n{showEidPopupButtonLabel}"
-            on-click="toggleEidPopup_">
-        </cr-icon-button>
-        <template is="dom-if" if="[[shouldShowEidPopup_]]" restamp>
-          <cellular-eid-popup class="eid-popup">
-          </cellular-eid-popup>
-        </template>
+    <template is="dom-if" if="[[!!euicc_]]" restamp>
+      <div class="cellular-network-list-header esim-list-header flex">
+        <div class="esim-list-title">$i18n{cellularNetworkEsimLabel}</div>
+        <div class="flex-column">
+          <cr-icon-button
+              id="eidPopupButton"
+              iron-icon="cr:info-outline"
+              title="$i18n{showEidPopupButtonLabel}"
+              aria-label="$i18n{showEidPopupButtonLabel}"
+              on-click="toggleEidPopup_">
+          </cr-icon-button>
+          <template is="dom-if" if="[[shouldShowEidPopup_]]" restamp>
+            <cellular-eid-popup class="eid-popup">
+            </cellular-eid-popup>
+          </template>
+        </div>
       </div>
-    </div>
-    <template is="dom-if"
-        if="[[shouldShowNetworkSublist_(eSimNetworks_, eSimPendingProfiles_)]]" restamp>
-      <div class="cellular-network-content">
-        <network-list id="esimNetworkList" show-buttons
-            show-technology-badge="[[showTechnologyBadge]]"
-            networks="[[eSimNetworks_]]"
-            custom-items="[[eSimPendingProfiles_]]"
-            device-state="[[deviceState]]">
-        </network-list>
-      </div>
-    </template>
-    <template
-        is="dom-if"
-        if="[[!shouldShowNetworkSublist_(eSimNetworks_, eSimPendingProfiles_)]]" restamp>
-      <div id="eSimNoNetworkFound"
-          class="cellular-network-content cellular-not-setup">
-        <settings-localized-link
-            on-link-clicked="onEsimLearnMoreClicked_"
-            localized-string="$i18n{eSimNetworkNotSetup}">
-        </settings-localized-link>
-      </div>
+      <template is="dom-if"
+          if="[[shouldShowNetworkSublist_(eSimNetworks_, eSimPendingProfileItems_)]]" restamp>
+        <div class="cellular-network-content">
+          <network-list id="esimNetworkList" show-buttons
+              show-technology-badge="[[showTechnologyBadge]]"
+              networks="[[eSimNetworks_]]"
+              custom-items="[[eSimPendingProfileItems_]]"
+              device-state="[[deviceState]]">
+          </network-list>
+        </div>
+      </template>
+      <template
+          is="dom-if"
+          if="[[!shouldShowNetworkSublist_(eSimNetworks_, eSimPendingProfileItems_)]]" restamp>
+        <div id="eSimNoNetworkFound"
+            class="cellular-network-content cellular-not-setup">
+          <settings-localized-link
+              on-link-clicked="onEsimLearnMoreClicked_"
+              localized-string="$i18n{eSimNetworkNotSetup}">
+          </settings-localized-link>
+        </div>
+      </template>
     </template>
     <div class="cellular-network-list-header">
       $i18n{cellularNetworkPsimLabel}
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/cellular_networks_list.js b/chrome/browser/resources/settings/chromeos/internet_page/cellular_networks_list.js
index c0636dd..48c7799 100644
--- a/chrome/browser/resources/settings/chromeos/internet_page/cellular_networks_list.js
+++ b/chrome/browser/resources/settings/chromeos/internet_page/cellular_networks_list.js
@@ -11,6 +11,7 @@
   is: 'cellular-networks-list',
 
   behaviors: [
+    ESimManagerListenerBehavior,
     I18nBehavior,
   ],
 
@@ -51,11 +52,24 @@
     },
 
     /**
+     * Dictionary mapping pending eSIM profile iccids to pending eSIM profiles.
+     * @type {!Map<string, chromeos.cellularSetup.mojom.ESimProfileRemote>}
+     * @private
+     */
+    profilesMap_: {
+      type: Object,
+      value() {
+        return new Map();
+      },
+    },
+
+    /**
      * The list of pending eSIM profiles to display after the list of eSIM
      * networks.
      * @type {!Array<NetworkList.CustomItemState>}
+     * @private
      */
-    eSimPendingProfiles_: {
+    eSimPendingProfileItems_: {
       type: Array,
       value() {
         return [];
@@ -90,11 +104,21 @@
     shouldShowEidPopup_: {
       type: Boolean,
       value: false,
+    },
+
+    /**
+     * Euicc object representing the active euicc_ module on the device
+     * @private {?chromeos.cellularSetup.mojom.EuiccRemote}
+     */
+    euicc_: {
+      type: Object,
+      value: null,
     }
   },
 
   listeners: {
     'close-eid-popup': 'toggleEidPopup_',
+    'install-profile': 'installProfile_',
   },
 
   /** @private {?chromeos.networkConfig.mojom.CrosNetworkConfigRemote} */
@@ -107,43 +131,105 @@
     this.fetchESimPendingProfileList_();
   },
 
+  /**
+   * @param {!chromeos.cellularSetup.mojom.EuiccRemote} euicc
+   * ESimManagerListenerBehavior override
+   */
+  onProfileListChanged(euicc) {
+    this.fetchESimPendingProfileListForEuicc_(euicc);
+  },
+
+  /**
+   * @param {!chromeos.cellularSetup.mojom.ESimProfileRemote} profile
+   * ESimManagerListenerBehavior override
+   */
+  onProfileChanged(profile) {
+    profile.getProperties().then(response => {
+      const eSimPendingProfileItem =
+          this.eSimPendingProfileItems_.find(item => {
+            return item.customData.iccid === response.properties.iccid;
+          });
+      if (!eSimPendingProfileItem) {
+        return;
+      }
+      eSimPendingProfileItem.customItemType = response.properties.state ===
+              chromeos.cellularSetup.mojom.ProfileState.kInstalling ?
+          NetworkList.CustomItemType.ESIM_INSTALLING_PROFILE :
+          NetworkList.CustomItemType.ESIM_PENDING_PROFILE;
+    });
+  },
+
   /** @private */
   fetchESimPendingProfileList_() {
     cellular_setup.getESimManagerRemote()
         .getAvailableEuiccs()
         .then(response => {
           if (response.euiccs.length > 0) {
-            return cellular_setup.getPendingESimProfiles(response.euiccs[0]);
+            // Use first available euicc as current. Only single Euicc modules are
+            // currently supported.
+            this.euicc_ = response.euiccs[0];
+            return this.fetchESimPendingProfileListForEuicc_(this.euicc_);
           }
-          throw new Error('No EUICCs available.');
-        })
-        .then(profiles => {
-          const pendingProfilePromises = profiles.map(profile => {
-            return profile.getProperties().then(response => {
-              return {
-                customItemType: NetworkList.CustomItemType.ESIM_PENDING_PROFILE,
-                customItemName:
-                    String.fromCharCode(...response.properties.name.data),
-                customItemSubtitle: String.fromCharCode(
-                    ...response.properties.serviceProvider.data),
-                polymerIcon: 'network:cellular-0',
-                showBeforeNetworksList: false,
-                customData: {
-                  iccid: response.properties.iccid,
-                },
-              };
-            });
-          });
-          Promise.all(pendingProfilePromises).then(profiles => {
-            this.eSimPendingProfiles_ = profiles;
-          });
-        })
-        .catch(error => {
-          console.error(error);
+          this.euicc_ = null;
         });
   },
 
   /**
+   * @param {!chromeos.cellularSetup.mojom.EuiccRemote} euicc
+   * @private
+   */
+  fetchESimPendingProfileListForEuicc_(euicc) {
+    cellular_setup.getPendingESimProfiles(euicc).then(
+        this.processESimPendingProfiles_.bind(this));
+  },
+
+  /**
+   * @param {Array<!chromeos.cellularSetup.mojom.ESimProfileRemote>} profiles
+   * @private
+   */
+  processESimPendingProfiles_(profiles) {
+    this.profilesMap_ = new Map();
+    const eSimPendingProfilePromises =
+        profiles.map(this.createESimPendingProfilePromise_.bind(this));
+    Promise.all(eSimPendingProfilePromises).then(eSimPendingProfileItems => {
+      this.eSimPendingProfileItems_ = eSimPendingProfileItems;
+    });
+  },
+
+  /**
+   * @param {!chromeos.cellularSetup.mojom.ESimProfileRemote} profile
+   * @return {!Promise<NetworkList.CustomItemState>}
+   * @private
+   */
+  createESimPendingProfilePromise_(profile) {
+    return profile.getProperties().then(response => {
+      this.profilesMap_.set(response.properties.iccid, profile);
+      return this.createESimPendingProfileItem_(response.properties);
+    });
+  },
+
+  /**
+   * @param {!chromeos.cellularSetup.mojom.ESimProfileProperties} properties
+   * @return {NetworkList.CustomItemState}
+   */
+  createESimPendingProfileItem_(properties) {
+    return {
+      customItemType: properties.state ===
+              chromeos.cellularSetup.mojom.ProfileState.kInstalling ?
+          NetworkList.CustomItemType.ESIM_INSTALLING_PROFILE :
+          NetworkList.CustomItemType.ESIM_PENDING_PROFILE,
+      customItemName: String.fromCharCode(...properties.name.data),
+      customItemSubtitle:
+          String.fromCharCode(...properties.serviceProvider.data),
+      polymerIcon: 'network:cellular-0',
+      showBeforeNetworksList: false,
+      customData: {
+        iccid: properties.iccid,
+      },
+    };
+  },
+
+  /**
    * @private
    */
   async onNetworksListChanged_() {
@@ -227,5 +313,19 @@
         this.$$('.eid-popup').focus();
       });
     }
-  }
+  },
+
+  /**
+   * @param {Event} event
+   * @private
+   */
+  installProfile_(event) {
+    const profileIccid = event.detail.iccid;
+    const profile = this.profilesMap_.get(profileIccid);
+    profile.installProfile('').then(
+        () => {
+            // TODO(crbug.com/1093185) Show error if install fails.
+            // Show confirmation code page if required.
+        });
+  },
 });
diff --git a/chrome/browser/resources/settings/chromeos/os_languages_page/input_page.html b/chrome/browser/resources/settings/chromeos/os_languages_page/input_page.html
index 8b1cebd04..0e04f47 100644
--- a/chrome/browser/resources/settings/chromeos/os_languages_page/input_page.html
+++ b/chrome/browser/resources/settings/chromeos/os_languages_page/input_page.html
@@ -137,7 +137,7 @@
           <template is="dom-repeat"
               items="[[languages.inputMethods.enabled]]">
             <div class$="list-item [[getInputMethodItemClass_(
-                item.id, languages.inputMethods.currentId)]] no-outline"
+                item.id, languages.inputMethods.currentId)]]"
                 actionable on-click="onInputMethodClick_"
                 on-keypress="onInputMethodKeyPress_"
                 tabindex$="[[getInputMethodTabIndex_(
diff --git a/chrome/browser/search_engines/template_url_service_unittest.cc b/chrome/browser/search_engines/template_url_service_unittest.cc
index 4c45c40..fd2058df 100644
--- a/chrome/browser/search_engines/template_url_service_unittest.cc
+++ b/chrome/browser/search_engines/template_url_service_unittest.cc
@@ -1967,41 +1967,6 @@
   EXPECT_EQ(user5, model()->GetTemplateURLForHost("test5"));
 }
 
-TEST_F(TemplateURLServiceTest, CleanUpLegacyUniquifiedKeywords) {
-  test_util()->VerifyLoad();
-
-  // 1. Add a "better" replaceable engine with keyword = "test1_".
-  // Expect that the keyword is reset to "test1".
-  const TemplateURL* replaceable1 = AddKeywordWithDate(
-      "replaceable1", "test1", "https://test1", std::string(), std::string(),
-      std::string(), true, "UTF-8", base::Time::FromTimeT(20));
-  ASSERT_TRUE(replaceable1);
-  EXPECT_EQ(replaceable1,
-            model()->GetTemplateURLForKeyword(base::ASCIIToUTF16("test1")));
-  EXPECT_FALSE(model()->GetTemplateURLForKeyword(base::ASCIIToUTF16("test1_")));
-
-  // 2. Add a "worse" replaceable engine with keyword = "test1_".
-  // Expect that it is silently collapsed with the above engine.
-  EXPECT_FALSE(AddKeywordWithDate("replaceable2", "test1_", "https://test1",
-                                  std::string(), std::string(), std::string(),
-                                  true));
-  EXPECT_EQ(replaceable1,
-            model()->GetTemplateURLForKeyword(base::ASCIIToUTF16("test1")));
-  EXPECT_FALSE(model()->GetTemplateURLForKeyword(base::ASCIIToUTF16("test1_")));
-
-  // 3. But for user engines (non-replaceable ones), do not reset keywords
-  // ending with underscores. Expect that it gets successfully added with
-  // "test1_" keyword, and that replaceable1 still exists with "test1" keyword.
-  const TemplateURL* user1 =
-      AddKeywordWithDate("user1", "test1_", "https://test1", std::string(),
-                         std::string(), std::string(), false);
-  ASSERT_TRUE(user1);
-  EXPECT_EQ(replaceable1,
-            model()->GetTemplateURLForKeyword(base::ASCIIToUTF16("test1")));
-  EXPECT_EQ(user1,
-            model()->GetTemplateURLForKeyword(base::ASCIIToUTF16("test1_")));
-}
-
 // Check that two extensions with the same engine are handled correctly.
 TEST_F(TemplateURLServiceTest, ExtensionsWithSameKeywords) {
   test_util()->VerifyLoad();
diff --git a/chrome/browser/serial/chrome_serial_browsertest.cc b/chrome/browser/serial/chrome_serial_browsertest.cc
index eb1a78d..efda58b2 100644
--- a/chrome/browser/serial/chrome_serial_browsertest.cc
+++ b/chrome/browser/serial/chrome_serial_browsertest.cc
@@ -3,8 +3,10 @@
 // found in the LICENSE file.
 
 #include "base/command_line.h"
+#include "base/test/scoped_feature_list.h"
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
+#include "chrome/browser/serial/serial_blocklist.h"
 #include "chrome/browser/serial/serial_chooser_context.h"
 #include "chrome/browser/serial/serial_chooser_context_factory.h"
 #include "chrome/browser/ui/browser.h"
@@ -45,10 +47,29 @@
     ui_test_utils::NavigateToURL(browser(), url);
   }
 
+  void TearDown() override {
+    // Because SerialBlocklist is a singleton it must be cleared after tests run
+    // to prevent leakage between tests.
+    feature_list_.Reset();
+    SerialBlocklist::Get().ResetToDefaultValuesForTesting();
+  }
+
+  void SetDynamicBlocklist(base::StringPiece value) {
+    feature_list_.Reset();
+
+    std::map<std::string, std::string> parameters;
+    parameters[kWebSerialBlocklistAdditions.name] = std::string(value);
+    feature_list_.InitWithFeaturesAndParameters(
+        {{kWebSerialBlocklist, parameters}}, {});
+
+    SerialBlocklist::Get().ResetToDefaultValuesForTesting();
+  }
+
   device::FakeSerialPortManager& port_manager() { return port_manager_; }
   SerialChooserContext* context() { return context_; }
 
  private:
+  base::test::ScopedFeatureList feature_list_;
   device::FakeSerialPortManager port_manager_;
   SerialChooserContext* context_;
 };
@@ -108,4 +129,53 @@
   EXPECT_EQ(true, content::EvalJs(web_contents, "removedPromise"));
 }
 
+class SerialBlocklistTest : public SerialTest {
+ public:
+  void SetUp() override {
+    // Add a single device to the blocklist. This has to happen before
+    // BrowserTestBase::SetUp() is run.
+    std::map<std::string, std::string> parameters;
+    parameters[kWebSerialBlocklistAdditions.name] = "usb:18D1:58F0";
+    feature_list_.InitWithFeaturesAndParameters(
+        {{kWebSerialBlocklist, parameters}}, {});
+
+    SerialTest::SetUp();
+  }
+
+  void TearDown() override {
+    // Because SerialBlocklist is a singleton it must be cleared after tests run
+    // to prevent leakage between tests.
+    feature_list_.Reset();
+    SerialBlocklist::Get().ResetToDefaultValuesForTesting();
+
+    SerialTest::TearDown();
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+IN_PROC_BROWSER_TEST_F(SerialBlocklistTest, Blocklist) {
+  content::WebContents* web_contents =
+      browser()->tab_strip_model()->GetActiveWebContents();
+
+  // Create port and grant permission to it.
+  auto port = device::mojom::SerialPortInfo::New();
+  port->token = base::UnguessableToken::Create();
+  port->has_vendor_id = true;
+  port->vendor_id = 0x18D1;
+  port->has_product_id = true;
+  port->product_id = 0x58F0;
+  url::Origin origin = web_contents->GetMainFrame()->GetLastCommittedOrigin();
+  context()->GrantPortPermission(origin, origin, *port);
+  port_manager().AddPort(port.Clone());
+
+  // Adding a USB device to the blocklist overrides any previously granted
+  // permissions.
+  EXPECT_EQ(0, content::EvalJs(web_contents, R"((async () => {
+        let ports = await navigator.serial.getPorts();
+        return ports.length;
+      })())"));
+}
+
 }  // namespace
diff --git a/chrome/browser/serial/serial_blocklist.cc b/chrome/browser/serial/serial_blocklist.cc
new file mode 100644
index 0000000..e9fddbc
--- /dev/null
+++ b/chrome/browser/serial/serial_blocklist.cc
@@ -0,0 +1,123 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/serial/serial_blocklist.h"
+
+#include <algorithm>
+#include <string>
+#include <tuple>
+
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "services/device/public/mojom/serial.mojom.h"
+
+namespace {
+
+// Returns true if the passed string is exactly 4 digits long and only contains
+// valid hexadecimal characters (no leading 0x).
+bool IsHexComponent(base::StringPiece string) {
+  if (string.length() != 4)
+    return false;
+
+  // This is necessary because base::HexStringToUInt allows whitespace and the
+  // "0x" prefix in its input.
+  for (char c : string) {
+    if (c >= '0' && c <= '9')
+      continue;
+    if (c >= 'a' && c <= 'f')
+      continue;
+    if (c >= 'A' && c <= 'F')
+      continue;
+    return false;
+  }
+  return true;
+}
+
+bool CompareEntry(const SerialBlocklist::Entry& a,
+                  const SerialBlocklist::Entry& b) {
+  return std::tie(a.usb_vendor_id, a.usb_product_id) <
+         std::tie(b.usb_vendor_id, b.usb_product_id);
+}
+
+// Returns true if an entry in [begin, end) matches the vendor and product IDs
+// of |entry| and has a device version greater than or equal to |entry|.
+template <class Iterator>
+bool EntryMatches(Iterator begin,
+                  Iterator end,
+                  const SerialBlocklist::Entry& entry) {
+  auto it = std::lower_bound(begin, end, entry, CompareEntry);
+  return it != end && it->usb_vendor_id == entry.usb_vendor_id &&
+         it->usb_product_id == entry.usb_product_id;
+}
+
+// This list must be sorted according to CompareEntry.
+constexpr SerialBlocklist::Entry kStaticEntries[] = {
+    {0x18D1, 0x58F3},  // Test entry: GOOGLE_HID_ECHO_GADGET
+};
+
+}  // namespace
+
+constexpr base::Feature kWebSerialBlocklist{"WebSerialBlocklist",
+                                            base::FEATURE_ENABLED_BY_DEFAULT};
+
+constexpr base::FeatureParam<std::string> kWebSerialBlocklistAdditions{
+    &kWebSerialBlocklist, "BlocklistAdditions", /*default_value=*/""};
+
+SerialBlocklist::~SerialBlocklist() = default;
+
+// static
+SerialBlocklist& SerialBlocklist::Get() {
+  static base::NoDestructor<SerialBlocklist> blocklist;
+  return *blocklist;
+}
+
+bool SerialBlocklist::IsExcluded(
+    const device::mojom::SerialPortInfo& port_info) const {
+  // Only USB devices can be matched.
+  if (!port_info.has_vendor_id || !port_info.has_product_id) {
+    return false;
+  }
+
+  Entry entry(port_info.vendor_id, port_info.product_id);
+  return EntryMatches(std::begin(kStaticEntries), std::end(kStaticEntries),
+                      entry) ||
+         EntryMatches(dynamic_entries_.begin(), dynamic_entries_.end(), entry);
+}
+
+void SerialBlocklist::ResetToDefaultValuesForTesting() {
+  dynamic_entries_.clear();
+  PopulateWithServerProvidedValues();
+}
+
+SerialBlocklist::SerialBlocklist() {
+  DCHECK(std::is_sorted(std::begin(kStaticEntries), std::end(kStaticEntries),
+                        CompareEntry));
+  PopulateWithServerProvidedValues();
+}
+
+void SerialBlocklist::PopulateWithServerProvidedValues() {
+  std::string blocklist_string = kWebSerialBlocklistAdditions.Get();
+
+  for (const auto& entry :
+       base::SplitStringPiece(blocklist_string, ",", base::TRIM_WHITESPACE,
+                              base::SPLIT_WANT_NONEMPTY)) {
+    std::vector<base::StringPiece> components = base::SplitStringPiece(
+        entry, ":", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
+    if (components.size() != 3 || components[0] != "usb" ||
+        !IsHexComponent(components[1]) || !IsHexComponent(components[2])) {
+      continue;
+    }
+
+    uint32_t vendor_id;
+    uint32_t product_id;
+    if (!base::HexStringToUInt(components[1], &vendor_id) ||
+        !base::HexStringToUInt(components[2], &product_id)) {
+      continue;
+    }
+
+    dynamic_entries_.emplace_back(vendor_id, product_id);
+  }
+
+  std::sort(dynamic_entries_.begin(), dynamic_entries_.end(), CompareEntry);
+}
diff --git a/chrome/browser/serial/serial_blocklist.h b/chrome/browser/serial/serial_blocklist.h
new file mode 100644
index 0000000..324d5a4
--- /dev/null
+++ b/chrome/browser/serial/serial_blocklist.h
@@ -0,0 +1,88 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_SERIAL_SERIAL_BLOCKLIST_H_
+#define CHROME_BROWSER_SERIAL_SERIAL_BLOCKLIST_H_
+
+#include <stdint.h>
+
+#include <string>
+#include <vector>
+
+#include "base/feature_list.h"
+#include "base/metrics/field_trial_params.h"
+#include "base/no_destructor.h"
+#include "services/device/public/mojom/serial.mojom-forward.h"
+
+// Feature used to configure entries in the Web Serial API blocklist which can
+// be deployed using a server configuration.
+extern const base::Feature kWebSerialBlocklist;
+
+// Dynamic additions to the Web Serial API device blocklist.
+//
+// The string must be a comma-separated list of entries which start with a type
+// identifier. The only currently supported type identifier is "usb:". Entries
+// may be separated by an arbitrary amount of whitespace.
+//
+// A USB entry provides a vendor ID and product ID, each a 16-bit integer
+// written as exactly 4 hexadecimal digits. For example, the entry
+// "usb:1000:001C" matches a device with a vendor ID of 0x1000 and a product
+// ID of 0x001C.
+//
+// Invalid entries in the list will be ignored.
+extern const base::FeatureParam<std::string> kWebSerialBlocklistAdditions;
+
+class SerialBlocklist final {
+ public:
+  // An entry in the blocklist. Represents a device that should not be
+  // accessible using the Web Serial API. Currently only USB devices can be
+  // matched by an entry but this could be expanded in the future to support a
+  // more expressive ruleset.
+  struct Entry {
+    constexpr Entry(uint16_t usb_vendor_id, uint16_t usb_product_id)
+        : usb_vendor_id(usb_vendor_id), usb_product_id(usb_product_id) {}
+
+    // Matched against the idVendor field of the USB Device Descriptor.
+    uint16_t usb_vendor_id;
+
+    // Matched against the idProduct field of the USB Device Descriptor.
+    uint16_t usb_product_id;
+  };
+
+  SerialBlocklist(const SerialBlocklist&) = delete;
+  SerialBlocklist& operator=(const SerialBlocklist&) = delete;
+  ~SerialBlocklist();
+
+  // Returns a singleton instance of the blocklist.
+  static SerialBlocklist& Get();
+
+  // Returns if a device is excluded from access.
+  bool IsExcluded(const device::mojom::SerialPortInfo& port_info) const;
+
+  // Size of the blocklist.
+  size_t GetDynamicEntryCountForTesting() const {
+    return dynamic_entries_.size();
+  }
+
+  // Reload the blocklist for testing purposes.
+  void ResetToDefaultValuesForTesting();
+
+ private:
+  // Friend NoDestructor to permit access to private constructor.
+  friend class base::NoDestructor<SerialBlocklist>;
+
+  SerialBlocklist();
+
+  // Populates the blocklist with values set via a Finch experiment which allows
+  // the set of blocked devices to be updated without shipping new executable
+  // versions.
+  //
+  // See kWebSerialBlocklistAdditions for the format of this parameter.
+  void PopulateWithServerProvidedValues();
+
+  // Set of blocklist entries.
+  std::vector<Entry> dynamic_entries_;
+};
+
+#endif  // CHROME_BROWSER_SERIAL_SERIAL_BLOCKLIST_H_
diff --git a/chrome/browser/serial/serial_blocklist_unittest.cc b/chrome/browser/serial/serial_blocklist_unittest.cc
new file mode 100644
index 0000000..60bfcd4
--- /dev/null
+++ b/chrome/browser/serial/serial_blocklist_unittest.cc
@@ -0,0 +1,132 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/serial/serial_blocklist.h"
+
+#include "base/strings/string_piece.h"
+#include "base/test/scoped_feature_list.h"
+#include "services/device/public/mojom/serial.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+class SerialBlocklistTest : public testing::Test {
+ public:
+  void SetDynamicBlocklist(base::StringPiece value) {
+    feature_list_.Reset();
+
+    std::map<std::string, std::string> parameters;
+    parameters[kWebSerialBlocklistAdditions.name] = std::string(value);
+    feature_list_.InitWithFeaturesAndParameters(
+        {{kWebSerialBlocklist, parameters}}, {});
+
+    SerialBlocklist::Get().ResetToDefaultValuesForTesting();
+  }
+
+  device::mojom::SerialPortInfoPtr CreateInfo(uint16_t usb_vendor_id,
+                                              uint16_t usb_product_id) {
+    auto info = device::mojom::SerialPortInfo::New();
+    info->has_vendor_id = true;
+    info->vendor_id = usb_vendor_id;
+    info->has_product_id = true;
+    info->product_id = usb_product_id;
+    return info;
+  }
+
+ private:
+  void TearDown() override {
+    // Because SerialBlocklist is a singleton it must be cleared after tests run
+    // to prevent leakage between tests.
+    feature_list_.Reset();
+    SerialBlocklist::Get().ResetToDefaultValuesForTesting();
+  }
+
+  base::test::ScopedFeatureList feature_list_;
+};
+
+TEST_F(SerialBlocklistTest, BasicExclusions) {
+  SetDynamicBlocklist("usb:18D1:58F0");
+  EXPECT_TRUE(SerialBlocklist::Get().IsExcluded(*CreateInfo(0x18D1, 0x58F0)));
+  // Devices with nearby vendor and product IDs are not blocked.
+  EXPECT_FALSE(SerialBlocklist::Get().IsExcluded(*CreateInfo(0x18D1, 0x58F1)));
+  EXPECT_FALSE(SerialBlocklist::Get().IsExcluded(*CreateInfo(0x18D1, 0x58EF)));
+  EXPECT_FALSE(SerialBlocklist::Get().IsExcluded(*CreateInfo(0x18D0, 0x58F0)));
+  EXPECT_FALSE(SerialBlocklist::Get().IsExcluded(*CreateInfo(0x18D2, 0x58F0)));
+}
+
+TEST_F(SerialBlocklistTest, NonUsbDevice) {
+  auto info = device::mojom::SerialPortInfo::New();
+  info->has_vendor_id = false;
+  info->has_product_id = false;
+  EXPECT_FALSE(SerialBlocklist::Get().IsExcluded(*info));
+}
+
+TEST_F(SerialBlocklistTest, StringsWithNoValidEntries) {
+  SetDynamicBlocklist("");
+  EXPECT_EQ(0u, SerialBlocklist::Get().GetDynamicEntryCountForTesting());
+
+  SetDynamicBlocklist("~!@#$%^&*()-_=+[]{}/*-");
+  EXPECT_EQ(0u, SerialBlocklist::Get().GetDynamicEntryCountForTesting());
+
+  SetDynamicBlocklist(":");
+  EXPECT_EQ(0u, SerialBlocklist::Get().GetDynamicEntryCountForTesting());
+
+  SetDynamicBlocklist("::");
+  EXPECT_EQ(0u, SerialBlocklist::Get().GetDynamicEntryCountForTesting());
+
+  SetDynamicBlocklist(",");
+  EXPECT_EQ(0u, SerialBlocklist::Get().GetDynamicEntryCountForTesting());
+
+  SetDynamicBlocklist(",,");
+  EXPECT_EQ(0u, SerialBlocklist::Get().GetDynamicEntryCountForTesting());
+
+  SetDynamicBlocklist(",::,");
+  EXPECT_EQ(0u, SerialBlocklist::Get().GetDynamicEntryCountForTesting());
+
+  SetDynamicBlocklist("usb:2:3");
+  EXPECT_EQ(0u, SerialBlocklist::Get().GetDynamicEntryCountForTesting());
+
+  SetDynamicBlocklist("usb:18D1:2");
+  EXPECT_EQ(0u, SerialBlocklist::Get().GetDynamicEntryCountForTesting());
+
+  SetDynamicBlocklist("usb:0000:0x00");
+  EXPECT_EQ(0u, SerialBlocklist::Get().GetDynamicEntryCountForTesting());
+
+  SetDynamicBlocklist("usb:0000:   0");
+  EXPECT_EQ(0u, SerialBlocklist::Get().GetDynamicEntryCountForTesting());
+
+  SetDynamicBlocklist("usb:000g:0000");
+  EXPECT_EQ(0u, SerialBlocklist::Get().GetDynamicEntryCountForTesting());
+
+  SetDynamicBlocklist("bluetooth:0000:0000");
+  EXPECT_EQ(0u, SerialBlocklist::Get().GetDynamicEntryCountForTesting());
+
+  SetDynamicBlocklist("☯");
+  EXPECT_EQ(0u, SerialBlocklist::Get().GetDynamicEntryCountForTesting());
+}
+
+TEST_F(SerialBlocklistTest, StringsWithOneValidEntry) {
+  SetDynamicBlocklist("usb:18D1:58F0");
+  EXPECT_EQ(1u, SerialBlocklist::Get().GetDynamicEntryCountForTesting());
+  EXPECT_TRUE(SerialBlocklist::Get().IsExcluded(*CreateInfo(0x18D1, 0x58F0)));
+
+  SetDynamicBlocklist(" usb:18D1:58F0  ");
+  EXPECT_EQ(1u, SerialBlocklist::Get().GetDynamicEntryCountForTesting());
+  EXPECT_TRUE(SerialBlocklist::Get().IsExcluded(*CreateInfo(0x18D1, 0x58F0)));
+
+  SetDynamicBlocklist(", usb:18D1:58F0,  ");
+  EXPECT_EQ(1u, SerialBlocklist::Get().GetDynamicEntryCountForTesting());
+  EXPECT_TRUE(SerialBlocklist::Get().IsExcluded(*CreateInfo(0x18D1, 0x58F0)));
+
+  SetDynamicBlocklist("usb:18D1:58F0, bluetooth:18D1:58F1");
+  EXPECT_EQ(1u, SerialBlocklist::Get().GetDynamicEntryCountForTesting());
+  EXPECT_TRUE(SerialBlocklist::Get().IsExcluded(*CreateInfo(0x18D1, 0x58F0)));
+}
+
+TEST_F(SerialBlocklistTest, StaticEntries) {
+  EXPECT_TRUE(SerialBlocklist::Get().IsExcluded(*CreateInfo(0x18D1, 0x58F3)));
+  // Devices with nearby vendor and product IDs are not blocked.
+  EXPECT_FALSE(SerialBlocklist::Get().IsExcluded(*CreateInfo(0x18D1, 0x58F4)));
+  EXPECT_FALSE(SerialBlocklist::Get().IsExcluded(*CreateInfo(0x18D1, 0x58F2)));
+  EXPECT_FALSE(SerialBlocklist::Get().IsExcluded(*CreateInfo(0x18D0, 0x58F3)));
+  EXPECT_FALSE(SerialBlocklist::Get().IsExcluded(*CreateInfo(0x18D2, 0x58F3)));
+}
diff --git a/chrome/browser/serial/serial_chooser_context.cc b/chrome/browser/serial/serial_chooser_context.cc
index d3079bfb..6130db6 100644
--- a/chrome/browser/serial/serial_chooser_context.cc
+++ b/chrome/browser/serial/serial_chooser_context.cc
@@ -13,6 +13,7 @@
 #include "build/build_config.h"
 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
 #include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/serial/serial_blocklist.h"
 #include "chrome/browser/serial/serial_chooser_histograms.h"
 #include "content/public/browser/device_service.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
@@ -226,6 +227,10 @@
     const url::Origin& requesting_origin,
     const url::Origin& embedding_origin,
     const device::mojom::SerialPortInfo& port) {
+  if (SerialBlocklist::Get().IsExcluded(port)) {
+    return false;
+  }
+
   if (!CanRequestObjectPermission(requesting_origin, embedding_origin)) {
     return false;
   }
diff --git a/chrome/browser/serial/serial_chooser_context_unittest.cc b/chrome/browser/serial/serial_chooser_context_unittest.cc
index 5180ec4..3201abe 100644
--- a/chrome/browser/serial/serial_chooser_context_unittest.cc
+++ b/chrome/browser/serial/serial_chooser_context_unittest.cc
@@ -8,8 +8,10 @@
 #include "base/run_loop.h"
 #include "base/scoped_observer.h"
 #include "base/test/metrics/histogram_tester.h"
+#include "base/test/scoped_feature_list.h"
 #include "build/build_config.h"
 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
+#include "chrome/browser/serial/serial_blocklist.h"
 #include "chrome/browser/serial/serial_chooser_context_factory.h"
 #include "chrome/browser/serial/serial_chooser_histograms.h"
 #include "chrome/test/base/testing_profile.h"
@@ -79,6 +81,24 @@
   SerialChooserContextTest(SerialChooserContextTest&) = delete;
   SerialChooserContextTest& operator=(SerialChooserContextTest&) = delete;
 
+  void TearDown() override {
+    // Because SerialBlocklist is a singleton it must be cleared after tests run
+    // to prevent leakage between tests.
+    feature_list_.Reset();
+    SerialBlocklist::Get().ResetToDefaultValuesForTesting();
+  }
+
+  void SetDynamicBlocklist(base::StringPiece value) {
+    feature_list_.Reset();
+
+    std::map<std::string, std::string> parameters;
+    parameters[kWebSerialBlocklistAdditions.name] = std::string(value);
+    feature_list_.InitWithFeaturesAndParameters(
+        {{kWebSerialBlocklist, parameters}}, {});
+
+    SerialBlocklist::Get().ResetToDefaultValuesForTesting();
+  }
+
   device::FakeSerialPortManager& port_manager() { return port_manager_; }
   TestingProfile* profile() { return &profile_; }
   SerialChooserContext* context() { return context_; }
@@ -89,6 +109,7 @@
 
  private:
   content::BrowserTaskEnvironment task_environment_;
+  base::test::ScopedFeatureList feature_list_;
   device::FakeSerialPortManager port_manager_;
   TestingProfile profile_;
   SerialChooserContext* context_;
@@ -439,3 +460,33 @@
       all_origin_objects = context()->GetAllGrantedObjects();
   EXPECT_EQ(1u, all_origin_objects.size());
 }
+
+TEST_F(SerialChooserContextTest, Blocklist) {
+  const auto origin = url::Origin::Create(GURL("https://google.com"));
+
+  auto port = device::mojom::SerialPortInfo::New();
+  port->token = base::UnguessableToken::Create();
+  port->has_vendor_id = true;
+  port->vendor_id = 0x18D1;
+  port->has_product_id = true;
+  port->product_id = 0x58F0;
+  context()->GrantPortPermission(origin, origin, *port);
+  EXPECT_TRUE(context()->HasPortPermission(origin, origin, *port));
+
+  // Adding a USB device to the blocklist overrides any previously granted
+  // permissions.
+  SetDynamicBlocklist("usb:18D1:58F0");
+  EXPECT_FALSE(context()->HasPortPermission(origin, origin, *port));
+
+  // The lists of granted permissions will still include the entry because
+  // permission storage does not include the USB vendor and product IDs on all
+  // platforms and users should still be made aware of permissions they've
+  // granted even if they are being blocked from taking effect.
+  std::vector<std::unique_ptr<permissions::ChooserContextBase::Object>>
+      objects = context()->GetGrantedObjects(origin, origin);
+  EXPECT_EQ(1u, objects.size());
+
+  std::vector<std::unique_ptr<permissions::ChooserContextBase::Object>>
+      all_origin_objects = context()->GetAllGrantedObjects();
+  EXPECT_EQ(1u, all_origin_objects.size());
+}
diff --git a/chrome/browser/sync_file_system/sync_file_system_service.cc b/chrome/browser/sync_file_system/sync_file_system_service.cc
index 3ed800f..ec2889d 100644
--- a/chrome/browser/sync_file_system/sync_file_system_service.cc
+++ b/chrome/browser/sync_file_system/sync_file_system_service.cc
@@ -114,7 +114,7 @@
 void DidGetFileSyncStatusForDump(
     base::ListValue* files,
     size_t* num_results,
-    const SyncFileSystemService::DumpFilesCallback& callback,
+    base::RepeatingCallback<void(const base::ListValue&)> callback,
     base::DictionaryValue* file,
     SyncStatusCode sync_status_code,
     SyncFileStatus sync_file_status) {
@@ -129,7 +129,10 @@
   if (++*num_results < files->GetSize())
     return;
 
-  callback.Run(*files);
+  // |callback| is backed by a DumpFilesCallback, which should only be called
+  // once. Move |callback| here to force repeated calls to crash instead of
+  // silently failing.
+  std::move(callback).Run(*files);
 }
 
 // We need this indirection because WeakPtr can only be bound to methods
@@ -300,13 +303,13 @@
 
 void SyncFileSystemService::GetExtensionStatusMap(
     ExtensionStatusMapCallback callback) {
-  remote_service_->GetOriginStatusMap(base::AdaptCallbackForRepeating(
+  remote_service_->GetOriginStatusMap(
       base::BindOnce(&SyncFileSystemService::DidGetExtensionStatusMap,
-                     AsWeakPtr(), std::move(callback))));
+                     AsWeakPtr(), std::move(callback)));
 }
 
 void SyncFileSystemService::DumpFiles(const GURL& origin,
-                                      const DumpFilesCallback& callback) {
+                                      DumpFilesCallback callback) {
   DCHECK(!origin.is_empty());
 
   content::StoragePartition* storage_partition =
@@ -315,14 +318,14 @@
       storage_partition->GetFileSystemContext();
   local_service_->MaybeInitializeFileSystemContext(
       origin, file_system_context,
-      base::Bind(&SyncFileSystemService::DidInitializeFileSystemForDump,
-                 AsWeakPtr(), origin, callback));
+      base::BindOnce(&SyncFileSystemService::DidInitializeFileSystemForDump,
+                     AsWeakPtr(), origin, std::move(callback)));
 }
 
-void SyncFileSystemService::DumpDatabase(const DumpFilesCallback& callback) {
+void SyncFileSystemService::DumpDatabase(DumpFilesCallback callback) {
   remote_service_->DumpDatabase(
-      base::Bind(&SyncFileSystemService::DidDumpDatabase,
-                 AsWeakPtr(), callback));
+      base::BindOnce(&SyncFileSystemService::DidDumpDatabase, AsWeakPtr(),
+                     std::move(callback)));
 }
 
 void SyncFileSystemService::GetFileSyncStatus(const FileSystemURL& url,
@@ -538,47 +541,40 @@
 
 void SyncFileSystemService::DidInitializeFileSystemForDump(
     const GURL& origin,
-    const DumpFilesCallback& callback,
+    DumpFilesCallback callback,
     SyncStatusCode status) {
   DCHECK(!origin.is_empty());
 
-  if (status != SYNC_STATUS_OK) {
-    callback.Run(base::ListValue());
-    return;
-  }
-
-  if (!remote_service_) {
-    callback.Run(base::ListValue());
+  if (status != SYNC_STATUS_OK || !remote_service_) {
+    std::move(callback).Run(base::ListValue());
     return;
   }
 
   remote_service_->DumpFiles(
-      origin,
-      base::Bind(
-          &SyncFileSystemService::DidDumpFiles,
-          AsWeakPtr(),
-          origin,
-          callback));
+      origin, base::BindOnce(&SyncFileSystemService::DidDumpFiles, AsWeakPtr(),
+                             origin, std::move(callback)));
 }
 
 void SyncFileSystemService::DidDumpFiles(
     const GURL& origin,
-    const DumpFilesCallback& callback,
+    DumpFilesCallback callback,
     std::unique_ptr<base::ListValue> dump_files) {
   if (!dump_files || !dump_files->GetSize() ||
       !local_service_ || !remote_service_) {
-    callback.Run(base::ListValue());
+    std::move(callback).Run(base::ListValue());
     return;
   }
 
   base::ListValue* files = dump_files.get();
-  base::Callback<void(base::DictionaryValue*,
-                      SyncStatusCode,
-                      SyncFileStatus)> completion_callback =
-      base::Bind(&DidGetFileSyncStatusForDump,
-                 base::Owned(dump_files.release()),
-                 base::Owned(new size_t(0)),
-                 callback);
+
+  using AccumulateFileSyncStatusCallback = base::RepeatingCallback<void(
+      base::DictionaryValue*, SyncStatusCode, SyncFileStatus)>;
+
+  // |accumulate_callback| should only call |callback| once.
+  AccumulateFileSyncStatusCallback accumulate_callback = base::BindRepeating(
+      &DidGetFileSyncStatusForDump, base::Owned(dump_files.release()),
+      base::Owned(new size_t(0)),
+      base::AdaptCallbackForRepeating(std::move(callback)));
 
   // After all metadata loaded, sync status can be added to each entry.
   for (size_t i = 0; i < files->GetSize(); ++i) {
@@ -587,30 +583,30 @@
     if (!files->GetDictionary(i, &file) ||
         !file->GetString("path", &path_string)) {
       NOTREACHED();
-      completion_callback.Run(
-          nullptr, SYNC_FILE_ERROR_FAILED, SYNC_FILE_STATUS_UNKNOWN);
+      accumulate_callback.Run(nullptr, SYNC_FILE_ERROR_FAILED,
+                              SYNC_FILE_STATUS_UNKNOWN);
       continue;
     }
 
     base::FilePath file_path = base::FilePath::FromUTF8Unsafe(path_string);
     FileSystemURL url = CreateSyncableFileSystemURL(origin, file_path);
-    GetFileSyncStatus(url, base::Bind(completion_callback, file));
+    GetFileSyncStatus(url, base::BindOnce(accumulate_callback, file));
   }
 }
 
 void SyncFileSystemService::DidDumpDatabase(
-    const DumpFilesCallback& callback,
+    DumpFilesCallback callback,
     std::unique_ptr<base::ListValue> list) {
   if (!list)
-    list = base::WrapUnique(new base::ListValue);
-  callback.Run(*list);
+    list = std::make_unique<base::ListValue>();
+  std::move(callback).Run(*list);
 }
 
 void SyncFileSystemService::DidGetExtensionStatusMap(
     ExtensionStatusMapCallback callback,
     std::unique_ptr<RemoteFileSyncService::OriginStatusMap> status_map) {
   if (!status_map)
-    status_map = base::WrapUnique(new RemoteFileSyncService::OriginStatusMap);
+    status_map = std::make_unique<RemoteFileSyncService::OriginStatusMap>();
   std::move(callback).Run(*status_map);
 }
 
diff --git a/chrome/browser/sync_file_system/sync_file_system_service.h b/chrome/browser/sync_file_system/sync_file_system_service.h
index 897fc7e..fb4a9d58 100644
--- a/chrome/browser/sync_file_system/sync_file_system_service.h
+++ b/chrome/browser/sync_file_system/sync_file_system_service.h
@@ -51,10 +51,9 @@
       public extensions::ExtensionRegistryObserver,
       public base::SupportsWeakPtr<SyncFileSystemService> {
  public:
-  typedef base::Callback<void(const base::ListValue&)> DumpFilesCallback;
-  typedef base::OnceCallback<void(
-      const RemoteFileSyncService::OriginStatusMap&)>
-      ExtensionStatusMapCallback;
+  using DumpFilesCallback = base::OnceCallback<void(const base::ListValue&)>;
+  using ExtensionStatusMapCallback =
+      base::OnceCallback<void(const RemoteFileSyncService::OriginStatusMap&)>;
 
   // KeyedService implementation.
   void Shutdown() override;
@@ -64,8 +63,8 @@
                         SyncStatusCallback callback);
 
   void GetExtensionStatusMap(ExtensionStatusMapCallback callback);
-  void DumpFiles(const GURL& origin, const DumpFilesCallback& callback);
-  void DumpDatabase(const DumpFilesCallback& callback);
+  void DumpFiles(const GURL& origin, DumpFilesCallback callback);
+  void DumpDatabase(DumpFilesCallback callback);
 
   // Returns the file |url|'s sync status.
   void GetFileSyncStatus(const storage::FileSystemURL& url,
@@ -111,13 +110,13 @@
                          SyncStatusCode status);
 
   void DidInitializeFileSystemForDump(const GURL& app_origin,
-                                      const DumpFilesCallback& callback,
+                                      DumpFilesCallback callback,
                                       SyncStatusCode status);
   void DidDumpFiles(const GURL& app_origin,
-                    const DumpFilesCallback& callback,
+                    DumpFilesCallback callback,
                     std::unique_ptr<base::ListValue> files);
 
-  void DidDumpDatabase(const DumpFilesCallback& callback,
+  void DidDumpDatabase(DumpFilesCallback callback,
                        std::unique_ptr<base::ListValue> list);
 
   void DidGetExtensionStatusMap(
diff --git a/chrome/browser/sync_file_system/sync_file_system_test_util.cc b/chrome/browser/sync_file_system/sync_file_system_test_util.cc
index 898810f..044d98b 100644
--- a/chrome/browser/sync_file_system/sync_file_system_test_util.cc
+++ b/chrome/browser/sync_file_system/sync_file_system_test_util.cc
@@ -29,9 +29,10 @@
   run_loop->Quit();
 }
 
-template <typename R> base::Callback<void(R)>
-AssignAndQuitCallback(base::RunLoop* run_loop, R* result) {
-  return base::Bind(&AssignAndQuit<R>, run_loop, base::Unretained(result));
+template <typename R>
+base::OnceCallback<void(R)> AssignAndQuitCallback(base::RunLoop* run_loop,
+                                                  R* result) {
+  return base::BindOnce(&AssignAndQuit<R>, run_loop, base::Unretained(result));
 }
 
 template <typename Arg, typename Param>
@@ -42,19 +43,20 @@
 }
 
 template <typename Arg>
-base::Callback<void(typename TypeTraits<Arg>::ParamType)>
+base::OnceCallback<void(typename TypeTraits<Arg>::ParamType)>
 CreateResultReceiver(Arg* arg_out) {
-  typedef typename TypeTraits<Arg>::ParamType Param;
-  return base::Bind(&ReceiveResult1<Arg, Param>,
-                    base::Owned(new bool(false)), arg_out);
+  using Param = typename TypeTraits<Arg>::ParamType;
+  return base::BindOnce(&ReceiveResult1<Arg, Param>,
+                        base::Owned(new bool(false)), arg_out);
 }
 
 // Instantiate versions we know callers will need.
-template base::Callback<void(SyncStatusCode)>
-AssignAndQuitCallback(base::RunLoop*, SyncStatusCode*);
+template base::OnceCallback<void(SyncStatusCode)> AssignAndQuitCallback(
+    base::RunLoop*,
+    SyncStatusCode*);
 
 #define INSTANTIATE_RECEIVER(type) \
-  template base::Callback<void(type)> CreateResultReceiver(type*)
+  template base::OnceCallback<void(type)> CreateResultReceiver(type*)
 INSTANTIATE_RECEIVER(SyncStatusCode);
 INSTANTIATE_RECEIVER(google_apis::DriveApiErrorCode);
 INSTANTIATE_RECEIVER(std::unique_ptr<RemoteFileSyncService::OriginStatusMap>);
diff --git a/chrome/browser/sync_file_system/sync_file_system_test_util.h b/chrome/browser/sync_file_system/sync_file_system_test_util.h
index 26bafaa..f2a421fa 100644
--- a/chrome/browser/sync_file_system/sync_file_system_test_util.h
+++ b/chrome/browser/sync_file_system/sync_file_system_test_util.h
@@ -52,23 +52,22 @@
 template <typename R>
 void AssignAndQuit(base::RunLoop* run_loop, R* result_out, R result);
 
-template <typename R> base::Callback<void(R)>
-AssignAndQuitCallback(base::RunLoop* run_loop, R* result);
+template <typename R>
+base::OnceCallback<void(R)> AssignAndQuitCallback(base::RunLoop* run_loop,
+                                                  R* result);
 
 template <typename Arg>
-base::Callback<void(typename TypeTraits<Arg>::ParamType)>
+base::OnceCallback<void(typename TypeTraits<Arg>::ParamType)>
 CreateResultReceiver(Arg* arg_out);
 
 template <typename Arg1, typename Arg2>
-base::Callback<void(typename TypeTraits<Arg1>::ParamType,
-                    typename TypeTraits<Arg2>::ParamType)>
-CreateResultReceiver(Arg1* arg1_out,
-                     Arg2* arg2_out) {
-  typedef typename TypeTraits<Arg1>::ParamType Param1;
-  typedef typename TypeTraits<Arg2>::ParamType Param2;
-  return base::Bind(&ReceiveResult2<Arg1, Arg2, Param1, Param2>,
-                    base::Owned(new bool(false)),
-                    arg1_out, arg2_out);
+base::OnceCallback<void(typename TypeTraits<Arg1>::ParamType,
+                        typename TypeTraits<Arg2>::ParamType)>
+CreateResultReceiver(Arg1* arg1_out, Arg2* arg2_out) {
+  using Param1 = typename TypeTraits<Arg1>::ParamType;
+  using Param2 = typename TypeTraits<Arg2>::ParamType;
+  return base::BindOnce(&ReceiveResult2<Arg1, Arg2, Param1, Param2>,
+                        base::Owned(new bool(false)), arg1_out, arg2_out);
 }
 
 }  // namespace sync_file_system
diff --git a/chrome/browser/thumbnail/cc/BUILD.gn b/chrome/browser/thumbnail/cc/BUILD.gn
index 9c709a7..e543ef4 100644
--- a/chrome/browser/thumbnail/cc/BUILD.gn
+++ b/chrome/browser/thumbnail/cc/BUILD.gn
@@ -19,6 +19,7 @@
     "//content/public/browser",
     "//media",
     "//mojo/public/cpp/bindings",
+    "//net",
     "//skia",
     "//ui/gfx",
   ]
@@ -45,6 +46,7 @@
   deps = [
     ":cc",
     "//base",
+    "//net",
     "//testing/gmock",
     "//testing/gtest",
   ]
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index 998d8d4..b9be6cf 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -3094,6 +3094,13 @@
       "cocoa/rosetta_required_infobar_delegate.mm",
       "cocoa/scoped_menu_bar_lock.h",
       "cocoa/scoped_menu_bar_lock.mm",
+      "cocoa/screentime/fake_webpage_controller.h",
+      "cocoa/screentime/fake_webpage_controller.mm",
+      "cocoa/screentime/tab_helper.h",
+      "cocoa/screentime/tab_helper.mm",
+      "cocoa/screentime/webpage_controller.h",
+      "cocoa/screentime/webpage_controller_impl.h",
+      "cocoa/screentime/webpage_controller_impl.mm",
       "cocoa/share_menu_controller.h",
       "cocoa/share_menu_controller.mm",
       "cocoa/simple_message_box_cocoa.h",
@@ -3161,6 +3168,7 @@
       "Carbon.framework",
       "Quartz.framework",
     ]
+    weak_frameworks = [ "ScreenTime.framework" ]
 
     if (enable_chromium_updater) {
       deps += [
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings.grd b/chrome/browser/ui/android/strings/android_chrome_strings.grd
index b77a584..82d307b 100644
--- a/chrome/browser/ui/android/strings/android_chrome_strings.grd
+++ b/chrome/browser/ui/android/strings/android_chrome_strings.grd
@@ -3096,6 +3096,9 @@
       <message name="IDS_READING_LIST_TITLE" desc="The title for the reading list page in main bookmark UI.">
         Reading list
       </message>
+      <message name="IDS_READING_LIST_TITLE_NEW" desc="The title for the reading list with a new text.">
+        Reading list <ph name="BEGIN_NEW">&lt;new&gt;</ph>New<ph name="END_NEW">&lt;/new&gt;</ph>
+      </message>
       <message name="IDS_READING_LIST_READ" desc="The header for the read section in the reading list UI.">
         Read
       </message>
@@ -3134,7 +3137,7 @@
         Save this page for later and get a reminder
       </message>
       <message name="IDS_READING_LIST_SAVE_PAGES_FOR_LATER" desc="The text on the reading list in product help bubble to introduce the feature to the user.">
-        Save pages for later and get a reminder
+        Add pages to your Reading List to get a reminder
       </message>
       <message name="IDS_READING_LIST_FIND_IN_BOOKMARKS" desc="The text to inform the user to find the reading list in bookmarks UI.">
         Find your reading list in Bookmarks
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_READING_LIST_SAVE_PAGES_FOR_LATER.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_READING_LIST_SAVE_PAGES_FOR_LATER.png.sha1
index 1dc0c56..202456aa 100644
--- a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_READING_LIST_SAVE_PAGES_FOR_LATER.png.sha1
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_READING_LIST_SAVE_PAGES_FOR_LATER.png.sha1
@@ -1 +1 @@
-26c8ba78ec652819da6c707f067832da8c37de89
\ No newline at end of file
+89c19bdab51f56cc41109a57ca8c2227ceea5f5a
\ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_READING_LIST_TITLE_NEW.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_READING_LIST_TITLE_NEW.png.sha1
new file mode 100644
index 0000000..e7b8b92
--- /dev/null
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_READING_LIST_TITLE_NEW.png.sha1
@@ -0,0 +1 @@
+50889cf3fbf93573b8531e5d2ef95907c0b57f11
\ No newline at end of file
diff --git a/chrome/browser/ui/ash/sharesheet/sharesheet_bubble_view.cc b/chrome/browser/ui/ash/sharesheet/sharesheet_bubble_view.cc
index 10c313a..ff324f88b 100644
--- a/chrome/browser/ui/ash/sharesheet/sharesheet_bubble_view.cc
+++ b/chrome/browser/ui/ash/sharesheet/sharesheet_bubble_view.cc
@@ -120,7 +120,7 @@
 
   void OnWidgetBoundsChanged(views::Widget* widget,
                              const gfx::Rect& bounds) override {
-    owner_->OnWidgetBoundsChanged(widget, bounds);
+    owner_->UpdateAnchorPosition();
   }
 
  private:
@@ -488,11 +488,6 @@
   }
 }
 
-void SharesheetBubbleView::OnWidgetBoundsChanged(views::Widget* widget,
-                                                 const gfx::Rect& new_bounds) {
-  UpdateAnchorPosition();
-}
-
 void SharesheetBubbleView::CreateBubble() {
   set_close_on_deactivate(false);
   SetButtons(ui::DIALOG_BUTTON_NONE);
diff --git a/chrome/browser/ui/ash/sharesheet/sharesheet_bubble_view.h b/chrome/browser/ui/ash/sharesheet/sharesheet_bubble_view.h
index c81d9de1..b522443 100644
--- a/chrome/browser/ui/ash/sharesheet/sharesheet_bubble_view.h
+++ b/chrome/browser/ui/ash/sharesheet/sharesheet_bubble_view.h
@@ -44,10 +44,6 @@
   void ResizeBubble(const int& width, const int& height);
   void CloseBubble();
 
-  // views::BubbleDialogDelegateView:
-  void OnWidgetBoundsChanged(views::Widget* widget,
-                             const gfx::Rect& new_bounds) override;
-
  private:
   class SharesheetParentWidgetObserver;
 
diff --git a/chrome/browser/ui/ash/tab_scrubber.cc b/chrome/browser/ui/ash/tab_scrubber.cc
index 8c19f25..1557711 100644
--- a/chrome/browser/ui/ash/tab_scrubber.cc
+++ b/chrome/browser/ui/ash/tab_scrubber.cc
@@ -293,14 +293,14 @@
   // one fourth of |x_offset| as the minimum (i.e. we need 38 tabs to reach
   // that minimum reduction).
   swipe_x_ += base::ClampToRange(
-      x_offset - (tab_strip_->tab_count() * 0.02f * x_offset), 0.25f * x_offset,
-      x_offset);
+      x_offset - (tab_strip_->GetTabCount() * 0.02f * x_offset),
+      0.25f * x_offset, x_offset);
 
   // In an RTL layout, everything is mirrored, i.e. the index of the first tab
   // (with the smallest X mirrored co-ordinates) is actually the index of the
   // last tab. Same for the index of the last tab.
-  int first_tab_index = base::i18n::IsRTL() ? tab_strip_->tab_count() - 1 : 0;
-  int last_tab_index = base::i18n::IsRTL() ? 0 : tab_strip_->tab_count() - 1;
+  int first_tab_index = base::i18n::IsRTL() ? tab_strip_->GetTabCount() - 1 : 0;
+  int last_tab_index = base::i18n::IsRTL() ? 0 : tab_strip_->GetTabCount() - 1;
 
   Tab* first_tab = tab_strip_->tab_at(first_tab_index);
   int first_tab_center = first_tab->GetMirroredBounds().CenterPoint().x();
diff --git a/chrome/browser/ui/browser.h b/chrome/browser/ui/browser.h
index 3d854e68b..62b62d2 100644
--- a/chrome/browser/ui/browser.h
+++ b/chrome/browser/ui/browser.h
@@ -246,6 +246,11 @@
 
     bool is_session_restore = false;
 
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+    // The id from the restore data to restore the browser window.
+    int32_t restore_id = 0;
+#endif
+
     bool is_focus_mode = false;
 
     // Whether this browser was created by a user gesture. We track this
diff --git a/chrome/browser/ui/browser_command_controller.cc b/chrome/browser/ui/browser_command_controller.cc
index b3d0dabb..ec7b2a36 100644
--- a/chrome/browser/ui/browser_command_controller.cc
+++ b/chrome/browser/ui/browser_command_controller.cc
@@ -690,6 +690,9 @@
     case IDC_SEND_TO_DESK_8:
       SendToDeskAtIndex(browser_, id - IDC_SEND_TO_DESK_1);
       break;
+    case IDC_TOGGLE_ASSIGN_TO_ALL_DESKS:
+      ToggleAssignedToAllDesks(browser_);
+      break;
 #endif
 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
     case IDC_FEEDBACK:
@@ -988,6 +991,7 @@
                 "IDC_SEND_TO_DESK_* commands must be in order.");
   auto* desks_helper = ash::DesksHelper::Get();
   UpdateCommandsForDesks(desks_helper ? desks_helper->GetNumberOfDesks() : 1);
+  command_updater_.UpdateCommandEnabled(IDC_TOGGLE_ASSIGN_TO_ALL_DESKS, true);
 #endif
 // TODO(crbug.com/1052397): Revisit the macro expression once build flag switch
 // of lacros-chrome is complete.
diff --git a/chrome/browser/ui/browser_commands_chromeos.cc b/chrome/browser/ui/browser_commands_chromeos.cc
index 58a1be5..3ffe91d 100644
--- a/chrome/browser/ui/browser_commands_chromeos.cc
+++ b/chrome/browser/ui/browser_commands_chromeos.cc
@@ -10,6 +10,7 @@
 #include "chrome/browser/ui/ash/chrome_screenshot_grabber.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_window.h"
+#include "ui/views/widget/widget.h"
 
 using base::UserMetricsAction;
 
@@ -24,3 +25,10 @@
   if (grabber->CanTakeScreenshot())
     grabber->HandleTakeScreenshotForAllRootWindows();
 }
+
+void ToggleAssignedToAllDesks(Browser* browser) {
+  auto* widget = views::Widget::GetWidgetForNativeWindow(
+      browser->window()->GetNativeWindow());
+  DCHECK(widget);
+  widget->SetVisibleOnAllWorkspaces(!widget->IsVisibleOnAllWorkspaces());
+}
diff --git a/chrome/browser/ui/browser_commands_chromeos.h b/chrome/browser/ui/browser_commands_chromeos.h
index 15d8d97..f859254 100644
--- a/chrome/browser/ui/browser_commands_chromeos.h
+++ b/chrome/browser/ui/browser_commands_chromeos.h
@@ -13,4 +13,7 @@
 // Takes a screenshot of the entire desktop (not just the browser window).
 void TakeScreenshot();
 
+// Toggles whether |browser| is assigned to all desks.
+void ToggleAssignedToAllDesks(Browser* browser);
+
 #endif  // CHROME_BROWSER_UI_BROWSER_COMMANDS_CHROMEOS_H_
diff --git a/chrome/browser/ui/cocoa/screentime/README.md b/chrome/browser/ui/cocoa/screentime/README.md
new file mode 100644
index 0000000..c918773
--- /dev/null
+++ b/chrome/browser/ui/cocoa/screentime/README.md
@@ -0,0 +1,23 @@
+This directory contains the integration between Chromium and the macOS
+ScreenTime system, which is a digital wellbeing tool allowing users to restrict
+their own use of apps and websites by category.
+
+The ScreenTime system API is documented [on
+apple.com](https://developer.apple.com/documentation/screentime?language=objc).
+The most pertinent class is `STWebpageController`, which is an
+`NSViewController` subclass. Clients of ScreenTime construct a single
+`STWebpageController` per tab and splice its corresponding NSView into their
+view tree in such a way that it covers the web contents. The NSView becomes
+opaque when screen time for that tab or website has been used up.
+
+The public interface to ScreenTime within Chromium is the
+`screentime::TabHelper` class, which is a
+[TabHelper](../../../../../docs/tab_helpers.md) that binds an
+STWebpageController to a WebContents.
+
+## Testing
+
+So that tests can avoid depending on the real ScreenTime system,
+STWebpageController is wrapped by a C++ class called
+screentime::WebpageController, which has a testing fake called
+screentime::FakeWebpageController.
diff --git a/chrome/browser/ui/cocoa/screentime/fake_webpage_controller.h b/chrome/browser/ui/cocoa/screentime/fake_webpage_controller.h
new file mode 100644
index 0000000..ebe2872
--- /dev/null
+++ b/chrome/browser/ui/cocoa/screentime/fake_webpage_controller.h
@@ -0,0 +1,38 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_COCOA_SCREENTIME_FAKE_WEBPAGE_CONTROLLER_H_
+#define CHROME_BROWSER_UI_COCOA_SCREENTIME_FAKE_WEBPAGE_CONTROLLER_H_
+
+#include "base/mac/scoped_nsobject.h"
+#include "chrome/browser/ui/cocoa/screentime/webpage_controller.h"
+
+namespace screentime {
+
+// An implementation of WebpageController that is not backed by the real
+// ScreenTime framework. This is used for testing and development on pre-11.0
+// devices that don't have the real ScreenTime API available.
+//
+// FakeWebpageController implements the following behavior:
+// 1. The ScreenTime "shield" view is a flat blue layer
+// 2. Every navigation causes it to toggle blocking / not blocking state
+//
+// Further testing hooks may be added to this class in future.
+class FakeWebpageController : public WebpageController {
+ public:
+  FakeWebpageController(const BlockedChangedCallback& callback);
+  ~FakeWebpageController() override;
+
+  NSView* GetView() override;
+  void PageURLChangedTo(const GURL& url) override;
+
+ private:
+  bool enabled_ = false;
+  base::scoped_nsobject<NSView> view_;
+  BlockedChangedCallback blocked_changed_callback_;
+};
+
+}  // namespace screentime
+
+#endif  // CHROME_BROWSER_UI_COCOA_SCREENTIME_FAKE_WEBPAGE_CONTROLLER_H_
diff --git a/chrome/browser/ui/cocoa/screentime/fake_webpage_controller.mm b/chrome/browser/ui/cocoa/screentime/fake_webpage_controller.mm
new file mode 100644
index 0000000..488cf1a
--- /dev/null
+++ b/chrome/browser/ui/cocoa/screentime/fake_webpage_controller.mm
@@ -0,0 +1,39 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/cocoa/screentime/fake_webpage_controller.h"
+
+#import <Cocoa/Cocoa.h>
+
+namespace {
+
+NSView* MakeView(bool enabled) {
+  NSView* view = [[NSView alloc] init];
+  view.wantsLayer = YES;
+  view.layer.backgroundColor = NSColor.blueColor.CGColor;
+  view.hidden = !enabled;
+  return view;
+}
+
+}  // namespace
+
+namespace screentime {
+
+FakeWebpageController::FakeWebpageController(
+    const BlockedChangedCallback& blocked_changed_callback)
+    : view_(MakeView(enabled_)),
+      blocked_changed_callback_(blocked_changed_callback) {}
+FakeWebpageController::~FakeWebpageController() = default;
+
+NSView* FakeWebpageController::GetView() {
+  return view_.get();
+}
+
+void FakeWebpageController::PageURLChangedTo(const GURL& url) {
+  enabled_ = !enabled_;
+  [view_ setHidden:!enabled_];
+  blocked_changed_callback_.Run(enabled_);
+}
+
+}  // namespace screentime
diff --git a/chrome/browser/ui/cocoa/screentime/tab_helper.h b/chrome/browser/ui/cocoa/screentime/tab_helper.h
new file mode 100644
index 0000000..9bed147
--- /dev/null
+++ b/chrome/browser/ui/cocoa/screentime/tab_helper.h
@@ -0,0 +1,50 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_COCOA_SCREENTIME_TAB_HELPER_H_
+#define CHROME_BROWSER_UI_COCOA_SCREENTIME_TAB_HELPER_H_
+
+#include <memory>
+
+#include "content/public/browser/web_contents_observer.h"
+#include "content/public/browser/web_contents_user_data.h"
+
+namespace content {
+class WebContents;
+}
+
+namespace screentime {
+
+class WebpageController;
+
+// A TabHelper connects a content::WebContents to a WebpageController,
+// passing state updates from the WebContents to the WebpageController and
+// from the WebpageController to the WebContents or other parts of the browser.
+class TabHelper : public content::WebContentsObserver,
+                  public content::WebContentsUserData<TabHelper> {
+ public:
+  static void UseFakeWebpageControllerForTesting();
+  static bool IsScreentimeEnabled();
+
+  TabHelper(content::WebContents* contents);
+  ~TabHelper() override;
+
+  // WebContentsObserver:
+  void DidFinishNavigation(content::NavigationHandle* handle) override;
+
+ private:
+  friend class content::WebContentsUserData<TabHelper>;
+
+  std::unique_ptr<WebpageController> MakeWebpageController();
+
+  void OnBlockedChanged(bool blocked);
+
+  WEB_CONTENTS_USER_DATA_KEY_DECL();
+
+  std::unique_ptr<WebpageController> page_controller_;
+};
+
+}  // namespace screentime
+
+#endif  // CHROME_BROWSER_UI_COCOA_SCREENTIME_TAB_HELPER_H_
diff --git a/chrome/browser/ui/cocoa/screentime/tab_helper.mm b/chrome/browser/ui/cocoa/screentime/tab_helper.mm
new file mode 100644
index 0000000..8876b44
--- /dev/null
+++ b/chrome/browser/ui/cocoa/screentime/tab_helper.mm
@@ -0,0 +1,77 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import <Cocoa/Cocoa.h>
+
+#include "base/command_line.h"
+#include "chrome/browser/ui/cocoa/screentime/fake_webpage_controller.h"
+#include "chrome/browser/ui/cocoa/screentime/tab_helper.h"
+#include "chrome/browser/ui/cocoa/screentime/webpage_controller.h"
+#include "chrome/browser/ui/cocoa/screentime/webpage_controller_impl.h"
+#include "content/public/browser/navigation_handle.h"
+#include "content/public/browser/web_contents.h"
+
+namespace screentime {
+
+namespace {
+bool g_use_fake_webpage_controller = false;
+}
+
+// static
+void TabHelper::UseFakeWebpageControllerForTesting() {
+  g_use_fake_webpage_controller = true;
+}
+
+// static
+bool TabHelper::IsScreentimeEnabled() {
+  static constexpr base::Feature kScreenTime{
+      "ScreenTime",
+      base::FEATURE_DISABLED_BY_DEFAULT,
+  };
+  return base::FeatureList::IsEnabled(kScreenTime);
+}
+
+TabHelper::TabHelper(content::WebContents* contents)
+    : WebContentsObserver(contents), page_controller_(MakeWebpageController()) {
+  NSView* contents_view = contents->GetNativeView().GetNativeNSView();
+  [contents_view addSubview:page_controller_->GetView()];
+}
+
+TabHelper::~TabHelper() = default;
+
+void TabHelper::DidFinishNavigation(content::NavigationHandle* handle) {
+  // TODO(ellyjones): Some defensive programming around chrome:// URLs would
+  // probably be a good idea here. It's not unimaginable that ScreenTime would
+  // misbehave and end up occluding those URLs, which would be very bad.
+  if (handle->IsInMainFrame() && handle->HasCommitted())
+    page_controller_->PageURLChangedTo(handle->GetURL());
+}
+
+std::unique_ptr<WebpageController> TabHelper::MakeWebpageController() {
+  const bool use_fake =
+      g_use_fake_webpage_controller ||
+      base::CommandLine::ForCurrentProcess()->HasSwitch("fake-screentime");
+
+  // The callback is owned by the WebpageController instance, which is in turn
+  // owned by this object, so it can't outlive us.
+  auto callback =
+      base::BindRepeating(&TabHelper::OnBlockedChanged, base::Unretained(this));
+  std::unique_ptr<WebpageController> controller;
+  if (use_fake)
+    controller = std::make_unique<FakeWebpageController>(callback);
+  else
+    controller = std::make_unique<WebpageControllerImpl>(callback);
+  return controller;
+}
+
+void TabHelper::OnBlockedChanged(bool blocked) {
+  // TODO: Pause/resume playing media, update occlusion state on the
+  // WebContents, and so on. Getting this behavior right will probably require
+  // some care.
+  NOTIMPLEMENTED();
+}
+
+WEB_CONTENTS_USER_DATA_KEY_IMPL(TabHelper)
+
+}  // namespace screentime
diff --git a/chrome/browser/ui/cocoa/screentime/webpage_controller.h b/chrome/browser/ui/cocoa/screentime/webpage_controller.h
new file mode 100644
index 0000000..ad9f2809f
--- /dev/null
+++ b/chrome/browser/ui/cocoa/screentime/webpage_controller.h
@@ -0,0 +1,37 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_COCOA_SCREENTIME_WEBPAGE_CONTROLLER_H_
+#define CHROME_BROWSER_UI_COCOA_SCREENTIME_WEBPAGE_CONTROLLER_H_
+
+#include "base/callback.h"
+#include "url/gurl.h"
+
+@class NSView;
+
+namespace screentime {
+
+// The interface for the per-page controller. This interface exists to allow for
+// abstracting away the concrete STWebpageController class, which is only
+// available on some platforms and ties into a systemwide API that makes unit
+// testing difficult. As little logic as possible should happen in
+// implementations of WebpageController.
+class WebpageController {
+ public:
+  using BlockedChangedCallback = base::RepeatingCallback<void(bool)>;
+
+  WebpageController() = default;
+  virtual ~WebpageController() = default;
+
+  virtual NSView* GetView() = 0;
+
+  // Called when the WebContents that this WebpageController is attached to
+  // changes its committed URL to |url|, to update ScreenTime's notion of the
+  // "page URL" (in Chrome parlance, the top-level frame URL).
+  virtual void PageURLChangedTo(const GURL& url) = 0;
+};
+
+}  // namespace screentime
+
+#endif  // CHROME_BROWSER_UI_COCOA_SCREENTIME_WEBPAGE_CONTROLLER_H_
diff --git a/chrome/browser/ui/cocoa/screentime/webpage_controller_impl.h b/chrome/browser/ui/cocoa/screentime/webpage_controller_impl.h
new file mode 100644
index 0000000..8f02dac
--- /dev/null
+++ b/chrome/browser/ui/cocoa/screentime/webpage_controller_impl.h
@@ -0,0 +1,34 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_COCOA_SCREENTIME_WEBPAGE_CONTROLLER_IMPL_H_
+#define CHROME_BROWSER_UI_COCOA_SCREENTIME_WEBPAGE_CONTROLLER_IMPL_H_
+
+#include "base/mac/scoped_nsobject.h"
+#include "chrome/browser/ui/cocoa/screentime/webpage_controller.h"
+
+@class STWebpageController;
+
+namespace screentime {
+
+// This class wraps the STWebpageController screentime class, to allow for tests
+// to use a fake controller.
+class WebpageControllerImpl : public WebpageController {
+ public:
+  WebpageControllerImpl(const BlockedChangedCallback& callback);
+  ~WebpageControllerImpl() override;
+
+  NSView* GetView() override;
+  void PageURLChangedTo(const GURL& url) override;
+
+  void OnBlockedChanged(bool blocked);
+
+ private:
+  base::scoped_nsobject<STWebpageController> platform_controller_;
+  BlockedChangedCallback blocked_changed_callback_;
+};
+
+}  // namespace screentime
+
+#endif  // CHROME_BROWSER_UI_COCOA_SCREENTIME_WEBPAGE_CONTROLLER_IMPL_H_
diff --git a/chrome/browser/ui/cocoa/screentime/webpage_controller_impl.mm b/chrome/browser/ui/cocoa/screentime/webpage_controller_impl.mm
new file mode 100644
index 0000000..0cbab2c
--- /dev/null
+++ b/chrome/browser/ui/cocoa/screentime/webpage_controller_impl.mm
@@ -0,0 +1,69 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/cocoa/screentime/webpage_controller_impl.h"
+
+#include "net/base/mac/url_conversions.h"
+
+#include <ScreenTime/ScreenTime.h>
+
+@interface BlockedObserver : NSObject
+@end
+
+NS_AVAILABLE_MAC(11.0)
+@implementation BlockedObserver {
+  screentime::WebpageControllerImpl* _controller;
+  STWebpageController* _nativeController;
+}
+
+- (instancetype)initWithController:
+                    (screentime::WebpageControllerImpl*)controller
+                  nativeController:(STWebpageController*)nativeController {
+  if (self = [super init]) {
+    _controller = controller;
+    _nativeController = nativeController;
+    [_nativeController addObserver:self
+                        forKeyPath:@"URLIsBlocked"
+                           options:0
+                           context:nullptr];
+  }
+  return self;
+}
+
+- (void)dealloc {
+  [_nativeController removeObserver:self forKeyPath:@"URLIsBlocked"];
+  [super dealloc];
+}
+
+- (void)observeValueForKeyPath:(NSString*)forKeyPath
+                      ofObject:(id)object
+                        change:(NSDictionary*)change
+                       context:(void*)context {
+  DCHECK([forKeyPath isEqualToString:@"URLIsBlocked"]);
+  _controller->OnBlockedChanged(_nativeController.URLIsBlocked);
+}
+
+@end
+
+namespace screentime {
+
+WebpageControllerImpl::WebpageControllerImpl(
+    const BlockedChangedCallback& blocked_changed_callback)
+    : platform_controller_([[STWebpageController alloc] init]),
+      blocked_changed_callback_(blocked_changed_callback) {}
+WebpageControllerImpl::~WebpageControllerImpl() = default;
+
+NSView* WebpageControllerImpl::GetView() {
+  return [platform_controller_ view];
+}
+
+void WebpageControllerImpl::PageURLChangedTo(const GURL& url) {
+  [platform_controller_ setURL:net::NSURLWithGURL(url)];
+}
+
+void WebpageControllerImpl::OnBlockedChanged(bool blocked) {
+  blocked_changed_callback_.Run(blocked);
+}
+
+}  // namespace screentime
diff --git a/chrome/browser/ui/serial/serial_chooser_controller.cc b/chrome/browser/ui/serial/serial_chooser_controller.cc
index 362fd032..d6d8c30b 100644
--- a/chrome/browser/ui/serial/serial_chooser_controller.cc
+++ b/chrome/browser/ui/serial/serial_chooser_controller.cc
@@ -12,6 +12,7 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/unguessable_token.h"
 #include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/serial/serial_blocklist.h"
 #include "chrome/browser/serial/serial_chooser_context_factory.h"
 #include "chrome/browser/serial/serial_chooser_histograms.h"
 #include "chrome/grit/generated_resources.h"
@@ -114,7 +115,7 @@
 
 void SerialChooserController::OnPortAdded(
     const device::mojom::SerialPortInfo& port) {
-  if (!FilterMatchesAny(port))
+  if (!DisplayDevice(port))
     return;
 
   ports_.push_back(port.Clone());
@@ -148,7 +149,7 @@
             });
 
   for (auto& port : ports) {
-    if (FilterMatchesAny(*port))
+    if (DisplayDevice(*port))
       ports_.push_back(std::move(port));
   }
 
@@ -156,8 +157,11 @@
     view()->OnOptionsInitialized();
 }
 
-bool SerialChooserController::FilterMatchesAny(
+bool SerialChooserController::DisplayDevice(
     const device::mojom::SerialPortInfo& port) const {
+  if (SerialBlocklist::Get().IsExcluded(port))
+    return false;
+
   if (filters_.empty())
     return true;
 
diff --git a/chrome/browser/ui/serial/serial_chooser_controller.h b/chrome/browser/ui/serial/serial_chooser_controller.h
index 21ec2bab..4d9a2df 100644
--- a/chrome/browser/ui/serial/serial_chooser_controller.h
+++ b/chrome/browser/ui/serial/serial_chooser_controller.h
@@ -52,7 +52,7 @@
 
  private:
   void OnGetDevices(std::vector<device::mojom::SerialPortInfoPtr> ports);
-  bool FilterMatchesAny(const device::mojom::SerialPortInfo& port) const;
+  bool DisplayDevice(const device::mojom::SerialPortInfo& port) const;
   void RunCallback(device::mojom::SerialPortInfoPtr port);
 
   std::vector<blink::mojom::SerialPortFilterPtr> filters_;
diff --git a/chrome/browser/ui/serial/serial_chooser_controller_unittest.cc b/chrome/browser/ui/serial/serial_chooser_controller_unittest.cc
index 065be5a..18ec27a 100644
--- a/chrome/browser/ui/serial/serial_chooser_controller_unittest.cc
+++ b/chrome/browser/ui/serial/serial_chooser_controller_unittest.cc
@@ -13,6 +13,7 @@
 #include "base/test/mock_callback.h"
 #include "build/build_config.h"
 #include "chrome/browser/chooser_controller/mock_chooser_controller_view.h"
+#include "chrome/browser/serial/serial_blocklist.h"
 #include "chrome/browser/serial/serial_chooser_context.h"
 #include "chrome/browser/serial/serial_chooser_context_factory.h"
 #include "chrome/browser/serial/serial_chooser_histograms.h"
@@ -39,6 +40,15 @@
         ->SetPortManagerForTesting(std::move(port_manager));
   }
 
+  void TearDown() override {
+    // Because SerialBlocklist is a singleton it must be cleared after tests run
+    // to prevent leakage between tests.
+    feature_list_.Reset();
+    SerialBlocklist::Get().ResetToDefaultValuesForTesting();
+
+    ChromeRenderViewHostTestHarness::TearDown();
+  }
+
   base::UnguessableToken AddPort(
       const std::string& display_name,
       const base::FilePath& path,
@@ -61,9 +71,21 @@
     return port_token;
   }
 
+  void SetDynamicBlocklist(base::StringPiece value) {
+    feature_list_.Reset();
+
+    std::map<std::string, std::string> parameters;
+    parameters[kWebSerialBlocklistAdditions.name] = std::string(value);
+    feature_list_.InitWithFeaturesAndParameters(
+        {{kWebSerialBlocklist, parameters}}, {});
+
+    SerialBlocklist::Get().ResetToDefaultValuesForTesting();
+  }
+
   device::FakeSerialPortManager& port_manager() { return port_manager_; }
 
  private:
+  base::test::ScopedFeatureList feature_list_;
   device::FakeSerialPortManager port_manager_;
 };
 
@@ -268,3 +290,60 @@
     run_loop.Run();
   }
 }
+
+TEST_F(SerialChooserControllerTest, Blocklist) {
+  base::HistogramTester histogram_tester;
+
+  // Create two ports from the same vendor with different product IDs.
+  base::UnguessableToken port_1 =
+      AddPort("Test Port 1", base::FilePath(FILE_PATH_LITERAL("/dev/ttyS0")),
+              0x1234, 0x0001);
+  base::UnguessableToken port_2 =
+      AddPort("Test Port 2", base::FilePath(FILE_PATH_LITERAL("/dev/ttyS1")),
+              0x1234, 0x0002);
+
+  // Add the second port to the blocklist.
+  SetDynamicBlocklist("usb:1234:0002");
+
+  std::vector<blink::mojom::SerialPortFilterPtr> filters;
+  auto controller = std::make_unique<SerialChooserController>(
+      main_rfh(), std::move(filters), base::DoNothing());
+
+  MockChooserControllerView view;
+  controller->set_view(&view);
+
+  {
+    base::RunLoop run_loop;
+    EXPECT_CALL(view, OnOptionsInitialized).WillOnce(Invoke([&] {
+      // Expect that only the first port is shown thanks to the filter.
+      EXPECT_EQ(1u, controller->NumOptions());
+      EXPECT_EQ(base::ASCIIToUTF16("Test Port 1 (ttyS0)"),
+                controller->GetOption(0));
+      run_loop.Quit();
+    }));
+    run_loop.Run();
+  }
+
+  // Removing the second port should be a no-op since it is filtered out.
+  EXPECT_CALL(view, OnOptionRemoved).Times(0);
+  port_manager().RemovePort(port_2);
+  base::RunLoop().RunUntilIdle();
+
+  // Adding it back should be a no-op as well.
+  EXPECT_CALL(view, OnOptionAdded).Times(0);
+  AddPort("Test Port 2", base::FilePath(FILE_PATH_LITERAL("/dev/ttyS1")),
+          0x1234, 0x0002);
+  base::RunLoop().RunUntilIdle();
+
+  // Removing the first port should trigger a change in the UI. This also acts
+  // as a synchronization point to make sure that the changes above were
+  // processed.
+  {
+    base::RunLoop run_loop;
+    EXPECT_CALL(view, OnOptionRemoved(0)).WillOnce(Invoke([&]() {
+      run_loop.Quit();
+    }));
+    port_manager().RemovePort(port_1);
+    run_loop.Run();
+  }
+}
diff --git a/chrome/browser/ui/tab_helpers.cc b/chrome/browser/ui/tab_helpers.cc
index 3291c31..6df56ea 100644
--- a/chrome/browser/ui/tab_helpers.cc
+++ b/chrome/browser/ui/tab_helpers.cc
@@ -156,6 +156,10 @@
 #include "chrome/browser/ui/hats/hats_helper.h"
 #endif
 
+#if defined(OS_MAC)
+#include "chrome/browser/ui/cocoa/screentime/tab_helper.h"
+#endif
+
 #if !defined(OS_ANDROID)
 #include "chrome/browser/media/feeds/media_feeds_contents_observer.h"
 #include "chrome/browser/media/feeds/media_feeds_service.h"
@@ -403,6 +407,11 @@
   web_modal::WebContentsModalDialogManager::CreateForWebContents(web_contents);
 #endif
 
+#if defined(OS_MAC)
+  if (screentime::TabHelper::IsScreentimeEnabled())
+    screentime::TabHelper::CreateForWebContents(web_contents);
+#endif
+
 #if BUILDFLAG(IS_CHROMEOS_ASH)
   app_list::CrOSActionRecorderTabTracker::CreateForWebContents(web_contents);
   chromeos::app_time::WebTimeNavigationObserver::MaybeCreateForWebContents(
diff --git a/chrome/browser/ui/tabs/tab_strip_model.cc b/chrome/browser/ui/tabs/tab_strip_model.cc
index ad3f9931..f90422b9 100644
--- a/chrome/browser/ui/tabs/tab_strip_model.cc
+++ b/chrome/browser/ui/tabs/tab_strip_model.cc
@@ -2018,25 +2018,34 @@
 
   group_model_->AddTabGroup(new_group, base::nullopt);
 
-  // Find a destination for the first tab that's not inside another group. We
-  // will stack the rest of the tabs up to its right.
+  // Find a destination for the first tab that's not pinned or inside another
+  // group. We will stack the rest of the tabs up to its right.
   int destination_index = -1;
   for (int i = indices[0]; i < count(); i++) {
     const int destination_candidate = i + 1;
-    const bool end_of_strip = !ContainsIndex(destination_candidate);
-    if (end_of_strip || !GetTabGroupForTab(destination_candidate).has_value() ||
-        GetTabGroupForTab(destination_candidate) !=
-            GetTabGroupForTab(indices[0])) {
+
+    // Grouping at the end of the tabstrip is always valid.
+    if (!ContainsIndex(destination_candidate)) {
+      destination_index = destination_candidate;
+      break;
+    }
+
+    // Grouping in the middle of pinned tabs is never valid.
+    if (IsTabPinned(destination_candidate))
+      continue;
+
+    // Otherwise, grouping is valid if the destination is not in the middle of a
+    // different group.
+    base::Optional<tab_groups::TabGroupId> destination_group =
+        GetTabGroupForTab(destination_candidate);
+    if (!destination_group.has_value() ||
+        destination_group != GetTabGroupForTab(indices[0])) {
       destination_index = destination_candidate;
       break;
     }
   }
 
-  // Unpin tabs when grouping -- the states should be mutually exclusive.
-  std::vector<int> new_indices = indices;
-  new_indices = SetTabsPinned(new_indices, false);
-
-  MoveTabsAndSetGroupImpl(new_indices, destination_index, new_group);
+  MoveTabsAndSetGroupImpl(indices, destination_index, new_group);
 }
 
 void TabStripModel::AddToExistingGroupImpl(
@@ -2053,9 +2062,6 @@
   if (!group_model_->ContainsTabGroup(group))
     return;
 
-  // Unpin tabs when grouping -- the states should be mutually exclusive.
-  std::vector<int> new_indices = SetTabsPinned(indices, false);
-
   const TabGroup* group_object = group_model_->GetTabGroup(group);
   int first_tab_in_group = group_object->GetFirstTab().value();
   int last_tab_in_group = group_object->GetLastTab().value();
@@ -2065,13 +2071,13 @@
   // that are inside the group.
   std::vector<int> tabs_left_of_group;
   std::vector<int> tabs_right_of_group;
-  for (int new_index : new_indices) {
-    if (new_index >= first_tab_in_group && new_index <= last_tab_in_group) {
-      GroupTab(new_index, group);
-    } else if (new_index < first_tab_in_group) {
-      tabs_left_of_group.push_back(new_index);
+  for (int index : indices) {
+    if (index >= first_tab_in_group && index <= last_tab_in_group) {
+      GroupTab(index, group);
+    } else if (index < first_tab_in_group) {
+      tabs_left_of_group.push_back(index);
     } else {
-      tabs_right_of_group.push_back(new_index);
+      tabs_right_of_group.push_back(index);
     }
   }
 
@@ -2111,12 +2117,22 @@
     int index,
     int new_index,
     base::Optional<tab_groups::TabGroupId> new_group) {
-  DCHECK(!IsTabPinned(index));
+  if (new_group.has_value()) {
+    // Unpin tabs when grouping -- the states should be mutually exclusive.
+    // Here we manually unpin the tab to avoid moving the tab twice, which can
+    // potentially cause race conditions.
+    if (IsTabPinned(index)) {
+      contents_data_[index]->set_pinned(false);
+      for (auto& observer : observers_) {
+        observer.TabPinnedStateChanged(
+            this, contents_data_[index]->web_contents(), index);
+      }
+    }
 
-  if (new_group.has_value())
     GroupTab(index, new_group.value());
-  else
+  } else {
     UngroupTab(index);
+  }
 
   if (index != new_index)
     MoveWebContentsAtImpl(index, new_index, false);
diff --git a/chrome/browser/ui/tabs/tab_style.h b/chrome/browser/ui/tabs/tab_style.h
index 37538b5..0ac1166 100644
--- a/chrome/browser/ui/tabs/tab_style.h
+++ b/chrome/browser/ui/tabs/tab_style.h
@@ -5,7 +5,10 @@
 #ifndef CHROME_BROWSER_UI_TABS_TAB_STYLE_H_
 #define CHROME_BROWSER_UI_TABS_TAB_STYLE_H_
 
+#include <tuple>
+
 #include "third_party/skia/include/core/SkColor.h"
+#include "ui/gfx/color_palette.h"
 #include "ui/gfx/font_list.h"
 #include "ui/gfx/geometry/insets.h"
 #include "ui/gfx/geometry/rect_f.h"
@@ -74,8 +77,17 @@
 
   // Colors for various parts of the tab derived by TabStyle.
   struct TabColors {
-    SkColor foreground_color;
-    SkColor background_color;
+    SkColor foreground_color = gfx::kPlaceholderColor;
+    SkColor background_color = gfx::kPlaceholderColor;
+
+    TabColors() = default;
+    TabColors(SkColor foreground_color, SkColor background_color)
+        : foreground_color(foreground_color),
+          background_color(background_color) {}
+    bool operator==(const TabColors& other) const {
+      return std::tie(foreground_color, background_color) ==
+             std::tie(other.foreground_color, other.background_color);
+    }
   };
 
   TabStyle(const TabStyle&) = delete;
diff --git a/chrome/browser/ui/toolbar/assign_to_desks_menu_model.cc b/chrome/browser/ui/toolbar/assign_to_desks_menu_model.cc
index 5d6d1d2f..b319331 100644
--- a/chrome/browser/ui/toolbar/assign_to_desks_menu_model.cc
+++ b/chrome/browser/ui/toolbar/assign_to_desks_menu_model.cc
@@ -6,23 +6,55 @@
 
 #include "ash/public/cpp/desks_helper.h"
 #include "chrome/app/chrome_command_ids.h"
+#include "chrome/grit/generated_resources.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/views/widget/widget.h"
 
 AssignToDesksMenuModel::AssignToDesksMenuModel(
-    ui::SimpleMenuModel::Delegate* delegate)
-    : SimpleMenuModel(delegate), desks_helper_(ash::DesksHelper::Get()) {
+    ui::SimpleMenuModel::Delegate* delegate,
+    views::Widget* browser_widget)
+    : SimpleMenuModel(delegate),
+      desks_helper_(ash::DesksHelper::Get()),
+      browser_widget_(browser_widget) {
   constexpr int kMaxNumberOfDesks = IDC_SEND_TO_DESK_8 - IDC_SEND_TO_DESK_1 + 1;
+  AddCheckItem(IDC_TOGGLE_ASSIGN_TO_ALL_DESKS,
+               l10n_util::GetStringUTF16(IDS_ASSIGN_TO_ALL_DESKS));
+  assign_to_all_desks_item_index_ = GetItemCount() - 1;
+  AddSeparator(ui::NORMAL_SEPARATOR);
+
+  // Store the number of items that occur before the group of send to desk items
+  // so we can retrieve the send to desk items' indices properly.
+  send_to_desk_group_offset_ = GetItemCount();
   for (int i = 0; i < kMaxNumberOfDesks; ++i)
     AddCheckItem(IDC_SEND_TO_DESK_1 + i, base::string16());
 }
 
 bool AssignToDesksMenuModel::IsVisibleAt(int index) const {
-  return index < desks_helper_->GetNumberOfDesks();
+  if (index == assign_to_all_desks_item_index_)
+    return true;
+  return OffsetIndexForSendToDeskGroup(index) <
+         desks_helper_->GetNumberOfDesks();
 }
 
 base::string16 AssignToDesksMenuModel::GetLabelAt(int index) const {
-  return desks_helper_->GetDeskName(index);
+  if (index == assign_to_all_desks_item_index_)
+    return l10n_util::GetStringUTF16(IDS_ASSIGN_TO_ALL_DESKS);
+  return desks_helper_->GetDeskName(OffsetIndexForSendToDeskGroup(index));
 }
 
 bool AssignToDesksMenuModel::IsItemCheckedAt(int index) const {
-  return index == desks_helper_->GetActiveDeskIndex();
+  const bool is_assigned_to_all_desks =
+      browser_widget_ && browser_widget_->IsVisibleOnAllWorkspaces();
+
+  if (index == assign_to_all_desks_item_index_)
+    return is_assigned_to_all_desks;
+
+  return !is_assigned_to_all_desks && OffsetIndexForSendToDeskGroup(index) ==
+                                          desks_helper_->GetActiveDeskIndex();
+}
+
+int AssignToDesksMenuModel::OffsetIndexForSendToDeskGroup(int index) const {
+  const int send_to_desk_group_index = index - send_to_desk_group_offset_;
+  DCHECK_GE(send_to_desk_group_index, 0);
+  return send_to_desk_group_index;
 }
diff --git a/chrome/browser/ui/toolbar/assign_to_desks_menu_model.h b/chrome/browser/ui/toolbar/assign_to_desks_menu_model.h
index d6ae9ca..7598878 100644
--- a/chrome/browser/ui/toolbar/assign_to_desks_menu_model.h
+++ b/chrome/browser/ui/toolbar/assign_to_desks_menu_model.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2020 The Chromium Authors. All rights reserved.
+// Copyright 2020 The Chromium Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -11,10 +11,15 @@
 class DesksHelper;
 }
 
+namespace views {
+class Widget;
+}
+
 // A menu model that builds the contents of the assign to desks menu.
 class AssignToDesksMenuModel : public ui::SimpleMenuModel {
  public:
-  explicit AssignToDesksMenuModel(ui::SimpleMenuModel::Delegate* delegate);
+  AssignToDesksMenuModel(ui::SimpleMenuModel::Delegate* delegate,
+                         views::Widget* browser_widget);
   ~AssignToDesksMenuModel() override = default;
 
   // SimpleMenuModel:
@@ -23,7 +28,20 @@
   bool IsItemCheckedAt(int index) const override;
 
  private:
+  // Returns |index| - |send_to_desk_group_offset_|. This value is the relative
+  // index within the group of send to desk items. This value must be greater
+  // than or equal to 0.
+  int OffsetIndexForSendToDeskGroup(int index) const;
+
   const ash::DesksHelper* const desks_helper_;
+  const views::Widget* const browser_widget_;
+
+  // This is the index of the assign to all desks item in the menu model.
+  int assign_to_all_desks_item_index_;
+
+  // This is the number of items in the menu model that occurs before the group
+  // of send to desk items.
+  int send_to_desk_group_offset_;
 };
 
 #endif  // CHROME_BROWSER_UI_TOOLBAR_ASSIGN_TO_DESKS_MENU_MODEL_H_
diff --git a/chrome/browser/ui/views/frame/browser_frame.cc b/chrome/browser/ui/views/frame/browser_frame.cc
index e31d4e6..5c03f25 100644
--- a/chrome/browser/ui/views/frame/browser_frame.cc
+++ b/chrome/browser/ui/views/frame/browser_frame.cc
@@ -85,6 +85,9 @@
 #if BUILDFLAG(IS_CHROMEOS_ASH)
   params.init_properties_container.SetProperty(
       full_restore::kWindowIdKey, browser_view_->browser()->session_id().id());
+  params.init_properties_container.SetProperty(
+      full_restore::kRestoreWindowIdKey,
+      browser_view_->browser()->create_params().restore_id);
 #endif
   if (browser_view_->browser()->is_type_normal() ||
       browser_view_->browser()->is_type_devtools() ||
diff --git a/chrome/browser/ui/views/frame/system_menu_model_builder.cc b/chrome/browser/ui/views/frame/system_menu_model_builder.cc
index 5dc3bcd..c837c74 100644
--- a/chrome/browser/ui/views/frame/system_menu_model_builder.cc
+++ b/chrome/browser/ui/views/frame/system_menu_model_builder.cc
@@ -33,6 +33,7 @@
 #include "components/user_manager/user_info.h"
 #include "components/user_manager/user_manager.h"
 #include "ui/base/l10n/l10n_util.h"
+#include "ui/views/widget/widget.h"
 #endif
 
 SystemMenuModelBuilder::SystemMenuModelBuilder(
@@ -133,6 +134,9 @@
   model->AddSeparator(ui::NORMAL_SEPARATOR);
   model->AddItemWithStringId(IDC_CLOSE_WINDOW, IDS_CLOSE);
 #endif
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  AppendAssignToDesksMenu(model);
+#endif
   AppendTeleportMenu(model);
 }
 
@@ -150,8 +154,10 @@
     ui::SimpleMenuModel* model) {
   if (ash::features::IsBentoEnabled()) {
     model->AddSeparator(ui::NORMAL_SEPARATOR);
-    assign_to_desks_model_ =
-        std::make_unique<AssignToDesksMenuModel>(&menu_delegate_);
+    assign_to_desks_model_ = std::make_unique<AssignToDesksMenuModel>(
+        &menu_delegate_,
+        views::Widget::GetWidgetForNativeWindow(
+            menu_delegate_.browser()->window()->GetNativeWindow()));
     model->AddSubMenuWithStringId(IDC_ASSIGN_TO_DESKS_MENU,
                                   IDS_ASSIGN_TO_DESKS_MENU,
                                   assign_to_desks_model_.get());
diff --git a/chrome/browser/ui/views/global_media_controls/media_dialog_view.cc b/chrome/browser/ui/views/global_media_controls/media_dialog_view.cc
index 17a61cf7..14b129d 100644
--- a/chrome/browser/ui/views/global_media_controls/media_dialog_view.cc
+++ b/chrome/browser/ui/views/global_media_controls/media_dialog_view.cc
@@ -137,13 +137,6 @@
   views::BubbleFrameView* frame = GetBubbleFrameView();
   if (frame)
     frame->SetCornerRadius(corner_radius);
-  if (!base::FeatureList::IsEnabled(
-          views::features::kEnableMDRoundedCornersOnDialogs)) {
-    SetPaintToLayer();
-    layer()->SetRoundedCornerRadius(gfx::RoundedCornersF(corner_radius));
-    if (base::FeatureList::IsEnabled(media::kLiveCaption))
-      layer()->SetFillsBoundsOpaquely(false);
-  }
   service_->SetDialogDelegate(this);
   speech::SodaInstaller::GetInstance()->AddObserver(this);
 }
@@ -217,6 +210,9 @@
       profile_(profile->GetOriginalProfile()),
       active_sessions_view_(
           AddChildView(std::make_unique<MediaNotificationListView>())) {
+  // Enable layer based clipping to ensure children using layers are clipped
+  // appropriately.
+  SetPaintClientToLayer(true);
   SetButtons(ui::DIALOG_BUTTON_NONE);
   DCHECK(service_);
 }
@@ -245,13 +241,6 @@
               gfx::Insets(kLiveCaptionHorizontalMarginDip,
                           kLiveCaptionVerticalMarginDip),
               kLiveCaptionBetweenChildSpacing));
-  if (!base::FeatureList::IsEnabled(
-          views::features::kEnableMDRoundedCornersOnDialogs)) {
-    SkColor native_theme_bg_color = GetNativeTheme()->GetSystemColor(
-        ui::NativeTheme::kColorId_BubbleBackground);
-    live_caption_container->SetBackground(
-        views::CreateSolidBackground(native_theme_bg_color));
-  }
 
   auto live_caption_image = std::make_unique<views::ImageView>();
   live_caption_image->SetImage(gfx::CreateVectorIcon(
diff --git a/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc b/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc
index 4532bdc8..824acb2c 100644
--- a/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc
+++ b/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc
@@ -776,7 +776,7 @@
                       is_active);
 
   // Try to show tab groups IPH if needed.
-  if (tabstrip_->tab_count() >= 6) {
+  if (tabstrip_->GetTabCount() >= 6) {
     feature_engagement_tracker_->NotifyEvent(
         feature_engagement::events::kSixthTabOpened);
 
diff --git a/chrome/browser/ui/views/tabs/new_tab_button.cc b/chrome/browser/ui/views/tabs/new_tab_button.cc
index b02ee99e1..875798f5 100644
--- a/chrome/browser/ui/views/tabs/new_tab_button.cc
+++ b/chrome/browser/ui/views/tabs/new_tab_button.cc
@@ -28,6 +28,7 @@
 #include "ui/views/animation/ink_drop_impl.h"
 #include "ui/views/animation/ink_drop_mask.h"
 #include "ui/views/controls/highlight_path_generator.h"
+#include "ui/views/metadata/metadata_impl_macros.h"
 #include "ui/views/widget/widget.h"
 
 #if defined(OS_WIN)
@@ -36,9 +37,6 @@
 #endif
 
 // static
-constexpr char NewTabButton::kClassName[];
-
-// static
 const gfx::Size NewTabButton::kButtonSize{28, 28};
 
 class NewTabButton::HighlightPathGenerator
@@ -91,10 +89,6 @@
   GetInkDrop()->AnimateToState(state);
 }
 
-const char* NewTabButton::GetClassName() const {
-  return kClassName;
-}
-
 void NewTabButton::AddLayerBeneathView(ui::Layer* new_layer) {
   ink_drop_container_->AddLayerBeneathView(new_layer);
 }
@@ -273,3 +267,6 @@
   SetInkDropBaseColor(
       color_utils::GetColorWithMaxContrast(GetButtonFillColor()));
 }
+
+BEGIN_METADATA(NewTabButton, views::ImageButton)
+END_METADATA
diff --git a/chrome/browser/ui/views/tabs/new_tab_button.h b/chrome/browser/ui/views/tabs/new_tab_button.h
index da8fdf3..036774a 100644
--- a/chrome/browser/ui/views/tabs/new_tab_button.h
+++ b/chrome/browser/ui/views/tabs/new_tab_button.h
@@ -10,6 +10,7 @@
 #include "chrome/browser/ui/views/frame/browser_view.h"
 #include "chrome/browser/ui/views/tabs/tab_strip.h"
 #include "ui/views/controls/button/image_button.h"
+#include "ui/views/metadata/metadata_header_macros.h"
 #include "ui/views/view.h"
 
 namespace views {
@@ -26,7 +27,7 @@
 class NewTabButton : public views::ImageButton,
                      public views::MaskedTargeterDelegate {
  public:
-  static constexpr char kClassName[] = "NewTabButton";
+  METADATA_HEADER(NewTabButton);
 
   static const gfx::Size kButtonSize;
 
@@ -43,7 +44,6 @@
   void AnimateInkDropToStateForTesting(views::InkDropState state);
 
   // views::ImageButton:
-  const char* GetClassName() const override;
   void AddLayerBeneathView(ui::Layer* new_layer) override;
   void RemoveLayerBeneathView(ui::Layer* old_layer) override;
 
diff --git a/chrome/browser/ui/views/tabs/tab.cc b/chrome/browser/ui/views/tabs/tab.cc
index 3e13de28..815d0de 100644
--- a/chrome/browser/ui/views/tabs/tab.cc
+++ b/chrome/browser/ui/views/tabs/tab.cc
@@ -1021,8 +1021,7 @@
 
   title_->SetEnabledColor(colors.foreground_color);
 
-  close_button_->SetIconColors(colors.foreground_color,
-                               colors.background_color);
+  close_button_->SetColors(colors);
 
   if (foreground_color_ != colors.foreground_color) {
     foreground_color_ = colors.foreground_color;
diff --git a/chrome/browser/ui/views/tabs/tab_close_button.cc b/chrome/browser/ui/views/tabs/tab_close_button.cc
index e65e991..7dc2270 100644
--- a/chrome/browser/ui/views/tabs/tab_close_button.cc
+++ b/chrome/browser/ui/views/tabs/tab_close_button.cc
@@ -26,6 +26,7 @@
 #include "ui/views/animation/ink_drop.h"
 #include "ui/views/controls/highlight_path_generator.h"
 #include "ui/views/layout/layout_provider.h"
+#include "ui/views/metadata/metadata_impl_macros.h"
 #include "ui/views/rect_based_targeting_utils.h"
 #include "ui/views/view_class_properties.h"
 
@@ -88,20 +89,23 @@
                                                   : kGlyphSize;
 }
 
-void TabCloseButton::SetIconColors(SkColor foreground_color,
-                                   SkColor background_color) {
-  icon_color_ = foreground_color;
-  SetInkDropBaseColor(color_utils::GetColorWithMaxContrast(background_color));
+TabStyle::TabColors TabCloseButton::GetColors() const {
+  return colors_;
+}
+
+void TabCloseButton::SetColors(TabStyle::TabColors colors) {
+  if (colors == colors_)
+    return;
+  colors_ = std::move(colors);
+  SetInkDropBaseColor(
+      color_utils::GetColorWithMaxContrast(colors_.background_color));
+  OnPropertyChanged(&colors_, views::kPropertyEffectsPaint);
 }
 
 void TabCloseButton::SetButtonPadding(const gfx::Insets& padding) {
   *GetProperty(views::kInternalPaddingKey) = padding;
 }
 
-const char* TabCloseButton::GetClassName() const {
-  return "TabCloseButton";
-}
-
 views::View* TabCloseButton::GetTooltipHandlerForPoint(
     const gfx::Point& point) {
   // Tab close button has no children, so tooltip handler should be the same
@@ -157,7 +161,7 @@
   flags.setAntiAlias(true);
   flags.setStrokeWidth(kStrokeWidth);
   flags.setStrokeCap(cc::PaintFlags::kRound_Cap);
-  flags.setColor(icon_color_);
+  flags.setColor(colors_.foreground_color);
   canvas->DrawLine(glyph_bounds.origin(), glyph_bounds.bottom_right(), flags);
   canvas->DrawLine(glyph_bounds.bottom_left(), glyph_bounds.top_right(), flags);
 }
@@ -194,3 +198,7 @@
   mask->addRect(gfx::RectToSkRect(GetMirroredRect(GetContentsBounds())));
   return true;
 }
+
+BEGIN_METADATA(TabCloseButton, views::ImageButton)
+ADD_PROPERTY_METADATA(TabStyle::TabColors, Colors)
+END_METADATA
diff --git a/chrome/browser/ui/views/tabs/tab_close_button.h b/chrome/browser/ui/views/tabs/tab_close_button.h
index 6f59db7..dce4685f 100644
--- a/chrome/browser/ui/views/tabs/tab_close_button.h
+++ b/chrome/browser/ui/views/tabs/tab_close_button.h
@@ -6,9 +6,10 @@
 #define CHROME_BROWSER_UI_VIEWS_TABS_TAB_CLOSE_BUTTON_H_
 
 #include "base/callback_forward.h"
-#include "ui/gfx/color_palette.h"
+#include "chrome/browser/ui/views/tabs/tab_style_views.h"
 #include "ui/views/controls/button/image_button.h"
 #include "ui/views/masked_targeter_delegate.h"
+#include "ui/views/metadata/metadata_header_macros.h"
 
 // This is a Button subclass that shows the tab closed icon.
 //
@@ -17,6 +18,8 @@
 class TabCloseButton : public views::ImageButton,
                        public views::MaskedTargeterDelegate {
  public:
+  METADATA_HEADER(TabCloseButton);
+
   using MouseEventCallback =
       base::RepeatingCallback<void(views::View*, const ui::MouseEvent&)>;
 
@@ -33,11 +36,12 @@
   // Returns the width/height of the tab close button, sans insets/padding.
   static int GetGlyphSize();
 
+  TabStyle::TabColors GetColors() const;
   // This function must be called before the tab is painted so it knows what
   // colors to use. It must also be called when the background color of the tab
   // changes (this class does not track tab activation state), and when the
   // theme changes.
-  void SetIconColors(SkColor foreground_color, SkColor background_color);
+  void SetColors(TabStyle::TabColors colors);
 
   // Sets the desired padding around the icon. Only the icon is a target for
   // mouse clicks, but the entire button is a target for touch events, since the
@@ -47,7 +51,6 @@
   void SetButtonPadding(const gfx::Insets& padding);
 
   // views::ImageButton:
-  const char* GetClassName() const override;
   View* GetTooltipHandlerForPoint(const gfx::Point& point) override;
   bool OnMousePressed(const ui::MouseEvent& event) override;
   void OnMouseReleased(const ui::MouseEvent& event) override;
@@ -67,7 +70,7 @@
 
   MouseEventCallback mouse_event_callback_;
 
-  SkColor icon_color_ = gfx::kPlaceholderColor;
+  TabStyle::TabColors colors_;
 };
 
 #endif  // CHROME_BROWSER_UI_VIEWS_TABS_TAB_CLOSE_BUTTON_H_
diff --git a/chrome/browser/ui/views/tabs/tab_drag_controller_interactive_uitest.cc b/chrome/browser/ui/views/tabs/tab_drag_controller_interactive_uitest.cc
index 0ff04054..7c623cc 100644
--- a/chrome/browser/ui/views/tabs/tab_drag_controller_interactive_uitest.cc
+++ b/chrome/browser/ui/views/tabs/tab_drag_controller_interactive_uitest.cc
@@ -3914,8 +3914,8 @@
       displays.first.id(),
       screen->GetDisplayNearestWindow(browser()->window()->GetNativeWindow())
           .id());
-  EXPECT_EQ(2, tab_strip->tab_count());
-  EXPECT_EQ(1, tab_strip2->tab_count());
+  EXPECT_EQ(2, tab_strip->GetTabCount());
+  EXPECT_EQ(1, tab_strip2->GetTabCount());
 
   // Move to the first tab and drag it enough so that it detaches, but not
   // enough that it attaches to browser2.
@@ -3932,8 +3932,8 @@
   ASSERT_TRUE(ReleaseInput());
 
   // tab should have moved
-  EXPECT_EQ(1, tab_strip->tab_count());
-  EXPECT_EQ(2, tab_strip2->tab_count());
+  EXPECT_EQ(1, tab_strip->GetTabCount());
+  EXPECT_EQ(2, tab_strip2->GetTabCount());
 
   ASSERT_FALSE(tab_strip2->GetDragContext()->IsDragSessionActive());
   ASSERT_FALSE(tab_strip->GetDragContext()->IsDragSessionActive());
diff --git a/chrome/browser/ui/views/tabs/tab_strip.cc b/chrome/browser/ui/views/tabs/tab_strip.cc
index 64ec699..6358ef8 100644
--- a/chrome/browser/ui/views/tabs/tab_strip.cc
+++ b/chrome/browser/ui/views/tabs/tab_strip.cc
@@ -89,6 +89,7 @@
 #include "ui/views/accessibility/view_accessibility.h"
 #include "ui/views/controls/image_view.h"
 #include "ui/views/masked_targeter_delegate.h"
+#include "ui/views/metadata/metadata_impl_macros.h"
 #include "ui/views/mouse_watcher_view_host.h"
 #include "ui/views/rect_based_targeting_utils.h"
 #include "ui/views/view_model_utils.h"
@@ -487,7 +488,7 @@
     return tab_strip_->GetModelIndexOf(view);
   }
 
-  int GetTabCount() const override { return tab_strip_->tab_count(); }
+  int GetTabCount() const override { return tab_strip_->GetTabCount(); }
 
   bool IsTabPinned(const Tab* tab) const override {
     return tab_strip_->IsTabPinned(tab);
@@ -1160,17 +1161,17 @@
 }
 
 void TabStrip::FrameColorsChanged() {
-  for (int i = 0; i < tab_count(); ++i)
+  for (int i = 0; i < GetTabCount(); ++i)
     tab_at(i)->FrameColorsChanged();
   UpdateContrastRatioValues();
   SchedulePaint();
 }
 
 void TabStrip::SetBackgroundOffset(int background_offset) {
-  if (background_offset != background_offset_) {
-    background_offset_ = background_offset;
-    SchedulePaint();
-  }
+  if (background_offset == background_offset_)
+    return;
+  background_offset_ = background_offset;
+  OnPropertyChanged(&background_offset_, views::kPropertyEffectsPaint);
 }
 
 bool TabStrip::IsRectInWindowCaption(const gfx::Rect& rect) {
@@ -1231,7 +1232,7 @@
 }
 
 void TabStrip::UpdateLoadingAnimations(const base::TimeDelta& elapsed_time) {
-  for (int i = 0; i < tab_count(); i++)
+  for (int i = 0; i < GetTabCount(); i++)
     tab_at(i)->StepLoadingAnimation(elapsed_time);
 }
 
@@ -1250,7 +1251,7 @@
     AnimateToIdealBounds();
   }
 
-  for (int i = 0; i < tab_count(); ++i)
+  for (int i = 0; i < GetTabCount(); ++i)
     tab_at(i)->Layout();
 }
 
@@ -1280,7 +1281,7 @@
 
   // Don't animate the first tab, it looks weird, and don't animate anything
   // if the containing window isn't visible yet.
-  if (tab_count() > 1 && GetWidget() && GetWidget()->IsVisible()) {
+  if (GetTabCount() > 1 && GetWidget() && GetWidget()->IsVisible()) {
     StartInsertTabAnimation(model_index,
                             pinned ? TabPinned::kPinned : TabPinned::kUnpinned);
   } else {
@@ -1310,9 +1311,9 @@
   Profile* profile = controller()->GetProfile();
   if (profile) {
     if (profile->IsGuestSession() || profile->IsEphemeralGuestProfile())
-      base::UmaHistogramCounts100("Tab.Count.Guest", tab_count());
+      base::UmaHistogramCounts100("Tab.Count.Guest", GetTabCount());
     else if (profile->IsIncognitoProfile())
-      base::UmaHistogramCounts100("Tab.Count.Incognito", tab_count());
+      base::UmaHistogramCounts100("Tab.Count.Incognito", GetTabCount());
   }
 }
 
@@ -1727,6 +1728,10 @@
   return tabs_.GetIndexOfView(view);
 }
 
+int TabStrip::GetTabCount() const {
+  return tabs_.view_size();
+}
+
 int TabStrip::GetModelCount() const {
   return controller_->GetCount();
 }
@@ -1766,7 +1771,7 @@
 }
 
 views::View* TabStrip::GetTabViewForPromoAnchor(int index_hint) {
-  return tab_at(base::ClampToRange(index_hint, 0, tab_count() - 1));
+  return tab_at(base::ClampToRange(index_hint, 0, GetTabCount() - 1));
 }
 
 views::View* TabStrip::GetDefaultFocusableChild() {
@@ -1905,11 +1910,11 @@
   int target_index;
   if (controller_->IsTabPinned(start_index)) {
     int temp_index = start_index + 1;
-    while (temp_index < tab_count() && controller_->IsTabPinned(temp_index))
+    while (temp_index < GetTabCount() && controller_->IsTabPinned(temp_index))
       ++temp_index;
     target_index = temp_index - 1;
   } else {
-    target_index = tab_count() - 1;
+    target_index = GetTabCount() - 1;
   }
 
   if (!IsValidModelIndex(target_index))
@@ -2390,11 +2395,6 @@
       base::TimeDelta::FromMicroseconds(10000), 50);
 }
 
-const char* TabStrip::GetClassName() const {
-  static const char kViewClassName[] = "TabStrip";
-  return kViewClassName;
-}
-
 gfx::Size TabStrip::GetMinimumSize() const {
   // If tabs can be stacked, our minimum width is the smallest width of the
   // stacked tabstrip.
@@ -2464,7 +2464,7 @@
   // coordinates since we calculate the drop index based on the
   // original (and therefore non-mirrored) positions of the tabs.
   const int x = GetMirroredXInView(event.x());
-  for (int i = 0; i < tab_count(); ++i) {
+  for (int i = 0; i < GetTabCount(); ++i) {
     Tab* tab = tab_at(i);
     const int tab_max_x = tab->x() + tab->width();
 
@@ -2480,7 +2480,7 @@
   }
 
   // The drop isn't over a tab, add it to the end.
-  return {tab_count(), true};
+  return {GetTabCount(), true};
 }
 
 views::View* TabStrip::GetViewForDrop() {
@@ -2572,7 +2572,7 @@
   if (model_index > 0) {
     // If we have a tab to our left, start at its right edge.
     bounds.set_x(tab_at(model_index - 1)->bounds().right() - tab_overlap);
-  } else if (model_index + 1 < tab_count()) {
+  } else if (model_index + 1 < GetTabCount()) {
     // Otherwise, if we have a tab to our right, start at its left edge.
     bounds.set_x(tab_at(model_index + 1)->bounds().x());
   } else {
@@ -2686,7 +2686,7 @@
 void TabStrip::AnimateToIdealBounds() {
   UpdateHoverCard(nullptr);
 
-  for (int i = 0; i < tab_count(); ++i) {
+  for (int i = 0; i < GetTabCount(); ++i) {
     // If the tab is being dragged manually, skip it.
     Tab* tab = tab_at(i);
     if (tab->dragging() && !bounds_animator_.IsAnimating(tab))
@@ -2736,7 +2736,7 @@
 }
 
 void TabStrip::SnapToIdealBounds() {
-  for (int i = 0; i < tab_count(); ++i)
+  for (int i = 0; i < GetTabCount(); ++i)
     tab_at(i)->SetBoundsRect(ideal_bounds(i));
 
   for (const auto& header_pair : group_views_) {
@@ -2810,7 +2810,7 @@
 }
 
 void TabStrip::UpdateAccessibleTabIndices() {
-  const int num_tabs = tab_count();
+  const int num_tabs = GetTabCount();
   for (int i = 0; i < num_tabs; ++i)
     tab_at(i)->GetViewAccessibility().OverridePosInSet(i + 1, num_tabs);
 }
@@ -2824,7 +2824,7 @@
 }
 
 const Tab* TabStrip::GetLastVisibleTab() const {
-  for (int i = tab_count() - 1; i >= 0; --i) {
+  for (int i = GetTabCount() - 1; i >= 0; --i) {
     const Tab* tab = tab_at(i);
 
     // The tab is marked not visible in a collapsed group, but is "visible" in
@@ -2851,14 +2851,14 @@
   // If to_model_index is beyond the end of the tab strip, then the tab is newly
   // added to the end of the tab strip. In that case we can just return one
   // beyond the view index of the last existing tab.
-  if (to_model_index >= tab_count())
-    return (tab_count() ? GetIndexOf(tab_at(tab_count() - 1)) + 1 : 0);
+  if (to_model_index >= GetTabCount())
+    return (GetTabCount() ? GetIndexOf(tab_at(GetTabCount() - 1)) + 1 : 0);
 
   // If there is no from_model_index, then the tab is newly added in the middle
   // of the tab strip. In that case we treat it as coming from the end of the
   // tab strip, since new views are ordered at the end by default.
   if (!from_model_index.has_value())
-    from_model_index = tab_count();
+    from_model_index = GetTabCount();
 
   DCHECK_NE(to_model_index, from_model_index.value());
 
@@ -3207,7 +3207,7 @@
   // We've been called back after the TabStrip has been emptied out (probably
   // just prior to the window being destroyed). We need to do nothing here or
   // else GetTabAt below will crash.
-  if (tab_count() == 0)
+  if (GetTabCount() == 0)
     return;
 
   // It is critically important that this is unhooked here, otherwise we will
@@ -3216,7 +3216,7 @@
 
   ExitTabClosingMode();
   int pinned_tab_count = GetPinnedTabCount();
-  if (pinned_tab_count == tab_count()) {
+  if (pinned_tab_count == GetTabCount()) {
     // Only pinned tabs, we know the tab widths won't have changed (all
     // pinned tabs have the same width), so there is nothing to do.
     return;
@@ -3265,11 +3265,11 @@
                                   bool* is_beneath) {
   DCHECK_NE(drop_index, -1);
 
-  Tab* tab = tab_at(std::min(drop_index, tab_count() - 1));
+  Tab* tab = tab_at(std::min(drop_index, GetTabCount() - 1));
   int center_x = tab->x();
   const int width = tab->width();
   const int overlap = TabStyle::GetTabOverlap();
-  if (drop_index < tab_count())
+  if (drop_index < GetTabCount())
     center_x += drop_before ? (overlap / 2) : (width / 2);
   else
     center_x += width - (overlap / 2);
@@ -3400,13 +3400,13 @@
 void TabStrip::PrepareForAnimation() {
   if (!drag_context_->IsDragSessionActive() &&
       !TabDragController::IsAttachedTo(GetDragContext())) {
-    for (int i = 0; i < tab_count(); ++i)
+    for (int i = 0; i < GetTabCount(); ++i)
       tab_at(i)->set_dragging(false);
   }
 }
 
 void TabStrip::UpdateIdealBounds() {
-  if (tab_count() == 0)
+  if (GetTabCount() == 0)
     return;  // Should only happen during creation/destruction, ignore.
 
   // Update |last_available_width_| in case there is a different amount of
@@ -3470,10 +3470,10 @@
 Tab* TabStrip::FindTabForEventFrom(const gfx::Point& point,
                                    int start,
                                    int delta) {
-  // |start| equals tab_count() when there are only pinned tabs.
-  if (start == tab_count())
+  // |start| equals GetTabCount() when there are only pinned tabs.
+  if (start == GetTabCount())
     start += delta;
-  for (int i = start; i >= 0 && i < tab_count(); i += delta) {
+  for (int i = start; i >= 0 && i < GetTabCount(); i += delta) {
     if (IsPointInTab(tab_at(i), point))
       return tab_at(i);
   }
@@ -3560,7 +3560,7 @@
     return false;
 
   const int pinned_tab_count = GetPinnedTabCount();
-  const int normal_count = tab_count() - pinned_tab_count;
+  const int normal_count = GetTabCount() - pinned_tab_count;
   if (normal_count <= 1)
     return false;
 
@@ -3779,3 +3779,18 @@
                 IDS_TAB_AX_ANNOUNCE_TAB_REMOVED_FROM_NAMED_GROUP, group_title,
                 contents_string));
 }
+
+BEGIN_METADATA(TabStrip, views::View)
+ADD_PROPERTY_METADATA(int, BackgroundOffset)
+ADD_READONLY_PROPERTY_METADATA(int, TabCount)
+ADD_READONLY_PROPERTY_METADATA(int, ModelCount)
+ADD_READONLY_PROPERTY_METADATA(int, PinnedTabCount)
+ADD_READONLY_PROPERTY_METADATA(base::Optional<int>, FocusedTabIndex)
+ADD_READONLY_PROPERTY_METADATA(int, StrokeThickness)
+ADD_READONLY_PROPERTY_METADATA(SkColor, ToolbarTopSeparatorColor)
+ADD_READONLY_PROPERTY_METADATA(SkColor, TabSeparatorColor)
+ADD_READONLY_PROPERTY_METADATA(float, HoverOpacityForRadialHighlight)
+ADD_READONLY_PROPERTY_METADATA(int, ActiveTabWidth)
+ADD_READONLY_PROPERTY_METADATA(int, InactiveTabWidth)
+ADD_READONLY_PROPERTY_METADATA(int, AvailableWidthForTabStrip)
+END_METADATA
diff --git a/chrome/browser/ui/views/tabs/tab_strip.h b/chrome/browser/ui/views/tabs/tab_strip.h
index 6a13ef8..55d4485b 100644
--- a/chrome/browser/ui/views/tabs/tab_strip.h
+++ b/chrome/browser/ui/views/tabs/tab_strip.h
@@ -34,6 +34,7 @@
 #include "ui/gfx/geometry/rect.h"
 #include "ui/views/animation/bounds_animator.h"
 #include "ui/views/controls/button/image_button.h"
+#include "ui/views/metadata/metadata_header_macros.h"
 #include "ui/views/mouse_watcher.h"
 #include "ui/views/view.h"
 #include "ui/views/view_model.h"
@@ -80,6 +81,7 @@
                  public TabController,
                  public BrowserRootView::DropTarget {
  public:
+  METADATA_HEADER(TabStrip);
   explicit TabStrip(std::unique_ptr<TabStripController> controller);
   TabStrip(const TabStrip&) = delete;
   TabStrip& operator=(const TabStrip&) = delete;
@@ -238,7 +240,7 @@
   int GetModelIndexOf(const TabSlotView* view) const;
 
   // Gets the number of Tabs in the tab strip.
-  int tab_count() const { return tabs_.view_size(); }
+  int GetTabCount() const;
 
   // Cover method for TabStripController::GetCount.
   int GetModelCount() const;
@@ -337,7 +339,6 @@
   // views::View:
   void Layout() override;
   void PaintChildren(const views::PaintInfo& paint_info) override;
-  const char* GetClassName() const override;
   gfx::Size GetMinimumSize() const override;
   gfx::Size CalculatePreferredSize() const override;
   views::View* GetTooltipHandlerForPoint(const gfx::Point& point) override;
@@ -471,14 +472,6 @@
   // differ from this width slightly due to rounding.
   int GetInactiveTabWidth() const;
 
-  // Returns the width of the area that contains tabs not including the new tab
-  // button.
-  int GetTabAreaWidth() const;
-
-  // Returns the width of the area right of the tabs reserved for the new tab
-  // button and the frame grab area.
-  int GetRightSideReservedWidth() const;
-
   // Returns the last tab in the strip that's actually visible.  This will be
   // the actual last tab unless the strip is in the overflow node_data.
   const Tab* GetLastVisibleTab() const;
@@ -680,9 +673,9 @@
   views::BoundsAnimator bounds_animator_{this};
 
   // If this value is defined, it is used as the width to lay out tabs
-  // (instead of GetTabAreaWidth()). It is defined when closing tabs with the
-  // mouse, and is used to control which tab will end up under the cursor
-  // after the close animation completes.
+  // (instead of GetAvailableWidthForTabStrip()). It is defined when closing
+  // tabs with the mouse, and is used to control which tab will end up under the
+  // cursor after the close animation completes.
   base::Optional<int> override_available_width_for_tabs_;
 
   // The background offset used by inactive tabs to match the frame image.
diff --git a/chrome/browser/ui/views/tabs/tab_strip_browsertest.cc b/chrome/browser/ui/views/tabs/tab_strip_browsertest.cc
index 2c1fb83..c43a14c 100644
--- a/chrome/browser/ui/views/tabs/tab_strip_browsertest.cc
+++ b/chrome/browser/ui/views/tabs/tab_strip_browsertest.cc
@@ -47,7 +47,7 @@
 
   std::vector<content::WebContents*> GetWebContentses() {
     std::vector<content::WebContents*> contentses;
-    for (int i = 0; i < tab_strip()->tab_count(); ++i)
+    for (int i = 0; i < tab_strip()->GetTabCount(); ++i)
       contentses.push_back(tab_strip_model()->GetWebContentsAt(i));
     return contentses;
   }
@@ -55,7 +55,7 @@
   std::vector<content::WebContents*> GetWebContentsesInOrder(
       const std::vector<int>& order) {
     std::vector<content::WebContents*> contentses;
-    for (int i = 0; i < tab_strip()->tab_count(); ++i)
+    for (int i = 0; i < tab_strip()->GetTabCount(); ++i)
       contentses.push_back(tab_strip_model()->GetWebContentsAt(order[i]));
     return contentses;
   }
diff --git a/chrome/browser/ui/views/tabs/tab_strip_unittest.cc b/chrome/browser/ui/views/tabs/tab_strip_unittest.cc
index 2d69d78..a6a4cc4c 100644
--- a/chrome/browser/ui/views/tabs/tab_strip_unittest.cc
+++ b/chrome/browser/ui/views/tabs/tab_strip_unittest.cc
@@ -193,14 +193,14 @@
 
   // Makes sure that all tabs have the correct AX indices.
   void VerifyTabIndices() {
-    for (int i = 0; i < tab_strip_->tab_count(); ++i) {
+    for (int i = 0; i < tab_strip_->GetTabCount(); ++i) {
       ui::AXNodeData ax_node_data;
       tab_strip_->tab_at(i)->GetViewAccessibility().GetAccessibleNodeData(
           &ax_node_data);
       EXPECT_EQ(i + 1, ax_node_data.GetIntAttribute(
                            ax::mojom::IntAttribute::kPosInSet));
       EXPECT_EQ(
-          tab_strip_->tab_count(),
+          tab_strip_->GetTabCount(),
           ax_node_data.GetIntAttribute(ax::mojom::IntAttribute::kSetSize));
     }
   }
@@ -218,7 +218,7 @@
     views::View::Views all_children = tab_strip_->children();
 
     const int num_tab_slot_views =
-        tab_strip_->tab_count() + tab_strip_->group_views_.size();
+        tab_strip_->GetTabCount() + tab_strip_->group_views_.size();
 
     return views::View::Views(all_children.begin(),
                               all_children.begin() + num_tab_slot_views);
@@ -231,7 +231,7 @@
 
     base::Optional<tab_groups::TabGroupId> prev_group = base::nullopt;
 
-    for (int i = 0; i < tab_strip_->tab_count(); ++i) {
+    for (int i = 0; i < tab_strip_->GetTabCount(); ++i) {
       Tab* tab = tab_strip_->tab_at(i);
 
       // If the current Tab is the first one in a group, first add the
@@ -312,13 +312,13 @@
 }
 
 TEST_P(TabStripTest, tab_count) {
-  EXPECT_EQ(0, tab_strip_->tab_count());
+  EXPECT_EQ(0, tab_strip_->GetTabCount());
 }
 
 TEST_P(TabStripTest, AddTabAt) {
   TestTabStripObserver observer(tab_strip_);
   controller_->AddTab(0, false);
-  ASSERT_EQ(1, tab_strip_->tab_count());
+  ASSERT_EQ(1, tab_strip_->GetTabCount());
   EXPECT_EQ(0, observer.last_tab_added());
   Tab* tab = tab_strip_->tab_at(0);
   EXPECT_FALSE(tab == NULL);
@@ -329,7 +329,7 @@
   controller_->AddTab(0, false);
   controller_->AddTab(1, false);
   controller_->AddTab(2, false);
-  ASSERT_EQ(3, tab_strip_->tab_count());
+  ASSERT_EQ(3, tab_strip_->GetTabCount());
   EXPECT_EQ(2, observer.last_tab_added());
   Tab* tab = tab_strip_->tab_at(0);
   controller_->MoveTab(0, 1);
@@ -344,11 +344,11 @@
   controller_->AddTab(0, false);
   controller_->AddTab(1, false);
   const size_t num_children = tab_strip_->children().size();
-  EXPECT_EQ(2, tab_strip_->tab_count());
+  EXPECT_EQ(2, tab_strip_->GetTabCount());
   controller_->RemoveTab(0);
   EXPECT_EQ(0, observer.last_tab_removed());
   // When removing a tab the tabcount should immediately decrement.
-  EXPECT_EQ(1, tab_strip_->tab_count());
+  EXPECT_EQ(1, tab_strip_->GetTabCount());
   // But the number of views should remain the same (it's animatining closed).
   EXPECT_EQ(num_children, tab_strip_->children().size());
 
@@ -453,12 +453,12 @@
   ASSERT_GT(i, 0);
   EXPECT_LT(i, invisible_tab_index);
   invisible_tab_index = i;
-  for (int i = invisible_tab_index + 1; i < tab_strip_->tab_count(); ++i)
+  for (int i = invisible_tab_index + 1; i < tab_strip_->GetTabCount(); ++i)
     EXPECT_FALSE(tab_strip_->tab_at(i)->GetVisible());
 
   // When we're already in overflow, adding tabs at the beginning or end of
   // the strip should not change how many tabs are visible.
-  controller_->AddTab(tab_strip_->tab_count(), false);
+  controller_->AddTab(tab_strip_->GetTabCount(), false);
   CompleteAnimationAndLayout();
   EXPECT_TRUE(tab_strip_->tab_at(invisible_tab_index - 1)->GetVisible());
   EXPECT_FALSE(tab_strip_->tab_at(invisible_tab_index)->GetVisible());
@@ -468,10 +468,10 @@
   EXPECT_FALSE(tab_strip_->tab_at(invisible_tab_index)->GetVisible());
 
   // If we remove enough tabs, all the tabs should be visible.
-  for (int i = tab_strip_->tab_count() - 1; i >= invisible_tab_index; --i)
+  for (int i = tab_strip_->GetTabCount() - 1; i >= invisible_tab_index; --i)
     controller_->RemoveTab(i);
   CompleteAnimationAndLayout();
-  EXPECT_TRUE(tab_strip_->tab_at(tab_strip_->tab_count() - 1)->GetVisible());
+  EXPECT_TRUE(tab_strip_->tab_at(tab_strip_->GetTabCount() - 1)->GetVisible());
 }
 
 TEST_P(TabStripTest, GroupedTabSlotVisibility) {
@@ -529,7 +529,7 @@
   controller_->AddTab(1, true);
   controller_->AddTab(2, false);
   controller_->AddTab(3, false);
-  ASSERT_EQ(4, tab_strip_->tab_count());
+  ASSERT_EQ(4, tab_strip_->GetTabCount());
 
   // Switch to stacked layout mode and force a layout to ensure tabs stack.
   tab_strip_->SetStackedLayout(true);
@@ -544,7 +544,7 @@
       int tab = tab_strip_->GetModelIndexOf(FindTabForEvent(p));
       if (tab == previous_tab)
         continue;
-      if ((tab != -1) || (previous_tab != tab_strip_->tab_count() - 1))
+      if ((tab != -1) || (previous_tab != tab_strip_->GetTabCount() - 1))
         EXPECT_EQ(previous_tab + 1, tab) << "p = " << p.ToString();
       previous_tab = tab;
     }
@@ -559,7 +559,7 @@
   controller_->AddTab(1, true);
   controller_->AddTab(2, false);
   controller_->AddTab(3, false);
-  ASSERT_EQ(4, tab_strip_->tab_count());
+  ASSERT_EQ(4, tab_strip_->GetTabCount());
 
   // Switch to stacked layout mode and force a layout to ensure tabs stack.
   tab_strip_->SetStackedLayout(true);
@@ -588,7 +588,7 @@
   controller_->AddTab(0, false);
   controller_->AddTab(1, true);
   controller_->AddTab(2, false);
-  ASSERT_EQ(3, tab_strip_->tab_count());
+  ASSERT_EQ(3, tab_strip_->GetTabCount());
 
   Tab* tab0 = tab_strip_->tab_at(0);
   Tab* tab1 = tab_strip_->tab_at(1);
@@ -661,7 +661,7 @@
   controller_->AddTab(0, false);
   controller_->AddTab(1, true);
   controller_->AddTab(2, false);
-  ASSERT_EQ(3, tab_strip_->tab_count());
+  ASSERT_EQ(3, tab_strip_->GetTabCount());
 
   Tab* tab0 = tab_strip_->tab_at(0);
   ASSERT_FALSE(tab0->IsActive());
@@ -738,7 +738,7 @@
   controller_->AddTab(2, false);
   controller_->AddTab(3, false);
   CompleteAnimationAndLayout();
-  ASSERT_EQ(4, tab_strip_->tab_count());
+  ASSERT_EQ(4, tab_strip_->GetTabCount());
 
   // Verify that the active tab will be a tooltip handler for points that hit
   // it.
@@ -802,7 +802,7 @@
   controller_->AddTab(2, false);
   controller_->AddTab(3, false);
   CompleteAnimationAndLayout();
-  ASSERT_EQ(4, tab_strip_->tab_count());
+  ASSERT_EQ(4, tab_strip_->GetTabCount());
 
   // Verify that the active tab will be a tooltip handler for points that hit
   // it.
@@ -903,17 +903,17 @@
     CompleteAnimationAndLayout();
   }
 
-  EXPECT_GT(tab_strip_->tab_count(), 1);
+  EXPECT_GT(tab_strip_->GetTabCount(), 1);
 
   const int active_index = controller_->GetActiveIndex();
-  EXPECT_EQ(tab_strip_->tab_count() - 1, active_index);
+  EXPECT_EQ(tab_strip_->GetTabCount() - 1, active_index);
   EXPECT_LT(tab_strip_->ideal_bounds(0).width(),
             tab_strip_->ideal_bounds(active_index).width());
 
   // During mouse-based tab closure, the active tab should remain at least as
   // wide as it's minimum width.
   controller_->SelectTab(0, dummy_event_);
-  while (tab_strip_->tab_count() > 0) {
+  while (tab_strip_->GetTabCount() > 0) {
     const int active_index = controller_->GetActiveIndex();
     EXPECT_GE(tab_strip_->ideal_bounds(active_index).width(),
               TabStyleViews::GetMinimumActiveWidth());
@@ -943,7 +943,7 @@
   // If there are only two tabs in the strip, then after closing one the
   // remaining one will be active and there will be no inactive tabs,
   // so we stop at 2.
-  while (tab_strip_->tab_count() > 2) {
+  while (tab_strip_->GetTabCount() > 2) {
     const int last_inactive_width = GetInactiveTabWidth();
     tab_strip_->CloseTab(tab_strip_->tab_at(controller_->GetActiveIndex()),
                          CLOSE_TAB_FROM_MOUSE);
@@ -963,23 +963,24 @@
   }
 
   // The test closes two tabs, we need at least one left over after that.
-  ASSERT_GE(tab_strip_->tab_count(), 3);
+  ASSERT_GE(tab_strip_->GetTabCount(), 3);
 
   // Close the second-to-last tab to enter tab closing mode.
-  tab_strip_->CloseTab(tab_strip_->tab_at(tab_strip_->tab_count() - 2),
+  tab_strip_->CloseTab(tab_strip_->tab_at(tab_strip_->GetTabCount() - 2),
                        CLOSE_TAB_FROM_MOUSE);
   CompleteAnimationAndLayout();
   ASSERT_LT(GetActiveTabWidth(), standard_width);
 
   // Close the last tab; tabs should reach standard width.
-  tab_strip_->CloseTab(tab_strip_->tab_at(tab_strip_->tab_count() - 1),
+  tab_strip_->CloseTab(tab_strip_->tab_at(tab_strip_->GetTabCount() - 1),
                        CLOSE_TAB_FROM_MOUSE);
   CompleteAnimationAndLayout();
   EXPECT_EQ(GetActiveTabWidth(), standard_width);
 
   // The tabstrip width should match the rightmost tab's right edge.
-  EXPECT_EQ(tab_strip_->bounds().width(),
-            tab_strip_->tab_at(tab_strip_->tab_count() - 1)->bounds().right());
+  EXPECT_EQ(
+      tab_strip_->bounds().width(),
+      tab_strip_->tab_at(tab_strip_->GetTabCount() - 1)->bounds().right());
 }
 
 // When dragged tabs are moving back to their position, changes to ideal bounds
@@ -1332,7 +1333,7 @@
   controller_->MoveTabIntoGroup(2, group_id);
 
   CompleteAnimationAndLayout();
-  ASSERT_EQ(3, tab_strip_->tab_count());
+  ASSERT_EQ(3, tab_strip_->GetTabCount());
   ASSERT_EQ(3, tab_strip_->GetModelCount());
   EXPECT_EQ(base::nullopt, tab_strip_->tab_at(0)->group());
   EXPECT_EQ(group_id, tab_strip_->tab_at(1)->group());
@@ -1362,7 +1363,7 @@
 
   // After finishing animations, there should be exactly 1 tab in no
   // group.
-  EXPECT_EQ(1, tab_strip_->tab_count());
+  EXPECT_EQ(1, tab_strip_->GetTabCount());
   EXPECT_EQ(base::nullopt, tab_strip_->tab_at(0)->group());
   EXPECT_EQ(1, tab_strip_->GetModelCount());
 }
diff --git a/chrome/browser/ui/views/tabs/tab_style_views.cc b/chrome/browser/ui/views/tabs/tab_style_views.cc
index 950c967..1e57de5 100644
--- a/chrome/browser/ui/views/tabs/tab_style_views.cc
+++ b/chrome/browser/ui/views/tabs/tab_style_views.cc
@@ -9,6 +9,7 @@
 
 #include "base/numerics/ranges.h"
 #include "base/numerics/safe_conversions.h"
+#include "base/strings/stringprintf.h"
 #include "cc/paint/paint_record.h"
 #include "chrome/browser/themes/theme_properties.h"
 #include "chrome/browser/ui/layout_constants.h"
@@ -945,6 +946,31 @@
 
 }  // namespace
 
+// static
+base::string16 views::metadata::TypeConverter<TabStyle::TabColors>::ToString(
+    views::metadata::ArgType<TabStyle::TabColors> source_value) {
+  return base::ASCIIToUTF16(base::StringPrintf(
+      "{%s,%s}",
+      color_utils::SkColorToRgbaString(source_value.foreground_color).c_str(),
+      color_utils::SkColorToRgbaString(source_value.background_color).c_str()));
+}
+
+// static
+base::Optional<TabStyle::TabColors> views::metadata::TypeConverter<
+    TabStyle::TabColors>::FromString(const base::string16& source_value) {
+  base::string16 pruned_string;
+  base::RemoveChars(source_value, base::ASCIIToUTF16("()rgba"), &pruned_string);
+  const auto values =
+      base::SplitStringPiece(pruned_string, base::ASCIIToUTF16("{,}"),
+                             base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
+  const auto foreground_color = RgbaPiecesToSkColor(values, 0);
+  const auto background_color = RgbaPiecesToSkColor(values, 4);
+  return (foreground_color.has_value() && background_color.has_value())
+             ? base::make_optional<TabStyle::TabColors>(
+                   foreground_color.value(), background_color.value())
+             : base::nullopt;
+}
+
 // TabStyle --------------------------------------------------------------------
 
 TabStyleViews::~TabStyleViews() = default;
diff --git a/chrome/browser/ui/views/tabs/tab_style_views.h b/chrome/browser/ui/views/tabs/tab_style_views.h
index ea5b9f6c..0a09ef6 100644
--- a/chrome/browser/ui/views/tabs/tab_style_views.h
+++ b/chrome/browser/ui/views/tabs/tab_style_views.h
@@ -10,6 +10,17 @@
 #include "chrome/browser/ui/tabs/tab_style.h"
 #include "chrome/browser/ui/views/tabs/glow_hover_controller.h"
 #include "ui/gfx/geometry/rect_f.h"
+#include "ui/views/metadata/type_conversion.h"
+
+template <>
+struct views::metadata::TypeConverter<TabStyle::TabColors> {
+  static constexpr bool is_serializable = true;
+  static bool IsSerializable() { return is_serializable; }
+  static base::string16 ToString(
+      views::metadata::ArgType<TabStyle::TabColors> source_value);
+  static base::Optional<TabStyle::TabColors> FromString(
+      const base::string16& source_value);
+};
 
 class Tab;
 
diff --git a/chrome/browser/ui/web_applications/system_web_app_ui_utils.cc b/chrome/browser/ui/web_applications/system_web_app_ui_utils.cc
index b3d8d79b..a3f18586 100644
--- a/chrome/browser/ui/web_applications/system_web_app_ui_utils.cc
+++ b/chrome/browser/ui/web_applications/system_web_app_ui_utils.cc
@@ -241,8 +241,9 @@
   content::WebContents* web_contents = nullptr;
 
   if (!browser) {
-    browser = CreateWebApplicationWindow(profile_for_launch, params->app_id,
-                                         params->disposition);
+    browser =
+        CreateWebApplicationWindow(profile_for_launch, params->app_id,
+                                   params->disposition, params->restore_id);
   }
 
   // Navigate application window to application's |url| if necessary.
diff --git a/chrome/browser/ui/web_applications/web_app_browsertest.cc b/chrome/browser/ui/web_applications/web_app_browsertest.cc
index ffa4fd98..8084c50e 100644
--- a/chrome/browser/ui/web_applications/web_app_browsertest.cc
+++ b/chrome/browser/ui/web_applications/web_app_browsertest.cc
@@ -1212,7 +1212,7 @@
   const AppId app_id = InstallPWA(app_url);
 
   Browser* const popup_browser = web_app::CreateWebApplicationWindow(
-      profile(), app_id, WindowOpenDisposition::NEW_POPUP);
+      profile(), app_id, WindowOpenDisposition::NEW_POPUP, /*restore_id=*/0);
 
   EXPECT_TRUE(
       popup_browser->CanSupportWindowFeature(Browser::FEATURE_LOCATIONBAR));
diff --git a/chrome/browser/ui/web_applications/web_app_launch_manager.cc b/chrome/browser/ui/web_applications/web_app_launch_manager.cc
index b51cdfe..312b269 100644
--- a/chrome/browser/ui/web_applications/web_app_launch_manager.cc
+++ b/chrome/browser/ui/web_applications/web_app_launch_manager.cc
@@ -104,7 +104,8 @@
 
 Browser* CreateWebApplicationWindow(Profile* profile,
                                     const std::string& app_id,
-                                    WindowOpenDisposition disposition) {
+                                    WindowOpenDisposition disposition,
+                                    int32_t restore_id) {
   std::string app_name = GenerateApplicationNameFromAppId(app_id);
   gfx::Rect initial_bounds;
   Browser::CreateParams browser_params =
@@ -116,6 +117,9 @@
                 app_name, /*trusted_source=*/true, initial_bounds, profile,
                 /*user_gesture=*/true);
   browser_params.initial_show_state = DetermineWindowShowState();
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  browser_params.restore_id = restore_id;
+#endif
   return Browser::Create(browser_params);
 }
 
@@ -192,8 +196,8 @@
       }
     }
     if (!browser) {
-      browser = CreateWebApplicationWindow(profile_, params.app_id,
-                                           params.disposition);
+      browser = CreateWebApplicationWindow(
+          profile_, params.app_id, params.disposition, params.restore_id);
     }
   }
 
diff --git a/chrome/browser/ui/web_applications/web_app_launch_manager.h b/chrome/browser/ui/web_applications/web_app_launch_manager.h
index 90d947d..2f2b809 100644
--- a/chrome/browser/ui/web_applications/web_app_launch_manager.h
+++ b/chrome/browser/ui/web_applications/web_app_launch_manager.h
@@ -74,7 +74,8 @@
 
 Browser* CreateWebApplicationWindow(Profile* profile,
                                     const std::string& app_id,
-                                    WindowOpenDisposition disposition);
+                                    WindowOpenDisposition disposition,
+                                    int32_t restore_id);
 
 content::WebContents* NavigateWebApplicationWindow(
     Browser* browser,
diff --git a/chrome/browser/ui/web_applications/web_app_menu_model.cc b/chrome/browser/ui/web_applications/web_app_menu_model.cc
index b6bdc13..979a42cd 100644
--- a/chrome/browser/ui/web_applications/web_app_menu_model.cc
+++ b/chrome/browser/ui/web_applications/web_app_menu_model.cc
@@ -11,6 +11,7 @@
 #include "chrome/browser/media/router/media_router_feature.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_window.h"
 #include "chrome/browser/ui/web_applications/app_browser_controller.h"
 #include "chrome/grit/chromium_strings.h"
 #include "chrome/grit/generated_resources.h"
@@ -23,6 +24,7 @@
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "ash/public/cpp/ash_features.h"
 #include "chrome/browser/ui/toolbar/assign_to_desks_menu_model.h"
+#include "ui/views/widget/widget.h"
 #endif
 
 constexpr int WebAppMenuModel::kUninstallAppCommandId;
@@ -74,7 +76,9 @@
 #if BUILDFLAG(IS_CHROMEOS_ASH)
   if (ash::features::IsBentoEnabled()) {
     AddSeparator(ui::NORMAL_SEPARATOR);
-    assign_to_desks_submenu_ = std::make_unique<AssignToDesksMenuModel>(this);
+    assign_to_desks_submenu_ = std::make_unique<AssignToDesksMenuModel>(
+        this, views::Widget::GetWidgetForNativeWindow(
+                  browser()->window()->GetNativeWindow()));
     AddSubMenuWithStringId(IDC_ASSIGN_TO_DESKS_MENU, IDS_ASSIGN_TO_DESKS_MENU,
                            assign_to_desks_submenu_.get());
   }
diff --git a/chrome/browser/ui/webui/chromeos/cellular_setup/cellular_setup_localized_strings_provider.cc b/chrome/browser/ui/webui/chromeos/cellular_setup/cellular_setup_localized_strings_provider.cc
index 6e4fda6a..88650430 100644
--- a/chrome/browser/ui/webui/chromeos/cellular_setup/cellular_setup_localized_strings_provider.cc
+++ b/chrome/browser/ui/webui/chromeos/cellular_setup/cellular_setup_localized_strings_provider.cc
@@ -65,7 +65,11 @@
     {"scanQRCodeSuccess", IDS_CELLULAR_SETUP_ESIM_PAGE_SCAN_QR_CODE_SUCCESS},
     {"qrCodeRetry", IDS_CELLULAR_SETUP_ESIM_PAGE_SCAN_QR_CODE_RETRY},
     {"scanQrCodeInvalid", IDS_CELLULAR_SETUP_ESIM_PAGE_SCAN_QR_CODE_INVALID},
-    {"profileListPageMessage", IDS_CELLULAR_SETUP_PROFILE_LIST_PAGE_MESSAGE}};
+    {"profileListPageMessage", IDS_CELLULAR_SETUP_PROFILE_LIST_PAGE_MESSAGE},
+    {"eidPopupTitle", IDS_CELLULAR_SETUP_EID_POPUP_TITLE},
+    {"eidPopupDescription", IDS_CELLULAR_SETUP_EID_POPUP_DESCRIPTION},
+    {"closeEidPopupButtonLabel",
+     IDS_CELLULAR_SETUP_CLOSE_EID_POPUP_BUTTON_LABEL}};
 
 struct NamedBoolean {
   const char* name;
diff --git a/chrome/browser/ui/webui/chromeos/network_element_localized_strings_provider.cc b/chrome/browser/ui/webui/chromeos/network_element_localized_strings_provider.cc
index 4a7c13c..7ebcf2a 100644
--- a/chrome/browser/ui/webui/chromeos/network_element_localized_strings_provider.cc
+++ b/chrome/browser/ui/webui/chromeos/network_element_localized_strings_provider.cc
@@ -82,6 +82,10 @@
      IDS_NETWORK_LIST_ITEM_LABEL_ESIM_PENDING_PROFILE},
     {"networkListItemLabelESimPendingProfileWithProviderName",
      IDS_NETWORK_LIST_ITEM_LABEL_ESIM_PENDING_PROFILE_WITH_PROVIDER_NAME},
+    {"networkListItemLabelESimPendingProfileInstalling",
+     IDS_NETWORK_LIST_ITEM_LABEL_ESIM_PENDING_PROFILE_INSTALLING},
+    {"networkListItemLabelESimPendingProfileWithProviderNameInstalling",
+     IDS_NETWORK_LIST_ITEM_LABEL_ESIM_PENDING_PROFILE_WITH_PROVIDER_NAME_INSTALLING},
     {"wifiNetworkStatusSecured", IDS_WIFI_NETWORK_STATUS_SECURED},
     {"wifiNetworkStatusUnsecured", IDS_WIFI_NETWORK_STATUS_UNSECURED},
     {"networkListItemNotAvailable", IDS_NETWORK_LIST_NOT_AVAILABLE},
@@ -90,6 +94,7 @@
     {"networkListItemNotConnected", IDS_NETWORK_LIST_NOT_CONNECTED},
     {"networkListItemNoNetwork", IDS_NETWORK_LIST_NO_NETWORK},
     {"networkListItemDownload", IDS_NETWORK_LIST_ITEM_DOWNLOAD},
+    {"networkListItemAddingProfile", IDS_NETWORK_LIST_ITEM_ADDING_PROFILE},
     {"vpnNameTemplate", IDS_NETWORK_LIST_THIRD_PARTY_VPN_NAME_TEMPLATE},
     {"networkIconLabelEthernet", IDS_NETWORK_ICON_LABEL_ETHERNET},
     {"networkIconLabelVpn", IDS_NETWORK_ICON_LABEL_VPN},
diff --git a/chrome/browser/ui/webui/settings/chromeos/internet_section.cc b/chrome/browser/ui/webui/settings/chromeos/internet_section.cc
index 6fb832d..437b3c29 100644
--- a/chrome/browser/ui/webui/settings/chromeos/internet_section.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/internet_section.cc
@@ -702,12 +702,8 @@
       {"eSimNetworkNotSetup",
        IDS_SETTINGS_INTERNET_ESIM_NOT_SETUP_WITH_SETUP_LINK},
       {"cellularNetworkTetherLabel", IDS_SETTINGS_INTERNET_TETHER_LABEL},
-      {"eidPopupTitle", IDS_SETTINGS_INTERNET_EID_POPUP_TITLE},
       {"showEidPopupButtonLabel",
        IDS_SETTINGS_INTERNET_SHOW_EID_POPUP_BUTTON_LABEL},
-      {"eidPopupDescription", IDS_SETTINGS_INTERNET_EID_POPUP_DESCRIPTION},
-      {"closeEidPopupButtonLabel",
-       IDS_SETTINGS_INTERNET_CLOSE_EID_POPUP_BUTTON_LABEL},
       {"eSimRenameProfileDialogLabel",
        IDS_SETTINGS_INTERNET_NETWORK_RENAME_DIALOG_RENAME_PROFILE},
       {"eSimRenameProfileDialogDone",
diff --git a/chrome/browser/video_tutorials/android/java/res/drawable/corner_bottom_end.xml b/chrome/browser/video_tutorials/android/java/res/drawable/corner_bottom_end.xml
index 8268a6e..babf2e8 100644
--- a/chrome/browser/video_tutorials/android/java/res/drawable/corner_bottom_end.xml
+++ b/chrome/browser/video_tutorials/android/java/res/drawable/corner_bottom_end.xml
@@ -12,6 +12,6 @@
     android:viewportHeight="10"
     android:autoMirrored="true">
     <path
-        android:fillColor="@android:color/white"
+        android:fillColor="@color/default_bg_color"
         android:pathData="M 10,0 A 10,10 0 0 1 0,10 L 10,10 Z" />
 </vector>
diff --git a/chrome/browser/video_tutorials/android/java/res/drawable/corner_bottom_start.xml b/chrome/browser/video_tutorials/android/java/res/drawable/corner_bottom_start.xml
index fda3be5..62aee05 100644
--- a/chrome/browser/video_tutorials/android/java/res/drawable/corner_bottom_start.xml
+++ b/chrome/browser/video_tutorials/android/java/res/drawable/corner_bottom_start.xml
@@ -12,6 +12,6 @@
     android:viewportHeight="10"
     android:autoMirrored="true">
     <path
-        android:fillColor="@android:color/white"
+        android:fillColor="@color/default_bg_color"
         android:pathData="M 0,0 A 10,10 0 0 0 10,10 L 0,10 Z" />
 </vector>
diff --git a/chrome/browser/video_tutorials/android/java/res/drawable/corner_top_end.xml b/chrome/browser/video_tutorials/android/java/res/drawable/corner_top_end.xml
index e58fc39..6d8bf7b 100644
--- a/chrome/browser/video_tutorials/android/java/res/drawable/corner_top_end.xml
+++ b/chrome/browser/video_tutorials/android/java/res/drawable/corner_top_end.xml
@@ -12,6 +12,6 @@
     android:viewportHeight="10"
     android:autoMirrored="true">
     <path
-        android:fillColor="@android:color/white"
+        android:fillColor="@color/default_bg_color"
         android:pathData="M 0,0 A 10,10 0 0 1 10,10 L 10,0 Z" />
 </vector>
diff --git a/chrome/browser/video_tutorials/android/java/res/drawable/corner_top_start.xml b/chrome/browser/video_tutorials/android/java/res/drawable/corner_top_start.xml
index 088f3fd6..61bc86b 100644
--- a/chrome/browser/video_tutorials/android/java/res/drawable/corner_top_start.xml
+++ b/chrome/browser/video_tutorials/android/java/res/drawable/corner_top_start.xml
@@ -12,6 +12,6 @@
     android:viewportHeight="10"
     android:autoMirrored="true">
     <path
-        android:fillColor="@android:color/white"
+        android:fillColor="@color/default_bg_color"
         android:pathData="M 0,10 A 10,10 0 0 1 10,0 L 0,0 Z" />
 </vector>
diff --git a/chrome/browser/video_tutorials/android/java/src/org/chromium/chrome/browser/video_tutorials/test/TestVideoTutorialService.java b/chrome/browser/video_tutorials/android/java/src/org/chromium/chrome/browser/video_tutorials/test/TestVideoTutorialService.java
index d23093d..96aa9977 100644
--- a/chrome/browser/video_tutorials/android/java/src/org/chromium/chrome/browser/video_tutorials/test/TestVideoTutorialService.java
+++ b/chrome/browser/video_tutorials/android/java/src/org/chromium/chrome/browser/video_tutorials/test/TestVideoTutorialService.java
@@ -78,7 +78,7 @@
 
     private void initializeTutorialList() {
         mTutorials.add(new Tutorial(FeatureType.CHROME_INTRO, "Introduction to chrome",
-                "https://www.gstatic.com/chrome/video-tutorials/images/1_Search_english.mp4",
+                "https://www.gstatic.com/chrome/video-tutorials/webm/1_Search_english.mp4",
                 "https://www.gstatic.com/chrome/video-tutorials/images/1_Search_english.png",
                 "https://www.gstatic.com/chrome/video-tutorials/gif/sample_anim.gif",
                 "https://www.gstatic.com/chrome/video-tutorials/images/1_Search_english.png",
@@ -86,7 +86,7 @@
 
         mTutorials.add(new Tutorial(FeatureType.DOWNLOAD,
                 "How to use Google Chrome's download functionality",
-                "https://www.gstatic.com/chrome/video-tutorials/images/1_Search_english.mp4",
+                "https://www.gstatic.com/chrome/video-tutorials/webm/1_Search_english.mp4",
                 "https://www.gstatic.com/chrome/video-tutorials/images/1_Search_english.png",
                 "https://www.gstatic.com/chrome/video-tutorials/gif/sample_anim.gif",
                 "https://www.gstatic.com/chrome/video-tutorials/images/1_Search_english.png",
@@ -94,7 +94,7 @@
 
         mTutorials.add(new Tutorial(FeatureType.SEARCH,
                 "How to efficiently search with Google Chrome",
-                "https://www.gstatic.com/chrome/video-tutorials/images/1_Search_english.mp4",
+                "https://www.gstatic.com/chrome/video-tutorials/webm/1_Search_english.mp4",
                 "https://www.gstatic.com/chrome/video-tutorials/images/1_Search_english.png",
                 "https://www.gstatic.com/chrome/video-tutorials/gif/sample_anim.gif",
                 "https://www.gstatic.com/chrome/video-tutorials/images/1_Search_english.png",
diff --git a/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/iph/VideoIPHCoordinatorImpl.java b/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/iph/VideoIPHCoordinatorImpl.java
index 99332d1..e0dfcd3e 100644
--- a/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/iph/VideoIPHCoordinatorImpl.java
+++ b/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/iph/VideoIPHCoordinatorImpl.java
@@ -78,7 +78,7 @@
     private void fetchImage(
             Callback<Drawable> consumer, int widthPx, int heightPx, Tutorial tutorial) {
         boolean useAnimatedGifUrl = ChromeFeatureList.getFieldTrialParamByFeatureAsBoolean(
-                ChromeFeatureList.VIDEO_TUTORIALS, VARIATION_USE_ANIMATED_GIF_URL, false);
+                ChromeFeatureList.VIDEO_TUTORIALS, VARIATION_USE_ANIMATED_GIF_URL, true);
         ImageFetcher.Params params = ImageFetcher.Params.create(
                 useAnimatedGifUrl ? tutorial.animatedGifUrl : tutorial.thumbnailUrl,
                 ImageFetcher.VIDEO_TUTORIALS_IPH_UMA_CLIENT_NAME, widthPx, heightPx);
diff --git a/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/player/VideoPlayerMediatorUnitTest.java b/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/player/VideoPlayerMediatorUnitTest.java
index 5a533065..ceb35cb1 100644
--- a/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/player/VideoPlayerMediatorUnitTest.java
+++ b/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/player/VideoPlayerMediatorUnitTest.java
@@ -227,4 +227,22 @@
         Assert.assertTrue(VideoTutorialUtils.shouldShowTryNow(FeatureType.VOICE_SEARCH));
         Assert.assertFalse(VideoTutorialUtils.shouldShowTryNow(99));
     }
+
+    @Test
+    public void testVideoPlayerURL() {
+        String videoUrl = "https://example/video.mp4";
+        String posterUrl = "https://example/poster.png";
+        String animationUrl = "https://example/anim.gif";
+        String thumbnailUrl = "https://example/thumb.png";
+        String captionUrl = "https://example/caption.vtt";
+        String shareUrl = "https://example/share.mp4";
+        Tutorial testTutorial = new Tutorial(FeatureType.CHROME_INTRO, "title", videoUrl, posterUrl,
+                animationUrl, thumbnailUrl, captionUrl, shareUrl, 25);
+
+        assertThat(VideoPlayerURLBuilder.buildFromTutorial(testTutorial),
+                equalTo("chrome-untrusted://video-tutorials/"
+                        + "?video_url=https://example/video.mp4"
+                        + "&poster_url=https://example/poster.png"
+                        + "&caption_url=https://example/caption.vtt"));
+    }
 }
diff --git a/chrome/browser/vr/animation.cc b/chrome/browser/vr/animation.cc
index 0c6fbb7..3e44f2d8 100644
--- a/chrome/browser/vr/animation.cc
+++ b/chrome/browser/vr/animation.cc
@@ -167,7 +167,7 @@
   base::EraseIf(keyframe_models_,
                 [target_property](
                     const std::unique_ptr<cc::KeyframeModel>& keyframe_model) {
-                  return keyframe_model->target_property_id() ==
+                  return keyframe_model->target_property_type() ==
                          target_property;
                 });
 }
@@ -255,7 +255,7 @@
 
 bool Animation::IsAnimatingProperty(int property) const {
   for (auto& keyframe_model : keyframe_models_) {
-    if (keyframe_model->target_property_id() == property)
+    if (keyframe_model->target_property_type() == property)
       return true;
   }
   return false;
@@ -293,17 +293,17 @@
       continue;
     if (keyframe_model->run_state() == cc::KeyframeModel::RUNNING ||
         keyframe_model->run_state() == cc::KeyframeModel::PAUSED) {
-      animated_properties[keyframe_model->target_property_id()] = true;
+      animated_properties[keyframe_model->target_property_type()] = true;
     }
   }
   for (auto& keyframe_model : keyframe_models_) {
     if (!include_infinite_animations &&
         keyframe_model->iterations() == std::numeric_limits<double>::infinity())
       continue;
-    if (!animated_properties[keyframe_model->target_property_id()] &&
+    if (!animated_properties[keyframe_model->target_property_type()] &&
         keyframe_model->run_state() ==
             cc::KeyframeModel::WAITING_FOR_TARGET_AVAILABILITY) {
-      animated_properties[keyframe_model->target_property_id()] = true;
+      animated_properties[keyframe_model->target_property_type()] = true;
       keyframe_model->SetRunState(cc::KeyframeModel::RUNNING, monotonic_time);
       keyframe_model->set_start_time(monotonic_time);
     }
@@ -361,9 +361,9 @@
   curve->AddKeyframe(AnimationTraits<ValueType>::KeyframeType::Create(
       transition_.duration, target, CreateTransitionTimingFunction()));
 
-  AddKeyframeModel(
-      cc::KeyframeModel::Create(std::move(curve), GetNextKeyframeModelId(),
-                                GetNextGroupId(), target_property));
+  AddKeyframeModel(cc::KeyframeModel::Create(
+      std::move(curve), GetNextKeyframeModelId(), GetNextGroupId(),
+      cc::KeyframeModel::TargetPropertyId(target_property)));
 }
 
 cc::KeyframeModel* Animation::GetRunningKeyframeModelForProperty(
@@ -371,7 +371,7 @@
   for (auto& keyframe_model : keyframe_models_) {
     if ((keyframe_model->run_state() == cc::KeyframeModel::RUNNING ||
          keyframe_model->run_state() == cc::KeyframeModel::PAUSED) &&
-        keyframe_model->target_property_id() == target_property) {
+        keyframe_model->target_property_type() == target_property) {
       return keyframe_model.get();
     }
   }
@@ -381,7 +381,7 @@
 cc::KeyframeModel* Animation::GetKeyframeModelForProperty(
     int target_property) const {
   for (auto& keyframe_model : keyframe_models_) {
-    if (keyframe_model->target_property_id() == target_property) {
+    if (keyframe_model->target_property_type() == target_property) {
       return keyframe_model.get();
     }
   }
diff --git a/chrome/browser/vr/animation_unittest.cc b/chrome/browser/vr/animation_unittest.cc
index 9794ee2..b792532 100644
--- a/chrome/browser/vr/animation_unittest.cc
+++ b/chrome/browser/vr/animation_unittest.cc
@@ -87,7 +87,7 @@
                                                    gfx::SizeF(20, 200),
                                                    MicrosecondsToDelta(10000)));
   EXPECT_EQ(1ul, animation.keyframe_models().size());
-  EXPECT_EQ(BOUNDS, animation.keyframe_models()[0]->target_property_id());
+  EXPECT_EQ(BOUNDS, animation.keyframe_models()[0]->target_property_type());
 
   cc::TransformOperations from_operations;
   from_operations.AppendTranslate(10, 100, 1000);
@@ -97,16 +97,16 @@
       2, 2, from_operations, to_operations, MicrosecondsToDelta(10000)));
 
   EXPECT_EQ(2ul, animation.keyframe_models().size());
-  EXPECT_EQ(TRANSFORM, animation.keyframe_models()[1]->target_property_id());
+  EXPECT_EQ(TRANSFORM, animation.keyframe_models()[1]->target_property_type());
 
   animation.AddKeyframeModel(CreateTransformAnimation(
       3, 3, from_operations, to_operations, MicrosecondsToDelta(10000)));
   EXPECT_EQ(3ul, animation.keyframe_models().size());
-  EXPECT_EQ(TRANSFORM, animation.keyframe_models()[2]->target_property_id());
+  EXPECT_EQ(TRANSFORM, animation.keyframe_models()[2]->target_property_type());
 
   animation.RemoveKeyframeModels(TRANSFORM);
   EXPECT_EQ(1ul, animation.keyframe_models().size());
-  EXPECT_EQ(BOUNDS, animation.keyframe_models()[0]->target_property_id());
+  EXPECT_EQ(BOUNDS, animation.keyframe_models()[0]->target_property_type());
 
   animation.RemoveKeyframeModel(animation.keyframe_models()[0]->id());
   EXPECT_TRUE(animation.keyframe_models().empty());
@@ -121,7 +121,7 @@
                                                    gfx::SizeF(20, 200),
                                                    MicrosecondsToDelta(10000)));
   EXPECT_EQ(1ul, animation.keyframe_models().size());
-  EXPECT_EQ(BOUNDS, animation.keyframe_models()[0]->target_property_id());
+  EXPECT_EQ(BOUNDS, animation.keyframe_models()[0]->target_property_type());
   EXPECT_EQ(cc::KeyframeModel::WAITING_FOR_TARGET_AVAILABILITY,
             animation.keyframe_models()[0]->run_state());
 
@@ -150,7 +150,7 @@
                                                    gfx::SizeF(20, 200),
                                                    MicrosecondsToDelta(10000)));
   EXPECT_EQ(1ul, animation.keyframe_models().size());
-  EXPECT_EQ(BOUNDS, animation.keyframe_models()[0]->target_property_id());
+  EXPECT_EQ(BOUNDS, animation.keyframe_models()[0]->target_property_type());
   EXPECT_EQ(cc::KeyframeModel::WAITING_FOR_TARGET_AVAILABILITY,
             animation.keyframe_models()[0]->run_state());
 
@@ -172,8 +172,8 @@
       3, 2, from_operations, to_operations, MicrosecondsToDelta(10000)));
 
   EXPECT_EQ(3ul, animation.keyframe_models().size());
-  EXPECT_EQ(BOUNDS, animation.keyframe_models()[1]->target_property_id());
-  EXPECT_EQ(TRANSFORM, animation.keyframe_models()[2]->target_property_id());
+  EXPECT_EQ(BOUNDS, animation.keyframe_models()[1]->target_property_type());
+  EXPECT_EQ(TRANSFORM, animation.keyframe_models()[2]->target_property_type());
   int id1 = animation.keyframe_models()[1]->id();
 
   animation.Tick(start_time + MicrosecondsToDelta(1));
diff --git a/chrome/browser/vr/elements/spinner.cc b/chrome/browser/vr/elements/spinner.cc
index 784fad2c..f6fc9f0 100644
--- a/chrome/browser/vr/elements/spinner.cc
+++ b/chrome/browser/vr/elements/spinner.cc
@@ -76,7 +76,8 @@
 
   std::unique_ptr<cc::KeyframeModel> keyframe_model(cc::KeyframeModel::Create(
       std::move(curve), Animation::GetNextKeyframeModelId(),
-      Animation::GetNextGroupId(), SPINNER_ROTATION));
+      Animation::GetNextGroupId(),
+      cc::KeyframeModel::TargetPropertyId(SPINNER_ROTATION)));
 
   keyframe_model->set_iterations(std::numeric_limits<double>::infinity());
   AddKeyframeModel(std::move(keyframe_model));
@@ -91,7 +92,8 @@
 
   keyframe_model = cc::KeyframeModel::Create(
       std::move(curve), Animation::GetNextKeyframeModelId(),
-      Animation::GetNextGroupId(), SPINNER_ANGLE_SWEEP);
+      Animation::GetNextGroupId(),
+      cc::KeyframeModel::TargetPropertyId(SPINNER_ANGLE_SWEEP));
 
   keyframe_model->set_iterations(std::numeric_limits<double>::infinity());
   AddKeyframeModel(std::move(keyframe_model));
@@ -105,7 +107,8 @@
 
   keyframe_model = cc::KeyframeModel::Create(
       std::move(curve), Animation::GetNextKeyframeModelId(),
-      Animation::GetNextGroupId(), SPINNER_ANGLE_START);
+      Animation::GetNextGroupId(),
+      cc::KeyframeModel::TargetPropertyId(SPINNER_ANGLE_START));
 
   keyframe_model->set_iterations(std::numeric_limits<double>::infinity());
   AddKeyframeModel(std::move(keyframe_model));
diff --git a/chrome/browser/vr/elements/throbber.cc b/chrome/browser/vr/elements/throbber.cc
index 5414aa77..def9258 100644
--- a/chrome/browser/vr/elements/throbber.cc
+++ b/chrome/browser/vr/elements/throbber.cc
@@ -65,7 +65,8 @@
 
   std::unique_ptr<cc::KeyframeModel> keyframe_model(cc::KeyframeModel::Create(
       std::move(curve), Animation::GetNextKeyframeModelId(),
-      Animation::GetNextGroupId(), CIRCLE_GROW));
+      Animation::GetNextGroupId(),
+      cc::KeyframeModel::TargetPropertyId(CIRCLE_GROW)));
   keyframe_model->set_iterations(std::numeric_limits<double>::infinity());
   AddKeyframeModel(std::move(keyframe_model));
 }
diff --git a/chrome/browser/vr/test/animation_utils.cc b/chrome/browser/vr/test/animation_utils.cc
index e45b631d..d3f4b78 100644
--- a/chrome/browser/vr/test/animation_utils.cc
+++ b/chrome/browser/vr/test/animation_utils.cc
@@ -21,7 +21,8 @@
       cc::TransformKeyframe::Create(base::TimeDelta(), from, nullptr));
   curve->AddKeyframe(cc::TransformKeyframe::Create(duration, to, nullptr));
   std::unique_ptr<cc::KeyframeModel> keyframe_model(cc::KeyframeModel::Create(
-      std::move(curve), id, group, TargetProperty::TRANSFORM));
+      std::move(curve), id, group,
+      cc::KeyframeModel::TargetPropertyId(TargetProperty::TRANSFORM)));
   return keyframe_model;
 }
 
@@ -37,7 +38,8 @@
       cc::SizeKeyframe::Create(base::TimeDelta(), from, nullptr));
   curve->AddKeyframe(cc::SizeKeyframe::Create(duration, to, nullptr));
   std::unique_ptr<cc::KeyframeModel> keyframe_model(cc::KeyframeModel::Create(
-      std::move(curve), id, group, TargetProperty::BOUNDS));
+      std::move(curve), id, group,
+      cc::KeyframeModel::TargetPropertyId(TargetProperty::BOUNDS)));
   return keyframe_model;
 }
 
@@ -53,7 +55,8 @@
       cc::FloatKeyframe::Create(base::TimeDelta(), from, nullptr));
   curve->AddKeyframe(cc::FloatKeyframe::Create(duration, to, nullptr));
   std::unique_ptr<cc::KeyframeModel> keyframe_model(cc::KeyframeModel::Create(
-      std::move(curve), id, group, TargetProperty::OPACITY));
+      std::move(curve), id, group,
+      cc::KeyframeModel::TargetPropertyId(TargetProperty::OPACITY)));
   return keyframe_model;
 }
 
@@ -69,7 +72,8 @@
       cc::ColorKeyframe::Create(base::TimeDelta(), from, nullptr));
   curve->AddKeyframe(cc::ColorKeyframe::Create(duration, to, nullptr));
   std::unique_ptr<cc::KeyframeModel> keyframe_model(cc::KeyframeModel::Create(
-      std::move(curve), id, group, TargetProperty::BACKGROUND_COLOR));
+      std::move(curve), id, group,
+      cc::KeyframeModel::TargetPropertyId(TargetProperty::BACKGROUND_COLOR)));
   return keyframe_model;
 }
 
diff --git a/chrome/browser/vr/ui_scene_creator.cc b/chrome/browser/vr/ui_scene_creator.cc
index 71f8023..48bd2b5 100644
--- a/chrome/browser/vr/ui_scene_creator.cc
+++ b/chrome/browser/vr/ui_scene_creator.cc
@@ -1005,7 +1005,8 @@
 
   e->AddKeyframeModel(cc::KeyframeModel::Create(
       std::move(curve), Animation::GetNextKeyframeModelId(),
-      Animation::GetNextGroupId(), TRANSFORM));
+      Animation::GetNextGroupId(),
+      cc::KeyframeModel::TargetPropertyId(TRANSFORM)));
 }
 
 #else
@@ -1090,7 +1091,8 @@
 
   e->AddKeyframeModel(cc::KeyframeModel::Create(
       std::move(curve), Animation::GetNextKeyframeModelId(),
-      Animation::GetNextGroupId(), TRANSFORM));
+      Animation::GetNextGroupId(),
+      cc::KeyframeModel::TargetPropertyId(TRANSFORM)));
 }
 
 #endif
diff --git a/chrome/browser/web_applications/web_app_mover_browsertest.cc b/chrome/browser/web_applications/web_app_mover_browsertest.cc
index 9a317d8..2548acf 100644
--- a/chrome/browser/web_applications/web_app_mover_browsertest.cc
+++ b/chrome/browser/web_applications/web_app_mover_browsertest.cc
@@ -30,7 +30,7 @@
     https_server_.AddDefaultHandlers(GetChromeTestDataDir());
     // Since the port is a part of the start_url, this needs to stay consistent
     // between the tests below.
-    CHECK(https_server_.Start(44221));
+    CHECK(https_server_.Start(16247));
     scoped_feature_list_.InitWithFeaturesAndParameters(
         {{features::kMoveWebApp,
           {{features::kMoveWebAppUninstallStartUrlPrefix.name,
diff --git a/chrome/browser/webshare/BUILD.gn b/chrome/browser/webshare/BUILD.gn
index c97e71b..72725dd9 100644
--- a/chrome/browser/webshare/BUILD.gn
+++ b/chrome/browser/webshare/BUILD.gn
@@ -4,10 +4,36 @@
 
 import("//build/config/chromeos/ui_mode.gni")
 
+static_library("storage") {
+  if (is_chromeos || is_mac) {
+    sources = [
+      "prepare_directory_task.cc",
+      "prepare_directory_task.h",
+      "store_file_task.cc",
+      "store_file_task.h",
+      "store_files_task.cc",
+      "store_files_task.h",
+    ]
+    deps = [
+      "//content/public/browser",
+      "//mojo/public/cpp/bindings",
+    ]
+    public_deps = [
+      "//base",
+      "//third_party/blink/public/common",
+    ]
+  }
+}
+
 source_set("unit_tests") {
   testonly = true
 
   sources = [ "share_service_unittest.cc" ]
+
+  if (is_chromeos || is_mac) {
+    sources += [ "prepare_directory_task_unittest.cc" ]
+  }
+
   deps = [
     "//base/test:test_support",
     "//build:chromeos_buildflags",
@@ -23,4 +49,8 @@
   if (is_chromeos_ash) {
     deps += [ "//chrome/browser/webshare/chromeos:unit_tests" ]
   }
+
+  if (is_chromeos || is_mac) {
+    deps += [ "//chrome/browser/webshare:storage" ]
+  }
 }
diff --git a/chrome/browser/webshare/OWNERS b/chrome/browser/webshare/OWNERS
index f19bd69..083fd1f 100644
--- a/chrome/browser/webshare/OWNERS
+++ b/chrome/browser/webshare/OWNERS
@@ -1,2 +1,4 @@
+cmp@chromium.org
+dmurph@chromium.org
 ericwilligers@chromium.org
 mgiuca@chromium.org
diff --git a/chrome/browser/webshare/chromeos/BUILD.gn b/chrome/browser/webshare/chromeos/BUILD.gn
index 94921943..d16fd28 100644
--- a/chrome/browser/webshare/chromeos/BUILD.gn
+++ b/chrome/browser/webshare/chromeos/BUILD.gn
@@ -2,39 +2,17 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-static_library("storage") {
-  sources = [
-    "prepare_directory_task.cc",
-    "prepare_directory_task.h",
-    "store_file_task.cc",
-    "store_file_task.h",
-    "store_files_task.cc",
-    "store_files_task.h",
-  ]
-  deps = [
-    "//content/public/browser",
-    "//mojo/public/cpp/bindings",
-  ]
-  public_deps = [
-    "//base",
-    "//third_party/blink/public/common",
-  ]
-}
-
 source_set("unit_tests") {
   testonly = true
 
-  sources = [
-    "prepare_directory_task_unittest.cc",
-    "sharesheet_client_unittest.cc",
-  ]
+  sources = [ "sharesheet_client_unittest.cc" ]
 
   deps = [
-    ":storage",
     "//base/test:test_support",
     "//build:chromeos_buildflags",
     "//chrome/browser",
     "//chrome/browser/chromeos:chromeos",
+    "//chrome/browser/webshare:storage",
     "//chrome/common",
     "//chrome/test:test_support",
     "//content/public/browser",
diff --git a/chrome/browser/webshare/chromeos/sharesheet_client.cc b/chrome/browser/webshare/chromeos/sharesheet_client.cc
index 03e314c..c7b40ed 100644
--- a/chrome/browser/webshare/chromeos/sharesheet_client.cc
+++ b/chrome/browser/webshare/chromeos/sharesheet_client.cc
@@ -20,9 +20,9 @@
 #include "chrome/browser/sharesheet/sharesheet_service.h"
 #include "chrome/browser/sharesheet/sharesheet_service_factory.h"
 #include "chrome/browser/visibility_timer_tab_helper.h"
-#include "chrome/browser/webshare/chromeos/prepare_directory_task.h"
-#include "chrome/browser/webshare/chromeos/store_files_task.h"
+#include "chrome/browser/webshare/prepare_directory_task.h"
 #include "chrome/browser/webshare/share_service_impl.h"
+#include "chrome/browser/webshare/store_files_task.h"
 #include "chrome/common/chrome_features.h"
 #include "components/services/app_service/public/cpp/intent_util.h"
 #include "content/public/browser/browser_context.h"
diff --git a/chrome/browser/webshare/chromeos/sharesheet_client_unittest.cc b/chrome/browser/webshare/chromeos/sharesheet_client_unittest.cc
index 3c1bf01..4414fee 100644
--- a/chrome/browser/webshare/chromeos/sharesheet_client_unittest.cc
+++ b/chrome/browser/webshare/chromeos/sharesheet_client_unittest.cc
@@ -17,8 +17,8 @@
 #include "chrome/browser/chromeos/file_manager/path_util.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/sharesheet/sharesheet_types.h"
-#include "chrome/browser/webshare/chromeos/prepare_directory_task.h"
-#include "chrome/browser/webshare/chromeos/store_file_task.h"
+#include "chrome/browser/webshare/prepare_directory_task.h"
+#include "chrome/browser/webshare/store_file_task.h"
 #include "chrome/test/base/chrome_render_view_host_test_harness.h"
 #include "content/public/browser/site_instance.h"
 #include "content/public/test/web_contents_tester.h"
diff --git a/chrome/browser/webshare/chromeos/prepare_directory_task.cc b/chrome/browser/webshare/prepare_directory_task.cc
similarity index 95%
rename from chrome/browser/webshare/chromeos/prepare_directory_task.cc
rename to chrome/browser/webshare/prepare_directory_task.cc
index ca5276e..7f148d7e 100644
--- a/chrome/browser/webshare/chromeos/prepare_directory_task.cc
+++ b/chrome/browser/webshare/prepare_directory_task.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/webshare/chromeos/prepare_directory_task.h"
+#include "chrome/browser/webshare/prepare_directory_task.h"
 
 #include "base/files/file.h"
 #include "base/files/file_enumerator.h"
@@ -10,8 +10,12 @@
 #include "base/task/task_traits.h"
 #include "base/task/thread_pool.h"
 #include "base/threading/scoped_blocking_call.h"
+#include "build/build_config.h"
 #include "content/public/browser/browser_thread.h"
+
+#if defined(OS_CHROMEOS)
 #include "third_party/cros_system_api/constants/cryptohome.h"
+#endif
 
 using content::BrowserThread;
 
@@ -80,12 +84,14 @@
       }
     }
 
+#if defined(OS_CHROMEOS)
     if (base::SysInfo::AmountOfFreeDiskSpace(directory) <
         static_cast<int64_t>(cryptohome::kMinFreeSpaceInBytes +
                              required_space)) {
       result = base::File::FILE_ERROR_NO_SPACE;
       VLOG(1) << "Insufficient space for sharing files";
     }
+#endif
   } else {
     DCHECK(result != base::File::FILE_OK);
     VLOG(1) << "Could not create directory for shared files";
diff --git a/chrome/browser/webshare/chromeos/prepare_directory_task.h b/chrome/browser/webshare/prepare_directory_task.h
similarity index 89%
rename from chrome/browser/webshare/chromeos/prepare_directory_task.h
rename to chrome/browser/webshare/prepare_directory_task.h
index 95c280a..c2c880f 100644
--- a/chrome/browser/webshare/chromeos/prepare_directory_task.h
+++ b/chrome/browser/webshare/prepare_directory_task.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_WEBSHARE_CHROMEOS_PREPARE_DIRECTORY_TASK_H_
-#define CHROME_BROWSER_WEBSHARE_CHROMEOS_PREPARE_DIRECTORY_TASK_H_
+#ifndef CHROME_BROWSER_WEBSHARE_PREPARE_DIRECTORY_TASK_H_
+#define CHROME_BROWSER_WEBSHARE_PREPARE_DIRECTORY_TASK_H_
 
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
@@ -51,4 +51,4 @@
 
 }  // namespace webshare
 
-#endif  // CHROME_BROWSER_WEBSHARE_CHROMEOS_PREPARE_DIRECTORY_TASK_H_
+#endif  // CHROME_BROWSER_WEBSHARE_PREPARE_DIRECTORY_TASK_H_
diff --git a/chrome/browser/webshare/chromeos/prepare_directory_task_unittest.cc b/chrome/browser/webshare/prepare_directory_task_unittest.cc
similarity index 96%
rename from chrome/browser/webshare/chromeos/prepare_directory_task_unittest.cc
rename to chrome/browser/webshare/prepare_directory_task_unittest.cc
index 0000684..3397f00 100644
--- a/chrome/browser/webshare/chromeos/prepare_directory_task_unittest.cc
+++ b/chrome/browser/webshare/prepare_directory_task_unittest.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/webshare/chromeos/prepare_directory_task.h"
+#include "chrome/browser/webshare/prepare_directory_task.h"
 
 #include <string>
 
diff --git a/chrome/browser/webshare/chromeos/store_file_task.cc b/chrome/browser/webshare/store_file_task.cc
similarity index 98%
rename from chrome/browser/webshare/chromeos/store_file_task.cc
rename to chrome/browser/webshare/store_file_task.cc
index a2aaa66..a8f6865 100644
--- a/chrome/browser/webshare/chromeos/store_file_task.cc
+++ b/chrome/browser/webshare/store_file_task.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/webshare/chromeos/store_file_task.h"
+#include "chrome/browser/webshare/store_file_task.h"
 
 #include "base/strings/string_util.h"
 #include "base/threading/scoped_blocking_call.h"
diff --git a/chrome/browser/webshare/chromeos/store_file_task.h b/chrome/browser/webshare/store_file_task.h
similarity index 91%
rename from chrome/browser/webshare/chromeos/store_file_task.h
rename to chrome/browser/webshare/store_file_task.h
index 31a6e2f..39b0fb2 100644
--- a/chrome/browser/webshare/chromeos/store_file_task.h
+++ b/chrome/browser/webshare/store_file_task.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_WEBSHARE_CHROMEOS_STORE_FILE_TASK_H_
-#define CHROME_BROWSER_WEBSHARE_CHROMEOS_STORE_FILE_TASK_H_
+#ifndef CHROME_BROWSER_WEBSHARE_STORE_FILE_TASK_H_
+#define CHROME_BROWSER_WEBSHARE_STORE_FILE_TASK_H_
 
 #include "base/files/file.h"
 #include "base/files/file_path.h"
@@ -63,4 +63,4 @@
 
 }  // namespace webshare
 
-#endif  // CHROME_BROWSER_WEBSHARE_CHROMEOS_STORE_FILE_TASK_H_
+#endif  // CHROME_BROWSER_WEBSHARE_STORE_FILE_TASK_H_
diff --git a/chrome/browser/webshare/chromeos/store_files_task.cc b/chrome/browser/webshare/store_files_task.cc
similarity index 96%
rename from chrome/browser/webshare/chromeos/store_files_task.cc
rename to chrome/browser/webshare/store_files_task.cc
index f053a9d4..4ad6dad 100644
--- a/chrome/browser/webshare/chromeos/store_files_task.cc
+++ b/chrome/browser/webshare/store_files_task.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/webshare/chromeos/store_files_task.h"
+#include "chrome/browser/webshare/store_files_task.h"
 
 #include "base/bind.h"
 #include "base/task/thread_pool.h"
diff --git a/chrome/browser/webshare/chromeos/store_files_task.h b/chrome/browser/webshare/store_files_task.h
similarity index 84%
rename from chrome/browser/webshare/chromeos/store_files_task.h
rename to chrome/browser/webshare/store_files_task.h
index 819043de..19ab698b 100644
--- a/chrome/browser/webshare/chromeos/store_files_task.h
+++ b/chrome/browser/webshare/store_files_task.h
@@ -2,15 +2,15 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_WEBSHARE_CHROMEOS_STORE_FILES_TASK_H_
-#define CHROME_BROWSER_WEBSHARE_CHROMEOS_STORE_FILES_TASK_H_
+#ifndef CHROME_BROWSER_WEBSHARE_STORE_FILES_TASK_H_
+#define CHROME_BROWSER_WEBSHARE_STORE_FILES_TASK_H_
 
 #include <memory>
 #include <vector>
 
 #include "base/files/file_path.h"
 #include "base/sequenced_task_runner.h"
-#include "chrome/browser/webshare/chromeos/store_file_task.h"
+#include "chrome/browser/webshare/store_file_task.h"
 #include "third_party/blink/public/mojom/webshare/webshare.mojom.h"
 
 namespace webshare {
@@ -45,4 +45,4 @@
 
 }  // namespace webshare
 
-#endif  // CHROME_BROWSER_WEBSHARE_CHROMEOS_STORE_FILES_TASK_H_
+#endif  // CHROME_BROWSER_WEBSHARE_STORE_FILES_TASK_H_
diff --git a/chrome/browser/xsurface/BUILD.gn b/chrome/browser/xsurface/BUILD.gn
index 63826464..ddc8791e 100644
--- a/chrome/browser/xsurface/BUILD.gn
+++ b/chrome/browser/xsurface/BUILD.gn
@@ -12,7 +12,6 @@
     "android/java/src/org/chromium/chrome/browser/xsurface/ImagePrefetcher.java",
     "android/java/src/org/chromium/chrome/browser/xsurface/ListContentManager.java",
     "android/java/src/org/chromium/chrome/browser/xsurface/ListContentManagerObserver.java",
-    "android/java/src/org/chromium/chrome/browser/xsurface/PersistentKeyValueCache.java",
     "android/java/src/org/chromium/chrome/browser/xsurface/ProcessScope.java",
     "android/java/src/org/chromium/chrome/browser/xsurface/ProcessScopeDependencyProvider.java",
     "android/java/src/org/chromium/chrome/browser/xsurface/SurfaceActionsHandler.java",
diff --git a/chrome/browser/xsurface/android/java/src/org/chromium/chrome/browser/xsurface/PersistentKeyValueCache.java b/chrome/browser/xsurface/android/java/src/org/chromium/chrome/browser/xsurface/PersistentKeyValueCache.java
deleted file mode 100644
index 154d8b6..0000000
--- a/chrome/browser/xsurface/android/java/src/org/chromium/chrome/browser/xsurface/PersistentKeyValueCache.java
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.xsurface;
-
-import androidx.annotation.Nullable;
-
-/** A simple key-value cache that is persisting all data on disk. Automatically evicts old data. */
-public interface PersistentKeyValueCache {
-    /** Consumes the result of PersistentKeyValueCache.lookup(). */
-    public interface ValueConsumer {
-        /**
-         * Called when a lookup is complete.
-         *
-         * @param value The value found. null if no value was present.
-         */
-        void run(@Nullable byte[] value);
-    }
-
-    /**
-     * Retrieves and returns the value associated with the given key if it exists.
-     * This does not affect the key/value's age for automatic eviction.
-     *
-     * @param key The key to look up.
-     * @param consumer The consumer called when the lookup is complete.
-     */
-    default void lookup(byte[] key, ValueConsumer consumer) {}
-
-    /**
-     * Inserts the given entry into the cache, overwriting any entry that might already exist
-     * against the given key.
-     *
-     * @param key The key to insert.
-     * @param value The value to insert.
-     * @param onComplete Called after the key/value was inserted.
-     */
-    default void put(byte[] key, byte[] value, @Nullable Runnable onComplete) {}
-
-    /**
-     * Evicts an entry from the cache by the specified key.
-     *
-     * @param key The key whose entry must be evicted.
-     * @param onComplete Called after the operation completes.
-     */
-    default void evict(byte[] key, @Nullable Runnable onComplete) {}
-}
diff --git a/chrome/browser/xsurface/android/java/src/org/chromium/chrome/browser/xsurface/ProcessScopeDependencyProvider.java b/chrome/browser/xsurface/android/java/src/org/chromium/chrome/browser/xsurface/ProcessScopeDependencyProvider.java
index 75fc160..68e13eb 100644
--- a/chrome/browser/xsurface/android/java/src/org/chromium/chrome/browser/xsurface/ProcessScopeDependencyProvider.java
+++ b/chrome/browser/xsurface/android/java/src/org/chromium/chrome/browser/xsurface/ProcessScopeDependencyProvider.java
@@ -58,11 +58,6 @@
         return null;
     }
 
-    @Nullable
-    default PersistentKeyValueCache getPersistentKeyValueCache() {
-        return null;
-    }
-
     // Posts task to the UI thread.
     int TASK_TYPE_UI_THREAD = 1;
     // Posts to a background thread. The task may block.
diff --git a/chrome/build/linux.pgo.txt b/chrome/build/linux.pgo.txt
index 18f5097..3f5ab7e 100644
--- a/chrome/build/linux.pgo.txt
+++ b/chrome/build/linux.pgo.txt
@@ -1 +1 @@
-chrome-linux-master-1610042255-43b4d84a21124c058546cf1e621ecfbb55dbe4e7.profdata
+chrome-linux-master-1610063718-75764fb6f7cc7a2d4d8119e0952784a5ac3fa72a.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index c95bdbd..498932ef 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-master-1610009713-66490881217dda72de1fa905fa3f863deeda7cfb.profdata
+chrome-win32-master-1610052964-246b43420c64aac53ecf14d1b97c19dc58b69e86.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index b37d1b5d..10b0b39b 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-master-1610042255-9fb87833d682e7ab89f65b59b49fe1c3c9960d61.profdata
+chrome-win64-master-1610052964-a33bb7f09ef1aa4be028cda8573fe902d004ec4a.profdata
diff --git a/chrome/common/chrome_features.cc b/chrome/common/chrome_features.cc
index 298bf428..5660057 100644
--- a/chrome/common/chrome_features.cc
+++ b/chrome/common/chrome_features.cc
@@ -923,7 +923,7 @@
 #endif  // defined(OS_WIN)
 
 const base::Feature kWindowNaming{"WindowNaming",
-                                  base::FEATURE_ENABLED_BY_DEFAULT};
+                                  base::FEATURE_DISABLED_BY_DEFAULT};
 
 // Enables writing basic system profile to the persistent histograms files
 // earlier.
diff --git a/chrome/services/printing/print_backend_service_impl.cc b/chrome/services/printing/print_backend_service_impl.cc
index e19ad94..3b3ba3f 100644
--- a/chrome/services/printing/print_backend_service_impl.cc
+++ b/chrome/services/printing/print_backend_service_impl.cc
@@ -10,10 +10,16 @@
 #include "base/logging.h"
 #include "base/notreached.h"
 #include "base/optional.h"
+#include "build/build_config.h"
 #include "chrome/services/printing/public/mojom/print_backend_service.mojom.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "printing/backend/print_backend.h"
 
+#if defined(OS_MAC)
+#include "base/threading/thread_restrictions.h"
+#include "chrome/common/printing/printer_capabilities_mac.h"
+#endif
+
 namespace printing {
 
 PrintBackendServiceImpl::PrintBackendServiceImpl(
@@ -58,4 +64,52 @@
   std::move(callback).Run(std::move(printer_caps));
 }
 
+void PrintBackendServiceImpl::FetchCapabilities(
+    const std::string& printer_name,
+    mojom::PrintBackendService::FetchCapabilitiesCallback callback) {
+  if (!print_backend_) {
+    std::move(callback).Run(base::nullopt, base::nullopt, base::nullopt);
+    return;
+  }
+
+  PrinterSemanticCapsAndDefaults::Papers user_defined_papers;
+#if defined(OS_MAC)
+  {
+    // Blocking is needed here for when macOS reads paper sizes from file.
+    //
+    // Fetching capabilities in the browser process happens from the thread
+    // pool with the MayBlock() trait for macOS.  However this call can also
+    // run from a utility process's main thread where blocking is not
+    // implicitly allowed.  In order to preserve ordering, the utility process
+    // must process this synchronously by blocking.
+    //
+    // TODO(crbug.com/1163635):  Investigate whether utility process main
+    // thread should be allowed to block like in-process workers are.
+    base::ScopedAllowBlocking allow_blocking;
+    user_defined_papers = GetMacCustomPaperSizes();
+  }
+#endif
+
+  PrinterBasicInfo printer_info;
+  bool result =
+      print_backend_->GetPrinterBasicInfo(printer_name, &printer_info);
+  if (!result) {
+    DLOG(ERROR) << "GetPrinterBasicInfo failed, last error is "
+                << logging::GetLastSystemErrorCode();
+    std::move(callback).Run(base::nullopt, base::nullopt, base::nullopt);
+    return;
+  }
+  PrinterSemanticCapsAndDefaults caps;
+  result =
+      print_backend_->GetPrinterSemanticCapsAndDefaults(printer_name, &caps);
+  if (!result) {
+    DLOG(ERROR) << "GetPrinterSemanticCapsAndDefaults failed, last error is "
+                << logging::GetLastSystemErrorCode();
+    std::move(callback).Run(base::nullopt, base::nullopt, base::nullopt);
+    return;
+  }
+  std::move(callback).Run(std::move(printer_info),
+                          std::move(user_defined_papers), std::move(caps));
+}
+
 }  // namespace printing
diff --git a/chrome/services/printing/print_backend_service_impl.h b/chrome/services/printing/print_backend_service_impl.h
index 21fba2f9..820e4c9 100644
--- a/chrome/services/printing/print_backend_service_impl.h
+++ b/chrome/services/printing/print_backend_service_impl.h
@@ -32,6 +32,10 @@
       base::OnceCallback<void(const base::Optional<std::string>& printer_name)>;
   using GetPrinterSemanticCapsAndDefaultsCallback = base::OnceCallback<void(
       base::Optional<PrinterSemanticCapsAndDefaults> printer_caps)>;
+  using FetchCapabilitiesCallback = base::OnceCallback<void(
+      base::Optional<PrinterBasicInfo>,
+      base::Optional<PrinterSemanticCapsAndDefaults::Papers>,
+      base::Optional<PrinterSemanticCapsAndDefaults>)>;
 
   // mojom::PrintBackendService implementation:
   void Init(const std::string& locale) override;
@@ -42,6 +46,9 @@
       const std::string& printer_name,
       mojom::PrintBackendService::GetPrinterSemanticCapsAndDefaultsCallback
           callback) override;
+  void FetchCapabilities(
+      const std::string& printer_name,
+      mojom::PrintBackendService::FetchCapabilitiesCallback callback) override;
 
   scoped_refptr<PrintBackend> print_backend_;
 
diff --git a/chrome/services/printing/public/mojom/print_backend_service.mojom b/chrome/services/printing/public/mojom/print_backend_service.mojom
index 85f41aa..36588eaf 100644
--- a/chrome/services/printing/public/mojom/print_backend_service.mojom
+++ b/chrome/services/printing/public/mojom/print_backend_service.mojom
@@ -23,4 +23,13 @@
   // No value provided for `printer_caps` if there is a failure.
   GetPrinterSemanticCapsAndDefaults(string printer_name)
     => (PrinterSemanticCapsAndDefaults? printer_caps);
+
+  // Gets the basic info, paper sizes, and semantic capabilities and defaults
+  // for a specific printer.
+  // No value for `printer_info`, `user_defined_papers`, and `printer_caps` if
+  // there is a failure.
+  FetchCapabilities(string printer_name)
+    => (PrinterBasicInfo? printer_info,
+        array<Paper>? user_defined_papers,
+        PrinterSemanticCapsAndDefaults? printer_caps);
 };
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 06da1dbc..4d91fff3 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -3238,6 +3238,7 @@
       "../browser/lacros/browser_test_util.cc",
       "../browser/lacros/browser_test_util.h",
       "../browser/lacros/clipboard_lacros_browsertest.cc",
+      "../browser/lacros/crosapi_pref_observer_lacros_browsertest.cc",
       "../browser/lacros/file_manager_lacros_browsertest.cc",
       "../browser/lacros/keystore_service_lacros_browsertest.cc",
       "../browser/lacros/media_session_lacros_browsertest.cc",
@@ -3575,11 +3576,9 @@
     "../browser/optimization_guide/optimization_guide_hints_manager_unittest.cc",
     "../browser/optimization_guide/optimization_guide_navigation_data_unittest.cc",
     "../browser/optimization_guide/optimization_guide_permissions_util_unittest.cc",
-    "../browser/optimization_guide/optimization_guide_session_statistic_unittest.cc",
     "../browser/optimization_guide/optimization_guide_top_host_provider_unittest.cc",
     "../browser/optimization_guide/prediction/prediction_manager_unittest.cc",
     "../browser/optimization_guide/prediction/prediction_model_download_manager_unittest.cc",
-    "../browser/optimization_guide/prediction/prediction_model_fetcher_unittest.cc",
     "../browser/page_load_metrics/metrics_web_contents_observer_unittest.cc",
     "../browser/page_load_metrics/observers/aborts_page_load_metrics_observer_unittest.cc",
     "../browser/page_load_metrics/observers/ad_metrics/ads_page_load_metrics_observer_unittest.cc",
@@ -4572,6 +4571,7 @@
       "../browser/send_tab_to_self/send_tab_to_self_client_service_unittest.cc",
       "../browser/send_tab_to_self/send_tab_to_self_desktop_util_unittest.cc",
       "../browser/send_tab_to_self/send_tab_to_self_util_unittest.cc",
+      "../browser/serial/serial_blocklist_unittest.cc",
       "../browser/serial/serial_chooser_context_unittest.cc",
       "../browser/sessions/tab_restore_service_unittest.cc",
       "../browser/signin/signin_promo_unittest.cc",
diff --git a/chrome/test/data/webui/cr_components/chromeos/cellular_setup/esim_flow_ui_test.js b/chrome/test/data/webui/cr_components/chromeos/cellular_setup/esim_flow_ui_test.js
index fc1af81..e8b367d 100644
--- a/chrome/test/data/webui/cr_components/chromeos/cellular_setup/esim_flow_ui_test.js
+++ b/chrome/test/data/webui/cr_components/chromeos/cellular_setup/esim_flow_ui_test.js
@@ -36,6 +36,11 @@
     Polymer.dom.flush();
   });
 
+  function assertSelectedPage(pageName, page) {
+    assertTrue(eSimPage.selectedESimPageName_ === pageName);
+    assertTrue(eSimPage.selectedESimPageName_ === page.id);
+  }
+
   test('No eSIM profile flow invalid activation code', async function() {
     eSimManagerRemote.addEuiccForTest(0);
     const availableEuiccs = await eSimManagerRemote.getAvailableEuiccs();
@@ -52,18 +57,14 @@
     assertTrue(!!finalPage);
 
     // Loading page should be showing.
-    assertTrue(
-        eSimPage.selectedESimPageName_ ===
-            cellular_setup.ESimPageName.PROFILE_LOADING &&
-        eSimPage.selectedESimPageName_ === profileLoadingPage.id);
+    assertSelectedPage(
+        cellular_setup.ESimPageName.PROFILE_LOADING, profileLoadingPage);
 
     await flushAsync();
 
     // Should now be at the activation code page.
-    assertTrue(
-        eSimPage.selectedESimPageName_ ===
-            cellular_setup.ESimPageName.ACTIVATION_CODE &&
-        eSimPage.selectedESimPageName_ === activationCodePage.id);
+    assertSelectedPage(
+        cellular_setup.ESimPageName.ACTIVATION_CODE, activationCodePage);
     // Insert an activation code.
     activationCodePage.$$('#activationCode').value = 'ACTIVATION_CODE';
 
@@ -77,10 +78,8 @@
     await flushAsync();
 
     // Install should fail and still be at activation code page.
-    assertTrue(
-        eSimPage.selectedESimPageName_ ===
-            cellular_setup.ESimPageName.ACTIVATION_CODE &&
-        eSimPage.selectedESimPageName_ === activationCodePage.id);
+    assertSelectedPage(
+        cellular_setup.ESimPageName.ACTIVATION_CODE, activationCodePage);
     assertTrue(activationCodePage.$$('#scanSuccessContainer').hidden);
     assertFalse(activationCodePage.$$('#scanFailureContainer').hidden);
   });
@@ -97,18 +96,14 @@
     assertTrue(!!finalPage);
 
     // Loading page should be showing.
-    assertTrue(
-        eSimPage.selectedESimPageName_ ===
-            cellular_setup.ESimPageName.PROFILE_LOADING &&
-        eSimPage.selectedESimPageName_ === profileLoadingPage.id);
+    assertSelectedPage(
+        cellular_setup.ESimPageName.PROFILE_LOADING, profileLoadingPage);
 
     await flushAsync();
 
     // Should now be at the activation code page.
-    assertTrue(
-        eSimPage.selectedESimPageName_ ===
-            cellular_setup.ESimPageName.ACTIVATION_CODE &&
-        eSimPage.selectedESimPageName_ === activationCodePage.id);
+    assertSelectedPage(
+        cellular_setup.ESimPageName.ACTIVATION_CODE, activationCodePage);
     // Insert an activation code.
     activationCodePage.$$('#activationCode').value = 'ACTIVATION_CODE';
 
@@ -122,9 +117,7 @@
     await flushAsync();
 
     // Should go to final page.
-    assertTrue(
-        eSimPage.selectedESimPageName_ === cellular_setup.ESimPageName.FINAL &&
-        eSimPage.selectedESimPageName_ === finalPage.id);
+    assertSelectedPage(cellular_setup.ESimPageName.FINAL, finalPage);
   });
 
   test('Single eSIM profile flow successful install', async function() {
@@ -137,17 +130,13 @@
     assertTrue(!!finalPage);
 
     // Loading page should be showing.
-    assertTrue(
-        eSimPage.selectedESimPageName_ ===
-            cellular_setup.ESimPageName.PROFILE_LOADING &&
-        eSimPage.selectedESimPageName_ === profileLoadingPage.id);
+    assertSelectedPage(
+        cellular_setup.ESimPageName.PROFILE_LOADING, profileLoadingPage);
 
     await flushAsync();
 
     // Should go directly to final page.
-    assertTrue(
-        eSimPage.selectedESimPageName_ === cellular_setup.ESimPageName.FINAL &&
-        eSimPage.selectedESimPageName_ === finalPage.id);
+    assertSelectedPage(cellular_setup.ESimPageName.FINAL, finalPage);
     assertFalse(!!finalPage.$$('.error'));
   });
 
@@ -165,20 +154,41 @@
     assertTrue(!!finalPage);
 
     // Loading page should be showing.
-    assertTrue(
-        eSimPage.selectedESimPageName_ ===
-            cellular_setup.ESimPageName.PROFILE_LOADING &&
-        eSimPage.selectedESimPageName_ === profileLoadingPage.id);
+    assertSelectedPage(
+        cellular_setup.ESimPageName.PROFILE_LOADING, profileLoadingPage);
 
     await flushAsync();
 
     // Should go directly to final page.
-    assertTrue(
-        eSimPage.selectedESimPageName_ === cellular_setup.ESimPageName.FINAL &&
-        eSimPage.selectedESimPageName_ === finalPage.id);
+    assertSelectedPage(cellular_setup.ESimPageName.FINAL, finalPage);
     assertTrue(!!finalPage.$$('.error'));
   });
 
+  test('Single eSIM profile flow confirmation code required', async function() {
+    eSimManagerRemote.addEuiccForTest(1);
+    const availableEuiccs = await eSimManagerRemote.getAvailableEuiccs();
+    const profileList = await availableEuiccs.euiccs[0].getProfileList();
+    profileList.profiles[0].setProfileInstallResultForTest(
+        chromeos.cellularSetup.mojom.ProfileInstallResult
+            .kErrorNeedsConfirmationCode);
+
+    const profileLoadingPage = eSimPage.$$('#profileLoadingPage');
+    const confirmationCodePage = eSimPage.$$('#confirmationCodePage');
+
+    assertTrue(!!profileLoadingPage);
+    assertTrue(!!confirmationCodePage);
+
+    // Loading page should be showing.
+    assertSelectedPage(
+        cellular_setup.ESimPageName.PROFILE_LOADING, profileLoadingPage);
+
+    await flushAsync();
+
+    // Confirmation code page should be showing.
+    assertSelectedPage(
+        cellular_setup.ESimPageName.CONFIRMATION_CODE, confirmationCodePage);
+  });
+
   test('Multiple eSIM profiles skip discovery flow', async function() {
     eSimManagerRemote.addEuiccForTest(2);
 
@@ -193,18 +203,14 @@
     assertTrue(!!finalPage);
 
     // Loading page should be showing.
-    assertTrue(
-        eSimPage.selectedESimPageName_ ===
-            cellular_setup.ESimPageName.PROFILE_LOADING &&
-        eSimPage.selectedESimPageName_ === profileLoadingPage.id);
+    assertSelectedPage(
+        cellular_setup.ESimPageName.PROFILE_LOADING, profileLoadingPage);
 
     await flushAsync();
 
     // Should go to profile discovery page.
-    assertTrue(
-        eSimPage.selectedESimPageName_ ===
-            cellular_setup.ESimPageName.PROFILE_DISCOVERY &&
-        eSimPage.selectedESimPageName_ === profileDiscoveryPage.id);
+    assertSelectedPage(
+        cellular_setup.ESimPageName.PROFILE_DISCOVERY, profileDiscoveryPage);
 
     // Simulate pressing 'Skip'.
     assertTrue(
@@ -214,10 +220,8 @@
     Polymer.dom.flush();
 
     // Should now be at the activation code page.
-    assertTrue(
-        eSimPage.selectedESimPageName_ ===
-            cellular_setup.ESimPageName.ACTIVATION_CODE &&
-        eSimPage.selectedESimPageName_ === activationCodePage.id);
+    assertSelectedPage(
+        cellular_setup.ESimPageName.ACTIVATION_CODE, activationCodePage);
 
     // Insert an activation code.
     activationCodePage.$$('#activationCode').value = 'ACTIVATION_CODE';
@@ -230,9 +234,7 @@
     await flushAsync();
 
     // Should now be at the final page.
-    assertTrue(
-        eSimPage.selectedESimPageName_ === cellular_setup.ESimPageName.FINAL &&
-        eSimPage.selectedESimPageName_ === finalPage.id);
+    assertSelectedPage(cellular_setup.ESimPageName.FINAL, finalPage);
   });
 
   test('Multiple eSIM profiles select flow', async function() {
@@ -249,18 +251,14 @@
     assertTrue(!!finalPage);
 
     // Loading page should be showing.
-    assertTrue(
-        eSimPage.selectedESimPageName_ ===
-            cellular_setup.ESimPageName.PROFILE_LOADING &&
-        eSimPage.selectedESimPageName_ === profileLoadingPage.id);
+    assertSelectedPage(
+        cellular_setup.ESimPageName.PROFILE_LOADING, profileLoadingPage);
 
     await flushAsync();
 
     // Should go to profile discovery page.
-    assertTrue(
-        eSimPage.selectedESimPageName_ ===
-            cellular_setup.ESimPageName.PROFILE_DISCOVERY &&
-        eSimPage.selectedESimPageName_ === profileDiscoveryPage.id);
+    assertSelectedPage(
+        cellular_setup.ESimPageName.PROFILE_DISCOVERY, profileDiscoveryPage);
 
     // Select the first profile on the list.
     const profileList = profileDiscoveryPage.$$('#profileList');
@@ -280,9 +278,59 @@
     await flushAsync();
 
     // Should now be at the final page.
-    assertTrue(
-        eSimPage.selectedESimPageName_ === cellular_setup.ESimPageName.FINAL &&
-        eSimPage.selectedESimPageName_ === finalPage.id);
+    assertSelectedPage(cellular_setup.ESimPageName.FINAL, finalPage);
     assertFalse(!!finalPage.$$('.error'));
   });
+
+  test(
+      'Multiple eSIM profiles select flow confirmation code required',
+      async function() {
+        eSimManagerRemote.addEuiccForTest(2);
+        const availableEuiccs = await eSimManagerRemote.getAvailableEuiccs();
+        const profileList = await availableEuiccs.euiccs[0].getProfileList();
+        profileList.profiles[0].setProfileInstallResultForTest(
+            chromeos.cellularSetup.mojom.ProfileInstallResult
+                .kErrorNeedsConfirmationCode);
+
+        const profileLoadingPage = eSimPage.$$('#profileLoadingPage');
+        const profileDiscoveryPage = eSimPage.$$('#profileDiscoveryPage');
+        const confirmationCodePage = eSimPage.$$('#confirmationCodePage');
+
+        assertTrue(!!profileLoadingPage);
+        assertTrue(!!profileDiscoveryPage);
+        assertTrue(!!confirmationCodePage);
+
+        // Loading page should be showing.
+        assertSelectedPage(
+            cellular_setup.ESimPageName.PROFILE_LOADING, profileLoadingPage);
+
+        await flushAsync();
+
+        // Should go to profile discovery page.
+        assertSelectedPage(
+            cellular_setup.ESimPageName.PROFILE_DISCOVERY,
+            profileDiscoveryPage);
+
+        // Select the first profile on the list.
+        const profileListUI = profileDiscoveryPage.$$('#profileList');
+        profileListUI.selectItem(profileListUI.items[0]);
+        Polymer.dom.flush();
+
+        // The 'Next' button should now be enabled.
+        assertTrue(
+            eSimPage.buttonState.next ===
+            cellularSetup.ButtonState.SHOWN_AND_ENABLED);
+        assertTrue(
+            eSimPage.buttonState.skipDiscovery ===
+            cellularSetup.ButtonState.HIDDEN);
+
+        // Simulate pressing 'Next'.
+        eSimPage.navigateForward();
+        await flushAsync();
+
+        // Confirmation code page should be showing.
+        assertSelectedPage(
+            cellular_setup.ESimPageName.CONFIRMATION_CODE,
+            confirmationCodePage);
+      });
 });
diff --git a/chrome/test/data/webui/cr_components/chromeos/cellular_setup/fake_esim_manager_remote.js b/chrome/test/data/webui/cr_components/chromeos/cellular_setup/fake_esim_manager_remote.js
index cae2dc30..62864ab2 100644
--- a/chrome/test/data/webui/cr_components/chromeos/cellular_setup/fake_esim_manager_remote.js
+++ b/chrome/test/data/webui/cr_components/chromeos/cellular_setup/fake_esim_manager_remote.js
@@ -44,6 +44,10 @@
      *     chromeos.cellularSetup.mojom.ProfileInstallResult},}>}
      */
     installProfile(confirmationCode) {
+      this.properties_.state =
+          chromeos.cellularSetup.mojom.ProfileState.kActive;
+      this.fakeEuicc_.notifyProfileChangedForTest(this);
+      this.fakeEuicc_.notifyProfileListChangedForTest();
       return Promise.resolve({
         result: this.profileInstallResult_ ?
             this.profileInstallResult_ :
@@ -119,6 +123,7 @@
 
     /** @override */
     uninstallProfile() {
+      this.fakeEuicc_.notifyProfileChangedForTest(this);
       this.defferedUninstallProfilePromise_ = this.deferredPromise_();
       return this.defferedUninstallProfilePromise_.promise;
     }
@@ -144,7 +149,8 @@
 
   /** @implements {chromeos.cellularSetup.mojom.Euicc} */
   class FakeEuicc {
-    constructor(numProfiles) {
+    constructor(numProfiles, fakeESimManager) {
+      this.fakeESimManager_ = fakeESimManager;
       this.profiles_ = [];
       for (let i = 0; i < numProfiles; i++) {
         this.addProfileForTest_();
@@ -180,6 +186,7 @@
      *     chromeos.cellularSetup.mojom.ProfileInstallResult},}>}
      */
     installProfileFromActivationCode(activationCode, confirmationCode) {
+      this.notifyProfileListChangedForTest();
       return Promise.resolve({
         result: this.profileInstallResult_ ?
             this.profileInstallResult_ :
@@ -218,6 +225,7 @@
       this.profiles_ = result;
 
       if (profileRemoved) {
+        this.notifyProfileListChangedForTest();
         return {
           result: chromeos.cellularSetup.mojom.ESimOperationResult.kSuccess
         };
@@ -226,12 +234,24 @@
         result: chromeos.cellularSetup.mojom.ESimOperationResult.kFailure
       };
     }
+
+    /**
+     * @param {FakeProfile} profile
+     */
+    notifyProfileChangedForTest(profile) {
+      this.fakeESimManager_.notifyProfileChangedForTest(profile);
+    }
+
+    notifyProfileListChangedForTest() {
+      this.fakeESimManager_.notifyProfileListChangedForTest(this);
+    }
   }
 
   /** @implements {chromeos.cellularSetup.mojom.ESimManagerInterface} */
   /* #export */ class FakeESimManagerRemote {
     constructor() {
       this.euiccs_ = [];
+      this.observers_ = [];
     }
 
     /**
@@ -248,9 +268,35 @@
      * @param {number} numProfiles The number of profiles the EUICC has.
      */
     addEuiccForTest(numProfiles) {
-      const euicc = new FakeEuicc(numProfiles);
+      const euicc = new FakeEuicc(numProfiles, this);
       this.euiccs_.push(euicc);
     }
+
+    /**
+     * @param {!chromeos.cellularSetup.mojom.ESimManagerObserverInterface}
+     *     observer
+     */
+    addObserver(observer) {
+      this.observers_.push(observer);
+    }
+
+    /**
+     * @param {FakeEuicc} euicc
+     */
+    notifyProfileListChangedForTest(euicc) {
+      for (const observer of this.observers_) {
+        observer.onProfileListChanged(euicc);
+      }
+    }
+
+    /**
+     * @param {FakeProfile} profile
+     */
+    notifyProfileChangedForTest(profile) {
+      for (const observer of this.observers_) {
+        observer.onProfileChanged(profile);
+      }
+    }
   }
 
   // #cr_define_end
diff --git a/chrome/test/data/webui/cr_components/chromeos/network/network_list_item_test.js b/chrome/test/data/webui/cr_components/chromeos/network/network_list_item_test.js
index 4ff7ff8b..675a1407 100644
--- a/chrome/test/data/webui/cr_components/chromeos/network/network_list_item_test.js
+++ b/chrome/test/data/webui/cr_components/chromeos/network/network_list_item_test.js
@@ -94,7 +94,7 @@
   });
 
   test(
-      'Pending eSIM profile name, provider, download button visibilty',
+      'Pending eSIM profile name, provider, install button visibilty',
       async () => {
         const itemName = 'Item Name';
         const itemSubtitle = 'Item Subtitle';
@@ -118,7 +118,44 @@
         assertTrue(!!subtitle);
         assertEquals(itemSubtitle, subtitle.textContent.trim());
 
-        let downloadButton = listItem.$$('#downloadButton');
-        assertTrue(!!downloadButton);
+        let installButton = listItem.$$('#installButton');
+        assertTrue(!!installButton);
+
+        let installProfileEventIccid = null;
+        listItem.addEventListener('install-profile', (event) => {
+          installProfileEventIccid = event.detail.iccid;
+        });
+        installButton.click();
+
+        await flushAsync();
+        assertEquals(installProfileEventIccid, 'iccid');
+      });
+
+  test(
+      'Installing eSIM profile name, provider, spinner visibilty', async () => {
+        const itemName = 'Item Name';
+        const itemSubtitle = 'Item Subtitle';
+        listItem.item = {
+          customItemType: NetworkList.CustomItemType.ESIM_INSTALLING_PROFILE,
+          customItemName: itemName,
+          customItemSubtitle: itemSubtitle,
+          polymerIcon: 'network:cellular-0',
+          showBeforeNetworksList: false,
+          customData: {
+            iccid: 'iccid',
+          },
+        };
+        await flushAsync();
+
+        let networkName = listItem.$$('#networkName');
+        assertTrue(!!networkName);
+        assertEquals(itemName, networkName.textContent.trim());
+
+        let subtitle = listItem.$$('#subtitle');
+        assertTrue(!!subtitle);
+        assertEquals(itemSubtitle, subtitle.textContent.trim());
+
+        let spinner = listItem.$$('paper-spinner-lite');
+        assertTrue(!!spinner);
       });
 });
diff --git a/chrome/test/data/webui/settings/chromeos/cellular_networks_list_test.js b/chrome/test/data/webui/settings/chromeos/cellular_networks_list_test.js
index e5fd658..459fbd3 100644
--- a/chrome/test/data/webui/settings/chromeos/cellular_networks_list_test.js
+++ b/chrome/test/data/webui/settings/chromeos/cellular_networks_list_test.js
@@ -32,7 +32,9 @@
 
     eSimManagerRemote = new cellular_setup.FakeESimManagerRemote();
     cellular_setup.setESimManagerRemoteForTesting(eSimManagerRemote);
+  });
 
+  function init() {
     cellularNetworkList = document.createElement('cellular-networks-list');
     // iron-list will not create list items if the container of the list is of
     // size zero.
@@ -40,7 +42,7 @@
     cellularNetworkList.style.width = '100%';
     document.body.appendChild(cellularNetworkList);
     Polymer.dom.flush();
-  });
+  }
 
   function setNetworksForTest(type, networks) {
     mojoApi_.resetForTest();
@@ -57,6 +59,8 @@
   }
 
   test('Tether, cellular and eSIM profiles', async () => {
+    init();
+
     const eSimNetwork1 = OncMojo.getDefaultNetworkState(
         mojom.NetworkType.kCellular, 'cellular_esim1');
     const eSimNetwork2 = OncMojo.getDefaultNetworkState(
@@ -102,10 +106,12 @@
   test(
       'Fire show cellular setup event on eSim/psim no network link click',
       async () => {
+        eSimManagerRemote.addEuiccForTest(0);
+        init();
+
         setNetworksForTest(mojom.NetworkType.kCellular, [
           OncMojo.getDefaultNetworkState(mojom.NetworkType.kTether, 'tether1'),
         ]);
-        eSimManagerRemote.addEuiccForTest(0);
         Polymer.dom.flush();
 
         await flushAsync();
@@ -139,8 +145,8 @@
       });
 
   test('Show EID and QR code popup', async () => {
-    eSimManagerRemote.addEuiccForTest(0);
-
+    eSimManagerRemote.addEuiccForTest(1);
+    init();
     let eidPopup = cellularNetworkList.$$('.eid-popup');
     assertFalse(!!eidPopup);
     const eidPopupBtn = cellularNetworkList.$$('#eidPopupButton');
@@ -152,4 +158,42 @@
     eidPopup = cellularNetworkList.$$('.eid-popup');
     assertTrue(!!eidPopup);
   });
+
+  test('Install pending eSIM profile', async () => {
+    eSimManagerRemote.addEuiccForTest(1);
+    init();
+    await flushAsync();
+
+    let eSimNetworkList = cellularNetworkList.$$('#esimNetworkList');
+    assertTrue(!!eSimNetworkList);
+    assertEquals(1, cellularNetworkList.eSimPendingProfileItems_.length);
+
+    const listItem = eSimNetworkList.$$('network-list-item');
+    assertTrue(!!listItem);
+    const installButton = listItem.$$('#installButton');
+    assertTrue(!!installButton);
+    installButton.click();
+
+    await flushAsync();
+
+    // eSIM network list should now be hidden and link showing.
+    eSimNetworkList = cellularNetworkList.$$('#esimNetworkList');
+    assertFalse(!!eSimNetworkList);
+    const esimNoNetworkAnchor = cellularNetworkList.$$('#eSimNoNetworkFound')
+                                    .querySelector('settings-localized-link')
+                                    .shadowRoot.querySelector('a');
+    assertTrue(!!esimNoNetworkAnchor);
+  });
+  test('Hide esim section when no EUICC is found', async () => {
+    setNetworksForTest(mojom.NetworkType.kCellular, [
+      OncMojo.getDefaultNetworkState(mojom.NetworkType.kTether, 'tether1'),
+    ]);
+    init();
+    Polymer.dom.flush();
+    await flushAsync();
+    const esimNetworkList = cellularNetworkList.$$('#esimNetworkList');
+
+    assertFalse(!!esimNetworkList);
+  });
+
 });
diff --git a/chromecast/media/cma/backend/mixer/post_processors/post_processor_unittest.cc b/chromecast/media/cma/backend/mixer/post_processors/post_processor_unittest.cc
index 3de040e2..1097c67 100644
--- a/chromecast/media/cma/backend/mixer/post_processors/post_processor_unittest.cc
+++ b/chromecast/media/cma/backend/mixer/post_processors/post_processor_unittest.cc
@@ -166,7 +166,10 @@
   int frames_remaining = status.ringing_time_frames;
   int frames_to_process = std::min(frames_remaining, kNumFrames);
   while (frames_remaining > 0) {
+    // Make sure |frames_to_process| is an even multiple of 8.
     frames_to_process = std::min(frames_to_process, frames_remaining);
+    frames_to_process = (frames_to_process + 7);
+    frames_to_process -= frames_to_process % 8;
     data.assign(frames_to_process * num_input_channels, 0);
     pp->ProcessFrames(data.data(), frames_to_process, &metadata);
     frames_remaining -= frames_to_process;
diff --git a/chromecast/media/cma/decoder/cast_audio_decoder.cc b/chromecast/media/cma/decoder/cast_audio_decoder.cc
index 90f370f..51d1e0f 100644
--- a/chromecast/media/cma/decoder/cast_audio_decoder.cc
+++ b/chromecast/media/cma/decoder/cast_audio_decoder.cc
@@ -266,8 +266,8 @@
     auto result = base::MakeRefCounted<::media::DecoderBuffer>(size);
 
     if (output_format_ == kOutputSigned16) {
-      bus->ToInterleaved(num_frames, OutputFormatSizeInBytes(output_format_),
-                         result->writable_data());
+      bus->ToInterleaved<::media::SignedInt16SampleTypeTraits>(
+          num_frames, reinterpret_cast<int16_t*>(result->writable_data()));
     } else if (output_format_ == kOutputPlanarFloat) {
       // Data in an AudioBus is already in planar float format; just copy each
       // channel into the result buffer in order.
diff --git a/chromeos/components/cdm_factory_daemon/chromeos_cdm_factory.cc b/chromeos/components/cdm_factory_daemon/chromeos_cdm_factory.cc
index f977aff..8afa66b 100644
--- a/chromeos/components/cdm_factory_daemon/chromeos_cdm_factory.cc
+++ b/chromeos/components/cdm_factory_daemon/chromeos_cdm_factory.cc
@@ -227,11 +227,14 @@
           &GetOutputProtectionOnTaskRunner,
           output_protection_remote.InitWithNewPipeAndPassReceiver()));
 
+  url::Origin cdm_origin;
+  frame_interfaces_->GetCdmOrigin(&cdm_origin);
+
   // Now create the remote CDM instance that links everything up.
-  remote_factory_->CreateCdm(cdm->GetClientInterface(),
-                             std::move(storage_remote),
-                             std::move(cros_cdm_pending_receiver),
-                             std::move(output_protection_remote));
+  remote_factory_->CreateCdm(
+      cdm->GetClientInterface(), std::move(storage_remote),
+      std::move(output_protection_remote), cdm_origin.host(),
+      std::move(cros_cdm_pending_receiver));
 
   base::ThreadTaskRunnerHandle::Get()->PostTask(
       FROM_HERE, base::BindOnce(std::move(cdm_created_cb), std::move(cdm), ""));
diff --git a/chromeos/components/cdm_factory_daemon/mojom/cdm_factory_daemon.mojom b/chromeos/components/cdm_factory_daemon/mojom/cdm_factory_daemon.mojom
index 86a834e..281f92a 100644
--- a/chromeos/components/cdm_factory_daemon/mojom/cdm_factory_daemon.mojom
+++ b/chromeos/components/cdm_factory_daemon/mojom/cdm_factory_daemon.mojom
@@ -9,7 +9,7 @@
 // interface can also be used to connect directly to the OEMCrypto
 // implementation for ARC.
 
-// Next MinVersion: 3
+// Next MinVersion: 4
 
 module chromeos.cdm.mojom;
 
@@ -22,20 +22,23 @@
 // Next Method ID: 2
 interface CdmFactory {
   // Deprecated, do not use.
-  CreateCdmDeprecated@0(
-      pending_associated_remote<ContentDecryptionModuleClient> client,
-      pending_associated_remote<CdmStorage> storage,
-      pending_associated_receiver<ContentDecryptionModule> cdm);
-
-  // Creates a new ContentDecryptionModule instance with the corresponding
-  // client, remote storage implementation and output protection. Use an
-  // associated interface to ensure ordering and that all become invalidated at
-  // the same time.
   [MinVersion=1]
-  CreateCdm@1(pending_associated_remote<ContentDecryptionModuleClient> client,
+  CreateCdmDeprecated@1(
+              pending_associated_remote<ContentDecryptionModuleClient> client,
               pending_associated_remote<CdmStorage> storage,
               pending_associated_receiver<ContentDecryptionModule> cdm,
               pending_remote<OutputProtection> output_protection);
+
+  // Creates a new ContentDecryptionModule instance for a |host| with the
+  // corresponding |client|, remote |storage| implementation and
+  // |output_protection|. Use an associated interface to ensure ordering and
+  // that all become invalidated at the same time.
+  [MinVersion=3]
+  CreateCdm@2(pending_associated_remote<ContentDecryptionModuleClient> client,
+              pending_associated_remote<CdmStorage> storage,
+              pending_remote<OutputProtection> output_protection,
+              string host,
+              pending_associated_receiver<ContentDecryptionModule> cdm);
 };
 
 // Next Method ID: 5
diff --git a/chromeos/components/help_app_ui/resources/browser_proxy.js b/chromeos/components/help_app_ui/resources/browser_proxy.js
index 3db70b1..ca0b96b 100644
--- a/chromeos/components/help_app_ui/resources/browser_proxy.js
+++ b/chromeos/components/help_app_ui/resources/browser_proxy.js
@@ -102,7 +102,7 @@
           locale: searchable_item.locale,
         };
       });
-      indexRemote.addOrUpdate(data_to_send);
+      return indexRemote.addOrUpdate(data_to_send);
     });
 
 guestMessagePipe.registerHandler(Message.CLEAR_SEARCH_INDEX, async () => {
diff --git a/chromeos/components/proximity_auth/proximity_auth_profile_pref_manager.cc b/chromeos/components/proximity_auth/proximity_auth_profile_pref_manager.cc
index d8cd04f..9f9bcce 100644
--- a/chromeos/components/proximity_auth/proximity_auth_profile_pref_manager.cc
+++ b/chromeos/components/proximity_auth/proximity_auth_profile_pref_manager.cc
@@ -175,7 +175,10 @@
     bool has_shown) {
   // This is persisted within SyncPrefsToLocalState() instead, since the local
   // state must act as the source of truth for this pref.
-  NOTREACHED();
+  
+  // TODO(crbug.com/1152491): Add a NOTREACHED() to ensure this method is not
+  // called. It is currently incorrectly, though harmlessly, called by virtual
+  // Chrome OS on Linux.
 }
 
 bool ProximityAuthProfilePrefManager::HasShownLoginDisabledMessage() const {
diff --git a/chromeos/crosapi/mojom/BUILD.gn b/chromeos/crosapi/mojom/BUILD.gn
index 562e6b9..446dd77 100644
--- a/chromeos/crosapi/mojom/BUILD.gn
+++ b/chromeos/crosapi/mojom/BUILD.gn
@@ -17,6 +17,7 @@
     "message_center.mojom",
     "metrics_reporting.mojom",
     "notification.mojom",
+    "prefs.mojom",
     "screen_manager.mojom",
     "select_file.mojom",
     "test_controller.mojom",
diff --git a/chromeos/crosapi/mojom/crosapi.mojom b/chromeos/crosapi/mojom/crosapi.mojom
index d6586d4c5..ebdb116 100644
--- a/chromeos/crosapi/mojom/crosapi.mojom
+++ b/chromeos/crosapi/mojom/crosapi.mojom
@@ -12,6 +12,7 @@
 import "chromeos/crosapi/mojom/keystore_service.mojom";
 import "chromeos/crosapi/mojom/message_center.mojom";
 import "chromeos/crosapi/mojom/metrics_reporting.mojom";
+import "chromeos/crosapi/mojom/prefs.mojom";
 import "chromeos/crosapi/mojom/screen_manager.mojom";
 import "chromeos/crosapi/mojom/select_file.mojom";
 import "chromeos/crosapi/mojom/test_controller.mojom";
@@ -43,8 +44,8 @@
 // milestone when you added it, to help us reason about compatibility between
 // lacros-chrome and older ash-chrome binaries.
 //
-// Next version: 11
-// Next method id: 16
+// Next version: 12
+// Next method id: 17
 [Stable, Uuid="8b79c34f-2bf8-4499-979a-b17cac522c1e"]
 interface AshChromeService {
   // Binds Chrome OS Account Manager for Identity management.
@@ -79,6 +80,10 @@
   [MinVersion=8]
   BindMetricsReporting@13(pending_receiver<MetricsReporting> receiver);
 
+  // Binds the prefs service which allows get, set, and notify update of prefs.
+  // Added in M89.
+  [MinVersion=11] BindPrefs@16(pending_receiver<Prefs> receiver);
+
   // Binds the ScreenManager interface for interacting with windows, screens and
   // displays.
   // Added in M86.
diff --git a/chromeos/crosapi/mojom/prefs.mojom b/chromeos/crosapi/mojom/prefs.mojom
new file mode 100644
index 0000000..66c8e95
--- /dev/null
+++ b/chromeos/crosapi/mojom/prefs.mojom
@@ -0,0 +1,44 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+module crosapi.mojom;
+
+import "mojo/public/mojom/base/values.mojom";
+
+// Pref paths.
+[Stable, Extensible]
+enum PrefPath {
+  kUnknown = 0,
+  // M89: metrics::prefs::kMetricsReportingEnabled (local state).
+  kMetricsReportingEnabled = 1,
+  // M89: ash::prefs::kAccessibilitySpokenFeedbackEnabled (profile).
+  kAccessibilitySpokenFeedbackEnabled = 2,
+};
+
+// Interface for pref observers. Implemented by lacros-chrome. Used by
+// ash-chrome to send pref updates.
+[Stable, Uuid="07d804d1-3d8d-4da1-b9b9-05f8a6bfe4c4"]
+interface PrefObserver {
+  // Called when an observed pref changes.
+  OnPrefChanged@0(mojo_base.mojom.Value value);
+};
+
+// Interface for prefs. Implemented by ash-chrome.
+// Next version: 1
+// Next method id: 3
+[Stable, Uuid="815df607-0596-46f7-9ed9-14683b4826a3"]
+interface Prefs {
+  // Gets the specified ash pref. Returns empty if ash-chrome does not have path
+  // registered, or if the pref is not found.
+  GetPref@0(PrefPath path) => (mojo_base.mojom.Value? value);
+
+  // Sets the specified ash pref. Does nothing if ash-chrome does not have path
+  // registered.
+  SetPref@1(PrefPath path, mojo_base.mojom.Value value) => ();
+
+  // Adds an observer for ash pref. The observer is fired immediately with the
+  // current value. Multiple observers may be registered for any given pref.
+  // Does nothing if ash-chrome does not have path registered.
+  AddObserver@2(PrefPath path, pending_remote<PrefObserver> observer);
+};
diff --git a/chromeos/lacros/lacros_chrome_service_impl.cc b/chromeos/lacros/lacros_chrome_service_impl.cc
index dbc677f..1bebf67 100644
--- a/chromeos/lacros/lacros_chrome_service_impl.cc
+++ b/chromeos/lacros/lacros_chrome_service_impl.cc
@@ -244,6 +244,12 @@
     ash_chrome_service_->BindMetricsReporting(std::move(receiver));
   }
 
+  void BindPrefsReceiver(
+      mojo::PendingReceiver<crosapi::mojom::Prefs> pending_receiver) {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    ash_chrome_service_->BindPrefs(std::move(pending_receiver));
+  }
+
   void BindTestControllerReceiver(
       mojo::PendingReceiver<crosapi::mojom::TestController> pending_receiver) {
     DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
@@ -454,6 +460,16 @@
             &LacrosChromeServiceNeverBlockingState::BindClipboardReceiver,
             weak_sequenced_state_, std::move(pending_receiver)));
   }
+
+  if (IsPrefsAvailable()) {
+    mojo::PendingReceiver<crosapi::mojom::Prefs> pending_receiver =
+        prefs_remote_.BindNewPipeAndPassReceiver();
+    never_blocking_sequence_->PostTask(
+        FROM_HERE,
+        base::BindOnce(
+            &LacrosChromeServiceNeverBlockingState::BindPrefsReceiver,
+            weak_sequenced_state_, std::move(pending_receiver)));
+  }
 }
 
 // static
@@ -625,6 +641,13 @@
              AshChromeService::MethodMinVersions::kBindCertDatabaseMinVersion;
 }
 
+bool LacrosChromeServiceImpl::IsPrefsAvailable() {
+  base::Optional<uint32_t> version = AshChromeServiceVersion();
+  return version &&
+         version.value() >=
+             AshChromeService::MethodMinVersions::kBindPrefsMinVersion;
+}
+
 bool LacrosChromeServiceImpl::IsOnLacrosStartupAvailable() {
   base::Optional<uint32_t> version = AshChromeServiceVersion();
   return version &&
diff --git a/chromeos/lacros/lacros_chrome_service_impl.h b/chromeos/lacros/lacros_chrome_service_impl.h
index 71082ad..3160829 100644
--- a/chromeos/lacros/lacros_chrome_service_impl.h
+++ b/chromeos/lacros/lacros_chrome_service_impl.h
@@ -20,6 +20,7 @@
 #include "chromeos/crosapi/mojom/keystore_service.mojom.h"
 #include "chromeos/crosapi/mojom/message_center.mojom.h"
 #include "chromeos/crosapi/mojom/metrics_reporting.mojom.h"
+#include "chromeos/crosapi/mojom/prefs.mojom.h"
 #include "chromeos/crosapi/mojom/screen_manager.mojom.h"
 #include "chromeos/crosapi/mojom/select_file.mojom.h"
 #include "chromeos/crosapi/mojom/test_controller.mojom.h"
@@ -213,6 +214,17 @@
     return clipboard_remote_;
   }
 
+  // Whether the Prefs API is available.
+  bool IsPrefsAvailable();
+
+  // This must be called on the affine sequence. It exposes a remote that can
+  // be used to interface with Prefs.
+  mojo::Remote<crosapi::mojom::Prefs>& prefs_remote() {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(affine_sequence_checker_);
+    DCHECK(IsPrefsAvailable());
+    return prefs_remote_;
+  }
+
   // --------------------------------------------------------------------------
   // Some clients will want to use mojo::Remotes on arbitrary sequences (e.g.
   // background threads). The following methods allow the client to construct a
@@ -307,6 +319,7 @@
   mojo::Remote<crosapi::mojom::FileManager> file_manager_remote_;
   mojo::Remote<crosapi::mojom::TestController> test_controller_remote_;
   mojo::Remote<crosapi::mojom::Clipboard> clipboard_remote_;
+  mojo::Remote<crosapi::mojom::Prefs> prefs_remote_;
 
   // This member is instantiated on the affine sequence alongside the
   // constructor. All subsequent invocations of this member, including
diff --git a/chromeos/services/assistant/BUILD.gn b/chromeos/services/assistant/BUILD.gn
index 9d0afbaa56..6b9c2d5 100644
--- a/chromeos/services/assistant/BUILD.gn
+++ b/chromeos/services/assistant/BUILD.gn
@@ -82,8 +82,8 @@
       "media_session/assistant_media_session.h",
       "platform/audio_device_owner.cc",
       "platform/audio_device_owner.h",
-      "platform/audio_input_host.cc",
-      "platform/audio_input_host.h",
+      "platform/audio_input_host_impl.cc",
+      "platform/audio_input_host_impl.h",
       "platform/audio_input_impl.cc",
       "platform/audio_input_impl.h",
       "platform/audio_input_provider_impl.cc",
diff --git a/chromeos/services/assistant/assistant_manager_service_delegate_impl.cc b/chromeos/services/assistant/assistant_manager_service_delegate_impl.cc
index a4a82b9..6f6a5ec 100644
--- a/chromeos/services/assistant/assistant_manager_service_delegate_impl.cc
+++ b/chromeos/services/assistant/assistant_manager_service_delegate_impl.cc
@@ -9,6 +9,7 @@
 #include <utility>
 
 #include "ash/public/cpp/assistant/assistant_state_base.h"
+#include "chromeos/services/assistant/platform/audio_input_host_impl.h"
 #include "chromeos/services/assistant/platform_api_impl.h"
 #include "chromeos/services/assistant/service_context.h"
 #include "libassistant/shared/internal_api/assistant_manager_internal.h"
@@ -20,21 +21,26 @@
 AssistantManagerServiceDelegateImpl::AssistantManagerServiceDelegateImpl(
     mojo::PendingRemote<device::mojom::BatteryMonitor> battery_monitor,
     ServiceContext* context)
-    : battery_monitor_(std::move(battery_monitor)),
-      context_(context) {}
+    : battery_monitor_(std::move(battery_monitor)), context_(context) {}
 
 AssistantManagerServiceDelegateImpl::~AssistantManagerServiceDelegateImpl() =
     default;
 
+std::unique_ptr<AudioInputHost>
+AssistantManagerServiceDelegateImpl::CreateAudioInputHost() {
+  return std::make_unique<AudioInputHostImpl>(
+      context_->cras_audio_handler(), context_->power_manager_client(),
+      context_->assistant_state()->locale().value());
+}
+
 std::unique_ptr<CrosPlatformApi>
 AssistantManagerServiceDelegateImpl::CreatePlatformApi(
     AssistantMediaSession* media_session,
     scoped_refptr<base::SingleThreadTaskRunner> background_thread_task_runner) {
   return std::make_unique<PlatformApiImpl>(
       media_session, context_->power_manager_client(),
-      context_->cras_audio_handler(), std::move(battery_monitor_),
-      context_->main_task_runner(), background_thread_task_runner,
-      context_->assistant_state()->locale().value());
+      std::move(battery_monitor_), context_->main_task_runner(),
+      background_thread_task_runner);
 }
 
 std::unique_ptr<assistant_client::AssistantManager>
diff --git a/chromeos/services/assistant/assistant_manager_service_delegate_impl.h b/chromeos/services/assistant/assistant_manager_service_delegate_impl.h
index 1d0eb8586..82836437 100644
--- a/chromeos/services/assistant/assistant_manager_service_delegate_impl.h
+++ b/chromeos/services/assistant/assistant_manager_service_delegate_impl.h
@@ -25,15 +25,15 @@
       ServiceContext* context);
   ~AssistantManagerServiceDelegateImpl() override;
 
+  // AssistantManagerServiceDelegate implementation:
+  std::unique_ptr<AudioInputHost> CreateAudioInputHost() override;
   std::unique_ptr<CrosPlatformApi> CreatePlatformApi(
       AssistantMediaSession* media_session,
       scoped_refptr<base::SingleThreadTaskRunner> background_thread_task_runner)
       override;
-
   std::unique_ptr<assistant_client::AssistantManager> CreateAssistantManager(
       assistant_client::PlatformApi* platform_api,
       const std::string& lib_assistant_config) override;
-
   assistant_client::AssistantManagerInternal* UnwrapAssistantManagerInternal(
       assistant_client::AssistantManager* assistant_manager) override;
 
diff --git a/chromeos/services/assistant/assistant_manager_service_impl.cc b/chromeos/services/assistant/assistant_manager_service_impl.cc
index 413dd9d..d4df871 100644
--- a/chromeos/services/assistant/assistant_manager_service_impl.cc
+++ b/chromeos/services/assistant/assistant_manager_service_impl.cc
@@ -40,6 +40,7 @@
 #include "chromeos/services/assistant/public/cpp/device_actions.h"
 #include "chromeos/services/assistant/public/cpp/features.h"
 #include "chromeos/services/assistant/public/cpp/migration/assistant_manager_service_delegate.h"
+#include "chromeos/services/assistant/public/cpp/migration/audio_input_host.h"
 #include "chromeos/services/assistant/public/cpp/migration/libassistant_v1_api.h"
 #include "chromeos/services/assistant/public/shared/utils.h"
 #include "chromeos/services/assistant/service_context.h"
@@ -198,6 +199,10 @@
   // To solve this chicken-and-egg problem, we need a separe Initialize() call.
   assistant_proxy_->Initialize(libassistant_service_host_.get());
 
+  audio_input_host_ = delegate_->CreateAudioInputHost();
+
+  platform_api_->InitializeAudioInputHost(*audio_input_host_);
+
   settings_delegate_ =
       std::make_unique<AssistantDeviceSettingsDelegate>(context);
 
@@ -361,7 +366,7 @@
 }
 
 void AssistantManagerServiceImpl::EnableHotword(bool enable) {
-  platform_api_->OnHotwordEnabled(enable);
+  audio_input_host_->OnHotwordEnabled(enable);
 }
 
 void AssistantManagerServiceImpl::SetArcPlayStoreEnabled(bool enable) {
@@ -449,14 +454,14 @@
   DCHECK(assistant_manager());
   DVLOG(1) << __func__;
 
-  platform_api_->SetMicState(true);
+  audio_input_host_->SetMicState(true);
   assistant_manager()->StartAssistantInteraction();
 }
 
 void AssistantManagerServiceImpl::StopActiveInteraction(
     bool cancel_conversation) {
   DVLOG(1) << __func__;
-  platform_api_->SetMicState(false);
+  audio_input_host_->SetMicState(false);
 
   if (!assistant_manager_internal()) {
     VLOG(1) << "Stopping interaction without assistant manager.";
@@ -578,7 +583,7 @@
       &AssistantManagerServiceImpl::OnConversationTurnStartedInternal,
       metadata);
 
-  platform_api_->OnConversationTurnStarted();
+  audio_input_host_->OnConversationTurnStarted();
 
   // Retrieve the cached interaction metadata associated with this conversation
   // turn or construct a new instance if there's no match in the cache.
@@ -607,10 +612,10 @@
   if (resolution != Resolution::NORMAL_WITH_FOLLOW_ON &&
       resolution != Resolution::CANCELLED &&
       resolution != Resolution::BARGE_IN) {
-    platform_api_->SetMicState(false);
+    audio_input_host_->SetMicState(false);
   }
 
-  platform_api_->OnConversationTurnFinished();
+  audio_input_host_->OnConversationTurnFinished();
 
   switch (resolution) {
     // Interaction ended normally.
@@ -1396,6 +1401,11 @@
   return api ? api->assistant_manager_internal() : nullptr;
 }
 
+void AssistantManagerServiceImpl::SetMicState(bool mic_open) {
+  DCHECK(audio_input_host_);
+  audio_input_host_->SetMicState(mic_open);
+}
+
 ServiceControllerProxy& AssistantManagerServiceImpl::service_controller() {
   return assistant_proxy_->service_controller();
 }
diff --git a/chromeos/services/assistant/assistant_manager_service_impl.h b/chromeos/services/assistant/assistant_manager_service_impl.h
index 26a3d96..cff59765 100644
--- a/chromeos/services/assistant/assistant_manager_service_impl.h
+++ b/chromeos/services/assistant/assistant_manager_service_impl.h
@@ -63,6 +63,7 @@
 class AssistantDeviceSettingsDelegate;
 class AssistantManagerServiceDelegate;
 class AssistantProxy;
+class AudioInputHost;
 class CrosPlatformApi;
 class ServiceContext;
 class ServiceControllerProxy;
@@ -220,7 +221,7 @@
 
   assistant_client::AssistantManager* assistant_manager();
   assistant_client::AssistantManagerInternal* assistant_manager_internal();
-  CrosPlatformApi* platform_api() { return platform_api_.get(); }
+  void SetMicState(bool mic_open);
 
   // assistant_client::MediaManager::Listener overrides:
   void OnPlaybackStateChange(
@@ -313,6 +314,7 @@
   std::unique_ptr<AssistantSettingsImpl> assistant_settings_;
 
   std::unique_ptr<AssistantProxy> assistant_proxy_;
+  std::unique_ptr<AudioInputHost> audio_input_host_;
 
   base::ObserverList<AssistantInteractionSubscriber> interaction_subscribers_;
   mojo::Remote<media_session::mojom::MediaController> media_controller_;
diff --git a/chromeos/services/assistant/assistant_settings_impl.cc b/chromeos/services/assistant/assistant_settings_impl.cc
index e68eb8f1..f064b87 100644
--- a/chromeos/services/assistant/assistant_settings_impl.cc
+++ b/chromeos/services/assistant/assistant_settings_impl.cc
@@ -13,7 +13,6 @@
 #include "chromeos/dbus/util/version_loader.h"
 #include "chromeos/services/assistant/assistant_manager_service_impl.h"
 #include "chromeos/services/assistant/public/cpp/features.h"
-#include "chromeos/services/assistant/public/cpp/migration/cros_platform_api.h"
 #include "chromeos/services/assistant/public/proto/assistant_device_settings_ui.pb.h"
 #include "chromeos/services/assistant/public/proto/settings_ui.pb.h"
 #include "chromeos/services/assistant/service_context.h"
@@ -124,7 +123,7 @@
   DCHECK(main_task_runner()->RunsTasksInCurrentSequence());
   DCHECK(!speaker_id_enrollment_client_);
 
-  assistant_manager_service_->platform_api()->SetMicState(true);
+  assistant_manager_service_->SetMicState(true);
 
   if (!assistant_manager_service_->assistant_manager_internal())
     return;
@@ -153,7 +152,7 @@
   DCHECK(HasStarted(assistant_manager_service_));
   DCHECK(main_task_runner()->RunsTasksInCurrentSequence());
 
-  assistant_manager_service_->platform_api()->SetMicState(false);
+  assistant_manager_service_->SetMicState(false);
 
   if (!assistant_manager_service_->assistant_manager_internal())
     return;
diff --git a/chromeos/services/assistant/platform/audio_input_host.h b/chromeos/services/assistant/platform/audio_input_host.h
deleted file mode 100644
index 528af7a9..0000000
--- a/chromeos/services/assistant/platform/audio_input_host.h
+++ /dev/null
@@ -1,80 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROMEOS_SERVICES_ASSISTANT_PLATFORM_AUDIO_INPUT_HOST_H_
-#define CHROMEOS_SERVICES_ASSISTANT_PLATFORM_AUDIO_INPUT_HOST_H_
-
-#include <string>
-
-#include "base/component_export.h"
-#include "base/memory/weak_ptr.h"
-#include "base/scoped_observation.h"
-#include "chromeos/dbus/power/power_manager_client.h"
-#include "chromeos/services/assistant/platform/audio_devices.h"
-
-namespace chromeos {
-namespace assistant {
-
-class AudioInputImpl;
-
-// Class that provides the bridge between the ChromeOS UI thread and the
-// Libassistant audio input class.
-// The goal is that |AudioInputImpl| no longer depends on any external events.
-// This will allow us to move it to the Libassistant mojom service (at which
-// point this class will talk to the Libassistant mojom service).
-class COMPONENT_EXPORT(ASSISTANT_SERVICE) AudioInputHost
-    : private chromeos::PowerManagerClient::Observer,
-      private AudioDevices::Observer
-
-{
- public:
-  AudioInputHost(AudioInputImpl* audio_input,
-                 CrasAudioHandler* cras_audio_handler,
-                 chromeos::PowerManagerClient* power_manager_client,
-                 const std::string& locale);
-  AudioInputHost(AudioInputHost&) = delete;
-  AudioInputHost& operator=(AudioInputHost&) = delete;
-  ~AudioInputHost() override;
-
-  // Called when the mic state associated with the interaction is changed.
-  void SetMicState(bool mic_open);
-
-  // Called when hotword enabled status changed.
-  void OnHotwordEnabled(bool enable);
-
-  void OnConversationTurnStarted();
-  void OnConversationTurnFinished();
-
-  // AudioDevices::Observer implementation:
-  void SetDeviceId(const base::Optional<std::string>& device_id) override;
-  void SetHotwordDeviceId(
-      const base::Optional<std::string>& device_id) override;
-
- private:
-  // chromeos::PowerManagerClient::Observer overrides:
-  void LidEventReceived(chromeos::PowerManagerClient::LidState state,
-                        base::TimeTicks timestamp) override;
-
-  void OnInitialLidStateReceived(
-      base::Optional<chromeos::PowerManagerClient::SwitchStates> switch_states);
-
-  // Owned by |PlatformApiImpl| which also owns |this|.
-  AudioInputImpl* const audio_input_;
-  chromeos::PowerManagerClient* const power_manager_client_;
-  base::ScopedObservation<chromeos::PowerManagerClient,
-                          chromeos::PowerManagerClient::Observer>
-      power_manager_client_observer_;
-
-  // Observes available audio devices and will set device-id/hotword-device-id
-  // accordingly.
-  AudioDevices audio_devices_;
-  AudioDevices::ScopedObservation audio_devices_observation_{this};
-
-  base::WeakPtrFactory<AudioInputHost> weak_factory_{this};
-};
-
-}  // namespace assistant
-}  // namespace chromeos
-
-#endif  // CHROMEOS_SERVICES_ASSISTANT_PLATFORM_AUDIO_INPUT_HOST_H_
diff --git a/chromeos/services/assistant/platform/audio_input_host.cc b/chromeos/services/assistant/platform/audio_input_host_impl.cc
similarity index 68%
rename from chromeos/services/assistant/platform/audio_input_host.cc
rename to chromeos/services/assistant/platform/audio_input_host_impl.cc
index 6ace7006..4f6dcd6e 100644
--- a/chromeos/services/assistant/platform/audio_input_host.cc
+++ b/chromeos/services/assistant/platform/audio_input_host_impl.cc
@@ -1,8 +1,8 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
+// Copyright 2021 The Chromium Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chromeos/services/assistant/platform/audio_input_host.h"
+#include "chromeos/services/assistant/platform/audio_input_host_impl.h"
 
 #include "base/check.h"
 #include "base/optional.h"
@@ -30,35 +30,38 @@
 
 }  // namespace
 
-chromeos::assistant::AudioInputHost::AudioInputHost(
-    AudioInputImpl* audio_input,
+chromeos::assistant::AudioInputHostImpl::AudioInputHostImpl(
     CrasAudioHandler* cras_audio_handler,
     chromeos::PowerManagerClient* power_manager_client,
     const std::string& locale)
-    : audio_input_(audio_input),
-      power_manager_client_(power_manager_client),
+    : power_manager_client_(power_manager_client),
       power_manager_client_observer_(this),
       audio_devices_(cras_audio_handler, locale) {
-  DCHECK(audio_input_);
   DCHECK(power_manager_client_);
-
-  audio_devices_observation_.Observe(&audio_devices_);
-  power_manager_client_observer_.Observe(power_manager_client);
-  power_manager_client->GetSwitchStates(base::BindOnce(
-      &AudioInputHost::OnInitialLidStateReceived, weak_factory_.GetWeakPtr()));
 }
 
-AudioInputHost::~AudioInputHost() = default;
+AudioInputHostImpl::~AudioInputHostImpl() = default;
 
-void AudioInputHost::SetMicState(bool mic_open) {
+void AudioInputHostImpl::Initialize(AudioInputImpl* audio_input) {
+  DCHECK(audio_input);
+  audio_input_ = audio_input;
+  audio_devices_observation_.Observe(&audio_devices_);
+  power_manager_client_observer_.Observe(power_manager_client_);
+  power_manager_client_->GetSwitchStates(
+      base::BindOnce(&AudioInputHostImpl::OnInitialLidStateReceived,
+                     weak_factory_.GetWeakPtr()));
+}
+
+void AudioInputHostImpl::SetMicState(bool mic_open) {
   audio_input_->SetMicState(mic_open);
 }
 
-void AudioInputHost::SetDeviceId(const base::Optional<std::string>& device_id) {
+void AudioInputHostImpl::SetDeviceId(
+    const base::Optional<std::string>& device_id) {
   audio_input_->SetDeviceId(device_id.value_or(""));
 }
 
-void AudioInputHost::OnConversationTurnStarted() {
+void AudioInputHostImpl::OnConversationTurnStarted() {
   audio_input_->OnConversationTurnStarted();
   // Inform power manager of a wake notification when Libassistant
   // recognized hotword and started a conversation. We intentionally
@@ -67,20 +70,20 @@
   power_manager_client_->NotifyWakeNotification();
 }
 
-void AudioInputHost::OnConversationTurnFinished() {
+void AudioInputHostImpl::OnConversationTurnFinished() {
   audio_input_->OnConversationTurnFinished();
 }
 
-void AudioInputHost::OnHotwordEnabled(bool enable) {
+void AudioInputHostImpl::OnHotwordEnabled(bool enable) {
   audio_input_->OnHotwordEnabled(enable);
 }
 
-void AudioInputHost::SetHotwordDeviceId(
+void AudioInputHostImpl::SetHotwordDeviceId(
     const base::Optional<std::string>& device_id) {
   audio_input_->SetHotwordDeviceId(device_id.value_or(""));
 }
 
-void AudioInputHost::LidEventReceived(
+void AudioInputHostImpl::LidEventReceived(
     chromeos::PowerManagerClient::LidState state,
     base::TimeTicks timestamp) {
   // Lid switch event still gets fired during system suspend, which enables
@@ -89,7 +92,7 @@
   audio_input_->OnLidStateChanged(ConvertLidState(state));
 }
 
-void AudioInputHost::OnInitialLidStateReceived(
+void AudioInputHostImpl::OnInitialLidStateReceived(
     base::Optional<chromeos::PowerManagerClient::SwitchStates> switch_states) {
   if (switch_states.has_value())
     audio_input_->OnLidStateChanged(ConvertLidState(switch_states->lid_state));
diff --git a/chromeos/services/assistant/platform/audio_input_host_impl.h b/chromeos/services/assistant/platform/audio_input_host_impl.h
new file mode 100644
index 0000000..fc3583a
--- /dev/null
+++ b/chromeos/services/assistant/platform/audio_input_host_impl.h
@@ -0,0 +1,73 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMEOS_SERVICES_ASSISTANT_PLATFORM_AUDIO_INPUT_HOST_IMPL_H_
+#define CHROMEOS_SERVICES_ASSISTANT_PLATFORM_AUDIO_INPUT_HOST_IMPL_H_
+
+#include "chromeos/services/assistant/public/cpp/migration/audio_input_host.h"
+
+#include <string>
+
+#include "base/component_export.h"
+#include "base/memory/weak_ptr.h"
+#include "base/optional.h"
+#include "base/scoped_observation.h"
+#include "base/time/time.h"
+#include "chromeos/dbus/power/power_manager_client.h"
+#include "chromeos/services/assistant/platform/audio_devices.h"
+
+namespace chromeos {
+namespace assistant {
+
+class COMPONENT_EXPORT(ASSISTANT_SERVICE) AudioInputHostImpl
+    : public AudioInputHost,
+      private chromeos::PowerManagerClient::Observer,
+      private AudioDevices::Observer {
+ public:
+  AudioInputHostImpl(CrasAudioHandler* cras_audio_handler,
+                     chromeos::PowerManagerClient* power_manager_client,
+                     const std::string& locale);
+  AudioInputHostImpl(const AudioInputHostImpl&) = delete;
+  AudioInputHostImpl& operator=(const AudioInputHostImpl&) = delete;
+  ~AudioInputHostImpl() override;
+
+  // AudioInputHost implementation:
+  void Initialize(AudioInputImpl* audio_input) override;
+  void SetMicState(bool mic_open) override;
+  void OnHotwordEnabled(bool enable) override;
+  void OnConversationTurnStarted() override;
+  void OnConversationTurnFinished() override;
+
+  // AudioDevices::Observer implementation:
+  void SetDeviceId(const base::Optional<std::string>& device_id) override;
+  void SetHotwordDeviceId(
+      const base::Optional<std::string>& device_id) override;
+
+ private:
+  // chromeos::PowerManagerClient::Observer overrides:
+  void LidEventReceived(chromeos::PowerManagerClient::LidState state,
+                        base::TimeTicks timestamp) override;
+
+  void OnInitialLidStateReceived(
+      base::Optional<chromeos::PowerManagerClient::SwitchStates> switch_states);
+
+  // Owned by |PlatformApiImpl| which also owns |this|.
+  AudioInputImpl* audio_input_ = nullptr;
+  chromeos::PowerManagerClient* const power_manager_client_;
+  base::ScopedObservation<chromeos::PowerManagerClient,
+                          chromeos::PowerManagerClient::Observer>
+      power_manager_client_observer_;
+
+  // Observes available audio devices and will set device-id/hotword-device-id
+  // accordingly.
+  AudioDevices audio_devices_;
+  AudioDevices::ScopedObservation audio_devices_observation_{this};
+
+  base::WeakPtrFactory<AudioInputHostImpl> weak_factory_{this};
+};
+
+}  // namespace assistant
+}  // namespace chromeos
+
+#endif  // CHROMEOS_SERVICES_ASSISTANT_PLATFORM_AUDIO_INPUT_HOST_IMPL_H_
diff --git a/chromeos/services/assistant/platform/audio_input_impl_unittest.cc b/chromeos/services/assistant/platform/audio_input_impl_unittest.cc
index 21811c7..28b6d5f 100644
--- a/chromeos/services/assistant/platform/audio_input_impl_unittest.cc
+++ b/chromeos/services/assistant/platform/audio_input_impl_unittest.cc
@@ -12,7 +12,7 @@
 #include "base/test/task_environment.h"
 #include "chromeos/audio/cras_audio_handler.h"
 #include "chromeos/dbus/power/fake_power_manager_client.h"
-#include "chromeos/services/assistant/platform/audio_input_host.h"
+#include "chromeos/services/assistant/platform/audio_input_host_impl.h"
 #include "chromeos/services/assistant/platform/audio_stream_factory_delegate.h"
 #include "chromeos/services/assistant/public/cpp/features.h"
 #include "chromeos/services/assistant/test_support/scoped_assistant_client.h"
@@ -101,9 +101,10 @@
     audio_input_impl_ = std::make_unique<AudioInputImpl>(
         &audio_stream_factory_delegate_, "fake-device-id");
 
-    audio_input_host_ = std::make_unique<AudioInputHost>(
-        audio_input_impl_.get(), cras_audio_handler_.Get(),
-        FakePowerManagerClient::Get(), "initial-locale");
+    audio_input_host_ = std::make_unique<AudioInputHostImpl>(
+        cras_audio_handler_.Get(), FakePowerManagerClient::Get(),
+        "initial-locale");
+    audio_input_host_->Initialize(audio_input_impl_.get());
     audio_input_host_->SetDeviceId("initial-device-id");
 
     audio_input_impl_->AddObserver(this);
@@ -152,7 +153,7 @@
   DefaultAudioStreamFactoryDelegate audio_stream_factory_delegate_;
   ScopedCrasAudioHandler cras_audio_handler_;
   std::unique_ptr<AudioInputImpl> audio_input_impl_;
-  std::unique_ptr<AudioInputHost> audio_input_host_;
+  std::unique_ptr<AudioInputHostImpl> audio_input_host_;
 
   DISALLOW_COPY_AND_ASSIGN(AudioInputImplTest);
 };
diff --git a/chromeos/services/assistant/platform_api_impl.cc b/chromeos/services/assistant/platform_api_impl.cc
index 84d44577..dc59df3 100644
--- a/chromeos/services/assistant/platform_api_impl.cc
+++ b/chromeos/services/assistant/platform_api_impl.cc
@@ -12,6 +12,7 @@
 #include "chromeos/services/assistant/platform/audio_devices.h"
 #include "chromeos/services/assistant/platform/power_manager_provider_impl.h"
 #include "chromeos/services/assistant/public/cpp/features.h"
+#include "chromeos/services/assistant/public/cpp/migration/audio_input_host.h"
 #include "chromeos/services/assistant/utils.h"
 #include "libassistant/shared/public/assistant_export.h"
 #include "libassistant/shared/public/platform_api.h"
@@ -78,19 +79,13 @@
 PlatformApiImpl::PlatformApiImpl(
     AssistantMediaSession* media_session,
     PowerManagerClient* power_manager_client,
-    CrasAudioHandler* cras_audio_handler,
     mojo::PendingRemote<device::mojom::BatteryMonitor> battery_monitor,
     scoped_refptr<base::SequencedTaskRunner> main_thread_task_runner,
-    scoped_refptr<base::SingleThreadTaskRunner> background_task_runner,
-    const std::string& pref_locale)
+    scoped_refptr<base::SingleThreadTaskRunner> background_task_runner)
     : audio_input_provider_(),
       audio_output_provider_(media_session,
                              background_task_runner,
-                             media::AudioDeviceDescription::kDefaultDeviceId),
-      audio_input_host_(&audio_input_provider_.GetAudioInput(),
-                        cras_audio_handler,
-                        power_manager_client,
-                        pref_locale) {
+                             media::AudioDeviceDescription::kDefaultDeviceId) {
   // Only enable native power features if they are supported by the UI.
   std::unique_ptr<PowerManagerProviderImpl> provider;
   if (features::IsPowerManagerEnabled()) {
@@ -127,20 +122,8 @@
   return *system_provider_;
 }
 
-void PlatformApiImpl::SetMicState(bool mic_open) {
-  audio_input_host_.SetMicState(mic_open);
-}
-
-void PlatformApiImpl::OnConversationTurnStarted() {
-  audio_input_host_.OnConversationTurnStarted();
-}
-
-void PlatformApiImpl::OnConversationTurnFinished() {
-  audio_input_host_.OnConversationTurnFinished();
-}
-
-void PlatformApiImpl::OnHotwordEnabled(bool enable) {
-  audio_input_host_.OnHotwordEnabled(enable);
+void PlatformApiImpl::InitializeAudioInputHost(AudioInputHost& host) {
+  host.Initialize(&audio_input_provider_.GetAudioInput());
 }
 
 }  // namespace assistant
diff --git a/chromeos/services/assistant/platform_api_impl.h b/chromeos/services/assistant/platform_api_impl.h
index 2ba1b1f..782ddc8 100644
--- a/chromeos/services/assistant/platform_api_impl.h
+++ b/chromeos/services/assistant/platform_api_impl.h
@@ -10,7 +10,6 @@
 #include <utility>
 #include <vector>
 
-#include "chromeos/services/assistant/platform/audio_input_host.h"
 #include "chromeos/services/assistant/platform/audio_input_provider_impl.h"
 #include "chromeos/services/assistant/platform/audio_output_provider_impl.h"
 #include "chromeos/services/assistant/platform/file_provider_impl.h"
@@ -23,7 +22,6 @@
 #include "services/device/public/mojom/battery_monitor.mojom.h"
 
 namespace chromeos {
-class CrasAudioHandler;
 class PowerManagerClient;
 
 namespace assistant {
@@ -36,11 +34,9 @@
   PlatformApiImpl(
       AssistantMediaSession* media_session,
       PowerManagerClient* power_manager_client,
-      CrasAudioHandler* cras_audio_handler,
       mojo::PendingRemote<device::mojom::BatteryMonitor> battery_monitor,
       scoped_refptr<base::SequencedTaskRunner> main_thread_task_runner,
-      scoped_refptr<base::SingleThreadTaskRunner> background_task_runner,
-      const std::string& pref_locale);
+      scoped_refptr<base::SingleThreadTaskRunner> background_task_runner);
   ~PlatformApiImpl() override;
 
   // assistant_client::PlatformApi overrides
@@ -51,14 +47,7 @@
   assistant_client::NetworkProvider& GetNetworkProvider() override;
   assistant_client::SystemProvider& GetSystemProvider() override;
 
-  // Called when the mic state associated with the interaction is changed.
-  void SetMicState(bool mic_open) override;
-
-  void OnConversationTurnStarted() override;
-  void OnConversationTurnFinished() override;
-
-  // Called when hotword enabled status changed.
-  void OnHotwordEnabled(bool enable) override;
+  void InitializeAudioInputHost(AudioInputHost& host) override;
 
  private:
   // ChromeOS does not use auth manager, so we don't yet need to implement a
@@ -98,7 +87,6 @@
   FakeAuthProvider auth_provider_;
   FileProviderImpl file_provider_;
   NetworkProviderImpl network_provider_;
-  AudioInputHost audio_input_host_;
   std::unique_ptr<SystemProviderImpl> system_provider_;
 
   DISALLOW_COPY_AND_ASSIGN(PlatformApiImpl);
diff --git a/chromeos/services/assistant/public/cpp/migration/BUILD.gn b/chromeos/services/assistant/public/cpp/migration/BUILD.gn
index d901cdc..910c427 100644
--- a/chromeos/services/assistant/public/cpp/migration/BUILD.gn
+++ b/chromeos/services/assistant/public/cpp/migration/BUILD.gn
@@ -11,6 +11,7 @@
 
   sources = [
     "assistant_manager_service_delegate.h",
+    "audio_input_host.h",
     "cros_platform_api.h",
     "libassistant_v1_api.cc",
     "libassistant_v1_api.h",
diff --git a/chromeos/services/assistant/public/cpp/migration/assistant_manager_service_delegate.h b/chromeos/services/assistant/public/cpp/migration/assistant_manager_service_delegate.h
index 446616a5..f92b2f2 100644
--- a/chromeos/services/assistant/public/cpp/migration/assistant_manager_service_delegate.h
+++ b/chromeos/services/assistant/public/cpp/migration/assistant_manager_service_delegate.h
@@ -20,6 +20,7 @@
 namespace assistant {
 
 class AssistantMediaSession;
+class AudioInputHost;
 class CrosPlatformApi;
 
 // Interface class that provides factory methods for assistant internal
@@ -29,6 +30,8 @@
   AssistantManagerServiceDelegate() = default;
   virtual ~AssistantManagerServiceDelegate() = default;
 
+  virtual std::unique_ptr<AudioInputHost> CreateAudioInputHost() = 0;
+
   virtual std::unique_ptr<CrosPlatformApi> CreatePlatformApi(
       AssistantMediaSession* media_session,
       scoped_refptr<base::SingleThreadTaskRunner>
diff --git a/chromeos/services/assistant/public/cpp/migration/audio_input_host.h b/chromeos/services/assistant/public/cpp/migration/audio_input_host.h
new file mode 100644
index 0000000..2314831
--- /dev/null
+++ b/chromeos/services/assistant/public/cpp/migration/audio_input_host.h
@@ -0,0 +1,41 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMEOS_SERVICES_ASSISTANT_PUBLIC_CPP_MIGRATION_AUDIO_INPUT_HOST_H_
+#define CHROMEOS_SERVICES_ASSISTANT_PUBLIC_CPP_MIGRATION_AUDIO_INPUT_HOST_H_
+
+#include <string>
+
+#include "base/component_export.h"
+
+namespace chromeos {
+namespace assistant {
+
+class AudioInputImpl;
+
+// Class that provides the bridge between the ChromeOS UI thread and the
+// Libassistant audio input class.
+class COMPONENT_EXPORT(ASSISTANT_SERVICE) AudioInputHost {
+ public:
+  virtual ~AudioInputHost() = default;
+
+  // Initialize this class by setting the backend that it's supposed to use.
+  // TODO(b/171748795): Should be gone when the Libassistant V2 migration is
+  // completed.
+  virtual void Initialize(AudioInputImpl* audio_input) = 0;
+
+  // Called when the mic state associated with the interaction is changed.
+  virtual void SetMicState(bool mic_open) = 0;
+
+  // Called when hotword enabled status changed.
+  virtual void OnHotwordEnabled(bool enable) = 0;
+
+  virtual void OnConversationTurnStarted() = 0;
+  virtual void OnConversationTurnFinished() = 0;
+};
+
+}  // namespace assistant
+}  // namespace chromeos
+
+#endif  // CHROMEOS_SERVICES_ASSISTANT_PUBLIC_CPP_MIGRATION_AUDIO_INPUT_HOST_H_
diff --git a/chromeos/services/assistant/public/cpp/migration/cros_platform_api.h b/chromeos/services/assistant/public/cpp/migration/cros_platform_api.h
index 409e7a10..41f4ee12 100644
--- a/chromeos/services/assistant/public/cpp/migration/cros_platform_api.h
+++ b/chromeos/services/assistant/public/cpp/migration/cros_platform_api.h
@@ -19,6 +19,8 @@
 namespace chromeos {
 namespace assistant {
 
+class AudioInputHost;
+
 // Platform API required by the voice assistant, extended with some methods used
 // when ChromeOS needs to make changes to the platform state.
 // Note that this no longer inherits from |assistant_client::PlatformApi|,
@@ -29,14 +31,10 @@
   CrosPlatformApi() = default;
   virtual ~CrosPlatformApi() = default;
 
-  // Called when the mic state associated with the interaction is changed.
-  virtual void SetMicState(bool mic_open) = 0;
-
-  virtual void OnConversationTurnStarted() = 0;
-  virtual void OnConversationTurnFinished() = 0;
-
-  // Called when hotword enabled status changed.
-  virtual void OnHotwordEnabled(bool enable) = 0;
+  // Initialize the AudioInputHost.
+  // TODO(b/171748795): Should be gone when the Libassistant V2 migration is
+  // completed.
+  virtual void InitializeAudioInputHost(AudioInputHost&) = 0;
 
   // Returns the platform's audio input provider.
   virtual assistant_client::AudioInputProvider& GetAudioInputProvider() = 0;
diff --git a/chromeos/services/assistant/public/cpp/migration/fake_assistant_manager_service_delegate.cc b/chromeos/services/assistant/public/cpp/migration/fake_assistant_manager_service_delegate.cc
index 4e0a16e..73c9b52 100644
--- a/chromeos/services/assistant/public/cpp/migration/fake_assistant_manager_service_delegate.cc
+++ b/chromeos/services/assistant/public/cpp/migration/fake_assistant_manager_service_delegate.cc
@@ -9,10 +9,30 @@
 #include "chromeos/assistant/internal/test_support/fake_assistant_manager.h"
 #include "chromeos/assistant/internal/test_support/fake_assistant_manager_internal.h"
 #include "chromeos/services/assistant//public/cpp/migration/fake_platform_api.h"
+#include "chromeos/services/assistant/public/cpp/migration/audio_input_host.h"
 
 namespace chromeos {
 namespace assistant {
 
+namespace {
+
+class FakeAudioInputHost : public AudioInputHost {
+ public:
+  FakeAudioInputHost() = default;
+  FakeAudioInputHost(const FakeAudioInputHost&) = delete;
+  FakeAudioInputHost& operator=(const FakeAudioInputHost&) = delete;
+  ~FakeAudioInputHost() override = default;
+
+  // AudioInputHost implementation:
+  void Initialize(AudioInputImpl* audio_input) override {}
+  void SetMicState(bool mic_open) override {}
+  void OnHotwordEnabled(bool enable) override {}
+  void OnConversationTurnStarted() override {}
+  void OnConversationTurnFinished() override {}
+};
+
+}  // namespace
+
 FakeAssistantManagerServiceDelegate::FakeAssistantManagerServiceDelegate() {
   // We start by creating a pending assistant manager, as our unittests
   // might need to access the assistant manager before it is created through
@@ -23,6 +43,11 @@
 FakeAssistantManagerServiceDelegate::~FakeAssistantManagerServiceDelegate() =
     default;
 
+std::unique_ptr<AudioInputHost>
+FakeAssistantManagerServiceDelegate::CreateAudioInputHost() {
+  return std::make_unique<FakeAudioInputHost>();
+}
+
 std::unique_ptr<CrosPlatformApi>
 FakeAssistantManagerServiceDelegate::CreatePlatformApi(
     AssistantMediaSession* media_session,
diff --git a/chromeos/services/assistant/public/cpp/migration/fake_assistant_manager_service_delegate.h b/chromeos/services/assistant/public/cpp/migration/fake_assistant_manager_service_delegate.h
index 1ba87f1e..1adddb28 100644
--- a/chromeos/services/assistant/public/cpp/migration/fake_assistant_manager_service_delegate.h
+++ b/chromeos/services/assistant/public/cpp/migration/fake_assistant_manager_service_delegate.h
@@ -26,6 +26,7 @@
   FakeAssistantManager* assistant_manager();
 
   // AssistantManagerServiceDelegate:
+  std::unique_ptr<AudioInputHost> CreateAudioInputHost() override;
   std::unique_ptr<CrosPlatformApi> CreatePlatformApi(
       AssistantMediaSession* media_session,
       scoped_refptr<base::SingleThreadTaskRunner> background_thread_task_runner)
diff --git a/chromeos/services/assistant/public/cpp/migration/fake_platform_api.h b/chromeos/services/assistant/public/cpp/migration/fake_platform_api.h
index 49fde32a..b730a4c 100644
--- a/chromeos/services/assistant/public/cpp/migration/fake_platform_api.h
+++ b/chromeos/services/assistant/public/cpp/migration/fake_platform_api.h
@@ -28,10 +28,7 @@
   assistant_client::FileProvider& GetFileProvider() override;
   assistant_client::NetworkProvider& GetNetworkProvider() override;
   assistant_client::SystemProvider& GetSystemProvider() override;
-  void SetMicState(bool mic_open) override {}
-  void OnHotwordEnabled(bool enable) override {}
-  void OnConversationTurnStarted() override {}
-  void OnConversationTurnFinished() override {}
+  void InitializeAudioInputHost(AudioInputHost&) override {}
 
  private:
   DISALLOW_COPY_AND_ASSIGN(FakePlatformApi);
diff --git a/components/crash/content/browser/error_reporting/mock_crash_endpoint.cc b/components/crash/content/browser/error_reporting/mock_crash_endpoint.cc
index 64f4490..c9575b2 100644
--- a/components/crash/content/browser/error_reporting/mock_crash_endpoint.cc
+++ b/components/crash/content/browser/error_reporting/mock_crash_endpoint.cc
@@ -89,8 +89,8 @@
   ++report_count_;
   last_report_ = Report(absolute_url.query(), request.content);
   auto http_response = std::make_unique<net::test_server::BasicHttpResponse>();
-  http_response->set_code(net::HTTP_OK);
-  http_response->set_content("123");
+  http_response->set_code(response_code_);
+  http_response->set_content(response_content_);
   http_response->set_content_type("text/plain");
   if (on_report_) {
     on_report_.Run();
diff --git a/components/crash/content/browser/error_reporting/mock_crash_endpoint.h b/components/crash/content/browser/error_reporting/mock_crash_endpoint.h
index 7d0fb98..7f6f85fc 100644
--- a/components/crash/content/browser/error_reporting/mock_crash_endpoint.h
+++ b/components/crash/content/browser/error_reporting/mock_crash_endpoint.h
@@ -10,6 +10,7 @@
 
 #include "base/callback.h"
 #include "base/optional.h"
+#include "net/http/http_status_code.h"
 #include "url/gurl.h"
 
 namespace net {
@@ -51,6 +52,12 @@
   // submitting crash reports.
   void set_consented(bool consented) { consented_ = consented; }
 
+  // Set the response that the server will return.
+  void set_response(net::HttpStatusCode code, const std::string& content) {
+    response_code_ = code;
+    response_content_ = content;
+  }
+
   // Returns the URL that tests should send crash reports to.
   std::string GetCrashEndpointURL() const;
 
@@ -66,6 +73,8 @@
   int report_count_ = 0;
   bool consented_ = true;
   base::RepeatingClosure on_report_;
+  net::HttpStatusCode response_code_ = net::HTTP_OK;
+  std::string response_content_ = "123";
 };
 
 #endif  // COMPONENTS_CRASH_CONTENT_BROWSER_ERROR_REPORTING_MOCK_CRASH_ENDPOINT_H_
diff --git a/components/dom_distiller/ios/BUILD.gn b/components/dom_distiller/ios/BUILD.gn
index 0bfc6a6c..29c300e 100644
--- a/components/dom_distiller/ios/BUILD.gn
+++ b/components/dom_distiller/ios/BUILD.gn
@@ -17,7 +17,6 @@
     "//components/dom_distiller/core/proto",
     "//components/favicon/ios",
     "//ios/web/public",
-    "//ios/web/public/deprecated",
     "//url",
   ]
 }
diff --git a/components/dom_distiller/ios/distiller_page_ios.h b/components/dom_distiller/ios/distiller_page_ios.h
index c7cfa77..2e28b36 100644
--- a/components/dom_distiller/ios/distiller_page_ios.h
+++ b/components/dom_distiller/ios/distiller_page_ios.h
@@ -5,7 +5,6 @@
 #ifndef COMPONENTS_DOM_DISTILLER_IOS_DISTILLER_PAGE_IOS_H_
 #define COMPONENTS_DOM_DISTILLER_IOS_DISTILLER_PAGE_IOS_H_
 
-#include <objc/objc.h>
 #include <memory>
 #include <string>
 
@@ -52,7 +51,7 @@
 
  private:
   // Called once the |script_| has been evaluated on the page.
-  void HandleJavaScriptResult(id result);
+  void HandleJavaScriptResult(const base::Value* result);
 
   // web::WebStateObserver implementation.
   void PageLoaded(
diff --git a/components/dom_distiller/ios/distiller_page_ios.mm b/components/dom_distiller/ios/distiller_page_ios.mm
index b53503a..4c58f7f 100644
--- a/components/dom_distiller/ios/distiller_page_ios.mm
+++ b/components/dom_distiller/ios/distiller_page_ios.mm
@@ -13,12 +13,9 @@
 #include "base/logging.h"
 #include "base/mac/foundation_util.h"
 #include "base/strings/string_split.h"
-#include "base/strings/sys_string_conversions.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/values.h"
 #include "ios/web/public/browser_state.h"
-#import "ios/web/public/deprecated/crw_js_injection_manager.h"
-#import "ios/web/public/deprecated/crw_js_injection_receiver.h"
 #import "ios/web/public/navigation/navigation_manager.h"
 #import "ios/web/public/navigation/web_state_policy_decider.h"
 #import "ios/web/public/web_state.h"
@@ -40,11 +37,13 @@
 
 int const kMaximumParsingRecursionDepth = 6;
 
-// Converts result of WKWebView script evaluation to base::Value, parsing
-// |wk_result| up to a depth of |max_depth|.
-base::Value ValueResultFromScriptResult(id wk_result, int max_depth) {
+// Returns a clone of |value| where double values are converted to integers if
+// the numbers has no fraction. |value| is only processed up to |max_depth|.
+base::Value ConvertedResultFromScriptResult(const base::Value* value,
+                                            int max_depth) {
   base::Value result;
-  if (!wk_result) {
+  if (!value || value->is_none()) {
+    DCHECK_EQ(result.type(), base::Value::Type::NONE);
     return result;
   }
 
@@ -53,52 +52,48 @@
     return result;
   }
 
-  CFTypeID result_type = CFGetTypeID(reinterpret_cast<CFTypeRef>(wk_result));
-  if (result_type == CFStringGetTypeID()) {
-    result = base::Value(base::SysNSStringToUTF8(wk_result));
+  if (value->is_string()) {
+    result = base::Value(value->GetString());
     DCHECK_EQ(result.type(), base::Value::Type::STRING);
-  } else if (result_type == CFNumberGetTypeID()) {
+  } else if (value->is_double()) {
     // Different implementation is here.
-    if ([wk_result intValue] != [wk_result doubleValue]) {
-      result = base::Value([wk_result doubleValue]);
-      DCHECK_EQ(result.type(), base::Value::Type::DOUBLE);
-    } else {
-      result = base::Value([wk_result intValue]);
+    double double_value = value->GetDouble();
+    int int_value = round(double_value);
+    if (double_value == int_value) {
+      result = base::Value(int_value);
       DCHECK_EQ(result.type(), base::Value::Type::INTEGER);
+    } else {
+      result = base::Value(double_value);
+      DCHECK_EQ(result.type(), base::Value::Type::DOUBLE);
     }
     // End of different implementation.
-  } else if (result_type == CFBooleanGetTypeID()) {
-    result = base::Value(static_cast<bool>([wk_result boolValue]));
+  } else if (value->is_bool()) {
+    result = base::Value(value);
     DCHECK_EQ(result.type(), base::Value::Type::BOOLEAN);
-  } else if (result_type == CFNullGetTypeID()) {
-    DCHECK_EQ(result.type(), base::Value::Type::NONE);
-  } else if (result_type == CFDictionaryGetTypeID()) {
+  } else if (value->is_dict()) {
     base::Value dictionary(base::Value::Type::DICTIONARY);
-    for (id key in wk_result) {
-      NSString* obj_c_string = base::mac::ObjCCast<NSString>(key);
-      base::Value value =
-          ValueResultFromScriptResult(wk_result[obj_c_string], max_depth - 1);
+    for (const auto kv : value->DictItems()) {
+      base::Value item_value =
+          ConvertedResultFromScriptResult(&kv.second, max_depth - 1);
 
-      if (value.type() == base::Value::Type::NONE) {
+      if (item_value.type() == base::Value::Type::NONE) {
         return result;
       }
-
-      std::string combined_path = base::SysNSStringToUTF8(obj_c_string);
-      std::vector<base::StringPiece> path = base::SplitStringPiece(
-          combined_path, ".", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
-      dictionary.SetPath(path, std::move(value));
+      dictionary.SetPath(kv.first, std::move(item_value));
     }
     result = std::move(dictionary);
     DCHECK_EQ(result.type(), base::Value::Type::DICTIONARY);
-  } else if (result_type == CFArrayGetTypeID()) {
+
+  } else if (value->is_list()) {
     std::vector<base::Value> list;
-    for (id list_item in wk_result) {
-      base::Value value = ValueResultFromScriptResult(list_item, max_depth - 1);
-      if (value.type() == base::Value::Type::NONE) {
+    for (const base::Value& list_item : value->GetList()) {
+      base::Value converted_item =
+          ConvertedResultFromScriptResult(&list_item, max_depth - 1);
+      if (converted_item.type() == base::Value::Type::NONE) {
         return result;
       }
 
-      list.push_back(std::move(value));
+      list.push_back(std::move(converted_item));
     }
     result = base::Value(list);
     DCHECK_EQ(result.type(), base::Value::Type::LIST);
@@ -214,19 +209,14 @@
   }
   // Inject the script.
   base::WeakPtr<DistillerPageIOS> weak_this = weak_ptr_factory_.GetWeakPtr();
-  [[web_state_->GetJSInjectionReceiver()
-      instanceOfClass:[CRWJSInjectionManager class]]
-      executeJavaScript:base::SysUTF8ToNSString(script_)
-      completionHandler:^(id result, NSError* error) {
-        DistillerPageIOS* distiller_page = weak_this.get();
-        if (distiller_page)
-          distiller_page->HandleJavaScriptResult(result);
-      }];
+  web_state_->ExecuteJavaScript(
+      base::UTF8ToUTF16(script_),
+      base::BindOnce(&DistillerPageIOS::HandleJavaScriptResult, weak_this));
 }
 
-void DistillerPageIOS::HandleJavaScriptResult(id result) {
+void DistillerPageIOS::HandleJavaScriptResult(const base::Value* result) {
   base::Value result_as_value =
-      ValueResultFromScriptResult(result, kMaximumParsingRecursionDepth);
+      ConvertedResultFromScriptResult(result, kMaximumParsingRecursionDepth);
 
   OnDistillationDone(url_, &result_as_value);
 }
diff --git a/components/domain_reliability/quic_error_mapping.cc b/components/domain_reliability/quic_error_mapping.cc
index bff8a2e4..57ff62e 100644
--- a/components/domain_reliability/quic_error_mapping.cc
+++ b/components/domain_reliability/quic_error_mapping.cc
@@ -434,6 +434,8 @@
     {quic::QUIC_INVALID_0RTT_PACKET_NUMBER_OUT_OF_ORDER,
      "quic.quic_invalid_0rtt_packet_number_out_of_order"},
     {quic::QUIC_INVALID_PRIORITY_UPDATE, "quic::quic_invalid_priority_update"},
+    {quic::QUIC_PEER_PORT_CHANGE_HANDSHAKE_UNCONFIRMED,
+     "quic.peer_port_change_handshake_unconfirmed"},
     // No error. Used as bound while iterating.
     {quic::QUIC_LAST_ERROR, "quic.last_error"}};
 
diff --git a/components/embedder_support/android/metrics/android_metrics_service_client.cc b/components/embedder_support/android/metrics/android_metrics_service_client.cc
index 1b108cbe6..ce00e9f 100644
--- a/components/embedder_support/android/metrics/android_metrics_service_client.cc
+++ b/components/embedder_support/android/metrics/android_metrics_service_client.cc
@@ -464,7 +464,7 @@
 }
 
 std::string AndroidMetricsServiceClient::GetVersionString() {
-  return version_info::GetVersionNumber();
+  return metrics::GetVersionString();
 }
 
 void AndroidMetricsServiceClient::CollectFinalMetricsForLog(
diff --git a/components/feature_engagement/public/android/java/src/org/chromium/components/feature_engagement/FeatureConstants.java b/components/feature_engagement/public/android/java/src/org/chromium/components/feature_engagement/FeatureConstants.java
index 34e01d6..bf7134d 100644
--- a/components/feature_engagement/public/android/java/src/org/chromium/components/feature_engagement/FeatureConstants.java
+++ b/components/feature_engagement/public/android/java/src/org/chromium/components/feature_engagement/FeatureConstants.java
@@ -67,6 +67,7 @@
     String READ_LATER_CONTEXT_MENU_FEATURE = "IPH_ReadLaterContextMenu";
     String READ_LATER_APP_MENU_BOOKMARK_THIS_PAGE_FEATURE = "IPH_ReadLaterAppMenuBookmarkThisPage";
     String READ_LATER_APP_MENU_BOOKMARKS_FEATURE = "IPH_ReadLaterAppMenuBookmarks";
+    String READ_LATER_BOTTOM_SHEET_FEATURE = "IPH_ReadLaterBottomSheet";
 
     /**
      * An IPH feature that encourages users to get better translations by enabling access to page
diff --git a/components/feature_engagement/public/feature_constants.cc b/components/feature_engagement/public/feature_constants.cc
index 9050024..fde7586 100644
--- a/components/feature_engagement/public/feature_constants.cc
+++ b/components/feature_engagement/public/feature_constants.cc
@@ -81,6 +81,8 @@
     "IPH_ReadLaterAppMenuBookmarkThisPage", base::FEATURE_DISABLED_BY_DEFAULT};
 const base::Feature kIPHReadLaterAppMenuBookmarksFeature{
     "IPH_ReadLaterAppMenuBookmarks", base::FEATURE_DISABLED_BY_DEFAULT};
+const base::Feature kIPHReadLaterBottomSheetFeature{
+    "IPH_ReadLaterBottomSheet", base::FEATURE_ENABLED_BY_DEFAULT};
 const base::Feature kIPHEphemeralTabFeature{"IPH_EphemeralTab",
                                             base::FEATURE_DISABLED_BY_DEFAULT};
 const base::Feature kIPHFeedCardMenuFeature{"IPH_FeedCardMenu",
diff --git a/components/feature_engagement/public/feature_constants.h b/components/feature_engagement/public/feature_constants.h
index 139b5dc7..c6bc56f2 100644
--- a/components/feature_engagement/public/feature_constants.h
+++ b/components/feature_engagement/public/feature_constants.h
@@ -68,6 +68,7 @@
 extern const base::Feature kIPHReadLaterContextMenuFeature;
 extern const base::Feature kIPHReadLaterAppMenuBookmarkThisPageFeature;
 extern const base::Feature kIPHReadLaterAppMenuBookmarksFeature;
+extern const base::Feature kIPHReadLaterBottomSheetFeature;
 extern const base::Feature kIPHTabGroupsQuicklyComparePagesFeature;
 extern const base::Feature kIPHTabGroupsTapToSeeAnotherTabFeature;
 extern const base::Feature kIPHTabGroupsYourTabsAreTogetherFeature;
diff --git a/components/feature_engagement/public/feature_list.cc b/components/feature_engagement/public/feature_list.cc
index 5580aeb..7b2f29d 100644
--- a/components/feature_engagement/public/feature_list.cc
+++ b/components/feature_engagement/public/feature_list.cc
@@ -52,6 +52,7 @@
     &kIPHReadLaterContextMenuFeature,
     &kIPHReadLaterAppMenuBookmarkThisPageFeature,
     &kIPHReadLaterAppMenuBookmarksFeature,
+    &kIPHReadLaterBottomSheetFeature,
     &kIPHTabGroupsQuicklyComparePagesFeature,
     &kIPHTabGroupsTapToSeeAnotherTabFeature,
     &kIPHTabGroupsYourTabsAreTogetherFeature,
diff --git a/components/feature_engagement/public/feature_list.h b/components/feature_engagement/public/feature_list.h
index bc3e77f9..38fb616 100644
--- a/components/feature_engagement/public/feature_list.h
+++ b/components/feature_engagement/public/feature_list.h
@@ -105,6 +105,8 @@
                        "IPH_ReadLaterAppMenuBookmarkThisPage");
 DEFINE_VARIATION_PARAM(kIPHReadLaterAppMenuBookmarksFeature,
                        "IPH_ReadLaterAppMenuBookmarks");
+DEFINE_VARIATION_PARAM(kIPHReadLaterBottomSheetFeature,
+                       "IPH_ReadLaterBottomSheet");
 DEFINE_VARIATION_PARAM(kIPHTabGroupsQuicklyComparePagesFeature,
                        "IPH_TabGroupsQuicklyComparePages");
 DEFINE_VARIATION_PARAM(kIPHTabGroupsTapToSeeAnotherTabFeature,
@@ -201,6 +203,7 @@
         VARIATION_ENTRY(kIPHReadLaterContextMenuFeature),
         VARIATION_ENTRY(kIPHReadLaterAppMenuBookmarkThisPageFeature),
         VARIATION_ENTRY(kIPHReadLaterAppMenuBookmarksFeature),
+        VARIATION_ENTRY(kIPHReadLaterBottomSheetFeature),
         VARIATION_ENTRY(kIPHTabGroupsQuicklyComparePagesFeature),
         VARIATION_ENTRY(kIPHTabGroupsTapToSeeAnotherTabFeature),
         VARIATION_ENTRY(kIPHTabGroupsYourTabsAreTogetherFeature),
diff --git a/components/feed/core/proto/BUILD.gn b/components/feed/core/proto/BUILD.gn
index 3221e2c..9244b9ae 100644
--- a/components/feed/core/proto/BUILD.gn
+++ b/components/feed/core/proto/BUILD.gn
@@ -23,7 +23,6 @@
     #UNUSED_IN_CHROME "v2/wire/in_place_update_handle.proto",
     #UNUSED_IN_CHROME "v2/wire/response_status_code.proto",
     #UNUSED_IN_CHROME "v2/wire/templates.proto",
-    "v2/keyvalue_store.proto",
     "v2/packing.proto",
     "v2/store.proto",
     "v2/ui.proto",
diff --git a/components/feed/core/proto/v2/keyvalue_store.proto b/components/feed/core/proto/v2/keyvalue_store.proto
deleted file mode 100644
index 7bdfbe4..0000000
--- a/components/feed/core/proto/v2/keyvalue_store.proto
+++ /dev/null
@@ -1,15 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-syntax = "proto3";
-
-package feedkvstore;
-
-option optimize_for = LITE_RUNTIME;
-
-message Entry {
-  bytes value = 1;
-  // Unix timetamp in milliseconds.
-  int64 modification_time = 2;
-}
diff --git a/components/feed/core/v2/BUILD.gn b/components/feed/core/v2/BUILD.gn
index d8bde6d..9fb5329a 100644
--- a/components/feed/core/v2/BUILD.gn
+++ b/components/feed/core/v2/BUILD.gn
@@ -31,8 +31,6 @@
     "notice_card_tracker.h",
     "offline_page_spy.cc",
     "offline_page_spy.h",
-    "persistent_key_value_store_impl.cc",
-    "persistent_key_value_store_impl.h",
     "prefs.cc",
     "prefs.h",
     "proto_util.cc",
@@ -43,8 +41,6 @@
     "public/feed_service.h",
     "public/feed_stream_api.cc",
     "public/feed_stream_api.h",
-    "public/persistent_key_value_store.cc",
-    "public/persistent_key_value_store.h",
     "public/types.h",
     "refresh_task_scheduler.h",
     "request_throttler.cc",
@@ -121,14 +117,12 @@
     "image_fetcher_unittest.cc",
     "metrics_reporter_unittest.cc",
     "notice_card_tracker_unittest.cc",
-    "persistent_key_value_store_impl_unittest.cc",
     "proto_util_unittest.cc",
     "protocol_translator_unittest.cc",
     "public/feed_service_unittest.cc",
     "request_throttler_unittest.cc",
     "scheduling_unittest.cc",
     "stream_model_unittest.cc",
-    "test/callback_receiver.cc",
     "test/callback_receiver.h",
     "test/callback_receiver_unittest.cc",
     "test/proto_printer.cc",
diff --git a/components/feed/core/v2/config.h b/components/feed/core/v2/config.h
index 025b287..1eb0d61 100644
--- a/components/feed/core/v2/config.h
+++ b/components/feed/core/v2/config.h
@@ -44,14 +44,6 @@
   base::TimeDelta session_id_max_age = base::TimeDelta::FromDays(30);
   // Maximum number of images prefetched per refresh.
   int max_prefetch_image_requests_per_refresh = 50;
-
-  // Configuration for `PersistentKeyValueStore`.
-
-  // Maximum total database size before items are evicted.
-  int64_t persistent_kv_store_maximum_size_before_eviction = 1000000;
-  // Eviction task is performed after this many bytes are written.
-  int persistent_kv_store_cleanup_interval_in_written_bytes = 1000000;
-
   // Set of optional capabilities included in requests. See
   // CreateFeedQueryRequest() for required capabilities.
   base::flat_set<feedwire::Capability> experimental_capabilities = {
diff --git a/components/feed/core/v2/feed_stream.cc b/components/feed/core/v2/feed_stream.cc
index cc73904..15e3d41 100644
--- a/components/feed/core/v2/feed_stream.cc
+++ b/components/feed/core/v2/feed_stream.cc
@@ -168,7 +168,6 @@
                        FeedNetwork* feed_network,
                        ImageFetcher* image_fetcher,
                        FeedStore* feed_store,
-                       PersistentKeyValueStoreImpl* persistent_key_value_store,
                        offline_pages::PrefetchService* prefetch_service,
                        offline_pages::OfflinePageModel* offline_page_model,
                        const ChromeInfo& chrome_info)
@@ -180,7 +179,6 @@
       feed_network_(feed_network),
       image_fetcher_(image_fetcher),
       store_(feed_store),
-      persistent_key_value_store_(persistent_key_value_store),
       chrome_info_(chrome_info),
       task_queue_(this),
       request_throttler_(profile_prefs),
@@ -729,10 +727,6 @@
   return image_fetcher_->Fetch(url, std::move(callback));
 }
 
-PersistentKeyValueStoreImpl* FeedStream::GetPersistentKeyValueStore() {
-  return persistent_key_value_store_;
-}
-
 void FeedStream::CancelImageFetch(ImageFetchId id) {
   image_fetcher_->Cancel(id);
 }
diff --git a/components/feed/core/v2/feed_stream.h b/components/feed/core/v2/feed_stream.h
index e40d7a9..1a269812 100644
--- a/components/feed/core/v2/feed_stream.h
+++ b/components/feed/core/v2/feed_stream.h
@@ -20,7 +20,6 @@
 #include "components/feed/core/proto/v2/wire/response.pb.h"
 #include "components/feed/core/v2/enums.h"
 #include "components/feed/core/v2/notice_card_tracker.h"
-#include "components/feed/core/v2/persistent_key_value_store_impl.h"
 #include "components/feed/core/v2/protocol_translator.h"
 #include "components/feed/core/v2/public/feed_stream_api.h"
 #include "components/feed/core/v2/request_throttler.h"
@@ -45,7 +44,6 @@
 class MetricsReporter;
 class OfflinePageSpy;
 class RefreshTaskScheduler;
-class PersistentKeyValueStoreImpl;
 class StreamModel;
 class SurfaceUpdater;
 struct StreamModelUpdateRequest;
@@ -112,7 +110,6 @@
              FeedNetwork* feed_network,
              ImageFetcher* image_fetcher,
              FeedStore* feed_store,
-             PersistentKeyValueStoreImpl* persistent_key_value_store,
              offline_pages::PrefetchService* prefetch_service,
              offline_pages::OfflinePageModel* offline_page_model,
              const ChromeInfo& chrome_info);
@@ -138,7 +135,6 @@
       const GURL& url,
       base::OnceCallback<void(NetworkResponse)> callback) override;
   void CancelImageFetch(ImageFetchId id) override;
-  PersistentKeyValueStoreImpl* GetPersistentKeyValueStore() override;
   void LoadMore(SurfaceId surface_id,
                 base::OnceCallback<void(bool)> callback) override;
   void ExecuteOperations(
@@ -331,7 +327,6 @@
   FeedNetwork* feed_network_;
   ImageFetcher* image_fetcher_;
   FeedStore* store_;
-  PersistentKeyValueStoreImpl* persistent_key_value_store_;
   const WireResponseTranslator* wire_response_translator_;
 
   ChromeInfo chrome_info_;
diff --git a/components/feed/core/v2/feed_stream_unittest.cc b/components/feed/core/v2/feed_stream_unittest.cc
index ccb9683a..78cbb826 100644
--- a/components/feed/core/v2/feed_stream_unittest.cc
+++ b/components/feed/core/v2/feed_stream_unittest.cc
@@ -26,7 +26,6 @@
 #include "base/threading/sequenced_task_runner_handle.h"
 #include "base/time/clock.h"
 #include "components/feed/core/common/pref_names.h"
-#include "components/feed/core/proto/v2/keyvalue_store.pb.h"
 #include "components/feed/core/proto/v2/store.pb.h"
 #include "components/feed/core/proto/v2/ui.pb.h"
 #include "components/feed/core/proto/v2/wire/chrome_client_info.pb.h"
@@ -38,10 +37,8 @@
 #include "components/feed/core/v2/feed_network.h"
 #include "components/feed/core/v2/image_fetcher.h"
 #include "components/feed/core/v2/metrics_reporter.h"
-#include "components/feed/core/v2/persistent_key_value_store_impl.h"
 #include "components/feed/core/v2/prefs.h"
 #include "components/feed/core/v2/protocol_translator.h"
-#include "components/feed/core/v2/public/persistent_key_value_store.h"
 #include "components/feed/core/v2/refresh_task_scheduler.h"
 #include "components/feed/core/v2/scheduling.h"
 #include "components/feed/core/v2/stream_model.h"
@@ -596,9 +593,8 @@
     // Ensure the task queue can return to idle. Failure to do so may be due
     // to a stuck task that never called |TaskComplete()|.
     WaitForIdleTaskQueue();
-    // ProtoDatabase requires PostTask to clean up.
+    // Store requires PostTask to clean up.
     store_.reset();
-    persistent_key_value_store_.reset();
     task_environment_.RunUntilIdle();
   }
 
@@ -629,8 +625,7 @@
     chrome_info.version = base::Version({99, 1, 9911, 2});
     stream_ = std::make_unique<FeedStream>(
         &refresh_scheduler_, metrics_reporter_.get(), this, &profile_prefs_,
-        &network_, image_fetcher_.get(), store_.get(),
-        persistent_key_value_store_.get(), &prefetch_service_,
+        &network_, image_fetcher_.get(), store_.get(), &prefetch_service_,
         &offline_page_model_, chrome_info);
 
     WaitForIdleTaskQueue();  // Wait for any initialization.
@@ -699,16 +694,8 @@
   std::unique_ptr<FeedStore> store_ = std::make_unique<FeedStore>(
       leveldb_proto::ProtoDatabaseProvider::GetUniqueDB<feedstore::Record>(
           leveldb_proto::ProtoDbType::FEED_STREAM_DATABASE,
-          /*db_dir=*/{},
+          /*file_path=*/{},
           task_environment_.GetMainThreadTaskRunner()));
-
-  std::unique_ptr<PersistentKeyValueStoreImpl> persistent_key_value_store_ =
-      std::make_unique<PersistentKeyValueStoreImpl>(
-          leveldb_proto::ProtoDatabaseProvider::GetUniqueDB<feedkvstore::Entry>(
-              leveldb_proto::ProtoDbType::FEED_KEY_VALUE_DATABASE,
-              /*db_dir=*/{},
-              task_environment_.GetMainThreadTaskRunner()));
-
   FakeRefreshTaskScheduler refresh_scheduler_;
   TestPrefetchService prefetch_service_;
   TestOfflinePageModel offline_page_model_;
@@ -2802,22 +2789,5 @@
   EXPECT_TIME_EQ(kExpiryTime, stream_->GetMetadata()->GetSessionIdExpiryTime());
 }
 
-TEST_F(FeedStreamTest, PersistentKeyValueStoreIsClearedOnClearAll) {
-  // Store some data and verify it exists.
-  PersistentKeyValueStore* store = stream_->GetPersistentKeyValueStore();
-  store->Put("x", "y", base::DoNothing());
-  CallbackReceiver<PersistentKeyValueStore::Result> get_result;
-  store->Get("x", get_result.Bind());
-  ASSERT_EQ("y", *get_result.RunAndGetResult().get_result);
-
-  stream_->OnCacheDataCleared();  // triggers ClearAll().
-  WaitForIdleTaskQueue();
-
-  // Verify ClearAll() deleted the data.
-  get_result.Clear();
-  store->Get("x", get_result.Bind());
-  EXPECT_FALSE(get_result.RunAndGetResult().get_result);
-}
-
 }  // namespace
 }  // namespace feed
diff --git a/components/feed/core/v2/persistent_key_value_store_impl.cc b/components/feed/core/v2/persistent_key_value_store_impl.cc
deleted file mode 100644
index 7be37daf..0000000
--- a/components/feed/core/v2/persistent_key_value_store_impl.cc
+++ /dev/null
@@ -1,325 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/feed/core/v2/persistent_key_value_store_impl.h"
-
-#include <vector>
-
-#include "base/bind.h"
-#include "base/rand_util.h"
-#include "base/time/time.h"
-#include "components/feed/core/proto/v2/keyvalue_store.pb.h"
-#include "components/feed/core/v2/config.h"
-#include "components/offline_pages/task/task.h"
-
-namespace feed {
-namespace {
-using ::feed::internal::kMaxEntriesInMemory;
-using feedkvstore::Entry;
-}  // namespace
-
-// Eviction task functionality.
-class EvictTask {
- public:
-  static void Start(base::WeakPtr<PersistentKeyValueStoreImpl> store,
-                    base::OnceCallback<void(bool)> done_callback) {
-    auto state = std::make_unique<State>();
-    state->store = store;
-    state->done_callback = std::move(done_callback);
-
-    auto* db = GetDbOrFinish(state);
-    if (!db)
-      return;
-    db->LoadKeys(base::BindOnce(&EvictTask::LoadKeysDone, std::move(state)));
-  }
-
- private:
-  struct EntryMetadata {
-    std::string key;
-    int64_t size_bytes;
-    int64_t modification_time;
-  };
-
-  struct State {
-    base::WeakPtr<PersistentKeyValueStoreImpl> store;
-    base::OnceCallback<void(bool)> done_callback;
-
-    std::vector<std::string> all_keys;
-    size_t next_key_index = 0;
-    std::vector<EntryMetadata> metadata;
-  };
-
-  static void Finish(std::unique_ptr<State> state) {
-    std::move(state->done_callback).Run(true);
-  }
-
-  static leveldb_proto::ProtoDatabase<Entry>* GetDbOrFinish(
-      std::unique_ptr<State>& state) {
-    if (state->store) {
-      return state->store->GetDatabase();
-    }
-    Finish(std::move(state));
-    return nullptr;
-  }
-
-  static void IndexMore(std::unique_ptr<State> state) {
-    auto* db = GetDbOrFinish(state);
-    if (!db)
-      return;
-    if (state->next_key_index >= state->all_keys.size()) {
-      IndexingDone(std::move(state));
-      return;
-    }
-    const size_t first_index = state->next_key_index;
-    std::string last_key;
-    state->next_key_index = std::min(
-        state->next_key_index + kMaxEntriesInMemory, state->all_keys.size());
-
-    std::string lower_bound = state->all_keys[first_index],
-                upper_bound = state->all_keys[state->next_key_index - 1];
-    db->LoadKeysAndEntriesInRange(
-        lower_bound, upper_bound,
-        base::BindOnce(&EvictTask::IndexMore_LoadChunkDone, std::move(state)));
-  }
-
-  static void IndexMore_LoadChunkDone(
-      std::unique_ptr<State> state,
-      bool ok,
-      std::unique_ptr<std::map<std::string, feedkvstore::Entry>> entries) {
-    const int64_t now =
-        base::Time::Now().ToDeltaSinceWindowsEpoch().InMilliseconds();
-    for (auto& entry : *entries) {
-      EntryMetadata m;
-      m.key = entry.first;
-      m.modification_time = entry.second.modification_time();
-      // If modification time is in the future, assume the information is out
-      // of date.
-      if (m.modification_time > now)
-        m.modification_time = 0;
-      m.size_bytes = entry.second.value().size();
-      state->metadata.push_back(m);
-    }
-    IndexMore(std::move(state));
-  }
-
-  static void LoadKeysDone(std::unique_ptr<State> state,
-                           bool ok,
-                           std::unique_ptr<std::vector<std::string>> keys) {
-    if (!ok || !keys) {
-      Finish(std::move(state));
-      return;
-    }
-    state->all_keys = std::move(*keys);
-    IndexMore(std::move(state));
-  }
-
-  static void IndexingDone(std::unique_ptr<State> state) {
-    auto* db = GetDbOrFinish(state);
-    if (!db)
-      return;
-    std::sort(state->metadata.begin(), state->metadata.end(),
-              [&](const EntryMetadata& a, const EntryMetadata& b) {
-                return a.modification_time > b.modification_time;
-              });
-
-    size_t i = 0;
-    int64_t total_size = 0;
-    const int64_t max_db_size_bytes =
-        GetFeedConfig().persistent_kv_store_maximum_size_before_eviction;
-    for (; i < state->metadata.size(); ++i) {
-      total_size += state->metadata[i].size_bytes;
-      if (total_size > max_db_size_bytes) {
-        break;
-      }
-    }
-
-    auto keys_to_remove = std::make_unique<std::vector<std::string>>();
-    for (; i < state->metadata.size(); ++i) {
-      keys_to_remove->push_back(state->metadata[i].key);
-    }
-
-    db->UpdateEntries(
-        std::make_unique<std::vector<std::pair<std::string, Entry>>>(),
-        std::move(keys_to_remove),
-        base::BindOnce([](std::unique_ptr<State> state,
-                          bool ok) { Finish(std::move(state)); },
-                       std::move(state)));
-  }
-};
-
-PersistentKeyValueStoreImpl::Task::Task() = default;
-PersistentKeyValueStoreImpl::Task::Task(TaskType task_type,
-                                        ResultCallback callback)
-    : Task(task_type, std::string(), std::move(callback)) {}
-PersistentKeyValueStoreImpl::Task::Task(TaskType task_type,
-                                        std::string key,
-                                        ResultCallback callback)
-    : type(task_type), key(key), done_callback(std::move(callback)) {}
-PersistentKeyValueStoreImpl::Task::Task(Task&&) noexcept = default;
-PersistentKeyValueStoreImpl::Task::~Task() = default;
-
-PersistentKeyValueStoreImpl::PersistentKeyValueStoreImpl(
-    std::unique_ptr<leveldb_proto::ProtoDatabase<feedkvstore::Entry>> database)
-    : database_(std::move(database)) {}
-
-PersistentKeyValueStoreImpl::~PersistentKeyValueStoreImpl() = default;
-
-void PersistentKeyValueStoreImpl::OnDatabaseInitialized(
-    leveldb_proto::Enums::InitStatus status) {
-  database_status_ = status;
-  TaskComplete({}, {});
-}
-
-bool PersistentKeyValueStoreImpl::IsInitialized() const {
-  return database_status_ == leveldb_proto::Enums::InitStatus::kOK;
-}
-
-void PersistentKeyValueStoreImpl::AddTask(Task task) {
-  if (!triggered_initialize_) {
-    triggered_initialize_ = true;
-    running_task_ = true;
-    database_->Init(base::BindOnce(
-        &PersistentKeyValueStoreImpl::OnDatabaseInitialized, GetWeakPtr()));
-  }
-  if (!running_task_) {
-    StartTask(std::move(task));
-  } else {
-    queued_tasks_.push(std::move(task));
-  }
-}
-
-void PersistentKeyValueStoreImpl::ClearAll(ResultCallback callback) {
-  AddTask({TaskType::kClearAll, std::move(callback)});
-}
-
-void PersistentKeyValueStoreImpl::Put(const std::string& key,
-                                      const std::string& value,
-                                      ResultCallback callback) {
-  // Use a random number to trigger EvictOldEntries().
-  // The expected number of calls to EvictOldEntries() is =~
-  // (sum of bytes written) / `cleanup_interval_in_written_bytes`.
-  int cleanup_interval_in_written_bytes =
-      GetFeedConfig().persistent_kv_store_cleanup_interval_in_written_bytes;
-  int rand_int = base::RandInt(0, cleanup_interval_in_written_bytes);
-  if (cleanup_interval_in_written_bytes > 0 &&
-      rand_int < static_cast<int>(value.size())) {
-    EvictOldEntries(base::DoNothing());
-  }
-  Task task(TaskType::kPut, key, std::move(callback));
-  task.put_value = value;
-  AddTask(std::move(task));
-}
-
-void PersistentKeyValueStoreImpl::Get(const std::string& key,
-                                      ResultCallback callback) {
-  AddTask({TaskType::kGet, key, std::move(callback)});
-}
-
-void PersistentKeyValueStoreImpl::Delete(const std::string& key,
-                                         ResultCallback callback) {
-  AddTask({TaskType::kDelete, key, std::move(callback)});
-}
-
-void PersistentKeyValueStoreImpl::EvictOldEntries(ResultCallback callback) {
-  AddTask({TaskType::kEvictOldEntries, std::move(callback)});
-}
-
-void PersistentKeyValueStoreImpl::StartTask(Task task) {
-  if (!IsInitialized()) {
-    TaskComplete(std::move(task), {});
-    return;
-  }
-  running_task_ = true;
-
-  switch (task.type) {
-    case TaskType::kGet: {
-      std::string key = std::move(task.key);
-      database_->GetEntry(key,
-                          base::BindOnce(&PersistentKeyValueStoreImpl::GetDone,
-                                         GetWeakPtr(), std::move(task)));
-      break;
-    }
-    case TaskType::kPut: {
-      auto entries_to_save = std::make_unique<
-          leveldb_proto::ProtoDatabase<feedkvstore::Entry>::KeyEntryVector>();
-      {
-        feedkvstore::Entry new_entry;
-        new_entry.set_value(std::move(task.put_value));
-        new_entry.set_modification_time(
-            base::Time::Now().ToDeltaSinceWindowsEpoch().InMilliseconds());
-        entries_to_save->emplace_back(task.key, std::move(new_entry));
-      }
-      database_->UpdateEntries(
-          std::move(entries_to_save),
-          /*keys_to_remove=*/std::make_unique<std::vector<std::string>>(),
-          base::BindOnce(&PersistentKeyValueStoreImpl::TaskCompleteBool,
-                         GetWeakPtr(), std::move(task)));
-      break;
-    }
-    case TaskType::kDelete: {
-      auto keys_to_remove = std::make_unique<std::vector<std::string>>();
-      keys_to_remove->push_back(task.key);
-      database_->UpdateEntries(
-          std::make_unique<std::vector<std::pair<std::string, Entry>>>(),
-          std::move(keys_to_remove),
-          base::BindOnce(&PersistentKeyValueStoreImpl::TaskCompleteBool,
-                         GetWeakPtr(), std::move(task)));
-      break;
-    }
-    case TaskType::kClearAll: {
-      auto filter = [](const std::string& key) { return true; };
-      database_->UpdateEntriesWithRemoveFilter(
-          std::make_unique<
-              std::vector<std::pair<std::string, feedkvstore::Entry>>>(),
-          base::BindRepeating(filter),
-          base::BindOnce(&PersistentKeyValueStoreImpl::TaskCompleteBool,
-                         GetWeakPtr(), std::move(task)));
-      break;
-    }
-    case TaskType::kEvictOldEntries: {
-      EvictTask::Start(
-          GetWeakPtr(),
-          base::BindOnce(&PersistentKeyValueStoreImpl::TaskCompleteBool,
-                         GetWeakPtr(), std::move(task)));
-      break;
-    }
-  }
-}
-
-void PersistentKeyValueStoreImpl::GetDone(
-    Task task,
-    bool ok,
-    std::unique_ptr<feedkvstore::Entry> get_entry) {
-  Result result;
-  if (ok && get_entry) {
-    result.success = true;
-    result.get_result = std::move(get_entry->value());
-  } else {
-    result.success = ok;
-  }
-  TaskComplete(std::move(task), std::move(result));
-}
-
-void PersistentKeyValueStoreImpl::TaskComplete(Task complete_task,
-                                               Result result) {
-  if (complete_task.done_callback) {
-    std::move(complete_task.done_callback).Run(std::move(result));
-  }
-  if (queued_tasks_.empty()) {
-    running_task_ = false;
-    return;
-  }
-  Task new_task = std::move(queued_tasks_.front());
-  queued_tasks_.pop();
-  StartTask(std::move(new_task));
-}
-
-void PersistentKeyValueStoreImpl::TaskCompleteBool(Task complete_task,
-                                                   bool ok) {
-  Result result;
-  result.success = ok;
-  return TaskComplete(std::move(complete_task), std::move(result));
-}
-
-}  // namespace feed
diff --git a/components/feed/core/v2/persistent_key_value_store_impl.h b/components/feed/core/v2/persistent_key_value_store_impl.h
deleted file mode 100644
index 520b760..0000000
--- a/components/feed/core/v2/persistent_key_value_store_impl.h
+++ /dev/null
@@ -1,115 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef COMPONENTS_FEED_CORE_V2_PERSISTENT_KEY_VALUE_STORE_IMPL_H_
-#define COMPONENTS_FEED_CORE_V2_PERSISTENT_KEY_VALUE_STORE_IMPL_H_
-
-#include <list>
-#include <memory>
-#include <string>
-
-#include "base/callback.h"
-#include "base/memory/weak_ptr.h"
-#include "base/optional.h"
-#include "components/feed/core/v2/public/persistent_key_value_store.h"
-#include "components/leveldb_proto/public/proto_database.h"
-#include "components/leveldb_proto/public/proto_database_provider.h"
-#include "components/offline_pages/task/task_queue.h"
-
-namespace feedkvstore {
-class Entry;
-}
-namespace feed {
-namespace internal {
-constexpr int kMaxEntriesInMemory = 50;
-}  // namespace internal
-
-// A generic persistent key-value cache. Has a maximum size determined by
-// `feed::Config`. Once size of all values exceed the maximum, older keys
-// are eventually evicted. Key age is determined only by the last call to
-// `Put()`.
-class PersistentKeyValueStoreImpl : public PersistentKeyValueStore {
- public:
-  using Result = PersistentKeyValueStore::Result;
-  using ResultCallback = base::OnceCallback<void(Result)>;
-
-  explicit PersistentKeyValueStoreImpl(
-      std::unique_ptr<leveldb_proto::ProtoDatabase<feedkvstore::Entry>>
-          database);
-  ~PersistentKeyValueStoreImpl() override;
-  PersistentKeyValueStoreImpl(const PersistentKeyValueStoreImpl&) = delete;
-  PersistentKeyValueStoreImpl& operator=(const PersistentKeyValueStoreImpl&) =
-      delete;
-
-  // PersistentKeyValueStore methods.
-
-  // Erase all data in the store.
-  void ClearAll(ResultCallback callback) override;
-  // Write/overwrite a key/value pair.
-  void Put(const std::string& key,
-           const std::string& value,
-           ResultCallback callback) override;
-  // Get a value by key.
-  void Get(const std::string& key, ResultCallback callback) override;
-  // Delete a value by key.
-  void Delete(const std::string& key, ResultCallback callback) override;
-
-  // Evict old stored entries until total size of all values in the database
-  // is less than max_db_size_bytes.
-  void EvictOldEntries(ResultCallback callback);
-
-  leveldb_proto::ProtoDatabase<feedkvstore::Entry>* GetDatabase() {
-    return database_.get();
-  }
-
-  base::WeakPtr<PersistentKeyValueStoreImpl> GetWeakPtr() {
-    return weak_ptr_factory_.GetWeakPtr();
-  }
-
-  bool IsTaskRunningForTesting() const { return running_task_; }
-
- private:
-  enum class TaskType { kGet, kPut, kDelete, kClearAll, kEvictOldEntries };
-  // Represents any operation on the database. Allows us to easily perform lazy
-  // initialization, and serialize db operations.
-  struct Task {
-    Task();
-    Task(TaskType type, ResultCallback callback);
-    Task(TaskType type, std::string key, ResultCallback callback);
-    Task(Task&&) noexcept;
-    Task(const Task&) = delete;
-    Task& operator=(const Task&) = delete;
-    ~Task();
-
-    TaskType type;
-    // Key for kGet, kPut, and kDelete.
-    std::string key;
-    // Value for kPut.
-    std::string put_value;
-    ResultCallback done_callback;
-  };
-
-  void AddTask(Task task);
-  // Implementation functions for potentially queueable actions.
-  void StartTask(Task task);
-
-  void GetDone(Task task, bool ok, std::unique_ptr<feedkvstore::Entry> entry);
-  void OnDatabaseInitialized(leveldb_proto::Enums::InitStatus status);
-  void TaskComplete(Task task, Result result);
-  void TaskCompleteBool(Task task, bool ok);
-
-  bool IsInitialized() const;
-
-  bool running_task_ = false;
-  base::queue<Task> queued_tasks_;
-  bool triggered_initialize_ = false;
-  leveldb_proto::Enums::InitStatus database_status_ =
-      leveldb_proto::Enums::InitStatus::kNotInitialized;
-  std::unique_ptr<leveldb_proto::ProtoDatabase<feedkvstore::Entry>> database_;
-  base::WeakPtrFactory<PersistentKeyValueStoreImpl> weak_ptr_factory_{this};
-};
-
-}  // namespace feed
-
-#endif  // COMPONENTS_FEED_CORE_V2_PERSISTENT_KEY_VALUE_STORE_IMPL_H_
diff --git a/components/feed/core/v2/persistent_key_value_store_impl_unittest.cc b/components/feed/core/v2/persistent_key_value_store_impl_unittest.cc
deleted file mode 100644
index 2f9b3a4..0000000
--- a/components/feed/core/v2/persistent_key_value_store_impl_unittest.cc
+++ /dev/null
@@ -1,411 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/feed/core/v2/persistent_key_value_store_impl.h"
-
-#include <map>
-#include <set>
-#include <utility>
-
-#include "base/hash/hash.h"
-#include "base/run_loop.h"
-#include "base/strings/string_number_conversions.h"
-#include "base/test/bind.h"
-#include "base/test/task_environment.h"
-#include "components/feed/core/proto/v2/keyvalue_store.pb.h"
-#include "components/feed/core/v2/config.h"
-#include "components/feed/core/v2/public/persistent_key_value_store.h"
-#include "components/feed/core/v2/test/callback_receiver.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace feed {
-namespace {
-using ::feed::internal::kMaxEntriesInMemory;
-
-int hash_int(int v) {
-  return static_cast<int>(base::PersistentHash(base::NumberToString(v)));
-}
-
-class PersistentKeyValueStoreTest : public testing::Test {
- public:
-  void SetUp() override {
-    // Disable automatic cleanup for deterministic testing.
-    Config config = GetFeedConfig();
-    config.persistent_kv_store_cleanup_interval_in_written_bytes = 0;
-    SetFeedConfigForTesting(config);
-    MakeStore();
-  }
-
-  void TearDown() override {
-    if (store_) {
-      ASSERT_FALSE(store_->IsTaskRunningForTesting());
-    }
-    // ProtoDatabase requires PostTask to clean up.
-    store_.reset();
-    base::RunLoop().RunUntilIdle();
-  }
-
- protected:
-  void MakeStore() {
-    store_ = std::make_unique<PersistentKeyValueStoreImpl>(
-        leveldb_proto::ProtoDatabaseProvider::GetUniqueDB<feedkvstore::Entry>(
-            leveldb_proto::ProtoDbType::FEED_STREAM_DATABASE,
-            /*db_dir=*/{}, task_environment_.GetMainThreadTaskRunner()));
-  }
-
-  void SetMaxSizeBeforeEviction(int size_in_bytes) {
-    Config config = GetFeedConfig();
-    config.persistent_kv_store_maximum_size_before_eviction = size_in_bytes;
-    SetFeedConfigForTesting(config);
-  }
-
-  void Put(const std::string& key, const std::string& value) {
-    CallbackReceiver<PersistentKeyValueStore::Result> callback;
-    store_->Put(key, value, callback.Bind());
-    ASSERT_TRUE(callback.RunAndGetResult().success);
-  }
-
-  std::string Get(const std::string& key) {
-    CallbackReceiver<PersistentKeyValueStore::Result> callback;
-    store_->Get(key, callback.Bind());
-    return callback.RunAndGetResult().get_result.value_or("<not-found>");
-  }
-
-  std::map<std::string, std::string> GetAllEntries() {
-    // Make sure any queued tasks are complete.
-    base::RunLoop().RunUntilIdle();
-    std::map<std::string, std::string> result;
-    auto callback =
-        [&](bool ok,
-            std::unique_ptr<std::map<std::string, feedkvstore::Entry>> data) {
-          CHECK(ok);
-          for (auto& entry : *data) {
-            result.emplace(entry.first, entry.second.value());
-          }
-        };
-    store_->GetDatabase()->LoadKeysAndEntries(
-        base::BindLambdaForTesting(callback));
-
-    base::RunLoop().RunUntilIdle();
-    return result;
-  }
-
-  base::test::TaskEnvironment task_environment_{
-      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
-  std::unique_ptr<PersistentKeyValueStoreImpl> store_;
-};
-
-TEST_F(PersistentKeyValueStoreTest, Put) {
-  CallbackReceiver<PersistentKeyValueStore::Result> callback;
-  store_->Put("x", "y", callback.Bind());
-
-  ASSERT_TRUE(callback.RunAndGetResult().success);
-  EXPECT_EQ((std::map<std::string, std::string>{{"x", "y"}}), GetAllEntries());
-}
-
-TEST_F(PersistentKeyValueStoreTest, GetEmptyKey) {
-  CallbackReceiver<PersistentKeyValueStore::Result> callback;
-  store_->Get("", callback.Bind());
-
-  EXPECT_TRUE(callback.RunAndGetResult().success);
-  EXPECT_FALSE(callback.GetResult()->get_result);
-}
-
-TEST_F(PersistentKeyValueStoreTest, GetKeyNotPresent) {
-  CallbackReceiver<PersistentKeyValueStore::Result> callback;
-  store_->Get("x", callback.Bind());
-
-  EXPECT_TRUE(callback.RunAndGetResult().success);
-  EXPECT_FALSE(callback.GetResult()->get_result);
-}
-
-TEST_F(PersistentKeyValueStoreTest, GetKeyPresent) {
-  Put("x", "y");
-
-  CallbackReceiver<PersistentKeyValueStore::Result> callback;
-  store_->Get("x", callback.Bind());
-  EXPECT_TRUE(callback.RunAndGetResult().success);
-  EXPECT_EQ("y", callback.RunAndGetResult().get_result);
-}
-
-TEST_F(PersistentKeyValueStoreTest, Delete) {
-  Put("x", "y");
-
-  CallbackReceiver<PersistentKeyValueStore::Result> callback;
-  store_->Delete("x", callback.Bind());
-  EXPECT_TRUE(callback.RunAndGetResult().success);
-  EXPECT_EQ("<not-found>", Get("x"));
-}
-
-TEST_F(PersistentKeyValueStoreTest, DeleteNotPresent) {
-  Put("x", "y");
-
-  CallbackReceiver<PersistentKeyValueStore::Result> callback;
-  store_->Delete("y", callback.Bind());
-  EXPECT_TRUE(callback.RunAndGetResult().success);
-  EXPECT_EQ("y", Get("x"));
-}
-
-TEST_F(PersistentKeyValueStoreTest, ClearAll) {
-  Put("x", "y");
-  Put("a", "b");
-
-  CallbackReceiver<PersistentKeyValueStore::Result> callback;
-  store_->ClearAll(callback.Bind());
-  EXPECT_TRUE(callback.RunAndGetResult().success);
-
-  EXPECT_EQ((std::map<std::string, std::string>{}), GetAllEntries());
-}
-
-TEST_F(PersistentKeyValueStoreTest, EvictOldEntriesOnEmptyDatabaseDoesntCrash) {
-  CallbackReceiver<PersistentKeyValueStore::Result> callback;
-  store_->EvictOldEntries(callback.Bind());
-  EXPECT_TRUE(callback.RunAndGetResult().success);
-}
-
-TEST_F(PersistentKeyValueStoreTest, EvictOldEntriesBelowSizeLimit) {
-  Put("x", "12345");
-
-  // Set config db size limit to equal size of 'x'.
-  SetMaxSizeBeforeEviction(5);
-  CallbackReceiver<PersistentKeyValueStore::Result> callback;
-  store_->EvictOldEntries(callback.Bind());
-  EXPECT_TRUE(callback.RunAndGetResult().success);
-
-  EXPECT_EQ((std::map<std::string, std::string>{{"x", "12345"}}),
-            GetAllEntries());
-}
-
-TEST_F(PersistentKeyValueStoreTest, EvictOldEntriesAboveSizeLimit) {
-  Put("x", "12345");
-
-  // Set config db size limit to just below size of 'x'.
-  SetMaxSizeBeforeEviction(4);
-  CallbackReceiver<PersistentKeyValueStore::Result> callback;
-  store_->EvictOldEntries(callback.Bind());
-  EXPECT_TRUE(callback.RunAndGetResult().success);
-
-  EXPECT_EQ((std::map<std::string, std::string>{}), GetAllEntries());
-}
-
-TEST_F(PersistentKeyValueStoreTest, PutAndGetAreQueuedWhileEvicting) {
-  SetMaxSizeBeforeEviction(0);
-  std::vector<std::string> calls;
-  auto record_call = base::BindLambdaForTesting(
-      [&](std::string label, PersistentKeyValueStore::Result) {
-        calls.push_back(label);
-      });
-  store_->Put("x", "12345", base::BindOnce(record_call, "put1"));
-  store_->EvictOldEntries(base::BindOnce(record_call, "evict"));
-  store_->Put("y", "123456", base::BindOnce(record_call, "put2"));
-  std::string get_result = Get("y");
-
-  EXPECT_EQ(std::vector<std::string>({"put1", "evict", "put2"}), calls);
-  EXPECT_EQ((std::map<std::string, std::string>{
-                {"y", "123456"},
-            }),
-            GetAllEntries());
-  EXPECT_EQ("123456", get_result);
-}
-
-TEST_F(PersistentKeyValueStoreTest, EvictOldEntriesDeletesOldEntriesFirst) {
-  Put("1", "x");
-  task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(1));
-  Put("2", "x");
-
-  SetMaxSizeBeforeEviction(1);
-  store_->EvictOldEntries(base::DoNothing());
-
-  EXPECT_EQ((std::map<std::string, std::string>{{"2", "x"}}), GetAllEntries());
-}
-
-TEST_F(PersistentKeyValueStoreTest,
-       EvictOldEntriesDeletesOldEntriesFirstReverseKeys) {
-  Put("2", "x");
-  task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(1));
-  Put("1", "x");
-
-  SetMaxSizeBeforeEviction(1);
-  store_->EvictOldEntries(base::DoNothing());
-
-  EXPECT_EQ((std::map<std::string, std::string>{{"1", "x"}}), GetAllEntries());
-}
-
-TEST_F(PersistentKeyValueStoreTest, EvictOldEntriesDeleteFutureEntriesFirst) {
-  // Insert two entries manually. The second entry has a modification time in
-  // the future, so it will be evicted preferentially.
-  {
-    // Trigger and wait for db initialization.
-    Get("foo");
-
-    auto entries_to_save = std::make_unique<
-        leveldb_proto::ProtoDatabase<feedkvstore::Entry>::KeyEntryVector>();
-    {
-      feedkvstore::Entry new_entry;
-      new_entry.set_value("1");
-      new_entry.set_modification_time(
-          (base::Time::Now().ToDeltaSinceWindowsEpoch()).InMilliseconds());
-      entries_to_save->emplace_back("key1", std::move(new_entry));
-    }
-    {
-      feedkvstore::Entry new_entry;
-      new_entry.set_value("2");
-      new_entry.set_modification_time(
-          (base::Time::Now().ToDeltaSinceWindowsEpoch() +
-           base::TimeDelta::FromMinutes(1))
-              .InMilliseconds());
-      entries_to_save->emplace_back("key2", std::move(new_entry));
-    }
-
-    CallbackReceiver<bool> callback;
-    store_->GetDatabase()->UpdateEntries(
-        std::move(entries_to_save),
-        /*keys_to_remove=*/std::make_unique<std::vector<std::string>>(),
-        callback.Bind());
-    ASSERT_TRUE(callback.RunAndGetResult());
-  }
-
-  SetMaxSizeBeforeEviction(1);
-  store_->EvictOldEntries(base::DoNothing());
-
-  EXPECT_EQ((std::map<std::string, std::string>{{"key1", "1"}}),
-            GetAllEntries());
-}
-
-TEST_F(PersistentKeyValueStoreTest, EvictOldEntriesManyEntries) {
-  const int kFinalEntryCount = kMaxEntriesInMemory * 2;
-  const int kInitialEntryCount = kFinalEntryCount + kMaxEntriesInMemory / 2;
-
-  for (int i = 0; i < kInitialEntryCount; ++i) {
-    // Make key order different than insertion order.
-    int key = hash_int(i);
-    Put(base::NumberToString(key), "x");
-    task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(1));
-  }
-
-  SetMaxSizeBeforeEviction(kFinalEntryCount);
-  store_->EvictOldEntries(base::DoNothing());
-
-  std::map<std::string, std::string> want_entries;
-  for (int i = kInitialEntryCount - kFinalEntryCount; i < kInitialEntryCount;
-       ++i) {
-    want_entries[base::NumberToString(hash_int(i))] = "x";
-  }
-  EXPECT_EQ(want_entries, GetAllEntries());
-}
-
-TEST_F(PersistentKeyValueStoreTest, EvictOldEntriesExactlyMaxEntriesInMemory) {
-  for (int i = 0; i < kMaxEntriesInMemory; ++i) {
-    // Make key order different than insertion order.
-    int key = hash_int(i);
-    Put(base::NumberToString(key), "x");
-    task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(1));
-  }
-
-  SetMaxSizeBeforeEviction(kMaxEntriesInMemory - 1);
-  store_->EvictOldEntries(base::DoNothing());
-
-  std::map<std::string, std::string> want_entries;
-  for (int i = 1; i < kMaxEntriesInMemory; ++i) {
-    want_entries[base::NumberToString(hash_int(i))] = "x";
-  }
-  EXPECT_EQ(want_entries, GetAllEntries());
-}
-
-TEST_F(PersistentKeyValueStoreTest, EvictOldEntriesMaxEntriesInMemoryPlusOne) {
-  for (int i = 0; i < kMaxEntriesInMemory + 1; ++i) {
-    // Make key order different than insertion order.
-    int key = hash_int(i);
-    Put(base::NumberToString(key), "x");
-    task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(1));
-  }
-
-  SetMaxSizeBeforeEviction(kMaxEntriesInMemory + 1 - 1);
-  store_->EvictOldEntries(base::DoNothing());
-
-  std::map<std::string, std::string> want_entries;
-  for (int i = 1; i < kMaxEntriesInMemory + 1; ++i) {
-    want_entries[base::NumberToString(hash_int(i))] = "x";
-  }
-  EXPECT_EQ(want_entries, GetAllEntries());
-}
-
-void CallAfterNPostTasks(int post_task_count, base::OnceClosure done) {
-  if (post_task_count == 0) {
-    std::move(done).Run();
-  } else {
-    base::SequencedTaskRunnerHandle::Get()->PostTask(
-        FROM_HERE,
-        base::BindOnce(base::BindOnce(&CallAfterNPostTasks, post_task_count - 1,
-                                      std::move(done))));
-  }
-}
-
-// Test that `EvictOldEntries()` completes without crashing, even when the
-// store is deleted between posted tasks.
-TEST_F(PersistentKeyValueStoreTest, DeleteStoreWhileEvictOldEntriesIsRunning) {
-  SetMaxSizeBeforeEviction(kMaxEntriesInMemory + 1);
-
-  constexpr int kMaxPostTasks = 32;  // Today, must be at least 16.
-  for (int post_tasks_before_delete = 0;
-       post_tasks_before_delete < kMaxPostTasks; ++post_tasks_before_delete) {
-    MakeStore();
-    for (int i = 0; i < kMaxEntriesInMemory + 1; ++i) {
-      Put(base::NumberToString(i), "x");
-      task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(1));
-    }
-    // Call EvictOldEntries(), and then eventually delete the store while
-    // EvictOldEntries() is running. If EvictOldEntries() completes first,
-    // then exit the loop because we've tried all possible orderings.
-    base::RunLoop run_loop;
-    bool evict_complete = false, delete_complete = false;
-    bool evict_complete_first = false;
-    auto complete_func = [&](bool is_evict_call) {
-      evict_complete |= is_evict_call;
-      delete_complete |= !is_evict_call;
-      if (evict_complete && delete_complete) {
-        evict_complete_first = !is_evict_call;
-        run_loop.QuitClosure().Run();
-      }
-    };
-    store_->EvictOldEntries(base::BindLambdaForTesting(
-        [&](PersistentKeyValueStore::Result) { complete_func(true); }));
-    CallAfterNPostTasks(post_tasks_before_delete,
-                        base::BindLambdaForTesting([&]() {
-                          store_.reset();
-                          complete_func(false);
-                        }));
-    run_loop.RunUntilIdle();
-    if (evict_complete_first) {
-      ASSERT_GT(post_tasks_before_delete, 2)
-          << "EvictOldEntries completed with fewer post tasks than expected";
-      return;
-    }
-  }
-  ASSERT_TRUE(false)
-      << "EvictOldEntries didn't complete after kMaxPostTasks post tasks?";
-}
-
-TEST_F(PersistentKeyValueStoreTest, DataStoreCleansOldDataAutomatically) {
-  // Simulate use of the store by inserting 10 byte entries. On average, we
-  // should perform eviction on every 10 Put() calls -- with a 1/10 chance on
-  // each call. We have a negligible probability of ~1.0e-46 of failing to run
-  // eviction after 1000 iterations.
-  Config config = GetFeedConfig();
-  config.persistent_kv_store_cleanup_interval_in_written_bytes = 100;
-  config.persistent_kv_store_maximum_size_before_eviction = 10;
-  SetFeedConfigForTesting(config);
-  MakeStore();
-
-  for (int i = 0;; ++i) {
-    ASSERT_LT(i, 1000);
-    Put(base::NumberToString(i), "1234567890");
-    task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(1));
-    if (Get("0") == "<not-found>")
-      break;
-  }
-}
-
-}  // namespace
-}  // namespace feed
diff --git a/components/feed/core/v2/public/feed_service.cc b/components/feed/core/v2/public/feed_service.cc
index 4c14bfd..135f2313 100644
--- a/components/feed/core/v2/public/feed_service.cc
+++ b/components/feed/core/v2/public/feed_service.cc
@@ -15,7 +15,6 @@
 #include "components/feed/core/v2/feed_stream.h"
 #include "components/feed/core/v2/image_fetcher.h"
 #include "components/feed/core/v2/metrics_reporter.h"
-#include "components/feed/core/v2/persistent_key_value_store_impl.h"
 #include "components/feed/core/v2/refresh_task_scheduler.h"
 #include "components/feed/feed_feature_list.h"
 #include "components/history/core/browser/history_service.h"
@@ -182,8 +181,6 @@
     PrefService* profile_prefs,
     PrefService* local_state,
     std::unique_ptr<leveldb_proto::ProtoDatabase<feedstore::Record>> database,
-    std::unique_ptr<leveldb_proto::ProtoDatabase<feedkvstore::Entry>>
-        key_value_store_database,
     signin::IdentityManager* identity_manager,
     history::HistoryService* history_service,
     offline_pages::PrefetchService* prefetch_service,
@@ -203,14 +200,12 @@
       profile_prefs);
   image_fetcher_ = std::make_unique<ImageFetcher>(url_loader_factory);
   store_ = std::make_unique<FeedStore>(std::move(database));
-  persistent_key_value_store_ = std::make_unique<PersistentKeyValueStoreImpl>(
-      std::move(key_value_store_database));
 
   stream_ = std::make_unique<FeedStream>(
       refresh_task_scheduler_.get(), metrics_reporter_.get(),
       stream_delegate_.get(), profile_prefs, feed_network_.get(),
-      image_fetcher_.get(), store_.get(), persistent_key_value_store_.get(),
-      prefetch_service, offline_page_model, chrome_info);
+      image_fetcher_.get(), store_.get(), prefetch_service, offline_page_model,
+      chrome_info);
 
   history_observer_ = std::make_unique<HistoryObserverImpl>(
       history_service, static_cast<FeedStream*>(stream_.get()),
diff --git a/components/feed/core/v2/public/feed_service.h b/components/feed/core/v2/public/feed_service.h
index 2890d65..d8c1f2a 100644
--- a/components/feed/core/v2/public/feed_service.h
+++ b/components/feed/core/v2/public/feed_service.h
@@ -31,9 +31,6 @@
 namespace feedstore {
 class Record;
 }  // namespace feedstore
-namespace feedkvstore {
-class Entry;
-}  // namespace feedkvstore
 namespace network {
 class SharedURLLoaderFactory;
 }  // namespace network
@@ -51,7 +48,6 @@
 class FeedNetwork;
 class FeedStore;
 class FeedStream;
-class PersistentKeyValueStoreImpl;
 class ImageFetcher;
 
 namespace internal {
@@ -86,8 +82,6 @@
       PrefService* profile_prefs,
       PrefService* local_state,
       std::unique_ptr<leveldb_proto::ProtoDatabase<feedstore::Record>> database,
-      std::unique_ptr<leveldb_proto::ProtoDatabase<feedkvstore::Entry>>
-          key_value_store_database,
       signin::IdentityManager* identity_manager,
       history::HistoryService* history_service,
       offline_pages::PrefetchService* prefetch_service,
@@ -129,7 +123,6 @@
   std::unique_ptr<FeedNetwork> feed_network_;
   std::unique_ptr<ImageFetcher> image_fetcher_;
   std::unique_ptr<FeedStore> store_;
-  std::unique_ptr<PersistentKeyValueStoreImpl> persistent_key_value_store_;
   std::unique_ptr<RefreshTaskScheduler> refresh_task_scheduler_;
   std::unique_ptr<HistoryObserverImpl> history_observer_;
   std::unique_ptr<IdentityManagerObserverImpl> identity_manager_observer_;
diff --git a/components/feed/core/v2/public/feed_stream_api.h b/components/feed/core/v2/public/feed_stream_api.h
index c531bed2..b33d9e8 100644
--- a/components/feed/core/v2/public/feed_stream_api.h
+++ b/components/feed/core/v2/public/feed_stream_api.h
@@ -23,7 +23,6 @@
 }  // namespace feedstore
 
 namespace feed {
-class PersistentKeyValueStore;
 
 // This is the public access point for interacting with the Feed stream
 // contents.
@@ -94,8 +93,6 @@
   // |id| doesn't match an active fetch, nothing happens.
   virtual void CancelImageFetch(ImageFetchId id) = 0;
 
-  virtual PersistentKeyValueStore* GetPersistentKeyValueStore() = 0;
-
   // Apply |operations| to the stream model. Does nothing if the model is not
   // yet loaded.
   virtual void ExecuteOperations(
diff --git a/components/feed/core/v2/public/persistent_key_value_store.cc b/components/feed/core/v2/public/persistent_key_value_store.cc
deleted file mode 100644
index ba15023..0000000
--- a/components/feed/core/v2/public/persistent_key_value_store.cc
+++ /dev/null
@@ -1,15 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/feed/core/v2/public/persistent_key_value_store.h"
-
-namespace feed {
-
-PersistentKeyValueStore::Result::Result() = default;
-PersistentKeyValueStore::Result::Result(Result&&) = default;
-PersistentKeyValueStore::Result& PersistentKeyValueStore::Result::operator=(
-    Result&&) = default;
-PersistentKeyValueStore::Result::~Result() = default;
-
-}  // namespace feed
diff --git a/components/feed/core/v2/public/persistent_key_value_store.h b/components/feed/core/v2/public/persistent_key_value_store.h
deleted file mode 100644
index 070d21e8..0000000
--- a/components/feed/core/v2/public/persistent_key_value_store.h
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef COMPONENTS_FEED_CORE_V2_PUBLIC_PERSISTENT_KEY_VALUE_STORE_H_
-#define COMPONENTS_FEED_CORE_V2_PUBLIC_PERSISTENT_KEY_VALUE_STORE_H_
-
-#include <memory>
-#include <string>
-
-#include "base/callback.h"
-#include "base/optional.h"
-
-namespace feed {
-
-// A generic persistent key-value cache. Has a maximum size determined by
-// `feed::Config`. Once size of all values exceed the maximum, older keys
-// are eventually evicted. Key age is determined only by the last call to
-// `Put()`.
-class PersistentKeyValueStore {
- public:
-  struct Result {
-    Result();
-    Result(Result&&);
-    Result& operator=(Result&&);
-    ~Result();
-    // Whether the operation succeeded. Failure may be due to a low level
-    // database error, or a missing key/value pair.
-    bool success = false;
-    // For `Get()` operations, the value of the key if it exists.
-    base::Optional<std::string> get_result;
-  };
-
-  using ResultCallback = base::OnceCallback<void(Result)>;
-
-  PersistentKeyValueStore() = default;
-  virtual ~PersistentKeyValueStore() = default;
-  PersistentKeyValueStore(const PersistentKeyValueStore&) = delete;
-  PersistentKeyValueStore& operator=(const PersistentKeyValueStore&) = delete;
-
-  // Erase all data in the store.
-  virtual void ClearAll(ResultCallback callback) = 0;
-  // Write/overwrite a key/value pair.
-  virtual void Put(const std::string& key,
-                   const std::string& value,
-                   ResultCallback callback) = 0;
-  // Get a value by key.
-  virtual void Get(const std::string& key, ResultCallback callback) = 0;
-  // Delete a value by key.
-  virtual void Delete(const std::string& key, ResultCallback callback) = 0;
-
- private:
-};
-
-}  // namespace feed
-
-#endif  // COMPONENTS_FEED_CORE_V2_PUBLIC_PERSISTENT_KEY_VALUE_STORE_H_
diff --git a/components/feed/core/v2/tasks/clear_all_task.cc b/components/feed/core/v2/tasks/clear_all_task.cc
index 45fb2c7..ddcf65d1 100644
--- a/components/feed/core/v2/tasks/clear_all_task.cc
+++ b/components/feed/core/v2/tasks/clear_all_task.cc
@@ -9,7 +9,6 @@
 
 #include "components/feed/core/v2/feed_store.h"
 #include "components/feed/core/v2/feed_stream.h"
-#include "components/feed/core/v2/public/persistent_key_value_store.h"
 
 namespace feed {
 
@@ -18,7 +17,6 @@
 
 void ClearAllTask::Run() {
   stream_->UnloadModel();
-  stream_->GetPersistentKeyValueStore()->ClearAll(base::DoNothing());
   stream_->GetStore()->ClearAll(
       base::BindOnce(&ClearAllTask::StoreClearComplete, GetWeakPtr()));
 }
diff --git a/components/feed/core/v2/test/callback_receiver.cc b/components/feed/core/v2/test/callback_receiver.cc
deleted file mode 100644
index a9621ee..0000000
--- a/components/feed/core/v2/test/callback_receiver.cc
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/feed/core/v2/test/callback_receiver.h"
-
-namespace feed {
-namespace internal {
-void CallbackReceiverBase::RunUntilCalled() {
-  if (called_)
-    return;
-  if (run_loop_) {
-    run_loop_->Run();
-  } else {
-    base::RunLoop run_loop;
-    run_loop_ = &run_loop;
-    run_loop.Run();
-    run_loop_ = nullptr;
-  }
-}
-void CallbackReceiverBase::Done() {
-  called_ = true;
-  if (run_loop_)
-    run_loop_->Quit();
-}
-
-}  // namespace internal
-}  // namespace feed
diff --git a/components/feed/core/v2/test/callback_receiver.h b/components/feed/core/v2/test/callback_receiver.h
index 3b2c4549..1cd9fc6 100644
--- a/components/feed/core/v2/test/callback_receiver.h
+++ b/components/feed/core/v2/test/callback_receiver.h
@@ -5,7 +5,6 @@
 #ifndef COMPONENTS_FEED_CORE_V2_TEST_CALLBACK_RECEIVER_H_
 #define COMPONENTS_FEED_CORE_V2_TEST_CALLBACK_RECEIVER_H_
 
-#include <memory>
 #include <tuple>
 #include <utility>
 
@@ -22,41 +21,23 @@
   return base::nullopt;
 }
 
-class CallbackReceiverBase {
- public:
-  explicit CallbackReceiverBase(base::RunLoop* run_loop = nullptr)
-      : run_loop_(run_loop) {}
-
-  void Clear() { called_ = false; }
-  bool called() const { return called_; }
-  void RunUntilCalled();
-  void Done();
-
- private:
-  bool called_ = false;
-  base::RunLoop* run_loop_;
-};
-
 }  // namespace internal
 
 template <typename... T>
-class CallbackReceiver : public internal::CallbackReceiverBase {
+class CallbackReceiver {
  public:
   explicit CallbackReceiver(base::RunLoop* run_loop = nullptr)
-      : CallbackReceiverBase(run_loop) {}
-
+      : run_loop_(run_loop) {}
   void Done(T... results) {
     results_ = std::make_tuple(std::move(results)...);
-    CallbackReceiverBase::Done();
+    if (run_loop_)
+      run_loop_->Quit();
   }
   base::OnceCallback<void(T...)> Bind() {
     return base::BindOnce(&CallbackReceiver::Done, base::Unretained(this));
   }
 
-  void Clear() {
-    CallbackReceiverBase::Clear();
-    results_ = std::make_tuple(internal::Nullopt<T>()...);
-  }
+  void Clear() { results_ = std::make_tuple(internal::Nullopt<T>()...); }
 
   // Get a result by its position in the arguments to Done().
   // Call GetResult() for the first argument or GetResult<I>().
@@ -66,12 +47,6 @@
     return std::get<I>(results_);
   }
 
-  template <size_t I = 0>
-  typename std::tuple_element<I, std::tuple<T...>>::type& RunAndGetResult() {
-    RunUntilCalled();
-    return std::get<I>(results_).value();
-  }
-
   // Get a result by its type. Won't compile if there is more than one matching
   // type.
   template <class C>
@@ -81,17 +56,7 @@
 
  private:
   std::tuple<base::Optional<T>...> results_;
-};
-
-template <>
-class CallbackReceiver<> : public internal::CallbackReceiverBase {
- public:
-  explicit CallbackReceiver(base::RunLoop* run_loop = nullptr)
-      : CallbackReceiverBase(run_loop) {}
-
-  base::OnceClosure Bind() {
-    return base::BindOnce(&CallbackReceiverBase::Done, base::Unretained(this));
-  }
+  base::RunLoop* run_loop_;
 };
 
 }  // namespace feed
diff --git a/components/feed/core/v2/test/callback_receiver_unittest.cc b/components/feed/core/v2/test/callback_receiver_unittest.cc
index c3a3e85..301cec7 100644
--- a/components/feed/core/v2/test/callback_receiver_unittest.cc
+++ b/components/feed/core/v2/test/callback_receiver_unittest.cc
@@ -5,9 +5,6 @@
 #include "components/feed/core/v2/test/callback_receiver.h"
 
 #include "base/optional.h"
-#include "base/test/bind.h"
-#include "base/test/task_environment.h"
-#include "base/threading/sequenced_task_runner_handle.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace feed {
@@ -44,25 +41,4 @@
   EXPECT_EQ(cr.GetResult<1>(), base::nullopt);
 }
 
-TEST(CallbackReceiverTest, RunAndGetResult) {
-  base::test::TaskEnvironment task_environment{
-      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
-
-  CallbackReceiver<int> cr1;
-  base::SequencedTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE, base::BindOnce(cr1.Bind(), 42));
-  EXPECT_EQ(42, cr1.RunAndGetResult());
-}
-
-TEST(CallbackReceiverTest, RunAndGetResultExternalRunLoop) {
-  base::test::TaskEnvironment task_environment{
-      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
-
-  base::RunLoop run_loop;
-  CallbackReceiver<int> cr1(&run_loop);
-  base::SequencedTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE, base::BindOnce(cr1.Bind(), 42));
-  EXPECT_EQ(42, cr1.RunAndGetResult());
-}
-
 }  // namespace feed
diff --git a/components/full_restore/full_restore_read_handler.cc b/components/full_restore/full_restore_read_handler.cc
index bd1e814..07b49fd70 100644
--- a/components/full_restore/full_restore_read_handler.cc
+++ b/components/full_restore/full_restore_read_handler.cc
@@ -41,6 +41,9 @@
     const base::FilePath& profile_path,
     Callback callback,
     std::unique_ptr<RestoreData> restore_data) {
+  if (restore_data)
+    profile_path_to_restore_data_[profile_path] = restore_data->Clone();
+
   std::move(callback).Run(std::move(restore_data));
 }
 
diff --git a/components/full_restore/full_restore_read_handler.h b/components/full_restore/full_restore_read_handler.h
index 57572d7..6104189b 100644
--- a/components/full_restore/full_restore_read_handler.h
+++ b/components/full_restore/full_restore_read_handler.h
@@ -5,6 +5,7 @@
 #ifndef COMPONENTS_FULL_RESTORE_FULL_RESTORE_READ_HANDLER_H_
 #define COMPONENTS_FULL_RESTORE_FULL_RESTORE_READ_HANDLER_H_
 
+#include <map>
 #include <memory>
 
 #include "base/callback.h"
@@ -49,6 +50,10 @@
                         Callback callback,
                         std::unique_ptr<RestoreData>);
 
+  // The restore data read from the full restore files.
+  std::map<base::FilePath, std::unique_ptr<RestoreData>>
+      profile_path_to_restore_data_;
+
   base::WeakPtrFactory<FullRestoreReadHandler> weak_factory_{this};
 };
 
diff --git a/components/full_restore/full_restore_save_handler.cc b/components/full_restore/full_restore_save_handler.cc
index 02699ce..f6b2ebcc 100644
--- a/components/full_restore/full_restore_save_handler.cc
+++ b/components/full_restore/full_restore_save_handler.cc
@@ -126,6 +126,22 @@
       it->second.second, window_id, window_info);
 }
 
+void FullRestoreSaveHandler::Flush(const base::FilePath& profile_path) {
+  if (save_running_.find(profile_path) != save_running_.end())
+    return;
+
+  save_running_.insert(profile_path);
+
+  BackendTaskRunner(profile_path)
+      ->PostTaskAndReply(
+          FROM_HERE,
+          base::BindOnce(&FullRestoreFileHandler::WriteToFile,
+                         GetFileHandler(profile_path),
+                         profile_path_to_restore_data_[profile_path].Clone()),
+          base::BindOnce(&FullRestoreSaveHandler::OnSaveFinished,
+                         weak_factory_.GetWeakPtr(), profile_path));
+}
+
 void FullRestoreSaveHandler::MaybeStartSaveTimer() {
   if (!save_timer_.IsRunning() && save_running_.empty()) {
     save_timer_.Start(FROM_HERE, kSaveDelay,
@@ -138,16 +154,9 @@
   if (pending_save_profile_paths_.empty())
     return;
 
-  for (const auto& file_path : pending_save_profile_paths_) {
-    save_running_.insert(file_path);
-    BackendTaskRunner(file_path)->PostTaskAndReply(
-        FROM_HERE,
-        base::BindOnce(&FullRestoreFileHandler::WriteToFile,
-                       GetFileHandler(file_path),
-                       profile_path_to_restore_data_[file_path].Clone()),
-        base::BindOnce(&FullRestoreSaveHandler::OnSaveFinished,
-                       weak_factory_.GetWeakPtr(), file_path));
-  }
+  for (const auto& file_path : pending_save_profile_paths_)
+    Flush(file_path);
+
   pending_save_profile_paths_.clear();
 }
 
diff --git a/components/full_restore/full_restore_save_handler.h b/components/full_restore/full_restore_save_handler.h
index 2b412a5..4e6cc9ff 100644
--- a/components/full_restore/full_restore_save_handler.h
+++ b/components/full_restore/full_restore_save_handler.h
@@ -61,6 +61,10 @@
   // Save |window_info| to |profile_path_to_restore_data_|.
   void SaveWindowInfo(const WindowInfo& window_info);
 
+  // Flushes the full restore file in |profile_path| with the current restore
+  // data.
+  void Flush(const base::FilePath& profile_path);
+
   base::OneShotTimer* GetTimerForTesting() { return &save_timer_; }
 
  private:
diff --git a/components/full_restore/full_restore_utils.cc b/components/full_restore/full_restore_utils.cc
index a5b017df..ca329658 100644
--- a/components/full_restore/full_restore_utils.cc
+++ b/components/full_restore/full_restore_utils.cc
@@ -15,6 +15,7 @@
 namespace full_restore {
 
 DEFINE_UI_CLASS_PROPERTY_KEY(int32_t, kWindowIdKey, 0)
+DEFINE_UI_CLASS_PROPERTY_KEY(int32_t, kRestoreWindowIdKey, 0)
 
 void SaveAppLaunchInfo(const base::FilePath& profile_dir,
                        std::unique_ptr<AppLaunchInfo> app_launch_info) {
diff --git a/components/full_restore/full_restore_utils.h b/components/full_restore/full_restore_utils.h
index f92ca52..0fcf698 100644
--- a/components/full_restore/full_restore_utils.h
+++ b/components/full_restore/full_restore_utils.h
@@ -29,6 +29,10 @@
 COMPONENT_EXPORT(FULL_RESTORE)
 extern const ui::ClassProperty<int32_t>* const kWindowIdKey;
 
+// A property key to indicate the restore id for the window from RestoreData.
+COMPONENT_EXPORT(FULL_RESTORE)
+extern const ui::ClassProperty<int32_t>* const kRestoreWindowIdKey;
+
 // Saves the app launch parameters to the full restore file.
 COMPONENT_EXPORT(FULL_RESTORE)
 void SaveAppLaunchInfo(const base::FilePath& profile_dir,
diff --git a/components/full_restore/restore_data.cc b/components/full_restore/restore_data.cc
index 303ca43..c9b00ac 100644
--- a/components/full_restore/restore_data.cc
+++ b/components/full_restore/restore_data.cc
@@ -117,4 +117,8 @@
     app_id_to_launch_list_.erase(app_id);
 }
 
+void RestoreData::RemoveApp(const std::string& app_id) {
+  app_id_to_launch_list_.erase(app_id);
+}
+
 }  // namespace full_restore
diff --git a/components/full_restore/restore_data.h b/components/full_restore/restore_data.h
index 02f6444..b50a369 100644
--- a/components/full_restore/restore_data.h
+++ b/components/full_restore/restore_data.h
@@ -91,6 +91,9 @@
   // Remove a AppRestoreData with |window_id| for |app_id|.
   void RemoveAppRestoreData(const std::string& app_id, int window_id);
 
+  // Remove the launch list for |app_id|.
+  void RemoveApp(const std::string& app_id);
+
   const AppIdToLaunchList& app_id_to_launch_list() const {
     return app_id_to_launch_list_;
   }
diff --git a/components/full_restore/restore_data_unittest.cc b/components/full_restore/restore_data_unittest.cc
index 7aec532..3c99402 100644
--- a/components/full_restore/restore_data_unittest.cc
+++ b/components/full_restore/restore_data_unittest.cc
@@ -271,7 +271,7 @@
   ModifyWindowInfos();
   VerifyRestoreData(restore_data());
 
-  // Remove kAppId1's kId1.
+  // Remove kAppId1's kWindowId1.
   restore_data().RemoveAppRestoreData(kAppId1, kWindowId1);
 
   EXPECT_EQ(2u, app_id_to_launch_list().size());
@@ -291,7 +291,7 @@
 
   EXPECT_TRUE(base::Contains(launch_list_it2->second, kWindowId3));
 
-  // Remove kAppId1's kId2.
+  // Remove kAppId1's kWindowId2.
   restore_data().RemoveAppRestoreData(kAppId1, kWindowId2);
 
   EXPECT_EQ(1u, app_id_to_launch_list().size());
@@ -306,12 +306,35 @@
 
   EXPECT_TRUE(base::Contains(launch_list_it2->second, kWindowId3));
 
-  // Remove kAppId2's kId3.
+  // Remove kAppId2's kWindowId3.
   restore_data().RemoveAppRestoreData(kAppId2, kWindowId3);
 
   EXPECT_EQ(0u, app_id_to_launch_list().size());
 }
 
+TEST_F(RestoreDataTest, RemoveApp) {
+  AddAppLaunchInfos();
+  ModifyWindowInfos();
+  VerifyRestoreData(restore_data());
+
+  // Remove kAppId1.
+  restore_data().RemoveApp(kAppId1);
+
+  EXPECT_EQ(1u, app_id_to_launch_list().size());
+
+  // Verify for |kAppId2|
+  auto launch_list_it2 = app_id_to_launch_list().find(kAppId2);
+  EXPECT_TRUE(launch_list_it2 != app_id_to_launch_list().end());
+  EXPECT_EQ(1u, launch_list_it2->second.size());
+
+  EXPECT_TRUE(base::Contains(launch_list_it2->second, kWindowId3));
+
+  // Remove kAppId2.
+  restore_data().RemoveApp(kAppId2);
+
+  EXPECT_EQ(0u, app_id_to_launch_list().size());
+}
+
 TEST_F(RestoreDataTest, Convert) {
   AddAppLaunchInfos();
   ModifyWindowInfos();
diff --git a/components/leveldb_proto/public/shared_proto_database_client_list.cc b/components/leveldb_proto/public/shared_proto_database_client_list.cc
index c2d3f632..eae251d 100644
--- a/components/leveldb_proto/public/shared_proto_database_client_list.cc
+++ b/components/leveldb_proto/public/shared_proto_database_client_list.cc
@@ -88,8 +88,6 @@
       return "NearbySharePublicCertificateDatabase";
     case ProtoDbType::VIDEO_TUTORIALS_DATABASE:
       return "VideoTutorialsDatabase";
-    case ProtoDbType::FEED_KEY_VALUE_DATABASE:
-      return "FeedKeyValueDatabase";
     case ProtoDbType::LAST:
       NOTREACHED();
       return std::string();
diff --git a/components/leveldb_proto/public/shared_proto_database_client_list.h b/components/leveldb_proto/public/shared_proto_database_client_list.h
index 55e2f9a77..57d6886 100644
--- a/components/leveldb_proto/public/shared_proto_database_client_list.h
+++ b/components/leveldb_proto/public/shared_proto_database_client_list.h
@@ -18,8 +18,7 @@
 // The enum values are used to index into the shared database. Do not rearrange
 // or reuse the integer values. Add new database types at the end of the enum,
 // and update the string mapping in ProtoDbTypeToString(). Also update the
-// suffix LevelDBClients in histogram_suffixes_list.xml to match the strings for
-// the types.
+// suffix LevelDBClients in histograms.xml to match the strings for the types.
 enum class ProtoDbType {
   TEST_DATABASE0 = 0,
   TEST_DATABASE1 = 1,
@@ -54,7 +53,6 @@
   UPBOARDING_QUERY_TILE_STORE = 28,
   NEARBY_SHARE_PUBLIC_CERTIFICATE_DATABASE = 29,
   VIDEO_TUTORIALS_DATABASE = 30,
-  FEED_KEY_VALUE_DATABASE = 31,
   LAST,
 };
 
@@ -70,7 +68,6 @@
     ProtoDbType::UPBOARDING_QUERY_TILE_STORE,
     ProtoDbType::NEARBY_SHARE_PUBLIC_CERTIFICATE_DATABASE,
     ProtoDbType::VIDEO_TUTORIALS_DATABASE,
-    ProtoDbType::FEED_KEY_VALUE_DATABASE,
     ProtoDbType::LAST,  // Marks the end of list.
 };
 
diff --git a/components/optimization_guide/DEPS b/components/optimization_guide/DEPS
index 8acabd3..a4bfbd0f 100644
--- a/components/optimization_guide/DEPS
+++ b/components/optimization_guide/DEPS
@@ -2,7 +2,6 @@
   "+components/leveldb_proto",
   "+components/prefs",
   "+components/variations",
-  "+content/public/browser",
   "+google_apis",
   "+net",
   "+services/network",
diff --git a/components/optimization_guide/core/BUILD.gn b/components/optimization_guide/core/BUILD.gn
index 51119dfa..ddd71c2b 100644
--- a/components/optimization_guide/core/BUILD.gn
+++ b/components/optimization_guide/core/BUILD.gn
@@ -39,6 +39,8 @@
     "optimization_guide_service.cc",
     "optimization_guide_service.h",
     "optimization_guide_service_observer.h",
+    "optimization_guide_session_statistic.cc",
+    "optimization_guide_session_statistic.h",
     "optimization_guide_store.cc",
     "optimization_guide_store.h",
     "optimization_guide_switches.cc",
@@ -50,6 +52,10 @@
     "optimization_target_model_observer.h",
     "prediction_model.cc",
     "prediction_model.h",
+    "prediction_model_fetcher.cc",
+    "prediction_model_fetcher.h",
+    "prediction_model_file.cc",
+    "prediction_model_file.h",
     "store_update_data.cc",
     "store_update_data.h",
     "top_host_provider.h",
@@ -107,9 +113,11 @@
     "optimization_filter_unittest.cc",
     "optimization_guide_features_unittest.cc",
     "optimization_guide_service_unittest.cc",
+    "optimization_guide_session_statistic_unittest.cc",
     "optimization_guide_store_unittest.cc",
     "optimization_guide_switches_unittest.cc",
     "optimization_metadata_unittest.cc",
+    "prediction_model_fetcher_unittest.cc",
     "prediction_model_unittest.cc",
     "store_update_data_unittest.cc",
     "url_pattern_with_wildcards_unittest.cc",
diff --git a/chrome/browser/optimization_guide/optimization_guide_session_statistic.cc b/components/optimization_guide/core/optimization_guide_session_statistic.cc
similarity index 89%
rename from chrome/browser/optimization_guide/optimization_guide_session_statistic.cc
rename to components/optimization_guide/core/optimization_guide_session_statistic.cc
index d7915d1..002085a 100644
--- a/chrome/browser/optimization_guide/optimization_guide_session_statistic.cc
+++ b/components/optimization_guide/core/optimization_guide_session_statistic.cc
@@ -2,10 +2,12 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/optimization_guide/optimization_guide_session_statistic.h"
+#include "components/optimization_guide/core/optimization_guide_session_statistic.h"
 
 #include <cmath>
 
+namespace optimization_guide {
+
 OptimizationGuideSessionStatistic::OptimizationGuideSessionStatistic()
     : num_samples_(0u), mean_(0.0), variance_sum_(0.0) {}
 
@@ -40,3 +42,5 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   return num_samples_;
 }
+
+}  // namespace optimization_guide
diff --git a/chrome/browser/optimization_guide/optimization_guide_session_statistic.h b/components/optimization_guide/core/optimization_guide_session_statistic.h
similarity index 81%
rename from chrome/browser/optimization_guide/optimization_guide_session_statistic.h
rename to components/optimization_guide/core/optimization_guide_session_statistic.h
index 25ce038..da3c76f 100644
--- a/chrome/browser/optimization_guide/optimization_guide_session_statistic.h
+++ b/components/optimization_guide/core/optimization_guide_session_statistic.h
@@ -2,12 +2,14 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_OPTIMIZATION_GUIDE_OPTIMIZATION_GUIDE_SESSION_STATISTIC_H_
-#define CHROME_BROWSER_OPTIMIZATION_GUIDE_OPTIMIZATION_GUIDE_SESSION_STATISTIC_H_
+#ifndef COMPONENTS_OPTIMIZATION_GUIDE_CORE_OPTIMIZATION_GUIDE_SESSION_STATISTIC_H_
+#define COMPONENTS_OPTIMIZATION_GUIDE_CORE_OPTIMIZATION_GUIDE_SESSION_STATISTIC_H_
 
 #include "base/sequence_checker.h"
 #include "base/values.h"
 
+namespace optimization_guide {
+
 // OptimizationGuideSessionStatistic calculates running statistics, mean and
 // variance, for real valued inputs one sample at a time.
 class OptimizationGuideSessionStatistic {
@@ -45,4 +47,6 @@
   DISALLOW_COPY_AND_ASSIGN(OptimizationGuideSessionStatistic);
 };
 
-#endif  // CHROME_BROWSER_OPTIMIZATION_GUIDE_OPTIMIZATION_GUIDE_SESSION_STATISTIC_H_
+}  // namespace optimization_guide
+
+#endif  // COMPONENTS_OPTIMIZATION_GUIDE_CORE_OPTIMIZATION_GUIDE_SESSION_STATISTIC_H_
diff --git a/chrome/browser/optimization_guide/optimization_guide_session_statistic_unittest.cc b/components/optimization_guide/core/optimization_guide_session_statistic_unittest.cc
similarity index 88%
rename from chrome/browser/optimization_guide/optimization_guide_session_statistic_unittest.cc
rename to components/optimization_guide/core/optimization_guide_session_statistic_unittest.cc
index 205cfe9..d0540386 100644
--- a/chrome/browser/optimization_guide/optimization_guide_session_statistic_unittest.cc
+++ b/components/optimization_guide/core/optimization_guide_session_statistic_unittest.cc
@@ -2,11 +2,13 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/optimization_guide/optimization_guide_session_statistic.h"
+#include "components/optimization_guide/core/optimization_guide_session_statistic.h"
 
 #include <cmath>
 #include "testing/gtest/include/gtest/gtest.h"
 
+namespace optimization_guide {
+
 TEST(OptimzationGuideSessionStatisticTest,
      CalculateSessionStatisticsForSamples) {
   OptimizationGuideSessionStatistic stat;
@@ -41,3 +43,5 @@
   EXPECT_EQ(0.0, stat.GetVariance());
   EXPECT_EQ(0.0, stat.GetStdDev());
 }
+
+}  // namespace optimization_guide
diff --git a/chrome/browser/optimization_guide/prediction/prediction_model_fetcher.cc b/components/optimization_guide/core/prediction_model_fetcher.cc
similarity index 93%
rename from chrome/browser/optimization_guide/prediction/prediction_model_fetcher.cc
rename to components/optimization_guide/core/prediction_model_fetcher.cc
index d8af38e..e976947b 100644
--- a/chrome/browser/optimization_guide/prediction/prediction_model_fetcher.cc
+++ b/components/optimization_guide/core/prediction_model_fetcher.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/optimization_guide/prediction/prediction_model_fetcher.h"
+#include "components/optimization_guide/core/prediction_model_fetcher.h"
 
 #include <memory>
 #include <string>
@@ -16,13 +16,13 @@
 #include "components/optimization_guide/core/optimization_guide_util.h"
 #include "components/optimization_guide/proto/models.pb.h"
 #include "components/variations/net/variations_http_headers.h"
-#include "content/public/browser/network_service_instance.h"
 #include "net/base/load_flags.h"
 #include "net/base/url_util.h"
 #include "net/http/http_request_headers.h"
 #include "net/http/http_response_headers.h"
 #include "net/http/http_status_code.h"
 #include "net/traffic_annotation/network_traffic_annotation.h"
+#include "services/network/public/cpp/network_connection_tracker.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 #include "services/network/public/cpp/simple_url_loader.h"
 
@@ -30,14 +30,16 @@
 
 PredictionModelFetcher::PredictionModelFetcher(
     scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
-    GURL optimization_guide_service_get_models_url)
+    const GURL& optimization_guide_service_get_models_url,
+    network::NetworkConnectionTracker* network_connection_tracker)
     : optimization_guide_service_get_models_url_(
           net::AppendOrReplaceQueryParameter(
               optimization_guide_service_get_models_url,
               "key",
               optimization_guide::features::
-                  GetOptimizationGuideServiceAPIKey())) {
-  url_loader_factory_ = std::move(url_loader_factory);
+                  GetOptimizationGuideServiceAPIKey())),
+      url_loader_factory_(url_loader_factory),
+      network_connection_tracker_(network_connection_tracker) {
   CHECK(optimization_guide_service_get_models_url_.SchemeIs(url::kHttpsScheme));
 }
 
@@ -51,7 +53,7 @@
     ModelsFetchedCallback models_fetched_callback) {
   SEQUENCE_CHECKER(sequence_checker_);
 
-  if (content::GetNetworkConnectionTracker()->IsOffline()) {
+  if (network_connection_tracker_->IsOffline()) {
     std::move(models_fetched_callback).Run(base::nullopt);
     return false;
   }
diff --git a/chrome/browser/optimization_guide/prediction/prediction_model_fetcher.h b/components/optimization_guide/core/prediction_model_fetcher.h
similarity index 85%
rename from chrome/browser/optimization_guide/prediction/prediction_model_fetcher.h
rename to components/optimization_guide/core/prediction_model_fetcher.h
index 55728171..ea98064a 100644
--- a/chrome/browser/optimization_guide/prediction/prediction_model_fetcher.h
+++ b/components/optimization_guide/core/prediction_model_fetcher.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_OPTIMIZATION_GUIDE_PREDICTION_PREDICTION_MODEL_FETCHER_H_
-#define CHROME_BROWSER_OPTIMIZATION_GUIDE_PREDICTION_PREDICTION_MODEL_FETCHER_H_
+#ifndef COMPONENTS_OPTIMIZATION_GUIDE_CORE_PREDICTION_MODEL_FETCHER_H_
+#define COMPONENTS_OPTIMIZATION_GUIDE_CORE_PREDICTION_MODEL_FETCHER_H_
 
 #include <memory>
 #include <string>
@@ -18,6 +18,7 @@
 #include "url/gurl.h"
 
 namespace network {
+class NetworkConnectionTracker;
 class SharedURLLoaderFactory;
 class SimpleURLLoader;
 }  // namespace network
@@ -39,7 +40,8 @@
  public:
   PredictionModelFetcher(
       scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
-      GURL optimization_guide_service_get_models_url);
+      const GURL& optimization_guide_service_get_models_url,
+      network::NetworkConnectionTracker* network_connection_tracker);
   virtual ~PredictionModelFetcher();
 
   // Requests PredictionModels and HostModelFeatures from the Optimization Guide
@@ -85,6 +87,10 @@
   // Used for creating a |url_loader_| when needed for request hints.
   scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_;
 
+  // Listens to changes around the network connection. Not owned. Guaranteed to
+  // outlive |this|.
+  network::NetworkConnectionTracker* network_connection_tracker_;
+
   SEQUENCE_CHECKER(sequence_checker_);
 
   DISALLOW_COPY_AND_ASSIGN(PredictionModelFetcher);
@@ -92,4 +98,4 @@
 
 }  // namespace optimization_guide
 
-#endif  // CHROME_BROWSER_OPTIMIZATION_GUIDE_PREDICTION_PREDICTION_MODEL_FETCHER_H_
+#endif  // COMPONENTS_OPTIMIZATION_GUIDE_CORE_PREDICTION_MODEL_FETCHER_H_
diff --git a/chrome/browser/optimization_guide/prediction/prediction_model_fetcher_unittest.cc b/components/optimization_guide/core/prediction_model_fetcher_unittest.cc
similarity index 96%
rename from chrome/browser/optimization_guide/prediction/prediction_model_fetcher_unittest.cc
rename to components/optimization_guide/core/prediction_model_fetcher_unittest.cc
index fa3202fa..7b960a1a 100644
--- a/chrome/browser/optimization_guide/prediction/prediction_model_fetcher_unittest.cc
+++ b/components/optimization_guide/core/prediction_model_fetcher_unittest.cc
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "components/optimization_guide/core/prediction_model_fetcher.h"
+
 #include <memory>
 #include <string>
 #include <vector>
@@ -14,7 +16,6 @@
 #include "base/strings/string_number_conversions.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/task_environment.h"
-#include "chrome/browser/optimization_guide/prediction/prediction_model_fetcher.h"
 #include "components/optimization_guide/core/optimization_guide_features.h"
 #include "components/optimization_guide/proto/models.pb.h"
 #include "net/base/url_util.h"
@@ -35,9 +36,11 @@
       : task_environment_(base::test::TaskEnvironment::MainThreadType::UI),
         shared_url_loader_factory_(
             base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
-                &test_url_loader_factory_)) {
+                &test_url_loader_factory_)),
+        network_tracker_(network::TestNetworkConnectionTracker::GetInstance()) {
     prediction_model_fetcher_ = std::make_unique<PredictionModelFetcher>(
-        shared_url_loader_factory_, GURL(optimization_guide_service_url));
+        shared_url_loader_factory_, GURL(optimization_guide_service_url),
+        network_tracker_);
   }
 
   ~PredictionModelFetcherTest() override {}
@@ -51,13 +54,11 @@
   bool models_fetched() { return models_fetched_; }
 
   void SetConnectionOffline() {
-    network_tracker_ = network::TestNetworkConnectionTracker::GetInstance();
     network_tracker_->SetConnectionType(
         network::mojom::ConnectionType::CONNECTION_NONE);
   }
 
   void SetConnectionOnline() {
-    network_tracker_ = network::TestNetworkConnectionTracker::GetInstance();
     network_tracker_->SetConnectionType(
         network::mojom::ConnectionType::CONNECTION_4G);
   }
diff --git a/chrome/browser/optimization_guide/prediction/prediction_model_file.cc b/components/optimization_guide/core/prediction_model_file.cc
similarity index 93%
rename from chrome/browser/optimization_guide/prediction/prediction_model_file.cc
rename to components/optimization_guide/core/prediction_model_file.cc
index 690ef7c..f21ec85 100644
--- a/chrome/browser/optimization_guide/prediction/prediction_model_file.cc
+++ b/components/optimization_guide/core/prediction_model_file.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/optimization_guide/prediction/prediction_model_file.h"
+#include "components/optimization_guide/core/prediction_model_file.h"
 
 #include "base/memory/ptr_util.h"
 #include "components/optimization_guide/core/optimization_guide_util.h"
diff --git a/chrome/browser/optimization_guide/prediction/prediction_model_file.h b/components/optimization_guide/core/prediction_model_file.h
similarity index 82%
rename from chrome/browser/optimization_guide/prediction/prediction_model_file.h
rename to components/optimization_guide/core/prediction_model_file.h
index 69a87af..d0489d4 100644
--- a/chrome/browser/optimization_guide/prediction/prediction_model_file.h
+++ b/components/optimization_guide/core/prediction_model_file.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_OPTIMIZATION_GUIDE_PREDICTION_PREDICTION_MODEL_FILE_H_
-#define CHROME_BROWSER_OPTIMIZATION_GUIDE_PREDICTION_PREDICTION_MODEL_FILE_H_
+#ifndef COMPONENTS_OPTIMIZATION_GUIDE_CORE_PREDICTION_MODEL_FILE_H_
+#define COMPONENTS_OPTIMIZATION_GUIDE_CORE_PREDICTION_MODEL_FILE_H_
 
 #include <memory>
 
@@ -40,4 +40,4 @@
 
 }  // namespace optimization_guide
 
-#endif  // CHROME_BROWSER_OPTIMIZATION_GUIDE_PREDICTION_PREDICTION_MODEL_FILE_H_
+#endif  // COMPONENTS_OPTIMIZATION_GUIDE_CORE_PREDICTION_MODEL_FILE_H_
diff --git a/components/policy/proto/record.proto b/components/policy/proto/record.proto
index 1450db6..d046b39 100644
--- a/components/policy/proto/record.proto
+++ b/components/policy/proto/record.proto
@@ -116,7 +116,8 @@
 
   // Public key id (required).
   // Identifies private key matching |public_asymmetric_key| for the server.
-  optional uint64 public_key_id = 2;
+  // Matches Encryptor::PublicKeyId.
+  optional int32 public_key_id = 2;
 
   // Signature of |public_asymmetric_key| (required).
   // Verified by client against a well-known signature.
diff --git a/components/search_engines/template_url_service.cc b/components/search_engines/template_url_service.cc
index 72064b66..124bd48 100644
--- a/components/search_engines/template_url_service.cc
+++ b/components/search_engines/template_url_service.cc
@@ -1831,26 +1831,7 @@
     template_url->data_.id = ++next_id_;
   }
 
-  bool force_reset_keyword = false;
-  if (template_url->safe_for_autoreplace() &&
-      base::EndsWith(template_url->keyword(), base::ASCIIToUTF16("_"))) {
-    // In the past, when we added new engines with a duplicate keyword as an
-    // existing engine, we uniquified it by adding underscores to the end.
-    // This was a problem, because it caused an increasing proliferation of
-    // entries like "example.com_" and "example.com__" in the database.
-    //
-    // For engines that are safe_for_autoreplace(), the only way they could have
-    // keywords ending with underscores is if we uniquified it. Now we detect
-    // these cases, and reverse it by resetting the keyword.
-    //
-    // This migration will be complete once the below-logged UMA goes to zero.
-    force_reset_keyword = true;
-    LogSearchTemplateURLEvent(
-        MIGRATE_SAFE_FOR_AUTOREPLACE_RESET_UNDERSCORE_KEYWORD);
-  }
-
-  template_url->ResetKeywordIfNecessary(search_terms_data(),
-                                        force_reset_keyword);
+  template_url->ResetKeywordIfNecessary(search_terms_data(), false);
 
   // Early exit if the newly added TemplateURL was a replaceable duplicate.
   // No need to inform either Sync or flag on the model-mutated in that case.
diff --git a/components/search_engines/template_url_service.h b/components/search_engines/template_url_service.h
index fa2e31d..8f43e43e 100644
--- a/components/search_engines/template_url_service.h
+++ b/components/search_engines/template_url_service.h
@@ -114,7 +114,6 @@
     SYNC_UPDATE_SUCCESS = 6,
     SYNC_UPDATE_CONVERTED_TO_ADD = 7,
     MIGRATE_SAFE_FOR_AUTOREPLACE_PLAY_API_ENGINE = 8,
-    MIGRATE_SAFE_FOR_AUTOREPLACE_RESET_UNDERSCORE_KEYWORD = 9,
     SEARCH_TEMPLATE_URL_EVENT_MAX,
   };
 
diff --git a/components/translate/core/browser/BUILD.gn b/components/translate/core/browser/BUILD.gn
index 1cfd0b7..b3464b3 100644
--- a/components/translate/core/browser/BUILD.gn
+++ b/components/translate/core/browser/BUILD.gn
@@ -136,6 +136,10 @@
     "//services/network/public/cpp:cpp",
     "//ui/base",
   ]
+
+  if (is_ios || is_android) {
+    sources += [ "translate_infobar_delegate_unittest.cc" ]
+  }
 }
 
 source_set("test_support") {
diff --git a/components/translate/core/browser/DEPS b/components/translate/core/browser/DEPS
index 96e8379..cdef75d 100644
--- a/components/translate/core/browser/DEPS
+++ b/components/translate/core/browser/DEPS
@@ -9,5 +9,8 @@
 specific_include_rules = {
   "translate_ranker_impl_unittest\.cc": [
     "+components/ukm",
+  ],
+  "translate_metrics_logger_impl_unittest\.cc": [
+    "+components/ukm",
   ]
 }
diff --git a/components/translate/core/browser/mock_translate_client.h b/components/translate/core/browser/mock_translate_client.h
index 5e832f8..640535f 100644
--- a/components/translate/core/browser/mock_translate_client.h
+++ b/components/translate/core/browser/mock_translate_client.h
@@ -12,6 +12,7 @@
 #include "components/infobars/core/infobar.h"
 #include "components/translate/core/browser/translate_client.h"
 #include "components/translate/core/browser/translate_driver.h"
+#include "components/translate/core/browser/translate_infobar_delegate.h"
 #include "components/translate/core/browser/translate_prefs.h"
 #include "components/translate/core/common/language_detection_details.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -40,11 +41,9 @@
   MOCK_CONST_METHOD0(GetInfobarIconID, int());
 
 #if !defined(USE_AURA)
-  MOCK_CONST_METHOD1(CreateInfoBarMock,
-                     infobars::InfoBar*(TranslateInfoBarDelegate*));
   std::unique_ptr<infobars::InfoBar> CreateInfoBar(
       std::unique_ptr<TranslateInfoBarDelegate> delegate) const {
-    return base::WrapUnique(CreateInfoBarMock(delegate.get()));
+    return std::make_unique<infobars::InfoBar>(std::move(delegate));
   }
 #endif
 
diff --git a/components/translate/core/browser/mock_translate_metrics_logger.h b/components/translate/core/browser/mock_translate_metrics_logger.h
index 8fba1eba..30ca4ada 100644
--- a/components/translate/core/browser/mock_translate_metrics_logger.h
+++ b/components/translate/core/browser/mock_translate_metrics_logger.h
@@ -30,6 +30,7 @@
   MOCK_METHOD1(OnPageLoadStart, void(bool));
   MOCK_METHOD1(OnForegroundChange, void(bool));
   MOCK_METHOD1(RecordMetrics, void(bool));
+  MOCK_METHOD1(SetUkmSourceId, void(ukm::SourceId));
   MOCK_METHOD2(LogRankerMetrics, void(RankerDecision, uint32_t));
   MOCK_METHOD1(LogTriggerDecision, void(TriggerDecision));
   MOCK_METHOD0(LogAutofillAssistantDeferredTriggerDecision, void());
diff --git a/components/translate/core/browser/translate_infobar_delegate.h b/components/translate/core/browser/translate_infobar_delegate.h
index e93cf44c5..2a9d6e4 100644
--- a/components/translate/core/browser/translate_infobar_delegate.h
+++ b/components/translate/core/browser/translate_infobar_delegate.h
@@ -248,7 +248,7 @@
       bool triggered_from_menu);
 
  private:
-  friend class TranslationInfoBarTest;
+  friend class TranslateInfoBarDelegateTest;
   typedef std::pair<std::string, base::string16> LanguageNamePair;
 
   bool is_off_the_record_;
diff --git a/components/translate/core/browser/translate_infobar_delegate_unittest.cc b/components/translate/core/browser/translate_infobar_delegate_unittest.cc
new file mode 100644
index 0000000..36cebede
--- /dev/null
+++ b/components/translate/core/browser/translate_infobar_delegate_unittest.cc
@@ -0,0 +1,327 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/translate/core/browser/translate_infobar_delegate.h"
+
+#include <string>
+#include <vector>
+
+#include "base/test/task_environment.h"
+#include "components/infobars/core/confirm_infobar_delegate.h"
+#include "components/infobars/core/infobar.h"
+#include "components/infobars/core/infobar_manager.h"
+#include "components/language/core/browser/language_model.h"
+#include "components/language/core/browser/language_prefs.h"
+#include "components/language/core/browser/pref_names.h"
+#include "components/pref_registry/pref_registry_syncable.h"
+#include "components/sync_preferences/testing_pref_service_syncable.h"
+#include "components/translate/core/browser/mock_translate_client.h"
+#include "components/translate/core/browser/mock_translate_driver.h"
+#include "components/translate/core/browser/mock_translate_ranker.h"
+#include "components/translate/core/browser/translate_accept_languages.h"
+#include "components/translate/core/browser/translate_client.h"
+#include "components/translate/core/browser/translate_infobar_delegate.h"
+#include "components/translate/core/browser/translate_manager.h"
+#include "components/translate/core/browser/translate_pref_names.h"
+#include "components/translate/core/browser/translate_prefs.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::_;
+using testing::Return;
+using testing::Test;
+using translate::testing::MockTranslateClient;
+using translate::testing::MockTranslateDriver;
+using translate::testing::MockTranslateRanker;
+
+namespace translate {
+
+const char kOriginalLanguage[] = "fr";
+const char kTargetLanguage[] = "en";
+
+namespace {
+
+class TestInfoBarManager : public infobars::InfoBarManager {
+ public:
+  TestInfoBarManager() = default;
+  // infobars::InfoBarManager:
+  ~TestInfoBarManager() override {}
+
+  // infobars::InfoBarManager:
+  std::unique_ptr<infobars::InfoBar> CreateConfirmInfoBar(
+      std::unique_ptr<ConfirmInfoBarDelegate> delegate) override {
+    return std::make_unique<infobars::InfoBar>(std::move(delegate));
+  }
+
+  // infobars::InfoBarManager:
+  int GetActiveEntryID() override { return 0; }
+
+  // infobars::InfoBarManager:
+  void OpenURL(const GURL& url, WindowOpenDisposition disposition) override {
+    NOTREACHED();
+  }
+};
+
+}  // namespace
+
+class MockObserver : public TranslateInfoBarDelegate::Observer {
+ public:
+  MOCK_METHOD(void,
+              OnTranslateInfoBarDelegateDestroyed,
+              (TranslateInfoBarDelegate*),
+              (override));
+  MOCK_METHOD(void,
+              OnTranslateStepChanged,
+              (translate::TranslateStep, TranslateErrors::Type),
+              (override));
+  MOCK_METHOD(void, OnTargetLanguageChanged, (const std::string&), (override));
+  MOCK_METHOD(bool, IsDeclinedByUser, (), (override));
+};
+
+class TestLanguageModel : public language::LanguageModel {
+  std::vector<LanguageDetails> GetLanguages() override {
+    return {LanguageDetails("en", 1.0)};
+  }
+};
+
+class TranslateInfoBarDelegateTest : public ::testing::Test {
+ public:
+  TranslateInfoBarDelegateTest() = default;
+
+ protected:
+  void SetUp() override {
+    pref_service_ =
+        std::make_unique<sync_preferences::TestingPrefServiceSyncable>();
+    language::LanguagePrefs::RegisterProfilePrefs(pref_service_->registry());
+    pref_service_->SetString(testing::accept_languages_prefs, std::string());
+    pref_service_->SetString(language::prefs::kAcceptLanguages, std::string());
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+    pref_service_->SetString(language::prefs::kPreferredLanguages,
+                             std::string());
+#endif
+    pref_service_->registry()->RegisterBooleanPref(
+        prefs::kOfferTranslateEnabled, true);
+    TranslatePrefs::RegisterProfilePrefs(pref_service_->registry());
+    ranker_ = std::make_unique<MockTranslateRanker>();
+    client_ =
+        std::make_unique<MockTranslateClient>(&driver_, pref_service_.get());
+    manager_ = std::make_unique<TranslateManager>(client_.get(), ranker_.get(),
+                                                  language_model_.get());
+    manager_->GetLanguageState()->set_translation_declined(false);
+    infobar_manager_ = std::make_unique<TestInfoBarManager>();
+  }
+
+  std::unique_ptr<TranslateInfoBarDelegate> ConstructInfoBarDelegate() {
+    return std::unique_ptr<TranslateInfoBarDelegate>(
+        new TranslateInfoBarDelegate(
+            manager_->GetWeakPtr(), /*is_off_the_record=*/false,
+            translate::TranslateStep::TRANSLATE_STEP_BEFORE_TRANSLATE,
+            kOriginalLanguage, kTargetLanguage, TranslateErrors::Type::NONE,
+            /*triggered_from_menu=*/false));
+  }
+
+  MockTranslateDriver driver_;
+  std::unique_ptr<sync_preferences::TestingPrefServiceSyncable> pref_service_;
+  std::unique_ptr<MockTranslateClient> client_;
+  std::unique_ptr<TestLanguageModel> language_model_;
+  std::unique_ptr<TranslateManager> manager_;
+  std::unique_ptr<MockTranslateRanker> ranker_;
+  std::unique_ptr<TestInfoBarManager> infobar_manager_;
+};
+
+TEST_F(TranslateInfoBarDelegateTest, CreateTranslateInfobarDelegate) {
+  EXPECT_EQ(infobar_manager_->infobar_count(), 0u);
+
+  // Create the initial InfoBar
+  TranslateInfoBarDelegate::Create(
+      /*replace_existing_infobar=*/false, manager_->GetWeakPtr(),
+      infobar_manager_.get(),
+      /*is_off_the_record=*/false,
+      translate::TranslateStep::TRANSLATE_STEP_TRANSLATING, kOriginalLanguage,
+      kTargetLanguage, TranslateErrors::Type::NONE,
+      /*triggered_from_menu=*/false);
+
+  EXPECT_EQ(infobar_manager_->infobar_count(), 1u);
+  TranslateInfoBarDelegate* delegate =
+      infobar_manager_->infobar_at(0)->delegate()->AsTranslateInfoBarDelegate();
+  EXPECT_FALSE(delegate->is_error());
+  EXPECT_EQ(translate::TranslateStep::TRANSLATE_STEP_TRANSLATING,
+            delegate->translate_step());
+  EXPECT_FALSE(delegate->is_off_the_record());
+  EXPECT_FALSE(delegate->triggered_from_menu());
+  EXPECT_EQ(delegate->target_language_code(), kTargetLanguage);
+  EXPECT_EQ(delegate->original_language_code(), kOriginalLanguage);
+
+  // Create another one and replace the old one
+  TranslateInfoBarDelegate::Create(
+      /*replace_existing_infobar=*/true, manager_->GetWeakPtr(),
+      infobar_manager_.get(),
+      /*is_off_the_record=*/true,
+      translate::TranslateStep::TRANSLATE_STEP_AFTER_TRANSLATE,
+      kOriginalLanguage, kTargetLanguage, TranslateErrors::Type::NONE,
+      /*triggered_from_menu=*/false);
+
+  EXPECT_EQ(infobar_manager_->infobar_count(), 1u);
+  delegate =
+      infobar_manager_->infobar_at(0)->delegate()->AsTranslateInfoBarDelegate();
+  EXPECT_EQ(delegate->translate_step(),
+            translate::TranslateStep::TRANSLATE_STEP_AFTER_TRANSLATE);
+
+  // Create but don't replace existing one.
+  TranslateInfoBarDelegate::Create(
+      /*replace_existing_infobar=*/false, manager_->GetWeakPtr(),
+      infobar_manager_.get(),
+      /*is_off_the_record=*/false,
+      translate::TranslateStep::TRANSLATE_STEP_BEFORE_TRANSLATE,
+      kOriginalLanguage, kTargetLanguage, TranslateErrors::Type::NONE,
+      /*triggered_from_menu=*/false);
+
+  EXPECT_EQ(infobar_manager_->infobar_count(), 1u);
+  delegate =
+      infobar_manager_->infobar_at(0)->delegate()->AsTranslateInfoBarDelegate();
+  ASSERT_EQ(delegate->translate_step(),
+            translate::TranslateStep::TRANSLATE_STEP_AFTER_TRANSLATE);
+}
+
+TEST_F(TranslateInfoBarDelegateTest, DestructTranslateInfobarDelegate) {
+  MockObserver mock_observer;
+  std::unique_ptr<TranslateInfoBarDelegate> delegate =
+      ConstructInfoBarDelegate();
+  EXPECT_CALL(mock_observer,
+              OnTranslateInfoBarDelegateDestroyed(delegate.get()));
+
+  delegate->AddObserver(&mock_observer);
+  delegate.reset();
+}
+
+TEST_F(TranslateInfoBarDelegateTest, IsTranslatableLanguage) {
+  // A language is translatable if it's not blocked or is not an accept
+  // language.
+  std::unique_ptr<TranslateInfoBarDelegate> delegate =
+      ConstructInfoBarDelegate();
+  TranslateAcceptLanguages accept_languages(pref_service_.get(),
+                                            testing::accept_languages_prefs);
+  ON_CALL(*(client_.get()), GetTranslateAcceptLanguages())
+      .WillByDefault(Return(&accept_languages));
+  ListPrefUpdate update(pref_service_.get(), language::prefs::kFluentLanguages);
+  update->Append(kOriginalLanguage);
+  pref_service_->SetString(language::prefs::kAcceptLanguages,
+                           kOriginalLanguage);
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  pref_service_->SetString(language::prefs::kPreferredLanguages,
+                           kOriginalLanguage);
+#endif
+
+  EXPECT_FALSE(delegate->IsTranslatableLanguageByPrefs());
+
+  // Remove kOriginalLanguage from the blocked languages.
+  update->EraseListValue(base::Value(kOriginalLanguage));
+  EXPECT_TRUE(delegate->IsTranslatableLanguageByPrefs());
+}
+
+TEST_F(TranslateInfoBarDelegateTest, ShouldAutoAlwaysTranslate) {
+  DictionaryPrefUpdate update_translate_accepted_count(
+      pref_service_.get(), TranslatePrefs::kPrefTranslateAcceptedCount);
+  base::DictionaryValue* update_translate_accepted_dict =
+      update_translate_accepted_count.Get();
+  // 6 = kAutoAlwaysThreshold + 1
+  update_translate_accepted_dict->SetInteger(kOriginalLanguage, 6);
+
+  const base::DictionaryValue* dict = pref_service_->GetDictionary(
+      TranslatePrefs::kPrefTranslateAutoAlwaysCount);
+  int translate_auto_always_count = 0;
+  dict->GetInteger(kOriginalLanguage, &translate_auto_always_count);
+  EXPECT_EQ(0, translate_auto_always_count);
+
+  TranslateInfoBarDelegate::Create(
+      /*replace_existing_infobar=*/true, manager_->GetWeakPtr(),
+      infobar_manager_.get(),
+      /*is_off_the_record=*/false,
+      translate::TranslateStep::TRANSLATE_STEP_TRANSLATING, kOriginalLanguage,
+      kTargetLanguage, TranslateErrors::Type::NONE,
+      /*triggered_from_menu=*/false);
+  TranslateInfoBarDelegate* delegate =
+      infobar_manager_->infobar_at(0)->delegate()->AsTranslateInfoBarDelegate();
+  EXPECT_TRUE(delegate->ShouldAutoAlwaysTranslate());
+
+  int count = -1;
+  update_translate_accepted_dict->GetInteger(kOriginalLanguage, &count);
+  EXPECT_EQ(0, count);
+  dict = pref_service_->GetDictionary(
+      TranslatePrefs::kPrefTranslateAutoAlwaysCount);
+  translate_auto_always_count = 0;
+  dict->GetInteger(kOriginalLanguage, &translate_auto_always_count);
+  EXPECT_EQ(1, translate_auto_always_count);
+}
+
+TEST_F(TranslateInfoBarDelegateTest, ShouldNotAutoAlwaysTranslate) {
+  // Create an off record info bar.
+  TranslateInfoBarDelegate::Create(
+      /*replace_existing_infobar=*/false, manager_->GetWeakPtr(),
+      infobar_manager_.get(), /*is_off_the_record=*/true,
+      translate::TranslateStep::TRANSLATE_STEP_TRANSLATING, kOriginalLanguage,
+      kTargetLanguage, TranslateErrors::Type::NONE,
+      /*triggered_from_menu=*/false);
+
+  EXPECT_EQ(infobar_manager_->infobar_count(), 1u);
+  TranslateInfoBarDelegate* delegate =
+      infobar_manager_->infobar_at(0)->delegate()->AsTranslateInfoBarDelegate();
+  EXPECT_FALSE(delegate->ShouldAutoAlwaysTranslate());
+}
+
+TEST_F(TranslateInfoBarDelegateTest, ShouldAutoNeverTranslate) {
+  TranslateAcceptLanguages accept_languages(pref_service_.get(),
+                                            testing::accept_languages_prefs);
+  ON_CALL(*(client_.get()), GetTranslateAcceptLanguages())
+      .WillByDefault(Return(&accept_languages));
+
+  DictionaryPrefUpdate update_translate_denied_count(
+      pref_service_.get(), TranslatePrefs::kPrefTranslateDeniedCount);
+  base::DictionaryValue* update_translate_denied_dict =
+      update_translate_denied_count.Get();
+  // 21 = kAutoNeverThreshold + 1
+  update_translate_denied_dict->SetInteger(kOriginalLanguage, 21);
+
+  const base::DictionaryValue* dict = pref_service_->GetDictionary(
+      TranslatePrefs::kPrefTranslateAutoNeverCount);
+  int translate_auto_never_count = 0;
+  dict->GetInteger(kOriginalLanguage, &translate_auto_never_count);
+  EXPECT_EQ(0, translate_auto_never_count);
+
+  TranslateInfoBarDelegate::Create(
+      /*replace_existing_infobar=*/true, manager_->GetWeakPtr(),
+      infobar_manager_.get(),
+      /*is_off_the_record=*/false,
+      translate::TranslateStep::TRANSLATE_STEP_TRANSLATING, kOriginalLanguage,
+      kTargetLanguage, TranslateErrors::Type::NONE,
+      /*triggered_from_menu=*/false);
+  TranslateInfoBarDelegate* delegate =
+      infobar_manager_->infobar_at(0)->delegate()->AsTranslateInfoBarDelegate();
+  EXPECT_TRUE(delegate->ShouldAutoNeverTranslate());
+
+  int count = -1;
+  update_translate_denied_dict->GetInteger(kOriginalLanguage, &count);
+  EXPECT_EQ(0, count);
+  dict = pref_service_->GetDictionary(
+      TranslatePrefs::kPrefTranslateAutoNeverCount);
+  translate_auto_never_count = 0;
+  dict->GetInteger(kOriginalLanguage, &translate_auto_never_count);
+  EXPECT_EQ(1, translate_auto_never_count);
+}
+
+TEST_F(TranslateInfoBarDelegateTest, ShouldAutoNeverTranslate_Not) {
+  // Create an off record info bar.
+  TranslateInfoBarDelegate::Create(
+      /*replace_existing_infobar=*/false, manager_->GetWeakPtr(),
+      infobar_manager_.get(), /*is_off_the_record=*/true,
+      translate::TranslateStep::TRANSLATE_STEP_TRANSLATING, kOriginalLanguage,
+      kTargetLanguage, TranslateErrors::Type::NONE,
+      /*triggered_from_menu=*/false);
+
+  EXPECT_EQ(infobar_manager_->infobar_count(), 1u);
+  TranslateInfoBarDelegate* delegate =
+      infobar_manager_->infobar_at(0)->delegate()->AsTranslateInfoBarDelegate();
+  EXPECT_FALSE(delegate->ShouldAutoNeverTranslate());
+}
+
+}  // namespace translate
diff --git a/components/translate/core/browser/translate_metrics_logger.h b/components/translate/core/browser/translate_metrics_logger.h
index a3c5b904..1782a3c2 100644
--- a/components/translate/core/browser/translate_metrics_logger.h
+++ b/components/translate/core/browser/translate_metrics_logger.h
@@ -9,6 +9,7 @@
 #include <string>
 
 #include "components/translate/core/common/translate_errors.h"
+#include "services/metrics/public/cpp/ukm_source_id.h"
 
 namespace translate {
 
@@ -57,6 +58,8 @@
   kMaxValue = kAutomaticTranslationByPref,
 };
 
+// These values are persisted to logs. Entries should not be renumbered and
+// numeric values should never be reused.
 enum class UIInteraction {
   kUninitialized = 0,
   kTranslate = 1,
@@ -89,6 +92,9 @@
   // won't be called again.
   virtual void RecordMetrics(bool is_final) = 0;
 
+  // Sets the UKM source ID for the current page load.
+  virtual void SetUkmSourceId(ukm::SourceId ukm_source_id) = 0;
+
   virtual void LogRankerMetrics(RankerDecision ranker_decision,
                                 uint32_t ranker_version) = 0;
 
diff --git a/components/translate/core/browser/translate_metrics_logger_impl.cc b/components/translate/core/browser/translate_metrics_logger_impl.cc
index 96460d2..9fcc6825 100644
--- a/components/translate/core/browser/translate_metrics_logger_impl.cc
+++ b/components/translate/core/browser/translate_metrics_logger_impl.cc
@@ -8,6 +8,9 @@
 #include "base/metrics/metrics_hashes.h"
 #include "base/time/default_tick_clock.h"
 #include "components/translate/core/browser/translate_manager.h"
+#include "services/metrics/public/cpp/metrics_utils.h"
+#include "services/metrics/public/cpp/ukm_builders.h"
+#include "services/metrics/public/cpp/ukm_recorder.h"
 
 namespace translate {
 
@@ -63,18 +66,74 @@
 void TranslateMetricsLoggerImpl::RecordMetrics(bool is_final) {
   UpdateTimeTranslated(current_state_is_translated_, is_foreground_);
 
+  // If a translation is still in progress, then use the previous state.
+  bool this_initial_state_is_translated =
+      is_initial_state_dependent_on_in_progress_translation_
+          ? previous_state_is_translated_
+          : initial_state_is_translated_;
+  bool this_current_state_is_translated = is_translation_in_progress_
+                                              ? previous_state_is_translated_
+                                              : current_state_is_translated_;
+
   // The first time |RecordMetrics| is called, record all page load frequency
   // UMA metrcis.
   if (sequence_no_ == 0)
-    RecordPageLoadUmaMetrics();
+    RecordPageLoadUmaMetrics(this_initial_state_is_translated,
+                             this_current_state_is_translated);
 
-  // TODO(curranmax): Log UKM metrics now that the page load is.
-  // completed. https://crbug.com/1114868.
+  // Record metrics to UKM.
+  ukm::UkmRecorder* ukm_recorder = ukm::UkmRecorder::Get();
+  ukm::builders::TranslatePageLoad(ukm_source_id_)
+      .SetSequenceNumber(sequence_no_)
+      .SetTriggerDecision(int(trigger_decision_))
+      .SetRankerDecision(int(ranker_decision_))
+      .SetRankerVersion(int(ranker_version_))
+      .SetInitialState(int(ConvertToTranslateState(
+          this_initial_state_is_translated, initial_state_is_ui_shown_,
+          initial_state_is_omnibox_icon_shown_)))
+      .SetFinalState(int(ConvertToTranslateState(
+          this_current_state_is_translated, current_state_is_ui_shown_,
+          current_state_is_omnibox_icon_shown_)))
+      .SetNumTranslations(
+          ukm::GetExponentialBucketMinForCounts1000(num_translations_))
+      .SetNumReversions(
+          ukm::GetExponentialBucketMinForCounts1000(num_reversions_))
+      .SetInitialSourceLanguage(
+          int(base::HashMetricName(initial_source_language_)))
+      .SetFinalSourceLanguage(
+          int(base::HashMetricName(current_source_language_)))
+      .SetInitialSourceLanguageInContentLanguages(
+          int(is_initial_source_language_in_users_content_languages_))
+      .SetInitialTargetLanguage(
+          int(base::HashMetricName(initial_target_language_)))
+      .SetFinalTargetLanguage(
+          int(base::HashMetricName(current_target_language_)))
+      .SetNumTargetLanguageChanges(ukm::GetExponentialBucketMinForCounts1000(
+          num_target_language_changes_))
+      .SetFirstUIInteraction(int(first_ui_interaction_))
+      .SetNumUIInteractions(
+          ukm::GetExponentialBucketMinForCounts1000(num_ui_interactions_))
+      .SetFirstTranslateError(int(first_translate_error_type_))
+      .SetNumTranslateErrors(
+          ukm::GetExponentialBucketMinForCounts1000(num_translate_errors_))
+      .SetTotalTimeTranslated(ukm::GetExponentialBucketMinForUserTiming(
+          total_time_translated_.InSeconds()))
+      .SetTotalTimeNotTranslated(ukm::GetExponentialBucketMinForUserTiming(
+          total_time_not_translated_.InSeconds()))
+      .SetMaxTimeToTranslate(ukm::GetExponentialBucketMinForUserTiming(
+          max_time_to_translate_.InMilliseconds()))
+      .Record(ukm_recorder);
 
   sequence_no_++;
 }
 
-void TranslateMetricsLoggerImpl::RecordPageLoadUmaMetrics() {
+void TranslateMetricsLoggerImpl::SetUkmSourceId(ukm::SourceId ukm_source_id) {
+  ukm_source_id_ = ukm_source_id;
+}
+
+void TranslateMetricsLoggerImpl::RecordPageLoadUmaMetrics(
+    bool initial_state_is_translated,
+    bool current_state_is_translated) {
   base::UmaHistogramEnumeration(kTranslatePageLoadRankerDecision,
                                 ranker_decision_);
   base::UmaHistogramSparse(kTranslatePageLoadRankerVersion,
@@ -85,23 +144,14 @@
       kTranslatePageLoadAutofillAssistantDeferredTriggerDecision,
       autofill_assistant_deferred_trigger_decision_);
 
-  // If a translation is still in progress, then use the previous state.
-  bool this_initial_state_is_translated =
-      is_initial_state_dependent_on_in_progress_translation_
-          ? previous_state_is_translated_
-          : initial_state_is_translated_;
-  bool this_current_state_is_translated = is_translation_in_progress_
-                                              ? previous_state_is_translated_
-                                              : current_state_is_translated_;
-
   base::UmaHistogramEnumeration(
       kTranslatePageLoadInitialState,
-      ConvertToTranslateState(this_initial_state_is_translated,
+      ConvertToTranslateState(initial_state_is_translated,
                               initial_state_is_ui_shown_,
                               initial_state_is_omnibox_icon_shown_));
   base::UmaHistogramEnumeration(
       kTranslatePageLoadFinalState,
-      ConvertToTranslateState(this_current_state_is_translated,
+      ConvertToTranslateState(current_state_is_translated,
                               current_state_is_ui_shown_,
                               current_state_is_omnibox_icon_shown_));
   base::UmaHistogramCounts10000(kTranslatePageLoadNumTranslations,
diff --git a/components/translate/core/browser/translate_metrics_logger_impl.h b/components/translate/core/browser/translate_metrics_logger_impl.h
index 93d1b12..10e4026 100644
--- a/components/translate/core/browser/translate_metrics_logger_impl.h
+++ b/components/translate/core/browser/translate_metrics_logger_impl.h
@@ -41,6 +41,7 @@
   void OnPageLoadStart(bool is_foreground) override {}
   void OnForegroundChange(bool is_foreground) override {}
   void RecordMetrics(bool is_final) override {}
+  void SetUkmSourceId(ukm::SourceId ukm_source_id) override {}
   void LogRankerMetrics(RankerDecision ranker_decision,
                         uint32_t ranker_version) override {}
   void LogTriggerDecision(TriggerDecision trigger_decision) override {}
@@ -85,6 +86,7 @@
   void OnPageLoadStart(bool is_foreground) override;
   void OnForegroundChange(bool is_foreground) override;
   void RecordMetrics(bool is_final) override;
+  void SetUkmSourceId(ukm::SourceId ukm_source_id) override;
   void LogRankerMetrics(RankerDecision ranker_decision,
                         uint32_t ranker_version) override;
   void LogTriggerDecision(TriggerDecision trigger_decision) override;
@@ -108,7 +110,8 @@
   friend class testing::TranslateMetricsLoggerImplTest;
 
   // Logs all page load frequency UMA metrics based on the stored state.
-  void RecordPageLoadUmaMetrics();
+  void RecordPageLoadUmaMetrics(bool initial_state_is_translated,
+                                bool current_stat_is_translated);
 
   // Helpter function to get the correct |TranslateState| value based on the
   // different dimensions we care about.
@@ -128,6 +131,9 @@
   // recorded UKM protos.
   unsigned int sequence_no_ = 0;
 
+  // The UKM source ID for the current page load.
+  ukm::SourceId ukm_source_id_ = ukm::kInvalidSourceId;
+
   // Tracks if the associated page is in the foreground (|true|) or the
   // background (|false|)
   bool is_foreground_ = false;
diff --git a/components/translate/core/browser/translate_metrics_logger_impl_unittest.cc b/components/translate/core/browser/translate_metrics_logger_impl_unittest.cc
index d4c3f44b..546f49f 100644
--- a/components/translate/core/browser/translate_metrics_logger_impl_unittest.cc
+++ b/components/translate/core/browser/translate_metrics_logger_impl_unittest.cc
@@ -5,23 +5,54 @@
 #include "components/translate/core/browser/translate_metrics_logger_impl.h"
 
 #include <memory>
+#include <vector>
 
 #include "base/logging.h"
 #include "base/metrics/metrics_hashes.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/simple_test_tick_clock.h"
+#include "base/test/task_environment.h"
+#include "components/ukm/test_ukm_recorder.h"
+#include "services/metrics/public/cpp/metrics_utils.h"
+#include "services/metrics/public/cpp/ukm_builders.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace translate {
 namespace testing {
 
+namespace {
+const char* kAllUkmMetricNames[] = {
+    ukm::builders::TranslatePageLoad::kSequenceNumberName,
+    ukm::builders::TranslatePageLoad::kTriggerDecisionName,
+    ukm::builders::TranslatePageLoad::kRankerDecisionName,
+    ukm::builders::TranslatePageLoad::kRankerVersionName,
+    ukm::builders::TranslatePageLoad::kInitialStateName,
+    ukm::builders::TranslatePageLoad::kFinalStateName,
+    ukm::builders::TranslatePageLoad::kNumTranslationsName,
+    ukm::builders::TranslatePageLoad::kNumReversionsName,
+    ukm::builders::TranslatePageLoad::kInitialSourceLanguageName,
+    ukm::builders::TranslatePageLoad::kFinalSourceLanguageName,
+    ukm::builders::TranslatePageLoad::
+        kInitialSourceLanguageInContentLanguagesName,
+    ukm::builders::TranslatePageLoad::kInitialTargetLanguageName,
+    ukm::builders::TranslatePageLoad::kFinalTargetLanguageName,
+    ukm::builders::TranslatePageLoad::kNumTargetLanguageChangesName,
+    ukm::builders::TranslatePageLoad::kFirstUIInteractionName,
+    ukm::builders::TranslatePageLoad::kNumUIInteractionsName,
+    ukm::builders::TranslatePageLoad::kFirstTranslateErrorName,
+    ukm::builders::TranslatePageLoad::kNumTranslateErrorsName,
+    ukm::builders::TranslatePageLoad::kTotalTimeTranslatedName,
+    ukm::builders::TranslatePageLoad::kTotalTimeNotTranslatedName,
+    ukm::builders::TranslatePageLoad::kMaxTimeToTranslateName};
+}  // namespace
+
 class TranslateMetricsLoggerImplTest : public ::testing::Test {
  public:
   void ResetTest() {
     translate_metrics_logger_ = std::make_unique<TranslateMetricsLoggerImpl>(
         nullptr /*translate_manager*/);
-
     histogram_tester_ = std::make_unique<base::HistogramTester>();
+    test_ukm_recorder_ = std::make_unique<ukm::TestAutoSetUkmRecorder>();
   }
 
   void SetUp() override { ResetTest(); }
@@ -32,6 +63,10 @@
 
   base::HistogramTester* histogram_tester() { return histogram_tester_.get(); }
 
+  ukm::TestAutoSetUkmRecorder* test_ukm_recorder() {
+    return test_ukm_recorder_.get();
+  }
+
   void CheckTranslateStateHistograms(TranslateState expected_initial_state,
                                      TranslateState expected_final_state,
                                      int expected_num_translations,
@@ -75,14 +110,341 @@
               expected_num_ui_interactions);
   }
 
+  // Helper functions to check that the metrics in the given UKM entry match
+  // expectations.
+  void CheckUkmEntrySequenceNumber(
+      const ukm::TestUkmRecorder::HumanReadableUkmEntry& ukm_entry,
+      int expected_sequence_number) {
+    EXPECT_EQ(ukm_entry.metrics.at(
+                  ukm::builders::TranslatePageLoad::kSequenceNumberName),
+              expected_sequence_number);
+  }
+
+  void CheckUkmEntryTriggerDecision(
+      const ukm::TestUkmRecorder::HumanReadableUkmEntry& ukm_entry,
+      TriggerDecision expected_trigger_decision) {
+    EXPECT_EQ(ukm_entry.metrics.at(
+                  ukm::builders::TranslatePageLoad::kTriggerDecisionName),
+              int(expected_trigger_decision));
+  }
+
+  void CheckUkmEntryRankerDecision(
+      const ukm::TestUkmRecorder::HumanReadableUkmEntry& ukm_entry,
+      RankerDecision expected_ranker_decision) {
+    EXPECT_EQ(ukm_entry.metrics.at(
+                  ukm::builders::TranslatePageLoad::kRankerDecisionName),
+              int(expected_ranker_decision));
+  }
+
+  void CheckUkmEntryRankerVersion(
+      const ukm::TestUkmRecorder::HumanReadableUkmEntry& ukm_entry,
+      uint32_t expected_ranker_version) {
+    EXPECT_EQ(ukm_entry.metrics.at(
+                  ukm::builders::TranslatePageLoad::kRankerVersionName),
+              int(expected_ranker_version));
+  }
+
+  void CheckUkmEntryInitialState(
+      const ukm::TestUkmRecorder::HumanReadableUkmEntry& ukm_entry,
+      TranslateState expected_initial_state) {
+    EXPECT_EQ(ukm_entry.metrics.at(
+                  ukm::builders::TranslatePageLoad::kInitialStateName),
+              int(expected_initial_state));
+  }
+
+  void CheckUkmEntryFinalState(
+      const ukm::TestUkmRecorder::HumanReadableUkmEntry& ukm_entry,
+      TranslateState expected_final_state) {
+    EXPECT_EQ(
+        ukm_entry.metrics.at(ukm::builders::TranslatePageLoad::kFinalStateName),
+        int(expected_final_state));
+  }
+
+  void CheckUkmEntryNumTranslations(
+      const ukm::TestUkmRecorder::HumanReadableUkmEntry& ukm_entry,
+      int expected_num_translations) {
+    EXPECT_EQ(
+        ukm_entry.metrics.at(
+            ukm::builders::TranslatePageLoad::kNumTranslationsName),
+        ukm::GetExponentialBucketMinForCounts1000(expected_num_translations));
+  }
+
+  void CheckUkmEntryNumReversions(
+      const ukm::TestUkmRecorder::HumanReadableUkmEntry& ukm_entry,
+      int expected_num_reversions) {
+    EXPECT_EQ(
+        ukm_entry.metrics.at(
+            ukm::builders::TranslatePageLoad::kNumReversionsName),
+        ukm::GetExponentialBucketMinForCounts1000(expected_num_reversions));
+  }
+
+  void CheckUkmEntryInitialSourceLanguage(
+      const ukm::TestUkmRecorder::HumanReadableUkmEntry& ukm_entry,
+      const std::string& expected_initial_source_language) {
+    EXPECT_EQ(ukm_entry.metrics.at(
+                  ukm::builders::TranslatePageLoad::kInitialSourceLanguageName),
+              int(base::HashMetricName(expected_initial_source_language)));
+  }
+
+  void CheckUkmEntryFinalSourceLanguage(
+      const ukm::TestUkmRecorder::HumanReadableUkmEntry& ukm_entry,
+      const std::string& expected_final_source_language) {
+    EXPECT_EQ(ukm_entry.metrics.at(
+                  ukm::builders::TranslatePageLoad::kFinalSourceLanguageName),
+              int(base::HashMetricName(expected_final_source_language)));
+  }
+
+  void CheckUkmEntryInitialSourceLanguageInContentLanguages(
+      const ukm::TestUkmRecorder::HumanReadableUkmEntry& ukm_entry,
+      bool expected_is_initial_source_language_in_content_languages) {
+    EXPECT_EQ(
+        ukm_entry.metrics.at(ukm::builders::TranslatePageLoad::
+                                 kInitialSourceLanguageInContentLanguagesName),
+        int(expected_is_initial_source_language_in_content_languages));
+  }
+
+  void CheckUkmEntryInitialTargetLanguage(
+      const ukm::TestUkmRecorder::HumanReadableUkmEntry& ukm_entry,
+      const std::string& expected_initial_target_language) {
+    EXPECT_EQ(ukm_entry.metrics.at(
+                  ukm::builders::TranslatePageLoad::kInitialTargetLanguageName),
+              int(base::HashMetricName(expected_initial_target_language)));
+  }
+
+  void CheckUkmEntryFinalTargetLanguage(
+      const ukm::TestUkmRecorder::HumanReadableUkmEntry& ukm_entry,
+      const std::string& expected_final_target_language) {
+    EXPECT_EQ(ukm_entry.metrics.at(
+                  ukm::builders::TranslatePageLoad::kFinalTargetLanguageName),
+              int(base::HashMetricName(expected_final_target_language)));
+  }
+
+  void CheckUkmEntryNumTargetLanguageChanges(
+      const ukm::TestUkmRecorder::HumanReadableUkmEntry& ukm_entry,
+      int expected_num_target_language_changes) {
+    EXPECT_EQ(
+        ukm_entry.metrics.at(
+            ukm::builders::TranslatePageLoad::kNumTargetLanguageChangesName),
+        ukm::GetExponentialBucketMinForCounts1000(
+            expected_num_target_language_changes));
+  }
+
+  void CheckUkmEntryFirstUIInteraction(
+      const ukm::TestUkmRecorder::HumanReadableUkmEntry& ukm_entry,
+      UIInteraction expected_first_ui_interaction) {
+    EXPECT_EQ(ukm_entry.metrics.at(
+                  ukm::builders::TranslatePageLoad::kFirstUIInteractionName),
+              int(expected_first_ui_interaction));
+  }
+
+  void CheckUkmEntryNumUIInteractions(
+      const ukm::TestUkmRecorder::HumanReadableUkmEntry& ukm_entry,
+      int expected_num_ui_interactions) {
+    EXPECT_EQ(ukm_entry.metrics.at(
+                  ukm::builders::TranslatePageLoad::kNumUIInteractionsName),
+              ukm::GetExponentialBucketMinForCounts1000(
+                  expected_num_ui_interactions));
+  }
+
+  void CheckUkmEntryFirstTranslateError(
+      const ukm::TestUkmRecorder::HumanReadableUkmEntry& ukm_entry,
+      TranslateErrors::Type expected_first_translate_error) {
+    EXPECT_EQ(ukm_entry.metrics.at(
+                  ukm::builders::TranslatePageLoad::kFirstTranslateErrorName),
+              int(expected_first_translate_error));
+  }
+
+  void CheckUkmEntryNumTranslateErrors(
+      const ukm::TestUkmRecorder::HumanReadableUkmEntry& ukm_entry,
+      int expected_num_translate_errors) {
+    EXPECT_EQ(ukm_entry.metrics.at(
+                  ukm::builders::TranslatePageLoad::kNumTranslateErrorsName),
+              ukm::GetExponentialBucketMinForCounts1000(
+                  expected_num_translate_errors));
+  }
+
+  void CheckUkmEntryTotalTimeTranslated(
+      const ukm::TestUkmRecorder::HumanReadableUkmEntry& ukm_entry,
+      const base::TimeDelta& expected_total_time_translated) {
+    EXPECT_EQ(ukm_entry.metrics.at(
+                  ukm::builders::TranslatePageLoad::kTotalTimeTranslatedName),
+              ukm::GetExponentialBucketMinForUserTiming(
+                  expected_total_time_translated.InSeconds()));
+  }
+
+  void CheckUkmEntryTotalTimeNotTranslated(
+      const ukm::TestUkmRecorder::HumanReadableUkmEntry& ukm_entry,
+      const base::TimeDelta& expected_total_time_not_translated) {
+    EXPECT_EQ(
+        ukm_entry.metrics.at(
+            ukm::builders::TranslatePageLoad::kTotalTimeNotTranslatedName),
+        ukm::GetExponentialBucketMinForUserTiming(
+            expected_total_time_not_translated.InSeconds()));
+  }
+
+  void CheckUkmEntryMaxTimeToTranslate(
+      const ukm::TestUkmRecorder::HumanReadableUkmEntry& ukm_entry,
+      const base::TimeDelta& expected_max_time_to_translate) {
+    EXPECT_EQ(ukm_entry.metrics.at(
+                  ukm::builders::TranslatePageLoad::kMaxTimeToTranslateName),
+              ukm::GetExponentialBucketMinForUserTiming(
+                  expected_max_time_to_translate.InMilliseconds()));
+  }
+
  private:
+  // Needed to set up the test UKM recorder.
+  base::test::SingleThreadTaskEnvironment task_environment_;
+
   // Test target.
   std::unique_ptr<TranslateMetricsLoggerImpl> translate_metrics_logger_;
 
   // Records the UMA histograms for each test.
   std::unique_ptr<base::HistogramTester> histogram_tester_;
+
+  // Record the UKM protos for each test.
+  std::unique_ptr<ukm::TestAutoSetUkmRecorder> test_ukm_recorder_;
 };
 
+TEST_F(TranslateMetricsLoggerImplTest, RecordUkmMetrics) {
+  // Establish constants for this test.
+  base::SimpleTestTickClock test_clock;
+
+  constexpr base::TimeDelta delay1 = base::TimeDelta::FromSeconds(100);
+  constexpr base::TimeDelta delay2 = base::TimeDelta::FromSeconds(200);
+  constexpr base::TimeDelta delay3 = base::TimeDelta::FromSeconds(300);
+  constexpr base::TimeDelta delay4 = base::TimeDelta::FromSeconds(400);
+  constexpr base::TimeDelta delay5 = base::TimeDelta::FromSeconds(500);
+
+  constexpr base::TimeDelta translation_delay1 =
+      base::TimeDelta::FromSeconds(10);
+  constexpr base::TimeDelta translation_delay2 =
+      base::TimeDelta::FromSeconds(30);
+  constexpr base::TimeDelta translation_delay3 =
+      base::TimeDelta::FromSeconds(20);
+
+  const ukm::SourceId ukm_source_id = 4321;
+
+  const RankerDecision ranker_decision = RankerDecision::kShowUI;
+  const uint32_t ranker_model_version = 1234;
+
+  const TriggerDecision trigger_decision =
+      TriggerDecision::kDisabledNeverTranslateSite;
+
+  const std::string initial_source_language = "es";
+  const bool is_initial_source_language_in_users_content_languages = true;
+  const std::string final_source_language = "it";
+
+  const std::string initial_target_language = "de";
+  const std::string final_target_language = "fr";
+
+  // Simulate a page load where the following happens: the Ranker decides to
+  // show the translate UI, the user initiates a manual translation which
+  // finishes without an error, the user reverts the translations, the user
+  // changes the source and target language, the user starts another
+  // translation but this one fails due to a network error, the user tries to
+  // translate again and this time the translation succeeds, and then finally
+  // the user closes the translate UI.
+  translate_metrics_logger()->SetInternalClockForTesting(&test_clock);
+  translate_metrics_logger()->OnPageLoadStart(true);
+  translate_metrics_logger()->SetUkmSourceId(ukm_source_id);
+
+  translate_metrics_logger()->LogInitialSourceLanguage(
+      initial_source_language,
+      is_initial_source_language_in_users_content_languages);
+  translate_metrics_logger()->LogTargetLanguage(initial_target_language);
+  translate_metrics_logger()->LogRankerMetrics(ranker_decision,
+                                               ranker_model_version);
+  translate_metrics_logger()->LogTriggerDecision(trigger_decision);
+  translate_metrics_logger()->LogUIChange(true);
+  translate_metrics_logger()->LogInitialState();
+
+  test_clock.Advance(delay1);
+
+  translate_metrics_logger()->LogUIInteraction(UIInteraction::kTranslate);
+
+  translate_metrics_logger()->LogTranslationStarted();
+  test_clock.Advance(translation_delay1);
+  translate_metrics_logger()->LogTranslationFinished(true,
+                                                     TranslateErrors::NONE);
+
+  test_clock.Advance(delay2);
+
+  translate_metrics_logger()->LogUIInteraction(UIInteraction::kRevert);
+  translate_metrics_logger()->LogReversion();
+
+  test_clock.Advance(delay3);
+
+  translate_metrics_logger()->LogUIInteraction(
+      UIInteraction::kChangeSourceLanguage);
+  translate_metrics_logger()->LogSourceLanguage(final_source_language);
+  translate_metrics_logger()->LogUIInteraction(
+      UIInteraction::kChangeTargetLanguage);
+  translate_metrics_logger()->LogTargetLanguage(final_target_language);
+  translate_metrics_logger()->LogUIInteraction(UIInteraction::kTranslate);
+
+  translate_metrics_logger()->LogTranslationStarted();
+  test_clock.Advance(translation_delay2);
+  translate_metrics_logger()->LogTranslationFinished(false,
+                                                     TranslateErrors::NETWORK);
+
+  test_clock.Advance(delay4);
+
+  translate_metrics_logger()->LogUIInteraction(UIInteraction::kTranslate);
+
+  translate_metrics_logger()->LogTranslationStarted();
+  test_clock.Advance(translation_delay3);
+  translate_metrics_logger()->LogTranslationFinished(true,
+                                                     TranslateErrors::NONE);
+
+  test_clock.Advance(delay5);
+
+  translate_metrics_logger()->LogUIInteraction(
+      UIInteraction::kCloseUIExplicitly);
+  translate_metrics_logger()->LogUIChange(false);
+
+  // Record stored metrics.
+  translate_metrics_logger()->RecordMetrics(true);
+
+  // Check that the recorded UKM proto matches expectations.
+  auto ukm_entries = test_ukm_recorder()->GetEntries(
+      ukm::builders::TranslatePageLoad::kEntryName,
+      std::vector<std::string>(std::begin(kAllUkmMetricNames),
+                               std::end(kAllUkmMetricNames)));
+
+  // Expect that ukm_entries has one element.
+  EXPECT_EQ(ukm_entries.size(), 1u);
+
+  // Only element has a source_id of ukm_source_id
+  EXPECT_EQ(ukm_entries[0].source_id, ukm_source_id);
+
+  // Check each metric in the UKM entry.
+  CheckUkmEntrySequenceNumber(ukm_entries[0], 0);
+  CheckUkmEntryTriggerDecision(ukm_entries[0], trigger_decision);
+  CheckUkmEntryRankerDecision(ukm_entries[0], ranker_decision);
+  CheckUkmEntryRankerVersion(ukm_entries[0], ranker_model_version);
+  CheckUkmEntryInitialState(ukm_entries[0],
+                            TranslateState::kNotTranslatedUIShown);
+  CheckUkmEntryFinalState(ukm_entries[0], TranslateState::kTranslatedNoUI);
+  CheckUkmEntryNumTranslations(ukm_entries[0], 2);
+  CheckUkmEntryNumReversions(ukm_entries[0], 1);
+  CheckUkmEntryInitialSourceLanguage(ukm_entries[0], initial_source_language);
+  CheckUkmEntryFinalSourceLanguage(ukm_entries[0], final_source_language);
+  CheckUkmEntryInitialSourceLanguageInContentLanguages(
+      ukm_entries[0], is_initial_source_language_in_users_content_languages);
+  CheckUkmEntryInitialTargetLanguage(ukm_entries[0], initial_target_language);
+  CheckUkmEntryFinalTargetLanguage(ukm_entries[0], final_target_language);
+  CheckUkmEntryNumTargetLanguageChanges(ukm_entries[0], 1);
+  CheckUkmEntryFirstUIInteraction(ukm_entries[0], UIInteraction::kTranslate);
+  CheckUkmEntryNumUIInteractions(ukm_entries[0], 7);
+  CheckUkmEntryFirstTranslateError(ukm_entries[0], TranslateErrors::NETWORK);
+  CheckUkmEntryNumTranslateErrors(ukm_entries[0], 1);
+  CheckUkmEntryTotalTimeTranslated(ukm_entries[0], delay2 + delay5);
+  CheckUkmEntryTotalTimeNotTranslated(
+      ukm_entries[0], delay1 + delay3 + delay4 + translation_delay1 +
+                          translation_delay2 + translation_delay3);
+  CheckUkmEntryMaxTimeToTranslate(ukm_entries[0], translation_delay3);
+}
+
 TEST_F(TranslateMetricsLoggerImplTest, MultipleRecordMetrics) {
   // Set test constants and log them with the test target.
   RankerDecision ranker_decision = RankerDecision::kShowUI;
diff --git a/content/browser/accessibility/dump_accessibility_tree_browsertest.cc b/content/browser/accessibility/dump_accessibility_tree_browsertest.cc
index 6b27692..d6409537 100644
--- a/content/browser/accessibility/dump_accessibility_tree_browsertest.cc
+++ b/content/browser/accessibility/dump_accessibility_tree_browsertest.cc
@@ -1705,11 +1705,35 @@
 }
 
 IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest,
+                       AccessibilityIframeAriaHidden) {
+  RunHtmlTest(FILE_PATH_LITERAL("iframe-aria-hidden.html"));
+}
+
+IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest, AccessibilityIframeCreate) {
+  RunHtmlTest(FILE_PATH_LITERAL("iframe-create.html"));
+}
+
+IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest,
+                       AccessibilityIframeCreateEmpty) {
+  RunHtmlTest(FILE_PATH_LITERAL("iframe-create-empty.html"));
+}
+
+IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest,
+                       AccessibilityIframeEmptyPositioned) {
+  RunHtmlTest(FILE_PATH_LITERAL("iframe-empty-positioned.html"));
+}
+
+IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest,
                        AccessibilityIframeScrollable) {
   RunHtmlTest(FILE_PATH_LITERAL("iframe-scrollable.html"));
 }
 
 IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest,
+                       AccessibilityIframeSrcdocChanged) {
+  RunHtmlTest(FILE_PATH_LITERAL("iframe-srcdoc-changed.html"));
+}
+
+IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest,
                        AccessibilityIframePostEnable) {
   enable_accessibility_after_navigating_ = true;
   RunHtmlTest(FILE_PATH_LITERAL("iframe.html"));
diff --git a/content/browser/browser_interface_binders.cc b/content/browser/browser_interface_binders.cc
index a5f5b62ac..c94711e 100644
--- a/content/browser/browser_interface_binders.cc
+++ b/content/browser/browser_interface_binders.cc
@@ -1156,6 +1156,8 @@
   map->Add<blink::mojom::PeriodicBackgroundSyncService>(
       BindServiceWorkerReceiver(
           &RenderProcessHostImpl::CreatePeriodicSyncService, host));
+  map->Add<blink::mojom::PushMessaging>(BindServiceWorkerReceiver(
+      &RenderProcessHostImpl::BindPushMessaging, host));
 }
 
 void PopulateBinderMapWithContext(
diff --git a/content/browser/renderer_host/render_process_host_impl.cc b/content/browser/renderer_host/render_process_host_impl.cc
index b3d7f24..85aaafea 100644
--- a/content/browser/renderer_host/render_process_host_impl.cc
+++ b/content/browser/renderer_host/render_process_host_impl.cc
@@ -2292,11 +2292,6 @@
           {base::TaskPriority::USER_BLOCKING, base::MayBlock()}));
 #endif
 
-  AddUIThreadInterface(
-      registry.get(),
-      base::BindRepeating(&RenderProcessHostImpl::BindPushMessagingManager,
-                          weak_factory_.GetWeakPtr()));
-
   file_system_manager_impl_.reset(new FileSystemManagerImpl(
       GetID(), storage_partition_impl_->GetFileSystemContext(),
       ChromeBlobStorageContext::GetFor(GetBrowserContext())));
@@ -2533,8 +2528,9 @@
       ->CreatePeriodicSyncService(std::move(receiver));
 }
 
-void RenderProcessHostImpl::BindPushMessagingManager(
+void RenderProcessHostImpl::BindPushMessaging(
     mojo::PendingReceiver<blink::mojom::PushMessaging> receiver) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
   push_messaging_manager_->AddPushMessagingReceiver(std::move(receiver));
 }
 
diff --git a/content/browser/renderer_host/render_process_host_impl.h b/content/browser/renderer_host/render_process_host_impl.h
index e2b3d5f..cb236a1 100644
--- a/content/browser/renderer_host/render_process_host_impl.h
+++ b/content/browser/renderer_host/render_process_host_impl.h
@@ -585,6 +585,11 @@
       mojo::PendingReceiver<media::mojom::VideoDecodePerfHistory> receiver)
       override;
 
+  // Binds `receiever` to the `PushMessagingManager` instance owned by the
+  // render process host, and is used by workers via `BrowserInterfaceBroker`.
+  void BindPushMessaging(
+      mojo::PendingReceiver<blink::mojom::PushMessaging> receiver);
+
   // Binds |receiver| to a OneShotBackgroundSyncService instance owned by the
   // StoragePartition associated with the render process host, and is used by
   // frames and service workers via BrowserInterfaceBroker.
@@ -766,8 +771,6 @@
       mojo::PendingReceiver<blink::mojom::WebDatabaseHost> receiver);
   void BindAecDumpManager(
       mojo::PendingReceiver<blink::mojom::AecDumpManager> receiver);
-  void BindPushMessagingManager(
-      mojo::PendingReceiver<blink::mojom::PushMessaging> receiver);
   void BindP2PSocketManager(
       mojo::PendingReceiver<network::mojom::P2PSocketManager> receiver);
   void CreateMediaLogRecordHost(
diff --git a/content/browser/service_worker/service_worker_controllee_request_handler.cc b/content/browser/service_worker/service_worker_controllee_request_handler.cc
index ff3bb07..485cfc6c 100644
--- a/content/browser/service_worker/service_worker_controllee_request_handler.cc
+++ b/content/browser/service_worker/service_worker_controllee_request_handler.cc
@@ -116,14 +116,14 @@
 void ServiceWorkerControlleeRequestHandler::MaybeCreateLoader(
     const network::ResourceRequest& tentative_resource_request,
     BrowserContext* browser_context,
-    ServiceWorkerLoaderCallback callback,
+    NavigationLoaderInterceptor::LoaderCallback loader_callback,
     NavigationLoaderInterceptor::FallbackCallback fallback_callback) {
   // InitializeContainerHost() will update the host. This is important to do
   // before falling back to network below, so service worker APIs still work
   // even if the service worker is bypassed for request interception.
   if (!InitializeContainerHost(tentative_resource_request)) {
     // We can't do anything other than to fall back to network.
-    std::move(callback).Run({});
+    std::move(loader_callback).Run({});
     return;
   }
 
@@ -131,7 +131,7 @@
   // request interception, or if the context is gone so we have to bypass
   // anyway.
   if (skip_service_worker_ || !context_) {
-    std::move(callback).Run({});
+    std::move(loader_callback).Run({});
     return;
   }
 
@@ -145,7 +145,7 @@
   // headers between now and when the request handler passed to
   // |loader_callback_| is invoked.
   if (ShouldFallbackToLoadOfflinePage(tentative_resource_request.headers)) {
-    std::move(callback).Run({});
+    std::move(loader_callback).Run({});
     return;
   }
 #endif  // BUILDFLAG(ENABLE_OFFLINE_PAGES)
@@ -160,7 +160,7 @@
       TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT, "URL",
       tentative_resource_request.url.spec());
 
-  loader_callback_ = std::move(callback);
+  loader_callback_ = std::move(loader_callback);
   fallback_callback_ = std::move(fallback_callback);
   registration_lookup_start_time_ = base::TimeTicks::Now();
   browser_context_ = browser_context;
@@ -426,8 +426,9 @@
       TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT, "Info",
       "Forwarded to the ServiceWorker");
   std::move(loader_callback_)
-      .Run(base::BindOnce(&ServiceWorkerMainResourceLoader::StartRequest,
-                          loader_wrapper_->get()->AsWeakPtr()));
+      .Run(base::MakeRefCounted<SingleRequestURLLoaderFactory>(
+          base::BindOnce(&ServiceWorkerMainResourceLoader::StartRequest,
+                         loader_wrapper_->get()->AsWeakPtr())));
 }
 
 void ServiceWorkerControlleeRequestHandler::DidUpdateRegistration(
diff --git a/content/browser/service_worker/service_worker_controllee_request_handler.h b/content/browser/service_worker/service_worker_controllee_request_handler.h
index 266b397..e9b5c26 100644
--- a/content/browser/service_worker/service_worker_controllee_request_handler.h
+++ b/content/browser/service_worker/service_worker_controllee_request_handler.h
@@ -52,12 +52,10 @@
   // This could get called multiple times during the lifetime in redirect
   // cases. (In fallback-to-network cases we basically forward the request
   // to the request to the next request handler)
-  using ServiceWorkerLoaderCallback =
-      base::OnceCallback<void(SingleRequestURLLoaderFactory::RequestHandler)>;
   void MaybeCreateLoader(
       const network::ResourceRequest& tentative_request,
       BrowserContext* browser_context,
-      ServiceWorkerLoaderCallback callback,
+      NavigationLoaderInterceptor::LoaderCallback loader_callback,
       NavigationLoaderInterceptor::FallbackCallback fallback_callback);
 
   // Does all initialization of |container_host_| for a request.
@@ -113,7 +111,7 @@
   bool force_update_started_;
   base::TimeTicks registration_lookup_start_time_;
 
-  ServiceWorkerLoaderCallback loader_callback_;
+  NavigationLoaderInterceptor::LoaderCallback loader_callback_;
   NavigationLoaderInterceptor::FallbackCallback fallback_callback_;
 
   ServiceWorkerAccessedCallback service_worker_accessed_callback_;
diff --git a/content/browser/service_worker/service_worker_main_resource_loader_interceptor.cc b/content/browser/service_worker/service_worker_main_resource_loader_interceptor.cc
index b74404b3..9d31f41 100644
--- a/content/browser/service_worker/service_worker_main_resource_loader_interceptor.cc
+++ b/content/browser/service_worker/service_worker_main_resource_loader_interceptor.cc
@@ -124,8 +124,7 @@
   ServiceWorkerContextCore* context_core =
       handle_->context_wrapper()->context();
   if (!context_core || !browser_context) {
-    LoaderCallbackWrapper(std::move(loader_callback),
-                          /*handler=*/{});
+    std::move(loader_callback).Run(/*handler=*/{});
     return;
   }
 
@@ -198,19 +197,15 @@
     // ControllerServiceWorkerInfoPtr and ServiceWorkerObjectHost from the
     // subresource loader params which is created by the interceptor.
     if (inherit_container_host_only) {
-      LoaderCallbackWrapper(std::move(loader_callback),
-                            /*handler=*/{});
+      std::move(loader_callback).Run(/*handler=*/{});
       return;
     }
   }
 
-  // Start the inner interceptor. We continue in
-  // LoaderCallbackWrapper() or the fallback callback is called.
+  // Start the inner interceptor. It will invoke the loader callback
+  // or fallback callback.
   handle_->interceptor()->MaybeCreateLoader(
-      tentative_resource_request, browser_context,
-      base::BindOnce(
-          &ServiceWorkerMainResourceLoaderInterceptor::LoaderCallbackWrapper,
-          GetWeakPtr(), std::move(loader_callback)),
+      tentative_resource_request, browser_context, std::move(loader_callback),
       std::move(fallback_callback));
 }
 
@@ -261,28 +256,6 @@
   return base::Optional<SubresourceLoaderParams>(std::move(params));
 }
 
-void ServiceWorkerMainResourceLoaderInterceptor::LoaderCallbackWrapper(
-    LoaderCallback loader_callback,
-    SingleRequestURLLoaderFactory::RequestHandler handler) {
-  DCHECK_CURRENTLY_ON(BrowserThread::UI);
-
-  if (!handler) {
-    std::move(loader_callback).Run({});
-    return;
-  }
-
-  // The inner interceptor wants to handle the request.
-  std::move(loader_callback)
-      .Run(base::MakeRefCounted<SingleRequestURLLoaderFactory>(
-          std::move(handler)));
-}
-
-base::WeakPtr<ServiceWorkerMainResourceLoaderInterceptor>
-ServiceWorkerMainResourceLoaderInterceptor::GetWeakPtr() {
-  DCHECK_CURRENTLY_ON(BrowserThread::UI);
-  return weak_factory_.GetWeakPtr();
-}
-
 ServiceWorkerMainResourceLoaderInterceptor::
     ServiceWorkerMainResourceLoaderInterceptor(
         base::WeakPtr<ServiceWorkerMainResourceHandle> handle,
diff --git a/content/browser/service_worker/service_worker_main_resource_loader_interceptor.h b/content/browser/service_worker/service_worker_main_resource_loader_interceptor.h
index b35cfb2..8997c46 100644
--- a/content/browser/service_worker/service_worker_main_resource_loader_interceptor.h
+++ b/content/browser/service_worker/service_worker_main_resource_loader_interceptor.h
@@ -72,12 +72,6 @@
   base::Optional<SubresourceLoaderParams> MaybeCreateSubresourceLoaderParams()
       override;
 
-  void LoaderCallbackWrapper(
-      LoaderCallback loader_callback,
-      SingleRequestURLLoaderFactory::RequestHandler handler);
-
-  base::WeakPtr<ServiceWorkerMainResourceLoaderInterceptor> GetWeakPtr();
-
  private:
   friend class ServiceWorkerMainResourceLoaderInterceptorTest;
 
@@ -132,9 +126,6 @@
   const int process_id_;
   const base::Optional<DedicatedOrSharedWorkerToken> worker_token_;
 
-  base::WeakPtrFactory<ServiceWorkerMainResourceLoaderInterceptor>
-      weak_factory_{this};
-
   DISALLOW_COPY_AND_ASSIGN(ServiceWorkerMainResourceLoaderInterceptor);
 };
 
diff --git a/content/public/browser/web_contents.h b/content/public/browser/web_contents.h
index fc894e22..c912ce5f 100644
--- a/content/public/browser/web_contents.h
+++ b/content/public/browser/web_contents.h
@@ -516,22 +516,48 @@
 
   // Internal state ------------------------------------------------------------
 
-  // Indicates whether the WebContents is being captured (e.g., for screenshots
-  // or mirroring).  Increment calls must be balanced with an equivalent number
-  // of decrement calls.  |capture_size| specifies the capturer's video
-  // resolution, but can be empty to mean "unspecified."  The first screen
-  // capturer that provides a non-empty |capture_size| will override the value
-  // returned by GetPreferredSize() until all captures have ended. |stay_hidden|
-  // determines whether to treat the underlying page as user-visible or not.
+  // Indicates whether the WebContents is being captured (e.g., for screenshots,
+  // or mirroring video and/or audio). Each IncrementCapturerCount() call must
+  // be balanced with a corresponding DecrementCapturerCount() call.
+  //
+  // Both internal-to-content and embedders must increment the capturer count
+  // while capturing to ensure "hidden rendering" optimizations are disabled.
+  // For example, renderers will be configured to produce compositor frames
+  // regardless of their "backgrounded" or on-screen occlusion state.
+  //
+  // Embedders can detect whether a WebContents is being captured (see
+  // IsBeingCaptured() below) and use this, for example, to provide an
+  // alternative user interface. So, developers should be careful to understand
+  // the side-effects from using or changing these APIs, both upstream and
+  // downstream of this API layer.
+  //
+  // |capture_size| is only used in the case of mirroring (i.e., screen capture
+  // video); otherwise, an empty gfx::Size should be provided. This specifies
+  // the capturer's target video resolution, but can be empty to mean
+  // "unspecified." This becomes a temporary override to GetPreferredSize(),
+  // allowing embedders to size the WebContents on-screen views for optimal
+  // capture quality.
+  //
+  // |stay_hidden| affects the page visibility state of the renderers (i.e., a
+  // web page can be made aware of whether it is actually user-visible). If
+  // true, the show/hide state of the WebContents will be passed to the
+  // renderers, like normal. If false, the renderers will always be told they
+  // are user-visible while being captured.
   virtual void IncrementCapturerCount(const gfx::Size& capture_size,
                                       bool stay_hidden) = 0;
   virtual void DecrementCapturerCount(bool stay_hidden) = 0;
+
+  // Returns true if audio/screenshot/video is being captured by the embedder,
+  // as indicated by calls to IncrementCapturerCount().
   virtual bool IsBeingCaptured() = 0;
-  // Returns true if there is any active capturer that called
-  // IncrementCaptureCount() with |stay_hidden|==false.
+
+  // Returns true if audio/screenshot/video is being captured by the embedder
+  // and renderers are being told they are always user-visible, as indicated by
+  // calls to IncrementCapturerCount().
   virtual bool IsBeingVisiblyCaptured() = 0;
 
   // Indicates/Sets whether all audio output from this WebContents is muted.
+  // This does not affect audio capture, just local/system output.
   virtual bool IsAudioMuted() = 0;
   virtual void SetAudioMuted(bool mute) = 0;
 
diff --git a/content/renderer/accessibility/blink_ax_tree_source.cc b/content/renderer/accessibility/blink_ax_tree_source.cc
index 686cb4b..69ec6c38 100644
--- a/content/renderer/accessibility/blink_ax_tree_source.cc
+++ b/content/renderer/accessibility/blink_ax_tree_source.cc
@@ -552,10 +552,6 @@
                              indirect_child_ids);
   }
 
-  if (src.IsScrollableContainer()) {
-    SerializeScrollAttributes(src, dst);
-  }
-
   if (dst->id == image_data_node_id_) {
     // In general, string attributes should be truncated using
     // TruncateAndAddStringAttribute, but ImageDataUrl contains a data url
@@ -705,30 +701,6 @@
       src.ContainerLiveRegionRelevant().Utf8());
 }
 
-void BlinkAXTreeSource::SerializeScrollAttributes(WebAXObject src,
-                                                  ui::AXNodeData* dst) const {
-  // Only mark as scrollable if user has actual scrollbars to use.
-  dst->AddBoolAttribute(ax::mojom::BoolAttribute::kScrollable,
-                        src.IsUserScrollable());
-  // Provide x,y scroll info if scrollable in any way (programmatically or via
-  // user).
-  const gfx::Point& scroll_offset = src.GetScrollOffset();
-  dst->AddIntAttribute(ax::mojom::IntAttribute::kScrollX, scroll_offset.x());
-  dst->AddIntAttribute(ax::mojom::IntAttribute::kScrollY, scroll_offset.y());
-
-  const gfx::Point& min_scroll_offset = src.MinimumScrollOffset();
-  dst->AddIntAttribute(ax::mojom::IntAttribute::kScrollXMin,
-                       min_scroll_offset.x());
-  dst->AddIntAttribute(ax::mojom::IntAttribute::kScrollYMin,
-                       min_scroll_offset.y());
-
-  const gfx::Point& max_scroll_offset = src.MaximumScrollOffset();
-  dst->AddIntAttribute(ax::mojom::IntAttribute::kScrollXMax,
-                       max_scroll_offset.x());
-  dst->AddIntAttribute(ax::mojom::IntAttribute::kScrollYMax,
-                       max_scroll_offset.y());
-}
-
 void BlinkAXTreeSource::SerializeChooserPopupAttributes(
     WebAXObject src,
     ui::AXNodeData* dst) const {
diff --git a/content/renderer/accessibility/blink_ax_tree_source.h b/content/renderer/accessibility/blink_ax_tree_source.h
index 4a2e02f..ebf3261 100644
--- a/content/renderer/accessibility/blink_ax_tree_source.h
+++ b/content/renderer/accessibility/blink_ax_tree_source.h
@@ -144,8 +144,6 @@
                                  ui::AXNodeData* dst) const;
   void SerializeLiveRegionAttributes(blink::WebAXObject src,
                                      ui::AXNodeData* dst) const;
-  void SerializeScrollAttributes(blink::WebAXObject src,
-                                 ui::AXNodeData* dst) const;
   void SerializeChooserPopupAttributes(blink::WebAXObject src,
                                        ui::AXNodeData* dst) const;
   void SerializeOtherScreenReaderAttributes(blink::WebAXObject src,
diff --git a/content/test/data/accessibility/html/iframe-aria-hidden-expected-android.txt b/content/test/data/accessibility/html/iframe-aria-hidden-expected-android.txt
new file mode 100644
index 0000000..32cf95fa
--- /dev/null
+++ b/content/test/data/accessibility/html/iframe-aria-hidden-expected-android.txt
@@ -0,0 +1,5 @@
+android.webkit.WebView focusable focused scrollable
+++android.view.View invisible
+++++android.view.View scrollable
+++android.view.View invisible
+++++android.view.View scrollable
diff --git a/content/test/data/accessibility/html/iframe-aria-hidden-expected-auralinux.txt b/content/test/data/accessibility/html/iframe-aria-hidden-expected-auralinux.txt
new file mode 100644
index 0000000..1c48268
--- /dev/null
+++ b/content/test/data/accessibility/html/iframe-aria-hidden-expected-auralinux.txt
@@ -0,0 +1,5 @@
+[document web]
+++[internal frame]
+++++[document web]
+++[internal frame]
+++++[document web]
\ No newline at end of file
diff --git a/content/test/data/accessibility/html/iframe-aria-hidden-expected-blink.txt b/content/test/data/accessibility/html/iframe-aria-hidden-expected-blink.txt
new file mode 100644
index 0000000..64e3db2
--- /dev/null
+++ b/content/test/data/accessibility/html/iframe-aria-hidden-expected-blink.txt
@@ -0,0 +1,11 @@
+rootWebArea
+++genericContainer ignored
+++++genericContainer ignored
+++++++iframe invisible
+++++++++rootWebArea
+++++++++++genericContainer ignored
+++++++++++++genericContainer ignored
+++++++iframe invisible
+++++++++rootWebArea
+++++++++++genericContainer ignored
+++++++++++++genericContainer ignored
diff --git a/content/test/data/accessibility/html/iframe-aria-hidden-expected-mac.txt b/content/test/data/accessibility/html/iframe-aria-hidden-expected-mac.txt
new file mode 100644
index 0000000..3a9d66f
--- /dev/null
+++ b/content/test/data/accessibility/html/iframe-aria-hidden-expected-mac.txt
@@ -0,0 +1 @@
+AXWebArea
diff --git a/content/test/data/accessibility/html/iframe-aria-hidden-expected-uia-win.txt b/content/test/data/accessibility/html/iframe-aria-hidden-expected-uia-win.txt
new file mode 100644
index 0000000..8190616
--- /dev/null
+++ b/content/test/data/accessibility/html/iframe-aria-hidden-expected-uia-win.txt
@@ -0,0 +1,5 @@
+Document
+++Document IsControlElement=false
+++++Document
+++Document IsControlElement=false
+++++Document
diff --git a/content/test/data/accessibility/html/iframe-aria-hidden-expected-win.txt b/content/test/data/accessibility/html/iframe-aria-hidden-expected-win.txt
new file mode 100644
index 0000000..5b037d3
--- /dev/null
+++ b/content/test/data/accessibility/html/iframe-aria-hidden-expected-win.txt
@@ -0,0 +1,5 @@
+ROLE_SYSTEM_DOCUMENT READONLY FOCUSABLE ia2_hypertext='<obj0><obj1>'
+++IA2_ROLE_INTERNAL_FRAME INVISIBLE ia2_hypertext='<obj0>'
+++++ROLE_SYSTEM_DOCUMENT READONLY FOCUSABLE
+++IA2_ROLE_INTERNAL_FRAME INVISIBLE ia2_hypertext='<obj0>'
+++++ROLE_SYSTEM_DOCUMENT READONLY FOCUSABLE
\ No newline at end of file
diff --git a/content/test/data/accessibility/html/iframe-aria-hidden.html b/content/test/data/accessibility/html/iframe-aria-hidden.html
new file mode 100644
index 0000000..e0f6af8
--- /dev/null
+++ b/content/test/data/accessibility/html/iframe-aria-hidden.html
@@ -0,0 +1,10 @@
+<!--
+@WIN-ALLOW:ia2_hypertext=*
+-->
+<iframe tabindex="-1" aria-hidden="true" src="frame/empty.html"
+        style="position: absolute; width: 9em; height: 9em; top: -99em;">
+</iframe>
+<!-- Use a unique @src for code that ensures all iframes are loaded -->
+<iframe aria-hidden="true" src="frame/empty.html#2"
+        style="position: absolute; width: 9em; height: 9em; top: -99em;">
+</iframe>
diff --git a/content/test/data/accessibility/html/iframe-create-empty-expected-android.txt b/content/test/data/accessibility/html/iframe-create-empty-expected-android.txt
new file mode 100644
index 0000000..4710ffa
--- /dev/null
+++ b/content/test/data/accessibility/html/iframe-create-empty-expected-android.txt
@@ -0,0 +1,4 @@
+android.webkit.WebView focusable focused scrollable
+++android.view.View
+++++android.view.View
+++++++android.view.View scrollable
\ No newline at end of file
diff --git a/content/test/data/accessibility/html/iframe-create-empty-expected-auralinux.txt b/content/test/data/accessibility/html/iframe-create-empty-expected-auralinux.txt
new file mode 100644
index 0000000..3e87e98
--- /dev/null
+++ b/content/test/data/accessibility/html/iframe-create-empty-expected-auralinux.txt
@@ -0,0 +1,4 @@
+[document web]
+++[section]
+++++[internal frame]
+++++++[document web]
\ No newline at end of file
diff --git a/content/test/data/accessibility/html/iframe-create-empty-expected-blink.txt b/content/test/data/accessibility/html/iframe-create-empty-expected-blink.txt
new file mode 100644
index 0000000..49582ee
--- /dev/null
+++ b/content/test/data/accessibility/html/iframe-create-empty-expected-blink.txt
@@ -0,0 +1,7 @@
+rootWebArea
+++genericContainer ignored
+++++genericContainer
+++++++iframe
+++++++++rootWebArea
+++++++++++genericContainer ignored
+++++++++++++genericContainer ignored
diff --git a/content/test/data/accessibility/html/iframe-create-empty-expected-mac.txt b/content/test/data/accessibility/html/iframe-create-empty-expected-mac.txt
new file mode 100644
index 0000000..92b4eb1
--- /dev/null
+++ b/content/test/data/accessibility/html/iframe-create-empty-expected-mac.txt
@@ -0,0 +1,4 @@
+AXWebArea
+++AXGroup
+++++AXGroup
+++++++AXWebArea
\ No newline at end of file
diff --git a/content/test/data/accessibility/html/iframe-create-empty-expected-uia-win.txt b/content/test/data/accessibility/html/iframe-create-empty-expected-uia-win.txt
new file mode 100644
index 0000000..a0d01774
--- /dev/null
+++ b/content/test/data/accessibility/html/iframe-create-empty-expected-uia-win.txt
@@ -0,0 +1,4 @@
+Document
+++Group IsControlElement=false
+++++Document
+++++++Document
\ No newline at end of file
diff --git a/content/test/data/accessibility/html/iframe-create-empty-expected-win.txt b/content/test/data/accessibility/html/iframe-create-empty-expected-win.txt
new file mode 100644
index 0000000..08fba45
--- /dev/null
+++ b/content/test/data/accessibility/html/iframe-create-empty-expected-win.txt
@@ -0,0 +1,4 @@
+ROLE_SYSTEM_DOCUMENT READONLY FOCUSABLE ia2_hypertext='<obj0>'
+++IA2_ROLE_SECTION ia2_hypertext='<obj0>'
+++++IA2_ROLE_INTERNAL_FRAME ia2_hypertext='<obj0>'
+++++++ROLE_SYSTEM_DOCUMENT READONLY FOCUSABLE
\ No newline at end of file
diff --git a/content/test/data/accessibility/html/iframe-create-empty.html b/content/test/data/accessibility/html/iframe-create-empty.html
new file mode 100644
index 0000000..50bb0915
--- /dev/null
+++ b/content/test/data/accessibility/html/iframe-create-empty.html
@@ -0,0 +1,10 @@
+<!--
+@WIN-ALLOW:ia2_hypertext=*
+-->
+<body>
+</body>
+<script>
+  const iframe = document.createElement('iframe');
+  iframe.src = 'frame/empty.html';
+  document.body.appendChild(iframe);
+</script>
diff --git a/content/test/data/accessibility/html/iframe-create-expected-android.txt b/content/test/data/accessibility/html/iframe-create-expected-android.txt
new file mode 100644
index 0000000..5d669779
--- /dev/null
+++ b/content/test/data/accessibility/html/iframe-create-expected-android.txt
@@ -0,0 +1,7 @@
+android.webkit.WebView focusable focused scrollable
+++android.view.View
+++++android.view.View
+++++++android.view.View scrollable
+++++++++android.view.View
+++++++++++android.view.View role_description='link' clickable focusable link name='done'
+++++++++++++android.widget.TextView name='done'
diff --git a/content/test/data/accessibility/html/iframe-create-expected-auralinux.txt b/content/test/data/accessibility/html/iframe-create-expected-auralinux.txt
new file mode 100644
index 0000000..32d4f91
--- /dev/null
+++ b/content/test/data/accessibility/html/iframe-create-expected-auralinux.txt
@@ -0,0 +1,7 @@
+[document web]
+++[section]
+++++[internal frame]
+++++++[document web]
+++++++++[section]
+++++++++++[link] name='done'
+++++++++++++[static] name='done'
\ No newline at end of file
diff --git a/content/test/data/accessibility/html/iframe-create-expected-blink.txt b/content/test/data/accessibility/html/iframe-create-expected-blink.txt
new file mode 100644
index 0000000..4add46a
--- /dev/null
+++ b/content/test/data/accessibility/html/iframe-create-expected-blink.txt
@@ -0,0 +1,10 @@
+rootWebArea
+++genericContainer ignored
+++++genericContainer
+++++++iframe
+++++++++rootWebArea
+++++++++++genericContainer ignored
+++++++++++++genericContainer
+++++++++++++++link name='done'
+++++++++++++++++staticText name='done'
+++++++++++++++++++inlineTextBox name='done'
diff --git a/content/test/data/accessibility/html/iframe-create-expected-mac.txt b/content/test/data/accessibility/html/iframe-create-expected-mac.txt
new file mode 100644
index 0000000..83dad19
--- /dev/null
+++ b/content/test/data/accessibility/html/iframe-create-expected-mac.txt
@@ -0,0 +1,7 @@
+AXWebArea
+++AXGroup
+++++AXGroup
+++++++AXWebArea
+++++++++AXGroup
+++++++++++AXLink AXDescription='done'
+++++++++++++AXStaticText AXValue='done'
diff --git a/content/test/data/accessibility/html/iframe-create-expected-uia-win.txt b/content/test/data/accessibility/html/iframe-create-expected-uia-win.txt
new file mode 100644
index 0000000..abd6efa
--- /dev/null
+++ b/content/test/data/accessibility/html/iframe-create-expected-uia-win.txt
@@ -0,0 +1,7 @@
+Document
+++Group IsControlElement=false
+++++Document
+++++++Document
+++++++++Group IsControlElement=false
+++++++++++Hyperlink Name='done'
+++++++++++++Text Name='done' IsControlElement=false
\ No newline at end of file
diff --git a/content/test/data/accessibility/html/iframe-create-expected-win.txt b/content/test/data/accessibility/html/iframe-create-expected-win.txt
new file mode 100644
index 0000000..07b8110
--- /dev/null
+++ b/content/test/data/accessibility/html/iframe-create-expected-win.txt
@@ -0,0 +1,7 @@
+ROLE_SYSTEM_DOCUMENT READONLY FOCUSABLE ia2_hypertext='<obj0>'
+++IA2_ROLE_SECTION ia2_hypertext='<obj0>'
+++++IA2_ROLE_INTERNAL_FRAME ia2_hypertext='<obj0>'
+++++++ROLE_SYSTEM_DOCUMENT value='about:srcdoc' READONLY FOCUSABLE ia2_hypertext='<obj0>'
+++++++++IA2_ROLE_SECTION ia2_hypertext='<obj0>'
+++++++++++ROLE_SYSTEM_LINK name='done' FOCUSABLE ia2_hypertext='done'
+++++++++++++ROLE_SYSTEM_STATICTEXT name='done' ia2_hypertext='done'
diff --git a/content/test/data/accessibility/html/iframe-create.html b/content/test/data/accessibility/html/iframe-create.html
new file mode 100644
index 0000000..230e51f
--- /dev/null
+++ b/content/test/data/accessibility/html/iframe-create.html
@@ -0,0 +1,11 @@
+<!--
+@WIN-ALLOW:ia2_hypertext=*
+@WAIT-FOR:done
+-->
+<body>
+</body>
+<script>
+  const iframe = document.createElement('iframe');
+  iframe.srcdoc = '<a href="/">done</a>';
+  document.body.appendChild(iframe);
+</script>
diff --git a/content/test/data/accessibility/html/iframe-empty-positioned-expected-android.txt b/content/test/data/accessibility/html/iframe-empty-positioned-expected-android.txt
new file mode 100644
index 0000000..4465176
--- /dev/null
+++ b/content/test/data/accessibility/html/iframe-empty-positioned-expected-android.txt
@@ -0,0 +1,5 @@
+android.webkit.WebView focusable focused scrollable
+++android.view.View
+++++android.view.View scrollable
+++android.view.View
+++++android.view.View scrollable
diff --git a/content/test/data/accessibility/html/iframe-empty-positioned-expected-auralinux.txt b/content/test/data/accessibility/html/iframe-empty-positioned-expected-auralinux.txt
new file mode 100644
index 0000000..1c48268
--- /dev/null
+++ b/content/test/data/accessibility/html/iframe-empty-positioned-expected-auralinux.txt
@@ -0,0 +1,5 @@
+[document web]
+++[internal frame]
+++++[document web]
+++[internal frame]
+++++[document web]
\ No newline at end of file
diff --git a/content/test/data/accessibility/html/iframe-empty-positioned-expected-blink.txt b/content/test/data/accessibility/html/iframe-empty-positioned-expected-blink.txt
new file mode 100644
index 0000000..3e04afa
--- /dev/null
+++ b/content/test/data/accessibility/html/iframe-empty-positioned-expected-blink.txt
@@ -0,0 +1,11 @@
+rootWebArea
+++genericContainer ignored
+++++genericContainer ignored
+++++++iframe
+++++++++rootWebArea
+++++++++++genericContainer ignored
+++++++++++++genericContainer ignored
+++++++iframe
+++++++++rootWebArea
+++++++++++genericContainer ignored
+++++++++++++genericContainer ignored
diff --git a/content/test/data/accessibility/html/iframe-empty-positioned-expected-mac.txt b/content/test/data/accessibility/html/iframe-empty-positioned-expected-mac.txt
new file mode 100644
index 0000000..ac1b05b
--- /dev/null
+++ b/content/test/data/accessibility/html/iframe-empty-positioned-expected-mac.txt
@@ -0,0 +1,5 @@
+AXWebArea
+++AXGroup
+++++AXWebArea
+++AXGroup
+++++AXWebArea
diff --git a/content/test/data/accessibility/html/iframe-empty-positioned-expected-uia-win.txt b/content/test/data/accessibility/html/iframe-empty-positioned-expected-uia-win.txt
new file mode 100644
index 0000000..de016ef
--- /dev/null
+++ b/content/test/data/accessibility/html/iframe-empty-positioned-expected-uia-win.txt
@@ -0,0 +1,5 @@
+Document
+++Document
+++++Document
+++Document
+++++Document
diff --git a/content/test/data/accessibility/html/iframe-empty-positioned-expected-win.txt b/content/test/data/accessibility/html/iframe-empty-positioned-expected-win.txt
new file mode 100644
index 0000000..870cb45
--- /dev/null
+++ b/content/test/data/accessibility/html/iframe-empty-positioned-expected-win.txt
@@ -0,0 +1,5 @@
+ROLE_SYSTEM_DOCUMENT READONLY FOCUSABLE ia2_hypertext='<obj0><obj1>'
+++IA2_ROLE_INTERNAL_FRAME ia2_hypertext='<obj0>'
+++++ROLE_SYSTEM_DOCUMENT READONLY FOCUSABLE
+++IA2_ROLE_INTERNAL_FRAME ia2_hypertext='<obj0>'
+++++ROLE_SYSTEM_DOCUMENT READONLY FOCUSABLE
\ No newline at end of file
diff --git a/content/test/data/accessibility/html/iframe-empty-positioned.html b/content/test/data/accessibility/html/iframe-empty-positioned.html
new file mode 100644
index 0000000..e5936932
--- /dev/null
+++ b/content/test/data/accessibility/html/iframe-empty-positioned.html
@@ -0,0 +1,10 @@
+<!--
+@WIN-ALLOW:ia2_hypertext=*
+-->
+<iframe tabindex="-1" src="frame/empty.html"
+        style="position: absolute; width: 9em; height: 9em; top: -99em;">
+</iframe>
+<!-- Use a unique @src for code that ensures all iframes are loaded -->
+<iframe onload="iframeLoaded()" src="frame/empty.html#2"
+        style="position: absolute; width: 9em; height: 9em; top: -99em;">
+</iframe>
diff --git a/content/test/data/accessibility/html/iframe-srcdoc-changed-expected-android.txt b/content/test/data/accessibility/html/iframe-srcdoc-changed-expected-android.txt
new file mode 100644
index 0000000..a0d86e77
--- /dev/null
+++ b/content/test/data/accessibility/html/iframe-srcdoc-changed-expected-android.txt
@@ -0,0 +1,4 @@
+android.webkit.WebView focusable focused scrollable name='done'
+++android.view.View
+++++android.view.View
+++++++android.view.View scrollable
\ No newline at end of file
diff --git a/content/test/data/accessibility/html/iframe-srcdoc-changed-expected-auralinux.txt b/content/test/data/accessibility/html/iframe-srcdoc-changed-expected-auralinux.txt
new file mode 100644
index 0000000..8d28d8c
--- /dev/null
+++ b/content/test/data/accessibility/html/iframe-srcdoc-changed-expected-auralinux.txt
@@ -0,0 +1,4 @@
+[document web] name='done'
+++[section]
+++++[internal frame]
+++++++[document web]
\ No newline at end of file
diff --git a/content/test/data/accessibility/html/iframe-srcdoc-changed-expected-blink.txt b/content/test/data/accessibility/html/iframe-srcdoc-changed-expected-blink.txt
new file mode 100644
index 0000000..6c84d3a
--- /dev/null
+++ b/content/test/data/accessibility/html/iframe-srcdoc-changed-expected-blink.txt
@@ -0,0 +1,7 @@
+rootWebArea name='done'
+++genericContainer ignored
+++++genericContainer
+++++++iframe
+++++++++rootWebArea
+++++++++++genericContainer ignored
+++++++++++++genericContainer ignored
\ No newline at end of file
diff --git a/content/test/data/accessibility/html/iframe-srcdoc-changed-expected-mac.txt b/content/test/data/accessibility/html/iframe-srcdoc-changed-expected-mac.txt
new file mode 100644
index 0000000..54f34edc
--- /dev/null
+++ b/content/test/data/accessibility/html/iframe-srcdoc-changed-expected-mac.txt
@@ -0,0 +1,4 @@
+AXWebArea AXTitle='done'
+++AXGroup
+++++AXGroup
+++++++AXWebArea
\ No newline at end of file
diff --git a/content/test/data/accessibility/html/iframe-srcdoc-changed-expected-uia-win.txt b/content/test/data/accessibility/html/iframe-srcdoc-changed-expected-uia-win.txt
new file mode 100644
index 0000000..b01fea99
--- /dev/null
+++ b/content/test/data/accessibility/html/iframe-srcdoc-changed-expected-uia-win.txt
@@ -0,0 +1,4 @@
+Document Name='done'
+++Group IsControlElement=false
+++++Document
+++++++Document
\ No newline at end of file
diff --git a/content/test/data/accessibility/html/iframe-srcdoc-changed-expected-win.txt b/content/test/data/accessibility/html/iframe-srcdoc-changed-expected-win.txt
new file mode 100644
index 0000000..e35bd84
--- /dev/null
+++ b/content/test/data/accessibility/html/iframe-srcdoc-changed-expected-win.txt
@@ -0,0 +1,4 @@
+ROLE_SYSTEM_DOCUMENT name='done' READONLY FOCUSABLE ia2_hypertext='<obj0>'
+++IA2_ROLE_SECTION ia2_hypertext='<obj0>'
+++++IA2_ROLE_INTERNAL_FRAME ia2_hypertext='<obj0>'
+++++++ROLE_SYSTEM_DOCUMENT value='about:srcdoc' READONLY FOCUSABLE
\ No newline at end of file
diff --git a/content/test/data/accessibility/html/iframe-srcdoc-changed.html b/content/test/data/accessibility/html/iframe-srcdoc-changed.html
new file mode 100644
index 0000000..06175d8f
--- /dev/null
+++ b/content/test/data/accessibility/html/iframe-srcdoc-changed.html
@@ -0,0 +1,18 @@
+<!--
+@WIN-ALLOW:ia2_hypertext=*
+@WAIT-FOR:done
+-->
+<body>
+</body>
+<script>
+  const iframe = document.createElement('iframe');
+  iframe.onload = () => {
+    iframe.onload = () => {
+      setTimeout(() => { document.title = 'done' }, 99);
+    };
+    iframe.srcdoc = '';
+  };
+  iframe.srcdoc = '<a href="/">link inside iframe</a>';
+  document.body.appendChild(iframe);
+</script>
+
diff --git a/content/web_test/renderer/web_ax_object_proxy.cc b/content/web_test/renderer/web_ax_object_proxy.cc
index 54f563d..b7ab5db 100644
--- a/content/web_test/renderer/web_ax_object_proxy.cc
+++ b/content/web_test/renderer/web_ax_object_proxy.cc
@@ -1459,12 +1459,12 @@
 
 int WebAXObjectProxy::ScrollX() {
   UpdateLayout();
-  return accessibility_object_.GetScrollOffset().x();
+  return GetAXNodeData().GetIntAttribute(ax::mojom::IntAttribute::kScrollX);
 }
 
 int WebAXObjectProxy::ScrollY() {
   UpdateLayout();
-  return accessibility_object_.GetScrollOffset().y();
+  return GetAXNodeData().GetIntAttribute(ax::mojom::IntAttribute::kScrollY);
 }
 
 std::string WebAXObjectProxy::ToString() {
diff --git a/docs/testing/regression-test-selection.md b/docs/testing/regression-test-selection.md
new file mode 100644
index 0000000..4f1417b
--- /dev/null
+++ b/docs/testing/regression-test-selection.md
@@ -0,0 +1,38 @@
+# Regression test selection (RTS)
+
+Regression Test Selection (RTS) is a technique to intellegently select tests to
+run, without spending too many resources on testing, but still detecting bad
+code changes.
+
+[TOC]
+
+## Current strategy
+
+The current strategy is to skip files which we deem unaffected by the CL's
+changelist. Affectedness is estimated using the heuristic that dependent files
+are committed together. We look at git history and measure how often each file
+A appears together each file B in the same commit. If A affects B,
+and B affects C, we assume A also affects C. We represent these transitive
+relationships in a graph where files are the nodes and the probability that the
+two files affect each other are the weighted edges. The distance between two
+nodes represents the (inverse) probability that these two files affect
+each other. Finally, we skip tests in files that are farther than a
+certain distance from the files in the CL.
+
+## Skipping mechanism
+
+Test skipping happens at the GN level in
+[source_set](/build/config/BUILDCONFIG.gn) and [test](/testing/test.gni)
+GN targets.
+
+## Known failure mode
+
+Consider a test file A that contains unit tests, as well as some variables
+used in another file B. When our RTS strategy excludes A, but not B, a
+compilation error will occur.
+
+## Design Docs
+
+- [File-level RTS](http://doc/1KWG82gNpkaRAchlp3jtENFdlefGvJxMHkAszlu9fo1c)
+- [Chrome RTS](http://doc/10RP1XRw8ZSrvgVky1flH7ykAaIaG15RymM4gW7bFURQ)
+
diff --git a/extensions/browser/api/BUILD.gn b/extensions/browser/api/BUILD.gn
index 30686d6..85f6363 100644
--- a/extensions/browser/api/BUILD.gn
+++ b/extensions/browser/api/BUILD.gn
@@ -134,9 +134,9 @@
       "media_perception_private/media_perception_private_api.h",
     ]
 
+    # TODO(crbug/1158984): as above, remove these deps.
     public_deps += [
       "//extensions/browser/api/cec_private",
-      "//extensions/browser/api/clipboard",
       "//extensions/browser/api/diagnostics",
       "//extensions/browser/api/virtual_keyboard",
       "//extensions/browser/api/vpn_provider",
@@ -207,6 +207,10 @@
     "//extensions/browser/api/virtual_keyboard_private",
     "//extensions/browser/api/web_request",
   ]
+
+  if (is_chromeos_ash) {
+    public_deps += [ "//extensions/browser/api/clipboard" ]
+  }
 }
 
 function_registration("api_registration") {
diff --git a/extensions/browser/api/automation_internal/automation_internal_api.cc b/extensions/browser/api/automation_internal/automation_internal_api.cc
index 091a1be9..c028d84 100644
--- a/extensions/browser/api/automation_internal/automation_internal_api.cc
+++ b/extensions/browser/api/automation_internal/automation_internal_api.cc
@@ -282,6 +282,10 @@
 
   ui::AXTreeID ax_tree_id = rfh->GetAXTreeID();
 
+  // The AXTreeID is not yet ready/set.
+  if (ax_tree_id == ui::AXTreeIDUnknown())
+    return RespondNow(Error("Tab is not ready."));
+
   // This gets removed when the extension process dies.
   AutomationEventRouter::GetInstance()->RegisterListenerForOneTree(
       extension_id(), source_process_id(), ax_tree_id);
diff --git a/extensions/browser/api/clipboard/BUILD.gn b/extensions/browser/api/clipboard/BUILD.gn
index f4d1009..145361c0 100644
--- a/extensions/browser/api/clipboard/BUILD.gn
+++ b/extensions/browser/api/clipboard/BUILD.gn
@@ -13,7 +13,11 @@
     "clipboard_api.h",
   ]
 
-  deps = [ "//extensions/common/api" ]
+  deps = [
+    "//extensions/browser/api",
+    "//extensions/common/api",
+    "//ui/base/clipboard",
+  ]
 
   public_deps = [ "//extensions/browser:browser_sources" ]
 }
diff --git a/extensions/browser/guest_view/web_view/web_view_apitest.cc b/extensions/browser/guest_view/web_view/web_view_apitest.cc
index bd5f486..815efc0 100644
--- a/extensions/browser/guest_view/web_view/web_view_apitest.cc
+++ b/extensions/browser/guest_view/web_view/web_view_apitest.cc
@@ -663,6 +663,13 @@
   RunTest("testNavOnSrcAttributeChange", "web_view/apitest");
 }
 
+IN_PROC_BROWSER_TEST_F(WebViewAPITest, TestLoadCommitUrlsWithIframe) {
+  const std::string app_location = "web_view/apitest";
+  StartTestServer(app_location);
+  RunTest("testLoadCommitUrlsWithIframe", app_location);
+  StopTestServer();
+}
+
 IN_PROC_BROWSER_TEST_F(WebViewAPITest, TestNewWindow) {
   std::string app_location = "web_view/apitest";
   StartTestServer(app_location);
diff --git a/extensions/renderer/resources/guest_view/web_view/web_view_events.js b/extensions/renderer/resources/guest_view/web_view/web_view_events.js
index 0110436a..0a04ac1 100644
--- a/extensions/renderer/resources/guest_view/web_view/web_view_events.js
+++ b/extensions/renderer/resources/guest_view/web_view/web_view_events.js
@@ -277,12 +277,6 @@
                          event.processId,
                          event.visibleUrl);
 
-  // TODO(1149615): The |url| property of the loadcommit event is documented as
-  // being set to the committed URL, but was being incorrectly set. This
-  // preserves the existing incorrect behaviour which we'll defer fixing in
-  // case existing code relies on this behaviour.
-  event.url = event.visibleUrl;
-
   var webViewEvent = this.makeDomEvent(event, eventName);
   this.view.dispatchEvent(webViewEvent);
 };
diff --git a/extensions/test/data/web_view/apitest/empty_frame.html b/extensions/test/data/web_view/apitest/empty_frame.html
new file mode 100644
index 0000000..5582149
--- /dev/null
+++ b/extensions/test/data/web_view/apitest/empty_frame.html
@@ -0,0 +1,11 @@
+<!doctype html>
+<!--
+ * Copyright (c) 2020 The Chromium Authors. All rights reserved.  Use of this
+ * source code is governed by a BSD-style license that can be found in the
+ * LICENSE file.
+-->
+<html>
+<body>
+  <p>Hello</p>
+</body>
+</html>
diff --git a/extensions/test/data/web_view/apitest/main.js b/extensions/test/data/web_view/apitest/main.js
index 0394384..8b149a8 100644
--- a/extensions/test/data/web_view/apitest/main.js
+++ b/extensions/test/data/web_view/apitest/main.js
@@ -12,6 +12,7 @@
   embedder.baseGuestURL = 'http://localhost:' + config.testServer.port;
   embedder.closeSocketURL = embedder.baseGuestURL + '/close-socket';
   embedder.emptyGuestURL = embedder.baseGuestURL + '/empty_guest.html';
+  embedder.emptyFrameURL = embedder.baseGuestURL + '/empty_frame.html';
   embedder.noReferrerGuestURL =
       embedder.baseGuestURL + '/guest_noreferrer.html';
   embedder.detectUserAgentURL = embedder.baseGuestURL + '/detect-user-agent';
@@ -1305,6 +1306,33 @@
   document.body.appendChild(webview);
 }
 
+// Tests that loadcommit has the correct |url| set when a guest's iframe commits
+// a navigation.
+function testLoadCommitUrlsWithIframe() {
+  let webview = document.createElement('webview');
+  let loadCommitSeen = 0;
+  webview.addEventListener('loadcommit', function(e) {
+    ++loadCommitSeen;
+    if (loadCommitSeen === 1) {
+      embedder.test.assertEq(embedder.emptyGuestURL, e.url);
+      embedder.test.assertEq(true, e.isTopLevel);
+      embedder.test.assertEq(embedder.emptyGuestURL, webview.src);
+      webview.executeScript({
+        code: "let iframe = document.createElement('iframe'); " +
+              "iframe.src = '" + embedder.emptyFrameURL + "'; " +
+              "document.body.appendChild(iframe);"
+      });
+    } else if (loadCommitSeen === 2) {
+      embedder.test.assertEq(embedder.emptyFrameURL, e.url);
+      embedder.test.assertEq(false, e.isTopLevel);
+      embedder.test.assertEq(embedder.emptyGuestURL, webview.src);
+      embedder.test.succeed();
+    }
+  });
+  webview.src = embedder.emptyGuestURL;
+  document.body.appendChild(webview);
+}
+
 // This test verifies that new window attachment functions as expected.
 //
 // TODO(crbug.com/594215) Test that opening a new window with a data URL is
@@ -1969,6 +1997,7 @@
   'testNavOnConsecutiveSrcAttributeChanges':
       testNavOnConsecutiveSrcAttributeChanges,
   'testNavOnSrcAttributeChange': testNavOnSrcAttributeChange,
+  'testLoadCommitUrlsWithIframe': testLoadCommitUrlsWithIframe,
   'testNewWindow': testNewWindow,
   'testNewWindowNoPreventDefault': testNewWindowNoPreventDefault,
   'testNewWindowNoReferrerLink': testNewWindowNoReferrerLink,
diff --git a/infra/config/generated/cr-buildbucket.cfg b/infra/config/generated/cr-buildbucket.cfg
index fad18e8..d8d94921 100644
--- a/infra/config/generated/cr-buildbucket.cfg
+++ b/infra/config/generated/cr-buildbucket.cfg
@@ -7879,6 +7879,10 @@
         value: 100
       }
       experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 30
+      }
+      experiments {
         key: "chromium.resultdb.result_sink.junit_tests"
         value: 100
       }
@@ -7930,6 +7934,10 @@
         value: 100
       }
       experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 30
+      }
+      experiments {
         key: "chromium.resultdb.result_sink.junit_tests"
         value: 100
       }
@@ -7981,6 +7989,10 @@
         value: 100
       }
       experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 30
+      }
+      experiments {
         key: "chromium.resultdb.result_sink.junit_tests"
         value: 100
       }
@@ -8530,6 +8542,10 @@
         value: 100
       }
       experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 30
+      }
+      experiments {
         key: "chromium.resultdb.result_sink.junit_tests"
         value: 100
       }
@@ -9544,6 +9560,10 @@
         value: 100
       }
       experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 30
+      }
+      experiments {
         key: "chromium.resultdb.result_sink.junit_tests"
         value: 100
       }
@@ -9593,6 +9613,10 @@
         value: 100
       }
       experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 30
+      }
+      experiments {
         key: "chromium.resultdb.result_sink.junit_tests"
         value: 100
       }
@@ -10459,6 +10483,10 @@
         value: 100
       }
       experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 30
+      }
+      experiments {
         key: "chromium.resultdb.result_sink.junit_tests"
         value: 100
       }
@@ -12300,6 +12328,10 @@
         value: 100
       }
       experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 30
+      }
+      experiments {
         key: "chromium.resultdb.result_sink.junit_tests"
         value: 100
       }
@@ -12553,6 +12585,10 @@
         value: 100
       }
       experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 30
+      }
+      experiments {
         key: "chromium.resultdb.result_sink.junit_tests"
         value: 100
       }
@@ -13768,6 +13804,10 @@
         value: 100
       }
       experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 30
+      }
+      experiments {
         key: "chromium.resultdb.result_sink.junit_tests"
         value: 100
       }
@@ -13818,6 +13858,10 @@
         value: 100
       }
       experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 30
+      }
+      experiments {
         key: "chromium.resultdb.result_sink.junit_tests"
         value: 100
       }
@@ -15497,6 +15541,10 @@
         value: 100
       }
       experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 30
+      }
+      experiments {
         key: "chromium.resultdb.result_sink.junit_tests"
         value: 100
       }
@@ -15548,6 +15596,10 @@
         value: 100
       }
       experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 30
+      }
+      experiments {
         key: "chromium.resultdb.result_sink.junit_tests"
         value: 100
       }
@@ -16564,6 +16616,10 @@
         value: 100
       }
       experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 30
+      }
+      experiments {
         key: "chromium.resultdb.result_sink.junit_tests"
         value: 100
       }
@@ -16615,6 +16671,10 @@
         value: 100
       }
       experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 30
+      }
+      experiments {
         key: "chromium.resultdb.result_sink.junit_tests"
         value: 100
       }
@@ -16666,6 +16726,10 @@
         value: 100
       }
       experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 30
+      }
+      experiments {
         key: "chromium.resultdb.result_sink.junit_tests"
         value: 100
       }
@@ -16717,6 +16781,10 @@
         value: 100
       }
       experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 30
+      }
+      experiments {
         key: "chromium.resultdb.result_sink.junit_tests"
         value: 100
       }
@@ -17533,6 +17601,10 @@
         value: 100
       }
       experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 30
+      }
+      experiments {
         key: "chromium.resultdb.result_sink.junit_tests"
         value: 100
       }
@@ -17890,6 +17962,10 @@
         value: 100
       }
       experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 30
+      }
+      experiments {
         key: "chromium.resultdb.result_sink.junit_tests"
         value: 100
       }
@@ -17941,6 +18017,10 @@
         value: 100
       }
       experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 30
+      }
+      experiments {
         key: "chromium.resultdb.result_sink.junit_tests"
         value: 100
       }
@@ -17992,6 +18072,10 @@
         value: 100
       }
       experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 30
+      }
+      experiments {
         key: "chromium.resultdb.result_sink.junit_tests"
         value: 100
       }
@@ -18043,6 +18127,10 @@
         value: 100
       }
       experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 30
+      }
+      experiments {
         key: "chromium.resultdb.result_sink.junit_tests"
         value: 100
       }
@@ -18094,6 +18182,10 @@
         value: 100
       }
       experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 30
+      }
+      experiments {
         key: "chromium.resultdb.result_sink.junit_tests"
         value: 100
       }
@@ -18145,6 +18237,10 @@
         value: 100
       }
       experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 30
+      }
+      experiments {
         key: "chromium.resultdb.result_sink.junit_tests"
         value: 100
       }
@@ -18351,6 +18447,10 @@
         value: 100
       }
       experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 30
+      }
+      experiments {
         key: "chromium.resultdb.result_sink.junit_tests"
         value: 100
       }
@@ -18511,6 +18611,10 @@
         value: 100
       }
       experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 30
+      }
+      experiments {
         key: "chromium.resultdb.result_sink.junit_tests"
         value: 100
       }
@@ -18564,6 +18668,10 @@
         value: 100
       }
       experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 30
+      }
+      experiments {
         key: "chromium.resultdb.result_sink.junit_tests"
         value: 100
       }
@@ -18670,6 +18778,10 @@
         value: 100
       }
       experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 30
+      }
+      experiments {
         key: "chromium.resultdb.result_sink.junit_tests"
         value: 100
       }
@@ -18776,6 +18888,10 @@
         value: 100
       }
       experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 30
+      }
+      experiments {
         key: "chromium.resultdb.result_sink.junit_tests"
         value: 100
       }
@@ -18829,6 +18945,10 @@
         value: 100
       }
       experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 30
+      }
+      experiments {
         key: "chromium.resultdb.result_sink.junit_tests"
         value: 100
       }
@@ -18882,6 +19002,10 @@
         value: 100
       }
       experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 30
+      }
+      experiments {
         key: "chromium.resultdb.result_sink.junit_tests"
         value: 100
       }
@@ -18935,6 +19059,10 @@
         value: 100
       }
       experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 30
+      }
+      experiments {
         key: "chromium.resultdb.result_sink.junit_tests"
         value: 100
       }
@@ -18988,6 +19116,10 @@
         value: 100
       }
       experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 30
+      }
+      experiments {
         key: "chromium.resultdb.result_sink.junit_tests"
         value: 100
       }
@@ -19041,6 +19173,10 @@
         value: 100
       }
       experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 30
+      }
+      experiments {
         key: "chromium.resultdb.result_sink.junit_tests"
         value: 100
       }
@@ -19398,6 +19534,10 @@
         value: 100
       }
       experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 30
+      }
+      experiments {
         key: "chromium.resultdb.result_sink.junit_tests"
         value: 100
       }
@@ -19551,6 +19691,10 @@
         value: 100
       }
       experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 30
+      }
+      experiments {
         key: "chromium.resultdb.result_sink.junit_tests"
         value: 100
       }
@@ -19653,6 +19797,10 @@
         value: 100
       }
       experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 30
+      }
+      experiments {
         key: "chromium.resultdb.result_sink.junit_tests"
         value: 100
       }
@@ -19704,6 +19852,10 @@
         value: 100
       }
       experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 30
+      }
+      experiments {
         key: "chromium.resultdb.result_sink.junit_tests"
         value: 100
       }
@@ -19755,6 +19907,10 @@
         value: 100
       }
       experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 30
+      }
+      experiments {
         key: "chromium.resultdb.result_sink.junit_tests"
         value: 100
       }
@@ -19806,6 +19962,10 @@
         value: 100
       }
       experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 30
+      }
+      experiments {
         key: "chromium.resultdb.result_sink.junit_tests"
         value: 100
       }
@@ -19908,6 +20068,10 @@
         value: 100
       }
       experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 30
+      }
+      experiments {
         key: "chromium.resultdb.result_sink.junit_tests"
         value: 100
       }
@@ -20010,6 +20174,10 @@
         value: 100
       }
       experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 30
+      }
+      experiments {
         key: "chromium.resultdb.result_sink.junit_tests"
         value: 100
       }
@@ -20061,6 +20229,10 @@
         value: 100
       }
       experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 30
+      }
+      experiments {
         key: "chromium.resultdb.result_sink.junit_tests"
         value: 100
       }
@@ -20112,6 +20284,10 @@
         value: 100
       }
       experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 30
+      }
+      experiments {
         key: "chromium.resultdb.result_sink.junit_tests"
         value: 100
       }
@@ -20163,6 +20339,10 @@
         value: 100
       }
       experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 30
+      }
+      experiments {
         key: "chromium.resultdb.result_sink.junit_tests"
         value: 100
       }
@@ -20214,6 +20394,10 @@
         value: 100
       }
       experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 30
+      }
+      experiments {
         key: "chromium.resultdb.result_sink.junit_tests"
         value: 100
       }
@@ -20316,6 +20500,10 @@
         value: 100
       }
       experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 30
+      }
+      experiments {
         key: "chromium.resultdb.result_sink.junit_tests"
         value: 100
       }
@@ -20418,6 +20606,10 @@
         value: 100
       }
       experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 30
+      }
+      experiments {
         key: "chromium.resultdb.result_sink.junit_tests"
         value: 100
       }
@@ -20723,6 +20915,10 @@
         value: 100
       }
       experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 30
+      }
+      experiments {
         key: "chromium.resultdb.result_sink.junit_tests"
         value: 100
       }
@@ -20774,6 +20970,10 @@
         value: 100
       }
       experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 30
+      }
+      experiments {
         key: "chromium.resultdb.result_sink.junit_tests"
         value: 100
       }
@@ -20825,6 +21025,10 @@
         value: 100
       }
       experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 30
+      }
+      experiments {
         key: "chromium.resultdb.result_sink.junit_tests"
         value: 100
       }
@@ -21335,6 +21539,10 @@
         value: 100
       }
       experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 30
+      }
+      experiments {
         key: "chromium.resultdb.result_sink.junit_tests"
         value: 100
       }
@@ -21438,6 +21646,10 @@
         value: 100
       }
       experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 30
+      }
+      experiments {
         key: "chromium.resultdb.result_sink.junit_tests"
         value: 100
       }
@@ -21490,6 +21702,10 @@
         value: 100
       }
       experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 30
+      }
+      experiments {
         key: "chromium.resultdb.result_sink.junit_tests"
         value: 100
       }
@@ -21542,6 +21758,10 @@
         value: 100
       }
       experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 30
+      }
+      experiments {
         key: "chromium.resultdb.result_sink.junit_tests"
         value: 100
       }
@@ -22197,6 +22417,10 @@
         value: 100
       }
       experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 30
+      }
+      experiments {
         key: "chromium.resultdb.result_sink.junit_tests"
         value: 100
       }
@@ -22247,6 +22471,10 @@
         value: 100
       }
       experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 30
+      }
+      experiments {
         key: "chromium.resultdb.result_sink.junit_tests"
         value: 100
       }
@@ -22397,6 +22625,10 @@
         value: 100
       }
       experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 30
+      }
+      experiments {
         key: "chromium.resultdb.result_sink.junit_tests"
         value: 100
       }
@@ -22447,6 +22679,10 @@
         value: 100
       }
       experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 30
+      }
+      experiments {
         key: "chromium.resultdb.result_sink.junit_tests"
         value: 100
       }
@@ -22497,6 +22733,10 @@
         value: 100
       }
       experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 30
+      }
+      experiments {
         key: "chromium.resultdb.result_sink.junit_tests"
         value: 100
       }
@@ -22694,6 +22934,10 @@
         value: 100
       }
       experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 30
+      }
+      experiments {
         key: "chromium.resultdb.result_sink.junit_tests"
         value: 100
       }
@@ -23152,6 +23396,10 @@
         value: 100
       }
       experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 30
+      }
+      experiments {
         key: "chromium.resultdb.result_sink.junit_tests"
         value: 100
       }
@@ -23407,6 +23655,10 @@
         value: 100
       }
       experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 30
+      }
+      experiments {
         key: "chromium.resultdb.result_sink.junit_tests"
         value: 100
       }
@@ -23759,6 +24011,10 @@
         value: 100
       }
       experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 30
+      }
+      experiments {
         key: "chromium.resultdb.result_sink.junit_tests"
         value: 100
       }
@@ -23810,6 +24066,10 @@
         value: 100
       }
       experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 30
+      }
+      experiments {
         key: "chromium.resultdb.result_sink.junit_tests"
         value: 100
       }
@@ -23861,6 +24121,10 @@
         value: 100
       }
       experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 30
+      }
+      experiments {
         key: "chromium.resultdb.result_sink.junit_tests"
         value: 100
       }
@@ -23912,6 +24176,10 @@
         value: 100
       }
       experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 30
+      }
+      experiments {
         key: "chromium.resultdb.result_sink.junit_tests"
         value: 100
       }
@@ -23962,6 +24230,10 @@
         value: 100
       }
       experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 30
+      }
+      experiments {
         key: "chromium.resultdb.result_sink.junit_tests"
         value: 100
       }
@@ -24011,6 +24283,10 @@
         value: 100
       }
       experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 30
+      }
+      experiments {
         key: "chromium.resultdb.result_sink.junit_tests"
         value: 100
       }
@@ -24521,6 +24797,10 @@
         value: 100
       }
       experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 30
+      }
+      experiments {
         key: "chromium.resultdb.result_sink.junit_tests"
         value: 100
       }
@@ -24776,6 +25056,10 @@
         value: 100
       }
       experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 30
+      }
+      experiments {
         key: "chromium.resultdb.result_sink.junit_tests"
         value: 100
       }
@@ -25028,6 +25312,10 @@
         value: 100
       }
       experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 30
+      }
+      experiments {
         key: "chromium.resultdb.result_sink.junit_tests"
         value: 100
       }
diff --git a/infra/config/lib/ci.star b/infra/config/lib/ci.star
index 15c5b019..f73965f 100644
--- a/infra/config/lib/ci.star
+++ b/infra/config/lib/ci.star
@@ -368,6 +368,9 @@
         builder_group = "chromium.fyi",
         execution_timeout = execution_timeout,
         goma_backend = goma_backend,
+        experiments = {
+            "chromium.resultdb.result_sink.gtests_local": 30,
+        },
         **kwargs
     )
 
diff --git a/ios/chrome/browser/reading_list/reading_list_distiller_page.h b/ios/chrome/browser/reading_list/reading_list_distiller_page.h
index 87849962..c56bcd6 100644
--- a/ios/chrome/browser/reading_list/reading_list_distiller_page.h
+++ b/ios/chrome/browser/reading_list/reading_list_distiller_page.h
@@ -5,6 +5,7 @@
 #ifndef IOS_CHROME_BROWSER_READING_LIST_READING_LIST_DISTILLER_PAGE_H_
 #define IOS_CHROME_BROWSER_READING_LIST_READING_LIST_DISTILLER_PAGE_H_
 
+#include <objc/objc.h>
 #include <memory>
 #include <string>
 
diff --git a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.arm64.zip.sha1 b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.arm64.zip.sha1
index bef2b038..f0f85075 100644
--- a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.arm64.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.arm64.zip.sha1
@@ -1 +1 @@
-3b26166428e3a19cc62e2e6bbd25c56fa56e0f14
\ No newline at end of file
+df0c5af3c01e211e892dfa7202abbd97923f42c8
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.x64.zip.sha1 b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.x64.zip.sha1
index 15ad719c..eb566b2 100644
--- a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.x64.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.x64.zip.sha1
@@ -1 +1 @@
-bade0d034e8d070145cfedc1b11d8cb4912921b9
\ No newline at end of file
+479a5cdbe222d03b81f46ad57802cbabe8d85142
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.arm64.zip.sha1 b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.arm64.zip.sha1
index 6f2432b7..51f978a2 100644
--- a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.arm64.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.arm64.zip.sha1
@@ -1 +1 @@
-517fece53c63d20775e8f6de18e075f9e9e1839d
\ No newline at end of file
+8aa76742ce99c2f6bf9abdf6fd8e1ae5d7c8b833
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.x64.zip.sha1 b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.x64.zip.sha1
index d654c36..cb695ab 100644
--- a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.x64.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.x64.zip.sha1
@@ -1 +1 @@
-2f5c59d957fae9d8facd01bfdd33fabb86a6aa4f
\ No newline at end of file
+0d6c6bcfbf96031da1e97b6ca45ec1b950c947d8
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.arm64.zip.sha1 b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.arm64.zip.sha1
index 1d9501af..d96943a 100644
--- a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.arm64.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.arm64.zip.sha1
@@ -1 +1 @@
-ce03b4b4e07bd0a2840c747488029982fdb16968
\ No newline at end of file
+6304c71654ccc27f78f770f348c50d9343ecc014
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.x64.zip.sha1 b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.x64.zip.sha1
index d46d87f..c550be42 100644
--- a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.x64.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.x64.zip.sha1
@@ -1 +1 @@
-a2ff48001fa30f443c32e3d419eedf0463e0b5d9
\ No newline at end of file
+fd43d9d0bdf5b608a75bde850d771f551137244e
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.arm64.zip.sha1 b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.arm64.zip.sha1
index 288e848..3e33356 100644
--- a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.arm64.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.arm64.zip.sha1
@@ -1 +1 @@
-28919fcd2b9da2fbc43a0ba8c5212db5b9c6f75c
\ No newline at end of file
+2f23f78fcdc06953931ecefc1454539aa68c35cb
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.x64.zip.sha1 b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.x64.zip.sha1
index 76c2772..601bf34 100644
--- a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.x64.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.x64.zip.sha1
@@ -1 +1 @@
-9bd7713148eebea17d890d5ffbd4c6c37f872fa3
\ No newline at end of file
+2cc660c0e8df8ad03130b81e30dc6cd86ca71d59
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.arm64.zip.sha1 b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.arm64.zip.sha1
index 811a3b9d..69ab0b8 100644
--- a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.arm64.zip.sha1
+++ b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.arm64.zip.sha1
@@ -1 +1 @@
-797902106a902871ac79e59ab66096f3b9419d6e
\ No newline at end of file
+d1a3948e1471f0d3eafef0d042257fce2406cbe1
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.x64.zip.sha1 b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.x64.zip.sha1
index 34f4df4..643fca35 100644
--- a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.x64.zip.sha1
+++ b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.x64.zip.sha1
@@ -1 +1 @@
-d8be2afb0bc3d6589c971a84f710f942189430cf
\ No newline at end of file
+cad27834b77d0b6819db2ecd2a9db07b1c386f06
\ No newline at end of file
diff --git a/ios/web/web_view_only/BUILD.gn b/ios/web/web_view_only/BUILD.gn
index c6f6054..e4b06df 100644
--- a/ios/web/web_view_only/BUILD.gn
+++ b/ios/web/web_view_only/BUILD.gn
@@ -31,6 +31,7 @@
 
   deps = [
     "//base",
+    "//ios/web/public",
     "//ios/web/public/web_view_only",
     "//ios/web/test:test_support",
 
diff --git a/media/audio/android/audio_android_unittest.cc b/media/audio/android/audio_android_unittest.cc
index e838ced..8dfff90 100644
--- a/media/audio/android/audio_android_unittest.cc
+++ b/media/audio/android/audio_android_unittest.cc
@@ -203,7 +203,8 @@
     // sufficient data remaining in the file to fill up the complete frame.
     int frames = max_size / (dest->channels() * kBytesPerSample);
     if (max_size) {
-      dest->FromInterleaved(file_->data() + pos_, frames, kBytesPerSample);
+      auto* source = reinterpret_cast<const int16_t*>(file_->data() + pos_);
+      dest->FromInterleaved<SignedInt16SampleTypeTraits>(source, frames);
       pos_ += max_size;
     }
 
diff --git a/media/audio/audio_low_latency_input_output_unittest.cc b/media/audio/audio_low_latency_input_output_unittest.cc
index dba6a55..859181d 100644
--- a/media/audio/audio_low_latency_input_output_unittest.cc
+++ b/media/audio/audio_low_latency_input_output_unittest.cc
@@ -210,8 +210,12 @@
       EXPECT_EQ(channels_, dest->channels());
       size = std::min(dest->frames() * frame_size_, size);
       EXPECT_EQ(static_cast<size_t>(size) % sizeof(*dest->channel(0)), 0U);
-      dest->FromInterleaved(source, size / frame_size_,
-                            frame_size_ / channels_);
+
+      // We should only have 16 bits per sample.
+      DCHECK_EQ(frame_size_ / channels_, 2);
+      dest->FromInterleaved<SignedInt16SampleTypeTraits>(
+          reinterpret_cast<const int16_t*>(source), size / channels_);
+
       buffer_->Seek(size);
       return size / frame_size_;
     }
diff --git a/media/audio/win/waveout_output_win.cc b/media/audio/win/waveout_output_win.cc
index d36d8571..c2c96fc 100644
--- a/media/audio/win/waveout_output_win.cc
+++ b/media/audio/win/waveout_output_win.cc
@@ -344,8 +344,10 @@
     // Note: If this ever changes to output raw float the data must be clipped
     // and sanitized since it may come from an untrusted source such as NaCl.
     audio_bus_->Scale(volume_);
-    audio_bus_->ToInterleaved(
-        frames_filled, format_.Format.wBitsPerSample / 8, buffer->lpData);
+
+    DCHECK_EQ(format_.Format.wBitsPerSample, 16);
+    audio_bus_->ToInterleaved<SignedInt16SampleTypeTraits>(
+        frames_filled, reinterpret_cast<int16_t*>(buffer->lpData));
 
     buffer->dwBufferLength = used * format_.Format.nChannels / channels_;
   } else {
diff --git a/media/base/audio_block_fifo.cc b/media/base/audio_block_fifo.cc
index 85fa6d27..ad49cff 100644
--- a/media/base/audio_block_fifo.cc
+++ b/media/base/audio_block_fifo.cc
@@ -116,13 +116,33 @@
         std::min(block_frames_ - write_pos_, frames_to_push);
 
     if (source) {
-      // Deinterleave the content to the FIFO and update the |write_pos_|.
-      current_block->FromInterleavedPartial(source_ptr, write_pos_, push_frames,
-                                            bytes_per_sample);
+      // Deinterleave the content to the FIFO.
+      switch (bytes_per_sample) {
+        case 1:
+          current_block->FromInterleavedPartial<UnsignedInt8SampleTypeTraits>(
+              source_ptr, write_pos_, push_frames);
+          break;
+        case 2:
+          current_block->FromInterleavedPartial<SignedInt16SampleTypeTraits>(
+              reinterpret_cast<const int16_t*>(source_ptr), write_pos_,
+              push_frames);
+          break;
+        case 4:
+          current_block->FromInterleavedPartial<SignedInt32SampleTypeTraits>(
+              reinterpret_cast<const int32_t*>(source_ptr), write_pos_,
+              push_frames);
+          break;
+        default:
+          NOTREACHED() << "Unsupported bytes per sample encountered: "
+                       << bytes_per_sample;
+          current_block->ZeroFramesPartial(write_pos_, push_frames);
+      }
     } else {
       current_block->ZeroFramesPartial(write_pos_, push_frames);
     }
+
     write_pos_ = (write_pos_ + push_frames) % block_frames_;
+
     if (!write_pos_) {
       // The current block is completely filled, increment |write_block_| and
       // |available_blocks_|.
diff --git a/media/base/audio_bus.cc b/media/base/audio_bus.cc
index 694b53e..76ee5c2 100644
--- a/media/base/audio_bus.cc
+++ b/media/base/audio_bus.cc
@@ -273,81 +273,6 @@
     channel_data_.push_back(data + i * aligned_frames);
 }
 
-// Forwards to non-deprecated version.
-void AudioBus::FromInterleaved(const void* source,
-                               int frames,
-                               int bytes_per_sample) {
-  DCHECK(!is_bitstream_format_);
-  switch (bytes_per_sample) {
-    case 1:
-      FromInterleaved<UnsignedInt8SampleTypeTraits>(
-          reinterpret_cast<const uint8_t*>(source), frames);
-      break;
-    case 2:
-      FromInterleaved<SignedInt16SampleTypeTraits>(
-          reinterpret_cast<const int16_t*>(source), frames);
-      break;
-    case 4:
-      FromInterleaved<SignedInt32SampleTypeTraits>(
-          reinterpret_cast<const int32_t*>(source), frames);
-      break;
-    default:
-      NOTREACHED() << "Unsupported bytes per sample encountered: "
-                   << bytes_per_sample;
-      ZeroFrames(frames);
-  }
-}
-
-// Forwards to non-deprecated version.
-void AudioBus::FromInterleavedPartial(const void* source,
-                                      int start_frame,
-                                      int frames,
-                                      int bytes_per_sample) {
-  DCHECK(!is_bitstream_format_);
-  switch (bytes_per_sample) {
-    case 1:
-      FromInterleavedPartial<UnsignedInt8SampleTypeTraits>(
-          reinterpret_cast<const uint8_t*>(source), start_frame, frames);
-      break;
-    case 2:
-      FromInterleavedPartial<SignedInt16SampleTypeTraits>(
-          reinterpret_cast<const int16_t*>(source), start_frame, frames);
-      break;
-    case 4:
-      FromInterleavedPartial<SignedInt32SampleTypeTraits>(
-          reinterpret_cast<const int32_t*>(source), start_frame, frames);
-      break;
-    default:
-      NOTREACHED() << "Unsupported bytes per sample encountered: "
-                   << bytes_per_sample;
-      ZeroFramesPartial(start_frame, frames);
-  }
-}
-
-// Forwards to non-deprecated version.
-void AudioBus::ToInterleaved(int frames,
-                             int bytes_per_sample,
-                             void* dest) const {
-  DCHECK(!is_bitstream_format_);
-  switch (bytes_per_sample) {
-    case 1:
-      ToInterleaved<UnsignedInt8SampleTypeTraits>(
-          frames, reinterpret_cast<uint8_t*>(dest));
-      break;
-    case 2:
-      ToInterleaved<SignedInt16SampleTypeTraits>(
-          frames, reinterpret_cast<int16_t*>(dest));
-      break;
-    case 4:
-      ToInterleaved<SignedInt32SampleTypeTraits>(
-          frames, reinterpret_cast<int32_t*>(dest));
-      break;
-    default:
-      NOTREACHED() << "Unsupported bytes per sample encountered: "
-                   << bytes_per_sample;
-  }
-}
-
 void AudioBus::CopyTo(AudioBus* dest) const {
   dest->set_is_bitstream_format(is_bitstream_format());
   if (is_bitstream_format()) {
diff --git a/media/base/audio_bus.h b/media/base/audio_bus.h
index 024fee8..1c520279 100644
--- a/media/base/audio_bus.h
+++ b/media/base/audio_bus.h
@@ -104,11 +104,6 @@
       const typename SourceSampleTypeTraits::ValueType* source_buffer,
       int num_frames_to_write);
 
-  // DEPRECATED (https://crbug.com/580391)
-  // Please use the version templated with SourceSampleTypeTraits instead.
-  // TODO(chfremer): Remove (https://crbug.com/619623)
-  void FromInterleaved(const void* source, int frames, int bytes_per_sample);
-
   // Similar to FromInterleaved...(), but overwrites the frames starting at a
   // given offset |write_offset_in_frames| and does not zero out frames that are
   // not overwritten.
@@ -118,12 +113,6 @@
       int write_offset_in_frames,
       int num_frames_to_write);
 
-  // DEPRECATED (https://crbug.com/580391)
-  // Please use the version templated with SourceSampleTypeTraits instead.
-  // TODO(chfremer): Remove (https://crbug.com/619623)
-  void FromInterleavedPartial(const void* source, int start_frame, int frames,
-                              int bytes_per_sample);
-
   // Reads the sample values stored in this AudioBus instance and places them
   // into the given |dest_buffer| in interleaved format using the sample format
   // specified by TargetSampleTypeTraits. For a list of ready-to-use
@@ -134,11 +123,6 @@
       int num_frames_to_read,
       typename TargetSampleTypeTraits::ValueType* dest_buffer) const;
 
-  // DEPRECATED (https://crbug.com/580391)
-  // Please use the version templated with TargetSampleTypeTraits instead.
-  // TODO(chfremer): Remove (https://crbug.com/619623)
-  void ToInterleaved(int frames, int bytes_per_sample, void* dest) const;
-
   // Similar to ToInterleaved(), but reads the frames starting at a given
   // offset |read_offset_in_frames|.
   template <class TargetSampleTypeTraits>
diff --git a/media/base/audio_bus_unittest.cc b/media/base/audio_bus_unittest.cc
index 79bb101..38d5cbd09 100644
--- a/media/base/audio_bus_unittest.cc
+++ b/media/base/audio_bus_unittest.cc
@@ -335,40 +335,6 @@
            kTestVectorFrameCount * sizeof(*expected->channel(ch)));
   }
 
-  // Test deprecated version that takes |bytes_per_sample| as an input.
-  {
-    SCOPED_TRACE("uint8_t");
-    bus->Zero();
-    bus->FromInterleaved(kTestVectorUint8, kTestVectorFrameCount,
-                         sizeof(*kTestVectorUint8));
-
-    // Biased uint8_t calculations have poor precision, so the epsilon here is
-    // slightly more permissive than int16_t and int32_t calculations.
-    VerifyAreEqualWithEpsilon(bus.get(), expected.get(),
-                              1.0f / (std::numeric_limits<uint8_t>::max() - 1));
-  }
-  {
-    SCOPED_TRACE("int16_t");
-    bus->Zero();
-    bus->FromInterleaved(kTestVectorInt16, kTestVectorFrameCount,
-                         sizeof(*kTestVectorInt16));
-    VerifyAreEqualWithEpsilon(
-        bus.get(), expected.get(),
-        1.0f / (std::numeric_limits<uint16_t>::max() + 1.0f));
-  }
-  {
-    SCOPED_TRACE("int32_t");
-    bus->Zero();
-    bus->FromInterleaved(kTestVectorInt32, kTestVectorFrameCount,
-                         sizeof(*kTestVectorInt32));
-
-    VerifyAreEqualWithEpsilon(
-        bus.get(), expected.get(),
-        1.0f / (std::numeric_limits<uint32_t>::max() + 1.0f));
-  }
-
-  // Test non-deprecated version that takes SampleTypeTraits as a template
-  // parameter.
   {
     SCOPED_TRACE("UnsignedInt8SampleTypeTraits");
     bus->Zero();
@@ -424,18 +390,6 @@
            kPartialFrames * sizeof(*expected->channel(ch)));
   }
 
-  // Test deprecated version that takes |bytes_per_sample| as an input.
-  {
-    SCOPED_TRACE("int32_t");
-    bus->Zero();
-    bus->FromInterleavedPartial(
-        kTestVectorInt32 + kPartialStart * bus->channels(), kPartialStart,
-        kPartialFrames, sizeof(*kTestVectorInt32));
-    VerifyAreEqual(bus.get(), expected.get());
-  }
-
-  // Test non-deprecated version that takes SampleTypeTraits as a template
-  // parameter.
   {
     SCOPED_TRACE("SignedInt32SampleTypeTraits");
     bus->Zero();
@@ -456,43 +410,6 @@
            kTestVectorFrameCount * sizeof(*bus->channel(ch)));
   }
 
-  // Test deprecated version that takes |bytes_per_sample| as an input.
-  {
-    SCOPED_TRACE("uint8_t");
-    uint8_t test_array[base::size(kTestVectorUint8)];
-    bus->ToInterleaved(bus->frames(), sizeof(*kTestVectorUint8), test_array);
-    ASSERT_EQ(0,
-              memcmp(test_array, kTestVectorUint8, sizeof(kTestVectorUint8)));
-  }
-  {
-    SCOPED_TRACE("int16_t");
-    int16_t test_array[base::size(kTestVectorInt16)];
-    bus->ToInterleaved(bus->frames(), sizeof(*kTestVectorInt16), test_array);
-    ASSERT_EQ(0,
-              memcmp(test_array, kTestVectorInt16, sizeof(kTestVectorInt16)));
-  }
-  {
-    SCOPED_TRACE("int32_t");
-    int32_t test_array[base::size(kTestVectorInt32)];
-    bus->ToInterleaved(bus->frames(), sizeof(*kTestVectorInt32), test_array);
-
-    // Some compilers get better precision than others on the half-max test, so
-    // let the test pass with an off by one check on the half-max.
-    int32_t alternative_acceptable_result[base::size(kTestVectorInt32)];
-    memcpy(alternative_acceptable_result, kTestVectorInt32,
-           sizeof(kTestVectorInt32));
-    ASSERT_EQ(alternative_acceptable_result[4],
-              std::numeric_limits<int32_t>::max() / 2);
-    alternative_acceptable_result[4]++;
-
-    ASSERT_TRUE(
-        memcmp(test_array, kTestVectorInt32, sizeof(kTestVectorInt32)) == 0 ||
-        memcmp(test_array, alternative_acceptable_result,
-               sizeof(alternative_acceptable_result)) == 0);
-  }
-
-  // Test non-deprecated version that takes SampleTypeTraits as a template
-  // parameter.
   {
     SCOPED_TRACE("UnsignedInt8SampleTypeTraits");
     uint8_t test_array[base::size(kTestVectorUint8)];
diff --git a/media/filters/audio_file_reader.cc b/media/filters/audio_file_reader.cc
index cb81d92..df95bac9 100644
--- a/media/filters/audio_file_reader.cc
+++ b/media/filters/audio_file_reader.cc
@@ -276,9 +276,25 @@
              sizeof(float) * frames_read);
     }
   } else {
-    audio_bus->FromInterleaved(
-        frame->data[0], frames_read,
-        av_get_bytes_per_sample(codec_context_->sample_fmt));
+    int bytes_per_sample = av_get_bytes_per_sample(codec_context_->sample_fmt);
+    switch (bytes_per_sample) {
+      case 1:
+        audio_bus->FromInterleaved<UnsignedInt8SampleTypeTraits>(
+            reinterpret_cast<const uint8_t*>(frame->data[0]), frames_read);
+        break;
+      case 2:
+        audio_bus->FromInterleaved<SignedInt16SampleTypeTraits>(
+            reinterpret_cast<const int16_t*>(frame->data[0]), frames_read);
+        break;
+      case 4:
+        audio_bus->FromInterleaved<SignedInt32SampleTypeTraits>(
+            reinterpret_cast<const int32_t*>(frame->data[0]), frames_read);
+        break;
+      default:
+        NOTREACHED() << "Unsupported bytes per sample encountered: "
+                     << bytes_per_sample;
+        audio_bus->ZeroFrames(frames_read);
+    }
   }
 
   (*total_frames) += frames_read;
diff --git a/media/gpu/android/media_codec_video_decoder_unittest.cc b/media/gpu/android/media_codec_video_decoder_unittest.cc
index 0fc5de4..e76456ae 100644
--- a/media/gpu/android/media_codec_video_decoder_unittest.cc
+++ b/media/gpu/android/media_codec_video_decoder_unittest.cc
@@ -21,6 +21,7 @@
 #include "media/base/decoder_buffer.h"
 #include "media/base/media_util.h"
 #include "media/base/test_helpers.h"
+#include "media/base/video_codecs.h"
 #include "media/base/video_frame.h"
 #include "media/gpu/android/android_video_surface_chooser_impl.h"
 #include "media/gpu/android/fake_codec_allocator.h"
@@ -996,6 +997,8 @@
     test_codecs.push_back(kCodecVP8);
   if (MediaCodecUtil::IsVp9DecoderAvailable())
     test_codecs.push_back(kCodecVP9);
+  if (MediaCodecUtil::IsAv1DecoderAvailable())
+    test_codecs.push_back(kCodecAV1);
   return test_codecs;
 }
 
@@ -1013,6 +1016,12 @@
              : std::vector<VideoCodec>();
 }
 
+static std::vector<VideoCodec> GetAv1IfAvailable() {
+  return MediaCodecUtil::IsAv1DecoderAvailable()
+             ? std::vector<VideoCodec>(1, kCodecAV1)
+             : std::vector<VideoCodec>();
+}
+
 INSTANTIATE_TEST_SUITE_P(MediaCodecVideoDecoderTest,
                          MediaCodecVideoDecoderTest,
                          testing::ValuesIn(GetTestList()));
@@ -1027,4 +1036,8 @@
                          MediaCodecVideoDecoderVp8Test,
                          testing::ValuesIn(GetVp8IfAvailable()));
 
+INSTANTIATE_TEST_SUITE_P(MediaCodecVideoDecoderAV1Test,
+                         MediaCodecVideoDecoderAV1Test,
+                         testing::ValuesIn(GetAv1IfAvailable()));
+
 }  // namespace media
diff --git a/media/gpu/vaapi/h264_vaapi_video_decoder_delegate.cc b/media/gpu/vaapi/h264_vaapi_video_decoder_delegate.cc
index 6ac0d7f..6d23197 100644
--- a/media/gpu/vaapi/h264_vaapi_video_decoder_delegate.cc
+++ b/media/gpu/vaapi/h264_vaapi_video_decoder_delegate.cc
@@ -369,11 +369,6 @@
   bool uses_crypto = false;
   VAEncryptionParameters crypto_params = {};
   if (IsEncryptedSession()) {
-    // Always indicate full sample since H264 can support that and we don't know
-    // yet which it is.
-    // TODO(jkardatzke): Fix full sample encryption, specifying true for it here
-    // always will cause H264 subsample to fail and we don't know which it is
-    // yet.
     const ProtectedSessionState state = SetupDecryptDecode(
         /*full_sample=*/false, size, &crypto_params, &encryption_segment_info_,
         subsamples);
diff --git a/media/gpu/vaapi/test/decode.cc b/media/gpu/vaapi/test/decode.cc
index 904e491..ae4e596 100644
--- a/media/gpu/vaapi/test/decode.cc
+++ b/media/gpu/vaapi/test/decode.cc
@@ -41,6 +41,7 @@
     "           --video=<video path>\n"
     "           [--frames=<number of frames to decode>]\n"
     "           [--out-prefix=<path prefix of decoded frame PNGs>]\n"
+    "           [--loop]\n"
     "           [--v=<log verbosity>]\n"
     "           [--help]\n";
 
@@ -59,21 +60,19 @@
     "        prefix (which may specify a directory) is provided here,\n"
     "        resulting in e.g. frame_0.png, frame_1.png, etc. if passed\n"
     "        \"frame\".\n"
+    "        If specified along with --loop (see below), only saves the first\n"
+    "        iteration of decoded frames.\n"
     "        If omitted, the output of this binary is error or lack thereof.\n"
+    "    --loop\n"
+    "        Optional. If specified, loops decoding until terminated\n"
+    "        externally or until an error occurs, at which point the current\n"
+    "        pass through the video completes and the binary exits.\n"
+    "        If specified with --frames, loops decoding that number of\n"
+    "        leading frames. If specified with --out-prefix, loops decoding,\n"
+    "        but only saves the first iteration of decoded frames.\n"
     "    --help\n"
     "        Display this help message and exit.\n";
 
-// Creates the appropriate decoder for the given |fourcc|.
-std::unique_ptr<VideoDecoder> CreateDecoder(
-    uint32_t fourcc,
-    std::unique_ptr<media::IvfParser> ivf_parser,
-    const VaapiDevice& va_device) {
-  if (fourcc == fourcc('V', 'P', '9', '0'))
-    return std::make_unique<Vp9Decoder>(std::move(ivf_parser), va_device);
-
-  return nullptr;
-}
-
 // Returns string representation of |fourcc|.
 std::string FourccStr(uint32_t fourcc) {
   std::stringstream s;
@@ -84,6 +83,30 @@
   return s.str();
 }
 
+// Creates the appropriate decoder for |stream_data| which is expected to point
+// to IVF data of length |stream_len|. The decoder will use |va_device| to issue
+// VAAPI calls. Returns nullptr on failure.
+std::unique_ptr<VideoDecoder> CreateDecoder(const VaapiDevice& va_device,
+                                            const uint8_t* stream_data,
+                                            size_t stream_len) {
+  // Set up video parser.
+  auto ivf_parser = std::make_unique<media::IvfParser>();
+  media::IvfFileHeader file_header{};
+  if (!ivf_parser->Initialize(stream_data, stream_len, &file_header)) {
+    LOG(ERROR) << "Couldn't initialize IVF parser";
+    return nullptr;
+  }
+
+  // Create appropriate decoder for codec.
+  VLOG(1) << "Creating decoder with codec " << FourccStr(file_header.fourcc);
+  if (file_header.fourcc == fourcc('V', 'P', '9', '0'))
+    return std::make_unique<Vp9Decoder>(std::move(ivf_parser), va_device);
+
+  LOG(ERROR) << "Codec " << FourccStr(file_header.fourcc) << " not supported.\n"
+             << kUsageMsg;
+  return nullptr;
+}
+
 }  // namespace
 
 int main(int argc, char** argv) {
@@ -119,20 +142,6 @@
     return EXIT_FAILURE;
   }
 
-  // Set up video stream and parser.
-  base::MemoryMappedFile stream;
-  if (!stream.Initialize(video_path)) {
-    LOG(ERROR) << "Couldn't open file: " << video_path;
-    return EXIT_FAILURE;
-  }
-
-  auto ivf_parser = std::make_unique<media::IvfParser>();
-  media::IvfFileHeader file_header{};
-  if (!ivf_parser->Initialize(stream.data(), stream.length(), &file_header)) {
-    LOG(ERROR) << "Couldn't initialize IVF parser for file: " << video_path;
-    return EXIT_FAILURE;
-  }
-
   // Initialize VA stubs.
   StubPathMap paths;
   const std::string va_suffix(base::NumberToString(VA_MAJOR_VERSION + 1));
@@ -146,43 +155,53 @@
     return EXIT_FAILURE;
   }
 
-  const VaapiDevice va_device;
-  std::unique_ptr<VideoDecoder> dec =
-      CreateDecoder(file_header.fourcc, std::move(ivf_parser), va_device);
-  if (!dec) {
-    LOG(ERROR) << "Codec " << FourccStr(file_header.fourcc)
-               << " not supported.\n"
-               << kUsageMsg;
+  // Set up video stream.
+  base::MemoryMappedFile stream;
+  if (!stream.Initialize(video_path)) {
+    LOG(ERROR) << "Couldn't open file: " << video_path;
     return EXIT_FAILURE;
   }
-  VLOG(1) << "Created decoder for codec " << FourccStr(file_header.fourcc);
 
-  VideoDecoder::Result res;
-  int i = 0;
+  const VaapiDevice va_device;
+  const bool loop_decode = cmd->HasSwitch("loop");
+  bool first_loop = true;
   bool errored = false;
-  while (true) {
-    LOG(INFO) << "Frame " << i << "...";
-    res = dec->DecodeNextFrame();
 
-    if (res == VideoDecoder::kEOStream) {
-      LOG(INFO) << "End of stream.";
-      break;
+  do {
+    const std::unique_ptr<VideoDecoder> dec =
+        CreateDecoder(va_device, stream.data(), stream.length());
+    if (!dec) {
+      LOG(ERROR) << "Failed to create decoder for file: " << video_path;
+      return EXIT_FAILURE;
+    }
+    int i = 0;
+
+    while (true) {
+      LOG(INFO) << "Frame " << i << "...";
+      const VideoDecoder::Result res = dec->DecodeNextFrame();
+
+      if (res == VideoDecoder::kEOStream) {
+        LOG(INFO) << "End of stream.";
+        break;
+      }
+
+      if (res == VideoDecoder::kFailed) {
+        LOG(ERROR) << "Failed to decode.";
+        errored = true;
+        continue;
+      }
+
+      if (!output_prefix.empty() && first_loop) {
+        dec->LastDecodedFrameToPNG(
+            base::StringPrintf("%s_%d.png", output_prefix.c_str(), i));
+      }
+
+      if (++i == n_frames)
+        break;
     }
 
-    if (res == VideoDecoder::kFailed) {
-      LOG(ERROR) << "Failed to decode.";
-      errored = true;
-      continue;
-    }
-
-    if (!output_prefix.empty()) {
-      dec->LastDecodedFrameToPNG(
-          base::StringPrintf("%s_%d.png", output_prefix.c_str(), i));
-    }
-
-    if (++i == n_frames)
-      break;
-  };
+    first_loop = false;
+  } while (loop_decode && !errored);
 
   LOG(INFO) << "Done reading.";
 
diff --git a/media/gpu/vaapi/vaapi_dmabuf_video_frame_mapper.cc b/media/gpu/vaapi/vaapi_dmabuf_video_frame_mapper.cc
index 6674b1e..fa533e05 100644
--- a/media/gpu/vaapi/vaapi_dmabuf_video_frame_mapper.cc
+++ b/media/gpu/vaapi/vaapi_dmabuf_video_frame_mapper.cc
@@ -162,6 +162,7 @@
     : VideoFrameMapper(format),
       vaapi_wrapper_(VaapiWrapper::Create(VaapiWrapper::kVideoProcess,
                                           VAProfileNone,
+                                          EncryptionScheme::kUnencrypted,
                                           base::DoNothing())) {}
 
 VaapiDmaBufVideoFrameMapper::~VaapiDmaBufVideoFrameMapper() {}
diff --git a/media/gpu/vaapi/vaapi_image_decoder.cc b/media/gpu/vaapi/vaapi_image_decoder.cc
index 3c13981..1c92c7b 100644
--- a/media/gpu/vaapi/vaapi_image_decoder.cc
+++ b/media/gpu/vaapi/vaapi_image_decoder.cc
@@ -29,7 +29,8 @@
 
 bool VaapiImageDecoder::Initialize(const ReportErrorToUMACB& error_uma_cb) {
   vaapi_wrapper_ =
-      VaapiWrapper::Create(VaapiWrapper::kDecode, va_profile_, error_uma_cb);
+      VaapiWrapper::Create(VaapiWrapper::kDecode, va_profile_,
+                           EncryptionScheme::kUnencrypted, error_uma_cb);
   return !!vaapi_wrapper_;
 }
 
diff --git a/media/gpu/vaapi/vaapi_jpeg_encode_accelerator.cc b/media/gpu/vaapi/vaapi_jpeg_encode_accelerator.cc
index d94c070..cce592e9 100644
--- a/media/gpu/vaapi/vaapi_jpeg_encode_accelerator.cc
+++ b/media/gpu/vaapi/vaapi_jpeg_encode_accelerator.cc
@@ -500,6 +500,7 @@
   client_ = client;
   scoped_refptr<VaapiWrapper> vaapi_wrapper = VaapiWrapper::Create(
       VaapiWrapper::kEncode, VAProfileJPEGBaseline,
+      EncryptionScheme::kUnencrypted,
       base::BindRepeating(&ReportVaapiErrorToUMA,
                           "Media.VaapiJpegEncodeAccelerator.VAAPIError"));
 
@@ -510,6 +511,7 @@
 
   scoped_refptr<VaapiWrapper> vpp_vaapi_wrapper = VaapiWrapper::Create(
       VaapiWrapper::kVideoProcess, VAProfileNone,
+      EncryptionScheme::kUnencrypted,
       base::BindRepeating(&ReportVaapiErrorToUMA,
                           "Media.VaapiJpegEncodeAccelerator.Vpp.VAAPIError"));
   if (!vpp_vaapi_wrapper) {
diff --git a/media/gpu/vaapi/vaapi_mjpeg_decode_accelerator.cc b/media/gpu/vaapi/vaapi_mjpeg_decode_accelerator.cc
index 3bbac0d..40ec729 100644
--- a/media/gpu/vaapi/vaapi_mjpeg_decode_accelerator.cc
+++ b/media/gpu/vaapi/vaapi_mjpeg_decode_accelerator.cc
@@ -141,6 +141,7 @@
 
   vpp_vaapi_wrapper_ = VaapiWrapper::Create(
       VaapiWrapper::kVideoProcess, VAProfileNone,
+      EncryptionScheme::kUnencrypted,
       base::BindRepeating(&ReportVaapiErrorToUMA,
                           "Media.VaapiMjpegDecodeAccelerator.Vpp.VAAPIError"));
   if (!vpp_vaapi_wrapper_) {
diff --git a/media/gpu/vaapi/vaapi_unittest.cc b/media/gpu/vaapi/vaapi_unittest.cc
index 16a3129..791cbdd0 100644
--- a/media/gpu/vaapi/vaapi_unittest.cc
+++ b/media/gpu/vaapi/vaapi_unittest.cc
@@ -279,7 +279,8 @@
     for (const auto& profile_and_entrypoints : configurations) {
       const VAProfile va_profile = profile_and_entrypoints.first;
       scoped_refptr<VaapiWrapper> wrapper = VaapiWrapper::Create(
-          VaapiWrapper::kEncode, va_profile, base::DoNothing());
+          VaapiWrapper::kEncode, va_profile, EncryptionScheme::kUnencrypted,
+          base::DoNothing());
 
       // Depending on the GPU Gen, flags and policies, we may or may not utilize
       // all entrypoints (e.g. we might always want VAEntrypointEncSliceLP if
diff --git a/media/gpu/vaapi/vaapi_utils_unittest.cc b/media/gpu/vaapi/vaapi_utils_unittest.cc
index 479ea443..834a2df9 100644
--- a/media/gpu/vaapi/vaapi_utils_unittest.cc
+++ b/media/gpu/vaapi/vaapi_utils_unittest.cc
@@ -42,6 +42,7 @@
     // Create a VaapiWrapper for testing.
     vaapi_wrapper_ =
         VaapiWrapper::Create(VaapiWrapper::kDecode, VAProfileJPEGBaseline,
+                             EncryptionScheme::kUnencrypted,
                              base::BindRepeating([](VaapiFunctions function) {
                                LOG(FATAL) << "Oh noes! Decoder failed";
                              }));
diff --git a/media/gpu/vaapi/vaapi_video_decode_accelerator.cc b/media/gpu/vaapi/vaapi_video_decode_accelerator.cc
index 7460b84..9d9cb7a 100644
--- a/media/gpu/vaapi/vaapi_video_decode_accelerator.cc
+++ b/media/gpu/vaapi/vaapi_video_decode_accelerator.cc
@@ -205,7 +205,7 @@
   VLOGF(2) << "Initializing VAVDA, profile: " << GetProfileName(profile);
 
   vaapi_wrapper_ = VaapiWrapper::CreateForVideoCodec(
-      VaapiWrapper::kDecode, profile,
+      VaapiWrapper::kDecode, profile, EncryptionScheme::kUnencrypted,
       base::BindRepeating(&ReportVaapiErrorToUMA,
                           "Media.VaapiVideoDecodeAccelerator.VAAPIError"));
 
@@ -620,7 +620,7 @@
   if (profile_ != new_profile) {
     profile_ = new_profile;
     auto new_vaapi_wrapper = VaapiWrapper::CreateForVideoCodec(
-        VaapiWrapper::kDecode, profile_,
+        VaapiWrapper::kDecode, profile_, EncryptionScheme::kUnencrypted,
         base::BindRepeating(&ReportVaapiErrorToUMA,
                             "Media.VaapiVideoDecodeAccelerator.VAAPIError"));
     RETURN_AND_NOTIFY_ON_FAILURE(new_vaapi_wrapper.get(),
@@ -715,6 +715,7 @@
     if (!vpp_vaapi_wrapper_) {
       vpp_vaapi_wrapper_ = VaapiWrapper::Create(
           VaapiWrapper::kVideoProcess, VAProfileNone,
+          EncryptionScheme::kUnencrypted,
           base::BindRepeating(
               &ReportVaapiErrorToUMA,
               "Media.VaapiVideoDecodeAccelerator.Vpp.VAAPIError"));
diff --git a/media/gpu/vaapi/vaapi_video_decoder.cc b/media/gpu/vaapi/vaapi_video_decoder.cc
index c0b2328..9d74a48 100644
--- a/media/gpu/vaapi/vaapi_video_decoder.cc
+++ b/media/gpu/vaapi/vaapi_video_decoder.cc
@@ -220,7 +220,7 @@
 #else
       VaapiWrapper::kDecode,
 #endif
-      profile,
+      profile, config.encryption_scheme(),
       base::BindRepeating(&ReportVaapiErrorToUMA,
                           "Media.VaapiVideoDecoder.VAAPIError"));
   UMA_HISTOGRAM_BOOLEAN("Media.VaapiVideoDecoder.VaapiWrapperCreationSuccess",
@@ -554,7 +554,7 @@
 #else
         VaapiWrapper::kDecode,
 #endif
-        profile_,
+        profile_, encryption_scheme_,
         base::BindRepeating(&ReportVaapiErrorToUMA,
                             "Media.VaapiVideoDecoder.VAAPIError"));
     if (!new_vaapi_wrapper.get()) {
diff --git a/media/gpu/vaapi/vaapi_video_decoder_delegate.cc b/media/gpu/vaapi/vaapi_video_decoder_delegate.cc
index 097fc72..b36cda8 100644
--- a/media/gpu/vaapi/vaapi_video_decoder_delegate.cc
+++ b/media/gpu/vaapi/vaapi_video_decoder_delegate.cc
@@ -92,7 +92,6 @@
       protected_session_state_ = ProtectedSessionState::kFailed;
       return protected_session_state_;
     }
-    full_sample_ = full_sample;
     // We need to start the creation of this, first part requires getting the
     // hw config data from the daemon.
     chromeos::ChromeOsCdmFactory::GetHwConfigData(BindToCurrentLoop(
@@ -106,14 +105,10 @@
 
   if (encryption_scheme_ == EncryptionScheme::kCenc) {
     crypto_params->encryption_type =
-        full_sample_ ? VA_ENCRYPTION_TYPE_CENC_CTR : VA_ENCRYPTION_TYPE_CTR_128;
+        full_sample ? VA_ENCRYPTION_TYPE_CENC_CTR : VA_ENCRYPTION_TYPE_CTR_128;
   } else {
-    if (full_sample_) {
-      LOG(ERROR) << "CBC encryption is not supported for CENCv1";
-      protected_session_state_ = ProtectedSessionState::kFailed;
-      return protected_session_state_;
-    }
-    crypto_params->encryption_type = VA_ENCRYPTION_TYPE_CBC;
+    crypto_params->encryption_type =
+        full_sample ? VA_ENCRYPTION_TYPE_CENC_CBC : VA_ENCRYPTION_TYPE_CBC;
   }
 
   if (subsamples.empty() ||
@@ -128,12 +123,6 @@
     return protected_session_state_;
   }
 
-  if (full_sample_ != full_sample) {
-    LOG(ERROR) << "Cannot switch between full/subsample mid session";
-    protected_session_state_ = ProtectedSessionState::kFailed;
-    return protected_session_state_;
-  }
-
   DCHECK(decrypt_config_);
   // We also need to make sure we have the key data for the active
   // DecryptConfig now that the protected session exists.
@@ -205,8 +194,8 @@
   }
 
   hw_identifier_.clear();
-  if (!vaapi_wrapper_->CreateProtectedSession(encryption_scheme_, full_sample_,
-                                              config_data, &hw_identifier_)) {
+  if (!vaapi_wrapper_->CreateProtectedSession(encryption_scheme_, config_data,
+                                              &hw_identifier_)) {
     LOG(ERROR) << "Failed to setup protected session";
     protected_session_state_ = ProtectedSessionState::kFailed;
     on_protected_session_update_cb_.Run(false);
diff --git a/media/gpu/vaapi/vaapi_video_decoder_delegate.h b/media/gpu/vaapi/vaapi_video_decoder_delegate.h
index fec3845..96ce00f 100644
--- a/media/gpu/vaapi/vaapi_video_decoder_delegate.h
+++ b/media/gpu/vaapi/vaapi_video_decoder_delegate.h
@@ -116,7 +116,6 @@
   EncryptionScheme encryption_scheme_;
   ProtectedSessionState protected_session_state_;
   std::unique_ptr<DecryptConfig> decrypt_config_;
-  bool full_sample_;
   std::vector<uint8_t> hw_identifier_;
   std::map<std::string, std::vector<uint8_t>> hw_key_data_map_;
 
diff --git a/media/gpu/vaapi/vaapi_video_encode_accelerator.cc b/media/gpu/vaapi/vaapi_video_encode_accelerator.cc
index 2dce0d8c..9ff92d1 100644
--- a/media/gpu/vaapi/vaapi_video_encode_accelerator.cc
+++ b/media/gpu/vaapi/vaapi_video_encode_accelerator.cc
@@ -335,7 +335,7 @@
         codec == kCodecVP9 ? VaapiWrapper::kEncodeConstantQuantizationParameter
                            : VaapiWrapper::kEncode;
     vaapi_wrapper_ = VaapiWrapper::CreateForVideoCodec(
-        mode, config.output_profile,
+        mode, config.output_profile, EncryptionScheme::kUnencrypted,
         base::BindRepeating(&ReportVaapiErrorToUMA,
                             "Media.VaapiVideoEncodeAccelerator.VAAPIError"));
     if (!vaapi_wrapper_) {
@@ -728,6 +728,7 @@
     if (!vpp_vaapi_wrapper_) {
       vpp_vaapi_wrapper_ = VaapiWrapper::Create(
           VaapiWrapper::kVideoProcess, VAProfileNone,
+          EncryptionScheme::kUnencrypted,
           base::BindRepeating(
               &ReportVaapiErrorToUMA,
               "Media.VaapiVideoEncodeAccelerator.Vpp.VAAPIError"));
diff --git a/media/gpu/vaapi/vaapi_wrapper.cc b/media/gpu/vaapi/vaapi_wrapper.cc
index 4fd51b8..a5debd39 100644
--- a/media/gpu/vaapi/vaapi_wrapper.cc
+++ b/media/gpu/vaapi/vaapi_wrapper.cc
@@ -787,8 +787,6 @@
         {VAConfigAttribProtectedContentCipherBlockSize, VA_PC_BLOCK_SIZE_128});
     required_attribs->push_back(
         {VAConfigAttribProtectedContentCipherMode, VA_PC_CIPHER_MODE_CTR});
-    required_attribs->push_back({VAConfigAttribProtectedContentCipherSampleType,
-                                 VA_PC_SAMPLE_TYPE_FULLSAMPLE});
 #endif
   } else {
     required_attribs->push_back({VAConfigAttribRTFormat, VA_RT_FORMAT_YUV420});
@@ -796,16 +794,8 @@
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
   if (mode == VaapiWrapper::kDecodeProtected && profile != VAProfileProtected) {
-    // TODO(jkardatzke): Remove this workarond once the iHD bug for full vs.
-    // subsample dependency here is fixed. VA_ENCRYPTION_TYPE_CTR_128 works for
-    // VP9. VA_ENCRYPTION_TYPE_CENC_CTR is needed for H264 full sample (and also
-    // works for H264 subsample). We can't know full vs. subsample at this point
-    // though, we only know codec.
     required_attribs->push_back(
-        {VAConfigAttribEncryption,
-         (profile == VAProfileVP9Profile0 || profile == VAProfileVP9Profile2)
-             ? VA_ENCRYPTION_TYPE_CTR_128
-             : VA_ENCRYPTION_TYPE_CENC_CTR});
+        {VAConfigAttribEncryption, VA_ENCRYPTION_TYPE_CENC_CTR});
   }
 #endif
 
@@ -1339,6 +1329,7 @@
 scoped_refptr<VaapiWrapper> VaapiWrapper::Create(
     CodecMode mode,
     VAProfile va_profile,
+    EncryptionScheme encryption_scheme,
     const ReportErrorToUMACB& report_error_to_uma_cb) {
   if (!VASupportedProfiles::Get().IsProfileSupported(mode, va_profile)) {
     DVLOG(1) << "Unsupported va_profile: " << vaProfileStr(va_profile);
@@ -1358,7 +1349,7 @@
 
   scoped_refptr<VaapiWrapper> vaapi_wrapper(new VaapiWrapper(mode));
   if (vaapi_wrapper->VaInitialize(report_error_to_uma_cb)) {
-    if (vaapi_wrapper->Initialize(mode, va_profile))
+    if (vaapi_wrapper->Initialize(mode, va_profile, encryption_scheme))
       return vaapi_wrapper;
   }
   LOG(ERROR) << "Failed to create VaapiWrapper for va_profile: "
@@ -1370,9 +1361,10 @@
 scoped_refptr<VaapiWrapper> VaapiWrapper::CreateForVideoCodec(
     CodecMode mode,
     VideoCodecProfile profile,
+    EncryptionScheme encryption_scheme,
     const ReportErrorToUMACB& report_error_to_uma_cb) {
   const VAProfile va_profile = ProfileToVAProfile(profile, mode);
-  return Create(mode, va_profile, report_error_to_uma_cb);
+  return Create(mode, va_profile, encryption_scheme, report_error_to_uma_cb);
 }
 
 // static
@@ -1738,8 +1730,7 @@
 }
 
 bool VaapiWrapper::CreateProtectedSession(
-    media::EncryptionScheme encryption,
-    bool full_sample,
+    EncryptionScheme encryption,
     const std::vector<uint8_t>& hw_config,
     std::vector<uint8_t>* hw_identifier_out) {
 #if BUILDFLAG(IS_CHROMEOS_ASH)
@@ -1748,7 +1739,7 @@
     LOG(ERROR) << "Cannot attached protected context if not in protected mode";
     return false;
   }
-  if (encryption == media::EncryptionScheme::kUnencrypted) {
+  if (encryption == EncryptionScheme::kUnencrypted) {
     LOG(ERROR) << "Must specify encryption scheme for protected mode";
     return false;
   }
@@ -1764,16 +1755,12 @@
     }
     DCHECK(!required_attribs.empty());
 
-    // We need to adjust the attributes for encryption scheme and sample mode.
+    // We need to adjust the attribute for encryption scheme.
     for (auto& attrib : required_attribs) {
       if (attrib.type == VAConfigAttribProtectedContentCipherMode) {
-        attrib.value = (encryption == media::EncryptionScheme::kCbcs)
+        attrib.value = (encryption == EncryptionScheme::kCbcs)
                            ? VA_PC_CIPHER_MODE_CBC
                            : VA_PC_CIPHER_MODE_CTR;
-      } else if (attrib.type ==
-                 VAConfigAttribProtectedContentCipherSampleType) {
-        attrib.value = full_sample ? VA_PC_SAMPLE_TYPE_FULLSAMPLE
-                                   : VA_PC_SAMPLE_TYPE_SUBSAMPLE;
       }
     }
 
@@ -2573,7 +2560,9 @@
   Deinitialize();
 }
 
-bool VaapiWrapper::Initialize(CodecMode mode, VAProfile va_profile) {
+bool VaapiWrapper::Initialize(CodecMode mode,
+                              VAProfile va_profile,
+                              EncryptionScheme encryption_scheme) {
 #if DCHECK_IS_ON()
   if (mode == kEncodeConstantQuantizationParameter) {
     DCHECK_NE(va_profile, VAProfileJPEGBaseline)
@@ -2581,6 +2570,12 @@
   }
 #endif  // DCHECK_IS_ON()
 
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  if (encryption_scheme != EncryptionScheme::kUnencrypted &&
+      mode != kDecodeProtected)
+    return false;
+#endif
+
   const VAEntrypoint entrypoint = GetDefaultVaEntryPoint(mode, va_profile);
 
   base::AutoLock auto_lock(*va_lock_);
@@ -2590,6 +2585,18 @@
     return false;
   }
 
+  if (encryption_scheme != EncryptionScheme::kUnencrypted) {
+    DCHECK(!required_attribs.empty());
+    // We need to adjust the attribute for encryption scheme.
+    for (auto& attrib : required_attribs) {
+      if (attrib.type == VAConfigAttribEncryption) {
+        attrib.value = (encryption_scheme == EncryptionScheme::kCbcs)
+                           ? VA_ENCRYPTION_TYPE_CENC_CBC
+                           : VA_ENCRYPTION_TYPE_CENC_CTR;
+      }
+    }
+  }
+
   const VAStatus va_res =
       vaCreateConfig(va_display_, va_profile, entrypoint,
                      required_attribs.empty() ? nullptr : &required_attribs[0],
diff --git a/media/gpu/vaapi/vaapi_wrapper.h b/media/gpu/vaapi/vaapi_wrapper.h
index ee3f033..f3aad8b 100644
--- a/media/gpu/vaapi/vaapi_wrapper.h
+++ b/media/gpu/vaapi/vaapi_wrapper.h
@@ -151,6 +151,7 @@
   static scoped_refptr<VaapiWrapper> Create(
       CodecMode mode,
       VAProfile va_profile,
+      EncryptionScheme encryption_scheme,
       const ReportErrorToUMACB& report_error_to_uma_cb);
 
   // Create VaapiWrapper for VideoCodecProfile. It maps VideoCodecProfile
@@ -160,6 +161,7 @@
   static scoped_refptr<VaapiWrapper> CreateForVideoCodec(
       CodecMode mode,
       VideoCodecProfile profile,
+      EncryptionScheme encryption_scheme,
       const ReportErrorToUMACB& report_error_to_uma_cb);
 
   // Return the supported video encode profiles.
@@ -265,14 +267,12 @@
   // decoding context to enable encrypted video decoding. If it cannot be
   // attached now, it will be attached when the decoding context is created or
   // re-created. |encryption| should be the encryption scheme from the
-  // DecryptConfig, |full_sample| should be true if full sample (i.e. CENC v1)
-  // encryption is used. |hw_config| should have been obtained from the
-  // OEMCrypto implementation via the CdmFactoryDaemonProxy. |hw_identifier_out|
-  // is an output parameter which will return session specific information which
-  // can be passed through the ChromeOsCdmContext to retrieve encrypted key
+  // DecryptConfig. |hw_config| should have been obtained from the OEMCrypto
+  // implementation via the CdmFactoryDaemonProxy. |hw_identifier_out| is an
+  // output parameter which will return session specific information which can
+  // be passed through the ChromeOsCdmContext to retrieve encrypted key
   // information. Returns true on success and false otherwise.
   bool CreateProtectedSession(media::EncryptionScheme encryption,
-                              bool full_sample,
                               const std::vector<uint8_t>& hw_config,
                               std::vector<uint8_t>* hw_identifier_out);
 
@@ -476,7 +476,9 @@
   FRIEND_TEST_ALL_PREFIXES(VaapiUtilsTest, BadScopedVAImage);
   FRIEND_TEST_ALL_PREFIXES(VaapiUtilsTest, BadScopedVABufferMapping);
 
-  bool Initialize(CodecMode mode, VAProfile va_profile) WARN_UNUSED_RESULT;
+  bool Initialize(CodecMode mode,
+                  VAProfile va_profile,
+                  EncryptionScheme encryption_scheme) WARN_UNUSED_RESULT;
   void Deinitialize();
   bool VaInitialize(const ReportErrorToUMACB& report_error_to_uma_cb)
       WARN_UNUSED_RESULT;
diff --git a/net/third_party/quiche/BUILD.gn b/net/third_party/quiche/BUILD.gn
index 797deb8..f3b0b3d 100644
--- a/net/third_party/quiche/BUILD.gn
+++ b/net/third_party/quiche/BUILD.gn
@@ -6,6 +6,10 @@
 import("//testing/libfuzzer/fuzzer_test.gni")
 import("//third_party/protobuf/proto_library.gni")
 
+config("quiche_config") {
+  include_dirs = [ "src" ]
+}
+
 # Since //net and //net/third_party/quiche have a circular dependency on each
 # other, exporting dependencies from the :quiche target directly does not work.
 # Thus, all public dependencies for QUICHE should go into the target below,
@@ -13,6 +17,7 @@
 source_set("quiche_public_deps") {
   visibility = [ "//net:net_public_deps" ]
 
+  public_configs = [ ":quiche_config" ]
   public_deps = [ "//third_party/abseil-cpp:absl" ]
 }
 
@@ -639,6 +644,8 @@
   cc_include = "net/base/net_export.h"
   component_build_force_source_set = true
 
+  proto_in_dir = "src"
+
   deps = [ "//net:net_export_header" ]
 
   defines = [ "NET_IMPLEMENTATION" ]
diff --git a/remoting/host/BUILD.gn b/remoting/host/BUILD.gn
index f0540a5..51157a100 100644
--- a/remoting/host/BUILD.gn
+++ b/remoting/host/BUILD.gn
@@ -174,6 +174,9 @@
     "host_power_save_blocker.h",
     "host_secret.cc",
     "host_secret.h",
+    "host_setting_keys.h",
+    "host_settings.cc",
+    "host_settings.h",
     "host_status_logger.cc",
     "host_status_logger.h",
     "host_status_monitor.cc",
@@ -409,6 +412,8 @@
       "desktop_resizer_mac.cc",
       "disconnect_window_mac.h",
       "disconnect_window_mac.mm",
+      "host_settings_mac.cc",
+      "host_settings_mac.h",
       "input_injector_mac.cc",
       "keyboard_layout_monitor_mac.cc",
       "pairing_registry_delegate_mac.cc",
@@ -418,9 +423,13 @@
     frameworks = [
       "Accelerate.framework",
       "Carbon.framework",
+      "CoreAudio.framework",
     ]
 
-    deps += [ ":remoting_version" ]
+    deps += [
+      ":remoting_version",
+      "//remoting/host/mac:constants",
+    ]
   }
 
   if (is_win) {
diff --git a/remoting/host/audio_capturer_mac.cc b/remoting/host/audio_capturer_mac.cc
index d9b0cb2..48b4894 100644
--- a/remoting/host/audio_capturer_mac.cc
+++ b/remoting/host/audio_capturer_mac.cc
@@ -10,8 +10,13 @@
 #include "base/containers/flat_set.h"
 #include "base/logging.h"
 #include "base/no_destructor.h"
+#include "base/strings/sys_string_conversions.h"
 #include "base/synchronization/lock.h"
 #include "base/threading/sequenced_task_runner_handle.h"
+#include "base/threading/thread_restrictions.h"
+#include "remoting/base/logging.h"
+#include "remoting/host/host_setting_keys.h"
+#include "remoting/host/host_settings.h"
 #include "remoting/proto/audio.pb.h"
 
 namespace remoting {
@@ -88,8 +93,10 @@
 
 }  // namespace
 
-AudioCapturerMac::AudioCapturerMac() {
+AudioCapturerMac::AudioCapturerMac(const std::string& audio_device_uid)
+    : audio_device_uid_(audio_device_uid) {
   DETACH_FROM_SEQUENCE(sequence_checker_);
+  DCHECK(!audio_device_uid.empty());
 
   stream_description_.mSampleRate = kSampleRate;
   stream_description_.mFormatID = kAudioFormatLinearPCM;
@@ -188,9 +195,6 @@
   // This runs on AudioQueue's internal thread. For some reason if we specify
   // inCallbackRunLoop to current thread, then the callback will never get
   // called.
-  // TODO(yuweih): Search for the loopback device directly instead of relying on
-  // the default input device. This would allow the user to keep their
-  // microphone as the default input device.
   OSStatus err =
       AudioQueueNewInput(&stream_description_, &HandleInputBufferOnAQThread,
                          /* inUserData= */ this, /* inCallbackRunLoop= */ NULL,
@@ -200,6 +204,18 @@
     return false;
   }
 
+  // Use the loopback device for input.
+  HOST_LOG << "Using loopback device: " << audio_device_uid_;
+  base::ScopedCFTypeRef<CFStringRef> device_uid =
+      base::SysUTF8ToCFStringRef(audio_device_uid_);
+  CFStringRef unowned_device_uid = device_uid.get();
+  err = AudioQueueSetProperty(input_queue_, kAudioQueueProperty_CurrentDevice,
+                              &unowned_device_uid, sizeof(unowned_device_uid));
+  if (HandleError(err,
+                  "AudioQueueSetProperty(kAudioQueueProperty_CurrentDevice)")) {
+    return false;
+  }
+
   // Setup buffers.
   for (int i = 0; i < kNumberBuffers; i++) {
     // |buffer| will automatically be freed when |input_queue_| is released.
@@ -216,6 +232,11 @@
 
   // Start input queue.
   err = AudioQueueStart(input_queue_, NULL);
+  if (err == kAudioQueueErr_InvalidDevice) {
+    LOG(ERROR) << "Loopback device " << audio_device_uid_
+               << " could not be located";
+    return false;
+  }
   if (HandleError(err, "AudioQueueStart")) {
     return false;
   }
@@ -260,29 +281,28 @@
 
 // AudioCapturer
 
-// AudioCapturer support on Mac is still experimental.
-
-#if defined(NDEBUG)
-
 bool AudioCapturer::IsSupported() {
-  return false;
-}
-
-std::unique_ptr<AudioCapturer> AudioCapturer::Create() {
-  NOTIMPLEMENTED();
-  return nullptr;
-}
-
-#else
-
-bool AudioCapturer::IsSupported() {
+  if (HostSettings::GetInstance()
+          ->GetString(kMacAudioCaptureDeviceUid)
+          .empty()) {
+    HOST_LOG << kMacAudioCaptureDeviceUid << " is not set or not a string. "
+             << "Audio capturer will be disabled.";
+    return false;
+  }
+  HOST_LOG << kMacAudioCaptureDeviceUid
+           << " is set. Audio capturer will be enabled.";
   return true;
 }
 
 std::unique_ptr<AudioCapturer> AudioCapturer::Create() {
-  return std::make_unique<AudioCapturerMac>();
+  std::string device_uid =
+      HostSettings::GetInstance()->GetString(kMacAudioCaptureDeviceUid);
+  if (device_uid.empty()) {
+    // AudioCapturer::Create is still called even when IsSupported() returns
+    // false.
+    return nullptr;
+  }
+  return std::make_unique<AudioCapturerMac>(device_uid);
 }
 
-#endif
-
 }  // namespace remoting
diff --git a/remoting/host/audio_capturer_mac.h b/remoting/host/audio_capturer_mac.h
index e906201..a875e984 100644
--- a/remoting/host/audio_capturer_mac.h
+++ b/remoting/host/audio_capturer_mac.h
@@ -7,6 +7,8 @@
 
 #include <AudioToolbox/AudioToolbox.h>
 
+#include <string>
+
 #include "base/macros.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/memory/weak_ptr.h"
@@ -23,7 +25,7 @@
 // audio on Mac through the loopback device.
 class AudioCapturerMac : public AudioCapturer {
  public:
-  AudioCapturerMac();
+  explicit AudioCapturerMac(const std::string& audio_device_uid);
   ~AudioCapturerMac() override;
 
   // AudioCapturer interface.
@@ -52,6 +54,8 @@
 
   SEQUENCE_CHECKER(sequence_checker_);
 
+  std::string audio_device_uid_;
+
   AudioStreamBasicDescription stream_description_;
   PacketCapturedCallback callback_;
   AudioQueueRef input_queue_ = nullptr;
diff --git a/remoting/host/host_setting_keys.h b/remoting/host/host_setting_keys.h
new file mode 100644
index 0000000..159de7585
--- /dev/null
+++ b/remoting/host/host_setting_keys.h
@@ -0,0 +1,19 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef REMOTING_HOST_HOST_SETTING_KEYS_H_
+#define REMOTING_HOST_HOST_SETTING_KEYS_H_
+
+#include "remoting/host/host_settings.h"
+
+namespace remoting {
+
+// If setting is provided, the Mac host will capture audio from the audio device
+// specified by the UID and stream it to the client. See AudioCapturerMac for
+// more information.
+constexpr HostSettingKey kMacAudioCaptureDeviceUid = "audio_capture_device_uid";
+
+}  // namespace remoting
+
+#endif  // REMOTING_HOST_HOST_SETTING_KEYS_H_
diff --git a/remoting/host/host_settings.cc b/remoting/host/host_settings.cc
new file mode 100644
index 0000000..5ab6f015
--- /dev/null
+++ b/remoting/host/host_settings.cc
@@ -0,0 +1,45 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "remoting/host/host_settings.h"
+
+#include "base/no_destructor.h"
+#include "build/build_config.h"
+
+namespace remoting {
+
+namespace {
+
+class EmptyHostSettings : public HostSettings {
+ public:
+  std::string GetString(const HostSettingKey key) const override {
+    return std::string();
+  }
+
+  void InitializeInstance() override {}
+};
+
+}  // namespace
+
+// static
+void HostSettings::Initialize() {
+  GetInstance()->InitializeInstance();
+}
+
+HostSettings::HostSettings() = default;
+
+HostSettings::~HostSettings() = default;
+
+// HostSettings is currently neither implemented nor used on non-Mac platforms.
+#if !defined(OS_APPLE)
+
+// static
+HostSettings* HostSettings::GetInstance() {
+  static base::NoDestructor<EmptyHostSettings> instance;
+  return instance.get();
+}
+
+#endif
+
+}  // namespace remoting
diff --git a/remoting/host/host_settings.h b/remoting/host/host_settings.h
new file mode 100644
index 0000000..722d9f7
--- /dev/null
+++ b/remoting/host/host_settings.h
@@ -0,0 +1,43 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef REMOTING_HOST_HOST_SETTINGS_H_
+#define REMOTING_HOST_HOST_SETTINGS_H_
+
+#include <string>
+
+#include "base/values.h"
+
+namespace remoting {
+
+using HostSettingKey = char[];
+
+// A class to read host settings, which are simple key-value pairs unrelated to
+// the host's identity, like UID of an audio device to capture audio from, or
+// whether a feature should be enabled.
+class HostSettings {
+ public:
+  // Initializes host settings. Must be called on a thread that allows blocking
+  // before calling GetValue().
+  static void Initialize();
+
+  static HostSettings* GetInstance();
+
+  // Gets the value of the setting. Returns empty string if the value is not
+  // found.
+  virtual std::string GetString(const HostSettingKey key) const = 0;
+
+  HostSettings(const HostSettings&) = delete;
+  HostSettings& operator=(const HostSettings&) = delete;
+
+ protected:
+  HostSettings();
+  virtual ~HostSettings();
+
+  virtual void InitializeInstance() = 0;
+};
+
+}  // namespace remoting
+
+#endif  // REMOTING_HOST_HOST_SETTINGS_H_
diff --git a/remoting/host/host_settings_mac.cc b/remoting/host/host_settings_mac.cc
new file mode 100644
index 0000000..fc9121c8
--- /dev/null
+++ b/remoting/host/host_settings_mac.cc
@@ -0,0 +1,62 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "remoting/host/host_settings_mac.h"
+
+#include "base/files/file_util.h"
+#include "base/json/json_file_value_serializer.h"
+#include "base/logging.h"
+#include "base/no_destructor.h"
+#include "remoting/base/logging.h"
+#include "remoting/host/mac/constants_mac.h"
+
+namespace remoting {
+
+HostSettingsMac::HostSettingsMac() = default;
+
+HostSettingsMac::~HostSettingsMac() = default;
+
+void HostSettingsMac::InitializeInstance() {
+  // TODO(yuweih): Make HostSettingsMac detect changes of the settings file.
+  if (settings_) {
+    return;
+  }
+
+  base::FilePath settings_file(kHostSettingsFilePath);
+  if (!base::PathIsReadable(settings_file)) {
+    HOST_LOG << "Host settings file " << kHostSettingsFilePath
+             << " does not exist.";
+    return;
+  }
+  JSONFileValueDeserializer deserializer(settings_file);
+  int error_code;
+  std::string error_message;
+  settings_ = deserializer.Deserialize(&error_code, &error_message);
+  if (!settings_) {
+    LOG(WARNING) << "Failed to load " << kHostSettingsFilePath
+                 << ". Code: " << error_code << ", message: " << error_message;
+    return;
+  }
+  HOST_LOG << "Host settings loaded.";
+}
+
+std::string HostSettingsMac::GetString(const HostSettingKey key) const {
+  if (!settings_) {
+    VLOG(1) << "Either Initialize() has not been called, or the settings file "
+               "doesn't exist.";
+    return std::string();
+  }
+  std::string* string_value = settings_->FindStringKey(key);
+  if (!string_value) {
+    return std::string();
+  }
+  return *string_value;
+}
+
+HostSettings* HostSettings::GetInstance() {
+  static base::NoDestructor<HostSettingsMac> instance;
+  return instance.get();
+}
+
+}  // namespace remoting
diff --git a/remoting/host/host_settings_mac.h b/remoting/host/host_settings_mac.h
new file mode 100644
index 0000000..30eddc7b
--- /dev/null
+++ b/remoting/host/host_settings_mac.h
@@ -0,0 +1,34 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef REMOTING_HOST_HOST_SETTINGS_MAC_H_
+#define REMOTING_HOST_HOST_SETTINGS_MAC_H_
+
+#include <memory>
+
+#include "remoting/host/host_settings.h"
+
+namespace remoting {
+
+class HostSettingsMac final : public HostSettings {
+ public:
+  HostSettingsMac();
+  ~HostSettingsMac() override;
+
+  // HostSettings implementation.
+  void InitializeInstance() override;
+  std::string GetString(const HostSettingKey key) const override;
+
+  HostSettingsMac(const HostSettingsMac&) = delete;
+  HostSettingsMac& operator=(const HostSettingsMac&) = delete;
+
+ private:
+  // TODO(yuweih): This needs to be guarded with a lock if we detect changes of
+  // the settings file.
+  std::unique_ptr<base::Value> settings_;
+};
+
+}  // namespace remoting
+
+#endif  // REMOTING_HOST_HOST_SETTINGS_MAC_H_
diff --git a/remoting/host/it2me/it2me_native_messaging_host_main.cc b/remoting/host/it2me/it2me_native_messaging_host_main.cc
index 3808a7e..a5da6b3 100644
--- a/remoting/host/it2me/it2me_native_messaging_host_main.cc
+++ b/remoting/host/it2me/it2me_native_messaging_host_main.cc
@@ -20,6 +20,7 @@
 #include "remoting/base/breakpad.h"
 #include "remoting/host/chromoting_host_context.h"
 #include "remoting/host/host_exit_codes.h"
+#include "remoting/host/host_settings.h"
 #include "remoting/host/it2me/it2me_native_messaging_host.h"
 #include "remoting/host/logging.h"
 #include "remoting/host/native_messaging/native_messaging_pipe.h"
@@ -80,6 +81,7 @@
 
   base::CommandLine::Init(argc, argv);
   remoting::InitHostLogging();
+  remoting::HostSettings::Initialize();
 
 #if defined(OS_APPLE)
   // Needed so we don't leak objects when threads are created.
diff --git a/remoting/host/mac/constants_mac.cc b/remoting/host/mac/constants_mac.cc
index 04e2a0a..a1f3558f 100644
--- a/remoting/host/mac/constants_mac.cc
+++ b/remoting/host/mac/constants_mac.cc
@@ -20,6 +20,8 @@
 
 const char kHostConfigFileName[] = SERVICE_NAME ".json";
 const char kHostConfigFilePath[] = HELPER_TOOLS_DIR SERVICE_NAME ".json";
+const char kHostSettingsFilePath[] =
+    HELPER_TOOLS_DIR SERVICE_NAME ".settings.json";
 
 const char kHostServiceBinaryPath[] = HELPER_TOOLS_DIR HOST_BUNDLE_NAME
     "/Contents/MacOS/remoting_me2me_host_service";
diff --git a/remoting/host/mac/constants_mac.h b/remoting/host/mac/constants_mac.h
index a149d15..ff5048f 100644
--- a/remoting/host/mac/constants_mac.h
+++ b/remoting/host/mac/constants_mac.h
@@ -17,6 +17,8 @@
 extern const char kHostConfigFileName[];
 extern const char kHostConfigFilePath[];
 
+extern const char kHostSettingsFilePath[];
+
 // This helper script is executed as root to enable/disable/configure the host
 // service.
 // It is also used (as non-root) to provide version information for the
diff --git a/remoting/host/remoting_me2me_host.cc b/remoting/host/remoting_me2me_host.cc
index c93b068..7a0bd73 100644
--- a/remoting/host/remoting_me2me_host.cc
+++ b/remoting/host/remoting_me2me_host.cc
@@ -67,6 +67,7 @@
 #include "remoting/host/host_exit_codes.h"
 #include "remoting/host/host_main.h"
 #include "remoting/host/host_power_save_blocker.h"
+#include "remoting/host/host_settings.h"
 #include "remoting/host/host_status_logger.h"
 #include "remoting/host/input_injector.h"
 #include "remoting/host/ipc_desktop_environment.h"
@@ -831,6 +832,8 @@
     return;
   }
 
+  HostSettings::Initialize();
+
   if (!report_offline_reason_.empty()) {
     // Don't need to do any UI initialization.
     context_->network_task_runner()->PostTask(
diff --git a/services/device/time_zone_monitor/time_zone_monitor_win.cc b/services/device/time_zone_monitor/time_zone_monitor_win.cc
index 9f5d5a6..4bcdc5f5d 100644
--- a/services/device/time_zone_monitor/time_zone_monitor_win.cc
+++ b/services/device/time_zone_monitor/time_zone_monitor_win.cc
@@ -11,6 +11,7 @@
 #include "base/bind.h"
 #include "base/callback_helpers.h"
 #include "base/macros.h"
+#include "base/memory/weak_ptr.h"
 #include "base/trace_event/trace_event.h"
 #include "third_party/icu/source/i18n/unicode/timezone.h"
 #include "ui/gfx/win/singleton_hwnd_observer.h"
@@ -24,21 +25,42 @@
         singleton_hwnd_observer_(new gfx::SingletonHwndObserver(
             base::BindRepeating(&TimeZoneMonitorWin::OnWndProc,
                                 base::Unretained(this)))) {}
+  TimeZoneMonitorWin(const TimeZoneMonitorWin&) = delete;
+  TimeZoneMonitorWin& operator=(const TimeZoneMonitorWin&) = delete;
 
   ~TimeZoneMonitorWin() override {}
 
  private:
   void OnWndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) {
-    if (message != WM_TIMECHANGE) {
-      return;
+    if (message == WM_TIMECHANGE && !pending_update_notification_tasks_) {
+      // Traces show that in some cases there are multiple WM_TIMECHANGE while
+      // performing a power resume. Only sending one is enough
+      // (http://crbug.com/1074036).
+      pending_update_notification_tasks_ = true;
+
+      // The notifications are sent through a delayed task to avoid running
+      // the observers code while the computer is still suspended. The thread
+      // controller is not dispatching delayed tasks uuntil the power resume
+      // signal is received.
+      constexpr auto kMinimalPostTaskDelay =
+          base::TimeDelta::FromMilliseconds(1);
+      base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
+          FROM_HERE,
+          base::BindOnce(&TimeZoneMonitorWin::OnWmTimechangeReceived,
+                         weak_ptr_factory_.GetWeakPtr()),
+          kMinimalPostTaskDelay);
     }
-    TRACE_EVENT0("browser", "TimeZoneMonitorWin::UpdateIcuAndNotifyClients");
+  }
+
+  void OnWmTimechangeReceived() {
+    TRACE_EVENT0("device", "TimeZoneMonitorWin::OnTimechangeReceived");
     UpdateIcuAndNotifyClients(DetectHostTimeZoneFromIcu());
+    pending_update_notification_tasks_ = false;
   }
 
   std::unique_ptr<gfx::SingletonHwndObserver> singleton_hwnd_observer_;
-
-  DISALLOW_COPY_AND_ASSIGN(TimeZoneMonitorWin);
+  bool pending_update_notification_tasks_ = false;
+  base::WeakPtrFactory<TimeZoneMonitorWin> weak_ptr_factory_{this};
 };
 
 // static
diff --git a/skia/config/SkUserConfig.h b/skia/config/SkUserConfig.h
index 655b0f2a..e485359 100644
--- a/skia/config/SkUserConfig.h
+++ b/skia/config/SkUserConfig.h
@@ -214,6 +214,10 @@
 #define SK_DISABLE_REDUCE_OPLIST_SPLITTING
 #endif
 
+#ifndef SK_SUPPORT_LEGACY_INHERITED_PICTURE_SHADER_FILTER
+#define SK_SUPPORT_LEGACY_INHERITED_PICTURE_SHADER_FILTER
+#endif
+
 // Max. verb count for paths rendered by the edge-AA tessellating path renderer.
 #define GR_AA_TESSELLATOR_MAX_VERB_COUNT 100
 
diff --git a/sql/database_unittest.cc b/sql/database_unittest.cc
index df61053..9ae6a17 100644
--- a/sql/database_unittest.cc
+++ b/sql/database_unittest.cc
@@ -853,13 +853,6 @@
   EXPECT_FALSE(GetPathExists(wal_path));
 }
 
-// WAL mode is currently not supported on Fuchsia
-#if !defined(OS_FUCHSIA)
-INSTANTIATE_TEST_SUITE_P(JournalMode, SQLDatabaseTest, testing::Bool());
-#else
-INSTANTIATE_TEST_SUITE_P(JournalMode, SQLDatabaseTest, testing::Values(false));
-#endif
-
 #if defined(OS_POSIX)  // This test operates on POSIX file permissions.
 TEST_P(SQLDatabaseTest, PosixFilePermissions) {
   db().Close();
@@ -1375,4 +1368,17 @@
 #endif  // !defined(OS_ANDROID) && !defined(OS_IOS) && !defined(OS_FUCHSIA)
 }
 
+// WAL mode is currently not supported on Fuchsia.
+#if !defined(OS_FUCHSIA)
+INSTANTIATE_TEST_SUITE_P(JournalMode, SQLDatabaseTest, testing::Bool());
+INSTANTIATE_TEST_SUITE_P(JournalMode,
+                         SQLDatabaseTestExclusiveMode,
+                         testing::Bool());
+#else
+INSTANTIATE_TEST_SUITE_P(JournalMode, SQLDatabaseTest, testing::Values(false));
+INSTANTIATE_TEST_SUITE_P(JournalMode,
+                         SQLDatabaseTestExclusiveMode,
+                         testing::Values(false));
+#endif
+
 }  // namespace sql
diff --git a/testing/buildbot/chromium.perf.json b/testing/buildbot/chromium.perf.json
index 78b9246..8049616 100644
--- a/testing/buildbot/chromium.perf.json
+++ b/testing/buildbot/chromium.perf.json
@@ -990,8 +990,8 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
-              "gpu": "10de:1cb3-384.90",
-              "os": "Ubuntu-14.04",
+              "gpu": "10de:1cb3-440.100",
+              "os": "Ubuntu-18.04",
               "pool": "chrome.tests.perf",
               "synthetic_product_name": "PowerEdge R230 (Dell Inc.)"
             }
diff --git a/testing/test.gni b/testing/test.gni
index 473b486..a9a0ece 100644
--- a/testing/test.gni
+++ b/testing/test.gni
@@ -41,6 +41,20 @@
 #      Similar to the GN arg 'enable_run_ios_unittests_with_xctest' but
 #      for build targets.
 template("test") {
+  # RTS
+  if (rts_exclude_file != "" && defined(invoker.sources) &&
+      invoker.sources != []) {
+    # Normalize paths
+    abs_paths = get_path_info(invoker.sources, "abspath")
+
+    # Filter
+    filtered_sources = filter_exclude(abs_paths, rts_exclusions)
+
+    # Do the replacement
+    invoker.sources = []
+    invoker.sources = filtered_sources
+  }
+
   testonly = true
   if (!is_ios) {
     assert(!defined(invoker.is_xctest) || !invoker.is_xctest,
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 6877c21d..47ad760 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -8111,8 +8111,7 @@
                         "default_locale": "en",
                         "experiment_tag": "",
                         "fetch_frequency": "15",
-                        "summary_card_poster_url": "https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png",
-                        "use_animated_gif_url": "false"
+                        "use_animated_gif_url": "true"
                     },
                     "enable_features": [
                         "VideoTutorials"
diff --git a/third_party/android_deps/build.gradle b/third_party/android_deps/build.gradle
index 5622cf2a..3081e22 100644
--- a/third_party/android_deps/build.gradle
+++ b/third_party/android_deps/build.gradle
@@ -11,7 +11,7 @@
     }
   }
   dependencies {
-    classpath "org.owasp:dependency-check-gradle:5.3.2.1"
+    classpath "org.owasp:dependency-check-gradle:6+"
   }
 }
 
diff --git a/third_party/blink/common/web_preferences/web_preferences_mojom_traits.cc b/third_party/blink/common/web_preferences/web_preferences_mojom_traits.cc
index 63d5d4e5..ac2cb47 100644
--- a/third_party/blink/common/web_preferences/web_preferences_mojom_traits.cc
+++ b/third_party/blink/common/web_preferences/web_preferences_mojom_traits.cc
@@ -131,6 +131,8 @@
       data.dont_send_key_events_to_javascript();
   out->barrel_button_for_drag_enabled = data.barrel_button_for_drag_enabled();
   out->sync_xhr_in_documents_enabled = data.sync_xhr_in_documents_enabled();
+  out->target_blank_implies_no_opener_enabled_will_be_removed =
+      data.target_blank_implies_no_opener_enabled_will_be_removed();
   out->number_of_cpu_cores = data.number_of_cpu_cores();
   out->editing_behavior = data.editing_behavior();
   out->supports_multiple_windows = data.supports_multiple_windows();
diff --git a/third_party/blink/manual_tests/forms/eye-dropper.html b/third_party/blink/manual_tests/forms/eye-dropper.html
index fb9cfca..736612e 100644
--- a/third_party/blink/manual_tests/forms/eye-dropper.html
+++ b/third_party/blink/manual_tests/forms/eye-dropper.html
@@ -62,7 +62,13 @@
     document.getElementById("color").addEventListener("input", function(e) {
       let entry = log("color updated to " + e.target.value + " expected: #ff0000");
       let span = document.createElement("span");
-      if (e.target.value == "#ff0000") {
+
+      let red = parseInt(e.target.value.substring(1, 3), 16);
+      let green = parseInt(e.target.value.substring(3, 5), 16);
+      let blue = parseInt(e.target.value.substring(5, 7), 16);
+      // Make sure the selected color is close to pure red (#FF0000), but allow
+      // some deviation due to monitor color calibration.
+      if (red >= 0xC0 && green <= 0x3F && blue <= 0x3F) {
         span.innerText = "PASS ";
         span.classList.add("passed");
       } else {
diff --git a/third_party/blink/public/common/web_preferences/web_preferences.h b/third_party/blink/public/common/web_preferences/web_preferences.h
index 95ab020..64444803 100644
--- a/third_party/blink/public/common/web_preferences/web_preferences.h
+++ b/third_party/blink/public/common/web_preferences/web_preferences.h
@@ -135,6 +135,8 @@
   bool dont_send_key_events_to_javascript;
   bool barrel_button_for_drag_enabled = false;
   bool sync_xhr_in_documents_enabled;
+  // TODO(https://crbug.com/1163644): Remove once Chrome Apps are deprecated.
+  bool target_blank_implies_no_opener_enabled_will_be_removed = true;
   int number_of_cpu_cores;
   blink::mojom::EditingBehavior editing_behavior;
   bool supports_multiple_windows;
diff --git a/third_party/blink/public/common/web_preferences/web_preferences_mojom_traits.h b/third_party/blink/public/common/web_preferences/web_preferences_mojom_traits.h
index 145106c..c071879c 100644
--- a/third_party/blink/public/common/web_preferences/web_preferences_mojom_traits.h
+++ b/third_party/blink/public/common/web_preferences/web_preferences_mojom_traits.h
@@ -360,6 +360,11 @@
     return r.sync_xhr_in_documents_enabled;
   }
 
+  static bool target_blank_implies_no_opener_enabled_will_be_removed(
+      const blink::web_pref::WebPreferences& r) {
+    return r.target_blank_implies_no_opener_enabled_will_be_removed;
+  }
+
   static uint32_t number_of_cpu_cores(
       const blink::web_pref::WebPreferences& r) {
     return r.number_of_cpu_cores;
diff --git a/third_party/blink/public/mojom/web_feature/web_feature.mojom b/third_party/blink/public/mojom/web_feature/web_feature.mojom
index 9117bb9a..4bfd95b 100644
--- a/third_party/blink/public/mojom/web_feature/web_feature.mojom
+++ b/third_party/blink/public/mojom/web_feature/web_feature.mojom
@@ -3064,6 +3064,7 @@
   kCrossOriginSubframeWithoutEmbeddingControl = 3742,
   kReadableStreamWithByteSource = 3743,
   kReadableStreamBYOBReader = 3744,
+  kEmbedElementWithoutTypeSrcChanged = 3745,
 
   // Add new features immediately above this line. Don't change assigned
   // numbers of any item, and don't reuse removed slots.
diff --git a/third_party/blink/public/mojom/webpreferences/web_preferences.mojom b/third_party/blink/public/mojom/webpreferences/web_preferences.mojom
index df3ffb49..519b354f 100644
--- a/third_party/blink/public/mojom/webpreferences/web_preferences.mojom
+++ b/third_party/blink/public/mojom/webpreferences/web_preferences.mojom
@@ -188,6 +188,7 @@
   bool dont_send_key_events_to_javascript;
   bool barrel_button_for_drag_enabled;
   bool sync_xhr_in_documents_enabled;
+  bool target_blank_implies_no_opener_enabled_will_be_removed;
   int32 number_of_cpu_cores;
   EditingBehavior editing_behavior;
   bool supports_multiple_windows;
diff --git a/third_party/blink/public/web/web_ax_object.h b/third_party/blink/public/web/web_ax_object.h
index f54825b..6813404 100644
--- a/third_party/blink/public/web/web_ax_object.h
+++ b/third_party/blink/public/web/web_ax_object.h
@@ -333,7 +333,6 @@
   // Programmatically scrollable.
   BLINK_EXPORT bool IsScrollableContainer() const;
   // Also scrollable by user.
-  BLINK_EXPORT bool IsUserScrollable() const;
   BLINK_EXPORT gfx::Point GetScrollOffset() const;
   BLINK_EXPORT gfx::Point MinimumScrollOffset() const;
   BLINK_EXPORT gfx::Point MaximumScrollOffset() const;
diff --git a/third_party/blink/public/web/web_settings.h b/third_party/blink/public/web/web_settings.h
index 7e97b5df..9944af8 100644
--- a/third_party/blink/public/web/web_settings.h
+++ b/third_party/blink/public/web/web_settings.h
@@ -219,6 +219,8 @@
   virtual void SetSupportDeprecatedTargetDensityDPI(bool) = 0;
   virtual void SetSupportsMultipleWindows(bool) = 0;
   virtual void SetSyncXHRInDocumentsEnabled(bool) = 0;
+  // TODO(https://crbug.com/1163644): Remove once Chrome Apps are deprecated.
+  virtual void SetTargetBlankImpliesNoOpenerEnabledWillBeRemoved(bool) = 0;
   virtual void SetTextAreasAreResizable(bool) = 0;
   virtual void SetTextAutosizingEnabled(bool) = 0;
   virtual void SetAccessibilityFontScaleFactor(float) = 0;
diff --git a/third_party/blink/renderer/build/scripts/templates/settings_macros.h.tmpl b/third_party/blink/renderer/build/scripts/templates/settings_macros.h.tmpl
index 6f917df..327431d 100644
--- a/third_party/blink/renderer/build/scripts/templates/settings_macros.h.tmpl
+++ b/third_party/blink/renderer/build/scripts/templates/settings_macros.h.tmpl
@@ -39,28 +39,16 @@
     return; \
   {{setting.name.to_class_data_member()}} = {{setting.name.to_snake_case()}}; \
   {% if setting.invalidate %}
-  Invalidate(SettingsDelegate::k{{setting.invalidate}}Change); \
+  {% for type in setting.invalidate %}
+  Invalidate(SettingsDelegate::ChangeType::k{{type}}); \
+  {% endfor %}
   {% endif  %}
 } \
 {% endfor %}
 void Settings::SetFromStrings(const String& name, const String& value) { \
   {% for setting in settings %}
   if (name == "{{setting.name}}") { \
-    Set{{setting.name.to_upper_camel_case()}}( \
-      {% if setting.type == 'String' %}
-      value \
-      {% elif setting.type == 'bool' %}
-      value.IsEmpty() || value == "true" \
-      {% elif setting.type == 'int' %}
-      value.ToInt() \
-      {% elif setting.type == 'float' %}
-      value.ToFloat() \
-      {% elif setting.type == 'double' %}
-      value.ToDouble() \
-      {% else %}
-      static_cast<{{setting.type}}>(value.ToInt()) \
-      {% endif %}
-    ); \
+    Set{{setting.name.to_upper_camel_case()}}(FromString<{{setting.type}}>()(value)); \
     return; \
   } \
   {% endfor %}
diff --git a/third_party/blink/renderer/core/animation/compositor_animations.cc b/third_party/blink/renderer/core/animation/compositor_animations.cc
index 58a8aa4..395e378 100644
--- a/third_party/blink/renderer/core/animation/compositor_animations.cc
+++ b/third_party/blink/renderer/core/animation/compositor_animations.cc
@@ -800,9 +800,18 @@
     }
     DCHECK(curve.get());
 
-    auto keyframe_model = std::make_unique<CompositorKeyframeModel>(
-        *curve, target_property, 0, group, std::move(custom_property_name),
-        native_property_type);
+    std::unique_ptr<CompositorKeyframeModel> keyframe_model;
+    if (!custom_property_name.IsEmpty()) {
+      keyframe_model = std::make_unique<CompositorKeyframeModel>(
+          *curve, target_property, 0, group, std::move(custom_property_name));
+    } else if (native_property_type !=
+               CompositorPaintWorkletInput::NativePropertyType::kInvalid) {
+      keyframe_model = std::make_unique<CompositorKeyframeModel>(
+          *curve, target_property, 0, group, native_property_type);
+    } else {
+      keyframe_model = std::make_unique<CompositorKeyframeModel>(
+          *curve, target_property, 0, group);
+    }
 
     if (start_time)
       keyframe_model->SetStartTime(start_time.value());
diff --git a/third_party/blink/renderer/core/exported/web_settings_impl.cc b/third_party/blink/renderer/core/exported/web_settings_impl.cc
index 69157fea..b216731 100644
--- a/third_party/blink/renderer/core/exported/web_settings_impl.cc
+++ b/third_party/blink/renderer/core/exported/web_settings_impl.cc
@@ -624,6 +624,11 @@
   settings_->SetSyncXHRInDocumentsEnabled(enabled);
 }
 
+void WebSettingsImpl::SetTargetBlankImpliesNoOpenerEnabledWillBeRemoved(
+    bool enabled) {
+  settings_->SetTargetBlankImpliesNoOpenerEnabledWillBeRemoved(enabled);
+}
+
 void WebSettingsImpl::SetCaretBrowsingEnabled(bool enabled) {
   settings_->SetCaretBrowsingEnabled(enabled);
 }
diff --git a/third_party/blink/renderer/core/exported/web_settings_impl.h b/third_party/blink/renderer/core/exported/web_settings_impl.h
index 2971a655..2a7838c 100644
--- a/third_party/blink/renderer/core/exported/web_settings_impl.h
+++ b/third_party/blink/renderer/core/exported/web_settings_impl.h
@@ -162,6 +162,7 @@
   void SetSupportDeprecatedTargetDensityDPI(bool) override;
   void SetSupportsMultipleWindows(bool) override;
   void SetSyncXHRInDocumentsEnabled(bool) override;
+  void SetTargetBlankImpliesNoOpenerEnabledWillBeRemoved(bool) override;
   void SetTextAreasAreResizable(bool) override;
   void SetTextAutosizingEnabled(bool) override;
   void SetAccessibilityFontScaleFactor(float) override;
diff --git a/third_party/blink/renderer/core/exported/web_view_impl.cc b/third_party/blink/renderer/core/exported/web_view_impl.cc
index 0d1cf9b1..a49a490 100644
--- a/third_party/blink/renderer/core/exported/web_view_impl.cc
+++ b/third_party/blink/renderer/core/exported/web_view_impl.cc
@@ -1404,6 +1404,8 @@
   blink::WebNetworkStateNotifier::SetSaveDataEnabled(prefs.data_saver_enabled);
   settings->SetLocalStorageEnabled(prefs.local_storage_enabled);
   settings->SetSyncXHRInDocumentsEnabled(prefs.sync_xhr_in_documents_enabled);
+  settings->SetTargetBlankImpliesNoOpenerEnabledWillBeRemoved(
+      prefs.target_blank_implies_no_opener_enabled_will_be_removed);
   RuntimeEnabledFeatures::SetDatabaseEnabled(prefs.databases_enabled);
   settings->SetOfflineWebApplicationCacheEnabled(
       prefs.application_cache_enabled);
diff --git a/third_party/blink/renderer/core/frame/settings.cc b/third_party/blink/renderer/core/frame/settings.cc
index b27f55e9..129589f 100644
--- a/third_party/blink/renderer/core/frame/settings.cc
+++ b/third_party/blink/renderer/core/frame/settings.cc
@@ -36,6 +36,46 @@
 
 namespace blink {
 
+namespace {
+
+// For generated Settings::SetFromStrings().
+template <typename T>
+struct FromString {
+  T operator()(const String& s) { return static_cast<T>(s.ToInt()); }
+};
+
+template <>
+struct FromString<String> {
+  const String& operator()(const String& s) { return s; }
+};
+
+template <>
+struct FromString<bool> {
+  bool operator()(const String& s) { return s.IsEmpty() || s == "true"; }
+};
+
+template <>
+struct FromString<float> {
+  float operator()(const String& s) { return s.ToFloat(); }
+};
+
+template <>
+struct FromString<double> {
+  double operator()(const String& s) { return s.ToDouble(); }
+};
+
+template <>
+struct FromString<IntSize> {
+  IntSize operator()(const String& s) {
+    Vector<String> fields;
+    s.Split(',', fields);
+    return IntSize(fields.size() > 0 ? fields[0].ToInt() : 0,
+                   fields.size() > 1 ? fields[1].ToInt() : 0);
+  }
+};
+
+}  // namespace
+
 // NOTEs
 //  1) EditingMacBehavior comprises builds on Mac;
 //  2) EditingWindowsBehavior comprises builds on Windows;
@@ -67,8 +107,7 @@
 static const bool kDefaultSelectTrailingWhitespaceEnabled = false;
 #endif
 
-Settings::Settings()
-    : text_autosizing_enabled_(false) SETTINGS_INITIALIZER_LIST {}
+Settings::Settings() : delegate_(nullptr) SETTINGS_INITIALIZER_LIST {}
 
 SETTINGS_SETTER_BODIES
 
@@ -81,23 +120,4 @@
     delegate_->SettingsChanged(change_type);
 }
 
-void Settings::SetTextAutosizingEnabled(bool text_autosizing_enabled) {
-  if (text_autosizing_enabled_ == text_autosizing_enabled)
-    return;
-
-  text_autosizing_enabled_ = text_autosizing_enabled;
-  Invalidate(SettingsDelegate::kTextAutosizingChange);
-}
-
-// TODO: Move to Settings.json5 once make_settings can understand IntSize.
-void Settings::SetTextAutosizingWindowSizeOverride(
-    const IntSize& text_autosizing_window_size_override) {
-  if (text_autosizing_window_size_override_ ==
-      text_autosizing_window_size_override)
-    return;
-
-  text_autosizing_window_size_override_ = text_autosizing_window_size_override;
-  Invalidate(SettingsDelegate::kTextAutosizingChange);
-}
-
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/frame/settings.h b/third_party/blink/renderer/core/frame/settings.h
index af8e5f4..5b6ab45 100644
--- a/third_party/blink/renderer/core/frame/settings.h
+++ b/third_party/blink/renderer/core/frame/settings.h
@@ -63,20 +63,7 @@
     return generic_font_family_settings_;
   }
   void NotifyGenericFontFamilyChange() {
-    Invalidate(SettingsDelegate::kFontFamilyChange);
-  }
-
-  void SetTextAutosizingEnabled(bool);
-  bool TextAutosizingEnabled() const { return text_autosizing_enabled_; }
-
-  void SetBypassCSP(bool enabled) { bypass_csp_ = enabled; }
-  bool BypassCSP() const { return bypass_csp_; }
-
-  // Only set by web tests, and only used if TextAutosizingEnabled() returns
-  // true.
-  void SetTextAutosizingWindowSizeOverride(const IntSize&);
-  const IntSize& TextAutosizingWindowSizeOverride() const {
-    return text_autosizing_window_size_override_;
+    Invalidate(SettingsDelegate::ChangeType::kFontFamily);
   }
 
   SETTINGS_GETTERS_AND_SETTERS
@@ -89,9 +76,6 @@
   SettingsDelegate* delegate_;
 
   GenericFontFamilySettings generic_font_family_settings_;
-  IntSize text_autosizing_window_size_override_;
-  bool text_autosizing_enabled_ : 1;
-  bool bypass_csp_ = false;
 
   SETTINGS_MEMBER_VARIABLES
 
diff --git a/third_party/blink/renderer/core/frame/settings.json5 b/third_party/blink/renderer/core/frame/settings.json5
index 3af88df..29ea43a 100644
--- a/third_party/blink/renderer/core/frame/settings.json5
+++ b/third_party/blink/renderer/core/frame/settings.json5
@@ -28,10 +28,15 @@
   // Valid parameters for data entries below.
   parameters: {
     type: {
-      default: "bool"
+      default: "bool",
+      valid_type: "str",  // The string value should be a C++ type name
     },
     initial: {},
-    invalidate: {},
+    invalidate: {
+      default: [],
+      valid_type: "list",
+      // See SettingsDelegate::ChangeType for valid values (without "k"),
+    },
   },
 
   data: [
@@ -70,7 +75,7 @@
     {
       name: "minimumFontSize",
       initial: 0,
-      invalidate: "Style",
+      invalidate: ["Style"],
       type: "int",
     },
 
@@ -79,19 +84,19 @@
     {
       name: "minimumLogicalFontSize",
       initial: 0,
-      invalidate: "Style",
+      invalidate: ["Style"],
       type: "int",
     },
     {
       name: "defaultFontSize",
       initial: 0,
-      invalidate: "Style",
+      invalidate: ["Style"],
       type: "int",
     },
     {
       name: "defaultFixedFontSize",
       initial: 0,
-      invalidate: "Style",
+      invalidate: ["Style"],
       type: "int",
     },
 
@@ -108,7 +113,7 @@
     {
       name: "allowUniversalAccessFromFileURLs",
       initial: true,
-      invalidate: "UniversalAccess",
+      invalidate: ["UniversalAccess"],
     },
     {
       name: "allowFileAccessFromFileURLs",
@@ -133,17 +138,17 @@
     {
       name: "HighlightAds",
       initial: false,
-      invalidate: "HighlightAds"
+      invalidate: ["HighlightAds"],
     },
     {
       name: "textAreasAreResizable",
       initial: false,
-      invalidate: "Style",
+      invalidate: ["Style"],
     },
     {
       name: "acceleratedCompositingEnabled",
       initial: false,
-      invalidate: "AcceleratedCompositing",
+      invalidate: ["AcceleratedCompositing"],
     },
 
     {
@@ -166,7 +171,7 @@
     {
       name: "preferCompositingToLCDTextEnabled",
       initial: false,
-      invalidate: "AcceleratedCompositing",
+      invalidate: ["AcceleratedCompositing"],
     },
 
     {
@@ -231,7 +236,7 @@
     {
       name: "immersiveModeEnabled",
       initial: false,
-      invalidate: "MediaControls",
+      invalidate: ["MediaControls"],
     },
 
     // Only affects main thread scrolling
@@ -246,7 +251,7 @@
     {
       name: "threadedScrollingEnabled",
       initial: true,
-      invalidate: "Style",
+      invalidate: ["Style"],
     },
 
     // Used in web tests for gesture tap highlights. Makes the highlights square
@@ -285,6 +290,10 @@
       initial: true,
     },
     {
+      name: "targetBlankImpliesNoOpenerEnabledWillBeRemoved",
+      initial: true,
+    },
+    {
       name: "cookieEnabled",
       initial: true,
     },
@@ -311,7 +320,7 @@
     {
       name: "spatialNavigationEnabled",
       initial: false,
-      invalidate: "SpatialNavigation",
+      invalidate: ["SpatialNavigation"],
     },
 
     // This setting adds a means to enable/disable touch initiated drag & drop. If
@@ -407,13 +416,13 @@
     {
       name: "forceZeroLayoutHeight",
       initial: false,
-      invalidate: "ViewportDescription",
+      invalidate: ["ViewportDescription"],
     },
 
     {
       name: "mainFrameClipsContent",
       initial: true,
-      invalidate: "ViewportPaintProperties",
+      invalidate: ["ViewportPaintProperties"],
     },
 
     // For android.webkit.WebSettings.setUseWideViewport()
@@ -421,7 +430,7 @@
     {
       name: "useWideViewport",
       initial: true,
-      invalidate: "ViewportDescription",
+      invalidate: ["ViewportDescription"],
     },
 
     // For android.webkit.WebSettings.setLoadWithOverviewMode()
@@ -429,7 +438,7 @@
     {
       name: "loadWithOverviewMode",
       initial: true,
-      invalidate: "ViewportDescription",
+      invalidate: ["ViewportDescription"],
     },
 
     // Used by android_webview to support legacy apps that inject script into a top-level initial empty
@@ -463,14 +472,14 @@
     {
       name: "caretBrowsingEnabled",
       initial: false,
-      invalidate: "Style",
+      invalidate: ["Style"],
     },
 
     // Font scale factor for accessibility, applied as part of text autosizing.
     {
       name: "accessibilityFontScaleFactor",
       initial: "1.0",
-      invalidate: "TextAutosizing",
+      invalidate: ["TextAutosizing"],
       type: "double",
     },
 
@@ -478,13 +487,13 @@
     {
       name: "mediaTypeOverride",
       initial: "\"\"",
-      invalidate: "MediaQuery",
+      invalidate: ["MediaQuery"],
       type: "String",
     },
     {
       name: "displayModeOverride",
       initial: "blink::mojom::DisplayMode::kUndefined",
-      invalidate: "MediaQuery",
+      invalidate: ["MediaQuery"],
       type: "blink::mojom::DisplayMode",
     },
 
@@ -493,12 +502,12 @@
     {
       name: "loadsImagesAutomatically",
       initial: false,
-      invalidate: "ImageLoading",
+      invalidate: ["ImageLoading"],
     },
     {
       name: "imagesEnabled",
       initial: true,
-      invalidate: "ImageLoading",
+      invalidate: ["ImageLoading"],
     },
     {
       name: "imageAnimationPolicy",
@@ -517,18 +526,18 @@
     {
       name: "pluginsEnabled",
       initial: false,
-      invalidate: "Plugins",
+      invalidate: ["Plugins"],
     },
 
     {
       name: "viewportEnabled",
       initial: false,
-      invalidate: "ViewportDescription",
+      invalidate: ["ViewportDescription"],
     },
     {
       name: "viewportMetaEnabled",
       initial: false,
-      invalidate: "ViewportDescription",
+      invalidate: ["ViewportDescription"],
     },
 
     // When true, Blink will use the content width and viewport size to set the
@@ -542,7 +551,7 @@
     {
       name: "dnsPrefetchingEnabled",
       initial: false,
-      invalidate: "DNSPrefetching",
+      invalidate: ["DNSPrefetching"],
     },
 
     // Clients that execute script should call ExecutionContext::canExecuteScripts()
@@ -563,7 +572,7 @@
     {
       name: "forceAndroidOverlayScrollbar",
       initial: false,
-      invalidate: "ScrollbarLayout",
+      invalidate: ["ScrollbarLayout"],
     },
 
     // Set the timeout seconds of the network-quiet timers in IdlenessDetector.
@@ -580,7 +589,7 @@
     {
       name: "forceMainWorldInitialization",
       initial: false,
-      invalidate: "DOMWorlds",
+      invalidate: ["DOMWorlds"],
     },
 
     // Forces TouchEventFeatureDetection conditional feature for all main
@@ -597,7 +606,7 @@
     {
       name: "deviceScaleAdjustment",
       initial: "1.0",
-      invalidate: "TextAutosizing",
+      invalidate: ["TextAutosizing"],
       type: "double",
     },
 
@@ -623,13 +632,13 @@
     {
       name: "availablePointerTypes",
       initial: "ui::POINTER_TYPE_NONE",
-      invalidate: "MediaQuery",
+      invalidate: ["MediaQuery"],
       type: "int",
     },
     {
       name: "availableHoverTypes",
       initial: "ui::HOVER_TYPE_NONE",
-      invalidate: "MediaQuery",
+      invalidate: ["MediaQuery"],
       type: "int",
     },
 
@@ -637,13 +646,13 @@
     {
       name: "primaryPointerType",
       initial: "mojom::blink::PointerType::kPointerNone",
-      invalidate: "MediaQuery",
+      invalidate: ["MediaQuery"],
       type: "blink::mojom::PointerType",
     },
     {
       name: "primaryHoverType",
       initial: "mojom::blink::HoverType::kHoverNone",
-      invalidate: "MediaQuery",
+      invalidate: ["MediaQuery"],
       type: "mojom::blink::HoverType",
     },
 
@@ -717,7 +726,7 @@
     {
       name: "viewportStyle",
       initial: "mojom::blink::ViewportStyle::kDefault",
-      invalidate: "ViewportRule",
+      invalidate: ["ViewportRule"],
       type: "mojom::blink::ViewportStyle",
     },
 
@@ -726,7 +735,7 @@
     {
       name: "textTrackKindUserPreference",
       initial: "TextTrackKindUserPreference::kDefault",
-      invalidate: "TextTrackKindUserPreference",
+      invalidate: ["TextTrackKindUserPreference"],
       type: "TextTrackKindUserPreference",
     },
 
@@ -836,7 +845,7 @@
     {
       name: "hideScrollbars",
       initial: false,
-      invalidate: "ViewportPaintProperties",
+      invalidate: ["ViewportPaintProperties"],
     },
 
     // Spellchecking is enabled by default for elements that do not specify it explicitly
@@ -863,7 +872,7 @@
     {
       name: "mediaControlsEnabled",
       initial: true,
-      invalidate: "MediaControls",
+      invalidate: ["MediaControls"],
     },
 
     // Whether we should not update selection attributes when mutating selection range.
@@ -886,7 +895,7 @@
     {
       name: "forceDarkModeEnabled",
       initial: false,
-      invalidate: "ForceDark",
+      invalidate: ["ColorScheme", "Paint"],
     },
 
     {
@@ -1008,7 +1017,7 @@
     {
       name: "preferredColorScheme",
       initial: "mojom::blink::PreferredColorScheme::kLight",
-      invalidate: "ColorScheme",
+      invalidate: ["ColorScheme"],
       type: "mojom::blink::PreferredColorScheme",
     },
 
@@ -1017,7 +1026,7 @@
     {
       name: "preferredContrast",
       initial: "mojom::blink::PreferredContrast::kNoPreference",
-      invalidate: "MediaQuery",
+      invalidate: ["MediaQuery"],
       type: "mojom::blink::PreferredContrast",
     },
 
@@ -1026,7 +1035,7 @@
     {
       name: "prefersReducedMotion",
       initial: false,
-      invalidate: "MediaQuery",
+      invalidate: ["MediaQuery"],
     },
     {
       name: "DontSendKeyEventsToJavascript",
@@ -1037,14 +1046,13 @@
     {
       name: "navigationControls",
       initial: "NavigationControls::kNone",
-      invalidate: "MediaQuery",
+      invalidate: ["MediaQuery"],
       type: "NavigationControls",
     },
     {
       name: "accessibilityAlwaysShowFocus",
       initial: false,
-      invalidate: "Style",
-      type: "bool"
+      invalidate: ["Style"],
     },
     // aria-modal="true" on some platforms requires the accessibility tree to
     // be pruned so no other background content is exposed to assistive
@@ -1053,7 +1061,6 @@
     {
       name: "ariaModalPrunesAXTree",
       initial: false,
-      type: "bool",
     },
     // The AXMenuList class provides a fake implementation of the
     // select element pop-up menu, which is required on platforms like
@@ -1067,12 +1074,25 @@
     {
       name: "selectionClipboardBufferAvailable",
       initial: false,
-      type: "bool",
     },
     {
       name: "accessibilityIncludeSvgGElement",
       initial: false,
-      type: "bool",
+    },
+    {
+      name: "bypassCSP",
+      initial: false,
+    },
+    {
+      name: "textAutosizingEnabled",
+      initial: false,
+      invalidate: ["TextAutosizing"],
+    },
+    // Only set by web tests, and only used if textAutosizingEnabled is true.
+    {
+      name: "textAutosizingWindowSizeOverride",
+      invalidate: ["TextAutosizing"],
+      type: "IntSize",
     },
   ],
 }
diff --git a/third_party/blink/renderer/core/frame/settings_delegate.h b/third_party/blink/renderer/core/frame/settings_delegate.h
index 5f00667..e631601 100644
--- a/third_party/blink/renderer/core/frame/settings_delegate.h
+++ b/third_party/blink/renderer/core/frame/settings_delegate.h
@@ -50,30 +50,29 @@
 
   // We currently use an enum instead of individual invalidation
   // functions to make generating Settings.in slightly easier.
-  enum ChangeType {
-    kStyleChange,
-    kViewportDescriptionChange,
-    kViewportRuleChange,
-    kViewportPaintPropertiesChange,
-    kDNSPrefetchingChange,
-    kImageLoadingChange,
-    kTextAutosizingChange,
-    kFontFamilyChange,
-    kAcceleratedCompositingChange,
-    kMediaQueryChange,
-    kAccessibilityStateChange,
-    kTextTrackKindUserPreferenceChange,
-    kDOMWorldsChange,
-    kMediaControlsChange,
-    kPluginsChange,
-    kHighlightAdsChange,
-    kPaintChange,
-    kScrollbarLayoutChange,
-    kColorSchemeChange,
-    kSpatialNavigationChange,
-    kUniversalAccessChange,
-    kVisionDeficiencyChange,
-    kForceDarkChange,
+  enum class ChangeType {
+    kStyle,
+    kViewportDescription,
+    kViewportRule,
+    kViewportPaintProperties,
+    kDNSPrefetching,
+    kImageLoading,
+    kTextAutosizing,
+    kFontFamily,
+    kAcceleratedCompositing,
+    kMediaQuery,
+    kAccessibilityState,
+    kTextTrackKindUserPreference,
+    kDOMWorlds,
+    kMediaControls,
+    kPlugins,
+    kHighlightAds,
+    kPaint,
+    kScrollbarLayout,
+    kColorScheme,
+    kSpatialNavigation,
+    kUniversalAccess,
+    kVisionDeficiency,
   };
 
   virtual void SettingsChanged(ChangeType) = 0;
diff --git a/third_party/blink/renderer/core/frame/web_frame_test.cc b/third_party/blink/renderer/core/frame/web_frame_test.cc
index dd71bae..5a579c3 100644
--- a/third_party/blink/renderer/core/frame/web_frame_test.cc
+++ b/third_party/blink/renderer/core/frame/web_frame_test.cc
@@ -1277,7 +1277,7 @@
       To<LocalFrame>(web_view_helper.GetWebView()->GetPage()->MainFrame())
           ->GetDocument();
   document->GetSettings()->SetTextAutosizingEnabled(true);
-  EXPECT_TRUE(document->GetSettings()->TextAutosizingEnabled());
+  EXPECT_TRUE(document->GetSettings()->GetTextAutosizingEnabled());
   web_view_helper.Resize(gfx::Size(viewport_width, viewport_height));
 
   EXPECT_TRUE(SetTextAutosizingMultiplier(document, 2));
@@ -1306,7 +1306,7 @@
       To<LocalFrame>(web_view_helper.GetWebView()->GetPage()->MainFrame())
           ->GetDocument();
   document->GetSettings()->SetTextAutosizingEnabled(true);
-  EXPECT_TRUE(document->GetSettings()->TextAutosizingEnabled());
+  EXPECT_TRUE(document->GetSettings()->GetTextAutosizingEnabled());
 
   web_view_helper.Resize(gfx::Size(490, 800));
 
@@ -1331,7 +1331,7 @@
   Document* document = main_frame->GetDocument();
   LocalFrameView* frame_view = web_view_helper.LocalMainFrame()->GetFrameView();
   document->GetSettings()->SetTextAutosizingEnabled(true);
-  EXPECT_TRUE(document->GetSettings()->TextAutosizingEnabled());
+  EXPECT_TRUE(document->GetSettings()->GetTextAutosizingEnabled());
   web_view_helper.Resize(gfx::Size(viewport_width, viewport_height));
 
   for (Frame* frame = main_frame; frame; frame = frame->Tree().TraverseNext()) {
diff --git a/third_party/blink/renderer/core/html/html_anchor_element.cc b/third_party/blink/renderer/core/html/html_anchor_element.cc
index f4137ea..5e1bcf7 100644
--- a/third_party/blink/renderer/core/html/html_anchor_element.cc
+++ b/third_party/blink/renderer/core/html/html_anchor_element.cc
@@ -544,7 +544,9 @@
   }
   if (HasRel(kRelationNoOpener) ||
       (EqualIgnoringASCIICase(target, "_blank") && !HasRel(kRelationOpener) &&
-       RuntimeEnabledFeatures::TargetBlankImpliesNoOpenerEnabled())) {
+       RuntimeEnabledFeatures::TargetBlankImpliesNoOpenerEnabled() &&
+       frame->GetSettings()
+           ->GetTargetBlankImpliesNoOpenerEnabledWillBeRemoved())) {
     frame_request.SetNoOpener();
   }
 
diff --git a/third_party/blink/renderer/core/html/html_embed_element.cc b/third_party/blink/renderer/core/html/html_embed_element.cc
index 0ea3c6f..35bc484 100644
--- a/third_party/blink/renderer/core/html/html_embed_element.cc
+++ b/third_party/blink/renderer/core/html/html_embed_element.cc
@@ -126,6 +126,9 @@
       if (FastHasAttribute(html_names::kTypeAttr)) {
         SetNeedsPluginUpdate(true);
         ReattachOnPluginChangeIfNeeded();
+      } else {
+        UseCounter::Count(GetDocument(),
+                          WebFeature::kEmbedElementWithoutTypeSrcChanged);
       }
     }
   } else {
diff --git a/third_party/blink/renderer/core/inspector/dev_tools_emulator.cc b/third_party/blink/renderer/core/inspector/dev_tools_emulator.cc
index 10c1c63..ee57650 100644
--- a/third_party/blink/renderer/core/inspector/dev_tools_emulator.cc
+++ b/third_party/blink/renderer/core/inspector/dev_tools_emulator.cc
@@ -71,7 +71,7 @@
       original_default_minimum_page_scale_factor_(0),
       original_default_maximum_page_scale_factor_(0),
       embedder_text_autosizing_enabled_(
-          web_view->GetPage()->GetSettings().TextAutosizingEnabled()),
+          web_view->GetPage()->GetSettings().GetTextAutosizingEnabled()),
       embedder_device_scale_adjustment_(
           web_view->GetPage()->GetSettings().GetDeviceScaleAdjustment()),
       embedder_prefer_compositing_to_lcd_text_enabled_(
diff --git a/third_party/blink/renderer/core/layout/hit_testing_test.cc b/third_party/blink/renderer/core/layout/hit_testing_test.cc
index a6f7248..5b51ba8a 100644
--- a/third_party/blink/renderer/core/layout/hit_testing_test.cc
+++ b/third_party/blink/renderer/core/layout/hit_testing_test.cc
@@ -173,4 +173,41 @@
   EXPECT_FALSE(hit_result.BoxFragment());
 }
 
+TEST_F(HitTestingTest, ScrolledInline) {
+  SetBodyInnerHTML(R"HTML(
+    <style>
+    body {
+      margin: 0;
+      font-size: 50px;
+      line-height: 1;
+    }
+    #scroller {
+      width: 400px;
+      height: 5em;
+      overflow: scroll;
+      white-space: pre;
+    }
+    </style>
+    <div id="scroller">line1
+line2
+line3
+line4
+line5
+line6
+line7
+line8
+line9</div>
+  )HTML");
+
+  // Scroll #scroller by 2 lines. "line3" should be at the top.
+  Element* scroller = GetElementById("scroller");
+  scroller->setScrollTop(100);
+
+  const auto& text = *To<Text>(GetElementById("scroller")->firstChild());
+
+  // Expect to hit test position 12 (beginning of line3).
+  EXPECT_EQ(PositionWithAffinity(Position(text, 12)),
+            HitTest(PhysicalOffset(5, 5)));
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/layout/layout_text.cc b/third_party/blink/renderer/core/layout/layout_text.cc
index b5b16c03..7cd7914 100644
--- a/third_party/blink/renderer/core/layout/layout_text.cc
+++ b/third_party/blink/renderer/core/layout/layout_text.cc
@@ -826,11 +826,18 @@
     // See All/LayoutViewHitTestTest.HitTestHorizontal/* and
     // All/LayoutViewHitTestTest.HitTestVerticalRL/*
     NGInlineCursor cursor;
+    const LayoutBlockFlow* containing_block_flow = ContainingNGBlockFlow();
+    PhysicalOffset point_in_contents = point;
+    if (containing_block_flow->IsScrollContainer()) {
+      point_in_contents += PhysicalOffset(
+          containing_block_flow->PixelSnappedScrolledContentOffset());
+    }
     for (cursor.MoveTo(*this); cursor; cursor.MoveToNextForSameLayoutObject()) {
       if (!EnclosingIntRect(cursor.Current().RectInContainerFragment())
-               .Contains(FlooredIntPoint(point)))
+               .Contains(FlooredIntPoint(point_in_contents)))
         continue;
-      if (auto position_with_affinity = cursor.PositionForPointInChild(point)) {
+      if (auto position_with_affinity =
+              cursor.PositionForPointInChild(point_in_contents)) {
         // Note: Due by Bidi adjustment, |position| isn't relative to this.
         const Position& position = position_with_affinity.GetPosition();
         DCHECK(position.IsOffsetInAnchor()) << position;
@@ -841,7 +848,7 @@
       }
     }
     // Try for leading and trailing spaces between lines.
-    return ContainingNGBlockFlow()->PositionForPoint(point);
+    return containing_block_flow->PositionForPoint(point);
   }
 
   DCHECK(CanUseInlineBox(*this));
diff --git a/third_party/blink/renderer/core/layout/scroll_anchor.cc b/third_party/blink/renderer/core/layout/scroll_anchor.cc
index c39d0c56..0494502 100644
--- a/third_party/blink/renderer/core/layout/scroll_anchor.cc
+++ b/third_party/blink/renderer/core/layout/scroll_anchor.cc
@@ -673,6 +673,15 @@
 }
 
 const SerializedAnchor ScrollAnchor::GetSerializedAnchor() {
+  if (auto* scroller_box = ScrollerLayoutBox(scroller_)) {
+    // This method may be called to find a serialized anchor on a document which
+    // needs a lifecycle update. Computing offsets below may currently compute
+    // style for ::first-line. If that is done with dirty active stylesheets, we
+    // may have null pointer crash as style computation assumes active sheets
+    // are up to date. Update active style if necessary here.
+    scroller_box->GetDocument().GetStyleEngine().UpdateActiveStyle();
+  }
+
   // It's safe to return saved_selector_ before checking anchor_object_, since
   // clearing anchor_object_ also clears saved_selector_.
   if (!saved_selector_.IsEmpty()) {
diff --git a/third_party/blink/renderer/core/layout/text_autosizer.cc b/third_party/blink/renderer/core/layout/text_autosizer.cc
index 10936b7..c37fd886 100644
--- a/third_party/blink/renderer/core/layout/text_autosizer.cc
+++ b/third_party/blink/renderer/core/layout/text_autosizer.cc
@@ -621,7 +621,7 @@
 
   PageInfo previous_page_info(page_info_);
   page_info_.setting_enabled_ =
-      document_->GetSettings()->TextAutosizingEnabled();
+      document_->GetSettings()->GetTextAutosizingEnabled();
 
   if (!page_info_.setting_enabled_ || document_->Printing()) {
     page_info_.page_needs_autosizing_ = false;
@@ -638,7 +638,7 @@
     } else {
       LocalFrame& main_frame = To<LocalFrame>(frame);
       IntSize frame_size =
-          document_->GetSettings()->TextAutosizingWindowSizeOverride();
+          document_->GetSettings()->GetTextAutosizingWindowSizeOverride();
       if (frame_size.IsEmpty())
         frame_size = WindowSize();
 
diff --git a/third_party/blink/renderer/core/loader/frame_loader.cc b/third_party/blink/renderer/core/loader/frame_loader.cc
index 931f91b8..a5cc196 100644
--- a/third_party/blink/renderer/core/loader/frame_loader.cc
+++ b/third_party/blink/renderer/core/loader/frame_loader.cc
@@ -1764,7 +1764,7 @@
   ContentSecurityPolicy* csp = MakeGarbageCollected<ContentSecurityPolicy>();
   csp->SetOverrideURLForSelf(response.CurrentRequestUrl());
 
-  if (frame_->GetSettings()->BypassCSP())
+  if (frame_->GetSettings()->GetBypassCSP())
     return csp;  // Empty CSP.
 
   // Parse CSP from the HTTP response.
diff --git a/third_party/blink/renderer/core/loader/http_equiv.cc b/third_party/blink/renderer/core/loader/http_equiv.cc
index d102213c..f39ba93 100644
--- a/third_party/blink/renderer/core/loader/http_equiv.cc
+++ b/third_party/blink/renderer/core/loader/http_equiv.cc
@@ -97,7 +97,7 @@
     const AtomicString& content) {
   if (!window || !window->GetFrame())
     return;
-  if (window->GetFrame()->GetSettings()->BypassCSP())
+  if (window->GetFrame()->GetSettings()->GetBypassCSP())
     return;
   if (EqualIgnoringASCIICase(equiv, "content-security-policy")) {
     window->GetContentSecurityPolicy()->DidReceiveHeader(
diff --git a/third_party/blink/renderer/core/loader/modulescript/document_module_script_fetcher.cc b/third_party/blink/renderer/core/loader/modulescript/document_module_script_fetcher.cc
index 01dd7a2..d49caaae 100644
--- a/third_party/blink/renderer/core/loader/modulescript/document_module_script_fetcher.cc
+++ b/third_party/blink/renderer/core/loader/modulescript/document_module_script_fetcher.cc
@@ -71,8 +71,7 @@
   client_->NotifyFetchFinishedSuccess(ModuleScriptCreationParams(
       /*source_url=*/url, /*base_url=*/url,
       ScriptSourceLocationType::kExternalFile, module_type,
-      script_resource->SourceText(), script_resource->CacheHandler(),
-      script_resource->GetResourceRequest().GetCredentialsMode(), streamer,
+      script_resource->SourceText(), script_resource->CacheHandler(), streamer,
       not_streamed_reason));
 }
 
diff --git a/third_party/blink/renderer/core/loader/modulescript/installed_service_worker_module_script_fetcher.cc b/third_party/blink/renderer/core/loader/modulescript/installed_service_worker_module_script_fetcher.cc
index a5da398..3367e89 100644
--- a/third_party/blink/renderer/core/loader/modulescript/installed_service_worker_module_script_fetcher.cc
+++ b/third_party/blink/renderer/core/loader/modulescript/installed_service_worker_module_script_fetcher.cc
@@ -110,8 +110,7 @@
       /*source_url=*/fetch_params.Url(), /*base_url=*/fetch_params.Url(),
       ScriptSourceLocationType::kExternalFile, module_type,
       ParkableString(script_data->TakeSourceText().Impl()),
-      /*cache_handler=*/nullptr,
-      fetch_params.GetResourceRequest().GetCredentialsMode()));
+      /*cache_handler=*/nullptr));
 }
 
 void InstalledServiceWorkerModuleScriptFetcher::Trace(Visitor* visitor) const {
diff --git a/third_party/blink/renderer/core/loader/modulescript/module_script_creation_params.h b/third_party/blink/renderer/core/loader/modulescript/module_script_creation_params.h
index b9ecfb8..d6fec809 100644
--- a/third_party/blink/renderer/core/loader/modulescript/module_script_creation_params.h
+++ b/third_party/blink/renderer/core/loader/modulescript/module_script_creation_params.h
@@ -33,7 +33,6 @@
       const ModuleType module_type,
       const ParkableString& source_text,
       SingleCachedMetadataHandler* cache_handler,
-      network::mojom::CredentialsMode credentials_mode,
       ScriptStreamer* script_streamer = nullptr,
       ScriptStreamer::NotStreamingReason not_streaming_reason =
           ScriptStreamer::NotStreamingReason::kStreamingDisabled)
@@ -45,7 +44,6 @@
         source_text_(source_text),
         isolated_source_text_(),
         cache_handler_(cache_handler),
-        credentials_mode_(credentials_mode),
         script_streamer_(script_streamer),
         not_streaming_reason_(not_streaming_reason) {
     DCHECK(source_location_type == ScriptSourceLocationType::kExternalFile ||
@@ -65,9 +63,9 @@
     String isolated_source_text =
         isolated_source_text_ ? isolated_source_text_.IsolatedCopy()
                               : GetSourceText().ToString().IsolatedCopy();
-    return ModuleScriptCreationParams(
-        SourceURL().Copy(), BaseURL().Copy(), source_location_type_,
-        GetModuleType(), isolated_source_text, GetFetchCredentialsMode());
+    return ModuleScriptCreationParams(SourceURL().Copy(), BaseURL().Copy(),
+                                      source_location_type_, GetModuleType(),
+                                      isolated_source_text);
   }
 
   ModuleType GetModuleType() const { return module_type_; }
@@ -91,17 +89,13 @@
   ModuleScriptCreationParams CopyWithClearedSourceText() const {
     return ModuleScriptCreationParams(
         source_url_, base_url_, source_location_type_, module_type_,
-        ParkableString(), /*cache_handler=*/nullptr, credentials_mode_,
+        ParkableString(), /*cache_handler=*/nullptr,
         /*script_streamer=*/nullptr,
         ScriptStreamer::NotStreamingReason::kStreamingDisabled);
   }
 
   SingleCachedMetadataHandler* CacheHandler() const { return cache_handler_; }
 
-  network::mojom::CredentialsMode GetFetchCredentialsMode() const {
-    return credentials_mode_;
-  }
-
   bool IsSafeToSendToAnotherThread() const {
     return source_url_.IsSafeToSendToAnotherThread() &&
            base_url_.IsSafeToSendToAnotherThread() && is_isolated_;
@@ -118,8 +112,7 @@
                              const KURL& base_url,
                              ScriptSourceLocationType source_location_type,
                              const ModuleType& module_type,
-                             const String& isolated_source_text,
-                             network::mojom::CredentialsMode credentials_mode)
+                             const String& isolated_source_text)
       : source_url_(source_url),
         base_url_(base_url),
         source_location_type_(source_location_type),
@@ -127,7 +120,6 @@
         is_isolated_(true),
         source_text_(),
         isolated_source_text_(isolated_source_text),
-        credentials_mode_(credentials_mode),
         // The ScriptStreamer is intentionally cleared since it cannot be passed
         // across threads. This only disables script streaming on worklet
         // top-level scripts where the ModuleScriptCreationParams is
@@ -150,8 +142,6 @@
   // |cache_handler_| is cleared when crossing thread boundaries.
   Persistent<SingleCachedMetadataHandler> cache_handler_;
 
-  const network::mojom::CredentialsMode credentials_mode_;
-
   // |script_streamer_| is cleared when crossing thread boundaries.
   Persistent<ScriptStreamer> script_streamer_;
   const ScriptStreamer::NotStreamingReason not_streaming_reason_;
diff --git a/third_party/blink/renderer/core/loader/modulescript/worker_module_script_fetcher.cc b/third_party/blink/renderer/core/loader/modulescript/worker_module_script_fetcher.cc
index e2404e0..94560a2f1 100644
--- a/third_party/blink/renderer/core/loader/modulescript/worker_module_script_fetcher.cc
+++ b/third_party/blink/renderer/core/loader/modulescript/worker_module_script_fetcher.cc
@@ -102,7 +102,6 @@
   }
 
   NotifyClient(resource->Url(), module_type,
-               script_resource->GetResourceRequest().GetCredentialsMode(),
                script_resource->SourceText(), resource->GetResponse(),
                script_resource->CacheHandler());
 }
@@ -110,7 +109,6 @@
 void WorkerModuleScriptFetcher::NotifyClient(
     const KURL& request_url,
     ModuleType module_type,
-    const network::mojom::CredentialsMode credentials_mode,
     const ParkableString& source_text,
     const ResourceResponse& response,
     SingleCachedMetadataHandler* cache_handler) {
@@ -188,7 +186,7 @@
   client_->NotifyFetchFinishedSuccess(ModuleScriptCreationParams(
       /*source_url=*/url, /*base_url=*/url,
       ScriptSourceLocationType::kExternalFile, module_type, source_text,
-      cache_handler, credentials_mode));
+      cache_handler));
 }
 
 void WorkerModuleScriptFetcher::DidReceiveData(base::span<const char> span) {
@@ -230,7 +228,6 @@
     source_text_.Append(decoder_->Flush());
   NotifyClient(worker_main_script_loader_->GetRequestURL(),
                ModuleType::kJavaScript,
-               network::mojom::CredentialsMode::kSameOrigin,
                ParkableString(source_text_.ToString().ReleaseImpl()), response,
                worker_main_script_loader_->CreateCachedMetadataHandler());
 }
diff --git a/third_party/blink/renderer/core/loader/modulescript/worker_module_script_fetcher.h b/third_party/blink/renderer/core/loader/modulescript/worker_module_script_fetcher.h
index f04f861..32be6a6 100644
--- a/third_party/blink/renderer/core/loader/modulescript/worker_module_script_fetcher.h
+++ b/third_party/blink/renderer/core/loader/modulescript/worker_module_script_fetcher.h
@@ -49,7 +49,6 @@
 
   void NotifyClient(const KURL& request_url,
                     ModuleType module_type,
-                    const network::mojom::CredentialsMode credentials_mode,
                     const ParkableString& source_text,
                     const ResourceResponse& response,
                     SingleCachedMetadataHandler* cache_handler);
diff --git a/third_party/blink/renderer/core/loader/modulescript/worklet_module_script_fetcher.cc b/third_party/blink/renderer/core/loader/modulescript/worklet_module_script_fetcher.cc
index bb6276e..ca3e6dd 100644
--- a/third_party/blink/renderer/core/loader/modulescript/worklet_module_script_fetcher.cc
+++ b/third_party/blink/renderer/core/loader/modulescript/worklet_module_script_fetcher.cc
@@ -56,8 +56,7 @@
     params.emplace(/*source_url=*/url, /*base_url=*/url,
                    ScriptSourceLocationType::kExternalFile, module_type,
                    script_resource->SourceText(),
-                   script_resource->CacheHandler(),
-                   script_resource->GetResourceRequest().GetCredentialsMode());
+                   script_resource->CacheHandler());
   }
 
   // This will eventually notify |client| passed to
diff --git a/third_party/blink/renderer/core/page/page.cc b/third_party/blink/renderer/core/page/page.cc
index 2d8f086..022b95e 100644
--- a/third_party/blink/renderer/core/page/page.cc
+++ b/third_party/blink/renderer/core/page/page.cc
@@ -621,12 +621,12 @@
   return subframe_count_;
 }
 
-void Page::SettingsChanged(SettingsDelegate::ChangeType change_type) {
+void Page::SettingsChanged(ChangeType change_type) {
   switch (change_type) {
-    case SettingsDelegate::kStyleChange:
+    case ChangeType::kStyle:
       InitialStyleChanged();
       break;
-    case SettingsDelegate::kViewportDescriptionChange:
+    case ChangeType::kViewportDescription:
       if (MainFrame() && MainFrame()->IsLocalFrame()) {
         DeprecatedLocalMainFrame()
             ->GetDocument()
@@ -639,7 +639,7 @@
         TextAutosizer::UpdatePageInfoInAllFrames(MainFrame());
       }
       break;
-    case SettingsDelegate::kViewportPaintPropertiesChange:
+    case ChangeType::kViewportPaintProperties:
       GetVisualViewport().SetNeedsPaintPropertyUpdate();
       GetVisualViewport().InitializeScrollbars();
       if (auto* local_frame = DynamicTo<LocalFrame>(MainFrame())) {
@@ -647,14 +647,14 @@
           view->SetNeedsPaintPropertyUpdate();
       }
       break;
-    case SettingsDelegate::kDNSPrefetchingChange:
+    case ChangeType::kDNSPrefetching:
       for (Frame* frame = MainFrame(); frame;
            frame = frame->Tree().TraverseNext()) {
         if (auto* local_frame = DynamicTo<LocalFrame>(frame))
           local_frame->GetDocument()->InitDNSPrefetch();
       }
       break;
-    case SettingsDelegate::kImageLoadingChange:
+    case ChangeType::kImageLoading:
       for (Frame* frame = MainFrame(); frame;
            frame = frame->Tree().TraverseNext()) {
         if (auto* local_frame = DynamicTo<LocalFrame>(frame)) {
@@ -665,14 +665,14 @@
         }
       }
       break;
-    case SettingsDelegate::kTextAutosizingChange:
+    case ChangeType::kTextAutosizing:
       if (!MainFrame())
         break;
       // We need to update even for remote main frames since this setting
       // could be changed via InternalSettings.
       TextAutosizer::UpdatePageInfoInAllFrames(MainFrame());
       break;
-    case SettingsDelegate::kFontFamilyChange:
+    case ChangeType::kFontFamily:
       for (Frame* frame = MainFrame(); frame;
            frame = frame->Tree().TraverseNext()) {
         if (auto* local_frame = DynamicTo<LocalFrame>(frame))
@@ -681,10 +681,10 @@
               .UpdateGenericFontFamilySettings();
       }
       break;
-    case SettingsDelegate::kAcceleratedCompositingChange:
+    case ChangeType::kAcceleratedCompositing:
       UpdateAcceleratedCompositingSettings();
       break;
-    case SettingsDelegate::kMediaQueryChange:
+    case ChangeType::kMediaQuery:
       for (Frame* frame = MainFrame(); frame;
            frame = frame->Tree().TraverseNext()) {
         if (auto* local_frame = DynamicTo<LocalFrame>(frame)) {
@@ -693,7 +693,7 @@
         }
       }
       break;
-    case SettingsDelegate::kAccessibilityStateChange:
+    case ChangeType::kAccessibilityState:
       if (!MainFrame() || !MainFrame()->IsLocalFrame())
         break;
       DeprecatedLocalMainFrame()
@@ -701,7 +701,7 @@
           ->AXObjectCacheOwner()
           .ClearAXObjectCache();
       break;
-    case SettingsDelegate::kViewportRuleChange: {
+    case ChangeType::kViewportRule: {
       auto* main_local_frame = DynamicTo<LocalFrame>(MainFrame());
       if (!main_local_frame)
         break;
@@ -709,7 +709,7 @@
         doc->GetStyleEngine().ViewportRulesChanged();
       break;
     }
-    case SettingsDelegate::kTextTrackKindUserPreferenceChange:
+    case ChangeType::kTextTrackKindUserPreference:
       for (Frame* frame = MainFrame(); frame;
            frame = frame->Tree().TraverseNext()) {
         if (auto* local_frame = DynamicTo<LocalFrame>(frame)) {
@@ -720,7 +720,7 @@
         }
       }
       break;
-    case SettingsDelegate::kDOMWorldsChange: {
+    case ChangeType::kDOMWorlds: {
       if (!GetSettings().GetForceMainWorldInitialization())
         break;
       for (Frame* frame = MainFrame(); frame;
@@ -733,7 +733,7 @@
       }
       break;
     }
-    case SettingsDelegate::kMediaControlsChange:
+    case ChangeType::kMediaControls:
       for (Frame* frame = MainFrame(); frame;
            frame = frame->Tree().TraverseNext()) {
         auto* local_frame = DynamicTo<LocalFrame>(frame);
@@ -744,11 +744,11 @@
           HTMLMediaElement::OnMediaControlsEnabledChange(doc);
       }
       break;
-    case SettingsDelegate::kPluginsChange: {
+    case ChangeType::kPlugins: {
       NotifyPluginsChanged();
       break;
     }
-    case SettingsDelegate::kHighlightAdsChange: {
+    case ChangeType::kHighlightAds: {
       for (Frame* frame = MainFrame(); frame;
            frame = frame->Tree().TraverseNext()) {
         if (auto* local_frame = DynamicTo<LocalFrame>(frame))
@@ -756,11 +756,11 @@
       }
       break;
     }
-    case SettingsDelegate::kPaintChange: {
+    case ChangeType::kPaint: {
       InvalidatePaint();
       break;
     }
-    case SettingsDelegate::kScrollbarLayoutChange: {
+    case ChangeType::kScrollbarLayout: {
       for (Frame* frame = MainFrame(); frame;
            frame = frame->Tree().TraverseNext()) {
         auto* local_frame = DynamicTo<LocalFrame>(frame);
@@ -783,16 +783,16 @@
       }
       break;
     }
-    case SettingsDelegate::kColorSchemeChange:
+    case ChangeType::kColorScheme:
       InvalidateColorScheme();
       break;
-    case SettingsDelegate::kSpatialNavigationChange:
+    case ChangeType::kSpatialNavigation:
       if (spatial_navigation_controller_ ||
           GetSettings().GetSpatialNavigationEnabled()) {
         GetSpatialNavigationController().OnSpatialNavigationSettingChanged();
       }
       break;
-    case SettingsDelegate::kUniversalAccessChange: {
+    case ChangeType::kUniversalAccess: {
       if (!GetSettings().GetAllowUniversalAccessFromFileURLs())
         break;
       for (Frame* frame = MainFrame(); frame;
@@ -807,15 +807,11 @@
       }
       break;
     }
-    case SettingsDelegate::kVisionDeficiencyChange: {
+    case ChangeType::kVisionDeficiency: {
       if (auto* main_local_frame = DynamicTo<LocalFrame>(MainFrame()))
         main_local_frame->GetDocument()->VisionDeficiencyChanged();
       break;
     }
-    case SettingsDelegate::kForceDarkChange:
-      InvalidateColorScheme();
-      InvalidatePaint();
-      break;
   }
 }
 
@@ -1092,21 +1088,21 @@
   }
   media_feature_overrides_->SetOverride(media_feature, value);
   if (media_feature == "prefers-color-scheme")
-    SettingsChanged(SettingsDelegate::kColorSchemeChange);
+    SettingsChanged(ChangeType::kColorScheme);
   else
-    SettingsChanged(SettingsDelegate::kMediaQueryChange);
+    SettingsChanged(ChangeType::kMediaQuery);
 }
 
 void Page::ClearMediaFeatureOverrides() {
   media_feature_overrides_.reset();
-  SettingsChanged(SettingsDelegate::kMediaQueryChange);
-  SettingsChanged(SettingsDelegate::kColorSchemeChange);
+  SettingsChanged(ChangeType::kMediaQuery);
+  SettingsChanged(ChangeType::kColorScheme);
 }
 
 void Page::SetVisionDeficiency(VisionDeficiency new_vision_deficiency) {
   if (new_vision_deficiency != vision_deficiency_) {
     vision_deficiency_ = new_vision_deficiency;
-    SettingsChanged(SettingsDelegate::kVisionDeficiencyChange);
+    SettingsChanged(ChangeType::kVisionDeficiency);
   }
 }
 
diff --git a/third_party/blink/renderer/core/script/module_map_test.cc b/third_party/blink/renderer/core/script/module_map_test.cc
index 3362c55..5a3023a 100644
--- a/third_party/blink/renderer/core/script/module_map_test.cc
+++ b/third_party/blink/renderer/core/script/module_map_test.cc
@@ -117,9 +117,8 @@
                ModuleGraphLevel,
                ModuleScriptFetcher::Client* client) override {
       CHECK_EQ(request.GetScriptType(), mojom::blink::ScriptType::kModule);
-      TestRequest* test_request = MakeGarbageCollected<TestRequest>(
-          request.Url(), request.GetResourceRequest().GetCredentialsMode(),
-          client);
+      TestRequest* test_request =
+          MakeGarbageCollected<TestRequest>(request.Url(), client);
       modulator_->test_requests_.push_back(test_request);
     }
     String DebugName() const override { return "TestModuleScriptFetcher"; }
@@ -148,21 +147,18 @@
   }
 
   struct TestRequest final : public GarbageCollected<TestRequest> {
-    TestRequest(const KURL& url,
-                network::mojom::CredentialsMode credential_mode,
-                ModuleScriptFetcher::Client* client)
-        : url_(url), credential_mode_(credential_mode), client_(client) {}
+    TestRequest(const KURL& url, ModuleScriptFetcher::Client* client)
+        : url_(url), client_(client) {}
     void NotifyFetchFinished() {
       client_->NotifyFetchFinishedSuccess(ModuleScriptCreationParams(
           url_, url_, ScriptSourceLocationType::kExternalFile,
           ModuleType::kJavaScript, ParkableString(String("").ReleaseImpl()),
-          nullptr, credential_mode_));
+          nullptr));
     }
     void Trace(Visitor* visitor) const { visitor->Trace(client_); }
 
    private:
     const KURL url_;
-    const network::mojom::CredentialsMode credential_mode_;
     Member<ModuleScriptFetcher::Client> client_;
   };
   HeapVector<Member<TestRequest>> test_requests_;
diff --git a/third_party/blink/renderer/core/script/module_script_test.cc b/third_party/blink/renderer/core/script/module_script_test.cc
index ae5ab6a4..69ac133 100644
--- a/third_party/blink/renderer/core/script/module_script_test.cc
+++ b/third_party/blink/renderer/core/script/module_script_test.cc
@@ -84,8 +84,8 @@
     ModuleScriptCreationParams params(
         KURL("https://fox.url/script.js"), KURL("https://fox.url/"),
         ScriptSourceLocationType::kInline, ModuleType::kJavaScript,
-        ParkableString(source_text.IsolatedCopy().ReleaseImpl()), cache_handler,
-        network::mojom::CredentialsMode::kOmit);
+        ParkableString(source_text.IsolatedCopy().ReleaseImpl()),
+        cache_handler);
     return JSModuleScript::Create(params, modulator, ScriptFetchOptions());
   }
 
diff --git a/third_party/blink/renderer/core/script/script_loader.cc b/third_party/blink/renderer/core/script/script_loader.cc
index 04faa7bf..d414833 100644
--- a/third_party/blink/renderer/core/script/script_loader.cc
+++ b/third_party/blink/renderer/core/script/script_loader.cc
@@ -728,7 +728,7 @@
         ModuleScriptCreationParams params(
             source_url, base_url, ScriptSourceLocationType::kInline,
             ModuleType::kJavaScript, ParkableString(source_text.Impl()),
-            nullptr, options.CredentialsMode());
+            nullptr);
         ModuleScript* module_script =
             JSModuleScript::Create(params, modulator, options, position);
 
diff --git a/third_party/blink/renderer/core/testing/internal_settings.cc b/third_party/blink/renderer/core/testing/internal_settings.cc
index c82cc8b..bb4fc83 100644
--- a/third_party/blink/renderer/core/testing/internal_settings.cc
+++ b/third_party/blink/renderer/core/testing/internal_settings.cc
@@ -64,9 +64,9 @@
     : original_csp_(RuntimeEnabledFeatures::
                         ExperimentalContentSecurityPolicyFeaturesEnabled()),
       original_editing_behavior_(settings->GetEditingBehaviorType()),
-      original_text_autosizing_enabled_(settings->TextAutosizingEnabled()),
+      original_text_autosizing_enabled_(settings->GetTextAutosizingEnabled()),
       original_text_autosizing_window_size_override_(
-          settings->TextAutosizingWindowSizeOverride()),
+          settings->GetTextAutosizingWindowSizeOverride()),
       original_accessibility_font_scale_factor_(
           settings->GetAccessibilityFontScaleFactor()),
       original_media_type_override_(settings->GetMediaTypeOverride()),
@@ -124,7 +124,7 @@
   backup_.RestoreTo(GetSettings());
   backup_ = Backup(GetSettings());
   backup_.original_text_autosizing_enabled_ =
-      GetSettings()->TextAutosizingEnabled();
+      GetSettings()->GetTextAutosizingEnabled();
 
   InternalSettingsGenerated::resetToConsistentState();
 }
diff --git a/third_party/blink/renderer/core/testing/module_test_base.cc b/third_party/blink/renderer/core/testing/module_test_base.cc
index 03e2f769..13b07bf 100644
--- a/third_party/blink/renderer/core/testing/module_test_base.cc
+++ b/third_party/blink/renderer/core/testing/module_test_base.cc
@@ -30,8 +30,7 @@
   ModuleScriptCreationParams params(
       /*source_url=*/url, /*base_url=*/url,
       ScriptSourceLocationType::kExternalFile, ModuleType::kJavaScript,
-      ParkableString(source.Impl()), nullptr,
-      network::mojom::CredentialsMode::kOmit);
+      ParkableString(source.Impl()), nullptr);
   return ModuleRecord::Compile(isolate, params, ScriptFetchOptions(),
                                TextPosition::MinimumPosition(),
                                exception_state);
diff --git a/third_party/blink/renderer/modules/accessibility/ax_object.cc b/third_party/blink/renderer/modules/accessibility/ax_object.cc
index 4db3271..b4aeb2fd 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_object.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_object.cc
@@ -961,6 +961,9 @@
       }
     }
   }
+
+  if (IsScrollableContainer())
+    SerializeScrollAttributes(node_data);
 }
 
 void AXObject::SerializeTableAttributes(ui::AXNodeData* node_data) {
@@ -1018,6 +1021,31 @@
   }
 }
 
+void AXObject::SerializeScrollAttributes(ui::AXNodeData* node_data) {
+  // Only mark as scrollable if user has actual scrollbars to use.
+  node_data->AddBoolAttribute(ax::mojom::blink::BoolAttribute::kScrollable,
+                              IsUserScrollable());
+  // Provide x,y scroll info if scrollable in any way (programmatically or via
+  // user).
+  const gfx::Point& scroll_offset = GetScrollOffset();
+  node_data->AddIntAttribute(ax::mojom::blink::IntAttribute::kScrollX,
+                             scroll_offset.x());
+  node_data->AddIntAttribute(ax::mojom::blink::IntAttribute::kScrollY,
+                             scroll_offset.y());
+
+  const gfx::Point& min_scroll_offset = MinimumScrollOffset();
+  node_data->AddIntAttribute(ax::mojom::blink::IntAttribute::kScrollXMin,
+                             min_scroll_offset.x());
+  node_data->AddIntAttribute(ax::mojom::blink::IntAttribute::kScrollYMin,
+                             min_scroll_offset.y());
+
+  const gfx::Point& max_scroll_offset = MaximumScrollOffset();
+  node_data->AddIntAttribute(ax::mojom::blink::IntAttribute::kScrollXMax,
+                             max_scroll_offset.x());
+  node_data->AddIntAttribute(ax::mojom::blink::IntAttribute::kScrollYMax,
+                             max_scroll_offset.y());
+}
+
 void AXObject::SerializeStyleAttributes(ui::AXNodeData* node_data) {
   // Text attributes.
   if (BackgroundColor()) {
diff --git a/third_party/blink/renderer/modules/accessibility/ax_object.h b/third_party/blink/renderer/modules/accessibility/ax_object.h
index 685fa3d..926e219 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_object.h
+++ b/third_party/blink/renderer/modules/accessibility/ax_object.h
@@ -1316,6 +1316,7 @@
   void SerializeSparseAttributes(ui::AXNodeData* node_data);
   void SerializeTableAttributes(ui::AXNodeData* node_data);
   void SerializeListAttributes(ui::AXNodeData* node_data);
+  void SerializeScrollAttributes(ui::AXNodeData* node_data);
 
  private:
   void UpdateDistributionForFlatTreeTraversal() const;
diff --git a/third_party/blink/renderer/modules/exported/web_ax_object.cc b/third_party/blink/renderer/modules/exported/web_ax_object.cc
index 817bcd7..0bb1729 100644
--- a/third_party/blink/renderer/modules/exported/web_ax_object.cc
+++ b/third_party/blink/renderer/modules/exported/web_ax_object.cc
@@ -849,13 +849,13 @@
 
 WebString WebAXObject::GetName(ax::mojom::NameFrom& out_name_from,
                                WebVector<WebAXObject>& out_name_objects) const {
+  out_name_from = ax::mojom::blink::NameFrom::kUninitialized;
+
   if (IsDetached())
     return WebString();
 
-  ax::mojom::NameFrom name_from = ax::mojom::NameFrom::kUninitialized;
   HeapVector<Member<AXObject>> name_objects;
-  WebString result = private_->GetName(name_from, &name_objects);
-  out_name_from = name_from;
+  WebString result = private_->GetName(out_name_from, &name_objects);
 
   out_name_objects.reserve(name_objects.size());
   out_name_objects.resize(name_objects.size());
@@ -877,15 +877,14 @@
     ax::mojom::NameFrom name_from,
     ax::mojom::DescriptionFrom& out_description_from,
     WebVector<WebAXObject>& out_description_objects) const {
+  out_description_from = ax::mojom::blink::DescriptionFrom::kUninitialized;
+
   if (IsDetached())
     return WebString();
 
-  ax::mojom::DescriptionFrom description_from =
-      ax::mojom::DescriptionFrom::kUninitialized;
   HeapVector<Member<AXObject>> description_objects;
-  String result =
-      private_->Description(name_from, description_from, &description_objects);
-  out_description_from = description_from;
+  String result = private_->Description(name_from, out_description_from,
+                                        &description_objects);
 
   out_description_objects.reserve(description_objects.size());
   out_description_objects.resize(description_objects.size());
@@ -1238,12 +1237,6 @@
   return private_->IsScrollableContainer();
 }
 
-bool WebAXObject::IsUserScrollable() const {
-  if (IsDetached())
-    return false;
-
-  return private_->IsUserScrollable();
-}
 gfx::Point WebAXObject::GetScrollOffset() const {
   if (IsDetached())
     return gfx::Point();
diff --git a/third_party/blink/renderer/modules/hid/hid_collection_info.idl b/third_party/blink/renderer/modules/hid/hid_collection_info.idl
index 787d63e..1bc289b 100644
--- a/third_party/blink/renderer/modules/hid/hid_collection_info.idl
+++ b/third_party/blink/renderer/modules/hid/hid_collection_info.idl
@@ -12,6 +12,9 @@
     // The 16-bit usage value associated with this collection. Zero if not set.
     unsigned short usage;
 
+    // The 8-bit collection type for this collection.
+    octet type;
+
     // The subcollections of this collection, in the order they were encountered
     // in the report descriptor.
     sequence<HIDCollectionInfo> children;
diff --git a/third_party/blink/renderer/modules/hid/hid_device.cc b/third_party/blink/renderer/modules/hid/hid_device.cc
index 2517808f..ecc7a54 100644
--- a/third_party/blink/renderer/modules/hid/hid_device.cc
+++ b/third_party/blink/renderer/modules/hid/hid_device.cc
@@ -184,6 +184,7 @@
   HIDCollectionInfo* result = HIDCollectionInfo::Create();
   result->setUsage(collection.usage->usage);
   result->setUsagePage(collection.usage->usage_page);
+  result->setType(collection.collection_type);
 
   HeapVector<Member<HIDReportInfo>> input_reports;
   for (const auto& report : collection.input_reports)
@@ -504,8 +505,14 @@
   HIDReportItem* result = HIDReportItem::Create();
   result->setIsAbsolute(!report_item.is_relative);
   result->setIsArray(!report_item.is_variable);
+  result->setIsBufferedBytes(report_item.is_buffered_bytes);
+  result->setIsConstant(report_item.is_constant);
+  result->setIsLinear(!report_item.is_non_linear);
   result->setIsRange(report_item.is_range);
+  result->setIsVolatile(report_item.is_volatile);
   result->setHasNull(report_item.has_null_position);
+  result->setHasPreferredState(!report_item.no_preferred_state);
+  result->setWrap(report_item.wrap);
   result->setReportSize(report_item.report_size);
   result->setReportCount(report_item.report_count);
   result->setUnitExponent(
diff --git a/third_party/blink/renderer/modules/hid/hid_report_item.idl b/third_party/blink/renderer/modules/hid/hid_report_item.idl
index 01ee6d86..078c7e0 100644
--- a/third_party/blink/renderer/modules/hid/hid_report_item.idl
+++ b/third_party/blink/renderer/modules/hid/hid_report_item.idl
@@ -40,13 +40,39 @@
     // all inputs simultaneously.
     boolean isArray;
 
+    // True if the item emits a fixed-size stream of bytes, or false if the item
+    // is a bit field.
+    boolean isBufferedBytes;
+
+    // True if the item is a read-only constant value, or false if the item is a
+    // report field with modifiable device data.
+    boolean isConstant;
+
+    // True if there is a linear relationship between the measured value and the
+    // raw data from the device, or false if the data has been processed and no
+    // longer represents a linear relationship.
+    boolean isLinear;
+
     // True if the usages for this item are defined by |usageMinimum| and
     // |usageMaximum| or false if the usages are defined by |usages|.
     boolean isRange;
 
+    // True if the item is a feature or output field that can change without
+    // host interaction, or false if the field should not change without host
+    // interaction.
+    boolean isVolatile;
+
     // True if the item uses an out-of-bounds value when there is no input.
     boolean hasNull;
 
+    // True if the item has a preferred state to which it will return when the
+    // user is not physically interacting with the control.
+    boolean hasPreferredState;
+
+    // True if the data rolls over when reaching either the extreme high or low
+    // value.
+    boolean wrap;
+
     // An ordered list of 32-bit usage values associated with this item. Unused
     // if |isRange| is true. If |reportCount| is two or more, usages are
     // assigned from the list until the list is exhausted.
diff --git a/third_party/blink/renderer/modules/push_messaging/push_provider.cc b/third_party/blink/renderer/modules/push_messaging/push_provider.cc
index 5e5dc5f..e426cf0 100644
--- a/third_party/blink/renderer/modules/push_messaging/push_provider.cc
+++ b/third_party/blink/renderer/modules/push_messaging/push_provider.cc
@@ -6,9 +6,11 @@
 
 #include <utility>
 
+#include "third_party/blink/public/common/browser_interface_broker_proxy.h"
 #include "third_party/blink/public/common/thread_safe_browser_interface_broker_proxy.h"
 #include "third_party/blink/public/mojom/push_messaging/push_messaging_status.mojom-blink.h"
 #include "third_party/blink/public/platform/platform.h"
+#include "third_party/blink/renderer/core/frame/local_dom_window.h"
 #include "third_party/blink/renderer/modules/push_messaging/push_error.h"
 #include "third_party/blink/renderer/modules/push_messaging/push_messaging_utils.h"
 #include "third_party/blink/renderer/modules/push_messaging/push_subscription.h"
@@ -45,8 +47,10 @@
 // static
 mojom::blink::PushMessaging* PushProvider::GetPushMessagingRemote() {
   if (!push_messaging_manager_.is_bound()) {
-    Platform::Current()->GetBrowserInterfaceBroker()->GetInterface(
-        push_messaging_manager_.BindNewPipeAndPassReceiver(
+    GetSupplementable()
+        ->GetExecutionContext()
+        ->GetBrowserInterfaceBroker()
+        .GetInterface(push_messaging_manager_.BindNewPipeAndPassReceiver(
             GetSupplementable()->GetExecutionContext()->GetTaskRunner(
                 TaskType::kMiscPlatformAPI)));
   }
diff --git a/third_party/blink/renderer/platform/animation/compositor_keyframe_model.cc b/third_party/blink/renderer/platform/animation/compositor_keyframe_model.cc
index 0a0c8a3..bf647430 100644
--- a/third_party/blink/renderer/platform/animation/compositor_keyframe_model.cc
+++ b/third_party/blink/renderer/platform/animation/compositor_keyframe_model.cc
@@ -25,18 +25,42 @@
     const CompositorAnimationCurve& curve,
     compositor_target_property::Type target_property,
     int keyframe_model_id,
-    int group_id,
-    const AtomicString& custom_property_name,
-    CompositorPaintWorkletInput::NativePropertyType native_property_type) {
-  if (!keyframe_model_id)
-    keyframe_model_id = AnimationIdProvider::NextKeyframeModelId();
-  if (!group_id)
-    group_id = AnimationIdProvider::NextGroupId();
+    int group_id)
+    : CompositorKeyframeModel(
+          curve,
+          keyframe_model_id,
+          group_id,
+          KeyframeModel::TargetPropertyId(target_property)) {}
 
-  keyframe_model_ = KeyframeModel::Create(
-      curve.CloneToAnimationCurve(), keyframe_model_id, group_id,
-      target_property, custom_property_name.Utf8().data(),
-      native_property_type);
+CompositorKeyframeModel::CompositorKeyframeModel(
+    const CompositorAnimationCurve& curve,
+    compositor_target_property::Type target_property,
+    int keyframe_model_id,
+    int group_id,
+    const AtomicString& custom_property_name)
+    : CompositorKeyframeModel(
+          curve,
+          keyframe_model_id,
+          group_id,
+          KeyframeModel::TargetPropertyId(target_property,
+                                          custom_property_name.Utf8().data())) {
+  DCHECK(!custom_property_name.IsEmpty());
+}
+
+CompositorKeyframeModel::CompositorKeyframeModel(
+    const CompositorAnimationCurve& curve,
+    compositor_target_property::Type target_property,
+    int keyframe_model_id,
+    int group_id,
+    CompositorPaintWorkletInput::NativePropertyType native_property_type)
+    : CompositorKeyframeModel(
+          curve,
+          keyframe_model_id,
+          group_id,
+          KeyframeModel::TargetPropertyId(target_property,
+                                          native_property_type)) {
+  DCHECK_NE(native_property_type,
+            CompositorPaintWorkletInput::NativePropertyType::kInvalid);
 }
 
 CompositorKeyframeModel::~CompositorKeyframeModel() = default;
@@ -49,10 +73,23 @@
   return keyframe_model_->group();
 }
 
+CompositorKeyframeModel::CompositorKeyframeModel(
+    const CompositorAnimationCurve& curve,
+    int keyframe_model_id,
+    int group_id,
+    const KeyframeModel::TargetPropertyId& id) {
+  if (!keyframe_model_id)
+    keyframe_model_id = AnimationIdProvider::NextKeyframeModelId();
+  if (!group_id)
+    group_id = AnimationIdProvider::NextGroupId();
+  keyframe_model_ = KeyframeModel::Create(curve.CloneToAnimationCurve(),
+                                          keyframe_model_id, group_id, id);
+}
+
 compositor_target_property::Type CompositorKeyframeModel::TargetProperty()
     const {
   return static_cast<compositor_target_property::Type>(
-      keyframe_model_->target_property_id());
+      keyframe_model_->target_property_type());
 }
 
 void CompositorKeyframeModel::SetElementId(CompositorElementId element_id) {
diff --git a/third_party/blink/renderer/platform/animation/compositor_keyframe_model.h b/third_party/blink/renderer/platform/animation/compositor_keyframe_model.h
index d567310..4124964 100644
--- a/third_party/blink/renderer/platform/animation/compositor_keyframe_model.h
+++ b/third_party/blink/renderer/platform/animation/compositor_keyframe_model.h
@@ -35,17 +35,22 @@
   using Direction = cc::KeyframeModel::Direction;
   using FillMode = cc::KeyframeModel::FillMode;
 
-  // The |custom_property_name| has a default value of an empty string,
-  // indicating that the animated property is a native property. When it is an
-  // animated custom property, it should be the property name.
+  CompositorKeyframeModel(const CompositorAnimationCurve&,
+                          compositor_target_property::Type,
+                          int keyframe_model_id,
+                          int group_id);
+  // The |custom_property_name| is the name of animated custom property.
+  CompositorKeyframeModel(const CompositorAnimationCurve&,
+                          compositor_target_property::Type,
+                          int keyframe_model_id,
+                          int group_id,
+                          const AtomicString& custom_property_name);
   CompositorKeyframeModel(
       const CompositorAnimationCurve&,
       compositor_target_property::Type,
       int keyframe_model_id,
       int group_id,
-      const AtomicString& custom_property_name = "",
-      CompositorPaintWorkletInput::NativePropertyType native_property_type =
-          CompositorPaintWorkletInput::NativePropertyType::kInvalid);
+      CompositorPaintWorkletInput::NativePropertyType native_property_type);
   ~CompositorKeyframeModel();
 
   // An id must be unique.
@@ -91,6 +96,11 @@
   }
 
  private:
+  CompositorKeyframeModel(const CompositorAnimationCurve& curve,
+                          int keyframe_model_id,
+                          int group_id,
+                          const cc::KeyframeModel::TargetPropertyId& id);
+
   std::unique_ptr<cc::KeyframeModel> keyframe_model_;
 
   DISALLOW_COPY_AND_ASSIGN(CompositorKeyframeModel);
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index bb3298b..9c35cb5 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -1831,8 +1831,7 @@
     },
     {
       name: "Serial",
-      origin_trial_feature_name: "Serial",
-      status: {"Android": "", "default": "experimental"},
+      status: {"Android": "", "default": "stable"},
     },
     {
       name: "ServiceWorkerClientLifecycleState",
diff --git a/third_party/blink/renderer/platform/webrtc/peer_connection_remote_audio_source.cc b/third_party/blink/renderer/platform/webrtc/peer_connection_remote_audio_source.cc
index 285de6bf..b968981 100644
--- a/third_party/blink/renderer/platform/webrtc/peer_connection_remote_audio_source.cc
+++ b/third_party/blink/renderer/platform/webrtc/peer_connection_remote_audio_source.cc
@@ -146,8 +146,11 @@
     audio_bus_ = media::AudioBus::Create(number_of_channels, number_of_frames);
   }
 
-  audio_bus_->FromInterleaved(audio_data, number_of_frames,
-                              bits_per_sample / 8);
+  // Only 16 bits per sample is ever used. The FromInterleaved() call should
+  // be updated if that is no longer the case.
+  DCHECK_EQ(bits_per_sample, 16);
+  audio_bus_->FromInterleaved<media::SignedInt16SampleTypeTraits>(
+      reinterpret_cast<const int16_t*>(audio_data), number_of_frames);
 
   media::AudioParameters params = MediaStreamAudioSource::GetAudioParameters();
   if (!params.IsValid() ||
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 9616d463..0865e97 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -2473,6 +2473,8 @@
 crbug.com/958381 [ Mac ] external/wpt/css/CSS2/tables/table-anonymous-objects-206.xht [ Failure ]
 
 # ====== New tests from wpt-importer added here ======
+crbug.com/626703 external/wpt/css/css-sizing/aspect-ratio/flex-aspect-ratio-025.html [ Failure ]
+crbug.com/626703 external/wpt/css/css-sizing/aspect-ratio/flex-aspect-ratio-026.html [ Failure ]
 crbug.com/626703 external/wpt/html/interaction/focus/document-level-focus-apis/document-has-system-focus.html [ Timeout ]
 crbug.com/626703 [ Mac11.0 ] virtual/threaded/external/wpt/animation-worklet/worklet-animation-set-keyframes.https.html [ Failure ]
 crbug.com/626703 external/wpt/websockets/cookies/004.html [ Timeout ]
diff --git a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
index 289a084..0dd813e2 100644
--- a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
+++ b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
@@ -10099,6 +10099,13 @@
       null,
       {}
      ]
+    ],
+    "nfc-prompt-manual.https.html": [
+     "a8b0ce636c9b5de5d045713b3d6566b0f00b2c98",
+     [
+      null,
+      {}
+     ]
     ]
    },
    "web-share": {
@@ -75099,7 +75106,7 @@
         {}
        ]
       ],
-      "replaced-alignment-with-aspect-ratio-001.tentative.html": [
+      "replaced-alignment-with-aspect-ratio-001.html": [
        "d7ab97b9ec0091b7869b1fbb69e7317ced70cc20",
        [
         null,
@@ -75112,7 +75119,7 @@
         {}
        ]
       ],
-      "replaced-alignment-with-aspect-ratio-002.tentative.html": [
+      "replaced-alignment-with-aspect-ratio-002.html": [
        "1a4e344dd045806ed512d9e3562d9f3b96d1fbd8",
        [
         null,
@@ -101865,6 +101872,32 @@
         {}
        ]
       ],
+      "flex-aspect-ratio-025.html": [
+       "fa9ce8ebce69f02e6b13d9d2233d1e3b46c7f1ab",
+       [
+        null,
+        [
+         [
+          "/css/reference/ref-filled-green-200px-square.html",
+          "=="
+         ]
+        ],
+        {}
+       ]
+      ],
+      "flex-aspect-ratio-026.html": [
+       "1df14468632baa8ffd03d690f7ddaa618acd7bf6",
+       [
+        null,
+        [
+         [
+          "/css/reference/ref-filled-green-200px-square.html",
+          "=="
+         ]
+        ],
+        {}
+       ]
+      ],
       "grid-aspect-ratio-001.html": [
        "ce91fe4e4e8b2bb7f610fb230a292f59188ff95b",
        [
@@ -174217,7 +174250,7 @@
      []
     ],
     "sab.js": [
-     "c7fd1a742e64f66744b416584952effe29fda208",
+     "d40dd7cca8da10ad321067e638c772550f1a042f",
      []
     ],
     "security-features": {
@@ -196023,7 +196056,7 @@
      },
      "support": {
       "grid-child-utils.js": [
-       "d75ad6eb7061349e5453c204235a172778528f6a",
+       "5e0c5caad12a879e68637b7ba058e44a89eafa01",
        []
       ]
      },
@@ -211923,6 +211956,10 @@
       "74e580747083b3e9ff8d48876c08b83ddaa8d5cb",
       []
      ],
+     "serialize-custom-props-expected.txt": [
+      "5dbde36cf866c4473fb005971edb4dfca1829cbc",
+      []
+     ],
      "serialize-values-expected.txt": [
       "f9c57d1a9fa21eca0068acc357132ff272b61c92",
       []
@@ -215152,7 +215189,7 @@
     ],
     "admin": {
      "index.md": [
-      "3073b9678fe890efee20ac122e73d30f0ce7fbb1",
+      "59569fd464c46c2010a48c8c62d8bfdd88b021c1",
       []
      ],
      "pywebsocket3.rst": [
@@ -220431,7 +220468,7 @@
      []
     ],
     "Ahem.ttf.headers": [
-     "659e3760fba9ee28df2a8bebb8bc27905b2ae0a3",
+     "6a0df8b1f530b45f34b3356599b057406d1eaecb",
      []
     ],
     "CSSTest": {
@@ -235475,7 +235512,7 @@
      ],
      "tools": {
       "format_json.py": [
-       "1059ac793aaf2fb5ee875ed9d7e8a02c0040be9e",
+       "6386d418de1199df47a2f97376b7dbb38f4bbb28",
        []
       ]
      }
@@ -237070,7 +237107,7 @@
     ]
    },
    "lint.ignore": [
-    "b8f22011a155c1003a8bbe177e6ad0a1cd186b19",
+    "049d4f81235cbe0538f41e7066c9cd98405b6a68",
     []
    ],
    "loading": {
@@ -241454,6 +241491,14 @@
      "d7b1f473416baf9f060e2d3d843f7943df0f25e0",
      []
     ],
+    "avoid-prefetching-on-text-plain-inner.html": [
+     "518e2465418ad6aaecc5a6fb3957eeef38259240",
+     []
+    ],
+    "avoid-prefetching-on-text-plain-inner.html.headers": [
+     "a1f9e38d9090c74e488c9dbce3bc339e243e3e60",
+     []
+    ],
     "download-resources-expected.txt": [
      "9721540846bbec4bdc596864ed86643a354c72e8",
      []
@@ -244286,7 +244331,7 @@
     ],
     "textcontrols": {
      "selectionchange.tentative-expected.txt": [
-      "444eafbe213b66faf8362f362e3605d33ebe1652",
+      "75062363bf08b2aa1788c074ed620d30692eddc1",
       []
      ]
     }
@@ -248712,6 +248757,10 @@
       ]
      },
      "scripted": {
+      "SVGAnimatedEnumeration-SVGFECompositeElement-expected.txt": [
+       "48365c539fffe9049f04e809ddb52a32a629703a",
+       []
+      ],
       "SVGAnimatedRect-expected.txt": [
        "cdc41ca795298f0cac130afa8ea317a1403ab290",
        []
@@ -296267,28 +296316,28 @@
        ]
       ],
       "grid-alignment-implies-size-change-011.html": [
-       "b53c32d5e8b5169edbc41cd7d9069fe1cc1a70bd",
+       "c06918538ddd961eac6c7e46a8d5ce11aec2d529",
        [
         null,
         {}
        ]
       ],
       "grid-alignment-implies-size-change-012.html": [
-       "4151e7b060e3b0baaa7c069fbe2d0e27b46e7b52",
+       "1fe7d2696fdf749e9fc467268f96119b695016e9",
        [
         null,
         {}
        ]
       ],
       "grid-alignment-implies-size-change-013.html": [
-       "e87481f530c96d04e08080e6acad6beb157e2b7c",
+       "266e2124fbf5b2f4d31ec72b93a6b5727106292a",
        [
         null,
         {}
        ]
       ],
       "grid-alignment-implies-size-change-014.html": [
-       "b74f09d138e97c71d5d4de608cd37a447ab573ed",
+       "23f79cba32479a5a519f452a0118d1cc28cc70ab",
        [
         null,
         {}
@@ -296309,14 +296358,14 @@
        ]
       ],
       "grid-alignment-implies-size-change-017.html": [
-       "b9b9114879c640eb6450f90d5f1521e1739e7bc9",
+       "2f45db5eaee7bd342d6312881f7987081d950083",
        [
         null,
         {}
        ]
       ],
       "grid-alignment-implies-size-change-018.html": [
-       "e9bfe63e62013abd79d085ed77a00583d5500281",
+       "2aa06ab2e9450da32948774fb796f5eb0f5c3231",
        [
         null,
         {}
@@ -296393,21 +296442,21 @@
        ]
       ],
       "grid-alignment-implies-size-change-029.html": [
-       "afe909f81f7dbc41c5d91ec57c095b5b086361a6",
+       "6d41acfb2a9ed4d08101a23bcfdcfa0599650343",
        [
         null,
         {}
        ]
       ],
       "grid-alignment-implies-size-change-030.html": [
-       "d3924e326567655a17715e3921e344a887a7ded9",
+       "559f04a11ead33261202ddd3d896b68e2175dbf8",
        [
         null,
         {}
        ]
       ],
       "grid-alignment-implies-size-change-031.html": [
-       "528e08b881c23bf6ab9e8813c5c0eb0fa96baf8a",
+       "843e1b2acd9759da5186f034c873c413bd048351",
        [
         null,
         {}
@@ -296435,14 +296484,14 @@
        ]
       ],
       "grid-alignment-implies-size-change-035.html": [
-       "18bb1bd061c7e29858dde266705e0649633b4217",
+       "b4b0725d8d67015222bcb260e15d581dbf35181c",
        [
         null,
         {}
        ]
       ],
       "grid-alignment-implies-size-change-036.html": [
-       "e63ce4fccdf45207b6cb11d1fd27e01b2cd62bee",
+       "99396bf17321d5b66036485465e41f9628720a83",
        [
         null,
         {}
@@ -311606,6 +311655,13 @@
        {}
       ]
      ],
+     "calc-rounds-to-integer.html": [
+      "80589785c36e14d0859b3f4a5555f7e9d47ac963",
+      [
+       null,
+       {}
+      ]
+     ],
      "calc-serialization-002.html": [
       "a06b33e492e323386c40c333b22bd344e281fd98",
       [
@@ -313463,6 +313519,13 @@
        {}
       ]
      ],
+     "serialize-custom-props.html": [
+      "cfe96ff0aacb983e66b5ccee305cc5fe84d10d72",
+      [
+       null,
+       {}
+      ]
+     ],
      "serialize-media-rule.html": [
       "90561fdf70971874adc8db08144a421d03392608",
       [
@@ -314055,6 +314118,15 @@
        {}
       ]
      ],
+     "mouseEvent-offsetXY-svg.html": [
+      "c0bd16448529b63424813b93022cfb215396cadd",
+      [
+       null,
+       {
+        "testdriver": true
+       }
+      ]
+     ],
      "mouseEvent.html": [
       "d50959729239599ff057a71143553b4a53f88975",
       [
@@ -315336,14 +315408,14 @@
       ]
      ],
      "focus-in-focus-event-001.html": [
-      "bd4145a3fe5390c55a4e7e9eb29bbcfa922bdf1e",
+      "0639b7785eb3c3eacb4e4478557caf80a3f7511e",
       [
        null,
        {}
       ]
      ],
      "focus-visible-001.html": [
-      "068368995794c0559d3551ca3dc3471f7d777240",
+      "d580b0771f85e7536abb2bce4e539b77e4ddeed6",
       [
        null,
        {
@@ -315352,7 +315424,7 @@
       ]
      ],
      "focus-visible-002.html": [
-      "2846ae465a5665ca1d33db3f4c0ec14bf620a47e",
+      "441c1dae9883d73f01093f56870a965f3d1a8aa4",
       [
        null,
        {
@@ -315362,7 +315434,7 @@
       ]
      ],
      "focus-visible-003.html": [
-      "9d57225708a89bfae2c1549144d2284f7f138ba6",
+      "aa73b4da44f0e831dbbd5516bd71daef245e7e65",
       [
        null,
        {
@@ -315371,7 +315443,7 @@
       ]
      ],
      "focus-visible-004.html": [
-      "318d1eeb05430bd06fb166b8ffc13fa0b67c0c5c",
+      "b30b9e2938ab4ed4e3972340953649c21e0085d9",
       [
        null,
        {
@@ -315380,7 +315452,7 @@
       ]
      ],
      "focus-visible-005.html": [
-      "4aec863e3b9af3d5ba568de34a320bf92dda2adf",
+      "c86c0574153243edd5f3c5a4f9ee24cb73e61374",
       [
        null,
        {
@@ -315389,7 +315461,7 @@
       ]
      ],
      "focus-visible-006.html": [
-      "89d9782ac8eb439154d5f65f95f622d2c25acf3f",
+      "a5256c107a7fd967cde60d2f67b2154a5c8145cf",
       [
        null,
        {
@@ -315407,7 +315479,7 @@
       ]
      ],
      "focus-visible-008.html": [
-      "c84985fe1015ee756459ad23967810988b38fedb",
+      "5f5b54e1f0f1f5deaf2bceac7b0cd41d50672f61",
       [
        null,
        {
@@ -315416,21 +315488,21 @@
       ]
      ],
      "focus-visible-009.html": [
-      "bbadd578e004e5b84203979c3a6c07a820bd7564",
+      "d9edb69c574aa1c7912ab66397d4bf1ab1590a37",
       [
        null,
        {}
       ]
      ],
      "focus-visible-010.html": [
-      "cfd4282e9b599b0caa62cbbfa15ea34d88217a29",
+      "8d02edbc3e6bd5ec257c652a4bf1fe07149ba699",
       [
        null,
        {}
       ]
      ],
      "focus-visible-011.html": [
-      "3d66dc91386ecc1fb7c7cc166150550c5a65275d",
+      "d45f5d8ed0f3903da5b5c37ea5a13ccbe690ceca",
       [
        null,
        {
@@ -315439,7 +315511,7 @@
       ]
      ],
      "focus-visible-012.html": [
-      "dcb00e18d2535a6ab826f089d1034185de6be446",
+      "b27abbe39c01b202196f5e25159da26cb5089f8e",
       [
        null,
        {
@@ -315448,14 +315520,14 @@
       ]
      ],
      "focus-visible-014.html": [
-      "8e52570e79b163cd91f9aca1e0d4ee545fa5c51d",
+      "6a4aef6e2bdf3c277b52ddb50679bc240b4cf744",
       [
        null,
        {}
       ]
      ],
      "focus-visible-015.html": [
-      "21568f3b0639a8df388eabffbea045ecf570cb8a",
+      "685baeb7bad07ad45a8efda163dba5db94b9ffe8",
       [
        null,
        {
@@ -315464,7 +315536,7 @@
       ]
      ],
      "focus-visible-016.html": [
-      "64d9bb777fb547f42639f14f51b92248ff606109",
+      "a65e5a5b3dd6aa2be7aa733d01725242e8b01cc4",
       [
        null,
        {
@@ -316382,7 +316454,7 @@
     ],
     "parser": {
      "parser-constructs-custom-element-in-document-write.html": [
-      "3b5256677d0d08f2dac9240df06abc334f577f7c",
+      "14c830b9ba961e701ea39656f42a73cd638e65df",
       [
        null,
        {}
@@ -369471,6 +369543,15 @@
         ]
        ]
       },
+      "embedded-content": {
+       "audio-controls-intrinsic-size.html": [
+        "6cbbcd02f5a0215801d960a319924bd86c90c70a",
+        [
+         null,
+         {}
+        ]
+       ]
+      },
       "svg-embedded-sizing": {
        "svg-in-img-auto.html": [
         "cef353067610e3ef5255163c395d2433b466ea37",
@@ -385473,6 +385554,13 @@
       {}
      ]
     ],
+    "same-document-with-document-root.html": [
+     "15cb7c4cbc278079b15da8af6bfa11a2d6ad3ddc",
+     [
+      null,
+      {}
+     ]
+    ],
     "same-document-zero-size-target.html": [
      "20bd11d4beb1e8bdd623eaad96f11788747f0d15",
      [
@@ -399060,6 +399148,13 @@
       {}
      ]
     ],
+    "avoid-prefetching-on-text-plain.html": [
+     "487cbbcaba75285ae6dd381fefe39b061ae6464f",
+     [
+      null,
+      {}
+     ]
+    ],
     "delaying-onload-link-preload-after-discovery.html": [
      "1c856d16d409479746f4c18c65028c38a026fbba",
      [
@@ -411723,6 +411818,20 @@
       {}
      ]
     ],
+    "fetch-cross-origin-redirect.https.html": [
+     "e11f21a45755b3fa6da42025d1730d23e8fc4bfb",
+     [
+      null,
+      {}
+     ]
+    ],
+    "font-timestamps.html": [
+     "56ecb5c4bfddb60e783ee77cd308b28a0c4ea9c2",
+     [
+      null,
+      {}
+     ]
+    ],
     "idlharness.any.js": [
      "aa860d3dd16a712fb3e81b5393c5ca2f3dccfde7",
      [
@@ -411767,7 +411876,7 @@
      ]
     ],
     "iframe-failed-commit.html": [
-     "bd51d33c80f2f5d0f9ab2d1fc644302747c2979a",
+     "56fe1d594f9479e3be31292147876741aebc7fa3",
      [
       null,
       {}
@@ -412066,7 +412175,7 @@
      ]
     ],
     "resource_timing_cross_origin_redirect.html": [
-     "9342f5b88389d2bf0f831dcf669192f5644024d5",
+     "60a7ef1564b788ab529b8d1f5e35429d82f76911",
      [
       null,
       {}
@@ -412096,7 +412205,7 @@
      ]
     ],
     "status-codes-create-entry.html": [
-     "c31505a452da6b6af146febcd15ca62ff186a767",
+     "cc0cd8ccb88250331186c38554b09ff8515cacee",
      [
       null,
       {}
@@ -413525,7 +413634,7 @@
     ],
     "textcontrols": {
      "selectionchange.tentative.html": [
-      "2216a4b514f9932118c2f8b645b4e4a1656117f2",
+      "adec8b31f3e096eb28ae0246164d637da10bd5ee",
       [
        null,
        {}
@@ -426314,7 +426423,7 @@
        ]
       ],
       "SVGAnimatedEnumeration-SVGFECompositeElement.html": [
-       "e6315b434504e754872dd460988187957efbfb07",
+       "0e99f5fe9cb3335f54591d75e75bffdc0262d0d2",
        [
         null,
         {}
@@ -450634,7 +450743,7 @@
      ]
     ],
     "event-timeout.any.js": [
-     "da8ca11bb8ea1a8ef673be20ae2de8715a4d1130",
+     "c73cd1a48540511a5f527b49668aac18e5330169",
      [
       "xhr/event-timeout.any.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 c7fd1a7..d40dd7c 100644
--- a/third_party/blink/web_tests/external/wpt/common/sab.js
+++ b/third_party/blink/web_tests/external/wpt/common/sab.js
@@ -5,6 +5,9 @@
     if (type === "ArrayBuffer") {
       return new ArrayBuffer(length);
     } else if (type === "SharedArrayBuffer") {
+      if (sabConstructor.name !== "SharedArrayBuffer") {
+        throw new Error("WebAssembly.Memory does not support shared:true");
+      }
       return new sabConstructor(length);
     } else {
       throw new Error("type has to be ArrayBuffer or SharedArrayBuffer");
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/name-tests.html b/third_party/blink/web_tests/external/wpt/cookies/http-state/name-tests.html
deleted file mode 100644
index e962539..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/name-tests.html
+++ /dev/null
@@ -1,63 +0,0 @@
-<!doctype html>
-<html>
-  <head>
-    <meta charset=utf-8>
-    <title>Tests basic cookie setting functionality</title>
-    <meta name=help href="https://tools.ietf.org/html/rfc6265#page-8">
-    <meta name="timeout" content="long">
-
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <script src="resources/cookie-http-state-template.js"></script>
-  </head>
-  <body>
-    <div id="log"></div>
-    <div id="iframes"></div>
-    <script>
-      setup({ explicit_timeout: true });
-
-      const TEST_CASES = [
-        {file: "name0001", name: "name0001"},
-        {file: "name0002", name: "name0002"},
-        {file: "name0003", name: "name0003"},
-        {file: "name0004", name: "name0004"},
-        {file: "name0005", name: "name0005"},
-        {file: "name0006", name: "name0006"},
-        {file: "name0007", name: "name0007"},
-        {file: "name0008", name: "name0008"},
-        {file: "name0009", name: "name0009"},
-        {file: "name0010", name: "name0010"},
-        {file: "name0011", name: "name0011"},
-        {file: "name0012", name: "name0012"},
-        {file: "name0013", name: "name0013"},
-        {file: "name0014", name: "name0014"},
-        {file: "name0015", name: "name0015"},
-        {file: "name0016", name: "name0016"},
-        {file: "name0017", name: "name0017"},
-        {file: "name0018", name: "name0018"},
-        {file: "name0019", name: "name0019"},
-        {file: "name0020", name: "name0020"},
-        {file: "name0021", name: "name0021"},
-        {file: "name0022", name: "name0022"},
-        {file: "name0023", name: "name0023"},
-        {file: "name0024", name: "name0024"},
-        {file: "name0025", name: "name0025"},
-        {file: "name0026", name: "name0026"},
-        {file: "name0027", name: "name0027"},
-        {file: "name0028", name: "name0028"},
-        {file: "name0029", name: "name0029"},
-        {file: "name0030", name: "name0030"},
-        {file: "name0031", name: "name0031"},
-        {file: "name0032", name: "name0032"},
-        {file: "name0033", name: "name0033"},
-      ];
-
-      for (const i in TEST_CASES) {
-        const t = TEST_CASES[i];
-        promise_test(createCookieTest(t.file),
-                     t.file + " - " + t.name);
-      }
-
-    </script>
-  </body>
-</html>
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0001-expected b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0001-expected
deleted file mode 100644
index 9652792..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0001-expected
+++ /dev/null
@@ -1 +0,0 @@
-Cookie: a=bar
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0001-test b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0001-test
deleted file mode 100644
index 3ce5f5f..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0001-test
+++ /dev/null
@@ -1 +0,0 @@
-Set-Cookie: a=bar
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0002-expected b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0002-expected
deleted file mode 100644
index d4d3cda8..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0002-expected
+++ /dev/null
@@ -1 +0,0 @@
-Cookie: 1=bar
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0002-test b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0002-test
deleted file mode 100644
index d6eac8a..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0002-test
+++ /dev/null
@@ -1 +0,0 @@
-Set-Cookie: 1=bar
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0003-expected b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0003-expected
deleted file mode 100644
index 0c00f45..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0003-expected
+++ /dev/null
@@ -1 +0,0 @@
-Cookie: $=bar
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0003-test b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0003-test
deleted file mode 100644
index 7ea9615..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0003-test
+++ /dev/null
@@ -1 +0,0 @@
-Set-Cookie: $=bar
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0004-expected b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0004-expected
deleted file mode 100644
index b079529..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0004-expected
+++ /dev/null
@@ -1 +0,0 @@
-Cookie: !a=bar
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0004-test b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0004-test
deleted file mode 100644
index 99f0e61..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0004-test
+++ /dev/null
@@ -1 +0,0 @@
-Set-Cookie: !a=bar
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0005-expected b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0005-expected
deleted file mode 100644
index a0f031b..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0005-expected
+++ /dev/null
@@ -1 +0,0 @@
-Cookie: @a=bar
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0005-test b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0005-test
deleted file mode 100644
index 9e33e0c..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0005-test
+++ /dev/null
@@ -1 +0,0 @@
-Set-Cookie: @a=bar
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0006-expected b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0006-expected
deleted file mode 100644
index ee0e7d7c..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0006-expected
+++ /dev/null
@@ -1 +0,0 @@
-Cookie: #a=bar
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0006-test b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0006-test
deleted file mode 100644
index fbd03632..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0006-test
+++ /dev/null
@@ -1 +0,0 @@
-Set-Cookie: #a=bar
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0007-expected b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0007-expected
deleted file mode 100644
index 6d6e56e..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0007-expected
+++ /dev/null
@@ -1 +0,0 @@
-Cookie: $a=bar
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0007-test b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0007-test
deleted file mode 100644
index d41e64b..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0007-test
+++ /dev/null
@@ -1 +0,0 @@
-Set-Cookie: $a=bar
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0008-expected b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0008-expected
deleted file mode 100644
index a4b8c242..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0008-expected
+++ /dev/null
@@ -1 +0,0 @@
-Cookie: %a=bar
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0008-test b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0008-test
deleted file mode 100644
index 7afcf70..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0008-test
+++ /dev/null
@@ -1 +0,0 @@
-Set-Cookie: %a=bar
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0009-expected b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0009-expected
deleted file mode 100644
index 49506ac..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0009-expected
+++ /dev/null
@@ -1 +0,0 @@
-Cookie: ^a=bar
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0009-test b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0009-test
deleted file mode 100644
index f40d2c4..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0009-test
+++ /dev/null
@@ -1 +0,0 @@
-Set-Cookie: ^a=bar
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0010-expected b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0010-expected
deleted file mode 100644
index 1e725578..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0010-expected
+++ /dev/null
@@ -1 +0,0 @@
-Cookie: &a=bar
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0010-test b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0010-test
deleted file mode 100644
index fb4fd92..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0010-test
+++ /dev/null
@@ -1 +0,0 @@
-Set-Cookie: &a=bar
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0011-expected b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0011-expected
deleted file mode 100644
index 260d702..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0011-expected
+++ /dev/null
@@ -1 +0,0 @@
-Cookie: *a=bar
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0011-test b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0011-test
deleted file mode 100644
index b36b723..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0011-test
+++ /dev/null
@@ -1 +0,0 @@
-Set-Cookie: *a=bar
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0012-expected b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0012-expected
deleted file mode 100644
index 0a2686a..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0012-expected
+++ /dev/null
@@ -1 +0,0 @@
-Cookie: (a=bar
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0012-test b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0012-test
deleted file mode 100644
index 6927aac6..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0012-test
+++ /dev/null
@@ -1 +0,0 @@
-Set-Cookie: (a=bar
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0013-expected b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0013-expected
deleted file mode 100644
index 87dec78b..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0013-expected
+++ /dev/null
@@ -1 +0,0 @@
-Cookie: )a=bar
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0013-test b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0013-test
deleted file mode 100644
index 59ada98..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0013-test
+++ /dev/null
@@ -1 +0,0 @@
-Set-Cookie: )a=bar
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0014-expected b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0014-expected
deleted file mode 100644
index 82bfe0e..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0014-expected
+++ /dev/null
@@ -1 +0,0 @@
-Cookie: -a=bar
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0014-test b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0014-test
deleted file mode 100644
index a113e99..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0014-test
+++ /dev/null
@@ -1 +0,0 @@
-Set-Cookie: -a=bar
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0015-expected b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0015-expected
deleted file mode 100644
index 390b77b..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0015-expected
+++ /dev/null
@@ -1 +0,0 @@
-Cookie: _a=bar
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0015-test b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0015-test
deleted file mode 100644
index 60fc074..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0015-test
+++ /dev/null
@@ -1 +0,0 @@
-Set-Cookie: _a=bar
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0016-expected b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0016-expected
deleted file mode 100644
index 7d4d9e33..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0016-expected
+++ /dev/null
@@ -1 +0,0 @@
-Cookie: +=bar
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0016-test b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0016-test
deleted file mode 100644
index 371dbcd..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0016-test
+++ /dev/null
@@ -1 +0,0 @@
-Set-Cookie: +=bar
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0017-expected b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0017-expected
deleted file mode 100644
index 9652792..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0017-expected
+++ /dev/null
@@ -1 +0,0 @@
-Cookie: a=bar
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0017-test b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0017-test
deleted file mode 100644
index 0561431..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0017-test
+++ /dev/null
@@ -1 +0,0 @@
-Set-Cookie: =a=bar
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0018-expected b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0018-expected
deleted file mode 100644
index 9652792..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0018-expected
+++ /dev/null
@@ -1 +0,0 @@
-Cookie: a=bar
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0018-test b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0018-test
deleted file mode 100644
index e86a4836..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0018-test
+++ /dev/null
@@ -1 +0,0 @@
-Set-Cookie: a =bar
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0019-expected b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0019-expected
deleted file mode 100644
index 8d0bc2d..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0019-expected
+++ /dev/null
@@ -1 +0,0 @@
-Cookie: "a=bar
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0019-test b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0019-test
deleted file mode 100644
index d48e3f6..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0019-test
+++ /dev/null
@@ -1 +0,0 @@
-Set-Cookie: "a=bar
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0020-expected b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0020-expected
deleted file mode 100644
index aa9cd6d..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0020-expected
+++ /dev/null
@@ -1 +0,0 @@
-Cookie: "a=b"=bar
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0020-test b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0020-test
deleted file mode 100644
index b84f64d17..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0020-test
+++ /dev/null
@@ -1 +0,0 @@
-Set-Cookie: "a=b"=bar
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0021-expected b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0021-expected
deleted file mode 100644
index 206ff76..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0021-expected
+++ /dev/null
@@ -1 +0,0 @@
-Cookie: "a=qux
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0021-test b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0021-test
deleted file mode 100644
index 56b319e..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0021-test
+++ /dev/null
@@ -1,2 +0,0 @@
-Set-Cookie: "a=b"=bar
-Set-Cookie: "a=qux
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0022-expected b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0022-expected
deleted file mode 100644
index b14d4f69..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0022-expected
+++ /dev/null
@@ -1 +0,0 @@
-Cookie: foo=bar
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0022-test b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0022-test
deleted file mode 100644
index cc59ff1..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0022-test
+++ /dev/null
@@ -1 +0,0 @@
-Set-Cookie:    foo=bar
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0023-expected b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0023-expected
deleted file mode 100644
index 5ab27668..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0023-expected
+++ /dev/null
@@ -1 +0,0 @@
-Cookie: foo
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0023-test b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0023-test
deleted file mode 100644
index b7f9cc2..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0023-test
+++ /dev/null
@@ -1 +0,0 @@
-Set-Cookie: foo;bar=baz
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0024-expected b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0024-expected
deleted file mode 100644
index 5ac4f25..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0024-expected
+++ /dev/null
@@ -1 +0,0 @@
-Cookie: $Version=1
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0024-test b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0024-test
deleted file mode 100644
index da7b696..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0024-test
+++ /dev/null
@@ -1 +0,0 @@
-Set-Cookie: $Version=1; foo=bar
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0025-expected b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0025-expected
deleted file mode 100644
index 68c38595..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0025-expected
+++ /dev/null
@@ -1 +0,0 @@
-Cookie: ==a=bar
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0025-test b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0025-test
deleted file mode 100644
index 708f006..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0025-test
+++ /dev/null
@@ -1 +0,0 @@
-Set-Cookie: ===a=bar
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0026-expected b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0026-expected
deleted file mode 100644
index b14d4f69..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0026-expected
+++ /dev/null
@@ -1 +0,0 @@
-Cookie: foo=bar
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0026-test b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0026-test
deleted file mode 100644
index bbeb77a0..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0026-test
+++ /dev/null
@@ -1 +0,0 @@
-Set-Cookie: foo=bar
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0027-expected b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0027-expected
deleted file mode 100644
index b14d4f69..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0027-expected
+++ /dev/null
@@ -1 +0,0 @@
-Cookie: foo=bar
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0027-test b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0027-test
deleted file mode 100644
index d222227..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0027-test
+++ /dev/null
@@ -1 +0,0 @@
-Set-Cookie: foo=bar    ;
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0028-expected b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0028-expected
deleted file mode 100644
index f14f993..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0028-expected
+++ /dev/null
@@ -1 +0,0 @@
-Cookie: a
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0028-test b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0028-test
deleted file mode 100644
index 1c197e3..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0028-test
+++ /dev/null
@@ -1 +0,0 @@
-Set-Cookie: =a
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0029-expected b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0029-expected
deleted file mode 100644
index e69de29..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0029-expected
+++ /dev/null
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0029-test b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0029-test
deleted file mode 100644
index 4421246..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0029-test
+++ /dev/null
@@ -1 +0,0 @@
-Set-Cookie: =
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0030-expected b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0030-expected
deleted file mode 100644
index a391380..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0030-expected
+++ /dev/null
@@ -1 +0,0 @@
-Cookie: foo bar=baz
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0030-test b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0030-test
deleted file mode 100644
index cf3ff16..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0030-test
+++ /dev/null
@@ -1 +0,0 @@
-Set-Cookie: foo bar=baz
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0031-expected b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0031-expected
deleted file mode 100644
index 0cd5a50..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0031-expected
+++ /dev/null
@@ -1 +0,0 @@
-Cookie: "foo
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0031-test b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0031-test
deleted file mode 100644
index 9394184..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0031-test
+++ /dev/null
@@ -1 +0,0 @@
-Set-Cookie: "foo;bar"=baz
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0032-expected b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0032-expected
deleted file mode 100644
index f4f7f3ff..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0032-expected
+++ /dev/null
@@ -1 +0,0 @@
-Cookie: "foo\"bar
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0032-test b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0032-test
deleted file mode 100644
index 93fc9752..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0032-test
+++ /dev/null
@@ -1 +0,0 @@
-Set-Cookie: "foo\"bar;baz"=qux
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0033-expected b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0033-expected
deleted file mode 100644
index 6e7762e..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0033-expected
+++ /dev/null
@@ -1 +0,0 @@
-Cookie: aaa
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0033-test b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0033-test
deleted file mode 100644
index 7bbdd89..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/name0033-test
+++ /dev/null
@@ -1,2 +0,0 @@
-Set-Cookie: =foo=bar
-Set-Cookie: aaa
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/value0001-expected b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/value0001-expected
deleted file mode 100644
index b14d4f69..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/value0001-expected
+++ /dev/null
@@ -1 +0,0 @@
-Cookie: foo=bar
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/value0001-test b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/value0001-test
deleted file mode 100644
index 38b7dd2..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/value0001-test
+++ /dev/null
@@ -1 +0,0 @@
-Set-Cookie: foo=  bar
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/value0002-expected b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/value0002-expected
deleted file mode 100644
index 9e96a81..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/value0002-expected
+++ /dev/null
@@ -1 +0,0 @@
-Cookie: foo="bar"
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/value0002-test b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/value0002-test
deleted file mode 100644
index bed691f..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/value0002-test
+++ /dev/null
@@ -1 +0,0 @@
-Set-Cookie: foo="bar"
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/value0003-expected b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/value0003-expected
deleted file mode 100644
index 5cc2d46..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/value0003-expected
+++ /dev/null
@@ -1 +0,0 @@
-Cookie: foo="  bar "
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/value0003-test b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/value0003-test
deleted file mode 100644
index ce1d455..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/value0003-test
+++ /dev/null
@@ -1 +0,0 @@
-Set-Cookie: foo="  bar "
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/value0004-expected b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/value0004-expected
deleted file mode 100644
index 400030f..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/value0004-expected
+++ /dev/null
@@ -1 +0,0 @@
-Cookie: foo="bar
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/value0004-test b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/value0004-test
deleted file mode 100644
index c569216..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/value0004-test
+++ /dev/null
@@ -1 +0,0 @@
-Set-Cookie: foo="bar;baz"
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/value0005-expected b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/value0005-expected
deleted file mode 100644
index cad285f7..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/value0005-expected
+++ /dev/null
@@ -1 +0,0 @@
-Cookie: foo="bar=baz"
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/value0005-test b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/value0005-test
deleted file mode 100644
index 514c0f1..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/value0005-test
+++ /dev/null
@@ -1 +0,0 @@
-Set-Cookie: foo="bar=baz"
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/value0006-expected b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/value0006-expected
deleted file mode 100644
index b14d4f69..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/value0006-expected
+++ /dev/null
@@ -1 +0,0 @@
-Cookie: foo=bar
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/value0006-test b/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/value0006-test
deleted file mode 100644
index a939a82..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/resources/test-files/value0006-test
+++ /dev/null
@@ -1 +0,0 @@
-Set-Cookie: 	foo	=	bar	 	;	ttt
diff --git a/third_party/blink/web_tests/external/wpt/cookies/http-state/value-tests.html b/third_party/blink/web_tests/external/wpt/cookies/http-state/value-tests.html
deleted file mode 100644
index 0d8daae..0000000
--- a/third_party/blink/web_tests/external/wpt/cookies/http-state/value-tests.html
+++ /dev/null
@@ -1,35 +0,0 @@
-<!doctype html>
-<html>
-  <head>
-    <meta charset=utf-8>
-    <title>Tests basic cookie setting functionality</title>
-    <meta name=help href="https://tools.ietf.org/html/rfc6265#page-8">
-
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <script src="resources/cookie-http-state-template.js"></script>
-  </head>
-  <body>
-    <div id="log"></div>
-    <div id="iframes"></div>
-    <script>
-      setup({ explicit_timeout: true });
-
-      const TEST_CASES = [
-        {file: "value0001", name: "value0001"},
-        {file: "value0002", name: "value0002"},
-        {file: "value0003", name: "value0003"},
-        {file: "value0004", name: "value0004"},
-        {file: "value0005", name: "value0005"},
-        {file: "value0006", name: "value0006"},
-      ];
-
-      for (const i in TEST_CASES) {
-        const t = TEST_CASES[i];
-        promise_test(createCookieTest(t.file),
-                     t.file + " - " + t.name);
-      }
-
-    </script>
-  </body>
-</html>
diff --git a/third_party/blink/web_tests/external/wpt/cookies/name/name.html b/third_party/blink/web_tests/external/wpt/cookies/name/name.html
index 18fd8765..39fdc75 100644
--- a/third_party/blink/web_tests/external/wpt/cookies/name/name.html
+++ b/third_party/blink/web_tests/external/wpt/cookies/name/name.html
@@ -12,9 +12,6 @@
   <body>
     <div id=log></div>
     <script>
-      // TODO: there is more to test here, these tests capture the old
-      // ported http-state tests. Feel free to delete this comment when more
-      // are added.
       const nameTests = [
         {
           cookie: "test1=;  path = /",
@@ -23,16 +20,88 @@
           defaultValue: false,
         },
         {
+          cookie: "=test=2",
+          expected: "test=2",
+          name: "Set a nameless cookie (that has an = in its value)",
+        },
+        {
+          cookie: "===test=2b",
+          expected: "==test=2b",
+          name: "Set a nameless cookie (that has multiple ='s in its value)",
+        },
+        {
+          cookie: "=test2c",
+          expected: "test2c",
+          name: "Set a nameless cookie",
+        },
+        {
+          cookie: "test =3",
+          expected: "test=3",
+          name: "Remove trailing WSP characters from the name string",
+        },
+        {
+          cookie: "   test=4",
+          expected: "test=4",
+          name: "Remove leading WSP characters from the name string",
+        },
+        {
+          cookie: ['"test=5"=test', '"test=5'],
+          expected: '"test=5',
+          name: "Only return the new cookie (with the same name)",
+        },
+        {
+          cookie: "test6;cool=dude",
+          expected: "test6",
+          name: "Ignore invalid attributes after nameless cookie",
+        },
+        {
+          cookie: "$Version=1; test=7",
+          expected: "$Version=1",
+          name: "Ignore invalid attributes after valid name (that looks like Cookie2 Version attribute)",
+        },
+        {
+          cookie: "test test=8",
+          expected: "test test=8",
+          name: "Set a cookie that has whitespace in its name",
+        },
+        {
+          cookie: '"test9;test"=9',
+          expected: '"test9',
+          name: "Set a nameless cookie ignoring characters after first ;",
+        },
+        {
+          cookie: '"test\"10;baz"=qux',
+          expected: '"test\"10',
+          name: "Set a nameless cookie ignoring characters after first ; (2)",
+        },
+        {
+          cookie: ["=test=11", "test11"],
+          expected: "test11",
+          name: "Return the most recent nameless cookie",
+        },
+        {
+          cookie: "=",
+          expected: "",
+          name: "Ignore cookie with empty name and empty value",
+        },
+        {
           cookie: "",
           expected: "",
           name: "Ignore cookie with no name or value",
         },
-
       ];
 
       for (const test of nameTests) {
         httpCookieTest(test.cookie, test.expected, test.name);
       }
+
+      for (const name of ["a", "1", "$", "!a", "@a", "#a", "$a", "%a",
+                          "^a", "&a", "*a", "(a", ")a", "-a", "_a", "+",
+                          '"a', '"a=b"'
+                        ]) {
+        const cookie = `${name}=test`;
+        httpCookieTest(cookie, cookie, `Name is set as expected for ${name}=test`);
+      }
     </script>
   </body>
 </html>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/cookies/value/value.html b/third_party/blink/web_tests/external/wpt/cookies/value/value.html
index c473a6bf..c191083 100644
--- a/third_party/blink/web_tests/external/wpt/cookies/value/value.html
+++ b/third_party/blink/web_tests/external/wpt/cookies/value/value.html
@@ -91,6 +91,41 @@
           expected: 'test="14 "',
           name: "Set cookie ignoring whitespace after value endquote",
         },
+        {
+          cookie: "test=15    ;",
+          expected: "test=15",
+          name: "Ignore whitespace and ; after value",
+        },
+        {
+          cookie: "test=  16",
+          expected: "test=16",
+          name: "Ignore whitespace preceding value",
+        },
+        {
+          cookie: 'test="17"',
+          expected: 'test="17"',
+          name: "Set cookie with quotes in value",
+        },
+        {
+          cookie: 'test="  18 "',
+          expected: 'test="  18 "',
+          name: "Set cookie keeping whitespace inside quoted value",
+        },
+        {
+          cookie: 'test="19;wow"',
+          expected: 'test="19',
+          name: "Set cookie value ignoring characters after semicolon",
+        },
+        {
+          cookie: 'test="20=20"',
+          expected: 'test="20=20"',
+          name: "Set cookie with another = inside quoted value",
+        },
+        {
+          cookie: "test	=	21	 	;	ttt",
+          expected: "test=21",
+          name: "Set cookie ignoring whitespace surrounding value and characters after first semicolon",
+        },
       ];
 
       for (const test of valueTests) {
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/support/grid-child-utils.js b/third_party/blink/web_tests/external/wpt/css/css-grid/support/grid-child-utils.js
index d75ad6e..5e0c5ca 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/support/grid-child-utils.js
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/support/grid-child-utils.js
@@ -5,7 +5,7 @@
 const gridChildHelperCol = "col";
 
 // Helper for building testcases for grid-template-* with a child div in
-// multiple positions. Prop is expected ot be one of gridChildHelperRow or
+// multiple positions. Prop is expected to be one of gridChildHelperRow or
 // gridChildHelperCol, to select testing grid rows or grid columns,
 // respectively.
 // The child div is found by the id of 'child'.
diff --git a/third_party/blink/web_tests/external/wpt/css/css-sizing/aspect-ratio/flex-aspect-ratio-025.html b/third_party/blink/web_tests/external/wpt/css/css-sizing/aspect-ratio/flex-aspect-ratio-025.html
new file mode 100644
index 0000000..fa9ce8e
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-sizing/aspect-ratio/flex-aspect-ratio-025.html
@@ -0,0 +1,73 @@
+<!DOCTYPE html>
+<html>
+  <title>CSS aspect-ratio: Test flex item's resolved width/min-width with border-box box-sizing in a row flex container</title>
+  <link rel="author" title="Ting-Yu Lin" href="mailto:tlin@mozilla.com">
+  <link rel="author" title="Mozilla" href="https://www.mozilla.org/">
+  <link rel="help" href="https://drafts.csswg.org/css-sizing-4/#aspect-ratio">
+  <link rel="match" href="../../reference/ref-filled-green-200px-square.html" />
+  <style>
+  .flexContainer {
+    display: flex;
+    flex-direction: row;
+    width: 1px;
+  }
+  .item {
+    background: green;
+    padding-top: 15px;
+    box-sizing: border-box;
+  }
+  </style>
+
+  <p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+
+  <!-- In the following four flex containers, the aspect-ratio works with border-box. -->
+  <div class="flexContainer" style="width: auto;">
+    <!-- The border-box height 25px is transferred to the main axis,
+         yielding a resolved flex base size of 200px. -->
+    <div class="item" style="min-width:0; height: 25px; aspect-ratio: 8/1;"></div>
+  </div>
+
+  <div class="flexContainer">
+    <!-- The border-box height 25px is transferred to the main axis,
+         yielding a resolved min-width:auto of 200px. -->
+    <div class="item" style="height: 25px; aspect-ratio: 8/1;"></div>
+  </div>
+
+  <div class="flexContainer">
+    <!-- The border-box min-height 25px is transferred to the main axis,
+         yielding a resolved min-width:auto of 200px. -->
+    <div class="item" style="min-height: 25px; aspect-ratio: 8/1;"></div>
+  </div>
+
+  <div class="flexContainer">
+    <!-- The border-box height 25px (clamped by max-height) is transferred to the main axis,
+         yielding a resolved min-width:auto of 200px. -->
+    <div class="item" style="max-height: 25px; height: 100px; aspect-ratio: 8/1;"></div>
+  </div>
+
+  <!-- In the following four flex containers, the aspect-ratio works with content-box
+       because its value contains 'auto'. -->
+  <div class="flexContainer" style="width: auto;">
+    <!-- The content-box height 10px is transferred to the main axis,
+         yielding a resolved flex base size of 200px. -->
+    <div class="item" style="min-width:0; height: 25px; aspect-ratio: auto 20/1;"></div>
+  </div>
+
+  <div class="flexContainer">
+    <!-- The content-box height 10px is transferred to the main axis,
+         yielding a resolved min-width:auto of 200px. -->
+    <div class="item" style="height: 25px; aspect-ratio: auto 20/1;"></div>
+  </div>
+
+  <div class="flexContainer">
+    <!-- The content-box min-height 10px is transferred to the main axis,
+         yielding a resolved min-width:auto of 200px. -->
+    <div class="item" style="min-height: 25px; aspect-ratio: auto 20/1;"></div>
+  </div>
+
+  <div class="flexContainer">
+    <!-- The content-box height 10px (clamped by max-height) is transferred
+         to the main axis, yielding a resolved min-width:auto of 200px. -->
+    <div class="item" style="max-height: 25px; height: 100px; aspect-ratio: auto 20/1;"></div>
+  </div>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-sizing/aspect-ratio/flex-aspect-ratio-026.html b/third_party/blink/web_tests/external/wpt/css/css-sizing/aspect-ratio/flex-aspect-ratio-026.html
new file mode 100644
index 0000000..1df1446
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-sizing/aspect-ratio/flex-aspect-ratio-026.html
@@ -0,0 +1,77 @@
+<!DOCTYPE html>
+<html>
+  <title>CSS aspect-ratio: Test flex item's resolved height/min-height with border-box box-sizing in a column flex container</title>
+  <link rel="author" title="Ting-Yu Lin" href="mailto:tlin@mozilla.com">
+  <link rel="author" title="Mozilla" href="https://www.mozilla.org/">
+  <link rel="help" href="https://drafts.csswg.org/css-sizing-4/#aspect-ratio">
+  <link rel="match" href="../../reference/ref-filled-green-200px-square.html" />
+  <style>
+  .flexContainer {
+    display: flex;
+    flex-direction: column;
+    float: left;
+    height: 1px;
+  }
+  .item {
+    background: green;
+    padding-left: 15px;
+    box-sizing: border-box;
+  }
+  .item > div {
+    height: 500px; /* Set a large content size suggestion for flex item. */
+  }
+  </style>
+
+  <p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+
+  <!-- In the following four flex containers, the aspect-ratio works with border-box. -->
+  <div class="flexContainer" style="height: auto;">
+    <!-- The border-box width 25px is transferred to the main axis,
+         yielding a resolved flex base size of 200px. -->
+    <div class="item" style="min-height:0; width: 25px; aspect-ratio: 1/8;"><div></div></div>
+  </div>
+
+  <div class="flexContainer">
+    <!-- The border-box width 25px is transferred to the main axis,
+         yielding a resolved min-height:auto of 200px. -->
+    <div class="item" style="width: 25px; aspect-ratio: 1/8;"><div></div></div>
+  </div>
+
+  <div class="flexContainer">
+    <!-- The border-box min-width 25px is transferred to the main axis,
+         yielding a resolved min-height:auto of 200px. -->
+    <div class="item" style="min-width: 25px; aspect-ratio: 1/8;"><div></div></div>
+  </div>
+
+  <div class="flexContainer">
+    <!-- The border-box width 25px (clamped by max-width) is transferred to the main axis,
+         yielding a resolved min-height:auto of 200px. -->
+    <div class="item" style="max-width: 25px; width: 100px; aspect-ratio: 1/8;"><div></div></div>
+  </div>
+
+  <!-- In the following four flex containers, the aspect-ratio works with content-box
+       because its value contains 'auto'. -->
+  <div class="flexContainer" style="height: auto;">
+    <!-- The content-box width 10px is transferred to the main axis,
+         yielding a resolved flex base size of 200px. -->
+    <div class="item" style="min-height:0; width: 25px; aspect-ratio: auto 1/20;"><div></div></div>
+  </div>
+
+  <div class="flexContainer">
+    <!-- The content-box width 10px is transferred to the main axis,
+         yielding a resolved min-height:auto of 200px. -->
+    <div class="item" style="width: 25px; aspect-ratio: auto 1/20;"><div></div></div>
+  </div>
+
+  <div class="flexContainer">
+    <!-- The content-box min-width 10px is transferred to the main axis,
+         yielding a resolved min-height:auto of 200px. -->
+    <div class="item" style="min-width: 25px; aspect-ratio: auto 1/20;"><div></div></div>
+  </div>
+
+  <div class="flexContainer">
+    <!-- The content-box width 10px (clamped by max-width) is transferred to the main axis,
+         yielding a resolved min-height:auto of 200px. -->
+    <div class="item" style="max-width: 25px; width: 100px; aspect-ratio: auto 1/20;"><div></div></div>
+  </div>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-values/calc-rounds-to-integer.html b/third_party/blink/web_tests/external/wpt/css/css-values/calc-rounds-to-integer.html
new file mode 100644
index 0000000..8058978
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-values/calc-rounds-to-integer.html
@@ -0,0 +1,120 @@
+<!doctype html>
+<title>Calc rounds to integer</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<link rel=author title="Tab Atkins-Bittner" href="https://www.xanthir.com/contact/">
+<link rel="help" href="https://drafts.csswg.org/css-values/#calc-range">
+<link rel="help" href="https://drafts.csswg.org/css-easing/#funcdef-step-easing-function-steps">
+<link rel="help" href="https://drafts.csswg.org/css-multicol-2/#column-span">
+<link rel="help" href="https://drafts.csswg.org/css-lists/#propdef-counter-reset">
+<link rel="help" href="https://drafts.csswg.org/css-lists/#propdef-counter-increment">
+<link rel="help" href="https://drafts.csswg.org/css-lists/#propdef-counter-set">
+<link rel="help" href="https://drafts.csswg.org/css-fonts-4/#font-feature-settings-prop">
+<link rel="help" href="https://drafts.csswg.org/css-grid/#propdef-grid-template-rows">
+<link rel="help" href="https://drafts.csswg.org/css-grid/#propdef-grid-row">
+<link rel="help" href="https://drafts.csswg.org/css-text-4/#propdef-hyphenate-limit-chars">
+<link rel="help" href="https://drafts.csswg.org/css-text-4/#propdef-hyphenate-limit-lines">
+<link rel="help" href="https://drafts.csswg.org/css-inline-3/#propdef-initial-letter">
+<link rel="help" href="https://drafts.csswg.org/css-overflow-4/#propdef-max-lines">
+<link rel="help" href="https://drafts.csswg.org/css-flexbox-1/#propdef-order">
+<link rel="help" href="https://drafts.csswg.org/css-break-4/#propdef-orphans">
+<link rel="help" href="https://drafts.csswg.org/css-writing-modes-4/#propdef-text-combine-upright">
+<link rel="help" href="https://drafts.csswg.org/css-break-4/#propdef-widows">
+<link rel="help" href="https://drafts.csswg.org/css2/#propdef-z-index">
+<!--
+  Verifying that, per V&U, a calc() that results in a non-integer value
+  gets rounded to the nearest integer
+  when used in a place that requires <integer> specifically.
+  This tests both straight-up decimal-point values,
+  and scinot, which is defined to parse as <number-token>.
+-->
+<body>
+
+<script>
+
+runTests("animation-timing-function", "steps(xxx)");
+runTests("column-span");
+runTests("counter-increment", "foo xxx");
+runTests("counter-reset", "foo xxx");
+runTests("counter-set", "foo xxx");
+runTests("font-feature-settings", '"fooo" xxx');
+runTests("grid-row");
+runTests("grid-template-rows", "repeat(xxx, 10px)");
+runTests("hyphenate-limit-chars");
+runTests("hyphenate-limit-lines");
+runTests("initial-letter", "1.1 xxx");
+runTests("max-lines");
+runTests("order");
+runTests("orphans");
+runTests("text-combine-upright", "digits xxx");
+runTests("transition-timing-function", "steps(xxx)");
+runTests("widows");
+runTests("z-index");
+
+
+function runTests(prop, valPattern="xxx") {
+    const el = document.body;
+
+    // Don't spuriously fail bc the prop or val isn't supported.
+    if(!verifySupport(el, prop, valPattern)) return;
+
+    const validValues = [
+        "10",
+        "calc(10)",
+        "calc(10.1)",
+        "calc(1e1)",
+        "calc(1.1e1)",
+    ];
+    const invalidValues = [
+        "1e1",
+        "1.1e1",
+        "10.1",
+    ];
+    for(let testVal of validValues) {
+        testInt(el, prop, testVal, valPattern);
+    }
+    for(let testVal of invalidValues) {
+        testIntInvalid(el, prop, testVal, valPattern);
+    }
+}
+
+function verifySupport(el, prop, valPattern) {
+    let testVal = "10";
+    if(valPattern !== undefined) {
+        testVal = valPattern.replace("xxx", testVal);
+    }
+    el.removeAttribute("style");
+    const nullVal = getComputedStyle(el)[prop];
+    el.style.setProperty(prop, testVal);
+    return getComputedStyle(el)[prop] != nullVal;
+}
+
+function testInt(el, prop, testVal, valPattern) {
+    // to avoid needing to specify serialization,
+    // just test whether it parses at all
+    if(valPattern !== undefined) {
+        testVal = valPattern.replace("xxx", testVal);
+    }
+    test(()=>{
+        el.removeAttribute("style");
+        const nullVal = getComputedStyle(el)[prop];
+        el.style.setProperty(prop, testVal);
+        assert_not_equals(getComputedStyle(el)[prop], nullVal);
+    }, `${prop} should accept "${testVal}"`)
+}
+
+function testIntInvalid(el, prop, testVal, valPattern) {
+    // similarly, just verify it doesn't parse at all
+    if(valPattern !== undefined) {
+        testVal = valPattern.replace("xxx", testVal);
+    }
+    test(()=>{
+        el.removeAttribute("style");
+        const nullVal = getComputedStyle(el)[prop];
+        el.style.setProperty(prop, testVal);
+        assert_equals(getComputedStyle(el)[prop], nullVal);
+    }, `${prop} should not accept "${testVal}"`)
+}
+
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/cssom-view/mouseEvent-offsetXY-svg.html b/third_party/blink/web_tests/external/wpt/css/cssom-view/mouseEvent-offsetXY-svg.html
new file mode 100644
index 0000000..c0bd164
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/cssom-view/mouseEvent-offsetXY-svg.html
@@ -0,0 +1,27 @@
+<!doctype html>
+<title>MouseEvent.offsetX/Y returns coordinates relative to the root svg</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="author" title="Mozilla" href="https://mozilla.org">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1684973">
+<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/1508">
+<link rel="help" href="https://drafts.csswg.org/cssom-view/#dom-mouseevent-offsetx">
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<style>
+  body { margin: 0 }
+</style>
+<svg width=100 height=100>
+  <rect width=30 height=30 x=50 y=50 fill=green></rect>
+</svg>
+<script>
+let t = async_test();
+let rect = document.querySelector("rect");
+rect.addEventListener("click", t.step_func_done(function(e) {
+  assert_true(e.offsetX >= 50);
+  assert_true(e.offsetY >= 50);
+}))
+
+test_driver.click(rect);
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/cssom/serialize-custom-props-expected.txt b/third_party/blink/web_tests/external/wpt/css/cssom/serialize-custom-props-expected.txt
new file mode 100644
index 0000000..5dbde36c
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/cssom/serialize-custom-props-expected.txt
@@ -0,0 +1,23 @@
+This is a testharness.js-based test.
+PASS z-index can take a 4-digit integer
+PASS An unregistered custom prop can take a 4-digit integer
+PASS An <integer> custom prop can take a 4-digit integer
+PASS z-index can take a custom property set to a 4-digit integer
+PASS z-index can take a 6-digit integer
+PASS An unregistered custom prop can take a 6-digit integer
+PASS An <integer> custom prop can take a 6-digit integer
+PASS z-index can take a custom property set to a 6-digit integer
+PASS z-index can take a 8-digit integer
+FAIL An unregistered custom prop can take a 8-digit integer assert_equals: expected "11111111" but got "1.11111e+7"
+FAIL An <integer> custom prop can take a 8-digit integer assert_equals: expected "11111111" but got "1.11111e+7"
+PASS z-index can take a custom property set to a 8-digit integer
+PASS z-index can take a 12-digit integer
+FAIL An unregistered custom prop can take a 12-digit integer assert_equals: expected "111111111111" but got "1.11111e+11"
+FAIL An <integer> custom prop can take a 12-digit integer assert_equals: expected "111111111111" but got "1.11111e+11"
+PASS z-index can take a custom property set to a 12-digit integer
+PASS z-index can take a 25-digit integer
+FAIL An unregistered custom prop can take a 25-digit integer assert_equals: expected "1111111111111111111111111" but got "1.11111e+24"
+FAIL An <integer> custom prop can take a 25-digit integer assert_equals: expected "1111111111111111111111111" but got "1.11111e+24"
+PASS z-index can take a custom property set to a 25-digit integer
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/css/cssom/serialize-custom-props.html b/third_party/blink/web_tests/external/wpt/css/cssom/serialize-custom-props.html
new file mode 100644
index 0000000..cfe96ff0
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/cssom/serialize-custom-props.html
@@ -0,0 +1,69 @@
+<link rel=author title="Tab Atkins-Bittner" href="https://www.xanthir.com/contact/">
+<link rel="help" href="https://drafts.csswg.org/css-values/#calc-range">
+<body><!doctype html>
+<title>Serializing Integers Never Uses Scinot</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<link rel=author title="Tab Atkins-Bittner" href="https://www.xanthir.com/contact/">
+<link rel="help" href="https://drafts.csswg.org/cssom/#serialize-a-css-component-value">
+<!--
+  Per CSSOM, integers always serialize all their digits out.
+  They never serialize to scinot, regardless of size,
+  because that makes them stop being an integer.
+  This applies to custom properties as well.
+-->
+<body>
+
+<script>
+
+try {
+CSS.registerProperty({
+    name: "--two",
+    value: "<integer>",
+    inherits: true,
+    initial: -1
+});
+}catch(e){}
+
+testIntLength(4);
+testIntLength(6);
+testIntLength(8);
+testIntLength(12);
+// JS starts serializing with scinot at 22 digits...
+testIntLength(25);
+
+function testIntLength(len) {
+    let el = document.body;
+    const val = "1".repeat(len);
+    test(()=>{
+        el.removeAttribute("style");
+        const nullVal = getComputedStyle(el).zIndex;
+        el.style.zIndex=val;
+        assert_not_equals(getComputedStyle(el).zIndex, nullVal)
+    }, `z-index can take a ${len}-digit integer`);
+
+    test(()=>{
+        el.removeAttribute("style");
+        el.style.setProperty("--one", val);
+        assert_equals(getComputedStyle(el).getPropertyValue("--one"), val);
+    }, `An unregistered custom prop can take a ${len}-digit integer`);
+
+    test(()=>{
+        el.removeAttribute("style");
+        el.style.setProperty("--two", val);
+        assert_equals(getComputedStyle(el).getPropertyValue("--two"), val);
+    }, `An <integer> custom prop can take a ${len}-digit integer`);
+
+    test(()=>{
+        el.removeAttribute("style");
+        el.style.zIndex = val;
+        const standardVal = getComputedStyle(el).zIndex;
+        el.removeAttribute("style");
+        el.style.setProperty("--three", val);
+        el.style.zIndex = "var(--three)";
+        assert_equals(getComputedStyle(el).zIndex, standardVal);
+    }, `z-index can take a custom property set to a ${len}-digit integer`);
+}
+
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/selectors/focus-in-focus-event-001.html b/third_party/blink/web_tests/external/wpt/css/selectors/focus-in-focus-event-001.html
index bd4145a3..0639b77 100644
--- a/third_party/blink/web_tests/external/wpt/css/selectors/focus-in-focus-event-001.html
+++ b/third_party/blink/web_tests/external/wpt/css/selectors/focus-in-focus-event-001.html
@@ -14,7 +14,7 @@
   var focused = document.querySelector(':focus');
   test(() => {
     assert_equals(e.target, focused, "':focus' matches event.target");
-  }, "Checks that ':focus' pseudo-class matches inside 'focus' evente handler");
+  }, "Checks that ':focus' pseudo-class matches inside 'focus' event handler");
 }, false);
 input.focus();
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/css/selectors/focus-visible-001.html b/third_party/blink/web_tests/external/wpt/css/selectors/focus-visible-001.html
index 0683689..d580b07 100644
--- a/third_party/blink/web_tests/external/wpt/css/selectors/focus-visible-001.html
+++ b/third_party/blink/web_tests/external/wpt/css/selectors/focus-visible-001.html
@@ -13,6 +13,7 @@
   <style>
     @supports not (selector(:focus-visible)) {
       :focus {
+        outline: red solid 5px;
         background-color: red;
       }
     }
diff --git a/third_party/blink/web_tests/external/wpt/css/selectors/focus-visible-002.html b/third_party/blink/web_tests/external/wpt/css/selectors/focus-visible-002.html
index 2846ae46..441c1da 100644
--- a/third_party/blink/web_tests/external/wpt/css/selectors/focus-visible-002.html
+++ b/third_party/blink/web_tests/external/wpt/css/selectors/focus-visible-002.html
@@ -14,6 +14,7 @@
   <style>
     @supports not (selector(:focus-visible)) {
       :focus {
+        outline: red solid 5px;
         background-color: red;
       }
     }
diff --git a/third_party/blink/web_tests/external/wpt/css/selectors/focus-visible-003.html b/third_party/blink/web_tests/external/wpt/css/selectors/focus-visible-003.html
index 9d572257..aa73b4d 100644
--- a/third_party/blink/web_tests/external/wpt/css/selectors/focus-visible-003.html
+++ b/third_party/blink/web_tests/external/wpt/css/selectors/focus-visible-003.html
@@ -14,6 +14,7 @@
     @supports not (selector(:focus-visible)) {
       :focus {
         outline: red solid 5px;
+        background-color: red;
       }
     }
 
diff --git a/third_party/blink/web_tests/external/wpt/css/selectors/focus-visible-004.html b/third_party/blink/web_tests/external/wpt/css/selectors/focus-visible-004.html
index 318d1ee..b30b9e2 100644
--- a/third_party/blink/web_tests/external/wpt/css/selectors/focus-visible-004.html
+++ b/third_party/blink/web_tests/external/wpt/css/selectors/focus-visible-004.html
@@ -14,6 +14,7 @@
     @supports not (selector(:focus-visible)) {
       :focus {
         outline: red solid 5px;
+        background-color: red;
       }
     }
 
diff --git a/third_party/blink/web_tests/external/wpt/css/selectors/focus-visible-005.html b/third_party/blink/web_tests/external/wpt/css/selectors/focus-visible-005.html
index 4aec863..c86c057 100644
--- a/third_party/blink/web_tests/external/wpt/css/selectors/focus-visible-005.html
+++ b/third_party/blink/web_tests/external/wpt/css/selectors/focus-visible-005.html
@@ -13,6 +13,7 @@
     @supports not (selector(:focus-visible)) {
       :focus {
         outline: red solid 5px;
+        background-color: red;
       }
     }
 
diff --git a/third_party/blink/web_tests/external/wpt/css/selectors/focus-visible-006.html b/third_party/blink/web_tests/external/wpt/css/selectors/focus-visible-006.html
index 89d9782..a5256c1 100644
--- a/third_party/blink/web_tests/external/wpt/css/selectors/focus-visible-006.html
+++ b/third_party/blink/web_tests/external/wpt/css/selectors/focus-visible-006.html
@@ -10,6 +10,13 @@
   <script src="/resources/testdriver.js"></script>
   <script src="/resources/testdriver-vendor.js"></script>
   <style>
+    @supports not (selector(:focus-visible)) {
+      :focus {
+        outline: red solid 5px;
+        background-color: red;
+      }
+    }
+
     span[contenteditable] {
         border: 1px solid black;
         background-color: white;
diff --git a/third_party/blink/web_tests/external/wpt/css/selectors/focus-visible-008.html b/third_party/blink/web_tests/external/wpt/css/selectors/focus-visible-008.html
index c84985f..5f5b54e 100644
--- a/third_party/blink/web_tests/external/wpt/css/selectors/focus-visible-008.html
+++ b/third_party/blink/web_tests/external/wpt/css/selectors/focus-visible-008.html
@@ -12,6 +12,7 @@
   <style>
     @supports not (selector(:focus-visible)) {
       #el:focus {
+        outline: red solid 5px;
         background-color: red;
       }
     }
diff --git a/third_party/blink/web_tests/external/wpt/css/selectors/focus-visible-009.html b/third_party/blink/web_tests/external/wpt/css/selectors/focus-visible-009.html
index bbadd578..d9edb69 100644
--- a/third_party/blink/web_tests/external/wpt/css/selectors/focus-visible-009.html
+++ b/third_party/blink/web_tests/external/wpt/css/selectors/focus-visible-009.html
@@ -9,7 +9,8 @@
   <script src="/resources/testharnessreport.js"></script>
   <style>
     @supports not (selector(:focus-visible)) {
-      #buton:focus {
+      #button:focus {
+        outline: red solid 5px;
         background-color: red;
       }
     }
diff --git a/third_party/blink/web_tests/external/wpt/css/selectors/focus-visible-010.html b/third_party/blink/web_tests/external/wpt/css/selectors/focus-visible-010.html
index cfd4282..8d02edbc 100644
--- a/third_party/blink/web_tests/external/wpt/css/selectors/focus-visible-010.html
+++ b/third_party/blink/web_tests/external/wpt/css/selectors/focus-visible-010.html
@@ -10,6 +10,7 @@
   <style>
     @supports not (selector(:focus-visible)) {
       :focus {
+        outline: red solid 5px;
         background-color: red;
       }
     }
diff --git a/third_party/blink/web_tests/external/wpt/css/selectors/focus-visible-011.html b/third_party/blink/web_tests/external/wpt/css/selectors/focus-visible-011.html
index 3d66dc9..d45f5d8e 100644
--- a/third_party/blink/web_tests/external/wpt/css/selectors/focus-visible-011.html
+++ b/third_party/blink/web_tests/external/wpt/css/selectors/focus-visible-011.html
@@ -12,6 +12,7 @@
   <style>
     @supports not (selector(:focus-visible)) {
       #next:focus {
+        outline: red solid 5px;
         background-color: red;
       }
     }
diff --git a/third_party/blink/web_tests/external/wpt/css/selectors/focus-visible-012.html b/third_party/blink/web_tests/external/wpt/css/selectors/focus-visible-012.html
index dcb00e1..b27abbe3 100644
--- a/third_party/blink/web_tests/external/wpt/css/selectors/focus-visible-012.html
+++ b/third_party/blink/web_tests/external/wpt/css/selectors/focus-visible-012.html
@@ -13,6 +13,7 @@
   <style>
     @supports not (selector(:focus-visible)) {
       :focus {
+        outline: red solid 5px;
         background-color: red;
       }
     }
diff --git a/third_party/blink/web_tests/external/wpt/css/selectors/focus-visible-014.html b/third_party/blink/web_tests/external/wpt/css/selectors/focus-visible-014.html
index 8e52570e..6a4aef6 100644
--- a/third_party/blink/web_tests/external/wpt/css/selectors/focus-visible-014.html
+++ b/third_party/blink/web_tests/external/wpt/css/selectors/focus-visible-014.html
@@ -9,6 +9,7 @@
 <style>
   @supports not (selector(:focus-visible)) {
     :focus {
+      outline: red solid 5px;
       background-color: red;
     }
   }
diff --git a/third_party/blink/web_tests/external/wpt/css/selectors/focus-visible-015.html b/third_party/blink/web_tests/external/wpt/css/selectors/focus-visible-015.html
index 21568f3..685baeb 100644
--- a/third_party/blink/web_tests/external/wpt/css/selectors/focus-visible-015.html
+++ b/third_party/blink/web_tests/external/wpt/css/selectors/focus-visible-015.html
@@ -11,6 +11,7 @@
 <style>
   @supports not (selector(:focus-visible)) {
     :focus {
+      outline: red solid 5px;
       background-color: red;
     }
   }
diff --git a/third_party/blink/web_tests/external/wpt/css/selectors/focus-visible-016.html b/third_party/blink/web_tests/external/wpt/css/selectors/focus-visible-016.html
index 64d9bb77..a65e5a5 100644
--- a/third_party/blink/web_tests/external/wpt/css/selectors/focus-visible-016.html
+++ b/third_party/blink/web_tests/external/wpt/css/selectors/focus-visible-016.html
@@ -11,6 +11,7 @@
 <style>
   @supports not (selector(:focus-visible)) {
     :focus {
+      outline: red solid 5px;
       background-color: red;
     }
   }
diff --git a/third_party/blink/web_tests/external/wpt/custom-elements/parser/parser-constructs-custom-element-in-document-write.html b/third_party/blink/web_tests/external/wpt/custom-elements/parser/parser-constructs-custom-element-in-document-write.html
index 3b52566..14c830b 100644
--- a/third_party/blink/web_tests/external/wpt/custom-elements/parser/parser-constructs-custom-element-in-document-write.html
+++ b/third_party/blink/web_tests/external/wpt/custom-elements/parser/parser-constructs-custom-element-in-document-write.html
@@ -14,10 +14,16 @@
 <div id="log"></div>
 <script>
 
-class MyCustomElement extends HTMLElement { }
+var numberOfChildNodesInConnectedCallback = 0;
+
+class MyCustomElement extends HTMLElement {
+    connectedCallback() {
+        numberOfChildNodesInConnectedCallback = this.childNodes.length;
+    }
+}
 customElements.define('my-custom-element', MyCustomElement);
 
-document.write('<my-custom-element></my-custom-element>');
+document.write('<my-custom-element>hello <b>world</b></my-custom-element>');
 
 test(function () {
     var instance = document.querySelector('my-custom-element');
@@ -27,6 +33,11 @@
 
 }, 'HTML parser must instantiate custom elements inside document.write');
 
+test(function () {
+  assert_equals(numberOfChildNodesInConnectedCallback, 0);
+
+}, 'HTML parser should call connectedCallback before appending child nodes inside document.write');
+
 </script>
 </body>
 </html>
diff --git a/third_party/blink/web_tests/external/wpt/docs/admin/index.md b/third_party/blink/web_tests/external/wpt/docs/admin/index.md
index 3073b96..59569fd4 100644
--- a/third_party/blink/web_tests/external/wpt/docs/admin/index.md
+++ b/third_party/blink/web_tests/external/wpt/docs/admin/index.md
@@ -44,15 +44,13 @@
 - (unknown registrar): http://testthewebforward.org
   - web-human@w3.org
 - [Google Domains](https://domains.google/): https://wpt.fyi
+  - smcgruer@google.com
   - foolip@google.com
-  - robertma@google.com
-  - mike@bocoup.com
 - (Google internal): https://wpt.live https://wptpr.live
+  - smcgruer@google.com
   - foolip@google.com
-  - robertma@google.com
 - [GitHub](https://github.com/): web-platform-tests
   - [@foolip](https://github.com/foolip)
-  - [@Hexcles](https://github.com/Hexcles)
   - [@jgraham](https://github.com/jgraham)
   - [@plehegar](https://github.com/plehegar)
   - [@thejohnjansen](https://github.com/thejohnjansen)
@@ -62,15 +60,12 @@
   - [@plehegar](https://github.com/plehegar)
   - [@sideshowbarker](https://github.com/sideshowbarker)
 - [Google Cloud Platform](https://cloud.google.com/): wptdashboard{-staging}
-  - robertma@google.com
   - smcgruer@google.com
   - foolip@google.com
 - [Google Cloud Platform](https://cloud.google.com/): wpt-live
   - smcgruer@google.com
-  - robertma@google.com
 - [Google Cloud Platform](https://cloud.google.com/): wpt-pr-bot
   - smcgruer@google.com
-  - robertma@google.com
 - E-mail address: wpt.pr.bot@gmail.com
   - smcgruer@google.com
   - boaz@bocoup.com
diff --git a/third_party/blink/web_tests/external/wpt/html/rendering/replaced-elements/embedded-content/audio-controls-intrinsic-size.html b/third_party/blink/web_tests/external/wpt/html/rendering/replaced-elements/embedded-content/audio-controls-intrinsic-size.html
new file mode 100644
index 0000000..6cbbcd0
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/rendering/replaced-elements/embedded-content/audio-controls-intrinsic-size.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<title>Audio intrinsic size doesn't depend on its max size</title>
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1683979">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div style="display: inline-block">
+  <audio controls style="max-width: 99%" id="test"></audio>
+</div>
+<script>
+let audio = document.getElementById("test");
+
+function computeSize() {
+  return audio.getBoundingClientRect().width;
+}
+
+let size = computeSize();
+async_test(function(t) {
+  requestAnimationFrame(t.step_func(function() {
+    assert_equals(computeSize(), size, "Shouldn't have changed size");
+    requestAnimationFrame(t.step_func_done(function() {
+      assert_equals(computeSize(), size, "Shouldn't have changed size");
+    }));
+  }));
+});
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/import-maps/data-driven/tools/format_json.py b/third_party/blink/web_tests/external/wpt/import-maps/data-driven/tools/format_json.py
index 1059ac7..6386d41 100644
--- a/third_party/blink/web_tests/external/wpt/import-maps/data-driven/tools/format_json.py
+++ b/third_party/blink/web_tests/external/wpt/import-maps/data-driven/tools/format_json.py
@@ -12,7 +12,7 @@
 
 def main():
     for filename in sys.argv[1:]:
-        print filename
+        print(filename)
         try:
             spec = json.load(
                 open(filename, u'r'), object_pairs_hook=collections.OrderedDict)
diff --git a/third_party/blink/web_tests/external/wpt/intersection-observer/same-document-with-document-root.html b/third_party/blink/web_tests/external/wpt/intersection-observer/same-document-with-document-root.html
new file mode 100644
index 0000000..15cb7c4c
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/intersection-observer/same-document-with-document-root.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<meta name="viewport" content="width=device-width,initial-scale=1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="./resources/intersection-observer-test-utils.js"></script>
+
+<style>
+    pre,
+    #log {
+        position: absolute;
+        top: 0;
+        left: 200px;
+    }
+
+    .spacer {
+        height: calc(100vh + 100px);
+    }
+
+    #target {
+        width: 100px;
+        height: 100px;
+        background-color: green;
+    }
+</style>
+
+<div class="spacer"></div>
+<div id="target"></div>
+<div class="spacer"></div>
+
+<script>
+    var vw = document.documentElement.clientWidth;
+    var vh = document.documentElement.clientHeight;
+
+    var entries = [];
+    var target;
+
+    runTestCycle(function () {
+        target = document.getElementById("target");
+        assert_true(!!target, "target exists");
+        var observer = new IntersectionObserver(function (changes) {
+            entries = entries.concat(changes)
+        }, {root: document});
+        observer.observe(target);
+        entries = entries.concat(observer.takeRecords());
+        assert_equals(entries.length, 0, "No initial notifications.");
+        runTestCycle(step0, "First rAF.");
+    }, "IntersectionObserver in a single document using document as root.");
+
+    function step0() {
+        document.scrollingElement.scrollTop = 300;
+        runTestCycle(step1, "document.scrollingElement.scrollTop = 300");
+        checkLastEntry(entries, 0, [8, 108, vh + 108, vh + 208, 0, 0, 0, 0, 0, vw, 0, vh, false]);
+    }
+
+    function step1() {
+        document.scrollingElement.scrollTop = 0;
+        checkLastEntry(entries, 1, [8, 108, vh - 192, vh - 92, 8, 108, vh - 192, vh - 92, 0, vw, 0, vh, true]);
+    }
+</script>
+
diff --git a/third_party/blink/web_tests/external/wpt/selection/textcontrols/selectionchange.tentative-expected.txt b/third_party/blink/web_tests/external/wpt/selection/textcontrols/selectionchange.tentative-expected.txt
index 444eafb..7506236 100644
--- a/third_party/blink/web_tests/external/wpt/selection/textcontrols/selectionchange.tentative-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/selection/textcontrols/selectionchange.tentative-expected.txt
@@ -3,6 +3,7 @@
 FAIL Modifying selectionEnd value of the input element assert_equals: expected 1 but got 0
 FAIL Calling setSelectionRange() on the input element assert_equals: expected 1 but got 0
 FAIL Calling select() on the input element assert_equals: expected 1 but got 0
+FAIL Calling setRangeText() on the input element assert_equals: expected 1 but got 0
 PASS Setting initial zero selectionStart value on the input element
 FAIL Setting the same selectionStart value twice on the input element assert_equals: expected 1 but got 0
 PASS Setting initial zero selectionEnd value on the input element
@@ -14,6 +15,7 @@
 FAIL Modifying selectionEnd value of the disconnected input element assert_equals: expected 1 but got 0
 FAIL Calling setSelectionRange() on the disconnected input element assert_equals: expected 1 but got 0
 FAIL Calling select() on the disconnected input element assert_equals: expected 1 but got 0
+FAIL Calling setRangeText() on the disconnected input element assert_equals: expected 1 but got 0
 PASS Setting initial zero selectionStart value on the disconnected input element
 FAIL Setting the same selectionStart value twice on the disconnected input element assert_equals: expected 1 but got 0
 PASS Setting initial zero selectionEnd value on the disconnected input element
@@ -25,6 +27,7 @@
 FAIL Modifying selectionEnd value of the textarea element assert_equals: expected 1 but got 0
 FAIL Calling setSelectionRange() on the textarea element assert_equals: expected 1 but got 0
 FAIL Calling select() on the textarea element assert_equals: expected 1 but got 0
+FAIL Calling setRangeText() on the textarea element assert_equals: expected 1 but got 0
 PASS Setting initial zero selectionStart value on the textarea element
 FAIL Setting the same selectionStart value twice on the textarea element assert_equals: expected 1 but got 0
 PASS Setting initial zero selectionEnd value on the textarea element
@@ -36,6 +39,7 @@
 FAIL Modifying selectionEnd value of the disconnected textarea element assert_equals: expected 1 but got 0
 FAIL Calling setSelectionRange() on the disconnected textarea element assert_equals: expected 1 but got 0
 FAIL Calling select() on the disconnected textarea element assert_equals: expected 1 but got 0
+FAIL Calling setRangeText() on the disconnected textarea element assert_equals: expected 1 but got 0
 PASS Setting initial zero selectionStart value on the disconnected textarea element
 FAIL Setting the same selectionStart value twice on the disconnected textarea element assert_equals: expected 1 but got 0
 PASS Setting initial zero selectionEnd value on the disconnected textarea element
diff --git a/third_party/blink/web_tests/external/wpt/selection/textcontrols/selectionchange.tentative.html b/third_party/blink/web_tests/external/wpt/selection/textcontrols/selectionchange.tentative.html
index 2216a4b5..adec8b3 100644
--- a/third_party/blink/web_tests/external/wpt/selection/textcontrols/selectionchange.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/selection/textcontrols/selectionchange.tentative.html
@@ -103,6 +103,15 @@
     promise_test(async () => {
       await data.initialize();
 
+      target.setRangeText("newmiddle", 2, 3, "select");
+
+      await data.assert_empty_spin();
+      assert_equals(collector.events.length, 1);
+    }, `Calling setRangeText() on ${name}`);
+
+    promise_test(async () => {
+      await data.initialize();
+
       target.selectionStart = 0;
 
       await data.assert_empty_spin();
diff --git a/third_party/blink/web_tests/external/wpt/svg/types/scripted/SVGAnimatedEnumeration-SVGFECompositeElement-expected.txt b/third_party/blink/web_tests/external/wpt/svg/types/scripted/SVGAnimatedEnumeration-SVGFECompositeElement-expected.txt
new file mode 100644
index 0000000..48365c5
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/svg/types/scripted/SVGAnimatedEnumeration-SVGFECompositeElement-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+FAIL Use of SVGAnimatedEnumeration within SVGFECompositeElement Failed to set the 'baseVal' property on 'SVGAnimatedEnumeration': The enumeration value provided is 0, which is not settable.
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/svg/types/scripted/SVGAnimatedEnumeration-SVGFECompositeElement.html b/third_party/blink/web_tests/external/wpt/svg/types/scripted/SVGAnimatedEnumeration-SVGFECompositeElement.html
index e6315b4..0e99f5fe9 100644
--- a/third_party/blink/web_tests/external/wpt/svg/types/scripted/SVGAnimatedEnumeration-SVGFECompositeElement.html
+++ b/third_party/blink/web_tests/external/wpt/svg/types/scripted/SVGAnimatedEnumeration-SVGFECompositeElement.html
@@ -34,13 +34,18 @@
   assert_equals(feCompositeElement.operator.baseVal, SVGFECompositeElement.SVG_FECOMPOSITE_OPERATOR_XOR);
   assert_equals(feCompositeElement.getAttribute('operator'), "xor");
 
+  // Switch to 'lighter'.
+  feCompositeElement.operator.baseVal = SVGFECompositeElement.SVG_FECOMPOSITE_OPERATOR_LIGHTER;
+  assert_equals(feCompositeElement.operator.baseVal, SVGFECompositeElement.SVG_FECOMPOSITE_OPERATOR_LIGHTER);
+  assert_equals(feCompositeElement.getAttribute('operator'), "lighter");
+
   // Switch to 'arithmetic'.
   feCompositeElement.operator.baseVal = SVGFECompositeElement.SVG_FECOMPOSITE_OPERATOR_ARITHMETIC;
   assert_equals(feCompositeElement.operator.baseVal, SVGFECompositeElement.SVG_FECOMPOSITE_OPERATOR_ARITHMETIC);
   assert_equals(feCompositeElement.getAttribute('operator'), "arithmetic");
 
   // Try setting invalid values.
-  assert_throws_js(TypeError, function() { feCompositeElement.operator.baseVal = 7; });
+  assert_throws_js(TypeError, function() { feCompositeElement.operator.baseVal = 8; });
   assert_equals(feCompositeElement.operator.baseVal, SVGFECompositeElement.SVG_FECOMPOSITE_OPERATOR_ARITHMETIC);
   assert_equals(feCompositeElement.getAttribute('operator'), "arithmetic");
 
@@ -56,10 +61,5 @@
   feCompositeElement.operator.baseVal = SVGFECompositeElement.SVG_FECOMPOSITE_OPERATOR_OVER;
   assert_equals(feCompositeElement.operator.baseVal, SVGFECompositeElement.SVG_FECOMPOSITE_OPERATOR_OVER);
   assert_equals(feCompositeElement.getAttribute('operator'), "over");
-
-  // Switch to 'lighter'.
-  assert_equals(SVGFECompositeElement.SVG_FECOMPOSITE_OPERATOR_LIGHTER, undefined);
-  feCompositeElement.setAttribute("operator", "lighter");
-  assert_equals(feCompositeElement.operator.baseVal, SVGFECompositeElement.SVG_FECOMPOSITE_OPERATOR_UNKNOWN);
 });
-</script>
\ No newline at end of file
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/xhr/event-timeout.any.js b/third_party/blink/web_tests/external/wpt/xhr/event-timeout.any.js
index da8ca11b..c73cd1a 100644
--- a/third_party/blink/web_tests/external/wpt/xhr/event-timeout.any.js
+++ b/third_party/blink/web_tests/external/wpt/xhr/event-timeout.any.js
@@ -14,5 +14,5 @@
   client.send(null);
   test.step_timeout(() => {
     assert_unreached("ontimeout not called.");
-  }, 10);
+  }, 1000);
 });
diff --git a/third_party/blink/web_tests/hid/hidDevice_deviceInfo.html b/third_party/blink/web_tests/http/tests/hid/hidDevice_deviceInfo.html
similarity index 83%
rename from third_party/blink/web_tests/hid/hidDevice_deviceInfo.html
rename to third_party/blink/web_tests/http/tests/hid/hidDevice_deviceInfo.html
index 66c515b..4f1184f 100644
--- a/third_party/blink/web_tests/hid/hidDevice_deviceInfo.html
+++ b/third_party/blink/web_tests/http/tests/hid/hidDevice_deviceInfo.html
@@ -1,14 +1,11 @@
 <!DOCTYPE html>
 <body>
-<script src="../resources/testharness.js"></script>
-<script src="../resources/testharnessreport.js"></script>
-<script src="../resources/testdriver.js"></script>
-<script src="../resources/testdriver-vendor.js"></script>
-<script src="file:///gen/layout_test_data/mojo/public/js/mojo_bindings.js"></script>
-<script src="file:///gen/services/device/public/mojom/hid.mojom.js"></script>
-<script src="file:///gen/third_party/blink/public/mojom/hid/hid.mojom.js"></script>
-<script src="resources/hid-test-utils.js"></script>
-<script>
+<script type="module">
+import '/resources/testharness.js';
+import '/resources/testharnessreport.js';
+
+import {hid_test, trustedClick} from './resources/hid-test-utils.js';
+import {GENERIC_DESKTOP_GAME_PAD, HID_COLLECTION_TYPE_APPLICATION, HidBusType, HidCollectionInfo, HidReportDescription, HidReportItem, HidUsageAndPage, PAGE_BUTTON, PAGE_GENERIC_DESKTOP} from '/gen/services/device/public/mojom/hid.mojom.m.js';
 
 const kTestVendorId = 0x1234;
 const kTestProductId = 0xabcd;
@@ -23,15 +20,15 @@
 // gamepad-like device with one input report. The input report has a single
 // 8-bit field with a button usage.
 function createDeviceWithInputReport(fake) {
-  const nullUsage = new device.mojom.HidUsageAndPage();
-  const buttonUsage = new device.mojom.HidUsageAndPage();
-  buttonUsage.usagePage = device.mojom.PAGE_BUTTON;
+  const nullUsage = new HidUsageAndPage();
+  const buttonUsage = new HidUsageAndPage();
+  buttonUsage.usagePage = PAGE_BUTTON;
   buttonUsage.usage = kButtonPrimary;
-  const gamePadUsage = new device.mojom.HidUsageAndPage();
-  gamePadUsage.usagePage = device.mojom.PAGE_GENERIC_DESKTOP;
-  gamePadUsage.usage = device.mojom.GENERIC_DESKTOP_GAME_PAD;
+  const gamePadUsage = new HidUsageAndPage();
+  gamePadUsage.usagePage = PAGE_GENERIC_DESKTOP;
+  gamePadUsage.usage = GENERIC_DESKTOP_GAME_PAD;
 
-  const reportItem = new device.mojom.HidReportItem();
+  const reportItem = new HidReportItem();
   reportItem.isRange = false;
   reportItem.isConstant = false;  // Data.
   reportItem.isVariable = true;  // Variable.
@@ -58,14 +55,14 @@
   reportItem.reportSize = 8;  // 1 byte.
   reportItem.reportCount = 1;
 
-  const report = new device.mojom.HidReportDescription();
+  const report = new HidReportDescription();
   report.reportId = kTestReportId;
   report.items = [reportItem];
 
-  const collection = new device.mojom.HidCollectionInfo();
+  const collection = new HidCollectionInfo();
   collection.usage = gamePadUsage;
   collection.reportIds = [kTestReportId];
-  collection.collectionType = device.mojom.HID_COLLECTION_TYPE_APP_APPLICATION;
+  collection.collectionType = HID_COLLECTION_TYPE_APPLICATION;
   collection.inputReports = [report];
   collection.outputReports = [];
   collection.featureReports = [];
@@ -75,7 +72,7 @@
   // The device guid will be set by fake.addDevice().
   deviceInfo.productName = kTestProductName;
   deviceInfo.serialNumber = kTestSerialNumber;
-  deviceInfo.busType = device.mojom.HidBusType.HID_BUS_TYPE_USB;
+  deviceInfo.busType = HidBusType.HID_BUS_TYPE_USB;
   deviceInfo.reportDescriptor = [];
   deviceInfo.collections = [collection];
   deviceInfo.hasReportId = true;
@@ -106,10 +103,9 @@
   assert_equals(d.collections.length, 1, 'device.collections.length');
 
   const c = d.collections[0];
-  assert_equals(c.usagePage, device.mojom.PAGE_GENERIC_DESKTOP,
-                'collection.usagePage');
-  assert_equals(c.usage, device.mojom.GENERIC_DESKTOP_GAME_PAD,
-                'collection.usage');
+  assert_equals(c.usagePage, PAGE_GENERIC_DESKTOP, 'collection.usagePage');
+  assert_equals(c.usage, GENERIC_DESKTOP_GAME_PAD, 'collection.usage');
+  assert_equals(c.type, HID_COLLECTION_TYPE_APPLICATION, 'collection.type');
   assert_equals(c.children.length, 0, 'collection.children.length');
   assert_equals(c.inputReports.length, 1, 'collection.inputReports.length');
   assert_equals(c.outputReports.length, 0, 'collection.outputReports.length');
@@ -122,8 +118,14 @@
   const i = r.items[0];
   assert_true(i.isAbsolute, 'reportItem.isAbsolute');
   assert_false(i.isArray, 'reportItem.isArray');
+  assert_false(i.isBufferedBytes, 'reportItem.isBufferedBytes');
+  assert_false(i.isConstant, 'reportItem.isConstant');
+  assert_true(i.isLinear, 'reportItem.isLinear');
   assert_false(i.isRange, 'reportItem.isRange');
+  assert_false(i.isVolatile, 'reportItem.isVolatile');
   assert_false(i.hasNull, 'reportItem.hasNull');
+  assert_true(i.hasPreferredState, 'reportItem.hasPreferredState');
+  assert_false(i.wrap, 'reportItem.wrap');
   assert_equals(i.usages.length, 1, 'reportItem.usages.length');
   assert_equals(i.usages[0], 0x00090001, 'reportItem.usages[0]');
   assert_equals(i.usageMinimum, 0, 'reportItem.usageMinimum');
diff --git a/third_party/blink/web_tests/hid/hidDevice_openAndClose.html b/third_party/blink/web_tests/http/tests/hid/hidDevice_openAndClose.html
similarity index 81%
rename from third_party/blink/web_tests/hid/hidDevice_openAndClose.html
rename to third_party/blink/web_tests/http/tests/hid/hidDevice_openAndClose.html
index 1765dc7..5cab2d60 100644
--- a/third_party/blink/web_tests/hid/hidDevice_openAndClose.html
+++ b/third_party/blink/web_tests/http/tests/hid/hidDevice_openAndClose.html
@@ -1,14 +1,10 @@
 <!DOCTYPE html>
 <body>
-<script src="../resources/testharness.js"></script>
-<script src="../resources/testharnessreport.js"></script>
-<script src="../resources/testdriver.js"></script>
-<script src="../resources/testdriver-vendor.js"></script>
-<script src="file:///gen/layout_test_data/mojo/public/js/mojo_bindings.js"></script>
-<script src="file:///gen/services/device/public/mojom/hid.mojom.js"></script>
-<script src="file:///gen/third_party/blink/public/mojom/hid/hid.mojom.js"></script>
-<script src="resources/hid-test-utils.js"></script>
-<script>
+<script type="module">
+import '/resources/testharness.js';
+import '/resources/testharnessreport.js';
+
+import {hid_test, trustedClick} from './resources/hid-test-utils.js';
 
 const kTestVendorId = 0x1234;
 const kTestProductId = 0xabcd;
diff --git a/third_party/blink/web_tests/hid/hidDevice_reports.html b/third_party/blink/web_tests/http/tests/hid/hidDevice_reports.html
similarity index 87%
rename from third_party/blink/web_tests/hid/hidDevice_reports.html
rename to third_party/blink/web_tests/http/tests/hid/hidDevice_reports.html
index af85f895..ae3bc6a8 100644
--- a/third_party/blink/web_tests/hid/hidDevice_reports.html
+++ b/third_party/blink/web_tests/http/tests/hid/hidDevice_reports.html
@@ -1,14 +1,10 @@
 <!DOCTYPE html>
 <body>
-<script src="../resources/testharness.js"></script>
-<script src="../resources/testharnessreport.js"></script>
-<script src="../resources/testdriver.js"></script>
-<script src="../resources/testdriver-vendor.js"></script>
-<script src="file:///gen/layout_test_data/mojo/public/js/mojo_bindings.js"></script>
-<script src="file:///gen/services/device/public/mojom/hid.mojom.js"></script>
-<script src="file:///gen/third_party/blink/public/mojom/hid/hid.mojom.js"></script>
-<script src="resources/hid-test-utils.js"></script>
-<script>
+<script type="module">
+import '/resources/testharness.js';
+import '/resources/testharnessreport.js';
+
+import {compareDataViews, hid_test, oninputreport, trustedClick} from './resources/hid-test-utils.js';
 
 const kTestVendorId = 0x1234;
 const kTestProductId = 0xabcd;
diff --git a/third_party/blink/web_tests/hid/hid_connectionEvents.html b/third_party/blink/web_tests/http/tests/hid/hid_connectionEvents.html
similarity index 75%
rename from third_party/blink/web_tests/hid/hid_connectionEvents.html
rename to third_party/blink/web_tests/http/tests/hid/hid_connectionEvents.html
index de214f5..c71d4d1 100644
--- a/third_party/blink/web_tests/hid/hid_connectionEvents.html
+++ b/third_party/blink/web_tests/http/tests/hid/hid_connectionEvents.html
@@ -1,11 +1,9 @@
 <!DOCTYPE html>
-<script src="../resources/testharness.js"></script>
-<script src="../resources/testharnessreport.js"></script>
-<script src="file:///gen/layout_test_data/mojo/public/js/mojo_bindings.js"></script>
-<script src="file:///gen/services/device/public/mojom/hid.mojom.js"></script>
-<script src="file:///gen/third_party/blink/public/mojom/hid/hid.mojom.js"></script>
-<script src="resources/hid-test-utils.js"></script>
-<script>
+<script type="module">
+import '/resources/testharness.js';
+import '/resources/testharnessreport.js';
+
+import {hid_test} from './resources/hid-test-utils.js';
 
 const kTestVendorId = 0x1234;
 const kTestProductId = 0xabcd;
diff --git a/third_party/blink/web_tests/hid/hid_getDevices.html b/third_party/blink/web_tests/http/tests/hid/hid_getDevices.html
similarity index 73%
rename from third_party/blink/web_tests/hid/hid_getDevices.html
rename to third_party/blink/web_tests/http/tests/hid/hid_getDevices.html
index f5652ec7..1cf603c 100644
--- a/third_party/blink/web_tests/hid/hid_getDevices.html
+++ b/third_party/blink/web_tests/http/tests/hid/hid_getDevices.html
@@ -1,18 +1,17 @@
 <!DOCTYPE html>
-<script src="../resources/testharness.js"></script>
-<script src="../resources/testharnessreport.js"></script>
-<script src="file:///gen/layout_test_data/mojo/public/js/mojo_bindings.js"></script>
-<script src="file:///gen/services/device/public/mojom/hid.mojom.js"></script>
-<script src="file:///gen/third_party/blink/public/mojom/hid/hid.mojom.js"></script>
-<script src="resources/hid-test-utils.js"></script>
-<script>
+<script type="module">
+import '../resources/testharness.js';
+import '../resources/testharnessreport.js';
+
+import {hid_test} from './resources/hid-test-utils.js';
+
+import {HidService} from '/gen/third_party/blink/public/mojom/hid/hid.mojom.m.js';
 
 const kTestVendorId = 0x1234;
 const kTestProductId = 0xabcd;
 
 promise_test(async () => {
-  let interceptor =
-      new MojoInterfaceInterceptor(blink.mojom.HidService.name);
+  let interceptor = new MojoInterfaceInterceptor(HidService.$interfaceName);
   interceptor.oninterfacerequest = e => e.handle.close();
   interceptor.start();
 
diff --git a/third_party/blink/web_tests/hid/hid_requestDevice.html b/third_party/blink/web_tests/http/tests/hid/hid_requestDevice.html
similarity index 85%
rename from third_party/blink/web_tests/hid/hid_requestDevice.html
rename to third_party/blink/web_tests/http/tests/hid/hid_requestDevice.html
index 1c09732..8e62859b 100644
--- a/third_party/blink/web_tests/hid/hid_requestDevice.html
+++ b/third_party/blink/web_tests/http/tests/hid/hid_requestDevice.html
@@ -1,14 +1,12 @@
 <!DOCTYPE html>
 <body>
-<script src="../resources/testharness.js"></script>
-<script src="../resources/testharnessreport.js"></script>
-<script src="../resources/testdriver.js"></script>
-<script src="../resources/testdriver-vendor.js"></script>
-<script src="file:///gen/layout_test_data/mojo/public/js/mojo_bindings.js"></script>
-<script src="file:///gen/services/device/public/mojom/hid.mojom.js"></script>
-<script src="file:///gen/third_party/blink/public/mojom/hid/hid.mojom.js"></script>
-<script src="resources/hid-test-utils.js"></script>
-<script>
+<script type="module">
+import '../resources/testharness.js';
+import '../resources/testharnessreport.js';
+
+import {hid_test, trustedClick} from './resources/hid-test-utils.js';
+
+import {HidService} from '/gen/third_party/blink/public/mojom/hid/hid.mojom.m.js';
 
 const kTestVendorId = 0x1234;
 const kTestProductId = 0xabcd;
@@ -19,8 +17,7 @@
 }, 'requestDevice() rejects without a user gesture');
 
 promise_test(async (t) => {
-  let interceptor =
-      new MojoInterfaceInterceptor(blink.mojom.HidService.name);
+  let interceptor = new MojoInterfaceInterceptor(HidService.$interfaceName);
   interceptor.oninterfacerequest = e => e.handle.close();
   interceptor.start();
 
diff --git a/third_party/blink/web_tests/hid/resources/hid-test-utils.js b/third_party/blink/web_tests/http/tests/hid/resources/hid-test-utils.js
similarity index 86%
rename from third_party/blink/web_tests/hid/resources/hid-test-utils.js
rename to third_party/blink/web_tests/http/tests/hid/resources/hid-test-utils.js
index f1338b6..c5733f9 100644
--- a/third_party/blink/web_tests/hid/resources/hid-test-utils.js
+++ b/third_party/blink/web_tests/http/tests/hid/resources/hid-test-utils.js
@@ -1,5 +1,13 @@
+import '/resources/testdriver.js';
+import '/resources/testdriver-vendor.js';
+import '/resources/testharness.js';
+import '/resources/testharnessreport.js';
+
+import {HidConnection, HidConnectionReceiver, HidDeviceInfo, HidManagerClientRemote} from '/gen/services/device/public/mojom/hid.mojom.m.js';
+import {HidService, HidServiceReceiver} from '/gen/third_party/blink/public/mojom/hid/hid.mojom.m.js';
+
 // Compare two DataViews byte-by-byte.
-function compareDataViews(actual, expected) {
+export function compareDataViews(actual, expected) {
   assert_true(actual instanceof DataView, 'actual is DataView');
   assert_true(expected instanceof DataView, 'expected is DataView');
   assert_equals(actual.byteLength, expected.byteLength, 'lengths equal');
@@ -10,8 +18,8 @@
 }
 
 // Returns a Promise that resolves once |device| receives an input report.
-function oninputreport(device) {
-  assert_true(device instanceof HIDDevice)
+export function oninputreport(device) {
+  assert_true(device instanceof HIDDevice);
   return new Promise(resolve => { device.oninputreport = resolve; });
 }
 
@@ -21,15 +29,14 @@
 class FakeHidConnection {
   constructor(client) {
     this.client_ = client;
+    this.receiver_ = new HidConnectionReceiver(this);
     this.expectedWrites_ = [];
     this.expectedGetFeatureReports_ = [];
     this.expectedSendFeatureReports_ = [];
   }
 
-  bind(request) {
-    assert_equals(this.binding, undefined);
-    this.binding = new mojo.Binding(device.mojom.HidConnection, this, request);
-    this.binding.setConnectionErrorHandler(() => { this.binding = undefined; });
+  bindNewPipeAndPassRemote() {
+    return this.receiver_.$.bindNewPipeAndPassRemote();
   }
 
   // Simulate an input report sent from the device to the host. The connection
@@ -46,8 +53,8 @@
   // parameters of the next write call must match |reportId| and |buffer|.
   queueExpectedWrite(success, reportId, reportData) {
     this.expectedWrites_.push({
-      params: { reportId: reportId, data: reportData },
-      result: { success: success },
+      params: {reportId, data: reportData},
+      result: {success},
     });
   }
 
@@ -56,8 +63,8 @@
   // The parameter of the next getFeatureReport call must match |reportId|.
   queueExpectedGetFeatureReport(success, reportId, reportData) {
     this.expectedGetFeatureReports_.push({
-      params: { reportId: reportId, },
-      result: { success: success, buffer: reportData },
+      params: {reportId},
+      result: {success, buffer: reportData},
     });
   }
 
@@ -67,8 +74,8 @@
   // |buffer|.
   queueExpectedSendFeatureReport(success, reportId, reportData) {
     this.expectedSendFeatureReports_.push({
-      params: { reportId: reportId, data: reportData },
-      result: { success: success },
+      params: {reportId, data: reportData},
+      result: {success},
     });
   }
 
@@ -79,6 +86,8 @@
     assert_equals(this.expectedSendFeatureReports_.length, 0);
   }
 
+  read() {}
+
   // Implementation of HidConnection::Write. Causes an assertion failure if
   // there are no expected write operations, or if the parameters do not match
   // the expected call.
@@ -133,10 +142,9 @@
 // granted permission.
 class FakeHidService {
   constructor() {
-    this.interceptor_ =
-        new MojoInterfaceInterceptor(blink.mojom.HidService.name);
+    this.interceptor_ = new MojoInterfaceInterceptor(HidService.$interfaceName);
     this.interceptor_.oninterfacerequest = e => this.bind(e.handle);
-    this.bindingSet_ = new mojo.BindingSet(blink.mojom.HidService);
+    this.receiver_ = new HidServiceReceiver(this);
     this.nextGuidValue_ = 0;
     this.reset();
   }
@@ -159,7 +167,7 @@
   // Creates and returns a HidDeviceInfo with the specified device IDs.
   makeDevice(vendorId, productId) {
     let guidValue = ++this.nextGuidValue_;
-    let info = new device.mojom.HidDeviceInfo();
+    let info = new HidDeviceInfo();
     info.guid = 'guid-' + guidValue.toString();
     info.physicalDeviceId = 'physical-device-id-' + guidValue.toString();
     info.vendorId = vendorId;
@@ -214,11 +222,11 @@
   }
 
   bind(handle) {
-    this.bindingSet_.addBinding(this, handle);
+    this.receiver_.$.bindHandle(handle);
   }
 
   registerClient(client) {
-    this.client_ = new device.mojom.HidManagerClientAssociatedPtr(client);
+    this.client_ = client;
   }
 
   // Returns an array of connected devices. Normally this would only include
@@ -229,30 +237,28 @@
     this.devices_.forEach((value) => {
       devices = devices.concat(value);
     });
-    return { devices: devices };
+    return {devices};
   }
 
   // Simulates a device chooser prompt, returning |selectedDevices_| as the
   // simulated selection. |filters| is ignored.
   async requestDevice(filters) {
-    return { devices: this.selectedDevices_ };
+    return {devices: this.selectedDevices_};
   }
 
   // Returns a fake connection to the device with the specified GUID. If
   // |connectionClient| is not null, its onInputReport method will be called
   // when input reports are received.
   async connect(guid, connectionClient) {
-    let fakeConnection = new FakeHidConnection(connectionClient);
-    let connectionPtr = new device.mojom.HidConnectionPtr();
-    fakeConnection.bind(mojo.makeRequest(connectionPtr));
+    const fakeConnection = new FakeHidConnection(connectionClient);
     this.fakeConnections_.set(guid, fakeConnection);
-    return { connection: connectionPtr };
+    return {connection: fakeConnection.bindNewPipeAndPassRemote()};
   }
 }
 
 let fakeHidService = new FakeHidService();
 
-function hid_test(func, name, properties) {
+export function hid_test(func, name, properties) {
   promise_test(async (test) => {
     fakeHidService.start();
     try {
@@ -264,7 +270,7 @@
   }, name, properties);
 }
 
-function trustedClick() {
+export function trustedClick() {
   return new Promise(resolve => {
     let button = document.createElement('button');
     button.textContent = 'click to continue test';
diff --git a/third_party/blink/web_tests/plugins/embed-src-change-usecounter.html b/third_party/blink/web_tests/plugins/embed-src-change-usecounter.html
new file mode 100644
index 0000000..771ed59
--- /dev/null
+++ b/third_party/blink/web_tests/plugins/embed-src-change-usecounter.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<script src="../resources/testharness.js"></script>
+<script src="../resources/testharnessreport.js"></script>
+
+<embed id=embedid src="../http/tests/resources/test.mp4">
+
+<script>
+test(() => {
+  embedid.src = '../http/tests/resources/test.webm';
+  assert_true(internals.isUseCounted(document, 3745));
+}, 'Verifies that changing embed src gets use counted.');
+</script>
diff --git a/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-properties-after-frame-navigated-expected.txt b/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-properties-after-frame-navigated-expected.txt
index 493dd253..d7c52ed2 100644
--- a/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-properties-after-frame-navigated-expected.txt
+++ b/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-properties-after-frame-navigated-expected.txt
@@ -42,6 +42,8 @@
 PASS window.cached_navigator_mediaSession.playbackState is 'none'
 PASS window.cached_navigator_presentation.defaultRequest is null
 PASS window.cached_navigator_presentation.receiver is null
+PASS window.cached_navigator_serial.onconnect is null
+PASS window.cached_navigator_serial.ondisconnect is null
 PASS window.cached_navigator_serviceWorker.controller is null
 PASS window.cached_navigator_serviceWorker.oncontrollerchange is null
 PASS window.cached_navigator_serviceWorker.onmessage is null
diff --git a/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-properties-after-frame-removed-and-gced-expected.txt b/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-properties-after-frame-removed-and-gced-expected.txt
index e2c76e8..463e454 100644
--- a/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-properties-after-frame-removed-and-gced-expected.txt
+++ b/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-properties-after-frame-removed-and-gced-expected.txt
@@ -42,6 +42,8 @@
 PASS window.cached_navigator_mediaSession.playbackState is 'none'
 PASS window.cached_navigator_presentation.defaultRequest is null
 PASS window.cached_navigator_presentation.receiver is null
+PASS window.cached_navigator_serial.onconnect is null
+PASS window.cached_navigator_serial.ondisconnect is null
 PASS window.cached_navigator_serviceWorker.controller is null
 PASS window.cached_navigator_serviceWorker.oncontrollerchange is null
 PASS window.cached_navigator_serviceWorker.onmessage is null
diff --git a/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-properties-after-frame-removed-expected.txt b/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-properties-after-frame-removed-expected.txt
index b8d1facc..350b91e 100644
--- a/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-properties-after-frame-removed-expected.txt
+++ b/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-properties-after-frame-removed-expected.txt
@@ -42,6 +42,8 @@
 PASS window.cached_navigator_mediaSession.playbackState is 'none'
 PASS window.cached_navigator_presentation.defaultRequest is null
 PASS window.cached_navigator_presentation.receiver is null
+PASS window.cached_navigator_serial.onconnect is null
+PASS window.cached_navigator_serial.ondisconnect is null
 PASS window.cached_navigator_serviceWorker.controller is null
 PASS window.cached_navigator_serviceWorker.oncontrollerchange is null
 PASS window.cached_navigator_serviceWorker.onmessage is null
diff --git a/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-window-after-frame-navigated-expected.txt b/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-window-after-frame-navigated-expected.txt
index 77c9e4ec..e8c74642 100644
--- a/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-window-after-frame-navigated-expected.txt
+++ b/third_party/blink/web_tests/virtual/stable/fast/dom/Window/property-access-on-cached-window-after-frame-navigated-expected.txt
@@ -48,6 +48,8 @@
 PASS oldChildWindow.navigator.presentation.receiver is newChildWindow.navigator.presentation.receiver
 PASS oldChildWindow.navigator.product is newChildWindow.navigator.product
 PASS oldChildWindow.navigator.productSub is newChildWindow.navigator.productSub
+PASS oldChildWindow.navigator.serial.onconnect is newChildWindow.navigator.serial.onconnect
+PASS oldChildWindow.navigator.serial.ondisconnect is newChildWindow.navigator.serial.ondisconnect
 PASS oldChildWindow.navigator.serviceWorker.controller is newChildWindow.navigator.serviceWorker.controller
 PASS oldChildWindow.navigator.serviceWorker.oncontrollerchange is newChildWindow.navigator.serviceWorker.oncontrollerchange
 PASS oldChildWindow.navigator.serviceWorker.onmessage is newChildWindow.navigator.serviceWorker.onmessage
diff --git a/third_party/blink/web_tests/virtual/stable/webexposed/feature-policy-features-expected.txt b/third_party/blink/web_tests/virtual/stable/webexposed/feature-policy-features-expected.txt
index 9b14739..c0da977 100644
--- a/third_party/blink/web_tests/virtual/stable/webexposed/feature-policy-features-expected.txt
+++ b/third_party/blink/web_tests/virtual/stable/webexposed/feature-policy-features-expected.txt
@@ -35,6 +35,7 @@
 picture-in-picture
 publickey-credentials-get
 screen-wake-lock
+serial
 sync-xhr
 usb
 xr-spatial-tracking
diff --git a/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-dedicated-worker-expected.txt b/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-dedicated-worker-expected.txt
index 58e9629c..1142718 100644
--- a/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-dedicated-worker-expected.txt
+++ b/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-dedicated-worker-expected.txt
@@ -1116,6 +1116,28 @@
 [Worker]     getter statusCode
 [Worker]     getter violatedDirective
 [Worker]     method constructor
+[Worker] interface Serial : EventTarget
+[Worker]     attribute @@toStringTag
+[Worker]     getter onconnect
+[Worker]     getter ondisconnect
+[Worker]     method constructor
+[Worker]     method getPorts
+[Worker]     setter onconnect
+[Worker]     setter ondisconnect
+[Worker] interface SerialPort : EventTarget
+[Worker]     attribute @@toStringTag
+[Worker]     getter onconnect
+[Worker]     getter ondisconnect
+[Worker]     getter readable
+[Worker]     getter writable
+[Worker]     method close
+[Worker]     method constructor
+[Worker]     method getInfo
+[Worker]     method getSignals
+[Worker]     method open
+[Worker]     method setSignals
+[Worker]     setter onconnect
+[Worker]     setter ondisconnect
 [Worker] interface ServiceWorkerRegistration : EventTarget
 [Worker]     attribute @@toStringTag
 [Worker]     getter active
@@ -2745,6 +2767,7 @@
 [Worker]     getter permissions
 [Worker]     getter platform
 [Worker]     getter product
+[Worker]     getter serial
 [Worker]     getter storage
 [Worker]     getter usb
 [Worker]     getter userAgent
diff --git a/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-expected.txt b/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-expected.txt
index 1d964008..31af8656 100644
--- a/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-expected.txt
+++ b/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-expected.txt
@@ -4599,6 +4599,7 @@
     getter product
     getter productSub
     getter scheduling
+    getter serial
     getter serviceWorker
     getter storage
     getter usb
@@ -6999,6 +7000,29 @@
     attribute @@toStringTag
     getter error
     method constructor
+interface Serial : EventTarget
+    attribute @@toStringTag
+    getter onconnect
+    getter ondisconnect
+    method constructor
+    method getPorts
+    method requestPort
+    setter onconnect
+    setter ondisconnect
+interface SerialPort : EventTarget
+    attribute @@toStringTag
+    getter onconnect
+    getter ondisconnect
+    getter readable
+    getter writable
+    method close
+    method constructor
+    method getInfo
+    method getSignals
+    method open
+    method setSignals
+    setter onconnect
+    setter ondisconnect
 interface ServiceWorker : EventTarget
     attribute @@toStringTag
     getter onerror
diff --git a/third_party/boringssl/BUILD.gn b/third_party/boringssl/BUILD.gn
index fab8905..17cf9cda 100644
--- a/third_party/boringssl/BUILD.gn
+++ b/third_party/boringssl/BUILD.gn
@@ -96,7 +96,9 @@
     } else if (current_cpu == "arm64") {
       if (is_linux || is_chromeos || is_android) {
         sources += crypto_sources_linux_aarch64
-      } else if (is_ios) {
+      } else if (is_apple) {
+        # TODO(davidben): Rename all the file lists, etc., upstream from mac
+        # and ios to apple.
         sources += crypto_sources_ios_aarch64
       } else {
         public_configs = [ ":no_asm_config" ]
diff --git a/third_party/libgav1/BUILD.gn b/third_party/libgav1/BUILD.gn
index 85add2a09..e4d870b 100644
--- a/third_party/libgav1/BUILD.gn
+++ b/third_party/libgav1/BUILD.gn
@@ -66,6 +66,7 @@
     public_configs = [ ":public_libgav1_config" ]
 
     sources = gav1_dsp_sources + gav1_dsp_headers_sources
+    sources += gav1_dsp_avx2_sources + gav1_dsp_avx2_headers_sources
   }
 
   # SSE4 sources are split to their own target as Chrome is currently built
@@ -83,7 +84,7 @@
     }
 
     sources = gav1_dsp_sse4_sources + gav1_dsp_sse4_headers_sources +
-              gav1_dsp_headers_sources
+              gav1_dsp_headers_sources + gav1_dsp_avx2_headers_sources
   }
 
   static_library("libgav1") {
diff --git a/third_party/libgav1/README.chromium b/third_party/libgav1/README.chromium
index 3cd0bda..8004e1e 100644
--- a/third_party/libgav1/README.chromium
+++ b/third_party/libgav1/README.chromium
@@ -2,9 +2,9 @@
 Short Name: libgav1
 URL: https://chromium.googlesource.com/codecs/libgav1/
 Version: 0
-Date: Saturday August 22 2020
+Date: Wednesday January 06 2021
 Branch: master
-Commit: a9449e612bc251b4271bbe1e3a0d12e9809bf74c
+Commit: a5ee0e00923c355ef3aad2b2829365a9fde84430
 License: Apache 2.0
 License File: libgav1/LICENSE
 Security Critical: yes
diff --git a/third_party/libgav1/generate_libgav1_src_gni.go b/third_party/libgav1/generate_libgav1_src_gni.go
index e84d6bb7..ef6c044 100644
--- a/third_party/libgav1/generate_libgav1_src_gni.go
+++ b/third_party/libgav1/generate_libgav1_src_gni.go
@@ -34,10 +34,17 @@
 	for _, file := range files {
 		if file.IsDir() {
 			paths = append(paths, getCppFiles(filepath.Join(dir, file.Name()))...)
+			continue
 		}
 		ext := filepath.Ext(file.Name())
 		if ext == ".cc" || ext == ".h" {
-			paths = append(paths, filepath.Join(dir, file.Name()))
+			isTestFile, err := filepath.Match("*_test.*", file.Name())
+			if err != nil {
+				panic(err)
+			}
+			if !isTestFile {
+				paths = append(paths, filepath.Join(dir, file.Name()))
+			}
 		}
 	}
 	return paths
@@ -102,7 +109,7 @@
 		for _, d := range topDirs {
 			if strings.HasPrefix(f, d) {
 				var bd string
-				for _, asm := range []string{"sse4"} {
+				for _, asm := range []string{"sse4", "avx2"} {
 					pattern := "*_" + asm + "*"
 					if match, err := filepath.Match(pattern, filepath.Base(f)); err != nil {
 						panic(err)
diff --git a/third_party/libgav1/libgav1_srcs.gni b/third_party/libgav1/libgav1_srcs.gni
index 92ba942..6ac5fef 100644
--- a/third_party/libgav1/libgav1_srcs.gni
+++ b/third_party/libgav1/libgav1_srcs.gni
@@ -84,6 +84,18 @@
   "//third_party/libgav1/src/src/dsp/weight_mask.cc",
 ]
 
+gav1_dsp_avx2_sources = [
+  "//third_party/libgav1/src/src/dsp/x86/convolve_avx2.cc",
+  "//third_party/libgav1/src/src/dsp/x86/loop_restoration_10bit_avx2.cc",
+  "//third_party/libgav1/src/src/dsp/x86/loop_restoration_avx2.cc",
+]
+
+gav1_dsp_avx2_headers_sources = [
+  "//third_party/libgav1/src/src/dsp/x86/common_avx2.h",
+  "//third_party/libgav1/src/src/dsp/x86/convolve_avx2.h",
+  "//third_party/libgav1/src/src/dsp/x86/loop_restoration_avx2.h",
+]
+
 gav1_dsp_headers_sources = [
   "//third_party/libgav1/src/src/dsp/arm/average_blend_neon.h",
   "//third_party/libgav1/src/src/dsp/arm/cdef_neon.h",
@@ -131,12 +143,14 @@
   "//third_party/libgav1/src/src/dsp/x86/cdef_sse4.cc",
   "//third_party/libgav1/src/src/dsp/x86/convolve_sse4.cc",
   "//third_party/libgav1/src/src/dsp/x86/distance_weighted_blend_sse4.cc",
+  "//third_party/libgav1/src/src/dsp/x86/film_grain_sse4.cc",
   "//third_party/libgav1/src/src/dsp/x86/intra_edge_sse4.cc",
   "//third_party/libgav1/src/src/dsp/x86/intrapred_cfl_sse4.cc",
   "//third_party/libgav1/src/src/dsp/x86/intrapred_smooth_sse4.cc",
   "//third_party/libgav1/src/src/dsp/x86/intrapred_sse4.cc",
   "//third_party/libgav1/src/src/dsp/x86/inverse_transform_sse4.cc",
   "//third_party/libgav1/src/src/dsp/x86/loop_filter_sse4.cc",
+  "//third_party/libgav1/src/src/dsp/x86/loop_restoration_10bit_sse4.cc",
   "//third_party/libgav1/src/src/dsp/x86/loop_restoration_sse4.cc",
   "//third_party/libgav1/src/src/dsp/x86/mask_blend_sse4.cc",
   "//third_party/libgav1/src/src/dsp/x86/motion_field_projection_sse4.cc",
@@ -153,6 +167,7 @@
   "//third_party/libgav1/src/src/dsp/x86/common_sse4.h",
   "//third_party/libgav1/src/src/dsp/x86/convolve_sse4.h",
   "//third_party/libgav1/src/src/dsp/x86/distance_weighted_blend_sse4.h",
+  "//third_party/libgav1/src/src/dsp/x86/film_grain_sse4.h",
   "//third_party/libgav1/src/src/dsp/x86/intra_edge_sse4.h",
   "//third_party/libgav1/src/src/dsp/x86/intrapred_sse4.h",
   "//third_party/libgav1/src/src/dsp/x86/inverse_transform_sse4.h",
diff --git a/tools/mb/mb_config.pyl b/tools/mb/mb_config.pyl
index c73dd1d..8fc5f5a 100644
--- a/tools/mb/mb_config.pyl
+++ b/tools/mb/mb_config.pyl
@@ -994,6 +994,7 @@
       'linux-perfetto-rel': 'perfetto_release_trybot',
       'linux-rel': 'gpu_tests_release_trybot_no_symbols_use_dummy_lastchange_code_coverage',
       'linux-rel-builderful': 'gpu_tests_release_trybot_no_symbols_use_dummy_lastchange_code_coverage',
+      'linux-rts': 'release_trybot_rts',
       'linux-trusty-rel': 'gpu_tests_release_trybot_no_symbols_use_dummy_lastchange',
       'linux-viz-rel': 'release_trybot',
       'linux-warmed': 'release_trybot_no_symbols_use_dummy_lastchange_code_coverage',
@@ -2592,6 +2593,10 @@
       'no_goma',
     ],
 
+    'release_trybot_rts': [
+      'release_bot', 'rts_bot',
+    ],
+
     'tsan_disable_nacl_debug_bot': [
       'tsan', 'disable_nacl', 'debug_bot',
     ],
@@ -3238,6 +3243,10 @@
       'gn_args': 'enable_resource_allowlist_generation=true',
     },
 
+    'rts_bot': {
+      'gn_args': 'rts_exclude_file="//testing/rts_exclude_file.txt"',
+    },
+
     'shared': {
       'gn_args': 'is_component_build=true',
     },
diff --git a/tools/mb/mb_config_expectations/tryserver.chromium.linux.json b/tools/mb/mb_config_expectations/tryserver.chromium.linux.json
index f58bdfc..b786fde6 100644
--- a/tools/mb/mb_config_expectations/tryserver.chromium.linux.json
+++ b/tools/mb/mb_config_expectations/tryserver.chromium.linux.json
@@ -554,6 +554,14 @@
       "use_goma": true
     }
   },
+  "linux-rts": {
+    "gn_args": {
+      "is_component_build": false,
+      "is_debug": false,
+      "rts_exclude_file": "//testing/rts_exclude_file.txt",
+      "use_goma": true
+    }
+  },
   "linux-trusty-rel": {
     "gn_args": {
       "dcheck_always_on": true,
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index dddd234e..27e293f 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -16721,6 +16721,7 @@
   <int value="0" label="Drag and drop"/>
   <int value="1" label="Move-window-to-desk keyboard shortcut"/>
   <int value="2" label="Send to desk menu"/>
+  <int value="3" label="Assign to desk menu"/>
 </enum>
 
 <enum name="DesksSwitchSource">
@@ -30754,6 +30755,7 @@
   <int value="3742" label="CrossOriginSubframeWithoutEmbeddingControl"/>
   <int value="3743" label="ReadableStreamWithByteSource"/>
   <int value="3744" label="ReadableStreamBYOBReader"/>
+  <int value="3745" label="EmbedElementWithoutTypeSrcChanged"/>
 </enum>
 
 <enum name="FeaturePolicyAllowlistType">
@@ -66758,7 +66760,6 @@
   <int value="6" label="SYNC_UPDATE_SUCCESS"/>
   <int value="7" label="SYNC_UPDATE_CONVERTED_TO_ADD"/>
   <int value="8" label="MIGRATE_SAFE_FOR_AUTOREPLACE_PLAY_API_ENGINE"/>
-  <int value="9" label="MIGRATE_SAFE_FOR_AUTOREPLACE_RESET_UNDERSCORE_KEYWORD"/>
 </enum>
 
 <enum name="SearchWidgetUseInfo">
@@ -74324,6 +74325,19 @@
   <int value="15" label="Automatic translation, by user preference"/>
 </enum>
 
+<enum name="TranslateUIInteraction">
+  <int value="0" label="Uninitialized"/>
+  <int value="1" label="Translate page"/>
+  <int value="2" label="Revert translation"/>
+  <int value="3" label="Always translate language"/>
+  <int value="4" label="Change source language"/>
+  <int value="5" label="Change target language"/>
+  <int value="6" label="Never translate language"/>
+  <int value="7" label="Never translate site"/>
+  <int value="8" label="Close UI explicitly"/>
+  <int value="9" label="Close UI by lost focus"/>
+</enum>
+
 <enum name="TrendingTileEvent">
   <int value="0" label="Tile was shown"/>
   <int value="1" label="Tile was removed"/>
diff --git a/tools/metrics/histograms/histograms_xml/crostini/histograms.xml b/tools/metrics/histograms/histograms_xml/crostini/histograms.xml
index f841a65..347a2d8 100644
--- a/tools/metrics/histograms/histograms_xml/crostini/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/crostini/histograms.xml
@@ -35,7 +35,7 @@
 </variants>
 
 <histogram name="Crostini.AppLaunch" enum="CrostiniAppLaunchAppType"
-    expires_after="2021-01-31">
+    expires_after="2022-01-06">
   <owner>clumptini@google.com</owner>
   <owner>tbuckley@chromium.org</owner>
   <summary>
@@ -45,7 +45,7 @@
 </histogram>
 
 <histogram name="Crostini.AppLaunchResult" enum="CrostiniResult"
-    expires_after="2021-05-30">
+    expires_after="2022-01-06">
   <owner>clumptini@google.com</owner>
   <owner>tbuckley@chromium.org</owner>
   <summary>
@@ -54,7 +54,7 @@
 </histogram>
 
 <histogram name="Crostini.AppsInstalledAtLogin" units="apps"
-    expires_after="2021-06-06">
+    expires_after="2022-01-06">
   <owner>clumptini@google.com</owner>
   <owner>tbuckley@chromium.org</owner>
   <summary>
@@ -65,7 +65,7 @@
 </histogram>
 
 <histogram name="Crostini.AvailableDiskCancel" units="MiB"
-    expires_after="2021-01-31">
+    expires_after="2022-01-06">
   <owner>clumptini@google.com</owner>
   <owner>tbuckley@chromium.org</owner>
   <summary>
@@ -77,7 +77,7 @@
 </histogram>
 
 <histogram name="Crostini.AvailableDiskError" units="MiB"
-    expires_after="2021-01-31">
+    expires_after="2022-01-06">
   <owner>clumptini@google.com</owner>
   <owner>tbuckley@chromium.org</owner>
   <summary>
@@ -89,7 +89,7 @@
 </histogram>
 
 <histogram name="Crostini.AvailableDiskSuccess" units="MiB"
-    expires_after="2021-01-31">
+    expires_after="2022-01-06">
   <owner>clumptini@google.com</owner>
   <owner>tbuckley@chromium.org</owner>
   <summary>
@@ -101,14 +101,14 @@
 </histogram>
 
 <histogram name="Crostini.Backup" enum="CrostiniExportContainerResult"
-    expires_after="2021-06-06">
+    expires_after="2022-01-06">
   <owner>clumptini@google.com</owner>
   <owner>tbuckley@chromium.org</owner>
   <summary>Result of crostini backup.</summary>
 </histogram>
 
 <histogram name="Crostini.BackupCompressedSizeLog2" units="units"
-    expires_after="2021-01-31">
+    expires_after="2022-01-06">
   <owner>clumptini@google.com</owner>
   <owner>tbuckley@chromium.org</owner>
   <summary>
@@ -119,7 +119,7 @@
 </histogram>
 
 <histogram name="Crostini.BackupContainerSizeLog2" units="units"
-    expires_after="2021-01-31">
+    expires_after="2022-01-06">
   <owner>clumptini@google.com</owner>
   <owner>tbuckley@chromium.org</owner>
   <summary>
@@ -130,7 +130,7 @@
 </histogram>
 
 <histogram name="Crostini.BackupSizeRatio" units="units"
-    expires_after="2021-01-31">
+    expires_after="2022-01-06">
   <owner>clumptini@google.com</owner>
   <owner>tbuckley@chromium.org</owner>
   <summary>
@@ -140,21 +140,21 @@
 </histogram>
 
 <histogram name="Crostini.BackupTimeFailed" units="ms"
-    expires_after="2021-06-06">
+    expires_after="2022-01-06">
   <owner>clumptini@google.com</owner>
   <owner>tbuckley@chromium.org</owner>
   <summary>Time taken for failed backup.</summary>
 </histogram>
 
 <histogram name="Crostini.BackupTimeSuccess" units="ms"
-    expires_after="2021-06-06">
+    expires_after="2022-01-06">
   <owner>clumptini@google.com</owner>
   <owner>tbuckley@chromium.org</owner>
   <summary>Time taken for successful backup.</summary>
 </histogram>
 
 <histogram name="Crostini.CleanSession.RestarterResult" enum="CrostiniResult"
-    expires_after="2021-06-13">
+    expires_after="2022-01-06">
   <owner>clumptini@google.com</owner>
   <owner>tbuckley@chromium.org</owner>
   <summary>
@@ -165,7 +165,7 @@
 </histogram>
 
 <histogram name="Crostini.ContainerOsVersion" enum="CrostiniContainerOsVersion"
-    expires_after="2021-06-06">
+    expires_after="2022-01-06">
   <owner>clumptini@google.com</owner>
   <owner>tbuckley@chromium.org</owner>
   <summary>
@@ -175,7 +175,7 @@
 </histogram>
 
 <histogram name="Crostini.Crosvm.CpuPercentage" units="%"
-    expires_after="2021-06-20">
+    expires_after="2022-01-06">
   <owner>clumptini@google.com</owner>
   <owner>tbuckley@chromium.org</owner>
   <summary>
@@ -186,7 +186,7 @@
 </histogram>
 
 <histogram name="Crostini.Crosvm.Processes.Count" units="processes"
-    expires_after="2021-06-06">
+    expires_after="2022-01-06">
   <owner>clumptini@google.com</owner>
   <owner>tbuckley@chromium.org</owner>
   <summary>
@@ -196,7 +196,7 @@
 </histogram>
 
 <histogram name="Crostini.Crosvm.RssPercentage" units="%"
-    expires_after="2021-06-13">
+    expires_after="2022-01-06">
   <owner>clumptini@google.com</owner>
   <owner>tbuckley@chromium.org</owner>
   <summary>
@@ -207,7 +207,7 @@
 </histogram>
 
 <histogram name="Crostini.DiskResize.Result" enum="CrostiniDiskImageStatus"
-    expires_after="2021-05-30">
+    expires_after="2022-01-06">
   <owner>clumptini@google.com</owner>
   <owner>davidmunro@google.com</owner>
   <summary>
@@ -217,7 +217,7 @@
 </histogram>
 
 <histogram name="Crostini.DiskResize.Started" enum="BooleanAttempted"
-    expires_after="2021-05-30">
+    expires_after="2022-01-06">
   <owner>clumptini@google.com</owner>
   <owner>davidmunro@google.com</owner>
   <summary>
@@ -229,7 +229,7 @@
 </histogram>
 
 <histogram name="Crostini.DiskType" enum="CrostiniDiskImageType"
-    expires_after="2021-03-31">
+    expires_after="2022-01-06">
   <owner>clumptini@google.com</owner>
   <owner>davidmunro@google.com</owner>
   <summary>
@@ -242,7 +242,7 @@
 </histogram>
 
 <histogram name="Crostini.EngagementTime.{Variant}" units="ms"
-    expires_after="2021-04-18">
+    expires_after="2022-01-06">
   <owner>davidmunro@google.com</owner>
   <owner>clumptini@google.com</owner>
   <summary>
@@ -274,7 +274,7 @@
 </histogram>
 
 <histogram name="Crostini.FilesystemCorruption" enum="CorruptionStates"
-    expires_after="2021-06-13">
+    expires_after="2022-01-06">
   <owner>clumptini@google.com</owner>
   <owner>tbuckley@chromium.org</owner>
   <summary>
@@ -284,7 +284,7 @@
 </histogram>
 
 <histogram name="Crostini.InvalidStateTransition" enum="CrostiniInstallerState"
-    expires_after="2021-04-04">
+    expires_after="2022-01-06">
   <owner>clumptini@google.com</owner>
   <owner>tbuckley@chromium.org</owner>
   <summary>
@@ -297,7 +297,7 @@
 </histogram>
 
 <histogram name="Crostini.RecoverySource" enum="CrostiniUISurface"
-    expires_after="2021-01-31">
+    expires_after="2022-01-06">
   <owner>clumptini@google.com</owner>
   <owner>tbuckley@chromium.org</owner>
   <summary>
@@ -307,7 +307,7 @@
 </histogram>
 
 <histogram name="Crostini.Restarter.Started" enum="BooleanAttempted"
-    expires_after="2021-06-06">
+    expires_after="2022-01-06">
   <owner>clumptini@google.com</owner>
   <owner>tbuckley@chromium.org</owner>
   <summary>
@@ -317,7 +317,7 @@
 </histogram>
 
 <histogram name="Crostini.RestarterResult" enum="CrostiniResult"
-    expires_after="2021-06-06">
+    expires_after="2022-01-06">
   <owner>clumptini@google.com</owner>
   <owner>tbuckley@chromium.org</owner>
   <summary>
@@ -327,7 +327,7 @@
 </histogram>
 
 <histogram name="Crostini.RestarterTimeInState.{state}" units="ms"
-    expires_after="2021-05-16">
+    expires_after="2022-01-06">
   <owner>clumptini@google.com</owner>
   <owner>tbuckley@chromium.org</owner>
   <summary>
@@ -340,28 +340,28 @@
 </histogram>
 
 <histogram name="Crostini.Restore" enum="CrostiniImportContainerResult"
-    expires_after="2021-06-06">
+    expires_after="2022-01-06">
   <owner>clumptini@google.com</owner>
   <owner>tbuckley@chromium.org</owner>
   <summary>Result of crostini restore.</summary>
 </histogram>
 
 <histogram name="Crostini.RestoreTimeFailed" units="ms"
-    expires_after="2021-01-31">
+    expires_after="2022-01-06">
   <owner>clumptini@google.com</owner>
   <owner>tbuckley@chromium.org</owner>
   <summary>Time taken for failed restore.</summary>
 </histogram>
 
 <histogram name="Crostini.RestoreTimeSuccess" units="ms"
-    expires_after="2021-01-31">
+    expires_after="2022-01-06">
   <owner>clumptini@google.com</owner>
   <owner>tbuckley@chromium.org</owner>
   <summary>Time taken for successful restore.</summary>
 </histogram>
 
 <histogram name="Crostini.SettingsEvent" enum="CrostiniSettingsEvent"
-    expires_after="2021-04-18">
+    expires_after="2022-01-06">
   <owner>clumptini@google.com</owner>
   <owner>tbuckley@chromium.org</owner>
   <owner>victorhsieh@chromium.org</owner>
@@ -369,7 +369,7 @@
 </histogram>
 
 <histogram name="Crostini.Setup.Started" enum="BooleanAttempted"
-    expires_after="2021-01-31">
+    expires_after="2022-01-06">
   <owner>clumptini@google.com</owner>
   <owner>tbuckley@chromium.org</owner>
   <summary>
@@ -379,7 +379,7 @@
 </histogram>
 
 <histogram name="Crostini.SetupResult" enum="CrostiniSetupResult"
-    expires_after="2021-06-06">
+    expires_after="2022-01-06">
   <owner>clumptini@google.com</owner>
   <owner>tbuckley@chromium.org</owner>
   <summary>
@@ -389,7 +389,7 @@
 </histogram>
 
 <histogram name="Crostini.SetupSource" enum="CrostiniUISurface"
-    expires_after="2021-01-31">
+    expires_after="2022-01-06">
   <owner>clumptini@google.com</owner>
   <owner>tbuckley@chromium.org</owner>
   <summary>
@@ -399,7 +399,7 @@
 </histogram>
 
 <histogram name="Crostini.Stability" enum="GuestOsFailureClasses"
-    expires_after="2021-06-09">
+    expires_after="2022-01-06">
   <owner>clumptini@google.com</owner>
   <owner>tbuckley@chromium.org</owner>
   <summary>
@@ -410,7 +410,7 @@
 </histogram>
 
 <histogram name="Crostini.TerminalSettingsChanged"
-    enum="CrostiniTerminalSetting" expires_after="2021-03-31">
+    enum="CrostiniTerminalSetting" expires_after="2022-01-06">
   <owner>joelhockey@chromium.org</owner>
   <owner>tbuckley@chromium.org</owner>
   <summary>
@@ -422,7 +422,7 @@
 </histogram>
 
 <histogram name="Crostini.TimeFromDeviceSetupToInstall" units="ms"
-    expires_after="2021-06-20">
+    expires_after="2022-01-06">
   <owner>clumptini@google.com</owner>
   <owner>tbuckley@chromium.org</owner>
   <summary>
@@ -431,7 +431,7 @@
 </histogram>
 
 <histogram name="Crostini.TimeToInstallCancel" units="ms"
-    expires_after="2021-06-20">
+    expires_after="2022-01-06">
   <owner>clumptini@google.com</owner>
   <owner>tbuckley@chromium.org</owner>
   <summary>
@@ -442,7 +442,7 @@
 </histogram>
 
 <histogram name="Crostini.TimeToInstallError" units="ms"
-    expires_after="2021-01-31">
+    expires_after="2022-01-06">
   <owner>clumptini@google.com</owner>
   <owner>tbuckley@chromium.org</owner>
   <summary>
@@ -454,7 +454,7 @@
 </histogram>
 
 <histogram name="Crostini.TimeToInstallSuccess" units="ms"
-    expires_after="2021-05-16">
+    expires_after="2022-01-06">
   <owner>clumptini@google.com</owner>
   <owner>tbuckley@chromium.org</owner>
   <summary>
@@ -465,7 +465,7 @@
 </histogram>
 
 <histogram name="Crostini.UncleanSession.RestarterResult" enum="CrostiniResult"
-    expires_after="2021-06-27">
+    expires_after="2022-01-06">
   <owner>clumptini@google.com</owner>
   <owner>tbuckley@chromium.org</owner>
   <summary>
@@ -476,7 +476,7 @@
 </histogram>
 
 <histogram name="Crostini.UninstallResult" enum="CrostiniUninstallResult"
-    expires_after="2021-06-06">
+    expires_after="2022-01-06">
   <owner>clumptini@google.com</owner>
   <owner>tbuckley@chromium.org</owner>
   <summary>
@@ -486,7 +486,7 @@
 </histogram>
 
 <histogram name="Crostini.UninstallSource" enum="CrostiniUISurface"
-    expires_after="2021-01-31">
+    expires_after="2022-01-06">
   <owner>clumptini@google.com</owner>
   <owner>tbuckley@chromium.org</owner>
   <summary>
@@ -496,7 +496,7 @@
 </histogram>
 
 <histogram base="true" name="Crostini.UnsupportedNotification.Reason"
-    enum="CrostiniUnsupportedNotificationReason" expires_after="2021-01-31">
+    enum="CrostiniUnsupportedNotificationReason" expires_after="2022-01-06">
   <owner>clumptini@google.com</owner>
   <owner>tbuckley@chromium.org</owner>
   <summary>
@@ -508,7 +508,7 @@
 
 <histogram name="Crostini.UpgradeAvailable"
     enum="CrostiniUpgradeAvailableNotificationClosed"
-    expires_after="2021-07-15">
+    expires_after="2022-01-06">
   <owner>clumptini@google.com</owner>
   <owner>tbuckley@chromium.org</owner>
   <summary>
@@ -518,7 +518,7 @@
 </histogram>
 
 <histogram name="Crostini.UpgradeDialogEvent" enum="CrostiniUpgradeDialogEvent"
-    expires_after="2021-05-09">
+    expires_after="2022-01-06">
   <owner>clumptini@google.com</owner>
   <owner>tbuckley@chromium.org</owner>
   <summary>
@@ -528,7 +528,7 @@
 </histogram>
 
 <histogram name="Crostini.UpgradeSource" enum="CrostiniUISurface"
-    expires_after="2021-01-31">
+    expires_after="2022-01-06">
   <owner>clumptini@google.com</owner>
   <owner>tbuckley@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/histograms_xml/histogram_suffixes_list.xml b/tools/metrics/histograms/histograms_xml/histogram_suffixes_list.xml
index e3ac3c5..b7b66e30 100644
--- a/tools/metrics/histograms/histograms_xml/histogram_suffixes_list.xml
+++ b/tools/metrics/histograms/histograms_xml/histogram_suffixes_list.xml
@@ -8205,8 +8205,6 @@
   </suffix>
   <suffix name="FeedJournalDatabase"
       label="Database for Feed journal storage."/>
-  <suffix name="FeedKeyValueDatabase"
-      label="Database for key value cache used in feed rendering."/>
   <suffix name="FeedStorageDatabase" label="Databases for Feed Storage.">
     <obsolete>
       Deprecated since 08/18.
diff --git a/tools/metrics/histograms/histograms_xml/interstitial/histograms.xml b/tools/metrics/histograms/histograms_xml/interstitial/histograms.xml
index ce22312..81d402bc 100644
--- a/tools/metrics/histograms/histograms_xml/interstitial/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/interstitial/histograms.xml
@@ -30,7 +30,7 @@
 </histogram>
 
 <histogram name="interstitial.decision" enum="SecurityInterstitialDecision"
-    expires_after="M89">
+    expires_after="M94">
   <owner>estark@chromium.org</owner>
   <owner>carlosil@chromium.org</owner>
   <summary>
@@ -42,7 +42,7 @@
 </histogram>
 
 <histogram name="interstitial.decision.repeat_visit"
-    enum="SecurityInterstitialDecision" expires_after="M89">
+    enum="SecurityInterstitialDecision" expires_after="M94">
   <owner>felt@chromium.org</owner>
   <owner>carlosil@chromium.org</owner>
   <summary>
@@ -55,7 +55,7 @@
 </histogram>
 
 <histogram name="interstitial.interaction"
-    enum="SecurityInterstitialInteraction" expires_after="M89">
+    enum="SecurityInterstitialInteraction" expires_after="M94">
   <owner>estark@chromium.org</owner>
   <owner>carlosil@chromium.org</owner>
   <summary>
@@ -96,7 +96,7 @@
 </histogram>
 
 <histogram name="interstitial.ssl.cause.overridable" enum="SSLErrorCauses"
-    expires_after="M89">
+    expires_after="M94">
   <owner>estark@chromium.org</owner>
   <owner>carlosil@chromium.org</owner>
   <summary>
@@ -142,8 +142,9 @@
 </histogram>
 
 <histogram name="interstitial.ssl.did_user_revoke_decisions2"
-    enum="BooleanRevoked" expires_after="M89">
+    enum="BooleanRevoked" expires_after="M94">
   <owner>carlosil@chromium.org</owner>
+  <owner>security-enamel@chromium.org</owner>
   <summary>
     Specifies when a user enters the page info menu whether or not the user
     pressed the SSL decisions revoke button. This is logged when the page info
@@ -161,7 +162,7 @@
 </histogram>
 
 <histogram name="interstitial.ssl_error_handler" enum="SSLErrorHandlerEvent"
-    expires_after="M89">
+    expires_after="M94">
   <owner>meacer@chromium.org</owner>
   <owner>carlosil@chromium.org</owner>
   <owner>estark@chromium.org</owner>
@@ -184,7 +185,7 @@
 </histogram>
 
 <histogram name="interstitial.ssl_error_type" enum="SSLErrorTypes"
-    expires_after="M89">
+    expires_after="M94">
   <owner>estark@chromium.org</owner>
   <owner>carlosil@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/histograms_xml/media/histograms.xml b/tools/metrics/histograms/histograms_xml/media/histograms.xml
index 319e46d..18c3da7a 100644
--- a/tools/metrics/histograms/histograms_xml/media/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/media/histograms.xml
@@ -3589,7 +3589,7 @@
 </histogram>
 
 <histogram name="Media.VAJDA.ResponseToClient"
-    enum="MjpegDecodeAcceleratorErrorCode" expires_after="2020-03-21">
+    enum="MjpegDecodeAcceleratorErrorCode" expires_after="2022-01-07">
   <owner>andrescj@chromium.org</owner>
   <owner>chromeos-gfx@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/histograms_xml/navigation/histograms.xml b/tools/metrics/histograms/histograms_xml/navigation/histograms.xml
index 88dbf0c..72ec02b 100644
--- a/tools/metrics/histograms/histograms_xml/navigation/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/navigation/histograms.xml
@@ -619,7 +619,7 @@
   </summary>
 </histogram>
 
-<histogram name="Navigation.EngagementTime.HTTP" units="ms" expires_after="M89">
+<histogram name="Navigation.EngagementTime.HTTP" units="ms" expires_after="M94">
   <owner>estark@chromium.org</owner>
   <owner>security-enamel@chromium.org</owner>
   <summary>
@@ -629,7 +629,7 @@
 </histogram>
 
 <histogram name="Navigation.EngagementTime.HTTPS" units="ms"
-    expires_after="M89">
+    expires_after="M94">
   <owner>estark@chromium.org</owner>
   <owner>security-enamel@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/histograms_xml/omnibox/histograms.xml b/tools/metrics/histograms/histograms_xml/omnibox/histograms.xml
index a278c9e4..a2870393 100644
--- a/tools/metrics/histograms/histograms_xml/omnibox/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/omnibox/histograms.xml
@@ -336,7 +336,7 @@
   </summary>
 </histogram>
 
-<histogram name="Omnibox.HoverTime" units="ms" expires_after="M88">
+<histogram name="Omnibox.HoverTime" units="ms" expires_after="M94">
   <owner>estark@chromium.org</owner>
   <owner>carlosil@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/histograms_xml/printing/histograms.xml b/tools/metrics/histograms/histograms_xml/printing/histograms.xml
index a64f5af5..37e1f65 100644
--- a/tools/metrics/histograms/histograms_xml/printing/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/printing/histograms.xml
@@ -350,6 +350,9 @@
 
 <histogram name="Printing.CUPS.ValidPpdReference" enum="BooleanSuccess"
     expires_after="2020-08-01">
+  <obsolete>
+    Data no longer needed. Bug is resolved. 2020-12.
+  </obsolete>
   <owner>skau@chromium.org</owner>
   <owner>luum@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/histograms_xml/security/histograms.xml b/tools/metrics/histograms/histograms_xml/security/histograms.xml
index 8f67c0e..b564ae3 100644
--- a/tools/metrics/histograms/histograms_xml/security/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/security/histograms.xml
@@ -544,7 +544,7 @@
 </histogram>
 
 <histogram name="Security.SecurityLevel.InsecureMainFrameFormSubmission"
-    enum="SecurityLevel" expires_after="M89">
+    enum="SecurityLevel" expires_after="M94">
   <owner>estark@chromium.org</owner>
   <owner>livvielin@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/histograms_xml/tab/histograms.xml b/tools/metrics/histograms/histograms_xml/tab/histograms.xml
index 9d77af9c..5702aef 100644
--- a/tools/metrics/histograms/histograms_xml/tab/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/tab/histograms.xml
@@ -1505,7 +1505,7 @@
 </histogram>
 
 <histogram name="Tabs.SadTab.Feedback.Event" enum="SadTabEvent"
-    expires_after="M85">
+    expires_after="2021-06-01">
   <owner>sonnyrao@chromium.org</owner>
   <owner>jamescook@chromium.org</owner>
   <summary>
@@ -1599,7 +1599,7 @@
 </histogram>
 
 <histogram name="Tabs.SadTab.Reload.Event" enum="SadTabEvent"
-    expires_after="M85">
+    expires_after="2021-06-01">
   <owner>sonnyrao@chromium.org</owner>
   <owner>jamescook@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/ukm/ukm.xml b/tools/metrics/ukm/ukm.xml
index 2b21554..f7df7d0 100644
--- a/tools/metrics/ukm/ukm.xml
+++ b/tools/metrics/ukm/ukm.xml
@@ -13373,6 +13373,154 @@
   <metric name="TargetLanguage" semantic_type="ST_DEMOGRAPHIC_INFO"/>
 </event>
 
+<event name="TranslatePageLoad">
+  <owner>curranmax@chromium.org</owner>
+  <owner>megjablon@chromium.org</owner>
+  <owner>chrome-language@google.com</owner>
+  <summary>
+    Summary of the user's interaction with Translate from the beginning of the
+    page load to when the event is recorded. This event is recorded when a page
+    load is completed and every time that Chrome is backgrounded during the
+    course of the page load. This means we can record multiple events for a
+    single page load, but it guarentees that we will still record data in
+    instances where the user backgrounds then kills Chrome.
+  </summary>
+  <metric name="FinalSourceLanguage" semantic_type="ST_DEMOGRAPHIC_INFO"
+      enum="CLD3LanguageCode">
+    <summary>
+      The source language at the time the event is recorded. In most cases this
+      will match the intial source language, but the user can change the source
+      language manually if they want. Note that the semantic_type attribute is
+      included in order to remain consistent with the previous Translate UKM
+      proto.
+    </summary>
+  </metric>
+  <metric name="FinalState" enum="TranslateState">
+    <summary>
+      At the time this event is recorded, the state of Translate. This includes
+      whether the page is translated or not and if no UI is shown, just the
+      omnibox icon is shown, or the full translate UI is shown.
+    </summary>
+  </metric>
+  <metric name="FinalTargetLanguage" semantic_type="ST_DEMOGRAPHIC_INFO"
+      enum="CLD3LanguageCode">
+    <summary>
+      The target language at the time the event is recorded. In most cases this
+      will match the initial target language, but the user can change the target
+      language manually if they want. Note that the semantic_type attribute is
+      included in order to remain consistent with the previous Translate UKM
+      proto.
+    </summary>
+  </metric>
+  <metric name="FirstTranslateError" enum="TranslateError">
+    <summary>
+      The first error to occur within Translate for this page load.
+    </summary>
+  </metric>
+  <metric name="FirstUIInteraction" enum="TranslateUIInteraction">
+    <summary>
+      The user's first interaction with the Translate UI.
+    </summary>
+  </metric>
+  <metric name="InitialSourceLanguage" semantic_type="ST_DEMOGRAPHIC_INFO"
+      enum="CLD3LanguageCode">
+    <summary>
+      The initial source language that Translate determines for the page. Note
+      that the semantic_type attribute is included in order to remain consistent
+      with the previous Translate UKM proto.
+    </summary>
+  </metric>
+  <metric name="InitialSourceLanguageInContentLanguages" enum="Boolean">
+    <summary>
+      Signals whether the initial source language is in the list of the user's
+      content languages. The user's content languages loosely match with the
+      languages the user has translated to before or has blocked for
+      translation.
+    </summary>
+  </metric>
+  <metric name="InitialState" enum="TranslateState">
+    <summary>
+      At the beginning of the page load, the state of Translate. This includes
+      whether the page is translated or not and if no UI is shown, just the
+      omnibox icon is shown, or the full translate UI is shown.
+    </summary>
+  </metric>
+  <metric name="InitialTargetLanguage" semantic_type="ST_DEMOGRAPHIC_INFO"
+      enum="CLD3LanguageCode">
+    <summary>
+      The initial target language that Translate thinks it should translate to.
+      Note that the semantic_type attribute is included in order to remain
+      consistent with the previous Translate UKM proto.
+    </summary>
+  </metric>
+  <metric name="MaxTimeToTranslate">
+    <summary>
+      Across all translations during this page load, the maximum amount of time
+      (in milliseconds) it took to translate the page.
+    </summary>
+  </metric>
+  <metric name="NumReversions">
+    <summary>
+      The number of times a translation was reverted.
+    </summary>
+  </metric>
+  <metric name="NumTargetLanguageChanges">
+    <summary>
+      The number of times that the target language was changed by the user over
+      the course of this page load.
+    </summary>
+  </metric>
+  <metric name="NumTranslateErrors">
+    <summary>
+      The number of errors to occur within Translate for this page load.
+    </summary>
+  </metric>
+  <metric name="NumTranslations">
+    <summary>
+      The number of times the page was translated.
+    </summary>
+  </metric>
+  <metric name="NumUIInteractions">
+    <summary>
+      The number of times that the user interacts with the Translate UI.
+    </summary>
+  </metric>
+  <metric name="RankerDecision" enum="TranslateRankerDecision">
+    <summary>
+      Decision of the Ranker whether to show the UI or not.
+    </summary>
+  </metric>
+  <metric name="RankerVersion">
+    <summary>
+      Version of Ranker used for this page load.
+    </summary>
+  </metric>
+  <metric name="SequenceNumber">
+    <summary>
+      In case multiple events are logged for one page load, we track the
+      sequence in the order each event is logged.
+    </summary>
+  </metric>
+  <metric name="TotalTimeNotTranslated">
+    <summary>
+      The amount of time (in seconds) this page was in the foreground and not
+      translated.
+    </summary>
+  </metric>
+  <metric name="TotalTimeTranslated">
+    <summary>
+      The amount of time (in seconds) this page was in the foreground and
+      translated.
+    </summary>
+  </metric>
+  <metric name="TriggerDecision" enum="TranslateTriggerDecision">
+    <summary>
+      The highest priority trigger that determined the initial state of
+      Translate for this page load.
+    </summary>
+  </metric>
+</event>
+
 <event name="TrustedWebActivity.Open" singular="True">
   <owner>peconn@chromium.org</owner>
   <summary>
diff --git a/tools/perf/benchmarks/benchmark_smoke_unittest.py b/tools/perf/benchmarks/benchmark_smoke_unittest.py
index 1c35c5f..4e8cb9a 100644
--- a/tools/perf/benchmarks/benchmark_smoke_unittest.py
+++ b/tools/perf/benchmarks/benchmark_smoke_unittest.py
@@ -101,11 +101,12 @@
 # The list of benchmark names to be excluded from our smoke tests.
 _BLACK_LIST_TEST_NAMES = [
     'memory.long_running_idle_gmail_background_tbmv2',
-    'tab_search', # crbug.com/1159462
+    'tab_search',  # crbug.com/1159462
     'tab_switching.typical_25',
     'UNSCHEDULED_oortonline_tbmv2',
     'webrtc',  # crbug.com/932036
-    'v8.runtime_stats.top_25'  # Fails in Windows, crbug.com/1043048
+    'v8.runtime_stats.top_25',  # Fails in Windows, crbug.com/1043048
+    'UNSCHEDULED_blink_perf.webgpu_fast_call',  # crbug.com/1164115
 ]
 
 
diff --git a/tools/perf/core/perf_data_generator.py b/tools/perf/core/perf_data_generator.py
index d6474d43..44c516f 100755
--- a/tools/perf/core/perf_data_generator.py
+++ b/tools/perf/core/perf_data_generator.py
@@ -697,8 +697,8 @@
         'platform':
         'linux',
         'dimension': {
-            'gpu': '10de:1cb3-384.90',
-            'os': 'Ubuntu-14.04',
+            'gpu': '10de:1cb3-440.100',
+            'os': 'Ubuntu-18.04',
             'pool': 'chrome.tests.perf',
             'synthetic_product_name': 'PowerEdge R230 (Dell Inc.)'
         },
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json
index 8fa635d..b371e75 100644
--- a/tools/perf/core/perfetto_binary_roller/binary_deps.json
+++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -1,16 +1,16 @@
 {
     "trace_processor_shell": {
         "win": {
-            "hash": "dd34e440b0a4d39037a1d2b3ed76eb75a67932b5",
-            "remote_path": "perfetto_binaries/trace_processor_shell/win/5ff758df67da94d17734c2e70eb6738c4902953e/trace_processor_shell.exe"
+            "hash": "e966ca8bd8894222cc224f0256aa5b32aef94669",
+            "remote_path": "perfetto_binaries/trace_processor_shell/win/070ef598892aee3d5cfb628f577867217c863142/trace_processor_shell.exe"
         },
         "mac": {
-            "hash": "dd8bde783d9cb141102961446d65f755560432cb",
-            "remote_path": "perfetto_binaries/trace_processor_shell/mac/5ff758df67da94d17734c2e70eb6738c4902953e/trace_processor_shell"
+            "hash": "e714905178b25795c55a9468c15a3aa0a4038d47",
+            "remote_path": "perfetto_binaries/trace_processor_shell/mac/070ef598892aee3d5cfb628f577867217c863142/trace_processor_shell"
         },
         "linux": {
-            "hash": "11280ccdc5bb43e61153805c87207334f12c39f7",
-            "remote_path": "perfetto_binaries/trace_processor_shell/linux/603d14ec2a266fdcf4734d1d42605e2d317e48d8/trace_processor_shell"
+            "hash": "153bdd7d922d3d225b8c2855de063cf00ff59efa",
+            "remote_path": "perfetto_binaries/trace_processor_shell/linux/070ef598892aee3d5cfb628f577867217c863142/trace_processor_shell"
         }
     },
     "power_profile.sql": {
diff --git a/tools/traffic_annotation/summary/annotations.xml b/tools/traffic_annotation/summary/annotations.xml
index 8e310dd..7d86df1 100644
--- a/tools/traffic_annotation/summary/annotations.xml
+++ b/tools/traffic_annotation/summary/annotations.xml
@@ -217,7 +217,7 @@
  <item id="open_search" added_in_milestone="62" hash_code="107267424" type="0" content_hash_code="83025542" os_list="linux,windows" file_path="components/search_engines/template_url_fetcher.cc"/>
  <item id="openscreen_message" added_in_milestone="83" hash_code="23036184" type="0" content_hash_code="124395439" os_list="linux,windows" file_path="components/openscreen_platform/udp_socket.cc"/>
  <item id="openscreen_tls_message" added_in_milestone="83" hash_code="40127335" type="0" content_hash_code="15991338" os_list="linux,windows" file_path="components/openscreen_platform/tls_connection_factory.cc"/>
- <item id="optimization_guide_model" added_in_milestone="79" hash_code="106373593" type="0" content_hash_code="32403047" os_list="linux,windows" file_path="chrome/browser/optimization_guide/prediction/prediction_model_fetcher.cc"/>
+ <item id="optimization_guide_model" added_in_milestone="79" hash_code="106373593" type="0" content_hash_code="32403047" os_list="linux,windows" file_path="components/optimization_guide/core/prediction_model_fetcher.cc"/>
  <item id="optimization_guide_model_download" added_in_milestone="88" hash_code="100143055" type="0" content_hash_code="97983899" os_list="linux,windows" file_path="chrome/browser/optimization_guide/prediction/prediction_model_download_manager.cc"/>
  <item id="origin_policy_loader" added_in_milestone="69" hash_code="6483617" type="0" content_hash_code="134028975" os_list="linux,windows" file_path="services/network/origin_policy/origin_policy_fetcher.cc"/>
  <item id="parallel_download_job" added_in_milestone="62" hash_code="135118587" type="0" content_hash_code="105330419" os_list="linux,windows" file_path="components/download/internal/common/parallel_download_job.cc"/>
diff --git a/ui/aura/client/aura_constants.cc b/ui/aura/client/aura_constants.cc
index 75108ad..ca98973 100644
--- a/ui/aura/client/aura_constants.cc
+++ b/ui/aura/client/aura_constants.cc
@@ -75,6 +75,7 @@
 DEFINE_UI_CLASS_PROPERTY_KEY(bool, kSkipImeProcessing, false)
 DEFINE_OWNED_UI_CLASS_PROPERTY_KEY(base::string16, kTitleKey, nullptr)
 DEFINE_UI_CLASS_PROPERTY_KEY(int, kTopViewInset, 0)
+DEFINE_UI_CLASS_PROPERTY_KEY(bool, kVisibleOnAllWorkspacesKey, false)
 DEFINE_OWNED_UI_CLASS_PROPERTY_KEY(gfx::ImageSkia, kWindowIconKey, nullptr)
 DEFINE_UI_CLASS_PROPERTY_KEY(int, kWindowCornerRadiusKey, -1)
 DEFINE_UI_CLASS_PROPERTY_KEY(int, kWindowWorkspaceKey, kUnassignedWorkspace)
diff --git a/ui/aura/client/aura_constants.h b/ui/aura/client/aura_constants.h
index eb22477..bb97f9b 100644
--- a/ui/aura/client/aura_constants.h
+++ b/ui/aura/client/aura_constants.h
@@ -154,6 +154,9 @@
 // the web contents for app windows and varies for fullscreen windows.
 AURA_EXPORT extern const WindowProperty<int>* const kTopViewInset;
 
+// A property key to store whether this window is visible on all workspaces.
+AURA_EXPORT extern const WindowProperty<bool>* const kVisibleOnAllWorkspacesKey;
+
 // A property key to store the window icon, typically 16x16 for title bars.
 AURA_EXPORT extern const WindowProperty<gfx::ImageSkia*>* const kWindowIconKey;
 
diff --git a/ui/base/ime/chromeos/input_method_chromeos.cc b/ui/base/ime/chromeos/input_method_chromeos.cc
index 7a62712c..9bc9912 100644
--- a/ui/base/ime/chromeos/input_method_chromeos.cc
+++ b/ui/base/ime/chromeos/input_method_chromeos.cc
@@ -501,8 +501,7 @@
   if (client != GetTextInputClient())
     return dispatch_details;
 
-  if (HasInputMethodResult())
-    ProcessInputMethodResult(event, handled);
+  MaybeProcessPendingInputMethodResult(event, handled);
 
   // In case the focus was changed when sending input method results to the
   // focused window.
@@ -564,8 +563,9 @@
   return details;
 }
 
-void InputMethodChromeOS::ProcessInputMethodResult(ui::KeyEvent* event,
-                                                   bool handled) {
+void InputMethodChromeOS::MaybeProcessPendingInputMethodResult(
+    ui::KeyEvent* event,
+    bool handled) {
   TextInputClient* client = GetTextInputClient();
   DCHECK(client);
 
@@ -621,10 +621,6 @@
        (!composing_text_ && result_text_.length() == 1));
 }
 
-bool InputMethodChromeOS::HasInputMethodResult() const {
-  return result_text_.length() || composition_changed_;
-}
-
 void InputMethodChromeOS::CommitText(const std::string& text) {
   if (text.empty())
     return;
diff --git a/ui/base/ime/chromeos/input_method_chromeos.h b/ui/base/ime/chromeos/input_method_chromeos.h
index 90e836c..2805383 100644
--- a/ui/base/ime/chromeos/input_method_chromeos.h
+++ b/ui/base/ime/chromeos/input_method_chromeos.h
@@ -122,9 +122,9 @@
   ui::EventDispatchDetails ProcessUnfilteredKeyPressEvent(ui::KeyEvent* event)
       WARN_UNUSED_RESULT;
 
-  // Sends input method result caused by the given key event to the focused text
-  // input client.
-  void ProcessInputMethodResult(ui::KeyEvent* event, bool filtered);
+  // Processes any pending input method operations that issued while handling
+  // the key event. Does not do anything if there were no pending operations.
+  void MaybeProcessPendingInputMethodResult(ui::KeyEvent* event, bool filtered);
 
   // Checks if the pending input method result needs inserting into the focused
   // text input client as a single character.
diff --git a/ui/base/ime/chromeos/input_method_chromeos_unittest.cc b/ui/base/ime/chromeos/input_method_chromeos_unittest.cc
index 24f53cc..4bf8a8d 100644
--- a/ui/base/ime/chromeos/input_method_chromeos_unittest.cc
+++ b/ui/base/ime/chromeos/input_method_chromeos_unittest.cc
@@ -1175,6 +1175,20 @@
   EXPECT_FALSE(input_method_manager_->state()->is_jp_ime());
 }
 
+TEST_F(InputMethodChromeOSKeyEventTest, SetAutocorrectRangeRunsAfterKeyEvent) {
+  input_type_ = TEXT_INPUT_TYPE_TEXT;
+  ime_->OnTextInputTypeChanged(this);
+  ime_->CommitText("a");
+
+  ui::KeyEvent event(ui::ET_KEY_PRESSED, ui::VKEY_A, ui::EF_NONE);
+  ime_->DispatchKeyEvent(&event);
+  ime_->SetAutocorrectRange(gfx::Range(0, 1));
+  std::move(mock_ime_engine_handler_->last_passed_callback())
+      .Run(/*handled=*/true);
+
+  EXPECT_EQ(gfx::Range(0, 1), GetAutocorrectRange());
+}
+
 TEST_F(InputMethodChromeOSKeyEventTest,
        SetAutocorrectRangeRunsAfterCommitText) {
   input_type_ = TEXT_INPUT_TYPE_TEXT;
diff --git a/ui/base/x/selection_owner.cc b/ui/base/x/selection_owner.cc
index e500dee..f2815ed 100644
--- a/ui/base/x/selection_owner.cc
+++ b/ui/base/x/selection_owner.cc
@@ -164,7 +164,7 @@
 }
 
 void SelectionOwner::OnSelectionClear(const x11::SelectionClearEvent& event) {
-  DLOG(ERROR) << "SelectionClear";
+  DVLOG(1) << "SelectionClear";
 
   // TODO(erg): If we receive a SelectionClear event while we're handling data,
   // we need to delay clearing.
diff --git a/ui/compositor/BUILD.gn b/ui/compositor/BUILD.gn
index 890c3ae9..c36f3b2 100644
--- a/ui/compositor/BUILD.gn
+++ b/ui/compositor/BUILD.gn
@@ -151,6 +151,8 @@
     "test/test_suite.h",
     "test/test_utils.cc",
     "test/test_utils.h",
+    "test/throughput_report_checker.cc",
+    "test/throughput_report_checker.h",
   ]
 
   if (is_android) {
diff --git a/ui/compositor/animation_throughput_reporter_unittest.cc b/ui/compositor/animation_throughput_reporter_unittest.cc
index e0ceb01..99f6b19 100644
--- a/ui/compositor/animation_throughput_reporter_unittest.cc
+++ b/ui/compositor/animation_throughput_reporter_unittest.cc
@@ -15,6 +15,7 @@
 #include "ui/compositor/layer_animator.h"
 #include "ui/compositor/scoped_layer_animation_settings.h"
 #include "ui/compositor/test/animation_throughput_reporter_test_base.h"
+#include "ui/compositor/test/throughput_report_checker.h"
 #include "ui/gfx/geometry/rect.h"
 
 namespace ui {
@@ -27,22 +28,18 @@
   layer.SetOpacity(0.5f);
   root_layer()->Add(&layer);
 
-  base::RunLoop run_loop;
+  ThroughputReportChecker checker(this);
   {
     LayerAnimator* animator = layer.GetAnimator();
-    AnimationThroughputReporter reporter(
-        animator, base::BindLambdaForTesting(
-                      [&](const cc::FrameSequenceMetrics::CustomReportData&) {
-                        run_loop.Quit();
-                      }));
+    AnimationThroughputReporter reporter(animator,
+                                         checker.repeating_callback());
 
     ScopedLayerAnimationSettings settings(animator);
     settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds(48));
     layer.SetOpacity(1.0f);
   }
   // The animation starts in next frame (16ms) and ends 48 ms later.
-  Advance(base::TimeDelta::FromMilliseconds(64));
-  run_loop.Run();
+  EXPECT_TRUE(checker.WaitUntilReported());
 }
 
 // Tests animation throughput collection with implicit animation setup before
@@ -51,14 +48,11 @@
   Layer layer;
   layer.SetOpacity(0.5f);
 
-  base::RunLoop run_loop;
+  ThroughputReportChecker checker(this);
   {
     LayerAnimator* animator = layer.GetAnimator();
-    AnimationThroughputReporter reporter(
-        animator, base::BindLambdaForTesting(
-                      [&](const cc::FrameSequenceMetrics::CustomReportData&) {
-                        run_loop.Quit();
-                      }));
+    AnimationThroughputReporter reporter(animator,
+                                         checker.repeating_callback());
 
     ScopedLayerAnimationSettings settings(animator);
     settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds(48));
@@ -67,8 +61,7 @@
 
   // Attach to root after animation setup.
   root_layer()->Add(&layer);
-  Advance(base::TimeDelta::FromMilliseconds(64));
-  run_loop.Run();
+  EXPECT_TRUE(checker.WaitUntilReported());
 }
 
 // Tests animation throughput collection with explicitly created animation
@@ -78,21 +71,15 @@
   layer.SetOpacity(0.5f);
   root_layer()->Add(&layer);
 
-  base::RunLoop run_loop;
-  {
-    LayerAnimator* animator = layer.GetAnimator();
-    AnimationThroughputReporter reporter(
-        animator, base::BindLambdaForTesting(
-                      [&](const cc::FrameSequenceMetrics::CustomReportData&) {
-                        run_loop.Quit();
-                      }));
+  ThroughputReportChecker checker(this);
+  LayerAnimator* animator = layer.GetAnimator();
+  AnimationThroughputReporter reporter(animator, checker.repeating_callback());
 
-    animator->ScheduleAnimation(
-        new LayerAnimationSequence(LayerAnimationElement::CreateOpacityElement(
-            1.0f, base::TimeDelta::FromMilliseconds(48))));
-  }
-  Advance(base::TimeDelta::FromMilliseconds(64));
-  run_loop.Run();
+  animator->ScheduleAnimation(
+      new LayerAnimationSequence(LayerAnimationElement::CreateOpacityElement(
+          1.0f, base::TimeDelta::FromMilliseconds(48))));
+
+  EXPECT_TRUE(checker.WaitUntilReported());
 }
 
 // Tests animation throughput collection for a persisted animator of a Layer.
@@ -106,24 +93,18 @@
       new LayerAnimator(base::TimeDelta::FromMilliseconds(48));
   layer->SetAnimator(animator);
 
-  std::unique_ptr<base::RunLoop> run_loop = std::make_unique<base::RunLoop>();
   // |reporter| keeps reporting as long as it is alive.
-  AnimationThroughputReporter reporter(
-      animator, base::BindLambdaForTesting(
-                    [&](const cc::FrameSequenceMetrics::CustomReportData&) {
-                      run_loop->Quit();
-                    }));
+  ThroughputReportChecker checker(this);
+  AnimationThroughputReporter reporter(animator, checker.repeating_callback());
 
   // Report data for animation of opacity goes to 1.
   layer->SetOpacity(1.0f);
-  Advance(base::TimeDelta::FromMilliseconds(64));
-  run_loop->Run();
+  EXPECT_TRUE(checker.WaitUntilReported());
 
   // Report data for animation of opacity goes to 0.5.
-  run_loop = std::make_unique<base::RunLoop>();
+  checker.reset();
   layer->SetOpacity(0.5f);
-  Advance(base::TimeDelta::FromMilliseconds(64));
-  run_loop->Run();
+  EXPECT_TRUE(checker.WaitUntilReported());
 }
 
 // Tests animation throughput not reported when animation is aborted.
@@ -132,13 +113,14 @@
   layer->SetOpacity(0.5f);
   root_layer()->Add(layer.get());
 
+  ThroughputReportChecker checker(this, /*fail_if_reported=*/true);
+
+  // Reporter started monitoring animation, then deleted, which should be
+  // reported when the animation ends.
   {
     LayerAnimator* animator = layer->GetAnimator();
-    AnimationThroughputReporter reporter(
-        animator, base::BindLambdaForTesting(
-                      [&](const cc::FrameSequenceMetrics::CustomReportData&) {
-                        ADD_FAILURE() << "No report for aborted animations.";
-                      }));
+    AnimationThroughputReporter reporter(animator,
+                                         checker.repeating_callback());
 
     ScopedLayerAnimationSettings settings(animator);
     settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds(48));
@@ -150,6 +132,9 @@
 
   // Wait a bit to ensure that report does not happen.
   Advance(base::TimeDelta::FromMilliseconds(100));
+
+  // TODO(crbug.com/1158510): Test the scenario where the report exists when the
+  // layer is removed.
 }
 
 // Tests no report and no leak when underlying layer is gone before reporter.
@@ -158,13 +143,9 @@
   layer->SetOpacity(0.5f);
   root_layer()->Add(layer.get());
 
+  ThroughputReportChecker checker(this, /*fail_if_reported=*/true);
   LayerAnimator* animator = layer->GetAnimator();
-  AnimationThroughputReporter reporter(
-      animator, base::BindLambdaForTesting(
-                    [&](const cc::FrameSequenceMetrics::CustomReportData&) {
-                      ADD_FAILURE() << "No report for aborted animations.";
-                    }));
-
+  AnimationThroughputReporter reporter(animator, checker.repeating_callback());
   {
     ScopedLayerAnimationSettings settings(animator);
     settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds(48));
@@ -184,14 +165,11 @@
   layer->SetOpacity(0.5f);
   root_layer()->Add(layer.get());
 
+  ThroughputReportChecker checker(this, /*fail_if_reported=*/true);
   {
     LayerAnimator* animator = layer->GetAnimator();
-    AnimationThroughputReporter reporter(
-        animator, base::BindLambdaForTesting(
-                      [&](const cc::FrameSequenceMetrics::CustomReportData&) {
-                        ADD_FAILURE() << "No report for aborted animations.";
-                      }));
-
+    AnimationThroughputReporter reporter(animator,
+                                         checker.repeating_callback());
     ScopedLayerAnimationSettings settings(animator);
     settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds(48));
     layer->SetOpacity(1.0f);
@@ -211,16 +189,12 @@
   auto layer = std::make_unique<Layer>();
   layer->SetOpacity(0.5f);
 
+  ThroughputReportChecker checker(this, /*fail_if_reported=*/true);
   LayerAnimator* animator = layer->GetAnimator();
-
   // Schedule an animation without being attached to a root.
   {
-    AnimationThroughputReporter reporter(
-        animator, base::BindLambdaForTesting(
-                      [&](const cc::FrameSequenceMetrics::CustomReportData&) {
-                        ADD_FAILURE() << "No report for aborted animations.";
-                      }));
-
+    AnimationThroughputReporter reporter(animator,
+                                         checker.repeating_callback());
     ScopedLayerAnimationSettings settings(animator);
     settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds(50));
     layer->SetOpacity(1.0f);
@@ -243,16 +217,12 @@
   layer->SetBounds(gfx::Rect(0, 0, 1, 2));
   root_layer()->Add(layer.get());
 
+  ThroughputReportChecker checker(this, /*fail_if_reported=*/true);
   LayerAnimator* animator = layer->GetAnimator();
-
   // Schedule an animation that will be preempted. No report should happen.
   {
-    AnimationThroughputReporter reporter(
-        animator, base::BindLambdaForTesting(
-                      [&](const cc::FrameSequenceMetrics::CustomReportData&) {
-                        ADD_FAILURE() << "No report for aborted animations.";
-                      }));
-
+    AnimationThroughputReporter reporter(animator,
+                                         checker.repeating_callback());
     ScopedLayerAnimationSettings settings(animator);
     settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds(50));
     layer->SetOpacity(0.5f);
@@ -260,14 +230,10 @@
   }
 
   // Animate to new target. Report should happen.
-  base::RunLoop run_loop;
+  ThroughputReportChecker checker2(this);
   {
-    AnimationThroughputReporter reporter(
-        animator, base::BindLambdaForTesting(
-                      [&](const cc::FrameSequenceMetrics::CustomReportData&) {
-                        run_loop.Quit();
-                      }));
-
+    AnimationThroughputReporter reporter(animator,
+                                         checker2.repeating_callback());
     ScopedLayerAnimationSettings settings(animator);
     settings.SetPreemptionStrategy(
         LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
@@ -275,8 +241,7 @@
     layer->SetOpacity(1.0f);
     layer->SetBounds(gfx::Rect(0, 0, 5, 6));
   }
-  Advance(base::TimeDelta::FromMilliseconds(64));
-  run_loop.Run();
+  EXPECT_TRUE(checker2.WaitUntilReported());
 }
 
 }  // namespace ui
diff --git a/ui/compositor/layer_animation_element.cc b/ui/compositor/layer_animation_element.cc
index c8aac298..d7b9d1d 100644
--- a/ui/compositor/layer_animation_element.cc
+++ b/ui/compositor/layer_animation_element.cc
@@ -452,7 +452,7 @@
                                        duration()));
     std::unique_ptr<cc::KeyframeModel> keyframe_model(cc::KeyframeModel::Create(
         std::move(animation_curve), keyframe_model_id(), animation_group_id(),
-        cc::TargetProperty::OPACITY));
+        cc::KeyframeModel::TargetPropertyId(cc::TargetProperty::OPACITY)));
     return keyframe_model;
   }
 
@@ -522,7 +522,7 @@
                                            duration()));
     std::unique_ptr<cc::KeyframeModel> keyframe_model(cc::KeyframeModel::Create(
         std::move(animation_curve), keyframe_model_id(), animation_group_id(),
-        cc::TargetProperty::TRANSFORM));
+        cc::KeyframeModel::TargetPropertyId(cc::TargetProperty::TRANSFORM)));
     return keyframe_model;
   }
 
diff --git a/ui/compositor/test/animation_throughput_reporter_test_base.cc b/ui/compositor/test/animation_throughput_reporter_test_base.cc
index 31a24a5..dc796ee 100644
--- a/ui/compositor/test/animation_throughput_reporter_test_base.cc
+++ b/ui/compositor/test/animation_throughput_reporter_test_base.cc
@@ -47,14 +47,14 @@
 
 void AnimationThroughputReporterTestBase::Advance(
     const base::TimeDelta& delta) {
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-  task_environment_.FastForwardBy(delta);
-#else
-  base::RunLoop run_loop;
+  run_loop_ = std::make_unique<base::RunLoop>();
   base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
-      FROM_HERE, run_loop.QuitClosure(), delta);
-  run_loop.Run();
-#endif
+      FROM_HERE, run_loop_->QuitClosure(), delta);
+  run_loop_->Run();
+}
+
+void AnimationThroughputReporterTestBase::QuitRunLoop() {
+  run_loop_->Quit();
 }
 
 }  // namespace ui
diff --git a/ui/compositor/test/animation_throughput_reporter_test_base.h b/ui/compositor/test/animation_throughput_reporter_test_base.h
index f3dce44..e383325 100644
--- a/ui/compositor/test/animation_throughput_reporter_test_base.h
+++ b/ui/compositor/test/animation_throughput_reporter_test_base.h
@@ -15,6 +15,10 @@
 #include "ui/compositor/layer.h"
 #include "ui/compositor/test/test_compositor_host.h"
 
+namespace base {
+class RunLoop;
+}
+
 namespace ui {
 class TestContextFactories;
 
@@ -39,13 +43,11 @@
   // Advances the time by |delta|.
   void Advance(const base::TimeDelta& delta);
 
+  void QuitRunLoop();
+
  private:
   base::test::TaskEnvironment task_environment_{
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-    base::test::TaskEnvironment::TimeSource::MOCK_TIME,
-#endif
-        base::test::TaskEnvironment::MainThreadType::UI
-  };
+      base::test::TaskEnvironment::MainThreadType::UI};
 
   std::unique_ptr<TestContextFactories> context_factories_;
   std::unique_ptr<TestCompositorHost> host_;
@@ -54,6 +56,8 @@
   // A timer to generate continuous compositor frames to trigger throughput
   // data being transferred back.
   base::RepeatingTimer frame_generation_timer_;
+
+  std::unique_ptr<base::RunLoop> run_loop_;
 };
 
 }  // namespace ui
diff --git a/ui/compositor/test/throughput_report_checker.cc b/ui/compositor/test/throughput_report_checker.cc
new file mode 100644
index 0000000..5559b7d7
--- /dev/null
+++ b/ui/compositor/test/throughput_report_checker.cc
@@ -0,0 +1,26 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/compositor/test/throughput_report_checker.h"
+
+#include "base/time/time.h"
+#include "ui/compositor/test/animation_throughput_reporter_test_base.h"
+
+namespace ui {
+
+bool ThroughputReportChecker::WaitUntilReported() {
+  DCHECK(!reported_);
+  test_base_->Advance(base::TimeDelta::FromSeconds(5));
+  return reported_;
+}
+
+void ThroughputReportChecker::OnReport(
+    const cc::FrameSequenceMetrics::CustomReportData&) {
+  reported_ = true;
+  if (fail_if_reported_)
+    ADD_FAILURE() << "It should not be reported.";
+  test_base_->QuitRunLoop();
+}
+
+}  // namespace ui
diff --git a/ui/compositor/test/throughput_report_checker.h b/ui/compositor/test/throughput_report_checker.h
new file mode 100644
index 0000000..c7837a7
--- /dev/null
+++ b/ui/compositor/test/throughput_report_checker.h
@@ -0,0 +1,58 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_COMPOSITOR_TEST_THROUGHPUT_REPORT_CHECKER_H_
+#define UI_COMPOSITOR_TEST_THROUGHPUT_REPORT_CHECKER_H_
+
+#include <memory>
+
+#include "base/bind.h"
+#include "cc/metrics/frame_sequence_metrics.h"
+
+namespace ui {
+class AnimationThroughputReporterTestBase;
+
+class ThroughputReportChecker {
+ public:
+  using ReportRepeatingCallback = base::RepeatingCallback<void(
+      const cc::FrameSequenceMetrics::CustomReportData&)>;
+  using ReportOnceCallback = base::OnceCallback<void(
+      const cc::FrameSequenceMetrics::CustomReportData&)>;
+
+  explicit ThroughputReportChecker(
+      AnimationThroughputReporterTestBase* test_base,
+      bool fail_if_reported = false)
+      : test_base_(test_base), fail_if_reported_(fail_if_reported) {}
+  ThroughputReportChecker(const ThroughputReportChecker&) = delete;
+  ThroughputReportChecker& operator=(const ThroughputReportChecker&) = delete;
+  ~ThroughputReportChecker() = default;
+
+  bool reported() const { return reported_; }
+
+  void reset() { reported_ = false; }
+
+  ReportRepeatingCallback repeating_callback() {
+    return base::BindRepeating(&ThroughputReportChecker::OnReport,
+                               base::Unretained(this));
+  }
+  ReportOnceCallback once_callback() {
+    return base::BindOnce(&ThroughputReportChecker::OnReport,
+                          base::Unretained(this));
+  }
+
+  // It waits until reported up to 5 seconds timeout. Returns true if it's
+  // reported.
+  bool WaitUntilReported();
+
+ private:
+  void OnReport(const cc::FrameSequenceMetrics::CustomReportData&);
+
+  AnimationThroughputReporterTestBase* test_base_;
+  bool reported_ = false;
+  bool fail_if_reported_ = false;
+};
+
+}  // namespace ui
+
+#endif  // UI_COMPOSITOR_TEST_THROUGHPUT_REPORT_CHECKER_H_
diff --git a/ui/compositor/total_animation_throughput_reporter_unittest.cc b/ui/compositor/total_animation_throughput_reporter_unittest.cc
index f24ec32..13589e4 100644
--- a/ui/compositor/total_animation_throughput_reporter_unittest.cc
+++ b/ui/compositor/total_animation_throughput_reporter_unittest.cc
@@ -10,114 +10,47 @@
 #include "base/test/bind.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
-#include "build/chromeos_buildflags.h"
 #include "cc/metrics/frame_sequence_metrics.h"
 #include "ui/compositor/layer.h"
 #include "ui/compositor/layer_animation_sequence.h"
 #include "ui/compositor/layer_animator.h"
 #include "ui/compositor/scoped_layer_animation_settings.h"
 #include "ui/compositor/test/animation_throughput_reporter_test_base.h"
-#include "ui/gfx/geometry/rect.h"
+#include "ui/compositor/test/throughput_report_checker.h"
 
 namespace ui {
-namespace {
-
-class TestReporter : public TotalAnimationThroughputReporter {
- public:
-  explicit TestReporter(AnimationThroughputReporterTestBase* test_base)
-      : ui::TotalAnimationThroughputReporter(
-            test_base->compositor(),
-            base::BindRepeating(&TestReporter::Reported,
-                                base::Unretained(this))),
-        test_base_(test_base) {}
-  TestReporter(AnimationThroughputReporterTestBase* test_base,
-               bool should_delete)
-      : ui::TotalAnimationThroughputReporter(
-            test_base->compositor(),
-            base::BindOnce(&TestReporter::Reported, base::Unretained(this)),
-            should_delete),
-        test_base_(test_base) {}
-
-  TestReporter(const TestReporter&) = delete;
-  TestReporter& operator=(const TestReporter&) = delete;
-  ~TestReporter() override = default;
-
-  void AdvanceUntilReported(const base::TimeDelta& delta) {
-    DCHECK(!reported_);
-    test_base_->Advance(delta);
-#if !BUILDFLAG(IS_CHROMEOS_ASH)
-    // Non ash-chrome platform uses native event loop which doesn't work well
-    // with mock time, so we still need to run the event loop.
-    if (!reported_) {
-      run_loop_ = std::make_unique<base::RunLoop>();
-      run_loop_->Run();
-    }
-#endif
-  }
-
-  bool reported() const { return reported_; }
-
-  void reset() { reported_ = false; }
-
- private:
-  void Reported(const cc::FrameSequenceMetrics::CustomReportData&) {
-    reported_ = true;
-#if !BUILDFLAG(IS_CHROMEOS_ASH)
-    if (run_loop_)
-      run_loop_->Quit();
-#endif
-  }
-
-  AnimationThroughputReporterTestBase* test_base_;
-  bool reported_ = false;
-#if !BUILDFLAG(IS_CHROMEOS_ASH)
-  std::unique_ptr<base::RunLoop> run_loop_;
-#endif
-};
-
-}  // namespace
 
 using TotalAnimationThroughputReporterTest =
     AnimationThroughputReporterTestBase;
 
-// Flaky on ChromeOS: crbug.com/1157649
-#if defined(OS_CHROMEOS)
-#define MAYBE_SingleAnimation DISABLED_SingleAnimation
-#else
-#define MAYBE_SingleAnimation SingleAnimation
-#endif
-TEST_F(TotalAnimationThroughputReporterTest, MAYBE_SingleAnimation) {
+TEST_F(TotalAnimationThroughputReporterTest, SingleAnimation) {
   Layer layer;
   layer.SetOpacity(0.5f);
   root_layer()->Add(&layer);
 
-  TestReporter reporter(this);
+  ThroughputReportChecker checker(this);
+  TotalAnimationThroughputReporter reporter(compositor(),
+                                            checker.repeating_callback());
   {
     LayerAnimator* animator = layer.GetAnimator();
-
     ScopedLayerAnimationSettings settings(animator);
     settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds(48));
     layer.SetOpacity(1.0f);
   }
   Advance(base::TimeDelta::FromMilliseconds(32));
-  EXPECT_FALSE(reporter.reported());
-  reporter.AdvanceUntilReported(base::TimeDelta::FromMilliseconds(32));
-  EXPECT_TRUE(reporter.reported());
+  EXPECT_FALSE(checker.reported());
+  EXPECT_TRUE(checker.WaitUntilReported());
 }
 
-// Flaky on ChromeOS: crbug.com/1157649
-#if defined(OS_CHROMEOS)
-#define MAYBE_StopAnimation DISABLED_StopAnimation
-#else
-#define MAYBE_StopAnimation StopAnimation
-#endif
 // Tests the stopping last animation will trigger the animation.
-TEST_F(TotalAnimationThroughputReporterTest, MAYBE_StopAnimation) {
+TEST_F(TotalAnimationThroughputReporterTest, StopAnimation) {
   Layer layer;
   layer.SetOpacity(0.5f);
   root_layer()->Add(&layer);
 
-  TestReporter reporter(this);
+  ThroughputReportChecker checker(this);
+  TotalAnimationThroughputReporter reporter(compositor(),
+                                            checker.repeating_callback());
   {
     LayerAnimator* animator = layer.GetAnimator();
 
@@ -126,10 +59,9 @@
     layer.SetOpacity(1.0f);
   }
   Advance(base::TimeDelta::FromMilliseconds(32));
-  EXPECT_FALSE(reporter.reported());
+  EXPECT_FALSE(checker.reported());
   layer.GetAnimator()->StopAnimating();
-  reporter.AdvanceUntilReported(base::TimeDelta::FromMilliseconds(32));
-  EXPECT_TRUE(reporter.reported());
+  EXPECT_TRUE(checker.WaitUntilReported());
 }
 
 // Tests the longest animation will trigger the report.
@@ -138,7 +70,9 @@
   layer1.SetOpacity(0.5f);
   root_layer()->Add(&layer1);
 
-  TestReporter reporter(this);
+  ThroughputReportChecker checker(this);
+  TotalAnimationThroughputReporter reporter(compositor(),
+                                            checker.repeating_callback());
   {
     LayerAnimator* animator = layer1.GetAnimator();
 
@@ -159,29 +93,22 @@
   }
 
   Advance(base::TimeDelta::FromMilliseconds(32));
-  EXPECT_FALSE(reporter.reported());
+  EXPECT_FALSE(checker.reported());
   Advance(base::TimeDelta::FromMilliseconds(32));
-  EXPECT_FALSE(reporter.reported());
-  reporter.AdvanceUntilReported(base::TimeDelta::FromMilliseconds(200));
-  EXPECT_TRUE(reporter.reported());
+  EXPECT_FALSE(checker.reported());
+  EXPECT_TRUE(checker.WaitUntilReported());
 }
 
-// Flaky on ChromeOS: crbug.com/1157649
-#if defined(OS_CHROMEOS)
-#define MAYBE_MultipleAnimationsOnSingleLayer \
-  DISABLED_MultipleAnimationsOnSingleLayer
-#else
-#define MAYBE_MultipleAnimationsOnSingleLayer MultipleAnimationsOnSingleLayer
-#endif
 // Tests the longest animation on a single layer will triger the report.
-TEST_F(TotalAnimationThroughputReporterTest,
-       MAYBE_MultipleAnimationsOnSingleLayer) {
+TEST_F(TotalAnimationThroughputReporterTest, MultipleAnimationsOnSingleLayer) {
   Layer layer;
   layer.SetOpacity(0.5f);
   layer.SetLayerBrightness(0.5f);
   root_layer()->Add(&layer);
 
-  TestReporter reporter(this);
+  ThroughputReportChecker checker(this);
+  TotalAnimationThroughputReporter reporter(compositor(),
+                                            checker.repeating_callback());
   {
     LayerAnimator* animator = layer.GetAnimator();
 
@@ -198,24 +125,19 @@
   }
 
   Advance(base::TimeDelta::FromMilliseconds(64));
-  EXPECT_FALSE(reporter.reported());
-  reporter.AdvanceUntilReported(base::TimeDelta::FromMilliseconds(48));
-  EXPECT_TRUE(reporter.reported());
+  EXPECT_FALSE(checker.reported());
+  EXPECT_TRUE(checker.WaitUntilReported());
 }
 
-// Flaky on ChromeOS: crbug.com/1157649
-#if defined(OS_CHROMEOS)
-#define MAYBE_AddAnimationWhileAnimating DISABLED_AddAnimationWhileAnimating
-#else
-#define MAYBE_AddAnimationWhileAnimating AddAnimationWhileAnimating
-#endif
 // Tests adding new animation will extends the duration.
-TEST_F(TotalAnimationThroughputReporterTest, MAYBE_AddAnimationWhileAnimating) {
+TEST_F(TotalAnimationThroughputReporterTest, AddAnimationWhileAnimating) {
   Layer layer1;
   layer1.SetOpacity(0.5f);
   root_layer()->Add(&layer1);
 
-  TestReporter reporter(this);
+  ThroughputReportChecker checker(this);
+  TotalAnimationThroughputReporter reporter(compositor(),
+                                            checker.repeating_callback());
   {
     LayerAnimator* animator = layer1.GetAnimator();
 
@@ -225,7 +147,7 @@
   }
 
   Advance(base::TimeDelta::FromMilliseconds(32));
-  EXPECT_FALSE(reporter.reported());
+  EXPECT_FALSE(checker.reported());
 
   // Add new animation while animating.
   Layer layer2;
@@ -242,25 +164,20 @@
 
   // The animation time is extended.
   Advance(base::TimeDelta::FromMilliseconds(32));
-  EXPECT_FALSE(reporter.reported());
+  EXPECT_FALSE(checker.reported());
 
-  reporter.AdvanceUntilReported(base::TimeDelta::FromMilliseconds(32));
-  EXPECT_TRUE(reporter.reported());
+  EXPECT_TRUE(checker.WaitUntilReported());
 }
 
-// Flaky on ChromeOS: crbug.com/1157649
-#if defined(OS_CHROMEOS)
-#define MAYBE_RemoveWhileAnimating DISABLED_RemoveWhileAnimating
-#else
-#define MAYBE_RemoveWhileAnimating RemoveWhileAnimating
-#endif
 // Tests removing last animation will call report callback.
-TEST_F(TotalAnimationThroughputReporterTest, MAYBE_RemoveWhileAnimating) {
+TEST_F(TotalAnimationThroughputReporterTest, RemoveWhileAnimating) {
   auto layer1 = std::make_unique<Layer>();
   layer1->SetOpacity(0.5f);
   root_layer()->Add(layer1.get());
 
-  TestReporter reporter(this);
+  ThroughputReportChecker checker(this);
+  TotalAnimationThroughputReporter reporter(compositor(),
+                                            checker.repeating_callback());
   {
     LayerAnimator* animator = layer1->GetAnimator();
 
@@ -281,22 +198,15 @@
     layer2.SetOpacity(1.0f);
   }
   Advance(base::TimeDelta::FromMilliseconds(48));
-  EXPECT_FALSE(reporter.reported());
+  EXPECT_FALSE(checker.reported());
   layer1.reset();
   // Aborting will be processed in next frame.
-  reporter.AdvanceUntilReported(base::TimeDelta::FromMilliseconds(16));
-  EXPECT_TRUE(reporter.reported());
+  EXPECT_TRUE(checker.WaitUntilReported());
 }
 
-// Flaky on ChromeOS: crbug.com/1157649
-#if defined(OS_CHROMEOS)
-#define MAYBE_StartWhileAnimating DISABLED_StartWhileAnimating
-#else
-#define MAYBE_StartWhileAnimating StartWhileAnimating
-#endif
 // Make sure the reporter can start measuring even if the animation
 // has started.
-TEST_F(TotalAnimationThroughputReporterTest, MAYBE_StartWhileAnimating) {
+TEST_F(TotalAnimationThroughputReporterTest, StartWhileAnimating) {
   Layer layer;
   layer.SetOpacity(0.5f);
   root_layer()->Add(&layer);
@@ -309,20 +219,15 @@
     layer.SetOpacity(1.0f);
   }
   Advance(base::TimeDelta::FromMilliseconds(32));
-  TestReporter reporter(this);
+  ThroughputReportChecker checker(this);
+  TotalAnimationThroughputReporter reporter(compositor(),
+                                            checker.repeating_callback());
   EXPECT_TRUE(reporter.IsMeasuringForTesting());
-  reporter.AdvanceUntilReported(base::TimeDelta::FromMilliseconds(100));
-  EXPECT_TRUE(reporter.reported());
+  EXPECT_TRUE(checker.WaitUntilReported());
 }
 
-// Flaky on ChromeOS: crbug.com/1157649
-#if defined(OS_CHROMEOS)
-#define MAYBE_PersistedAnimation DISABLED_PersistedAnimation
-#else
-#define MAYBE_PersistedAnimation PersistedAnimation
-#endif
 // Tests the reporter is called multiple times for persistent animation.
-TEST_F(TotalAnimationThroughputReporterTest, MAYBE_PersistedAnimation) {
+TEST_F(TotalAnimationThroughputReporterTest, PersistedAnimation) {
   Layer layer;
   layer.SetOpacity(0.5f);
   root_layer()->Add(&layer);
@@ -333,28 +238,22 @@
   layer.SetAnimator(animator);
 
   // |reporter| keeps reporting as long as it is alive.
-  TestReporter reporter(this);
+  ThroughputReportChecker checker(this);
+  TotalAnimationThroughputReporter reporter(compositor(),
+                                            checker.repeating_callback());
 
   // Report data for animation of opacity goes to 1.
   layer.SetOpacity(1.0f);
-  reporter.AdvanceUntilReported(base::TimeDelta::FromMilliseconds(100));
-  EXPECT_TRUE(reporter.reported());
+  EXPECT_TRUE(checker.WaitUntilReported());
 
   // Report data for animation of opacity goes to 0.5.
-  reporter.reset();
+  checker.reset();
   layer.SetOpacity(0.5f);
-  reporter.AdvanceUntilReported(base::TimeDelta::FromMilliseconds(100));
-  EXPECT_TRUE(reporter.reported());
+  EXPECT_TRUE(checker.WaitUntilReported());
 }
 
-// Flaky on ChromeOS: crbug.com/1157649
-#if defined(OS_CHROMEOS)
-#define MAYBE_OnceReporter DISABLED_OnceReporter
-#else
-#define MAYBE_OnceReporter OnceReporter
-#endif
 // Make sure the once reporter is called only once.
-TEST_F(TotalAnimationThroughputReporterTest, MAYBE_OnceReporter) {
+TEST_F(TotalAnimationThroughputReporterTest, OnceReporter) {
   Layer layer;
   layer.SetOpacity(0.5f);
   root_layer()->Add(&layer);
@@ -364,29 +263,24 @@
       new LayerAnimator(base::TimeDelta::FromMilliseconds(32));
   layer.SetAnimator(animator);
 
-  TestReporter reporter(this, /*should_delete=*/false);
+  ThroughputReportChecker checker(this);
+  TotalAnimationThroughputReporter reporter(
+      compositor(), checker.once_callback(), /*should_delete=*/false);
 
   // Report data for animation of opacity goes to 1.
   layer.SetOpacity(1.0f);
-  reporter.AdvanceUntilReported(base::TimeDelta::FromMilliseconds(100));
-  EXPECT_TRUE(reporter.reported());
+  EXPECT_TRUE(checker.WaitUntilReported());
 
   // Report data for animation of opacity goes to 0.5.
-  reporter.reset();
+  checker.reset();
   layer.SetOpacity(1.0f);
   Advance(base::TimeDelta::FromMilliseconds(100));
-  EXPECT_FALSE(reporter.reported());
+  EXPECT_FALSE(checker.reported());
 }
 
-// Flaky on ChromeOS: crbug.com/1157649
-#if defined(OS_CHROMEOS)
-#define MAYBE_OnceReporterShouldDelete DISABLED_OnceReporterShouldDelete
-#else
-#define MAYBE_OnceReporterShouldDelete OnceReporterShouldDelete
-#endif
 // One reporter marked as "should_delete" should be deleted when
 // reported.
-TEST_F(TotalAnimationThroughputReporterTest, MAYBE_OnceReporterShouldDelete) {
+TEST_F(TotalAnimationThroughputReporterTest, OnceReporterShouldDelete) {
   class DeleteTestReporter : public TotalAnimationThroughputReporter {
    public:
     DeleteTestReporter(Compositor* compositor,
@@ -425,14 +319,7 @@
 
   // Report data for animation of opacity goes to 1.
   layer.SetOpacity(1.0f);
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-  Advance(base::TimeDelta::FromMilliseconds(48));
-  EXPECT_FALSE(run_loop.running());
-#else
-  // Non ash-chrome platform uses native event loop which doesn't work
-  // with mock time, so we need to run more the event loop.
   run_loop.Run();
-#endif
   EXPECT_TRUE(deleted);
 }
 
diff --git a/ui/file_manager/externs/background/file_browser_background_full.js b/ui/file_manager/externs/background/file_browser_background_full.js
index e8c2e7d..efa0f34e 100644
--- a/ui/file_manager/externs/background/file_browser_background_full.js
+++ b/ui/file_manager/externs/background/file_browser_background_full.js
@@ -80,4 +80,21 @@
    * @param {boolean} enable
    */
   forceFileOperationErrorForTest(enable) {}
+
+  /**
+   * Registers a dialog (file picker or save as) in the background page.
+   * Dialogs are opened by the browser directly and should register themselves
+   * in the background page.
+   * @param {!Window} window
+   */
+  registerDialog(window) {}
+
+  /**
+   * Launches a new File Manager window.
+   *
+   * @param {Object=} opt_appState App state.
+   * @return {!Promise<chrome.app.window.AppWindow|string>} Resolved with the
+   *     App ID.
+   */
+  async launchFileManager(opt_appState) {}
 }
diff --git a/ui/file_manager/externs/background_window.js b/ui/file_manager/externs/background_window.js
index 81f424d..410d8fce 100644
--- a/ui/file_manager/externs/background_window.js
+++ b/ui/file_manager/externs/background_window.js
@@ -19,14 +19,5 @@
      * @type {!BackgroundBase}
      */
     this.background;
-
-    /**
-     * @type {!Object}
-     */
-    this.launcher = {};
   }
-  /**
-   * @param {Window} window
-   */
-  registerDialog(window) {}
 }
diff --git a/ui/file_manager/file_manager/background/js/background.js b/ui/file_manager/file_manager/background/js/background.js
index 3cf30a8..6f4aa4d 100644
--- a/ui/file_manager/file_manager/background/js/background.js
+++ b/ui/file_manager/file_manager/background/js/background.js
@@ -184,6 +184,33 @@
   }
 
   /**
+   * Registers dialog window to the background page.
+   *
+   * @param {!Window} dialogWindow Window of the dialog.
+   */
+  registerDialog(dialogWindow) {
+    const id = DIALOG_ID_PREFIX + (nextFileManagerDialogID++);
+    this.dialogs[id] = dialogWindow;
+    if (window.IN_TEST) {
+      dialogWindow.IN_TEST = true;
+    }
+    dialogWindow.addEventListener('pagehide', () => {
+      delete this.dialogs[id];
+    });
+  }
+
+  /**
+   * Launches a new File Manager window.
+   *
+   * @param {Object=} opt_appState App state.
+   * @return {!Promise<chrome.app.window.AppWindow|string>} Resolved with the
+   *     App ID.
+   */
+  async launchFileManager(opt_appState) {
+    return launcher.launchFileManager(opt_appState);
+  }
+
+  /**
    * Opens the volume root (or opt directoryPath) in main UI.
    *
    * @param {!Event} event An event with the volumeId or
@@ -553,21 +580,6 @@
  */
 let nextFileManagerDialogID = 0;
 
-/**
- * Registers dialog window to the background page.
- *
- * @param {!Window} dialogWindow Window of the dialog.
- */
-/* #export */ function registerDialog(dialogWindow) {
-  const id = DIALOG_ID_PREFIX + (nextFileManagerDialogID++);
-  window.background.dialogs[id] = dialogWindow;
-  if (window.IN_TEST) {
-    dialogWindow.IN_TEST = true;
-  }
-  dialogWindow.addEventListener('pagehide', () => {
-    delete window.background.dialogs[id];
-  });
-}
 
 /** @const {!string} */
 const GPLUS_PHOTOS_APP_ORIGIN =
@@ -577,7 +589,8 @@
  * Singleton instance of Background object.
  * @type {!FileBrowserBackgroundFull}
  */
-window.background = new FileBrowserBackgroundImpl();
+/* #export */ const background = new FileBrowserBackgroundImpl();
+window.background = background;
 
 /**
  * Lastly, end recording of the background page Load.BackgroundScript metric.
diff --git a/ui/file_manager/file_manager/foreground/js/file_manager.js b/ui/file_manager/file_manager/foreground/js/file_manager.js
index d19e849..1cff976 100644
--- a/ui/file_manager/file_manager/foreground/js/file_manager.js
+++ b/ui/file_manager/file_manager/foreground/js/file_manager.js
@@ -505,7 +505,7 @@
    * @param {Object=} appState App state.
    */
   launchFileManager(appState) {
-    this.backgroundPage_.launcher.launchFileManager(appState);
+    this.fileBrowserBackground_.launchFileManager(appState);
   }
 
   /**
@@ -867,7 +867,7 @@
     await new Promise(resolve => this.fileBrowserBackground_.ready(resolve));
     loadTimeData.data = this.fileBrowserBackground_.stringData;
     if (util.runningInBrowser()) {
-      this.backgroundPage_.registerDialog(window);
+      this.fileBrowserBackground_.registerDialog(window);
     }
     this.fileOperationManager_ =
         this.fileBrowserBackground_.fileOperationManager;
diff --git a/ui/ozone/platform/wayland/BUILD.gn b/ui/ozone/platform/wayland/BUILD.gn
index 31edd89c..ac195b3 100644
--- a/ui/ozone/platform/wayland/BUILD.gn
+++ b/ui/ozone/platform/wayland/BUILD.gn
@@ -51,8 +51,9 @@
     "host/shell_object_factory.h",
     "host/shell_popup_wrapper.cc",
     "host/shell_popup_wrapper.h",
-    "host/shell_surface_wrapper.cc",
     "host/shell_surface_wrapper.h",
+    "host/shell_toplevel_wrapper.cc",
+    "host/shell_toplevel_wrapper.h",
     "host/wayland_auxiliary_window.cc",
     "host/wayland_auxiliary_window.h",
     "host/wayland_buffer_manager_connector.cc",
@@ -138,6 +139,8 @@
     "host/xdg_popup_wrapper_impl.h",
     "host/xdg_surface_wrapper_impl.cc",
     "host/xdg_surface_wrapper_impl.h",
+    "host/xdg_toplevel_wrapper_impl.cc",
+    "host/xdg_toplevel_wrapper_impl.h",
     "host/zwp_primary_selection_device.cc",
     "host/zwp_primary_selection_device.h",
     "host/zwp_primary_selection_device_manager.cc",
@@ -147,6 +150,12 @@
     "host/zwp_text_input_wrapper.h",
     "host/zwp_text_input_wrapper_v1.cc",
     "host/zwp_text_input_wrapper_v1.h",
+    "host/zxdg_popup_v6_wrapper_impl.cc",
+    "host/zxdg_popup_v6_wrapper_impl.h",
+    "host/zxdg_surface_v6_wrapper_impl.cc",
+    "host/zxdg_surface_v6_wrapper_impl.h",
+    "host/zxdg_toplevel_v6_wrapper_impl.cc",
+    "host/zxdg_toplevel_v6_wrapper_impl.h",
     "ozone_platform_wayland.cc",
     "ozone_platform_wayland.h",
   ]
diff --git a/ui/ozone/platform/wayland/host/shell_object_factory.cc b/ui/ozone/platform/wayland/host/shell_object_factory.cc
index dea7148..ed7842b 100644
--- a/ui/ozone/platform/wayland/host/shell_object_factory.cc
+++ b/ui/ozone/platform/wayland/host/shell_object_factory.cc
@@ -8,20 +8,37 @@
 #include "ui/ozone/platform/wayland/host/wayland_connection.h"
 #include "ui/ozone/platform/wayland/host/xdg_popup_wrapper_impl.h"
 #include "ui/ozone/platform/wayland/host/xdg_surface_wrapper_impl.h"
+#include "ui/ozone/platform/wayland/host/xdg_toplevel_wrapper_impl.h"
+#include "ui/ozone/platform/wayland/host/zxdg_popup_v6_wrapper_impl.h"
+#include "ui/ozone/platform/wayland/host/zxdg_surface_v6_wrapper_impl.h"
+#include "ui/ozone/platform/wayland/host/zxdg_toplevel_v6_wrapper_impl.h"
 
 namespace ui {
 
 ShellObjectFactory::ShellObjectFactory() = default;
 ShellObjectFactory::~ShellObjectFactory() = default;
 
-std::unique_ptr<ShellSurfaceWrapper>
-ShellObjectFactory::CreateShellSurfaceWrapper(WaylandConnection* connection,
-                                              WaylandWindow* wayland_window) {
-  if (connection->shell() || connection->shell_v6()) {
+std::unique_ptr<ShellToplevelWrapper>
+ShellObjectFactory::CreateShellToplevelWrapper(WaylandConnection* connection,
+                                               WaylandWindow* wayland_window) {
+  if (connection->shell()) {
     auto surface =
         std::make_unique<XDGSurfaceWrapperImpl>(wayland_window, connection);
-    return surface->Initialize(true /* with_top_level */) ? std::move(surface)
-                                                          : nullptr;
+    if (!surface->Initialize())
+      return nullptr;
+
+    auto toplevel = std::make_unique<XDGToplevelWrapperImpl>(
+        std::move(surface), wayland_window, connection);
+    return toplevel->Initialize() ? std::move(toplevel) : nullptr;
+  } else if (connection->shell_v6()) {
+    auto surface =
+        std::make_unique<ZXDGSurfaceV6WrapperImpl>(wayland_window, connection);
+    if (!surface->Initialize())
+      return nullptr;
+
+    auto toplevel = std::make_unique<ZXDGToplevelV6WrapperImpl>(
+        std::move(surface), wayland_window, connection);
+    return toplevel->Initialize() ? std::move(toplevel) : nullptr;
   }
   LOG(WARNING) << "Shell protocol is not available.";
   return nullptr;
@@ -31,15 +48,24 @@
     WaylandConnection* connection,
     WaylandWindow* wayland_window,
     const gfx::Rect& bounds) {
-  if (connection->shell() || connection->shell_v6()) {
+  if (connection->shell()) {
     auto surface =
         std::make_unique<XDGSurfaceWrapperImpl>(wayland_window, connection);
-    if (!surface->Initialize(false /* with_top_level */))
+    if (!surface->Initialize())
       return nullptr;
 
     auto popup = std::make_unique<XDGPopupWrapperImpl>(std::move(surface),
                                                        wayland_window);
     return popup->Initialize(connection, bounds) ? std::move(popup) : nullptr;
+  } else if (connection->shell_v6()) {
+    auto surface =
+        std::make_unique<ZXDGSurfaceV6WrapperImpl>(wayland_window, connection);
+    if (!surface->Initialize())
+      return nullptr;
+
+    auto popup = std::make_unique<ZXDGPopupV6WrapperImpl>(std::move(surface),
+                                                          wayland_window);
+    return popup->Initialize(connection, bounds) ? std::move(popup) : nullptr;
   }
   LOG(WARNING) << "Shell protocol is not available.";
   return nullptr;
diff --git a/ui/ozone/platform/wayland/host/shell_object_factory.h b/ui/ozone/platform/wayland/host/shell_object_factory.h
index a915326c..da3472a 100644
--- a/ui/ozone/platform/wayland/host/shell_object_factory.h
+++ b/ui/ozone/platform/wayland/host/shell_object_factory.h
@@ -13,7 +13,7 @@
 
 namespace ui {
 
-class ShellSurfaceWrapper;
+class ShellToplevelWrapper;
 class ShellPopupWrapper;
 class WaylandConnection;
 class WaylandWindow;
@@ -27,8 +27,8 @@
   ShellObjectFactory();
   ~ShellObjectFactory();
 
-  // Creates and initializes a ShellSurfaceWrapper.
-  std::unique_ptr<ShellSurfaceWrapper> CreateShellSurfaceWrapper(
+  // Creates and initializes a ShellToplevelWrapper.
+  std::unique_ptr<ShellToplevelWrapper> CreateShellToplevelWrapper(
       WaylandConnection* connection,
       WaylandWindow* wayland_window);
 
@@ -41,4 +41,4 @@
 
 }  // namespace ui
 
-#endif  // UI_OZONE_PLATFORM_WAYLAND_HOST_SHELL_OBJECT_FACTORY_H_
\ No newline at end of file
+#endif  // UI_OZONE_PLATFORM_WAYLAND_HOST_SHELL_OBJECT_FACTORY_H_
diff --git a/ui/ozone/platform/wayland/host/shell_popup_wrapper.cc b/ui/ozone/platform/wayland/host/shell_popup_wrapper.cc
index f9f5aaa..469d2b9 100644
--- a/ui/ozone/platform/wayland/host/shell_popup_wrapper.cc
+++ b/ui/ozone/platform/wayland/host/shell_popup_wrapper.cc
@@ -6,6 +6,10 @@
 
 #include "base/check_op.h"
 #include "base/notreached.h"
+#include "ui/ozone/platform/wayland/common/wayland_util.h"
+#include "ui/ozone/platform/wayland/host/wayland_connection.h"
+#include "ui/ozone/platform/wayland/host/wayland_event_source.h"
+#include "ui/ozone/platform/wayland/host/wayland_toplevel_window.h"
 
 namespace ui {
 
@@ -79,4 +83,117 @@
   return anchor_rect;
 }
 
+WlAnchor GetAnchor(MenuType menu_type, const gfx::Rect& bounds) {
+  WlAnchor anchor = WlAnchor::None;
+  switch (menu_type) {
+    case MenuType::TYPE_RIGHT_CLICK:
+      anchor = WlAnchor::TopLeft;
+      break;
+    case MenuType::TYPE_3DOT_PARENT_MENU:
+      anchor = WlAnchor::BottomRight;
+      break;
+    case MenuType::TYPE_3DOT_CHILD_MENU:
+      // Chromium may want to manually position a child menu on the left side of
+      // its parent menu. Thus, react accordingly. Positive x means the child is
+      // located on the right side of the parent and negative - on the left
+      // side.
+      if (bounds.x() >= 0)
+        anchor = WlAnchor::TopRight;
+      else
+        anchor = WlAnchor::TopLeft;
+      break;
+    case MenuType::TYPE_UNKNOWN:
+      NOTREACHED() << "Unsupported menu type";
+      break;
+  }
+
+  return anchor;
+}
+
+WlGravity GetGravity(MenuType menu_type, const gfx::Rect& bounds) {
+  WlGravity gravity = WlGravity::None;
+  switch (menu_type) {
+    case MenuType::TYPE_RIGHT_CLICK:
+      gravity = WlGravity::BottomRight;
+      break;
+    case MenuType::TYPE_3DOT_PARENT_MENU:
+      gravity = WlGravity::BottomRight;
+      break;
+    case MenuType::TYPE_3DOT_CHILD_MENU:
+      // Chromium may want to manually position a child menu on the left side of
+      // its parent menu. Thus, react accordingly. Positive x means the child is
+      // located on the right side of the parent and negative - on the left
+      // side.
+      if (bounds.x() >= 0)
+        gravity = WlGravity::BottomRight;
+      else
+        gravity = WlGravity::BottomLeft;
+      break;
+    case MenuType::TYPE_UNKNOWN:
+      NOTREACHED() << "Unsupported menu type";
+      break;
+  }
+
+  return gravity;
+}
+
+WlConstraintAdjustment GetConstraintAdjustment(MenuType menu_type) {
+  WlConstraintAdjustment constraint = WlConstraintAdjustment::None;
+
+  switch (menu_type) {
+    case MenuType::TYPE_RIGHT_CLICK:
+      constraint = WlConstraintAdjustment::SlideX |
+                   WlConstraintAdjustment::SlideY |
+                   WlConstraintAdjustment::FlipY;
+      break;
+    case MenuType::TYPE_3DOT_PARENT_MENU:
+      constraint =
+          WlConstraintAdjustment::SlideX | WlConstraintAdjustment::FlipY;
+      break;
+    case MenuType::TYPE_3DOT_CHILD_MENU:
+      constraint =
+          WlConstraintAdjustment::SlideY | WlConstraintAdjustment::FlipX;
+      break;
+    case MenuType::TYPE_UNKNOWN:
+      NOTREACHED() << "Unsupported menu type";
+      break;
+  }
+
+  return constraint;
+}
+
+MenuType ShellPopupWrapper::GetMenuTypeForPositioner(
+    WaylandConnection* connection,
+    WaylandWindow* parent_window) const {
+  bool is_right_click_menu =
+      connection->event_source()->last_pointer_button_pressed() &
+      EF_RIGHT_MOUSE_BUTTON;
+
+  // Different types of menu require different anchors, constraint adjustments,
+  // gravity and etc.
+  if (is_right_click_menu)
+    return MenuType::TYPE_RIGHT_CLICK;
+  else if (!wl::IsMenuType(parent_window->type()))
+    return MenuType::TYPE_3DOT_PARENT_MENU;
+  else
+    return MenuType::TYPE_3DOT_CHILD_MENU;
+}
+
+bool ShellPopupWrapper::CanGrabPopup(WaylandConnection* connection) const {
+  // When drag process starts, as described the protocol -
+  // https://goo.gl/1Mskq3, the client must have an active implicit grab. If
+  // we try to create a popup and grab it, it will be immediately dismissed.
+  // Thus, do not take explicit grab during drag process.
+  if (connection->IsDragInProgress() || !connection->seat())
+    return false;
+
+  // According to the definition of the xdg protocol, the grab request must be
+  // used in response to some sort of user action like a button press, key
+  // press, or touch down event.
+  EventType last_event_type = connection->event_serial().event_type;
+  return last_event_type == ET_TOUCH_PRESSED ||
+         last_event_type == ET_KEY_PRESSED ||
+         last_event_type == ET_MOUSE_PRESSED;
+}
+
 }  // namespace ui
diff --git a/ui/ozone/platform/wayland/host/shell_popup_wrapper.h b/ui/ozone/platform/wayland/host/shell_popup_wrapper.h
index 2970549..14ca68a97 100644
--- a/ui/ozone/platform/wayland/host/shell_popup_wrapper.h
+++ b/ui/ozone/platform/wayland/host/shell_popup_wrapper.h
@@ -11,6 +11,7 @@
 namespace ui {
 
 class WaylandConnection;
+class WaylandWindow;
 
 enum class MenuType {
   TYPE_RIGHT_CLICK,
@@ -73,11 +74,18 @@
   // Initializes the popup surface.
   virtual bool Initialize(WaylandConnection* connection,
                           const gfx::Rect& bounds) = 0;
+
+  MenuType GetMenuTypeForPositioner(WaylandConnection* connection,
+                                    WaylandWindow* parent_window) const;
+  bool CanGrabPopup(WaylandConnection* connection) const;
 };
 
 gfx::Rect GetAnchorRect(MenuType menu_type,
                         const gfx::Rect& menu_bounds,
                         const gfx::Rect& parent_window_bounds);
+WlAnchor GetAnchor(MenuType menu_type, const gfx::Rect& bounds);
+WlGravity GetGravity(MenuType menu_type, const gfx::Rect& bounds);
+WlConstraintAdjustment GetConstraintAdjustment(MenuType menu_type);
 
 }  // namespace ui
 
diff --git a/ui/ozone/platform/wayland/host/shell_surface_wrapper.h b/ui/ozone/platform/wayland/host/shell_surface_wrapper.h
index ff5ab6c..de2dceb7 100644
--- a/ui/ozone/platform/wayland/host/shell_surface_wrapper.h
+++ b/ui/ozone/platform/wayland/host/shell_surface_wrapper.h
@@ -5,90 +5,27 @@
 #ifndef UI_OZONE_PLATFORM_WAYLAND_HOST_SHELL_SURFACE_WRAPPER_H_
 #define UI_OZONE_PLATFORM_WAYLAND_HOST_SHELL_SURFACE_WRAPPER_H_
 
-#include "base/strings/string16.h"
-#include "ui/ozone/platform/wayland/common/wayland_object.h"
-
 namespace gfx {
 class Rect;
 }
 
 namespace ui {
 
-class WaylandConnection;
-
-enum class DecorationMode {
-  // Client-side decoration for a window.
-  // In this case, the client is responsible for drawing decorations
-  // for a window (e.g. caption bar, close button). This is suitable for
-  // windows using custom frame.
-  kClientSide = 1,
-  // Server-side decoration for a window.
-  // In this case, the ash window manager is responsible for drawing
-  // decorations. This is suitable for windows using native frame.
-  // e.g. taskmanager.
-  kServerSide
-};
-
-// Wrapper interface for different wayland shells shell versions.
+// Wrapper interface for different wayland xdg-shell surface versions.
 class ShellSurfaceWrapper {
  public:
   virtual ~ShellSurfaceWrapper() {}
 
-  // Initializes the ShellSurface. Some protocols may require to create shell
-  // surface without toplevel role and assign a popup role to it later.
-  virtual bool Initialize(bool with_toplevel) = 0;
-
-  // Sets a native window to maximized state.
-  virtual void SetMaximized() = 0;
-
-  // Unsets a native window from maximized state.
-  virtual void UnSetMaximized() = 0;
-
-  // Sets a native window to fullscreen state.
-  virtual void SetFullscreen() = 0;
-
-  // Unsets a native window from fullscreen state.
-  virtual void UnSetFullscreen() = 0;
-
-  // Sets a native window to minimized state.
-  virtual void SetMinimized() = 0;
-
-  // Tells wayland to start interactive window drag.
-  virtual void SurfaceMove(WaylandConnection* connection) = 0;
-
-  // Tells wayland to start interactive window resize.
-  virtual void SurfaceResize(WaylandConnection* connection,
-                             uint32_t hittest) = 0;
-
-  // Sets a title of a native window.
-  virtual void SetTitle(const base::string16& title) = 0;
+  // Initializes the ShellSurface.
+  virtual bool Initialize() = 0;
 
   // Sends acknowledge configure event back to wayland.
   virtual void AckConfigure() = 0;
 
   // Sets a desired window geometry once wayland requests client to do so.
   virtual void SetWindowGeometry(const gfx::Rect& bounds) = 0;
-
-  // Sets the minimum size for the top level.
-  virtual void SetMinSize(int32_t width, int32_t height) = 0;
-
-  // Sets the maximum size for the top level.
-  virtual void SetMaxSize(int32_t width, int32_t height) = 0;
-
-  // Sets an app id of the native window that is shown as an application name
-  // and hints the compositor that it can group application surfaces together by
-  // their app id. This also helps the compositor to identify application's
-  // .desktop file and use the icon set there.
-  virtual void SetAppId(const std::string& app_id) = 0;
-
-  // In case of kClientSide or kServerSide, this function sends a
-  // request to the wayland compositor to update the decoration mode
-  // for a surface associated with this top level window.
-  virtual void SetDecoration(DecorationMode decoration) = 0;
 };
 
-bool CheckIfWlArrayHasValue(struct wl_array* wl_array, uint32_t value);
-
 }  // namespace ui
 
 #endif  // UI_OZONE_PLATFORM_WAYLAND_HOST_SHELL_SURFACE_WRAPPER_H_
diff --git a/ui/ozone/platform/wayland/host/shell_surface_wrapper.cc b/ui/ozone/platform/wayland/host/shell_toplevel_wrapper.cc
similarity index 92%
rename from ui/ozone/platform/wayland/host/shell_surface_wrapper.cc
rename to ui/ozone/platform/wayland/host/shell_toplevel_wrapper.cc
index ea7d3ca..e84d7ca 100644
--- a/ui/ozone/platform/wayland/host/shell_surface_wrapper.cc
+++ b/ui/ozone/platform/wayland/host/shell_toplevel_wrapper.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "ui/ozone/platform/wayland/host/shell_surface_wrapper.h"
+#include "ui/ozone/platform/wayland/host/shell_toplevel_wrapper.h"
 
 namespace ui {
 
diff --git a/ui/ozone/platform/wayland/host/shell_toplevel_wrapper.h b/ui/ozone/platform/wayland/host/shell_toplevel_wrapper.h
new file mode 100644
index 0000000..0352f48
--- /dev/null
+++ b/ui/ozone/platform/wayland/host/shell_toplevel_wrapper.h
@@ -0,0 +1,95 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_OZONE_PLATFORM_WAYLAND_HOST_SHELL_TOPLEVEL_WRAPPER_H_
+#define UI_OZONE_PLATFORM_WAYLAND_HOST_SHELL_TOPLEVEL_WRAPPER_H_
+
+#include "base/strings/string16.h"
+#include "ui/ozone/platform/wayland/common/wayland_object.h"
+
+namespace gfx {
+class Rect;
+}
+
+namespace ui {
+
+class WaylandConnection;
+
+// A wrapper around different versions of xdg toplevels. Allows
+// WaylandToplevelWindow to set window-like properties such as maximize,
+// fullscreen, and minimize, set application-specific metadata like title and
+// id, as well as trigger user interactive operations such as interactive resize
+// and move.
+class ShellToplevelWrapper {
+ public:
+  enum class DecorationMode {
+    // Client-side decoration for a window.
+    // In this case, the client is responsible for drawing decorations
+    // for a window (e.g. caption bar, close button). This is suitable for
+    // windows using custom frame.
+    kClientSide = 1,
+    // Server-side decoration for a window.
+    // In this case, the ash window manager is responsible for drawing
+    // decorations. This is suitable for windows using native frame.
+    // e.g. taskmanager.
+    kServerSide
+  };
+
+  virtual ~ShellToplevelWrapper() = default;
+
+  // Initializes the ShellToplevel.
+  virtual bool Initialize() = 0;
+
+  // Sets a native window to maximized state.
+  virtual void SetMaximized() = 0;
+
+  // Unsets a native window from maximized state.
+  virtual void UnSetMaximized() = 0;
+
+  // Sets a native window to fullscreen state.
+  virtual void SetFullscreen() = 0;
+
+  // Unsets a native window from fullscreen state.
+  virtual void UnSetFullscreen() = 0;
+
+  // Sets a native window to minimized state.
+  virtual void SetMinimized() = 0;
+
+  // Tells wayland to start interactive window drag.
+  virtual void SurfaceMove(WaylandConnection* connection) = 0;
+
+  // Tells wayland to start interactive window resize.
+  virtual void SurfaceResize(WaylandConnection* connection,
+                             uint32_t hittest) = 0;
+
+  // Sets a title of a native window.
+  virtual void SetTitle(const base::string16& title) = 0;
+
+  // Sets a desired window geometry once wayland requests client to do so.
+  virtual void SetWindowGeometry(const gfx::Rect& bounds) = 0;
+
+  // Sets the minimum size for the top level.
+  virtual void SetMinSize(int32_t width, int32_t height) = 0;
+
+  // Sets the maximum size for the top level.
+  virtual void SetMaxSize(int32_t width, int32_t height) = 0;
+
+  // Sets an app id of the native window that is shown as an application name
+  // and hints the compositor that it can group application surfaces together by
+  // their app id. This also helps the compositor to identify application's
+  // .desktop file and use the icon set there.
+  virtual void SetAppId(const std::string& app_id) = 0;
+
+  // In case of kClientSide or kServerSide, this function sends a request to the
+  // wayland compositor to update the decoration mode for a surface associated
+  // with this top level window.
+  virtual void SetDecoration(DecorationMode decoration) = 0;
+};
+
+// Look for |value| in |wl_array| in C++ style.
+bool CheckIfWlArrayHasValue(struct wl_array* wl_array, uint32_t value);
+
+}  // namespace ui
+
+#endif  // UI_OZONE_PLATFORM_WAYLAND_HOST_SHELL_TOPLEVEL_WRAPPER_H_
diff --git a/ui/ozone/platform/wayland/host/wayland_toplevel_window.cc b/ui/ozone/platform/wayland/host/wayland_toplevel_window.cc
index 1de78dcc..acfc1e7 100644
--- a/ui/ozone/platform/wayland/host/wayland_toplevel_window.cc
+++ b/ui/ozone/platform/wayland/host/wayland_toplevel_window.cc
@@ -12,7 +12,7 @@
 #include "ui/base/hit_test.h"
 #include "ui/gfx/native_widget_types.h"
 #include "ui/ozone/platform/wayland/host/shell_object_factory.h"
-#include "ui/ozone/platform/wayland/host/shell_surface_wrapper.h"
+#include "ui/ozone/platform/wayland/host/shell_toplevel_wrapper.h"
 #include "ui/ozone/platform/wayland/host/wayland_buffer_manager_host.h"
 #include "ui/ozone/platform/wayland/host/wayland_connection.h"
 #include "ui/ozone/platform/wayland/host/wayland_event_source.h"
@@ -40,21 +40,21 @@
 
 WaylandToplevelWindow::~WaylandToplevelWindow() = default;
 
-bool WaylandToplevelWindow::CreateShellSurface() {
+bool WaylandToplevelWindow::CreateShellToplevel() {
   ShellObjectFactory factory;
-  shell_surface_ = factory.CreateShellSurfaceWrapper(connection(), this);
-  if (!shell_surface_) {
-    LOG(ERROR) << "Failed to create a ShellSurface.";
+  shell_toplevel_ = factory.CreateShellToplevelWrapper(connection(), this);
+  if (!shell_toplevel_) {
+    LOG(ERROR) << "Failed to create a ShellToplevel.";
     return false;
   }
 
 #if BUILDFLAG(IS_CHROMEOS_LACROS)
-  shell_surface_->SetAppId(window_unique_id_);
+  shell_toplevel_->SetAppId(window_unique_id_);
 #else
-  shell_surface_->SetAppId(wm_class_class_);
+  shell_toplevel_->SetAppId(wm_class_class_);
 #endif
   SetDecorationMode();
-  shell_surface_->SetTitle(window_title_);
+  shell_toplevel_->SetTitle(window_title_);
   SetSizeConstraints();
   TriggerStateChanges();
   InitializeAuraShellSurface();
@@ -64,10 +64,10 @@
 void WaylandToplevelWindow::ApplyPendingBounds() {
   if (pending_bounds_dip_.IsEmpty())
     return;
-  DCHECK(shell_surface_);
+  DCHECK(shell_toplevel_);
 
   SetBoundsDip(pending_bounds_dip_);
-  shell_surface_->SetWindowGeometry(pending_bounds_dip_);
+  shell_toplevel_->SetWindowGeometry(pending_bounds_dip_);
   pending_bounds_dip_ = gfx::Rect();
   connection()->ScheduleFlush();
 }
@@ -75,22 +75,22 @@
 void WaylandToplevelWindow::DispatchHostWindowDragMovement(
     int hittest,
     const gfx::Point& pointer_location_in_px) {
-  DCHECK(shell_surface_);
+  DCHECK(shell_toplevel_);
 
   connection()->event_source()->ResetPointerFlags();
   if (hittest == HTCAPTION)
-    shell_surface_->SurfaceMove(connection());
+    shell_toplevel_->SurfaceMove(connection());
   else
-    shell_surface_->SurfaceResize(connection(), hittest);
+    shell_toplevel_->SurfaceResize(connection(), hittest);
 
   connection()->ScheduleFlush();
 }
 
 void WaylandToplevelWindow::Show(bool inactive) {
-  if (shell_surface_)
+  if (shell_toplevel_)
     return;
 
-  if (!CreateShellSurface()) {
+  if (!CreateShellToplevel()) {
     Close();
     return;
   }
@@ -104,7 +104,7 @@
 }
 
 void WaylandToplevelWindow::Hide() {
-  if (!shell_surface_)
+  if (!shell_toplevel_)
     return;
 
   if (child_window()) {
@@ -112,7 +112,7 @@
     set_child_window(nullptr);
   }
 
-  shell_surface_.reset();
+  shell_toplevel_.reset();
   connection()->ScheduleFlush();
 
   // Detach buffer from surface in order to completely shutdown menus and
@@ -123,7 +123,7 @@
 bool WaylandToplevelWindow::IsVisible() const {
   // X and Windows return true if the window is minimized. For consistency, do
   // the same.
-  return !!shell_surface_ || state_ == PlatformWindowState::kMinimized;
+  return !!shell_toplevel_ || state_ == PlatformWindowState::kMinimized;
 }
 
 void WaylandToplevelWindow::SetTitle(const base::string16& title) {
@@ -132,8 +132,8 @@
 
   window_title_ = title;
 
-  if (shell_surface_) {
-    shell_surface_->SetTitle(title);
+  if (shell_toplevel_) {
+    shell_toplevel_->SetTitle(title);
     connection()->ScheduleFlush();
   }
 }
@@ -167,7 +167,7 @@
 }
 
 void WaylandToplevelWindow::Restore() {
-  DCHECK(shell_surface_);
+  DCHECK(shell_toplevel_);
 
   // Differently from other platforms, under Wayland, unmaximizing the dragged
   // window before starting the drag loop is not needed as it is assumed to be
@@ -188,7 +188,7 @@
 
 void WaylandToplevelWindow::SizeConstraintsChanged() {
   // Size constraints only make sense for normal windows.
-  if (!shell_surface_)
+  if (!shell_toplevel_)
     return;
 
   DCHECK(delegate());
@@ -209,7 +209,7 @@
   if (use_native_frame_ == use_native_frame)
     return;
   use_native_frame_ = use_native_frame;
-  if (shell_surface_)
+  if (shell_toplevel_)
     SetDecorationMode();
 }
 
@@ -330,7 +330,7 @@
 }
 
 void WaylandToplevelWindow::TriggerStateChanges() {
-  if (!shell_surface_)
+  if (!shell_toplevel_)
     return;
 
   // Call UnSetMaximized only if current state is normal. Otherwise, if the
@@ -338,15 +338,15 @@
   // UnSetMaximized may result in wrong restored window position that clients
   // are not allowed to know about.
   if (state_ == PlatformWindowState::kMinimized) {
-    shell_surface_->SetMinimized();
+    shell_toplevel_->SetMinimized();
   } else if (state_ == PlatformWindowState::kFullScreen) {
-    shell_surface_->SetFullscreen();
+    shell_toplevel_->SetFullscreen();
   } else if (previous_state_ == PlatformWindowState::kFullScreen) {
-    shell_surface_->UnSetFullscreen();
+    shell_toplevel_->UnSetFullscreen();
   } else if (state_ == PlatformWindowState::kMaximized) {
-    shell_surface_->SetMaximized();
+    shell_toplevel_->SetMaximized();
   } else if (state_ == PlatformWindowState::kNormal) {
-    shell_surface_->UnSetMaximized();
+    shell_toplevel_->UnSetMaximized();
   }
 
   connection()->ScheduleFlush();
@@ -364,9 +364,9 @@
 
 void WaylandToplevelWindow::SetSizeConstraints() {
   if (min_size_.has_value())
-    shell_surface_->SetMinSize(min_size_->width(), min_size_->height());
+    shell_toplevel_->SetMinSize(min_size_->width(), min_size_->height());
   if (max_size_.has_value())
-    shell_surface_->SetMaxSize(max_size_->width(), max_size_->height());
+    shell_toplevel_->SetMaxSize(max_size_->width(), max_size_->height());
 
   connection()->ScheduleFlush();
 }
@@ -387,7 +387,7 @@
 void WaylandToplevelWindow::InitializeAuraShellSurface() {
   // InitializeAuraShellSurface() should be called after the XDG surface is
   // initialized.
-  DCHECK(shell_surface_);
+  DCHECK(shell_toplevel_);
 
   if (connection()->zaura_shell() && !aura_surface_) {
     aura_surface_.reset(zaura_shell_get_aura_surface(
@@ -397,13 +397,15 @@
 }
 
 void WaylandToplevelWindow::SetDecorationMode() {
-  DCHECK(shell_surface_);
+  DCHECK(shell_toplevel_);
   if (use_native_frame_) {
     // Set server-side decoration for windows using a native frame,
     // e.g. taskmanager
-    shell_surface_->SetDecoration(DecorationMode::kServerSide);
+    shell_toplevel_->SetDecoration(
+        ShellToplevelWrapper::DecorationMode::kServerSide);
   } else {
-    shell_surface_->SetDecoration(DecorationMode::kClientSide);
+    shell_toplevel_->SetDecoration(
+        ShellToplevelWrapper::DecorationMode::kClientSide);
   }
 }
 
diff --git a/ui/ozone/platform/wayland/host/wayland_toplevel_window.h b/ui/ozone/platform/wayland/host/wayland_toplevel_window.h
index 591ca199..eb0636a 100644
--- a/ui/ozone/platform/wayland/host/wayland_toplevel_window.h
+++ b/ui/ozone/platform/wayland/host/wayland_toplevel_window.h
@@ -14,7 +14,7 @@
 
 namespace ui {
 
-class ShellSurfaceWrapper;
+class ShellToplevelWrapper;
 
 class WaylandToplevelWindow : public WaylandWindow,
                               public WmMoveResizeHandler,
@@ -27,7 +27,7 @@
   WaylandToplevelWindow& operator=(const WaylandToplevelWindow&) = delete;
   ~WaylandToplevelWindow() override;
 
-  ShellSurfaceWrapper* shell_surface() const { return shell_surface_.get(); }
+  ShellToplevelWrapper* shell_toplevel() const { return shell_toplevel_.get(); }
 
   // Apply the bounds specified in the most recent configure event. This should
   // be called after processing all pending events in the wayland connection.
@@ -77,11 +77,11 @@
   void SetWindowState(PlatformWindowState state);
 
   // Creates a surface window, which is visible as a main window.
-  bool CreateShellSurface();
+  bool CreateShellToplevel();
 
   WmMoveResizeHandler* AsWmMoveResizeHandler();
 
-  // Propagates the |min_size_| and |max_size_| to the ShellSurface.
+  // Propagates the |min_size_| and |max_size_| to the ShellToplevel.
   void SetSizeConstraints();
 
   void SetOrResetRestoredBounds();
@@ -94,7 +94,7 @@
   void SetDecorationMode();
 
   // Wrappers around shell surface.
-  std::unique_ptr<ShellSurfaceWrapper> shell_surface_;
+  std::unique_ptr<ShellToplevelWrapper> shell_toplevel_;
 
   // These bounds attributes below have suffices that indicate units used.
   // Wayland operates in DIP but the platform operates in physical pixels so
@@ -127,7 +127,7 @@
   std::string wm_class_class_;
 #endif
 
-  // Title of the ShellSurface.
+  // Title of the ShellToplevel.
   base::string16 window_title_;
 
   // Max and min sizes of the WaylandToplevelWindow window.
diff --git a/ui/ozone/platform/wayland/host/xdg_popup_wrapper_impl.cc b/ui/ozone/platform/wayland/host/xdg_popup_wrapper_impl.cc
index cee4e46..ceffc66f 100644
--- a/ui/ozone/platform/wayland/host/xdg_popup_wrapper_impl.cc
+++ b/ui/ozone/platform/wayland/host/xdg_popup_wrapper_impl.cc
@@ -17,17 +17,17 @@
 #include "ui/gfx/geometry/rect.h"
 #include "ui/ozone/platform/wayland/common/wayland_util.h"
 #include "ui/ozone/platform/wayland/host/wayland_connection.h"
-#include "ui/ozone/platform/wayland/host/wayland_event_source.h"
 #include "ui/ozone/platform/wayland/host/wayland_pointer.h"
 #include "ui/ozone/platform/wayland/host/wayland_popup.h"
 #include "ui/ozone/platform/wayland/host/wayland_toplevel_window.h"
 #include "ui/ozone/platform/wayland/host/xdg_surface_wrapper_impl.h"
+#include "ui/ozone/platform/wayland/host/xdg_toplevel_wrapper_impl.h"
 
 namespace ui {
 
 namespace {
 
-uint32_t TranslateAnchorStable(WlAnchor anchor) {
+uint32_t TranslateAnchor(WlAnchor anchor) {
   switch (anchor) {
     case WlAnchor::None:
       return XDG_POSITIONER_ANCHOR_NONE;
@@ -50,7 +50,7 @@
   }
 }
 
-uint32_t TranslateGravityStable(WlGravity gravity) {
+uint32_t TranslateGravity(WlGravity gravity) {
   switch (gravity) {
     case WlGravity::None:
       return XDG_POSITIONER_GRAVITY_NONE;
@@ -73,7 +73,7 @@
   }
 }
 
-uint32_t TranslateContraintAdjustmentStable(
+uint32_t TranslateContraintAdjustment(
     WlConstraintAdjustment constraint_adjustment) {
   uint32_t res = 0;
   if ((constraint_adjustment & WlConstraintAdjustment::SlideX) !=
@@ -97,175 +97,14 @@
   return res;
 }
 
-uint32_t TranslateAnchorV6(WlAnchor anchor) {
-  switch (anchor) {
-    case WlAnchor::None:
-      return ZXDG_POSITIONER_V6_ANCHOR_NONE;
-    case WlAnchor::Top:
-      return ZXDG_POSITIONER_V6_ANCHOR_TOP;
-    case WlAnchor::Bottom:
-      return ZXDG_POSITIONER_V6_ANCHOR_BOTTOM;
-    case WlAnchor::Left:
-      return ZXDG_POSITIONER_V6_ANCHOR_LEFT;
-    case WlAnchor::Right:
-      return ZXDG_POSITIONER_V6_ANCHOR_RIGHT;
-    case WlAnchor::TopLeft:
-      return ZXDG_POSITIONER_V6_ANCHOR_TOP | ZXDG_POSITIONER_V6_ANCHOR_LEFT;
-    case WlAnchor::BottomLeft:
-      return ZXDG_POSITIONER_V6_ANCHOR_BOTTOM | ZXDG_POSITIONER_V6_ANCHOR_LEFT;
-    case WlAnchor::TopRight:
-      return ZXDG_POSITIONER_V6_ANCHOR_TOP | ZXDG_POSITIONER_V6_ANCHOR_RIGHT;
-    case WlAnchor::BottomRight:
-      return ZXDG_POSITIONER_V6_ANCHOR_BOTTOM | ZXDG_POSITIONER_V6_ANCHOR_RIGHT;
-  }
-}
-
-uint32_t TranslateGravityV6(WlGravity gravity) {
-  switch (gravity) {
-    case WlGravity::None:
-      return ZXDG_POSITIONER_V6_GRAVITY_NONE;
-    case WlGravity::Top:
-      return ZXDG_POSITIONER_V6_GRAVITY_TOP;
-    case WlGravity::Bottom:
-      return ZXDG_POSITIONER_V6_GRAVITY_BOTTOM;
-    case WlGravity::Left:
-      return ZXDG_POSITIONER_V6_GRAVITY_LEFT;
-    case WlGravity::Right:
-      return ZXDG_POSITIONER_V6_GRAVITY_RIGHT;
-    case WlGravity::TopLeft:
-      return ZXDG_POSITIONER_V6_GRAVITY_TOP | ZXDG_POSITIONER_V6_GRAVITY_LEFT;
-    case WlGravity::BottomLeft:
-      return ZXDG_POSITIONER_V6_GRAVITY_BOTTOM |
-             ZXDG_POSITIONER_V6_GRAVITY_LEFT;
-    case WlGravity::TopRight:
-      return ZXDG_POSITIONER_V6_GRAVITY_TOP | ZXDG_POSITIONER_V6_GRAVITY_RIGHT;
-    case WlGravity::BottomRight:
-      return ZXDG_POSITIONER_V6_GRAVITY_BOTTOM |
-             ZXDG_POSITIONER_V6_GRAVITY_RIGHT;
-  }
-}
-
-uint32_t TranslateContraintAdjustmentV6(
-    WlConstraintAdjustment constraint_adjustment) {
-  uint32_t res = 0;
-  if ((constraint_adjustment & WlConstraintAdjustment::SlideX) !=
-      WlConstraintAdjustment::None)
-    res |= ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_SLIDE_X;
-  if ((constraint_adjustment & WlConstraintAdjustment::SlideY) !=
-      WlConstraintAdjustment::None)
-    res |= ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_SLIDE_Y;
-  if ((constraint_adjustment & WlConstraintAdjustment::FlipX) !=
-      WlConstraintAdjustment::None)
-    res |= ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_FLIP_X;
-  if ((constraint_adjustment & WlConstraintAdjustment::FlipY) !=
-      WlConstraintAdjustment::None)
-    res |= ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_FLIP_Y;
-  if ((constraint_adjustment & WlConstraintAdjustment::ResizeX) !=
-      WlConstraintAdjustment::None)
-    res |= ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_RESIZE_X;
-  if ((constraint_adjustment & WlConstraintAdjustment::ResizeY) !=
-      WlConstraintAdjustment::None)
-    res |= ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_RESIZE_Y;
-  return res;
-}
-
-uint32_t GetAnchor(MenuType menu_type, const gfx::Rect& bounds, bool stable) {
-  WlAnchor anchor = WlAnchor::None;
-  switch (menu_type) {
-    case MenuType::TYPE_RIGHT_CLICK:
-      anchor = WlAnchor::TopLeft;
-      break;
-    case MenuType::TYPE_3DOT_PARENT_MENU:
-      anchor = WlAnchor::BottomRight;
-      break;
-    case MenuType::TYPE_3DOT_CHILD_MENU:
-      // Chromium may want to manually position a child menu on the left side of
-      // its parent menu. Thus, react accordingly. Positive x means the child is
-      // located on the right side of the parent and negative - on the left
-      // side.
-      if (bounds.x() >= 0)
-        anchor = WlAnchor::TopRight;
-      else
-        anchor = WlAnchor::TopLeft;
-      break;
-    case MenuType::TYPE_UNKNOWN:
-      NOTREACHED() << "Unsupported menu type";
-      break;
-  }
-
-  if (stable)
-    return TranslateAnchorStable(anchor);
-  else {
-    return TranslateAnchorV6(anchor);
-  }
-}
-
-uint32_t GetGravity(MenuType menu_type, const gfx::Rect& bounds, bool stable) {
-  WlGravity gravity = WlGravity::None;
-  switch (menu_type) {
-    case MenuType::TYPE_RIGHT_CLICK:
-      gravity = WlGravity::BottomRight;
-      break;
-    case MenuType::TYPE_3DOT_PARENT_MENU:
-      gravity = WlGravity::BottomRight;
-      break;
-    case MenuType::TYPE_3DOT_CHILD_MENU:
-      // Chromium may want to manually position a child menu on the left side of
-      // its parent menu. Thus, react accordingly. Positive x means the child is
-      // located on the right side of the parent and negative - on the left
-      // side.
-      if (bounds.x() >= 0)
-        gravity = WlGravity::BottomRight;
-      else
-        gravity = WlGravity::BottomLeft;
-      break;
-    case MenuType::TYPE_UNKNOWN:
-      NOTREACHED() << "Unsupported menu type";
-      break;
-  }
-
-  if (stable)
-    return TranslateGravityStable(gravity);
-  else {
-    return TranslateGravityV6(gravity);
-  }
-}
-
-uint32_t GetConstraintAdjustment(MenuType menu_type, bool stable) {
-  WlConstraintAdjustment constraint = WlConstraintAdjustment::None;
-
-  switch (menu_type) {
-    case MenuType::TYPE_RIGHT_CLICK:
-      constraint = WlConstraintAdjustment::SlideX |
-                   WlConstraintAdjustment::SlideY |
-                   WlConstraintAdjustment::FlipY;
-      break;
-    case MenuType::TYPE_3DOT_PARENT_MENU:
-      constraint =
-          WlConstraintAdjustment::SlideX | WlConstraintAdjustment::FlipY;
-      break;
-    case MenuType::TYPE_3DOT_CHILD_MENU:
-      constraint =
-          WlConstraintAdjustment::SlideY | WlConstraintAdjustment::FlipX;
-      break;
-    case MenuType::TYPE_UNKNOWN:
-      NOTREACHED() << "Unsupported menu type";
-      break;
-  }
-  if (stable)
-    return TranslateContraintAdjustmentStable(constraint);
-  else {
-    return TranslateContraintAdjustmentV6(constraint);
-  }
-}
-
 }  // namespace
 
 XDGPopupWrapperImpl::XDGPopupWrapperImpl(
     std::unique_ptr<XDGSurfaceWrapperImpl> surface,
     WaylandWindow* wayland_window)
-    : wayland_window_(wayland_window), xdg_surface_(std::move(surface)) {
-  DCHECK(xdg_surface_);
+    : wayland_window_(wayland_window),
+      xdg_surface_wrapper_(std::move(surface)) {
+  DCHECK(xdg_surface_wrapper_);
   DCHECK(wayland_window_ && wayland_window_->parent_window());
 }
 
@@ -286,15 +125,16 @@
         static_cast<WaylandPopup*>(wayland_window_->parent_window());
     XDGPopupWrapperImpl* popup =
         static_cast<XDGPopupWrapperImpl*>(wayland_popup->shell_popup());
-    parent_xdg_surface = popup->xdg_surface();
+    parent_xdg_surface = popup->xdg_surface_wrapper();
   } else {
     WaylandToplevelWindow* wayland_surface =
         static_cast<WaylandToplevelWindow*>(wayland_window_->parent_window());
     parent_xdg_surface =
-        static_cast<XDGSurfaceWrapperImpl*>(wayland_surface->shell_surface());
+        static_cast<XDGToplevelWrapperImpl*>(wayland_surface->shell_toplevel())
+            ->xdg_surface_wrapper();
   }
 
-  if (!xdg_surface_ || !parent_xdg_surface)
+  if (!xdg_surface_wrapper_ || !parent_xdg_surface)
     return false;
 
   auto new_bounds = bounds;
@@ -306,8 +146,6 @@
 
   if (connection->shell())
     return InitializeStable(connection, new_bounds, parent_xdg_surface);
-  else if (connection->shell_v6())
-    return InitializeV6(connection, new_bounds, parent_xdg_surface);
   return false;
 }
 
@@ -316,16 +154,16 @@
     const gfx::Rect& bounds,
     XDGSurfaceWrapperImpl* parent_xdg_surface) {
   static const struct xdg_popup_listener xdg_popup_listener = {
-      &XDGPopupWrapperImpl::ConfigureStable,
-      &XDGPopupWrapperImpl::PopupDoneStable,
+      &XDGPopupWrapperImpl::Configure,
+      &XDGPopupWrapperImpl::PopupDone,
   };
 
-  struct xdg_positioner* positioner = CreatePositionerStable(
-      connection, wayland_window_->parent_window(), bounds);
+  struct xdg_positioner* positioner =
+      CreatePositioner(connection, wayland_window_->parent_window(), bounds);
   if (!positioner)
     return false;
 
-  xdg_popup_.reset(xdg_surface_get_popup(xdg_surface_->xdg_surface(),
+  xdg_popup_.reset(xdg_surface_get_popup(xdg_surface_wrapper_->xdg_surface(),
                                          parent_xdg_surface->xdg_surface(),
                                          positioner));
   if (!xdg_popup_)
@@ -342,7 +180,7 @@
   return true;
 }
 
-struct xdg_positioner* XDGPopupWrapperImpl::CreatePositionerStable(
+struct xdg_positioner* XDGPopupWrapperImpl::CreatePositioner(
     WaylandConnection* connection,
     WaylandWindow* parent_window,
     const gfx::Rect& bounds) {
@@ -366,115 +204,19 @@
   xdg_positioner_set_anchor_rect(positioner, anchor_rect.x(), anchor_rect.y(),
                                  anchor_rect.width(), anchor_rect.height());
   xdg_positioner_set_size(positioner, bounds.width(), bounds.height());
-  xdg_positioner_set_anchor(positioner, GetAnchor(menu_type, bounds, true));
-  xdg_positioner_set_gravity(positioner, GetGravity(menu_type, bounds, true));
+  xdg_positioner_set_anchor(positioner,
+                            TranslateAnchor(GetAnchor(menu_type, bounds)));
+  xdg_positioner_set_gravity(positioner,
+                             TranslateGravity(GetGravity(menu_type, bounds)));
   xdg_positioner_set_constraint_adjustment(
-      positioner, GetConstraintAdjustment(menu_type, true));
+      positioner,
+      TranslateContraintAdjustment(GetConstraintAdjustment(menu_type)));
   return positioner;
 }
 
-bool XDGPopupWrapperImpl::InitializeV6(
-    WaylandConnection* connection,
-    const gfx::Rect& bounds,
-    XDGSurfaceWrapperImpl* parent_xdg_surface) {
-  static const struct zxdg_popup_v6_listener zxdg_popup_v6_listener = {
-      &XDGPopupWrapperImpl::ConfigureV6,
-      &XDGPopupWrapperImpl::PopupDoneV6,
-  };
-
-  zxdg_positioner_v6* positioner =
-      CreatePositionerV6(connection, wayland_window_->parent_window(), bounds);
-  if (!positioner)
-    return false;
-
-  zxdg_popup_v6_.reset(zxdg_surface_v6_get_popup(
-      xdg_surface_->zxdg_surface(), parent_xdg_surface->zxdg_surface(),
-      positioner));
-  if (!zxdg_popup_v6_)
-    return false;
-
-  zxdg_positioner_v6_destroy(positioner);
-
-  if (CanGrabPopup(connection)) {
-    zxdg_popup_v6_grab(zxdg_popup_v6_.get(), connection->seat(),
-                       connection->serial());
-  }
-  zxdg_popup_v6_add_listener(zxdg_popup_v6_.get(), &zxdg_popup_v6_listener,
-                             this);
-
-  wayland_window_->root_surface()->Commit();
-  return true;
-}
-
-zxdg_positioner_v6* XDGPopupWrapperImpl::CreatePositionerV6(
-    WaylandConnection* connection,
-    WaylandWindow* parent_window,
-    const gfx::Rect& bounds) {
-  struct zxdg_positioner_v6* positioner;
-  positioner = zxdg_shell_v6_create_positioner(connection->shell_v6());
-  if (!positioner)
-    return nullptr;
-
-  auto menu_type = GetMenuTypeForPositioner(connection, parent_window);
-
-  // The parent we got must be the topmost in the stack of the same family
-  // windows.
-  DCHECK_EQ(parent_window->GetTopMostChildWindow(), parent_window);
-
-  // Place anchor to the end of the possible position.
-  gfx::Rect anchor_rect = GetAnchorRect(
-      menu_type, bounds,
-      gfx::ScaleToRoundedRect(parent_window->GetBounds(),
-                              1.0 / parent_window->buffer_scale()));
-
-  zxdg_positioner_v6_set_anchor_rect(positioner, anchor_rect.x(),
-                                     anchor_rect.y(), anchor_rect.width(),
-                                     anchor_rect.height());
-  zxdg_positioner_v6_set_size(positioner, bounds.width(), bounds.height());
-  zxdg_positioner_v6_set_anchor(positioner,
-                                GetAnchor(menu_type, bounds, false));
-  zxdg_positioner_v6_set_gravity(positioner,
-                                 GetGravity(menu_type, bounds, false));
-  zxdg_positioner_v6_set_constraint_adjustment(
-      positioner, GetConstraintAdjustment(menu_type, false));
-  return positioner;
-}
-
-MenuType XDGPopupWrapperImpl::GetMenuTypeForPositioner(
-    WaylandConnection* connection,
-    WaylandWindow* parent_window) const {
-  bool is_right_click_menu =
-      connection->event_source()->last_pointer_button_pressed() &
-      EF_RIGHT_MOUSE_BUTTON;
-
-  // Different types of menu require different anchors, constraint adjustments,
-  // gravity and etc.
-  if (is_right_click_menu)
-    return MenuType::TYPE_RIGHT_CLICK;
-  else if (!wl::IsMenuType(parent_window->type()))
-    return MenuType::TYPE_3DOT_PARENT_MENU;
-  else
-    return MenuType::TYPE_3DOT_CHILD_MENU;
-}
-
-bool XDGPopupWrapperImpl::CanGrabPopup(WaylandConnection* connection) const {
-  // When drag process starts, as described the protocol -
-  // https://goo.gl/1Mskq3, the client must have an active implicit grab. If
-  // we try to create a popup and grab it, it will be immediately dismissed.
-  // Thus, do not take explicit grab during drag process.
-  if (connection->IsDragInProgress() || !connection->seat())
-    return false;
-
-  // According to the definition of the xdg protocol, the grab request must be
-  // used in response to some sort of user action like a button press, key
-  // press, or touch down event.
-  EventType last_event_type = connection->event_serial().event_type;
-  return last_event_type == ET_TOUCH_PRESSED ||
-         last_event_type == ET_KEY_PRESSED ||
-         last_event_type == ET_MOUSE_PRESSED;
-}
-
+// static
 void XDGPopupWrapperImpl::Configure(void* data,
+                                    struct xdg_popup* xdg_popup,
                                     int32_t x,
                                     int32_t y,
                                     int32_t width,
@@ -492,7 +234,7 @@
 }
 
 // static
-void XDGPopupWrapperImpl::PopupDone(void* data) {
+void XDGPopupWrapperImpl::PopupDone(void* data, struct xdg_popup* xdg_popup) {
   WaylandWindow* window =
       static_cast<XDGPopupWrapperImpl*>(data)->wayland_window_;
   DCHECK(window);
@@ -500,41 +242,9 @@
   window->OnCloseRequest();
 }
 
-// static
-void XDGPopupWrapperImpl::ConfigureStable(void* data,
-                                          struct xdg_popup* xdg_popup,
-                                          int32_t x,
-                                          int32_t y,
-                                          int32_t width,
-                                          int32_t height) {
-  Configure(data, x, y, width, height);
-}
-
-// static
-void XDGPopupWrapperImpl::PopupDoneStable(void* data,
-                                          struct xdg_popup* xdg_popup) {
-  PopupDone(data);
-}
-
-// static
-void XDGPopupWrapperImpl::ConfigureV6(void* data,
-                                      struct zxdg_popup_v6* zxdg_popup_v6,
-                                      int32_t x,
-                                      int32_t y,
-                                      int32_t width,
-                                      int32_t height) {
-  Configure(data, x, y, width, height);
-}
-
-// static
-void XDGPopupWrapperImpl::PopupDoneV6(void* data,
-                                      struct zxdg_popup_v6* zxdg_popup_v6) {
-  PopupDone(data);
-}
-
-XDGSurfaceWrapperImpl* XDGPopupWrapperImpl::xdg_surface() {
-  DCHECK(xdg_surface_.get());
-  return xdg_surface_.get();
+XDGSurfaceWrapperImpl* XDGPopupWrapperImpl::xdg_surface_wrapper() const {
+  DCHECK(xdg_surface_wrapper_.get());
+  return xdg_surface_wrapper_.get();
 }
 
 }  // namespace ui
diff --git a/ui/ozone/platform/wayland/host/xdg_popup_wrapper_impl.h b/ui/ozone/platform/wayland/host/xdg_popup_wrapper_impl.h
index 62f38a2..2ffb230 100644
--- a/ui/ozone/platform/wayland/host/xdg_popup_wrapper_impl.h
+++ b/ui/ozone/platform/wayland/host/xdg_popup_wrapper_impl.h
@@ -16,7 +16,7 @@
 class WaylandConnection;
 class WaylandWindow;
 
-// Popup wrapper for xdg-shell stable and xdg-shell-unstable-v6
+// Popup wrapper for xdg-shell stable
 class XDGPopupWrapperImpl : public ShellPopupWrapper {
  public:
   XDGPopupWrapperImpl(std::unique_ptr<XDGSurfaceWrapperImpl> surface,
@@ -30,57 +30,30 @@
  private:
   bool InitializeStable(WaylandConnection* connection,
                         const gfx::Rect& bounds,
-                        XDGSurfaceWrapperImpl* parent_xdg_surface);
-  struct xdg_positioner* CreatePositionerStable(WaylandConnection* connection,
-                                                WaylandWindow* parent_window,
-                                                const gfx::Rect& bounds);
-
-  bool InitializeV6(WaylandConnection* connection,
-                    const gfx::Rect& bounds,
-                    XDGSurfaceWrapperImpl* parent_xdg_surface);
-  struct zxdg_positioner_v6* CreatePositionerV6(WaylandConnection* connection,
-                                                WaylandWindow* parent_window,
-                                                const gfx::Rect& bounds);
-
-  MenuType GetMenuTypeForPositioner(WaylandConnection* connection,
-                                    WaylandWindow* parent_window) const;
-
-  bool CanGrabPopup(WaylandConnection* connection) const;
+                        XDGSurfaceWrapperImpl* parent_xdg_surface_wrapper);
+  struct xdg_positioner* CreatePositioner(WaylandConnection* connection,
+                                          WaylandWindow* parent_window,
+                                          const gfx::Rect& bounds);
 
   // xdg_popup_listener
   static void Configure(void* data,
-                        int32_t x,
-                        int32_t y,
-                        int32_t width,
-                        int32_t height);
-  static void ConfigureStable(void* data,
                               struct xdg_popup* xdg_popup,
                               int32_t x,
                               int32_t y,
                               int32_t width,
                               int32_t height);
-  static void ConfigureV6(void* data,
-                          struct zxdg_popup_v6* zxdg_popup_v6,
-                          int32_t x,
-                          int32_t y,
-                          int32_t width,
-                          int32_t height);
-  static void PopupDone(void* data);
-  static void PopupDoneStable(void* data, struct xdg_popup* xdg_popup);
-  static void PopupDoneV6(void* data, struct zxdg_popup_v6* zxdg_popup_v6);
+  static void PopupDone(void* data, struct xdg_popup* xdg_popup);
 
-  XDGSurfaceWrapperImpl* xdg_surface();
+  XDGSurfaceWrapperImpl* xdg_surface_wrapper() const;
 
   // Non-owned WaylandWindow that uses this popup.
   WaylandWindow* const wayland_window_;
 
   // Ground surface for this popup.
-  std::unique_ptr<XDGSurfaceWrapperImpl> xdg_surface_;
+  std::unique_ptr<XDGSurfaceWrapperImpl> xdg_surface_wrapper_;
 
   // XDG Shell Stable object.
   wl::Object<xdg_popup> xdg_popup_;
-  // XDG Shell V6 object.
-  wl::Object<zxdg_popup_v6> zxdg_popup_v6_;
 
   DISALLOW_COPY_AND_ASSIGN(XDGPopupWrapperImpl);
 };
diff --git a/ui/ozone/platform/wayland/host/xdg_surface_wrapper_impl.cc b/ui/ozone/platform/wayland/host/xdg_surface_wrapper_impl.cc
index 1957a55..8d5bf2f3 100644
--- a/ui/ozone/platform/wayland/host/xdg_surface_wrapper_impl.cc
+++ b/ui/ozone/platform/wayland/host/xdg_surface_wrapper_impl.cc
@@ -4,14 +4,8 @@
 
 #include "ui/ozone/platform/wayland/host/xdg_surface_wrapper_impl.h"
 
-#include <xdg-decoration-unstable-v1-client-protocol.h>
 #include <xdg-shell-client-protocol.h>
-#include <xdg-shell-unstable-v6-client-protocol.h>
 
-#include "base/strings/utf_string_conversions.h"
-#include "ui/base/hit_test.h"
-#include "ui/ozone/platform/wayland/common/wayland_util.h"
-#include "ui/ozone/platform/wayland/host/shell_surface_wrapper.h"
 #include "ui/ozone/platform/wayland/host/wayland_connection.h"
 #include "ui/ozone/platform/wayland/host/wayland_window.h"
 
@@ -19,281 +13,19 @@
 
 XDGSurfaceWrapperImpl::XDGSurfaceWrapperImpl(WaylandWindow* wayland_window,
                                              WaylandConnection* connection)
-    : wayland_window_(wayland_window),
-      connection_(connection),
-      decoration_mode_(DecorationMode::kClientSide) {}
+    : wayland_window_(wayland_window), connection_(connection) {}
 
-XDGSurfaceWrapperImpl::~XDGSurfaceWrapperImpl() {}
+XDGSurfaceWrapperImpl::~XDGSurfaceWrapperImpl() = default;
 
-bool XDGSurfaceWrapperImpl::Initialize(bool with_toplevel) {
-  if (connection_->shell())
-    return InitializeStable(with_toplevel);
-  else if (connection_->shell_v6())
-    return InitializeV6(with_toplevel);
-  NOTREACHED() << "Wrong shell protocol";
-  return false;
-}
-
-void XDGSurfaceWrapperImpl::SetMaximized() {
-  if (xdg_toplevel_) {
-    xdg_toplevel_set_maximized(xdg_toplevel_.get());
-  } else {
-    DCHECK(zxdg_toplevel_v6_);
-    zxdg_toplevel_v6_set_maximized(zxdg_toplevel_v6_.get());
+bool XDGSurfaceWrapperImpl::Initialize() {
+  if (!connection_->shell()) {
+    NOTREACHED() << "Wrong shell protocol";
+    return false;
   }
-}
 
-void XDGSurfaceWrapperImpl::UnSetMaximized() {
-  if (xdg_toplevel_) {
-    xdg_toplevel_unset_maximized(xdg_toplevel_.get());
-  } else {
-    DCHECK(zxdg_toplevel_v6_);
-    zxdg_toplevel_v6_unset_maximized(zxdg_toplevel_v6_.get());
-  }
-}
-
-void XDGSurfaceWrapperImpl::SetFullscreen() {
-  if (xdg_toplevel_) {
-    xdg_toplevel_set_fullscreen(xdg_toplevel_.get(), nullptr);
-  } else {
-    DCHECK(zxdg_toplevel_v6_);
-    zxdg_toplevel_v6_set_fullscreen(zxdg_toplevel_v6_.get(), nullptr);
-  }
-}
-
-void XDGSurfaceWrapperImpl::UnSetFullscreen() {
-  if (xdg_toplevel_) {
-    xdg_toplevel_unset_fullscreen(xdg_toplevel_.get());
-  } else {
-    DCHECK(zxdg_toplevel_v6_);
-    zxdg_toplevel_v6_unset_fullscreen(zxdg_toplevel_v6_.get());
-  }
-}
-
-void XDGSurfaceWrapperImpl::SetMinimized() {
-  if (xdg_toplevel_) {
-    xdg_toplevel_set_minimized(xdg_toplevel_.get());
-  } else {
-    DCHECK(zxdg_toplevel_v6_);
-    zxdg_toplevel_v6_set_minimized(zxdg_toplevel_v6_.get());
-  }
-}
-
-void XDGSurfaceWrapperImpl::SurfaceMove(WaylandConnection* connection) {
-  if (xdg_toplevel_) {
-    xdg_toplevel_move(xdg_toplevel_.get(), connection_->seat(),
-                      connection_->serial());
-  } else {
-    DCHECK(zxdg_toplevel_v6_);
-    zxdg_toplevel_v6_move(zxdg_toplevel_v6_.get(), connection_->seat(),
-                          connection_->serial());
-  }
-}
-
-void XDGSurfaceWrapperImpl::SurfaceResize(WaylandConnection* connection,
-                                          uint32_t hittest) {
-  if (xdg_toplevel_) {
-    xdg_toplevel_resize(xdg_toplevel_.get(), connection_->seat(),
-                        connection_->serial(),
-                        wl::IdentifyDirection(*connection, hittest));
-  } else {
-    DCHECK(zxdg_toplevel_v6_);
-    zxdg_toplevel_v6_resize(zxdg_toplevel_v6_.get(), connection_->seat(),
-                            connection_->serial(),
-                            wl::IdentifyDirection(*connection, hittest));
-  }
-}
-
-void XDGSurfaceWrapperImpl::SetTitle(const base::string16& title) {
-  if (xdg_toplevel_) {
-    xdg_toplevel_set_title(xdg_toplevel_.get(),
-                           base::UTF16ToUTF8(title).c_str());
-  } else {
-    DCHECK(zxdg_toplevel_v6_);
-    zxdg_toplevel_v6_set_title(zxdg_toplevel_v6_.get(),
-                               base::UTF16ToUTF8(title).c_str());
-  }
-}
-
-void XDGSurfaceWrapperImpl::AckConfigure() {
-  if (xdg_surface_) {
-    xdg_surface_ack_configure(xdg_surface_.get(), pending_configure_serial_);
-  } else {
-    DCHECK(zxdg_surface_v6_);
-    zxdg_surface_v6_ack_configure(zxdg_surface_v6_.get(),
-                                  pending_configure_serial_);
-  }
-  connection_->wayland_window_manager()->NotifyWindowConfigured(
-      wayland_window_);
-}
-
-void XDGSurfaceWrapperImpl::SetWindowGeometry(const gfx::Rect& bounds) {
-  if (xdg_surface_) {
-    xdg_surface_set_window_geometry(xdg_surface_.get(), bounds.x(), bounds.y(),
-                                    bounds.width(), bounds.height());
-  } else {
-    DCHECK(zxdg_surface_v6_);
-    zxdg_surface_v6_set_window_geometry(zxdg_surface_v6_.get(), bounds.x(),
-                                        bounds.y(), bounds.width(),
-                                        bounds.height());
-  }
-}
-
-void XDGSurfaceWrapperImpl::SetMinSize(int32_t width, int32_t height) {
-  if (xdg_toplevel_) {
-    xdg_toplevel_set_min_size(xdg_toplevel_.get(), width, height);
-  } else {
-    DCHECK(zxdg_toplevel_v6_);
-    zxdg_toplevel_v6_set_min_size(zxdg_toplevel_v6_.get(), width, height);
-  }
-}
-
-void XDGSurfaceWrapperImpl::SetMaxSize(int32_t width, int32_t height) {
-  if (xdg_toplevel_) {
-    xdg_toplevel_set_max_size(xdg_toplevel_.get(), width, height);
-  } else {
-    DCHECK(zxdg_toplevel_v6_);
-    zxdg_toplevel_v6_set_max_size(zxdg_toplevel_v6_.get(), width, height);
-  }
-}
-
-void XDGSurfaceWrapperImpl::SetAppId(const std::string& app_id) {
-  if (xdg_toplevel_) {
-    xdg_toplevel_set_app_id(xdg_toplevel_.get(), app_id.c_str());
-  } else {
-    DCHECK(zxdg_toplevel_v6_);
-    zxdg_toplevel_v6_set_app_id(zxdg_toplevel_v6_.get(), app_id.c_str());
-  }
-}
-
-void XDGSurfaceWrapperImpl::SetDecoration(DecorationMode decoration) {
-  SetTopLevelDecorationMode(decoration);
-}
-
-// static
-void XDGSurfaceWrapperImpl::ConfigureStable(void* data,
-                                            struct xdg_surface* xdg_surface,
-                                            uint32_t serial) {
-  auto* surface = static_cast<XDGSurfaceWrapperImpl*>(data);
-  DCHECK(surface);
-  surface->pending_configure_serial_ = serial;
-
-  surface->AckConfigure();
-}
-
-// static
-void XDGSurfaceWrapperImpl::ConfigureTopLevelStable(
-    void* data,
-    struct xdg_toplevel* xdg_toplevel,
-    int32_t width,
-    int32_t height,
-    struct wl_array* states) {
-  auto* surface = static_cast<XDGSurfaceWrapperImpl*>(data);
-  DCHECK(surface);
-
-  bool is_maximized =
-      CheckIfWlArrayHasValue(states, XDG_TOPLEVEL_STATE_MAXIMIZED);
-  bool is_fullscreen =
-      CheckIfWlArrayHasValue(states, XDG_TOPLEVEL_STATE_FULLSCREEN);
-  bool is_activated =
-      CheckIfWlArrayHasValue(states, XDG_TOPLEVEL_STATE_ACTIVATED);
-
-  surface->wayland_window_->HandleSurfaceConfigure(width, height, is_maximized,
-                                                   is_fullscreen, is_activated);
-}
-
-// static
-void XDGSurfaceWrapperImpl::CloseTopLevelStable(
-    void* data,
-    struct xdg_toplevel* xdg_toplevel) {
-  auto* surface = static_cast<XDGSurfaceWrapperImpl*>(data);
-  DCHECK(surface);
-  surface->wayland_window_->OnCloseRequest();
-}
-
-void XDGSurfaceWrapperImpl::SetTopLevelDecorationMode(
-    DecorationMode requested_mode) {
-  if (!zxdg_toplevel_decoration_ || requested_mode == decoration_mode_)
-    return;
-
-  decoration_mode_ = requested_mode;
-  zxdg_toplevel_decoration_v1_set_mode(zxdg_toplevel_decoration_.get(),
-                                       static_cast<uint32_t>(requested_mode));
-}
-
-// static
-void XDGSurfaceWrapperImpl::ConfigureV6(void* data,
-                                        struct zxdg_surface_v6* zxdg_surface_v6,
-                                        uint32_t serial) {
-  auto* surface = static_cast<XDGSurfaceWrapperImpl*>(data);
-  DCHECK(surface);
-  surface->pending_configure_serial_ = serial;
-
-  surface->AckConfigure();
-}
-
-// static
-void XDGSurfaceWrapperImpl::ConfigureTopLevelV6(
-    void* data,
-    struct zxdg_toplevel_v6* zxdg_toplevel_v6,
-    int32_t width,
-    int32_t height,
-    struct wl_array* states) {
-  auto* surface = static_cast<XDGSurfaceWrapperImpl*>(data);
-  DCHECK(surface);
-
-  bool is_maximized =
-      CheckIfWlArrayHasValue(states, ZXDG_TOPLEVEL_V6_STATE_MAXIMIZED);
-  bool is_fullscreen =
-      CheckIfWlArrayHasValue(states, ZXDG_TOPLEVEL_V6_STATE_FULLSCREEN);
-  bool is_activated =
-      CheckIfWlArrayHasValue(states, ZXDG_TOPLEVEL_V6_STATE_ACTIVATED);
-
-  surface->wayland_window_->HandleSurfaceConfigure(width, height, is_maximized,
-                                                   is_fullscreen, is_activated);
-}
-
-// static
-void XDGSurfaceWrapperImpl::CloseTopLevelV6(
-    void* data,
-    struct zxdg_toplevel_v6* zxdg_toplevel_v6) {
-  auto* surface = static_cast<XDGSurfaceWrapperImpl*>(data);
-  DCHECK(surface);
-  surface->wayland_window_->OnCloseRequest();
-}
-
-zxdg_surface_v6* XDGSurfaceWrapperImpl::zxdg_surface() const {
-  DCHECK(zxdg_surface_v6_);
-  return zxdg_surface_v6_.get();
-}
-
-xdg_surface* XDGSurfaceWrapperImpl::xdg_surface() const {
-  DCHECK(xdg_surface_);
-  return xdg_surface_.get();
-}
-
-// static
-void XDGSurfaceWrapperImpl::ConfigureDecoration(
-    void* data,
-    struct zxdg_toplevel_decoration_v1* decoration,
-    uint32_t mode) {
-  auto* surface = static_cast<XDGSurfaceWrapperImpl*>(data);
-  DCHECK(surface);
-  surface->SetTopLevelDecorationMode(static_cast<DecorationMode>(mode));
-}
-
-bool XDGSurfaceWrapperImpl::InitializeStable(bool with_toplevel) {
   static const xdg_surface_listener xdg_surface_listener = {
-      &XDGSurfaceWrapperImpl::ConfigureStable,
+      &XDGSurfaceWrapperImpl::Configure,
   };
-  static const xdg_toplevel_listener xdg_toplevel_listener = {
-      &XDGSurfaceWrapperImpl::ConfigureTopLevelStable,
-      &XDGSurfaceWrapperImpl::CloseTopLevelStable,
-  };
-
-  // if this surface is created for the popup role, mark that it requires
-  // configuration acknowledgement on each configure event.
-  surface_for_popup_ = !with_toplevel;
 
   xdg_surface_.reset(xdg_wm_base_get_xdg_surface(
       connection_->shell(), wayland_window_->root_surface()->surface()));
@@ -301,84 +33,39 @@
     LOG(ERROR) << "Failed to create xdg_surface";
     return false;
   }
+
   xdg_surface_add_listener(xdg_surface_.get(), &xdg_surface_listener, this);
-  // XDGPopup requires a separate surface to be created, so this is just a
-  // request to get an xdg_surface for it.
-  if (surface_for_popup_) {
-    connection_->ScheduleFlush();
-    return true;
-  }
-
-  xdg_toplevel_.reset(xdg_surface_get_toplevel(xdg_surface_.get()));
-  if (!xdg_toplevel_) {
-    LOG(ERROR) << "Failed to create xdg_toplevel";
-    return false;
-  }
-
-  xdg_toplevel_add_listener(xdg_toplevel_.get(), &xdg_toplevel_listener, this);
-
-  InitializeXdgDecoration();
-
-  wayland_window_->root_surface()->Commit();
   connection_->ScheduleFlush();
   return true;
 }
 
-bool XDGSurfaceWrapperImpl::InitializeV6(bool with_toplevel) {
-  static const zxdg_surface_v6_listener zxdg_surface_v6_listener = {
-      &XDGSurfaceWrapperImpl::ConfigureV6,
-  };
-  static const zxdg_toplevel_v6_listener zxdg_toplevel_v6_listener = {
-      &XDGSurfaceWrapperImpl::ConfigureTopLevelV6,
-      &XDGSurfaceWrapperImpl::CloseTopLevelV6,
-  };
-
-  // if this surface is created for the popup role, mark that it requires
-  // configuration acknowledgement on each configure event.
-  surface_for_popup_ = !with_toplevel;
-
-  zxdg_surface_v6_.reset(zxdg_shell_v6_get_xdg_surface(
-      connection_->shell_v6(), wayland_window_->root_surface()->surface()));
-  if (!zxdg_surface_v6_) {
-    LOG(ERROR) << "Failed to create zxdg_surface";
-    return false;
-  }
-  zxdg_surface_v6_add_listener(zxdg_surface_v6_.get(),
-                               &zxdg_surface_v6_listener, this);
-  // XDGPopupV6 requires a separate surface to be created, so this is just a
-  // request to get an xdg_surface for it.
-  if (surface_for_popup_) {
-    connection_->ScheduleFlush();
-    return true;
-  }
-
-  zxdg_toplevel_v6_.reset(zxdg_surface_v6_get_toplevel(zxdg_surface_v6_.get()));
-  if (!zxdg_toplevel_v6_) {
-    LOG(ERROR) << "Failed to create zxdg_toplevel";
-    return false;
-  }
-  zxdg_toplevel_v6_add_listener(zxdg_toplevel_v6_.get(),
-                                &zxdg_toplevel_v6_listener, this);
-
-  InitializeXdgDecoration();
-
-  wayland_window_->root_surface()->Commit();
-  connection_->ScheduleFlush();
-  return true;
+void XDGSurfaceWrapperImpl::AckConfigure() {
+  DCHECK(xdg_surface_);
+  xdg_surface_ack_configure(xdg_surface_.get(), pending_configure_serial_);
+  connection_->wayland_window_manager()->NotifyWindowConfigured(
+      wayland_window_);
 }
 
-void XDGSurfaceWrapperImpl::InitializeXdgDecoration() {
-  if (connection_->xdg_decoration_manager_v1()) {
-    DCHECK(!zxdg_toplevel_decoration_);
-    static const zxdg_toplevel_decoration_v1_listener decoration_listener = {
-        &XDGSurfaceWrapperImpl::ConfigureDecoration,
-    };
-    zxdg_toplevel_decoration_.reset(
-        zxdg_decoration_manager_v1_get_toplevel_decoration(
-            connection_->xdg_decoration_manager_v1(), xdg_toplevel_.get()));
-    zxdg_toplevel_decoration_v1_add_listener(zxdg_toplevel_decoration_.get(),
-                                             &decoration_listener, this);
-  }
+void XDGSurfaceWrapperImpl::SetWindowGeometry(const gfx::Rect& bounds) {
+  DCHECK(xdg_surface_);
+  xdg_surface_set_window_geometry(xdg_surface_.get(), bounds.x(), bounds.y(),
+                                  bounds.width(), bounds.height());
+}
+
+// static
+void XDGSurfaceWrapperImpl::Configure(void* data,
+                                      struct xdg_surface* xdg_surface,
+                                      uint32_t serial) {
+  auto* surface = static_cast<XDGSurfaceWrapperImpl*>(data);
+  DCHECK(surface);
+  surface->pending_configure_serial_ = serial;
+
+  surface->AckConfigure();
+}
+
+xdg_surface* XDGSurfaceWrapperImpl::xdg_surface() const {
+  DCHECK(xdg_surface_);
+  return xdg_surface_.get();
 }
 
 }  // namespace ui
diff --git a/ui/ozone/platform/wayland/host/xdg_surface_wrapper_impl.h b/ui/ozone/platform/wayland/host/xdg_surface_wrapper_impl.h
index 6cbb8b68..3210806 100644
--- a/ui/ozone/platform/wayland/host/xdg_surface_wrapper_impl.h
+++ b/ui/ozone/platform/wayland/host/xdg_surface_wrapper_impl.h
@@ -7,8 +7,6 @@
 
 #include "ui/ozone/platform/wayland/host/shell_surface_wrapper.h"
 
-#include <xdg-decoration-unstable-v1-client-protocol.h>
-
 #include <cstdint>
 #include <string>
 
@@ -24,7 +22,7 @@
 class WaylandConnection;
 class WaylandWindow;
 
-// Surface wrapper for xdg-shell stable and xdg-shell-unstable-v6
+// Surface wrapper for xdg-shell stable
 class XDGSurfaceWrapperImpl : public ShellSurfaceWrapper {
  public:
   XDGSurfaceWrapperImpl(WaylandWindow* wayland_window,
@@ -34,85 +32,25 @@
   ~XDGSurfaceWrapperImpl() override;
 
   // ShellSurfaceWrapper overrides:
-  bool Initialize(bool with_toplevel) override;
-  void SetMaximized() override;
-  void UnSetMaximized() override;
-  void SetFullscreen() override;
-  void UnSetFullscreen() override;
-  void SetMinimized() override;
-  void SurfaceMove(WaylandConnection* connection) override;
-  void SurfaceResize(WaylandConnection* connection, uint32_t hittest) override;
-  void SetTitle(const base::string16& title) override;
+  bool Initialize() override;
   void AckConfigure() override;
   void SetWindowGeometry(const gfx::Rect& bounds) override;
-  void SetMinSize(int32_t width, int32_t height) override;
-  void SetMaxSize(int32_t width, int32_t height) override;
-  void SetAppId(const std::string& app_id) override;
-  void SetDecoration(DecorationMode decoration) override;
 
   // xdg_surface_listener
-  static void ConfigureV6(void* data,
-                          struct zxdg_surface_v6* zxdg_surface_v6,
-                          uint32_t serial);
-  static void ConfigureTopLevelV6(void* data,
-                                  struct zxdg_toplevel_v6* zxdg_toplevel_v6,
-                                  int32_t width,
-                                  int32_t height,
-                                  struct wl_array* states);
-
-  static void ConfigureStable(void* data,
-                              struct xdg_surface* xdg_surface,
-                              uint32_t serial);
-  static void ConfigureTopLevelStable(void* data,
-                                      struct xdg_toplevel* xdg_toplevel,
-                                      int32_t width,
-                                      int32_t height,
-                                      struct wl_array* states);
-
-  // xdg_toplevel_listener
-  static void CloseTopLevelStable(void* data,
-                                  struct xdg_toplevel* xdg_toplevel);
-  static void CloseTopLevelV6(void* data,
-                              struct zxdg_toplevel_v6* zxdg_toplevel_v6);
-
-  // Send request to wayland compositor to enable a requested decoration mode.
-  void SetTopLevelDecorationMode(DecorationMode requested_mode);
-
-  // zxdg_decoration_listener
-  static void ConfigureDecoration(
-      void* data,
-      struct zxdg_toplevel_decoration_v1* decoration,
-      uint32_t mode);
+  static void Configure(void* data,
+                        struct xdg_surface* xdg_surface,
+                        uint32_t serial);
 
   struct xdg_surface* xdg_surface() const;
-  zxdg_surface_v6* zxdg_surface() const;
 
  private:
-  // Initializes using XDG Shell Stable protocol.
-  bool InitializeStable(bool with_toplevel);
-  // Initializes using XDG Shell V6 protocol.
-  bool InitializeV6(bool with_toplevel);
-
-  // Initializes the xdg-decoration protocol extension, if available.
-  void InitializeXdgDecoration();
-
   // Non-owing WaylandWindow that uses this surface wrapper.
   WaylandWindow* const wayland_window_;
   WaylandConnection* const connection_;
 
   uint32_t pending_configure_serial_ = 0;
 
-  wl::Object<zxdg_surface_v6> zxdg_surface_v6_;
-  wl::Object<zxdg_toplevel_v6> zxdg_toplevel_v6_;
   wl::Object<struct xdg_surface> xdg_surface_;
-  wl::Object<xdg_toplevel> xdg_toplevel_;
-  wl::Object<zxdg_toplevel_decoration_v1> zxdg_toplevel_decoration_;
-
-  bool surface_for_popup_ = false;
-
-  // On client side, it keeps track of the decoration mode currently in
-  // use if xdg-decoration protocol extension is available.
-  DecorationMode decoration_mode_;
 };
 
 }  // namespace ui
diff --git a/ui/ozone/platform/wayland/host/xdg_toplevel_wrapper_impl.cc b/ui/ozone/platform/wayland/host/xdg_toplevel_wrapper_impl.cc
new file mode 100644
index 0000000..56695c4
--- /dev/null
+++ b/ui/ozone/platform/wayland/host/xdg_toplevel_wrapper_impl.cc
@@ -0,0 +1,197 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/ozone/platform/wayland/host/xdg_toplevel_wrapper_impl.h"
+
+#include <xdg-decoration-unstable-v1-client-protocol.h>
+#include <xdg-shell-client-protocol.h>
+#include <xdg-shell-unstable-v6-client-protocol.h>
+
+#include "base/strings/utf_string_conversions.h"
+#include "ui/base/hit_test.h"
+#include "ui/ozone/platform/wayland/common/wayland_util.h"
+#include "ui/ozone/platform/wayland/host/shell_surface_wrapper.h"
+#include "ui/ozone/platform/wayland/host/wayland_connection.h"
+#include "ui/ozone/platform/wayland/host/wayland_window.h"
+#include "ui/ozone/platform/wayland/host/xdg_surface_wrapper_impl.h"
+
+namespace ui {
+
+XDGToplevelWrapperImpl::XDGToplevelWrapperImpl(
+    std::unique_ptr<XDGSurfaceWrapperImpl> surface,
+    WaylandWindow* wayland_window,
+    WaylandConnection* connection)
+    : xdg_surface_wrapper_(std::move(surface)),
+      wayland_window_(wayland_window),
+      connection_(connection),
+      decoration_mode_(DecorationMode::kClientSide) {}
+
+XDGToplevelWrapperImpl::~XDGToplevelWrapperImpl() = default;
+
+bool XDGToplevelWrapperImpl::Initialize() {
+  if (!connection_->shell()) {
+    NOTREACHED() << "Wrong shell protocol";
+    return false;
+  }
+
+  static const xdg_toplevel_listener xdg_toplevel_listener = {
+      &XDGToplevelWrapperImpl::ConfigureTopLevel,
+      &XDGToplevelWrapperImpl::CloseTopLevel,
+  };
+
+  if (!xdg_surface_wrapper_)
+    return false;
+
+  xdg_toplevel_.reset(
+      xdg_surface_get_toplevel(xdg_surface_wrapper_->xdg_surface()));
+  if (!xdg_toplevel_) {
+    LOG(ERROR) << "Failed to create xdg_toplevel";
+    return false;
+  }
+
+  xdg_toplevel_add_listener(xdg_toplevel_.get(), &xdg_toplevel_listener, this);
+
+  InitializeXdgDecoration();
+
+  wayland_window_->root_surface()->Commit();
+  connection_->ScheduleFlush();
+  return true;
+}
+
+void XDGToplevelWrapperImpl::SetMaximized() {
+  DCHECK(xdg_toplevel_);
+  xdg_toplevel_set_maximized(xdg_toplevel_.get());
+}
+
+void XDGToplevelWrapperImpl::UnSetMaximized() {
+  DCHECK(xdg_toplevel_);
+  xdg_toplevel_unset_maximized(xdg_toplevel_.get());
+}
+
+void XDGToplevelWrapperImpl::SetFullscreen() {
+  DCHECK(xdg_toplevel_);
+  xdg_toplevel_set_fullscreen(xdg_toplevel_.get(), nullptr);
+}
+
+void XDGToplevelWrapperImpl::UnSetFullscreen() {
+  DCHECK(xdg_toplevel_);
+  xdg_toplevel_unset_fullscreen(xdg_toplevel_.get());
+}
+
+void XDGToplevelWrapperImpl::SetMinimized() {
+  DCHECK(xdg_toplevel_);
+  xdg_toplevel_set_minimized(xdg_toplevel_.get());
+}
+
+void XDGToplevelWrapperImpl::SurfaceMove(WaylandConnection* connection) {
+  DCHECK(xdg_toplevel_);
+  xdg_toplevel_move(xdg_toplevel_.get(), connection->seat(),
+                    connection->serial());
+}
+
+void XDGToplevelWrapperImpl::SurfaceResize(WaylandConnection* connection,
+                                           uint32_t hittest) {
+  DCHECK(xdg_toplevel_);
+  xdg_toplevel_resize(xdg_toplevel_.get(), connection->seat(),
+                      connection->serial(),
+                      wl::IdentifyDirection(*connection, hittest));
+}
+
+void XDGToplevelWrapperImpl::SetTitle(const base::string16& title) {
+  DCHECK(xdg_toplevel_);
+  xdg_toplevel_set_title(xdg_toplevel_.get(), base::UTF16ToUTF8(title).c_str());
+}
+
+void XDGToplevelWrapperImpl::SetWindowGeometry(const gfx::Rect& bounds) {
+  xdg_surface_wrapper_->SetWindowGeometry(bounds);
+}
+
+void XDGToplevelWrapperImpl::SetMinSize(int32_t width, int32_t height) {
+  DCHECK(xdg_toplevel_);
+  xdg_toplevel_set_min_size(xdg_toplevel_.get(), width, height);
+}
+
+void XDGToplevelWrapperImpl::SetMaxSize(int32_t width, int32_t height) {
+  DCHECK(xdg_toplevel_);
+  xdg_toplevel_set_max_size(xdg_toplevel_.get(), width, height);
+}
+
+void XDGToplevelWrapperImpl::SetAppId(const std::string& app_id) {
+  DCHECK(xdg_toplevel_);
+  xdg_toplevel_set_app_id(xdg_toplevel_.get(), app_id.c_str());
+}
+
+void XDGToplevelWrapperImpl::SetDecoration(DecorationMode decoration) {
+  SetTopLevelDecorationMode(decoration);
+}
+
+// static
+void XDGToplevelWrapperImpl::ConfigureTopLevel(
+    void* data,
+    struct xdg_toplevel* xdg_toplevel,
+    int32_t width,
+    int32_t height,
+    struct wl_array* states) {
+  auto* surface = static_cast<XDGToplevelWrapperImpl*>(data);
+  DCHECK(surface);
+
+  bool is_maximized =
+      CheckIfWlArrayHasValue(states, XDG_TOPLEVEL_STATE_MAXIMIZED);
+  bool is_fullscreen =
+      CheckIfWlArrayHasValue(states, XDG_TOPLEVEL_STATE_FULLSCREEN);
+  bool is_activated =
+      CheckIfWlArrayHasValue(states, XDG_TOPLEVEL_STATE_ACTIVATED);
+
+  surface->wayland_window_->HandleSurfaceConfigure(width, height, is_maximized,
+                                                   is_fullscreen, is_activated);
+}
+
+// static
+void XDGToplevelWrapperImpl::CloseTopLevel(void* data,
+                                           struct xdg_toplevel* xdg_toplevel) {
+  auto* surface = static_cast<XDGToplevelWrapperImpl*>(data);
+  DCHECK(surface);
+  surface->wayland_window_->OnCloseRequest();
+}
+
+void XDGToplevelWrapperImpl::SetTopLevelDecorationMode(
+    DecorationMode requested_mode) {
+  if (!zxdg_toplevel_decoration_ || requested_mode == decoration_mode_)
+    return;
+
+  decoration_mode_ = requested_mode;
+  zxdg_toplevel_decoration_v1_set_mode(zxdg_toplevel_decoration_.get(),
+                                       static_cast<uint32_t>(requested_mode));
+}
+
+// static
+void XDGToplevelWrapperImpl::ConfigureDecoration(
+    void* data,
+    struct zxdg_toplevel_decoration_v1* decoration,
+    uint32_t mode) {
+  auto* surface = static_cast<XDGToplevelWrapperImpl*>(data);
+  DCHECK(surface);
+  surface->SetTopLevelDecorationMode(static_cast<DecorationMode>(mode));
+}
+
+void XDGToplevelWrapperImpl::InitializeXdgDecoration() {
+  if (connection_->xdg_decoration_manager_v1()) {
+    DCHECK(!zxdg_toplevel_decoration_);
+    static const zxdg_toplevel_decoration_v1_listener decoration_listener = {
+        &XDGToplevelWrapperImpl::ConfigureDecoration,
+    };
+    zxdg_toplevel_decoration_.reset(
+        zxdg_decoration_manager_v1_get_toplevel_decoration(
+            connection_->xdg_decoration_manager_v1(), xdg_toplevel_.get()));
+    zxdg_toplevel_decoration_v1_add_listener(zxdg_toplevel_decoration_.get(),
+                                             &decoration_listener, this);
+  }
+}
+
+XDGSurfaceWrapperImpl* XDGToplevelWrapperImpl::xdg_surface_wrapper() const {
+  DCHECK(xdg_surface_wrapper_.get());
+  return xdg_surface_wrapper_.get();
+}
+
+}  // namespace ui
diff --git a/ui/ozone/platform/wayland/host/xdg_toplevel_wrapper_impl.h b/ui/ozone/platform/wayland/host/xdg_toplevel_wrapper_impl.h
new file mode 100644
index 0000000..14314c6e
--- /dev/null
+++ b/ui/ozone/platform/wayland/host/xdg_toplevel_wrapper_impl.h
@@ -0,0 +1,87 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_OZONE_PLATFORM_WAYLAND_HOST_XDG_TOPLEVEL_WRAPPER_IMPL_H_
+#define UI_OZONE_PLATFORM_WAYLAND_HOST_XDG_TOPLEVEL_WRAPPER_IMPL_H_
+
+#include <memory>
+
+#include "base/macros.h"
+#include "ui/ozone/platform/wayland/host/shell_toplevel_wrapper.h"
+
+namespace ui {
+
+class XDGSurfaceWrapperImpl;
+class WaylandConnection;
+class WaylandWindow;
+
+// Toplevel wrapper for xdg-shell stable
+class XDGToplevelWrapperImpl : public ShellToplevelWrapper {
+ public:
+  XDGToplevelWrapperImpl(std::unique_ptr<XDGSurfaceWrapperImpl> surface,
+                         WaylandWindow* wayland_window,
+                         WaylandConnection* connection);
+  XDGToplevelWrapperImpl(const XDGToplevelWrapperImpl&) = delete;
+  XDGToplevelWrapperImpl& operator=(const XDGToplevelWrapperImpl&) = delete;
+  ~XDGToplevelWrapperImpl() override;
+
+  // ShellSurfaceWrapper overrides:
+  bool Initialize() override;
+  void SetMaximized() override;
+  void UnSetMaximized() override;
+  void SetFullscreen() override;
+  void UnSetFullscreen() override;
+  void SetMinimized() override;
+  void SurfaceMove(WaylandConnection* connection) override;
+  void SurfaceResize(WaylandConnection* connection, uint32_t hittest) override;
+  void SetTitle(const base::string16& title) override;
+  void SetWindowGeometry(const gfx::Rect& bounds) override;
+  void SetMinSize(int32_t width, int32_t height) override;
+  void SetMaxSize(int32_t width, int32_t height) override;
+  void SetAppId(const std::string& app_id) override;
+  void SetDecoration(DecorationMode decoration) override;
+
+  XDGSurfaceWrapperImpl* xdg_surface_wrapper() const;
+
+ private:
+  // xdg_toplevel_listener
+  static void ConfigureTopLevel(void* data,
+                                struct xdg_toplevel* xdg_toplevel,
+                                int32_t width,
+                                int32_t height,
+                                struct wl_array* states);
+  static void CloseTopLevel(void* data, struct xdg_toplevel* xdg_toplevel);
+
+  // zxdg_decoration_listener
+  static void ConfigureDecoration(
+      void* data,
+      struct zxdg_toplevel_decoration_v1* decoration,
+      uint32_t mode);
+
+  // Send request to wayland compositor to enable a requested decoration mode.
+  void SetTopLevelDecorationMode(DecorationMode requested_mode);
+
+  // Initializes the xdg-decoration protocol extension, if available.
+  void InitializeXdgDecoration();
+
+  // Ground surface for this toplevel wrapper.
+  std::unique_ptr<XDGSurfaceWrapperImpl> xdg_surface_wrapper_;
+
+  // Non-owing WaylandWindow that uses this toplevel wrapper.
+  WaylandWindow* const wayland_window_;
+  WaylandConnection* const connection_;
+
+  // XDG Shell Stable object.
+  wl::Object<xdg_toplevel> xdg_toplevel_;
+
+  wl::Object<zxdg_toplevel_decoration_v1> zxdg_toplevel_decoration_;
+
+  // On client side, it keeps track of the decoration mode currently in
+  // use if xdg-decoration protocol extension is available.
+  DecorationMode decoration_mode_;
+};
+
+}  // namespace ui
+
+#endif  // UI_OZONE_PLATFORM_WAYLAND_HOST_XDG_TOPLEVEL_WRAPPER_IMPL_H_
diff --git a/ui/ozone/platform/wayland/host/zxdg_popup_v6_wrapper_impl.cc b/ui/ozone/platform/wayland/host/zxdg_popup_v6_wrapper_impl.cc
new file mode 100644
index 0000000..9b65ac38
--- /dev/null
+++ b/ui/ozone/platform/wayland/host/zxdg_popup_v6_wrapper_impl.cc
@@ -0,0 +1,257 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/ozone/platform/wayland/host/zxdg_popup_v6_wrapper_impl.h"
+
+#include <xdg-shell-unstable-v6-client-protocol.h>
+
+#include <memory>
+
+#include "base/environment.h"
+#include "base/nix/xdg_util.h"
+#include "ui/events/event.h"
+#include "ui/events/event_constants.h"
+#include "ui/events/types/event_type.h"
+#include "ui/gfx/geometry/rect.h"
+#include "ui/ozone/platform/wayland/common/wayland_util.h"
+#include "ui/ozone/platform/wayland/host/wayland_connection.h"
+#include "ui/ozone/platform/wayland/host/wayland_pointer.h"
+#include "ui/ozone/platform/wayland/host/wayland_popup.h"
+#include "ui/ozone/platform/wayland/host/wayland_toplevel_window.h"
+#include "ui/ozone/platform/wayland/host/zxdg_surface_v6_wrapper_impl.h"
+#include "ui/ozone/platform/wayland/host/zxdg_toplevel_v6_wrapper_impl.h"
+
+namespace ui {
+
+namespace {
+
+uint32_t TranslateAnchor(WlAnchor anchor) {
+  switch (anchor) {
+    case WlAnchor::None:
+      return ZXDG_POSITIONER_V6_ANCHOR_NONE;
+    case WlAnchor::Top:
+      return ZXDG_POSITIONER_V6_ANCHOR_TOP;
+    case WlAnchor::Bottom:
+      return ZXDG_POSITIONER_V6_ANCHOR_BOTTOM;
+    case WlAnchor::Left:
+      return ZXDG_POSITIONER_V6_ANCHOR_LEFT;
+    case WlAnchor::Right:
+      return ZXDG_POSITIONER_V6_ANCHOR_RIGHT;
+    case WlAnchor::TopLeft:
+      return ZXDG_POSITIONER_V6_ANCHOR_TOP | ZXDG_POSITIONER_V6_ANCHOR_LEFT;
+    case WlAnchor::BottomLeft:
+      return ZXDG_POSITIONER_V6_ANCHOR_BOTTOM | ZXDG_POSITIONER_V6_ANCHOR_LEFT;
+    case WlAnchor::TopRight:
+      return ZXDG_POSITIONER_V6_ANCHOR_TOP | ZXDG_POSITIONER_V6_ANCHOR_RIGHT;
+    case WlAnchor::BottomRight:
+      return ZXDG_POSITIONER_V6_ANCHOR_BOTTOM | ZXDG_POSITIONER_V6_ANCHOR_RIGHT;
+  }
+}
+
+uint32_t TranslateGravity(WlGravity gravity) {
+  switch (gravity) {
+    case WlGravity::None:
+      return ZXDG_POSITIONER_V6_GRAVITY_NONE;
+    case WlGravity::Top:
+      return ZXDG_POSITIONER_V6_GRAVITY_TOP;
+    case WlGravity::Bottom:
+      return ZXDG_POSITIONER_V6_GRAVITY_BOTTOM;
+    case WlGravity::Left:
+      return ZXDG_POSITIONER_V6_GRAVITY_LEFT;
+    case WlGravity::Right:
+      return ZXDG_POSITIONER_V6_GRAVITY_RIGHT;
+    case WlGravity::TopLeft:
+      return ZXDG_POSITIONER_V6_GRAVITY_TOP | ZXDG_POSITIONER_V6_GRAVITY_LEFT;
+    case WlGravity::BottomLeft:
+      return ZXDG_POSITIONER_V6_GRAVITY_BOTTOM |
+             ZXDG_POSITIONER_V6_GRAVITY_LEFT;
+    case WlGravity::TopRight:
+      return ZXDG_POSITIONER_V6_GRAVITY_TOP | ZXDG_POSITIONER_V6_GRAVITY_RIGHT;
+    case WlGravity::BottomRight:
+      return ZXDG_POSITIONER_V6_GRAVITY_BOTTOM |
+             ZXDG_POSITIONER_V6_GRAVITY_RIGHT;
+  }
+}
+
+uint32_t TranslateConstraintAdjustment(
+    WlConstraintAdjustment constraint_adjustment) {
+  uint32_t res = 0;
+  if ((constraint_adjustment & WlConstraintAdjustment::SlideX) !=
+      WlConstraintAdjustment::None)
+    res |= ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_SLIDE_X;
+  if ((constraint_adjustment & WlConstraintAdjustment::SlideY) !=
+      WlConstraintAdjustment::None)
+    res |= ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_SLIDE_Y;
+  if ((constraint_adjustment & WlConstraintAdjustment::FlipX) !=
+      WlConstraintAdjustment::None)
+    res |= ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_FLIP_X;
+  if ((constraint_adjustment & WlConstraintAdjustment::FlipY) !=
+      WlConstraintAdjustment::None)
+    res |= ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_FLIP_Y;
+  if ((constraint_adjustment & WlConstraintAdjustment::ResizeX) !=
+      WlConstraintAdjustment::None)
+    res |= ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_RESIZE_X;
+  if ((constraint_adjustment & WlConstraintAdjustment::ResizeY) !=
+      WlConstraintAdjustment::None)
+    res |= ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_RESIZE_Y;
+  return res;
+}
+
+}  // namespace
+
+ZXDGPopupV6WrapperImpl::ZXDGPopupV6WrapperImpl(
+    std::unique_ptr<ZXDGSurfaceV6WrapperImpl> surface,
+    WaylandWindow* wayland_window)
+    : wayland_window_(wayland_window),
+      zxdg_surface_v6_wrapper_(std::move(surface)) {
+  DCHECK(zxdg_surface_v6_wrapper_);
+  DCHECK(wayland_window_ && wayland_window_->parent_window());
+}
+
+ZXDGPopupV6WrapperImpl::~ZXDGPopupV6WrapperImpl() = default;
+
+bool ZXDGPopupV6WrapperImpl::Initialize(WaylandConnection* connection,
+                                        const gfx::Rect& bounds) {
+  if (!connection->shell() && !connection->shell_v6()) {
+    NOTREACHED() << "Wrong shell protocol";
+    return false;
+  }
+
+  ZXDGSurfaceV6WrapperImpl* parent_xdg_surface = nullptr;
+  // If the parent window is a popup, the surface of that popup must be used as
+  // a parent.
+  if (wl::IsMenuType(wayland_window_->parent_window()->type())) {
+    auto* wayland_popup =
+        static_cast<WaylandPopup*>(wayland_window_->parent_window());
+    ZXDGPopupV6WrapperImpl* popup =
+        static_cast<ZXDGPopupV6WrapperImpl*>(wayland_popup->shell_popup());
+    parent_xdg_surface = popup->zxdg_surface_v6_wrapper();
+  } else {
+    WaylandToplevelWindow* wayland_surface =
+        static_cast<WaylandToplevelWindow*>(wayland_window_->parent_window());
+    parent_xdg_surface = static_cast<ZXDGToplevelV6WrapperImpl*>(
+                             wayland_surface->shell_toplevel())
+                             ->zxdg_surface_v6_wrapper();
+  }
+
+  if (!zxdg_surface_v6_wrapper_ || !parent_xdg_surface)
+    return false;
+
+  auto new_bounds = bounds;
+  // Wayland doesn't allow empty bounds. If a zero or negative size is set, the
+  // invalid_input error is raised. Thus, use the least possible one.
+  // WaylandPopup will update its bounds upon the following configure event.
+  if (new_bounds.IsEmpty())
+    new_bounds.set_size({1, 1});
+
+  if (connection->shell_v6())
+    return InitializeV6(connection, new_bounds, parent_xdg_surface);
+
+  return false;
+}
+
+bool ZXDGPopupV6WrapperImpl::InitializeV6(
+    WaylandConnection* connection,
+    const gfx::Rect& bounds,
+    ZXDGSurfaceV6WrapperImpl* parent_xdg_surface) {
+  static const struct zxdg_popup_v6_listener zxdg_popup_v6_listener = {
+      &ZXDGPopupV6WrapperImpl::Configure,
+      &ZXDGPopupV6WrapperImpl::PopupDone,
+  };
+
+  zxdg_positioner_v6* positioner =
+      CreatePositioner(connection, wayland_window_->parent_window(), bounds);
+  if (!positioner)
+    return false;
+
+  zxdg_popup_v6_.reset(zxdg_surface_v6_get_popup(
+      zxdg_surface_v6_wrapper_->zxdg_surface(),
+      parent_xdg_surface->zxdg_surface(), positioner));
+  if (!zxdg_popup_v6_)
+    return false;
+
+  zxdg_positioner_v6_destroy(positioner);
+
+  if (CanGrabPopup(connection)) {
+    zxdg_popup_v6_grab(zxdg_popup_v6_.get(), connection->seat(),
+                       connection->serial());
+  }
+  zxdg_popup_v6_add_listener(zxdg_popup_v6_.get(), &zxdg_popup_v6_listener,
+                             this);
+
+  wayland_window_->root_surface()->Commit();
+  return true;
+}
+
+zxdg_positioner_v6* ZXDGPopupV6WrapperImpl::CreatePositioner(
+    WaylandConnection* connection,
+    WaylandWindow* parent_window,
+    const gfx::Rect& bounds) {
+  struct zxdg_positioner_v6* positioner;
+  positioner = zxdg_shell_v6_create_positioner(connection->shell_v6());
+  if (!positioner)
+    return nullptr;
+
+  auto menu_type = GetMenuTypeForPositioner(connection, parent_window);
+
+  // The parent we got must be the topmost in the stack of the same family
+  // windows.
+  DCHECK_EQ(parent_window->GetTopMostChildWindow(), parent_window);
+
+  // Place anchor to the end of the possible position.
+  gfx::Rect anchor_rect = GetAnchorRect(
+      menu_type, bounds,
+      gfx::ScaleToRoundedRect(parent_window->GetBounds(),
+                              1.0 / parent_window->buffer_scale()));
+
+  zxdg_positioner_v6_set_anchor_rect(positioner, anchor_rect.x(),
+                                     anchor_rect.y(), anchor_rect.width(),
+                                     anchor_rect.height());
+  zxdg_positioner_v6_set_size(positioner, bounds.width(), bounds.height());
+  zxdg_positioner_v6_set_anchor(positioner,
+                                TranslateAnchor(GetAnchor(menu_type, bounds)));
+  zxdg_positioner_v6_set_gravity(
+      positioner, TranslateGravity(GetGravity(menu_type, bounds)));
+  zxdg_positioner_v6_set_constraint_adjustment(
+      positioner,
+      TranslateConstraintAdjustment(GetConstraintAdjustment(menu_type)));
+  return positioner;
+}
+
+// static
+void ZXDGPopupV6WrapperImpl::Configure(void* data,
+                                       struct zxdg_popup_v6* zxdg_popup_v6,
+                                       int32_t x,
+                                       int32_t y,
+                                       int32_t width,
+                                       int32_t height) {
+  // As long as the Wayland compositor repositions/requires to position windows
+  // relative to their parents, do not propagate final bounds information to
+  // Chromium. The browser places windows in respect to screen origin, but
+  // Wayland requires doing so in respect to parent window's origin. To properly
+  // place windows, the bounds are translated and adjusted according to the
+  // Wayland compositor needs during WaylandWindow::CreateXdgPopup call.
+  WaylandWindow* window =
+      static_cast<ZXDGPopupV6WrapperImpl*>(data)->wayland_window_;
+  DCHECK(window);
+  window->HandlePopupConfigure({x, y, width, height});
+}
+
+// static
+void ZXDGPopupV6WrapperImpl::PopupDone(void* data,
+                                       struct zxdg_popup_v6* zxdg_popup_v6) {
+  WaylandWindow* window =
+      static_cast<ZXDGPopupV6WrapperImpl*>(data)->wayland_window_;
+  DCHECK(window);
+  window->Hide();
+  window->OnCloseRequest();
+}
+
+ZXDGSurfaceV6WrapperImpl* ZXDGPopupV6WrapperImpl::zxdg_surface_v6_wrapper()
+    const {
+  DCHECK(zxdg_surface_v6_wrapper_.get());
+  return zxdg_surface_v6_wrapper_.get();
+}
+
+}  // namespace ui
diff --git a/ui/ozone/platform/wayland/host/zxdg_popup_v6_wrapper_impl.h b/ui/ozone/platform/wayland/host/zxdg_popup_v6_wrapper_impl.h
new file mode 100644
index 0000000..b7113c37
--- /dev/null
+++ b/ui/ozone/platform/wayland/host/zxdg_popup_v6_wrapper_impl.h
@@ -0,0 +1,63 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_OZONE_PLATFORM_WAYLAND_HOST_ZXDG_POPUP_V6_WRAPPER_IMPL_H_
+#define UI_OZONE_PLATFORM_WAYLAND_HOST_ZXDG_POPUP_V6_WRAPPER_IMPL_H_
+
+#include <memory>
+
+#include "base/macros.h"
+#include "ui/ozone/platform/wayland/host/shell_popup_wrapper.h"
+
+namespace ui {
+
+class ZXDGSurfaceV6WrapperImpl;
+class WaylandConnection;
+class WaylandWindow;
+
+// Popup wrapper for xdg-shell-unstable-v6
+class ZXDGPopupV6WrapperImpl : public ShellPopupWrapper {
+ public:
+  ZXDGPopupV6WrapperImpl(std::unique_ptr<ZXDGSurfaceV6WrapperImpl> surface,
+                         WaylandWindow* wayland_window);
+  ~ZXDGPopupV6WrapperImpl() override;
+
+  // XDGPopupWrapper:
+  bool Initialize(WaylandConnection* connection,
+                  const gfx::Rect& bounds) override;
+
+ private:
+  bool InitializeV6(WaylandConnection* connection,
+                    const gfx::Rect& bounds,
+                    ZXDGSurfaceV6WrapperImpl* parent_zxdg_surface_v6_wrapper);
+  struct zxdg_positioner_v6* CreatePositioner(WaylandConnection* connection,
+                                              WaylandWindow* parent_window,
+                                              const gfx::Rect& bounds);
+
+  // zxdg_popup_v6_listener
+  static void Configure(void* data,
+                        struct zxdg_popup_v6* zxdg_popup_v6,
+                        int32_t x,
+                        int32_t y,
+                        int32_t width,
+                        int32_t height);
+  static void PopupDone(void* data, struct zxdg_popup_v6* zxdg_popup_v6);
+
+  ZXDGSurfaceV6WrapperImpl* zxdg_surface_v6_wrapper() const;
+
+  // Non-owned WaylandWindow that uses this popup.
+  WaylandWindow* const wayland_window_;
+
+  // Ground surface for this popup.
+  std::unique_ptr<ZXDGSurfaceV6WrapperImpl> zxdg_surface_v6_wrapper_;
+
+  // XDG Shell V6 object.
+  wl::Object<zxdg_popup_v6> zxdg_popup_v6_;
+
+  DISALLOW_COPY_AND_ASSIGN(ZXDGPopupV6WrapperImpl);
+};
+
+}  // namespace ui
+
+#endif  // UI_OZONE_PLATFORM_WAYLAND_HOST_ZXDG_POPUP_V6_WRAPPER_IMPL_H_
diff --git a/ui/ozone/platform/wayland/host/zxdg_surface_v6_wrapper_impl.cc b/ui/ozone/platform/wayland/host/zxdg_surface_v6_wrapper_impl.cc
new file mode 100644
index 0000000..e203be2b
--- /dev/null
+++ b/ui/ozone/platform/wayland/host/zxdg_surface_v6_wrapper_impl.cc
@@ -0,0 +1,76 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/ozone/platform/wayland/host/zxdg_surface_v6_wrapper_impl.h"
+
+#include <xdg-shell-unstable-v6-client-protocol.h>
+
+#include "ui/ozone/platform/wayland/host/wayland_connection.h"
+#include "ui/ozone/platform/wayland/host/wayland_window.h"
+
+namespace ui {
+
+ZXDGSurfaceV6WrapperImpl::ZXDGSurfaceV6WrapperImpl(
+    WaylandWindow* wayland_window,
+    WaylandConnection* connection)
+    : wayland_window_(wayland_window), connection_(connection) {}
+
+ZXDGSurfaceV6WrapperImpl::~ZXDGSurfaceV6WrapperImpl() = default;
+
+bool ZXDGSurfaceV6WrapperImpl::Initialize() {
+  if (!connection_->shell_v6()) {
+    NOTREACHED() << "Wrong shell protocol";
+    return false;
+  }
+
+  static const zxdg_surface_v6_listener zxdg_surface_v6_listener = {
+      &ZXDGSurfaceV6WrapperImpl::Configure,
+  };
+
+  zxdg_surface_v6_.reset(zxdg_shell_v6_get_xdg_surface(
+      connection_->shell_v6(), wayland_window_->root_surface()->surface()));
+  if (!zxdg_surface_v6_) {
+    LOG(ERROR) << "Failed to create zxdg_surface";
+    return false;
+  }
+
+  zxdg_surface_v6_add_listener(zxdg_surface_v6_.get(),
+                               &zxdg_surface_v6_listener, this);
+  connection_->ScheduleFlush();
+  return true;
+}
+
+void ZXDGSurfaceV6WrapperImpl::AckConfigure() {
+  DCHECK(zxdg_surface_v6_);
+  zxdg_surface_v6_ack_configure(zxdg_surface_v6_.get(),
+                                pending_configure_serial_);
+  connection_->wayland_window_manager()->NotifyWindowConfigured(
+      wayland_window_);
+}
+
+void ZXDGSurfaceV6WrapperImpl::SetWindowGeometry(const gfx::Rect& bounds) {
+  DCHECK(zxdg_surface_v6_);
+  zxdg_surface_v6_set_window_geometry(zxdg_surface_v6_.get(), bounds.x(),
+                                      bounds.y(), bounds.width(),
+                                      bounds.height());
+}
+
+// static
+void ZXDGSurfaceV6WrapperImpl::Configure(
+    void* data,
+    struct zxdg_surface_v6* zxdg_surface_v6,
+    uint32_t serial) {
+  auto* surface = static_cast<ZXDGSurfaceV6WrapperImpl*>(data);
+  DCHECK(surface);
+  surface->pending_configure_serial_ = serial;
+
+  surface->AckConfigure();
+}
+
+zxdg_surface_v6* ZXDGSurfaceV6WrapperImpl::zxdg_surface() const {
+  DCHECK(zxdg_surface_v6_);
+  return zxdg_surface_v6_.get();
+}
+
+}  // namespace ui
diff --git a/ui/ozone/platform/wayland/host/zxdg_surface_v6_wrapper_impl.h b/ui/ozone/platform/wayland/host/zxdg_surface_v6_wrapper_impl.h
new file mode 100644
index 0000000..c52b44e
--- /dev/null
+++ b/ui/ozone/platform/wayland/host/zxdg_surface_v6_wrapper_impl.h
@@ -0,0 +1,58 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_OZONE_PLATFORM_WAYLAND_HOST_ZXDG_SURFACE_V6_WRAPPER_IMPL_H_
+#define UI_OZONE_PLATFORM_WAYLAND_HOST_ZXDG_SURFACE_V6_WRAPPER_IMPL_H_
+
+#include "ui/ozone/platform/wayland/host/shell_surface_wrapper.h"
+
+#include <cstdint>
+#include <string>
+
+#include "base/strings/string16.h"
+#include "ui/ozone/platform/wayland/common/wayland_object.h"
+
+namespace gfx {
+class Rect;
+}
+
+namespace ui {
+
+class WaylandConnection;
+class WaylandWindow;
+
+// Surface wrapper for xdg-shell-unstable-v6
+class ZXDGSurfaceV6WrapperImpl : public ShellSurfaceWrapper {
+ public:
+  ZXDGSurfaceV6WrapperImpl(WaylandWindow* wayland_window,
+                           WaylandConnection* connection);
+  ZXDGSurfaceV6WrapperImpl(const ZXDGSurfaceV6WrapperImpl&) = delete;
+  ZXDGSurfaceV6WrapperImpl& operator=(const ZXDGSurfaceV6WrapperImpl&) = delete;
+  ~ZXDGSurfaceV6WrapperImpl() override;
+
+  // ShellSurfaceWrapper overrides:
+  bool Initialize() override;
+  void AckConfigure() override;
+  void SetWindowGeometry(const gfx::Rect& bounds) override;
+
+  // zxdg_surface_v6_listener
+  static void Configure(void* data,
+                        struct zxdg_surface_v6* zxdg_surface_v6,
+                        uint32_t serial);
+
+  zxdg_surface_v6* zxdg_surface() const;
+
+ private:
+  // Non-owing WaylandWindow that uses this surface wrapper.
+  WaylandWindow* const wayland_window_;
+  WaylandConnection* const connection_;
+
+  uint32_t pending_configure_serial_ = 0;
+
+  wl::Object<zxdg_surface_v6> zxdg_surface_v6_;
+};
+
+}  // namespace ui
+
+#endif  // UI_OZONE_PLATFORM_WAYLAND_HOST_ZXDG_SURFACE_V6_WRAPPER_IMPL_H_
diff --git a/ui/ozone/platform/wayland/host/zxdg_toplevel_v6_wrapper_impl.cc b/ui/ozone/platform/wayland/host/zxdg_toplevel_v6_wrapper_impl.cc
new file mode 100644
index 0000000..49ddd77
--- /dev/null
+++ b/ui/ozone/platform/wayland/host/zxdg_toplevel_v6_wrapper_impl.cc
@@ -0,0 +1,161 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/ozone/platform/wayland/host/zxdg_toplevel_v6_wrapper_impl.h"
+
+#include <xdg-decoration-unstable-v1-client-protocol.h>
+#include <xdg-shell-client-protocol.h>
+#include <xdg-shell-unstable-v6-client-protocol.h>
+
+#include "base/strings/utf_string_conversions.h"
+#include "ui/base/hit_test.h"
+#include "ui/ozone/platform/wayland/common/wayland_util.h"
+#include "ui/ozone/platform/wayland/host/shell_surface_wrapper.h"
+#include "ui/ozone/platform/wayland/host/wayland_connection.h"
+#include "ui/ozone/platform/wayland/host/wayland_window.h"
+#include "ui/ozone/platform/wayland/host/zxdg_surface_v6_wrapper_impl.h"
+
+namespace ui {
+
+ZXDGToplevelV6WrapperImpl::ZXDGToplevelV6WrapperImpl(
+    std::unique_ptr<ZXDGSurfaceV6WrapperImpl> surface,
+    WaylandWindow* wayland_window,
+    WaylandConnection* connection)
+    : zxdg_surface_v6_wrapper_(std::move(surface)),
+      wayland_window_(wayland_window),
+      connection_(connection) {}
+
+ZXDGToplevelV6WrapperImpl::~ZXDGToplevelV6WrapperImpl() = default;
+
+bool ZXDGToplevelV6WrapperImpl::Initialize() {
+  if (!connection_->shell_v6()) {
+    NOTREACHED() << "Wrong shell protocol";
+    return false;
+  }
+
+  static const zxdg_toplevel_v6_listener zxdg_toplevel_v6_listener = {
+      &ZXDGToplevelV6WrapperImpl::ConfigureTopLevel,
+      &ZXDGToplevelV6WrapperImpl::CloseTopLevel,
+  };
+
+  if (!zxdg_surface_v6_wrapper_)
+    return false;
+
+  zxdg_toplevel_v6_.reset(
+      zxdg_surface_v6_get_toplevel(zxdg_surface_v6_wrapper_->zxdg_surface()));
+  if (!zxdg_toplevel_v6_) {
+    LOG(ERROR) << "Failed to create zxdg_toplevel";
+    return false;
+  }
+  zxdg_toplevel_v6_add_listener(zxdg_toplevel_v6_.get(),
+                                &zxdg_toplevel_v6_listener, this);
+
+  wayland_window_->root_surface()->Commit();
+  connection_->ScheduleFlush();
+  return true;
+}
+
+void ZXDGToplevelV6WrapperImpl::SetMaximized() {
+  DCHECK(zxdg_toplevel_v6_);
+  zxdg_toplevel_v6_set_maximized(zxdg_toplevel_v6_.get());
+}
+
+void ZXDGToplevelV6WrapperImpl::UnSetMaximized() {
+  DCHECK(zxdg_toplevel_v6_);
+  zxdg_toplevel_v6_unset_maximized(zxdg_toplevel_v6_.get());
+}
+
+void ZXDGToplevelV6WrapperImpl::SetFullscreen() {
+  DCHECK(zxdg_toplevel_v6_);
+  zxdg_toplevel_v6_set_fullscreen(zxdg_toplevel_v6_.get(), nullptr);
+}
+
+void ZXDGToplevelV6WrapperImpl::UnSetFullscreen() {
+  DCHECK(zxdg_toplevel_v6_);
+  zxdg_toplevel_v6_unset_fullscreen(zxdg_toplevel_v6_.get());
+}
+
+void ZXDGToplevelV6WrapperImpl::SetMinimized() {
+  DCHECK(zxdg_toplevel_v6_);
+  zxdg_toplevel_v6_set_minimized(zxdg_toplevel_v6_.get());
+}
+
+void ZXDGToplevelV6WrapperImpl::SurfaceMove(WaylandConnection* connection) {
+  DCHECK(zxdg_toplevel_v6_);
+  zxdg_toplevel_v6_move(zxdg_toplevel_v6_.get(), connection->seat(),
+                        connection->serial());
+}
+
+void ZXDGToplevelV6WrapperImpl::SurfaceResize(WaylandConnection* connection,
+                                              uint32_t hittest) {
+  DCHECK(zxdg_toplevel_v6_);
+  zxdg_toplevel_v6_resize(zxdg_toplevel_v6_.get(), connection->seat(),
+                          connection->serial(),
+                          wl::IdentifyDirection(*connection, hittest));
+}
+
+void ZXDGToplevelV6WrapperImpl::SetTitle(const base::string16& title) {
+  DCHECK(zxdg_toplevel_v6_);
+  zxdg_toplevel_v6_set_title(zxdg_toplevel_v6_.get(),
+                             base::UTF16ToUTF8(title).c_str());
+}
+
+void ZXDGToplevelV6WrapperImpl::SetWindowGeometry(const gfx::Rect& bounds) {
+  zxdg_surface_v6_wrapper_->SetWindowGeometry(bounds);
+}
+
+void ZXDGToplevelV6WrapperImpl::SetMinSize(int32_t width, int32_t height) {
+  DCHECK(zxdg_toplevel_v6_);
+  zxdg_toplevel_v6_set_min_size(zxdg_toplevel_v6_.get(), width, height);
+}
+
+void ZXDGToplevelV6WrapperImpl::SetMaxSize(int32_t width, int32_t height) {
+  DCHECK(zxdg_toplevel_v6_);
+  zxdg_toplevel_v6_set_max_size(zxdg_toplevel_v6_.get(), width, height);
+}
+
+void ZXDGToplevelV6WrapperImpl::SetAppId(const std::string& app_id) {
+  DCHECK(zxdg_toplevel_v6_);
+  zxdg_toplevel_v6_set_app_id(zxdg_toplevel_v6_.get(), app_id.c_str());
+}
+
+void ZXDGToplevelV6WrapperImpl::SetDecoration(DecorationMode decoration) {}
+
+// static
+void ZXDGToplevelV6WrapperImpl::ConfigureTopLevel(
+    void* data,
+    struct zxdg_toplevel_v6* zxdg_toplevel_v6,
+    int32_t width,
+    int32_t height,
+    struct wl_array* states) {
+  auto* surface = static_cast<ZXDGToplevelV6WrapperImpl*>(data);
+  DCHECK(surface);
+
+  bool is_maximized =
+      CheckIfWlArrayHasValue(states, ZXDG_TOPLEVEL_V6_STATE_MAXIMIZED);
+  bool is_fullscreen =
+      CheckIfWlArrayHasValue(states, ZXDG_TOPLEVEL_V6_STATE_FULLSCREEN);
+  bool is_activated =
+      CheckIfWlArrayHasValue(states, ZXDG_TOPLEVEL_V6_STATE_ACTIVATED);
+
+  surface->wayland_window_->HandleSurfaceConfigure(width, height, is_maximized,
+                                                   is_fullscreen, is_activated);
+}
+
+// static
+void ZXDGToplevelV6WrapperImpl::CloseTopLevel(
+    void* data,
+    struct zxdg_toplevel_v6* zxdg_toplevel_v6) {
+  auto* surface = static_cast<ZXDGToplevelV6WrapperImpl*>(data);
+  DCHECK(surface);
+  surface->wayland_window_->OnCloseRequest();
+}
+
+ZXDGSurfaceV6WrapperImpl* ZXDGToplevelV6WrapperImpl::zxdg_surface_v6_wrapper()
+    const {
+  DCHECK(zxdg_surface_v6_wrapper_.get());
+  return zxdg_surface_v6_wrapper_.get();
+}
+
+}  // namespace ui
diff --git a/ui/ozone/platform/wayland/host/zxdg_toplevel_v6_wrapper_impl.h b/ui/ozone/platform/wayland/host/zxdg_toplevel_v6_wrapper_impl.h
new file mode 100644
index 0000000..29b8300c
--- /dev/null
+++ b/ui/ozone/platform/wayland/host/zxdg_toplevel_v6_wrapper_impl.h
@@ -0,0 +1,71 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_OZONE_PLATFORM_WAYLAND_HOST_ZXDG_TOPLEVEL_V6_WRAPPER_IMPL_H_
+#define UI_OZONE_PLATFORM_WAYLAND_HOST_ZXDG_TOPLEVEL_V6_WRAPPER_IMPL_H_
+
+#include <memory>
+
+#include "base/macros.h"
+#include "ui/ozone/platform/wayland/host/shell_toplevel_wrapper.h"
+
+namespace ui {
+
+class ZXDGSurfaceV6WrapperImpl;
+class WaylandConnection;
+class WaylandWindow;
+
+// Toplevel wrapper for xdg-shell-unstable-v6
+class ZXDGToplevelV6WrapperImpl : public ShellToplevelWrapper {
+ public:
+  ZXDGToplevelV6WrapperImpl(std::unique_ptr<ZXDGSurfaceV6WrapperImpl> surface,
+                            WaylandWindow* wayland_window,
+                            WaylandConnection* connection);
+  ZXDGToplevelV6WrapperImpl(const ZXDGToplevelV6WrapperImpl&) = delete;
+  ZXDGToplevelV6WrapperImpl& operator=(const ZXDGToplevelV6WrapperImpl&) =
+      delete;
+  ~ZXDGToplevelV6WrapperImpl() override;
+
+  // ShellSurfaceWrapper overrides:
+  bool Initialize() override;
+  void SetMaximized() override;
+  void UnSetMaximized() override;
+  void SetFullscreen() override;
+  void UnSetFullscreen() override;
+  void SetMinimized() override;
+  void SurfaceMove(WaylandConnection* connection) override;
+  void SurfaceResize(WaylandConnection* connection, uint32_t hittest) override;
+  void SetTitle(const base::string16& title) override;
+  void SetWindowGeometry(const gfx::Rect& bounds) override;
+  void SetMinSize(int32_t width, int32_t height) override;
+  void SetMaxSize(int32_t width, int32_t height) override;
+  void SetAppId(const std::string& app_id) override;
+  void SetDecoration(DecorationMode decoration) override;
+
+  ZXDGSurfaceV6WrapperImpl* zxdg_surface_v6_wrapper() const;
+
+ private:
+  // zxdg_toplevel_v6_listener
+  static void ConfigureTopLevel(void* data,
+                                struct zxdg_toplevel_v6* zxdg_toplevel_v6,
+                                int32_t width,
+                                int32_t height,
+                                struct wl_array* states);
+  static void CloseTopLevel(void* data,
+                            struct zxdg_toplevel_v6* zxdg_toplevel_v6);
+
+  // Ground surface for this toplevel wrapper.
+  std::unique_ptr<ZXDGSurfaceV6WrapperImpl> zxdg_surface_v6_wrapper_;
+
+  // Non-owing WaylandWindow that uses this toplevel wrapper.
+  WaylandWindow* const wayland_window_;
+  WaylandConnection* const connection_;
+
+  // XDG Shell V6 object.
+  wl::Object<zxdg_toplevel_v6> zxdg_toplevel_v6_;
+};
+
+}  // namespace ui
+
+#endif  // UI_OZONE_PLATFORM_WAYLAND_HOST_ZXDG_TOPLEVEL_V6_WRAPPER_IMPL_H_
diff --git a/ui/strings/ui_strings.grd b/ui/strings/ui_strings.grd
index fb96af0..b50f3e3 100644
--- a/ui/strings/ui_strings.grd
+++ b/ui/strings/ui_strings.grd
@@ -620,6 +620,11 @@
           Emoji &amp;&amp; Symbols
         </message>
       </if>
+      <if expr="chromeos">
+        <message name="IDS_APP_SHOW_CLIPBOARD_HISTORY" desc="The context menu item to show the clipboard history menu.">
+          Clipboard
+        </message>
+      </if>
 
       <!-- Generic terms -->
       <message name="IDS_APP_OK" desc="Used for Ok on buttons">
diff --git a/ui/strings/ui_strings_grd/IDS_APP_SHOW_CLIPBOARD_HISTORY.png.sha1 b/ui/strings/ui_strings_grd/IDS_APP_SHOW_CLIPBOARD_HISTORY.png.sha1
new file mode 100644
index 0000000..edfc185
--- /dev/null
+++ b/ui/strings/ui_strings_grd/IDS_APP_SHOW_CLIPBOARD_HISTORY.png.sha1
@@ -0,0 +1 @@
+5ab94be1bbbebc839acf4bb0b07c6b5f8ce60b99
\ No newline at end of file
diff --git a/ui/views/BUILD.gn b/ui/views/BUILD.gn
index 308e63f6..6570153 100644
--- a/ui/views/BUILD.gn
+++ b/ui/views/BUILD.gn
@@ -180,6 +180,7 @@
     "controls/tree/tree_view_controller.h",
     "controls/tree/tree_view_drawing_provider.h",
     "controls/views_text_services_context_menu.h",
+    "controls/views_text_services_context_menu_base.h",
     "debug_utils.h",
     "drag_controller.h",
     "drag_utils.h",
@@ -386,7 +387,6 @@
     "controls/tree/tree_view_controller.cc",
     "controls/tree/tree_view_drawing_provider.cc",
     "controls/views_text_services_context_menu_base.cc",
-    "controls/views_text_services_context_menu_base.h",
     "debug_utils.cc",
     "drag_utils.cc",
     "focus/external_focus_tracker.cc",
@@ -562,6 +562,10 @@
 
   if (is_chromeos_ash || is_chromeos_lacros) {
     sources += [ "controls/menu/menu_config_chromeos.cc" ]
+    if (!is_chromeos_lacros) {
+      public += [ "controls/views_text_services_context_menu_chromeos.h" ]
+      sources += [ "controls/views_text_services_context_menu_chromeos.cc" ]
+    }
   }
 
   if (is_mac) {
diff --git a/ui/views/controls/views_text_services_context_menu_base.cc b/ui/views/controls/views_text_services_context_menu_base.cc
index a2c0df0..531ebe3 100644
--- a/ui/views/controls/views_text_services_context_menu_base.cc
+++ b/ui/views/controls/views_text_services_context_menu_base.cc
@@ -85,7 +85,7 @@
   return command_id == IDS_CONTENT_CONTEXT_EMOJI;
 }
 
-#if !defined(OS_APPLE)
+#if !defined(OS_APPLE) && !BUILDFLAG(IS_CHROMEOS_ASH)
 // static
 std::unique_ptr<ViewsTextServicesContextMenu>
 ViewsTextServicesContextMenu::Create(ui::SimpleMenuModel* menu,
diff --git a/ui/views/controls/views_text_services_context_menu_base.h b/ui/views/controls/views_text_services_context_menu_base.h
index 2318cf8..f3568f52 100644
--- a/ui/views/controls/views_text_services_context_menu_base.h
+++ b/ui/views/controls/views_text_services_context_menu_base.h
@@ -7,12 +7,14 @@
 
 #include "build/build_config.h"
 #include "ui/views/controls/views_text_services_context_menu.h"
+#include "ui/views/views_export.h"
 
 namespace views {
 
 // This base class is used to add and handle text service items in the textfield
 // context menu. Specific platforms may subclass and add additional items.
-class ViewsTextServicesContextMenuBase : public ViewsTextServicesContextMenu {
+class VIEWS_EXPORT ViewsTextServicesContextMenuBase
+    : public ViewsTextServicesContextMenu {
  public:
   ViewsTextServicesContextMenuBase(ui::SimpleMenuModel* menu,
                                    Textfield* client);
@@ -31,7 +33,7 @@
   bool SupportsCommand(int command_id) const override;
 
  protected:
-#if defined(OS_APPLE)
+#if defined(OS_APPLE) || defined(OS_CHROMEOS)
   Textfield* client() { return client_; }
   const Textfield* client() const { return client_; }
 #endif
diff --git a/ui/views/controls/views_text_services_context_menu_chromeos.cc b/ui/views/controls/views_text_services_context_menu_chromeos.cc
new file mode 100644
index 0000000..5a2d082
--- /dev/null
+++ b/ui/views/controls/views_text_services_context_menu_chromeos.cc
@@ -0,0 +1,79 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/views/controls/views_text_services_context_menu_chromeos.h"
+
+#include <utility>
+
+#include "base/no_destructor.h"
+#include "ui/views/controls/views_text_services_context_menu_base.h"
+
+namespace views {
+
+namespace {
+
+using ImplFactory = ViewsTextServicesContextMenuChromeos::ImplFactory;
+ImplFactory& GetImplFactory() {
+  static base::NoDestructor<ImplFactory> impl_factory;
+  return *impl_factory;
+}
+
+}  // namespace
+
+// static
+void ViewsTextServicesContextMenuChromeos::SetImplFactory(
+    ImplFactory impl_factory) {
+  GetImplFactory() = std::move(impl_factory);
+}
+
+ViewsTextServicesContextMenuChromeos::ViewsTextServicesContextMenuChromeos(
+    ui::SimpleMenuModel* menu,
+    Textfield* client) {
+  auto& impl_factory = GetImplFactory();
+
+  // In unit tests, `impl_factory` may not be set. Use
+  // `ViewTextServicesContextMenuBase` in that case.
+  if (impl_factory)
+    impl_ = impl_factory.Run(menu, client);
+  else
+    impl_ = std::make_unique<ViewsTextServicesContextMenuBase>(menu, client);
+}
+
+ViewsTextServicesContextMenuChromeos::~ViewsTextServicesContextMenuChromeos() =
+    default;
+
+bool ViewsTextServicesContextMenuChromeos::GetAcceleratorForCommandId(
+    int command_id,
+    ui::Accelerator* accelerator) const {
+  return impl_->GetAcceleratorForCommandId(command_id, accelerator);
+}
+
+bool ViewsTextServicesContextMenuChromeos::IsCommandIdChecked(
+    int command_id) const {
+  return impl_->IsCommandIdChecked(command_id);
+}
+
+bool ViewsTextServicesContextMenuChromeos::IsCommandIdEnabled(
+    int command_id) const {
+  return impl_->IsCommandIdEnabled(command_id);
+}
+
+void ViewsTextServicesContextMenuChromeos::ExecuteCommand(int command_id,
+                                                          int event_flags) {
+  return impl_->ExecuteCommand(command_id, event_flags);
+}
+
+bool ViewsTextServicesContextMenuChromeos::SupportsCommand(
+    int command_id) const {
+  return impl_->IsCommandIdEnabled(command_id);
+}
+
+// static
+std::unique_ptr<ViewsTextServicesContextMenu>
+ViewsTextServicesContextMenu::Create(ui::SimpleMenuModel* menu,
+                                     Textfield* client) {
+  return std::make_unique<ViewsTextServicesContextMenuChromeos>(menu, client);
+}
+
+}  // namespace views
diff --git a/ui/views/controls/views_text_services_context_menu_chromeos.h b/ui/views/controls/views_text_services_context_menu_chromeos.h
new file mode 100644
index 0000000..4dfd7d0
--- /dev/null
+++ b/ui/views/controls/views_text_services_context_menu_chromeos.h
@@ -0,0 +1,51 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_VIEWS_CONTROLS_VIEWS_TEXT_SERVICES_CONTEXT_MENU_CHROMEOS_H_
+#define UI_VIEWS_CONTROLS_VIEWS_TEXT_SERVICES_CONTEXT_MENU_CHROMEOS_H_
+
+#include <memory>
+
+#include "ui/views/controls/views_text_services_context_menu.h"
+#include "ui/views/views_export.h"
+
+namespace views {
+
+// This class is used to add and handle text service items in the text context
+// menu under the CrOS environment.
+class VIEWS_EXPORT ViewsTextServicesContextMenuChromeos
+    : public ViewsTextServicesContextMenu {
+ public:
+  using ImplFactory = base::RepeatingCallback<std::unique_ptr<
+      ViewsTextServicesContextMenu>(ui::SimpleMenuModel*, Textfield*)>;
+
+  // Injects the method to construct `impl_`.
+  static void SetImplFactory(ImplFactory factory);
+
+  ViewsTextServicesContextMenuChromeos(ui::SimpleMenuModel* menu,
+                                       Textfield* client);
+  ViewsTextServicesContextMenuChromeos(
+      const ViewsTextServicesContextMenuChromeos&) = delete;
+  ViewsTextServicesContextMenuChromeos& operator=(
+      const ViewsTextServicesContextMenuChromeos&) = delete;
+  ~ViewsTextServicesContextMenuChromeos() override;
+
+  // ViewsTextServicesContextMenu:
+  bool GetAcceleratorForCommandId(int command_id,
+                                  ui::Accelerator* accelerator) const override;
+  bool IsCommandIdChecked(int command_id) const override;
+  bool IsCommandIdEnabled(int command_id) const override;
+  void ExecuteCommand(int command_id, int event_flags) override;
+  bool SupportsCommand(int command_id) const override;
+
+ private:
+  // CrOS functionality must be provided by the embedder, so requests are
+  // forwarded to this concrete object, whose construction can be controlled by
+  // `SetImplFactory()`.
+  std::unique_ptr<ViewsTextServicesContextMenu> impl_;
+};
+
+}  // namespace views
+
+#endif  // UI_VIEWS_CONTROLS_VIEWS_TEXT_SERVICES_CONTEXT_MENU_CHROMEOS_H_
diff --git a/ui/views/metadata/type_conversion.cc b/ui/views/metadata/type_conversion.cc
index ac0cf76..7b51d5c 100644
--- a/ui/views/metadata/type_conversion.cc
+++ b/ui/views/metadata/type_conversion.cc
@@ -19,6 +19,24 @@
 namespace views {
 namespace metadata {
 
+base::Optional<SkColor> RgbaPiecesToSkColor(
+    const std::vector<base::StringPiece16>& pieces,
+    size_t start_piece) {
+  int r, g, b;
+  double a;
+  return ((pieces.size() >= start_piece + 4) &&
+          base::StringToInt(pieces[start_piece], &r) &&
+          base::IsValueInRangeForNumericType<uint8_t>(r) &&
+          base::StringToInt(pieces[start_piece + 1], &g) &&
+          base::IsValueInRangeForNumericType<uint8_t>(g) &&
+          base::StringToInt(pieces[start_piece + 2], &b) &&
+          base::IsValueInRangeForNumericType<uint8_t>(b) &&
+          base::StringToDouble(pieces[start_piece + 3], &a))
+             ? base::make_optional(SkColorSetARGB(
+                   base::ClampRound<SkAlpha>(a * SK_AlphaOPAQUE), r, g, b))
+             : base::nullopt;
+}
+
 const base::string16& GetNullOptStr() {
   static const base::NoDestructor<base::string16> kNullOptStr(
       base::ASCIIToUTF16("<Empty>"));
@@ -215,16 +233,15 @@
     const auto members = base::SplitStringPiece(
         member_string, base::ASCIIToUTF16(","), base::TRIM_WHITESPACE,
         base::SPLIT_WANT_NONEMPTY);
-    int x, y, r, g, b, a;
+    int x, y;
     double blur;
+    const auto color = RgbaPiecesToSkColor(members, 3);
 
     if ((members.size() == 7) && base::StringToInt(members[0], &x) &&
         base::StringToInt(members[1], &y) &&
         base::StringToDouble(UTF16ToASCII(members[2]), &blur) &&
-        base::StringToInt(members[3], &r) &&
-        base::StringToInt(members[4], &g) &&
-        base::StringToInt(members[5], &b) && base::StringToInt(members[6], &a))
-      ret.emplace_back(gfx::Vector2d(x, y), blur, SkColorSetARGB(a, r, g, b));
+        color.has_value())
+      ret.emplace_back(gfx::Vector2d(x, y), blur, color.value());
   }
   return ret;
 }
diff --git a/ui/views/metadata/type_conversion.h b/ui/views/metadata/type_conversion.h
index 9ba57d4..cf770a3 100644
--- a/ui/views/metadata/type_conversion.h
+++ b/ui/views/metadata/type_conversion.h
@@ -107,6 +107,13 @@
 
 // String Conversions ---------------------------------------------------------
 
+// Converts the four elements of |pieces| beginning at |start_piece| to an
+// SkColor by assuming the pieces are split from a string like "rgba(r,g,b,a)".
+// Returns nullopt if conversion was unsuccessful.
+VIEWS_EXPORT base::Optional<SkColor> RgbaPiecesToSkColor(
+    const std::vector<base::StringPiece16>& pieces,
+    size_t start_piece);
+
 #define DECLARE_CONVERSIONS(T)                                               \
   template <>                                                                \
   struct VIEWS_EXPORT TypeConverter<T> {                                     \
diff --git a/ui/views/metadata/type_conversion_unittest.cc b/ui/views/metadata/type_conversion_unittest.cc
index d1630597..c9d36c2 100644
--- a/ui/views/metadata/type_conversion_unittest.cc
+++ b/ui/views/metadata/type_conversion_unittest.cc
@@ -95,7 +95,7 @@
 TEST_F(TypeConversionTest, TestConversion_StringToShadowValues) {
   base::Optional<gfx::ShadowValues> opt_result =
       metadata::TypeConverter<gfx::ShadowValues>::FromString(base::ASCIIToUTF16(
-          "[ (6,4),0.53,rgba(23,44,0,255); (93,83),4.33,rgba(10,20,0,15) ]"));
+          "[ (6,4),0.53,rgba(23,44,0,1); (93,83),4.33,rgba(10,20,0,0.059) ]"));
 
   EXPECT_EQ(opt_result.has_value(), true);
   gfx::ShadowValues result = opt_result.value();
diff --git a/ui/views/widget/native_widget_aura.cc b/ui/views/widget/native_widget_aura.cc
index ede9124..b5d6fe4a 100644
--- a/ui/views/widget/native_widget_aura.cc
+++ b/ui/views/widget/native_widget_aura.cc
@@ -253,6 +253,8 @@
     SetRestoreBounds(window_, window_bounds);
   else
     SetBounds(window_bounds);
+  // For similar reasons, wait to set visible on all workspaces.
+  SetVisibleOnAllWorkspaces(params.visible_on_all_workspaces);
   window_->SetEventTargetingPolicy(
       params.accept_events ? aura::EventTargetingPolicy::kTargetAndDescendants
                            : aura::EventTargetingPolicy::kNone);
@@ -657,11 +659,13 @@
 }
 
 void NativeWidgetAura::SetVisibleOnAllWorkspaces(bool always_visible) {
-  // Not implemented on chromeos or for child widgets.
+  window_->SetProperty(aura::client::kVisibleOnAllWorkspacesKey,
+                       always_visible);
 }
 
 bool NativeWidgetAura::IsVisibleOnAllWorkspaces() const {
-  return false;
+  return window_ &&
+         window_->GetProperty(aura::client::kVisibleOnAllWorkspacesKey);
 }
 
 void NativeWidgetAura::Maximize() {
diff --git a/ui/webui/resources/cr_components/BUILD.gn b/ui/webui/resources/cr_components/BUILD.gn
index c2e2d2e4..a261bcb7 100644
--- a/ui/webui/resources/cr_components/BUILD.gn
+++ b/ui/webui/resources/cr_components/BUILD.gn
@@ -143,6 +143,7 @@
       "chromeos/cellular_setup/cellular_setup_icons.m.js",
       "chromeos/cellular_setup/cellular_setup.m.js",
       "chromeos/cellular_setup/cellular_types.m.js",
+      "chromeos/cellular_setup/confirmation_code_page.m.js",
       "chromeos/cellular_setup/esim_flow_ui.m.js",
       "chromeos/cellular_setup/final_page.m.js",
       "chromeos/cellular_setup/mojo_interface_provider.m.js",
@@ -153,6 +154,7 @@
       "chromeos/cellular_setup/provisioning_page.m.js",
       "chromeos/cellular_setup/psim_flow_ui.m.js",
       "chromeos/cellular_setup/setup_selection_flow.m.js",
+      "chromeos/cellular_setup/cellular_eid_popup.m.js",
       "chromeos/cellular_setup/setup_loading_page.m.js",
       "chromeos/cellular_setup/subflow_behavior.m.js",
       "chromeos/cellular_setup/webview_post_util.m.js",
@@ -259,6 +261,8 @@
       "chromeos/cellular_setup/cellular_setup.js",
       "chromeos/cellular_setup/cellular_types.html",
       "chromeos/cellular_setup/cellular_types.js",
+      "chromeos/cellular_setup/confirmation_code_page.html",
+      "chromeos/cellular_setup/confirmation_code_page.js",
       "chromeos/cellular_setup/esim_flow_ui.html",
       "chromeos/cellular_setup/esim_flow_ui.js",
       "chromeos/cellular_setup/final_page.html",
@@ -279,6 +283,8 @@
       "chromeos/cellular_setup/psim_flow_ui.js",
       "chromeos/cellular_setup/setup_selection_flow.html",
       "chromeos/cellular_setup/setup_selection_flow.js",
+      "chromeos/cellular_setup/cellular_eid_popup.html",
+      "chromeos/cellular_setup/cellular_eid_popup.js",
       "chromeos/cellular_setup/setup_loading_page.html",
       "chromeos/cellular_setup/setup_loading_page.js",
       "chromeos/cellular_setup/subflow_behavior.html",
diff --git a/ui/webui/resources/cr_components/chromeos/cellular_setup/BUILD.gn b/ui/webui/resources/cr_components/chromeos/cellular_setup/BUILD.gn
index 293475a..1d59ced 100644
--- a/ui/webui/resources/cr_components/chromeos/cellular_setup/BUILD.gn
+++ b/ui/webui/resources/cr_components/chromeos/cellular_setup/BUILD.gn
@@ -14,9 +14,11 @@
     ":activation_code_page",
     ":base_page",
     ":button_bar",
+    ":cellular_eid_popup",
     ":cellular_setup",
     ":cellular_setup_delegate",
     ":cellular_types",
+    ":confirmation_code_page",
     ":esim_manager_listener_behavior",
     ":esim_manager_utils",
     ":final_page",
@@ -81,6 +83,10 @@
   ]
 }
 
+js_library("confirmation_code_page") {
+  deps = [ "//ui/webui/resources/js:i18n_behavior" ]
+}
+
 js_library("profile_discovery_list_page") {
   deps = [
     "//third_party/polymer/v1_0/components-chromium/iron-list:iron-list-extracted",
@@ -111,6 +117,7 @@
   deps = [
     ":activation_code_page",
     ":cellular_setup_delegate",
+    ":confirmation_code_page",
     ":esim_manager_utils",
     ":mojo_interface_provider",
     ":profile_discovery_list_item",
@@ -129,6 +136,10 @@
   ]
 }
 
+js_library("cellular_eid_popup") {
+  deps = [ "//ui/webui/resources/js:i18n_behavior" ]
+}
+
 js_library("subflow_behavior") {
   deps = [
     ":cellular_types",
@@ -182,6 +193,7 @@
     ":cellular_setup.m",
     ":cellular_setup_delegate.m",
     ":cellular_types.m",
+    ":confirmation_code_page.m",
     ":esim_flow_ui.m",
     ":esim_manager_listener_behavior.m",
     ":esim_manager_utils.m",
@@ -228,6 +240,12 @@
   extra_deps = [ ":activation_code_page_module" ]
 }
 
+js_library("confirmation_code_page.m") {
+  sources = [ "$root_gen_dir/ui/webui/resources/cr_components/chromeos/cellular_setup/confirmation_code_page.m.js" ]
+  deps = [ "//ui/webui/resources/js:i18n_behavior.m" ]
+  extra_deps = [ ":confirmation_code_page_module" ]
+}
+
 js_library("profile_discovery_list_item.m") {
   sources = [ "$root_gen_dir/ui/webui/resources/cr_components/chromeos/cellular_setup/profile_discovery_list_item.m.js" ]
   deps = [ "//ui/webui/resources/js:i18n_behavior.m" ]
@@ -350,6 +368,7 @@
     ":activation_code_page.m",
     ":cellular_setup_delegate.m",
     ":cellular_types.m",
+    ":confirmation_code_page.m",
     ":esim_manager_utils.m",
     ":final_page.m",
     ":mojo_interface_provider.m",
@@ -373,6 +392,12 @@
   extra_deps = [ ":setup_selection_flow_module" ]
 }
 
+js_library("cellular_eid_popup.m") {
+  sources = [ "$root_gen_dir/ui/webui/resources/cr_components/chromeos/cellular_setup/cellular_eid_popup.m.js" ]
+  deps = [ "//ui/webui/resources/js:i18n_behavior.m" ]
+  extra_deps = [ ":cellular_eid_popup_module" ]
+}
+
 js_library("cellular_setup.m") {
   sources = [ "$root_gen_dir/ui/webui/resources/cr_components/chromeos/cellular_setup/cellular_setup.m.js" ]
   deps = [
@@ -392,8 +417,10 @@
     ":activation_code_page_module",
     ":base_page_module",
     ":button_bar_module",
+    ":cellular_eid_popup_module",
     ":cellular_setup_icons_module",
     ":cellular_setup_module",
+    ":confirmation_code_page_module",
     ":esim_flow_ui_module",
     ":final_page_module",
     ":modulize",
@@ -438,6 +465,14 @@
   auto_imports = cr_components_chromeos_auto_imports
 }
 
+polymer_modulizer("confirmation_code_page") {
+  js_file = "confirmation_code_page.js"
+  html_file = "confirmation_code_page.html"
+  html_type = "dom-module"
+  namespace_rewrites = cr_components_chromeos_namespace_rewrites
+  auto_imports = cr_components_chromeos_auto_imports
+}
+
 polymer_modulizer("setup_loading_page") {
   js_file = "setup_loading_page.js"
   html_file = "setup_loading_page.html"
@@ -494,6 +529,14 @@
   auto_imports = cr_components_chromeos_auto_imports
 }
 
+polymer_modulizer("cellular_eid_popup") {
+  js_file = "cellular_eid_popup.js"
+  html_file = "cellular_eid_popup.html"
+  html_type = "dom-module"
+  namespace_rewrites = cr_components_chromeos_namespace_rewrites
+  auto_imports = cr_components_chromeos_auto_imports
+}
+
 polymer_modulizer("cellular_setup") {
   js_file = "cellular_setup.js"
   html_file = "cellular_setup.html"
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/cellular_eid_popup.html b/ui/webui/resources/cr_components/chromeos/cellular_setup/cellular_eid_popup.html
similarity index 80%
rename from chrome/browser/resources/settings/chromeos/internet_page/cellular_eid_popup.html
rename to ui/webui/resources/cr_components/chromeos/cellular_setup/cellular_eid_popup.html
index d4e3484a..c0ef8b78 100644
--- a/chrome/browser/resources/settings/chromeos/internet_page/cellular_eid_popup.html
+++ b/ui/webui/resources/cr_components/chromeos/cellular_setup/cellular_eid_popup.html
@@ -4,14 +4,10 @@
 <link rel="import" href="chrome://resources/html/i18n_behavior.html">
 <link rel="import" href="chrome://resources/cr_elements/shared_style_css.html">
 <link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="../../chromeos/os_settings_icons_css.html">
-<link rel="import" href="./internet_shared_css.html">
 
 <dom-module id="cellular-eid-popup">
   <template>
-    <style include="cr-shared-style
-        os-settings-icons
-        settings-shared internet-shared iron-flex">
+    <style include="cr-shared-style iron-flex">
 
       .cellular-network-list-header {
         border-top: var(--cr-separator-line);
@@ -58,19 +54,19 @@
         aria-describedby="eidDescription">
       <div class="header">
         <div id="eidTitle" class="title" arian-hidden="true">
-          $i18n{eidPopupTitle}
+          [[i18n('eidPopupTitle')]]
         </div>
         <cr-icon-button
             id="eidPopupCloseIcon"
             iron-icon="cr:close"
-            title="$i18n{closeEidPopupButtonLabel}"
-            aria-label="$i18n{closeEidPopupButtonLabel}"
+            title="[[i18n('closeEidPopupButtonLabel')]]"
+            aria-label="[[i18n('closeEidPopupButtonLabel')]]"
             on-click="onCloseTap_">
         </cr-icon-button>
       </div>
       <div class="body">
         <div id="eidDescription" arian-hidden="true">
-          $i18n{eidPopupDescription}
+          [[i18n('eidPopupDescription')]]
         </div>
         <!-- TODO(crbug/1093185): Add eid code -->
       </div>
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/cellular_eid_popup.js b/ui/webui/resources/cr_components/chromeos/cellular_setup/cellular_eid_popup.js
similarity index 100%
rename from chrome/browser/resources/settings/chromeos/internet_page/cellular_eid_popup.js
rename to ui/webui/resources/cr_components/chromeos/cellular_setup/cellular_eid_popup.js
diff --git a/ui/webui/resources/cr_components/chromeos/cellular_setup/confirmation_code_page.html b/ui/webui/resources/cr_components/chromeos/cellular_setup/confirmation_code_page.html
new file mode 100644
index 0000000..2d24cee
--- /dev/null
+++ b/ui/webui/resources/cr_components/chromeos/cellular_setup/confirmation_code_page.html
@@ -0,0 +1,18 @@
+<link rel="import" href="../../../html/polymer.html">
+
+<link rel="import" href="../../../html/i18n_behavior.html">
+<link rel="import" href="base_page.html">
+
+<dom-module id="confirmation-code-page">
+  <template>
+    <style include="iron-flex">
+    </style>
+    <base-page>
+      <div slot="page-body" class="layout horizontal center-center">
+        <!-- TODO(crbug.com/1093185): Create UI for confirmation code page. -->
+        Please enter your confirmation code for [[getProfileName_(profileProperties_)]].
+      </div>
+    </base-page>
+  </template>
+  <script src="confirmation_code_page.js"></script>
+</dom-module>
diff --git a/ui/webui/resources/cr_components/chromeos/cellular_setup/confirmation_code_page.js b/ui/webui/resources/cr_components/chromeos/cellular_setup/confirmation_code_page.js
new file mode 100644
index 0000000..84b91e80
--- /dev/null
+++ b/ui/webui/resources/cr_components/chromeos/cellular_setup/confirmation_code_page.js
@@ -0,0 +1,55 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * Page in eSIM Cellular Setup flow shown if an eSIM profile requires a
+ * confirmation code to install. This element contains an input for the user to
+ * enter the confirmation code.
+ */
+Polymer({
+  is: 'confirmation-code-page',
+
+  behaviors: [I18nBehavior],
+
+  properties: {
+    /**
+     * @type {?chromeos.cellularSetup.mojom.ESimProfileRemote}
+     */
+    profile: {
+      type: Object,
+      observer: 'onProfileChanged_',
+    },
+
+    /**
+     * @type {?chromeos.cellularSetup.mojom.ESimProfileProperties}
+     * @private
+     */
+    profileProperties_: {
+      type: Object,
+      value: null,
+    },
+  },
+
+  /** @private */
+  onProfileChanged_() {
+    if (!this.profile) {
+      this.profileProperties_ = null;
+      return;
+    }
+    this.profile.getProperties().then(response => {
+      this.profileProperties_ = response.properties;
+    });
+  },
+
+  /**
+   * @return {string}
+   * @private
+   */
+  getProfileName_() {
+    if (!this.profileProperties_) {
+      return '';
+    }
+    return String.fromCharCode(...this.profileProperties_.name.data);
+  },
+});
\ No newline at end of file
diff --git a/ui/webui/resources/cr_components/chromeos/cellular_setup/esim_flow_ui.html b/ui/webui/resources/cr_components/chromeos/cellular_setup/esim_flow_ui.html
index f6f0cbc..79704c5 100644
--- a/ui/webui/resources/cr_components/chromeos/cellular_setup/esim_flow_ui.html
+++ b/ui/webui/resources/cr_components/chromeos/cellular_setup/esim_flow_ui.html
@@ -11,6 +11,7 @@
 <link rel="import" href="activation_code_page.html">
 <link rel="import" href="final_page.html">
 <link rel="import" href="profile_discovery_list_page.html">
+<link rel="import" href="confirmation_code_page.html">
 
 <dom-module id="esim-flow-ui">
   <template>
@@ -37,6 +38,9 @@
           show-no-profiles-message="[[getShowNoProfilesMessage_(pendingProfiles_)]]"
           show-error="{{showError_}}">
       </activation-code-page>
+      <confirmation-code-page id="confirmationCodePage"
+          profile="[[selectedProfile_]]">
+      </confirmation-code-page>
       <final-page
         id="finalPage"
         delegate="[[delegate]]"
diff --git a/ui/webui/resources/cr_components/chromeos/cellular_setup/esim_flow_ui.js b/ui/webui/resources/cr_components/chromeos/cellular_setup/esim_flow_ui.js
index 71f8b81..414a91a 100644
--- a/ui/webui/resources/cr_components/chromeos/cellular_setup/esim_flow_ui.js
+++ b/ui/webui/resources/cr_components/chromeos/cellular_setup/esim_flow_ui.js
@@ -8,6 +8,7 @@
     PROFILE_LOADING: 'profileLoadingPage',
     PROFILE_DISCOVERY: 'profileDiscoveryPage',
     ACTIVATION_CODE: 'activationCodePage',
+    CONFIRMATION_CODE: 'confirmationCodePage',
     FINAL: 'finalPage',
   };
 
@@ -15,6 +16,7 @@
   /* #export */ const ESimUiState = {
     PROFILE_SEARCH: 'profile-search',
     ACTIVATION_CODE_ENTRY: 'activation-code-entry',
+    CONFIRMATION_CODE_ENTRY: 'confirmation-code-entry',
     PROFILE_SELECTION: 'profile-selection',
     SETUP_FINISH: 'setup-finish',
   };
@@ -73,7 +75,7 @@
       },
 
       /**
-       * Profile selected in profileDiscoveryPage to be installed.
+       * Profile selected to be installed.
        * @type {?chromeos.cellularSetup.mojom.ESimProfileRemote}
        * @private
        */
@@ -141,9 +143,10 @@
                 this.state_ = ESimUiState.ACTIVATION_CODE_ENTRY;
                 break;
               case 1:
+                this.selectedProfile_ = profiles[0];
                 // Assume installing the profile doesn't require a confirmation
                 // code, send an empty string.
-                profiles[0].installProfile('').then(
+                this.selectedProfile_.installProfile('').then(
                     this.handleProfileInstallResponse_.bind(this));
                 break;
               default:
@@ -159,8 +162,6 @@
      *     response
      */
     handleProfileInstallResponse_(response) {
-      // TODO(crbug.com/1093185) Handle
-      // confirmation code if needed.
       this.showError_ = response.result !==
           chromeos.cellularSetup.mojom.ProfileInstallResult.kSuccess;
       if (response.result ===
@@ -168,6 +169,11 @@
           response.result ===
               chromeos.cellularSetup.mojom.ProfileInstallResult.kFailure) {
         this.state_ = ESimUiState.SETUP_FINISH;
+      } else if (
+          response.result ===
+          chromeos.cellularSetup.mojom.ProfileInstallResult
+              .kErrorNeedsConfirmationCode) {
+        this.state_ = ESimUiState.CONFIRMATION_CODE_ENTRY;
       }
     },
 
@@ -180,6 +186,9 @@
         case ESimUiState.ACTIVATION_CODE_ENTRY:
           this.selectedESimPageName_ = ESimPageName.ACTIVATION_CODE;
           break;
+        case ESimUiState.CONFIRMATION_CODE_ENTRY:
+          this.selectedESimPageName_ = ESimPageName.CONFIRMATION_CODE;
+          break;
         case ESimUiState.PROFILE_SELECTION:
           this.selectedESimPageName_ = ESimPageName.PROFILE_DISCOVERY;
           break;
@@ -209,6 +218,19 @@
             skipDiscovery: cellularSetup.ButtonState.HIDDEN,
           };
           break;
+        case ESimUiState.CONFIRMATION_CODE_ENTRY:
+          buttonState = {
+            backward: cellularSetup.ButtonState.SHOWN_AND_ENABLED,
+            cancel: this.delegate.shouldShowCancelButton() ?
+                cellularSetup.ButtonState.SHOWN_AND_ENABLED :
+                cellularSetup.ButtonState.HIDDEN,
+            done: cellularSetup.ButtonState.SHOWN_AND_ENABLED,
+            next: cellularSetup.ButtonState.HIDDEN,
+            tryAgain: cellularSetup.ButtonState.HIDDEN,
+            skipDiscovery: cellularSetup.ButtonState.HIDDEN,
+            // TODO(crbug.com/1093185) Add a "Confirm" button state.
+          };
+          break;
         case ESimUiState.PROFILE_SELECTION:
           buttonState = {
             backward: cellularSetup.ButtonState.HIDDEN,
diff --git a/ui/webui/resources/cr_components/chromeos/cellular_setup/esim_manager_listener_behavior.js b/ui/webui/resources/cr_components/chromeos/cellular_setup/esim_manager_listener_behavior.js
index e2c9783..339c815 100644
--- a/ui/webui/resources/cr_components/chromeos/cellular_setup/esim_manager_listener_behavior.js
+++ b/ui/webui/resources/cr_components/chromeos/cellular_setup/esim_manager_listener_behavior.js
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-// #import {getESimManagerRemote} from './mojo_interface_provider.m.js';
+// #import {observeESimManager} from './mojo_interface_provider.m.js';
 
 /**
  * @fileoverview Polymer behavior for observing ESimManagerObserver
@@ -16,10 +16,7 @@
 
   /** @override */
   attached() {
-    this.observer_ =
-        new chromeos.cellularSetup.mojom.ESimManagerObserverReceiver(this);
-    cellular_setup.getESimManagerRemote().addObserver(
-        this.observer_.$.bindNewPipeAndPassRemote());
+    cellular_setup.observeESimManager(this);
   },
 
   // ESimManagerObserver methods. Override these in the implementation.
diff --git a/ui/webui/resources/cr_components/chromeos/cellular_setup/mojo_interface_provider.js b/ui/webui/resources/cr_components/chromeos/cellular_setup/mojo_interface_provider.js
index c416b1c..5b79070 100644
--- a/ui/webui/resources/cr_components/chromeos/cellular_setup/mojo_interface_provider.js
+++ b/ui/webui/resources/cr_components/chromeos/cellular_setup/mojo_interface_provider.js
@@ -14,6 +14,7 @@
 cr.define('cellular_setup', function() {
   let cellularRemote = null;
   let eSimManagerRemote = null;
+  let isTesting = false;
 
   /**
    * @param {?chromeos.cellularSetup.mojom.CellularSetupRemote}
@@ -21,6 +22,7 @@
    */
   /* #export */ function setCellularSetupRemoteForTesting(testCellularRemote) {
     cellularRemote = testCellularRemote;
+    isTesting = true;
   }
 
   /**
@@ -42,6 +44,7 @@
    */
   /* #export */ function setESimManagerRemoteForTesting(testESimManagerRemote) {
     eSimManagerRemote = testESimManagerRemote;
+    isTesting = true;
   }
 
   /**
@@ -57,11 +60,31 @@
     return eSimManagerRemote;
   }
 
+  /**
+   * @param {!chromeos.cellularSetup.mojom.ESimManagerObserverInterface}
+   *     observer
+   * @returns {?chromeos.cellularSetup.mojom.ESimManagerObserverReceiver}
+   */
+  /* #export */ function observeESimManager(observer) {
+    if (isTesting) {
+      getESimManagerRemote().addObserver(
+          /** @type {!chromeos.cellularSetup.mojom.ESimManagerObserverRemote} */
+          (observer));
+      return null;
+    }
+
+    const receiver =
+        new chromeos.cellularSetup.mojom.ESimManagerObserverReceiver(observer);
+    getESimManagerRemote().addObserver(receiver.$.bindNewPipeAndPassRemote());
+    return receiver;
+  }
+
   // #cr_define_end
   return {
     setCellularSetupRemoteForTesting,
     getCellularSetupRemote,
     setESimManagerRemoteForTesting,
-    getESimManagerRemote
+    getESimManagerRemote,
+    observeESimManager
   };
 });
diff --git a/ui/webui/resources/cr_components/chromeos/network/BUILD.gn b/ui/webui/resources/cr_components/chromeos/network/BUILD.gn
index 60495ca..957ac95c8 100644
--- a/ui/webui/resources/cr_components/chromeos/network/BUILD.gn
+++ b/ui/webui/resources/cr_components/chromeos/network/BUILD.gn
@@ -454,6 +454,7 @@
   deps = [
     ":mojo_interface_provider.m",
     ":network_list_types.m",
+    "//third_party/polymer/v3_0/components-chromium/paper-spinner:paper-spinner-lite",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
     "//ui/webui/resources/js/cr/ui:focus_row_behavior.m",
   ]
diff --git a/ui/webui/resources/cr_components/chromeos/network/network_list_item.html b/ui/webui/resources/cr_components/chromeos/network/network_list_item.html
index fb0256e..8a58ff8d7 100644
--- a/ui/webui/resources/cr_components/chromeos/network/network_list_item.html
+++ b/ui/webui/resources/cr_components/chromeos/network/network_list_item.html
@@ -1,6 +1,7 @@
 <link rel="import" href="../../../html/polymer.html">
 
 <link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/iron-flex-layout-classes.html">
+<link rel="import" href="chrome://resources/polymer/v1_0/paper-spinner/paper-spinner-lite.html">
 <link rel="import" href="../../../cr_elements/cr_icon_button/cr_icon_button.html">
 <link rel="import" href="../../../cr_elements/icons.html">
 <link rel="import" href="../../../cr_elements/policy/cr_policy_indicator.html">
@@ -78,6 +79,12 @@
         --iron-icon-fill-color: #1A73E8;
         margin-inline-end: 8px;
       }
+
+      paper-spinner-lite {
+        height: 20px;
+        margin-inline-end: 16px;
+        width: 20px;
+      }
     </style>
     <div id="wrapper" focus-row-container
          class="layout horizontal center flex">
@@ -99,9 +106,11 @@
           </network-icon>
         </template>
         <template is="dom-if" if="[[item.polymerIcon]]">
-          <iron-icon icon="[[item.polymerIcon]]" class$="[[getItemClassName_(item)]]"></iron-icon>
+          <iron-icon icon="[[item.polymerIcon]]"
+              class$="[[getItemClassName_(item, item.customItemType]]"></iron-icon>
         </template>
-        <div id="divText" class$="layout horizontal flex [[getItemClassName_(item)]]">
+        <div id="divText" class$="layout horizontal flex
+            [[getItemClassName_(item, item.customItemType)]]">
           <div id="networkName" aria-hidden="true">
             [[getItemName_(item)]]
           </div>
@@ -135,14 +144,18 @@
             </cr-icon-button>
           </div>
         </template>
-        <template is="dom-if" if="[[isESimPendingProfile_(item)]]" restamp>
-          <!-- TODO(crbug.com/1093185): Download profile when clicked. -->
-          <cr-button id="downloadButton"
-              aria-label$="[[getItemName_(item)]], $i18n{networkListItemDownload}">
+        <template is="dom-if" if="[[isESimPendingProfile_(item, item.customItemType)]]" restamp>
+          <cr-button id="installButton"
+              aria-label$="[[getItemName_(item)]], $i18n{networkListItemDownload}"
+              on-click="onInstallButtonClick_">
             <iron-icon icon="network:download"></iron-icon>
             $i18n{networkListItemDownload}
           </cr-button>
         </template>
+        <template is="dom-if" if="[[isESimInstallingProfile_(item, item.customItemType)]]" restamp>
+          <paper-spinner-lite active></paper-spinner-lite>
+          $i18n{networkListItemAddingProfile}
+        </template>
       </div>
     </div>
   </template>
diff --git a/ui/webui/resources/cr_components/chromeos/network/network_list_item.js b/ui/webui/resources/cr_components/chromeos/network/network_list_item.js
index af8d29d..f106d20 100644
--- a/ui/webui/resources/cr_components/chromeos/network/network_list_item.js
+++ b/ui/webui/resources/cr_components/chromeos/network/network_list_item.js
@@ -377,6 +377,15 @@
           return this.i18n(
               'networkListItemLabelESimPendingProfile', index, total,
               this.getItemName_());
+        } else if (this.isESimInstallingProfile_()) {
+          if (this.subtitle_) {
+            return this.i18n(
+                'networkListItemLabelESimPendingProfileWithProviderNameInstalling',
+                index, total, this.getItemName_(), this.subtitle_);
+          }
+          return this.i18n(
+              'networkListItemLabelESimPendingProfileInstalling', index, total,
+              this.getItemName_());
         }
         return this.i18n(
             'networkListItemLabel', index, total, this.getItemName_());
@@ -495,6 +504,8 @@
     if (this.isSubpageButtonVisible_(this.networkState, this.showButtons) &&
         this.$$('#subpage-button') === this.shadowRoot.activeElement) {
       this.fireShowDetails_(event);
+    } else if (this.isESimPendingProfile_()) {
+      this.onInstallButtonClick_();
     } else if (this.item.hasOwnProperty('customItemName')) {
       this.fire('custom-item-selected', this.item);
     } else {
@@ -552,12 +563,17 @@
     return this.isFocused ? 'polite' : 'off';
   },
 
+  /** @private */
+  onInstallButtonClick_() {
+    this.fire('install-profile', {iccid: this.item.customData.iccid});
+  },
+
   /**
    * @return {boolean}
    * @private
    */
   isESimPendingProfile_() {
-    return this.item.hasOwnProperty('customItemType') &&
+    return !!this.item && this.item.hasOwnProperty('customItemType') &&
         this.item.customItemType ===
         NetworkList.CustomItemType.ESIM_PENDING_PROFILE;
   },
@@ -569,4 +585,14 @@
   getItemClassName_() {
     return this.isESimPendingProfile_() ? 'esim-pending-profile' : '';
   },
+
+  /**
+   * @return {boolean}
+   * @private
+   */
+  isESimInstallingProfile_() {
+    return !!this.item && this.item.hasOwnProperty('customItemType') &&
+        this.item.customItemType ===
+        NetworkList.CustomItemType.ESIM_INSTALLING_PROFILE;
+  },
 });
diff --git a/ui/webui/resources/cr_components/chromeos/network/network_list_types.js b/ui/webui/resources/cr_components/chromeos/network/network_list_types.js
index b9b9fde..a873ff6 100644
--- a/ui/webui/resources/cr_components/chromeos/network/network_list_types.js
+++ b/ui/webui/resources/cr_components/chromeos/network/network_list_types.js
@@ -14,6 +14,7 @@
 NetworkList.CustomItemType = {
   OOBE: 1,
   ESIM_PENDING_PROFILE: 2,
+  ESIM_INSTALLING_PROFILE: 3,
 };
 
 /**
diff --git a/ui/webui/resources/cr_components/chromeos/os_cr_components.gni b/ui/webui/resources/cr_components/chromeos/os_cr_components.gni
index ed430a9..f1a7cbe 100644
--- a/ui/webui/resources/cr_components/chromeos/os_cr_components.gni
+++ b/ui/webui/resources/cr_components/chromeos/os_cr_components.gni
@@ -19,6 +19,7 @@
   "cellular_setup.setCellularSetupRemoteForTesting|setCellularSetupRemoteForTesting",
   "cellular_setup.getESimManagerRemote|getESimManagerRemote",
   "cellular_setup.setESimManagerRemoteForTesting|setESimManagerRemoteForTesting",
+  "cellular_setup.observeESimManager|observeESimManager",
   "cellular_setup.getPendingESimProfiles|getPendingESimProfiles",
   "cellular_setup.CellularSetupDelegate|CellularSetupDelegate",
   "network_config.MojoInterfaceProvider|MojoInterfaceProvider",
@@ -33,7 +34,7 @@
   "ui/webui/resources/cr_components/chromeos/cellular_setup/cellular_types.html|ButtonState,Button,ButtonBarState,CellularSetupPageName",
   "ui/webui/resources/cr_components/chromeos/cellular_setup/cellular_setup_delegate.html|CellularSetupDelegate",
   "ui/webui/resources/cr_components/chromeos/cellular_setup/subflow_behavior.html|SubflowBehavior",
-  "ui/webui/resources/cr_components/chromeos/cellular_setup/mojo_interface_provider.html|setCellularSetupRemoteForTesting,getCellularSetupRemote,setESimManagerRemoteForTesting,getESimManagerRemote",
+  "ui/webui/resources/cr_components/chromeos/cellular_setup/mojo_interface_provider.html|setCellularSetupRemoteForTesting,getCellularSetupRemote,setESimManagerRemoteForTesting,getESimManagerRemote,observeESimManager",
   "ui/webui/resources/cr_components/chromeos/cellular_setup/esim_manager_utils.html|getPendingESimProfiles",
   "ui/webui/resources/cr_components/chromeos/cellular_setup/esim_manager_listener_behavior.html|ESimManagerListenerBehavior",
   "ui/webui/resources/cr_components/chromeos/network/cr_policy_network_behavior_mojo.html|CrPolicyNetworkBehaviorMojo",
diff --git a/weblayer/browser/android/javatests/skew/mb_config.pyl b/weblayer/browser/android/javatests/skew/mb_config.pyl
index b77af3ca..da42c9e2 100644
--- a/weblayer/browser/android/javatests/skew/mb_config.pyl
+++ b/weblayer/browser/android/javatests/skew/mb_config.pyl
@@ -67,7 +67,7 @@
     },
 
     'release_bot': {
-      'mixins': ['release', 'static'],
+      'mixins': ['release', 'static', 'goma'],
     },
 
     'static': {
diff --git a/weblayer/browser/content_browser_client_impl.cc b/weblayer/browser/content_browser_client_impl.cc
index ae92265..8e62033 100644
--- a/weblayer/browser/content_browser_client_impl.cc
+++ b/weblayer/browser/content_browser_client_impl.cc
@@ -661,11 +661,26 @@
     content::NavigationHandle* handle) {
   std::vector<std::unique_ptr<content::NavigationThrottle>> throttles;
 
+  TabImpl* tab = TabImpl::FromWebContents(handle->GetWebContents());
+  NavigationControllerImpl* navigation_controller = nullptr;
+  if (tab) {
+    navigation_controller =
+        static_cast<NavigationControllerImpl*>(tab->GetNavigationController());
+  }
   if (handle->IsInMainFrame()) {
     NavigationUIDataImpl* navigation_ui_data =
         static_cast<NavigationUIDataImpl*>(handle->GetNavigationUIData());
+
+    NavigationImpl* navigation_impl = nullptr;
+    if (navigation_controller) {
+      navigation_impl =
+          navigation_controller->GetNavigationImplFromHandle(handle);
+    }
+
     if ((!navigation_ui_data ||
          !navigation_ui_data->disable_network_error_auto_reload()) &&
+        (!navigation_impl ||
+         !navigation_impl->disable_network_error_auto_reload()) &&
         IsNetworkErrorAutoReloadEnabled()) {
       auto auto_reload_throttle =
           error_page::NetErrorAutoReloader::MaybeCreateThrottleFor(handle);
@@ -688,11 +703,8 @@
 
   // The next highest priority throttle *must* be this as it's responsible for
   // calling to NavigationController for certain events.
-  TabImpl* tab = TabImpl::FromWebContents(handle->GetWebContents());
   if (tab) {
-    auto throttle =
-        static_cast<NavigationControllerImpl*>(tab->GetNavigationController())
-            ->CreateNavigationThrottle(handle);
+    auto throttle = navigation_controller->CreateNavigationThrottle(handle);
     if (throttle)
       throttles.push_back(std::move(throttle));
   }
diff --git a/weblayer/browser/errorpage_browsertest.cc b/weblayer/browser/errorpage_browsertest.cc
index 58abe90e..362b6357 100644
--- a/weblayer/browser/errorpage_browsertest.cc
+++ b/weblayer/browser/errorpage_browsertest.cc
@@ -72,9 +72,12 @@
       WARN_UNUSED_RESULT {
     content::TestNavigationManager navigation(web_contents(), url);
     NavigationController::NavigateParams params;
-    params.disable_network_error_auto_reload =
-        disable_network_error_auto_reload;
-    shell()->tab()->GetNavigationController()->Navigate(url, params);
+    auto* navigation_controller = shell()->tab()->GetNavigationController();
+    std::unique_ptr<DisableAutoReload> disable_auto_reload;
+    if (disable_network_error_auto_reload)
+      disable_auto_reload =
+          std::make_unique<DisableAutoReload>(navigation_controller);
+    navigation_controller->Navigate(url, params);
     navigation.WaitForNavigationFinished();
     return navigation.was_successful();
   }
@@ -98,6 +101,23 @@
   }
 
  private:
+  class DisableAutoReload : public NavigationObserver {
+   public:
+    explicit DisableAutoReload(NavigationController* controller)
+        : controller_(controller) {
+      controller_->AddObserver(this);
+    }
+    ~DisableAutoReload() override { controller_->RemoveObserver(this); }
+
+    // NavigationObserver implementation:
+    void NavigationStarted(Navigation* navigation) override {
+      navigation->DisableNetworkErrorAutoReload();
+    }
+
+   private:
+    NavigationController* controller_;
+  };
+
   base::test::ScopedFeatureList feature_list_;
 };
 
diff --git a/weblayer/browser/java/org/chromium/weblayer_private/NavigationImpl.java b/weblayer/browser/java/org/chromium/weblayer_private/NavigationImpl.java
index 48a8e3ef..4212397 100644
--- a/weblayer/browser/java/org/chromium/weblayer_private/NavigationImpl.java
+++ b/weblayer/browser/java/org/chromium/weblayer_private/NavigationImpl.java
@@ -193,6 +193,13 @@
         return NavigationImplJni.get().isServedFromBackForwardCache(mNativeNavigationImpl);
     }
 
+    @Override
+    public void disableNetworkErrorAutoReload() {
+        if (!NavigationImplJni.get().disableNetworkErrorAutoReload(mNativeNavigationImpl)) {
+            throw new IllegalStateException();
+        }
+    }
+
     public void setIntentLaunched() {
         mIntentLaunched = true;
     }
@@ -255,5 +262,6 @@
         boolean isPageInitiated(long nativeNavigationImpl);
         boolean isReload(long nativeNavigationImpl);
         boolean isServedFromBackForwardCache(long nativeNavigationImpl);
+        boolean disableNetworkErrorAutoReload(long nativeNavigationImpl);
     }
 }
diff --git a/weblayer/browser/java/org/chromium/weblayer_private/interfaces/INavigation.aidl b/weblayer/browser/java/org/chromium/weblayer_private/interfaces/INavigation.aidl
index 00d369b0..9787b7e 100644
--- a/weblayer/browser/java/org/chromium/weblayer_private/interfaces/INavigation.aidl
+++ b/weblayer/browser/java/org/chromium/weblayer_private/interfaces/INavigation.aidl
@@ -38,4 +38,5 @@
   boolean isUserDecidingIntentLaunch() = 14;
   boolean isKnownProtocol() = 15;
   boolean isServedFromBackForwardCache() = 16;
+  void disableNetworkErrorAutoReload() = 17;
 }
diff --git a/weblayer/browser/navigation_controller_impl.cc b/weblayer/browser/navigation_controller_impl.cc
index ccbf419..fa3b988 100644
--- a/weblayer/browser/navigation_controller_impl.cc
+++ b/weblayer/browser/navigation_controller_impl.cc
@@ -302,11 +302,6 @@
       std::make_unique<content::NavigationController::LoadURLParams>(url);
   load_params->should_replace_current_entry =
       params.should_replace_current_entry;
-  if (params.disable_network_error_auto_reload) {
-    auto data = std::make_unique<NavigationUIDataImpl>();
-    data->set_disable_network_error_auto_reload(true);
-    load_params->navigation_ui_data = std::move(data);
-  }
   if (params.enable_auto_play)
     load_params->was_activated = content::mojom::WasActivatedOption::kYes;
 
@@ -396,6 +391,7 @@
   base::AutoReset<NavigationImpl*> auto_reset(&navigation_starting_,
                                               navigation);
   navigation->set_safe_to_set_request_headers(true);
+  navigation->set_safe_to_disable_network_error_auto_reload(true);
 
 #if defined(OS_ANDROID)
   // Desktop mode and per-navigation UA use the same mechanism and so don't
@@ -434,6 +430,7 @@
     observer.NavigationStarted(navigation);
   navigation->set_safe_to_set_user_agent(false);
   navigation->set_safe_to_set_request_headers(false);
+  navigation->set_safe_to_disable_network_error_auto_reload(false);
 }
 
 void NavigationControllerImpl::DidRedirectNavigation(
diff --git a/weblayer/browser/navigation_impl.cc b/weblayer/browser/navigation_impl.cc
index ce04b0c..5634520 100644
--- a/weblayer/browser/navigation_impl.cc
+++ b/weblayer/browser/navigation_impl.cc
@@ -94,6 +94,13 @@
   return true;
 }
 
+jboolean NavigationImpl::DisableNetworkErrorAutoReload(JNIEnv* env) {
+  if (!safe_to_disable_network_error_auto_reload_)
+    return false;
+  DisableNetworkErrorAutoReload();
+  return true;
+}
+
 void NavigationImpl::SetResponse(
     std::unique_ptr<embedder_support::WebResourceResponse> response) {
   response_ = std::move(response);
@@ -218,6 +225,11 @@
   set_user_agent_string_called_ = true;
 }
 
+void NavigationImpl::DisableNetworkErrorAutoReload() {
+  DCHECK(safe_to_disable_network_error_auto_reload_);
+  disable_network_error_auto_reload_ = true;
+}
+
 #if defined(OS_ANDROID)
 static jboolean JNI_NavigationImpl_IsValidRequestHeaderName(
     JNIEnv* env,
diff --git a/weblayer/browser/navigation_impl.h b/weblayer/browser/navigation_impl.h
index 1898d5f..8b11be41 100644
--- a/weblayer/browser/navigation_impl.h
+++ b/weblayer/browser/navigation_impl.h
@@ -49,10 +49,18 @@
     safe_to_set_user_agent_ = value;
   }
 
+  void set_safe_to_disable_network_error_auto_reload(bool value) {
+    safe_to_disable_network_error_auto_reload_ = value;
+  }
+
   void set_was_stopped() { was_stopped_ = true; }
 
   bool set_user_agent_string_called() { return set_user_agent_string_called_; }
 
+  bool disable_network_error_auto_reload() {
+    return disable_network_error_auto_reload_;
+  }
+
   void SetParamsToLoadWhenSafe(
       std::unique_ptr<content::NavigationController::LoadURLParams> params);
   std::unique_ptr<content::NavigationController::LoadURLParams>
@@ -83,6 +91,7 @@
   jboolean IsServedFromBackForwardCache(JNIEnv* env) {
     return IsServedFromBackForwardCache();
   }
+  jboolean DisableNetworkErrorAutoReload(JNIEnv* env);
 
   void SetResponse(
       std::unique_ptr<embedder_support::WebResourceResponse> response);
@@ -107,6 +116,7 @@
   void SetRequestHeader(const std::string& name,
                         const std::string& value) override;
   void SetUserAgentString(const std::string& value) override;
+  void DisableNetworkErrorAutoReload() override;
   bool IsPageInitiated() override;
   bool IsReload() override;
   bool IsServedFromBackForwardCache() override;
@@ -134,6 +144,11 @@
   // Whether SetUserAgentString was called.
   bool set_user_agent_string_called_ = false;
 
+  // Whether DisableNetworkErrorAutoReload is allowed at this time.
+  bool safe_to_disable_network_error_auto_reload_ = false;
+
+  bool disable_network_error_auto_reload_ = false;
+
 #if defined(OS_ANDROID)
   base::android::ScopedJavaGlobalRef<jobject> java_navigation_;
   std::unique_ptr<embedder_support::WebResourceResponse> response_;
diff --git a/weblayer/public/java/org/chromium/weblayer/NavigateParams.java b/weblayer/public/java/org/chromium/weblayer/NavigateParams.java
index 3d6a71f..0fc051ad 100644
--- a/weblayer/public/java/org/chromium/weblayer/NavigateParams.java
+++ b/weblayer/public/java/org/chromium/weblayer/NavigateParams.java
@@ -64,7 +64,10 @@
 
         /**
          * Disables auto-reload for this navigation if the network is down and comes back later.
-         *          Auto-reload is enabled by default.
+         * Auto-reload is enabled by default. This is deprecated as of 89, instead use
+         * {@link Navigation#disableNetworkErrorAutoReload} which works for both embedder-initiated
+         * navigations and also user-initiated navigations (such as back or forward). Auto-reload
+         * is disabled if either method is called.
          */
         @NonNull
         public Builder disableNetworkErrorAutoReload() {
diff --git a/weblayer/public/java/org/chromium/weblayer/Navigation.java b/weblayer/public/java/org/chromium/weblayer/Navigation.java
index 61e078f..4b11aa66 100644
--- a/weblayer/public/java/org/chromium/weblayer/Navigation.java
+++ b/weblayer/public/java/org/chromium/weblayer/Navigation.java
@@ -255,6 +255,28 @@
     }
 
     /**
+     * Disables auto-reload for this navigation if the network is down and comes back later.
+     * Auto-reload is enabled by default. This method may only be called from
+     * {@link NavigationCallback.onNavigationStarted}.
+     *
+     * @throws IllegalStateException If not called during start.
+     *
+     * @since 89
+     */
+    public void disableNetworkErrorAutoReload() {
+        ThreadCheck.ensureOnUiThread();
+        if (WebLayer.shouldPerformVersionChecks()
+                && WebLayer.getSupportedMajorVersionInternal() < 89) {
+            throw new UnsupportedOperationException();
+        }
+        try {
+            mNavigationImpl.disableNetworkErrorAutoReload();
+        } catch (RemoteException e) {
+            throw new APICallException(e);
+        }
+    }
+
+    /**
      * Sets the user-agent string that applies to the current navigation. This user-agent is not
      * sticky, it applies to this navigation only (and any redirects or resources that are loaded).
      * This method may only be called from {@link NavigationCallback.onNavigationStarted}.
diff --git a/weblayer/public/navigation.h b/weblayer/public/navigation.h
index 2f1f4d1..22729fc 100644
--- a/weblayer/public/navigation.h
+++ b/weblayer/public/navigation.h
@@ -119,6 +119,11 @@
   // SetRequestHeader().
   virtual void SetUserAgentString(const std::string& value) = 0;
 
+  // Disables auto-reload for this navigation if the network is down and comes
+  // back later. Auto-reload is enabled by default. This function may only be
+  // called from NavigationObserver::NavigationStarted().
+  virtual void DisableNetworkErrorAutoReload() = 0;
+
   // Whether the navigation was initiated by the page. Examples of
   // page-initiated navigations include:
   //  * <a> link click
diff --git a/weblayer/public/navigation_controller.h b/weblayer/public/navigation_controller.h
index 56691c87..9216002 100644
--- a/weblayer/public/navigation_controller.h
+++ b/weblayer/public/navigation_controller.h
@@ -19,7 +19,6 @@
   // |NavigationController::LoadURLParams|.
   struct NavigateParams {
     bool should_replace_current_entry = false;
-    bool disable_network_error_auto_reload = false;
     bool enable_auto_play = false;
   };
 
diff --git a/weblayer/weblayer_module.gni b/weblayer/weblayer_module.gni
index 98004e72..eeb2a50 100644
--- a/weblayer/weblayer_module.gni
+++ b/weblayer/weblayer_module.gni
@@ -5,5 +5,4 @@
 weblayer_module_desc = {
   name = "weblayer"
   android_manifest = "//weblayer/browser/java/AndroidManifest_monochrome.xml"
-  supports_isolated_split = true
 }