diff --git a/DEPS b/DEPS
index 5d615f1..7b3e1e2a 100644
--- a/DEPS
+++ b/DEPS
@@ -217,7 +217,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
-  'pdfium_revision': '1c8311278ae8bfbb4b4f86895899cabf48f67542',
+  'pdfium_revision': 'a15da341200e10f12262331da42fc81b3c4389ef',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling BoringSSL
   # and whatever else without interference from each other.
@@ -272,7 +272,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': 'd347b31c0e2ec5337a08f7bcf3a0a9bf7d83f459',
+  'devtools_frontend_revision': 'a4cfc762b19482760a1bb868d0c342e5f4b0dd59',
   # 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.
@@ -324,7 +324,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'dawn_revision': 'da1f66c8cee18677bf921181613732a9378e9864',
+  'dawn_revision': 'aabde6c88f91f450b5845c8a720356bc4382ed01',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -551,7 +551,7 @@
   },
 
   'src/ios/third_party/material_components_ios/src': {
-      'url': Var('chromium_git') + '/external/github.com/material-components/material-components-ios.git' + '@' + 'd9e294ab0002f2c8569e0dc0e74825e4284d13ee',
+      'url': Var('chromium_git') + '/external/github.com/material-components/material-components-ios.git' + '@' + 'ada54161e9302ec4d28a2cd27f3fae358bd49203',
       'condition': 'checkout_ios',
   },
 
@@ -1341,7 +1341,7 @@
       'packages': [
           {
               'package': 'fuchsia/third_party/aemu/linux-amd64',
-              'version': 'BisfFk67ojvPEsXt398o1XuIEAeGMM6KsrXCD1gY2RIC'
+              'version': '2_kHlztQ5lFU-IhCP021uv4v5Ms-aBhhsqtZ1V02tbIC'
           },
       ],
       'condition': 'host_os == "linux" and checkout_fuchsia',
@@ -1506,7 +1506,7 @@
   },
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + 'f42a94a7f50d697bdd1f94b584c94f86c5fff67a',
+    Var('webrtc_git') + '/src.git' + '@' + '99df1af2aed6a8d10bc62dc348f63d6c084c1949',
 
   'src/third_party/libgifcodec':
      Var('skia_git') + '/libgifcodec' + '@'+  Var('libgifcodec_revision'),
@@ -1578,7 +1578,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@01909e01660410274727b773dc3387c802e2cb8b',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@ca62d830fc00873996bf96aa67ccc51107d573ea',
     'condition': 'checkout_src_internal',
   },
 
@@ -1597,7 +1597,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/media_app/app',
-        'version': 'lncFJYEwVu27fQaQFf8XRHbjYztYJXFXPbFvQaiqj_0C',
+        'version': 'rTSxyOBRhg8Kuak4SK_kmsuxt14L_08GfLdW4Vm5OFMC',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index 046deea..0e191cc 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -129,10 +129,15 @@
     "accelerators/pre_target_accelerator_handler.h",
     "accelerators/spoken_feedback_toggler.cc",
     "accelerators/spoken_feedback_toggler.h",
+    "accelerometer/accelerometer_constants.h",
     "accelerometer/accelerometer_file_reader.cc",
     "accelerometer/accelerometer_file_reader.h",
+    "accelerometer/accelerometer_provider_mojo.cc",
+    "accelerometer/accelerometer_provider_mojo.h",
     "accelerometer/accelerometer_reader.cc",
     "accelerometer/accelerometer_reader.h",
+    "accelerometer/accelerometer_samples_observer.cc",
+    "accelerometer/accelerometer_samples_observer.h",
     "accelerometer/accelerometer_types.cc",
     "accelerometer/accelerometer_types.h",
     "accessibility/accessibility_controller_impl.cc",
@@ -1763,6 +1768,8 @@
     # TODO(https://crbug.com/644336): Make CrasAudioHandler Chrome or Ash only.
     "//chromeos/audio",
     "//chromeos/components/multidevice/logging",
+    "//chromeos/components/sensors:sensors",
+    "//chromeos/components/sensors/mojom",
     "//chromeos/constants",
     "//chromeos/dbus",
 
@@ -1920,6 +1927,8 @@
     "accelerators/accelerator_unittest.cc",
     "accelerators/magnifier_key_scroller_unittest.cc",
     "accelerators/spoken_feedback_toggler_unittest.cc",
+    "accelerometer/accelerometer_provider_mojo_unittest.cc",
+    "accelerometer/accelerometer_samples_observer_unittest.cc",
     "accessibility/accessibility_controller_unittest.cc",
     "accessibility/accessibility_focus_ring_controller_unittest.cc",
     "accessibility/accessibility_focus_ring_group_unittest.cc",
@@ -2366,6 +2375,9 @@
     "//chromeos/components/bloom/public/cpp",
     "//chromeos/components/phonehub:test_support",
     "//chromeos/components/quick_answers:quick_answers",
+    "//chromeos/components/sensors:sensors",
+    "//chromeos/components/sensors:test_support",
+    "//chromeos/components/sensors/mojom",
     "//chromeos/constants",
     "//chromeos/dbus:test_support",
     "//chromeos/dbus/audio",
diff --git a/ash/DEPS b/ash/DEPS
index a197460..425f400a 100644
--- a/ash/DEPS
+++ b/ash/DEPS
@@ -62,6 +62,7 @@
   "+chromeos/components/proximity_auth/public/mojom",
   "+chromeos/components/quick_answers",
   "+chromeos/components/security_token_pin",
+  "+chromeos/components/sensors",
   "+chromeos/constants",
   # TODO(https://crbug.com/940810): Eliminate this.
   "+chromeos/dbus/initialize_dbus_client.h",
diff --git a/ash/accelerometer/accelerometer_constants.h b/ash/accelerometer/accelerometer_constants.h
new file mode 100644
index 0000000..1005e80e
--- /dev/null
+++ b/ash/accelerometer/accelerometer_constants.h
@@ -0,0 +1,23 @@
+// 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 ASH_ACCELEROMETER_ACCELEROMETER_CONSTANTS_H_
+#define ASH_ACCELEROMETER_ACCELEROMETER_CONSTANTS_H_
+
+#include "ash/accelerometer/accelerometer_types.h"
+
+namespace ash {
+
+const char kAccelerometerChannels[][8] = {"accel_x", "accel_y", "accel_z"};
+
+// The number of axes for which there are accelerometer readings.
+constexpr uint32_t kNumberOfAxes = 3u;
+
+// The names of the accelerometers. Matches up with the enum AccelerometerSource
+// in ash/accelerometer/accelerometer_types.h.
+const char kLocationStrings[ACCELEROMETER_SOURCE_COUNT][5] = {"lid", "base"};
+
+}  // namespace ash
+
+#endif  // ASH_ACCELEROMETER_ACCELEROMETER_CONSTANTS_H_
diff --git a/ash/accelerometer/accelerometer_file_reader.cc b/ash/accelerometer/accelerometer_file_reader.cc
index 805f4818..e6cc865 100644
--- a/ash/accelerometer/accelerometer_file_reader.cc
+++ b/ash/accelerometer/accelerometer_file_reader.cc
@@ -10,6 +10,7 @@
 #include <string>
 #include <vector>
 
+#include "ash/accelerometer/accelerometer_constants.h"
 #include "ash/public/cpp/tablet_mode_observer.h"
 #include "ash/shell.h"
 #include "ash/wm/tablet_mode/tablet_mode_controller.h"
@@ -26,6 +27,8 @@
 #include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
 #include "base/system/sys_info.h"
+#include "base/task/task_traits.h"
+#include "base/task/thread_pool.h"
 #include "base/task_runner.h"
 #include "base/task_runner_util.h"
 #include "base/threading/platform_thread.h"
@@ -78,11 +81,6 @@
 constexpr char kAccelerometerScanIndexPathFormatString[] =
     "scan_elements/in_accel_%s_index";
 
-// The names of the accelerometers. Matches up with the enum AccelerometerSource
-// in ash/accelerometer/accelerometer_types.h.
-constexpr char kAccelerometerNames[ACCELEROMETER_SOURCE_COUNT][5] = {"lid",
-                                                                     "base"};
-
 // The axes on each accelerometer. The order was changed on kernel 3.18+.
 constexpr char kAccelerometerAxes[][2] = {"x", "y", "z"};
 constexpr char kLegacyAccelerometerAxes[][2] = {"y", "x", "z"};
@@ -93,9 +91,6 @@
 // The size of individual values.
 constexpr size_t kDataSize = 2;
 
-// The number of axes for which there are acceleration readings.
-constexpr int kNumberOfAxes = 3;
-
 // The size of data in one reading of the accelerometers.
 constexpr int kSizeOfReading = kDataSize * kNumberOfAxes;
 
@@ -150,9 +145,14 @@
     : observers_(
           new base::ObserverListThreadSafe<AccelerometerReader::Observer>()) {}
 
-void AccelerometerFileReader::PrepareAndInitialize(
-    scoped_refptr<base::SequencedTaskRunner> sequenced_task_runner) {
-  task_runner_ = sequenced_task_runner;
+void AccelerometerFileReader::PrepareAndInitialize() {
+  // AccelerometerReader is important for screen orientation so we need
+  // USER_VISIBLE priority.
+  // Use CONTINUE_ON_SHUTDOWN to avoid blocking shutdown since the datareading
+  // could get blocked on certain devices. See https://crbug.com/1023989.
+  task_runner_ = base::ThreadPool::CreateSequencedTaskRunner(
+      {base::MayBlock(), base::TaskPriority::USER_VISIBLE,
+       base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN});
 
   initialization_state_ = State::INITIALIZING;
 
@@ -287,8 +287,8 @@
               3 * static_cast<int>(configuration_.count)) {
         const char* axis = legacy_cross_accel ? kLegacyAccelerometerAxes[j]
                                               : kAccelerometerAxes[j];
-        LOG(ERROR) << "Field index for " << kAccelerometerNames[i] << " "
-                   << axis << " axis out of bounds.";
+        LOG(ERROR) << "Field index for " << kLocationStrings[i] << " " << axis
+                   << " axis out of bounds.";
         initialization_state_ = State::FAILED;
         return;
       }
@@ -438,12 +438,12 @@
     const base::FilePath& name,
     const std::string& location) {
   size_t config_index = 0;
-  for (; config_index < base::size(kAccelerometerNames); ++config_index) {
-    if (location == kAccelerometerNames[config_index])
+  for (; config_index < base::size(kLocationStrings); ++config_index) {
+    if (location == kLocationStrings[config_index])
       break;
   }
 
-  if (config_index >= base::size(kAccelerometerNames)) {
+  if (config_index >= base::size(kLocationStrings)) {
     LOG(ERROR) << "Unrecognized location: " << location << " for device "
                << name.MaybeAsASCII() << "\n";
     return false;
@@ -487,11 +487,11 @@
       base::FilePath(kAccelerometerDevicePath).Append(name.BaseName());
   // Read configuration of each accelerometer axis from each accelerometer from
   // /sys/bus/iio/devices/iio:deviceX/.
-  for (size_t i = 0; i < base::size(kAccelerometerNames); ++i) {
+  for (size_t i = 0; i < base::size(kLocationStrings); ++i) {
     configuration_.has[i] = false;
     // Read scale of accelerometer.
-    std::string accelerometer_scale_path = base::StringPrintf(
-        kLegacyScaleNameFormatString, kAccelerometerNames[i]);
+    std::string accelerometer_scale_path =
+        base::StringPrintf(kLegacyScaleNameFormatString, kLocationStrings[i]);
     // Read the scale for all axes.
     int scale_divisor = 0;
     if (!ReadFileToInt(iio_path.Append(accelerometer_scale_path.c_str()),
@@ -507,9 +507,9 @@
     configuration_.has[i] = true;
     for (size_t j = 0; j < base::size(kLegacyAccelerometerAxes); ++j) {
       configuration_.scale[i][j] = base::kMeanGravityFloat / scale_divisor;
-      std::string accelerometer_index_path = base::StringPrintf(
-          kLegacyAccelerometerScanIndexPathFormatString,
-          kLegacyAccelerometerAxes[j], kAccelerometerNames[i]);
+      std::string accelerometer_index_path =
+          base::StringPrintf(kLegacyAccelerometerScanIndexPathFormatString,
+                             kLegacyAccelerometerAxes[j], kLocationStrings[i]);
       if (!ReadFileToInt(iio_path.Append(accelerometer_index_path.c_str()),
                          &(configuration_.index[i][j]))) {
         configuration_.has[i] = false;
diff --git a/ash/accelerometer/accelerometer_file_reader.h b/ash/accelerometer/accelerometer_file_reader.h
index eb45be9..e8814c5 100644
--- a/ash/accelerometer/accelerometer_file_reader.h
+++ b/ash/accelerometer/accelerometer_file_reader.h
@@ -15,8 +15,6 @@
 
 namespace ash {
 
-enum class State { INITIALIZING, SUCCESS, FAILED };
-
 // 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.
@@ -28,8 +26,7 @@
   AccelerometerFileReader& operator=(const AccelerometerFileReader&) = delete;
 
   // AccelerometerProviderInterface:
-  void PrepareAndInitialize(
-      scoped_refptr<base::SequencedTaskRunner> sequenced_task_runner) override;
+  void PrepareAndInitialize() override;
   void AddObserver(AccelerometerReader::Observer* observer) override;
   void RemoveObserver(AccelerometerReader::Observer* observer) override;
   void StartListenToTabletModeController() override;
@@ -48,8 +45,9 @@
   // Tracks if accelerometer initialization is completed.
   void CheckInitStatus();
 
-  // With ChromeOS EC lid angle driver present, accelerometer read is cancelled
-  // in clamshell mode, and triggered when entering tablet mode.
+  // 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();
 
@@ -130,9 +128,6 @@
 
   void SetEmitEventsInternal(bool emit_events);
 
-  // The current initialization state of reader.
-  State initialization_state_ = State::INITIALIZING;
-
   // True if periodical accelerometer read is on.
   bool accelerometer_read_on_ = false;
 
diff --git a/ash/accelerometer/accelerometer_provider_mojo.cc b/ash/accelerometer/accelerometer_provider_mojo.cc
new file mode 100644
index 0000000..4b62136
--- /dev/null
+++ b/ash/accelerometer/accelerometer_provider_mojo.cc
@@ -0,0 +1,568 @@
+// 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 "ash/accelerometer/accelerometer_provider_mojo.h"
+
+#include <iterator>
+#include <utility>
+
+#include "ash/accelerometer/accelerometer_constants.h"
+#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"
+#include "base/strings/string_util.h"
+#include "base/time/time.h"
+#include "chromeos/components/sensors/sensor_hal_dispatcher.h"
+
+namespace ash {
+
+namespace {
+
+// Delay of the reconnection to Sensor Hal Dispatcher.
+constexpr base::TimeDelta kDelayReconnect =
+    base::TimeDelta::FromMilliseconds(1000);
+
+}  // namespace
+
+AccelerometerProviderMojo::AccelerometerProviderMojo()
+    : sensor_hal_client_(this),
+      observers_(
+          new base::ObserverListThreadSafe<AccelerometerReader::Observer>()),
+      update_(new AccelerometerUpdate()) {}
+
+void AccelerometerProviderMojo::PrepareAndInitialize() {
+  // 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)));
+}
+
+void AccelerometerProviderMojo::AddObserver(
+    AccelerometerReader::Observer* observer) {
+  DCHECK(task_runner_);
+
+  // Do not move this into AddObserverOnThread, as the current task runner will
+  // be used in ObserverListThreadSafe::Notify.
+  observers_->AddObserver(observer);
+
+  task_runner_->PostNonNestableTask(
+      FROM_HERE, base::BindOnce(&AccelerometerProviderMojo::AddObserverOnThread,
+                                base::Unretained(this), observer));
+}
+
+void AccelerometerProviderMojo::RemoveObserver(
+    AccelerometerReader::Observer* observer) {
+  DCHECK(task_runner_);
+
+  observers_->RemoveObserver(observer);
+}
+
+void AccelerometerProviderMojo::StartListenToTabletModeController() {
+  Shell::Get()->tablet_mode_controller()->AddObserver(this);
+}
+
+void AccelerometerProviderMojo::StopListenToTabletModeController() {
+  Shell::Get()->tablet_mode_controller()->RemoveObserver(this);
+}
+
+void AccelerometerProviderMojo::SetEmitEvents(bool emit_events) {
+  task_runner_->PostNonNestableTask(
+      FROM_HERE,
+      base::BindOnce(&AccelerometerProviderMojo::SetEmitEventsOnThread, this,
+                     emit_events));
+}
+
+void AccelerometerProviderMojo::OnTabletPhysicalStateChanged() {
+  // 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));
+}
+
+void AccelerometerProviderMojo::SetUpChannel(
+    mojo::PendingRemote<chromeos::sensors::mojom::SensorService>
+        pending_remote) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (sensor_service_remote_.is_bound()) {
+    LOG(ERROR) << "Ignoring the second Remote<SensorService>";
+    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_->GetDeviceIds(
+        chromeos::sensors::mojom::DeviceType::ANGL,
+        base::BindOnce(&AccelerometerProviderMojo::GetLidAngleIdsCallback,
+                       base::Unretained(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();
+}
+
+State AccelerometerProviderMojo::GetInitializationStateForTesting() const {
+  return initialization_state_;
+}
+
+AccelerometerProviderMojo::AccelerometerData::AccelerometerData() = default;
+AccelerometerProviderMojo::AccelerometerData::~AccelerometerData() = default;
+
+AccelerometerProviderMojo::~AccelerometerProviderMojo() = default;
+
+void AccelerometerProviderMojo::RegisterSensorClient() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  chromeos::sensors::SensorHalDispatcher::GetInstance()->RegisterClient(
+      sensor_hal_client_.BindNewPipeAndPassRemote());
+
+  sensor_hal_client_.set_disconnect_handler(
+      base::BindOnce(&AccelerometerProviderMojo::OnSensorHalClientFailure,
+                     base::Unretained(this)));
+}
+
+void AccelerometerProviderMojo::OnSensorHalClientFailure() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  LOG(ERROR) << "OnSensorHalClientFailure";
+
+  ResetSensorService();
+  sensor_hal_client_.reset();
+
+  task_runner_->PostDelayedTask(
+      FROM_HERE,
+      base::BindOnce(&AccelerometerProviderMojo::RegisterSensorClient,
+                     base::Unretained(this)),
+      kDelayReconnect);
+}
+
+void AccelerometerProviderMojo::OnSensorServiceDisconnect() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  LOG(ERROR) << "OnSensorServiceDisconnect";
+
+  ResetSensorService();
+}
+
+void AccelerometerProviderMojo::ResetSensorService() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  for (auto& accelerometer : accelerometers_) {
+    accelerometer.second.remote.reset();
+    accelerometer.second.samples_observer.reset();
+  }
+  sensor_service_remote_.reset();
+}
+
+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);
+
+  if (!lid_angle_ids.empty()) {
+    ec_lid_angle_driver_status_ = ECLidAngleDriverStatus::SUPPORTED;
+  } else {
+    ec_lid_angle_driver_status_ = ECLidAngleDriverStatus::NOT_SUPPORTED;
+    EnableAccelerometerReading();
+  }
+
+  if (pending_on_tablet_physical_state_changed_)
+    OnTabletPhysicalStateChanged();
+}
+
+void AccelerometerProviderMojo::GetAccelerometerIdsCallback(
+    const std::vector<int32_t>& accelerometer_ids) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  if (accelerometer_ids.empty()) {
+    FailedToInitialize();
+    return;
+  }
+
+  for (int32_t id : accelerometer_ids)
+    RegisterAccelerometerWithId(id);
+}
+
+void AccelerometerProviderMojo::RegisterAccelerometerWithId(int32_t id) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  auto& accelerometer = accelerometers_[id];
+
+  if (accelerometer.ignored) {
+    // Something went wrong in the previous initialization. Ignoring this accel.
+    return;
+  }
+
+  DCHECK(!accelerometer.remote.is_bound());
+  DCHECK(!accelerometer.samples_observer.get());
+
+  if (!sensor_service_remote_.is_bound()) {
+    // Something went wrong. Skipping here.
+    return;
+  }
+
+  accelerometer.remote.reset();
+
+  sensor_service_remote_->GetDevice(
+      id, accelerometer.remote.BindNewPipeAndPassReceiver());
+  accelerometer.remote.set_disconnect_handler(base::BindOnce(
+      &AccelerometerProviderMojo::OnAccelerometerRemoteDisconnect,
+      base::Unretained(this), id));
+
+  std::vector<std::string> attr_names;
+  if (!accelerometer.location.has_value())
+    attr_names.push_back(chromeos::sensors::mojom::kLocation);
+  if (!accelerometer.scale.has_value())
+    attr_names.push_back(chromeos::sensors::mojom::kScale);
+
+  if (!attr_names.empty()) {
+    accelerometer.remote->GetAttributes(
+        attr_names,
+        base::BindOnce(&AccelerometerProviderMojo::GetAttributesCallback,
+                       base::Unretained(this), id));
+  } else {
+    // Create the observer directly if the attributes have already been
+    // retrieved.
+    CreateAccelerometerSamplesObserver(id);
+  }
+}
+
+void AccelerometerProviderMojo::OnAccelerometerRemoteDisconnect(int32_t id) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  auto& accelerometer = accelerometers_[id];
+
+  LOG(ERROR) << "Accelerometer with id: " << id << " disconnected";
+  accelerometer.remote.reset();
+  accelerometer.samples_observer.reset();
+
+  if (!sensor_service_remote_.is_bound())
+    return;
+
+  RegisterAccelerometerWithId(id);
+}
+
+void AccelerometerProviderMojo::GetAttributesCallback(
+    int32_t id,
+    const std::vector<base::Optional<std::string>>& values) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  auto& accelerometer = accelerometers_[id];
+  DCHECK(accelerometer.remote.is_bound());
+
+  size_t index = 0;
+  if (!accelerometer.location.has_value()) {
+    if (index >= values.size()) {
+      LOG(ERROR) << "values doesn't contain location attribute.";
+      IgnoreAccelerometer(id);
+      return;
+    }
+
+    if (!values[index].has_value()) {
+      LOG(WARNING) << "No location attribute for accel with id: " << id;
+      IgnoreAccelerometer(id);
+      return;
+    }
+
+    auto* it = base::ranges::find(kLocationStrings, values[index]);
+    if (it == std::end(kLocationStrings)) {
+      LOG(WARNING) << "Unrecognized location: " << values[index].value()
+                   << " for device with id: ";
+      IgnoreAccelerometer(id);
+      return;
+    }
+
+    AccelerometerSource source = static_cast<AccelerometerSource>(
+        std::distance(std::begin(kLocationStrings), it));
+    accelerometer.location = source;
+
+    if (location_to_accelerometer_id_.find(source) !=
+        location_to_accelerometer_id_.end()) {
+      LOG(ERROR) << "Duplicated location source " << source
+                 << " of accel id: " << id
+                 << ", and accel id: " << location_to_accelerometer_id_[source];
+      FailedToInitialize();
+      return;
+    }
+
+    location_to_accelerometer_id_[source] = id;
+    ++index;
+  }
+
+  if (!accelerometer.scale.has_value()) {
+    if (index >= values.size()) {
+      LOG(ERROR) << "values doesn't contain scale attribute.";
+      IgnoreAccelerometer(id);
+      return;
+    }
+
+    double scale = 0.0;
+    if (!values[index].has_value() ||
+        !base::StringToDouble(values[index].value(), &scale)) {
+      LOG(ERROR) << "Invalid scale: " << values[index].value_or("")
+                 << ", for accel with id: " << id;
+      IgnoreAccelerometer(id);
+      return;
+    }
+    accelerometer.scale = scale;
+
+    ++index;
+  }
+
+  CheckInitialization();
+
+  CreateAccelerometerSamplesObserver(id);
+}
+
+void AccelerometerProviderMojo::IgnoreAccelerometer(int32_t id) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  auto& accelerometer = accelerometers_[id];
+
+  LOG(WARNING) << "Ignoring accel with id: " << id;
+  accelerometer.ignored = true;
+  accelerometer.remote.reset();
+
+  CheckInitialization();
+}
+
+void AccelerometerProviderMojo::CheckInitialization() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK_NE(ec_lid_angle_driver_status_, ECLidAngleDriverStatus::UNKNOWN);
+
+  if (initialization_state_ != State::INITIALIZING)
+    return;
+
+  bool has_accelerometer_lid = false;
+  for (const auto& accelerometer : accelerometers_) {
+    if (accelerometer.second.ignored) {
+      if (!accelerometer.second.location.has_value())
+        continue;
+
+      if (accelerometer.second.location == ACCELEROMETER_SOURCE_SCREEN ||
+          ec_lid_angle_driver_status_ ==
+              ECLidAngleDriverStatus::NOT_SUPPORTED) {
+        // This ignored accelerometer is essential.
+        FailedToInitialize();
+        return;
+      }
+
+      continue;
+    }
+
+    if (!accelerometer.second.scale.has_value() ||
+        !accelerometer.second.location.has_value())
+      return;
+
+    if (accelerometer.second.location == ACCELEROMETER_SOURCE_SCREEN)
+      has_accelerometer_lid = true;
+    else
+      has_accelerometer_base_ = true;
+  }
+
+  if (has_accelerometer_lid) {
+    if (!has_accelerometer_base_) {
+      LOG(WARNING)
+          << "Initialization succeeded without an accelerometer on the base";
+    }
+
+    initialization_state_ = State::SUCCESS;
+  } else {
+    FailedToInitialize();
+  }
+}
+
+void AccelerometerProviderMojo::CreateAccelerometerSamplesObserver(int32_t id) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  auto& accelerometer = accelerometers_[id];
+  DCHECK(accelerometer.remote.is_bound());
+  DCHECK(!accelerometer.ignored);
+  DCHECK(accelerometer.scale.has_value() && accelerometer.location.has_value());
+
+  if (accelerometer.location == ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD &&
+      ec_lid_angle_driver_status_ == 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.
+    return;
+  }
+
+  accelerometer.samples_observer =
+      std::make_unique<AccelerometerSamplesObserver>(
+          id, std::move(accelerometer.remote), accelerometer.scale.value(),
+          base::BindRepeating(
+              &AccelerometerProviderMojo::OnSampleUpdatedCallback,
+              base::Unretained(this)));
+
+  if (accelerometer_read_on_ || one_time_read_)
+    accelerometer.samples_observer->SetEnabled(true);
+}
+
+void AccelerometerProviderMojo::EnableAccelerometerReading() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK_NE(ec_lid_angle_driver_status_, ECLidAngleDriverStatus::UNKNOWN);
+  if (accelerometer_read_on_)
+    return;
+
+  accelerometer_read_on_ = true;
+  for (auto& accelerometer : accelerometers_) {
+    if (!accelerometer.second.samples_observer.get())
+      continue;
+
+    accelerometer.second.samples_observer->SetEnabled(true);
+  }
+}
+
+void AccelerometerProviderMojo::DisableAccelerometerReading() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK_EQ(ec_lid_angle_driver_status_, 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;
+
+    accelerometer.second.samples_observer->SetEnabled(false);
+  }
+}
+
+void AccelerometerProviderMojo::OnSampleUpdatedCallback(
+    int iio_device_id,
+    std::vector<float> sample) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK_EQ(sample.size(), kNumberOfAxes);
+
+  auto& accelerometer = accelerometers_[iio_device_id];
+  DCHECK(accelerometer.location.has_value());
+
+  bool need_two_accelerometers =
+      (ec_lid_angle_driver_status_ == ECLidAngleDriverStatus::NOT_SUPPORTED &&
+       has_accelerometer_base_);
+
+  if (!one_time_read_ && !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]);
+
+  if (need_two_accelerometers &&
+      (!update_->has(ACCELEROMETER_SOURCE_SCREEN) ||
+       !update_->has(ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD))) {
+    // Wait for the other accel to be updated.
+    return;
+  }
+
+  observers_->Notify(FROM_HERE,
+                     &AccelerometerReader::Observer::OnAccelerometerUpdated,
+                     update_);
+  update_ = new AccelerometerUpdate();
+
+  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::SetEmitEventsOnThread(bool emit_events) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  emit_events_ = emit_events;
+}
+
+void AccelerometerProviderMojo::FailedToInitialize() {
+  DCHECK_NE(initialization_state_, State::SUCCESS);
+
+  LOG(ERROR) << "Failed to initialize for accelerometer read.";
+  initialization_state_ = State::FAILED;
+
+  accelerometers_.clear();
+  ResetSensorService();
+  sensor_hal_client_.reset();
+}
+
+void AccelerometerProviderMojo::AddObserverOnThread(
+    AccelerometerReader::Observer* observer) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  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);
+  }
+}
+
+}  // namespace ash
diff --git a/ash/accelerometer/accelerometer_provider_mojo.h b/ash/accelerometer/accelerometer_provider_mojo.h
new file mode 100644
index 0000000..30212a7
--- /dev/null
+++ b/ash/accelerometer/accelerometer_provider_mojo.h
@@ -0,0 +1,178 @@
+// 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 ASH_ACCELEROMETER_ACCELEROMETER_PROVIDER_MOJO_H_
+#define ASH_ACCELEROMETER_ACCELEROMETER_PROVIDER_MOJO_H_
+
+#include <stdint.h>
+
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#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/memory/ref_counted.h"
+#include "base/observer_list_threadsafe.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"
+
+namespace ash {
+
+// Work that runs on the UI thread. As a sensor client, it communicates with IIO
+// Service, determines the accelerometers' configuration, and waits for the
+// accelerometers' samples. Upon receiving a sample, it will notify all
+// observers.
+class ASH_EXPORT AccelerometerProviderMojo
+    : public AccelerometerProviderInterface,
+      public TabletModeObserver,
+      public chromeos::sensors::mojom::SensorHalClient {
+ public:
+  AccelerometerProviderMojo();
+  AccelerometerProviderMojo(const AccelerometerProviderMojo&) = delete;
+  AccelerometerProviderMojo& operator=(const AccelerometerProviderMojo&) =
+      delete;
+
+  // 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;
+
+  // 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;
+
+ private:
+  struct AccelerometerData {
+    AccelerometerData();
+    ~AccelerometerData();
+
+    bool ignored = false;
+    // Temporarily stores the accelerometer remote, waiting for it's scale and
+    // location information. It'll be passed to |samples_observer| as an
+    // argument after all information is collected.
+    mojo::Remote<chromeos::sensors::mojom::SensorDevice> remote;
+    base::Optional<AccelerometerSource> location;
+    base::Optional<float> scale;
+    std::unique_ptr<AccelerometerSamplesObserver> samples_observer;
+  };
+
+  ~AccelerometerProviderMojo() override;
+
+  // Registers chromeos::sensors::mojom::SensorHalClient to Sensor Hal
+  // Dispatcher, waiting for the Mojo connection to IIO Service.
+  void RegisterSensorClient();
+  void OnSensorHalClientFailure();
+
+  void OnSensorServiceDisconnect();
+  void ResetSensorService();
+
+  // Callback of GetDeviceIds(ANGL), containing the lid-angle device's id if it
+  // exists.
+  void GetLidAngleIdsCallback(const std::vector<int32_t>& lid_angle_ids);
+
+  // Callback of GetDeviceIds(ACCEL), containing all iio_device_ids of
+  // accelerometers.
+  void GetAccelerometerIdsCallback(
+      const std::vector<int32_t>& accelerometer_ids);
+
+  // Creates the Mojo channel for the accelerometer, and requests the
+  // accelerometer's required attributes before creating the
+  // AccelerometerSamplesObserver of it.
+  void RegisterAccelerometerWithId(int32_t id);
+  void OnAccelerometerRemoteDisconnect(int32_t id);
+  void GetAttributesCallback(
+      int32_t id,
+      const std::vector<base::Optional<std::string>>& values);
+
+  // Ignores the accelerometer as the attributes are not expected.
+  void IgnoreAccelerometer(int32_t id);
+  // Checks and sets |initialization_state_| if all information is retrieved.
+  void CheckInitialization();
+
+  // Creates the AccelerometerSamplesObserver for the accelerometer with |id|.
+  void CreateAccelerometerSamplesObserver(int32_t id);
+
+  // Controls accelerometer reading.
+  void EnableAccelerometerReading();
+  void DisableAccelerometerReading();
+
+  // Called by |observers_|, containing a sample of the accelerometer.
+  void OnSampleUpdatedCallback(int iio_device_id, std::vector<float> sample);
+
+  void SetEmitEventsOnThread(bool emit_events);
+
+  // Sets FAILED to |initialization_state_| due to an error.
+  void FailedToInitialize();
+
+  void AddObserverOnThread(AccelerometerReader::Observer* observer);
+
+  // 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_;
+
+  // The Mojo channel to query and request for devices.
+  mojo::Remote<chromeos::sensors::mojom::SensorService> sensor_service_remote_;
+
+  // The existence of the accelerometer on the base.
+  bool has_accelerometer_base_ = false;
+
+  // First is the accelerometer's iio device id, second is it's data, mojo
+  // remote and samples observer.
+  std::map<int32_t, AccelerometerData> accelerometers_;
+
+  // First is the location index, second is the id of the accelerometer being
+  // used in this reader.
+  std::map<AccelerometerSource, int32_t> location_to_accelerometer_id_;
+
+  // The flag to delay |OnTabletPhysicalStateChanged| until
+  // |ec_lid_angle_driver_status_| is set.
+  bool pending_on_tablet_physical_state_changed_ = false;
+
+  // One time read upon |AddObserverOnThread|.
+  // 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.
+  scoped_refptr<base::ObserverListThreadSafe<AccelerometerReader::Observer>>
+      observers_;
+
+  // The last seen accelerometer data.
+  scoped_refptr<AccelerometerUpdate> update_;
+
+  SEQUENCE_CHECKER(sequence_checker_);
+};
+
+}  // namespace ash
+
+#endif  // ASH_ACCELEROMETER_ACCELEROMETER_PROVIDER_MOJO_H_
diff --git a/ash/accelerometer/accelerometer_provider_mojo_unittest.cc b/ash/accelerometer/accelerometer_provider_mojo_unittest.cc
new file mode 100644
index 0000000..6109fb4
--- /dev/null
+++ b/ash/accelerometer/accelerometer_provider_mojo_unittest.cc
@@ -0,0 +1,274 @@
+// 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 "ash/accelerometer/accelerometer_provider_mojo.h"
+
+#include <memory>
+#include <utility>
+
+#include "ash/accelerometer/accelerometer_constants.h"
+#include "ash/accelerometer/accelerometer_reader.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/run_loop.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/test/task_environment.h"
+#include "chromeos/components/sensors/fake_sensor_device.h"
+#include "chromeos/components/sensors/fake_sensor_hal_server.h"
+#include "chromeos/components/sensors/sensor_hal_dispatcher.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace ash {
+
+namespace {
+
+constexpr double kFakeScaleValue = 10.0;
+
+constexpr int kFakeLidAccelerometerId = 1;
+constexpr int kFakeBaseAccelerometerId = 2;
+constexpr int kFakeLidAngleId = 3;
+
+constexpr int64_t kFakeSampleData[] = {1, 2, 3};
+
+class FakeObserver : public AccelerometerReader::Observer {
+ public:
+  void OnAccelerometerUpdated(
+      scoped_refptr<const AccelerometerUpdate> update) override {
+    if (!update.get())
+      return;
+
+    for (uint32_t index = 0; index < ACCELEROMETER_SOURCE_COUNT; ++index) {
+      auto source = static_cast<AccelerometerSource>(index);
+      if (!update->has(source))
+        continue;
+
+      EXPECT_EQ(update->get(source).x, kFakeSampleData[0] * kFakeScaleValue);
+      EXPECT_EQ(update->get(source).y, kFakeSampleData[1] * kFakeScaleValue);
+      EXPECT_EQ(update->get(source).z, kFakeSampleData[2] * kFakeScaleValue);
+    }
+
+    update_ = update;
+  }
+
+  scoped_refptr<const AccelerometerUpdate> update_;
+};
+
+class AccelerometerProviderMojoTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    sensor_hal_server_ =
+        std::make_unique<chromeos::sensors::FakeSensorHalServer>();
+    provider_ = new AccelerometerProviderMojo();
+
+    chromeos::sensors::SensorHalDispatcher::Initialize();
+    provider_->PrepareAndInitialize();
+    provider_->AddObserver(&observer_);
+
+    AddDevice(kFakeLidAccelerometerId,
+              chromeos::sensors::mojom::DeviceType::ACCEL,
+              base::NumberToString(kFakeScaleValue),
+              kLocationStrings[ACCELEROMETER_SOURCE_SCREEN]);
+  }
+
+  void TearDown() override {
+    chromeos::sensors::SensorHalDispatcher::Shutdown();
+  }
+
+  void AddDevice(int32_t iio_device_id,
+                 chromeos::sensors::mojom::DeviceType type,
+                 base::Optional<std::string> scale,
+                 base::Optional<std::string> location) {
+    std::set<chromeos::sensors::mojom::DeviceType> types;
+    types.emplace(type);
+
+    std::vector<chromeos::sensors::FakeSensorDevice::ChannelData> channels_data;
+    if (type == chromeos::sensors::mojom::DeviceType::ACCEL) {
+      channels_data.resize(kNumberOfAxes);
+      for (uint32_t i = 0; i < kNumberOfAxes; ++i) {
+        channels_data[i].id = kAccelerometerChannels[i];
+        channels_data[i].sample_data = kFakeSampleData[i];
+      }
+    }
+
+    std::unique_ptr<chromeos::sensors::FakeSensorDevice> sensor_device(
+        new chromeos::sensors::FakeSensorDevice(std::move(channels_data)));
+    if (scale.has_value())
+      sensor_device->SetAttribute(chromeos::sensors::mojom::kScale,
+                                  scale.value());
+    if (location.has_value())
+      sensor_device->SetAttribute(chromeos::sensors::mojom::kLocation,
+                                  location.value());
+
+    sensor_hal_server_->GetSensorService()->SetDevice(
+        iio_device_id, std::move(types), std::move(sensor_device));
+  }
+
+  FakeObserver observer_;
+  std::unique_ptr<chromeos::sensors::FakeSensorHalServer> sensor_hal_server_;
+  scoped_refptr<AccelerometerProviderMojo> provider_;
+
+  base::test::SingleThreadTaskEnvironment task_environment;
+};
+
+TEST_F(AccelerometerProviderMojoTest, CheckNoScale) {
+  AddDevice(kFakeBaseAccelerometerId,
+            chromeos::sensors::mojom::DeviceType::ACCEL, base::nullopt,
+            kLocationStrings[ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD]);
+
+  chromeos::sensors::SensorHalDispatcher::GetInstance()->RegisterServer(
+      sensor_hal_server_->PassRemote());
+
+  // Wait until initialization failed.
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_EQ(provider_->GetInitializationStateForTesting(), State::FAILED);
+}
+
+TEST_F(AccelerometerProviderMojoTest, CheckNoLocation) {
+  AddDevice(kFakeBaseAccelerometerId,
+            chromeos::sensors::mojom::DeviceType::ACCEL,
+            base::NumberToString(kFakeScaleValue), base::nullopt);
+
+  chromeos::sensors::SensorHalDispatcher::GetInstance()->RegisterServer(
+      sensor_hal_server_->PassRemote());
+
+  // Wait until initialization failed.
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_EQ(provider_->GetInitializationStateForTesting(), State::SUCCESS);
+}
+
+TEST_F(AccelerometerProviderMojoTest, GetSamplesOfOneAccel) {
+  chromeos::sensors::SensorHalDispatcher::GetInstance()->RegisterServer(
+      sensor_hal_server_->PassRemote());
+
+  // Wait until a sample is received.
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_EQ(provider_->GetInitializationStateForTesting(), State::SUCCESS);
+
+  EXPECT_TRUE(observer_.update_.get());
+  EXPECT_TRUE(observer_.update_->has(ACCELEROMETER_SOURCE_SCREEN));
+  EXPECT_FALSE(observer_.update_->has(ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD));
+
+  observer_.update_.reset();
+
+  // Simulate a disconnection of the accelerometer's mojo channel in IIO
+  // Service.
+  AddDevice(kFakeLidAccelerometerId,
+            chromeos::sensors::mojom::DeviceType::ACCEL,
+            base::NumberToString(kFakeScaleValue),
+            kLocationStrings[ACCELEROMETER_SOURCE_SCREEN]);
+
+  // Wait until the accelerometer's mojo channel is re-established and a sample
+  // is received.
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_TRUE(observer_.update_.get());
+  EXPECT_TRUE(observer_.update_->has(ACCELEROMETER_SOURCE_SCREEN));
+  EXPECT_FALSE(observer_.update_->has(ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD));
+}
+
+TEST_F(AccelerometerProviderMojoTest, GetSamplesWithNoLidAngle) {
+  AddDevice(kFakeBaseAccelerometerId,
+            chromeos::sensors::mojom::DeviceType::ACCEL,
+            base::NumberToString(kFakeScaleValue),
+            kLocationStrings[ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD]);
+
+  chromeos::sensors::SensorHalDispatcher::GetInstance()->RegisterServer(
+      sensor_hal_server_->PassRemote());
+
+  // Wait until samples are received.
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_EQ(provider_->GetInitializationStateForTesting(), State::SUCCESS);
+
+  EXPECT_TRUE(observer_.update_.get());
+  EXPECT_TRUE(observer_.update_->has(ACCELEROMETER_SOURCE_SCREEN));
+  EXPECT_TRUE(observer_.update_->has(ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD));
+
+  observer_.update_.reset();
+
+  // Simulate a disconnection of the accelerometer's mojo channel in IIO
+  // Service.
+  AddDevice(kFakeLidAccelerometerId,
+            chromeos::sensors::mojom::DeviceType::ACCEL,
+            base::NumberToString(kFakeScaleValue),
+            kLocationStrings[ACCELEROMETER_SOURCE_SCREEN]);
+
+  // Wait until the accelerometer's mojo channel is re-established and a sample
+  // is received.
+  base::RunLoop().RunUntilIdle();
+
+  // Get the second sample from only one accelerometer.
+  EXPECT_FALSE(observer_.update_.get());
+
+  // Simulate a disconnection of the other accelerometer's mojo channel in IIO
+  // Service.
+  AddDevice(kFakeBaseAccelerometerId,
+            chromeos::sensors::mojom::DeviceType::ACCEL,
+            base::NumberToString(kFakeScaleValue),
+            kLocationStrings[ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD]);
+
+  // Wait until the other accelerometer's mojo channel is re-established and a
+  // sample is received.
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_TRUE(observer_.update_.get());
+  EXPECT_TRUE(observer_.update_->has(ACCELEROMETER_SOURCE_SCREEN));
+  EXPECT_TRUE(observer_.update_->has(ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD));
+}
+
+TEST_F(AccelerometerProviderMojoTest, GetSamplesWithLidAngle) {
+  AddDevice(kFakeBaseAccelerometerId,
+            chromeos::sensors::mojom::DeviceType::ACCEL,
+            base::NumberToString(kFakeScaleValue),
+            kLocationStrings[ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD]);
+  AddDevice(kFakeLidAngleId, chromeos::sensors::mojom::DeviceType::ANGL,
+            base::nullopt, base::nullopt);
+
+  chromeos::sensors::SensorHalDispatcher::GetInstance()->RegisterServer(
+      sensor_hal_server_->PassRemote());
+
+  // Wait until all setups are finished and the one time read is done.
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_EQ(provider_->GetInitializationStateForTesting(), State::SUCCESS);
+  EXPECT_TRUE(observer_.update_.get());
+  EXPECT_TRUE(observer_.update_->has(ACCELEROMETER_SOURCE_SCREEN));
+  EXPECT_FALSE(observer_.update_->has(ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD));
+
+  observer_.update_.reset();
+
+  provider_->TriggerRead();
+
+  // Wait until samples are received.
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_TRUE(observer_.update_.get());
+  EXPECT_TRUE(observer_.update_->has(ACCELEROMETER_SOURCE_SCREEN));
+  EXPECT_FALSE(observer_.update_->has(ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD));
+
+  observer_.update_.reset();
+
+  // Simulate a disconnection of IIO Service.
+  sensor_hal_server_->GetSensorService()->OnServiceDisconnect();
+  sensor_hal_server_->OnServerDisconnect();
+
+  // Wait until the disconnect arrives at the dispatcher.
+  base::RunLoop().RunUntilIdle();
+
+  chromeos::sensors::SensorHalDispatcher::GetInstance()->RegisterServer(
+      sensor_hal_server_->PassRemote());
+
+  // Wait until samples are received.
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_TRUE(observer_.update_.get());
+  EXPECT_TRUE(observer_.update_->has(ACCELEROMETER_SOURCE_SCREEN));
+  EXPECT_FALSE(observer_.update_->has(ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD));
+}
+
+}  // namespace
+
+}  // namespace ash
diff --git a/ash/accelerometer/accelerometer_reader.cc b/ash/accelerometer/accelerometer_reader.cc
index 0d0e9f6..c26ef06 100644
--- a/ash/accelerometer/accelerometer_reader.cc
+++ b/ash/accelerometer/accelerometer_reader.cc
@@ -4,22 +4,31 @@
 
 #include "ash/accelerometer/accelerometer_reader.h"
 
+#include <grp.h>
+
 #include "ash/accelerometer/accelerometer_file_reader.h"
+#include "ash/accelerometer/accelerometer_provider_mojo.h"
 #include "base/memory/singleton.h"
+#include "base/posix/eintr_wrapper.h"
 #include "base/sequenced_task_runner.h"
+#include "base/threading/sequenced_task_runner_handle.h"
 
 namespace ash {
 
+namespace {
+
+// Group name of IIO Service, used to check if IIO Service exists.
+constexpr char kIioServiceGroupName[] = "iioservice";
+
+}  // namespace
+
 // static
 AccelerometerReader* AccelerometerReader::GetInstance() {
   return base::Singleton<AccelerometerReader>::get();
 }
 
-void AccelerometerReader::Initialize(
-    scoped_refptr<base::SequencedTaskRunner> sequenced_task_runner) {
-  DCHECK(sequenced_task_runner.get());
-
-  accelerometer_provider_->PrepareAndInitialize(sequenced_task_runner);
+void AccelerometerReader::Initialize() {
+  accelerometer_provider_->PrepareAndInitialize();
 }
 
 void AccelerometerReader::AddObserver(Observer* observer) {
@@ -52,8 +61,19 @@
       ec_lid_angle_driver_status);
 }
 
-AccelerometerReader::AccelerometerReader()
-    : accelerometer_provider_(new AccelerometerFileReader()) {}
+AccelerometerReader::AccelerometerReader() {
+  char buf[1024];
+  struct group result;
+  struct group* resultp;
+
+  if (HANDLE_EINTR(getgrnam_r(kIioServiceGroupName, &result, buf, sizeof(buf),
+                              &resultp)) < 0 ||
+      !resultp) {
+    accelerometer_provider_ = new AccelerometerFileReader();
+  } else {
+    accelerometer_provider_ = new AccelerometerProviderMojo();
+  }
+}
 
 AccelerometerReader::~AccelerometerReader() = default;
 
diff --git a/ash/accelerometer/accelerometer_reader.h b/ash/accelerometer/accelerometer_reader.h
index e1630a2..d2b5170 100644
--- a/ash/accelerometer/accelerometer_reader.h
+++ b/ash/accelerometer/accelerometer_reader.h
@@ -19,6 +19,8 @@
 
 namespace ash {
 
+enum class State { INITIALIZING, SUCCESS, FAILED };
+
 enum class ECLidAngleDriverStatus { UNKNOWN, SUPPORTED, NOT_SUPPORTED };
 
 class AccelerometerProviderInterface;
@@ -39,8 +41,7 @@
 
   static AccelerometerReader* GetInstance();
 
-  void Initialize(
-      scoped_refptr<base::SequencedTaskRunner> sequenced_task_runner);
+  void Initialize();
 
   // Add/Remove observers.
   void AddObserver(Observer* observer);
@@ -79,10 +80,8 @@
 class AccelerometerProviderInterface
     : public base::RefCountedThreadSafe<AccelerometerProviderInterface> {
  public:
-  // Prepare and start async initialization. SetSensorClient function
-  // contains actual code for initialization.
-  virtual void PrepareAndInitialize(
-      scoped_refptr<base::SequencedTaskRunner> sequenced_task_runner) = 0;
+  // Prepare and start async initialization.
+  virtual void PrepareAndInitialize() = 0;
 
   // Add/Remove observers.
   virtual void AddObserver(AccelerometerReader::Observer* observer) = 0;
@@ -104,6 +103,9 @@
  protected:
   virtual ~AccelerometerProviderInterface() = default;
 
+  // The current initialization state of reader.
+  State initialization_state_ = State::INITIALIZING;
+
   // State of ChromeOS EC lid angle driver, if SUPPORTED, it means EC can handle
   // lid angle calculation.
   ECLidAngleDriverStatus ec_lid_angle_driver_status_ =
diff --git a/ash/accelerometer/accelerometer_samples_observer.cc b/ash/accelerometer/accelerometer_samples_observer.cc
new file mode 100644
index 0000000..e383bf8
--- /dev/null
+++ b/ash/accelerometer/accelerometer_samples_observer.cc
@@ -0,0 +1,249 @@
+// 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 "ash/accelerometer/accelerometer_samples_observer.h"
+
+#include <utility>
+
+#include "base/bind.h"
+
+namespace ash {
+
+namespace {
+
+constexpr int kTimeoutToleranceInMilliseconds = 500;
+constexpr double kReadFrequencyInHz = 10.0;
+
+}  // namespace
+
+AccelerometerSamplesObserver::AccelerometerSamplesObserver(
+    int iio_device_id,
+    mojo::Remote<chromeos::sensors::mojom::SensorDevice> sensor_device_remote,
+    float scale,
+    OnSampleUpdatedCallback on_sample_updated_callback)
+    : iio_device_id_(iio_device_id),
+      sensor_device_remote_(std::move(sensor_device_remote)),
+      scale_(scale),
+      on_sample_updated_callback_(std::move(on_sample_updated_callback)) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(sensor_device_remote_.is_bound());
+
+  sensor_device_remote_->GetAllChannelIds(
+      base::BindOnce(&AccelerometerSamplesObserver::GetAllChannelIdsCallback,
+                     weak_factory_.GetWeakPtr()));
+}
+
+AccelerometerSamplesObserver::~AccelerometerSamplesObserver() = default;
+
+void AccelerometerSamplesObserver::SetEnabled(bool enabled) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  if (enabled_ == enabled)
+    return;
+
+  enabled_ = enabled;
+
+  UpdateSensorDeviceFrequency();
+}
+
+void AccelerometerSamplesObserver::OnSampleUpdated(
+    const base::flat_map<int32_t, int64_t>& sample) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  if (sample.size() != kNumberOfAxes) {
+    LOG(ERROR) << "Invalid sample with size: " << sample.size();
+    OnErrorOccurred(chromeos::sensors::mojom::ObserverErrorType::READ_FAILED);
+    return;
+  }
+
+  std::vector<float> output_sample;
+  for (size_t axes = 0; axes < kNumberOfAxes; ++axes) {
+    auto it = sample.find(channel_indices_[axes]);
+    if (it == sample.end()) {
+      LOG(ERROR) << "Missing channel: " << kAccelerometerChannels[axes]
+                 << " in sample";
+      OnErrorOccurred(chromeos::sensors::mojom::ObserverErrorType::READ_FAILED);
+      return;
+    }
+
+    output_sample.push_back(it->second * scale_);
+  }
+
+  on_sample_updated_callback_.Run(iio_device_id_, output_sample);
+}
+
+void AccelerometerSamplesObserver::OnErrorOccurred(
+    chromeos::sensors::mojom::ObserverErrorType type) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  switch (type) {
+    case chromeos::sensors::mojom::ObserverErrorType::ALREADY_STARTED:
+      LOG(ERROR) << "Device " << iio_device_id_
+                 << ": Another observer has already started to read samples";
+      Reset();
+      break;
+
+    case chromeos::sensors::mojom::ObserverErrorType::FREQUENCY_INVALID:
+      if (!enabled_)  // It's normal if this observer is not enabled
+        break;
+
+      LOG(ERROR) << "Device " << iio_device_id_
+                 << ": Observer started with an invalid frequency";
+      UpdateSensorDeviceFrequency();
+
+      break;
+
+    case chromeos::sensors::mojom::ObserverErrorType::NO_ENABLED_CHANNELS:
+      LOG(ERROR) << "Device " << iio_device_id_
+                 << ": Observer started with no channels enabled";
+      if (sensor_device_remote_.is_bound()) {
+        sensor_device_remote_->SetChannelsEnabled(
+            std::vector<int32_t>(channel_indices_,
+                                 channel_indices_ + kNumberOfAxes),
+            /*enable=*/true,
+            base::BindOnce(
+                &AccelerometerSamplesObserver::SetChannelsEnabledCallback,
+                weak_factory_.GetWeakPtr()));
+      }
+
+      break;
+
+    case chromeos::sensors::mojom::ObserverErrorType::SET_FREQUENCY_IO_FAILED:
+      LOG(ERROR) << "Device " << iio_device_id_
+                 << ": Failed to set frequency to the physical device";
+      break;
+
+    case chromeos::sensors::mojom::ObserverErrorType::GET_FD_FAILED:
+      LOG(ERROR) << "Device " << iio_device_id_
+                 << ": Failed to get the device's fd to poll on";
+      break;
+
+    case chromeos::sensors::mojom::ObserverErrorType::READ_FAILED:
+      LOG(ERROR) << "Device " << iio_device_id_ << ": Failed to read a sample";
+      break;
+
+    case chromeos::sensors::mojom::ObserverErrorType::READ_TIMEOUT:
+      LOG(ERROR) << "Device " << iio_device_id_ << ": A read timed out";
+      break;
+
+    default:
+      LOG(ERROR) << "Device " << iio_device_id_ << ": error "
+                 << static_cast<int>(type);
+      break;
+  }
+}
+
+void AccelerometerSamplesObserver::Reset() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  LOG(ERROR) << "Resetting AccelerometerSamplesObserver: " << iio_device_id_;
+  receiver_.reset();
+  sensor_device_remote_.reset();
+}
+
+void AccelerometerSamplesObserver::GetAllChannelIdsCallback(
+    const std::vector<std::string>& iio_channel_ids) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(sensor_device_remote_.is_bound());
+
+  iio_channel_ids_ = std::move(iio_channel_ids);
+
+  for (size_t axis = 0; axis < kNumberOfAxes; ++axis) {
+    bool found = false;
+    for (size_t channel_index = 0; channel_index < iio_channel_ids_.size();
+         ++channel_index) {
+      if (iio_channel_ids_[channel_index].compare(
+              kAccelerometerChannels[axis]) == 0) {
+        found = true;
+        channel_indices_[axis] = channel_index;
+        break;
+      }
+    }
+
+    if (!found) {
+      LOG(ERROR) << "Missing channel: " << kAccelerometerChannels[axis];
+      Reset();
+      return;
+    }
+  }
+
+  sensor_device_remote_->SetChannelsEnabled(
+      std::vector<int32_t>(channel_indices_, channel_indices_ + kNumberOfAxes),
+      /*enable=*/true,
+      base::BindOnce(&AccelerometerSamplesObserver::SetChannelsEnabledCallback,
+                     weak_factory_.GetWeakPtr()));
+
+  StartReading();
+}
+
+void AccelerometerSamplesObserver::StartReading() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(sensor_device_remote_.is_bound());
+
+  sensor_device_remote_->SetTimeout(kTimeoutToleranceInMilliseconds);
+
+  sensor_device_remote_->StartReadingSamples(GetPendingRemote());
+}
+
+void AccelerometerSamplesObserver::UpdateSensorDeviceFrequency() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  if (!sensor_device_remote_.is_bound())
+    return;
+
+  sensor_device_remote_->SetFrequency(
+      enabled_ ? kReadFrequencyInHz : 0.0,
+      base::BindOnce(&AccelerometerSamplesObserver::SetFrequencyCallback,
+                     weak_factory_.GetWeakPtr()));
+}
+
+mojo::PendingRemote<chromeos::sensors::mojom::SensorDeviceSamplesObserver>
+AccelerometerSamplesObserver::GetPendingRemote() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  auto pending_remote = receiver_.BindNewPipeAndPassRemote();
+
+  receiver_.set_disconnect_handler(base::BindOnce(
+      &AccelerometerSamplesObserver::ObserverConnectionErrorCallback,
+      weak_factory_.GetWeakPtr()));
+  return pending_remote;
+}
+
+void AccelerometerSamplesObserver::ObserverConnectionErrorCallback() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  LOG(ERROR) << "Observer connection error";
+  receiver_.reset();
+
+  if (sensor_device_remote_.is_bound())
+    StartReading();
+}
+
+void AccelerometerSamplesObserver::SetFrequencyCallback(
+    double result_frequency) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  if ((result_frequency > 0.0 && enabled_) ||
+      (result_frequency == 0.0 && !enabled_)) {
+    return;
+  }
+
+  LOG(ERROR) << "Failed to set frequency: " << result_frequency
+             << " with the samples observer enabled: "
+             << (enabled_ ? "true" : "false");
+  Reset();
+}
+
+void AccelerometerSamplesObserver::SetChannelsEnabledCallback(
+    const std::vector<int32_t>& failed_indices) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  for (int32_t index : failed_indices)
+    LOG(ERROR) << "Failed to enable " << iio_channel_ids_[index];
+
+  if (!failed_indices.empty())
+    Reset();
+}
+
+}  // namespace ash
diff --git a/ash/accelerometer/accelerometer_samples_observer.h b/ash/accelerometer/accelerometer_samples_observer.h
new file mode 100644
index 0000000..6f84bf27
--- /dev/null
+++ b/ash/accelerometer/accelerometer_samples_observer.h
@@ -0,0 +1,100 @@
+// 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 ASH_ACCELEROMETER_ACCELEROMETER_SAMPLES_OBSERVER_H_
+#define ASH_ACCELEROMETER_ACCELEROMETER_SAMPLES_OBSERVER_H_
+
+#include <stdint.h>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "ash/accelerometer/accelerometer_constants.h"
+#include "ash/ash_export.h"
+#include "base/sequence_checker.h"
+#include "base/sequenced_task_runner.h"
+#include "chromeos/components/sensors/sensor_hal_dispatcher.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/remote.h"
+
+namespace ash {
+
+// A SamplesObserver for an accelerometer device. When a sample is updated from
+// IIO Service, it's sent to the AccelerometerProviderMojo via the callback
+// |on_sample_udpated_callback_| registered in the constructor.
+// AccelerometerSamplesObserver should only be used on the UI thread.
+class ASH_EXPORT AccelerometerSamplesObserver
+    : public chromeos::sensors::mojom::SensorDeviceSamplesObserver {
+ public:
+  using OnSampleUpdatedCallback =
+      base::RepeatingCallback<void(int iio_device_id,
+                                   std::vector<float> sample)>;
+
+  AccelerometerSamplesObserver(
+      int iio_device_id,
+      mojo::Remote<chromeos::sensors::mojom::SensorDevice> sensor_device_remote,
+      float scale,
+      OnSampleUpdatedCallback on_sample_updated_callback);
+  AccelerometerSamplesObserver(const AccelerometerSamplesObserver&) = delete;
+  AccelerometerSamplesObserver& operator=(const AccelerometerSamplesObserver&) =
+      delete;
+  ~AccelerometerSamplesObserver() override;
+
+  // Sets the observer |enabled| by setting the frequency to iioservice.
+  // Should be called on |task_runner_|.
+  void SetEnabled(bool enabled);
+
+  // chromeos::sensors::mojom::SensorDeviceSamplesObserver overrides:
+  void OnSampleUpdated(const base::flat_map<int32_t, int64_t>& sample) override;
+  void OnErrorOccurred(
+      chromeos::sensors::mojom::ObserverErrorType type) override;
+
+ private:
+  void Reset();
+
+  void GetAllChannelIdsCallback(
+      const std::vector<std::string>& iio_channel_ids);
+  void StartReading();
+
+  // Update this sensor device's frequency to kReadFrequencyInHz if |enabled_|
+  // is true, and to 0 if |enabled_| is false.
+  void UpdateSensorDeviceFrequency();
+
+  mojo::PendingRemote<chromeos::sensors::mojom::SensorDeviceSamplesObserver>
+  GetPendingRemote();
+
+  void ObserverConnectionErrorCallback();
+  void SetFrequencyCallback(double result_frequency);
+  void SetChannelsEnabledCallback(const std::vector<int32_t>& failed_indices);
+
+  int iio_device_id_;
+  mojo::Remote<chromeos::sensors::mojom::SensorDevice> sensor_device_remote_;
+
+  double scale_;
+
+  // Callback to send samples to the owner of this class.
+  OnSampleUpdatedCallback on_sample_updated_callback_;
+
+  // Boolean to indicate if this accelerometer should set a valid frequency and
+  // keep reading samples.
+  bool enabled_ = false;
+
+  // The list of channel ids retrieved from iioservice. Use channels' indices
+  // in this list to identify them.
+  std::vector<std::string> iio_channel_ids_;
+  // Channel indices (of accel_x, accel_y, and accel_z respectively) to
+  // enable.
+  int32_t channel_indices_[kNumberOfAxes];
+
+  mojo::Receiver<chromeos::sensors::mojom::SensorDeviceSamplesObserver>
+      receiver_{this};
+
+  SEQUENCE_CHECKER(sequence_checker_);
+
+  base::WeakPtrFactory<AccelerometerSamplesObserver> weak_factory_{this};
+};
+
+}  // namespace ash
+
+#endif  // ASH_ACCELEROMETER_ACCELEROMETER_SAMPLES_OBSERVER_H_
diff --git a/ash/accelerometer/accelerometer_samples_observer_unittest.cc b/ash/accelerometer/accelerometer_samples_observer_unittest.cc
new file mode 100644
index 0000000..de02b5f
--- /dev/null
+++ b/ash/accelerometer/accelerometer_samples_observer_unittest.cc
@@ -0,0 +1,149 @@
+// 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 "ash/accelerometer/accelerometer_samples_observer.h"
+
+#include <memory>
+#include <utility>
+
+#include "ash/accelerometer/accelerometer_constants.h"
+#include "base/run_loop.h"
+#include "base/test/task_environment.h"
+#include "chromeos/components/sensors/fake_sensor_device.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace ash {
+
+namespace {
+
+constexpr int kFakeAccelerometerId = 1;
+
+constexpr int64_t kFakeSampleData[] = {1, 2, 3};
+
+constexpr double kFakeScaleValue = 10.0;
+
+class AccelerometerSamplesObserverTest : public ::testing::Test {
+ protected:
+  void SetChannels(uint32_t num_of_axes) {
+    CHECK_LE(num_of_axes, kNumberOfAxes);
+    std::vector<chromeos::sensors::FakeSensorDevice::ChannelData> channels_data(
+        num_of_axes);
+    for (uint32_t i = 0; i < num_of_axes; ++i) {
+      channels_data[i].id = kAccelerometerChannels[i];
+      channels_data[i].sample_data = kFakeSampleData[i];
+    }
+
+    sensor_device_ = std::make_unique<chromeos::sensors::FakeSensorDevice>(
+        std::move(channels_data));
+  }
+
+  void SetObserver(
+      mojo::Remote<chromeos::sensors::mojom::SensorDevice> accelerometer) {
+    observer_ = std::make_unique<AccelerometerSamplesObserver>(
+        kFakeAccelerometerId, std::move(accelerometer), kFakeScaleValue,
+        base::BindRepeating(
+            &AccelerometerSamplesObserverTest::OnSampleUpdatedCallback,
+            base::Unretained(this)));
+  }
+
+  void OnSampleUpdatedCallback(int iio_device_id, std::vector<float> sample) {
+    EXPECT_EQ(iio_device_id, kFakeAccelerometerId);
+    EXPECT_EQ(sample.size(), kNumberOfAxes);
+    for (uint32_t i = 0; i < kNumberOfAxes; ++i)
+      EXPECT_EQ(sample[i], kFakeSampleData[i] * kFakeScaleValue);
+
+    ++num_samples_;
+  }
+
+  void DisableFirstChannel() {
+    sensor_device_->SetChannelsEnabled(
+        {0}, false,
+        base::BindOnce(
+            &AccelerometerSamplesObserverTest::SetChannelsEnabledCallback,
+            base::Unretained(this)));
+  }
+
+  void SetChannelsEnabledCallback(const std::vector<int32_t>& failed_indices) {
+    EXPECT_EQ(failed_indices.size(), 0u);
+  }
+
+  std::unique_ptr<chromeos::sensors::FakeSensorDevice> sensor_device_;
+  std::unique_ptr<AccelerometerSamplesObserver> observer_;
+
+  int num_samples_ = 0;
+
+  base::test::SingleThreadTaskEnvironment task_environment;
+};
+
+TEST_F(AccelerometerSamplesObserverTest, MissingChannels) {
+  SetChannels(kNumberOfAxes - 1);
+
+  mojo::Remote<chromeos::sensors::mojom::SensorDevice> accelerometer;
+  sensor_device_->AddReceiver(accelerometer.BindNewPipeAndPassReceiver());
+  SetObserver(std::move(accelerometer));
+
+  // Wait until the mojo connection is reset.
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_FALSE(sensor_device_->HasReceivers());
+}
+
+TEST_F(AccelerometerSamplesObserverTest, StartReadingTwiceError) {
+  SetChannels(kNumberOfAxes);
+
+  mojo::Remote<chromeos::sensors::mojom::SensorDevice> accelerometer;
+  sensor_device_->AddReceiver(accelerometer.BindNewPipeAndPassReceiver());
+
+  mojo::PendingRemote<chromeos::sensors::mojom::SensorDeviceSamplesObserver>
+      pending_remote;
+  auto null_receiver = pending_remote.InitWithNewPipeAndPassReceiver();
+  accelerometer->StartReadingSamples(std::move(pending_remote));
+
+  SetObserver(std::move(accelerometer));
+
+  // Wait until the mojo connection is reset.
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_FALSE(sensor_device_->HasReceivers());
+}
+
+TEST_F(AccelerometerSamplesObserverTest, GetSamples) {
+  SetChannels(kNumberOfAxes);
+
+  mojo::Remote<chromeos::sensors::mojom::SensorDevice> accelerometer;
+  sensor_device_->AddReceiver(accelerometer.BindNewPipeAndPassReceiver());
+
+  SetObserver(std::move(accelerometer));
+  observer_->SetEnabled(true);
+
+  // Wait until a sample is received.
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_TRUE(sensor_device_->HasReceivers());
+  EXPECT_EQ(num_samples_, 1);
+
+  // Simulate a disconnection of the observer's mojo channel in IIO Service.
+  sensor_device_->StopReadingSamples();
+
+  // Wait until the observer's mojo channel is re-established and a sample is
+  // received.
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_TRUE(sensor_device_->HasReceivers());
+  EXPECT_EQ(num_samples_, 2);
+
+  DisableFirstChannel();
+
+  // Wait until a sample is received.
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_TRUE(sensor_device_->HasReceivers());
+  // The updated sample is not sent to |OnSampleUpdatedCallback|.
+  EXPECT_EQ(num_samples_, 2);
+}
+
+}  // namespace
+
+}  // namespace ash
diff --git a/ash/shell.cc b/ash/shell.cc
index c6cba6f..1f3a868 100644
--- a/ash/shell.cc
+++ b/ash/shell.cc
@@ -175,8 +175,6 @@
 #include "base/notreached.h"
 #include "base/system/sys_info.h"
 #include "base/task/post_task.h"
-#include "base/task/task_traits.h"
-#include "base/task/thread_pool.h"
 #include "base/trace_event/trace_event.h"
 #include "chromeos/components/bloom/public/cpp/bloom_controller.h"
 #include "chromeos/constants/chromeos_features.h"
@@ -566,14 +564,7 @@
   // Ash doesn't properly remove pre-target-handlers.
   ui::EventHandler::DisableCheckTargets();
 
-  // AccelerometerReader is important for screen orientation so we need
-  // USER_VISIBLE priority.
-  // Use CONTINUE_ON_SHUTDOWN to avoid blocking shutdown since the data reading
-  // could get blocked on certain devices. See https://crbug.com/1023989.
-  AccelerometerReader::GetInstance()->Initialize(
-      base::ThreadPool::CreateSequencedTaskRunner(
-          {base::MayBlock(), base::TaskPriority::USER_VISIBLE,
-           base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN}));
+  AccelerometerReader::GetInstance()->Initialize();
 
   login_screen_controller_ =
       std::make_unique<LoginScreenController>(system_tray_notifier_.get());
diff --git a/base/allocator/partition_allocator/address_pool_manager.cc b/base/allocator/partition_allocator/address_pool_manager.cc
index b6681df..a95de280 100644
--- a/base/allocator/partition_allocator/address_pool_manager.cc
+++ b/base/allocator/partition_allocator/address_pool_manager.cc
@@ -15,7 +15,7 @@
 #include "base/allocator/partition_allocator/page_allocator_constants.h"
 #include "base/allocator/partition_allocator/page_allocator_internal.h"
 #include "base/allocator/partition_allocator/partition_alloc_check.h"
-#include "base/bits.h"
+#include "base/allocator/partition_allocator/partition_alloc_constants.h"
 #include "base/lazy_instance.h"
 #include "base/notreached.h"
 #include "base/stl_util.h"
@@ -142,8 +142,8 @@
 uintptr_t AddressPoolManager::Pool::FindChunk(size_t requested_size) {
   base::AutoLock scoped_lock(lock_);
 
-  const size_t required_size = bits::Align(requested_size, kSuperPageSize);
-  const size_t need_bits = required_size >> kSuperPageShift;
+  PA_CHECK(!(requested_size & kSuperPageOffsetMask));
+  const size_t need_bits = requested_size >> kSuperPageShift;
 
   // Use first-fit policy to find an available chunk from free chunks. Start
   // from |bit_hint_|, because we know there are no free chunks before.
@@ -183,7 +183,7 @@
       }
       uintptr_t address = address_begin_ + beg_bit * kSuperPageSize;
 #if DCHECK_IS_ON()
-      PA_DCHECK(address + required_size <= address_end_);
+      PA_DCHECK(address + requested_size <= address_end_);
 #endif
       return address;
     }
@@ -196,16 +196,16 @@
 void AddressPoolManager::Pool::FreeChunk(uintptr_t address, size_t free_size) {
   base::AutoLock scoped_lock(lock_);
 
-  PA_DCHECK(!(address & kSuperPageOffsetMask));
+  PA_CHECK(!(address & kSuperPageOffsetMask));
+  PA_CHECK(!(free_size & kSuperPageOffsetMask));
 
-  const size_t size = bits::Align(free_size, kSuperPageSize);
   DCHECK_LE(address_begin_, address);
 #if DCHECK_IS_ON()
-  PA_DCHECK(address + size <= address_end_);
+  PA_DCHECK(address + free_size <= address_end_);
 #endif
 
   const size_t beg_bit = (address - address_begin_) / kSuperPageSize;
-  const size_t end_bit = beg_bit + size / kSuperPageSize;
+  const size_t end_bit = beg_bit + free_size / kSuperPageSize;
   for (size_t i = beg_bit; i < end_bit; ++i) {
     PA_DCHECK(alloc_bitset_.test(i));
     alloc_bitset_.reset(i);
diff --git a/base/allocator/partition_allocator/partition_alloc_constants.h b/base/allocator/partition_allocator/partition_alloc_constants.h
index 4ae2ca6..c147394 100644
--- a/base/allocator/partition_allocator/partition_alloc_constants.h
+++ b/base/allocator/partition_allocator/partition_alloc_constants.h
@@ -256,7 +256,9 @@
 // crbug.com/998048 for details.
 PAGE_ALLOCATOR_CONSTANTS_DECLARE_CONSTEXPR ALWAYS_INLINE size_t
 MaxDirectMapped() {
-  return (1UL << 31) - PageAllocationGranularity();
+  // Subtract kSuperPageSize to accommodate for alignment inside
+  // PartitionRoot::GetDirectMapReservedSize.
+  return (1UL << 31) - kSuperPageSize;
 }
 static const size_t kBitsPerSizeT = sizeof(void*) * CHAR_BIT;
 
diff --git a/base/allocator/partition_allocator/partition_alloc_unittest.cc b/base/allocator/partition_allocator/partition_alloc_unittest.cc
index 8bb1c9ca..34b4d169 100644
--- a/base/allocator/partition_allocator/partition_alloc_unittest.cc
+++ b/base/allocator/partition_allocator/partition_alloc_unittest.cc
@@ -22,6 +22,7 @@
 #include "base/allocator/partition_allocator/partition_ref_count.h"
 #include "base/allocator/partition_allocator/partition_tag.h"
 #include "base/allocator/partition_allocator/partition_tag_bitmap.h"
+#include "base/bits.h"
 #include "base/logging.h"
 #include "base/rand_util.h"
 #include "base/stl_util.h"
@@ -95,8 +96,7 @@
     100,
     base::SystemPageSize(),
     base::SystemPageSize() + 1,
-    base::PartitionRoot<
-        base::internal::ThreadSafe>::Bucket::get_direct_map_size(100),
+    base::PartitionRoot<base::internal::ThreadSafe>::GetDirectMapSlotSize(100),
     1 << 20,
     1 << 21,
 };
@@ -1599,36 +1599,26 @@
 #endif  // !ENABLE_REF_COUNT_FOR_BACKUP_REF_PTR
 
 // Check that guard pages are present where expected.
-TEST_F(PartitionAllocDeathTest, GuardPages) {
-// PartitionAlloc adds PartitionPageSize() to the requested size
-// (for metadata), and then rounds that size to PageAllocationGranularity().
-// To be able to reliably write one past a direct allocation, choose a size
-// that's
-// a) larger than kMaxBucketed (to make the allocation direct)
-// b) aligned at PageAllocationGranularity() boundaries after
-//    PartitionPageSize() has been added to it.
-// (On 32-bit, PartitionAlloc adds another SystemPageSize() to the
-// allocation size before rounding, but there it marks the memory right
-// after size as inaccessible, so it's fine to write 1 past the size we
-// hand to PartitionAlloc and we don't need to worry about allocation
-// granularities.)
-#define ALIGN(N, A) (((N) + (A)-1) / (A) * (A))
-  const size_t kSize = ALIGN(kMaxBucketed + 1 + PartitionPageSize(),
-                             PageAllocationGranularity()) -
-                       PartitionPageSize();
-#undef ALIGN
-  EXPECT_GT(kSize, kMaxBucketed)
-      << "allocation not large enough for direct allocation";
-  size_t size = kSize - kExtraAllocSize;
-  void* ptr = allocator.root()->Alloc(size, type_name);
+TEST_F(PartitionAllocDeathTest, DirectMapGuardPages) {
+  const size_t kSizes[] = {
+      kMaxBucketed + kExtraAllocSize + 1, kMaxBucketed + SystemPageSize(),
+      kMaxBucketed + PartitionPageSize(),
+      bits::Align(kMaxBucketed + kSuperPageSize, kSuperPageSize) -
+          PartitionRoot<ThreadSafe>::GetDirectMapMetadataAndGuardPagesSize()};
+  for (size_t size : kSizes) {
+    size -= kExtraAllocSize;
+    EXPECT_GT(size, kMaxBucketed)
+        << "allocation not large enough for direct allocation";
+    void* ptr = allocator.root()->Alloc(size, type_name);
 
-  EXPECT_TRUE(ptr);
-  char* char_ptr = reinterpret_cast<char*>(ptr) - kPointerOffset;
+    EXPECT_TRUE(ptr);
+    char* char_ptr = reinterpret_cast<char*>(ptr) - kPointerOffset;
 
-  EXPECT_DEATH(*(char_ptr - 1) = 'A', "");
-  EXPECT_DEATH(*(char_ptr + size + kExtraAllocSize) = 'A', "");
+    EXPECT_DEATH(*(char_ptr - 1) = 'A', "");
+    EXPECT_DEATH(*(char_ptr + bits::Align(size, SystemPageSize())) = 'A', "");
 
-  allocator.root()->Free(ptr);
+    allocator.root()->Free(ptr);
+  }
 }
 
 #endif  // !defined(OS_ANDROID) && !defined(OS_IOS)
@@ -2591,6 +2581,132 @@
   }
 }
 
+TEST_F(PartitionAllocTest, Bookkeeping) {
+  auto& root = *allocator.root();
+
+  EXPECT_EQ(0U, root.total_size_of_committed_pages);
+  EXPECT_EQ(0U, root.total_size_of_super_pages);
+  size_t small_size = 1000 - kExtraAllocSize;
+
+  // A full slot span of size 1 partition page is committed.
+  void* ptr = root.Alloc(small_size - kExtraAllocSize, type_name);
+  size_t expected_committed_size = PartitionPageSize();
+  size_t expected_super_pages_size = kSuperPageSize;
+  EXPECT_EQ(expected_committed_size, root.total_size_of_committed_pages);
+  EXPECT_EQ(expected_super_pages_size, root.total_size_of_super_pages);
+
+  // Freeing memory doesn't result in decommitting pages right away.
+  root.Free(ptr);
+  EXPECT_EQ(expected_committed_size, root.total_size_of_committed_pages);
+  EXPECT_EQ(expected_super_pages_size, root.total_size_of_super_pages);
+
+  // Allocating the same size lands it in the same slot span.
+  ptr = root.Alloc(small_size - kExtraAllocSize, type_name);
+  EXPECT_EQ(expected_committed_size, root.total_size_of_committed_pages);
+  EXPECT_EQ(expected_super_pages_size, root.total_size_of_super_pages);
+
+  // Freeing memory doesn't result in decommitting pages right away.
+  root.Free(ptr);
+  EXPECT_EQ(expected_committed_size, root.total_size_of_committed_pages);
+  EXPECT_EQ(expected_super_pages_size, root.total_size_of_super_pages);
+
+  // Allocating another size commits another slot span.
+  ptr = root.Alloc(2 * small_size - kExtraAllocSize, type_name);
+  expected_committed_size += PartitionPageSize();
+  EXPECT_EQ(expected_committed_size, root.total_size_of_committed_pages);
+  EXPECT_EQ(expected_super_pages_size, root.total_size_of_super_pages);
+
+  // Freeing memory doesn't result in decommitting pages right away.
+  root.Free(ptr);
+  EXPECT_EQ(expected_committed_size, root.total_size_of_committed_pages);
+  EXPECT_EQ(expected_super_pages_size, root.total_size_of_super_pages);
+
+  // Single-slot slot spans...
+  size_t big_size = kMaxBucketed - SystemPageSize();
+  size_t bucket_index = SizeToIndex(big_size + kExtraAllocSize);
+  PartitionBucket<base::internal::ThreadSafe>* bucket =
+      &root.buckets[bucket_index];
+  ASSERT_LT(big_size, bucket->get_bytes_per_span());
+  ASSERT_NE(big_size % PartitionPageSize(), 0U);
+  ptr = root.Alloc(big_size - kExtraAllocSize, type_name);
+  expected_committed_size += bucket->get_bytes_per_span();
+  EXPECT_EQ(expected_committed_size, root.total_size_of_committed_pages);
+  EXPECT_EQ(expected_super_pages_size, root.total_size_of_super_pages);
+
+  // Allocating 2nd time doesn't overflow the super page...
+  void* ptr2 = root.Alloc(big_size - kExtraAllocSize, type_name);
+  expected_committed_size += bucket->get_bytes_per_span();
+  EXPECT_EQ(expected_committed_size, root.total_size_of_committed_pages);
+  EXPECT_EQ(expected_super_pages_size, root.total_size_of_super_pages);
+
+  // ... but 3rd time does.
+  void* ptr3 = root.Alloc(big_size - kExtraAllocSize, type_name);
+  expected_committed_size += bucket->get_bytes_per_span();
+  expected_super_pages_size += kSuperPageSize;
+  EXPECT_EQ(expected_committed_size, root.total_size_of_committed_pages);
+  EXPECT_EQ(expected_super_pages_size, root.total_size_of_super_pages);
+
+  // Freeing memory doesn't result in decommitting pages right away.
+  root.Free(ptr);
+  root.Free(ptr2);
+  root.Free(ptr3);
+  EXPECT_EQ(expected_committed_size, root.total_size_of_committed_pages);
+  EXPECT_EQ(expected_super_pages_size, root.total_size_of_super_pages);
+
+  // Now everything should be decommitted. The reserved space for super pages
+  // stays the same and will never go away (by design).
+  root.PurgeMemory(PartitionPurgeDecommitEmptySlotSpans);
+  expected_committed_size = 0;
+  EXPECT_EQ(expected_committed_size, root.total_size_of_committed_pages);
+  EXPECT_EQ(expected_super_pages_size, root.total_size_of_super_pages);
+
+  // None of the above should affect the direct map space.
+  EXPECT_EQ(0U, root.total_size_of_direct_mapped_pages);
+
+  size_t huge_sizes[] = {
+      kMaxBucketed + SystemPageSize(),
+      kMaxBucketed + SystemPageSize() + 123,
+      kSuperPageSize - PageAllocationGranularity(),
+      kSuperPageSize - SystemPageSize() - PartitionPageSize(),
+      kSuperPageSize - PartitionPageSize(),
+      kSuperPageSize - SystemPageSize(),
+      kSuperPageSize,
+      kSuperPageSize + SystemPageSize(),
+      kSuperPageSize + PartitionPageSize(),
+      kSuperPageSize + SystemPageSize() + PartitionPageSize(),
+      kSuperPageSize + PageAllocationGranularity(),
+  };
+  for (size_t huge_size : huge_sizes) {
+    // For direct map, we commit only as many pages as needed.
+    size_t aligned_size = bits::Align(huge_size, SystemPageSize());
+    ptr = root.Alloc(huge_size - kExtraAllocSize, type_name);
+    expected_committed_size += aligned_size;
+    size_t surrounding_pages_size = PartitionPageSize();
+#if !defined(PA_HAS_64_BITS_POINTERS)
+    surrounding_pages_size += SystemPageSize();
+#endif
+    size_t alignment = PageAllocationGranularity();
+#if defined(PA_HAS_64_BITS_POINTERS)
+    if (root.UsesGigaCage())
+      alignment = kSuperPageSize;
+#endif
+    size_t expected_direct_map_size =
+        bits::Align(aligned_size + surrounding_pages_size, alignment);
+    EXPECT_EQ(expected_committed_size, root.total_size_of_committed_pages);
+    EXPECT_EQ(expected_super_pages_size, root.total_size_of_super_pages);
+    EXPECT_EQ(expected_direct_map_size, root.total_size_of_direct_mapped_pages);
+
+    // Freeing memory in the diret map decommits pages right away. The address
+    // space is released for re-use too.
+    root.Free(ptr);
+    expected_committed_size -= aligned_size;
+    expected_direct_map_size = 0;
+    EXPECT_EQ(expected_committed_size, root.total_size_of_committed_pages);
+    EXPECT_EQ(expected_super_pages_size, root.total_size_of_super_pages);
+    EXPECT_EQ(expected_direct_map_size, root.total_size_of_direct_mapped_pages);
+  }
+}
+
 #if ENABLE_REF_COUNT_FOR_BACKUP_REF_PTR
 
 TEST_F(PartitionAllocTest, RefCountBasic) {
diff --git a/base/allocator/partition_allocator/partition_bucket.cc b/base/allocator/partition_allocator/partition_bucket.cc
index 83c593e..bc0c289 100644
--- a/base/allocator/partition_allocator/partition_bucket.cc
+++ b/base/allocator/partition_allocator/partition_bucket.cc
@@ -32,28 +32,11 @@
 ALWAYS_INLINE SlotSpanMetadata<thread_safe>*
 PartitionDirectMap(PartitionRoot<thread_safe>* root, int flags, size_t raw_size)
     EXCLUSIVE_LOCKS_REQUIRED(root->lock_) {
-  size_t slot_size =
-      PartitionBucket<thread_safe>::get_direct_map_size(raw_size);
-
-  // Because we need to fake looking like a super page, we need to allocate
-  // a bunch of system pages more than |slot_size|:
-  // - The first few system pages are the partition page in which the super
-  // page metadata is stored. We commit just one system page out of a partition
-  // page sized clump.
-  // - We add a trailing guard page on 32-bit (on 64-bit we rely on the
-  // massive address space plus randomization instead; additionally GigaCage
-  // guarantees that the region is in the company of regions that have leading
-  // guard pages).
-  size_t reserved_size = slot_size + PartitionPageSize();
-#if !defined(ARCH_CPU_64_BITS)
-  reserved_size += SystemPageSize();
-#endif
-  // Round up to the allocation granularity.
-  reserved_size = bits::Align(reserved_size, PageAllocationGranularity());
-  size_t map_size = reserved_size - PartitionPageSize();
-#if !defined(ARCH_CPU_64_BITS)
-  map_size -= SystemPageSize();
-#endif
+  size_t slot_size = PartitionRoot<thread_safe>::GetDirectMapSlotSize(raw_size);
+  size_t reserved_size = root->GetDirectMapReservedSize(raw_size);
+  size_t map_size =
+      reserved_size -
+      PartitionRoot<thread_safe>::GetDirectMapMetadataAndGuardPagesSize();
   PA_DCHECK(slot_size <= map_size);
 
   char* ptr = nullptr;
@@ -71,22 +54,24 @@
   if (UNLIKELY(!ptr))
     return nullptr;
 
-  size_t committed_page_size = slot_size + SystemPageSize();
-  root->total_size_of_direct_mapped_pages.fetch_add(committed_page_size,
+  root->total_size_of_direct_mapped_pages.fetch_add(reserved_size,
                                                     std::memory_order_relaxed);
-  root->IncreaseCommittedPages(committed_page_size);
+  root->IncreaseCommittedPages(slot_size);
 
-  char* slot = ptr + PartitionPageSize();
+  // Decommit everything in the initial partition page, except one system page
+  // for metadata.
   SetSystemPagesAccess(ptr, SystemPageSize(), PageInaccessible);
   SetSystemPagesAccess(ptr + (SystemPageSize() * 2),
                        PartitionPageSize() - (SystemPageSize() * 2),
                        PageInaccessible);
-#if !defined(ARCH_CPU_64_BITS)
-  // TODO(bartekn): Uncommit all the way up to reserved_size, or in case of
-  // GigaCage, all the way up to 2MB boundary.
-  PA_DCHECK(slot + slot_size + SystemPageSize() <= ptr + reserved_size);
-  SetSystemPagesAccess(slot + slot_size, SystemPageSize(), PageInaccessible);
-#endif
+  char* slot = ptr + PartitionPageSize();
+  // Decommit everything past the slot, until the end of the reserved region.
+  PA_DCHECK(slot + slot_size <= ptr + reserved_size);
+  if (slot + slot_size < ptr + reserved_size) {
+    SetSystemPagesAccess(slot + slot_size,
+                         (ptr + reserved_size) - (slot + slot_size),
+                         PageInaccessible);
+  }
 
   auto* metadata = reinterpret_cast<PartitionDirectMapMetadata<thread_safe>*>(
       PartitionSuperPageToMetadataArea(ptr));
@@ -676,9 +661,12 @@
     PA_DCHECK(new_slot_span);
   } else {
     // Third. If we get here, we need a brand new slot span.
+    // TODO(bartekn): For single-slot slot spans, we can use rounded raw_size
+    // as slot_span_committed_size.
     uint16_t num_partition_pages = get_pages_per_slot_span();
-    void* raw_memory = AllocNewSlotSpan(root, flags, num_partition_pages,
-                                        get_bytes_per_span());
+    void* raw_memory =
+        AllocNewSlotSpan(root, flags, num_partition_pages,
+                         /* slot_span_committed_size= */ get_bytes_per_span());
     if (LIKELY(raw_memory != nullptr)) {
       new_slot_span =
           SlotSpanMetadata<thread_safe>::FromPointerNoAlignmentCheck(
diff --git a/base/allocator/partition_allocator/partition_bucket.h b/base/allocator/partition_allocator/partition_bucket.h
index 125c4c877..107417c 100644
--- a/base/allocator/partition_allocator/partition_bucket.h
+++ b/base/allocator/partition_allocator/partition_bucket.h
@@ -87,14 +87,6 @@
            NumSystemPagesPerPartitionPage();
   }
 
-  static ALWAYS_INLINE size_t get_direct_map_size(size_t size) {
-    // Caller must check that the size is not above the MaxDirectMapped()
-    // limit before calling. This also guards against integer overflow in the
-    // calculation here.
-    PA_DCHECK(size <= MaxDirectMapped());
-    return (size + SystemPageOffsetMask()) & SystemPageBaseMask();
-  }
-
   // This helper function scans a bucket's active slot span list for a suitable
   // new active slot span.  When it finds a suitable new active slot span (one
   // that has free slots and is not empty), it is set as the new active slot
diff --git a/base/allocator/partition_allocator/partition_page.cc b/base/allocator/partition_allocator/partition_page.cc
index 3cdf091a..22725f6 100644
--- a/base/allocator/partition_allocator/partition_page.cc
+++ b/base/allocator/partition_allocator/partition_page.cc
@@ -27,7 +27,6 @@
   auto* root = PartitionRoot<thread_safe>::FromSlotSpan(slot_span);
   root->lock_.AssertAcquired();
   auto* extent = PartitionDirectMapExtent<thread_safe>::FromSlotSpan(slot_span);
-  size_t unmap_size = extent->map_size;
 
   // Maintain the doubly-linked list of all direct mappings.
   if (extent->prev_extent) {
@@ -41,27 +40,22 @@
     extent->next_extent->prev_extent = extent->prev_extent;
   }
 
-  // Add the size of the trailing guard page (32-bit only) and preceding
-  // partition page.
-  unmap_size += PartitionPageSize();
-#if !defined(ARCH_CPU_64_BITS)
-  unmap_size += SystemPageSize();
-#endif
+  root->DecreaseCommittedPages(slot_span->bucket->slot_size);
 
-  size_t uncommitted_page_size =
-      slot_span->bucket->slot_size + SystemPageSize();
-  root->DecreaseCommittedPages(uncommitted_page_size);
-  PA_DCHECK(root->total_size_of_direct_mapped_pages >= uncommitted_page_size);
-  root->total_size_of_direct_mapped_pages -= uncommitted_page_size;
-
-  PA_DCHECK(!(unmap_size & PageAllocationGranularityOffsetMask()));
+  size_t reserved_size =
+      extent->map_size +
+      PartitionRoot<thread_safe>::GetDirectMapMetadataAndGuardPagesSize();
+  PA_DCHECK(!(reserved_size & PageAllocationGranularityOffsetMask()));
+  PA_DCHECK(root->total_size_of_direct_mapped_pages >= reserved_size);
+  root->total_size_of_direct_mapped_pages -= reserved_size;
+  PA_DCHECK(!(reserved_size & PageAllocationGranularityOffsetMask()));
 
   char* ptr = reinterpret_cast<char*>(
       SlotSpanMetadata<thread_safe>::ToPointer(slot_span));
   // Account for the mapping starting a partition page before the actual
   // allocation address.
   ptr -= PartitionPageSize();
-  return {ptr, unmap_size};
+  return {ptr, reserved_size};
 }
 
 template <bool thread_safe>
diff --git a/base/allocator/partition_allocator/partition_root.cc b/base/allocator/partition_allocator/partition_root.cc
index d28e984..08cd35b 100644
--- a/base/allocator/partition_allocator/partition_root.cc
+++ b/base/allocator/partition_allocator/partition_root.cc
@@ -444,7 +444,7 @@
       internal::PartitionSizeAdjustAdd(allow_extras, requested_size);
   // Note that the new size isn't a bucketed size; this function is called
   // whenever we're reallocating a direct mapped allocation.
-  size_t new_slot_size = Bucket::get_direct_map_size(raw_size);
+  size_t new_slot_size = GetDirectMapSlotSize(raw_size);
   if (new_slot_size < kMinDirectMappedDownsize)
     return false;
 
@@ -453,13 +453,17 @@
   char* char_ptr = static_cast<char*>(SlotSpan::ToPointer(slot_span));
   if (new_slot_size == current_slot_size) {
     // No need to move any memory around, but update size and cookie below.
+    // That's because raw_size may have changed.
   } else if (new_slot_size < current_slot_size) {
-    size_t map_size = DirectMapExtent::FromSlotSpan(slot_span)->map_size;
+    size_t current_map_size =
+        DirectMapExtent::FromSlotSpan(slot_span)->map_size;
+    size_t new_map_size = GetDirectMapReservedSize(raw_size) -
+                          GetDirectMapMetadataAndGuardPagesSize();
 
-    // Don't reallocate in-place if new size is less than 80 % of the full
-    // map size, to avoid holding on to too much unused address space.
-    if ((new_slot_size / SystemPageSize()) * 5 <
-        (map_size / SystemPageSize()) * 4)
+    // Don't reallocate in-place if new map size would be less than 80 % of the
+    // current map size, to avoid holding on to too much unused address space.
+    if ((new_map_size / SystemPageSize()) * 5 <
+        (current_map_size / SystemPageSize()) * 4)
       return false;
 
     // Shrink by decommitting unneeded pages and making them inaccessible.
diff --git a/base/allocator/partition_allocator/partition_root.h b/base/allocator/partition_allocator/partition_root.h
index e6cc60c..28b70e5 100644
--- a/base/allocator/partition_allocator/partition_root.h
+++ b/base/allocator/partition_allocator/partition_root.h
@@ -33,6 +33,7 @@
 #include <atomic>
 
 #include "base/allocator/partition_allocator/page_allocator.h"
+#include "base/allocator/partition_allocator/page_allocator_constants.h"
 #include "base/allocator/partition_allocator/partition_alloc-inl.h"
 #include "base/allocator/partition_allocator/partition_alloc_check.h"
 #include "base/allocator/partition_allocator/partition_alloc_constants.h"
@@ -47,6 +48,7 @@
 #include "base/allocator/partition_allocator/partition_tag.h"
 #include "base/allocator/partition_allocator/pcscan.h"
 #include "base/allocator/partition_allocator/thread_cache.h"
+#include "base/bits.h"
 #include "base/optional.h"
 #include "build/build_config.h"
 
@@ -148,7 +150,14 @@
 #endif
 
   // Bookkeeping.
-  // Invariant: total_size_of_committed_pages <=
+  // - total_size_of_super_pages - total virtual address space for normal bucket
+  //     super pages
+  // - total_size_of_direct_mapped_pages - total virtual address space for
+  //     direct-map regions
+  // - total_size_of_committed_pages - total committed pages for slots (doesn't
+  //     include metadata, bitmaps (if any), or any data outside or regions
+  //     described in #1 and #2)
+  // Invariant: total_size_of_committed_pages <
   //                total_size_of_super_pages +
   //                total_size_of_direct_mapped_pages.
   // Since all operations on these atomic variables have relaxed semantics, we
@@ -317,6 +326,49 @@
     }
   }
 
+  static PAGE_ALLOCATOR_CONSTANTS_DECLARE_CONSTEXPR ALWAYS_INLINE size_t
+  GetDirectMapMetadataAndGuardPagesSize() {
+    // Because we need to fake a direct-map region to look like a super page, we
+    // need to allocate a bunch of system pages more around the payload:
+    // - The first few system pages are the partition page in which the super
+    // page metadata is stored.
+    // - We add a trailing guard page on 32-bit (on 64-bit we rely on the
+    // massive address space plus randomization instead; additionally GigaCage
+    // guarantees that the region is followed by region with a preceding guard
+    // page or inaccessible in the direct map-pool).
+    size_t ret = PartitionPageSize();
+#if !defined(PA_HAS_64_BITS_POINTERS)
+    ret += SystemPageSize();
+#endif
+    return ret;
+  }
+
+  static PAGE_ALLOCATOR_CONSTANTS_DECLARE_CONSTEXPR ALWAYS_INLINE size_t
+  GetDirectMapSlotSize(size_t raw_size) {
+    // Caller must check that the size is not above the MaxDirectMapped()
+    // limit before calling. This also guards against integer overflow in the
+    // calculation here.
+    PA_DCHECK(raw_size <= MaxDirectMapped());
+    return bits::Align(raw_size, SystemPageSize());
+  }
+
+  ALWAYS_INLINE size_t GetDirectMapReservedSize(size_t raw_size) {
+    // Caller must check that the size is not above the MaxDirectMapped()
+    // limit before calling. This also guards against integer overflow in the
+    // calculation here.
+    PA_DCHECK(raw_size <= MaxDirectMapped());
+    // Align to allocation granularity. However, when 64-bit GigaCage is used,
+    // the granularity is super page size.
+    size_t alignment = PageAllocationGranularity();
+#if defined(PA_HAS_64_BITS_POINTERS)
+    if (UsesGigaCage()) {
+      alignment = kSuperPageSize;
+    }
+#endif
+    return bits::Align(raw_size + GetDirectMapMetadataAndGuardPagesSize(),
+                       alignment);
+  }
+
  private:
   // Allocates memory, without initializing extras.
   //
@@ -1060,7 +1112,7 @@
   } else if (size > MaxDirectMapped()) {
     // Too large to allocate => return the size unchanged.
   } else {
-    size = Bucket::get_direct_map_size(size);
+    size = GetDirectMapSlotSize(size);
   }
   size = internal::PartitionSizeAdjustSubtract(allow_extras, size);
   return size;
diff --git a/base/android/java/src/org/chromium/base/compat/ApiHelperForO.java b/base/android/java/src/org/chromium/base/compat/ApiHelperForO.java
index 511a870..1806e546 100644
--- a/base/android/java/src/org/chromium/base/compat/ApiHelperForO.java
+++ b/base/android/java/src/org/chromium/base/compat/ApiHelperForO.java
@@ -11,14 +11,12 @@
 import android.content.pm.PackageManager;
 import android.content.res.Configuration;
 import android.os.Build;
-import android.view.autofill.AutofillManager;
 import android.view.Display;
 import android.view.View;
 import android.view.Window;
 
 import org.chromium.base.StrictModeContext;
 import org.chromium.base.annotations.VerifiesOnO;
-import org.chromium.base.ContextUtils;
 
 /**
  * Utility class to use new APIs that were added in O (API level 26). These need to exist in a
@@ -72,13 +70,4 @@
             return context.createContextForSplit(name);
         }
     }
-
-    /** See {@link AutofillManager@cancel()}. */
-    public static void cancelAutofillSession() {
-        AutofillManager autofillManager = ContextUtils.getApplicationContext().getSystemService(
-            AutofillManager.class);
-        if (autofillManager != null) {
-            autofillManager.cancel();
-        }
-    }
 }
diff --git a/base/cpu_unittest.cc b/base/cpu_unittest.cc
index b4a59cf3..fa245a1 100644
--- a/base/cpu_unittest.cc
+++ b/base/cpu_unittest.cc
@@ -20,6 +20,7 @@
   ASSERT_TRUE(cpu.has_mmx());
   ASSERT_TRUE(cpu.has_sse());
   ASSERT_TRUE(cpu.has_sse2());
+  ASSERT_TRUE(cpu.has_sse3());
 
 // GCC and clang instruction test.
 #if defined(COMPILER_GCC)
@@ -32,10 +33,8 @@
   // Execute an SSE 2 instruction.
   __asm__ __volatile__("psrldq $0, %%xmm0\n" : : : "xmm0");
 
-  if (cpu.has_sse3()) {
-    // Execute an SSE 3 instruction.
-    __asm__ __volatile__("addsubpd %%xmm0, %%xmm0\n" : : : "xmm0");
-  }
+  // Execute an SSE 3 instruction.
+  __asm__ __volatile__("addsubpd %%xmm0, %%xmm0\n" : : : "xmm0");
 
   if (cpu.has_ssse3()) {
     // Execute a Supplimental SSE 3 instruction.
@@ -80,10 +79,8 @@
   // Execute an SSE 2 instruction.
   __asm psrldq xmm0, 0;
 
-  if (cpu.has_sse3()) {
-    // Execute an SSE 3 instruction.
-    __asm addsubpd xmm0, xmm0;
-  }
+  // Execute an SSE 3 instruction.
+  __asm addsubpd xmm0, xmm0;
 
   if (cpu.has_ssse3()) {
     // Execute a Supplimental SSE 3 instruction.
diff --git a/base/system/sys_info.h b/base/system/sys_info.h
index eee730e..902a63a 100644
--- a/base/system/sys_info.h
+++ b/base/system/sys_info.h
@@ -66,6 +66,12 @@
   // on failure.
   static int64_t AmountOfTotalDiskSpace(const FilePath& path);
 
+#if defined(OS_FUCHSIA)
+  // Sets the total amount of disk space to report under the specified |path|.
+  // If |bytes| is -ve then any existing entry for |path| is removed.
+  static void SetAmountOfTotalDiskSpace(const FilePath& path, int64_t bytes);
+#endif
+
   // Returns system uptime.
   static TimeDelta Uptime();
 
diff --git a/base/system/sys_info_fuchsia.cc b/base/system/sys_info_fuchsia.cc
index 1205f63..e4fd0eee 100644
--- a/base/system/sys_info_fuchsia.cc
+++ b/base/system/sys_info_fuchsia.cc
@@ -6,13 +6,57 @@
 
 #include <zircon/syscalls.h>
 
+#include "base/containers/flat_map.h"
+#include "base/files/file_util.h"
 #include "base/fuchsia/fuchsia_logging.h"
 #include "base/logging.h"
+#include "base/no_destructor.h"
+#include "base/synchronization/lock.h"
 #include "base/threading/scoped_blocking_call.h"
 #include "build/build_config.h"
 
 namespace base {
 
+namespace {
+
+struct TotalDiskSpace {
+  Lock lock;
+  flat_map<FilePath, int64_t> space_map GUARDED_BY(lock);
+};
+
+TotalDiskSpace& GetTotalDiskSpace() {
+  static NoDestructor<TotalDiskSpace> total_disk_space;
+  return *total_disk_space;
+}
+
+// Returns the total-disk-space set for the volume containing |path|. If
+// |volume_path| is non-null then it receives the path to the relevant volume.
+// Returns -1, and does not modify |volume_path|, if no match is found.
+int64_t GetAmountOfTotalDiskSpaceAndVolumePath(const FilePath& path,
+                                               base::FilePath* volume_path) {
+  CHECK(path.IsAbsolute());
+  TotalDiskSpace& total_disk_space = GetTotalDiskSpace();
+
+  AutoLock l(total_disk_space.lock);
+  int64_t result = -1;
+  base::FilePath matched_path;
+  for (const auto& path_and_size : total_disk_space.space_map) {
+    if (path_and_size.first == path || path_and_size.first.IsParent(path)) {
+      // If a deeper path was already matched then ignore this entry.
+      if (!matched_path.empty() && !matched_path.IsParent(path_and_size.first))
+        continue;
+      matched_path = path_and_size.first;
+      result = path_and_size.second;
+    }
+  }
+
+  if (volume_path)
+    *volume_path = matched_path;
+  return result;
+}
+
+}  // namespace
+
 // static
 int64_t SysInfo::AmountOfPhysicalMemoryImpl() {
   return zx_system_get_physmem();
@@ -42,18 +86,37 @@
 
 // static
 int64_t SysInfo::AmountOfFreeDiskSpace(const FilePath& path) {
-  base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
-                                                base::BlockingType::MAY_BLOCK);
-  NOTIMPLEMENTED_LOG_ONCE();
-  return -1;
+  ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
+  base::FilePath volume_path;
+  const int64_t total_space =
+      GetAmountOfTotalDiskSpaceAndVolumePath(path, &volume_path);
+  if (total_space < 0)
+    return -1;
+
+  // TODO(crbug.com/1148334): Replace this with an efficient implementation.
+  const int64_t used_space = ComputeDirectorySize(volume_path);
+  if (used_space < total_space)
+    return total_space - used_space;
+
+  return 0;
 }
 
 // static
 int64_t SysInfo::AmountOfTotalDiskSpace(const FilePath& path) {
-  base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
-                                                base::BlockingType::MAY_BLOCK);
-  NOTIMPLEMENTED_LOG_ONCE();
-  return -1;
+  if (path.empty())
+    return -1;
+  return GetAmountOfTotalDiskSpaceAndVolumePath(path, nullptr);
+}
+
+// static
+void SysInfo::SetAmountOfTotalDiskSpace(const FilePath& path, int64_t bytes) {
+  DCHECK(path.IsAbsolute());
+  TotalDiskSpace& total_disk_space = GetTotalDiskSpace();
+  AutoLock l(total_disk_space.lock);
+  if (bytes >= 0)
+    total_disk_space.space_map[path] = bytes;
+  else
+    total_disk_space.space_map.erase(path);
 }
 
 // static
diff --git a/base/system/sys_info_unittest.cc b/base/system/sys_info_unittest.cc
index 34b5c17a..82f00bd 100644
--- a/base/system/sys_info_unittest.cc
+++ b/base/system/sys_info_unittest.cc
@@ -89,34 +89,54 @@
 }
 #endif  // defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_ANDROID)
 
-#if defined(OS_FUCHSIA)
-// TODO(crbug.com/851734): Implementation depends on statvfs, which is not
-// implemented on Fuchsia
-#define MAYBE_AmountOfFreeDiskSpace DISABLED_AmountOfFreeDiskSpace
-#else
-#define MAYBE_AmountOfFreeDiskSpace AmountOfFreeDiskSpace
-#endif
-TEST_F(SysInfoTest, MAYBE_AmountOfFreeDiskSpace) {
+TEST_F(SysInfoTest, AmountOfFreeDiskSpace) {
   // We aren't actually testing that it's correct, just that it's sane.
   FilePath tmp_path;
   ASSERT_TRUE(GetTempDir(&tmp_path));
+#if defined(OS_FUCHSIA)
+  // Fuchsia currently requires "total disk space" be set explicitly.
+  // See crbug.com/1148334.
+  SysInfo::SetAmountOfTotalDiskSpace(tmp_path, 1024);
+#endif
   EXPECT_GE(SysInfo::AmountOfFreeDiskSpace(tmp_path), 0) << tmp_path.value();
 }
 
-#if defined(OS_FUCHSIA)
-// TODO(crbug.com/851734): Implementation depends on statvfs, which is not
-// implemented on Fuchsia
-#define MAYBE_AmountOfTotalDiskSpace DISABLED_AmountOfTotalDiskSpace
-#else
-#define MAYBE_AmountOfTotalDiskSpace AmountOfTotalDiskSpace
-#endif
-TEST_F(SysInfoTest, MAYBE_AmountOfTotalDiskSpace) {
+TEST_F(SysInfoTest, AmountOfTotalDiskSpace) {
   // We aren't actually testing that it's correct, just that it's sane.
   FilePath tmp_path;
   ASSERT_TRUE(GetTempDir(&tmp_path));
+#if defined(OS_FUCHSIA)
+  // Fuchsia currently requires "total disk space" be set explicitly.
+  // See crbug.com/1148334.
+  SysInfo::SetAmountOfTotalDiskSpace(tmp_path, 1024);
+#endif
   EXPECT_GT(SysInfo::AmountOfTotalDiskSpace(tmp_path), 0) << tmp_path.value();
 }
 
+#if defined(OS_FUCHSIA)
+// Verify that specifying total disk space for nested directories matches
+// the deepest-nested.
+TEST_F(SysInfoTest, NestedVolumesAmountOfTotalDiskSpace) {
+  constexpr int64_t kOuterVolumeQuota = 1024;
+  constexpr int64_t kInnerVolumeQuota = kOuterVolumeQuota / 2;
+
+  FilePath tmp_path;
+  ASSERT_TRUE(GetTempDir(&tmp_path));
+  SysInfo::SetAmountOfTotalDiskSpace(tmp_path, kOuterVolumeQuota);
+  const FilePath subdirectory_path = tmp_path.Append("subdirectory");
+  SysInfo::SetAmountOfTotalDiskSpace(subdirectory_path, kInnerVolumeQuota);
+
+  EXPECT_EQ(SysInfo::AmountOfTotalDiskSpace(tmp_path), kOuterVolumeQuota);
+  EXPECT_EQ(SysInfo::AmountOfTotalDiskSpace(subdirectory_path),
+            kInnerVolumeQuota);
+
+  // Remove the inner directory quota setting and check again.
+  SysInfo::SetAmountOfTotalDiskSpace(subdirectory_path, -1);
+  EXPECT_EQ(SysInfo::AmountOfTotalDiskSpace(subdirectory_path),
+            kOuterVolumeQuota);
+}
+#endif  // defined(OS_FUCHSIA)
+
 #if defined(OS_WIN) || defined(OS_APPLE) || defined(OS_LINUX) || \
     defined(OS_CHROMEOS) || defined(OS_FUCHSIA)
 TEST_F(SysInfoTest, OperatingSystemVersionNumbers) {
diff --git a/build/android/resource_sizes.gni b/build/android/resource_sizes.gni
index 1cc8aecb..5bc430a 100644
--- a/build/android/resource_sizes.gni
+++ b/build/android/resource_sizes.gni
@@ -81,8 +81,6 @@
 #   to_resource_sizes_py: Scope containing data to pass to resource_sizes.py,
 #     processed by generate_commit_size_analysis.py.
 #   supersize_input_file: Main input for SuperSize.
-#   version: Version number, uses to notify build bots that significant changes
-#     has occurred, so that gs:// should be ignored.
 template("android_size_bot_config") {
   _full_target_name = get_label_info(target_name, "label_no_toolchain")
   _out_json = {
@@ -92,7 +90,6 @@
                              "mapping_files",
                              "to_resource_sizes_py",
                              "supersize_input_file",
-                             "version",
                            ])
   }
   _output_json_path = "$root_build_dir/config/${invoker.name}_size_config.json"
diff --git a/build/config/compiler/BUILD.gn b/build/config/compiler/BUILD.gn
index 9d66490..faff9259 100644
--- a/build/config/compiler/BUILD.gn
+++ b/build/config/compiler/BUILD.gn
@@ -750,6 +750,7 @@
       cflags += [
         "-m64",
         "-march=$x64_arch",
+        "-msse3",
       ]
       ldflags += [ "-m64" ]
     } else if (current_cpu == "x86") {
@@ -757,9 +758,8 @@
       ldflags += [ "-m32" ]
       if (!is_nacl) {
         cflags += [
-          "-msse2",
           "-mfpmath=sse",
-          "-mmmx",
+          "-msse3",
         ]
       }
     } else if (current_cpu == "arm") {
diff --git a/build/config/sanitizers/BUILD.gn b/build/config/sanitizers/BUILD.gn
index 68ea4c3..0473a8d 100644
--- a/build/config/sanitizers/BUILD.gn
+++ b/build/config/sanitizers/BUILD.gn
@@ -157,12 +157,6 @@
     ldflags = []
     if (is_asan) {
       ldflags += [ "-fsanitize=address" ]
-      if (is_mac) {
-        # https://crbug.com/708707
-        ldflags += [ "-fno-sanitize-address-use-after-scope" ]
-      } else {
-        ldflags += [ "-fsanitize-address-use-after-scope" ]
-      }
     }
     if (is_hwasan) {
       ldflags += [ "-fsanitize=hwaddress" ]
@@ -277,12 +271,6 @@
   cflags = []
   if (is_asan) {
     cflags += [ "-fsanitize=address" ]
-    if (!is_mac) {
-      cflags += [ "-fsanitize-address-use-after-scope" ]
-    } else {
-      # https://crbug.com/708707
-      cflags += [ "-fno-sanitize-address-use-after-scope" ]
-    }
     if (asan_use_blacklist) {
       if (is_win) {
         if (!defined(asan_win_blacklist_path)) {
diff --git a/build/config/win/BUILD.gn b/build/config/win/BUILD.gn
index 7e1cea5..9ad01d02 100644
--- a/build/config/win/BUILD.gn
+++ b/build/config/win/BUILD.gn
@@ -118,6 +118,13 @@
       assert(false, "unknown current_cpu " + current_cpu)
     }
 
+    # Chrome currently requires SSE3. Clang supports targeting any Intel
+    # microarchitecture. MSVC only supports a subset of architectures, and the
+    # next step after SSE2 will be AVX.
+    if (current_cpu == "x86" || current_cpu == "x64") {
+      cflags += [ "-msse3" ]
+    }
+
     if (exec_script("//build/win/use_ansi_codes.py", [], "trim string") ==
         "True") {
       cflags += [
diff --git a/build/fuchsia/linux.sdk.sha1 b/build/fuchsia/linux.sdk.sha1
index 6cf3511..29b0834 100644
--- a/build/fuchsia/linux.sdk.sha1
+++ b/build/fuchsia/linux.sdk.sha1
@@ -1 +1 @@
-0.20201113.3.1
+0.20201114.1.1
diff --git a/build/fuchsia/mac.sdk.sha1 b/build/fuchsia/mac.sdk.sha1
index 46f24e4..fbe0516 100644
--- a/build/fuchsia/mac.sdk.sha1
+++ b/build/fuchsia/mac.sdk.sha1
@@ -1 +1 @@
-0.20201113.2.2
+0.20201114.2.1
diff --git a/chrome/VERSION b/chrome/VERSION
index 5b859d3c..9802319 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
 MAJOR=89
 MINOR=0
-BUILD=4325
+BUILD=4327
 PATCH=0
diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
index e6f4935..ef2cc98f9 100644
--- a/chrome/android/BUILD.gn
+++ b/chrome/android/BUILD.gn
@@ -3014,7 +3014,6 @@
       apk_name = "apks/$_minimal_apks_filename"
     }
     supersize_input_file = "apks/$_minimal_apks_filename"
-    version = "1"
   }
 }
 
@@ -3100,7 +3099,6 @@
       trichrome_webview = "apks/$_trichrome_webview_basename"
     }
     supersize_input_file = "apks/$_ssargs_filename"
-    version = "1"
   }
 }
 
diff --git a/chrome/android/chrome_java_resources.gni b/chrome/android/chrome_java_resources.gni
index e254f21c..53a8d19 100644
--- a/chrome/android/chrome_java_resources.gni
+++ b/chrome/android/chrome_java_resources.gni
@@ -688,6 +688,7 @@
   "java/res/drawable/ic_sync_on_48dp.xml",
   "java/res/drawable/ic_toolbar_share_offset_24dp.xml",
   "java/res/drawable/ic_translate.xml",
+  "java/res/drawable/ic_trending_down_black.xml",
   "java/res/drawable/ic_tv_options_input_settings_rotated_grey.xml",
   "java/res/drawable/ic_visibility_black.xml",
   "java/res/drawable/ic_visibility_off_black.xml",
diff --git a/chrome/android/chrome_java_sources.gni b/chrome/android/chrome_java_sources.gni
index e299da2..17ba60ad 100644
--- a/chrome/android/chrome_java_sources.gni
+++ b/chrome/android/chrome_java_sources.gni
@@ -92,6 +92,7 @@
   "java/src/org/chromium/chrome/browser/app/video_tutorials/NewTabPageVideoIPHManager.java",
   "java/src/org/chromium/chrome/browser/app/video_tutorials/VideoPlayerActivity.java",
   "java/src/org/chromium/chrome/browser/app/video_tutorials/VideoTutorialListActivity.java",
+  "java/src/org/chromium/chrome/browser/app/video_tutorials/VideoTutorialShareHelper.java",
   "java/src/org/chromium/chrome/browser/autofill/AutofillExpirationDateFixFlowBridge.java",
   "java/src/org/chromium/chrome/browser/autofill/AutofillExpirationDateFixFlowPrompt.java",
   "java/src/org/chromium/chrome/browser/autofill/AutofillLogger.java",
diff --git a/chrome/android/expectations/lint-suppressions.xml b/chrome/android/expectations/lint-suppressions.xml
index 3c477dc..fa96c402 100644
--- a/chrome/android/expectations/lint-suppressions.xml
+++ b/chrome/android/expectations/lint-suppressions.xml
@@ -323,8 +323,7 @@
     <ignore regexp="The resource `R.string.ntp_article_suggestions_section_empty` appears to be unused"/>
     <!-- 1: TODO(crbug.com/1137985) resource is used in downstream image editor. -->
     <ignore regexp="The resource `R.string.clear` appears to be unused"/>
-    <!-- 4: Temporarily suppressed until impelmentation is ready, see https://crbug.com/1144742, https://crbug.com/1148020 -->
-    <ignore regexp="The resource `R.string.menu_track_prices` appears to be unused"/>
+    <!-- 3: Temporarily suppressed until impelmentation is ready, see https://crbug.com/1148020 -->
     <ignore regexp="The resource `R.string.price_drop_spotted_title` appears to be unused"/>
     <ignore regexp="The resource `R.string.price_drop_spotted_content` appears to be unused"/>
     <ignore regexp="The resource `R.string.price_drop_spotted_show_me` appears to be unused"/>
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabUiFeatureUtilities.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabUiFeatureUtilities.java
index dfa55027..ae361f2 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabUiFeatureUtilities.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabUiFeatureUtilities.java
@@ -202,4 +202,11 @@
     public static boolean isLaunchBugFixEnabled() {
         return ENABLE_LAUNCH_BUG_FIX.getValue();
     }
+
+    /**
+     * @return Whether the price tracking feature is enabled and available for use.
+     */
+    public static boolean isPriceTrackingEnabled() {
+        return ENABLE_PRICE_TRACKING.getValue();
+    }
 }
diff --git a/chrome/android/java/res/drawable/ic_trending_down_black.xml b/chrome/android/java/res/drawable/ic_trending_down_black.xml
new file mode 100644
index 0000000..c6b0a5c
--- /dev/null
+++ b/chrome/android/java/res/drawable/ic_trending_down_black.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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. -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:pathData="M20,12v2.58L13.41,8l-4,4 -6,-6L2,7.41l7.41,7.42 4,-4L18.58,16H16v2h6v-6z"
+      android:fillColor="@color/default_icon_color" />
+</vector>
diff --git a/chrome/android/java/res/menu/main_menu.xml b/chrome/android/java/res/menu/main_menu.xml
index 502f309..1458baf 100644
--- a/chrome/android/java/res/menu/main_menu.xml
+++ b/chrome/android/java/res/menu/main_menu.xml
@@ -139,6 +139,17 @@
         <item android:id="@+id/menu_group_tabs"
             android:title="@string/menu_group_tabs"
             android:icon="@drawable/ic_widgets" />
+        <item android:id="@+id/track_prices_row_menu_id"
+            android:title="@null">
+          <menu>
+              <item android:id="@+id/track_prices_id"
+                android:title="@string/menu_track_prices"
+                android:icon="@drawable/ic_trending_down_black" />
+              <item android:id="@+id/track_prices_check_id"
+                android:title="@null"
+                android:checkable="true" />
+          </menu>
+        </item>
         <item android:id="@id/preferences_id"
             android:title="@string/menu_settings"
             android:icon="@drawable/settings_cog" />
@@ -174,6 +185,17 @@
         <item android:id="@id/menu_group_tabs"
             android:title="@string/menu_group_tabs"
             android:icon="@drawable/ic_widgets" />
+        <item android:id="@id/track_prices_row_menu_id"
+            android:title="@null">
+          <menu>
+              <item android:id="@id/track_prices_id"
+                android:title="@string/menu_track_prices"
+                android:icon="@drawable/ic_trending_down_black" />
+              <item android:id="@id/track_prices_check_id"
+                android:title="@null"
+                android:checkable="true" />
+          </menu>
+        </item>
         <item android:id="@id/preferences_id"
             android:title="@string/menu_settings"
             android:icon="@drawable/settings_cog" />
diff --git a/chrome/android/java/res/menu/main_menu_regroup.xml b/chrome/android/java/res/menu/main_menu_regroup.xml
index 1e1ae57..840be2e 100644
--- a/chrome/android/java/res/menu/main_menu_regroup.xml
+++ b/chrome/android/java/res/menu/main_menu_regroup.xml
@@ -198,6 +198,17 @@
         <item android:id="@+id/menu_group_tabs"
             android:title="@string/menu_group_tabs"
             android:icon="@drawable/ic_widgets" />
+        <item android:id="@+id/track_prices_row_menu_id"
+            android:title="@null">
+          <menu>
+              <item android:id="@+id/track_prices_id"
+                android:title="@string/menu_track_prices"
+                android:icon="@drawable/ic_trending_down_black" />
+              <item android:id="@+id/track_prices_check_id"
+                android:title="@null"
+                android:checkable="true" />
+          </menu>
+        </item>
         <item android:id="@id/preferences_id"
             android:title="@string/menu_settings"
             android:icon="@drawable/settings_cog" />
@@ -237,6 +248,17 @@
         <item android:id="@id/menu_group_tabs"
             android:title="@string/menu_group_tabs"
             android:icon="@drawable/ic_widgets" />
+        <item android:id="@id/track_prices_row_menu_id"
+            android:title="@null">
+          <menu>
+              <item android:id="@id/track_prices_id"
+                android:title="@string/menu_track_prices"
+                android:icon="@drawable/ic_trending_down_black" />
+              <item android:id="@id/track_prices_check_id"
+                android:title="@null"
+                android:checkable="true" />
+          </menu>
+        </item>
         <item android:id="@id/preferences_id"
             android:title="@string/menu_settings"
             android:icon="@drawable/settings_cog" />
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/LaunchIntentDispatcher.java b/chrome/android/java/src/org/chromium/chrome/browser/LaunchIntentDispatcher.java
index 47b35ac8..f416c78 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/LaunchIntentDispatcher.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/LaunchIntentDispatcher.java
@@ -28,6 +28,7 @@
 import org.chromium.base.StrictModeContext;
 import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.chrome.browser.app.ChromeActivity;
+import org.chromium.chrome.browser.app.video_tutorials.VideoTutorialShareHelper;
 import org.chromium.chrome.browser.browserservices.BrowserServicesIntentDataProvider.CustomTabsUiType;
 import org.chromium.chrome.browser.browserservices.SessionDataHolder;
 import org.chromium.chrome.browser.browserservices.ui.splashscreen.trustedwebactivity.TwaSplashController;
@@ -159,6 +160,11 @@
             return Action.FINISH_ACTIVITY;
         }
 
+        // Check if the URL is a video tutorial and needs to be handled in a video player.
+        if (VideoTutorialShareHelper.handleVideoTutorialURL(url)) {
+            return Action.FINISH_ACTIVITY;
+        }
+
         // Check if a LIVE WebappActivity has to be brought back to the foreground.  We can't
         // check for a dead WebappActivity because we don't have that information without a global
         // TabManager.  If that ever lands, code to bring back any Tab could be consolidated
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 1f3f9caa..9ad8d6b 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
@@ -523,6 +523,9 @@
                                 .getTabsWithNoOtherRelatedTabs()
                                 .size()
                         > 1;
+        boolean isPriceTrackingVisible = TabUiFeatureUtilities.isPriceTrackingEnabled()
+                && !DeviceClassManager.enableAccessibilityLayout();
+        boolean isPriceTrackingEnabled = isPriceTrackingVisible;
 
         for (int i = 0; i < menu.size(); ++i) {
             MenuItem item = menu.getItem(i);
@@ -571,6 +574,10 @@
                 item.setVisible(isMenuGroupTabsVisible);
                 item.setEnabled(isMenuGroupTabsEnabled);
             }
+            if (item.getItemId() == R.id.track_prices_row_menu_id) {
+                item.setVisible(isPriceTrackingVisible);
+                item.setEnabled(isPriceTrackingEnabled);
+            }
             if (item.getItemId() == R.id.close_all_tabs_menu_id) {
                 boolean hasTabs = mTabModelSelector.getTotalTabCount() > 0;
                 item.setVisible(!isIncognito);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/video_tutorials/NewTabPageVideoIPHManager.java b/chrome/android/java/src/org/chromium/chrome/browser/app/video_tutorials/NewTabPageVideoIPHManager.java
index b20a2f7..e5d315b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/app/video_tutorials/NewTabPageVideoIPHManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/app/video_tutorials/NewTabPageVideoIPHManager.java
@@ -124,7 +124,7 @@
 
     @VisibleForTesting
     protected void launchVideoPlayer(Tutorial tutorial) {
-        VideoPlayerActivity.playVideoTutorial(mContext, tutorial);
+        VideoPlayerActivity.playVideoTutorial(mContext, tutorial.featureType);
     }
 
     @VisibleForTesting
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/video_tutorials/VideoPlayerActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/app/video_tutorials/VideoPlayerActivity.java
index 17c9854a..a79d4d7 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/app/video_tutorials/VideoPlayerActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/app/video_tutorials/VideoPlayerActivity.java
@@ -81,10 +81,11 @@
     }
 
     /** Called to launch this activity to play a given video tutorial. */
-    static void playVideoTutorial(Context context, Tutorial tutorial) {
+    public static void playVideoTutorial(Context context, @FeatureType int featureType) {
         Intent intent = new Intent();
         intent.setClass(context, VideoPlayerActivity.class);
-        intent.putExtra(VideoPlayerActivity.EXTRA_VIDEO_TUTORIAL, tutorial.featureType);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.putExtra(VideoPlayerActivity.EXTRA_VIDEO_TUTORIAL, featureType);
         context.startActivity(intent);
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/video_tutorials/VideoTutorialListActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/app/video_tutorials/VideoTutorialListActivity.java
index b6c3eb2c..dfe2540 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/app/video_tutorials/VideoTutorialListActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/app/video_tutorials/VideoTutorialListActivity.java
@@ -42,6 +42,6 @@
     }
 
     private void onTutorialSelected(Tutorial tutorial) {
-        VideoPlayerActivity.playVideoTutorial(this, tutorial);
+        VideoPlayerActivity.playVideoTutorial(this, tutorial.featureType);
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/video_tutorials/VideoTutorialShareHelper.java b/chrome/android/java/src/org/chromium/chrome/browser/app/video_tutorials/VideoTutorialShareHelper.java
new file mode 100644
index 0000000..d3d930c
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/app/video_tutorials/VideoTutorialShareHelper.java
@@ -0,0 +1,97 @@
+// 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.app.video_tutorials;
+
+import android.text.TextUtils;
+
+import org.chromium.base.ContextUtils;
+import org.chromium.chrome.browser.flags.ChromeFeatureList;
+import org.chromium.chrome.browser.init.BrowserParts;
+import org.chromium.chrome.browser.init.ChromeBrowserInitializer;
+import org.chromium.chrome.browser.init.EmptyBrowserParts;
+import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
+import org.chromium.chrome.browser.preferences.SharedPreferencesManager;
+import org.chromium.chrome.browser.profiles.Profile;
+import org.chromium.chrome.browser.video_tutorials.FeatureType;
+import org.chromium.chrome.browser.video_tutorials.Tutorial;
+import org.chromium.chrome.browser.video_tutorials.VideoTutorialService;
+import org.chromium.chrome.browser.video_tutorials.VideoTutorialServiceFactory;
+import org.chromium.chrome.browser.video_tutorials.metrics.VideoTutorialMetrics;
+import org.chromium.chrome.browser.video_tutorials.metrics.VideoTutorialMetrics.UserAction;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Helper class to handle share intents for video tutorials.
+ */
+public class VideoTutorialShareHelper {
+    /**
+     * Handles the URL, if it is a video tutorial share URL, otherwise returns false.
+     * @param url The URL being checked.
+     * @return True, if the URL was recognized as a video tutorial URL and handled, false otherwise.
+     */
+    public static boolean handleVideoTutorialURL(String url) {
+        Set<String> videoTutorialUrls = SharedPreferencesManager.getInstance().readStringSet(
+                ChromePreferenceKeys.VIDEO_TUTORIALS_SHARE_URL_SET, new HashSet<>());
+        if (!videoTutorialUrls.contains(url)) return false;
+        if (!ChromeFeatureList.isEnabled(ChromeFeatureList.VIDEO_TUTORIALS)) return false;
+
+        launchVideoPlayer(url);
+        return true;
+    }
+
+    /**
+     * Writes the video tutorial share URLs to shared preferences. This is later used to identify
+     * whether a URL is a video tutorial.
+     */
+    public static void saveUrlsToSharedPrefs() {
+        if (!ChromeFeatureList.isEnabled(ChromeFeatureList.VIDEO_TUTORIALS)) {
+            SharedPreferencesManager.getInstance().removeKey(
+                    ChromePreferenceKeys.VIDEO_TUTORIALS_SHARE_URL_SET);
+            return;
+        }
+
+        VideoTutorialService videoTutorialService =
+                VideoTutorialServiceFactory.getForProfile(Profile.getLastUsedRegularProfile());
+        videoTutorialService.getTutorials(tutorials -> {
+            Set<String> shareUrlSet = new HashSet<>();
+            for (Tutorial tutorial : tutorials) {
+                if (TextUtils.isEmpty(tutorial.shareUrl)) continue;
+                shareUrlSet.add(tutorial.shareUrl);
+            }
+            SharedPreferencesManager.getInstance().writeStringSet(
+                    ChromePreferenceKeys.VIDEO_TUTORIALS_SHARE_URL_SET, shareUrlSet);
+        });
+    }
+
+    private static void launchVideoPlayer(String shareUrl) {
+        assert !TextUtils.isEmpty(shareUrl);
+
+        BrowserParts parts = new EmptyBrowserParts() {
+            @Override
+            public void finishNativeInitialization() {
+                VideoTutorialService videoTutorialService =
+                        VideoTutorialServiceFactory.getForProfile(
+                                Profile.getLastUsedRegularProfile());
+                videoTutorialService.getTutorials(tutorials -> {
+                    for (Tutorial tutorial : tutorials) {
+                        if (TextUtils.equals(tutorial.shareUrl, shareUrl)) {
+                            VideoTutorialMetrics.recordUserAction(
+                                    tutorial.featureType, UserAction.OPEN_SHARED_VIDEO);
+                            VideoPlayerActivity.playVideoTutorial(
+                                    ContextUtils.getApplicationContext(), tutorial.featureType);
+                            return;
+                        }
+                    }
+                    VideoTutorialMetrics.recordUserAction(
+                            FeatureType.INVALID, UserAction.INVALID_SHARE_URL);
+                });
+            }
+        };
+        ChromeBrowserInitializer.getInstance().handlePreNativeStartup(parts);
+        ChromeBrowserInitializer.getInstance().handlePostNativeStartup(true, parts);
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbar.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbar.java
index 84119a78..0c9f02cf 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbar.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbar.java
@@ -746,6 +746,9 @@
         }
 
         @Override
+        public void onIncognitoStateChanged() {}
+
+        @Override
         public void updateLoadingState(boolean updateUrl) {
             if (updateUrl) onUrlChanged();
             updateStatusIcon();
@@ -831,9 +834,6 @@
         @Override
         public void revertChanges() {}
 
-        @Override
-        public void updateMicButtonState() {}
-
         @Nullable
         @Override
         public FakeboxDelegate getFakeboxDelegate() {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/init/ProcessInitializationHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/init/ProcessInitializationHandler.java
index ec39211..d694e11 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/init/ProcessInitializationHandler.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/init/ProcessInitializationHandler.java
@@ -36,6 +36,7 @@
 import org.chromium.chrome.browser.DefaultBrowserInfo;
 import org.chromium.chrome.browser.DeferredStartupHandler;
 import org.chromium.chrome.browser.DevToolsServer;
+import org.chromium.chrome.browser.app.video_tutorials.VideoTutorialShareHelper;
 import org.chromium.chrome.browser.banners.AppBannerManager;
 import org.chromium.chrome.browser.bookmarkswidget.BookmarkWidgetProvider;
 import org.chromium.chrome.browser.contacts_picker.ChromePickerAdapter;
@@ -434,6 +435,8 @@
                 () -> OfflineContentAvailabilityStatusProvider.getInstance());
         deferredStartupHandler.addDeferredTask(
                 () -> EnterpriseInfo.getInstance().logDeviceEnterpriseInfo());
+        deferredStartupHandler.addDeferredTask(
+                () -> VideoTutorialShareHelper.saveUrlsToSharedPrefs());
     }
 
     private void initChannelsAsync() {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/messages/MessageContainerCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/messages/MessageContainerCoordinator.java
index bdd3634..f98735d 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/messages/MessageContainerCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/messages/MessageContainerCoordinator.java
@@ -44,12 +44,12 @@
         mContainer.setLayoutParams(params);
     }
 
-    public void showMessageContainer() {
+    protected void showMessageContainer() {
         mContainer.setVisibility(View.VISIBLE);
         updateMargins();
     }
 
-    public void hideMessageContainer() {
+    protected void hideMessageContainer() {
         mContainer.setVisibility(View.GONE);
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBar.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBar.java
index 23dab75c..2028dd3 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBar.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBar.java
@@ -76,15 +76,12 @@
      */
     View getSecurityIconView();
 
-    /** Updates the state of the mic button if there is one. */
-    void updateMicButtonState();
 
     /** Returns the {@link VoiceRecognitionHandler} associated with this LocationBar. */
     @Nullable
     default VoiceRecognitionHandler getVoiceRecognitionHandler() {
         return null;
     }
-
     /**
      * Returns a (@link FakeboxDelegate}.
      *
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarCoordinator.java
index 901201f..d43a3bf2e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarCoordinator.java
@@ -243,11 +243,6 @@
         return mLocationBarLayout.getSecurityIconView();
     }
 
-    @Override
-    public void updateMicButtonState() {
-        mLocationBarLayout.updateMicButtonState();
-    }
-
     @Nullable
     @Override
     public View getViewForUrlBackFocus() {
@@ -339,6 +334,11 @@
         return isTablet() ? mLocationBarLayout : null;
     }
 
+    @Override
+    public void onIncognitoStateChanged() {
+        mLocationBarLayout.updateMicButtonState();
+    }
+
     /**
      * Returns the {@link LocationBarCoordinatorPhone} for this coordinator.
      *
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarDataProvider.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarDataProvider.java
index 865a79b..5f05b94 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarDataProvider.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarDataProvider.java
@@ -31,9 +31,10 @@
      */
     interface Observer {
         void onTitleChanged();
+        void onUrlChanged();
+        void onIncognitoStateChanged();
         // TODO(https://crbug.com/1139481): Add methods for other LocationBarDataProvider
         // data, e.g. NTP and security state.
-        void onUrlChanged();
     }
 
     /** Adds an observer of changes to LocationBarDataProvider's data. */
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/UrlBar.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/UrlBar.java
index ae727dc..73144f4e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/UrlBar.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/UrlBar.java
@@ -39,7 +39,6 @@
 import org.chromium.base.Log;
 import org.chromium.base.SysUtils;
 import org.chromium.base.ThreadUtils;
-import org.chromium.base.compat.ApiHelperForO;
 import org.chromium.base.metrics.RecordUserAction;
 import org.chromium.chrome.browser.WindowDelegate;
 import org.chromium.ui.KeyboardVisibilityDelegate;
@@ -288,14 +287,6 @@
             mPendingScroll = false;
         }
         fixupTextDirection();
-
-        if (!focused && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
-            // https://crbug.com/1103555: On Android Q+, the URL bar can trigger augmented
-            // Autofill, which disables ordinary Autofill requests for the duration of the
-            // Autofill session. This is worked around by canceling the session when the user
-            // focuses another view.
-            ApiHelperForO.cancelAutofillSession();
-        }
     }
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/UrlBarApi26.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/UrlBarApi26.java
index 1f1f191c..5f725b1 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/UrlBarApi26.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/UrlBarApi26.java
@@ -5,6 +5,7 @@
 package org.chromium.chrome.browser.omnibox;
 
 import android.content.Context;
+import android.os.Build;
 import android.util.AttributeSet;
 import android.view.ViewStructure;
 
@@ -31,4 +32,16 @@
         super.onProvideAutofillStructure(structure, autofillFlags);
         mRequestingAutofillStructure = false;
     }
+
+    @Override
+    public int getAutofillType() {
+        // https://crbug.com/1103555: Prevent augmented autofill service from taking over the
+        // session by disabling both standard and augmented autofill on versions of Android
+        // where both are supported.
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+            return AUTOFILL_TYPE_NONE;
+        } else {
+            return super.getAutofillType();
+        }
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/ChromePaymentRequestService.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/ChromePaymentRequestService.java
index f999e13..2d6bfd99 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/ChromePaymentRequestService.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/ChromePaymentRequestService.java
@@ -39,7 +39,6 @@
 import org.chromium.components.payments.PaymentRequestSpec;
 import org.chromium.components.payments.PaymentResponseHelperInterface;
 import org.chromium.components.payments.PaymentUIsObserver;
-import org.chromium.components.payments.PaymentValidator;
 import org.chromium.components.payments.Section;
 import org.chromium.components.payments.SkipToGPayHelper;
 import org.chromium.content_public.browser.RenderFrameHost;
@@ -82,7 +81,6 @@
 
     private final PaymentUiService mPaymentUiService;
     private final PaymentOptions mPaymentOptions;
-    private final boolean mRequestShipping;
     private boolean mWasRetryCalled;
 
     private boolean mHasClosed;
@@ -137,7 +135,6 @@
 
         mPaymentOptions = paymentRequestService.getPaymentOptions();
         assert mPaymentOptions != null;
-        mRequestShipping = mPaymentOptions.requestShipping;
 
         mPaymentRequestService = paymentRequestService;
         mPaymentUiService = new PaymentUiService(/*delegate=*/this,
@@ -641,25 +638,13 @@
         }
 
         mPaymentUiService.onPaymentRequestComplete(result,
-                /*onMinimalUiErroredAndClosed=*/this::close, onCompleteHandled::run);
+                /*onMinimalUiErroredAndClosed=*/this::close, onCompleteHandled);
     }
 
     // Implement BrowserPaymentRequest:
     @Override
     public void retry(PaymentValidationErrors errors) {
-        if (mPaymentRequestService == null) return;
-        // mSpec.retry() can be used only when mSpec has not been destroyed.
-        assert !mSpec.isDestroyed();
-
-        if (!PaymentValidator.validatePaymentValidationErrors(errors)) {
-            mJourneyLogger.setAborted(AbortReason.INVALID_DATA_FROM_RENDERER);
-            disconnectFromClientWithDebugMessage(ErrorStrings.INVALID_VALIDATION_ERRORS);
-            return;
-        }
-        mSpec.retry(errors);
-
         mWasRetryCalled = true;
-
         mPaymentUiService.onRetry(errors);
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/minimal/MinimalUICoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/minimal/MinimalUICoordinator.java
index 3359a5b..c53d9ec89 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/minimal/MinimalUICoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/minimal/MinimalUICoordinator.java
@@ -76,7 +76,7 @@
     /**
      * Shows the minimal UI.
      *
-     * @param chromeActivity  The activity where the UI should be shown.
+     * @param context         The context where the UI should be shown.
      * @param app             The app that contains the details to display and can be invoked
      *                        upon user confirmation.
      * @param formatter       Formats the account balance amount according to its currency.
@@ -137,7 +137,7 @@
      * has closed.
      * @param observer The observer to notify when the UI has closed.
      */
-    public void showCompleteAndClose(CompleteAndCloseObserver observer) {
+    private void showCompleteAndClose(CompleteAndCloseObserver observer) {
         mMediator.showCompleteAndClose(observer);
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/ui/PaymentUiService.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/ui/PaymentUiService.java
index 63447c0..e44ee78 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/ui/PaymentUiService.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/ui/PaymentUiService.java
@@ -224,7 +224,7 @@
         }
 
         /** A callback invoked when the Payment Request UI is closed. */
-        public void onPaymentRequestUiClosed() {
+        private void onPaymentRequestUiClosed() {
             assert mPaymentRequestUI == null;
             mShouldShowDialog = false;
         }
@@ -1215,7 +1215,7 @@
         mJourneyLogger.setNumberOfSuggestionsShown(
                 Section.SHIPPING_ADDRESS, addresses.size(), hasCompleteShippingAddress);
 
-        int missingFields = 0;
+        int missingFields;
         if (addresses.isEmpty()) {
             // All fields are missing.
             missingFields = AutofillAddress.CompletionStatus.INVALID_RECIPIENT
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabImpl.java
index 920b11a..ef9a257 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabImpl.java
@@ -452,9 +452,9 @@
 
             // We load the URL from the tab rather than directly from the ContentView so the tab has
             // a chance of using a prerenderer page is any.
-            int loadType = TabImplJni.get().loadUrl(mNativeTabAndroid, TabImpl.this,
-                    params.getUrl(), params.getInitiatorOrigin(), params.getVerbatimHeaders(),
-                    params.getPostData(), params.getTransitionType(),
+            int loadType = TabImplJni.get().loadUrl(mNativeTabAndroid, params.getUrl(),
+                    params.getInitiatorOrigin(), params.getVerbatimHeaders(), params.getPostData(),
+                    params.getTransitionType(),
                     params.getReferrer() != null ? params.getReferrer().getUrl() : null,
                     // Policy will be ignored for null referrer url, 0 is just a placeholder.
                     // TODO(ppi): Should we pass Referrer jobject and add JNI methods to read it
@@ -680,7 +680,7 @@
         // destroying the native tab cleanups up any remaining infobars. The infobar container
         // expects all infobars to be cleaned up before its own destruction.
         if (mNativeTabAndroid != 0) {
-            TabImplJni.get().destroy(mNativeTabAndroid, TabImpl.this);
+            TabImplJni.get().destroy(mNativeTabAndroid);
             assert mNativeTabAndroid == 0;
         }
     }
@@ -1029,8 +1029,8 @@
      */
     void pushNativePageStateToNavigationEntry() {
         assert mNativeTabAndroid != 0 && getNativePage() != null;
-        TabImplJni.get().setActiveNavigationEntryTitleForUrl(mNativeTabAndroid, TabImpl.this,
-                getNativePage().getUrl(), getNativePage().getTitle());
+        TabImplJni.get().setActiveNavigationEntryTitleForUrl(
+                mNativeTabAndroid, getNativePage().getUrl(), getNativePage().getTitle());
     }
 
     /**
@@ -1063,7 +1063,7 @@
      */
     void loadOriginalImage() {
         if (mNativeTabAndroid != 0) {
-            TabImplJni.get().loadOriginalImage(mNativeTabAndroid, TabImpl.this);
+            TabImplJni.get().loadOriginalImage(mNativeTabAndroid);
         }
     }
 
@@ -1198,7 +1198,7 @@
             if (bounds != null) {
                 assert mNativeTabAndroid != 0;
                 TabImplJni.get().onPhysicalBackingSizeChanged(
-                        mNativeTabAndroid, TabImpl.this, webContents, bounds.right, bounds.bottom);
+                        mNativeTabAndroid, webContents, bounds.right, bounds.bottom);
             }
             webContents.onShow();
             initWebContents(webContents);
@@ -1287,8 +1287,8 @@
             mWebContentsDelegate = createWebContentsDelegate();
 
             assert mNativeTabAndroid != 0;
-            TabImplJni.get().initWebContents(mNativeTabAndroid, TabImpl.this, mIncognito,
-                    isDetached(this), webContents, mSourceTabId, mWebContentsDelegate,
+            TabImplJni.get().initWebContents(mNativeTabAndroid, mIncognito, isDetached(this),
+                    webContents, mSourceTabId, mWebContentsDelegate,
                     new TabContextMenuPopulatorFactory(
                             mDelegateFactory.createContextMenuPopulatorFactory(this), this));
 
@@ -1352,7 +1352,7 @@
 
         WebContents webContents = getWebContents();
         if (webContents != null) {
-            TabImplJni.get().updateDelegates(mNativeTabAndroid, TabImpl.this, mWebContentsDelegate,
+            TabImplJni.get().updateDelegates(mNativeTabAndroid, mWebContentsDelegate,
                     new TabContextMenuPopulatorFactory(
                             mDelegateFactory.createContextMenuPopulatorFactory(this), this));
             webContents.notifyRendererPreferenceUpdate();
@@ -1520,9 +1520,9 @@
         if (deleteNativeWebContents) {
             // Destruction of the native WebContents will call back into Java to destroy the Java
             // WebContents.
-            TabImplJni.get().destroyWebContents(mNativeTabAndroid, TabImpl.this);
+            TabImplJni.get().destroyWebContents(mNativeTabAndroid);
         } else {
-            TabImplJni.get().releaseWebContents(mNativeTabAndroid, TabImpl.this);
+            TabImplJni.get().releaseWebContents(mNativeTabAndroid);
             // Since the native WebContents is still alive, we need to clear its reference to the
             // Java WebContents. While doing so, it will also call back into Java to destroy the
             // Java WebContents.
@@ -1534,27 +1534,24 @@
     interface Natives {
         TabImpl fromWebContents(WebContents webContents);
         void init(TabImpl caller);
-        void destroy(long nativeTabAndroid, TabImpl caller);
-        void initWebContents(long nativeTabAndroid, TabImpl caller, boolean incognito,
-                boolean isBackgroundTab, WebContents webContents, int parentTabId,
+        void destroy(long nativeTabAndroid);
+        void initWebContents(long nativeTabAndroid, boolean incognito, boolean isBackgroundTab,
+                WebContents webContents, int parentTabId,
                 TabWebContentsDelegateAndroidImpl delegate,
                 ContextMenuPopulatorFactory contextMenuPopulatorFactory);
-        void updateDelegates(long nativeTabAndroid, TabImpl caller,
-                TabWebContentsDelegateAndroidImpl delegate,
+        void updateDelegates(long nativeTabAndroid, TabWebContentsDelegateAndroidImpl delegate,
                 ContextMenuPopulatorFactory contextMenuPopulatorFactory);
-        void destroyWebContents(long nativeTabAndroid, TabImpl caller);
-        void releaseWebContents(long nativeTabAndroid, TabImpl caller);
-        void onPhysicalBackingSizeChanged(long nativeTabAndroid, TabImpl caller,
-                WebContents webContents, int width, int height);
-        int loadUrl(long nativeTabAndroid, TabImpl caller, String url, Origin initiatorOrigin,
-                String extraHeaders, ResourceRequestBody postData, int transition,
-                String referrerUrl, int referrerPolicy, boolean isRendererInitiated,
-                boolean shoulReplaceCurrentEntry, boolean hasUserGesture,
-                boolean shouldClearHistoryList, long inputStartTimestamp,
+        void destroyWebContents(long nativeTabAndroid);
+        void releaseWebContents(long nativeTabAndroid);
+        void onPhysicalBackingSizeChanged(
+                long nativeTabAndroid, WebContents webContents, int width, int height);
+        int loadUrl(long nativeTabAndroid, String url, Origin initiatorOrigin, String extraHeaders,
+                ResourceRequestBody postData, int transition, String referrerUrl,
+                int referrerPolicy, boolean isRendererInitiated, boolean shoulReplaceCurrentEntry,
+                boolean hasUserGesture, boolean shouldClearHistoryList, long inputStartTimestamp,
                 long intentReceivedTimestamp);
-        void setActiveNavigationEntryTitleForUrl(
-                long nativeTabAndroid, TabImpl caller, String url, String title);
-        void loadOriginalImage(long nativeTabAndroid, TabImpl caller);
+        void setActiveNavigationEntryTitleForUrl(long nativeTabAndroid, String url, String title);
+        void loadOriginalImage(long nativeTabAndroid);
         void setAddApi2TransitionToFutureNavigations(long nativeTabAndroid, boolean shouldAdd);
         boolean getAddApi2TransitionToFutureNavigations(long nativeTabAndroid);
         void setHideFutureNavigations(long nativeTabAndroid, boolean hide);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/LocationBarModel.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/LocationBarModel.java
index 8eda190..b26a0873 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/LocationBarModel.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/LocationBarModel.java
@@ -106,6 +106,7 @@
 
     /**
      * Sets the tab that contains the information to be displayed in the toolbar.
+     *
      * @param tab The tab associated currently with the toolbar.
      * @param isIncognito Whether the incognito model is currently selected, which must match the
      *                    passed in tab if non-null.
@@ -113,7 +114,10 @@
     public void setTab(Tab tab, boolean isIncognito) {
         assert tab == null || tab.isIncognito() == isIncognito;
         mTab = tab;
-        mIsIncognito = isIncognito;
+        if (mIsIncognito != isIncognito) {
+            mIsIncognito = isIncognito;
+            notifyIncognitoStateChanged();
+        }
         updateUsingBrandColor();
         notifyTitleChanged();
         notifyUrlChanged();
@@ -285,6 +289,12 @@
         return mIsIncognito;
     }
 
+    private void notifyIncognitoStateChanged() {
+        for (LocationBarDataProvider.Observer observer : mLocationBarDataObservers) {
+            observer.onIncognitoStateChanged();
+        }
+    }
+
     /**
      * @return Whether the location bar is showing in overview mode. If the location bar should not
      *  currently be showing in overview mode, returns false.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarLayout.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarLayout.java
index 9e92c68..8e5d63f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarLayout.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarLayout.java
@@ -525,7 +525,6 @@
      */
     void onTabOrModelChanged() {
         mTabOrModelChangeRunnable.run();
-        getLocationBar().updateMicButtonState();
     }
 
     /**
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/app/appmenu/OverviewAppMenuTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/app/appmenu/OverviewAppMenuTest.java
index 18af3eb..0044bc3 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/app/appmenu/OverviewAppMenuTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/app/appmenu/OverviewAppMenuTest.java
@@ -23,7 +23,6 @@
 import org.chromium.base.test.util.Feature;
 import org.chromium.base.test.util.Restriction;
 import org.chromium.chrome.R;
-import org.chromium.chrome.browser.flags.CachedFeatureFlags;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.browser.ui.appmenu.AppMenuTestSupport;
@@ -57,9 +56,9 @@
     @SmallTest
     @Feature({"Browser", "Main"})
     @Features.EnableFeatures({ChromeFeatureList.TAB_GROUPS_ANDROID})
+    @Features.DisableFeatures({ChromeFeatureList.START_SURFACE_ANDROID})
     public void testAllMenuItemsWithoutStartSurface() throws Exception {
         TestThreadUtils.runOnUiThreadBlocking(() -> {
-            CachedFeatureFlags.setForTesting(ChromeFeatureList.START_SURFACE_ANDROID, false);
             AppMenuTestSupport.showAppMenu(mActivityTestRule.getAppMenuCoordinator(), null, false);
         });
 
@@ -74,8 +73,10 @@
                         || itemId == R.id.new_incognito_tab_menu_id
                         || itemId == R.id.close_all_tabs_menu_id
                         || itemId == R.id.close_all_incognito_tabs_menu_id
-                        || itemId == R.id.menu_group_tabs || itemId == R.id.preferences_id);
-                if (itemId == R.id.close_all_incognito_tabs_menu_id) {
+                        || itemId == R.id.menu_group_tabs || itemId == R.id.track_prices_row_menu_id
+                        || itemId == R.id.preferences_id);
+                if (itemId == R.id.close_all_incognito_tabs_menu_id
+                        || itemId == R.id.track_prices_row_menu_id) {
                     assertFalse(item.isVisible());
                 } else {
                     assertTrue(item.isVisible());
@@ -83,17 +84,17 @@
                 checkedMenuItems++;
             }
         }
-        assertThat(checkedMenuItems, equalTo(6));
+        assertThat(checkedMenuItems, equalTo(7));
     }
 
     @Test
     @SmallTest
     @Feature({"Browser", "Main"})
     @Features.EnableFeatures({ChromeFeatureList.TAB_GROUPS_ANDROID})
+    @Features.DisableFeatures({ChromeFeatureList.START_SURFACE_ANDROID})
     public void testIncognitoAllMenuItemsWithoutStartSurface() throws Exception {
         TestThreadUtils.runOnUiThreadBlocking(() -> {
             mActivityTestRule.getActivity().getTabModelSelector().selectModel(true);
-            CachedFeatureFlags.setForTesting(ChromeFeatureList.START_SURFACE_ANDROID, false);
             AppMenuTestSupport.showAppMenu(mActivityTestRule.getAppMenuCoordinator(), null, false);
         });
 
@@ -108,8 +109,10 @@
                         || itemId == R.id.new_incognito_tab_menu_id
                         || itemId == R.id.close_all_tabs_menu_id
                         || itemId == R.id.close_all_incognito_tabs_menu_id
-                        || itemId == R.id.menu_group_tabs || itemId == R.id.preferences_id);
-                if (itemId == R.id.close_all_tabs_menu_id) {
+                        || itemId == R.id.menu_group_tabs || itemId == R.id.track_prices_row_menu_id
+                        || itemId == R.id.preferences_id);
+                if (itemId == R.id.close_all_tabs_menu_id
+                        || itemId == R.id.track_prices_row_menu_id) {
                     assertFalse(item.isVisible());
                 } else {
                     assertTrue(item.isVisible());
@@ -117,16 +120,16 @@
                 checkedMenuItems++;
             }
         }
-        assertThat(checkedMenuItems, equalTo(6));
+        assertThat(checkedMenuItems, equalTo(7));
     }
 
     @Test
     @SmallTest
     @Feature({"Browser", "Main"})
-    @Features.EnableFeatures({ChromeFeatureList.TAB_GROUPS_ANDROID})
+    @Features.
+    EnableFeatures({ChromeFeatureList.START_SURFACE_ANDROID, ChromeFeatureList.TAB_GROUPS_ANDROID})
     public void testAllMenuItemsWithStartSurface() throws Exception {
         TestThreadUtils.runOnUiThreadBlocking(() -> {
-            CachedFeatureFlags.setForTesting(ChromeFeatureList.START_SURFACE_ANDROID, true);
             AppMenuTestSupport.showAppMenu(mActivityTestRule.getAppMenuCoordinator(), null, false);
         });
 
@@ -143,8 +146,10 @@
                         || itemId == R.id.recent_tabs_menu_id || itemId == R.id.open_history_menu_id
                         || itemId == R.id.downloads_menu_id || itemId == R.id.close_all_tabs_menu_id
                         || itemId == R.id.close_all_incognito_tabs_menu_id
-                        || itemId == R.id.menu_group_tabs || itemId == R.id.preferences_id);
-                if (itemId == R.id.close_all_incognito_tabs_menu_id) {
+                        || itemId == R.id.menu_group_tabs || itemId == R.id.track_prices_row_menu_id
+                        || itemId == R.id.preferences_id);
+                if (itemId == R.id.close_all_incognito_tabs_menu_id
+                        || itemId == R.id.track_prices_row_menu_id) {
                     assertFalse(item.isVisible());
                 } else {
                     assertTrue(item.isVisible());
@@ -152,17 +157,17 @@
                 checkedMenuItems++;
             }
         }
-        assertThat(checkedMenuItems, equalTo(10));
+        assertThat(checkedMenuItems, equalTo(11));
     }
 
     @Test
     @SmallTest
     @Feature({"Browser", "Main"})
-    @Features.EnableFeatures({ChromeFeatureList.TAB_GROUPS_ANDROID})
+    @Features.
+    EnableFeatures({ChromeFeatureList.START_SURFACE_ANDROID, ChromeFeatureList.TAB_GROUPS_ANDROID})
     public void testIncognitoAllMenuItemsWithStartSurface() throws Exception {
         TestThreadUtils.runOnUiThreadBlocking(() -> {
             mActivityTestRule.getActivity().getTabModelSelector().selectModel(true);
-            CachedFeatureFlags.setForTesting(ChromeFeatureList.START_SURFACE_ANDROID, true);
             AppMenuTestSupport.showAppMenu(mActivityTestRule.getAppMenuCoordinator(), null, false);
         });
 
@@ -179,8 +184,10 @@
                         || itemId == R.id.recent_tabs_menu_id || itemId == R.id.open_history_menu_id
                         || itemId == R.id.downloads_menu_id || itemId == R.id.close_all_tabs_menu_id
                         || itemId == R.id.close_all_incognito_tabs_menu_id
-                        || itemId == R.id.menu_group_tabs || itemId == R.id.preferences_id);
-                if (itemId == R.id.close_all_tabs_menu_id || itemId == R.id.recent_tabs_menu_id) {
+                        || itemId == R.id.menu_group_tabs || itemId == R.id.track_prices_row_menu_id
+                        || itemId == R.id.preferences_id);
+                if (itemId == R.id.close_all_tabs_menu_id || itemId == R.id.recent_tabs_menu_id
+                        || itemId == R.id.track_prices_row_menu_id) {
                     assertFalse(item.isVisible());
                 } else {
                     assertTrue(item.isVisible());
@@ -188,16 +195,16 @@
                 checkedMenuItems++;
             }
         }
-        assertThat(checkedMenuItems, equalTo(10));
+        assertThat(checkedMenuItems, equalTo(11));
     }
 
     @Test
     @SmallTest
     @Feature({"Browser", "Main"})
-    @Features.DisableFeatures({ChromeFeatureList.TAB_GROUPS_ANDROID})
+    @Features.
+    DisableFeatures({ChromeFeatureList.START_SURFACE_ANDROID, ChromeFeatureList.TAB_GROUPS_ANDROID})
     public void testGroupTabsIsDisabled() throws Exception {
         TestThreadUtils.runOnUiThreadBlocking(() -> {
-            CachedFeatureFlags.setForTesting(ChromeFeatureList.START_SURFACE_ANDROID, false);
             AppMenuTestSupport.showAppMenu(mActivityTestRule.getAppMenuCoordinator(), null, false);
         });
 
@@ -217,9 +224,9 @@
     @SmallTest
     @Feature({"Browser", "Main"})
     @Features.EnableFeatures({ChromeFeatureList.TAB_GROUPS_ANDROID})
+    @Features.DisableFeatures({ChromeFeatureList.START_SURFACE_ANDROID})
     public void testGroupTabsIsEnabled() throws Exception {
         TestThreadUtils.runOnUiThreadBlocking(() -> {
-            CachedFeatureFlags.setForTesting(ChromeFeatureList.START_SURFACE_ANDROID, false);
             AppMenuTestSupport.showAppMenu(mActivityTestRule.getAppMenuCoordinator(), null, false);
         });
 
@@ -244,10 +251,10 @@
     @Test
     @SmallTest
     @Feature({"Browser", "Main"})
+    @Features.EnableFeatures({ChromeFeatureList.START_SURFACE_ANDROID})
     @Features.DisableFeatures({ChromeFeatureList.TAB_GROUPS_ANDROID})
     public void testGroupTabsIsDisabledWithStartSurface() throws Exception {
         TestThreadUtils.runOnUiThreadBlocking(() -> {
-            CachedFeatureFlags.setForTesting(ChromeFeatureList.START_SURFACE_ANDROID, true);
             AppMenuTestSupport.showAppMenu(mActivityTestRule.getAppMenuCoordinator(), null, false);
         });
 
@@ -266,10 +273,10 @@
     @Test
     @SmallTest
     @Feature({"Browser", "Main"})
-    @Features.EnableFeatures({ChromeFeatureList.TAB_GROUPS_ANDROID})
+    @Features.
+    EnableFeatures({ChromeFeatureList.START_SURFACE_ANDROID, ChromeFeatureList.TAB_GROUPS_ANDROID})
     public void testGroupTabsIsEnabledWithStartSurface() throws Exception {
         TestThreadUtils.runOnUiThreadBlocking(() -> {
-            CachedFeatureFlags.setForTesting(ChromeFeatureList.START_SURFACE_ANDROID, true);
             AppMenuTestSupport.showAppMenu(mActivityTestRule.getAppMenuCoordinator(), null, false);
         });
 
@@ -290,4 +297,116 @@
         }
         assertThat(checkedMenuItems, equalTo(2));
     }
+
+    @Test
+    @SmallTest
+    @Feature({"Browser", "Main"})
+    @Features.EnableFeatures({ChromeFeatureList.TAB_GRID_LAYOUT_ANDROID + "<Study"})
+    @Features.DisableFeatures({ChromeFeatureList.START_SURFACE_ANDROID})
+    @CommandLineFlags.Add({"force-fieldtrials=Study/Group",
+            "force-fieldtrial-params=Study.Group:enable_price_tracking/false"})
+    public void
+    testTrackPriceOnTabsIsDisabled() throws Exception {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            AppMenuTestSupport.showAppMenu(mActivityTestRule.getAppMenuCoordinator(), null, false);
+        });
+
+        int checkedMenuItems = 0;
+        Menu menu = mActivityTestRule.getMenu();
+        for (int i = 0; i < menu.size(); ++i) {
+            MenuItem item = menu.getItem(i);
+            if (item.getItemId() == R.id.track_prices_row_menu_id) {
+                assertFalse(item.isVisible());
+                checkedMenuItems++;
+            }
+        }
+        assertThat(checkedMenuItems, equalTo(2));
+    }
+
+    @Test
+    @SmallTest
+    @Feature({"Browser", "Main"})
+    @Features.EnableFeatures({ChromeFeatureList.TAB_GRID_LAYOUT_ANDROID + "<Study"})
+    @Features.DisableFeatures({ChromeFeatureList.START_SURFACE_ANDROID})
+    @CommandLineFlags.Add({"force-fieldtrials=Study/Group",
+            "force-fieldtrial-params=Study.Group:enable_price_tracking/true"})
+    public void
+    testTrackPriceOnTabsIsEnabled() throws Exception {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            AppMenuTestSupport.showAppMenu(mActivityTestRule.getAppMenuCoordinator(), null, false);
+        });
+
+        int checkedMenuItems = 0;
+        Menu menu = mActivityTestRule.getMenu();
+        for (int i = 0; i < menu.size(); ++i) {
+            MenuItem item = menu.getItem(i);
+            if (item.getItemId() == R.id.track_prices_row_menu_id) {
+                int itemGroupId = item.getGroupId();
+                if (itemGroupId == R.id.OVERVIEW_MODE_MENU) {
+                    assertTrue(item.isVisible());
+                }
+                if (itemGroupId == R.id.START_SURFACE_MODE_MENU) {
+                    assertFalse(item.isVisible());
+                }
+                checkedMenuItems++;
+            }
+        }
+        assertThat(checkedMenuItems, equalTo(2));
+    }
+
+    @Test
+    @SmallTest
+    @Feature({"Browser", "Main"})
+    @Features.EnableFeatures({ChromeFeatureList.START_SURFACE_ANDROID,
+            ChromeFeatureList.TAB_GRID_LAYOUT_ANDROID + "<Study"})
+    @CommandLineFlags.Add({"force-fieldtrials=Study/Group",
+            "force-fieldtrial-params=Study.Group:enable_price_tracking/false"})
+    public void
+    testTrackPriceOnTabsIsDisabledWithStartSurface() throws Exception {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            AppMenuTestSupport.showAppMenu(mActivityTestRule.getAppMenuCoordinator(), null, false);
+        });
+
+        int checkedMenuItems = 0;
+        Menu menu = mActivityTestRule.getMenu();
+        for (int i = 0; i < menu.size(); ++i) {
+            MenuItem item = menu.getItem(i);
+            if (item.getItemId() == R.id.track_prices_row_menu_id) {
+                assertFalse(item.isVisible());
+                checkedMenuItems++;
+            }
+        }
+        assertThat(checkedMenuItems, equalTo(2));
+    }
+
+    @Test
+    @SmallTest
+    @Feature({"Browser", "Main"})
+    @Features.EnableFeatures({ChromeFeatureList.START_SURFACE_ANDROID,
+            ChromeFeatureList.TAB_GRID_LAYOUT_ANDROID + "<Study"})
+    @CommandLineFlags.Add({"force-fieldtrials=Study/Group",
+            "force-fieldtrial-params=Study.Group:enable_price_tracking/true"})
+    public void
+    testTrackPriceOnTabsIsEnabledWithStartSurface() throws Exception {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            AppMenuTestSupport.showAppMenu(mActivityTestRule.getAppMenuCoordinator(), null, false);
+        });
+
+        int checkedMenuItems = 0;
+        Menu menu = mActivityTestRule.getMenu();
+        for (int i = 0; i < menu.size(); ++i) {
+            MenuItem item = menu.getItem(i);
+            if (item.getItemId() == R.id.track_prices_row_menu_id) {
+                int itemGroupId = item.getGroupId();
+                if (itemGroupId == R.id.OVERVIEW_MODE_MENU) {
+                    assertFalse(item.isVisible());
+                }
+                if (itemGroupId == R.id.START_SURFACE_MODE_MENU) {
+                    assertTrue(item.isVisible());
+                }
+                checkedMenuItems++;
+            }
+        }
+        assertThat(checkedMenuItems, equalTo(2));
+    }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/LocationBarModelTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/LocationBarModelTest.java
index ae3ea94..87aaf3b 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/LocationBarModelTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/LocationBarModelTest.java
@@ -4,6 +4,12 @@
 
 package org.chromium.chrome.browser.toolbar;
 
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
 import android.support.test.InstrumentationRegistry;
 
 import androidx.test.filters.MediumTest;
@@ -16,25 +22,34 @@
 import org.junit.runner.RunWith;
 
 import org.chromium.base.ContextUtils;
+import org.chromium.base.test.params.ParameterAnnotations;
+import org.chromium.base.test.params.ParameterProvider;
+import org.chromium.base.test.params.ParameterSet;
+import org.chromium.base.test.params.ParameterizedRunner;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ChromeTabbedActivity;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
+import org.chromium.chrome.browser.omnibox.LocationBarDataProvider;
 import org.chromium.chrome.browser.omnibox.UrlBarData;
 import org.chromium.chrome.browser.tab.MockTab;
 import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.tab.TabSelectionType;
 import org.chromium.chrome.browser.toolbar.top.ToolbarLayout;
-import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
 import org.chromium.chrome.test.util.ChromeTabUtils;
 import org.chromium.components.embedder_support.util.UrlConstants;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
 /**
  * Tests for LocationBarModel.
  */
-@RunWith(ChromeJUnit4ClassRunner.class)
+@RunWith(ParameterizedRunner.class)
 @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
 public class LocationBarModelTest {
     @Rule
@@ -57,9 +72,9 @@
                 getCurrentTabId(mActivityTestRule.getActivity()));
         ChromeTabUtils.closeCurrentTab(
                 InstrumentationRegistry.getInstrumentation(), mActivityTestRule.getActivity());
-        Assert.assertEquals("Didn't close all tabs.", 0,
+        assertEquals("Didn't close all tabs.", 0,
                 ChromeTabUtils.getNumOpenTabs(mActivityTestRule.getActivity()));
-        Assert.assertEquals("LocationBarModel is still trying to show a tab.", Tab.INVALID_TAB_ID,
+        assertEquals("LocationBarModel is still trying to show a tab.", Tab.INVALID_TAB_ID,
                 getCurrentTabId(mActivityTestRule.getActivity()));
     }
 
@@ -88,13 +103,107 @@
         });
     }
 
+    /** Provides parameters for different types of transitions between tabs. */
+    public static class IncognitoTransitionParamProvider implements ParameterProvider {
+        @Override
+        public Iterable<ParameterSet> getParameters() {
+            List<ParameterSet> result = new ArrayList<>(8);
+            for (boolean fromIncognito : Arrays.asList(true, false)) {
+                for (boolean toIncognito : Arrays.asList(true, false)) {
+                    result.add(new ParameterSet()
+                                       .value(fromIncognito, toIncognito)
+                                       .name(String.format(
+                                               "from_%b_to_%b", fromIncognito, toIncognito)));
+                }
+            }
+            return result;
+        }
+    }
+
+    @Test
+    @MediumTest
+    @ParameterAnnotations.UseMethodParameter(IncognitoTransitionParamProvider.class)
+    public void testOnIncognitoStateChange_switchTab(boolean fromIncognito, boolean toIncognito) {
+        // Add a regular tab next to the one created in setup.
+        mActivityTestRule.loadUrlInNewTab("about:blank", /*incognito=*/false);
+        // Add two incognito tabs.
+        mActivityTestRule.loadUrlInNewTab("about:blank", /*incognito=*/true);
+        mActivityTestRule.loadUrlInNewTab("about:blank", /*incognito=*/true);
+
+        ChromeTabbedActivity activity = mActivityTestRule.getActivity();
+        LocationBarModel locationBarModel =
+                activity.getToolbarManager().getLocationBarModelForTesting();
+        LocationBarDataProvider.Observer observer = mock(LocationBarDataProvider.Observer.class);
+        doAnswer((invocation) -> {
+            assertEquals(toIncognito, locationBarModel.isIncognito());
+            return null;
+        })
+                .when(observer)
+                .onIncognitoStateChanged();
+
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            mActivityTestRule.getActivity().getTabModelSelector().selectModel(fromIncognito);
+            locationBarModel.addObserver(observer);
+
+            // Switch to an existing tab.
+            mActivityTestRule.getActivity().getTabModelSelector().selectModel(/*incognito=*/
+                    toIncognito);
+            mActivityTestRule.getActivity().getTabModelSelector().getCurrentModel().setIndex(
+                    0, TabSelectionType.FROM_USER);
+        });
+
+        assertEquals(toIncognito, locationBarModel.isIncognito());
+        if (fromIncognito != toIncognito) {
+            verify(observer).onIncognitoStateChanged();
+        } else {
+            verify(observer, times(0)).onIncognitoStateChanged();
+        }
+    }
+
+    @Test
+    @MediumTest
+    @ParameterAnnotations.UseMethodParameter(IncognitoTransitionParamProvider.class)
+    public void testOnIncognitoStateChange_newTab(boolean fromIncognito, boolean toIncognito) {
+        // Add a regular tab next to the one created in setup.
+        mActivityTestRule.loadUrlInNewTab("about:blank", /*incognito=*/false);
+        // Add two incognito tabs.
+        mActivityTestRule.loadUrlInNewTab("about:blank", /*incognito=*/true);
+        mActivityTestRule.loadUrlInNewTab("about:blank", /*incognito=*/true);
+
+        ChromeTabbedActivity activity = mActivityTestRule.getActivity();
+        LocationBarModel locationBarModel =
+                activity.getToolbarManager().getLocationBarModelForTesting();
+        LocationBarDataProvider.Observer observer = mock(LocationBarDataProvider.Observer.class);
+        doAnswer((invocation) -> {
+            assertEquals(toIncognito, locationBarModel.isIncognito());
+            return null;
+        })
+                .when(observer)
+                .onIncognitoStateChanged();
+
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            mActivityTestRule.getActivity().getTabModelSelector().selectModel(fromIncognito);
+            locationBarModel.addObserver(observer);
+        });
+
+        // Switch to a new tab.
+        mActivityTestRule.loadUrlInNewTab("about:blank", toIncognito);
+
+        assertEquals(toIncognito, locationBarModel.isIncognito());
+        if (fromIncognito != toIncognito) {
+            verify(observer).onIncognitoStateChanged();
+        } else {
+            verify(observer, times(0)).onIncognitoStateChanged();
+        }
+    }
+
     private void assertDisplayAndEditText(
             ToolbarDataProvider dataProvider, String displayText, String editText) {
         TestThreadUtils.runOnUiThreadBlocking(() -> {
             UrlBarData urlBarData = dataProvider.getUrlBarData();
-            Assert.assertEquals(
+            assertEquals(
                     "Display text did not match", displayText, urlBarData.displayText.toString());
-            Assert.assertEquals("Editing text did not match", editText, urlBarData.editingText);
+            assertEquals("Editing text did not match", editText, urlBarData.editingText);
         });
     }
 
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 3535928d..086c720d 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -4666,6 +4666,8 @@
       "win/titlebar_config.h",
       "win/ui_automation_util.cc",
       "win/ui_automation_util.h",
+      "win/uninstallation_via_os_settings.cc",
+      "win/uninstallation_via_os_settings.h",
       "win/util_win_service.cc",
       "win/util_win_service.h",
     ]
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index eeffe355..2d0ef27 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -56,7 +56,6 @@
 #include "chrome/browser/unexpire_flags.h"
 #include "chrome/browser/unexpire_flags_gen.h"
 #include "chrome/browser/video_tutorials/switches.h"
-#include "chrome/browser/web_applications/components/external_app_install_features.h"
 #include "chrome/common/buildflags.h"
 #include "chrome/common/channel_info.h"
 #include "chrome/common/chrome_content_client.h"
@@ -3752,7 +3751,7 @@
 #if defined(OS_CHROMEOS)
     {"camera-system-web-app", flag_descriptions::kCameraSystemWebAppName,
      flag_descriptions::kCameraSystemWebAppDescription, kOsCrOS,
-     FEATURE_VALUE_TYPE(web_app::kCameraSystemWebApp)},
+     FEATURE_VALUE_TYPE(chromeos::features::kCameraSystemWebApp)},
     {"crostini-gpu-support", flag_descriptions::kCrostiniGpuSupportName,
      flag_descriptions::kCrostiniGpuSupportDescription, kOsCrOS,
      FEATURE_VALUE_TYPE(chromeos::features::kCrostiniGpuSupport)},
diff --git a/chrome/browser/android/tab_android.cc b/chrome/browser/android/tab_android.cc
index 03cdb2a..6b029714 100644
--- a/chrome/browser/android/tab_android.cc
+++ b/chrome/browser/android/tab_android.cc
@@ -240,13 +240,12 @@
   observers_.RemoveObserver(observer);
 }
 
-void TabAndroid::Destroy(JNIEnv* env, const JavaParamRef<jobject>& obj) {
+void TabAndroid::Destroy(JNIEnv* env) {
   delete this;
 }
 
 void TabAndroid::InitWebContents(
     JNIEnv* env,
-    const JavaParamRef<jobject>& obj,
     jboolean incognito,
     jboolean is_background_tab,
     const JavaParamRef<jobject>& jweb_contents,
@@ -292,7 +291,6 @@
 
 void TabAndroid::UpdateDelegates(
     JNIEnv* env,
-    const JavaParamRef<jobject>& obj,
     const JavaParamRef<jobject>& jweb_contents_delegate,
     const JavaParamRef<jobject>& jcontext_menu_populator_factory) {
   ContextMenuHelper::FromWebContents(web_contents())
@@ -314,8 +312,7 @@
 }
 }  // namespace
 
-void TabAndroid::DestroyWebContents(JNIEnv* env,
-                                    const JavaParamRef<jobject>& obj) {
+void TabAndroid::DestroyWebContents(JNIEnv* env) {
   WillRemoveWebContentsFromTab(web_contents());
 
   // Terminate the renderer process if this is the last tab.
@@ -335,8 +332,7 @@
   synced_tab_delegate_->ResetWebContents();
 }
 
-void TabAndroid::ReleaseWebContents(JNIEnv* env,
-                                    const JavaParamRef<jobject>& obj) {
+void TabAndroid::ReleaseWebContents(JNIEnv* env) {
   WillRemoveWebContentsFromTab(web_contents());
 
   // Ownership of |released_contents| is assumed by the code that initiated the
@@ -352,7 +348,6 @@
 
 void TabAndroid::OnPhysicalBackingSizeChanged(
     JNIEnv* env,
-    const JavaParamRef<jobject>& obj,
     const JavaParamRef<jobject>& jweb_contents,
     jint width,
     jint height) {
@@ -364,7 +359,6 @@
 
 TabAndroid::TabLoadStatus TabAndroid::LoadUrl(
     JNIEnv* env,
-    const JavaParamRef<jobject>& obj,
     const JavaParamRef<jstring>& url,
     const JavaParamRef<jobject>& j_initiator_origin,
     const JavaParamRef<jstring>& j_extra_headers,
@@ -441,7 +435,6 @@
 
 void TabAndroid::SetActiveNavigationEntryTitleForUrl(
     JNIEnv* env,
-    const JavaParamRef<jobject>& obj,
     const JavaParamRef<jstring>& jurl,
     const JavaParamRef<jstring>& jtitle) {
   DCHECK(web_contents());
@@ -460,8 +453,7 @@
     entry->SetTitle(title);
 }
 
-void TabAndroid::LoadOriginalImage(JNIEnv* env,
-                                   const JavaParamRef<jobject>& obj) {
+void TabAndroid::LoadOriginalImage(JNIEnv* env) {
   content::RenderFrameHost* render_frame_host =
       web_contents()->GetFocusedFrame();
   mojo::AssociatedRemote<chrome::mojom::ChromeRenderFrame> renderer;
diff --git a/chrome/browser/android/tab_android.h b/chrome/browser/android/tab_android.h
index fa2aeab06..c8f0ba2d 100644
--- a/chrome/browser/android/tab_android.h
+++ b/chrome/browser/android/tab_android.h
@@ -128,10 +128,9 @@
 
   // Methods called from Java via JNI -----------------------------------------
 
-  void Destroy(JNIEnv* env, const base::android::JavaParamRef<jobject>& obj);
+  void Destroy(JNIEnv* env);
   void InitWebContents(
       JNIEnv* env,
-      const base::android::JavaParamRef<jobject>& obj,
       jboolean incognito,
       jboolean is_background_tab,
       const base::android::JavaParamRef<jobject>& jweb_contents,
@@ -141,23 +140,18 @@
           jcontext_menu_populator_factory);
   void UpdateDelegates(
       JNIEnv* env,
-      const base::android::JavaParamRef<jobject>& obj,
       const base::android::JavaParamRef<jobject>& jweb_contents_delegate,
       const base::android::JavaParamRef<jobject>&
           jcontext_menu_populator_factory);
-  void DestroyWebContents(JNIEnv* env,
-                          const base::android::JavaParamRef<jobject>& obj);
-  void ReleaseWebContents(JNIEnv* env,
-                          const base::android::JavaParamRef<jobject>& obj);
+  void DestroyWebContents(JNIEnv* env);
+  void ReleaseWebContents(JNIEnv* env);
   void OnPhysicalBackingSizeChanged(
       JNIEnv* env,
-      const base::android::JavaParamRef<jobject>& obj,
       const base::android::JavaParamRef<jobject>& jweb_contents,
       jint width,
       jint height);
   TabLoadStatus LoadUrl(
       JNIEnv* env,
-      const base::android::JavaParamRef<jobject>& obj,
       const base::android::JavaParamRef<jstring>& url,
       const base::android::JavaParamRef<jobject>& j_initiator_origin,
       const base::android::JavaParamRef<jstring>& j_extra_headers,
@@ -173,12 +167,10 @@
       jlong intent_received_timestamp);
   void SetActiveNavigationEntryTitleForUrl(
       JNIEnv* env,
-      const base::android::JavaParamRef<jobject>& obj,
       const base::android::JavaParamRef<jstring>& jurl,
       const base::android::JavaParamRef<jstring>& jtitle);
 
-  void LoadOriginalImage(JNIEnv* env,
-                         const base::android::JavaParamRef<jobject>& obj);
+  void LoadOriginalImage(JNIEnv* env);
   void SetAddApi2TransitionToFutureNavigations(JNIEnv* env,
                                                jboolean should_add);
   jboolean GetAddApi2TransitionToFutureNavigations(JNIEnv* env) {
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index 6a5bebf..439d38d 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -3842,28 +3842,6 @@
 
 #endif  // defined(OS_WIN)
 
-void ChromeContentBrowserClient::WillStartServiceManager() {
-#if defined(OS_WIN) || defined(OS_MAC) || \
-    (defined(OS_LINUX) && !defined(OS_CHROMEOS))
-  auto* chrome_feature_list_creator =
-      startup_data_.chrome_feature_list_creator();
-  // This has to run very early before ServiceManagerContext is created.
-  const policy::PolicyMap& policies =
-      chrome_feature_list_creator->browser_policy_connector()
-          ->GetPolicyService()
-          ->GetPolicies(policy::PolicyNamespace(policy::POLICY_DOMAIN_CHROME,
-                                                std::string()));
-  const base::Value* audio_sandbox_enabled_policy_value =
-      policies.GetValue(policy::key::kAudioSandboxEnabled);
-  if (audio_sandbox_enabled_policy_value) {
-    bool force_enable_audio_sandbox;
-    audio_sandbox_enabled_policy_value->GetAsBoolean(
-        &force_enable_audio_sandbox);
-    SetForceAudioServiceSandboxed(force_enable_audio_sandbox);
-  }
-#endif
-}
-
 void ChromeContentBrowserClient::OpenURL(
     content::SiteInstance* site_instance,
     const content::OpenURLParams& params,
diff --git a/chrome/browser/chrome_content_browser_client.h b/chrome/browser/chrome_content_browser_client.h
index e349796d..d08435df 100644
--- a/chrome/browser/chrome_content_browser_client.h
+++ b/chrome/browser/chrome_content_browser_client.h
@@ -429,7 +429,6 @@
   void BindHostReceiverForRenderer(
       content::RenderProcessHost* render_process_host,
       mojo::GenericPendingReceiver receiver) override;
-  void WillStartServiceManager() override;
   void OpenURL(
       content::SiteInstance* site_instance,
       const content::OpenURLParams& params,
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index 2996907..ac4f72c 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -1978,6 +1978,8 @@
     "net/network_diagnostics/gateway_can_be_pinged_routine.h",
     "net/network_diagnostics/has_secure_wifi_connection_routine.cc",
     "net/network_diagnostics/has_secure_wifi_connection_routine.h",
+    "net/network_diagnostics/host_resolver.cc",
+    "net/network_diagnostics/host_resolver.h",
     "net/network_diagnostics/http_firewall_routine.cc",
     "net/network_diagnostics/http_firewall_routine.h",
     "net/network_diagnostics/http_request_manager.cc",
@@ -1998,6 +2000,8 @@
     "net/network_diagnostics/signal_strength_routine.h",
     "net/network_diagnostics/tls_prober.cc",
     "net/network_diagnostics/tls_prober.h",
+    "net/network_diagnostics/udp_prober.cc",
+    "net/network_diagnostics/udp_prober.h",
     "net/network_health/network_health.cc",
     "net/network_health/network_health.h",
     "net/network_health/network_health_localized_strings.cc",
@@ -3593,8 +3597,11 @@
     "net/network_diagnostics/fake_network_context.h",
     "net/network_diagnostics/fake_tcp_connected_socket.cc",
     "net/network_diagnostics/fake_tcp_connected_socket.h",
+    "net/network_diagnostics/fake_udp_socket.cc",
+    "net/network_diagnostics/fake_udp_socket.h",
     "net/network_diagnostics/gateway_can_be_pinged_routine_unittest.cc",
     "net/network_diagnostics/has_secure_wifi_connection_routine_unittest.cc",
+    "net/network_diagnostics/host_resolver_unittest.cc",
     "net/network_diagnostics/http_firewall_routine_unittest.cc",
     "net/network_diagnostics/http_request_manager_unittest.cc",
     "net/network_diagnostics/https_firewall_routine_unittest.cc",
@@ -3605,6 +3612,7 @@
     "net/network_diagnostics/network_diagnostics_util_unittest.cc",
     "net/network_diagnostics/signal_strength_routine_unittest.cc",
     "net/network_diagnostics/tls_prober_unittest.cc",
+    "net/network_diagnostics/udp_prober_unittest.cc",
     "net/network_health/network_health_unittest.cc",
     "net/network_portal_detector_impl_unittest.cc",
     "net/network_pref_state_observer_unittest.cc",
diff --git a/chrome/browser/chromeos/net/network_diagnostics/fake_host_resolver.cc b/chrome/browser/chromeos/net/network_diagnostics/fake_host_resolver.cc
index 381891c..8ecd070 100644
--- a/chrome/browser/chromeos/net/network_diagnostics/fake_host_resolver.cc
+++ b/chrome/browser/chromeos/net/network_diagnostics/fake_host_resolver.cc
@@ -43,13 +43,13 @@
     receiver_.reset();
     return;
   }
-  mojo::Remote<network::mojom::ResolveHostClient> response_client(
-      std::move(pending_response_client));
-  DnsResult* result = fake_dns_results_.front();
-  DCHECK(result);
-  fake_dns_results_.pop_front();
-  response_client->OnComplete(result->result_, result->resolve_error_info_,
-                              result->resolved_addresses_);
+  response_client_.Bind(std::move(pending_response_client));
+
+  DCHECK(fake_dns_result_);
+  response_client_->OnComplete(fake_dns_result_->result_,
+                               fake_dns_result_->resolve_error_info_,
+                               fake_dns_result_->resolved_addresses_);
+  fake_dns_result_.release();
 }
 
 void FakeHostResolver::MdnsListen(
@@ -60,5 +60,12 @@
   NOTIMPLEMENTED();
 }
 
+void FakeHostResolver::SetFakeDnsResult(
+    std::unique_ptr<DnsResult> fake_dns_result) {
+  DCHECK(!fake_dns_result_);
+
+  fake_dns_result_ = std::move(fake_dns_result);
+}
+
 }  // namespace network_diagnostics
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/net/network_diagnostics/fake_host_resolver.h b/chrome/browser/chromeos/net/network_diagnostics/fake_host_resolver.h
index c75243c2..7508513 100644
--- a/chrome/browser/chromeos/net/network_diagnostics/fake_host_resolver.h
+++ b/chrome/browser/chromeos/net/network_diagnostics/fake_host_resolver.h
@@ -5,7 +5,8 @@
 #ifndef CHROME_BROWSER_CHROMEOS_NET_NETWORK_DIAGNOSTICS_FAKE_HOST_RESOLVER_H_
 #define CHROME_BROWSER_CHROMEOS_NET_NETWORK_DIAGNOSTICS_FAKE_HOST_RESOLVER_H_
 
-#include <deque>
+#include <memory>
+#include <utility>
 
 #include "base/optional.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
@@ -49,10 +50,8 @@
       mojo::PendingRemote<network::mojom::MdnsListenClient> response_client,
       MdnsListenCallback callback) override;
 
-  // Sets the fake dns results.
-  void set_fake_dns_results(std::deque<DnsResult*> fake_dns_results) {
-    fake_dns_results_ = std::move(fake_dns_results);
-  }
+  // Sets the fake DNS result for single host resolutions.
+  void SetFakeDnsResult(std::unique_ptr<DnsResult> fake_dns_result);
 
   // If set to true, the binding pipe will be disconnected when attempting to
   // connect.
@@ -61,10 +60,12 @@
   }
 
  private:
+  // Handles calls to the HostResolver.
   mojo::Receiver<network::mojom::HostResolver> receiver_;
-  // Use the list of fake dns results to fake different responses for multiple
-  // calls to the host_resolver's ResolveHost().
-  std::deque<DnsResult*> fake_dns_results_;
+  // Responds to calls made to |this|.
+  mojo::Remote<network::mojom::ResolveHostClient> response_client_;
+  // Use the |fake_dns_result| to fake a single host resolution.
+  std::unique_ptr<DnsResult> fake_dns_result_ = nullptr;
   // Used to mimic the scenario where network::mojom::HostResolver receiver
   // is disconnected.
   bool disconnect_ = false;
diff --git a/chrome/browser/chromeos/net/network_diagnostics/fake_network_context.cc b/chrome/browser/chromeos/net/network_diagnostics/fake_network_context.cc
index d9b6a59..4d37e06 100644
--- a/chrome/browser/chromeos/net/network_diagnostics/fake_network_context.cc
+++ b/chrome/browser/chromeos/net/network_diagnostics/fake_network_context.cc
@@ -6,6 +6,8 @@
 
 #include <utility>
 
+#include "base/logging.h"
+
 namespace chromeos {
 namespace network_diagnostics {
 
@@ -30,8 +32,8 @@
     mojo::PendingReceiver<network::mojom::HostResolver> receiver) {
   DCHECK(!resolver_);
   resolver_ = std::make_unique<FakeHostResolver>(std::move(receiver));
-  resolver_->set_fake_dns_results(std::move(fake_dns_results_));
   resolver_->set_disconnect_during_host_resolution(host_resolution_disconnect_);
+  resolver_->SetFakeDnsResult(std::move(fake_dns_result_));
 }
 
 void FakeNetworkContext::CreateTCPConnectedSocket(
@@ -59,6 +61,24 @@
                           mojo::ScopedDataPipeProducerHandle());
 }
 
+void FakeNetworkContext::CreateUDPSocket(
+    mojo::PendingReceiver<network::mojom::UDPSocket> receiver,
+    mojo::PendingRemote<network::mojom::UDPSocketListener> listener) {
+  if (udp_connection_attempt_disconnect_) {
+    receiver.reset();
+    listener.reset();
+    return;
+  }
+
+  // Bind the receiver if UDP connection is successful.
+  if (udp_connect_code_ == net::OK) {
+    DCHECK(fake_udp_socket_);
+
+    fake_udp_socket_->BindReceiver(std::move(receiver));
+    fake_udp_socket_->BindRemote(std::move(listener));
+  }
+}
+
 void FakeNetworkContext::SetTCPConnectCode(
     base::Optional<net::Error>& tcp_connect_code) {
   if (tcp_connect_code.has_value()) {
@@ -74,5 +94,41 @@
   }
 }
 
+void FakeNetworkContext::SetUdpConnectCode(net::Error udp_connect_code) {
+  fake_udp_socket_ = std::make_unique<FakeUdpSocket>();
+  fake_udp_socket_->set_udp_connect_code(udp_connect_code);
+}
+
+void FakeNetworkContext::SetUdpSendCode(net::Error udp_send_code) {
+  DCHECK(fake_udp_socket_);
+
+  fake_udp_socket_->set_udp_send_code(udp_send_code);
+}
+
+void FakeNetworkContext::SetDisconnectDuringUdpSendAttempt(bool disconnect) {
+  DCHECK(fake_udp_socket_);
+
+  fake_udp_socket_->set_disconnect_during_udp_send_attempt(disconnect);
+}
+
+void FakeNetworkContext::SetUdpOnReceivedCode(net::Error udp_on_received_code) {
+  DCHECK(fake_udp_socket_);
+
+  fake_udp_socket_->set_udp_on_received_code(udp_on_received_code);
+}
+
+void FakeNetworkContext::SetUdpOnReceivedData(
+    base::span<const uint8_t> udp_on_received_data) {
+  DCHECK(fake_udp_socket_);
+
+  fake_udp_socket_->set_udp_on_received_data(std::move(udp_on_received_data));
+}
+
+void FakeNetworkContext::SetDisconnectDuringUdpReceiveAttempt(bool disconnect) {
+  DCHECK(fake_udp_socket_);
+
+  fake_udp_socket_->set_disconnect_during_udp_receive_attempt(disconnect);
+}
+
 }  // namespace network_diagnostics
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/net/network_diagnostics/fake_network_context.h b/chrome/browser/chromeos/net/network_diagnostics/fake_network_context.h
index 6a0a9cc..99432c4 100644
--- a/chrome/browser/chromeos/net/network_diagnostics/fake_network_context.h
+++ b/chrome/browser/chromeos/net/network_diagnostics/fake_network_context.h
@@ -7,9 +7,13 @@
 
 #include <deque>
 #include <memory>
+#include <utility>
 
+#include "base/containers/span.h"
+#include "base/optional.h"
 #include "chrome/browser/chromeos/net/network_diagnostics/fake_host_resolver.h"
 #include "chrome/browser/chromeos/net/network_diagnostics/fake_tcp_connected_socket.h"
+#include "chrome/browser/chromeos/net/network_diagnostics/fake_udp_socket.h"
 #include "services/network/test/test_network_context.h"
 
 namespace chromeos {
@@ -20,6 +24,8 @@
 class FakeNetworkContext : public network::TestNetworkContext {
  public:
   FakeNetworkContext();
+  FakeNetworkContext(const FakeNetworkContext&) = delete;
+  FakeNetworkContext& operator=(const FakeNetworkContext&) = delete;
   ~FakeNetworkContext() override;
 
   // network::TestNetworkContext:
@@ -36,16 +42,40 @@
       mojo::PendingRemote<network::mojom::SocketObserver> observer,
       CreateTCPConnectedSocketCallback callback) override;
 
-  // Sets the fake TCP connect code.
+  void CreateUDPSocket(
+      mojo::PendingReceiver<network::mojom::UDPSocket> receiver,
+      mojo::PendingRemote<network::mojom::UDPSocketListener> listener) override;
+
+  // Sets the fake TCP connect code. TODO(khegde): Change this to
+  // SetTCPConnectCompleteCode.
   void SetTCPConnectCode(base::Optional<net::Error>& tcp_connect_code);
 
   // Sets the fake TLS upgrade code.
   void SetTLSUpgradeCode(base::Optional<net::Error>& tls_upgrade_code);
 
-  // Sets the fake dns results.
-  void set_fake_dns_results(
-      std::deque<FakeHostResolver::DnsResult*> fake_dns_results) {
-    fake_dns_results_ = std::move(fake_dns_results);
+  // Sets the fake UDP connect code.
+  void SetUdpConnectCode(net::Error udp_connect_code);
+
+  // Sets the fake UDP send code.
+  void SetUdpSendCode(net::Error udp_send_code);
+
+  // Sets the state to mimic a fake disconnect during a UDP send attempt.
+  void SetDisconnectDuringUdpSendAttempt(bool disconnect);
+
+  // Sets the fake UDP on received code.
+  void SetUdpOnReceivedCode(net::Error udp_on_received_code);
+
+  // Sets the fake UDP on received data.
+  void SetUdpOnReceivedData(base::span<const uint8_t> udp_on_received_data);
+
+  // Sets the state to mimic a fake disconnect after receiving successful send
+  // confirmation, but before receiving any data.
+  void SetDisconnectDuringUdpReceiveAttempt(bool disconnect);
+
+  // Sets the fake DNS result. Used to test a single host resolution.
+  void set_fake_dns_result(
+      std::unique_ptr<FakeHostResolver::DnsResult> fake_dns_result) {
+    fake_dns_result_ = std::move(fake_dns_result);
   }
 
   // If set to true, the binding pipe will be disconnected when attempting to
@@ -66,15 +96,28 @@
     tls_upgrade_attempt_disconnect_ = disconnect;
   }
 
+  // If set to true, the binding pipe will be disconnected when attempting to
+  // connect.
+  void set_disconnect_during_udp_connection_attempt(bool disconnect) {
+    udp_connection_attempt_disconnect_ = disconnect;
+  }
+
  private:
   // Fake host resolver.
   std::unique_ptr<FakeHostResolver> resolver_;
   // Fake DNS lookup results.
   std::deque<FakeHostResolver::DnsResult*> fake_dns_results_;
+  // Fake DNS lookup result.
+  std::unique_ptr<FakeHostResolver::DnsResult> fake_dns_result_;
   // Provides the TCP socket functionality for tests.
   std::unique_ptr<FakeTCPConnectedSocket> fake_tcp_connected_socket_;
+  // Provides the UDP socket functionality for tests.
+  std::unique_ptr<FakeUdpSocket> fake_udp_socket_;
   // TCP connect code corresponding to the connection attempt.
   net::Error tcp_connect_code_ = net::OK;
+  // UDP connect code corresponding to the connection attempt.
+  net::Error udp_connect_code_ = net::OK;
+
   // Used to mimic the scenario where network::mojom::HostResolver receiver
   // is disconnected.
   bool host_resolution_disconnect_ = false;
@@ -84,6 +127,9 @@
   // Used to mimic the scenario where network::mojom::TLSClientSocket receiver
   // is disconnected.
   bool tls_upgrade_attempt_disconnect_ = false;
+  // Used to mimic the scenario where network::mojom::UDPSocket receiver is
+  // disconnected while connecting.
+  bool udp_connection_attempt_disconnect_ = false;
 };
 
 }  // namespace network_diagnostics
diff --git a/chrome/browser/chromeos/net/network_diagnostics/fake_udp_socket.cc b/chrome/browser/chromeos/net/network_diagnostics/fake_udp_socket.cc
new file mode 100644
index 0000000..9c59ac7
--- /dev/null
+++ b/chrome/browser/chromeos/net/network_diagnostics/fake_udp_socket.cc
@@ -0,0 +1,117 @@
+// 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/chromeos/net/network_diagnostics/fake_udp_socket.h"
+
+#include <utility>
+
+#include "base/optional.h"
+#include "net/base/ip_endpoint.h"
+
+namespace chromeos {
+namespace network_diagnostics {
+
+namespace {}  // namespace
+
+FakeUdpSocket::FakeUdpSocket() = default;
+
+FakeUdpSocket::~FakeUdpSocket() = default;
+
+void FakeUdpSocket::Connect(const net::IPEndPoint& remote_addr,
+                            network::mojom::UDPSocketOptionsPtr options,
+                            ConnectCallback callback) {
+  if (mojo_disconnect_on_connect_) {
+    receiver_.reset();
+    return;
+  }
+  std::move(callback).Run(udp_connect_code_, net::IPEndPoint());
+}
+
+void FakeUdpSocket::Bind(const net::IPEndPoint& local_addr,
+                         network::mojom::UDPSocketOptionsPtr options,
+                         BindCallback callback) {
+  NOTREACHED();
+}
+
+void FakeUdpSocket::SetBroadcast(bool broadcast,
+                                 SetBroadcastCallback callback) {
+  NOTREACHED();
+}
+
+void FakeUdpSocket::SetSendBufferSize(int32_t send_buffer_size,
+                                      SetSendBufferSizeCallback callback) {
+  NOTREACHED();
+}
+
+void FakeUdpSocket::SetReceiveBufferSize(int32_t receive_buffer_size,
+                                         SetSendBufferSizeCallback callback) {
+  NOTREACHED();
+}
+
+void FakeUdpSocket::JoinGroup(const net::IPAddress& group_address,
+                              JoinGroupCallback callback) {
+  NOTREACHED();
+}
+
+void FakeUdpSocket::LeaveGroup(const net::IPAddress& group_address,
+                               LeaveGroupCallback callback) {
+  NOTREACHED();
+}
+
+void FakeUdpSocket::ReceiveMore(uint32_t num_additional_datagrams) {
+  DCHECK(remote_.is_bound());
+
+  if (mojo_disconnect_on_receive_) {
+    remote_.reset();
+    return;
+  }
+
+  remote_->OnReceived(udp_on_received_code_, net::IPEndPoint(),
+                      std::move(udp_on_received_data_));
+}
+
+void FakeUdpSocket::ReceiveMoreWithBufferSize(uint32_t num_additional_datagrams,
+                                              uint32_t buffer_size) {
+  NOTREACHED();
+}
+
+void FakeUdpSocket::SendTo(
+    const net::IPEndPoint& dest_addr,
+    base::span<const uint8_t> data,
+    const net::MutableNetworkTrafficAnnotationTag& traffic_annotation,
+    SendToCallback callback) {
+  NOTREACHED();
+}
+
+void FakeUdpSocket::Send(
+    base::span<const uint8_t> data,
+    const net::MutableNetworkTrafficAnnotationTag& traffic_annotation,
+    SendCallback callback) {
+  if (mojo_disconnect_on_send_) {
+    receiver_.reset();
+    return;
+  }
+  std::move(callback).Run(udp_send_code_);
+}
+
+void FakeUdpSocket::Close() {
+  NOTREACHED();
+}
+
+void FakeUdpSocket::BindReceiver(
+    mojo::PendingReceiver<network::mojom::UDPSocket> socket) {
+  DCHECK(!receiver_.is_bound());
+
+  receiver_.Bind(std::move(socket));
+}
+
+void FakeUdpSocket::BindRemote(
+    mojo::PendingRemote<network::mojom::UDPSocketListener> socket_listener) {
+  DCHECK(!remote_.is_bound());
+
+  remote_.Bind(std::move(socket_listener));
+}
+
+}  // namespace network_diagnostics
+}  // namespace chromeos
diff --git a/chrome/browser/chromeos/net/network_diagnostics/fake_udp_socket.h b/chrome/browser/chromeos/net/network_diagnostics/fake_udp_socket.h
new file mode 100644
index 0000000..41be34f
--- /dev/null
+++ b/chrome/browser/chromeos/net/network_diagnostics/fake_udp_socket.h
@@ -0,0 +1,110 @@
+// 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_CHROMEOS_NET_NETWORK_DIAGNOSTICS_FAKE_UDP_SOCKET_H_
+#define CHROME_BROWSER_CHROMEOS_NET_NETWORK_DIAGNOSTICS_FAKE_UDP_SOCKET_H_
+
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "net/base/net_errors.h"
+#include "services/network/public/mojom/udp_socket.mojom.h"
+
+namespace chromeos {
+namespace network_diagnostics {
+
+// Provides some UDP socket functionality in tests. Most methods, unless
+// otherwise noted, are not expected to be called in tests or in a non Chrome OS
+// environment.
+class FakeUdpSocket : public network::mojom::UDPSocket {
+ public:
+  FakeUdpSocket();
+  FakeUdpSocket(const FakeUdpSocket&) = delete;
+  FakeUdpSocket& operator=(const FakeUdpSocket&) = delete;
+  ~FakeUdpSocket() override;
+
+  // network::mojom::UDPSocket:
+  // Used in the fake.
+  void Connect(const net::IPEndPoint& remote_addr,
+               network::mojom::UDPSocketOptionsPtr options,
+               ConnectCallback callback) override;
+  void Bind(const net::IPEndPoint& local_addr,
+            network::mojom::UDPSocketOptionsPtr options,
+            BindCallback callback) override;
+  void SetBroadcast(bool broadcast, SetBroadcastCallback callback) override;
+  void SetSendBufferSize(int32_t send_buffer_size,
+                         SetSendBufferSizeCallback callback) override;
+  void SetReceiveBufferSize(int32_t receive_buffer_size,
+                            SetSendBufferSizeCallback callback) override;
+  void JoinGroup(const net::IPAddress& group_address,
+                 JoinGroupCallback callback) override;
+  void LeaveGroup(const net::IPAddress& group_address,
+                  LeaveGroupCallback callback) override;
+  // Used in the fake.
+  void ReceiveMore(uint32_t num_additional_datagrams) override;
+  void ReceiveMoreWithBufferSize(uint32_t num_additional_datagrams,
+                                 uint32_t buffer_size) override;
+  void SendTo(const net::IPEndPoint& dest_addr,
+              base::span<const uint8_t> data,
+              const net::MutableNetworkTrafficAnnotationTag& traffic_annotation,
+              SendToCallback callback) override;
+  // Used in the fake.
+  void Send(base::span<const uint8_t> data,
+            const net::MutableNetworkTrafficAnnotationTag& traffic_annotation,
+            SendCallback callback) override;
+  void Close() override;
+
+  // Binds the pending receiver to |this|.
+  void BindReceiver(mojo::PendingReceiver<network::mojom::UDPSocket> socket);
+
+  // Binds the pending remote to a remote held by |this|.
+  void BindRemote(
+      mojo::PendingRemote<network::mojom::UDPSocketListener> socket_listener);
+
+  void set_udp_connect_code(net::Error udp_connect_code) {
+    udp_connect_code_ = udp_connect_code;
+  }
+
+  void set_udp_send_code(net::Error udp_send_code) {
+    udp_send_code_ = udp_send_code;
+  }
+
+  void set_udp_on_received_code(net::Error udp_on_received_code) {
+    udp_on_received_code_ = udp_on_received_code;
+  }
+
+  void set_udp_on_received_data(
+      base::span<const uint8_t> udp_on_received_data) {
+    udp_on_received_data_ = std::move(udp_on_received_data);
+  }
+
+  void set_disconnect_during_udp_connection_attempt(bool disconnect) {
+    mojo_disconnect_on_connect_ = disconnect;
+  }
+
+  void set_disconnect_during_udp_send_attempt(bool disconnect) {
+    mojo_disconnect_on_send_ = disconnect;
+  }
+
+  void set_disconnect_during_udp_receive_attempt(bool disconnect) {
+    mojo_disconnect_on_receive_ = disconnect;
+  }
+
+ private:
+  mojo::Receiver<network::mojom::UDPSocket> receiver_{this};
+  mojo::Remote<network::mojom::UDPSocketListener> remote_;
+  net::Error udp_connect_code_ = net::ERR_FAILED;
+  net::Error udp_send_code_ = net::ERR_FAILED;
+  net::Error udp_on_received_code_ = net::ERR_FAILED;
+  base::span<const uint8_t> udp_on_received_data_ = {};
+  bool mojo_disconnect_on_connect_ = false;
+  bool mojo_disconnect_on_send_ = false;
+  bool mojo_disconnect_on_receive_ = false;
+};
+
+}  // namespace network_diagnostics
+}  // namespace chromeos
+
+#endif  // CHROME_BROWSER_CHROMEOS_NET_NETWORK_DIAGNOSTICS_FAKE_UDP_SOCKET_H_
diff --git a/chrome/browser/chromeos/net/network_diagnostics/host_resolver.cc b/chrome/browser/chromeos/net/network_diagnostics/host_resolver.cc
new file mode 100644
index 0000000..e8bce56
--- /dev/null
+++ b/chrome/browser/chromeos/net/network_diagnostics/host_resolver.cc
@@ -0,0 +1,80 @@
+// 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/chromeos/net/network_diagnostics/host_resolver.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/optional.h"
+#include "net/base/net_errors.h"
+#include "net/base/network_isolation_key.h"
+#include "net/dns/public/dns_config_overrides.h"
+
+namespace chromeos {
+namespace network_diagnostics {
+
+HostResolver::ResolutionResult::ResolutionResult(
+    int result,
+    const net::ResolveErrorInfo& resolve_error_info,
+    const base::Optional<net::AddressList>& resolved_addresses)
+    : result(result),
+      resolve_error_info(resolve_error_info),
+      resolved_addresses(resolved_addresses) {}
+
+HostResolver::ResolutionResult::~ResolutionResult() = default;
+
+HostResolver::HostResolver(const GURL& url,
+                           network::mojom::NetworkContext* network_context,
+                           OnResolutionComplete callback)
+    : HostResolver(net::HostPortPair::FromURL(url),
+                   network_context,
+                   std::move(callback)) {}
+
+HostResolver::HostResolver(const net::HostPortPair& host_port_pair,
+                           network::mojom::NetworkContext* network_context,
+                           OnResolutionComplete callback)
+    : callback_(std::move(callback)) {
+  DCHECK(network_context);
+  DCHECK(callback_);
+
+  network_context->CreateHostResolver(
+      net::DnsConfigOverrides(), host_resolver_.BindNewPipeAndPassReceiver());
+  // Disconnect handler will be invoked if the network service crashes.
+  host_resolver_.set_disconnect_handler(base::BindOnce(
+      &HostResolver::OnMojoConnectionError, base::Unretained(this)));
+
+  network::mojom::ResolveHostParametersPtr parameters =
+      network::mojom::ResolveHostParameters::New();
+  parameters->dns_query_type = net::DnsQueryType::A;
+  parameters->source = net::HostResolverSource::DNS;
+  parameters->cache_usage =
+      network::mojom::ResolveHostParameters::CacheUsage::DISALLOWED;
+
+  host_resolver_->ResolveHost(
+      host_port_pair, net::NetworkIsolationKey::CreateTransient(),
+      std::move(parameters), receiver_.BindNewPipeAndPassRemote());
+}
+
+HostResolver::~HostResolver() = default;
+
+void HostResolver::OnComplete(
+    int result,
+    const net::ResolveErrorInfo& resolve_error_info,
+    const base::Optional<net::AddressList>& resolved_addresses) {
+  receiver_.reset();
+  host_resolver_.reset();
+
+  ResolutionResult resolution_result{result, resolve_error_info,
+                                     resolved_addresses};
+  std::move(callback_).Run(resolution_result);
+}
+
+void HostResolver::OnMojoConnectionError() {
+  OnComplete(net::ERR_NAME_NOT_RESOLVED, net::ResolveErrorInfo(net::ERR_FAILED),
+             base::nullopt);
+}
+
+}  // namespace network_diagnostics
+}  // namespace chromeos
diff --git a/chrome/browser/chromeos/net/network_diagnostics/host_resolver.h b/chrome/browser/chromeos/net/network_diagnostics/host_resolver.h
new file mode 100644
index 0000000..e10078bf
--- /dev/null
+++ b/chrome/browser/chromeos/net/network_diagnostics/host_resolver.h
@@ -0,0 +1,73 @@
+// 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_CHROMEOS_NET_NETWORK_DIAGNOSTICS_HOST_RESOLVER_H_
+#define CHROME_BROWSER_CHROMEOS_NET_NETWORK_DIAGNOSTICS_HOST_RESOLVER_H_
+
+#include "base/callback.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/net_errors.h"
+#include "services/network/public/cpp/resolve_host_client_base.h"
+#include "services/network/public/mojom/network_context.mojom.h"
+#include "url/gurl.h"
+
+namespace chromeos {
+namespace network_diagnostics {
+
+// Performs a DNS host resolution. This is a single-use class.
+class HostResolver : public network::ResolveHostClientBase {
+ public:
+  struct ResolutionResult {
+    ResolutionResult(
+        int result,
+        const net::ResolveErrorInfo& resolve_error_info,
+        const base::Optional<net::AddressList>& resolved_addresses);
+    ~ResolutionResult();
+
+    int result;
+    net::ResolveErrorInfo resolve_error_info;
+    base::Optional<net::AddressList> resolved_addresses;
+  };
+  using OnResolutionComplete = base::OnceCallback<void(ResolutionResult&)>;
+
+  // Performs the DNS resolution of a specified |url|. Note that |callback|
+  // will not be called until construction is complete.
+  HostResolver(const GURL& url,
+               network::mojom::NetworkContext* network_context,
+               OnResolutionComplete callback);
+
+  // Performs the DNS resolution of a specified |host_port_pair|. Note that
+  // |callback| will not be called until construction is complete.
+  HostResolver(const net::HostPortPair& host_port_pair,
+               network::mojom::NetworkContext* network_context,
+               OnResolutionComplete callback);
+
+  HostResolver(const HostResolver&) = delete;
+  HostResolver& operator=(const HostResolver&) = delete;
+  ~HostResolver() override;
+
+  // network::mojom::ResolveHostClient:
+  void OnComplete(
+      int result,
+      const net::ResolveErrorInfo& resolve_error_info,
+      const base::Optional<net::AddressList>& resolved_addresses) override;
+
+ private:
+  // Handles Mojo connection errors during host resolution.
+  void OnMojoConnectionError();
+
+  // Callback invoked once resolution is complete.
+  OnResolutionComplete callback_;
+  // Receiver endpoint to handle host resolution results.
+  mojo::Receiver<network::mojom::ResolveHostClient> receiver_{this};
+  // Remote endpoint that calls the network service.
+  mojo::Remote<network::mojom::HostResolver> host_resolver_;
+};
+
+}  // namespace network_diagnostics
+}  // namespace chromeos
+
+#endif  // CHROME_BROWSER_CHROMEOS_NET_NETWORK_DIAGNOSTICS_HOST_RESOLVER_H_
diff --git a/chrome/browser/chromeos/net/network_diagnostics/host_resolver_unittest.cc b/chrome/browser/chromeos/net/network_diagnostics/host_resolver_unittest.cc
new file mode 100644
index 0000000..3b874c8
--- /dev/null
+++ b/chrome/browser/chromeos/net/network_diagnostics/host_resolver_unittest.cc
@@ -0,0 +1,174 @@
+// 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/chromeos/net/network_diagnostics/host_resolver.h"
+
+#include <memory>
+
+#include "base/bind.h"
+#include "base/run_loop.h"
+#include "chrome/browser/chromeos/net/network_diagnostics/fake_host_resolver.h"
+#include "chrome/browser/chromeos/net/network_diagnostics/fake_network_context.h"
+#include "content/public/test/browser_task_environment.h"
+#include "net/base/address_list.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/ip_address.h"
+#include "net/base/ip_endpoint.h"
+#include "net/dns/public/resolve_error_info.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace chromeos {
+namespace network_diagnostics {
+
+class HostResolverTest : public ::testing::Test {
+ public:
+  HostResolverTest() = default;
+  HostResolverTest(const HostResolverTest&) = delete;
+  HostResolverTest& operator=(const HostResolverTest&) = delete;
+
+  void InitializeNetworkContext(
+      std::unique_ptr<FakeHostResolver::DnsResult> fake_dns_result) {
+    fake_network_context_.set_fake_dns_result(std::move(fake_dns_result));
+  }
+
+  FakeNetworkContext* fake_network_context() { return &fake_network_context_; }
+
+ protected:
+  const net::HostPortPair kFakeHostPortPair =
+      net::HostPortPair::FromString("fake_stun_server.com:80");
+  const GURL kFakeUrl{"https://www.FAKE_HOST_NAME.com:1234/"};
+  const net::IPEndPoint kFakeIPAddress{
+      net::IPEndPoint(net::IPAddress::IPv4Localhost(), /*port=*/1234)};
+  std::unique_ptr<HostResolver> host_resolver_;
+
+ private:
+  content::BrowserTaskEnvironment task_environment_;
+  FakeNetworkContext fake_network_context_;
+};
+
+TEST_F(HostResolverTest, TestSuccessfulResolutionWithUrl) {
+  auto address_list = net::AddressList(kFakeIPAddress);
+  auto fake_dns_result = std::make_unique<FakeHostResolver::DnsResult>(
+      net::OK, net::ResolveErrorInfo(net::OK), address_list);
+  InitializeNetworkContext(std::move(fake_dns_result));
+  HostResolver::ResolutionResult resolution_result{
+      net::ERR_FAILED, net::ResolveErrorInfo(net::OK), base::nullopt};
+  base::RunLoop run_loop;
+  host_resolver_ = std::make_unique<HostResolver>(
+      kFakeUrl, fake_network_context(),
+      base::BindOnce(
+          [](HostResolver::ResolutionResult* resolution_result,
+             base::OnceClosure quit_closure,
+             HostResolver::ResolutionResult& res_result) {
+            resolution_result->result = res_result.result;
+            resolution_result->resolve_error_info =
+                res_result.resolve_error_info;
+            resolution_result->resolved_addresses =
+                res_result.resolved_addresses;
+            std::move(quit_closure).Run();
+          },
+          &resolution_result, run_loop.QuitClosure()));
+  run_loop.Run();
+
+  EXPECT_EQ(resolution_result.result, net::OK);
+  EXPECT_EQ(resolution_result.resolve_error_info,
+            net::ResolveErrorInfo(net::OK));
+  EXPECT_EQ(resolution_result.resolved_addresses.value().size(), 1);
+  EXPECT_EQ(resolution_result.resolved_addresses.value().front(),
+            address_list.front());
+}
+
+TEST_F(HostResolverTest, TestSuccessfulResolutionWithHostPortPair) {
+  auto address_list = net::AddressList(kFakeIPAddress);
+  auto fake_dns_result = std::make_unique<FakeHostResolver::DnsResult>(
+      net::OK, net::ResolveErrorInfo(net::OK), address_list);
+  InitializeNetworkContext(std::move(fake_dns_result));
+  HostResolver::ResolutionResult resolution_result{
+      net::ERR_FAILED, net::ResolveErrorInfo(net::OK), base::nullopt};
+  base::RunLoop run_loop;
+  host_resolver_ = std::make_unique<HostResolver>(
+      kFakeHostPortPair, fake_network_context(),
+      base::BindOnce(
+          [](HostResolver::ResolutionResult* resolution_result,
+             base::OnceClosure quit_closure,
+             HostResolver::ResolutionResult& res_result) {
+            resolution_result->result = res_result.result;
+            resolution_result->resolve_error_info =
+                res_result.resolve_error_info;
+            resolution_result->resolved_addresses =
+                res_result.resolved_addresses;
+            std::move(quit_closure).Run();
+          },
+          &resolution_result, run_loop.QuitClosure()));
+  run_loop.Run();
+
+  EXPECT_EQ(resolution_result.result, net::OK);
+  EXPECT_EQ(resolution_result.resolve_error_info,
+            net::ResolveErrorInfo(net::OK));
+  EXPECT_EQ(resolution_result.resolved_addresses.value().size(), 1);
+  EXPECT_EQ(resolution_result.resolved_addresses.value().front(),
+            address_list.front());
+}
+
+TEST_F(HostResolverTest, TestFailedHostResolution) {
+  auto fake_dns_result = std::make_unique<FakeHostResolver::DnsResult>(
+      net::ERR_NAME_NOT_RESOLVED,
+      net::ResolveErrorInfo(net::ERR_NAME_NOT_RESOLVED), base::nullopt);
+  InitializeNetworkContext(std::move(fake_dns_result));
+  HostResolver::ResolutionResult resolution_result{
+      net::ERR_FAILED, net::ResolveErrorInfo(net::OK), base::nullopt};
+  base::RunLoop run_loop;
+  host_resolver_ = std::make_unique<HostResolver>(
+      kFakeHostPortPair, fake_network_context(),
+      base::BindOnce(
+          [](HostResolver::ResolutionResult* resolution_result,
+             base::OnceClosure quit_closure,
+             HostResolver::ResolutionResult& res_result) {
+            resolution_result->result = res_result.result;
+            resolution_result->resolve_error_info =
+                res_result.resolve_error_info;
+            resolution_result->resolved_addresses =
+                res_result.resolved_addresses;
+            std::move(quit_closure).Run();
+          },
+          &resolution_result, run_loop.QuitClosure()));
+  run_loop.Run();
+
+  EXPECT_EQ(resolution_result.result, net::ERR_NAME_NOT_RESOLVED);
+  EXPECT_EQ(resolution_result.resolve_error_info,
+            net::ResolveErrorInfo(net::ERR_NAME_NOT_RESOLVED));
+  ASSERT_FALSE(resolution_result.resolved_addresses.has_value());
+}
+
+TEST_F(HostResolverTest, TestMojoDisconnectDuringHostResolution) {
+  InitializeNetworkContext(/*fake_dns_result=*/{});
+  fake_network_context()->set_disconnect_during_host_resolution(true);
+  HostResolver::ResolutionResult resolution_result{
+      net::ERR_FAILED, net::ResolveErrorInfo(net::OK), base::nullopt};
+  base::RunLoop run_loop;
+  host_resolver_ = std::make_unique<HostResolver>(
+      kFakeHostPortPair, fake_network_context(),
+      base::BindOnce(
+          [](HostResolver::ResolutionResult* resolution_result,
+             base::OnceClosure quit_closure,
+             HostResolver::ResolutionResult& res_result) {
+            resolution_result->result = res_result.result;
+            resolution_result->resolve_error_info =
+                res_result.resolve_error_info;
+            resolution_result->resolved_addresses =
+                res_result.resolved_addresses;
+            std::move(quit_closure).Run();
+          },
+          &resolution_result, run_loop.QuitClosure()));
+  run_loop.Run();
+
+  EXPECT_EQ(resolution_result.result, net::ERR_NAME_NOT_RESOLVED);
+  EXPECT_EQ(resolution_result.resolve_error_info,
+            net::ResolveErrorInfo(net::ERR_FAILED));
+  ASSERT_FALSE(resolution_result.resolved_addresses.has_value());
+}
+
+}  // namespace network_diagnostics
+}  // namespace chromeos
diff --git a/chrome/browser/chromeos/net/network_diagnostics/network_diagnostics_util.cc b/chrome/browser/chromeos/net/network_diagnostics/network_diagnostics_util.cc
index e07d6ee..c138b64 100644
--- a/chrome/browser/chromeos/net/network_diagnostics/network_diagnostics_util.cc
+++ b/chrome/browser/chromeos/net/network_diagnostics/network_diagnostics_util.cc
@@ -149,6 +149,39 @@
   return profile;
 }
 
+const std::array<uint8_t, kStunHeaderSize>& GetStunHeader() {
+  static std::array<uint8_t, kStunHeaderSize> stun_header = {
+      0x00, 0x01, 0x00, 0x00, 0x21, 0x12, 0xa4, 0x42, 0x79, 0x64,
+      0x66, 0x36, 0x66, 0x53, 0x42, 0x73, 0x76, 0x77, 0x76, 0x75};
+
+  return stun_header;
+}
+
+net::NetworkTrafficAnnotationTag GetStunNetworkAnnotationTag() {
+  return net::DefineNetworkTrafficAnnotation("network_diagnostics_routines",
+                                             R"(
+      semantics {
+        sender: "NetworkDiagnosticsRoutines"
+        description:
+            "Routines send network traffic to hosts in order to "
+            "validate the internet connection on a device."
+        trigger:
+            "A routine attempts a socket connection or makes an http/s "
+            "request."
+        data:
+          "For UDP connections, data is sent along with the origin "
+          "(scheme-host-port). The primary purpose of the UDP prober is to "
+          "send a STUN packet header to a STUN server. For TCP connections, "
+          "only the origin is sent. No user identifier is sent along with the "
+          "data."
+        destination: GOOGLE_OWNED_SERVICE
+      }
+      policy {
+        cookies_allowed: NO
+      }
+  )");
+}
+
 }  // namespace util
 
 }  // namespace network_diagnostics
diff --git a/chrome/browser/chromeos/net/network_diagnostics/network_diagnostics_util.h b/chrome/browser/chromeos/net/network_diagnostics/network_diagnostics_util.h
index 3d824ed..850c67a 100644
--- a/chrome/browser/chromeos/net/network_diagnostics/network_diagnostics_util.h
+++ b/chrome/browser/chromeos/net/network_diagnostics/network_diagnostics_util.h
@@ -5,9 +5,12 @@
 #ifndef CHROME_BROWSER_CHROMEOS_NET_NETWORK_DIAGNOSTICS_NETWORK_DIAGNOSTICS_UTIL_H_
 #define CHROME_BROWSER_CHROMEOS_NET_NETWORK_DIAGNOSTICS_NETWORK_DIAGNOSTICS_UTIL_H_
 
+#include <array>
+#include <cstdint>
 #include <string>
 #include <vector>
 
+#include "net/traffic_annotation/network_traffic_annotation.h"
 #include "url/gurl.h"
 
 class Profile;
@@ -20,6 +23,9 @@
 // Generate 204 path.
 extern const char kGenerate204Path[];
 
+// STUN packet header size.
+constexpr int kStunHeaderSize = 20;
+
 // Returns the Gstatic host suffix. Network diagnostic routines attach a random
 // prefix to |kGstaticHostSuffix| to get a complete hostname.
 const char* GetGstaticHostSuffix();
@@ -78,6 +84,12 @@
 // Returns the profile associated with this account.
 Profile* GetUserProfile();
 
+// Returns a STUN packet with a header defined in RFC 5389.
+const std::array<uint8_t, kStunHeaderSize>& GetStunHeader();
+
+// Returns the traffic annotation tag for STUN traffic.
+net::NetworkTrafficAnnotationTag GetStunNetworkAnnotationTag();
+
 }  // namespace util
 
 }  // namespace network_diagnostics
diff --git a/chrome/browser/chromeos/net/network_diagnostics/tls_prober.cc b/chrome/browser/chromeos/net/network_diagnostics/tls_prober.cc
index 664aac8b..a8f9938f 100644
--- a/chrome/browser/chromeos/net/network_diagnostics/tls_prober.cc
+++ b/chrome/browser/chromeos/net/network_diagnostics/tls_prober.cc
@@ -54,89 +54,6 @@
 
 }  // namespace
 
-class TlsProber::HostResolver : public network::ResolveHostClientBase {
- public:
-  HostResolver(network::mojom::NetworkContext* network_context,
-               TlsProber* tls_prober);
-  HostResolver(const HostResolver&) = delete;
-  HostResolver& operator=(const HostResolver&) = delete;
-  ~HostResolver() override;
-
-  // network::mojom::ResolveHostClient:
-  void OnComplete(
-      int result,
-      const net::ResolveErrorInfo& resolve_error_info,
-      const base::Optional<net::AddressList>& resolved_addresses) override;
-
-  // Performs the DNS resolution.
-  void Run(const GURL& url);
-
-  network::mojom::NetworkContext* network_context() const {
-    return network_context_;
-  }
-
- private:
-  void CreateHostResolver();
-  void OnMojoConnectionError();
-
-  network::mojom::NetworkContext* network_context_ = nullptr;  // Unowned
-  TlsProber* tls_prober_;                                      // Unowned
-  mojo::Receiver<network::mojom::ResolveHostClient> receiver_{this};
-  mojo::Remote<network::mojom::HostResolver> host_resolver_;
-};
-
-TlsProber::HostResolver::HostResolver(
-    network::mojom::NetworkContext* network_context,
-    TlsProber* tls_prober)
-    : network_context_(network_context), tls_prober_(tls_prober) {
-  DCHECK(network_context_);
-  DCHECK(tls_prober_);
-}
-
-TlsProber::HostResolver::~HostResolver() = default;
-
-void TlsProber::HostResolver::OnComplete(
-    int result,
-    const net::ResolveErrorInfo& resolve_error_info,
-    const base::Optional<net::AddressList>& resolved_addresses) {
-  receiver_.reset();
-  host_resolver_.reset();
-
-  tls_prober_->OnHostResolutionComplete(result, resolve_error_info,
-                                        resolved_addresses);
-}
-
-void TlsProber::HostResolver::Run(const GURL& url) {
-  CreateHostResolver();
-  DCHECK(host_resolver_);
-  DCHECK(!receiver_.is_bound());
-
-  network::mojom::ResolveHostParametersPtr parameters =
-      network::mojom::ResolveHostParameters::New();
-  parameters->dns_query_type = net::DnsQueryType::A;
-  parameters->source = net::HostResolverSource::DNS;
-  parameters->cache_usage =
-      network::mojom::ResolveHostParameters::CacheUsage::DISALLOWED;
-
-  host_resolver_->ResolveHost(net::HostPortPair::FromURL(url),
-                              net::NetworkIsolationKey::CreateTransient(),
-                              std::move(parameters),
-                              receiver_.BindNewPipeAndPassRemote());
-}
-
-void TlsProber::HostResolver::CreateHostResolver() {
-  network_context()->CreateHostResolver(
-      net::DnsConfigOverrides(), host_resolver_.BindNewPipeAndPassReceiver());
-  // Disconnect handler will be invoked if the network service crashes.
-  host_resolver_.set_disconnect_handler(base::BindOnce(
-      &HostResolver::OnMojoConnectionError, base::Unretained(this)));
-}
-
-void TlsProber::HostResolver::OnMojoConnectionError() {
-  OnComplete(net::ERR_NAME_NOT_RESOLVED, net::ResolveErrorInfo(net::ERR_FAILED),
-             base::nullopt);
-}
-
 TlsProber::TlsProber(NetworkContextGetter network_context_getter,
                      const GURL& url,
                      TlsProbeCompleteCallback callback)
@@ -151,9 +68,10 @@
       network_context_getter_.Run();
   DCHECK(network_context);
 
-  host_resolver_ = std::make_unique<HostResolver>(network_context, this);
-  DCHECK(host_resolver_);
-  host_resolver_->Run(url);
+  host_resolver_ = std::make_unique<HostResolver>(
+      net::HostPortPair::FromURL(url_), network_context,
+      base::BindOnce(&TlsProber::OnHostResolutionComplete,
+                     weak_factory_.GetWeakPtr()));
 }
 
 TlsProber::TlsProber() = default;
@@ -161,15 +79,15 @@
 TlsProber::~TlsProber() = default;
 
 void TlsProber::OnHostResolutionComplete(
-    int result,
-    const net::ResolveErrorInfo& resolve_error_info,
-    const base::Optional<net::AddressList>& resolved_addresses) {
+    HostResolver::ResolutionResult& resolution_result) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
-  bool success = result == net::OK && !resolved_addresses->empty() &&
-                 resolved_addresses.has_value();
+  host_resolver_.reset();
+  bool success = resolution_result.result == net::OK &&
+                 !resolution_result.resolved_addresses->empty() &&
+                 resolution_result.resolved_addresses.has_value();
   if (!success) {
-    OnDone(result, ProbeExitEnum::kDnsFailure);
+    OnDone(resolution_result.result, ProbeExitEnum::kDnsFailure);
     return;
   }
 
@@ -187,7 +105,8 @@
   DCHECK(network_context);
 
   network_context->CreateTCPConnectedSocket(
-      /*local_addr=*/base::nullopt, resolved_addresses.value(),
+      /*local_addr=*/base::nullopt,
+      resolution_result.resolved_addresses.value(),
       /*options=*/nullptr,
       net::MutableNetworkTrafficAnnotationTag(GetTrafficAnnotationTag()),
       std::move(pending_receiver), /*observer=*/mojo::NullRemote(),
diff --git a/chrome/browser/chromeos/net/network_diagnostics/tls_prober.h b/chrome/browser/chromeos/net/network_diagnostics/tls_prober.h
index 9f433ec..f02f1e8d 100644
--- a/chrome/browser/chromeos/net/network_diagnostics/tls_prober.h
+++ b/chrome/browser/chromeos/net/network_diagnostics/tls_prober.h
@@ -11,6 +11,7 @@
 #include "base/memory/scoped_refptr.h"
 #include "base/memory/weak_ptr.h"
 #include "base/sequenced_task_runner.h"
+#include "chrome/browser/chromeos/net/network_diagnostics/host_resolver.h"
 #include "mojo/public/cpp/bindings/remote.h"
 #include "net/base/completion_once_callback.h"
 #include "services/network/public/mojom/network_context.mojom.h"
@@ -18,10 +19,6 @@
 #include "services/network/public/mojom/tls_socket.mojom.h"
 #include "url/gurl.h"
 
-namespace net {
-class AddressList;
-}
-
 namespace chromeos {
 namespace network_diagnostics {
 
@@ -39,7 +36,6 @@
     kMojoDisconnectFailure,
     kSuccess,
   };
-  class HostResolver;
   using NetworkContextGetter =
       base::RepeatingCallback<network::mojom::NetworkContext*()>;
   using OnConnectCompleteOnUIThreadCallback = base::OnceCallback<
@@ -62,9 +58,7 @@
 
   // Processes the results of the DNS resolution done by |host_resolver_|.
   void OnHostResolutionComplete(
-      int result,
-      const net::ResolveErrorInfo& resolve_error_info,
-      const base::Optional<net::AddressList>& resolved_addresses);
+      HostResolver::ResolutionResult& resolution_result);
 
  protected:
   // Test-only constructor.
diff --git a/chrome/browser/chromeos/net/network_diagnostics/tls_prober_unittest.cc b/chrome/browser/chromeos/net/network_diagnostics/tls_prober_unittest.cc
index 349c45d..16d1905 100644
--- a/chrome/browser/chromeos/net/network_diagnostics/tls_prober_unittest.cc
+++ b/chrome/browser/chromeos/net/network_diagnostics/tls_prober_unittest.cc
@@ -4,7 +4,6 @@
 
 #include "chrome/browser/chromeos/net/network_diagnostics/tls_prober.h"
 
-#include <deque>
 #include <memory>
 #include <utility>
 #include <vector>
@@ -48,11 +47,11 @@
       const TlsProberWithFakeNetworkContextTest&) = delete;
 
   void InitializeProberNetworkContext(
-      std::deque<FakeHostResolver::DnsResult*> fake_dns_result,
+      std::unique_ptr<FakeHostResolver::DnsResult> fake_dns_result,
       base::Optional<net::Error> tcp_connect_code,
       base::Optional<net::Error> tls_upgrade_code) {
     fake_network_context_ = std::make_unique<FakeNetworkContext>();
-    fake_network_context_->set_fake_dns_results(std::move(fake_dns_result));
+    fake_network_context_->set_fake_dns_result(std::move(fake_dns_result));
     fake_network_context_->SetTCPConnectCode(tcp_connect_code);
     fake_network_context_->SetTLSUpgradeCode(tls_upgrade_code);
   }
@@ -65,8 +64,7 @@
             [](network::mojom::NetworkContext* network_context) {
               return network_context;
             },
-            static_cast<network::mojom::NetworkContext*>(
-                fake_network_context_.get())),
+            fake_network_context_.get()),
         url, std::move(callback));
   }
 
@@ -87,10 +85,9 @@
 
 TEST_F(TlsProberWithFakeNetworkContextTest,
        SocketConnectedAndUpgradedSuccessfully) {
-  auto dns_result = std::make_unique<FakeHostResolver::DnsResult>(
+  auto fake_dns_result = std::make_unique<FakeHostResolver::DnsResult>(
       net::OK, net::ResolveErrorInfo(net::OK),
       net::AddressList(kFakeIPAddress));
-  std::deque<FakeHostResolver::DnsResult*> fake_dns_result = {dns_result.get()};
   net::Error tcp_connect_code = net::OK;
   net::Error tls_upgrade_code = net::OK;
   InitializeProberNetworkContext(std::move(fake_dns_result), tcp_connect_code,
@@ -114,10 +111,9 @@
 }
 
 TEST_F(TlsProberWithFakeNetworkContextTest, FailedDnsLookup) {
-  auto dns_result = std::make_unique<FakeHostResolver::DnsResult>(
+  auto fake_dns_result = std::make_unique<FakeHostResolver::DnsResult>(
       net::ERR_NAME_NOT_RESOLVED,
       net::ResolveErrorInfo(net::ERR_NAME_NOT_RESOLVED), net::AddressList());
-  std::deque<FakeHostResolver::DnsResult*> fake_dns_result = {dns_result.get()};
   // Neither TCP connect nor TLS upgrade should not be called in this scenario.
   InitializeProberNetworkContext(std::move(fake_dns_result),
                                  /*tcp_connect_code=*/base::nullopt,
@@ -141,10 +137,9 @@
 }
 
 TEST_F(TlsProberWithFakeNetworkContextTest, MojoDisconnectDuringDnsLookup) {
-  // Host resolution will not be successful due to Mojo disconnect.
-  std::deque<FakeHostResolver::DnsResult*> fake_dns_result = {};
-  // Neither TCP connect nor TLS upgrade should not be called in this scenario.
-  InitializeProberNetworkContext(std::move(fake_dns_result),
+  // Host resolution will not be successful due to Mojo disconnect. Neither TCP
+  // connect nor TLS upgrade should not be called in this scenario.
+  InitializeProberNetworkContext(/*fake_dns_result=*/{},
                                  /*tcp_connect_code=*/base::nullopt,
                                  /*tls_upgrade_code=*/base::nullopt);
   fake_network_context()->set_disconnect_during_host_resolution(true);
@@ -167,10 +162,9 @@
 }
 
 TEST_F(TlsProberWithFakeNetworkContextTest, FailedTcpConnection) {
-  auto dns_result = std::make_unique<FakeHostResolver::DnsResult>(
+  auto fake_dns_result = std::make_unique<FakeHostResolver::DnsResult>(
       net::OK, net::ResolveErrorInfo(net::OK),
       net::AddressList(kFakeIPAddress));
-  std::deque<FakeHostResolver::DnsResult*> fake_dns_result = {dns_result.get()};
   net::Error tcp_connect_code = net::ERR_CONNECTION_FAILED;
   // TLS upgrade should not be called in this scenario.
   InitializeProberNetworkContext(std::move(fake_dns_result), tcp_connect_code,
@@ -194,10 +188,9 @@
 }
 
 TEST_F(TlsProberWithFakeNetworkContextTest, FailedTlsUpgrade) {
-  auto dns_result = std::make_unique<FakeHostResolver::DnsResult>(
+  auto fake_dns_result = std::make_unique<FakeHostResolver::DnsResult>(
       net::OK, net::ResolveErrorInfo(net::OK),
       net::AddressList(kFakeIPAddress));
-  std::deque<FakeHostResolver::DnsResult*> fake_dns_result = {dns_result.get()};
   net::Error tcp_connect_code = net::OK;
   net::Error tls_upgrade_code = net::ERR_SSL_PROTOCOL_ERROR;
   InitializeProberNetworkContext(std::move(fake_dns_result), tcp_connect_code,
@@ -222,10 +215,9 @@
 
 TEST_F(TlsProberWithFakeNetworkContextTest,
        MojoDisconnectedDuringTcpConnectionAttempt) {
-  auto dns_result = std::make_unique<FakeHostResolver::DnsResult>(
+  auto fake_dns_result = std::make_unique<FakeHostResolver::DnsResult>(
       net::OK, net::ResolveErrorInfo(net::OK),
       net::AddressList(kFakeIPAddress));
-  std::deque<FakeHostResolver::DnsResult*> fake_dns_result = {dns_result.get()};
   // Since the TCP connection is disconnected, no connection codes are needed.
   InitializeProberNetworkContext(std::move(fake_dns_result),
                                  /*tcp_connect_code=*/base::nullopt,
@@ -251,10 +243,9 @@
 
 TEST_F(TlsProberWithFakeNetworkContextTest,
        MojoDisconnectedDuringTlsUpgradeAttempt) {
-  auto dns_result = std::make_unique<FakeHostResolver::DnsResult>(
+  auto fake_dns_result = std::make_unique<FakeHostResolver::DnsResult>(
       net::OK, net::ResolveErrorInfo(net::OK),
       net::AddressList(kFakeIPAddress));
-  std::deque<FakeHostResolver::DnsResult*> fake_dns_result = {dns_result.get()};
   net::Error tcp_connect_code = net::OK;
   // TLS upgrade attempt will fail due to disconnection. |tls_upgrade_code|
   // is only populated to correctly initialize the FakeNetworkContext instance.
diff --git a/chrome/browser/chromeos/net/network_diagnostics/udp_prober.cc b/chrome/browser/chromeos/net/network_diagnostics/udp_prober.cc
new file mode 100644
index 0000000..09b82fc
--- /dev/null
+++ b/chrome/browser/chromeos/net/network_diagnostics/udp_prober.cc
@@ -0,0 +1,150 @@
+// 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/chromeos/net/network_diagnostics/udp_prober.h"
+
+#include <utility>
+
+#include "base/logging.h"
+#include "chrome/browser/profiles/profile_manager.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/browser/browser_thread.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "net/base/address_list.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/net_errors.h"
+#include "net/dns/public/resolve_error_info.h"
+#include "net/traffic_annotation/network_traffic_annotation.h"
+
+namespace chromeos {
+namespace network_diagnostics {
+
+UdpProber::UdpProber(NetworkContextGetter network_context_getter,
+                     net::HostPortPair host_port_pair,
+                     base::span<const uint8_t> data,
+                     net::NetworkTrafficAnnotationTag tag,
+                     UdpProbeCompleteCallback callback)
+    : network_context_getter_(std::move(network_context_getter)),
+      host_port_pair_(host_port_pair),
+      data_(std::move(data)),
+      tag_(tag),
+      callback_(std::move(callback)) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  DCHECK(!data_.empty());
+  DCHECK(callback_);
+  DCHECK(!host_port_pair_.IsEmpty());
+
+  network::mojom::NetworkContext* network_context =
+      network_context_getter_.Run();
+  DCHECK(network_context);
+
+  host_resolver_ = std::make_unique<HostResolver>(
+      host_port_pair_, network_context,
+      base::BindOnce(&UdpProber::OnHostResolutionComplete,
+                     weak_factory_.GetWeakPtr()));
+}
+
+UdpProber::~UdpProber() = default;
+
+void UdpProber::OnHostResolutionComplete(
+    HostResolver::ResolutionResult& resolution_result) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+  bool success = resolution_result.result == net::OK &&
+                 !resolution_result.resolved_addresses->empty() &&
+                 resolution_result.resolved_addresses.has_value();
+  if (!success) {
+    OnDone(resolution_result.result, ProbeExitEnum::kDnsFailure);
+    return;
+  }
+
+  network::mojom::NetworkContext* network_context =
+      network_context_getter_.Run();
+  DCHECK(network_context);
+
+  auto pending_receiver = udp_socket_remote_.BindNewPipeAndPassReceiver();
+  udp_socket_remote_.set_disconnect_handler(
+      base::BindOnce(&UdpProber::OnDisconnect, weak_factory_.GetWeakPtr()));
+
+  auto pending_remote =
+      udp_socket_listener_receiver_.BindNewPipeAndPassRemote();
+  udp_socket_listener_receiver_.set_disconnect_handler(
+      base::BindOnce(&UdpProber::OnDisconnect, weak_factory_.GetWeakPtr()));
+
+  network_context->CreateUDPSocket(std::move(pending_receiver),
+                                   std::move(pending_remote));
+  udp_socket_remote_->Connect(
+      resolution_result.resolved_addresses.value().front(), nullptr,
+      base::BindOnce(&UdpProber::OnConnectComplete,
+                     weak_factory_.GetWeakPtr()));
+}
+
+void UdpProber::OnConnectComplete(
+    int result,
+    const base::Optional<net::IPEndPoint>& local_addr_out) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+  if (result != net::OK) {
+    OnDone(result, ProbeExitEnum::kConnectFailure);
+    return;
+  }
+  udp_socket_remote_->Send(
+      std::move(data_), net::MutableNetworkTrafficAnnotationTag(tag_),
+      base::BindOnce(&UdpProber::OnSendComplete, weak_factory_.GetWeakPtr()));
+}
+
+void UdpProber::OnSendComplete(int result) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+  if (result != net::OK) {
+    OnDone(result, ProbeExitEnum::kSendFailure);
+    return;
+  }
+  udp_socket_remote_->ReceiveMore(/*num_additional_datagrams=*/1);
+}
+
+void UdpProber::OnReceived(int32_t result,
+                           const base::Optional<net::IPEndPoint>& src_ip,
+                           base::Optional<base::span<const uint8_t>> data) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+  if (result != net::OK) {
+    OnDone(result, ProbeExitEnum::kNetworkErrorOnReceiveFailure);
+    return;
+  }
+
+  // The UdpProber instance is only interested in validating whether
+  // data can be received from the destination host.
+  if (!data.has_value() || data.value().empty()) {
+    // Note that net::ERR_FAILED is reported even if |result| is net::OK
+    // when no data is received.
+    OnDone(net::ERR_FAILED, ProbeExitEnum::kNoDataReceivedFailure);
+    return;
+  }
+  OnDone(net::OK, ProbeExitEnum::kSuccess);
+}
+
+void UdpProber::OnDone(int result, ProbeExitEnum probe_exit_enum) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+  // Invalidate pending callbacks.
+  weak_factory_.InvalidateWeakPtrs();
+  // Destroy the socket connection.
+  udp_socket_listener_receiver_.reset();
+  udp_socket_remote_.reset();
+  // Reset the host resolver.
+  host_resolver_.reset();
+
+  std::move(callback_).Run(result, probe_exit_enum);
+}
+
+void UdpProber::OnDisconnect() {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+  OnDone(net::ERR_FAILED, ProbeExitEnum::kMojoDisconnectFailure);
+}
+
+}  // namespace network_diagnostics
+}  // namespace chromeos
diff --git a/chrome/browser/chromeos/net/network_diagnostics/udp_prober.h b/chrome/browser/chromeos/net/network_diagnostics/udp_prober.h
new file mode 100644
index 0000000..9acd39a
--- /dev/null
+++ b/chrome/browser/chromeos/net/network_diagnostics/udp_prober.h
@@ -0,0 +1,123 @@
+// 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_CHROMEOS_NET_NETWORK_DIAGNOSTICS_UDP_PROBER_H_
+#define CHROME_BROWSER_CHROMEOS_NET_NETWORK_DIAGNOSTICS_UDP_PROBER_H_
+
+#include <cstdint>
+
+#include "base/callback.h"
+#include "base/containers/span.h"
+#include "base/memory/weak_ptr.h"
+#include "base/optional.h"
+#include "chrome/browser/chromeos/net/network_diagnostics/host_resolver.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "net/base/address_list.h"
+#include "net/base/ip_endpoint.h"
+#include "net/dns/public/resolve_error_info.h"
+#include "net/traffic_annotation/network_traffic_annotation.h"
+#include "services/network/public/mojom/network_context.mojom.h"
+#include "services/network/public/mojom/udp_socket.mojom.h"
+#include "url/gurl.h"
+
+namespace chromeos {
+namespace network_diagnostics {
+
+// Uses a UDP socket to send data to a remote destination. After sending data,
+// the prober listens for received data. It confirms that data was received but
+// does not validate the content, hence no data is parsed. Used by network
+// diagnostic routines.
+class UdpProber : public network::mojom::UDPSocketListener {
+ public:
+  // Lists the ways a prober may end. The callback passed into the prober's
+  // constructor is invoked while exiting.
+  enum ProbeExitEnum {
+    kDnsFailure,
+    kConnectFailure,
+    kSendFailure,
+    kNetworkErrorOnReceiveFailure,
+    kMojoDisconnectFailure,
+    kNoDataReceivedFailure,
+    kSuccess,
+  };
+
+  using NetworkContextGetter =
+      base::RepeatingCallback<network::mojom::NetworkContext*()>;
+  using ConnectCallback = base::OnceCallback<
+      void(int result, const base::Optional<net::IPEndPoint>& local_addr_out)>;
+  using SendCallback = base::OnceCallback<void(int result)>;
+  using UdpProbeCompleteCallback =
+      base::OnceCallback<void(int result, ProbeExitEnum probe_exit_enum)>;
+
+  // Establishes a UDP connection and sends |data| to |host_port_pair|. The
+  // traffic sent by the prober is described by |tag|. Note that the constructor
+  // will not invoke |callback|, which is passed into UdpProber during
+  // construction. This ensures the UdpProber instance is constructed before
+  // |callback| is invoked. The UdpProber must be created on the UI thread and
+  // will invoke |callback| on the UI thread. |network_context_getter| will be
+  // invoked on the UI thread.
+  UdpProber(NetworkContextGetter network_context_getter,
+            net::HostPortPair host_port_pair,
+            base::span<const uint8_t> data,
+            net::NetworkTrafficAnnotationTag tag,
+            UdpProbeCompleteCallback callback);
+  UdpProber(const UdpProber&) = delete;
+  UdpProber& operator=(const UdpProber&) = delete;
+  ~UdpProber() override;
+
+ private:
+  // Processes the results of the DNS resolution done by |host_resolver_|.
+  void OnHostResolutionComplete(
+      HostResolver::ResolutionResult& resolution_result);
+
+  // On success, the UDP socket is connected to the destination and is ready to
+  // send data. On failure, the UdpProber exits with a failure.
+  void OnConnectComplete(int result,
+                         const base::Optional<net::IPEndPoint>& local_addr_out);
+
+  // On success, the UDP socket is ready to receive data. So long as the
+  // received data is not empty, it is considered valid. The content itself is
+  // not verified.
+  void OnSendComplete(int result);
+
+  // network::mojom::UDPSocketListener:
+  void OnReceived(int32_t result,
+                  const base::Optional<net::IPEndPoint>& src_ip,
+                  base::Optional<base::span<const uint8_t>> data) override;
+
+  // Signals the end of the probe. Manages the clean up and returns a response
+  // to the caller.
+  void OnDone(int result, ProbeExitEnum probe_exit_enum);
+
+  // Handles disconnects on the UDPSocket remote and UDPSocketListener receiver.
+  void OnDisconnect();
+
+  // Gets the active profile-specific network context.
+  NetworkContextGetter network_context_getter_;
+  // Contains the hostname and port.
+  net::HostPortPair host_port_pair_;
+  // Data to be sent to the destination.
+  base::span<const uint8_t> data_;
+  // Network annotation tag describing the socket traffic.
+  net::NetworkTrafficAnnotationTag tag_;
+  // Host resolver used for DNS lookup.
+  std::unique_ptr<HostResolver> host_resolver_;
+  // Stores the callback invoked once probe is complete or interrupted.
+  UdpProbeCompleteCallback callback_;
+  // Holds the UDPSocket remote.
+  mojo::Remote<network::mojom::UDPSocket> udp_socket_remote_;
+  // Listens to the response from hostname specified by |url_|.
+  mojo::Receiver<network::mojom::UDPSocketListener>
+      udp_socket_listener_receiver_{this};
+
+  // Must be the last member so that any callbacks taking a weak pointer to this
+  // instance are invalidated first during object destruction.
+  base::WeakPtrFactory<UdpProber> weak_factory_{this};
+};
+
+}  // namespace network_diagnostics
+}  // namespace chromeos
+
+#endif  // CHROME_BROWSER_CHROMEOS_NET_NETWORK_DIAGNOSTICS_UDP_PROBER_H_
diff --git a/chrome/browser/chromeos/net/network_diagnostics/udp_prober_unittest.cc b/chrome/browser/chromeos/net/network_diagnostics/udp_prober_unittest.cc
new file mode 100644
index 0000000..9b236bf
--- /dev/null
+++ b/chrome/browser/chromeos/net/network_diagnostics/udp_prober_unittest.cc
@@ -0,0 +1,254 @@
+// 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/chromeos/net/network_diagnostics/udp_prober.h"
+
+#include <cstdint>
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/containers/span.h"
+#include "base/run_loop.h"
+#include "base/test/bind.h"
+#include "chrome/browser/chromeos/net/network_diagnostics/fake_host_resolver.h"
+#include "chrome/browser/chromeos/net/network_diagnostics/fake_network_context.h"
+#include "chrome/browser/chromeos/net/network_diagnostics/network_diagnostics_util.h"
+#include "content/public/test/browser_task_environment.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/ip_endpoint.h"
+#include "net/base/net_errors.h"
+#include "net/traffic_annotation/network_traffic_annotation.h"
+#include "services/network/test/test_network_context.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace chromeos {
+namespace network_diagnostics {
+
+namespace {
+
+using ProbeExitEnum = UdpProber::ProbeExitEnum;
+
+}  // namespace
+
+class UdpProberWithFakeNetworkContextTest : public ::testing::Test {
+ public:
+  UdpProberWithFakeNetworkContextTest() = default;
+  UdpProberWithFakeNetworkContextTest(
+      const UdpProberWithFakeNetworkContextTest&) = delete;
+  UdpProberWithFakeNetworkContextTest& operator=(
+      const UdpProberWithFakeNetworkContextTest&) = delete;
+
+  void InitializeProberNetworkContext(
+      std::unique_ptr<FakeHostResolver::DnsResult> fake_dns_result,
+      base::Optional<net::Error> udp_connect_complete_code,
+      base::Optional<net::Error> udp_send_complete_code,
+      base::Optional<net::Error> udp_on_received_code,
+      base::Optional<base::span<const uint8_t>> udp_on_received_data) {
+    fake_network_context_ = std::make_unique<FakeNetworkContext>();
+    fake_network_context_->set_fake_dns_result(std::move(fake_dns_result));
+    if (udp_connect_complete_code.has_value()) {
+      fake_network_context_->SetUdpConnectCode(
+          udp_connect_complete_code.value());
+    }
+    if (udp_send_complete_code.has_value()) {
+      fake_network_context_->SetUdpSendCode(udp_send_complete_code.value());
+    }
+    if (udp_on_received_code.has_value()) {
+      fake_network_context_->SetUdpOnReceivedCode(udp_on_received_code.value());
+    }
+    if (udp_on_received_data.has_value()) {
+      fake_network_context_->SetUdpOnReceivedData(
+          std::move(udp_on_received_data.value()));
+    }
+  }
+
+  void CreateAndExecuteUdpProber(base::span<const uint8_t> data,
+                                 UdpProber::UdpProbeCompleteCallback callback) {
+    ASSERT_TRUE(fake_network_context_);
+    udp_prober_ = std::make_unique<UdpProber>(
+        base::BindRepeating(
+            [](network::mojom::NetworkContext* network_context) {
+              return network_context;
+            },
+            fake_network_context_.get()),
+        kFakeHostPortPair, std::move(data), kStunTag, std::move(callback));
+  }
+
+  void RunProberExpectingResult(int expected_result,
+                                ProbeExitEnum expected_exit_enum) {
+    int probe_result = -1;
+    ProbeExitEnum probe_exit_enum = ProbeExitEnum::kConnectFailure;
+    base::RunLoop run_loop;
+    CreateAndExecuteUdpProber(
+        kValidStunData,
+        base::BindLambdaForTesting([&](int result, ProbeExitEnum exit_enum) {
+          probe_result = result;
+          probe_exit_enum = exit_enum;
+          EXPECT_EQ(expected_result, probe_result);
+          EXPECT_EQ(expected_exit_enum, probe_exit_enum);
+          run_loop.Quit();
+        }));
+    run_loop.Run();
+  }
+
+  FakeNetworkContext* fake_network_context() {
+    return fake_network_context_.get();
+  }
+
+ protected:
+  const net::HostPortPair kFakeHostPortPair =
+      net::HostPortPair::FromString("fake_stun_server.com:80");
+  const net::IPEndPoint kFakeIPAddress{
+      net::IPEndPoint(net::IPAddress::IPv4Localhost(), /*port=*/1234)};
+  const base::span<const uint8_t> kValidStunData = util::GetStunHeader();
+  const net::NetworkTrafficAnnotationTag kStunTag =
+      util::GetStunNetworkAnnotationTag();
+
+ private:
+  content::BrowserTaskEnvironment task_environment_;
+  std::unique_ptr<FakeNetworkContext> fake_network_context_;
+  std::unique_ptr<UdpProber> udp_prober_;
+};
+
+TEST_F(UdpProberWithFakeNetworkContextTest, SuccessfulEndToEndResponse) {
+  auto fake_dns_result = std::make_unique<FakeHostResolver::DnsResult>(
+      net::OK, net::ResolveErrorInfo(net::OK),
+      net::AddressList(kFakeIPAddress));
+  std::array<uint8_t, 1> udp_on_received_data = {0x00};
+  InitializeProberNetworkContext(std::move(fake_dns_result),
+                                 /*udp_connect_code=*/net::OK,
+                                 /*udp_send_complete_code=*/net::OK,
+                                 /*udp_on_received_code=*/net::OK,
+                                 udp_on_received_data);
+  RunProberExpectingResult(net::OK, ProbeExitEnum::kSuccess);
+}
+
+TEST_F(UdpProberWithFakeNetworkContextTest, FailedDnsLookup) {
+  auto fake_dns_result = std::make_unique<FakeHostResolver::DnsResult>(
+      net::ERR_NAME_NOT_RESOLVED,
+      net::ResolveErrorInfo(net::ERR_NAME_NOT_RESOLVED), net::AddressList());
+  // UDP connect and subsequent steps will not happen in this scenario.
+  InitializeProberNetworkContext(std::move(fake_dns_result),
+                                 /*udp_connect_code=*/base::nullopt,
+                                 /*udp_send_complete_code=*/base::nullopt,
+                                 /*udp_on_received_code=*/base::nullopt,
+                                 /*udp_on_received_data=*/base::nullopt);
+  RunProberExpectingResult(net::ERR_NAME_NOT_RESOLVED,
+                           ProbeExitEnum::kDnsFailure);
+}
+
+TEST_F(UdpProberWithFakeNetworkContextTest, MojoDisconnectDnsLookup) {
+  // UDP connect and subsequent steps will not happen in this scenario.
+  InitializeProberNetworkContext(/*fake_dns_result=*/{},
+                                 /*udp_connect_code=*/base::nullopt,
+                                 /*udp_send_code=*/base::nullopt,
+                                 /*udp_on_received_code=*/base::nullopt,
+                                 /*udp_on_received_data=*/base::nullopt);
+  fake_network_context()->set_disconnect_during_host_resolution(true);
+  RunProberExpectingResult(net::ERR_NAME_NOT_RESOLVED,
+                           ProbeExitEnum::kDnsFailure);
+}
+
+TEST_F(UdpProberWithFakeNetworkContextTest, FailedUdpConnection) {
+  auto fake_dns_result = std::make_unique<FakeHostResolver::DnsResult>(
+      net::OK, net::ResolveErrorInfo(net::OK),
+      net::AddressList(kFakeIPAddress));
+  InitializeProberNetworkContext(
+      std::move(fake_dns_result),
+      /*udp_connect_code=*/net::ERR_CONNECTION_FAILED,
+      /*udp_send_code=*/base::nullopt,
+      /*udp_on_received_code=*/base::nullopt,
+      /*udp_on_received_data=*/base::nullopt);
+  RunProberExpectingResult(net::ERR_CONNECTION_FAILED,
+                           ProbeExitEnum::kConnectFailure);
+}
+
+TEST_F(UdpProberWithFakeNetworkContextTest, MojoDisconnectDuringUdpConnection) {
+  auto fake_dns_result = std::make_unique<FakeHostResolver::DnsResult>(
+      net::OK, net::ResolveErrorInfo(net::OK),
+      net::AddressList(kFakeIPAddress));
+  InitializeProberNetworkContext(std::move(fake_dns_result),
+                                 /*udp_connect_code=*/base::nullopt,
+                                 /*udp_send_code=*/base::nullopt,
+                                 /*udp_on_received_code=*/base::nullopt,
+                                 /*udp_on_received_data=*/base::nullopt);
+  fake_network_context()->set_disconnect_during_udp_connection_attempt(true);
+  RunProberExpectingResult(net::ERR_FAILED,
+                           ProbeExitEnum::kMojoDisconnectFailure);
+}
+
+TEST_F(UdpProberWithFakeNetworkContextTest, FailedUdpSend) {
+  auto fake_dns_result = std::make_unique<FakeHostResolver::DnsResult>(
+      net::OK, net::ResolveErrorInfo(net::OK),
+      net::AddressList(kFakeIPAddress));
+  InitializeProberNetworkContext(std::move(fake_dns_result),
+                                 /*udp_connect_code=*/net::OK,
+                                 /*udp_send_code=*/net::ERR_CONNECTION_FAILED,
+                                 /*udp_on_received_code=*/base::nullopt,
+                                 /*udp_on_received_data=*/base::nullopt);
+  RunProberExpectingResult(net::ERR_CONNECTION_FAILED,
+                           ProbeExitEnum::kSendFailure);
+}
+
+TEST_F(UdpProberWithFakeNetworkContextTest, MojoDisconnectDuringUdpSend) {
+  auto fake_dns_result = std::make_unique<FakeHostResolver::DnsResult>(
+      net::OK, net::ResolveErrorInfo(net::OK),
+      net::AddressList(kFakeIPAddress));
+  net::Error udp_connect_code = net::OK;
+  InitializeProberNetworkContext(std::move(fake_dns_result), udp_connect_code,
+                                 /*udp_send_code=*/base::nullopt,
+                                 /*udp_on_received_code=*/base::nullopt,
+                                 /*udp_on_received_data=*/base::nullopt);
+  fake_network_context()->SetDisconnectDuringUdpSendAttempt(true);
+  RunProberExpectingResult(net::ERR_FAILED,
+                           ProbeExitEnum::kMojoDisconnectFailure);
+}
+
+TEST_F(UdpProberWithFakeNetworkContextTest, BadUdpNetworkCodeOnReceive) {
+  auto fake_dns_result = std::make_unique<FakeHostResolver::DnsResult>(
+      net::OK, net::ResolveErrorInfo(net::OK),
+      net::AddressList(kFakeIPAddress));
+  InitializeProberNetworkContext(
+      std::move(fake_dns_result),
+      /*udp_connect_code=*/net::OK,
+      /*udp_send_code=*/net::OK,
+      /*udp_on_received_code=*/net::ERR_CONNECTION_FAILED,
+      /*udp_on_received_data=*/base::nullopt);
+  RunProberExpectingResult(net::ERR_CONNECTION_FAILED,
+                           ProbeExitEnum::kNetworkErrorOnReceiveFailure);
+}
+
+TEST_F(UdpProberWithFakeNetworkContextTest, NoDataReceivedOnReceiveFailure) {
+  auto fake_dns_result = std::make_unique<FakeHostResolver::DnsResult>(
+      net::OK, net::ResolveErrorInfo(net::OK),
+      net::AddressList(kFakeIPAddress));
+  InitializeProberNetworkContext(std::move(fake_dns_result),
+                                 /*udp_connect_code=*/net::OK,
+                                 /*udp_send_code=*/net::OK,
+                                 /*udp_on_received_code*/ net::OK,
+                                 /*udp_on_received_data=*/{});
+  RunProberExpectingResult(net::ERR_FAILED,
+                           ProbeExitEnum::kNoDataReceivedFailure);
+}
+
+TEST_F(UdpProberWithFakeNetworkContextTest, MojoDisconnectDuringUdpReceive) {
+  auto fake_dns_result = std::make_unique<FakeHostResolver::DnsResult>(
+      net::OK, net::ResolveErrorInfo(net::OK),
+      net::AddressList(kFakeIPAddress));
+  InitializeProberNetworkContext(std::move(fake_dns_result),
+                                 /*udp_connect_code=*/net::OK,
+                                 /*udp_send_code=*/net::OK,
+                                 /*udp_on_received_code=*/net::OK,
+                                 /*udp_on_received_data=*/{});
+  fake_network_context()->SetDisconnectDuringUdpReceiveAttempt(true);
+  RunProberExpectingResult(net::ERR_FAILED,
+                           ProbeExitEnum::kMojoDisconnectFailure);
+}
+
+}  // namespace network_diagnostics
+}  // namespace chromeos
diff --git a/chrome/browser/chromeos/scanning/scan_service.cc b/chrome/browser/chromeos/scanning/scan_service.cc
index d1ef6fb..98bc774 100644
--- a/chrome/browser/chromeos/scanning/scan_service.cc
+++ b/chrome/browser/chromeos/scanning/scan_service.cc
@@ -90,7 +90,12 @@
     return;
   }
 
+  scan_job_observer_.reset();
   scan_job_observer_.Bind(std::move(observer));
+  // Unretained is safe here, because `this` owns `scan_job_observer_`, and no
+  // reply callbacks will be invoked once the mojo::Remote is destroyed.
+  scan_job_observer_.set_disconnect_handler(
+      base::BindOnce(&ScanService::CancelScan, base::Unretained(this)));
 
   base::Time::Now().LocalExplode(&start_time_);
   save_failed_ = false;
@@ -106,6 +111,11 @@
   std::move(callback).Run(true);
 }
 
+void ScanService::CancelScan() {
+  lorgnette_scanner_manager_->CancelScan(base::BindOnce(
+      &ScanService::OnCancelCompleted, weak_ptr_factory_.GetWeakPtr()));
+}
+
 void ScanService::BindInterface(
     mojo::PendingReceiver<mojo_ipc::ScanService> pending_receiver) {
   receiver_.Bind(std::move(pending_receiver));
@@ -159,7 +169,6 @@
 void ScanService::OnProgressPercentReceived(uint32_t progress_percent,
                                             uint32_t page_number) {
   DCHECK_LE(progress_percent, kMaxProgressPercent);
-  DCHECK(scan_job_observer_.is_connected());
   scan_job_observer_->OnPageProgress(page_number, progress_percent);
 }
 
@@ -171,7 +180,6 @@
   // vector.
   // In case the last reported progress percent was less than 100, send one
   // final progress event before the page complete event.
-  DCHECK(scan_job_observer_.is_connected());
   scan_job_observer_->OnPageProgress(page_number, kMaxProgressPercent);
   scan_job_observer_->OnPageComplete(
       std::vector<uint8_t>(scanned_image.begin(), scanned_image.end()));
@@ -208,9 +216,11 @@
 }
 
 void ScanService::OnScanCompleted(bool success) {
-  DCHECK(scan_job_observer_.is_connected());
   scan_job_observer_->OnScanComplete(success && !save_failed_);
-  scan_job_observer_.reset();
+}
+
+void ScanService::OnCancelCompleted(bool success) {
+  scan_job_observer_->OnCancelComplete(success);
 }
 
 bool ScanService::FilePathSupported(const base::FilePath& file_path) {
diff --git a/chrome/browser/chromeos/scanning/scan_service.h b/chrome/browser/chromeos/scanning/scan_service.h
index 781dea3..28e3b2b2 100644
--- a/chrome/browser/chromeos/scanning/scan_service.h
+++ b/chrome/browser/chromeos/scanning/scan_service.h
@@ -48,6 +48,7 @@
                  scanning::mojom::ScanSettingsPtr settings,
                  mojo::PendingRemote<scanning::mojom::ScanJobObserver> observer,
                  StartScanCallback callback) override;
+  void CancelScan() override;
 
   // Binds receiver_ by consuming |pending_receiver|.
   void BindInterface(
@@ -90,6 +91,10 @@
   // Processes the final result of calling LorgnetteScannerManager::Scan().
   void OnScanCompleted(bool success);
 
+  // Processes the final result of calling
+  // LorgnetteScannerManager::CancelScan().
+  void OnCancelCompleted(bool success);
+
   // TODO(jschettler): Replace this with a generic helper function when one is
   // available.
   // Determines whether the service supports saving scanned images to
diff --git a/chrome/browser/chromeos/scanning/scan_service_unittest.cc b/chrome/browser/chromeos/scanning/scan_service_unittest.cc
index 7aa812f..282b21e 100644
--- a/chrome/browser/chromeos/scanning/scan_service_unittest.cc
+++ b/chrome/browser/chromeos/scanning/scan_service_unittest.cc
@@ -88,6 +88,10 @@
 
   void OnScanComplete(bool success) override { scan_success_ = success; }
 
+  void OnCancelComplete(bool success) override {
+    cancel_scan_success_ = success;
+  }
+
   // Creates a pending remote that can be passed in calls to
   // ScanService::StartScan().
   mojo::PendingRemote<mojo_ipc::ScanJobObserver> GenerateRemote() {
@@ -104,10 +108,14 @@
     return progress_ == 100 && page_complete_ && scan_success_;
   }
 
+  // Returns true if the cancel scan request completed successfully.
+  bool cancel_scan_success() const { return cancel_scan_success_; }
+
  private:
   uint32_t progress_ = 0;
   bool page_complete_ = false;
   bool scan_success_ = false;
+  bool cancel_scan_success_ = false;
   mojo::Receiver<mojo_ipc::ScanJobObserver> receiver_{this};
 };
 
@@ -152,6 +160,12 @@
     return success;
   }
 
+  // Performs a cancel scan request.
+  void CancelScan() {
+    scan_service_remote_->CancelScan();
+    scan_service_remote_.FlushForTesting();
+  }
+
  protected:
   base::ScopedTempDir temp_dir_;
   FakeLorgnetteScannerManager fake_lorgnette_scanner_manager_;
@@ -295,4 +309,21 @@
   }
 }
 
+// Test that canceling sends an update to the observer OnCancelComplete().
+TEST_F(ScanServiceTest, CancelScanBeforeScanCompletes) {
+  fake_lorgnette_scanner_manager_.SetGetScannerNamesResponse(
+      {kFirstTestScannerName});
+  fake_lorgnette_scanner_manager_.SetScanResponse("TestData");
+  auto scanners = GetScanners();
+  ASSERT_EQ(scanners.size(), 1u);
+
+  scan_service_.SetMyFilesPathForTesting(temp_dir_.GetPath());
+  mojo_ipc::ScanSettings settings;
+  settings.scan_to_path = temp_dir_.GetPath();
+
+  Scan(scanners[0]->id, settings.Clone());
+  CancelScan();
+  EXPECT_TRUE(fake_scan_job_observer_.cancel_scan_success());
+}
+
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/web_applications/camera_app_integration_browsertest.cc b/chrome/browser/chromeos/web_applications/camera_app_integration_browsertest.cc
index 5cf0030..f1590674 100644
--- a/chrome/browser/chromeos/web_applications/camera_app_integration_browsertest.cc
+++ b/chrome/browser/chromeos/web_applications/camera_app_integration_browsertest.cc
@@ -7,16 +7,17 @@
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_finder.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
-#include "chrome/browser/web_applications/components/external_app_install_features.h"
 #include "chrome/browser/web_applications/system_web_app_manager_browsertest.h"
 #include "chrome/test/base/ui_test_utils.h"
+#include "chromeos/constants/chromeos_features.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/test_navigation_observer.h"
 
 class CameraAppIntegrationTest : public SystemWebAppIntegrationTest {
  public:
   CameraAppIntegrationTest() {
-    scoped_feature_list_.InitAndEnableFeature(web_app::kCameraSystemWebApp);
+    scoped_feature_list_.InitWithFeatures(
+        {chromeos::features::kCameraSystemWebApp}, {});
   }
 
  private:
diff --git a/chrome/browser/extensions/component_loader.cc b/chrome/browser/extensions/component_loader.cc
index cf8c45c1..38bfa86 100644
--- a/chrome/browser/extensions/component_loader.cc
+++ b/chrome/browser/extensions/component_loader.cc
@@ -395,8 +395,10 @@
 }
 
 void ComponentLoader::AddChromeCameraApp() {
-  // TODO(crbug.com/1135280): Remove all the logic here once CCA is fully
-  // migrated to SWA.
+  if (base::FeatureList::IsEnabled(chromeos::features::kCameraSystemWebApp)) {
+    return;
+  }
+
   base::FilePath resources_path;
   if (base::PathService::Get(chrome::DIR_RESOURCES, &resources_path)) {
     AddComponentFromDir(resources_path.Append(extension_misc::kCameraAppPath),
diff --git a/chrome/browser/media/audio_service_util.cc b/chrome/browser/media/audio_service_util.cc
index 693197d..7da7a06 100644
--- a/chrome/browser/media/audio_service_util.cc
+++ b/chrome/browser/media/audio_service_util.cc
@@ -4,22 +4,38 @@
 
 #include "chrome/browser/media/audio_service_util.h"
 
+#include <string>
+
 #include "base/feature_list.h"
+#include "base/optional.h"
+#include "base/values.h"
+#include "build/build_config.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/policy/chrome_browser_policy_connector.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/core/common/policy_namespace.h"
+#include "components/policy/core/common/policy_service.h"
+#include "components/policy/policy_constants.h"
 #include "content/public/common/content_features.h"
 
-static base::Optional<bool> g_force_audio_service_sandbox;
-
 bool IsAudioServiceSandboxEnabled() {
-  return g_force_audio_service_sandbox.value_or(
+  base::Optional<bool> force_enable_audio_sandbox;
+#if defined(OS_WIN) || defined(OS_MAC) || \
+    (defined(OS_LINUX) && !defined(OS_CHROMEOS))
+  const policy::PolicyMap& policies =
+      g_browser_process->browser_policy_connector()
+          ->GetPolicyService()
+          ->GetPolicies(policy::PolicyNamespace(policy::POLICY_DOMAIN_CHROME,
+                                                std::string()));
+  const base::Value* audio_sandbox_enabled_policy_value =
+      policies.GetValue(policy::key::kAudioSandboxEnabled);
+  if (audio_sandbox_enabled_policy_value) {
+    force_enable_audio_sandbox.emplace();
+    audio_sandbox_enabled_policy_value->GetAsBoolean(
+        &force_enable_audio_sandbox.value());
+  }
+#endif
+  return force_enable_audio_sandbox.value_or(
       base::FeatureList::IsEnabled(features::kAudioServiceSandbox));
 }
 
-// If |force_audio_service_sandbox| is:
-// * null: use the default audio-service sandbox configuration (varies per
-//         platform)
-// * true: enable the audio-service sandbox, regardless of default settings.
-// * false: disable the audio-service sandbox, regardless of default settings.
-void SetForceAudioServiceSandboxed(
-    const base::Optional<bool>& force_audio_service_sandbox) {
-  g_force_audio_service_sandbox = force_audio_service_sandbox;
-}
diff --git a/chrome/browser/media/audio_service_util.h b/chrome/browser/media/audio_service_util.h
index 4d2706c5..721d386 100644
--- a/chrome/browser/media/audio_service_util.h
+++ b/chrome/browser/media/audio_service_util.h
@@ -5,16 +5,6 @@
 #ifndef CHROME_BROWSER_MEDIA_AUDIO_SERVICE_UTIL_H_
 #define CHROME_BROWSER_MEDIA_AUDIO_SERVICE_UTIL_H_
 
-#include "base/optional.h"
-
 bool IsAudioServiceSandboxEnabled();
 
-// If |force_audio_service_sandbox| is:
-// * null: use the default audio-service sandbox configuration (varies per
-//         platform)
-// * true: enable the audio-service sandbox, regardless of default settings.
-// * false: disable the audio-service sandbox, regardless of default settings.
-void SetForceAudioServiceSandboxed(
-    const base::Optional<bool>& force_audio_service_sandbox);
-
 #endif  // CHROME_BROWSER_MEDIA_AUDIO_SERVICE_UTIL_H_
diff --git a/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java b/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java
index 4cc976e..27dc28d 100644
--- a/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java
+++ b/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java
@@ -725,6 +725,7 @@
 
     public static final String VERIFIED_DIGITAL_ASSET_LINKS = "verified_digital_asset_links";
 
+    public static final String VIDEO_TUTORIALS_SHARE_URL_SET = "Chrome.VideoTutorials.ShareUrls";
     public static final String VR_EXIT_TO_2D_COUNT = "VR_EXIT_TO_2D_COUNT";
     public static final String VR_FEEDBACK_OPT_OUT = "VR_FEEDBACK_OPT_OUT";
 
@@ -835,7 +836,8 @@
                 SETTINGS_SAFETY_CHECK_LAST_RUN_TIMESTAMP,
                 SETTINGS_SAFETY_CHECK_RUN_COUNTER,
                 SIGNIN_PROMO_IMPRESSIONS_COUNT_NTP,
-                TWA_DISCLOSURE_SEEN_PACKAGES
+                TWA_DISCLOSURE_SEEN_PACKAGES,
+                VIDEO_TUTORIALS_SHARE_URL_SET
         );
         // clang-format on
     }
diff --git a/chrome/browser/prefetch/no_state_prefetch/prerender_unittest.cc b/chrome/browser/prefetch/no_state_prefetch/prerender_unittest.cc
index 96535a6..bdc1ec8 100644
--- a/chrome/browser/prefetch/no_state_prefetch/prerender_unittest.cc
+++ b/chrome/browser/prefetch/no_state_prefetch/prerender_unittest.cc
@@ -1503,31 +1503,6 @@
   EXPECT_FALSE(prerender_manager()->FindEntry(url));
 }
 
-TEST_F(PrerenderTest, LinkManagerRendererDisconnect) {
-  prerender_manager()->SetTickClockForTesting(tick_clock());
-  EXPECT_TRUE(IsEmptyPrerenderLinkManager());
-  GURL url("http://www.myexample.com");
-  DummyPrerenderContents* prerender_contents =
-      prerender_manager()->CreateNextPrerenderContents(url,
-                                                       FINAL_STATUS_TIMED_OUT);
-
-  EXPECT_TRUE(AddSimplePrerender(url));
-  EXPECT_TRUE(prerender_contents->prerendering_has_started());
-  EXPECT_FALSE(prerender_contents->prerendering_has_been_cancelled());
-  ASSERT_EQ(prerender_contents, prerender_manager()->FindEntry(url));
-
-  // Disconnect all clients. Spin the run loop to give the link manager
-  // opportunity to detect disconnection.
-  DisconnectAllPrerenderProcessorClients();
-  base::RunLoop().RunUntilIdle();
-
-  tick_clock()->Advance(prerender_manager()->config().abandon_time_to_live +
-                        TimeDelta::FromSeconds(1));
-
-  EXPECT_FALSE(prerender_manager()->FindEntry(url));
-  EXPECT_TRUE(IsEmptyPrerenderLinkManager());
-}
-
 // Creates two prerenders, one of which should be blocked by the
 // max_link_concurrency; abandons both of them and waits to make sure both
 // are cleared from the PrerenderLinkManager.
diff --git a/chrome/browser/printing/print_backend_browsertest.cc b/chrome/browser/printing/print_backend_browsertest.cc
index c8a95046..7ec3df9 100644
--- a/chrome/browser/printing/print_backend_browsertest.cc
+++ b/chrome/browser/printing/print_backend_browsertest.cc
@@ -131,7 +131,10 @@
   EXPECT_FALSE(default_printer_name.has_value());
 }
 
-IN_PROC_BROWSER_TEST_F(PrintBackendBrowserTest, GetDefaultPrinterName) {
+// TODO(crbug.com/809738):  Re-enable after the updates for setting up the
+// printer test environment are made to print_backend_service.mojom.
+IN_PROC_BROWSER_TEST_F(PrintBackendBrowserTest,
+                       DISABLED_GetDefaultPrinterName) {
   base::Optional<std::string> default_printer_name;
 
   DoInitAndSetupTestData();
diff --git a/chrome/browser/resources/nearby_share/nearby_discovery_page.html b/chrome/browser/resources/nearby_share/nearby_discovery_page.html
index 96e0fbf..4e26b1ed 100644
--- a/chrome/browser/resources/nearby_share/nearby_discovery_page.html
+++ b/chrome/browser/resources/nearby_share/nearby_discovery_page.html
@@ -18,6 +18,7 @@
 
   #deviceList {
     display: block;
+    margin-top: 12px;
     overflow: auto;
     padding-inline-end:  var(--nearby-page-space-large-inline);
     padding-inline-start:  var(--nearby-page-space-large-inline);
@@ -58,10 +59,10 @@
   }
 
   #process-row {
-    align-items: center;
+    align-items: flex-start;
     display: flex;
-    flex-grow: 1;
     justify-content: space-between;
+    margin: auto 0;
     margin-block-start: 48px;
     overflow: hidden;
   }
@@ -73,6 +74,7 @@
   }
 
   #placeholder {
+    align-self: center;
     color: rgb(95, 99, 104);
     font-size: 13px;
     line-height: 20px;
@@ -103,11 +105,9 @@
             aria-label="$i18n{nearbyShareDiscoveryPageSubtitle}">
           <template>
             <nearby-device tabindex$="[[tabIndex]]" share-target="[[item]]"
-                is-selected="[[isShareTargetSelected_(
-                                 item, selectedShareTarget)]]"
+                is-selected="[[selected]]"
                 role="radio"
-                aria-checked$="[[isShareTargetSelectedStr_(
-                                 item, selectedShareTarget)]]">
+                aria-checked$="[[boolToString_(selected)]]">
             </nearby-device>
           </template>
         </iron-list>
diff --git a/chrome/browser/resources/nearby_share/nearby_discovery_page.js b/chrome/browser/resources/nearby_share/nearby_discovery_page.js
index 80984a1f9..937215f 100644
--- a/chrome/browser/resources/nearby_share/nearby_discovery_page.js
+++ b/chrome/browser/resources/nearby_share/nearby_discovery_page.js
@@ -271,35 +271,35 @@
 
   /** @private */
   onSelectedShareTargetChanged_() {
-    // The device list gets removed from the DOM if there are no share targets.
-    if (!this.$.deviceList) {
+    const deviceList = this.$$('#deviceList');
+    if (!deviceList) {
+      // deviceList is in dom-if and may not be found
       return;
     }
 
-    // <iron-list> causes |this.$.deviceList.selectedItem| to be null if tapped
-    // a second time. Manually reselect the last item to preserve selection.
-    if (!this.$.deviceList.selectedItem && this.lastSelectedShareTarget_) {
-      this.$.deviceList.selectItem(this.lastSelectedShareTarget_);
+    // <iron-list> causes |selectedItem| to be null if tapped a second time.
+    // Manually reselect the last item to preserve selection.
+    if (!deviceList.selectedItem && this.lastSelectedShareTarget_) {
+      // Use async to make sure this happens after |selectedItem| gets its
+      // final value.
+      this.async(() => {
+        const deviceList = this.$$('#deviceList');
+        if (!deviceList.selectedItem) {
+          deviceList.selectItem(this.lastSelectedShareTarget_);
+        }
+      });
+    } else {
+      this.lastSelectedShareTarget_ = deviceList.selectedItem;
     }
-    this.lastSelectedShareTarget_ = this.$.deviceList.selectedItem;
   },
 
   /**
-   * @param {!nearbyShare.mojom.ShareTarget} shareTarget
-   * @return {boolean}
-   * @private
-   */
-  isShareTargetSelected_(shareTarget) {
-    return this.selectedShareTarget === shareTarget;
-  },
-
-  /**
-   * @param {!nearbyShare.mojom.ShareTarget} shareTarget
+   * @param {boolean} bool
    * @return {string}
    * @private
    */
-  isShareTargetSelectedStr_(shareTarget) {
-    return this.isShareTargetSelected_(shareTarget).toString();
+  boolToString_(bool) {
+    return bool.toString();
   },
 
   /**
diff --git a/chrome/browser/resources/tab_search/tab_search_search_field.html b/chrome/browser/resources/tab_search/tab_search_search_field.html
index 7039004..9db6523 100644
--- a/chrome/browser/resources/tab_search/tab_search_search_field.html
+++ b/chrome/browser/resources/tab_search/tab_search_search_field.html
@@ -51,6 +51,10 @@
     text-overflow: ellipsis;
   }
 
+  #searchInput::-webkit-search-cancel-button {
+    display: none;
+  }
+
   @media (prefers-color-scheme: dark) {
     #searchIcon {
       color: var(--google-blue-refresh-300);
diff --git a/chrome/browser/site_isolation/site_per_process_interactive_browsertest.cc b/chrome/browser/site_isolation/site_per_process_interactive_browsertest.cc
index 4d51765..5b51984 100644
--- a/chrome/browser/site_isolation/site_per_process_interactive_browsertest.cc
+++ b/chrome/browser/site_isolation/site_per_process_interactive_browsertest.cc
@@ -48,7 +48,6 @@
 #include "net/dns/mock_host_resolver.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
 #include "ui/base/test/ui_controls.h"
-#include "ui/base/ui_base_features.h"
 #include "ui/display/display.h"
 #include "ui/display/screen.h"
 #include "ui/gfx/geometry/point.h"
@@ -426,11 +425,6 @@
 // Verifies against regression of https://crbug.com/702330
 IN_PROC_BROWSER_TEST_F(SitePerProcessInteractiveBrowserTest,
                        TabAndMouseFocusNavigation) {
-#if defined(USE_OZONE)
-  // TODO(https://crbug.com/1109696): enable for ozone.
-  if (features::IsUsingOzonePlatform())
-    return;
-#endif
   GURL main_url(embedded_test_server()->GetURL(
       "a.com", "/cross_site_iframe_factory.html?a(b,c)"));
   ui_test_utils::NavigateToURL(browser(), main_url);
diff --git a/chrome/browser/sync/test/integration/two_client_web_apps_bmo_sync_test.cc b/chrome/browser/sync/test/integration/two_client_web_apps_bmo_sync_test.cc
index c2e2550..d90a52d 100644
--- a/chrome/browser/sync/test/integration/two_client_web_apps_bmo_sync_test.cc
+++ b/chrome/browser/sync/test/integration/two_client_web_apps_bmo_sync_test.cc
@@ -625,6 +625,7 @@
   expected_os_hook_requests[OsHookType::kShortcuts] = true;
   expected_os_hook_requests[OsHookType::kRunOnOsLogin] = false;
   expected_os_hook_requests[OsHookType::kShortcutsMenu] = true;
+  expected_os_hook_requests[OsHookType::kUninstallationViaOsSettings] = true;
   expected_os_hook_requests[OsHookType::kFileHandlers] = true;
   EXPECT_EQ(expected_os_hook_requests, last_options->os_hooks);
   EXPECT_TRUE(last_options->add_to_desktop);
diff --git a/chrome/browser/ui/android/appmenu/internal/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenuTest.java b/chrome/browser/ui/android/appmenu/internal/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenuTest.java
index 0a3db69..9b65114 100644
--- a/chrome/browser/ui/android/appmenu/internal/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenuTest.java
+++ b/chrome/browser/ui/android/appmenu/internal/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenuTest.java
@@ -27,6 +27,7 @@
 import org.junit.runner.RunWith;
 import org.mockito.Mockito;
 
+import org.chromium.base.test.util.Batch;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.CriteriaHelper;
@@ -53,6 +54,7 @@
  */
 @RunWith(ChromeJUnit4ClassRunner.class)
 @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
+@Batch(Batch.PER_CLASS)
 public class AppMenuTest extends DummyUiActivityTestCase {
     private AppMenuCoordinatorImpl mAppMenuCoordinator;
     private AppMenuHandlerImpl mAppMenuHandler;
@@ -146,6 +148,8 @@
         Assert.assertEquals("Popup should be aligned with right of anchor. Anchor rect: " + viewRect
                         + ", popup rect: " + popupRect,
                 viewRect.right, popupRect.right);
+
+        AppMenuCoordinatorImpl.setHasPermanentMenuKeyForTesting(null);
     }
 
     @Test
@@ -162,6 +166,7 @@
         Assert.assertNotEquals("Popup should be offset from right of anchor."
                         + "Anchor rect: " + viewRect + ", popup rect: " + popupRect,
                 viewRect.right, popupRect.right);
+        AppMenuCoordinatorImpl.setHasPermanentMenuKeyForTesting(null);
     }
 
     @Test
diff --git a/chrome/browser/ui/startup/startup_browser_creator.cc b/chrome/browser/ui/startup/startup_browser_creator.cc
index d9c4992..27f6fb8 100644
--- a/chrome/browser/ui/startup/startup_browser_creator.cc
+++ b/chrome/browser/ui/startup/startup_browser_creator.cc
@@ -58,6 +58,7 @@
 #include "chrome/browser/ui/startup/launch_mode_recorder.h"
 #include "chrome/browser/ui/startup/startup_browser_creator_impl.h"
 #include "chrome/browser/ui/ui_features.h"
+#include "chrome/browser/ui/web_applications/web_app_ui_manager_impl.h"
 #include "chrome/common/buildflags.h"
 #include "chrome/common/chrome_constants.h"
 #include "chrome/common/chrome_features.h"
@@ -837,6 +838,23 @@
     return true;
   }
 
+#if defined(OS_WIN)
+  // If --uninstall-app-id is specified, remove the target web app.
+  if (command_line.HasSwitch(switches::kUninstallAppId)) {
+    std::string app_id =
+        command_line.GetSwitchValueASCII(switches::kUninstallAppId);
+
+    web_app::WebAppUiManagerImpl::Get(last_used_profile)
+        ->UninstallWebAppFromStartupSwitch(app_id);
+
+    // Return true to allow startup to continue and for the main event loop to
+    // run. The process will shut down if no browser windows are open when the
+    // uninstall completes thanks to UninstallWebAppFromStartupSwitch's
+    // ScopedKeepAlive.
+    return true;
+  }
+#endif  // defined(OS_WIN)
+
   if (command_line.HasSwitch(extensions::switches::kLoadApps) &&
       can_use_last_profile) {
     if (!ProcessLoadApps(command_line, cur_dir, last_used_profile))
diff --git a/chrome/browser/ui/views/web_apps/web_app_uninstall_dialog_view.cc b/chrome/browser/ui/views/web_apps/web_app_uninstall_dialog_view.cc
index 63403d3..619feae 100644
--- a/chrome/browser/ui/views/web_apps/web_app_uninstall_dialog_view.cc
+++ b/chrome/browser/ui/views/web_apps/web_app_uninstall_dialog_view.cc
@@ -132,11 +132,9 @@
           : HistogramCloseAction::kUninstall;
   UMA_HISTOGRAM_ENUMERATION("Webapp.UninstallDialogAction", action);
 
-  bool uninstalled = Uninstall();
+  Uninstall();
   if (checkbox_->GetChecked())
     ClearWebAppSiteData();
-
-  std::exchange(dialog_, nullptr)->CallCallback(uninstalled);
 }
 
 void WebAppUninstallDialogDelegateView::OnDialogCanceled() {
@@ -151,18 +149,28 @@
   return image_;
 }
 
-bool WebAppUninstallDialogDelegateView::Uninstall() {
+void WebAppUninstallDialogDelegateView::Uninstall() {
   auto* provider = web_app::WebAppProvider::Get(profile_);
   DCHECK(provider);
 
-  if (!provider->install_finalizer().CanUserUninstallExternalApp(app_id_))
-    return false;
+  if (!provider->install_finalizer().CanUserUninstallExternalApp(app_id_)) {
+    std::exchange(dialog_, nullptr)->CallCallback(/*uninstalled=*/false);
+    return;
+  }
 
   dialog_->UninstallStarted();
 
-  provider->install_finalizer().UninstallExternalAppByUser(app_id_,
-                                                           base::DoNothing());
-  return true;
+  // Callback to the WebAppUninstallDialogViews because
+  // WebAppUninstallDialogDelegateView lifetime is controlled by Widget and it
+  // is terminiated as soon as dialog is closed regardless of web app
+  // uninstallation.
+  provider->install_finalizer().UninstallExternalAppByUser(
+      app_id_, base::BindOnce(&WebAppUninstallDialogViews::CallCallback,
+                              dialog_->GetWeakPtr()));
+  // We successfully call Web App Uninstall routine, then
+  // WebAppUninstallDialogDelegateView can be terminated, but can't call the
+  // callback of the dialog caller.
+  dialog_ = nullptr;
 }
 
 void WebAppUninstallDialogDelegateView::ClearWebAppSiteData() {
@@ -281,6 +289,11 @@
   std::move(closed_callback_).Run(uninstalled);
 }
 
+base::WeakPtr<WebAppUninstallDialogViews>
+WebAppUninstallDialogViews::GetWeakPtr() {
+  return weak_ptr_factory_.GetWeakPtr();
+}
+
 // static
 std::unique_ptr<web_app::WebAppUninstallDialog>
 web_app::WebAppUninstallDialog::Create(Profile* profile,
diff --git a/chrome/browser/ui/views/web_apps/web_app_uninstall_dialog_view.h b/chrome/browser/ui/views/web_apps/web_app_uninstall_dialog_view.h
index 84c438c..a9b537b 100644
--- a/chrome/browser/ui/views/web_apps/web_app_uninstall_dialog_view.h
+++ b/chrome/browser/ui/views/web_apps/web_app_uninstall_dialog_view.h
@@ -53,8 +53,8 @@
   // views::DialogDelegateView:
   gfx::ImageSkia GetWindowIcon() override;
 
-  // Uninstalls the web app. Returns true on success.
-  bool Uninstall();
+  // Uninstalls the web app.
+  void Uninstall();
   void ClearWebAppSiteData();
 
   void OnDialogAccepted();
@@ -94,6 +94,7 @@
 
   void UninstallStarted();
   void CallCallback(bool uninstalled);
+  base::WeakPtr<WebAppUninstallDialogViews> GetWeakPtr();
 
  private:
   // web_app::AppRegistrarObserver:
diff --git a/chrome/browser/ui/web_applications/web_app_dialog_manager.cc b/chrome/browser/ui/web_applications/web_app_dialog_manager.cc
index 920ddd09..77a48b58 100644
--- a/chrome/browser/ui/web_applications/web_app_dialog_manager.cc
+++ b/chrome/browser/ui/web_applications/web_app_dialog_manager.cc
@@ -73,6 +73,10 @@
       base::UmaHistogramBoolean(
           "WebApp.UninstallDialog.AppsPageUninstallSuccess", uninstalled);
       break;
+    case UninstallSource::kOsSettings:
+      base::UmaHistogramBoolean(
+          "WebApp.UninstallDialog.OsSettingsUninstallSuccess", uninstalled);
+      break;
   }
 
   std::move(callback).Run(/*success=*/uninstalled);
diff --git a/chrome/browser/ui/web_applications/web_app_dialog_manager.h b/chrome/browser/ui/web_applications/web_app_dialog_manager.h
index 21f566b..16484af0 100644
--- a/chrome/browser/ui/web_applications/web_app_dialog_manager.h
+++ b/chrome/browser/ui/web_applications/web_app_dialog_manager.h
@@ -30,6 +30,7 @@
   enum class UninstallSource {
     kAppMenu,
     kAppsPage,
+    kOsSettings,
   };
 
   using Callback = base::OnceCallback<void(bool success)>;
diff --git a/chrome/browser/ui/web_applications/web_app_ui_manager_impl.cc b/chrome/browser/ui/web_applications/web_app_ui_manager_impl.cc
index c849cc0..af0d2c3 100644
--- a/chrome/browser/ui/web_applications/web_app_ui_manager_impl.cc
+++ b/chrome/browser/ui/web_applications/web_app_ui_manager_impl.cc
@@ -35,6 +35,12 @@
 #include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
 #endif
 
+#if defined(OS_WIN)
+#include "components/keep_alive_registry/keep_alive_types.h"
+#include "components/keep_alive_registry/scoped_keep_alive.h"
+#include "ui/gfx/native_widget_types.h"
+#endif  // defined(OS_WIN)
+
 namespace web_app {
 
 namespace {
@@ -49,6 +55,34 @@
   return installed;
 }
 
+#if defined(OS_WIN)
+
+// UninstallWebAppWithDialog handles WebApp uninstallation from the
+// Windows Settings.
+void UninstallWebAppWithDialog(
+    const AppId& app_id,
+    Profile* profile,
+    std::unique_ptr<ScopedKeepAlive> keep_browser_alive) {
+  auto* provider = WebAppProvider::Get(profile);
+  if (!provider->registrar().IsLocallyInstalled(app_id)) {
+    // App does not exist and controller is destroyed.
+    return;
+  }
+
+  WebAppUiManagerImpl::Get(profile)->dialog_manager().UninstallWebApp(
+      app_id, WebAppDialogManager::UninstallSource::kOsSettings,
+      gfx::kNullNativeWindow,
+      base::BindOnce(
+          [](std::unique_ptr<ScopedKeepAlive> keep_browser_alive,
+             bool /*uninstalled*/) {
+            // This callback exists to own |keep_browser_alive|,
+            // until after the uninstallation completes.
+          },
+          std::move(keep_browser_alive)));
+}
+
+#endif  // defined(OS_WIN)
+
 }  // namespace
 
 // static
@@ -289,6 +323,18 @@
   windows_closed_requests_map_.erase(app_id);
 }
 
+#if defined(OS_WIN)
+void WebAppUiManagerImpl::UninstallWebAppFromStartupSwitch(
+    const AppId& app_id) {
+  auto keep_browser_alive = std::make_unique<ScopedKeepAlive>(
+      KeepAliveOrigin::APP_UNINSTALLATION_FROM_OS_SETTINGS,
+      KeepAliveRestartOption::DISABLED);
+  WebAppProvider::Get(profile_)->on_registry_ready().Post(
+      FROM_HERE, base::BindOnce(&UninstallWebAppWithDialog, app_id, profile_,
+                                std::move(keep_browser_alive)));
+}
+#endif  //  defined(OS_WIN)
+
 bool WebAppUiManagerImpl::IsBrowserForInstalledApp(Browser* browser) {
   if (browser->profile() != profile_)
     return false;
diff --git a/chrome/browser/ui/web_applications/web_app_ui_manager_impl.h b/chrome/browser/ui/web_applications/web_app_ui_manager_impl.h
index 6dc83b9..03be401 100644
--- a/chrome/browser/ui/web_applications/web_app_ui_manager_impl.h
+++ b/chrome/browser/ui/web_applications/web_app_ui_manager_impl.h
@@ -12,6 +12,7 @@
 #include "base/callback_forward.h"
 #include "base/memory/weak_ptr.h"
 #include "base/optional.h"
+#include "build/build_config.h"
 #include "chrome/browser/ui/browser_list_observer.h"
 #include "chrome/browser/web_applications/components/web_app_ui_manager.h"
 
@@ -65,6 +66,12 @@
   void OnBrowserAdded(Browser* browser) override;
   void OnBrowserRemoved(Browser* browser) override;
 
+#if defined(OS_WIN)
+  // Attempts to uninstall the given web app id. Meant to be used with OS-level
+  // uninstallation support/hooks.
+  void UninstallWebAppFromStartupSwitch(const AppId& app_id);
+#endif
+
  private:
   // Returns true if Browser is for an installed App.
   bool IsBrowserForInstalledApp(Browser* browser);
diff --git a/chrome/browser/ui/webui/ntp/app_launcher_handler.cc b/chrome/browser/ui/webui/ntp/app_launcher_handler.cc
index 763b8fd1..db7bef1 100644
--- a/chrome/browser/ui/webui/ntp/app_launcher_handler.cc
+++ b/chrome/browser/ui/webui/ntp/app_launcher_handler.cc
@@ -1359,6 +1359,7 @@
   options.os_hooks[web_app::OsHookType::kShortcutsMenu] = true;
   options.os_hooks[web_app::OsHookType::kFileHandlers] = true;
   options.os_hooks[web_app::OsHookType::kRunOnOsLogin] = false;
+  options.os_hooks[web_app::OsHookType::kUninstallationViaOsSettings] = true;
 
   web_app_provider_->os_integration_manager().InstallOsHooks(
       app_id,
diff --git a/chrome/browser/video_tutorials/BUILD.gn b/chrome/browser/video_tutorials/BUILD.gn
index 91f5faa..efd8d69 100644
--- a/chrome/browser/video_tutorials/BUILD.gn
+++ b/chrome/browser/video_tutorials/BUILD.gn
@@ -68,6 +68,7 @@
       "android/java/src/org/chromium/chrome/browser/video_tutorials/iph/VideoTutorialIPHUtils.java",
       "android/java/src/org/chromium/chrome/browser/video_tutorials/iph/VideoTutorialTryNowTracker.java",
       "android/java/src/org/chromium/chrome/browser/video_tutorials/list/TutorialListCoordinator.java",
+      "android/java/src/org/chromium/chrome/browser/video_tutorials/metrics/VideoTutorialMetrics.java",
       "android/java/src/org/chromium/chrome/browser/video_tutorials/player/VideoPlayerCoordinator.java",
     ]
 
diff --git a/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/metrics/VideoTutorialMetrics.java b/chrome/browser/video_tutorials/android/java/src/org/chromium/chrome/browser/video_tutorials/metrics/VideoTutorialMetrics.java
similarity index 93%
rename from chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/metrics/VideoTutorialMetrics.java
rename to chrome/browser/video_tutorials/android/java/src/org/chromium/chrome/browser/video_tutorials/metrics/VideoTutorialMetrics.java
index af1087e..d4420de 100644
--- a/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/metrics/VideoTutorialMetrics.java
+++ b/chrome/browser/video_tutorials/android/java/src/org/chromium/chrome/browser/video_tutorials/metrics/VideoTutorialMetrics.java
@@ -33,7 +33,8 @@
     // Please treat this list as append only and keep it in sync with
     // VideoTutorials.UserAction in enums.xml.
     @IntDef({UserAction.CHANGE_LANGUAGE, UserAction.WATCH_NEXT_VIDEO, UserAction.TRY_NOW,
-            UserAction.SHARE, UserAction.CLOSE, UserAction.BACK_PRESS_WHEN_SHOWING_VIDEO_PLAYER})
+            UserAction.SHARE, UserAction.CLOSE, UserAction.BACK_PRESS_WHEN_SHOWING_VIDEO_PLAYER,
+            UserAction.OPEN_SHARED_VIDEO, UserAction.INVALID_SHARE_URL})
     @Retention(RetentionPolicy.SOURCE)
     public @interface UserAction {
         int CHANGE_LANGUAGE = 0;
@@ -42,7 +43,9 @@
         int SHARE = 3;
         int CLOSE = 4;
         int BACK_PRESS_WHEN_SHOWING_VIDEO_PLAYER = 5;
-        int NUM_ENTRIES = 6;
+        int OPEN_SHARED_VIDEO = 6;
+        int INVALID_SHARE_URL = 7;
+        int NUM_ENTRIES = 8;
     }
 
     /** Called to record various user actions on the video player. */
diff --git a/chrome/browser/video_tutorials/internal/BUILD.gn b/chrome/browser/video_tutorials/internal/BUILD.gn
index 358c5c6..9db20bb9 100644
--- a/chrome/browser/video_tutorials/internal/BUILD.gn
+++ b/chrome/browser/video_tutorials/internal/BUILD.gn
@@ -76,7 +76,6 @@
       "android/java/src/org/chromium/chrome/browser/video_tutorials/list/TutorialCardViewBinder.java",
       "android/java/src/org/chromium/chrome/browser/video_tutorials/list/TutorialListCoordinatorImpl.java",
       "android/java/src/org/chromium/chrome/browser/video_tutorials/list/TutorialListMediator.java",
-      "android/java/src/org/chromium/chrome/browser/video_tutorials/metrics/VideoTutorialMetrics.java",
       "android/java/src/org/chromium/chrome/browser/video_tutorials/player/VideoPlayerCoordinatorImpl.java",
       "android/java/src/org/chromium/chrome/browser/video_tutorials/player/VideoPlayerMediator.java",
       "android/java/src/org/chromium/chrome/browser/video_tutorials/player/VideoPlayerProperties.java",
diff --git a/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/VideoTutorialUtils.java b/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/VideoTutorialUtils.java
index a5f4371db..ef347f7 100644
--- a/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/VideoTutorialUtils.java
+++ b/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/VideoTutorialUtils.java
@@ -4,7 +4,12 @@
 
 package org.chromium.chrome.browser.video_tutorials;
 
+import android.content.ActivityNotFoundException;
+import android.content.Context;
+import android.content.Intent;
+
 import org.chromium.base.Callback;
+import org.chromium.base.Log;
 
 import java.util.List;
 import java.util.Locale;
@@ -13,6 +18,31 @@
  * Handles various feature utility functions associated with video tutorials UI.
  */
 public class VideoTutorialUtils {
+    private static final String TAG = "VideoTutorialShare";
+
+    /**
+     * Creates and launches an Intent that allows sharing a video tutorial.
+     */
+    public static void launchShareIntent(Context context, Tutorial tutorial) {
+        Intent intent = new Intent();
+        intent.setType("video/*");
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.setAction(Intent.ACTION_SEND);
+        intent.putExtra(Intent.EXTRA_TEXT, tutorial.shareUrl);
+        startShareIntent(context, intent);
+    }
+
+    private static void startShareIntent(Context context, Intent intent) {
+        try {
+            context.startActivity(Intent.createChooser(
+                    intent, context.getString(R.string.share_link_chooser_title)));
+        } catch (ActivityNotFoundException e) {
+            Log.e(TAG, "Cannot find activity for sharing");
+        } catch (Exception e) {
+            Log.e(TAG, "Cannot start activity for sharing, exception: " + e);
+        }
+    }
+
     /**
      * Converts a duration string in ms to a human-readable form.
      * @param videoLengthSeconds The video length in seconds.
diff --git a/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/player/VideoPlayerMediator.java b/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/player/VideoPlayerMediator.java
index 4cd3970..d33a6cc 100644
--- a/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/player/VideoPlayerMediator.java
+++ b/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/player/VideoPlayerMediator.java
@@ -177,6 +177,7 @@
 
     private void share() {
         VideoTutorialMetrics.recordUserAction(mTutorial.featureType, UserAction.SHARE);
+        VideoTutorialUtils.launchShareIntent(mContext, mTutorial);
     }
 
     private void close() {
diff --git a/chrome/browser/web_applications/components/BUILD.gn b/chrome/browser/web_applications/components/BUILD.gn
index 44480a9c..d739a25 100644
--- a/chrome/browser/web_applications/components/BUILD.gn
+++ b/chrome/browser/web_applications/components/BUILD.gn
@@ -67,6 +67,8 @@
     "web_app_tab_helper_base.cc",
     "web_app_tab_helper_base.h",
     "web_app_ui_manager.h",
+    "web_app_uninstallation_via_os_settings_registration.cc",
+    "web_app_uninstallation_via_os_settings_registration.h",
     "web_app_url_loader.cc",
     "web_app_url_loader.h",
     "web_app_utils.cc",
@@ -119,6 +121,7 @@
       "web_app_shortcut_win.h",
       "web_app_shortcuts_menu_win.cc",
       "web_app_shortcuts_menu_win.h",
+      "web_app_uninstallation_via_os_settings_registration_win.cc",
     ]
   }
 
diff --git a/chrome/browser/web_applications/components/external_app_install_features.cc b/chrome/browser/web_applications/components/external_app_install_features.cc
index 63dd9c87..7592179 100644
--- a/chrome/browser/web_applications/components/external_app_install_features.cc
+++ b/chrome/browser/web_applications/components/external_app_install_features.cc
@@ -16,9 +16,6 @@
 constexpr const base::Feature* kExternalAppInstallFeatures[] = {
     &kMigrateDefaultChromeAppToWebAppsGSuite,
     &kMigrateDefaultChromeAppToWebAppsNonGSuite,
-#if defined(OS_CHROMEOS)
-    &kCameraSystemWebApp
-#endif  // OS_CHROMEOS
 };
 
 bool g_always_enabled_for_testing = false;
@@ -37,12 +34,6 @@
     "MigrateDefaultChromeAppToWebAppsNonGSuite",
     base::FEATURE_DISABLED_BY_DEFAULT};
 
-#if defined(OS_CHROMEOS)
-// Enable or disables running the Camera App as a System Web App.
-const base::Feature kCameraSystemWebApp{"CameraSystemWebApp",
-                                        base::FEATURE_DISABLED_BY_DEFAULT};
-#endif  // OS_CHROMEOS
-
 bool IsExternalAppInstallFeatureEnabled(base::StringPiece feature_name) {
   if (g_always_enabled_for_testing)
     return true;
diff --git a/chrome/browser/web_applications/components/external_app_install_features.h b/chrome/browser/web_applications/components/external_app_install_features.h
index e8cbb52e..32eed21ca 100644
--- a/chrome/browser/web_applications/components/external_app_install_features.h
+++ b/chrome/browser/web_applications/components/external_app_install_features.h
@@ -15,10 +15,6 @@
 
 extern const base::Feature kMigrateDefaultChromeAppToWebAppsNonGSuite;
 
-#if defined(OS_CHROMEOS)
-extern const base::Feature kCameraSystemWebApp;
-#endif  // OS_CHROMEOS
-
 // Returns the base::Feature in |kExternalAppInstallFeatures| that corresponds
 // to |feature_name|. Used by external app install configs to gate installation
 // on features listed in |kExternalAppInstallFeatures|.
diff --git a/chrome/browser/web_applications/components/os_integration_manager.cc b/chrome/browser/web_applications/components/os_integration_manager.cc
index c2b80eff..7b99430 100644
--- a/chrome/browser/web_applications/components/os_integration_manager.cc
+++ b/chrome/browser/web_applications/components/os_integration_manager.cc
@@ -18,6 +18,7 @@
 #include "chrome/browser/web_applications/components/web_app_id.h"
 #include "chrome/browser/web_applications/components/web_app_shortcut.h"
 #include "chrome/browser/web_applications/components/web_app_ui_manager.h"
+#include "chrome/browser/web_applications/components/web_app_uninstallation_via_os_settings_registration.h"
 #include "chrome/common/chrome_features.h"
 #include "content/public/browser/browser_thread.h"
 
@@ -182,10 +183,9 @@
     return;
   }
 
-  base::RepeatingCallback<void(OsHookType::Type os_hook, bool completed)>
-      barrier = base::BindRepeating(
-          &OsHooksBarrierInfo::Run,
-          base::Owned(new OsHooksBarrierInfo(std::move(callback))));
+  BarrierCallback barrier = base::BindRepeating(
+      &OsHooksBarrierInfo::Run,
+      base::Owned(new OsHooksBarrierInfo(std::move(callback))));
   CallbackFactory callback_factory = CallbackFactory(barrier);
 
   if (os_hooks[OsHookType::kShortcutsMenu] &&
@@ -231,7 +231,6 @@
     callback_factory.CreateCallack(OsHookType::kRunOnOsLogin)
         .Run(/*completed=*/true);
   }
-
   // TODO(https://crbug.com/1108109) we should return the result of file handler
   // unregistration and record errors during unregistration.
   if (os_hooks[OsHookType::kFileHandlers])
@@ -239,6 +238,15 @@
 
   callback_factory.CreateCallack(OsHookType::kFileHandlers)
       .Run(/*completed=*/true);
+
+  // There is a chance uninstallation point was created with feature flag
+  // enabled so we need to clean it up regardless of feature flag state.
+  if (os_hooks[OsHookType::kUninstallationViaOsSettings] &&
+      ShouldRegisterUninstallationViaOsSettingsWithOs()) {
+    UnegisterUninstallationViaOsSettingsWithOs(app_id, profile_);
+  }
+  callback_factory.CreateCallack(OsHookType::kUninstallationViaOsSettings)
+      .Run(/*completed=*/true);
 }
 
 void OsIntegrationManager::UpdateOsHooks(
@@ -489,6 +497,19 @@
                                       OsHookType::kRunOnOsLogin),
                                   /*completed=*/false));
   }
+
+  if (options.os_hooks[OsHookType::kUninstallationViaOsSettings] &&
+      base::FeatureList::IsEnabled(
+          features::kEnableWebAppUninstallFromOsSettings) &&
+      ShouldRegisterUninstallationViaOsSettingsWithOs()) {
+    RegisterUninstallationViaOsSettingsWithOs(
+        app_id, registrar_->GetAppShortName(app_id), profile_);
+    callback_factory.CreateCallack(OsHookType::kUninstallationViaOsSettings)
+        .Run(/*completed=*/true);
+  } else {
+    callback_factory.CreateCallack(OsHookType::kUninstallationViaOsSettings)
+        .Run(/*completed=*/false);
+  }
 }
 
 void OsIntegrationManager::OnShortcutsDeleted(const AppId& app_id,
diff --git a/chrome/browser/web_applications/components/web_app_constants.h b/chrome/browser/web_applications/components/web_app_constants.h
index 26c7cbf0..f5f433bf 100644
--- a/chrome/browser/web_applications/components/web_app_constants.h
+++ b/chrome/browser/web_applications/components/web_app_constants.h
@@ -43,6 +43,7 @@
   kShortcuts = 0,
   kRunOnOsLogin,
   kShortcutsMenu,
+  kUninstallationViaOsSettings,
   kFileHandlers,
   kMaxValue = kFileHandlers,
 };
diff --git a/chrome/browser/web_applications/components/web_app_uninstallation_via_os_settings_registration.cc b/chrome/browser/web_applications/components/web_app_uninstallation_via_os_settings_registration.cc
new file mode 100644
index 0000000..92c3802
--- /dev/null
+++ b/chrome/browser/web_applications/components/web_app_uninstallation_via_os_settings_registration.cc
@@ -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.
+
+#include "chrome/browser/web_applications/components/web_app_uninstallation_via_os_settings_registration.h"
+
+#include "base/check.h"
+#include "build/build_config.h"
+
+namespace web_app {
+
+#if !defined(OS_WIN)
+
+// This block defines stub implementations of OS specific methods for
+// uninstallation command. Currently, only Windows has their own implementation.
+bool ShouldRegisterUninstallationViaOsSettingsWithOs() {
+  return false;
+}
+
+void RegisterUninstallationViaOsSettingsWithOs(const AppId& app_id,
+                                               const std::string& app_name,
+                                               Profile* profile) {
+  DCHECK(ShouldRegisterUninstallationViaOsSettingsWithOs());
+  // Stub function for OS's which don't register uninstallation command with the
+  // OS.
+}
+
+void UnegisterUninstallationViaOsSettingsWithOs(const AppId& app_id,
+                                                Profile* profile) {
+  DCHECK(ShouldRegisterUninstallationViaOsSettingsWithOs());
+  // Stub function for OS's which don't register uninstallation command with the
+  // OS.
+}
+
+#endif  //  !defined (OS_WIN)
+
+}  // namespace web_app
diff --git a/chrome/browser/web_applications/components/web_app_uninstallation_via_os_settings_registration.h b/chrome/browser/web_applications/components/web_app_uninstallation_via_os_settings_registration.h
new file mode 100644
index 0000000..ef9b184
--- /dev/null
+++ b/chrome/browser/web_applications/components/web_app_uninstallation_via_os_settings_registration.h
@@ -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.
+
+#ifndef CHROME_BROWSER_WEB_APPLICATIONS_COMPONENTS_WEB_APP_UNINSTALLATION_VIA_OS_SETTINGS_REGISTRATION_H_
+#define CHROME_BROWSER_WEB_APPLICATIONS_COMPONENTS_WEB_APP_UNINSTALLATION_VIA_OS_SETTINGS_REGISTRATION_H_
+
+#include <string>
+
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "build/build_config.h"
+#include "chrome/browser/web_applications/components/web_app_id.h"
+#include "components/services/app_service/public/cpp/file_handler.h"
+
+class Profile;
+
+namespace web_app {
+
+// True if uninstallation via os settings are managed externally by the
+// operating system. Windows is the only Os that support this feature now.
+bool ShouldRegisterUninstallationViaOsSettingsWithOs();
+
+// Do OS-specific registration for the web app. The registration writes
+// an entry to the global uninstall location in the Windows registry.
+// Once an entry exists in the given Windows registry, it will be
+// displayed in the Windows OS settings so that user can uninstall from
+// there like any other native apps.
+void RegisterUninstallationViaOsSettingsWithOs(const AppId& app_id,
+                                               const std::string& app_name,
+                                               Profile* profile);
+
+// Remove an entry from the Windows uninstall registry.
+void UnegisterUninstallationViaOsSettingsWithOs(const AppId& app_id,
+                                                Profile* profile);
+
+}  // namespace web_app
+
+#endif  // CHROME_BROWSER_WEB_APPLICATIONS_COMPONENTS_WEB_APP_UNINSTALLATION_VIA_OS_SETTINGS_REGISTRATION_H_
diff --git a/chrome/browser/web_applications/components/web_app_uninstallation_via_os_settings_registration_win.cc b/chrome/browser/web_applications/components/web_app_uninstallation_via_os_settings_registration_win.cc
new file mode 100644
index 0000000..9191e195
--- /dev/null
+++ b/chrome/browser/web_applications/components/web_app_uninstallation_via_os_settings_registration_win.cc
@@ -0,0 +1,147 @@
+// 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/web_applications/components/web_app_uninstallation_via_os_settings_registration.h"
+
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/hash/md5.h"
+#include "base/path_service.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "build/build_config.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/web_applications/components/web_app_helpers.h"
+#include "chrome/browser/web_applications/components/web_app_shortcut_win.h"
+#include "chrome/browser/win/uninstallation_via_os_settings.h"
+#include "chrome/common/chrome_constants.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/common/chrome_paths_internal.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/install_static/install_util.h"
+#include "content/public/browser/browser_thread.h"
+
+namespace web_app {
+
+namespace {
+
+// UninstallationViaOsSettingsHelper is a axilliary class for calculate the
+// uninstallation registry key by |profile_path| and |app_id|.
+class UninstallationViaOsSettingsHelper {
+ public:
+  UninstallationViaOsSettingsHelper(const base::FilePath& profile_path,
+                                    const web_app::AppId& app_id)
+      : profile_path_(profile_path), app_id_(app_id) {}
+
+  UninstallationViaOsSettingsHelper(
+      const UninstallationViaOsSettingsHelper& other) = delete;
+  UninstallationViaOsSettingsHelper& operator=(
+      const UninstallationViaOsSettingsHelper& other) = delete;
+
+  // Returns an identifier for the web app installed for the
+  // profile at |profile_path|. The identifier is guaranteed to be unique among
+  // all web apps installed in all profiles across all browser installations
+  // for the user.
+  std::wstring GetUninstallStringKey() const {
+    // We don't normalize (lower/upper) cases here mainly because people
+    // don't change shortcut file case. If anyone changes the file name
+    // or case, then it is the user's responsibility for cleanup the apps.
+    // (we assume he/she is a power user when could change
+    // the system created file.).
+    std::wstring key =
+        base::StringPrintf(L"%ls_%ls", profile_path_.value().c_str(),
+                           base::ASCIIToWide(app_id_).c_str());
+    base::MD5Digest digest;
+    base::MD5Sum(key.c_str(), key.size() * sizeof(wchar_t), &digest);
+    return base::ASCIIToUTF16(base::MD5DigestToBase16(digest));
+  }
+
+  base::CommandLine GetCommandLine() const {
+    base::FilePath full_exe_name;
+
+    base::PathService::Get(base::FILE_EXE, &full_exe_name);
+    base::CommandLine uninstall_commandline(full_exe_name);
+
+    // If the current user data directory isn't default, the uninstall
+    // string should have it.
+    base::FilePath default_user_data_dir;
+    base::FilePath user_data_dir;
+    if (chrome::GetDefaultUserDataDirectory(&default_user_data_dir) &&
+        base::PathService::Get(chrome::DIR_USER_DATA, &user_data_dir) &&
+        default_user_data_dir != user_data_dir) {
+      uninstall_commandline.AppendSwitchNative(switches::kUserDataDir,
+                                               user_data_dir.value());
+    }
+
+    base::FilePath profile_name = profile_path_.BaseName();
+    uninstall_commandline.AppendSwitchNative(switches::kProfileDirectory,
+                                             profile_name.value());
+
+    uninstall_commandline.AppendSwitchASCII(switches::kUninstallAppId, app_id_);
+
+    // e.g. uninstall_commandline
+    // "C:\Users\account\AppData\Local\Microsoft\Chromium\
+    //        Application\chrome.exe"
+    // --user-data-dir=c:\users\account\appdata\local\chromium\
+    //        CustomUserData (optional)
+    // --profile-directory=Default
+    // --uninstall-app-id=dadckofbdkccdemmkofcgkcbpjbnafgf
+    return uninstall_commandline;
+  }
+
+  base::FilePath GetWebAppIconPath(const std::string& app_name) {
+    base::FilePath web_app_icon_dir = GetOsIntegrationResourcesDirectoryForApp(
+        profile_path_, app_id_, GURL());
+
+    return internals::GetIconFilePath(web_app_icon_dir,
+                                      base::UTF8ToUTF16(app_name));
+  }
+
+ private:
+  const base::FilePath profile_path_;
+  const AppId app_id_;
+};
+
+}  // namespace
+
+bool ShouldRegisterUninstallationViaOsSettingsWithOs() {
+  return true;
+}
+
+void RegisterUninstallationViaOsSettingsWithOs(const AppId& app_id,
+                                               const std::string& app_name,
+                                               Profile* profile) {
+  DCHECK(ShouldRegisterUninstallationViaOsSettingsWithOs());
+
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+  UninstallationViaOsSettingsHelper uninstall_os_settings_helper(
+      profile->GetPath(), app_id);
+  base::string16 hash_key =
+      uninstall_os_settings_helper.GetUninstallStringKey();
+
+  auto uninstall_commandline = uninstall_os_settings_helper.GetCommandLine();
+  base::FilePath icon_path =
+      uninstall_os_settings_helper.GetWebAppIconPath(app_name);
+  base::string16 product_name = install_static::GetChromeInstallSubDirectory();
+
+  ::RegisterUninstallationViaOsSettings(hash_key, base::UTF8ToUTF16(app_name),
+                                        product_name, uninstall_commandline,
+                                        icon_path);
+}
+
+void UnegisterUninstallationViaOsSettingsWithOs(const AppId& app_id,
+                                                Profile* profile) {
+  DCHECK(ShouldRegisterUninstallationViaOsSettingsWithOs());
+
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+  UninstallationViaOsSettingsHelper uninstall_os_settings_helper(
+      profile->GetPath(), app_id);
+  base::string16 hash_key =
+      uninstall_os_settings_helper.GetUninstallStringKey();
+  ::UnregisterUninstallationViaOsSettings(hash_key);
+}
+
+}  // namespace web_app
diff --git a/chrome/browser/web_applications/pending_app_install_task.cc b/chrome/browser/web_applications/pending_app_install_task.cc
index 5c010fd..c879884 100644
--- a/chrome/browser/web_applications/pending_app_install_task.cc
+++ b/chrome/browser/web_applications/pending_app_install_task.cc
@@ -277,6 +277,7 @@
   // TODO(crbug.com/1087219): Determine if |register_file_handlers| should be
   // configured from somewhere else rather than always true.
   options.os_hooks[OsHookType::kFileHandlers] = true;
+  options.os_hooks[OsHookType::kUninstallationViaOsSettings] = true;
 
   os_integration_manager_->InstallOsHooks(
       app_id,
diff --git a/chrome/browser/web_applications/system_web_app_manager.cc b/chrome/browser/web_applications/system_web_app_manager.cc
index 56d0eab..2ee6b89 100644
--- a/chrome/browser/web_applications/system_web_app_manager.cc
+++ b/chrome/browser/web_applications/system_web_app_manager.cc
@@ -56,7 +56,6 @@
 #include "chrome/browser/chromeos/web_applications/scanning_system_web_app_info.h"
 #include "chrome/browser/chromeos/web_applications/terminal_source.h"
 #include "chrome/browser/chromeos/web_applications/terminal_system_web_app_info.h"
-#include "chrome/browser/web_applications/components/external_app_install_features.h"
 #include "chromeos/components/camera_app_ui/url_constants.h"
 #include "chromeos/components/connectivity_diagnostics/url_constants.h"
 #include "chromeos/components/help_app_ui/url_constants.h"
@@ -338,7 +337,8 @@
     case SystemAppType::SETTINGS:
       return true;
     case SystemAppType::CAMERA:
-      return base::FeatureList::IsEnabled(web_app::kCameraSystemWebApp);
+      return base::FeatureList::IsEnabled(
+          chromeos::features::kCameraSystemWebApp);
     case SystemAppType::TERMINAL:
       return true;
     case SystemAppType::MEDIA:
diff --git a/chrome/browser/web_applications/web_app_install_finalizer.cc b/chrome/browser/web_applications/web_app_install_finalizer.cc
index eb75e2a9..3e3126a1 100644
--- a/chrome/browser/web_applications/web_app_install_finalizer.cc
+++ b/chrome/browser/web_applications/web_app_install_finalizer.cc
@@ -329,8 +329,16 @@
 void WebAppInstallFinalizer::UninstallWebApp(const AppId& app_id,
                                              UninstallWebAppCallback callback) {
   registrar().NotifyWebAppUninstalled(app_id);
-  os_integration_manager().UninstallAllOsHooks(app_id, base::DoNothing());
+  os_integration_manager().UninstallAllOsHooks(
+      app_id, base::BindOnce(&WebAppInstallFinalizer::OnUninstallOsHooks,
+                             weak_ptr_factory_.GetWeakPtr(), app_id,
+                             std::move(callback)));
+}
 
+void WebAppInstallFinalizer::OnUninstallOsHooks(
+    const AppId& app_id,
+    UninstallWebAppCallback callback,
+    OsHooksResults os_hooks_info) {
   ScopedRegistryUpdate update(registry_controller().AsWebAppSyncBridge());
   update->DeleteApp(app_id);
 
diff --git a/chrome/browser/web_applications/web_app_install_finalizer.h b/chrome/browser/web_applications/web_app_install_finalizer.h
index 7064e15..88fd51b 100644
--- a/chrome/browser/web_applications/web_app_install_finalizer.h
+++ b/chrome/browser/web_applications/web_app_install_finalizer.h
@@ -11,6 +11,7 @@
 
 #include "base/memory/weak_ptr.h"
 #include "chrome/browser/web_applications/components/install_finalizer.h"
+#include "chrome/browser/web_applications/components/os_integration_manager.h"
 #include "chrome/browser/web_applications/components/web_app_constants.h"
 #include "chrome/browser/web_applications/components/web_application_info.h"
 #include "third_party/skia/include/core/SkBitmap.h"
@@ -91,6 +92,9 @@
       std::string old_name,
       const WebApplicationInfo& web_app_info,
       bool success);
+  void OnUninstallOsHooks(const AppId& app_id,
+                          UninstallWebAppCallback callback,
+                          OsHooksResults os_hooks_info);
 
   WebAppRegistrar& GetWebAppRegistrar() const;
 
diff --git a/chrome/browser/web_applications/web_app_install_task.cc b/chrome/browser/web_applications/web_app_install_task.cc
index 8aef81ea..5ded2a3 100644
--- a/chrome/browser/web_applications/web_app_install_task.cc
+++ b/chrome/browser/web_applications/web_app_install_task.cc
@@ -817,6 +817,7 @@
     // configured from somewhere else rather than always true.
     options.os_hooks[OsHookType::kFileHandlers] = true;
   }
+  options.os_hooks[OsHookType::kUninstallationViaOsSettings] = true;
   auto hooks_created_callback =
       base::BindOnce(&WebAppInstallTask::OnOsHooksCreated, GetWeakPtr(),
                      web_app_info->open_as_window, app_id);
diff --git a/chrome/browser/win/uninstallation_via_os_settings.cc b/chrome/browser/win/uninstallation_via_os_settings.cc
new file mode 100644
index 0000000..2dde7cab
--- /dev/null
+++ b/chrome/browser/win/uninstallation_via_os_settings.cc
@@ -0,0 +1,83 @@
+// 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/win/uninstallation_via_os_settings.h"
+
+#include <windows.h>
+
+#include "base/check_op.h"
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/win/registry.h"
+#include "base/win/windows_types.h"
+
+namespace {
+
+// Win32 App registry entry for uninstallation. System detects the registry
+// and show its App for App or Remove Settings.
+constexpr wchar_t kUninstallRegistryKey[] =
+    L"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall";
+
+}  // namespace
+
+bool RegisterUninstallationViaOsSettings(
+    const std::wstring& key,
+    const std::wstring& display_name,
+    const std::wstring& publisher,
+    const base::CommandLine& uninstall_command,
+    const base::FilePath& icon_path) {
+  DCHECK(!key.empty());
+  DCHECK(!display_name.empty());
+  DCHECK(!publisher.empty());
+  DCHECK(!uninstall_command.GetProgram().empty());
+
+  base::win::RegKey uninstall_reg_key;
+  LONG result = uninstall_reg_key.Open(HKEY_CURRENT_USER, kUninstallRegistryKey,
+                                       KEY_CREATE_SUB_KEY);
+  if (result != ERROR_SUCCESS)
+    return false;
+
+  base::win::RegKey uninstall_reg_entry_key;
+  DWORD disposition;
+
+  result = uninstall_reg_entry_key.CreateWithDisposition(
+      uninstall_reg_key.Handle(), key.c_str(), &disposition, KEY_WRITE);
+  if (result != ERROR_SUCCESS)
+    return false;
+
+  if (disposition != REG_CREATED_NEW_KEY)
+    return false;
+
+  // Add Uninstall values. Windows will show the icon at index 0
+  // if no index is specified in this value.
+  uninstall_reg_entry_key.WriteValue(L"DisplayIcon", icon_path.value().c_str());
+  uninstall_reg_entry_key.WriteValue(L"DisplayName", display_name.c_str());
+  uninstall_reg_entry_key.WriteValue(L"DisplayVersion", L"1.0");
+  uninstall_reg_entry_key.WriteValue(L"ApplicationVersion", L"1.0");
+
+  static constexpr wchar_t kDateFormat[] = L"yyyyyMMdd";
+  wchar_t date_str[base::size(kDateFormat)] = {};
+  int len = ::GetDateFormatW(LOCALE_INVARIANT, 0, nullptr, kDateFormat,
+                             date_str, base::size(date_str));
+  if (len)
+    uninstall_reg_entry_key.WriteValue(L"InstallDate", date_str);
+
+  uninstall_reg_entry_key.WriteValue(L"Publisher", publisher.c_str());
+  uninstall_reg_entry_key.WriteValue(
+      L"UninstallString", uninstall_command.GetCommandLineString().c_str());
+  uninstall_reg_entry_key.WriteValue(L"NoRepair", 1);
+  uninstall_reg_entry_key.WriteValue(L"NoModify", 1);
+
+  return true;
+}
+
+void UnregisterUninstallationViaOsSettings(const std::wstring& name) {
+  base::win::RegKey uninstall_reg_key;
+  LONG result = uninstall_reg_key.Open(HKEY_CURRENT_USER, kUninstallRegistryKey,
+                                       KEY_QUERY_VALUE);
+  if (result != ERROR_SUCCESS)
+    return;
+
+  uninstall_reg_key.DeleteKey(name.c_str());
+}
diff --git a/chrome/browser/win/uninstallation_via_os_settings.h b/chrome/browser/win/uninstallation_via_os_settings.h
new file mode 100644
index 0000000..4146d6c
--- /dev/null
+++ b/chrome/browser/win/uninstallation_via_os_settings.h
@@ -0,0 +1,40 @@
+// 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_WIN_UNINSTALLATION_VIA_OS_SETTINGS_H_
+#define CHROME_BROWSER_WIN_UNINSTALLATION_VIA_OS_SETTINGS_H_
+
+#include <string>
+
+namespace base {
+
+class CommandLine;
+class FilePath;
+
+}  // namespace base
+
+// Registers an uninstall command for a program |key| with the
+// Windows Programs and Features control panel.
+// The API doesn't allow same duplicate entry for the |key|. If the caller
+// updates the uninstall string, it should delete the previous one first.
+// |key|: A command ID that is a representative of the application.
+// |display_name|: Display name that will be shown on App or Remove Program.
+// |publisher|: URL Origin name of the App where it is installed from.
+// |uninstall_command|: A command line that has command string that is run
+// when the uninstall command is executed for the target application.
+// |icon_path|: An icon that will be shown with name in the uninstall UI
+// such as Program and Features.
+// The system default icon will be used if |icon_path| is empty.
+bool RegisterUninstallationViaOsSettings(
+    const std::wstring& key,
+    const std::wstring& display_name,
+    const std::wstring& publisher,
+    const base::CommandLine& uninstall_command,
+    const base::FilePath& icon_path);
+
+// Removes the uninstall command for the program |key|. |key| should be
+// same with what is used for RegisterUninstallationViaOsSettings for the entry.
+void UnregisterUninstallationViaOsSettings(const std::wstring& key);
+
+#endif  // CHROME_BROWSER_WIN_UNINSTALLATION_VIA_OS_SETTINGS_H_
diff --git a/chrome/browser/win/uninstallation_via_os_settings_unittest.cc b/chrome/browser/win/uninstallation_via_os_settings_unittest.cc
new file mode 100644
index 0000000..91d16a9
--- /dev/null
+++ b/chrome/browser/win/uninstallation_via_os_settings_unittest.cc
@@ -0,0 +1,127 @@
+// 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/win/uninstallation_via_os_settings.h"
+
+#include <string>
+
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/test/test_reg_util_win.h"
+#include "base/win/registry.h"
+#include "base/win/scoped_com_initializer.h"
+#include "chrome/common/chrome_switches.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+constexpr wchar_t kUninstallRegistryKey[] =
+    L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\";
+
+class RegisterUninstallationViaOsSettingsTest : public testing::Test {
+ protected:
+  void SetUp() override {
+    const HKEY kRoot = HKEY_CURRENT_USER;
+    ASSERT_NO_FATAL_FAILURE(registry_override_manager_.OverrideRegistry(kRoot));
+    ASSERT_TRUE(
+        base::win::RegKey(kRoot, kUninstallRegistryKey, KEY_CREATE_SUB_KEY)
+            .Valid());
+
+    ASSERT_EQ(uninstall_key_.Create(kRoot, kUninstallRegistryKey, KEY_WRITE),
+              ERROR_SUCCESS);
+
+    uninstall_commandline_ =
+        std::make_unique<base::CommandLine>(base::FilePath(L"chrome.exe"));
+    uninstall_commandline_->AppendSwitchNative(switches::kProfileDirectory,
+                                               L"Default");
+    uninstall_commandline_->AppendSwitchNative(switches::kUninstallAppId,
+                                               L"fhkpfpfifagoflnfpbdgegnghg");
+  }
+
+  base::win::ScopedCOMInitializer com_initializer_;
+  base::win::RegKey uninstall_reg_key_;
+
+  const std::wstring register_key_ = L"12cde1ab";
+  std::unique_ptr<base::CommandLine> uninstall_commandline_;
+  base::win::RegKey uninstall_key_;
+  registry_util::RegistryOverrideManager registry_override_manager_;
+};
+
+}  // namespace
+
+// Test that the uninstall registry key are inserted during shortcut
+// creation on the Start Menu only.
+TEST_F(RegisterUninstallationViaOsSettingsTest, DuplicateKey) {
+  ASSERT_TRUE(RegisterUninstallationViaOsSettings(
+      register_key_, L"Display_Name", L"Chromium", *uninstall_commandline_,
+      base::FilePath(L"C:\\users\\account\\AppData\\Local\\Chromium\\User "
+                     "Data\\Default\\Icons\\icon.ico")));
+
+  ASSERT_EQ(uninstall_key_.OpenKey(register_key_.c_str(), KEY_QUERY_VALUE),
+            ERROR_SUCCESS);
+
+  // It should be failed for duplicate key.
+  ASSERT_FALSE(RegisterUninstallationViaOsSettings(
+      register_key_, L"Display_Name", L"Chromium", *uninstall_commandline_,
+      base::FilePath(L"C:\\users\\account\\AppData\\Local\\Chromium\\User "
+                     "Data\\Default\\Icons\\icon.ico")));
+
+  UnregisterUninstallationViaOsSettings(register_key_);
+
+  ASSERT_FALSE(
+      base::win::RegKey(
+          HKEY_CURRENT_USER,
+          (std::wstring(kUninstallRegistryKey) + register_key_).c_str(),
+          KEY_QUERY_VALUE)
+          .Valid());
+}
+
+TEST_F(RegisterUninstallationViaOsSettingsTest, RegValues) {
+  // Registry entry is inserted as it has --app-id argument and
+  // uninstall_string property set.
+  std::wstring display_name = L"Display_Name";
+  std::wstring publisher = L"Chromium";
+
+  base::FilePath icon_path(
+      L"C:\\users\\account\\AppData\\Local\\Chromium\\User "
+      "Data\\Default\\Icons\\icon.ico");
+
+  ASSERT_TRUE(RegisterUninstallationViaOsSettings(
+      register_key_, display_name, publisher, *uninstall_commandline_,
+      icon_path));
+
+  ASSERT_EQ(uninstall_key_.OpenKey(register_key_.c_str(), KEY_QUERY_VALUE),
+            ERROR_SUCCESS);
+
+  std::wstring value;
+  ASSERT_EQ(uninstall_key_.ReadValue(L"DisplayName", &value), ERROR_SUCCESS);
+  EXPECT_EQ(value, display_name);
+
+  ASSERT_EQ(uninstall_key_.ReadValue(L"DisplayVersion", &value), ERROR_SUCCESS);
+  EXPECT_EQ(value, L"1.0");
+
+  ASSERT_EQ(uninstall_key_.ReadValue(L"ApplicationVersion", &value),
+            ERROR_SUCCESS);
+  EXPECT_EQ(value, L"1.0");
+
+  ASSERT_EQ(uninstall_key_.ReadValue(L"InstallDate", &value), ERROR_SUCCESS);
+
+  ASSERT_EQ(uninstall_key_.ReadValue(L"UninstallString", &value),
+            ERROR_SUCCESS);
+  EXPECT_EQ(value,
+            L"chrome.exe --profile-directory=Default "
+            "--uninstall-app-id=fhkpfpfifagoflnfpbdgegnghg");
+
+  DWORD int_value;
+  ASSERT_EQ(uninstall_key_.ReadValueDW(L"NoRepair", &int_value), ERROR_SUCCESS);
+  EXPECT_EQ(int_value, 1U);
+
+  ASSERT_EQ(uninstall_key_.ReadValueDW(L"NoModify", &int_value), ERROR_SUCCESS);
+  EXPECT_EQ(int_value, 1U);
+
+  ASSERT_EQ(uninstall_key_.ReadValue(L"DisplayIcon", &value), ERROR_SUCCESS);
+  EXPECT_EQ(value, icon_path.value());
+
+  UnregisterUninstallationViaOsSettings(register_key_);
+}
diff --git a/chrome/build/linux.pgo.txt b/chrome/build/linux.pgo.txt
index 858e291..31773102 100644
--- a/chrome/build/linux.pgo.txt
+++ b/chrome/build/linux.pgo.txt
@@ -1 +1 @@
-chrome-linux-master-1605290311-c0a8bccef266e50cf7d2fe9fae22d951549688a8.profdata
+chrome-linux-master-1605418474-d40e0a66ac214c017c874665540974720359ae8b.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index a7bd8471..43b16ea 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-master-1605278951-6acf5f1c81bf8e5b853d44ef9575d8aab067a4b3.profdata
+chrome-win32-master-1605301167-f2e539f21f91663031225bb0d3160662a773cdbd.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index 568cfbc2..aca24a0 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-master-1605268075-daeeb1bc6080c8b4d9f48b3dab7bc22bf5a02dcb.profdata
+chrome-win64-master-1605322732-f3f9a6c28d3c2189d459e96b0d723f5210a3cdc8.profdata
diff --git a/chrome/common/chrome_features.cc b/chrome/common/chrome_features.cc
index eee1a69..39a2838 100644
--- a/chrome/common/chrome_features.cc
+++ b/chrome/common/chrome_features.cc
@@ -352,6 +352,10 @@
     "EnableIncognitoShortcutOnDesktop", base::FEATURE_DISABLED_BY_DEFAULT};
 #endif
 
+// Enable web app uninstallation from Windows settings or control panel.
+const base::Feature kEnableWebAppUninstallFromOsSettings{
+    "EnableWebAppUninstallFromOsSettings", base::FEATURE_DISABLED_BY_DEFAULT};
+
 #if defined(OS_MAC)
 const base::Feature kEnterpriseReportingApiKeychainRecreation{
     "EnterpriseReportingApiKeychainRecreation",
diff --git a/chrome/common/chrome_features.h b/chrome/common/chrome_features.h
index 738a659..2ed72720 100644
--- a/chrome/common/chrome_features.h
+++ b/chrome/common/chrome_features.h
@@ -234,6 +234,9 @@
 extern const base::Feature kEnableIncognitoShortcutOnDesktop;
 #endif
 
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kEnableWebAppUninstallFromOsSettings;
+
 #if !defined(OS_ANDROID)
 COMPONENT_EXPORT(CHROME_FEATURES)
 extern const base::Feature kEnterpriseRealtimeExtensionRequest;
diff --git a/chrome/common/chrome_switches.cc b/chrome/common/chrome_switches.cc
index 7a14aa6..4a0793e 100644
--- a/chrome/common/chrome_switches.cc
+++ b/chrome/common/chrome_switches.cc
@@ -764,6 +764,9 @@
 // Runs un-installation steps that were done by chrome first-run.
 const char kUninstall[]                     = "uninstall";
 
+// Specifies that the WebApp with the specified id should be uninstalled.
+const char kUninstallAppId[] = "uninstall-app-id";
+
 // Specifies the version of the Progressive-Web-App launcher that launched
 // Chrome, used to determine whether to update all launchers.
 // NOTE: changing this switch requires adding legacy handling for the previous
diff --git a/chrome/common/chrome_switches.h b/chrome/common/chrome_switches.h
index f6f11272e..6d6a866e 100644
--- a/chrome/common/chrome_switches.h
+++ b/chrome/common/chrome_switches.h
@@ -236,6 +236,7 @@
 extern const char kPwaLauncherVersion[];
 extern const char kShowIcons[];
 extern const char kUninstall[];
+extern const char kUninstallAppId[];
 #endif  // defined(OS_WIN)
 
 #if BUILDFLAG(ENABLE_PRINT_PREVIEW) && !defined(OFFICIAL_BUILD)
diff --git a/chrome/credential_provider/extension/BUILD.gn b/chrome/credential_provider/extension/BUILD.gn
index 60bb635..e6aa7bb 100644
--- a/chrome/credential_provider/extension/BUILD.gn
+++ b/chrome/credential_provider/extension/BUILD.gn
@@ -72,7 +72,7 @@
     ":version",
     "../eventlog:gcp_eventlog_messages",
     "../gaiacp:common",
-    "../gaiacp:policies",
+    "../gaiacp:tasks",
     "../gaiacp:util",
     "//base",
     "//components/crash/core/app:crash_export_thunks",
diff --git a/chrome/credential_provider/extension/extension_main.cc b/chrome/credential_provider/extension/extension_main.cc
index 3858456..af11aeb 100644
--- a/chrome/credential_provider/extension/extension_main.cc
+++ b/chrome/credential_provider/extension/extension_main.cc
@@ -14,6 +14,8 @@
 #include "chrome/credential_provider/extension/os_service_manager.h"
 #include "chrome/credential_provider/extension/service.h"
 #include "chrome/credential_provider/extension/task_manager.h"
+#include "chrome/credential_provider/gaiacp/experiments_fetcher.h"
+#include "chrome/credential_provider/gaiacp/experiments_manager.h"
 #include "chrome/credential_provider/gaiacp/gem_device_details_manager.h"
 #include "chrome/credential_provider/gaiacp/logging.h"
 #include "chrome/credential_provider/gaiacp/reg_utils.h"
@@ -38,6 +40,13 @@
         "UploadDeviceDetails", credential_provider::GemDeviceDetailsManager::
                                    UploadDeviceDetailsTaskCreator());
   }
+
+  // Task to fetch experiments for all GCPW users.
+  if (credential_provider::ExperimentsManager::Get()->ExperimentsEnabled()) {
+    credential_provider::extension::TaskManager::Get()->RegisterTask(
+        "FetchExperiments", credential_provider::ExperimentsFetcher::
+                                GetFetchExperimentsTaskCreator());
+  }
 }
 
 int APIENTRY wWinMain(HINSTANCE hInstance,
diff --git a/chrome/credential_provider/gaiacp/BUILD.gn b/chrome/credential_provider/gaiacp/BUILD.gn
index b883d6b..d4bbb0d 100644
--- a/chrome/credential_provider/gaiacp/BUILD.gn
+++ b/chrome/credential_provider/gaiacp/BUILD.gn
@@ -99,14 +99,18 @@
   ]
 }
 
-# This component is used for the cloud policies feature.
-component("policies") {
-  output_name = "gcpw_policies"
+# This component is used for the periodically executed tasks.
+component("tasks") {
+  output_name = "gcpw_tasks"
   sources = [
     "device_policies.cc",
     "device_policies.h",
     "device_policies_manager.cc",
     "device_policies_manager.h",
+    "experiments_fetcher.cc",
+    "experiments_fetcher.h",
+    "experiments_manager.cc",
+    "experiments_manager.h",
     "gcpw_version.cc",
     "gcpw_version.h",
     "user_policies.cc",
@@ -114,12 +118,13 @@
     "user_policies_manager.cc",
     "user_policies_manager.h",
   ]
-  defines = [ "IS_GCPW_POLICIES_IMPL" ]
+  defines = [ "IS_GCPW_TASKS_IMPL" ]
   deps = [
     ":common",
     ":util",
     "../extension:extension_lib",
     "//base",
+    "//chrome/common:version_header",
     "//chrome/updater/protos:omaha_proto",
     "//components/policy/proto",
     "//url",
@@ -175,9 +180,9 @@
   public_deps = [ ":common" ]
   deps = [
     ":gaia_credential_provider_idl",
-    ":policies",
     ":static_resources",
     ":string_resources",
+    ":tasks",
     ":util",
     "../eventlog:gcp_eventlog_messages",
     "../extension:common",
@@ -274,7 +279,7 @@
   deps = [
     ":common",
     ":gaiacp_lib",
-    ":policies",
+    ":tasks",
     ":util",
     ":version",
     "//base",
diff --git a/chrome/credential_provider/gaiacp/device_policies.h b/chrome/credential_provider/gaiacp/device_policies.h
index 660fb58..da30813 100644
--- a/chrome/credential_provider/gaiacp/device_policies.h
+++ b/chrome/credential_provider/gaiacp/device_policies.h
@@ -14,7 +14,7 @@
 namespace credential_provider {
 
 // Structure to hold the policies for the device.
-struct COMPONENT_EXPORT(GCPW_POLICIES) DevicePolicies {
+struct COMPONENT_EXPORT(GCPW_TASKS) DevicePolicies {
   // Controls whether MDM enrollment is enabled/disabled.
   bool enable_dm_enrollment;
 
diff --git a/chrome/credential_provider/gaiacp/device_policies_manager.h b/chrome/credential_provider/gaiacp/device_policies_manager.h
index cc15dc5..7a6d7d5 100644
--- a/chrome/credential_provider/gaiacp/device_policies_manager.h
+++ b/chrome/credential_provider/gaiacp/device_policies_manager.h
@@ -11,7 +11,7 @@
 namespace credential_provider {
 
 // Manager used to fetch user policies from GCPW backends.
-class COMPONENT_EXPORT(GCPW_POLICIES) DevicePoliciesManager {
+class COMPONENT_EXPORT(GCPW_TASKS) DevicePoliciesManager {
  public:
   // Get the user policies manager instance.
   static DevicePoliciesManager* Get();
diff --git a/chrome/credential_provider/gaiacp/experiments_fetcher.cc b/chrome/credential_provider/gaiacp/experiments_fetcher.cc
new file mode 100644
index 0000000..94def07b
--- /dev/null
+++ b/chrome/credential_provider/gaiacp/experiments_fetcher.cc
@@ -0,0 +1,236 @@
+// 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/credential_provider/gaiacp/experiments_fetcher.h"
+
+#include "base/bind.h"
+#include "base/files/file.h"
+#include "base/files/file_enumerator.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
+#include "base/path_service.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
+#include "chrome/common/chrome_version.h"
+#include "chrome/credential_provider/gaiacp/experiments_manager.h"
+#include "chrome/credential_provider/gaiacp/gcp_utils.h"
+#include "chrome/credential_provider/gaiacp/logging.h"
+#include "chrome/credential_provider/gaiacp/reg_utils.h"
+
+namespace credential_provider {
+namespace {
+
+// HTTP endpoint on the GCPW service to fetch experiments.
+const wchar_t kGcpwServiceFetchExperimentsPath[] = L"/v1/experiments";
+
+// Default timeout when trying to make requests to the GCPW service.
+const base::TimeDelta kDefaultFetchExperimentsRequestTimeout =
+    base::TimeDelta::FromMilliseconds(5000);
+
+// Maximum number of retries if a HTTP call to the backend fails.
+constexpr unsigned int kMaxNumHttpRetries = 1;
+
+// HTTP query parameters for fetch experiments RPC.
+const char kObfuscatedUserIdKey[] = "obfuscated_gaia_id";
+const char kGcpwVersionKey[] = "gcpw_version";
+const char kDmTokenKey[] = "dm_token";
+const char kDeviceResourceIdKey[] = "device_resource_id";
+const char kFeaturesKey[] = "feature";
+
+// Defines a task that is called by the ESA to perform the experiments fetch
+// operation.
+class ExperimentsFetchTask : public extension::Task {
+ public:
+  static std::unique_ptr<extension::Task> Create() {
+    std::unique_ptr<extension::Task> esa_task(new ExperimentsFetchTask());
+    return esa_task;
+  }
+
+  // ESA calls this to retrieve a configuration for the task execution. Return
+  // a default config for now.
+  extension::Config GetConfig() final { return extension::Config(); }
+
+  // ESA calls this to set all the user-device contexts for the execution of the
+  // task.
+  HRESULT SetContext(const std::vector<extension::UserDeviceContext>& c) final {
+    context_ = c;
+    return S_OK;
+  }
+
+  // ESA calls execute function to perform the actual task.
+  HRESULT Execute() final {
+    HRESULT task_status = S_OK;
+    for (const auto& c : context_) {
+      HRESULT hr = ExperimentsFetcher::Get()->FetchAndStoreExperiments(c);
+      if (FAILED(hr)) {
+        LOGFN(ERROR) << "Failed fetching experiments for " << c.user_sid
+                     << ". hr=" << putHR(hr);
+        task_status = hr;
+      }
+    }
+
+    return task_status;
+  }
+
+ private:
+  std::vector<extension::UserDeviceContext> context_;
+};
+
+// Builds the request dictionary to fetch experiments from the backend. If
+// |dm_token| is empty, it isn't added into request. If user id isn't found for
+// the given |sid|, returns an empty dictionary.
+std::unique_ptr<base::DictionaryValue> GetExperimentsRequestDict(
+    const base::string16& sid,
+    const base::string16& device_resource_id,
+    const base::string16& dm_token) {
+  std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue);
+
+  base::string16 user_id;
+
+  HRESULT status = GetIdFromSid(sid.c_str(), &user_id);
+  if (FAILED(status)) {
+    LOGFN(ERROR) << "Could not get user id from sid " << sid;
+    return nullptr;
+  }
+
+  dict->SetString(kObfuscatedUserIdKey, user_id);
+
+  if (!dm_token.empty()) {
+    dict->SetString(kDmTokenKey, dm_token);
+  }
+  dict->SetString(kDeviceResourceIdKey, device_resource_id);
+
+  dict->SetString(kGcpwVersionKey, TEXT(CHROME_VERSION_STRING));
+
+  auto keys = std::make_unique<base::ListValue>();
+  for (auto& experiment : ExperimentsManager::Get()->GetExperimentsList())
+    keys->AppendString(experiment);
+
+  dict->Set(kFeaturesKey, std::move(keys));
+
+  return dict;
+}
+
+}  // namespace
+
+GURL ExperimentsFetcher::GetExperimentsUrl() {
+  GURL gcpw_service_url = GetGcpwServiceUrl();
+  return gcpw_service_url.Resolve(kGcpwServiceFetchExperimentsPath);
+}
+
+// static
+ExperimentsFetcher* ExperimentsFetcher::Get() {
+  return *GetInstanceStorage();
+}
+
+// static
+ExperimentsFetcher** ExperimentsFetcher::GetInstanceStorage() {
+  static ExperimentsFetcher instance;
+  static ExperimentsFetcher* instance_storage = &instance;
+  return &instance_storage;
+}
+
+// static
+extension::TaskCreator ExperimentsFetcher::GetFetchExperimentsTaskCreator() {
+  return base::BindRepeating(&ExperimentsFetchTask::Create);
+}
+
+ExperimentsFetcher::ExperimentsFetcher() {}
+
+ExperimentsFetcher::~ExperimentsFetcher() = default;
+
+HRESULT ExperimentsFetcher::FetchAndStoreExperiments(
+    const extension::UserDeviceContext& context) {
+  return FetchAndStoreExperimentsInternal(
+      context.user_sid, /* access_token= */ "",
+      GetExperimentsRequestDict(context.user_sid, context.device_resource_id,
+                                context.dm_token));
+}
+
+HRESULT ExperimentsFetcher::FetchAndStoreExperiments(
+    const base::string16& sid,
+    const std::string& access_token) {
+  HRESULT hr = FetchAndStoreExperimentsInternal(
+      sid, access_token,
+      GetExperimentsRequestDict(sid, GetUserDeviceResourceId(sid),
+                                /* dm_token= */ L""));
+  return hr;
+}
+
+HRESULT ExperimentsFetcher::FetchAndStoreExperimentsInternal(
+    const base::string16& sid,
+    const std::string& access_token,
+    std::unique_ptr<base::DictionaryValue> request_dict) {
+  if (!request_dict) {
+    LOGFN(ERROR) << "Request dictionary is null";
+    return E_FAIL;
+  }
+
+  // Make the fetch experiments HTTP request.
+  base::Optional<base::Value> request_result;
+  HRESULT hr = WinHttpUrlFetcher::BuildRequestAndFetchResultFromHttpService(
+      GetExperimentsUrl(), access_token, {}, *request_dict.get(),
+      kDefaultFetchExperimentsRequestTimeout, kMaxNumHttpRetries,
+      &request_result);
+
+  if (FAILED(hr)) {
+    LOGFN(ERROR) << "BuildRequestAndFetchResultFromHttpService hr="
+                 << putHR(hr);
+    return hr;
+  }
+
+  std::string experiments_data;
+  if (request_result && request_result->is_dict()) {
+    if (!base::JSONWriter::Write(*request_result, &experiments_data)) {
+      LOGFN(ERROR) << "base::JSONWriter::Write failed";
+      return E_FAIL;
+    }
+  } else {
+    LOGFN(ERROR) << "Failed to parse experiments response!";
+    return E_FAIL;
+  }
+
+  uint32_t open_flags = base::File::FLAG_CREATE_ALWAYS |
+                        base::File::FLAG_WRITE |
+                        base::File::FLAG_EXCLUSIVE_WRITE;
+  std::unique_ptr<base::File> experiments_file = GetOpenedFileForUser(
+      sid, open_flags, kGcpwExperimentsDirectory, kGcpwUserExperimentsFileName);
+  if (!experiments_file) {
+    LOGFN(ERROR) << "Failed to open " << kGcpwUserExperimentsFileName
+                 << " file.";
+    return E_FAIL;
+  }
+
+  int num_bytes_written = experiments_file->Write(0, experiments_data.c_str(),
+                                                  experiments_data.size());
+
+  experiments_file.reset();
+
+  if (size_t(num_bytes_written) != experiments_data.size()) {
+    LOGFN(ERROR) << "Failed writing experiments data to file! Only "
+                 << num_bytes_written << " bytes written out of "
+                 << experiments_data.size();
+    return E_FAIL;
+  }
+
+  base::Time fetch_time = base::Time::Now();
+  base::string16 fetch_time_millis = base::NumberToString16(
+      fetch_time.ToDeltaSinceWindowsEpoch().InMilliseconds());
+
+  if (!ExperimentsManager::Get()->ReloadExperiments(sid)) {
+    LOGFN(ERROR) << "Error when loading experiments for user with sid " << sid;
+  }
+
+  // Store the fetch time so we know whether a refresh is needed.
+  SetUserProperty(sid, kLastUserExperimentsRefreshTimeRegKey,
+                  fetch_time_millis);
+
+  return S_OK;
+}
+
+}  // namespace credential_provider
diff --git a/chrome/credential_provider/gaiacp/experiments_fetcher.h b/chrome/credential_provider/gaiacp/experiments_fetcher.h
new file mode 100644
index 0000000..0f2776b
--- /dev/null
+++ b/chrome/credential_provider/gaiacp/experiments_fetcher.h
@@ -0,0 +1,59 @@
+// 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_CREDENTIAL_PROVIDER_GAIACP_EXPERIMENTS_FETCHER_H_
+#define CHROME_CREDENTIAL_PROVIDER_GAIACP_EXPERIMENTS_FETCHER_H_
+
+#include "base/component_export.h"
+#include "base/values.h"
+#include "base/win/windows_types.h"
+#include "chrome/credential_provider/extension/task_manager.h"
+#include "url/gurl.h"
+
+namespace credential_provider {
+
+// Fetcher used to fetch experiments from GCPW backends.
+class COMPONENT_EXPORT(GCPW_TASKS) ExperimentsFetcher {
+ public:
+  // Gets the singleton instance.
+  static ExperimentsFetcher* Get();
+
+  // Provides the GCPW extension with a TaskCreator which can be used to create
+  // a task for fetching experiments.
+  static extension::TaskCreator GetFetchExperimentsTaskCreator();
+
+  // Fetches the experiments for the user implied with the |access_token| from
+  // the GCPW backend and saves it in file storage replacing any previously
+  // fetched versions.
+  HRESULT FetchAndStoreExperiments(const base::string16& sid,
+                                   const std::string& access_token);
+
+  // Fetches the experiments for the user-device |context| provided by the GCPW
+  // extension service from the GCPW backend and saves it in file storage
+  // replacing any previously fetched versions.
+  HRESULT FetchAndStoreExperiments(const extension::UserDeviceContext& context);
+
+  // Returns the experiments fetch endpoint.
+  GURL GetExperimentsUrl();
+
+ private:
+  // Returns the storage used for the instance pointer.
+  static ExperimentsFetcher** GetInstanceStorage();
+
+  ExperimentsFetcher();
+  virtual ~ExperimentsFetcher();
+
+  // Fetches experiments with the provided |request_dict|. |access_token| needs
+  // to be present if the experiments are being fetched in the presence of an
+  // oauth token. Otherwise |request_dict| should be carrying the obfuscated
+  // user id as well as DM token.
+  HRESULT FetchAndStoreExperimentsInternal(
+      const base::string16& sid,
+      const std::string& access_token,
+      std::unique_ptr<base::DictionaryValue> request_dict);
+};
+
+}  // namespace credential_provider
+
+#endif  // CHROME_CREDENTIAL_PROVIDER_GAIACP_EXPERIMENTS_FETCHER_H_
diff --git a/chrome/credential_provider/gaiacp/experiments_manager.cc b/chrome/credential_provider/gaiacp/experiments_manager.cc
new file mode 100644
index 0000000..98edc36
--- /dev/null
+++ b/chrome/credential_provider/gaiacp/experiments_manager.cc
@@ -0,0 +1,159 @@
+// 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/credential_provider/gaiacp/experiments_manager.h"
+
+#include <vector>
+
+#include "base/files/file.h"
+#include "base/json/json_reader.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "chrome/credential_provider/gaiacp/gcp_utils.h"
+#include "chrome/credential_provider/gaiacp/logging.h"
+#include "chrome/credential_provider/gaiacp/reg_utils.h"
+
+namespace credential_provider {
+namespace {
+
+// Registry key to control whether experiments feature is enabled.
+const wchar_t kExperimentsEnabledRegKey[] = L"experiments_enabled";
+
+// Name of the keys found in the experiments server response.
+const char kResponseExperimentsKeyName[] = "experiments";
+const char kResponseFeatureKeyName[] = "feature";
+const char kResponseValueKeyName[] = "value";
+
+}  // namespace
+
+// static
+ExperimentsManager* ExperimentsManager::Get() {
+  return *GetInstanceStorage();
+}
+
+// static
+ExperimentsManager** ExperimentsManager::GetInstanceStorage() {
+  static ExperimentsManager instance;
+  static ExperimentsManager* instance_storage = &instance;
+  return &instance_storage;
+}
+
+ExperimentsManager::ExperimentsManager() {
+  if (ExperimentsEnabled()) {
+    RegisterExperiments();
+    ReloadAllExperiments();
+  }
+}
+
+ExperimentsManager::~ExperimentsManager() = default;
+
+void ExperimentsManager::RegisterExperiments() {
+  experiments_to_values_.clear();
+  for (ExperimentMetadata em : experiments) {
+    // Experiment name to default value mapping is created prior to reading
+    // experiments file.
+    experiments_to_values_[em.name].first = em.default_value;
+  }
+}
+
+// Reloads the experiment values from fetched experiments file for the given
+// |sid|. Loads the fetched experiments values into |experiments_to_values_|.
+bool ExperimentsManager::ReloadExperiments(const base::string16& sid) {
+  uint32_t open_flags = base::File::FLAG_OPEN | base::File::FLAG_READ;
+  std::unique_ptr<base::File> experiments_file = GetOpenedFileForUser(
+      sid, open_flags, kGcpwExperimentsDirectory, kGcpwUserExperimentsFileName);
+  if (!experiments_file) {
+    return false;
+  }
+
+  std::vector<char> buffer(experiments_file->GetLength());
+  experiments_file->Read(0, buffer.data(), buffer.size());
+  experiments_file.reset();
+
+  base::Optional<base::Value> experiments_data =
+      base::JSONReader::Read(base::StringPiece(buffer.data(), buffer.size()),
+                             base::JSON_ALLOW_TRAILING_COMMAS);
+  if (!experiments_data || !experiments_data->is_dict()) {
+    LOGFN(ERROR) << "Failed to read experiments data from file!";
+    return false;
+  }
+
+  const base::Value* experiments =
+      experiments_data->FindListKey(kResponseExperimentsKeyName);
+  if (!experiments) {
+    LOGFN(ERROR) << "User experiments not found!";
+    return false;
+  }
+
+  if (experiments->is_list()) {
+    for (const auto& item : experiments->GetList()) {
+      auto* f = item.FindStringKey(kResponseFeatureKeyName);
+      auto* v = item.FindStringKey(kResponseValueKeyName);
+      if (!f || !v) {
+        LOGFN(WARNING) << "Either feature or value are not found!";
+      }
+
+      experiments_to_values_[*f].second[base::UTF16ToUTF8(sid)] = *v;
+    }
+  }
+  return true;
+}
+
+// TODO(crbug.com/1143829): Reload experiments if they were fetched by ESA.
+void ExperimentsManager::ReloadAllExperiments() {
+  std::map<base::string16, UserTokenHandleInfo> sid_to_gaia_id;
+  HRESULT hr = GetUserTokenHandles(&sid_to_gaia_id);
+
+  if (FAILED(hr)) {
+    LOGFN(ERROR) << "Unable to get the list of associated users";
+    return;
+  }
+
+  for (auto& sid : sid_to_gaia_id) {
+    if (!ReloadExperiments(sid.first)) {
+      LOGFN(ERROR) << "Error when loading experiments for user with sid "
+                   << sid.first;
+    }
+  }
+}
+
+std::string ExperimentsManager::GetExperimentForUser(const std::string& sid,
+                                                     Experiment experiment) {
+  std::string experiment_name = experiments[experiment].name;
+
+  // There shouldn't be a case where a registered experiment can't be found, but
+  // just in case we return an empty string.
+  if (experiments_to_values_.find(experiment_name) ==
+      experiments_to_values_.end())
+    return "";
+
+  auto& sid_to_experiment_values =
+      experiments_to_values_[experiment_name].second;
+
+  // If the experiment value doesn't exist for the given sid, return the default
+  // value.
+  if (sid_to_experiment_values.find(sid) == sid_to_experiment_values.end()) {
+    return experiments_to_values_[experiment_name].first;
+  }
+
+  return sid_to_experiment_values[sid];
+}
+
+bool ExperimentsManager::GetExperimentForUserAsBool(const std::string& sid,
+                                                    Experiment experiment) {
+  return base::ToLowerASCII(GetExperimentForUser(sid, experiment)) == "true";
+}
+
+bool ExperimentsManager::ExperimentsEnabled() const {
+  return GetGlobalFlagOrDefault(kExperimentsEnabledRegKey, 0);
+}
+
+std::vector<std::string> ExperimentsManager::GetExperimentsList() const {
+  std::vector<std::string> experiment_names;
+  for (auto& experiment : experiments)
+    experiment_names.push_back(experiment.name);
+  return experiment_names;
+}
+
+}  // namespace credential_provider
diff --git a/chrome/credential_provider/gaiacp/experiments_manager.h b/chrome/credential_provider/gaiacp/experiments_manager.h
new file mode 100644
index 0000000..3512844
--- /dev/null
+++ b/chrome/credential_provider/gaiacp/experiments_manager.h
@@ -0,0 +1,88 @@
+// 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_CREDENTIAL_PROVIDER_GAIACP_EXPERIMENTS_MANAGER_H_
+#define CHROME_CREDENTIAL_PROVIDER_GAIACP_EXPERIMENTS_MANAGER_H_
+
+#include <map>
+#include <vector>
+
+#include "base/component_export.h"
+#include "base/values.h"
+#include "base/win/windows_types.h"
+#include "chrome/credential_provider/extension/task_manager.h"
+#include "url/gurl.h"
+
+namespace credential_provider {
+
+// List of experiments which must be kept in sync with the metadata for the
+// experiment below.
+enum Experiment { TEST_CLIENT_FLAG, TEST_CLIENT_FLAG2 };
+
+class COMPONENT_EXPORT(GCPW_TASKS) ExperimentsManager {
+ public:
+  // Gets the singleton instance.
+  static ExperimentsManager* Get();
+
+  // Called as the first thing when ExperimentsManager is getting constructed.
+  // Registers all the experiments used in GCPW and ESA with their default
+  // values. If fetching experiments happen to fail, defaults set by this
+  // function are used.
+  void RegisterExperiments();
+
+  // Reloads the experiments for the given |sid|.
+  bool ReloadExperiments(const base::string16& sid);
+
+  // Returns the experiment value for the provided |sid| and |experiment|.
+  std::string GetExperimentForUser(const std::string& sid,
+                                   Experiment experiment);
+
+  // Returns the experiment value as a boolean for the provided |sid| and
+  // |experiment|.
+  bool GetExperimentForUserAsBool(const std::string& sid,
+                                  Experiment experiment);
+
+  // Return true if experiments feature is enabled.
+  bool ExperimentsEnabled() const;
+
+  // Returns the list of experiments to fetch from the backend.
+  std::vector<std::string> GetExperimentsList() const;
+
+ private:
+  // Returns the storage used for the instance pointer.
+  static ExperimentsManager** GetInstanceStorage();
+
+  ExperimentsManager();
+  virtual ~ExperimentsManager();
+
+  // Updates the cached values of experiments with the recent fetch.
+  void ReloadAllExperiments();
+
+  // sid to experiment value mapping.
+  typedef std::map<std::string, std::string> PerUserValues;
+
+  // default value and per user experiment value pair.
+  typedef std::pair<std::string, PerUserValues> ExperimentValue;
+
+  // Map of features to <default value, per user value mapping>
+  std::map<std::string, ExperimentValue> experiments_to_values_;
+
+  // Struct which contains an individual experiment name and default value.
+  struct ExperimentMetadata {
+    Experiment experiment;
+    std::string name;
+    std::string default_value;
+  };
+
+  // Metadata for list of supported experiments.
+  std::vector<ExperimentMetadata> experiments = {
+      // TODO(crbug.com/1147522): Clean up the test experiments when actual
+      // experiments are introduced. These were added for e2e testing.
+      {TEST_CLIENT_FLAG, "test_client_flag", "false"},
+      {TEST_CLIENT_FLAG2, "test_client_flag2", "false"}};
+};
+
+}  // namespace credential_provider
+
+#endif  // CHROME_CREDENTIAL_PROVIDER_GAIACP_EXPERIMENTS_MANAGER_H_
diff --git a/chrome/credential_provider/gaiacp/experiments_manager_unittests.cc b/chrome/credential_provider/gaiacp/experiments_manager_unittests.cc
new file mode 100644
index 0000000..85d47a8
--- /dev/null
+++ b/chrome/credential_provider/gaiacp/experiments_manager_unittests.cc
@@ -0,0 +1,202 @@
+// 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/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "chrome/credential_provider/extension/task.h"
+#include "chrome/credential_provider/gaiacp/experiments_fetcher.h"
+#include "chrome/credential_provider/gaiacp/experiments_manager.h"
+#include "chrome/credential_provider/gaiacp/gcp_utils.h"
+#include "chrome/credential_provider/gaiacp/logging.h"
+#include "chrome/credential_provider/gaiacp/reg_utils.h"
+#include "chrome/credential_provider/test/gcp_fakes.h"
+#include "chrome/credential_provider/test/gls_runner_test_base.h"
+#include "chrome/credential_provider/test/test_credential.h"
+#include "google_apis/gaia/gaia_urls.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace credential_provider {
+
+namespace testing {
+
+class ExperimentsManagerTest : public GlsRunnerTestBase {
+ protected:
+  void SetUp() override;
+};
+
+void ExperimentsManagerTest::SetUp() {
+  GlsRunnerTestBase::SetUp();
+
+  ASSERT_EQ(S_OK, SetGlobalFlagForTesting(L"experiments_enabled", 1));
+  FakesForTesting fakes;
+  fakes.fake_win_http_url_fetcher_creator =
+      fake_http_url_fetcher_factory()->GetCreatorCallback();
+  WinHttpUrlFetcher::SetCreatorForTesting(
+      fakes.fake_win_http_url_fetcher_creator);
+
+  // Set token result a valid access token.
+  fake_http_url_fetcher_factory()->SetFakeResponse(
+      GURL(GaiaUrls::GetInstance()->oauth2_token_url().spec().c_str()),
+      FakeWinHttpUrlFetcher::Headers(), "{\"access_token\": \"dummy_token\"}");
+}
+
+TEST_F(ExperimentsManagerTest, DefaultValues) {
+  EXPECT_EQ("false", ExperimentsManager::Get()->GetExperimentForUser(
+                         "test_sid", Experiment::TEST_CLIENT_FLAG));
+  EXPECT_EQ("false", ExperimentsManager::Get()->GetExperimentForUser(
+                         "test_sid", Experiment::TEST_CLIENT_FLAG2));
+
+  EXPECT_FALSE(ExperimentsManager::Get()->GetExperimentForUserAsBool(
+      "test_sid", Experiment::TEST_CLIENT_FLAG));
+  EXPECT_FALSE(ExperimentsManager::Get()->GetExperimentForUserAsBool(
+      "test_sid", Experiment::TEST_CLIENT_FLAG2));
+}
+
+// Tests different outcomes of fetching experiments through GCPW:
+// 0 - Experiments are fetched successfully.
+// 1 - Failed fetching experiments.
+// 2 - Response in fetching experiments contains malformed payload.
+class ExperimentsManagerGcpwE2ETest
+    : public ExperimentsManagerTest,
+      public ::testing::WithParamInterface<int> {};
+
+TEST_P(ExperimentsManagerGcpwE2ETest, FetchingExperiments) {
+  int experiment_fetch_status = GetParam();
+
+  // Create a fake user that has the same gaia id as the test gaia id.
+  CComBSTR sid;
+  ASSERT_EQ(S_OK,
+            fake_os_user_manager()->CreateTestOSUser(
+                L"foo", L"password", L"Full Name", L"comment",
+                base::UTF8ToUTF16(kDefaultGaiaId), L"user@company.com", &sid));
+
+  base::string16 device_resource_id = L"foo_resource_id";
+  ASSERT_EQ(S_OK, SetUserProperty(OLE2W(sid), L"device_resource_id",
+                                  device_resource_id));
+
+  // Re-registering effectively clears the experiment values from the previous
+  // fetches.
+  ExperimentsManager::Get()->RegisterExperiments();
+
+  GURL url = ExperimentsFetcher::Get()->GetExperimentsUrl();
+
+  if (experiment_fetch_status == 0) {
+    fake_http_url_fetcher_factory()->SetFakeResponse(
+        url, FakeWinHttpUrlFetcher::Headers(),
+        "{\"experiments\": [{\"feature\": \"test_client_flag\", \"value\": "
+        "\"abc\"}, {\"feature\": \"test_client_flag2\", \"value\": \"def\"} ] "
+        "}");
+  } else if (experiment_fetch_status == 1) {
+    fake_http_url_fetcher_factory()->SetFakeFailedResponse(url, E_FAIL);
+  } else {
+    fake_http_url_fetcher_factory()->SetFakeResponse(
+        url, FakeWinHttpUrlFetcher::Headers(), "{\"bad_experiments\": [ ] }");
+  }
+
+  // Create provider and start logon.
+  Microsoft::WRL::ComPtr<ICredentialProviderCredential> cred;
+
+  ASSERT_EQ(S_OK, InitializeProviderAndGetCredential(0, &cred));
+
+  Microsoft::WRL::ComPtr<ITestCredential> test;
+  ASSERT_EQ(S_OK, cred.As(&test));
+
+  ASSERT_EQ(S_OK, StartLogonProcessAndWait());
+
+  // Email should be the same as the default one.
+  EXPECT_EQ(test->GetFinalEmail(), kDefaultEmail);
+
+  std::string experiment1_value = "false";
+  std::string experiment2_value = "false";
+
+  if (experiment_fetch_status == 0) {
+    experiment1_value = "abc";
+    experiment2_value = "def";
+  }
+
+  EXPECT_EQ(experiment1_value,
+            ExperimentsManager::Get()->GetExperimentForUser(
+                base::UTF16ToUTF8(OLE2W(sid)), Experiment::TEST_CLIENT_FLAG));
+  EXPECT_EQ(experiment2_value,
+            ExperimentsManager::Get()->GetExperimentForUser(
+                base::UTF16ToUTF8(OLE2W(sid)), Experiment::TEST_CLIENT_FLAG2));
+}
+
+INSTANTIATE_TEST_SUITE_P(All,
+                         ExperimentsManagerGcpwE2ETest,
+                         ::testing::Values(0, 1, 2));
+
+// Tests different outcomes of fetching experiments through ESA:
+// 0 - Experiments are fetched successfully.
+// 1 - Failed fetching experiments.
+// 2 - Response in fetching experiments contains malformed payload.
+class ExperimentsManagerESAE2ETest : public ExperimentsManagerTest,
+                                     public ::testing::WithParamInterface<int> {
+};
+
+TEST_P(ExperimentsManagerESAE2ETest, FetchingExperiments) {
+  int experiment_fetch_status = GetParam();
+
+  // Create a fake user that has the same gaia id as the test gaia id.
+  CComBSTR sid;
+  ASSERT_EQ(S_OK,
+            fake_os_user_manager()->CreateTestOSUser(
+                L"foo", L"password", L"Full Name", L"comment",
+                base::UTF8ToUTF16(kDefaultGaiaId), L"user@company.com", &sid));
+
+  ASSERT_EQ(S_OK, GenerateGCPWDmToken((BSTR)sid));
+
+  base::string16 device_resource_id = L"foo_resource_id";
+  ASSERT_EQ(S_OK, SetUserProperty(OLE2W(sid), L"device_resource_id",
+                                  device_resource_id));
+
+  // Re-registering effectively clears the experiment values from the previous
+  // fetches.
+  ExperimentsManager::Get()->RegisterExperiments();
+
+  GURL url = ExperimentsFetcher::Get()->GetExperimentsUrl();
+
+  if (experiment_fetch_status == 0) {
+    fake_http_url_fetcher_factory()->SetFakeResponse(
+        url, FakeWinHttpUrlFetcher::Headers(),
+        "{\"experiments\": [{\"feature\": \"test_client_flag\", \"value\": "
+        "\"abc\"}, {\"feature\": \"test_client_flag2\", \"value\": \"def\"} ] "
+        "}");
+  } else if (experiment_fetch_status == 1) {
+    fake_http_url_fetcher_factory()->SetFakeFailedResponse(url, E_FAIL);
+  } else {
+    fake_http_url_fetcher_factory()->SetFakeResponse(
+        url, FakeWinHttpUrlFetcher::Headers(), "{\"bad_experiments\": [ ] }");
+  }
+
+  base::string16 dm_token;
+  ASSERT_EQ(S_OK, GetGCPWDmToken((BSTR)sid, &dm_token));
+
+  std::unique_ptr<extension::Task> task(
+      ExperimentsFetcher::GetFetchExperimentsTaskCreator().Run());
+  task->SetContext({{device_resource_id, L"", L"", OLE2W(sid), dm_token}});
+  task->Execute();
+
+  std::string experiment1_value = "false";
+  std::string experiment2_value = "false";
+
+  if (experiment_fetch_status == 0) {
+    experiment1_value = "abc";
+    experiment2_value = "def";
+  }
+
+  EXPECT_EQ(experiment1_value,
+            ExperimentsManager::Get()->GetExperimentForUser(
+                base::UTF16ToUTF8(OLE2W(sid)), Experiment::TEST_CLIENT_FLAG));
+  EXPECT_EQ(experiment2_value,
+            ExperimentsManager::Get()->GetExperimentForUser(
+                base::UTF16ToUTF8(OLE2W(sid)), Experiment::TEST_CLIENT_FLAG2));
+}
+
+INSTANTIATE_TEST_SUITE_P(All,
+                         ExperimentsManagerESAE2ETest,
+                         ::testing::Values(0, 1, 2));
+
+}  // namespace testing
+}  // namespace credential_provider
diff --git a/chrome/credential_provider/gaiacp/gaia_credential_base.cc b/chrome/credential_provider/gaiacp/gaia_credential_base.cc
index 1e72b9c6..db57f206 100644
--- a/chrome/credential_provider/gaiacp/gaia_credential_base.cc
+++ b/chrome/credential_provider/gaiacp/gaia_credential_base.cc
@@ -40,6 +40,8 @@
 #include "chrome/credential_provider/gaiacp/auth_utils.h"
 #include "chrome/credential_provider/gaiacp/device_policies_manager.h"
 #include "chrome/credential_provider/gaiacp/event_logs_upload_manager.h"
+#include "chrome/credential_provider/gaiacp/experiments_fetcher.h"
+#include "chrome/credential_provider/gaiacp/experiments_manager.h"
 #include "chrome/credential_provider/gaiacp/gaia_credential_provider.h"
 #include "chrome/credential_provider/gaiacp/gaia_credential_provider_i.h"
 #include "chrome/credential_provider/gaiacp/gaia_resources.h"
@@ -810,6 +812,44 @@
   return HRESULT_FROM_WIN32(NERR_UserExists);
 }
 
+// If GCPW user policies or experiments are stale, make sure to fetch them
+// before proceeding with the login.
+void GetUserConfigsIfStale(const base::string16& sid,
+                           const base::string16& gaia_id,
+                           const base::string16& access_token) {
+  if (UserPoliciesManager::Get()->CloudPoliciesEnabled() &&
+      UserPoliciesManager::Get()->IsUserPolicyStaleOrMissing(sid)) {
+    // Save gaia id since it is needed for the cloud policies server request.
+    HRESULT hr = SetUserProperty(sid, kUserId, gaia_id);
+    if (FAILED(hr)) {
+      LOGFN(ERROR) << "SetUserProperty(id) hr=" << putHR(hr);
+    }
+
+    hr = UserPoliciesManager::Get()->FetchAndStoreCloudUserPolicies(
+        sid, base::UTF16ToUTF8(access_token));
+    if (FAILED(hr)) {
+      LOGFN(ERROR) << "Failed fetching user policies for user " << sid
+                   << " Error: " << putHR(hr);
+    }
+  }
+
+  if (ExperimentsManager::Get()->ExperimentsEnabled() &&
+      GetTimeDeltaSinceLastFetch(sid, kLastUserExperimentsRefreshTimeRegKey) >
+          kMaxTimeDeltaSinceLastExperimentsFetch) {
+    HRESULT hr = SetUserProperty(sid, kUserId, gaia_id);
+    if (FAILED(hr)) {
+      LOGFN(ERROR) << "SetUserProperty(id) hr=" << putHR(hr);
+    }
+
+    hr = ExperimentsFetcher::Get()->FetchAndStoreExperiments(
+        sid, base::UTF16ToUTF8(access_token));
+    if (FAILED(hr)) {
+      LOGFN(ERROR) << "Failed fetching experiments for user " << sid
+                   << " Error: " << putHR(hr);
+    }
+  }
+}
+
 }  // namespace
 
 CGaiaCredentialBase::UIProcessInfo::UIProcessInfo() {}
@@ -2492,27 +2532,12 @@
                                                                   : "false"));
   }
 
-  base::string16 sid = OLE2CW(user_sid_);
-  if (UserPoliciesManager::Get()->CloudPoliciesEnabled() &&
-      UserPoliciesManager::Get()->IsUserPolicyStaleOrMissing(sid)) {
-    // Save gaia id since it is needed for the cloud policies server request.
-    base::string16 gaia_id = GetDictString(*authentication_results_, kKeyId);
-    HRESULT hr = SetUserProperty(sid, kUserId, gaia_id);
-    if (FAILED(hr)) {
-      LOGFN(ERROR) << "SetUserProperty(id) hr=" << putHR(hr);
-    }
-
-    // TODO(crbug.com/976744) Use downscoped token here.
-    base::string16 access_token =
-        GetDictString(*authentication_results_, kKeyAccessToken);
-    hr = UserPoliciesManager::Get()->FetchAndStoreCloudUserPolicies(
-        sid, base::UTF16ToUTF8(access_token));
-    SecurelyClearString(access_token);
-    if (FAILED(hr)) {
-      LOGFN(ERROR) << "Failed fetching user policies for user " << sid
-                   << " Error: " << putHR(hr);
-    }
-  }
+  base::string16 gaia_id = GetDictString(*authentication_results_, kKeyId);
+  // TODO(crbug.com/976744) Use downscoped token here.
+  base::string16 access_token =
+      GetDictString(*authentication_results_, kKeyAccessToken);
+  GetUserConfigsIfStale(OLE2CW(user_sid_), gaia_id, access_token);
+  SecurelyClearString(access_token);
 
   base::string16 local_password =
       GetDictString(*authentication_results_, kKeyPassword);
diff --git a/chrome/credential_provider/gaiacp/gaia_credential_base_unittests.cc b/chrome/credential_provider/gaiacp/gaia_credential_base_unittests.cc
index 59dd0c1..beedba1d 100644
--- a/chrome/credential_provider/gaiacp/gaia_credential_base_unittests.cc
+++ b/chrome/credential_provider/gaiacp/gaia_credential_base_unittests.cc
@@ -3596,7 +3596,7 @@
   ASSERT_EQ(S_OK, FinishLogonProcess(true, true, 0));
 
   base::TimeDelta time_since_last_fetch =
-      UserPoliciesManager::Get()->GetTimeDeltaSinceLastPolicyFetch(sid);
+      GetTimeDeltaSinceLastFetch(sid, kLastUserPolicyRefreshTimeRegKey);
 
   if (cloud_policies_enabled && !policy_refreshed_recently) {
     ASSERT_EQ(1, fake_user_policies_manager.GetNumTimesFetchAndStoreCalled());
diff --git a/chrome/credential_provider/gaiacp/gcp_utils.cc b/chrome/credential_provider/gaiacp/gcp_utils.cc
index eb870b5..e90b7ce7 100644
--- a/chrome/credential_provider/gaiacp/gcp_utils.cc
+++ b/chrome/credential_provider/gaiacp/gcp_utils.cc
@@ -92,6 +92,12 @@
 constexpr int kMaxNumConsecutiveUploadDeviceFailures = 3;
 const base::TimeDelta kMaxTimeDeltaSinceLastUserPolicyRefresh =
     base::TimeDelta::FromDays(1);
+const base::TimeDelta kMaxTimeDeltaSinceLastExperimentsFetch =
+    base::TimeDelta::FromDays(1);
+
+// Path elements for the path where the experiments are stored on disk.
+const wchar_t kGcpwExperimentsDirectory[] = L"Experiments";
+const wchar_t kGcpwUserExperimentsFileName[] = L"ExperimentsFetchResponse";
 
 namespace {
 
@@ -109,6 +115,7 @@
 #define HANDLE_LANGUAGE(l_, o_) {L## #l_, o_},
         DO_LANGUAGES
 #undef HANDLE_LANGUAGE
+
 };
 
 base::FilePath GetStartupSentinelLocation(const base::string16& version) {
@@ -233,6 +240,23 @@
   return S_OK;
 }
 
+// Get the path to the directory under DIR_COMMON_APP_DATA with the given |sid|
+// and |file_dir|.
+base::FilePath GetDirectoryFilePath(const base::string16& sid,
+                                    const base::string16& file_dir) {
+  base::FilePath path;
+  if (!base::PathService::Get(base::DIR_COMMON_APP_DATA, &path)) {
+    HRESULT hr = HRESULT_FROM_WIN32(::GetLastError());
+    LOGFN(ERROR) << "PathService::Get(DIR_COMMON_APP_DATA) hr=" << putHR(hr);
+    return base::FilePath();
+  }
+  path = path.Append(GetInstallParentDirectoryName())
+             .Append(kCredentialProviderFolder)
+             .Append(file_dir)
+             .Append(sid);
+  return path;
+}
+
 }  // namespace
 
 // GoogleRegistrationDataForTesting //////////////////////////////////////////
@@ -1255,4 +1279,62 @@
   return url;
 }
 
+std::unique_ptr<base::File> GetOpenedFileForUser(
+    const base::string16& sid,
+    uint32_t open_flags,
+    const base::string16& file_dir,
+    const base::string16& file_name) {
+  base::FilePath experiments_dir = GetDirectoryFilePath(sid, file_dir);
+  if (!base::DirectoryExists(experiments_dir)) {
+    base::File::Error error;
+    if (!CreateDirectoryAndGetError(experiments_dir, &error)) {
+      LOGFN(ERROR) << "Experiments data directory could not be created for "
+                   << sid << " Error: " << error;
+      return nullptr;
+    }
+  }
+
+  base::FilePath experiments_file_path = experiments_dir.Append(file_name);
+  std::unique_ptr<base::File> experiments_file(
+      new base::File(experiments_file_path, open_flags));
+
+  if (!experiments_file->IsValid()) {
+    LOGFN(ERROR) << "Error opening experiments file for user " << sid
+                 << " with flags " << open_flags
+                 << " Error: " << experiments_file->error_details();
+    return nullptr;
+  }
+
+  base::File::Error lock_error =
+      experiments_file->Lock(base::File::LockMode::kExclusive);
+  if (lock_error != base::File::FILE_OK) {
+    LOGFN(ERROR)
+        << "Failed to obtain exclusive lock on experiments file! Error: "
+        << lock_error;
+    return nullptr;
+  }
+
+  return experiments_file;
+}
+
+base::TimeDelta GetTimeDeltaSinceLastFetch(const base::string16& sid,
+                                           const base::string16& flag) {
+  wchar_t last_fetch_millis[512];
+  ULONG last_fetch_size = base::size(last_fetch_millis);
+  HRESULT hr = GetUserProperty(sid, flag, last_fetch_millis, &last_fetch_size);
+
+  if (FAILED(hr)) {
+    return base::TimeDelta::Max();
+  }
+
+  int64_t last_fetch_millis_int64;
+  base::StringToInt64(last_fetch_millis, &last_fetch_millis_int64);
+
+  int64_t time_delta_from_last_fetch_ms =
+      base::Time::Now().ToDeltaSinceWindowsEpoch().InMilliseconds() -
+      last_fetch_millis_int64;
+
+  return base::TimeDelta::FromMilliseconds(time_delta_from_last_fetch_ms);
+}
+
 }  // namespace credential_provider
diff --git a/chrome/credential_provider/gaiacp/gcp_utils.h b/chrome/credential_provider/gaiacp/gcp_utils.h
index b9cb3a4..2a932412 100644
--- a/chrome/credential_provider/gaiacp/gcp_utils.h
+++ b/chrome/credential_provider/gaiacp/gcp_utils.h
@@ -10,6 +10,7 @@
 #include <string>
 
 #include "base/callback.h"
+#include "base/files/file.h"
 #include "base/files/file_path.h"
 #include "base/strings/string16.h"
 #include "base/values.h"
@@ -75,6 +76,14 @@
 // again.
 extern const base::TimeDelta kMaxTimeDeltaSinceLastUserPolicyRefresh;
 
+// Maximum allowed time delta after which experiments should be fetched
+// again.
+extern const base::TimeDelta kMaxTimeDeltaSinceLastExperimentsFetch;
+
+// Path elements for the path where the experiments are stored on disk.
+extern const wchar_t kGcpwExperimentsDirectory[];
+extern const wchar_t kGcpwUserExperimentsFileName[];
+
 // Because of some strange dependency problems with windows header files,
 // define STATUS_SUCCESS here instead of including ntstatus.h or SubAuth.h
 #define STATUS_SUCCESS ((NTSTATUS)0x00000000L)
@@ -404,6 +413,20 @@
 base::string16 GetDevelopmentUrl(const base::string16& url,
                                  const base::string16& dev);
 
+// Returns a handle to a file which is stored under DIR_COMMON_APP_DATA > |sid|
+// > |file_dir| > |file_name|. The file is opened with the provided
+// |open_flags|.
+std::unique_ptr<base::File> GetOpenedFileForUser(
+    const base::string16& sid,
+    uint32_t open_flags,
+    const base::string16& file_dir,
+    const base::string16& file_name);
+
+// Returns the time delta since the last fetch for the given |sid|. |flag|
+// stores the last fetch time.
+base::TimeDelta GetTimeDeltaSinceLastFetch(const base::string16& sid,
+                                           const base::string16& flag);
+
 }  // namespace credential_provider
 
 #endif  // CHROME_CREDENTIAL_PROVIDER_GAIACP_GCP_UTILS_H_
diff --git a/chrome/credential_provider/gaiacp/gcpw_version.h b/chrome/credential_provider/gaiacp/gcpw_version.h
index dbedfe3f..c960841 100644
--- a/chrome/credential_provider/gaiacp/gcpw_version.h
+++ b/chrome/credential_provider/gaiacp/gcpw_version.h
@@ -13,7 +13,7 @@
 namespace credential_provider {
 
 // A structure to hold the version of GCPW.
-class COMPONENT_EXPORT(GCPW_POLICIES) GcpwVersion {
+class COMPONENT_EXPORT(GCPW_TASKS) GcpwVersion {
  public:
   // Create a default version which is not valid.
   GcpwVersion();
diff --git a/chrome/credential_provider/gaiacp/reg_utils.cc b/chrome/credential_provider/gaiacp/reg_utils.cc
index bf14a8e..9bb0af64 100644
--- a/chrome/credential_provider/gaiacp/reg_utils.cc
+++ b/chrome/credential_provider/gaiacp/reg_utils.cc
@@ -65,6 +65,10 @@
 constexpr wchar_t kEmailDomainsKey[] = L"ed";  // deprecated.
 constexpr wchar_t kEmailDomainsKeyNew[] = L"domains_allowed_to_login";
 
+const wchar_t kLastUserPolicyRefreshTimeRegKey[] = L"last_policy_refresh_time";
+const wchar_t kLastUserExperimentsRefreshTimeRegKey[] =
+    L"last_experiments_refresh_time";
+
 namespace {
 
 constexpr wchar_t kAccountPicturesRootRegKey[] =
diff --git a/chrome/credential_provider/gaiacp/reg_utils.h b/chrome/credential_provider/gaiacp/reg_utils.h
index f9b7cea..f7f2407 100644
--- a/chrome/credential_provider/gaiacp/reg_utils.h
+++ b/chrome/credential_provider/gaiacp/reg_utils.h
@@ -83,6 +83,14 @@
 extern const wchar_t kEmailDomainsKey[];  // Older deprecated key.
 extern const wchar_t kEmailDomainsKeyNew[];
 
+// Registry key where the the last time the policy is refreshed for the user is
+// stored.
+extern const wchar_t kLastUserPolicyRefreshTimeRegKey[];
+
+// Registry key where the the last time the experiments is refreshed for the
+// user is stored.
+extern const wchar_t kLastUserExperimentsRefreshTimeRegKey[];
+
 // Gets any HKLM registry key on the system.
 HRESULT GetMachineRegDWORD(const base::string16& key_name,
                            const base::string16& name,
diff --git a/chrome/credential_provider/gaiacp/user_policies.h b/chrome/credential_provider/gaiacp/user_policies.h
index c410ed0..fee2a84 100644
--- a/chrome/credential_provider/gaiacp/user_policies.h
+++ b/chrome/credential_provider/gaiacp/user_policies.h
@@ -12,7 +12,7 @@
 namespace credential_provider {
 
 // Structure to hold the policies for each user.
-struct COMPONENT_EXPORT(GCPW_POLICIES) UserPolicies {
+struct COMPONENT_EXPORT(GCPW_TASKS) UserPolicies {
   // Controls whether MDM enrollment is enabled/disabled.
   bool enable_dm_enrollment;
 
diff --git a/chrome/credential_provider/gaiacp/user_policies_manager.cc b/chrome/credential_provider/gaiacp/user_policies_manager.cc
index c21236b..bb118771 100644
--- a/chrome/credential_provider/gaiacp/user_policies_manager.cc
+++ b/chrome/credential_provider/gaiacp/user_policies_manager.cc
@@ -41,13 +41,8 @@
     base::TimeDelta::FromMilliseconds(5000);
 
 // Path elements for the path where the policies are stored on disk.
-constexpr base::FilePath::CharType kGcpwPoliciesDirectory[] = L"Policies";
-constexpr base::FilePath::CharType kGcpwUserPolicyFileName[] =
-    L"PolicyFetchResponse";
-
-// Registry key where the the last time the policy is refreshed for the user is
-// stored.
-const wchar_t kLastUserPolicyRefreshTimeRegKey[] = L"last_policy_refresh_time";
+constexpr wchar_t kGcpwPoliciesDirectory[] = L"Policies";
+constexpr wchar_t kGcpwUserPolicyFileName[] = L"PolicyFetchResponse";
 
 // Maximum number of retries if a HTTP call to the backend fails.
 constexpr unsigned int kMaxNumHttpRetries = 1;
@@ -62,57 +57,6 @@
 // True when cloud policies feature is enabled.
 bool g_cloud_policies_enabled = false;
 
-// Get the path to the directory where the policies will be stored for the user
-// with |sid|.
-base::FilePath GetUserPolicyDirectoryFilePath(const base::string16& sid) {
-  base::FilePath path;
-  if (!base::PathService::Get(base::DIR_COMMON_APP_DATA, &path)) {
-    HRESULT hr = HRESULT_FROM_WIN32(::GetLastError());
-    LOGFN(ERROR) << "PathService::Get(DIR_COMMON_APP_DATA) hr=" << putHR(hr);
-    return base::FilePath();
-  }
-  path = path.Append(GetInstallParentDirectoryName())
-             .Append(kCredentialProviderFolder)
-             .Append(kGcpwPoliciesDirectory)
-             .Append(sid);
-  return path;
-}
-
-std::unique_ptr<base::File> GetOpenedPolicyFileForUser(
-    const base::string16& sid,
-    uint32_t open_flags) {
-  base::FilePath policy_dir = GetUserPolicyDirectoryFilePath(sid);
-  if (!base::DirectoryExists(policy_dir)) {
-    base::File::Error error;
-    if (!CreateDirectoryAndGetError(policy_dir, &error)) {
-      LOGFN(ERROR) << "Policy data directory could not be created for " << sid
-                   << " Error: " << error;
-      return nullptr;
-    }
-  }
-
-  base::FilePath policy_file_path = policy_dir.Append(kGcpwUserPolicyFileName);
-  std::unique_ptr<base::File> policy_file(
-      new base::File(policy_file_path, open_flags));
-
-  if (!policy_file->IsValid()) {
-    LOGFN(ERROR) << "Error opening policy file for user " << sid
-                 << " with flags " << open_flags
-                 << " Error: " << policy_file->error_details();
-    return nullptr;
-  }
-
-  base::File::Error lock_error =
-      policy_file->Lock(base::File::LockMode::kExclusive);
-  if (lock_error != base::File::FILE_OK) {
-    LOGFN(ERROR) << "Failed to obtain exclusive lock on policy file! Error: "
-                 << lock_error;
-    return nullptr;
-  }
-
-  return policy_file;
-}
-
 // Creates the URL used to fetch the policies from the backend based on the
 // credential present (OAuth vs DM token) for authentication.
 GURL GetFetchUserPoliciesUrl(const base::string16& sid,
@@ -294,8 +238,8 @@
   uint32_t open_flags = base::File::FLAG_CREATE_ALWAYS |
                         base::File::FLAG_WRITE |
                         base::File::FLAG_EXCLUSIVE_WRITE;
-  std::unique_ptr<base::File> policy_file =
-      GetOpenedPolicyFileForUser(sid, open_flags);
+  std::unique_ptr<base::File> policy_file = GetOpenedFileForUser(
+      sid, open_flags, kGcpwPoliciesDirectory, kGcpwUserPolicyFileName);
   if (!policy_file) {
     return (fetch_status_ = E_FAIL);
   }
@@ -322,35 +266,14 @@
   return (fetch_status_ = S_OK);
 }
 
-base::TimeDelta UserPoliciesManager::GetTimeDeltaSinceLastPolicyFetch(
-    const base::string16& sid) const {
-  wchar_t last_fetch_millis[512];
-  ULONG last_fetch_size = base::size(last_fetch_millis);
-  HRESULT hr = GetUserProperty(sid, kLastUserPolicyRefreshTimeRegKey,
-                               last_fetch_millis, &last_fetch_size);
-
-  if (FAILED(hr)) {
-    // The policy was never fetched before.
-    return base::TimeDelta::Max();
-  }
-
-  int64_t last_fetch_millis_int64;
-  base::StringToInt64(last_fetch_millis, &last_fetch_millis_int64);
-
-  int64_t time_delta_from_last_fetch_ms =
-      base::Time::Now().ToDeltaSinceWindowsEpoch().InMilliseconds() -
-      last_fetch_millis_int64;
-
-  return base::TimeDelta::FromMilliseconds(time_delta_from_last_fetch_ms);
-}
 
 bool UserPoliciesManager::GetUserPolicies(const base::string16& sid,
                                           UserPolicies* user_policies) const {
   DCHECK(user_policies);
 
   uint32_t open_flags = base::File::FLAG_OPEN | base::File::FLAG_READ;
-  std::unique_ptr<base::File> policy_file =
-      GetOpenedPolicyFileForUser(sid, open_flags);
+  std::unique_ptr<base::File> policy_file = GetOpenedFileForUser(
+      sid, open_flags, kGcpwPoliciesDirectory, kGcpwUserPolicyFileName);
   if (!policy_file) {
     return false;
   }
@@ -387,7 +310,7 @@
     return true;
   }
 
-  if (GetTimeDeltaSinceLastPolicyFetch(sid) >
+  if (GetTimeDeltaSinceLastFetch(sid, kLastUserPolicyRefreshTimeRegKey) >
       kMaxTimeDeltaSinceLastUserPolicyRefresh) {
     return true;
   }
diff --git a/chrome/credential_provider/gaiacp/user_policies_manager.h b/chrome/credential_provider/gaiacp/user_policies_manager.h
index e8d7ef3..1f5f646e 100644
--- a/chrome/credential_provider/gaiacp/user_policies_manager.h
+++ b/chrome/credential_provider/gaiacp/user_policies_manager.h
@@ -17,7 +17,7 @@
 namespace credential_provider {
 
 // Manager used to fetch user policies from GCPW backends.
-class COMPONENT_EXPORT(GCPW_POLICIES) UserPoliciesManager {
+class COMPONENT_EXPORT(GCPW_TASKS) UserPoliciesManager {
  public:
   // Get the user policies manager instance.
   static UserPoliciesManager* Get();
@@ -42,11 +42,6 @@
   virtual HRESULT FetchAndStoreCloudUserPolicies(
       const extension::UserDeviceContext& context);
 
-  // Return the elapsed time delta since the last time the policies were
-  // successfully fetched for the user with |sid|.
-  base::TimeDelta GetTimeDeltaSinceLastPolicyFetch(
-      const base::string16& sid) const;
-
   // Get the URL of GCPW service for HTTP request for fetching user policies
   // when the caller has a valid OAuth token for authentication.
   GURL GetGcpwServiceUserPoliciesUrl(const base::string16& sid);
diff --git a/chrome/credential_provider/test/BUILD.gn b/chrome/credential_provider/test/BUILD.gn
index e540efa4..0d6c630 100644
--- a/chrome/credential_provider/test/BUILD.gn
+++ b/chrome/credential_provider/test/BUILD.gn
@@ -11,6 +11,7 @@
     "../extension/task_manager_unittests.cc",
     "../gaiacp/associated_user_validator_unittests.cc",
     "../gaiacp/device_policies_manager_unittests.cc",
+    "../gaiacp/experiments_manager_unittests.cc",
     "../gaiacp/gaia_credential_base_unittests.cc",
     "../gaiacp/gaia_credential_other_user_unittests.cc",
     "../gaiacp/gaia_credential_provider_unittests.cc",
@@ -39,8 +40,8 @@
     "../gaiacp:common",
     "../gaiacp:gaia_credential_provider_idl",
     "../gaiacp:gaiacp_lib",
-    "../gaiacp:policies",
     "../gaiacp:string_resources",
+    "../gaiacp:tasks",
     "../gaiacp:util",
     "../gaiacp:version",
     "../setup:common",
diff --git a/chrome/installer/setup/setup_util.cc b/chrome/installer/setup/setup_util.cc
index 347427d..fbf3bd3 100644
--- a/chrome/installer/setup/setup_util.cc
+++ b/chrome/installer/setup/setup_util.cc
@@ -429,7 +429,7 @@
 
 bool IsProcessorSupported() {
 #if defined(ARCH_CPU_X86_FAMILY)
-  return base::CPU().has_sse2();
+  return base::CPU().has_sse3();
 #elif defined(ARCH_CPU_ARM64)
   return true;
 #else
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index fdefd67..931b6c59 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -3890,6 +3890,7 @@
       "../browser/win/jumplist_file_util_unittest.cc",
       "../browser/win/jumplist_update_util_unittest.cc",
       "../browser/win/taskbar_icon_finder_unittest.cc",
+      "../browser/win/uninstallation_via_os_settings_unittest.cc",
       "../common/chrome_constants_win_unittest.cc",
       "../common/conflicts/module_watcher_win_unittest.cc",
       "../common/conflicts/remote_module_watcher_win_unittest.cc",
diff --git a/chrome/test/data/webui/chromeos/scanning/scanning_app_test.js b/chrome/test/data/webui/chromeos/scanning/scanning_app_test.js
index e20514e0..e53b5e7 100644
--- a/chrome/test/data/webui/chromeos/scanning/scanning_app_test.js
+++ b/chrome/test/data/webui/chromeos/scanning/scanning_app_test.js
@@ -91,6 +91,7 @@
     this.resolverMap_.set('getScanners', new PromiseResolver());
     this.resolverMap_.set('getScannerCapabilities', new PromiseResolver());
     this.resolverMap_.set('startScan', new PromiseResolver());
+    this.resolverMap_.set('cancelScan', new PromiseResolver());
   }
 
   /**
@@ -206,6 +207,10 @@
       resolve({success: true});
     });
   }
+
+  cancelScan() {
+    this.methodCalled('cancelScan');
+  }
 }
 
 export function scanningAppTest() {
@@ -309,6 +314,8 @@
     let resolutionSelect;
     /** @type {!CrButtonElement} */
     let scanButton;
+    /** @type {!CrButtonElement} */
+    let cancelButton;
     /** @type {!HTMLElement} */
     let statusText;
     /** @type {!HTMLElement} */
@@ -330,6 +337,8 @@
           resolutionSelect = scanningApp.$$('#resolutionSelect').$$('select');
           scanButton =
               /** @type {!CrButtonElement} */ (scanningApp.$$('#scanButton'));
+          cancelButton =
+              /** @type {!CrButtonElement} */ (scanningApp.$$('#cancelButton'));
           statusText =
               /** @type {!HTMLElement} */ (scanningApp.$$('#statusText'));
           helperText = scanningApp.$$('#scanPreview').$$('#helperText');
@@ -365,6 +374,8 @@
           assertFalse(pageSizeSelect.disabled);
           assertFalse(resolutionSelect.disabled);
           assertFalse(scanButton.disabled);
+          assertTrue(isVisible(scanButton));
+          assertFalse(isVisible(cancelButton));
           assertEquals('', statusText.textContent.trim());
           assertTrue(isVisible(helperText));
           assertFalse(isVisible(scanProgress));
@@ -387,6 +398,8 @@
           assertTrue(pageSizeSelect.disabled);
           assertTrue(resolutionSelect.disabled);
           assertTrue(scanButton.disabled);
+          assertFalse(isVisible(scanButton));
+          assertTrue(isVisible(cancelButton));
           assertFalse(isVisible(helperText));
           assertTrue(isVisible(scanProgress));
           assertFalse(isVisible(
@@ -445,6 +458,8 @@
           assertFalse(pageSizeSelect.disabled);
           assertFalse(resolutionSelect.disabled);
           assertFalse(scanButton.disabled);
+          assertTrue(isVisible(scanButton));
+          assertFalse(isVisible(cancelButton));
           assertTrue(isVisible(helperText));
           assertFalse(isVisible(scanProgress));
           assertFalse(isVisible(
@@ -452,6 +467,65 @@
         });
   });
 
+  test('CancelScan', () => {
+    const expectedScanners = [
+      createScanner(firstScannerId, firstScannerName),
+      createScanner(secondScannerId, secondScannerName)
+    ];
+
+    let capabilities = new Map();
+    capabilities.set(firstScannerId, firstCapabilities);
+    capabilities.set(secondScannerId, secondCapabilities);
+
+    /** @type {!CrButtonElement} */
+    let scanButton;
+    /** @type {!CrButtonElement} */
+    let cancelButton;
+
+    return initializeScanningApp(expectedScanners, capabilities)
+        .then(() => {
+          scanButton =
+              /** @type {!CrButtonElement} */ (scanningApp.$$('#scanButton'));
+          cancelButton =
+              /** @type {!CrButtonElement} */ (scanningApp.$$('#cancelButton'));
+          return fakeScanService_.whenCalled('getScannerCapabilities');
+        })
+        .then(() => {
+          // Before the scan button is clicked, the scan button should be
+          // visible and enabled, and the cancel button shouldn't be visible.
+          assertFalse(scanButton.disabled);
+          assertTrue(isVisible(scanButton));
+          assertFalse(isVisible(cancelButton));
+
+          // Click the Scan button and wait till the scan is started.
+          scanButton.click();
+          return fakeScanService_.whenCalled('startScan');
+        })
+        .then(() => {
+          // After the scan button is clicked and the scan has started, the scan
+          // button should be disabled and not visible, and the cancel button
+          // should be visible.
+          assertTrue(scanButton.disabled);
+          assertFalse(isVisible(scanButton));
+          assertTrue(isVisible(cancelButton));
+
+          // Simulate a progress update and verify the progress bar and text are
+          // updated correctly.
+          return fakeScanService_.simulateProgress(1, 17);
+        })
+        .then(() => {
+          // Click the cancel button to cancel the scan.
+          cancelButton.click();
+          return fakeScanService_.cancelScan();
+        })
+        .then(() => {
+          // After canceling is complete, the scan button should be visible and
+          // enabled, and the cancel button shouldn't be visible.
+          assertTrue(isVisible(scanButton));
+          assertFalse(isVisible(cancelButton));
+        });
+  });
+
   test('PanelContainerContent', () => {
     const scanners = [];
     const capabilities = new Map();
diff --git a/chrome/updater/constants.h b/chrome/updater/constants.h
index 6371f86..e2b81fb 100644
--- a/chrome/updater/constants.h
+++ b/chrome/updater/constants.h
@@ -204,6 +204,9 @@
 constexpr bool kInstallPolicyDefault = kPolicyEnabled;
 constexpr bool kUpdatePolicyDefault = kPolicyEnabled;
 
+constexpr int kUninstallPingReasonUninstalled = 0;
+constexpr int kUninstallPingReasonUserNotAnOwner = 1;
+
 }  // namespace updater
 
 #endif  // CHROME_UPDATER_CONSTANTS_H_
diff --git a/chrome/updater/control_service_impl.cc b/chrome/updater/control_service_impl.cc
index 11aaeba..c87a5e9 100644
--- a/chrome/updater/control_service_impl.cc
+++ b/chrome/updater/control_service_impl.cc
@@ -13,6 +13,8 @@
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
 #include "base/logging.h"
+#include "base/task/post_task.h"
+#include "base/task/thread_pool.h"
 #include "base/threading/sequenced_task_runner_handle.h"
 #include "base/time/time.h"
 #include "chrome/updater/configurator.h"
@@ -20,7 +22,9 @@
 #include "chrome/updater/persisted_data.h"
 #include "chrome/updater/prefs.h"
 #include "chrome/updater/update_service_impl.h"
+#include "chrome/updater/util.h"
 #include "components/prefs/pref_service.h"
+#include "components/update_client/update_client.h"
 
 namespace updater {
 
@@ -28,24 +32,49 @@
     scoped_refptr<updater::Configurator> config)
     : config_(config),
       persisted_data_(
-          base::MakeRefCounted<PersistedData>(config_->GetPrefService())) {}
+          base::MakeRefCounted<PersistedData>(config_->GetPrefService())),
+      update_client_(update_client::UpdateClientFactory(config_)),
+      number_of_pings_remaining_(0) {}
 
 void ControlServiceImpl::Run(base::OnceClosure callback) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-
-  UnregisterMissingApps();
-  MaybeCheckForUpdates(std::move(callback));
+  callback_ = std::move(callback);
+  UnregisterMissingApps(GetRegisteredApps());
 }
 
 void ControlServiceImpl::InitializeUpdateService(base::OnceClosure callback) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  VLOG(1) << __func__;
   std::move(callback).Run();
 }
 
-void ControlServiceImpl::MaybeCheckForUpdates(base::OnceClosure callback) {
+std::vector<ControlServiceImpl::AppInfo>
+ControlServiceImpl::GetRegisteredApps() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
+  std::vector<AppInfo> apps_to_unregister;
+  for (const std::string& app_id : persisted_data_->GetAppIds()) {
+    if (app_id == kUpdaterAppId)
+      continue;
+
+    const base::FilePath ecp = persisted_data_->GetExistenceCheckerPath(app_id);
+    if (!ecp.empty()) {
+      apps_to_unregister.push_back(
+          AppInfo(app_id, persisted_data_->GetProductVersion(app_id), ecp));
+    }
+  }
+  return apps_to_unregister;
+}
+
+bool ControlServiceImpl::WaitingOnUninstallPings() const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  return number_of_pings_remaining_ > 0;
+}
+
+void ControlServiceImpl::MaybeCheckForUpdates() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  scoped_refptr<UpdateServiceImpl> update_service =
+      base::MakeRefCounted<UpdateServiceImpl>(config_);
+
   const base::Time lastUpdateTime =
       config_->GetPrefService()->GetTime(kPrefUpdateTime);
 
@@ -57,13 +86,10 @@
     VLOG(0) << "Skipping checking for updates:  "
             << timeSinceUpdate.InMinutes();
     base::SequencedTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                                     std::move(callback));
+                                                     std::move(callback_));
     return;
   }
 
-  scoped_refptr<UpdateServiceImpl> update_service =
-      base::MakeRefCounted<UpdateServiceImpl>(config_);
-
   update_service->UpdateAll(
       base::DoNothing(),
       base::BindOnce(
@@ -78,22 +104,81 @@
             }
             std::move(closure).Run();
           },
-          base::BindOnce(std::move(callback)), config_));
+          base::BindOnce(std::move(callback_)), config_));
 }
 
-void ControlServiceImpl::UnregisterMissingApps() {
+void ControlServiceImpl::UnregisterMissingApps(std::vector<AppInfo> apps) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  for (const auto& app_id : persisted_data_->GetAppIds()) {
+
+  base::ThreadPool::PostTaskAndReplyWithResult(
+      FROM_HERE, {base::MayBlock()},
+      base::BindOnce(&ControlServiceImpl::GetAppIDsToRemove, this, apps),
+      base::BindOnce(&ControlServiceImpl::RemoveAppIDsAndSendUninstallPings,
+                     this));
+}
+
+void ControlServiceImpl::UnregisterMissingAppsDone() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  MaybeCheckForUpdates();
+}
+
+std::vector<ControlServiceImpl::PingInfo> ControlServiceImpl::GetAppIDsToRemove(
+    std::vector<AppInfo> apps) {
+  std::vector<PingInfo> app_ids_to_remove;
+  for (const auto& app : apps) {
     // Skip if app_id is equal to updater app id.
-    if (app_id == kUpdaterAppId)
+    if (app.app_id_ == kUpdaterAppId)
       continue;
 
-    const base::FilePath ecp = persisted_data_->GetExistenceCheckerPath(app_id);
-    if (!ecp.empty() && !base::PathExists(ecp)) {
-      if (!persisted_data_->RemoveApp(app_id))
-        VLOG(0) << "Could not remove registration of app " << app_id;
+    if (!base::PathExists(app.ecp_)) {
+      app_ids_to_remove.push_back(PingInfo(app.app_id_, app.app_version_,
+                                           kUninstallPingReasonUninstalled));
+    } else if (!PathOwnedByUser(app.ecp_)) {
+      app_ids_to_remove.push_back(PingInfo(app.app_id_, app.app_version_,
+                                           kUninstallPingReasonUserNotAnOwner));
     }
   }
+
+  return app_ids_to_remove;
+}
+
+void ControlServiceImpl::RemoveAppIDsAndSendUninstallPings(
+    std::vector<PingInfo> app_ids_to_remove) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  if (app_ids_to_remove.empty()) {
+    UnregisterMissingAppsDone();
+    return;
+  }
+
+  for (const auto& app_id_to_remove : app_ids_to_remove) {
+    const auto app_id = app_id_to_remove.app_id_;
+    const int ping_reason = app_id_to_remove.ping_reason_;
+    const base::Version app_version = app_id_to_remove.app_version_;
+
+    if (persisted_data_->RemoveApp(app_id)) {
+      VLOG(1) << "Uninstall ping for app id: " << app_id
+              << ". Ping reason: " << ping_reason;
+      number_of_pings_remaining_++;
+      update_client_->SendUninstallPing(
+          app_id, app_version, ping_reason,
+          base::BindOnce(&ControlServiceImpl::UninstallPingSent, this));
+    } else {
+      VLOG(0) << "Could not remove registration of app " << app_id;
+    }
+  }
+}
+
+void ControlServiceImpl::UninstallPingSent(update_client::Error error) {
+  number_of_pings_remaining_--;
+
+  if (error != update_client::Error::NONE)
+    VLOG(0) << __func__ << ": Error: " << static_cast<int>(error);
+
+  if (!WaitingOnUninstallPings())
+    std::move(
+        base::BindOnce(&ControlServiceImpl::UnregisterMissingAppsDone, this))
+        .Run();
 }
 
 void ControlServiceImpl::Uninitialize() {
diff --git a/chrome/updater/control_service_impl.h b/chrome/updater/control_service_impl.h
index 8dd3fb9..a4d263e 100644
--- a/chrome/updater/control_service_impl.h
+++ b/chrome/updater/control_service_impl.h
@@ -5,11 +5,22 @@
 #ifndef CHROME_UPDATER_CONTROL_SERVICE_IMPL_H_
 #define CHROME_UPDATER_CONTROL_SERVICE_IMPL_H_
 
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
 #include "base/callback_forward.h"
+#include "base/files/file_path.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/sequence_checker.h"
+#include "base/version.h"
 #include "chrome/updater/control_service.h"
 
+namespace update_client {
+enum class Error;
+class UpdateClient;
+}  // namespace update_client
+
 namespace updater {
 
 class Configurator;
@@ -31,16 +42,63 @@
 
   SEQUENCE_CHECKER(sequence_checker_);
 
+  struct AppInfo {
+    AppInfo(const std::string& app_id,
+            const base::Version& app_version,
+            const base::FilePath& ecp)
+        : app_id_(app_id), app_version_(app_version), ecp_(ecp) {}
+    std::string app_id_;
+    base::Version app_version_;
+    base::FilePath ecp_;
+  };
+
+  struct PingInfo {
+    PingInfo(const std::string& app_id,
+             const base::Version& app_version,
+             int ping_reason)
+        : app_id_(app_id),
+          app_version_(app_version),
+          ping_reason_(ping_reason) {}
+    std::string app_id_;
+    base::Version app_version_;
+    int ping_reason_;
+  };
+
   // Checks for updates of all registered applications if it has been longer
   // than the last check time by NextCheckDelay() amount defined in the config.
-  void MaybeCheckForUpdates(base::OnceClosure callback);
+  void MaybeCheckForUpdates();
+
+  // Returns a list of apps registered with the updater.
+  std::vector<AppInfo> GetRegisteredApps();
+
+  void UnregisterMissingAppsDone();
 
   // Provides a way to remove apps from the persisted data if the app is no
   // longer installed on the machine.
-  void UnregisterMissingApps();
+  void UnregisterMissingApps(std::vector<AppInfo> apps);
+
+  // After an uninstall ping has been processed, reduces the number of pings
+  // that we need to wait on before checking for updates.
+  void UninstallPingSent(update_client::Error error);
+
+  // Returns true if there are uninstall ping tasks which haven't finished.
+  // Returns false if |number_of_pings_remaining_| is 0.
+  bool WaitingOnUninstallPings() const;
+
+  // Returns a list of apps that need to be unregistered.
+  std::vector<ControlServiceImpl::PingInfo> GetAppIDsToRemove(
+      std::vector<AppInfo> apps);
+
+  // Unregisters the apps in |app_ids_to_remove| and starts an update check
+  // if necessary.
+  void RemoveAppIDsAndSendUninstallPings(
+      std::vector<PingInfo> app_ids_to_remove);
 
   scoped_refptr<updater::Configurator> config_;
   scoped_refptr<updater::PersistedData> persisted_data_;
+  scoped_refptr<update_client::UpdateClient> update_client_;
+  base::OnceClosure callback_;
+  int number_of_pings_remaining_;
 };
 
 }  // namespace updater
diff --git a/chrome/updater/mac/BUILD.gn b/chrome/updater/mac/BUILD.gn
index cd4176c..4369361 100644
--- a/chrome/updater/mac/BUILD.gn
+++ b/chrome/updater/mac/BUILD.gn
@@ -87,6 +87,7 @@
 
   deps = [
     "//base",
+    "//chrome/updater:base",
     "//chrome/updater:version_header",
   ]
 
diff --git a/chrome/updater/mac/util.mm b/chrome/updater/mac/util.mm
index 6e0c5325..e7224a3b 100644
--- a/chrome/updater/mac/util.mm
+++ b/chrome/updater/mac/util.mm
@@ -4,13 +4,16 @@
 
 #import "chrome/updater/mac/util.h"
 
+#include <pwd.h>
 #include <unistd.h>
 
+#include "base/files/file.h"
 #include "base/files/file_path.h"
 #include "base/logging.h"
 #include "base/mac/foundation_util.h"
 #include "base/version.h"
 #include "chrome/updater/updater_version.h"
+#include "chrome/updater/util.h"
 
 namespace updater {
 namespace {
@@ -68,4 +71,41 @@
       .Append(FILE_PATH_LITERAL(PRODUCT_FULLNAME_STRING));
 }
 
+bool PathOwnedByUser(const base::FilePath& path) {
+  struct passwd* result = nullptr;
+  struct passwd user_info = {};
+  char pwbuf[2048] = {};
+  uid_t user_uid = geteuid();
+
+  int error = getpwuid_r(user_uid, &user_info, pwbuf, sizeof(pwbuf), &result);
+
+  if (error) {
+    VLOG(1) << "Failed to get user info.";
+    return true;
+  }
+
+  if (result == nullptr) {
+    VLOG(1) << "No entry for user.";
+    return true;
+  }
+
+  base::stat_wrapper_t stat_info = {};
+  if (base::File::Lstat(path.value().c_str(), &stat_info) != 0) {
+    DPLOG(ERROR) << "Failed to get information on path " << path.value();
+    return false;
+  }
+
+  if (S_ISLNK(stat_info.st_mode)) {
+    DLOG(ERROR) << "Path " << path.value() << " is a symbolic link.";
+    return false;
+  }
+
+  if (stat_info.st_uid != user_uid) {
+    DLOG(ERROR) << "Path " << path.value() << " is owned by the wrong user.";
+    return false;
+  }
+
+  return true;
+}
+
 }  // namespace updater
diff --git a/chrome/updater/persisted_data.cc b/chrome/updater/persisted_data.cc
index 5d5e375..1517d49 100644
--- a/chrome/updater/persisted_data.cc
+++ b/chrome/updater/persisted_data.cc
@@ -67,7 +67,14 @@
 base::FilePath PersistedData::GetExistenceCheckerPath(
     const std::string& id) const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  return base::FilePath().AppendASCII(GetString(id, kECP));
+#if defined(OS_WIN)
+  base::FilePath::StringType ecp;
+  const std::string str = GetString(id, kECP);
+  return base::UTF8ToWide(str.c_str(), str.size(), &ecp) ? base::FilePath(ecp)
+                                                         : base::FilePath();
+#else
+  return base::FilePath(GetString(id, kECP));
+#endif  // OS_WIN
 }
 
 void PersistedData::SetExistenceCheckerPath(const std::string& id,
diff --git a/chrome/updater/test/integration_tests.cc b/chrome/updater/test/integration_tests.cc
index 7e920b3..26f75e1 100644
--- a/chrome/updater/test/integration_tests.cc
+++ b/chrome/updater/test/integration_tests.cc
@@ -18,6 +18,7 @@
 #include "chrome/updater/constants.h"
 #include "chrome/updater/persisted_data.h"
 #include "chrome/updater/prefs.h"
+#include "chrome/updater/registration_data.h"
 #include "chrome/updater/test/test_app/constants.h"
 #include "chrome/updater/test/test_app/test_app_version.h"
 #include "chrome/updater/updater_version.h"
@@ -181,6 +182,75 @@
   ExpectActive();
   Uninstall();
 }
+
+TEST_F(IntegrationTest, UnregisterUninstalledApp) {
+  RegisterTestApp();
+  ExpectInstalled();
+  ExpectActiveVersion(UPDATER_VERSION_STRING);
+  ExpectActive();
+
+  {
+    std::unique_ptr<GlobalPrefs> global_prefs = CreateGlobalPrefs();
+    auto persisted_data =
+        base::MakeRefCounted<PersistedData>(global_prefs->GetPrefService());
+    base::FilePath fake_ecp =
+        persisted_data->GetExistenceCheckerPath(kTestAppId)
+            .Append(FILE_PATH_LITERAL("NOT_THERE"));
+    persisted_data->SetExistenceCheckerPath(kTestAppId, fake_ecp);
+
+    PrefsCommitPendingWrites(global_prefs->GetPrefService());
+
+    EXPECT_EQ(fake_ecp.value(),
+              persisted_data->GetExistenceCheckerPath(kTestAppId).value());
+  }
+
+  RunWake(0);
+
+  {
+    std::unique_ptr<GlobalPrefs> global_prefs = CreateGlobalPrefs();
+    auto persisted_data =
+        base::MakeRefCounted<PersistedData>(global_prefs->GetPrefService());
+    EXPECT_EQ(base::FilePath(FILE_PATH_LITERAL("")).value(),
+              persisted_data->GetExistenceCheckerPath(kTestAppId).value());
+  }
+
+  Uninstall();
+  Clean();
+}
+
+TEST_F(IntegrationTest, UnregisterUnownedApp) {
+  RegisterTestApp();
+  ExpectInstalled();
+  ExpectActiveVersion(UPDATER_VERSION_STRING);
+  ExpectActive();
+
+  {
+    std::unique_ptr<GlobalPrefs> global_prefs = CreateGlobalPrefs();
+    auto persisted_data =
+        base::MakeRefCounted<PersistedData>(global_prefs->GetPrefService());
+    base::FilePath fake_ecp{FILE_PATH_LITERAL("/Library")};
+    persisted_data->SetExistenceCheckerPath(kTestAppId, fake_ecp);
+
+    PrefsCommitPendingWrites(global_prefs->GetPrefService());
+
+    EXPECT_EQ(fake_ecp.value(),
+              persisted_data->GetExistenceCheckerPath(kTestAppId).value());
+  }
+
+  RunWake(0);
+
+  {
+    std::unique_ptr<GlobalPrefs> global_prefs = CreateGlobalPrefs();
+    auto persisted_data =
+        base::MakeRefCounted<PersistedData>(global_prefs->GetPrefService());
+    EXPECT_EQ(base::FilePath(FILE_PATH_LITERAL("")).value(),
+              persisted_data->GetExistenceCheckerPath(kTestAppId).value());
+  }
+
+  Uninstall();
+  Clean();
+}
+
 #endif  // OS_MAC
 
 #if defined(OS_WIN)
diff --git a/chrome/updater/util.h b/chrome/updater/util.h
index 72564c5..67ce73c 100644
--- a/chrome/updater/util.h
+++ b/chrome/updater/util.h
@@ -37,6 +37,9 @@
 // %localappdata%\Chromium\ChromiumUpdater\1.2.3.4 for a User install.
 bool GetVersionedDirectory(base::FilePath* path);
 
+// Returns true if the user running the updater also owns the |path|.
+bool PathOwnedByUser(const base::FilePath& path);
+
 // Initializes logging for an executable.
 void InitLogging(const base::FilePath::StringType& filename);
 
diff --git a/chrome/updater/win/util.cc b/chrome/updater/win/util.cc
index d507832..a95d4805 100644
--- a/chrome/updater/win/util.cc
+++ b/chrome/updater/win/util.cc
@@ -387,4 +387,10 @@
   return token_handle;
 }
 
+bool PathOwnedByUser(const base::FilePath& path) {
+  // TODO(crbug.com/1147094): Implement for Win.
+
+  return true;
+}
+
 }  // namespace updater
diff --git a/chromecast/media/cma/backend/desktop/mixer_output_stream_desktop.cc b/chromecast/media/cma/backend/desktop/mixer_output_stream_desktop.cc
index c0318b00..998ed2d 100644
--- a/chromecast/media/cma/backend/desktop/mixer_output_stream_desktop.cc
+++ b/chromecast/media/cma/backend/desktop/mixer_output_stream_desktop.cc
@@ -14,7 +14,7 @@
 namespace media {
 
 constexpr base::TimeDelta kTargetWritePeriod =
-    base::TimeDelta::FromMilliseconds(10);
+    base::TimeDelta::FromMilliseconds(20);
 
 bool MixerOutputStreamDesktop::Start(int requested_sample_rate, int channels) {
   sample_rate_ = requested_sample_rate;
diff --git a/chromeos/chromeos_strings.grd b/chromeos/chromeos_strings.grd
index 6e38157..e06a96e7 100644
--- a/chromeos/chromeos_strings.grd
+++ b/chromeos/chromeos_strings.grd
@@ -570,6 +570,9 @@
       <message name="IDS_SCANNING_APP_DONE_BUTTON_TEXT" desc="The text displayed for the button the user clicks to return to the intial Scanning App page after a scan job is completed.">
         Done
       </message>
+      <message name="IDS_SCANNING_APP_CANCEL_BUTTON_TEXT" desc="The text displayed for the button to cancel an ongoing scan job.">
+        Cancel
+      </message>
 
       <!-- Diagnostics App -->
       <!-- TODO(michaelcheco): Update with finalized copies of the strings -->
diff --git a/chromeos/chromeos_strings_grd/IDS_SCANNING_APP_CANCEL_BUTTON_TEXT.png.sha1 b/chromeos/chromeos_strings_grd/IDS_SCANNING_APP_CANCEL_BUTTON_TEXT.png.sha1
new file mode 100644
index 0000000..3e2817c
--- /dev/null
+++ b/chromeos/chromeos_strings_grd/IDS_SCANNING_APP_CANCEL_BUTTON_TEXT.png.sha1
@@ -0,0 +1 @@
+26cd0a3d5ec5481f7cdd51682119a76dd5402c55
\ No newline at end of file
diff --git a/chromeos/components/phonehub/connection_manager_impl.cc b/chromeos/components/phonehub/connection_manager_impl.cc
index 5dd5c82..f157fba 100644
--- a/chromeos/components/phonehub/connection_manager_impl.cc
+++ b/chromeos/components/phonehub/connection_manager_impl.cc
@@ -199,7 +199,7 @@
 }
 
 void ConnectionManagerImpl::OnConnectionTimeout() {
-  PA_LOG(WARNING) << "AttemptionConnection() has timed out. Closing connection "
+  PA_LOG(WARNING) << "AttemptConnection() has timed out. Closing connection "
                   << "attempt.";
 
   connection_attempt_.reset();
diff --git a/chromeos/components/scanning/mojom/scanning.mojom b/chromeos/components/scanning/mojom/scanning.mojom
index eac7d5e..153b591 100644
--- a/chromeos/components/scanning/mojom/scanning.mojom
+++ b/chromeos/components/scanning/mojom/scanning.mojom
@@ -105,6 +105,10 @@
   // Called when the scan is complete. |success| indicates whether the scan
   // completed successfully.
   OnScanComplete(bool success);
+
+  // Called when canceling the current scan job is complete. |success|
+  // indicates whether the scan job was cancelled successfully.
+  OnCancelComplete(bool success);
 };
 
 // Interface used to obtain information about and interact with connected
@@ -127,4 +131,8 @@
   // |observer|. |success| indicates whether the scan started successfully.
   StartScan(mojo_base.mojom.UnguessableToken scanner_id, ScanSettings settings,
       pending_remote<ScanJobObserver> observer) => (bool success);
+
+  // Attempts to cancel the currently running scan job. The success of the
+  // cancel attempt is reported through the ScanJobObserver.
+  CancelScan();
 };
diff --git a/chromeos/components/scanning/resources/scanning_app.html b/chromeos/components/scanning/resources/scanning_app.html
index 0f6b851..e756044 100644
--- a/chromeos/components/scanning/resources/scanning_app.html
+++ b/chromeos/components/scanning/resources/scanning_app.html
@@ -143,9 +143,14 @@
         </iron-collapse>
         <div class="scan-button-container">
           <cr-button id="scanButton" class="action-button" on-click="onScanClick_"
-              disabled$="[[areSettingsDisabled_(appState_)]]">
+              disabled$="[[areSettingsDisabled_(appState_)]]"
+              hidden$="[[shouldShowCancelButton_(appState_)]]">
             [[i18n('scanButtonText')]]
           </cr-button>
+          <cr-button id="cancelButton" on-click="onCancelClick_"
+              hidden$="[[!shouldShowCancelButton_(appState_)]]">
+            [[i18n('cancelButtonText')]]
+          </cr-button>
         </div>
         <p id="statusText">[[statusText_]]</p>
       </template>
diff --git a/chromeos/components/scanning/resources/scanning_app.js b/chromeos/components/scanning/resources/scanning_app.js
index e71f6b2..e930019 100644
--- a/chromeos/components/scanning/resources/scanning_app.js
+++ b/chromeos/components/scanning/resources/scanning_app.js
@@ -386,6 +386,28 @@
     return this.i18n(fileSavedText);
   },
 
+  /** @private */
+  onCancelClick_() {
+    assert(this.appState_ === AppState.SCANNING);
+
+    this.scanService_.cancelScan();
+    this.setAppState_(AppState.READY);
+  },
+
+  /**
+   * Overrides chromeos.scanning.mojom.ScanJobObserverInterface.
+   * @param {boolean} success
+   */
+  onCancelComplete(success) {},
+
+  /**
+   * @return {boolean}
+   * @private
+   */
+  shouldShowCancelButton_() {
+    return this.appState_ === AppState.SCANNING;
+  },
+
   /**
    * Sets the app state if the state transition is allowed.
    * @param {!AppState} newState
diff --git a/chromeos/components/scanning/scanning_ui.cc b/chromeos/components/scanning/scanning_ui.cc
index c10dad1..2d50326 100644
--- a/chromeos/components/scanning/scanning_ui.cc
+++ b/chromeos/components/scanning/scanning_ui.cc
@@ -55,6 +55,7 @@
       {"a4OptionText", IDS_SCANNING_APP_A4_OPTION_TEXT},
       {"appTitle", IDS_SCANNING_APP_TITLE},
       {"blackAndWhiteOptionText", IDS_SCANNING_APP_BLACK_AND_WHITE_OPTION_TEXT},
+      {"cancelButtonText", IDS_SCANNING_APP_CANCEL_BUTTON_TEXT},
       {"colorModeDropdownLabel", IDS_SCANNING_APP_COLOR_MODE_DROPDOWN_LABEL},
       {"colorOptionText", IDS_SCANNING_APP_COLOR_OPTION_TEXT},
       {"defaultSourceOptionText", IDS_SCANNING_APP_DEFAULT_SOURCE_OPTION_TEXT},
diff --git a/chromeos/constants/chromeos_features.cc b/chromeos/constants/chromeos_features.cc
index 895cd68..93f7ad6c 100644
--- a/chromeos/constants/chromeos_features.cc
+++ b/chromeos/constants/chromeos_features.cc
@@ -145,6 +145,10 @@
 const base::Feature kBluetoothPhoneFilter{"BluetoothPhoneFilter",
                                           base::FEATURE_ENABLED_BY_DEFAULT};
 
+// Enable or disables running the Camera App as a System Web App.
+const base::Feature kCameraSystemWebApp{"CameraSystemWebApp",
+                                        base::FEATURE_DISABLED_BY_DEFAULT};
+
 // If enabled, will use the CDM in the Chrome OS daemon rather than loading the
 // CDM using the library CDM interface.
 const base::Feature kCdmFactoryDaemon{"CdmFactoryDaemon",
diff --git a/chromeos/constants/chromeos_features.h b/chromeos/constants/chromeos_features.h
index 5bb46456..4e47fe7 100644
--- a/chromeos/constants/chromeos_features.h
+++ b/chromeos/constants/chromeos_features.h
@@ -80,6 +80,8 @@
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
 extern const base::Feature kBluetoothPhoneFilter;
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
+extern const base::Feature kCameraSystemWebApp;
+COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
 extern const base::Feature kCdmFactoryDaemon;
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
 extern const base::Feature kChildSpecificSignin;
diff --git a/chromeos/services/ime/decoder/decoder_engine.cc b/chromeos/services/ime/decoder/decoder_engine.cc
index c81bc8c..c27b1e3 100644
--- a/chromeos/services/ime/decoder/decoder_engine.cc
+++ b/chromeos/services/ime/decoder/decoder_engine.cc
@@ -8,7 +8,6 @@
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
 #include "chromeos/services/ime/constants.h"
-#include "chromeos/services/ime/ime_decoder.h"
 #include "chromeos/services/ime/public/cpp/buildflags.h"
 
 namespace chromeos {
@@ -66,12 +65,11 @@
 DecoderEngine::~DecoderEngine() {}
 
 bool DecoderEngine::TryLoadDecoder() {
-  if (engine_main_entry_)
-    return true;
-
   auto* decoder = ImeDecoder::GetInstance();
-  if (decoder->GetStatus() == ImeDecoder::Status::kSuccess) {
-    engine_main_entry_ = decoder->CreateMainEntry(platform_);
+  if (decoder->GetStatus() == ImeDecoder::Status::kSuccess &&
+      decoder->GetEntryPoints().isReady) {
+    decoder_entry_points_ = decoder->GetEntryPoints();
+    decoder_entry_points_->initOnce(platform_);
     return true;
   }
   return false;
@@ -86,7 +84,7 @@
     // Activates an IME engine via the shared library. Passing a
     // |ClientDelegate| for engine instance created by the shared library to
     // make safe calls on the client.
-    if (engine_main_entry_->ActivateIme(
+    if (decoder_entry_points_->activateIme(
             ime_spec.c_str(),
             new ClientDelegate(ime_spec, std::move(remote)))) {
       decoder_channel_receivers_.Add(this, std::move(receiver));
@@ -102,8 +100,10 @@
 }
 
 bool DecoderEngine::IsImeSupportedByDecoder(const std::string& ime_spec) {
-  return engine_main_entry_ &&
-         engine_main_entry_->IsImeSupported(ime_spec.c_str());
+  if (decoder_entry_points_) {
+    return decoder_entry_points_->support(ime_spec.c_str());
+  }
+  return false;
 }
 
 void DecoderEngine::ProcessMessage(const std::vector<uint8_t>& message,
@@ -112,8 +112,9 @@
   std::vector<uint8_t> result;
 
   // Handle message via corresponding functions of loaded decoder.
-  if (engine_main_entry_)
-    engine_main_entry_->Process(message.data(), message.size());
+  if (decoder_entry_points_) {
+    decoder_entry_points_->process(message.data(), message.size());
+  }
 
   std::move(callback).Run(result);
 }
diff --git a/chromeos/services/ime/decoder/decoder_engine.h b/chromeos/services/ime/decoder/decoder_engine.h
index 1a85603..9d857a6 100644
--- a/chromeos/services/ime/decoder/decoder_engine.h
+++ b/chromeos/services/ime/decoder/decoder_engine.h
@@ -5,7 +5,9 @@
 #ifndef CHROMEOS_SERVICES_IME_DECODER_DECODER_ENGINE_H_
 #define CHROMEOS_SERVICES_IME_DECODER_DECODER_ENGINE_H_
 
+#include "base/optional.h"
 #include "base/scoped_native_library.h"
+#include "chromeos/services/ime/ime_decoder.h"
 #include "chromeos/services/ime/input_engine.h"
 #include "chromeos/services/ime/public/cpp/shared_lib/interfaces.h"
 #include "chromeos/services/ime/public/mojom/input_engine.mojom.h"
@@ -48,13 +50,10 @@
   // Returns whether the decoder shared library supports this ime_spec.
   bool IsImeSupportedByDecoder(const std::string& ime_spec);
 
-  // Shared library handle of the implementation for input logic with decoders.
-  base::ScopedNativeLibrary library_;
-
-  ImeEngineMainEntry* engine_main_entry_ = nullptr;
-
   ImeCrosPlatform* platform_ = nullptr;
 
+  base::Optional<ImeDecoder::EntryPoints> decoder_entry_points_;
+
   mojo::ReceiverSet<mojom::InputChannel> decoder_channel_receivers_;
 
   DISALLOW_COPY_AND_ASSIGN(DecoderEngine);
diff --git a/chromeos/services/ime/decoder/system_engine.h b/chromeos/services/ime/decoder/system_engine.h
index 29929a2e..f5be8fa2 100644
--- a/chromeos/services/ime/decoder/system_engine.h
+++ b/chromeos/services/ime/decoder/system_engine.h
@@ -57,9 +57,6 @@
   void OnReply(const std::vector<uint8_t>& message,
                mojo::Remote<mojom::InputChannel>& remote);
 
-  // Shared library handle of the implementation for input logic with decoders.
-  base::ScopedNativeLibrary library_;
-
   ImeEngineMainEntry* engine_main_entry_ = nullptr;
 
   ImeCrosPlatform* platform_ = nullptr;
diff --git a/chromeos/services/ime/ime_decoder.cc b/chromeos/services/ime/ime_decoder.cc
index 47f0492..eabc794c 100644
--- a/chromeos/services/ime/ime_decoder.cc
+++ b/chromeos/services/ime/ime_decoder.cc
@@ -45,6 +45,13 @@
       break;
   }
 }
+
+// Check whether the crucial members of an EntryPoints are loaded.
+bool isEntryPointsLoaded(ImeDecoder::EntryPoints entry) {
+  return (entry.initOnce && entry.support && entry.activateIme &&
+          entry.process && entry.closeDecoder);
+}
+
 }  // namespace
 
 ImeDecoder::ImeDecoder() : status_(Status::kUninitialized) {
@@ -65,21 +72,35 @@
     return;
   }
 
+  // TODO(b/172527471): Remove it when decoder DSO is uprevved.
   createMainEntry_ = reinterpret_cast<ImeMainEntryCreateFn>(
       library.GetFunctionPointer(IME_MAIN_ENTRY_CREATE_FN_NAME));
-  if (!createMainEntry_) {
+
+  // TODO(b/172527471): Create a macro to fetch function pointers.
+  entry_points_.initOnce = reinterpret_cast<ImeDecoderInitOnceFn>(
+      library.GetFunctionPointer("ImeDecoderInitOnce"));
+  entry_points_.support = reinterpret_cast<ImeDecoderSupportsFn>(
+      library.GetFunctionPointer("ImeDecoderSupports"));
+  entry_points_.activateIme = reinterpret_cast<ImeDecoderActivateImeFn>(
+      library.GetFunctionPointer("ImeDecoderActivateIme"));
+  entry_points_.process = reinterpret_cast<ImeDecoderProcessFn>(
+      library.GetFunctionPointer("ImeDecoderProcess"));
+  entry_points_.closeDecoder = reinterpret_cast<ImeDecoderCloseFn>(
+      library.GetFunctionPointer("ImeDecoderClose"));
+  if (!isEntryPointsLoaded(entry_points_)) {
     status_ = Status::kFunctionMissing;
     return;
   }
+  entry_points_.isReady = true;
 
+  // Optional function pointer.
   ImeEngineLoggerSetterFn loggerSetter =
       reinterpret_cast<ImeEngineLoggerSetterFn>(
           library.GetFunctionPointer("SetImeEngineLogger"));
   if (loggerSetter) {
     loggerSetter(ImeLoggerBridge);
   } else {
-    // Not a blocking issue yet.
-    LOG(ERROR) << "Failed to load SetImeEngineLogger function.";
+    LOG(WARNING) << "Failed to set a Chrome Logger for decoder DSO.";
   }
 
   library_ = std::move(library);
@@ -97,10 +118,16 @@
   return status_;
 }
 
+// TODO(b/172527471): Remove it when decoder DSO is uprevved.
 ImeEngineMainEntry* ImeDecoder::CreateMainEntry(ImeCrosPlatform* platform) {
-  DCHECK(status_ == Status::kSuccess);
+  DCHECK(createMainEntry_);
   return createMainEntry_(platform);
 }
 
+ImeDecoder::EntryPoints ImeDecoder::GetEntryPoints() {
+  DCHECK(status_ == Status::kSuccess);
+  return entry_points_;
+}
+
 }  // namespace ime
 }  // namespace chromeos
diff --git a/chromeos/services/ime/ime_decoder.h b/chromeos/services/ime/ime_decoder.h
index 90ace22..a15f5f9e 100644
--- a/chromeos/services/ime/ime_decoder.h
+++ b/chromeos/services/ime/ime_decoder.h
@@ -28,6 +28,19 @@
     kFunctionMissing = 4,
   };
 
+  // This contains the function pointers to the entry points for the loaded
+  // decoder shared library.
+  struct EntryPoints {
+    ImeDecoderInitOnceFn initOnce;
+    ImeDecoderSupportsFn support;
+    ImeDecoderActivateImeFn activateIme;
+    ImeDecoderProcessFn process;
+    ImeDecoderCloseFn closeDecoder;
+
+    // Whether the EntryPoints is ready to use.
+    bool isReady;
+  };
+
   // Gets the singleton ImeDecoder.
   static ImeDecoder* GetInstance();
 
@@ -36,8 +49,12 @@
   Status GetStatus() const;
 
   // Returns an instance of ImeEngineMainEntry from the IME shared library.
+  // TODO(b/172527471): Remove it when decoder DSO is uprevved.
   ImeEngineMainEntry* CreateMainEntry(ImeCrosPlatform* platform);
 
+  // Returns entry points of the loaded decoder shared library.
+  EntryPoints GetEntryPoints();
+
  private:
   friend class base::NoDestructor<ImeDecoder>;
 
@@ -49,8 +66,13 @@
 
   // Result of IME decoder DSO initialization.
   base::Optional<base::ScopedNativeLibrary> library_;
+
+  // Function pointors from decoder DSO.
+  // TODO(b/172527471): Remove it when decoder DSO is uprevved.
   ImeMainEntryCreateFn createMainEntry_;
 
+  EntryPoints entry_points_;
+
   DISALLOW_COPY_AND_ASSIGN(ImeDecoder);
 };
 
diff --git a/chromeos/services/ime/public/cpp/shared_lib/interfaces.h b/chromeos/services/ime/public/cpp/shared_lib/interfaces.h
index 4968af8..694a6cb 100644
--- a/chromeos/services/ime/public/cpp/shared_lib/interfaces.h
+++ b/chromeos/services/ime/public/cpp/shared_lib/interfaces.h
@@ -209,6 +209,7 @@
 // This class is implemented in the shared library and processes messages from
 // clients of the IME service. The shared library will exposes its create
 // function to the IME service.
+// DEPRECATED: Will be removed soon.
 class ImeEngineMainEntry {
  protected:
   virtual ~ImeEngineMainEntry() = default;
@@ -252,6 +253,12 @@
 // For use when bridging logs logged in IME shared library to Chrome logging.
 typedef void (*ImeEngineLoggerSetterFn)(ChromeLoggerFunc);
 
+typedef void (*ImeDecoderInitOnceFn)(ImeCrosPlatform*);
+typedef bool (*ImeDecoderSupportsFn)(const char*);
+typedef bool (*ImeDecoderActivateImeFn)(const char*, ImeClientDelegate*);
+typedef void (*ImeDecoderProcessFn)(const uint8_t*, size_t);
+typedef void (*ImeDecoderCloseFn)();
+
 // Defined name of ImeMainEntryCreateFn exported from shared library.
 #define IME_MAIN_ENTRY_CREATE_FN_NAME "CreateImeMainEntry"
 
diff --git a/components/keep_alive_registry/keep_alive_types.cc b/components/keep_alive_registry/keep_alive_types.cc
index 86122c05..fbbf528a 100644
--- a/components/keep_alive_registry/keep_alive_types.cc
+++ b/components/keep_alive_registry/keep_alive_types.cc
@@ -42,6 +42,8 @@
       return out << "APP_LIST_SERVICE_VIEWS";
     case KeepAliveOrigin::APP_LIST_SHOWER:
       return out << "APP_LIST_SHOWER";
+    case KeepAliveOrigin::APP_UNINSTALLATION_FROM_OS_SETTINGS:
+      return out << "APP_UNINSTALLATION_FROM_OS_SETTINGS";
     case KeepAliveOrigin::CHROME_APP_DELEGATE:
       return out << "CHROME_APP_DELEGATE";
     case KeepAliveOrigin::CHROME_VIEWS_DELEGATE:
diff --git a/components/keep_alive_registry/keep_alive_types.h b/components/keep_alive_registry/keep_alive_types.h
index 4183281..3697ba16 100644
--- a/components/keep_alive_registry/keep_alive_types.h
+++ b/components/keep_alive_registry/keep_alive_types.h
@@ -49,6 +49,7 @@
   // c/b/ui
   APP_LIST_SERVICE_VIEWS,
   APP_LIST_SHOWER,
+  APP_UNINSTALLATION_FROM_OS_SETTINGS,
   CHROME_APP_DELEGATE,
   CHROME_VIEWS_DELEGATE,
   PANEL,
diff --git a/components/messages/android/java/res/layout/message_banner_view.xml b/components/messages/android/java/res/layout/message_banner_view.xml
index 47d6a84..2d25077 100644
--- a/components/messages/android/java/res/layout/message_banner_view.xml
+++ b/components/messages/android/java/res/layout/message_banner_view.xml
@@ -9,15 +9,19 @@
 <org.chromium.components.messages.MessageBannerView
     xmlns:tools="http://schemas.android.com/tools"
     xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
     tools:ignore="UnusedResources"
     android:layout_height="@dimen/message_banner_height"
     android:layout_width="match_parent"
+    android:layout_gravity="center_horizontal"
     android:orientation="horizontal"
     android:layout_marginTop="@dimen/message_shadow_top_margin"
     android:layout_marginBottom="@dimen/message_shadow_bottom_margin"
     android:layout_marginStart="@dimen/message_shadow_lateral_margin"
     android:layout_marginEnd="@dimen/message_shadow_lateral_margin"
     android:elevation="@dimen/message_banner_elevation"
+    app:maxWidthPortrait="@dimen/message_max_width"
+    app:maxWidthLandscape="@dimen/message_max_width"
     android:background="@drawable/message_bg_tinted">
 
     <ImageView
diff --git a/components/messages/android/java/res/values/dimens.xml b/components/messages/android/java/res/values/dimens.xml
index 42ab861f..9a37fb7d 100644
--- a/components/messages/android/java/res/values/dimens.xml
+++ b/components/messages/android/java/res/values/dimens.xml
@@ -18,5 +18,6 @@
     <dimen name="message_shadow_bottom_margin">16dp</dimen>
     <dimen name="message_bubble_inset">8dp</dimen>
     <dimen name="message_hide_threshold">16dp</dimen>
+    <dimen name="message_max_width">380dp</dimen>
 
 </resources>
\ No newline at end of file
diff --git a/components/messages/android/java/src/org/chromium/components/messages/MessageBannerView.java b/components/messages/android/java/src/org/chromium/components/messages/MessageBannerView.java
index a749de02..44404ed3 100644
--- a/components/messages/android/java/src/org/chromium/components/messages/MessageBannerView.java
+++ b/components/messages/android/java/src/org/chromium/components/messages/MessageBannerView.java
@@ -11,18 +11,18 @@
 import android.view.MotionEvent;
 import android.view.View;
 import android.widget.ImageView;
-import android.widget.LinearLayout;
 import android.widget.TextView;
 
 import androidx.annotation.Nullable;
 
+import org.chromium.components.browser_ui.widget.BoundedLinearLayout;
 import org.chromium.components.browser_ui.widget.gesture.SwipeGestureListener;
 import org.chromium.components.browser_ui.widget.gesture.SwipeGestureListener.SwipeHandler;
 
 /**
  * View representing the message banner.
  */
-public class MessageBannerView extends LinearLayout {
+public class MessageBannerView extends BoundedLinearLayout {
     private ImageView mIconView;
     private TextView mTitle;
     private TextView mDescription;
diff --git a/components/no_state_prefetch/browser/BUILD.gn b/components/no_state_prefetch/browser/BUILD.gn
index e3702c1..af27de6 100644
--- a/components/no_state_prefetch/browser/BUILD.gn
+++ b/components/no_state_prefetch/browser/BUILD.gn
@@ -54,13 +54,15 @@
   testonly = true
   sources = [
     "prerender_history_unittest.cc",
+    "prerender_processor_impl_unittest.cc",
     "prerender_util_unittest.cc",
   ]
 
   deps = [
-    ":browser",
     "//base",
     "//base/test:test_support",
+    "//components/no_state_prefetch/browser",
+    "//content/test:test_support",
     "//google_apis:google_apis",
     "//testing/gtest",
     "//url:url",
diff --git a/components/no_state_prefetch/browser/DEPS b/components/no_state_prefetch/browser/DEPS
index 85f97c0..2857626e 100644
--- a/components/no_state_prefetch/browser/DEPS
+++ b/components/no_state_prefetch/browser/DEPS
@@ -5,6 +5,7 @@
   "+components/keyed_service/core",
   "+content/public/browser",
   "+content/public/common",
+  "+content/public/test",
   "+mojo/public",
   "+net/http",
   "+services/metrics/public/cpp",
diff --git a/components/no_state_prefetch/browser/prerender_link_manager.cc b/components/no_state_prefetch/browser/prerender_link_manager.cc
index 30639e2..c78a146 100644
--- a/components/no_state_prefetch/browser/prerender_link_manager.cc
+++ b/components/no_state_prefetch/browser/prerender_link_manager.cc
@@ -125,12 +125,6 @@
       std::move(attributes), initiator_origin, std::move(processor_client),
       manager_->GetCurrentTimeTicks(), prerender_contents);
 
-  // Observe disconnect of the client to abandon the running prerender. The raw
-  // pointer to |this| is safe because |prerender| is owned by |this|.
-  prerender->remote_processor_client.set_disconnect_handler(
-      base::BindOnce(&PrerenderLinkManager::OnAbandonPrerender,
-                     base::Unretained(this), prerender->prerender_id));
-
   // Stash pointer used only for comparison later.
   const LinkPrerender* prerender_ptr = prerender.get();
 
diff --git a/components/no_state_prefetch/browser/prerender_link_manager.h b/components/no_state_prefetch/browser/prerender_link_manager.h
index 5fe79e12..9f93591 100644
--- a/components/no_state_prefetch/browser/prerender_link_manager.h
+++ b/components/no_state_prefetch/browser/prerender_link_manager.h
@@ -40,7 +40,7 @@
   // Called when a <link rel=prerender ...> element has been inserted into the
   // document. Returns the prerender id that is used for canceling or abandoning
   // prerendering. Returns base::nullopt if the prerender was not started.
-  base::Optional<int> OnStartPrerender(
+  virtual base::Optional<int> OnStartPrerender(
       int launcher_render_process_id,
       int launcher_render_view_id,
       blink::mojom::PrerenderAttributesPtr attributes,
@@ -50,13 +50,13 @@
 
   // Called when a <link rel=prerender ...> element has been explicitly removed
   // from a document.
-  void OnCancelPrerender(int prerender_id);
+  virtual void OnCancelPrerender(int prerender_id);
 
   // Called when a renderer launching <link rel=prerender ...> has navigated
   // away from the launching page, the launching renderer process has crashed,
   // or perhaps the renderer process was fast-closed when the last render view
   // in it was closed.
-  void OnAbandonPrerender(int prerender_id);
+  virtual void OnAbandonPrerender(int prerender_id);
 
  private:
   friend class PrerenderBrowserTest;
diff --git a/components/no_state_prefetch/browser/prerender_processor_impl.cc b/components/no_state_prefetch/browser/prerender_processor_impl.cc
index 7649241f..ace3ac5 100644
--- a/components/no_state_prefetch/browser/prerender_processor_impl.cc
+++ b/components/no_state_prefetch/browser/prerender_processor_impl.cc
@@ -10,7 +10,6 @@
 #include "content/public/browser/render_process_host.h"
 #include "content/public/browser/render_view_host.h"
 #include "content/public/common/referrer.h"
-#include "mojo/public/cpp/bindings/self_owned_receiver.h"
 
 namespace prerender {
 
@@ -18,11 +17,16 @@
     int render_process_id,
     int render_frame_id,
     const url::Origin& initiator_origin,
+    mojo::PendingReceiver<blink::mojom::PrerenderProcessor> receiver,
     std::unique_ptr<PrerenderProcessorImplDelegate> delegate)
     : render_process_id_(render_process_id),
       render_frame_id_(render_frame_id),
       initiator_origin_(initiator_origin),
-      delegate_(std::move(delegate)) {}
+      delegate_(std::move(delegate)) {
+  receiver_.Bind(std::move(receiver));
+  receiver_.set_disconnect_handler(
+      base::BindOnce(&PrerenderProcessorImpl::Abandon, base::Unretained(this)));
+}
 
 PrerenderProcessorImpl::~PrerenderProcessorImpl() = default;
 
@@ -31,11 +35,12 @@
     content::RenderFrameHost* frame_host,
     mojo::PendingReceiver<blink::mojom::PrerenderProcessor> receiver,
     std::unique_ptr<PrerenderProcessorImplDelegate> delegate) {
-  mojo::MakeSelfOwnedReceiver(
-      std::make_unique<PrerenderProcessorImpl>(
-          frame_host->GetProcess()->GetID(), frame_host->GetRoutingID(),
-          frame_host->GetLastCommittedOrigin(), std::move(delegate)),
-      std::move(receiver));
+  // PrerenderProcessorImpl is a self-owned object. This deletes itself on the
+  // mojo disconnect handler.
+  new PrerenderProcessorImpl(frame_host->GetProcess()->GetID(),
+                             frame_host->GetRoutingID(),
+                             frame_host->GetLastCommittedOrigin(),
+                             std::move(receiver), std::move(delegate));
 }
 
 void PrerenderProcessorImpl::Start(
@@ -79,6 +84,15 @@
     link_manager->OnCancelPrerender(*prerender_id_);
 }
 
+void PrerenderProcessorImpl::Abandon() {
+  if (prerender_id_) {
+    auto* link_manager = GetPrerenderLinkManager();
+    if (link_manager)
+      link_manager->OnAbandonPrerender(*prerender_id_);
+  }
+  delete this;
+}
+
 PrerenderLinkManager* PrerenderProcessorImpl::GetPrerenderLinkManager() {
   auto* render_frame_host =
       content::RenderFrameHost::FromID(render_process_id_, render_frame_id_);
diff --git a/components/no_state_prefetch/browser/prerender_processor_impl.h b/components/no_state_prefetch/browser/prerender_processor_impl.h
index 09620613..dafe6f8a 100644
--- a/components/no_state_prefetch/browser/prerender_processor_impl.h
+++ b/components/no_state_prefetch/browser/prerender_processor_impl.h
@@ -6,6 +6,7 @@
 #define COMPONENTS_NO_STATE_PREFETCH_BROWSER_PRERENDER_PROCESSOR_IMPL_H_
 
 #include "components/no_state_prefetch/browser/prerender_processor_impl_delegate.h"
+#include "mojo/public/cpp/bindings/receiver.h"
 #include "third_party/blink/public/mojom/prerender/prerender.mojom.h"
 #include "url/origin.h"
 
@@ -15,12 +16,17 @@
 
 namespace prerender {
 
+// PrerenderProcessorImpl implements blink::mojom::PrerenderProcessor and works
+// as the browser-side entry point of NoStatePrefetch for <link rel=prerender>.
+// This is a self-owned object and deletes itself when the mojo connection is
+// lost.
 class PrerenderProcessorImpl : public blink::mojom::PrerenderProcessor {
  public:
   PrerenderProcessorImpl(
       int render_process_id,
       int render_frame_id,
       const url::Origin& initiator_origin,
+      mojo::PendingReceiver<blink::mojom::PrerenderProcessor> receiver,
       std::unique_ptr<PrerenderProcessorImplDelegate> delegate);
   ~PrerenderProcessorImpl() override;
 
@@ -36,6 +42,10 @@
   void Cancel() override;
 
  private:
+  // Abandons prerendering and deletes `this`. Called from the mojo disconnect
+  // handler.
+  void Abandon();
+
   PrerenderLinkManager* GetPrerenderLinkManager();
 
   const int render_process_id_;
@@ -46,6 +56,8 @@
   // The ID of PrerenderLinkManager::LinkPrerender. Used for canceling or
   // abandoning prerendering.
   base::Optional<int> prerender_id_;
+
+  mojo::Receiver<blink::mojom::PrerenderProcessor> receiver_{this};
 };
 
 }  // namespace prerender
diff --git a/components/no_state_prefetch/browser/prerender_processor_impl_unittest.cc b/components/no_state_prefetch/browser/prerender_processor_impl_unittest.cc
new file mode 100644
index 0000000..2c922864
--- /dev/null
+++ b/components/no_state_prefetch/browser/prerender_processor_impl_unittest.cc
@@ -0,0 +1,176 @@
+// 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/no_state_prefetch/browser/prerender_processor_impl.h"
+
+#include "base/run_loop.h"
+#include "components/no_state_prefetch/browser/prerender_link_manager.h"
+#include "components/no_state_prefetch/browser/prerender_processor_impl_delegate.h"
+#include "content/public/test/test_browser_context.h"
+#include "content/public/test/test_renderer_host.h"
+#include "third_party/blink/public/common/features.h"
+
+namespace prerender {
+
+class MockPrerenderLinkManager final : public PrerenderLinkManager {
+ public:
+  MockPrerenderLinkManager() : PrerenderLinkManager(/*manager=*/nullptr) {}
+
+  base::Optional<int> OnStartPrerender(
+      int launcher_render_process_id,
+      int launcher_render_view_id,
+      blink::mojom::PrerenderAttributesPtr attributes,
+      const url::Origin& initiator_origin,
+      mojo::PendingRemote<blink::mojom::PrerenderProcessorClient>
+          processor_client) override {
+    DCHECK(!is_start_called_);
+    is_start_called_ = true;
+    return prerender_id_;
+  }
+
+  void OnCancelPrerender(int prerender_id) override {
+    DCHECK_EQ(prerender_id_, prerender_id);
+    DCHECK(!is_cancel_called_);
+    is_cancel_called_ = true;
+  }
+
+  void OnAbandonPrerender(int prerender_id) override {
+    DCHECK_EQ(prerender_id_, prerender_id);
+    DCHECK(!is_abandon_called_);
+    is_abandon_called_ = true;
+  }
+
+  bool is_start_called() const { return is_start_called_; }
+  bool is_cancel_called() const { return is_cancel_called_; }
+  bool is_abandon_called() const { return is_abandon_called_; }
+
+ private:
+  const int prerender_id_ = 100;
+  bool is_start_called_ = false;
+  bool is_cancel_called_ = false;
+  bool is_abandon_called_ = false;
+};
+
+class MockPrerenderProcessorImplDelegate final
+    : public PrerenderProcessorImplDelegate {
+ public:
+  explicit MockPrerenderProcessorImplDelegate(
+      MockPrerenderLinkManager* link_manager)
+      : link_manager_(link_manager) {}
+
+  PrerenderLinkManager* GetPrerenderLinkManager(
+      content::BrowserContext* browser_context) override {
+    return link_manager_;
+  }
+
+ private:
+  MockPrerenderLinkManager* link_manager_;
+};
+
+class PrerenderProcessorImplTest
+    : public content::RenderViewHostTestHarness,
+      public blink::mojom::PrerenderProcessorClient {
+ public:
+  // blink::mojom::PrerenderProcessorClient:
+  void OnPrerenderStart() override {}
+  void OnPrerenderStopLoading() override {}
+  void OnPrerenderDomContentLoaded() override {}
+  void OnPrerenderStop() override {}
+
+ protected:
+  mojo::Receiver<blink::mojom::PrerenderProcessorClient> receiver_{this};
+};
+
+TEST_F(PrerenderProcessorImplTest, StartCancelAbandon) {
+  auto link_manager = std::make_unique<MockPrerenderLinkManager>();
+
+  mojo::Remote<blink::mojom::PrerenderProcessor> remote;
+  PrerenderProcessorImpl::Create(
+      main_rfh(), remote.BindNewPipeAndPassReceiver(),
+      std::make_unique<MockPrerenderProcessorImplDelegate>(link_manager.get()));
+
+  auto attributes = blink::mojom::PrerenderAttributes::New();
+  attributes->url = GURL("https://example.com/prefetch");
+  attributes->referrer = blink::mojom::Referrer::New();
+
+  // Start() call should be propagated to the link manager.
+  EXPECT_FALSE(link_manager->is_start_called());
+  remote->Start(std::move(attributes), receiver_.BindNewPipeAndPassRemote());
+  remote.FlushForTesting();
+  EXPECT_TRUE(link_manager->is_start_called());
+
+  // Cancel() call should be propagated to the link manager.
+  EXPECT_FALSE(link_manager->is_cancel_called());
+  remote->Cancel();
+  remote.FlushForTesting();
+  EXPECT_TRUE(link_manager->is_cancel_called());
+
+  // Connection lost should abandon the link manager.
+  EXPECT_FALSE(link_manager->is_abandon_called());
+  remote.reset();
+  // FlushForTesting() is no longer available. Instead, use base::RunLoop.
+  base::RunLoop().RunUntilIdle();
+  EXPECT_TRUE(link_manager->is_abandon_called());
+}
+
+TEST_F(PrerenderProcessorImplTest, StartAbandon) {
+  auto link_manager = std::make_unique<MockPrerenderLinkManager>();
+
+  mojo::Remote<blink::mojom::PrerenderProcessor> remote;
+  PrerenderProcessorImpl::Create(
+      main_rfh(), remote.BindNewPipeAndPassReceiver(),
+      std::make_unique<MockPrerenderProcessorImplDelegate>(link_manager.get()));
+
+  auto attributes = blink::mojom::PrerenderAttributes::New();
+  attributes->url = GURL("https://example.com/prefetch");
+  attributes->referrer = blink::mojom::Referrer::New();
+
+  // Start() call should be propagated to the link manager.
+  EXPECT_FALSE(link_manager->is_start_called());
+  remote->Start(std::move(attributes), receiver_.BindNewPipeAndPassRemote());
+  remote.FlushForTesting();
+  EXPECT_TRUE(link_manager->is_start_called());
+
+  // Connection lost should abandon the link manager.
+  EXPECT_FALSE(link_manager->is_abandon_called());
+  remote.reset();
+  // FlushForTesting() is no longer available. Instead, use base::RunLoop.
+  base::RunLoop().RunUntilIdle();
+  EXPECT_TRUE(link_manager->is_abandon_called());
+}
+
+TEST_F(PrerenderProcessorImplTest, Cancel) {
+  auto link_manager = std::make_unique<MockPrerenderLinkManager>();
+
+  mojo::Remote<blink::mojom::PrerenderProcessor> remote;
+  PrerenderProcessorImpl::Create(
+      main_rfh(), remote.BindNewPipeAndPassReceiver(),
+      std::make_unique<MockPrerenderProcessorImplDelegate>(link_manager.get()));
+
+  // Call Cancel() before Start().
+  EXPECT_FALSE(link_manager->is_cancel_called());
+  remote->Cancel();
+  remote.FlushForTesting();
+  // The cancellation should not be propagated to the link manager.
+  EXPECT_FALSE(link_manager->is_cancel_called());
+}
+
+TEST_F(PrerenderProcessorImplTest, Abandon) {
+  auto link_manager = std::make_unique<MockPrerenderLinkManager>();
+
+  mojo::Remote<blink::mojom::PrerenderProcessor> remote;
+  PrerenderProcessorImpl::Create(
+      main_rfh(), remote.BindNewPipeAndPassReceiver(),
+      std::make_unique<MockPrerenderProcessorImplDelegate>(link_manager.get()));
+
+  // Disconnect before Start().
+  EXPECT_FALSE(link_manager->is_abandon_called());
+  remote.reset();
+  // FlushForTesting() is no longer available. Instead, use base::RunLoop.
+  base::RunLoop().RunUntilIdle();
+  // The disconnection should not be propagated to the link manager.
+  EXPECT_FALSE(link_manager->is_abandon_called());
+}
+
+}  // namespace prerender
diff --git a/components/payments/content/android/java/src/org/chromium/components/payments/PaymentRequestService.java b/components/payments/content/android/java/src/org/chromium/components/payments/PaymentRequestService.java
index 9fb69d2..86a908c 100644
--- a/components/payments/content/android/java/src/org/chromium/components/payments/PaymentRequestService.java
+++ b/components/payments/content/android/java/src/org/chromium/components/payments/PaymentRequestService.java
@@ -369,14 +369,6 @@
     }
 
     /**
-     * Sets that the payment details is no longer pending to be updated because the promise that
-     * was passed into PaymentRequest.show() has been resolved.
-     */
-    public void resetWaitingForUpdatedDetails() {
-        mIsShowWaitingForUpdatedDetails = false;
-    }
-
-    /**
      * Called to open a new PaymentHandler UI on the showing PaymentRequest.
      * @param url The url of the payment app to be displayed in the UI.
      * @return The WebContents of the payment handler that's just opened when the opening is
@@ -557,7 +549,7 @@
      * @param paymentApp The payment app to be invoked.
      * @param paymentResponseHelper The helper to create and fill the response to send to the
      *         merchant. The helper should have this instance as the delegate {@link
-     *         PaymentResponseHelperInterface.PaymentResponseRequesterDelegate}.
+     *         PaymentResponseHelperInterface.PaymentResponseResultCallback}.
      */
     public void invokePaymentApp(
             PaymentApp paymentApp, PaymentResponseHelperInterface paymentResponseHelper) {
@@ -726,28 +718,12 @@
         return mIsUserGestureShow;
     }
 
-    /**
-     * Records that PaymentRequest.show() was invoked with a user gesture.
-     * @param userGestureShow Whether it is invoked with a user gesture.
-     */
-    public void setUserGestureShow(boolean userGestureShow) {
-        mIsUserGestureShow = userGestureShow;
-    }
-
     /** @return Whether the current payment request service has called show(). */
     public boolean isCurrentPaymentRequestShowing() {
         return mIsCurrentPaymentRequestShowing;
     }
 
     /**
-     * Records whether the current payment request service has called show().
-     * @param isShowing Whether show() has been called.
-     */
-    public void setCurrentPaymentRequestShowing(boolean isShowing) {
-        mIsCurrentPaymentRequestShowing = isShowing;
-    }
-
-    /**
      * @return Whether all payment apps have been queried of canMakePayment() and
      *         hasEnrolledInstrument().
      */
@@ -866,11 +842,6 @@
         }
     }
 
-    /** @return Whether the created payment apps includes any autofill payment app. */
-    public boolean getHasNonAutofillApp() {
-        return mHasNonAutofillApp;
-    }
-
     /**
      * @param methodDataList A list of PaymentMethodData.
      * @return The validated method data, a mapping of method names to its PaymentMethodData(s);
@@ -1085,6 +1056,15 @@
      */
     /* package */ void retry(PaymentValidationErrors errors) {
         if (mBrowserPaymentRequest == null) return;
+        if (!PaymentValidator.validatePaymentValidationErrors(errors)) {
+            mJourneyLogger.setAborted(AbortReason.INVALID_DATA_FROM_RENDERER);
+            disconnectFromClientWithDebugMessage(
+                    ErrorStrings.INVALID_VALIDATION_ERRORS, PaymentErrorReason.USER_CANCEL);
+            return;
+        }
+        assert mSpec != null;
+        assert !mSpec.isDestroyed() : "mSpec should not be used after being destroyed.";
+        mSpec.retry(errors);
         mBrowserPaymentRequest.retry(errors);
     }
 
@@ -1216,11 +1196,6 @@
         if (mClient != null) mClient.onPayerDetailChange(detail);
     }
 
-    /** Invokes {@link PaymentRequestClient.onError}. */
-    public void onError(int error, String errorMessage) {
-        if (mClient != null) mClient.onError(error, errorMessage);
-    }
-
     /** Invokes {@link PaymentRequestClient.warnNoFavicon}. */
     public void warnNoFavicon() {
         if (mClient != null) mClient.warnNoFavicon();
diff --git a/content/browser/renderer_host/compositor_impl_android.cc b/content/browser/renderer_host/compositor_impl_android.cc
index 5d60f752..44d35de 100644
--- a/content/browser/renderer_host/compositor_impl_android.cc
+++ b/content/browser/renderer_host/compositor_impl_android.cc
@@ -367,6 +367,9 @@
 
 void CompositorImpl::SetSurface(const base::android::JavaRef<jobject>& surface,
                                 bool can_be_used_with_surface_control) {
+  can_be_used_with_surface_control &=
+      !root_window_->ApplyDisableSurfaceControlWorkaround();
+
   JNIEnv* env = base::android::AttachCurrentThread();
   gpu::GpuSurfaceTracker* tracker = gpu::GpuSurfaceTracker::Get();
 
diff --git a/content/browser/service_manager/service_manager_context.cc b/content/browser/service_manager/service_manager_context.cc
index e361e18..d13cc27 100644
--- a/content/browser/service_manager/service_manager_context.cc
+++ b/content/browser/service_manager/service_manager_context.cc
@@ -289,28 +289,6 @@
                                   base::Token{}, base::Token::CreateRandom()),
         std::move(system_remote), metadata.BindNewPipeAndPassReceiver());
     metadata->SetPID(base::GetCurrentProcId());
-
-    service_manager_->SetInstanceQuitCallback(
-        base::BindOnce(&OnInstanceQuitOnServiceManagerThread,
-                       std::move(ui_thread_task_runner)));
-  }
-
-  static void OnInstanceQuitOnServiceManagerThread(
-      scoped_refptr<base::SequencedTaskRunner> ui_thread_task_runner,
-      const service_manager::Identity& id) {
-    ui_thread_task_runner->PostTask(FROM_HERE,
-                                    base::BindOnce(&OnInstanceQuit, id));
-  }
-
-  static void OnInstanceQuit(const service_manager::Identity& id) {
-    if (GetContentClient()->browser()->ShouldTerminateOnServiceQuit(id)) {
-      // Don't LOG(FATAL) because we don't want a browser crash report.
-      LOG(ERROR) << "Terminating because service '" << id.name()
-                 << "' quit unexpectedly.";
-      // Skip shutdown to reduce the risk that other code in the browser will
-      // respond to the service pipe closing.
-      exit(1);
-    }
   }
 
   void ShutDownOnServiceManagerThread() {
@@ -370,8 +348,6 @@
   // GetConnectorForIOThread().
   g_io_thread_connector.Get() = system_connection->GetConnector()->Clone();
 
-  GetContentClient()->browser()->WillStartServiceManager();
-
   in_process_context_->Start(
       manifests, std::move(system_remote),
       base::BindRepeating(&ServiceManagerContext::RunServiceInstance,
diff --git a/content/browser/xr/service/xr_runtime_manager_impl.cc b/content/browser/xr/service/xr_runtime_manager_impl.cc
index fc6106d..a547590 100644
--- a/content/browser/xr/service/xr_runtime_manager_impl.cc
+++ b/content/browser/xr/service/xr_runtime_manager_impl.cc
@@ -229,9 +229,11 @@
 #endif
 
 #if BUILDFLAG(ENABLE_OPENXR)
-  auto* openxr = GetRuntime(device::mojom::XRDeviceId::OPENXR_DEVICE_ID);
-  if (openxr && openxr->SupportsArBlendMode())
-    return openxr;
+  if (base::FeatureList::IsEnabled(features::kOpenXrExtendedFeatureSupport)) {
+    auto* openxr = GetRuntime(device::mojom::XRDeviceId::OPENXR_DEVICE_ID);
+    if (openxr && openxr->SupportsArBlendMode())
+      return openxr;
+  }
 #endif
 
   return nullptr;
diff --git a/content/public/browser/content_browser_client.cc b/content/public/browser/content_browser_client.cc
index 1ee7cc5..418501e 100644
--- a/content/public/browser/content_browser_client.cc
+++ b/content/public/browser/content_browser_client.cc
@@ -721,11 +721,6 @@
     const service_manager::Identity& identity,
     mojo::PendingReceiver<service_manager::mojom::Service>* receiver) {}
 
-bool ContentBrowserClient::ShouldTerminateOnServiceQuit(
-    const service_manager::Identity& id) {
-  return false;
-}
-
 base::Optional<service_manager::Manifest>
 ContentBrowserClient::GetServiceManifestOverlay(base::StringPiece name) {
   return base::nullopt;
diff --git a/content/public/browser/content_browser_client.h b/content/public/browser/content_browser_client.h
index 2806836..482b60a 100644
--- a/content/public/browser/content_browser_client.h
+++ b/content/public/browser/content_browser_client.h
@@ -1086,9 +1086,6 @@
       RenderProcessHost* render_process_host,
       mojo::GenericPendingReceiver receiver) {}
 
-  // Called just before the Service Manager is initialized.
-  virtual void WillStartServiceManager() {}
-
   // Handles a service instance request for a new service instance with identity
   // |identity|. If the client knows how to run the named service, it should
   // bind |*receiver| accordingly, in the browser process.
@@ -1101,11 +1098,6 @@
       const service_manager::Identity& identity,
       mojo::PendingReceiver<service_manager::mojom::Service>* receiver);
 
-  // Allows the embedder to terminate the browser if a specific service instance
-  // quits or crashes.
-  virtual bool ShouldTerminateOnServiceQuit(
-      const service_manager::Identity& id);
-
   // Allows the embedder to amend service manifests for existing services.
   // Specifically, the sets of exposed and required capabilities, interface
   // filter capabilities (deprecated), packaged services, and preloaded files
diff --git a/content/public/common/content_features.cc b/content/public/common/content_features.cc
index 28c332a..112814b 100644
--- a/content/public/common/content_features.cc
+++ b/content/public/common/content_features.cc
@@ -438,6 +438,14 @@
 const base::Feature kOriginPolicy{"OriginPolicy",
                                   base::FEATURE_DISABLED_BY_DEFAULT};
 
+// Some WebXR features may have been enabled for ARCore, but are not yet ready
+// to be plumbed up from the OpenXR backend. This feature provides a mechanism
+// to gate such support in a generic way. Note that this feature should not be
+// used for features we intend to ship simultaneously on both OpenXR and ArCore.
+// For those features, a feature-specific flag should be created if needed.
+const base::Feature kOpenXrExtendedFeatureSupport{
+    "OpenXrExtendedFeatureSupport", base::FEATURE_DISABLED_BY_DEFAULT};
+
 // History navigation in response to horizontal overscroll (aka gesture-nav).
 const base::Feature kOverscrollHistoryNavigation {
   "OverscrollHistoryNavigation",
diff --git a/content/public/common/content_features.h b/content/public/common/content_features.h
index 0e92e70..89259a3e 100644
--- a/content/public/common/content_features.h
+++ b/content/public/common/content_features.h
@@ -99,6 +99,7 @@
 CONTENT_EXPORT extern const base::Feature kNotificationTriggers;
 CONTENT_EXPORT extern const base::Feature kOriginIsolationHeader;
 CONTENT_EXPORT extern const base::Feature kOriginPolicy;
+CONTENT_EXPORT extern const base::Feature kOpenXrExtendedFeatureSupport;
 CONTENT_EXPORT extern const base::Feature kOverscrollHistoryNavigation;
 CONTENT_EXPORT extern const base::Feature kPeriodicBackgroundSync;
 CONTENT_EXPORT extern const base::Feature kPermissionsPolicyHeader;
diff --git a/content/shell/browser/shell_content_browser_client.cc b/content/shell/browser/shell_content_browser_client.cc
index 6512d180..a69f4c853 100644
--- a/content/shell/browser/shell_content_browser_client.cc
+++ b/content/shell/browser/shell_content_browser_client.cc
@@ -200,13 +200,6 @@
   return false;
 }
 
-bool ShellContentBrowserClient::ShouldTerminateOnServiceQuit(
-    const service_manager::Identity& id) {
-  if (should_terminate_on_service_quit_callback_)
-    return std::move(should_terminate_on_service_quit_callback_).Run(id);
-  return false;
-}
-
 void ShellContentBrowserClient::AppendExtraCommandLineSwitches(
     base::CommandLine* command_line,
     int child_process_id) {
diff --git a/content/shell/browser/shell_content_browser_client.h b/content/shell/browser/shell_content_browser_client.h
index ba39b6e..af0417d 100644
--- a/content/shell/browser/shell_content_browser_client.h
+++ b/content/shell/browser/shell_content_browser_client.h
@@ -43,8 +43,6 @@
   std::unique_ptr<BrowserMainParts> CreateBrowserMainParts(
       const MainFunctionParams& parameters) override;
   bool IsHandledURL(const GURL& url) override;
-  bool ShouldTerminateOnServiceQuit(
-      const service_manager::Identity& id) override;
   void AppendExtraCommandLineSwitches(base::CommandLine* command_line,
                                       int child_process_id) override;
   std::string GetAcceptLangs(BrowserContext* context) override;
@@ -129,10 +127,6 @@
     select_client_certificate_callback_ =
         std::move(select_client_certificate_callback);
   }
-  void set_should_terminate_on_service_quit_callback(
-      base::OnceCallback<bool(const service_manager::Identity&)> callback) {
-    should_terminate_on_service_quit_callback_ = std::move(callback);
-  }
   void set_login_request_callback(
       base::OnceCallback<void(bool is_main_frame)> login_request_callback) {
     login_request_callback_ = std::move(login_request_callback);
@@ -183,8 +177,6 @@
   static bool allow_any_cors_exempt_header_for_browser_;
 
   base::OnceClosure select_client_certificate_callback_;
-  base::OnceCallback<bool(const service_manager::Identity&)>
-      should_terminate_on_service_quit_callback_;
   base::OnceCallback<void(bool is_main_frame)> login_request_callback_;
   base::RepeatingCallback<void(const network::mojom::URLLoaderFactoryParams*,
                                const url::Origin&,
diff --git a/docs/accessibility/chromevox_on_desktop_linux.md b/docs/accessibility/chromevox_on_desktop_linux.md
index 07ef834..9f016cd 100644
--- a/docs/accessibility/chromevox_on_desktop_linux.md
+++ b/docs/accessibility/chromevox_on_desktop_linux.md
@@ -85,9 +85,13 @@
 Pick the latest version and
 
 ```
-gsutil cp gs://chromeos-localmirror/distfiles/espeak-ng-20180801.tar.gz /usr/share/chromeos-assets/speech_synthesis/espeak-ng/
-tar xvf /usr/share/chromeos-assets/speech_synthesis/espeak-ng/espeak-ng-20180801.tar.gz
-rm /usr/share/chromeos-assets/speech_synthesis/espeak-ng/espeak-ng-20180801.tar.gz
+TMPDIR=$(mktemp -d)
+gsutil cp gs://chromeos-localmirror/distfiles/espeak-ng-20180801.tar.gz $TMPDIR
+tar -C $TMPDIR -xvf ~/espeak-ng/espeak-ng-20180801.tar.gz
+sudo mkdir -p /usr/share/chromeos-assets/speech_synthesis/espeak-ng/
+sudo chown -R $(whoami) /usr/share/chromeos-assets/
+cp -r $TMPDIR/espeak-ng/chrome-extension/* /usr/share/chromeos-assets/speech_synthesis/espeak-ng
+rm -rf $TMPDIR
 ```
 
 **Be sure to check permissions of /usr/share/chromeos-assets, some users report
diff --git a/extensions/browser/api/web_request/web_request_permissions.cc b/extensions/browser/api/web_request/web_request_permissions.cc
index eaf6ed2b..54e16ea 100644
--- a/extensions/browser/api/web_request/web_request_permissions.cc
+++ b/extensions/browser/api/web_request/web_request_permissions.cc
@@ -270,7 +270,9 @@
     if (request.web_request_type !=
             extensions::WebRequestResourceType::MAIN_FRAME &&
         request.web_request_type !=
-            extensions::WebRequestResourceType::SUB_FRAME) {
+            extensions::WebRequestResourceType::SUB_FRAME &&
+        request.web_request_type !=
+            extensions::WebRequestResourceType::OBJECT) {
       // TODO(crbug.com/1145496): Remove crash key logging once the DCHECK
       // failure below is fixed.
       static auto* web_request_type_key = base::debug::AllocateCrashKeyString(
@@ -284,10 +286,7 @@
       base::debug::ScopedCrashKeyString scoped_url(
           url_key, request.url.possibly_invalid_spec());
 
-      DCHECK(request.web_request_type ==
-                 extensions::WebRequestResourceType::MAIN_FRAME ||
-             request.web_request_type ==
-                 extensions::WebRequestResourceType::SUB_FRAME);
+      DCHECK(false);
     }
 
     // Hide sub-frame requests to clientsX.google.com.
diff --git a/fuchsia/engine/browser/web_engine_browser_context.cc b/fuchsia/engine/browser/web_engine_browser_context.cc
index 03a6e9fa..6d83fa02 100644
--- a/fuchsia/engine/browser/web_engine_browser_context.cc
+++ b/fuchsia/engine/browser/web_engine_browser_context.cc
@@ -12,25 +12,45 @@
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
 #include "base/path_service.h"
+#include "base/system/sys_info.h"
 #include "components/keyed_service/core/simple_key_map.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/resource_context.h"
 #include "fuchsia/engine/browser/web_engine_net_log_observer.h"
 #include "fuchsia/engine/browser/web_engine_permission_delegate.h"
+#include "fuchsia/engine/switches.h"
 #include "media/capabilities/in_memory_video_decode_stats_db_impl.h"
 #include "media/mojo/services/video_decode_perf_history.h"
 #include "services/network/public/cpp/network_switches.h"
 
-class WebEngineBrowserContext::ResourceContext
-    : public content::ResourceContext {
- public:
-  ResourceContext() = default;
-  ~ResourceContext() override = default;
+namespace {
 
- private:
-  DISALLOW_COPY_AND_ASSIGN(ResourceContext);
-};
+// Determines whether a data directory is configured, and returns its path.
+// Passes the quota, if specified, for SysInfo to report as total disk space.
+base::FilePath InitializeDataDirectoryAndQuotaFromCommandLine() {
+  base::FilePath data_directory_path;
+
+  const base::CommandLine* command_line =
+      base::CommandLine::ForCurrentProcess();
+  if (!base::PathService::Get(base::DIR_APP_DATA, &data_directory_path) ||
+      !base::PathExists(data_directory_path)) {
+    // Run in incognito mode if /data doesn't exist.
+    return base::FilePath();
+  }
+
+  if (command_line->HasSwitch(switches::kDataQuotaBytes)) {
+    // Configure SysInfo to use the specified quota as the total-disk-space
+    // for the |data_dir_path_|.
+    uint64_t quota_bytes = 0;
+    CHECK(base::StringToUint64(
+        command_line->GetSwitchValueASCII(switches::kDataQuotaBytes),
+        &quota_bytes));
+    base::SysInfo::SetAmountOfTotalDiskSpace(data_directory_path, quota_bytes);
+  }
+
+  return data_directory_path;
+}
 
 std::unique_ptr<WebEngineNetLogObserver> CreateNetLogObserver() {
   std::unique_ptr<WebEngineNetLogObserver> result;
@@ -46,16 +66,25 @@
   return result;
 }
 
+}  // namespace
+
+class WebEngineBrowserContext::ResourceContext
+    : public content::ResourceContext {
+ public:
+  ResourceContext() = default;
+  ~ResourceContext() override = default;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ResourceContext);
+};
+
 WebEngineBrowserContext::WebEngineBrowserContext(bool force_incognito)
     : net_log_observer_(CreateNetLogObserver()),
       resource_context_(new ResourceContext()) {
   if (!force_incognito) {
-    base::PathService::Get(base::DIR_APP_DATA, &data_dir_path_);
-    if (!base::PathExists(data_dir_path_)) {
-      // Run in incognito mode if /data doesn't exist.
-      data_dir_path_.clear();
-    }
+    data_dir_path_ = InitializeDataDirectoryAndQuotaFromCommandLine();
   }
+
   simple_factory_key_ =
       std::make_unique<SimpleFactoryKey>(GetPath(), IsOffTheRecord());
   SimpleKeyMap::GetInstance()->Associate(this, simple_factory_key_.get());
diff --git a/fuchsia/engine/switches.cc b/fuchsia/engine/switches.cc
index 4cfc7d2..f51e111 100644
--- a/fuchsia/engine/switches.cc
+++ b/fuchsia/engine/switches.cc
@@ -19,5 +19,6 @@
 const char kEnableCastStreamingReceiver[] = "enable-cast-streaming-receiver";
 const char kCdmDataDirectory[] = "cdm-data-directory";
 const char kUseLegacyAndroidUserAgent[] = "use-legacy-android-user-agent";
+const char kDataQuotaBytes[] = "data-quota-bytes";
 
 }  // namespace switches
diff --git a/fuchsia/engine/switches.h b/fuchsia/engine/switches.h
index 9c76afd..a5bd8f87 100644
--- a/fuchsia/engine/switches.h
+++ b/fuchsia/engine/switches.h
@@ -56,6 +56,9 @@
 // Enables reporting of an Android-like User Agent string.
 extern const char kUseLegacyAndroidUserAgent[];
 
+// Soft quota to apply to the Context's persistent data directory, in bytes.
+extern const char kDataQuotaBytes[];
+
 }  // namespace switches
 
 #endif  // FUCHSIA_ENGINE_SWITCHES_H_
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 bde94d3..018cdbd 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 @@
-2e70359a2f65abe783393044df9035e2e62e9435
\ No newline at end of file
+107b86543d4590561ee78fd325cfa43432950e0c
\ 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 f4d3ac1b..70c2881 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 @@
-fd1950c178269ca145bd476ef49b835ae13a1210
\ No newline at end of file
+2dde7818365e13a68254d3fc5b51ce976ef5241a
\ 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 89cf27f0..3f36d51 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 @@
-bd8783ac9236d5e312417d9d3847874198b89344
\ No newline at end of file
+caad13a3e31e7be41532f338e7acf8e79f563024
\ 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 5089c3ff..50886c0cc 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 @@
-7318b45a159d95a82c38b739124b9497f262c5ab
\ No newline at end of file
+f4692362b3eded8cdf8977fa16f7ce3078075726
\ 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 e410881..e7c53ad 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 @@
-1a1f781a5343368ef49fc390c2c526d4be3edaa3
\ No newline at end of file
+73df533b644903b48c71931a8a7f32648e3ddaf2
\ 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 006e048..257c738f2 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 @@
-cd187f7e5b1ebb8324b2b50b494a1d698ae7cbd2
\ No newline at end of file
+9dc90738ee8b434b235ac81e83089f7c45016e91
\ 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 8291faab..9fe122d 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 @@
-27a7ec540cf98de940912f1c238258fa9e85f43b
\ No newline at end of file
+750a4c2a02668526966a190ebfd61e43e49c0988
\ 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 1ab74e5d..2b857e09 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 @@
-5289996c4dc6a18b53a184e30552ac3c3b0d0eb1
\ No newline at end of file
+c671f6dd5161862879a3c0fd62837678e76a9623
\ 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 a9028d00..b0f69ba 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 @@
-a0a065aa31a2f29119a781c29b4b239d92959c1f
\ No newline at end of file
+a58824c580aa0666ae2a1e801c59422813200642
\ 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 7408a14..4ecf484 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 @@
-a42a2ccbde5f3c7fb81d8fcbaad9916c0ca60f7e
\ No newline at end of file
+2fef576903e43a1855b872ed2a16332542c88c4f
\ No newline at end of file
diff --git a/media/gpu/BUILD.gn b/media/gpu/BUILD.gn
index a26415e..473d255e 100644
--- a/media/gpu/BUILD.gn
+++ b/media/gpu/BUILD.gn
@@ -297,6 +297,7 @@
     ]
   }
 
+  public_deps = []
   deps = [
     ":buildflags",
     "//base",
@@ -307,6 +308,17 @@
     "//ui/gfx:memory_buffer",
     "//ui/gfx/geometry",
   ]
+
+  if (is_ash && use_vaapi) {
+    assert(use_libgav1_parser)
+    sources += [
+      "av1_decoder.cc",
+      "av1_decoder.h",
+      "av1_picture.cc",
+      "av1_picture.h",
+    ]
+    public_deps += [ "//third_party/libgav1:libgav1" ]
+  }
 }
 
 source_set("command_buffer_helper") {
@@ -524,6 +536,12 @@
   if (use_v4l2_codec || use_vaapi) {
     sources += [ "vp8_decoder_unittest.cc" ]
   }
+
+  if (is_ash && use_vaapi) {
+    sources += [ "av1_decoder_unittest.cc" ]
+    deps += [ "//third_party/ffmpeg" ]
+  }
+
   if (is_win && enable_library_cdms) {
     sources += [
       "windows/d3d11_copying_texture_wrapper_unittest.cc",
diff --git a/media/gpu/av1_decoder.cc b/media/gpu/av1_decoder.cc
new file mode 100644
index 0000000..1dbbe50
--- /dev/null
+++ b/media/gpu/av1_decoder.cc
@@ -0,0 +1,450 @@
+// 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 "media/gpu/av1_decoder.h"
+
+#include <bitset>
+
+#include "base/callback_helpers.h"
+#include "base/logging.h"
+#include "base/memory/ptr_util.h"
+#include "base/stl_util.h"
+#include "media/base/limits.h"
+#include "media/gpu/av1_picture.h"
+#include "third_party/libgav1/src/src/decoder_state.h"
+#include "third_party/libgav1/src/src/gav1/status_code.h"
+#include "third_party/libgav1/src/src/utils/constants.h"
+
+namespace media {
+namespace {
+// (Section 6.4.1):
+//
+// - "An operating point specifies which spatial and temporal layers should be
+//   decoded."
+//
+// - "The order of operating points indicates the preferred order for producing
+//   an output: a decoder should select the earliest operating point in the list
+//   that meets its decoding capabilities as expressed by the level associated
+//   with each operating point."
+//
+// For simplicity, we always select operating point 0 and will validate that it
+// doesn't have scalability information.
+constexpr unsigned int kDefaultOperatingPoint = 0;
+
+// Conversion function from libgav1 profiles to media::VideoCodecProfile.
+VideoCodecProfile AV1ProfileToVideoCodecProfile(
+    libgav1::BitstreamProfile profile) {
+  switch (profile) {
+    case libgav1::kProfile0:
+      return AV1PROFILE_PROFILE_MAIN;
+    case libgav1::kProfile1:
+      return AV1PROFILE_PROFILE_HIGH;
+    case libgav1::kProfile2:
+      return AV1PROFILE_PROFILE_PRO;
+    default:
+      // ObuParser::ParseSequenceHeader() validates the profile.
+      NOTREACHED() << "Invalid profile: " << base::strict_cast<int>(profile);
+      return AV1PROFILE_PROFILE_MAIN;
+  }
+}
+
+// Returns true iff the sequence has spatial or temporal scalability information
+// for the selected operating point.
+bool SequenceUsesScalability(int operating_point_idc) {
+  return operating_point_idc != 0;
+}
+
+bool IsYUV420Sequence(const libgav1::ColorConfig& color_config) {
+  return color_config.subsampling_x == 1 && color_config.subsampling_y == 1 &&
+         !color_config.is_monochrome;
+}
+}  // namespace
+
+AV1Decoder::AV1Decoder(std::unique_ptr<AV1Accelerator> accelerator,
+                       VideoCodecProfile profile)
+    : buffer_pool_(std::make_unique<libgav1::BufferPool>(
+          /*on_frame_buffer_size_changed=*/nullptr,
+          /*get_frame_buffer=*/nullptr,
+          /*release_frame_buffer=*/nullptr,
+          /*callback_private_data=*/nullptr)),
+      state_(std::make_unique<libgav1::DecoderState>()),
+      accelerator_(std::move(accelerator)),
+      profile_(profile) {
+  ref_frames_.fill(nullptr);
+}
+
+AV1Decoder::~AV1Decoder() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  // |buffer_pool_| checks that all the allocated frames are released in its
+  // dtor. Explicitly destruct |state_| before |buffer_pool_| to release frames
+  // in |reference_frame| in |state_|.
+  state_.reset();
+}
+
+bool AV1Decoder::HasNewSequenceHeader() const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(parser_);
+  const auto& obu_headers = parser_->obu_headers();
+  const bool has_sequence_header =
+      std::find_if(obu_headers.begin(), obu_headers.end(),
+                   [](const auto& obu_header) {
+                     return obu_header.type == libgav1::kObuSequenceHeader;
+                   }) != obu_headers.end();
+  if (!has_sequence_header)
+    return false;
+  if (!current_sequence_header_)
+    return true;
+  return parser_->sequence_header().ParametersChanged(
+      *current_sequence_header_);
+}
+
+bool AV1Decoder::Flush() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DVLOG(2) << "Decoder flush";
+  Reset();
+  return true;
+}
+
+void AV1Decoder::Reset() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  ClearCurrentFrame();
+  // Resetting current sequence header might not be necessary. But this violates
+  // AcceleratedVideoDecoder interface spec, "Reset any current state that may
+  // be cached in the accelerator."
+  // TODO(hiroh): We may want to change this interface spec so that a caller
+  // doesn't have to allocate video frames buffers every seek operation.
+  current_sequence_header_.reset();
+  visible_rect_ = gfx::Rect();
+  frame_size_ = gfx::Size();
+  profile_ = VideoCodecProfile::VIDEO_CODEC_PROFILE_UNKNOWN;
+  stream_id_ = 0;
+  stream_ = nullptr;
+  stream_size_ = 0;
+  on_error_ = false;
+
+  state_ = std::make_unique<libgav1::DecoderState>();
+  ClearReferenceFrames();
+  parser_.reset();
+
+  buffer_pool_ = std::make_unique<libgav1::BufferPool>(
+      /*on_frame_buffer_size_changed=*/nullptr,
+      /*get_frame_buffer=*/nullptr,
+      /*release_frame_buffer=*/nullptr,
+      /*callback_private_data=*/nullptr);
+}
+
+void AV1Decoder::SetStream(int32_t id, const DecoderBuffer& decoder_buffer) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  stream_id_ = id;
+  stream_ = decoder_buffer.data();
+  stream_size_ = decoder_buffer.data_size();
+  ClearCurrentFrame();
+
+  parser_ = base::WrapUnique(new (std::nothrow) libgav1::ObuParser(
+      decoder_buffer.data(), decoder_buffer.data_size(), kDefaultOperatingPoint,
+      buffer_pool_.get(), state_.get()));
+  if (!parser_) {
+    on_error_ = true;
+    return;
+  }
+
+  if (current_sequence_header_)
+    parser_->set_sequence_header(*current_sequence_header_);
+}
+
+void AV1Decoder::ClearCurrentFrame() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  current_frame_.reset();
+  current_frame_header_.reset();
+}
+
+AcceleratedVideoDecoder::DecodeResult AV1Decoder::Decode() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (on_error_)
+    return kDecodeError;
+  auto result = DecodeInternal();
+  on_error_ = result == kDecodeError;
+  return result;
+}
+
+AcceleratedVideoDecoder::DecodeResult AV1Decoder::DecodeInternal() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (!parser_) {
+    DLOG(ERROR) << "Decode() is called before SetStream()";
+    return kDecodeError;
+  }
+  while (parser_->HasData() || current_frame_header_) {
+    base::ScopedClosureRunner clear_current_frame(
+        base::BindOnce(&AV1Decoder::ClearCurrentFrame, base::Unretained(this)));
+    if (!current_frame_header_) {
+      libgav1::StatusCode status_code = parser_->ParseOneFrame(&current_frame_);
+      if (status_code != libgav1::kStatusOk) {
+        DLOG(WARNING) << "Failed to parse OBU: "
+                      << libgav1::GetErrorString(status_code);
+        return kDecodeError;
+      }
+      if (!current_frame_) {
+        DLOG(WARNING) << "No frame found. Skipping the current stream";
+        continue;
+      }
+
+      current_frame_header_ = parser_->frame_header();
+      // Detects if a new coded video sequence is starting.
+      // TODO(b/171853869): Replace HasNewSequenceHeader() with whatever
+      // libgav1::ObuParser provides for more than one sequence headers case.
+      if (HasNewSequenceHeader()) {
+        // TODO(b/171853869): Remove this check once libgav1::ObuParser does
+        // this check.
+        if (current_frame_header_->frame_type != libgav1::kFrameKey ||
+            !current_frame_header_->show_frame ||
+            current_frame_header_->show_existing_frame ||
+            current_frame_->temporal_id() != 0) {
+          // Section 7.5.
+          DVLOG(1)
+              << "The first frame successive to sequence header OBU must be a "
+              << "keyframe with show_frame=1, show_existing_frame=0 and "
+              << "temporal_id=0";
+          return kDecodeError;
+        }
+        if (SequenceUsesScalability(
+                parser_->sequence_header()
+                    .operating_point_idc[kDefaultOperatingPoint])) {
+          DVLOG(3) << "Either temporal or spatial layer decoding is not "
+                   << "supported";
+          return kDecodeError;
+        }
+
+        current_sequence_header_ = parser_->sequence_header();
+        // TODO(hiroh): Expose the bit depth to let the AV1Decoder client
+        // perform these checks.
+        if (current_sequence_header_->color_config.bitdepth != 8) {
+          // TODO(hiroh): Handle 10/12 bit depth.
+          DVLOG(1) << "10/12 bit depth are not supported";
+          return kDecodeError;
+        }
+        if (!IsYUV420Sequence(current_sequence_header_->color_config)) {
+          DVLOG(1) << "Only YUV 4:2:0 is supported";
+          return kDecodeError;
+        }
+
+        const gfx::Size new_frame_size(
+            base::strict_cast<int>(current_sequence_header_->max_frame_width),
+            base::strict_cast<int>(current_sequence_header_->max_frame_height));
+        gfx::Rect new_visible_rect(
+            base::strict_cast<int>(current_frame_header_->render_width),
+            base::strict_cast<int>(current_frame_header_->render_height));
+        const VideoCodecProfile new_profile =
+            AV1ProfileToVideoCodecProfile(current_sequence_header_->profile);
+        DCHECK(!new_frame_size.IsEmpty());
+        if (!gfx::Rect(new_frame_size).Contains(new_visible_rect)) {
+          DVLOG(1) << "Render size exceeds picture size. render size: "
+                   << new_visible_rect.ToString()
+                   << ", picture size: " << new_frame_size.ToString();
+          new_visible_rect = gfx::Rect(new_frame_size);
+        }
+
+        ClearReferenceFrames();
+        // Issues kConfigChange only if either the dimensions or profile is
+        // changed.
+        if (frame_size_ != new_frame_size ||
+            visible_rect_ != new_visible_rect || profile_ != new_profile) {
+          frame_size_ = new_frame_size;
+          visible_rect_ = new_visible_rect;
+          profile_ = new_profile;
+          clear_current_frame.ReplaceClosure(base::DoNothing());
+          return kConfigChange;
+        }
+      }
+    }
+
+    if (!current_sequence_header_) {
+      // Decoding is not doable because we haven't received a sequence header.
+      // This occurs when seeking a video.
+      DVLOG(3) << "Discarded the current frame because no sequence header has "
+               << "been found yet";
+      continue;
+    }
+
+    DCHECK(current_frame_header_);
+    const auto& frame_header = *current_frame_header_;
+    if (frame_header.show_existing_frame) {
+      const size_t frame_to_show =
+          base::checked_cast<size_t>(frame_header.frame_to_show);
+      DCHECK_LE(0u, frame_to_show);
+      DCHECK_LT(frame_to_show, ref_frames_.size());
+      if (!CheckAndCleanUpReferenceFrames()) {
+        DLOG(ERROR) << "The states of reference frames are different between"
+                    << "|ref_frames_| and |state_->reference_frame|";
+        return kDecodeError;
+      }
+
+      auto pic = ref_frames_[frame_to_show];
+      CHECK(pic);
+      pic = pic->Duplicate();
+      if (!pic) {
+        DVLOG(1) << "Failed duplication";
+        return kDecodeError;
+      }
+
+      pic->set_bitstream_id(stream_id_);
+      if (!accelerator_->OutputPicture(*pic)) {
+        return kDecodeError;
+      }
+
+      // libgav1::ObuParser sets |current_frame_| to the frame to show while
+      // |current_frame_header_| is the frame header of the currently parsed
+      // frame. If |current_frame_| is a keyframe, then refresh_frame_flags must
+      // be 0xff. Otherwise, refresh_frame_flags must be 0x00 (Section 5.9.2).
+      DCHECK(current_frame_->frame_type() == libgav1::kFrameKey ||
+             current_frame_header_->refresh_frame_flags == 0x00);
+      DCHECK(current_frame_->frame_type() != libgav1::kFrameKey ||
+             current_frame_header_->refresh_frame_flags == 0xff);
+      UpdateReferenceFrames(std::move(pic));
+      continue;
+    }
+
+    if (parser_->tile_buffers().empty()) {
+      // The last call to ParseOneFrame() didn't actually have any tile groups.
+      // This could happen in rare cases (for example, if there is a Metadata
+      // OBU after the TileGroup OBU). Ignore this case.
+      continue;
+    }
+
+    const gfx::Size current_frame_size(
+        base::strict_cast<int>(frame_header.width),
+        base::strict_cast<int>(frame_header.height));
+    if (current_frame_size != frame_size_) {
+      // TODO(hiroh): This must be handled in decoding spatial layer.
+      DVLOG(1) << "Resolution change in the middle of video sequence (i.e."
+               << " between sequence headers) is not supported";
+      return kDecodeError;
+    }
+    if (current_frame_size.width() !=
+        base::strict_cast<int>(frame_header.upscaled_width)) {
+      DVLOG(1) << "Super resolution is not supported";
+      return kDecodeError;
+    }
+    const gfx::Rect current_visible_rect(
+        base::strict_cast<int>(frame_header.render_width),
+        base::strict_cast<int>(frame_header.render_height));
+    if (current_visible_rect != visible_rect_) {
+      // TODO(andrescj): Handle the visible rectangle change in the middle of
+      // video sequence.
+      DVLOG(1) << "Visible rectangle change in the middle of video sequence"
+               << "(i.e. between sequence headers) is not supported";
+      return kDecodeError;
+    }
+
+    auto pic = accelerator_->CreateAV1Picture(
+        frame_header.film_grain_params.apply_grain);
+    if (!pic) {
+      clear_current_frame.ReplaceClosure(base::DoNothing());
+      return kRanOutOfSurfaces;
+    }
+
+    pic->set_visible_rect(current_visible_rect);
+    pic->set_bitstream_id(stream_id_);
+    pic->frame_header = frame_header;
+    // TODO(hiroh): Set color space and decrypt config.
+    if (!DecodeAndOutputPicture(std::move(pic), parser_->tile_buffers()))
+      return kDecodeError;
+  }
+  return kRanOutOfStreamData;
+}
+
+void AV1Decoder::UpdateReferenceFrames(scoped_refptr<AV1Picture> pic) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(state_);
+  DCHECK(current_frame_header_);
+  const uint8_t refresh_frame_flags =
+      current_frame_header_->refresh_frame_flags;
+  DCHECK(current_frame_->frame_type() != libgav1::kFrameKey ||
+         current_frame_header_->refresh_frame_flags == 0xff);
+  const std::bitset<libgav1::kNumReferenceFrameTypes> update_reference_frame(
+      refresh_frame_flags);
+  for (size_t i = 0; i < libgav1::kNumReferenceFrameTypes; ++i) {
+    if (update_reference_frame[i])
+      ref_frames_[i] = pic;
+  }
+  state_->UpdateReferenceFrames(current_frame_,
+                                base::strict_cast<int>(refresh_frame_flags));
+}
+
+void AV1Decoder::ClearReferenceFrames() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(state_);
+  ref_frames_.fill(nullptr);
+  // If AV1Decoder has decided to clear the reference frames, then ObuParser
+  // must have also decided to do so.
+  DCHECK_EQ(base::STLCount(state_->reference_frame, nullptr),
+            static_cast<int>(state_->reference_frame.size()));
+}
+
+bool AV1Decoder::CheckAndCleanUpReferenceFrames() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(state_);
+  const auto& reference_valid = state_->reference_valid;
+  for (size_t i = 0; i < libgav1::kNumReferenceFrameTypes; ++i) {
+    if (reference_valid[i] && !ref_frames_[i])
+      return false;
+    if (!reference_valid[i] && ref_frames_[i])
+      ref_frames_[i].reset();
+  }
+  return true;
+}
+
+bool AV1Decoder::DecodeAndOutputPicture(
+    scoped_refptr<AV1Picture> pic,
+    const libgav1::Vector<libgav1::TileBuffer>& tile_buffers) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(pic);
+  DCHECK(current_sequence_header_);
+  DCHECK(stream_);
+  DCHECK_GT(stream_size_, 0u);
+  if (!CheckAndCleanUpReferenceFrames()) {
+    DLOG(ERROR) << "The states of reference frames are different between"
+                << "|ref_frames_| and |state_->reference_frame|";
+    return false;
+  }
+  if (!accelerator_->SubmitDecode(*pic, *current_sequence_header_, ref_frames_,
+                                  tile_buffers,
+                                  base::make_span(stream_, stream_size_))) {
+    return false;
+  }
+
+  if (pic->frame_header.show_frame && !accelerator_->OutputPicture(*pic))
+    return false;
+  UpdateReferenceFrames(std::move(pic));
+  return true;
+}
+
+gfx::Size AV1Decoder::GetPicSize() const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  // TODO(hiroh): It should be safer to align this by 64 or 128 (depending on
+  // use_128x128_superblock) so that a driver doesn't touch out of the buffer.
+  return frame_size_;
+}
+
+gfx::Rect AV1Decoder::GetVisibleRect() const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  return visible_rect_;
+}
+
+VideoCodecProfile AV1Decoder::GetProfile() const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  return profile_;
+}
+
+size_t AV1Decoder::GetRequiredNumOfPictures() const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  // TODO(hiroh): Double this value in the case of film grain sequence.
+  constexpr size_t kPicsInPipeline = limits::kMaxVideoFrames + 1;
+  return kPicsInPipeline + GetNumReferenceFrames();
+}
+
+size_t AV1Decoder::GetNumReferenceFrames() const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  return libgav1::kNumReferenceFrameTypes;
+}
+}  // namespace media
diff --git a/media/gpu/av1_decoder.h b/media/gpu/av1_decoder.h
new file mode 100644
index 0000000..a646df39
--- /dev/null
+++ b/media/gpu/av1_decoder.h
@@ -0,0 +1,150 @@
+// 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 MEDIA_GPU_AV1_DECODER_H_
+#define MEDIA_GPU_AV1_DECODER_H_
+
+#include <array>
+#include <memory>
+
+#include "base/containers/span.h"
+#include "base/macros.h"
+#include "base/sequence_checker.h"
+#include "media/base/video_codecs.h"
+#include "media/base/video_color_space.h"
+#include "media/gpu/accelerated_video_decoder.h"
+#include "media/gpu/media_gpu_export.h"
+#include "third_party/libgav1/src/src/utils/constants.h"
+
+// For libgav1::RefCountedBufferPtr.
+#include "third_party/libgav1/src/src/buffer_pool.h"
+// For libgav1::ObuSequenceHeader. base::Optional demands ObuSequenceHeader to
+// fulfill std::is_trivially_constructible if it is forward-declared. But
+// ObuSequenceHeader doesn't.
+#include "third_party/libgav1/src/src/obu_parser.h"
+
+namespace libgav1 {
+struct DecoderState;
+struct ObuFrameHeader;
+template <typename T>
+class Vector;
+}  // namespace libgav1
+
+namespace media {
+class AV1Picture;
+using AV1ReferenceFrameVector =
+    std::array<scoped_refptr<AV1Picture>, libgav1::kNumReferenceFrameTypes>;
+
+// Clients of this class are expected to pass an AV1 OBU stream and are expected
+// to provide an implementation of AV1Accelerator for offloading final steps
+// of the decoding process.
+//
+// This class must be created, called and destroyed on a single thread, and
+// does nothing internally on any other thread.
+class MEDIA_GPU_EXPORT AV1Decoder : public AcceleratedVideoDecoder {
+ public:
+  class MEDIA_GPU_EXPORT AV1Accelerator {
+   public:
+    AV1Accelerator() = default;
+    virtual ~AV1Accelerator() = default;
+    AV1Accelerator(const AV1Accelerator&) = delete;
+    AV1Accelerator& operator=(const AV1Accelerator&) = delete;
+
+    // Creates an AV1Picture that the AV1Decoder can use to store some of the
+    // information needed to request accelerated decoding. This picture is later
+    // passed when calling SubmitDecode() so that the AV1Accelerator can submit
+    // the decode request to the driver. It may also be stored for use as
+    // reference to decode other pictures.
+    // When a picture is no longer needed by the decoder, it will just drop
+    // its reference to it, and it may do so at any time.
+    // Note that this may return nullptr if the accelerator is not able to
+    // provide any new pictures at the given time. The decoder must handle this
+    // case and treat it as normal, returning kRanOutOfSurfaces from Decode().
+    virtual scoped_refptr<AV1Picture> CreateAV1Picture(bool apply_grain) = 0;
+
+    // Submits |pic| to the driver for accelerated decoding. The following
+    // parameters are also passed:
+    // - |sequence_header|: the current OBU sequence header.
+    // - |ref_frames|: the pictures used as reference for decoding |pic|.
+    // - |tile_buffers|: tile information.
+    // - |data|: the entire data of the DecoderBuffer set by
+    //           AV1Decoder::SetStream().
+    // Note that returning from this method does not mean that the decode
+    // process is finished, but the caller may drop its references to |pic|
+    // and |ref_frames| immediately, and |data| does not need to remain valid
+    // after this method returns.
+    // Returns true when successful, false otherwise.
+    virtual bool SubmitDecode(
+        const AV1Picture& pic,
+        const libgav1::ObuSequenceHeader& sequence_header,
+        const AV1ReferenceFrameVector& ref_frames,
+        const libgav1::Vector<libgav1::TileBuffer>& tile_buffers,
+        base::span<const uint8_t> data) = 0;
+
+    // Schedules output (display) of |pic|.
+    // Note that returning from this method does not mean that |pic| has already
+    // been outputted (displayed), but guarantees that all pictures will be
+    // outputted in the same order as this method was called for them, and that
+    // they are decoded before outputting (assuming SubmitDecode() has been
+    // called for them beforehand).
+    // Returns true when successful, false otherwise.
+    virtual bool OutputPicture(const AV1Picture& pic) = 0;
+  };
+
+  AV1Decoder(std::unique_ptr<AV1Accelerator> accelerator,
+             VideoCodecProfile profile);
+  ~AV1Decoder() override;
+  AV1Decoder(const AV1Decoder&) = delete;
+  AV1Decoder& operator=(const AV1Decoder&) = delete;
+
+  // AcceleratedVideoDecoder implementation.
+  void SetStream(int32_t id, const DecoderBuffer& decoder_buffer) override;
+  bool Flush() override WARN_UNUSED_RESULT;
+  void Reset() override;
+  DecodeResult Decode() override WARN_UNUSED_RESULT;
+  gfx::Size GetPicSize() const override;
+  gfx::Rect GetVisibleRect() const override;
+  VideoCodecProfile GetProfile() const override;
+  size_t GetRequiredNumOfPictures() const override;
+  size_t GetNumReferenceFrames() const override;
+
+ private:
+  // Returns whether the current stream contains a new OBU sequence header.
+  bool HasNewSequenceHeader() const;
+  bool DecodeAndOutputPicture(
+      scoped_refptr<AV1Picture> pic,
+      const libgav1::Vector<libgav1::TileBuffer>& tile_buffers);
+  void UpdateReferenceFrames(scoped_refptr<AV1Picture> pic);
+  void ClearReferenceFrames();
+  bool CheckAndCleanUpReferenceFrames();
+  void ClearCurrentFrame();
+  DecodeResult DecodeInternal();
+
+  bool on_error_ = false;
+
+  std::unique_ptr<libgav1::BufferPool> buffer_pool_;
+  std::unique_ptr<libgav1::DecoderState> state_;
+  std::unique_ptr<libgav1::ObuParser> parser_;
+
+  const std::unique_ptr<AV1Accelerator> accelerator_;
+  AV1ReferenceFrameVector ref_frames_;
+
+  base::Optional<libgav1::ObuSequenceHeader> current_sequence_header_;
+  base::Optional<libgav1::ObuFrameHeader> current_frame_header_;
+  libgav1::RefCountedBufferPtr current_frame_;
+
+  gfx::Rect visible_rect_;
+  gfx::Size frame_size_;
+  VideoCodecProfile profile_;
+
+  int32_t stream_id_ = 0;
+  const uint8_t* stream_ = nullptr;
+  size_t stream_size_ = 0;
+
+  SEQUENCE_CHECKER(sequence_checker_);
+};
+
+}  // namespace media
+
+#endif  // MEDIA_GPU_AV1_DECODER_H_
diff --git a/media/gpu/av1_decoder_unittest.cc b/media/gpu/av1_decoder_unittest.cc
new file mode 100644
index 0000000..1878d8f
--- /dev/null
+++ b/media/gpu/av1_decoder_unittest.cc
@@ -0,0 +1,392 @@
+// 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 "media/gpu/av1_decoder.h"
+
+#include <string.h>
+
+#include <string>
+#include <vector>
+
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "base/numerics/safe_conversions.h"
+#include "media/base/decoder_buffer.h"
+#include "media/base/test_data_util.h"
+#include "media/ffmpeg/ffmpeg_common.h"
+#include "media/filters/ffmpeg_demuxer.h"
+#include "media/filters/in_memory_url_protocol.h"
+#include "media/filters/ivf_parser.h"
+#include "media/gpu/av1_picture.h"
+#include "media/media_buildflags.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/libgav1/src/src/obu_parser.h"
+#include "third_party/libgav1/src/src/utils/constants.h"
+#include "third_party/libgav1/src/src/utils/types.h"
+
+#if !BUILDFLAG(USE_CHROMEOS_MEDIA_ACCELERATION)
+#error "This test requires Chrome OS media acceleration"
+#endif
+#include "media/gpu/chromeos/fourcc.h"
+
+using ::testing::_;
+using ::testing::Return;
+
+namespace media {
+namespace {
+
+class FakeAV1Picture : public AV1Picture {
+ public:
+  FakeAV1Picture() = default;
+
+ protected:
+  ~FakeAV1Picture() override = default;
+
+ private:
+  scoped_refptr<AV1Picture> CreateDuplicate() override {
+    return base::MakeRefCounted<FakeAV1Picture>();
+  }
+};
+
+bool IsYUV420(int8_t subsampling_x, int8_t subsampling_y, bool is_monochrome) {
+  return subsampling_x == 1 && subsampling_y == 1 && !is_monochrome;
+}
+
+MATCHER_P(SameAV1PictureInstance, av1_picture, "") {
+  return &arg == av1_picture.get();
+}
+
+MATCHER_P2(MatchesFrameSizeAndRenderSize, frame_size, render_size, "") {
+  const auto& frame_header = arg.frame_header;
+  return base::strict_cast<int>(frame_header.width) == frame_size.width() &&
+         base::strict_cast<int>(frame_header.height) == frame_size.height() &&
+         base::strict_cast<int>(frame_header.render_width) ==
+             render_size.width() &&
+         base::strict_cast<int>(frame_header.render_height) ==
+             render_size.height();
+}
+
+MATCHER_P4(MatchesFrameHeader,
+           frame_size,
+           render_size,
+           show_existing_frame,
+           show_frame,
+           "") {
+  const auto& frame_header = arg.frame_header;
+  return base::strict_cast<int>(frame_header.width) == frame_size.width() &&
+         base::strict_cast<int>(frame_header.height) == frame_size.height() &&
+         base::strict_cast<int>(frame_header.render_width) ==
+             render_size.width() &&
+         base::strict_cast<int>(frame_header.render_height) ==
+             render_size.height() &&
+         frame_header.show_existing_frame == show_existing_frame &&
+         frame_header.show_frame == show_frame;
+}
+
+MATCHER_P4(MatchesYUV420SequenceHeader,
+           profile,
+           bitdepth,
+           max_frame_size,
+           film_grain_params_present,
+           "") {
+  return arg.profile == profile && arg.color_config.bitdepth == bitdepth &&
+         base::strict_cast<int>(arg.max_frame_width) ==
+             max_frame_size.width() &&
+         base::strict_cast<int>(arg.max_frame_height) ==
+             max_frame_size.height() &&
+         arg.film_grain_params_present == film_grain_params_present &&
+         IsYUV420(arg.color_config.subsampling_x,
+                  arg.color_config.subsampling_y,
+                  arg.color_config.is_monochrome);
+}
+
+MATCHER(NonEmptyTileBuffers, "") {
+  return !arg.empty();
+}
+
+MATCHER_P(MatchesFrameData, decoder_buffer, "") {
+  return arg.data() == decoder_buffer->data() &&
+         arg.size() == decoder_buffer->data_size();
+}
+
+class MockAV1Accelerator : public AV1Decoder::AV1Accelerator {
+ public:
+  MockAV1Accelerator() = default;
+  ~MockAV1Accelerator() override = default;
+
+  MOCK_METHOD1(CreateAV1Picture, scoped_refptr<AV1Picture>(bool));
+  MOCK_METHOD5(SubmitDecode,
+               bool(const AV1Picture&,
+                    const libgav1::ObuSequenceHeader&,
+                    const AV1ReferenceFrameVector&,
+                    const libgav1::Vector<libgav1::TileBuffer>&,
+                    base::span<const uint8_t>));
+  MOCK_METHOD1(OutputPicture, bool(const AV1Picture&));
+};
+
+class AV1DecoderTest : public ::testing::Test {
+ public:
+  using DecodeResult = AcceleratedVideoDecoder::DecodeResult;
+
+  AV1DecoderTest() = default;
+  ~AV1DecoderTest() override = default;
+  void SetUp() override;
+  std::vector<DecodeResult> Decode(scoped_refptr<DecoderBuffer> buffer);
+  scoped_refptr<DecoderBuffer> ReadDecoderBuffer(const std::string& fname);
+  std::vector<scoped_refptr<DecoderBuffer>> ReadIVF(const std::string& fname);
+  std::vector<scoped_refptr<DecoderBuffer>> ReadWebm(const std::string& fname);
+
+ protected:
+  base::FilePath GetTestFilePath(const std::string& fname) {
+    base::FilePath file_path(base::FilePath(base::FilePath::kCurrentDirectory)
+                                 .Append(base::FilePath::StringType(fname)));
+    if (base::PathExists(file_path)) {
+      return file_path;
+    }
+    return GetTestDataFilePath(fname);
+  }
+
+  // Owned by |decoder_|.
+  MockAV1Accelerator* mock_accelerator_;
+
+  std::unique_ptr<AV1Decoder> decoder_;
+  int32_t bitstream_id_ = 0;
+};
+
+void AV1DecoderTest::SetUp() {
+  auto accelerator = std::make_unique<MockAV1Accelerator>();
+  mock_accelerator_ = accelerator.get();
+  decoder_ = std::make_unique<AV1Decoder>(std::move(accelerator),
+                                          VIDEO_CODEC_PROFILE_UNKNOWN);
+}
+
+std::vector<AcceleratedVideoDecoder::DecodeResult> AV1DecoderTest::Decode(
+    scoped_refptr<DecoderBuffer> buffer) {
+  decoder_->SetStream(bitstream_id_++, *buffer);
+
+  std::vector<DecodeResult> results;
+  DecodeResult res;
+  do {
+    res = decoder_->Decode();
+    results.push_back(res);
+  } while (res != DecodeResult::kDecodeError &&
+           res != DecodeResult::kRanOutOfStreamData);
+  return results;
+}  // namespace
+
+scoped_refptr<DecoderBuffer> AV1DecoderTest::ReadDecoderBuffer(
+    const std::string& fname) {
+  auto input_file = GetTestFilePath(fname);
+  std::string bitstream;
+
+  EXPECT_TRUE(base::ReadFileToString(input_file, &bitstream));
+  auto buffer = DecoderBuffer::CopyFrom(
+      reinterpret_cast<const uint8_t*>(bitstream.data()), bitstream.size());
+  EXPECT_TRUE(!!buffer);
+  return buffer;
+}
+
+std::vector<scoped_refptr<DecoderBuffer>> AV1DecoderTest::ReadIVF(
+    const std::string& fname) {
+  std::string ivf_data;
+  auto input_file = GetTestFilePath(fname);
+  EXPECT_TRUE(base::ReadFileToString(input_file, &ivf_data));
+
+  IvfParser ivf_parser;
+  IvfFileHeader ivf_header{};
+  EXPECT_TRUE(
+      ivf_parser.Initialize(reinterpret_cast<const uint8_t*>(ivf_data.data()),
+                            ivf_data.size(), &ivf_header));
+  EXPECT_EQ(ivf_header.fourcc, ComposeFourcc('A', 'V', '0', '1'));
+
+  std::vector<scoped_refptr<DecoderBuffer>> buffers;
+  IvfFrameHeader ivf_frame_header{};
+  const uint8_t* data;
+  while (ivf_parser.ParseNextFrame(&ivf_frame_header, &data)) {
+    buffers.push_back(DecoderBuffer::CopyFrom(
+        reinterpret_cast<const uint8_t*>(data), ivf_frame_header.frame_size));
+  }
+  return buffers;
+}
+
+std::vector<scoped_refptr<DecoderBuffer>> AV1DecoderTest::ReadWebm(
+    const std::string& fname) {
+  std::string webm_data;
+  auto input_file = GetTestFilePath(fname);
+  EXPECT_TRUE(base::ReadFileToString(input_file, &webm_data));
+
+  InMemoryUrlProtocol protocol(
+      reinterpret_cast<const uint8_t*>(webm_data.data()), webm_data.size(),
+      false);
+  FFmpegGlue glue(&protocol);
+  LOG_ASSERT(glue.OpenContext());
+  int stream_index = -1;
+  for (unsigned int i = 0; i < glue.format_context()->nb_streams; ++i) {
+    const AVStream* stream = glue.format_context()->streams[i];
+    const AVCodecParameters* codec_parameters = stream->codecpar;
+    const AVMediaType codec_type = codec_parameters->codec_type;
+    const AVCodecID codec_id = codec_parameters->codec_id;
+    if (codec_type == AVMEDIA_TYPE_VIDEO && codec_id == AV_CODEC_ID_AV1) {
+      stream_index = i;
+      break;
+    }
+  }
+  EXPECT_NE(stream_index, -1) << "No AV1 data found in " << input_file;
+
+  std::vector<scoped_refptr<DecoderBuffer>> buffers;
+  AVPacket packet{};
+  while (av_read_frame(glue.format_context(), &packet) >= 0) {
+    if (packet.stream_index == stream_index)
+      buffers.push_back(DecoderBuffer::CopyFrom(packet.data, packet.size));
+    av_packet_unref(&packet);
+  }
+  return buffers;
+}
+
+TEST_F(AV1DecoderTest, DecodeInvalidOBU) {
+  std::string kInvalidData = "ThisIsInvalidData";
+  auto kInvalidBuffer = DecoderBuffer::CopyFrom(
+      reinterpret_cast<const uint8_t*>(kInvalidData.data()),
+      kInvalidData.size());
+  std::vector<DecodeResult> results = Decode(kInvalidBuffer);
+  std::vector<DecodeResult> expected = {DecodeResult::kDecodeError};
+  EXPECT_EQ(results, expected);
+}
+
+TEST_F(AV1DecoderTest, DecodeEmptyOBU) {
+  auto kEmptyBuffer = base::MakeRefCounted<DecoderBuffer>(0);
+  std::vector<DecodeResult> results = Decode(kEmptyBuffer);
+  std::vector<DecodeResult> expected = {DecodeResult::kRanOutOfStreamData};
+  EXPECT_EQ(results, expected);
+}
+
+TEST_F(AV1DecoderTest, DecodeOneIFrame) {
+  constexpr gfx::Size kFrameSize(320, 240);
+  constexpr gfx::Size kRenderSize(320, 240);
+  constexpr auto kProfile = libgav1::BitstreamProfile::kProfile0;
+  const std::string kIFrame("av1-I-frame-320x240");
+  scoped_refptr<DecoderBuffer> i_frame_buffer = ReadDecoderBuffer(kIFrame);
+  ASSERT_TRUE(!!i_frame_buffer);
+  auto av1_picture = base::MakeRefCounted<AV1Picture>();
+  ::testing::InSequence s;
+  EXPECT_CALL(*mock_accelerator_, CreateAV1Picture(/*apply_grain=*/false))
+      .WillOnce(Return(av1_picture));
+  EXPECT_CALL(
+      *mock_accelerator_,
+      SubmitDecode(
+          MatchesFrameHeader(kFrameSize, kRenderSize,
+                             /*show_existing_frame=*/false,
+                             /*show_frame=*/true),
+          MatchesYUV420SequenceHeader(kProfile, /*bitdepth=*/8, kFrameSize,
+                                      /*film_grain_params_present=*/false),
+          _, NonEmptyTileBuffers(), MatchesFrameData(i_frame_buffer)))
+      .WillOnce(Return(true));
+  EXPECT_CALL(*mock_accelerator_,
+              OutputPicture(SameAV1PictureInstance(av1_picture)))
+      .WillOnce(Return(true));
+  std::vector<DecodeResult> results = Decode(i_frame_buffer);
+  std::vector<DecodeResult> expected = {DecodeResult::kConfigChange,
+                                        DecodeResult::kRanOutOfStreamData};
+  EXPECT_EQ(results, expected);
+}
+
+TEST_F(AV1DecoderTest, DecodeSimpleStream) {
+  constexpr gfx::Size kFrameSize(320, 240);
+  constexpr gfx::Size kRenderSize(320, 240);
+  constexpr auto kProfile = libgav1::BitstreamProfile::kProfile0;
+  const std::string kSimpleStream("bear-av1.webm");
+  std::vector<scoped_refptr<DecoderBuffer>> buffers = ReadWebm(kSimpleStream);
+  ASSERT_FALSE(buffers.empty());
+  std::vector<DecodeResult> expected = {DecodeResult::kConfigChange};
+  std::vector<DecodeResult> results;
+  for (auto buffer : buffers) {
+    ::testing::InSequence sequence;
+    auto av1_picture = base::MakeRefCounted<AV1Picture>();
+    EXPECT_CALL(*mock_accelerator_, CreateAV1Picture(/*apply_grain=*/false))
+        .WillOnce(Return(av1_picture));
+    EXPECT_CALL(
+        *mock_accelerator_,
+        SubmitDecode(
+            MatchesFrameHeader(kFrameSize, kRenderSize,
+                               /*show_existing_frame=*/false,
+                               /*show_frame=*/true),
+            MatchesYUV420SequenceHeader(kProfile, /*bitdepth=*/8, kFrameSize,
+                                        /*film_grain_params_present=*/false),
+            _, NonEmptyTileBuffers(), MatchesFrameData(buffer)))
+        .WillOnce(Return(true));
+    EXPECT_CALL(*mock_accelerator_,
+                OutputPicture(SameAV1PictureInstance(av1_picture)))
+        .WillOnce(Return(true));
+    for (DecodeResult r : Decode(buffer))
+      results.push_back(r);
+    expected.push_back(DecodeResult::kRanOutOfStreamData);
+    testing::Mock::VerifyAndClearExpectations(mock_accelerator_);
+  }
+  EXPECT_EQ(results, expected);
+}
+
+TEST_F(AV1DecoderTest, DecodeShowExistingPictureStream) {
+  constexpr gfx::Size kFrameSize(208, 144);
+  constexpr gfx::Size kRenderSize(208, 144);
+  constexpr auto kProfile = libgav1::BitstreamProfile::kProfile0;
+  constexpr size_t kDecodedFrames = 10;
+  constexpr size_t kOutputFrames = 10;
+  const std::string kShowExistingFrameStream("av1-show_existing_frame.ivf");
+  std::vector<scoped_refptr<DecoderBuffer>> buffers =
+      ReadIVF(kShowExistingFrameStream);
+  ASSERT_FALSE(buffers.empty());
+
+  // TODO(hiroh): Test what's unique about the show_existing_frame path.
+  std::vector<DecodeResult> expected = {DecodeResult::kConfigChange};
+  std::vector<DecodeResult> results;
+  EXPECT_CALL(*mock_accelerator_, CreateAV1Picture(/*apply_grain=*/false))
+      .Times(kDecodedFrames)
+      .WillRepeatedly(Return(base::MakeRefCounted<FakeAV1Picture>()));
+  EXPECT_CALL(
+      *mock_accelerator_,
+      SubmitDecode(
+          MatchesFrameSizeAndRenderSize(kFrameSize, kRenderSize),
+          MatchesYUV420SequenceHeader(kProfile, /*bitdepth=*/8, kFrameSize,
+                                      /*film_grain_params_present=*/false),
+          _, NonEmptyTileBuffers(), _))
+      .Times(kDecodedFrames)
+      .WillRepeatedly(Return(true));
+  EXPECT_CALL(*mock_accelerator_, OutputPicture(_))
+      .Times(kOutputFrames)
+      .WillRepeatedly(Return(true));
+
+  for (auto buffer : buffers) {
+    for (DecodeResult r : Decode(buffer))
+      results.push_back(r);
+    expected.push_back(DecodeResult::kRanOutOfStreamData);
+  }
+  EXPECT_EQ(results, expected);
+}
+
+TEST_F(AV1DecoderTest, Decode10bitStream) {
+  const std::string k10bitStream("bear-av1-320x180-10bit.webm");
+  std::vector<scoped_refptr<DecoderBuffer>> buffers = ReadWebm(k10bitStream);
+  ASSERT_FALSE(buffers.empty());
+  std::vector<DecodeResult> expected = {DecodeResult::kDecodeError};
+  EXPECT_EQ(Decode(buffers[0]), expected);
+  // Once AV1Decoder gets into an error state, Decode() returns kDecodeError
+  // until Reset().
+  EXPECT_EQ(Decode(buffers[1]), expected);
+}
+
+TEST_F(AV1DecoderTest, DecodeSVCStream) {
+  const std::string kSVCStream("av1-svc-L2T2.ivf");
+  std::vector<scoped_refptr<DecoderBuffer>> buffers = ReadIVF(kSVCStream);
+  ASSERT_FALSE(buffers.empty());
+  std::vector<DecodeResult> expected = {DecodeResult::kDecodeError};
+  EXPECT_EQ(Decode(buffers[0]), expected);
+  // Once AV1Decoder gets into an error state, Decode() returns kDecodeError
+  // until Reset().
+  EXPECT_EQ(Decode(buffers[1]), expected);
+}
+
+// TODO(hiroh): Add more tests, non-YUV420 stream, Reset() flow, mid-stream
+// configuration change, and reference frame tracking.
+}  // namespace
+}  // namespace media
diff --git a/media/gpu/av1_picture.cc b/media/gpu/av1_picture.cc
new file mode 100644
index 0000000..393f1072
--- /dev/null
+++ b/media/gpu/av1_picture.cc
@@ -0,0 +1,30 @@
+// 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 "media/gpu/av1_picture.h"
+#include <memory>
+
+namespace media {
+AV1Picture::AV1Picture() = default;
+AV1Picture::~AV1Picture() = default;
+
+scoped_refptr<AV1Picture> AV1Picture::Duplicate() {
+  scoped_refptr<AV1Picture> dup_pic = CreateDuplicate();
+  if (!dup_pic)
+    return nullptr;
+
+  // Copy members of AV1Picture and CodecPicture.
+  // A proper bitstream id is set in AV1Decoder.
+  // Note that decrypt_config_ is not used in here, so skip copying it.
+  dup_pic->frame_header = frame_header;
+  dup_pic->set_bitstream_id(bitstream_id());
+  dup_pic->set_visible_rect(visible_rect());
+  dup_pic->set_colorspace(get_colorspace());
+  return dup_pic;
+}
+
+scoped_refptr<AV1Picture> AV1Picture::CreateDuplicate() {
+  return nullptr;
+}
+}  // namespace media
diff --git a/media/gpu/av1_picture.h b/media/gpu/av1_picture.h
new file mode 100644
index 0000000..505c9cd
--- /dev/null
+++ b/media/gpu/av1_picture.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 MEDIA_GPU_AV1_PICTURE_H_
+#define MEDIA_GPU_AV1_PICTURE_H_
+
+#include <memory>
+
+#include "media/gpu/codec_picture.h"
+#include "media/gpu/media_gpu_export.h"
+#include "third_party/libgav1/src/src/utils/types.h"
+
+namespace media {
+// AV1Picture carries the parsed frame header needed for decoding an AV1 frame.
+// It also owns the decoded frame itself.
+class MEDIA_GPU_EXPORT AV1Picture : public CodecPicture {
+ public:
+  AV1Picture();
+  AV1Picture(const AV1Picture&) = delete;
+  AV1Picture& operator=(const AV1Picture&) = delete;
+
+  // Create a duplicate instance and copy the data to it. It is used to support
+  // the AV1 show_existing_frame feature. Return the scoped_refptr pointing to
+  // the duplicate instance, or nullptr on failure.
+  scoped_refptr<AV1Picture> Duplicate();
+
+  libgav1::ObuFrameHeader frame_header = {};
+
+ protected:
+  ~AV1Picture() override;
+
+ private:
+  // Create a duplicate instance.
+  virtual scoped_refptr<AV1Picture> CreateDuplicate();
+};
+}  // namespace media
+#endif  // MEDIA_GPU_AV1_PICTURE_H_
diff --git a/media/gpu/mac/vt_video_encode_accelerator_mac.cc b/media/gpu/mac/vt_video_encode_accelerator_mac.cc
index dce3bab..40301bf 100644
--- a/media/gpu/mac/vt_video_encode_accelerator_mac.cc
+++ b/media/gpu/mac/vt_video_encode_accelerator_mac.cc
@@ -7,6 +7,7 @@
 #include <memory>
 
 #include "base/logging.h"
+#include "base/mac/mac_logging.h"
 #include "base/memory/shared_memory_mapping.h"
 #include "base/memory/unsafe_shared_memory_region.h"
 #include "base/threading/thread_task_runner_handle.h"
@@ -142,6 +143,9 @@
   DCHECK(thread_checker_.CalledOnValidThread());
   DCHECK(client);
 
+  // Clients are expected to call Flush() before reinitializing the encoder.
+  DCHECK_EQ(pending_encodes_, 0);
+
   if (config.input_format != PIXEL_FORMAT_I420 &&
       config.input_format != PIXEL_FORMAT_NV12) {
     DLOG(ERROR) << "Input format not supported= "
@@ -254,6 +258,19 @@
   delete this;
 }
 
+void VTVideoEncodeAccelerator::Flush(FlushCallback flush_callback) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  encoder_thread_task_runner_->PostTask(
+      FROM_HERE,
+      base::BindOnce(&VTVideoEncodeAccelerator::FlushTask,
+                     base::Unretained(this), std::move(flush_callback)));
+}
+
+bool VTVideoEncodeAccelerator::IsFlushSupported() {
+  return true;
+}
+
 void VTVideoEncodeAccelerator::EncodeTask(scoped_refptr<VideoFrame> frame,
                                           bool force_keyframe) {
   DCHECK(encoder_thread_task_runner_->BelongsToCurrentThread());
@@ -284,12 +301,13 @@
   // We can pass the ownership of |request| to the encode callback if
   // successful. Otherwise let it fall out of scope.
   OSStatus status = VTCompressionSessionEncodeFrame(
-      compression_session_, pixel_buffer, timestamp_cm, CMTime{0, 0, 0, 0},
+      compression_session_, pixel_buffer, timestamp_cm, kCMTimeInvalid,
       frame_props, reinterpret_cast<void*>(request.get()), nullptr);
   if (status != noErr) {
     DLOG(ERROR) << " VTCompressionSessionEncodeFrame failed: " << status;
     NotifyError(kPlatformFailureError);
   } else {
+    ++pending_encodes_;
     CHECK(request.release());
   }
 }
@@ -360,8 +378,6 @@
 
   // Cancel all encoder thread callbacks.
   encoder_task_weak_factory_.InvalidateWeakPtrs();
-
-  // This call blocks until all pending frames are flushed out.
   DestroyCompressionSession();
 }
 
@@ -408,6 +424,9 @@
     std::unique_ptr<EncodeOutput> encode_output) {
   DCHECK(encoder_thread_task_runner_->BelongsToCurrentThread());
 
+  --pending_encodes_;
+  DCHECK_GE(pending_encodes_, 0);
+
   if (status != noErr) {
     DLOG(ERROR) << " encode failed: " << status;
     NotifyError(kPlatformFailureError);
@@ -440,6 +459,7 @@
         base::BindOnce(&Client::BitstreamBufferReady, client_, buffer_ref->id,
                        BitstreamBufferMetadata(
                            0, false, encode_output->capture_timestamp)));
+    MaybeRunFlushCallback();
     return;
   }
 
@@ -466,6 +486,7 @@
           &Client::BitstreamBufferReady, client_, buffer_ref->id,
           BitstreamBufferMetadata(used_buffer_size, keyframe,
                                   encode_output->capture_timestamp)));
+  MaybeRunFlushCallback();
 }
 
 bool VTVideoEncodeAccelerator::ResetCompressionSession() {
@@ -557,4 +578,48 @@
   }
 }
 
+void VTVideoEncodeAccelerator::FlushTask(FlushCallback flush_callback) {
+  DVLOG(3) << __func__;
+  DCHECK(encoder_thread_task_runner_->BelongsToCurrentThread());
+  DCHECK(flush_callback);
+
+  if (!compression_session_) {
+    client_task_runner_->PostTask(
+        FROM_HERE, base::BindOnce(std::move(flush_callback), false));
+    return;
+  }
+
+  // Even though this will block until all frames are returned, the frames will
+  // be posted to the current task runner, so we can't run the flush callback
+  // at this time.
+  OSStatus status =
+      VTCompressionSessionCompleteFrames(compression_session_, kCMTimeInvalid);
+
+  if (status != noErr) {
+    OSSTATUS_DLOG(ERROR, status)
+        << " VTCompressionSessionCompleteFrames failed: " << status;
+    client_task_runner_->PostTask(
+        FROM_HERE,
+        base::BindOnce(std::move(flush_callback), /*success=*/false));
+    return;
+  }
+
+  pending_flush_cb_ = std::move(flush_callback);
+  MaybeRunFlushCallback();
+}
+
+void VTVideoEncodeAccelerator::MaybeRunFlushCallback() {
+  DCHECK(encoder_thread_task_runner_->BelongsToCurrentThread());
+
+  if (!pending_flush_cb_)
+    return;
+
+  if (pending_encodes_ || !encoder_output_queue_.empty())
+    return;
+
+  client_task_runner_->PostTask(
+      FROM_HERE,
+      base::BindOnce(std::move(pending_flush_cb_), /*success=*/true));
+}
+
 }  // namespace media
diff --git a/media/gpu/mac/vt_video_encode_accelerator_mac.h b/media/gpu/mac/vt_video_encode_accelerator_mac.h
index 5f955e53..70d1f76 100644
--- a/media/gpu/mac/vt_video_encode_accelerator_mac.h
+++ b/media/gpu/mac/vt_video_encode_accelerator_mac.h
@@ -38,6 +38,8 @@
   void RequestEncodingParametersChange(uint32_t bitrate,
                                        uint32_t framerate) override;
   void Destroy() override;
+  void Flush(FlushCallback flush_callback) override;
+  bool IsFlushSupported() override;
 
  private:
   // Holds the associated data of a video frame being processed.
@@ -93,6 +95,11 @@
   // encoding work).
   void DestroyCompressionSession();
 
+  // Flushes the encoder. The flush callback won't be run until all pending
+  // encodes have been completed.
+  void FlushTask(FlushCallback flush_callback);
+  void MaybeRunFlushCallback();
+
   base::ScopedCFTypeRef<VTCompressionSessionRef> compression_session_;
 
   gfx::Size input_visible_size_;
@@ -131,6 +138,11 @@
   base::Thread encoder_thread_;
   scoped_refptr<base::SingleThreadTaskRunner> encoder_thread_task_runner_;
 
+  // Tracking information for ensuring flushes aren't completed until all
+  // pending encodes have been returned.
+  int pending_encodes_ = 0;
+  FlushCallback pending_flush_cb_;
+
   // Declared last to ensure that all weak pointers are invalidated before
   // other destructors run.
   base::WeakPtr<VTVideoEncodeAccelerator> encoder_weak_ptr_;
diff --git a/media/gpu/vaapi/BUILD.gn b/media/gpu/vaapi/BUILD.gn
index af59943..225173d2 100644
--- a/media/gpu/vaapi/BUILD.gn
+++ b/media/gpu/vaapi/BUILD.gn
@@ -109,7 +109,10 @@
   ]
 
   if (is_ash) {
+    assert(use_libgav1_parser)
     sources += [
+      "av1_vaapi_video_decoder_delegate.cc",
+      "av1_vaapi_video_decoder_delegate.h",
       "vaapi_jpeg_encode_accelerator.cc",
       "vaapi_jpeg_encode_accelerator.h",
       "vaapi_mjpeg_decode_accelerator.cc",
diff --git a/media/gpu/vaapi/av1_vaapi_video_decoder_delegate.cc b/media/gpu/vaapi/av1_vaapi_video_decoder_delegate.cc
new file mode 100644
index 0000000..d189e19
--- /dev/null
+++ b/media/gpu/vaapi/av1_vaapi_video_decoder_delegate.cc
@@ -0,0 +1,797 @@
+// 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 "media/gpu/vaapi/av1_vaapi_video_decoder_delegate.h"
+
+#include <string.h>
+#include <va/va.h>
+#include <algorithm>
+#include <vector>
+
+#include "base/logging.h"
+#include "base/memory/scoped_refptr.h"
+#include "media/gpu/av1_picture.h"
+#include "media/gpu/decode_surface_handler.h"
+#include "media/gpu/vaapi/vaapi_common.h"
+#include "media/gpu/vaapi/vaapi_wrapper.h"
+#include "third_party/libgav1/src/src/obu_parser.h"
+#include "third_party/libgav1/src/src/utils/types.h"
+#include "third_party/libgav1/src/src/warp_prediction.h"
+
+namespace media {
+namespace {
+
+#define ARRAY_SIZE(ar) (sizeof(ar) / sizeof(ar[0]))
+#define STD_ARRAY_SIZE(ar) (std::tuple_size<decltype(ar)>::value)
+
+void FillSegmentInfo(VASegmentationStructAV1& va_seg_info,
+                     const libgav1::Segmentation& segmentation) {
+  auto& va_seg_info_fields = va_seg_info.segment_info_fields.bits;
+  va_seg_info_fields.enabled = segmentation.enabled;
+  va_seg_info_fields.update_map = segmentation.update_map;
+  va_seg_info_fields.temporal_update = segmentation.temporal_update;
+  va_seg_info_fields.update_data = segmentation.update_data;
+
+  static_assert(libgav1::kMaxSegments == 8 && libgav1::kSegmentFeatureMax == 8,
+                "Invalid Segment array size");
+  static_assert(ARRAY_SIZE(segmentation.feature_data) == 8 &&
+                    ARRAY_SIZE(segmentation.feature_data[0]) == 8 &&
+                    ARRAY_SIZE(segmentation.feature_enabled) == 8 &&
+                    ARRAY_SIZE(segmentation.feature_enabled[0]) == 8,
+                "Invalid segmentation array size");
+  static_assert(ARRAY_SIZE(va_seg_info.feature_data) == 8 &&
+                    ARRAY_SIZE(va_seg_info.feature_data[0]) == 8 &&
+                    ARRAY_SIZE(va_seg_info.feature_mask) == 8,
+                "Invalid feature array size");
+  for (size_t i = 0; i < libgav1::kMaxSegments; ++i) {
+    for (size_t j = 0; j < libgav1::kSegmentFeatureMax; ++j)
+      va_seg_info.feature_data[i][j] = segmentation.feature_data[i][j];
+  }
+  for (size_t i = 0; i < libgav1::kMaxSegments; ++i) {
+    uint8_t feature_mask = 0;
+    for (size_t j = 0; j < libgav1::kSegmentFeatureMax; ++j) {
+      if (segmentation.feature_enabled[i][j])
+        feature_mask |= 1 << j;
+    }
+    va_seg_info.feature_mask[i] = feature_mask;
+  }
+}
+
+void FillFilmGrainInfo(VAFilmGrainStructAV1& va_film_grain_info,
+                       const libgav1::FilmGrainParams& film_grain_params) {
+  if (!film_grain_params.apply_grain)
+    return;
+
+#define COPY_FILM_GRAIN_FIELD(a) \
+  va_film_grain_info.film_grain_info_fields.bits.a = film_grain_params.a
+  COPY_FILM_GRAIN_FIELD(apply_grain);
+  COPY_FILM_GRAIN_FIELD(chroma_scaling_from_luma);
+  COPY_FILM_GRAIN_FIELD(grain_scale_shift);
+  COPY_FILM_GRAIN_FIELD(overlap_flag);
+  COPY_FILM_GRAIN_FIELD(clip_to_restricted_range);
+#undef COPY_FILM_GRAIN_FIELD
+  va_film_grain_info.film_grain_info_fields.bits.ar_coeff_lag =
+      film_grain_params.auto_regression_coeff_lag;
+  DCHECK_GE(film_grain_params.chroma_scaling, 8u);
+  DCHECK_GE(film_grain_params.auto_regression_shift, 6u);
+  va_film_grain_info.film_grain_info_fields.bits.grain_scaling_minus_8 =
+      film_grain_params.chroma_scaling - 8;
+  va_film_grain_info.film_grain_info_fields.bits.ar_coeff_shift_minus_6 =
+      film_grain_params.auto_regression_shift - 6;
+
+  constexpr size_t kFilmGrainPointYSize = 14;
+  constexpr size_t kFilmGrainPointUVSize = 10;
+  static_assert(
+      ARRAY_SIZE(va_film_grain_info.point_y_value) == kFilmGrainPointYSize &&
+          ARRAY_SIZE(va_film_grain_info.point_y_scaling) ==
+              kFilmGrainPointYSize &&
+          ARRAY_SIZE(va_film_grain_info.point_cb_value) ==
+              kFilmGrainPointUVSize &&
+          ARRAY_SIZE(va_film_grain_info.point_cb_scaling) ==
+              kFilmGrainPointUVSize &&
+          ARRAY_SIZE(va_film_grain_info.point_cr_value) ==
+              kFilmGrainPointUVSize &&
+          ARRAY_SIZE(va_film_grain_info.point_cr_scaling) ==
+              kFilmGrainPointUVSize &&
+          ARRAY_SIZE(film_grain_params.point_y_value) == kFilmGrainPointYSize &&
+          ARRAY_SIZE(film_grain_params.point_y_scaling) ==
+              kFilmGrainPointYSize &&
+          ARRAY_SIZE(film_grain_params.point_u_value) ==
+              kFilmGrainPointUVSize &&
+          ARRAY_SIZE(film_grain_params.point_u_scaling) ==
+              kFilmGrainPointUVSize &&
+          ARRAY_SIZE(film_grain_params.point_v_value) ==
+              kFilmGrainPointUVSize &&
+          ARRAY_SIZE(film_grain_params.point_v_scaling) ==
+              kFilmGrainPointUVSize,
+      "Invalid array size of film grain values");
+  DCHECK_LE(film_grain_params.num_y_points, kFilmGrainPointYSize);
+  DCHECK_LE(film_grain_params.num_u_points, kFilmGrainPointUVSize);
+  DCHECK_LE(film_grain_params.num_v_points, kFilmGrainPointUVSize);
+#define COPY_FILM_GRAIN_FIELD2(a, b) va_film_grain_info.a = film_grain_params.b
+#define COPY_FILM_GRAIN_FIELD3(a) COPY_FILM_GRAIN_FIELD2(a, a)
+  COPY_FILM_GRAIN_FIELD3(grain_seed);
+  COPY_FILM_GRAIN_FIELD3(num_y_points);
+  for (uint8_t i = 0; i < film_grain_params.num_y_points; ++i) {
+    COPY_FILM_GRAIN_FIELD3(point_y_value[i]);
+    COPY_FILM_GRAIN_FIELD3(point_y_scaling[i]);
+  }
+#undef COPY_FILM_GRAIN_FIELD3
+  COPY_FILM_GRAIN_FIELD2(num_cb_points, num_u_points);
+  for (uint8_t i = 0; i < film_grain_params.num_u_points; ++i) {
+    COPY_FILM_GRAIN_FIELD2(point_cb_value[i], point_u_value[i]);
+    COPY_FILM_GRAIN_FIELD2(point_cb_scaling[i], point_u_scaling[i]);
+  }
+  COPY_FILM_GRAIN_FIELD2(num_cr_points, num_v_points);
+  for (uint8_t i = 0; i < film_grain_params.num_v_points; ++i) {
+    COPY_FILM_GRAIN_FIELD2(point_cr_value[i], point_v_value[i]);
+    COPY_FILM_GRAIN_FIELD2(point_cr_scaling[i], point_v_scaling[i]);
+  }
+
+  constexpr size_t kAutoRegressionCoeffYSize = 24;
+  constexpr size_t kAutoRegressionCoeffUVSize = 25;
+  static_assert(
+      ARRAY_SIZE(va_film_grain_info.ar_coeffs_y) == kAutoRegressionCoeffYSize &&
+          ARRAY_SIZE(va_film_grain_info.ar_coeffs_cb) ==
+              kAutoRegressionCoeffUVSize &&
+          ARRAY_SIZE(va_film_grain_info.ar_coeffs_cr) ==
+              kAutoRegressionCoeffUVSize &&
+          ARRAY_SIZE(film_grain_params.auto_regression_coeff_y) ==
+              kAutoRegressionCoeffYSize &&
+          ARRAY_SIZE(film_grain_params.auto_regression_coeff_u) ==
+              kAutoRegressionCoeffUVSize &&
+          ARRAY_SIZE(film_grain_params.auto_regression_coeff_v) ==
+              kAutoRegressionCoeffUVSize,
+      "Invalid array size of auto-regressive coefficients");
+  const size_t num_pos_y = (film_grain_params.auto_regression_coeff_lag * 2) *
+                           (film_grain_params.auto_regression_coeff_lag + 1);
+  const size_t num_pos_uv = num_pos_y + (film_grain_params.num_y_points > 0);
+  if (film_grain_params.num_y_points > 0) {
+    DCHECK_LE(num_pos_y, kAutoRegressionCoeffYSize);
+    for (size_t i = 0; i < num_pos_y; ++i)
+      COPY_FILM_GRAIN_FIELD2(ar_coeffs_y[i], auto_regression_coeff_y[i]);
+  }
+  if (film_grain_params.chroma_scaling_from_luma ||
+      film_grain_params.num_u_points > 0 ||
+      film_grain_params.num_v_points > 0) {
+    DCHECK_LE(num_pos_uv, kAutoRegressionCoeffUVSize);
+    for (size_t i = 0; i < num_pos_uv; ++i) {
+      if (film_grain_params.chroma_scaling_from_luma ||
+          film_grain_params.num_u_points > 0) {
+        COPY_FILM_GRAIN_FIELD2(ar_coeffs_cb[i], auto_regression_coeff_u[i]);
+      }
+      if (film_grain_params.chroma_scaling_from_luma ||
+          film_grain_params.num_v_points > 0) {
+        COPY_FILM_GRAIN_FIELD2(ar_coeffs_cr[i], auto_regression_coeff_v[i]);
+      }
+    }
+  }
+  if (film_grain_params.num_u_points > 0) {
+    COPY_FILM_GRAIN_FIELD2(cb_mult, u_multiplier + 128);
+    COPY_FILM_GRAIN_FIELD2(cb_luma_mult, u_luma_multiplier + 128);
+    COPY_FILM_GRAIN_FIELD2(cb_offset, u_offset + 256);
+  }
+  if (film_grain_params.num_v_points > 0) {
+    COPY_FILM_GRAIN_FIELD2(cr_mult, v_multiplier + 128);
+    COPY_FILM_GRAIN_FIELD2(cr_luma_mult, v_luma_multiplier + 128);
+    COPY_FILM_GRAIN_FIELD2(cr_offset, v_offset + 256);
+  }
+#undef COPY_FILM_GRAIN_FIELD2
+}
+
+void FillGlobalMotionInfo(
+    VAWarpedMotionParamsAV1 va_warped_motion[7],
+    const std::array<libgav1::GlobalMotion, libgav1::kNumReferenceFrameTypes>&
+        global_motion) {
+  // global_motion[0] (for kReferenceFrameIntra) is not used.
+  constexpr size_t kWarpedMotionSize = libgav1::kNumReferenceFrameTypes - 1;
+  for (size_t i = 0; i < kWarpedMotionSize; ++i) {
+    // Copy |global_motion| because SetupShear updates the affine variables of
+    // the |global_motion|.
+    auto gm = global_motion[i + 1];
+    switch (gm.type) {
+      case libgav1::kGlobalMotionTransformationTypeIdentity:
+        va_warped_motion[i].wmtype = VAAV1TransformationIdentity;
+        break;
+      case libgav1::kGlobalMotionTransformationTypeTranslation:
+        va_warped_motion[i].wmtype = VAAV1TransformationTranslation;
+        break;
+      case libgav1::kGlobalMotionTransformationTypeRotZoom:
+        va_warped_motion[i].wmtype = VAAV1TransformationRotzoom;
+        break;
+      case libgav1::kGlobalMotionTransformationTypeAffine:
+        va_warped_motion[i].wmtype = VAAV1TransformationAffine;
+        break;
+      default:
+        NOTREACHED() << "Invalid global motion transformation type, "
+                     << va_warped_motion[i].wmtype;
+    }
+    static_assert(ARRAY_SIZE(va_warped_motion[i].wmmat) == 8 &&
+                      ARRAY_SIZE(gm.params) == 6,
+                  "Invalid size of warp motion parameters");
+    for (size_t j = 0; j < 6; ++j)
+      va_warped_motion[i].wmmat[j] = gm.params[j];
+    va_warped_motion[i].wmmat[6] = 0;
+    va_warped_motion[i].wmmat[7] = 0;
+    va_warped_motion[i].invalid = !libgav1::SetupShear(&gm);
+  }
+}
+
+bool FillTileInfo(VADecPictureParameterBufferAV1& va_pic_param,
+                  const libgav1::TileInfo& tile_info) {
+  // Since gav1 decoder doesn't support decoding with tile lists (i.e. large
+  // scale tile decoding), libgav1::ObuParser doesn't parse tile list, so that
+  // we cannot acquire anchor_frames_num, anchor_frames_list, tile_count_minus_1
+  // and output_frame_width/height_in_tiles_minus_1, and thus must set them and
+  // large_scale_tile to 0 or false. This is already done by the memset in
+  // SubmitDecode(). libgav1::ObuParser returns kStatusUnimplemented on
+  // ParseOneFrame(), a fallback to av1 software decoder happens in the large
+  // scale tile decoding.
+  // TODO(hiroh): Support the large scale tile decoding once libgav1::ObuParser
+  // supports it.
+  va_pic_param.tile_cols = base::checked_cast<uint8_t>(tile_info.tile_columns);
+  va_pic_param.tile_rows = base::checked_cast<uint8_t>(tile_info.tile_rows);
+
+  if (!tile_info.uniform_spacing) {
+    constexpr int kVaSizeOfTileWidthAndHeightArray = 63;
+    static_assert(
+        ARRAY_SIZE(tile_info.tile_column_width_in_superblocks) == 65 &&
+            ARRAY_SIZE(tile_info.tile_row_height_in_superblocks) == 65 &&
+            ARRAY_SIZE(va_pic_param.width_in_sbs_minus_1) ==
+                kVaSizeOfTileWidthAndHeightArray &&
+            ARRAY_SIZE(va_pic_param.height_in_sbs_minus_1) ==
+                kVaSizeOfTileWidthAndHeightArray,
+        "Invalid sizes of tile column widths and row heights");
+    const int tile_columns =
+        std::min(kVaSizeOfTileWidthAndHeightArray, tile_info.tile_columns);
+    for (int i = 0; i < tile_columns; i++) {
+      if (!base::CheckSub<int>(tile_info.tile_column_width_in_superblocks[i], 1)
+               .AssignIfValid(&va_pic_param.width_in_sbs_minus_1[i])) {
+        return false;
+      }
+    }
+    const int tile_rows =
+        std::min(kVaSizeOfTileWidthAndHeightArray, tile_info.tile_rows);
+    for (int i = 0; i < tile_rows; i++) {
+      if (!base::CheckSub<int>(tile_info.tile_row_height_in_superblocks[i], 1)
+               .AssignIfValid(&va_pic_param.height_in_sbs_minus_1[i])) {
+        return false;
+      }
+    }
+  }
+
+  va_pic_param.context_update_tile_id =
+      base::checked_cast<uint16_t>(tile_info.context_update_id);
+  return true;
+}
+
+void FillLoopFilterInfo(VADecPictureParameterBufferAV1& va_pic_param,
+                        const libgav1::LoopFilter& loop_filter) {
+  static_assert(STD_ARRAY_SIZE(loop_filter.level) == libgav1::kFrameLfCount &&
+                    libgav1::kFrameLfCount == 4 &&
+                    ARRAY_SIZE(va_pic_param.filter_level) == 2,
+                "Invalid size of loop filter strength array");
+  va_pic_param.filter_level[0] =
+      base::checked_cast<uint8_t>(loop_filter.level[0]);
+  va_pic_param.filter_level[1] =
+      base::checked_cast<uint8_t>(loop_filter.level[1]);
+  va_pic_param.filter_level_u =
+      base::checked_cast<uint8_t>(loop_filter.level[2]);
+  va_pic_param.filter_level_v =
+      base::checked_cast<uint8_t>(loop_filter.level[3]);
+
+  va_pic_param.loop_filter_info_fields.bits.sharpness_level =
+      loop_filter.sharpness;
+  va_pic_param.loop_filter_info_fields.bits.mode_ref_delta_enabled =
+      loop_filter.delta_enabled;
+  va_pic_param.loop_filter_info_fields.bits.mode_ref_delta_update =
+      loop_filter.delta_update;
+
+  static_assert(libgav1::kNumReferenceFrameTypes == 8 &&
+                    ARRAY_SIZE(va_pic_param.ref_deltas) ==
+                        libgav1::kNumReferenceFrameTypes &&
+                    STD_ARRAY_SIZE(loop_filter.ref_deltas) ==
+                        libgav1::kNumReferenceFrameTypes,
+                "Invalid size of ref deltas array");
+  static_assert(libgav1::kLoopFilterMaxModeDeltas == 2 &&
+                    ARRAY_SIZE(va_pic_param.mode_deltas) ==
+                        libgav1::kLoopFilterMaxModeDeltas &&
+                    STD_ARRAY_SIZE(loop_filter.mode_deltas) ==
+                        libgav1::kLoopFilterMaxModeDeltas,
+                "Invalid size of mode deltas array");
+  for (size_t i = 0; i < libgav1::kNumReferenceFrameTypes; i++)
+    va_pic_param.ref_deltas[i] = loop_filter.ref_deltas[i];
+  for (size_t i = 0; i < libgav1::kLoopFilterMaxModeDeltas; i++)
+    va_pic_param.mode_deltas[i] = loop_filter.mode_deltas[i];
+}
+
+void FillQuantizationInfo(VADecPictureParameterBufferAV1& va_pic_param,
+                          const libgav1::QuantizerParameters& quant_param) {
+  va_pic_param.base_qindex = quant_param.base_index;
+  static_assert(
+      libgav1::kPlaneY == 0 && libgav1::kPlaneU == 1 && libgav1::kPlaneV == 2,
+      "Invalid plane index");
+  static_assert(libgav1::kMaxPlanes == 3 &&
+                    ARRAY_SIZE(quant_param.delta_dc) == libgav1::kMaxPlanes &&
+                    ARRAY_SIZE(quant_param.delta_ac) == libgav1::kMaxPlanes,
+                "Invalid size of delta dc/ac array");
+  va_pic_param.y_dc_delta_q = quant_param.delta_dc[0];
+  va_pic_param.u_dc_delta_q = quant_param.delta_dc[1];
+  va_pic_param.v_dc_delta_q = quant_param.delta_dc[2];
+  // quant_param.delta_ac[0] is useless as it is always 0.
+  va_pic_param.u_ac_delta_q = quant_param.delta_ac[1];
+  va_pic_param.v_ac_delta_q = quant_param.delta_ac[2];
+
+  va_pic_param.qmatrix_fields.bits.using_qmatrix = quant_param.use_matrix;
+  if (!quant_param.use_matrix)
+    return;
+  static_assert(ARRAY_SIZE(quant_param.matrix_level) == libgav1::kMaxPlanes,
+                "Invalid size of matrix levels");
+  va_pic_param.qmatrix_fields.bits.qm_y =
+      base::checked_cast<uint16_t>(quant_param.matrix_level[0]);
+  va_pic_param.qmatrix_fields.bits.qm_u =
+      base::checked_cast<uint16_t>(quant_param.matrix_level[1]);
+  va_pic_param.qmatrix_fields.bits.qm_v =
+      base::checked_cast<uint16_t>(quant_param.matrix_level[2]);
+}
+
+void FillCdefInfo(VADecPictureParameterBufferAV1& va_pic_param,
+                  const libgav1::Cdef& cdef,
+                  uint8_t color_bitdepth) {
+  // Damping value parsed in libgav1 is from the spec + (bitdepth - 8).
+  // All the strength values parsed in libgav1 are from the spec and left
+  // shifted by (bitdepth - 8).
+  CHECK_GE(color_bitdepth, 8u);
+  const uint8_t coeff_shift = color_bitdepth - 8u;
+  va_pic_param.cdef_damping_minus_3 =
+      base::checked_cast<uint8_t>(cdef.damping - coeff_shift - 3u);
+
+  va_pic_param.cdef_bits = cdef.bits;
+  static_assert(
+      libgav1::kMaxCdefStrengths == 8 &&
+          ARRAY_SIZE(cdef.y_primary_strength) == libgav1::kMaxCdefStrengths &&
+          ARRAY_SIZE(cdef.y_secondary_strength) == libgav1::kMaxCdefStrengths &&
+          ARRAY_SIZE(cdef.uv_primary_strength) == libgav1::kMaxCdefStrengths &&
+          ARRAY_SIZE(cdef.uv_secondary_strength) ==
+              libgav1::kMaxCdefStrengths &&
+          ARRAY_SIZE(va_pic_param.cdef_y_strengths) ==
+              libgav1::kMaxCdefStrengths &&
+          ARRAY_SIZE(va_pic_param.cdef_uv_strengths) ==
+              libgav1::kMaxCdefStrengths,
+      "Invalid size of cdef strengths");
+  const size_t num_cdef_strengths = 1 << cdef.bits;
+  DCHECK_LE(num_cdef_strengths,
+            static_cast<size_t>(libgav1::kMaxCdefStrengths));
+  for (size_t i = 0; i < num_cdef_strengths; ++i) {
+    const uint8_t prim_strength = cdef.y_primary_strength[i] >> coeff_shift;
+    uint8_t sec_strength = cdef.y_secondary_strength[i] >> coeff_shift;
+    DCHECK_LE(sec_strength, 4u);
+    if (sec_strength == 4)
+      sec_strength--;
+    va_pic_param.cdef_y_strengths[i] =
+        ((prim_strength & 0xf) << 2) | (sec_strength & 0x03);
+  }
+
+  for (size_t i = 0; i < num_cdef_strengths; ++i) {
+    const uint8_t prim_strength = cdef.uv_primary_strength[i] >> coeff_shift;
+    uint8_t sec_strength = cdef.uv_secondary_strength[i] >> coeff_shift;
+    DCHECK_LE(sec_strength, 4u);
+    if (sec_strength == 4)
+      sec_strength--;
+    va_pic_param.cdef_uv_strengths[i] =
+        ((prim_strength & 0xf) << 2) | (sec_strength & 0x03);
+  }
+}
+
+void FillModeControlInfo(VADecPictureParameterBufferAV1& va_pic_param,
+                         const libgav1::ObuFrameHeader& frame_header) {
+  auto& mode_control = va_pic_param.mode_control_fields.bits;
+  mode_control.delta_q_present_flag = frame_header.delta_q.present;
+  mode_control.log2_delta_q_res = frame_header.delta_q.scale;
+  mode_control.delta_lf_present_flag = frame_header.delta_lf.present;
+  mode_control.log2_delta_lf_res = frame_header.delta_lf.scale;
+  mode_control.delta_lf_multi = frame_header.delta_lf.multi;
+  switch (frame_header.tx_mode) {
+    case libgav1::TxMode::kTxModeOnly4x4:
+    case libgav1::TxMode::kTxModeLargest:
+    case libgav1::TxMode::kTxModeSelect:
+      mode_control.tx_mode = base::strict_cast<uint32_t>(frame_header.tx_mode);
+      break;
+    default:
+      NOTREACHED() << "Unknown tx mode: "
+                   << base::strict_cast<int>(frame_header.tx_mode);
+  }
+  mode_control.reference_select = frame_header.reference_mode_select;
+  mode_control.reduced_tx_set_used = frame_header.reduced_tx_set;
+  mode_control.skip_mode_present = frame_header.skip_mode_present;
+}
+
+void FillLoopRestorationInfo(VADecPictureParameterBufferAV1& va_pic_param,
+                             const libgav1::LoopRestoration& loop_restoration) {
+  auto to_frame_restoration_type = [](libgav1::LoopRestorationType lr_type) {
+    // Spec. 6.10.15
+    switch (lr_type) {
+      case libgav1::LoopRestorationType::kLoopRestorationTypeNone:
+        return 0;
+      case libgav1::LoopRestorationType::kLoopRestorationTypeSwitchable:
+        return 3;
+      case libgav1::LoopRestorationType::kLoopRestorationTypeWiener:
+        return 1;
+      case libgav1::LoopRestorationType::kLoopRestorationTypeSgrProj:
+        return 2;
+      case libgav1::LoopRestorationType::kNumLoopRestorationTypes:
+      default:
+        NOTREACHED() << "Invalid restoration type"
+                     << base::strict_cast<int>(lr_type);
+        return 0;
+    }
+  };
+  static_assert(
+      ARRAY_SIZE(loop_restoration.type) == libgav1::kMaxPlanes &&
+          ARRAY_SIZE(loop_restoration.unit_size_log2) == libgav1::kMaxPlanes,
+      "Invalid size of loop restoration values");
+  auto& va_loop_restoration = va_pic_param.loop_restoration_fields.bits;
+  va_loop_restoration.yframe_restoration_type =
+      to_frame_restoration_type(loop_restoration.type[0]);
+  va_loop_restoration.cbframe_restoration_type =
+      to_frame_restoration_type(loop_restoration.type[1]);
+  va_loop_restoration.crframe_restoration_type =
+      to_frame_restoration_type(loop_restoration.type[2]);
+
+  const size_t num_planes = libgav1::kMaxPlanes;
+  const bool use_loop_restoration =
+      std::find_if(std::begin(loop_restoration.type),
+                   std::begin(loop_restoration.type) + num_planes,
+                   [](const auto type) {
+                     return type != libgav1::kLoopRestorationTypeNone;
+                   }) != (loop_restoration.type + num_planes);
+  if (!use_loop_restoration)
+    return;
+  DCHECK_GE(loop_restoration.unit_size_log2[0], 6);
+  DCHECK_GE(loop_restoration.unit_size_log2[0],
+            loop_restoration.unit_size_log2[1]);
+  va_loop_restoration.lr_unit_shift = loop_restoration.unit_size_log2[0] - 6;
+  va_loop_restoration.lr_uv_shift =
+      loop_restoration.unit_size_log2[0] - loop_restoration.unit_size_log2[1];
+}
+
+bool FillAV1PictureParameter(const AV1Picture& pic,
+                             const libgav1::ObuSequenceHeader& sequence_header,
+                             const AV1ReferenceFrameVector& ref_frames,
+                             VADecPictureParameterBufferAV1& va_pic_param) {
+  memset(&va_pic_param, 0, sizeof(VADecPictureParameterBufferAV1));
+  DCHECK_LE(base::strict_cast<uint8_t>(sequence_header.profile), 2u)
+      << "Unknown profile: " << base::strict_cast<int>(sequence_header.profile);
+  va_pic_param.profile = base::strict_cast<uint8_t>(sequence_header.profile);
+
+  if (sequence_header.enable_order_hint) {
+    DCHECK_GT(sequence_header.order_hint_bits, 0);
+    DCHECK_LE(sequence_header.order_hint_bits, 8);
+    va_pic_param.order_hint_bits_minus_1 = sequence_header.order_hint_bits - 1;
+  }
+
+  switch (sequence_header.color_config.bitdepth) {
+    case 8:
+      va_pic_param.bit_depth_idx = 0;
+      break;
+    case 10:
+      va_pic_param.bit_depth_idx = 1;
+      break;
+    case 12:
+      va_pic_param.bit_depth_idx = 2;
+      break;
+    default:
+      NOTREACHED() << "Unknown bit depth: "
+                   << base::strict_cast<int>(
+                          sequence_header.color_config.bitdepth);
+  }
+  switch (sequence_header.color_config.matrix_coefficients) {
+    case libgav1::kMatrixCoefficientsIdentity:
+    case libgav1::kMatrixCoefficientsBt709:
+    case libgav1::kMatrixCoefficientsUnspecified:
+    case libgav1::kMatrixCoefficientsFcc:
+    case libgav1::kMatrixCoefficientsBt470BG:
+    case libgav1::kMatrixCoefficientsBt601:
+    case libgav1::kMatrixCoefficientsSmpte240:
+    case libgav1::kMatrixCoefficientsSmpteYcgco:
+    case libgav1::kMatrixCoefficientsBt2020Ncl:
+    case libgav1::kMatrixCoefficientsBt2020Cl:
+    case libgav1::kMatrixCoefficientsSmpte2085:
+    case libgav1::kMatrixCoefficientsChromatNcl:
+    case libgav1::kMatrixCoefficientsChromatCl:
+    case libgav1::kMatrixCoefficientsIctcp:
+      va_pic_param.matrix_coefficients = base::checked_cast<uint8_t>(
+          sequence_header.color_config.matrix_coefficients);
+      break;
+    default:
+      DLOG(ERROR) << "Invalid matrix coefficients: "
+                  << static_cast<int>(
+                         sequence_header.color_config.matrix_coefficients);
+      return false;
+  }
+
+  DCHECK(!sequence_header.color_config.is_monochrome);
+#define COPY_SEQ_FIELD(a) \
+  va_pic_param.seq_info_fields.fields.a = sequence_header.a
+#define COPY_SEQ_FIELD2(a, b) va_pic_param.seq_info_fields.fields.a = b
+  COPY_SEQ_FIELD(still_picture);
+  COPY_SEQ_FIELD(use_128x128_superblock);
+  COPY_SEQ_FIELD(enable_filter_intra);
+  COPY_SEQ_FIELD(enable_intra_edge_filter);
+  COPY_SEQ_FIELD(enable_interintra_compound);
+  COPY_SEQ_FIELD(enable_masked_compound);
+  COPY_SEQ_FIELD(enable_dual_filter);
+  COPY_SEQ_FIELD(enable_order_hint);
+  COPY_SEQ_FIELD(enable_jnt_comp);
+  COPY_SEQ_FIELD(enable_cdef);
+  COPY_SEQ_FIELD2(mono_chrome, sequence_header.color_config.is_monochrome);
+  COPY_SEQ_FIELD2(subsampling_x, sequence_header.color_config.subsampling_x);
+  COPY_SEQ_FIELD2(subsampling_y, sequence_header.color_config.subsampling_y);
+  COPY_SEQ_FIELD(film_grain_params_present);
+#undef COPY_SEQ_FIELD
+  switch (sequence_header.color_config.color_range) {
+    case libgav1::kColorRangeStudio:
+    case libgav1::kColorRangeFull:
+      COPY_SEQ_FIELD2(color_range,
+                      base::strict_cast<uint32_t>(
+                          sequence_header.color_config.color_range));
+      break;
+    default:
+      NOTREACHED() << "Unknown color range: "
+                   << static_cast<int>(
+                          sequence_header.color_config.color_range);
+  }
+#undef COPY_SEQ_FILED2
+
+  const libgav1::ObuFrameHeader& frame_header = pic.frame_header;
+  const auto* vaapi_pic = static_cast<const VaapiAV1Picture*>(&pic);
+  DCHECK(!!vaapi_pic->display_va_surface() &&
+         !!vaapi_pic->reconstruct_va_surface());
+  if (frame_header.film_grain_params.apply_grain) {
+    DCHECK_NE(vaapi_pic->display_va_surface()->id(),
+              vaapi_pic->reconstruct_va_surface()->id())
+        << "When using film grain synthesis, the display and reconstruct "
+           "surfaces"
+        << " should be different.";
+    va_pic_param.current_frame = vaapi_pic->reconstruct_va_surface()->id();
+    va_pic_param.current_display_picture =
+        vaapi_pic->display_va_surface()->id();
+  } else {
+    DCHECK_EQ(vaapi_pic->display_va_surface()->id(),
+              vaapi_pic->reconstruct_va_surface()->id())
+        << "When not using film grain synthesis, the display and reconstruct"
+        << " surfaces should be the same.";
+    va_pic_param.current_frame = vaapi_pic->display_va_surface()->id();
+    va_pic_param.current_display_picture = VA_INVALID_SURFACE;
+  }
+
+  if (!base::CheckSub<int32_t>(frame_header.width, 1)
+           .AssignIfValid(&va_pic_param.frame_width_minus1) ||
+      !base::CheckSub<int32_t>(frame_header.height, 1)
+           .AssignIfValid(&va_pic_param.frame_height_minus1)) {
+    DLOG(ERROR) << "Invalid frame width and height"
+                << ", width=" << frame_header.width
+                << ", height=" << frame_header.height;
+    return false;
+  }
+
+  static_assert(libgav1::kNumReferenceFrameTypes == 8 &&
+                    ARRAY_SIZE(va_pic_param.ref_frame_map) ==
+                        libgav1::kNumReferenceFrameTypes,
+                "Invalid size of reference frames");
+  // TODO(hiroh): We should start with 1 and set ref_frame_map[0] to
+  // VA_INVALID_SURFACE based on the libva documentation.
+  for (int8_t i = 0; i < libgav1::kNumReferenceFrameTypes; ++i) {
+    const auto* ref_pic =
+        static_cast<const VaapiAV1Picture*>(ref_frames[i].get());
+    va_pic_param.ref_frame_map[i] =
+        ref_pic ? ref_pic->reconstruct_va_surface()->id() : VA_INVALID_SURFACE;
+  }
+
+  static_assert(libgav1::kNumInterReferenceFrameTypes == 7 &&
+                    ARRAY_SIZE(frame_header.reference_frame_index) ==
+                        libgav1::kNumInterReferenceFrameTypes &&
+                    ARRAY_SIZE(va_pic_param.ref_frame_idx) ==
+                        libgav1::kNumInterReferenceFrameTypes,
+                "Invalid size of reference frame indices");
+  for (size_t i = 0; i < libgav1::kNumInterReferenceFrameTypes; ++i) {
+    const int8_t index = frame_header.reference_frame_index[i];
+    if (index < 0)
+      continue;
+    CHECK_LT(index, libgav1::kNumReferenceFrameTypes);
+    CHECK_NE(va_pic_param.ref_frame_map[index], VA_INVALID_SURFACE);
+    va_pic_param.ref_frame_idx[i] = base::checked_cast<uint8_t>(index);
+  }
+
+  va_pic_param.primary_ref_frame =
+      base::checked_cast<uint8_t>(frame_header.primary_reference_frame);
+  va_pic_param.order_hint = frame_header.order_hint;
+
+  FillSegmentInfo(va_pic_param.seg_info, frame_header.segmentation);
+  FillFilmGrainInfo(va_pic_param.film_grain_info,
+                    frame_header.film_grain_params);
+
+  if (!FillTileInfo(va_pic_param, frame_header.tile_info))
+    return false;
+
+  if (frame_header.use_superres) {
+    DVLOG(2) << "Upscaling (use_superres=1) is not supported";
+    return false;
+  }
+  auto& va_pic_info_fields = va_pic_param.pic_info_fields.bits;
+  va_pic_info_fields.uniform_tile_spacing_flag =
+      frame_header.tile_info.uniform_spacing;
+#define COPY_PIC_FIELD(a) va_pic_info_fields.a = frame_header.a
+  COPY_PIC_FIELD(show_frame);
+  COPY_PIC_FIELD(showable_frame);
+  COPY_PIC_FIELD(error_resilient_mode);
+  COPY_PIC_FIELD(allow_screen_content_tools);
+  COPY_PIC_FIELD(force_integer_mv);
+  COPY_PIC_FIELD(allow_intrabc);
+  COPY_PIC_FIELD(use_superres);
+  COPY_PIC_FIELD(allow_high_precision_mv);
+  COPY_PIC_FIELD(is_motion_mode_switchable);
+  COPY_PIC_FIELD(use_ref_frame_mvs);
+  COPY_PIC_FIELD(allow_warped_motion);
+#undef COPY_PIC_FIELD
+  switch (frame_header.frame_type) {
+    case libgav1::FrameType::kFrameKey:
+    case libgav1::FrameType::kFrameInter:
+    case libgav1::FrameType::kFrameIntraOnly:
+    case libgav1::FrameType::kFrameSwitch:
+      va_pic_info_fields.frame_type =
+          base::strict_cast<uint32_t>(frame_header.frame_type);
+      break;
+    default:
+      NOTREACHED() << "Unknown frame type: "
+                   << base::strict_cast<int>(frame_header.frame_type);
+  }
+  va_pic_info_fields.disable_cdf_update = !frame_header.enable_cdf_update;
+  va_pic_info_fields.disable_frame_end_update_cdf =
+      !frame_header.enable_frame_end_update_cdf;
+
+  CHECK_EQ(frame_header.superres_scale_denominator,
+           libgav1::kSuperResScaleNumerator);
+  va_pic_param.superres_scale_denominator =
+      frame_header.superres_scale_denominator;
+  DCHECK_LE(base::strict_cast<uint8_t>(frame_header.interpolation_filter), 4u)
+      << "Unknown interpolation filter: "
+      << base::strict_cast<int>(frame_header.interpolation_filter);
+  va_pic_param.interp_filter =
+      base::strict_cast<uint8_t>(frame_header.interpolation_filter);
+
+  FillQuantizationInfo(va_pic_param, frame_header.quantizer);
+  FillLoopFilterInfo(va_pic_param, frame_header.loop_filter);
+  FillModeControlInfo(va_pic_param, frame_header);
+  FillLoopRestorationInfo(va_pic_param, frame_header.loop_restoration);
+  FillGlobalMotionInfo(va_pic_param.wm, frame_header.global_motion);
+  FillCdefInfo(
+      va_pic_param, frame_header.cdef,
+      base::checked_cast<uint8_t>(sequence_header.color_config.bitdepth));
+  return true;
+}
+
+bool FillAV1SliceParameters(
+    const libgav1::Vector<libgav1::TileBuffer>& tile_buffers,
+    const size_t tile_columns,
+    base::span<const uint8_t> data,
+    std::vector<VASliceParameterBufferAV1>& va_slice_params) {
+  CHECK_GT(tile_columns, 0u);
+  const uint16_t num_tiles = base::checked_cast<uint16_t>(tile_buffers.size());
+  va_slice_params.resize(num_tiles);
+  for (uint16_t tile = 0; tile < num_tiles; ++tile) {
+    VASliceParameterBufferAV1& va_tile_param = va_slice_params[tile];
+    memset(&va_tile_param, 0, sizeof(VASliceParameterBufferAV1));
+    va_tile_param.slice_data_flag = VA_SLICE_DATA_FLAG_ALL;
+    va_tile_param.tile_row = tile / base::checked_cast<uint16_t>(tile_columns);
+    va_tile_param.tile_column =
+        tile % base::checked_cast<uint16_t>(tile_columns);
+    if (!base::CheckedNumeric<size_t>(tile_buffers[tile].size)
+             .AssignIfValid(&va_tile_param.slice_data_size)) {
+      return false;
+    }
+    CHECK(tile_buffers[tile].data >= data.data());
+    base::CheckedNumeric<uint32_t> safe_va_slice_data_end(
+        va_tile_param.slice_data_offset);
+    safe_va_slice_data_end += va_tile_param.slice_data_size;
+    size_t va_slice_data_end;
+    if (!safe_va_slice_data_end.AssignIfValid(&va_slice_data_end) ||
+        va_slice_data_end > data.size()) {
+      DLOG(ERROR) << "Invalid tile offset and size"
+                  << ", offset=" << va_tile_param.slice_data_size
+                  << ", size=" << va_tile_param.slice_data_offset
+                  << ", entire data size=" << data.size();
+      return false;
+    }
+  }
+  return true;
+}
+}  // namespace
+
+AV1VaapiVideoDecoderDelegate::AV1VaapiVideoDecoderDelegate(
+    DecodeSurfaceHandler<VASurface>* const vaapi_dec,
+    scoped_refptr<VaapiWrapper> vaapi_wrapper)
+    : VaapiVideoDecoderDelegate(vaapi_dec, std::move(vaapi_wrapper)) {}
+
+AV1VaapiVideoDecoderDelegate::~AV1VaapiVideoDecoderDelegate() = default;
+
+scoped_refptr<AV1Picture> AV1VaapiVideoDecoderDelegate::CreateAV1Picture(
+    bool apply_grain) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  const auto display_va_surface = vaapi_dec_->CreateSurface();
+  if (!display_va_surface)
+    return nullptr;
+
+  auto reconstruct_va_surface = display_va_surface;
+  if (apply_grain) {
+    // TODO(hiroh): When no surface is available here, this returns nullptr and
+    // |display_va_surface| is released. Since the surface is back to the pool,
+    // VaapiVideoDecoder will detect that there are surfaces available and will
+    // start another decode task which means that CreateSurface() might fail
+    // again for |reconstruct_va_surface| since only one surface might have gone
+    // back to the pool (the one for |display_va_surface|). We should avoid this
+    // loop for the sake of efficiency.
+    reconstruct_va_surface = vaapi_dec_->CreateSurface();
+    if (!reconstruct_va_surface)
+      return nullptr;
+  }
+
+  return base::MakeRefCounted<VaapiAV1Picture>(
+      std::move(display_va_surface), std::move(reconstruct_va_surface));
+}
+
+bool AV1VaapiVideoDecoderDelegate::OutputPicture(const AV1Picture& pic) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  const auto* vaapi_pic = static_cast<const VaapiAV1Picture*>(&pic);
+  vaapi_dec_->SurfaceReady(vaapi_pic->display_va_surface(),
+                           vaapi_pic->bitstream_id(), vaapi_pic->visible_rect(),
+                           vaapi_pic->get_colorspace());
+  return true;
+}
+
+bool AV1VaapiVideoDecoderDelegate::SubmitDecode(
+    const AV1Picture& pic,
+    const libgav1::ObuSequenceHeader& seq_header,
+    const AV1ReferenceFrameVector& ref_frames,
+    const libgav1::Vector<libgav1::TileBuffer>& tile_buffers,
+    base::span<const uint8_t> data) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  // libgav1 ensures that tile_columns is >= 0 and <= MAX_TILE_COLS.
+  DCHECK_LE(0, pic.frame_header.tile_info.tile_columns);
+  DCHECK_LE(pic.frame_header.tile_info.tile_columns, libgav1::kMaxTileColumns);
+  const size_t tile_columns =
+      base::checked_cast<size_t>(pic.frame_header.tile_info.tile_columns);
+
+  VADecPictureParameterBufferAV1 pic_param;
+  std::vector<VASliceParameterBufferAV1> slice_params;
+  if (!FillAV1PictureParameter(pic, seq_header, ref_frames, pic_param) ||
+      !FillAV1SliceParameters(tile_buffers, tile_columns, data, slice_params)) {
+    return false;
+  }
+
+  // TODO(hiroh): Batch VABuffer submissions like Vp9VaapiVideoDecoderDelegate.
+  // Submit the picture parameters.
+  if (!vaapi_wrapper_->SubmitBuffer(VAPictureParameterBufferType, &pic_param))
+    return false;
+
+  // Submit the entire buffer and the per-tile information.
+  // TODO(hiroh): Don't submit the entire coded data to the buffer. Instead,
+  // only pass the data starting from the tile list OBU to reduce the size of
+  // the VA buffer.
+  if (!vaapi_wrapper_->SubmitBuffer(VASliceDataBufferType, data.size(),
+                                    data.data())) {
+    return false;
+  }
+  for (const VASliceParameterBufferAV1& tile_param : slice_params) {
+    if (!vaapi_wrapper_->SubmitBuffer(VASliceParameterBufferType,
+                                      &tile_param)) {
+      return false;
+    }
+  }
+
+  const auto* vaapi_pic = static_cast<const VaapiAV1Picture*>(&pic);
+  return vaapi_wrapper_->ExecuteAndDestroyPendingBuffers(
+      vaapi_pic->display_va_surface()->id());
+}
+}  // namespace media
diff --git a/media/gpu/vaapi/av1_vaapi_video_decoder_delegate.h b/media/gpu/vaapi/av1_vaapi_video_decoder_delegate.h
new file mode 100644
index 0000000..327c6a5
--- /dev/null
+++ b/media/gpu/vaapi/av1_vaapi_video_decoder_delegate.h
@@ -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.
+
+#ifndef MEDIA_GPU_VAAPI_AV1_VAAPI_VIDEO_DECODER_DELEGATE_H_
+#define MEDIA_GPU_VAAPI_AV1_VAAPI_VIDEO_DECODER_DELEGATE_H_
+
+#include "media/gpu/av1_decoder.h"
+#include "media/gpu/vaapi/vaapi_video_decoder_delegate.h"
+
+namespace media {
+class AV1VaapiVideoDecoderDelegate : public AV1Decoder::AV1Accelerator,
+                                     public VaapiVideoDecoderDelegate {
+ public:
+  AV1VaapiVideoDecoderDelegate(DecodeSurfaceHandler<VASurface>* const vaapi_dec,
+                               scoped_refptr<VaapiWrapper> vaapi_wrapper);
+  ~AV1VaapiVideoDecoderDelegate() override;
+  AV1VaapiVideoDecoderDelegate(const AV1VaapiVideoDecoderDelegate&) = delete;
+  AV1VaapiVideoDecoderDelegate& operator=(const AV1VaapiVideoDecoderDelegate&) =
+      delete;
+
+  // AV1Decoder::AV1Accelerator implementation.
+  scoped_refptr<AV1Picture> CreateAV1Picture(bool apply_grain) override;
+  bool SubmitDecode(const AV1Picture& pic,
+                    const libgav1::ObuSequenceHeader& seq_header,
+                    const AV1ReferenceFrameVector& ref_frames,
+                    const libgav1::Vector<libgav1::TileBuffer>& tile_buffers,
+                    base::span<const uint8_t> data) override;
+  bool OutputPicture(const AV1Picture& pic) override;
+};
+}  // namespace media
+#endif  // MEDIA_GPU_VAAPI_AV1_VAAPI_VIDEO_DECODER_DELEGATE_H_
diff --git a/media/gpu/vaapi/vaapi_common.cc b/media/gpu/vaapi/vaapi_common.cc
index 415f84a..44e7db0d 100644
--- a/media/gpu/vaapi/vaapi_common.cc
+++ b/media/gpu/vaapi/vaapi_common.cc
@@ -48,4 +48,18 @@
   return new VaapiVP9Picture(va_surface_);
 }
 
+#if BUILDFLAG(IS_ASH)
+VaapiAV1Picture::VaapiAV1Picture(
+    scoped_refptr<VASurface> display_va_surface,
+    scoped_refptr<VASurface> reconstruct_va_surface)
+    : display_va_surface_(std::move(display_va_surface)),
+      reconstruct_va_surface_(std::move(reconstruct_va_surface)) {}
+
+VaapiAV1Picture::~VaapiAV1Picture() = default;
+
+scoped_refptr<AV1Picture> VaapiAV1Picture::CreateDuplicate() {
+  return base::MakeRefCounted<VaapiAV1Picture>(display_va_surface_,
+                                               reconstruct_va_surface_);
+}
+#endif  // BUILDFLAG(IS_ASH)
 }  // namespace media
diff --git a/media/gpu/vaapi/vaapi_common.h b/media/gpu/vaapi/vaapi_common.h
index 1c71ce6..efbd4a0f 100644
--- a/media/gpu/vaapi/vaapi_common.h
+++ b/media/gpu/vaapi/vaapi_common.h
@@ -14,6 +14,10 @@
 #include "media/gpu/h265_dpb.h"
 #endif
 
+#if BUILDFLAG(IS_ASH)
+#include "media/gpu/av1_picture.h"
+#endif  // BUILDFLAG(IS_ASH)
+
 namespace media {
 
 // These picture classes derive from platform-independent, codec-specific
@@ -97,6 +101,38 @@
   DISALLOW_COPY_AND_ASSIGN(VaapiVP9Picture);
 };
 
+#if BUILDFLAG(IS_ASH)
+class VaapiAV1Picture : public AV1Picture {
+ public:
+  VaapiAV1Picture(scoped_refptr<VASurface> display_va_surface,
+                  scoped_refptr<VASurface> reconstruct_va_surface);
+  VaapiAV1Picture(const VaapiAV1Picture&) = delete;
+  VaapiAV1Picture& operator=(const VaapiAV1Picture&) = delete;
+
+  const scoped_refptr<VASurface>& display_va_surface() const {
+    return display_va_surface_;
+  }
+  const scoped_refptr<VASurface>& reconstruct_va_surface() const {
+    return reconstruct_va_surface_;
+  }
+
+ protected:
+  ~VaapiAV1Picture() override;
+
+ private:
+  scoped_refptr<AV1Picture> CreateDuplicate() override;
+
+  // |display_va_surface_| refers to the final decoded frame, both when using
+  // film grain synthesis and when not using film grain.
+  // |reconstruct_va_surface_| is only useful when using film grain synthesis:
+  // it's the decoded frame prior to applying the film grain.
+  // When not using film grain synthesis, |reconstruct_va_surface_| is equal to
+  // |display_va_surface_|. This is necessary to simplify the reference frame
+  // code when filling the VA-API structures.
+  scoped_refptr<VASurface> display_va_surface_;
+  scoped_refptr<VASurface> reconstruct_va_surface_;
+};
+#endif  // BUILDFLAG(IS_ASH)
 }  // namespace media
 
 #endif  // MEDIA_GPU_VAAPI_VAAPI_COMMON_H_
diff --git a/media/gpu/vaapi/vaapi_video_decoder.cc b/media/gpu/vaapi/vaapi_video_decoder.cc
index 9672aaf..08fe795 100644
--- a/media/gpu/vaapi/vaapi_video_decoder.cc
+++ b/media/gpu/vaapi/vaapi_video_decoder.cc
@@ -32,6 +32,11 @@
 #include "media/gpu/vaapi/h265_vaapi_video_decoder_delegate.h"
 #endif
 
+#if BUILDFLAG(IS_ASH)
+#include "media/gpu/av1_decoder.h"
+#include "media/gpu/vaapi/av1_vaapi_video_decoder_delegate.h"
+#endif  // BUILDFLAG(IS_ASH)
+
 namespace media {
 
 namespace {
@@ -643,16 +648,27 @@
 
     decoder_.reset(
         new VP9Decoder(std::move(accelerator), profile_, color_space_));
+  }
 #if BUILDFLAG(ENABLE_PLATFORM_HEVC)
-  } else if (profile_ >= HEVCPROFILE_MIN && profile_ <= HEVCPROFILE_MAX) {
+  else if (profile_ >= HEVCPROFILE_MIN && profile_ <= HEVCPROFILE_MAX) {
     auto accelerator =
         std::make_unique<H265VaapiVideoDecoderDelegate>(this, vaapi_wrapper_);
     decoder_delegate_ = accelerator.get();
 
     decoder_.reset(
         new H265Decoder(std::move(accelerator), profile_, color_space_));
+  }
 #endif  // BUILDFLAG(ENABLE_PLATFORM_HEVC)
-  } else {
+#if BUILDFLAG(IS_ASH)
+  else if (profile_ >= AV1PROFILE_MIN && profile_ <= AV1PROFILE_MAX) {
+    auto accelerator =
+        std::make_unique<AV1VaapiVideoDecoderDelegate>(this, vaapi_wrapper_);
+    decoder_delegate_ = accelerator.get();
+
+    decoder_.reset(new AV1Decoder(std::move(accelerator), profile_));
+  }
+#endif  // BUILDFLAG(IS_ASH)
+  else {
     return Status(StatusCode::kDecoderUnsupportedProfile)
         .WithData("profile", profile_);
   }
diff --git a/net/http/transport_security_state_static.json b/net/http/transport_security_state_static.json
index 858eac98..4a34a21 100644
--- a/net/http/transport_security_state_static.json
+++ b/net/http/transport_security_state_static.json
@@ -120015,7 +120015,6 @@
     { "name": "ramydent.no", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "ranking-mensesthe.jp", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "rdxbioscience.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "reach.gov", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "readingea.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "redlands.net.au", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "refurb-tracker.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -122249,6 +122248,70 @@
     { "name": "washingtoncountyar.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
     { "name": "worthcountyiowa.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
     { "name": "zerodeathsmd.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "azjlbc.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "cannoncountytn.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "carrollcountyiowa.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "churchillcountynv.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "cityofelynv.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "cityofherculaneum.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "clearcreekcountyco.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "decaturcountyiowa.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "dubuquecountyiowa.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "fillmorecountyne.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "floridafx.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "floridahealthcareconnections.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "flyhealthy.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "gajqc.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "gcso.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "greenecountyny.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "greenecountyohio.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "hancockcountyia.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "haughtonla.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "hoodrivercounty.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "horrycountysc.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "kissimmee.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "lafayetteco.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "lenoirnc.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "louisville.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "millscountyiowa.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "millwoodwa.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "modestoca.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "montgomerycountyia.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "monticelloky.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "mtpleasant-tn.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "muscatinecountyiowa.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "myvotect.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "newberryfl.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "njleg.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "nvigate.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "ocsan.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "odenvilleal.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "osceolacountyia.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "pcbfl.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "pikecountyohcommissioners.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "placercountyelections.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "plymouthcountyiowa.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "portsmouthsheriffsofficeva.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "priorlakemn.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "pwcva.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "quantum.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "radcliffky.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "reach.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "sanpatriciocountytx.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "southbridge-ma.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "stephensoncountyil.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "swa-il.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "tijerasnm.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "usicecenter.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "villageoftikiisland.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "votecitrus.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "wakpamnilake-nsn.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "wawarsingny.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "waynecountyne.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "westlakehills.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "wisdotplans.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "yavapaiaz.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "yellowstonecountymt.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
     // END OF ETLD-OWNER REQUESTED ENTRIES
 
     // To avoid trailing comma changes from showing up in diffs, we place a
diff --git a/printing/BUILD.gn b/printing/BUILD.gn
index 3e70dc1d..e71403b 100644
--- a/printing/BUILD.gn
+++ b/printing/BUILD.gn
@@ -322,6 +322,7 @@
 test("printing_unittests") {
   sources = [
     "backend/mojom/print_backend_mojom_traits_unittest.cc",
+    "backend/test_print_backend_unittest.cc",
     "metafile_skia_unittest.cc",
     "nup_parameters_unittest.cc",
     "page_number_unittest.cc",
@@ -337,6 +338,7 @@
   configs += [ "//build/config/compiler:noshadowing" ]
   deps = [
     ":printing",
+    ":test_support",
     "//base/test:test_support",
     "//build:chromeos_buildflags",
     "//mojo/core/test:run_all_unittests",
diff --git a/printing/backend/print_backend.cc b/printing/backend/print_backend.cc
index 7176a773..36661cc 100644
--- a/printing/backend/print_backend.cc
+++ b/printing/backend/print_backend.cc
@@ -4,6 +4,9 @@
 
 #include "printing/backend/print_backend.h"
 
+#include <string>
+
+#include "base/memory/scoped_refptr.h"
 #include "build/chromeos_buildflags.h"
 
 namespace {
@@ -17,10 +20,31 @@
 
 PrinterBasicInfo::PrinterBasicInfo() = default;
 
+PrinterBasicInfo::PrinterBasicInfo(const std::string& printer_name,
+                                   const std::string& display_name,
+                                   const std::string& printer_description,
+                                   int printer_status,
+                                   bool is_default,
+                                   const PrinterBasicInfoOptions& options)
+    : printer_name(printer_name),
+      display_name(display_name),
+      printer_description(printer_description),
+      printer_status(printer_status),
+      is_default(is_default),
+      options(options) {}
+
 PrinterBasicInfo::PrinterBasicInfo(const PrinterBasicInfo& other) = default;
 
 PrinterBasicInfo::~PrinterBasicInfo() = default;
 
+bool PrinterBasicInfo::operator==(const PrinterBasicInfo& other) const {
+  return printer_name == other.printer_name &&
+         display_name == other.display_name &&
+         printer_description == other.printer_description &&
+         printer_status == other.printer_status &&
+         is_default == other.is_default && options == other.options;
+}
+
 #if BUILDFLAG(IS_ASH)
 
 AdvancedCapabilityValue::AdvancedCapabilityValue() = default;
diff --git a/printing/backend/print_backend.h b/printing/backend/print_backend.h
index b8b46ff..4aeb423 100644
--- a/printing/backend/print_backend.h
+++ b/printing/backend/print_backend.h
@@ -35,9 +35,17 @@
 
 struct PRINTING_EXPORT PrinterBasicInfo {
   PrinterBasicInfo();
+  PrinterBasicInfo(const std::string& printer_name,
+                   const std::string& display_name,
+                   const std::string& printer_description,
+                   int printer_status,
+                   bool is_default,
+                   const PrinterBasicInfoOptions& options);
   PrinterBasicInfo(const PrinterBasicInfo& other);
   ~PrinterBasicInfo();
 
+  bool operator==(const PrinterBasicInfo& other) const;
+
   // The name of the printer as understood by OS.
   std::string printer_name;
 
diff --git a/printing/backend/test_print_backend.cc b/printing/backend/test_print_backend.cc
index 2eff8c9a..8a0f0a1 100644
--- a/printing/backend/test_print_backend.cc
+++ b/printing/backend/test_print_backend.cc
@@ -5,8 +5,11 @@
 #include "printing/backend/test_print_backend.h"
 
 #include <memory>
+#include <string>
 #include <utility>
 
+#include "base/check.h"
+#include "base/logging.h"
 #include "base/stl_util.h"
 #include "printing/backend/print_backend.h"
 
@@ -17,11 +20,17 @@
 TestPrintBackend::~TestPrintBackend() = default;
 
 bool TestPrintBackend::EnumeratePrinters(PrinterList* printer_list) {
-  // TODO(crbug.com/809738) There was never a call to provide a list of
-  // printers for the environment, so this would always be empty.
-  // This needs to be updated as part of a larger cleanup to make
-  // TestPrintBackend have consistent behavior across its APIs.
-  return false;
+  if (printer_map_.empty())
+    return false;
+
+  for (const auto& entry : printer_map_) {
+    const std::unique_ptr<PrinterData>& data = entry.second;
+
+    // Can only return basic info for printers which have registered info.
+    if (data->info)
+      printer_list->emplace_back(*data->info);
+  }
+  return true;
 }
 
 std::string TestPrintBackend::GetDefaultPrinterName() {
@@ -30,53 +39,97 @@
 
 bool TestPrintBackend::GetPrinterBasicInfo(const std::string& printer_name,
                                            PrinterBasicInfo* printer_info) {
-  NOTREACHED() << "Not implemented";
-  return false;
+  auto found = printer_map_.find(printer_name);
+  if (found == printer_map_.end())
+    return false;  // Matching entry not found.
+
+  // Basic info might not have been provided.
+  const std::unique_ptr<PrinterData>& data = found->second;
+  if (!data->info)
+    return false;
+
+  *printer_info = *data->info;
+  return true;
 }
 
 bool TestPrintBackend::GetPrinterSemanticCapsAndDefaults(
     const std::string& printer_name,
-    PrinterSemanticCapsAndDefaults* printer_info) {
-  auto found = valid_printers_.find(printer_name);
-  if (found == valid_printers_.end())
+    PrinterSemanticCapsAndDefaults* printer_caps) {
+  auto found = printer_map_.find(printer_name);
+  if (found == printer_map_.end())
     return false;
 
-  const std::unique_ptr<PrinterSemanticCapsAndDefaults>& caps = found->second;
-  if (!caps)
+  // Capabilities might not have been provided.
+  const std::unique_ptr<PrinterData>& data = found->second;
+  if (!data->caps)
     return false;
 
-  *printer_info = *(found->second);
+  *printer_caps = *data->caps;
   return true;
 }
 
 bool TestPrintBackend::GetPrinterCapsAndDefaults(
     const std::string& printer_name,
-    PrinterCapsAndDefaults* printer_info) {
+    PrinterCapsAndDefaults* printer_caps) {
   // not implemented
   return false;
 }
 
 std::string TestPrintBackend::GetPrinterDriverInfo(
-    const std::string& printr_name) {
+    const std::string& printer_name) {
   // not implemented
   return "";
 }
 
 bool TestPrintBackend::IsValidPrinter(const std::string& printer_name) {
-  return base::Contains(valid_printers_, printer_name);
+  return base::Contains(printer_map_, printer_name);
 }
 
 void TestPrintBackend::SetDefaultPrinterName(const std::string& printer_name) {
+  if (default_printer_name_ == printer_name)
+    return;
+
+  auto found = printer_map_.find(printer_name);
+  if (found == printer_map_.end()) {
+    DLOG(ERROR) << "Unable to set an unknown printer as the default.  Unknown "
+                << "printer name: " << printer_name;
+    return;
+  }
+
+  // Previous default printer is no longer the default.
+  const std::unique_ptr<PrinterData>& new_default_data = found->second;
+  if (!default_printer_name_.empty())
+    printer_map_[default_printer_name_]->info->is_default = false;
+
+  // Now update new printer as default.
   default_printer_name_ = printer_name;
+  if (!default_printer_name_.empty())
+    new_default_data->info->is_default = true;
 }
 
 void TestPrintBackend::AddValidPrinter(
     const std::string& printer_name,
     std::unique_ptr<PrinterSemanticCapsAndDefaults> caps,
     std::unique_ptr<PrinterBasicInfo> info) {
-  // TODO(crbug.com/809738) Utilize the extra `info` parameter to improve
-  // TestPrintBackend internal consistency.
-  valid_printers_[printer_name] = std::move(caps);
+  DCHECK(!printer_name.empty());
+
+  const bool is_default = info && info->is_default;
+  printer_map_[printer_name] =
+      std::make_unique<PrinterData>(std::move(caps), std::move(info));
+
+  // Ensure that default settings are honored if more than one is attempted to
+  // be marked as default or if this prior default should no longer be so.
+  if (is_default)
+    SetDefaultPrinterName(printer_name);
+  else if (default_printer_name_ == printer_name)
+    default_printer_name_.clear();
 }
 
+TestPrintBackend::PrinterData::PrinterData(
+    std::unique_ptr<PrinterSemanticCapsAndDefaults> caps,
+    std::unique_ptr<PrinterBasicInfo> info)
+    : caps(std::move(caps)), info(std::move(info)) {}
+
+TestPrintBackend::PrinterData::~PrinterData() = default;
+
 }  // namespace printing
diff --git a/printing/backend/test_print_backend.h b/printing/backend/test_print_backend.h
index c80b8b0..17ff074 100644
--- a/printing/backend/test_print_backend.h
+++ b/printing/backend/test_print_backend.h
@@ -5,11 +5,10 @@
 #ifndef PRINTING_BACKEND_TEST_PRINT_BACKEND_H_
 #define PRINTING_BACKEND_TEST_PRINT_BACKEND_H_
 
-#include <map>
 #include <memory>
 #include <string>
-#include <vector>
 
+#include "base/containers/flat_map.h"
 #include "printing/backend/print_backend.h"
 
 namespace printing {
@@ -33,17 +32,18 @@
   std::string GetPrinterDriverInfo(const std::string& printer_name) override;
   bool IsValidPrinter(const std::string& printer_name) override;
 
-  // Set a default printer.  The default is the empty string.
+  // Methods for test setup:
+
+  // Sets a default printer.  The default is the empty string.
   void SetDefaultPrinterName(const std::string& printer_name);
 
-  // Add a printer to satisfy IsValidPrinter(), EnumeratePrinters(),
+  // Adds a printer to satisfy IsValidPrinter(), EnumeratePrinters(),
   // GetPrinterBasicInfo(), and GetPrinterSemanticCapsAndDefaults().
   // While `caps` can be null, it will cause queries for the capabilities to
   // fail, and thus is likely not of interest for most tests.  IsValidPrinter()
   // will still show true even if `caps` is null, which provides the benefit of
   // simulating a printer that exists in the system but cannot be queried.
-  // `info` can be null, which will result in empty information being provided
-  // for any queries.
+  // `info` can be null, which will result in queries for basic info to fail.
   // Calling EnumeratePrinters() will include the identified `printer_name`
   // even if either parameter is null.
   void AddValidPrinter(const std::string& printer_name,
@@ -54,9 +54,21 @@
   ~TestPrintBackend() override;
 
  private:
+  struct PrinterData {
+    PrinterData(std::unique_ptr<PrinterSemanticCapsAndDefaults> caps,
+                std::unique_ptr<PrinterBasicInfo> info);
+    ~PrinterData();
+
+    std::unique_ptr<PrinterSemanticCapsAndDefaults> caps;
+    std::unique_ptr<PrinterBasicInfo> info;
+  };
+
   std::string default_printer_name_;
-  std::map<std::string, std::unique_ptr<PrinterSemanticCapsAndDefaults>>
-      valid_printers_;
+  // The values in `printer_map_` will not be null.  The use of a pointer to
+  // PrinterData is just a workaround for the deleted copy assignment operator
+  // for the `caps` field, as that prevents the PrinterData container from
+  // being copied into the map.
+  base::flat_map<std::string, std::unique_ptr<PrinterData>> printer_map_;
 };
 
 }  // namespace printing
diff --git a/printing/backend/test_print_backend_unittest.cc b/printing/backend/test_print_backend_unittest.cc
new file mode 100644
index 0000000..5628819df
--- /dev/null
+++ b/printing/backend/test_print_backend_unittest.cc
@@ -0,0 +1,232 @@
+// 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 "printing/backend/test_print_backend.h"
+
+#include <stdint.h>
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/memory/scoped_refptr.h"
+#include "base/stl_util.h"
+#include "base/test/gtest_util.h"
+#include "printing/backend/print_backend.h"
+#include "printing/mojom/print.mojom.h"
+#include "testing/gmock/include/gmock/gmock-matchers.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/geometry/size.h"
+
+namespace printing {
+
+namespace {
+
+constexpr char kDefaultPrinterName[] = "default-test-printer";
+constexpr char kAlternatePrinterName[] = "alternate-test-printer";
+constexpr char kNullDataPrinterName[] = "null-data-test-printer";
+constexpr char kInvalidPrinterName[] = "invalid-test-printer";
+
+constexpr int kDefaultPrinterStatus = 0;
+constexpr int kAlternatePrinterStatus = 1;
+
+const PrinterBasicInfo kDefaultPrinterInfo(
+    /*printer_name=*/kDefaultPrinterName,
+    /*display_name=*/"default test printer",
+    /*printer_description=*/"Default printer for testing.",
+    /*printer_status=*/kDefaultPrinterStatus,
+    /*is_default=*/true,
+    /*options=*/PrinterBasicInfoOptions{});
+const PrinterBasicInfo kAlternatePrinterInfo(
+    /*printer_name=*/kAlternatePrinterName,
+    /*display_name=*/"alternate test printer",
+    /*printer_description=*/"Alternate printer for testing.",
+    /*printer_status=*/kAlternatePrinterStatus,
+    /*is_default=*/false,
+    /*options=*/PrinterBasicInfoOptions{});
+
+constexpr int32_t kDefaultCopiesMax = 123;
+constexpr int32_t kAlternateCopiesMax = 456;
+
+}  // namespace
+
+class TestPrintBackendTest : public testing::Test {
+ public:
+  void SetUp() override {
+    test_print_backend_ = base::MakeRefCounted<TestPrintBackend>();
+  }
+
+  void AddPrinters() {
+    // Add some printers; only bother to set one capabilities field that will
+    // be paid attention to in the tests as way of knowing it has provided the
+    // real capabilities.
+    auto caps = std::make_unique<PrinterSemanticCapsAndDefaults>();
+    caps->copies_max = kDefaultCopiesMax;
+    test_print_backend_->AddValidPrinter(
+        kDefaultPrinterName, std::move(caps),
+        std::make_unique<PrinterBasicInfo>(kDefaultPrinterInfo));
+
+    caps = std::make_unique<PrinterSemanticCapsAndDefaults>();
+    caps->copies_max = kAlternateCopiesMax;
+    test_print_backend_->AddValidPrinter(
+        kAlternatePrinterName, std::move(caps),
+        std::make_unique<PrinterBasicInfo>(kAlternatePrinterInfo));
+
+    test_print_backend_->AddValidPrinter(kNullDataPrinterName, /*caps=*/nullptr,
+                                         /*info=*/nullptr);
+  }
+
+  // Get the test print backend.
+  TestPrintBackend* GetPrintBackend() const {
+    return test_print_backend_.get();
+  }
+
+ private:
+  scoped_refptr<TestPrintBackend> test_print_backend_;
+};
+
+TEST_F(TestPrintBackendTest, EnumeratePrinters) {
+  const PrinterList kPrinterList{kAlternatePrinterInfo, kDefaultPrinterInfo};
+  PrinterList printer_list;
+
+  // Should return false when there are no printers in the environment.
+  EXPECT_FALSE(GetPrintBackend()->EnumeratePrinters(&printer_list));
+
+  AddPrinters();
+
+  EXPECT_TRUE(GetPrintBackend()->EnumeratePrinters(&printer_list));
+  EXPECT_THAT(printer_list, testing::ContainerEq(kPrinterList));
+}
+
+TEST_F(TestPrintBackendTest, DefaultPrinterName) {
+  // If no printers added then no default.
+  EXPECT_TRUE(GetPrintBackend()->GetDefaultPrinterName().empty());
+
+  // Once printers are available, should be a default.
+  AddPrinters();
+  EXPECT_EQ(GetPrintBackend()->GetDefaultPrinterName(), kDefaultPrinterName);
+
+  // Changing default should be reflected on next query.
+  GetPrintBackend()->SetDefaultPrinterName(kAlternatePrinterName);
+  EXPECT_EQ(GetPrintBackend()->GetDefaultPrinterName(), kAlternatePrinterName);
+
+  // Adding a new printer to environment which is marked as default should
+  // automatically make it the new default.
+  static constexpr char kNewDefaultPrinterName[] = "new-default-test-printer";
+  auto caps = std::make_unique<PrinterSemanticCapsAndDefaults>();
+  auto printer_info = std::make_unique<PrinterBasicInfo>();
+  printer_info->printer_name = kNewDefaultPrinterName;
+  printer_info->is_default = true;
+  GetPrintBackend()->AddValidPrinter(kNewDefaultPrinterName, std::move(caps),
+                                     std::move(printer_info));
+  EXPECT_EQ(GetPrintBackend()->GetDefaultPrinterName(), kNewDefaultPrinterName);
+
+  // Requesting an invalid printer name to be a default should have no effect.
+  GetPrintBackend()->SetDefaultPrinterName(kInvalidPrinterName);
+  EXPECT_EQ(GetPrintBackend()->GetDefaultPrinterName(), kNewDefaultPrinterName);
+
+  // Verify that re-adding a printer that was previously the default with null
+  // basic info results in no default printer anymore.
+  GetPrintBackend()->AddValidPrinter(kNewDefaultPrinterName, /*caps=*/nullptr,
+                                     /*info=*/nullptr);
+  EXPECT_TRUE(GetPrintBackend()->GetDefaultPrinterName().empty());
+}
+
+TEST_F(TestPrintBackendTest, PrinterBasicInfo) {
+  PrinterBasicInfo printer_info;
+
+  AddPrinters();
+
+  EXPECT_TRUE(GetPrintBackend()->GetPrinterBasicInfo(kDefaultPrinterName,
+                                                     &printer_info));
+  EXPECT_EQ(printer_info.printer_name, kDefaultPrinterName);
+  EXPECT_EQ(printer_info.printer_status, kDefaultPrinterStatus);
+  EXPECT_TRUE(printer_info.is_default);
+
+  EXPECT_TRUE(GetPrintBackend()->GetPrinterBasicInfo(kAlternatePrinterName,
+                                                     &printer_info));
+  EXPECT_EQ(printer_info.printer_name, kAlternatePrinterName);
+  EXPECT_EQ(printer_info.printer_status, kAlternatePrinterStatus);
+  EXPECT_FALSE(printer_info.is_default);
+
+  EXPECT_FALSE(GetPrintBackend()->GetPrinterBasicInfo(kInvalidPrinterName,
+                                                      &printer_info));
+
+  // Changing default should be reflected on next query.
+  GetPrintBackend()->SetDefaultPrinterName(kAlternatePrinterName);
+  EXPECT_TRUE(GetPrintBackend()->GetPrinterBasicInfo(kAlternatePrinterName,
+                                                     &printer_info));
+  EXPECT_TRUE(printer_info.is_default);
+  EXPECT_TRUE(GetPrintBackend()->GetPrinterBasicInfo(kDefaultPrinterName,
+                                                     &printer_info));
+  EXPECT_FALSE(printer_info.is_default);
+
+  // Printers added with null basic info fail to get data on a query.
+  EXPECT_FALSE(GetPrintBackend()->GetPrinterBasicInfo(kNullDataPrinterName,
+                                                      &printer_info));
+
+  // Verify that (re)adding a printer with null basic info results in a failure
+  // the next time when trying to get the basic info.
+  GetPrintBackend()->AddValidPrinter(kAlternatePrinterName, /*caps=*/nullptr,
+                                     /*info=*/nullptr);
+  EXPECT_FALSE(GetPrintBackend()->GetPrinterBasicInfo(kAlternatePrinterName,
+                                                      &printer_info));
+}
+
+TEST_F(TestPrintBackendTest, GetPrinterSemanticCapsAndDefaults) {
+  PrinterSemanticCapsAndDefaults caps;
+
+  // Should fail when there are no printers in the environment.
+  EXPECT_FALSE(GetPrintBackend()->GetPrinterSemanticCapsAndDefaults(
+      kDefaultPrinterName, &caps));
+
+  AddPrinters();
+
+  EXPECT_TRUE(GetPrintBackend()->GetPrinterSemanticCapsAndDefaults(
+      kDefaultPrinterName, &caps));
+  EXPECT_EQ(caps.copies_max, kDefaultCopiesMax);
+
+  EXPECT_TRUE(GetPrintBackend()->GetPrinterSemanticCapsAndDefaults(
+      kAlternatePrinterName, &caps));
+  EXPECT_EQ(caps.copies_max, kAlternateCopiesMax);
+
+  EXPECT_FALSE(GetPrintBackend()->GetPrinterSemanticCapsAndDefaults(
+      kInvalidPrinterName, &caps));
+
+  // Printers added with null capabilities fail to get data on a query.
+  EXPECT_FALSE(GetPrintBackend()->GetPrinterSemanticCapsAndDefaults(
+      kNullDataPrinterName, &caps));
+
+  // Verify that (re)adding a printer with null capabilities results in a
+  // failure the next time when trying to get capabilities.
+  GetPrintBackend()->AddValidPrinter(kAlternatePrinterName, /*caps=*/nullptr,
+                                     /*info=*/nullptr);
+  EXPECT_FALSE(GetPrintBackend()->GetPrinterSemanticCapsAndDefaults(
+      kAlternatePrinterName, &caps));
+}
+
+TEST_F(TestPrintBackendTest, IsValidPrinter) {
+  PrinterSemanticCapsAndDefaults caps;
+
+  // Should fail when there are no printers in the environment.
+  EXPECT_FALSE(GetPrintBackend()->IsValidPrinter(kDefaultPrinterName));
+
+  AddPrinters();
+
+  EXPECT_TRUE(GetPrintBackend()->IsValidPrinter(kDefaultPrinterName));
+  EXPECT_TRUE(GetPrintBackend()->IsValidPrinter(kAlternatePrinterName));
+  EXPECT_FALSE(GetPrintBackend()->IsValidPrinter(kInvalidPrinterName));
+
+  // Verify that still shows as valid printer even if basic info and
+  // capabilities were originally null.
+  EXPECT_TRUE(GetPrintBackend()->IsValidPrinter(kNullDataPrinterName));
+
+  // Verify that (re)adding a printer with null info and capabilities still
+  // shows as valid.
+  GetPrintBackend()->AddValidPrinter(kAlternatePrinterName, /*caps=*/nullptr,
+                                     /*info=*/nullptr);
+  EXPECT_TRUE(GetPrintBackend()->IsValidPrinter(kAlternatePrinterName));
+}
+
+}  // namespace printing
diff --git a/remoting/protocol/connection_unittest.cc b/remoting/protocol/connection_unittest.cc
index ff4b858e0..d81147eb 100644
--- a/remoting/protocol/connection_unittest.cc
+++ b/remoting/protocol/connection_unittest.cc
@@ -530,8 +530,7 @@
   run_loop.Run();
 }
 
-// TODO(crbug.com/1143311): Test is flaky.
-TEST_P(ConnectionTest, DISABLED_Video) {
+TEST_P(ConnectionTest, Video) {
   Connect();
 
   std::unique_ptr<VideoStream> video_stream =
diff --git a/services/service_manager/service_manager.cc b/services/service_manager/service_manager.cc
index af7d55c..4599480 100644
--- a/services/service_manager/service_manager.cc
+++ b/services/service_manager/service_manager.cc
@@ -190,11 +190,6 @@
   instances_.clear();
 }
 
-void ServiceManager::SetInstanceQuitCallback(
-    base::OnceCallback<void(const Identity&)> callback) {
-  instance_quit_callback_ = std::move(callback);
-}
-
 ServiceInstance* ServiceManager::FindOrCreateMatchingTargetInstance(
     const ServiceInstance& source_instance,
     const ServiceFilter& partial_target_filter) {
@@ -404,9 +399,6 @@
   for (auto& listener : listeners_) {
     listener->OnServiceStopped(identity);
   }
-
-  if (!instance_quit_callback_.is_null())
-    std::move(instance_quit_callback_).Run(identity);
 }
 
 ServiceInstance* ServiceManager::GetExistingInstance(
diff --git a/services/service_manager/service_manager.h b/services/service_manager/service_manager.h
index d4e84ab..4336d6b 100644
--- a/services/service_manager/service_manager.h
+++ b/services/service_manager/service_manager.h
@@ -112,12 +112,6 @@
 
   ~ServiceManager() override;
 
-  // Provide a callback to be notified whenever an instance is destroyed.
-  // Typically the creator of the Service Manager will use this to determine
-  // when some set of services it created are destroyed, so it can shut down.
-  void SetInstanceQuitCallback(
-      base::OnceCallback<void(const Identity&)> callback);
-
   // Directly requests that the Service Manager start a new instance for
   // |service_name| if one is not already running.
   //
@@ -212,7 +206,6 @@
   ServiceInstance* service_manager_instance_;
 
   mojo::RemoteSet<mojom::ServiceManagerListener> listeners_;
-  base::OnceCallback<void(const Identity&)> instance_quit_callback_;
 
   DISALLOW_COPY_AND_ASSIGN(ServiceManager);
 };
diff --git a/third_party/abseil-cpp/symbols_x64_dbg.def b/third_party/abseil-cpp/symbols_x64_dbg.def
index a6ef4bba2..c932512 100644
--- a/third_party/abseil-cpp/symbols_x64_dbg.def
+++ b/third_party/abseil-cpp/symbols_x64_dbg.def
@@ -1336,7 +1336,6 @@
     ??RStringReleaser@?M@???$?0V?$basic_string@DU?$char_traits@D@__1@std@@V?$allocator@D@23@@__1@std@@$0A@@Cord@absl@@QEAA@$$QEAV?$basic_string@DU?$char_traits@D@__1@std@@V?$allocator@D@23@@__1@std@@@Z@QEAAXVstring_view@2@@Z
     ??Sabsl@@YA?AVuint128@0@V10@@Z
     ??Tabsl@@YA?AVuint128@0@V10@0@Z
-    ??Uabsl@@YA?AVuint128@0@V10@0@Z
     ??XDuration@absl@@QEAAAEAV01@N@Z
     ??XDuration@absl@@QEAAAEAV01@_J@Z
     ??Xint128@absl@@QEAAAEAV01@V01@@Z
diff --git a/third_party/abseil-cpp/symbols_x86_dbg.def b/third_party/abseil-cpp/symbols_x86_dbg.def
index 204aa4a..d56bc53 100644
--- a/third_party/abseil-cpp/symbols_x86_dbg.def
+++ b/third_party/abseil-cpp/symbols_x86_dbg.def
@@ -1333,7 +1333,6 @@
     ??RByUnixTime@Transition@cctz@time_internal@absl@@QBE_NABU1234@0@Z
     ??RStringReleaser@?M@???$?0V?$basic_string@DU?$char_traits@D@__1@std@@V?$allocator@D@23@@__1@std@@$0A@@Cord@absl@@QAE@$$QAV?$basic_string@DU?$char_traits@D@__1@std@@V?$allocator@D@23@@__1@std@@@Z@QAEXVstring_view@2@@Z
     ??Sabsl@@YA?AVuint128@0@V10@@Z
-    ??Uabsl@@YA?AVuint128@0@V10@0@Z
     ??XDuration@absl@@QAEAAV01@N@Z
     ??XDuration@absl@@QAEAAV01@_J@Z
     ??Xint128@absl@@QAEAAV01@V01@@Z
diff --git a/third_party/blink/renderer/core/css/css_counter_style_rule.cc b/third_party/blink/renderer/core/css/css_counter_style_rule.cc
index e6155be..930dcfc 100644
--- a/third_party/blink/renderer/core/css/css_counter_style_rule.cc
+++ b/third_party/blink/renderer/core/css/css_counter_style_rule.cc
@@ -4,6 +4,7 @@
 
 #include "third_party/blink/renderer/core/css/css_counter_style_rule.h"
 #include "third_party/blink/renderer/core/css/style_rule.h"
+#include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
 
 namespace blink {
 
@@ -15,7 +16,84 @@
 CSSCounterStyleRule::~CSSCounterStyleRule() = default;
 
 String CSSCounterStyleRule::cssText() const {
-  return String();
+  StringBuilder result;
+  result.Append("@counter-style ");
+  result.Append(name());
+  result.Append(" {");
+
+  // Note: The exact serialization isn't well specified.
+  String system_text = system();
+  if (system_text.length()) {
+    result.Append(" system: ");
+    result.Append(system_text);
+    result.Append(";");
+  }
+
+  String symbols_text = symbols();
+  if (symbols_text.length()) {
+    result.Append(" symbols: ");
+    result.Append(symbols_text);
+    result.Append(";");
+  }
+
+  String additive_symbols_text = additiveSymbols();
+  if (additive_symbols_text.length()) {
+    result.Append(" additive-symbols: ");
+    result.Append(additive_symbols_text);
+    result.Append(";");
+  }
+
+  String negative_text = negative();
+  if (negative_text.length()) {
+    result.Append(" negative: ");
+    result.Append(negative_text);
+    result.Append(";");
+  }
+
+  String prefix_text = prefix();
+  if (prefix_text.length()) {
+    result.Append(" prefix: ");
+    result.Append(prefix_text);
+    result.Append(";");
+  }
+
+  String suffix_text = suffix();
+  if (suffix_text.length()) {
+    result.Append(" suffix: ");
+    result.Append(suffix_text);
+    result.Append(";");
+  }
+
+  String pad_text = pad();
+  if (pad_text.length()) {
+    result.Append(" pad: ");
+    result.Append(pad_text);
+    result.Append(";");
+  }
+
+  String range_text = range();
+  if (range_text.length()) {
+    result.Append(" range: ");
+    result.Append(range_text);
+    result.Append(";");
+  }
+
+  String fallback_text = fallback();
+  if (fallback_text.length()) {
+    result.Append(" fallback: ");
+    result.Append(fallback_text);
+    result.Append(";");
+  }
+
+  String speak_as_text = speakAs();
+  if (speak_as_text.length()) {
+    result.Append(" speak-as: ");
+    result.Append(speak_as_text);
+    result.Append(";");
+  }
+
+  result.Append(" }");
+  return result.ToString();
 }
 
 void CSSCounterStyleRule::Reattach(StyleRuleBase* rule) {
diff --git a/third_party/blink/renderer/core/css/css_value_keywords.json5 b/third_party/blink/renderer/core/css/css_value_keywords.json5
index dba0e266..afe28fcc 100644
--- a/third_party/blink/renderer/core/css/css_value_keywords.json5
+++ b/third_party/blink/renderer/core/css/css_value_keywords.json5
@@ -1447,5 +1447,24 @@
     "more",
     "less",
     "forced",
+
+    // @counter-style system
+    "cyclic",
+    // fixed,
+    "symbolic",
+    // alphabetic,
+    "numeric",
+    "additive",
+    "extends",
+
+    // @counter-style range
+    // infinite,
+
+    // @counter-style speak-as
+    // auto
+    "bullets",
+    "numbers",
+    "words",
+    // spell-out,
   ],
 }
diff --git a/third_party/blink/renderer/core/css/parser/at_rule_counter_style_descriptor_parser.cc b/third_party/blink/renderer/core/css/parser/at_rule_counter_style_descriptor_parser.cc
index 6d524bea..4dc23632 100644
--- a/third_party/blink/renderer/core/css/parser/at_rule_counter_style_descriptor_parser.cc
+++ b/third_party/blink/renderer/core/css/parser/at_rule_counter_style_descriptor_parser.cc
@@ -6,6 +6,7 @@
 
 #include "third_party/blink/renderer/core/css/css_string_value.h"
 #include "third_party/blink/renderer/core/css/css_value.h"
+#include "third_party/blink/renderer/core/css/css_value_pair.h"
 #include "third_party/blink/renderer/core/css/parser/css_parser_token_range.h"
 #include "third_party/blink/renderer/core/css/properties/css_parsing_utils.h"
 
@@ -13,48 +14,208 @@
 
 namespace {
 
-CSSValue* ConsumeCounterStyleName(CSSParserTokenRange& range) {
-  // TODO(crbug.com/687225): Implement.
+CSSValue* ConsumeCounterStyleName(CSSParserTokenRange& range,
+                                  const CSSParserContext& context) {
+  // <counter-style-name> is a <custom-ident> that is not an ASCII
+  // case-insensitive match for "none".
+  CSSCustomIdentValue* name =
+      css_parsing_utils::ConsumeCustomIdent(range, context);
+  if (!name || EqualIgnoringASCIICase(name->Value(), "none"))
+    return nullptr;
+  return name;
+}
+
+CSSValue* ConsumeCounterStyleSymbol(CSSParserTokenRange& range,
+                                    const CSSParserContext& context) {
+  // <symbol> = <string> | <image> | <custom-ident>
+  if (CSSValue* string = css_parsing_utils::ConsumeString(range))
+    return string;
+  if (CSSValue* image = css_parsing_utils::ConsumeImage(range, context))
+    return image;
+  if (CSSCustomIdentValue* custom_ident =
+          css_parsing_utils::ConsumeCustomIdent(range, context)) {
+    return custom_ident;
+  }
   return nullptr;
 }
 
-CSSValue* ConsumeCounterStyleSymbol(CSSParserTokenRange& range) {
-  // TODO(crbug.com/687225): Implement.
+CSSValue* ConsumeCounterStyleSystem(CSSParserTokenRange& range,
+                                    const CSSParserContext& context) {
+  // Syntax: cyclic | numeric | alphabetic | symbolic | additive |
+  // [ fixed <integer>? ] | [ extends <counter-style-name> ]
+  if (CSSValue* ident = css_parsing_utils::ConsumeIdent<
+          CSSValueID::kCyclic, CSSValueID::kSymbolic, CSSValueID::kAlphabetic,
+          CSSValueID::kNumeric, CSSValueID::kAdditive>(range))
+    return ident;
+
+  if (CSSValue* ident =
+          css_parsing_utils::ConsumeIdent<CSSValueID::kFixed>(range)) {
+    CSSValue* first_symbol_value =
+        css_parsing_utils::ConsumeInteger(range, context);
+    if (!first_symbol_value) {
+      first_symbol_value = CSSNumericLiteralValue::Create(
+          1, CSSPrimitiveValue::UnitType::kInteger);
+    }
+    return MakeGarbageCollected<CSSValuePair>(
+        ident, first_symbol_value, CSSValuePair::kKeepIdenticalValues);
+  }
+
+  if (CSSValue* ident =
+          css_parsing_utils::ConsumeIdent<CSSValueID::kExtends>(range)) {
+    CSSValue* extended = ConsumeCounterStyleName(range, context);
+    if (!extended)
+      return nullptr;
+    return MakeGarbageCollected<CSSValuePair>(
+        ident, extended, CSSValuePair::kKeepIdenticalValues);
+  }
   return nullptr;
 }
 
-CSSValue* ConsumeCounterStyleSystem(CSSParserTokenRange& range) {
-  // TODO(crbug.com/687225): Implement.
+CSSValue* ConsumeCounterStyleNegative(CSSParserTokenRange& range,
+                                      const CSSParserContext& context) {
+  // Syntax: <symbol> <symbol>?
+  CSSValue* prepend = ConsumeCounterStyleSymbol(range, context);
+  if (!prepend)
+    return nullptr;
+  if (range.AtEnd())
+    return prepend;
+
+  CSSValue* append = ConsumeCounterStyleSymbol(range, context);
+  if (!append || !range.AtEnd())
+    return nullptr;
+
+  return MakeGarbageCollected<CSSValuePair>(prepend, append,
+                                            CSSValuePair::kKeepIdenticalValues);
+}
+
+CSSValue* ConsumeCounterStyleRangeBound(CSSParserTokenRange& range,
+                                        const CSSParserContext& context) {
+  if (CSSValue* infinite =
+          css_parsing_utils::ConsumeIdent<CSSValueID::kInfinite>(range))
+    return infinite;
+  if (CSSValue* integer = css_parsing_utils::ConsumeInteger(range, context))
+    return integer;
   return nullptr;
 }
 
-CSSValue* ConsumeCounterStyleNegative(CSSParserTokenRange& range) {
-  // TODO(crbug.com/687225): Implement.
-  return nullptr;
+CSSValue* ConsumeCounterStyleRange(CSSParserTokenRange& range,
+                                   const CSSParserContext& context) {
+  // Syntax: [ [ <integer> | infinite ]{2} ]# | auto
+  if (CSSValue* auto_value =
+          css_parsing_utils::ConsumeIdent<CSSValueID::kAuto>(range))
+    return auto_value;
+
+  CSSValueList* list = CSSValueList::CreateCommaSeparated();
+  do {
+    CSSValue* lower_bound = ConsumeCounterStyleRangeBound(range, context);
+    if (!lower_bound)
+      return nullptr;
+    CSSValue* upper_bound = ConsumeCounterStyleRangeBound(range, context);
+    if (!upper_bound)
+      return nullptr;
+
+    // If the lower bound of any range is higher than the upper bound, the
+    // entire descriptor is invalid and must be ignored.
+    if (lower_bound->IsPrimitiveValue() && upper_bound->IsPrimitiveValue() &&
+        To<CSSPrimitiveValue>(lower_bound)->GetIntValue() >
+            To<CSSPrimitiveValue>(upper_bound)->GetIntValue())
+      return nullptr;
+
+    list->Append(*MakeGarbageCollected<CSSValuePair>(
+        lower_bound, upper_bound, CSSValuePair::kKeepIdenticalValues));
+  } while (css_parsing_utils::ConsumeCommaIncludingWhitespace(range));
+  if (!range.AtEnd() || !list->length())
+    return nullptr;
+  return list;
 }
 
-CSSValue* ConsumeCounterStyleRange(CSSParserTokenRange& range) {
-  // TODO(crbug.com/687225): Implement.
-  return nullptr;
+CSSValue* ConsumeCounterStylePad(CSSParserTokenRange& range,
+                                 const CSSParserContext& context) {
+  // Syntax: <integer [0,∞]> && <symbol>
+  CSSValue* integer = nullptr;
+  CSSValue* symbol = nullptr;
+  while (!integer || !symbol) {
+    if (!integer) {
+      integer = css_parsing_utils::ConsumeInteger(range, context, 0);
+      if (integer)
+        continue;
+    }
+    if (!symbol) {
+      symbol = ConsumeCounterStyleSymbol(range, context);
+      if (symbol)
+        continue;
+    }
+    return nullptr;
+  }
+  if (!range.AtEnd())
+    return nullptr;
+
+  return MakeGarbageCollected<CSSValuePair>(integer, symbol,
+                                            CSSValuePair::kKeepIdenticalValues);
 }
 
-CSSValue* ConsumeCounterStylePad(CSSParserTokenRange& range) {
-  // TODO(crbug.com/687225): Implement.
-  return nullptr;
+CSSValue* ConsumeCounterStyleSymbols(CSSParserTokenRange& range,
+                                     const CSSParserContext& context) {
+  // Syntax: <symbol>+
+  CSSValueList* list = CSSValueList::CreateSpaceSeparated();
+  while (!range.AtEnd()) {
+    CSSValue* symbol = ConsumeCounterStyleSymbol(range, context);
+    if (!symbol)
+      return nullptr;
+    list->Append(*symbol);
+  }
+  if (!list->length())
+    return nullptr;
+  return list;
 }
 
-CSSValue* ConsumeCounterStyleSymbols(CSSParserTokenRange& range) {
-  // TODO(crbug.com/687225): Implement.
-  return nullptr;
+CSSValue* ConsumeCounterStyleAdditiveSymbols(CSSParserTokenRange& range,
+                                             const CSSParserContext& context) {
+  // Syntax: [ <integer [0,∞]> && <symbol> ]#
+  CSSValueList* list = CSSValueList::CreateCommaSeparated();
+  CSSPrimitiveValue* last_integer = nullptr;
+  do {
+    CSSPrimitiveValue* integer = nullptr;
+    CSSValue* symbol = nullptr;
+    while (!integer || !symbol) {
+      if (!integer) {
+        integer = css_parsing_utils::ConsumeInteger(range, context, 0);
+        if (integer)
+          continue;
+      }
+      if (!symbol) {
+        symbol = ConsumeCounterStyleSymbol(range, context);
+        if (symbol)
+          continue;
+      }
+      return nullptr;
+    }
+
+    if (last_integer) {
+      // The additive tuples must be specified in order of strictly descending
+      // weight; otherwise, the declaration is invalid and must be ignored.
+      if (integer->GetIntValue() >= last_integer->GetIntValue())
+        return nullptr;
+    }
+    last_integer = integer;
+
+    list->Append(*MakeGarbageCollected<CSSValuePair>(
+        integer, symbol, CSSValuePair::kKeepIdenticalValues));
+  } while (css_parsing_utils::ConsumeCommaIncludingWhitespace(range));
+  if (!range.AtEnd() || !list->length())
+    return nullptr;
+  return list;
 }
 
-CSSValue* ConsumeCounterStyleAdditiveSymbols(CSSParserTokenRange& range) {
-  // TODO(crbug.com/687225): Implement.
-  return nullptr;
-}
-
-CSSValue* ConsumeCounterStyleSpeakAs(CSSParserTokenRange& range) {
-  // TODO(crbug.com/687225): Implement.
+CSSValue* ConsumeCounterStyleSpeakAs(CSSParserTokenRange& range,
+                                     const CSSParserContext& context) {
+  // Syntax: auto | bullets | numbers | words | spell-out | <counter-style-name>
+  if (CSSValue* ident = css_parsing_utils::ConsumeIdent<
+          CSSValueID::kAuto, CSSValueID::kBullets, CSSValueID::kNumbers,
+          CSSValueID::kWords, CSSValueID::kSpellOut>(range))
+    return ident;
+  if (CSSValue* name = ConsumeCounterStyleName(range, context))
+    return name;
   return nullptr;
 }
 
@@ -70,40 +231,40 @@
   switch (id) {
     case AtRuleDescriptorID::System:
       range.ConsumeWhitespace();
-      parsed_value = ConsumeCounterStyleSystem(range);
+      parsed_value = ConsumeCounterStyleSystem(range, context);
       break;
     case AtRuleDescriptorID::Negative:
       range.ConsumeWhitespace();
-      parsed_value = ConsumeCounterStyleNegative(range);
+      parsed_value = ConsumeCounterStyleNegative(range, context);
       break;
     case AtRuleDescriptorID::Prefix:
     case AtRuleDescriptorID::Suffix:
       range.ConsumeWhitespace();
-      parsed_value = ConsumeCounterStyleSymbol(range);
+      parsed_value = ConsumeCounterStyleSymbol(range, context);
       break;
     case AtRuleDescriptorID::Range:
       range.ConsumeWhitespace();
-      parsed_value = ConsumeCounterStyleRange(range);
+      parsed_value = ConsumeCounterStyleRange(range, context);
       break;
     case AtRuleDescriptorID::Pad:
       range.ConsumeWhitespace();
-      parsed_value = ConsumeCounterStylePad(range);
+      parsed_value = ConsumeCounterStylePad(range, context);
       break;
     case AtRuleDescriptorID::Fallback:
       range.ConsumeWhitespace();
-      parsed_value = ConsumeCounterStyleName(range);
+      parsed_value = ConsumeCounterStyleName(range, context);
       break;
     case AtRuleDescriptorID::Symbols:
       range.ConsumeWhitespace();
-      parsed_value = ConsumeCounterStyleSymbols(range);
+      parsed_value = ConsumeCounterStyleSymbols(range, context);
       break;
     case AtRuleDescriptorID::AdditiveSymbols:
       range.ConsumeWhitespace();
-      parsed_value = ConsumeCounterStyleAdditiveSymbols(range);
+      parsed_value = ConsumeCounterStyleAdditiveSymbols(range, context);
       break;
     case AtRuleDescriptorID::SpeakAs:
       range.ConsumeWhitespace();
-      parsed_value = ConsumeCounterStyleSpeakAs(range);
+      parsed_value = ConsumeCounterStyleSpeakAs(range, context);
       break;
     default:
       break;
diff --git a/third_party/blink/renderer/core/css/parser/css_parser_impl.cc b/third_party/blink/renderer/core/css/parser/css_parser_impl.cc
index 55c4c9e..f738200 100644
--- a/third_party/blink/renderer/core/css/parser/css_parser_impl.cc
+++ b/third_party/blink/renderer/core/css/parser/css_parser_impl.cc
@@ -943,9 +943,11 @@
   if (!prelude.AtEnd())
     return nullptr;
 
-  // TODO(crbug.com/687225): If the name is invalid (none, decimal, disc and
-  // CSS-wide keywords), should the entire rule be invalid, or does it simply
-  // not define a counter style?
+  if (name_token.GetType() != kIdentToken ||
+      !css_parsing_utils::IsCustomIdent<CSSValueID::kNone, CSSValueID::kDecimal,
+                                        CSSValueID::kDisc>(name_token.Id()))
+    return nullptr;
+
   AtomicString name(name_token.Value().ToString());
 
   if (observer_) {
diff --git a/third_party/blink/renderer/core/dom/scripted_animation_controller.cc b/third_party/blink/renderer/core/dom/scripted_animation_controller.cc
index 3f909b3..36d77748 100644
--- a/third_party/blink/renderer/core/dom/scripted_animation_controller.cc
+++ b/third_party/blink/renderer/core/dom/scripted_animation_controller.cc
@@ -69,6 +69,7 @@
   visitor->Trace(callback_collection_);
   visitor->Trace(event_queue_);
   visitor->Trace(media_query_list_listeners_);
+  visitor->Trace(media_query_list_listeners_set_);
   visitor->Trace(per_frame_events_);
 }
 
@@ -169,7 +170,8 @@
 
 void ScriptedAnimationController::CallMediaQueryListListeners() {
   MediaQueryListListeners listeners;
-  listeners.Swap(media_query_list_listeners_);
+  listeners.swap(media_query_list_listeners_);
+  media_query_list_listeners_set_.clear();
 
   for (const auto& listener : listeners) {
     listener->NotifyMediaQueryChanged();
@@ -267,8 +269,13 @@
 void ScriptedAnimationController::EnqueueMediaQueryChangeListeners(
     HeapVector<Member<MediaQueryListListener>>& listeners) {
   for (const auto& listener : listeners) {
-    media_query_list_listeners_.insert(listener);
+    if (!media_query_list_listeners_set_.Contains(listener)) {
+      media_query_list_listeners_.push_back(listener);
+      media_query_list_listeners_set_.insert(listener);
+    }
   }
+  DCHECK_EQ(media_query_list_listeners_.size(),
+            media_query_list_listeners_set_.size());
   ScheduleAnimationIfNeeded();
 }
 
diff --git a/third_party/blink/renderer/core/dom/scripted_animation_controller.h b/third_party/blink/renderer/core/dom/scripted_animation_controller.h
index 7f55d6b6..6e09d92 100644
--- a/third_party/blink/renderer/core/dom/scripted_animation_controller.h
+++ b/third_party/blink/renderer/core/dom/scripted_animation_controller.h
@@ -121,9 +121,11 @@
   using PerFrameEventsMap =
       HeapHashMap<Member<const EventTarget>, HashSet<const StringImpl*>>;
   PerFrameEventsMap per_frame_events_;
-  using MediaQueryListListeners =
-      HeapListHashSet<Member<MediaQueryListListener>>;
+  using MediaQueryListListeners = HeapVector<Member<MediaQueryListListener>>;
   MediaQueryListListeners media_query_list_listeners_;
+  // This is used to quickly lookup if a listener exists in
+  // media_query_list_listeners_. The contents should be exactly the same.
+  HeapHashSet<Member<MediaQueryListListener>> media_query_list_listeners_set_;
   double current_frame_time_ms_ = 0.0;
   double current_frame_legacy_time_ms_ = 0.0;
 
diff --git a/third_party/blink/renderer/core/frame/web_frame_widget_base.cc b/third_party/blink/renderer/core/frame/web_frame_widget_base.cc
index bc6ed57..4c36665 100644
--- a/third_party/blink/renderer/core/frame/web_frame_widget_base.cc
+++ b/third_party/blink/renderer/core/frame/web_frame_widget_base.cc
@@ -1311,6 +1311,10 @@
 }
 
 void WebFrameWidgetBase::SetZoomLevel(double zoom_level) {
+  // Override the zoom level with the testing one if necessary.
+  if (zoom_level_for_testing_ != -INFINITY)
+    zoom_level = zoom_level_for_testing_;
+
   View()->SetZoomLevel(zoom_level);
 
   // Part of the UpdateVisualProperties dance we send the zoom level to
@@ -3059,6 +3063,10 @@
   return {};
 }
 
+float WebFrameWidgetBase::GetTestingDeviceScaleFactorOverride() {
+  return device_scale_factor_for_testing_;
+}
+
 void WebFrameWidgetBase::ReleaseMouseLockAndPointerCaptureForTesting() {
   GetPage()->GetPointerLockController().ExitPointerLock();
   MouseCaptureLost();
@@ -3075,6 +3083,49 @@
   return CoreHitTestResultAt(point);
 }
 
+void WebFrameWidgetBase::SetZoomLevelForTesting(double zoom_level) {
+  DCHECK(ForMainFrame());
+  DCHECK_NE(zoom_level, -INFINITY);
+  zoom_level_for_testing_ = zoom_level;
+  SetZoomLevel(zoom_level);
+}
+
+void WebFrameWidgetBase::ResetZoomLevelForTesting() {
+  DCHECK(ForMainFrame());
+  zoom_level_for_testing_ = -INFINITY;
+  SetZoomLevel(0);
+}
+
+void WebFrameWidgetBase::SetDeviceScaleFactorForTesting(float factor) {
+  DCHECK(ForMainFrame());
+  DCHECK_GE(factor, 0.f);
+
+  // Stash the window size before we adjust the scale factor, as subsequent
+  // calls to convert will use the new scale factor.
+  gfx::Size size_in_dips = widget_base_->BlinkSpaceToFlooredDIPs(Size());
+  device_scale_factor_for_testing_ = factor;
+
+  // Receiving a 0 is used to reset between tests, it removes the override in
+  // order to listen to the browser for the next test.
+  if (!factor)
+    return;
+
+  // We are changing the device scale factor from the renderer, so allocate a
+  // new viz::LocalSurfaceId to avoid surface invariants violations in tests.
+  widget_base_->LayerTreeHost()->RequestNewLocalSurfaceId();
+
+  ScreenInfo info = widget_base_->GetScreenInfo();
+  info.device_scale_factor = factor;
+  gfx::Size size_with_dsf = gfx::ScaleToCeiledSize(size_in_dips, factor);
+  widget_base_->UpdateCompositorViewportAndScreenInfo(gfx::Rect(size_with_dsf),
+                                                      info);
+  if (!AutoResizeMode()) {
+    // This picks up the new device scale factor as
+    // `UpdateCompositorViewportAndScreenInfo()` has applied a new value.
+    Resize(widget_base_->DIPsToCeiledBlinkSpace(size_in_dips));
+  }
+}
+
 WebPlugin* WebFrameWidgetBase::GetFocusedPluginContainer() {
   LocalFrame* focused_frame = FocusedLocalFrameInWidget();
   if (!focused_frame)
diff --git a/third_party/blink/renderer/core/frame/web_frame_widget_base.h b/third_party/blink/renderer/core/frame/web_frame_widget_base.h
index a7965977..ccc14a57 100644
--- a/third_party/blink/renderer/core/frame/web_frame_widget_base.h
+++ b/third_party/blink/renderer/core/frame/web_frame_widget_base.h
@@ -300,6 +300,9 @@
   void ReleaseMouseLockAndPointerCaptureForTesting() override;
   const viz::FrameSinkId& GetFrameSinkId() override;
   WebHitTestResult HitTestResultAt(const gfx::PointF&) override;
+  void SetZoomLevelForTesting(double zoom_level) override;
+  void ResetZoomLevelForTesting() override;
+  void SetDeviceScaleFactorForTesting(float factor) override;
 
   // Called when a drag-n-drop operation should begin.
   void StartDragging(const WebDragData&,
@@ -404,6 +407,7 @@
   void WasHidden() override;
   void WasShown(bool was_evicted) override;
   KURL GetURLForDebugTrace() override;
+  float GetTestingDeviceScaleFactorOverride() override;
 
   // mojom::blink::FrameWidget methods.
   void DragTargetDragEnter(const WebDragData&,
@@ -477,7 +481,7 @@
   // Called when the FrameView for this Widget's local root is created.
   virtual void DidCreateLocalRootView() {}
 
-  virtual void SetZoomLevel(double zoom_level);
+  void SetZoomLevel(double zoom_level);
 
   // Enable or disable auto-resize. This is part of
   // UpdateVisualProperties though tests may call to it more directly.
@@ -835,6 +839,20 @@
     return child_local_root_data_;
   }
 
+  // Web tests override the zoom factor in the renderer with this. We store it
+  // to keep the override if the browser passes along VisualProperties with the
+  // real device scale factor. A value of -INFINITY means this is ignored.
+  // It is always valid to read this variable but it can only be set for main
+  // frame widgets.
+  double zoom_level_for_testing_ = -INFINITY;
+
+  // Web tests override the device scale factor in the renderer with this. We
+  // store it to keep the override if the browser passes along VisualProperties
+  // with the real device scale factor. A value of 0.f means this is ignored.
+  // It is always valid to read this variable but it can only be set for main
+  // frame widgets.
+  float device_scale_factor_for_testing_ = 0;
+
   friend class WebViewImpl;
   friend class ReportTimeSwapPromise;
 };
diff --git a/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc b/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc
index 7474306b..7d9d3b1 100644
--- a/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc
+++ b/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc
@@ -345,20 +345,6 @@
   return true;
 }
 
-void WebFrameWidgetImpl::SetZoomLevelForTesting(double zoom_level) {
-  // Zoom level is only controlled for testing on the main frame.
-  NOTREACHED();
-}
-
-void WebFrameWidgetImpl::ResetZoomLevelForTesting() {
-  // Zoom level is only controlled for testing on the main frame.
-  NOTREACHED();
-}
-
-void WebFrameWidgetImpl::SetDeviceScaleFactorForTesting(float factor) {
-  NOTREACHED();
-}
-
 void WebFrameWidgetImpl::IntrinsicSizingInfoChanged(
     mojom::blink::IntrinsicSizingInfoPtr sizing_info) {
   GetAssociatedFrameWidgetHost()->IntrinsicSizingInfoChanged(
diff --git a/third_party/blink/renderer/core/frame/web_frame_widget_impl.h b/third_party/blink/renderer/core/frame/web_frame_widget_impl.h
index aeaded62..2def196 100644
--- a/third_party/blink/renderer/core/frame/web_frame_widget_impl.h
+++ b/third_party/blink/renderer/core/frame/web_frame_widget_impl.h
@@ -102,9 +102,6 @@
 
   // WebFrameWidget implementation.
   bool ScrollFocusedEditableElementIntoView() override;
-  void SetZoomLevelForTesting(double zoom_level) override;
-  void ResetZoomLevelForTesting() override;
-  void SetDeviceScaleFactorForTesting(float factor) override;
 
   PaintLayerCompositor* Compositor() const;
 
diff --git a/third_party/blink/renderer/core/frame/web_view_frame_widget.cc b/third_party/blink/renderer/core/frame/web_view_frame_widget.cc
index 9cb5d2f..4fab15d 100644
--- a/third_party/blink/renderer/core/frame/web_view_frame_widget.cc
+++ b/third_party/blink/renderer/core/frame/web_view_frame_widget.cc
@@ -115,10 +115,6 @@
   web_view_->SetFocus(enable);
 }
 
-float WebViewFrameWidget::GetDeviceScaleFactorForTesting() {
-  return device_scale_factor_for_testing_;
-}
-
 bool WebViewFrameWidget::ShouldHandleImeEvents() {
   return HasFocus();
 }
@@ -373,53 +369,6 @@
   return nullptr;
 }
 
-void WebViewFrameWidget::SetZoomLevelForTesting(double zoom_level) {
-  DCHECK_NE(zoom_level, -INFINITY);
-  zoom_level_for_testing_ = zoom_level;
-  SetZoomLevel(zoom_level);
-}
-
-void WebViewFrameWidget::ResetZoomLevelForTesting() {
-  zoom_level_for_testing_ = -INFINITY;
-  SetZoomLevel(0);
-}
-
-void WebViewFrameWidget::SetDeviceScaleFactorForTesting(float factor) {
-  DCHECK_GE(factor, 0.f);
-
-  // Stash the window size before we adjust the scale factor, as subsequent
-  // calls to convert will use the new scale factor.
-  gfx::Size size_in_dips = widget_base_->BlinkSpaceToFlooredDIPs(size_);
-  device_scale_factor_for_testing_ = factor;
-
-  // Receiving a 0 is used to reset between tests, it removes the override in
-  // order to listen to the browser for the next test.
-  if (!factor)
-    return;
-
-  // We are changing the device scale factor from the renderer, so allocate a
-  // new viz::LocalSurfaceId to avoid surface invariants violations in tests.
-  widget_base_->LayerTreeHost()->RequestNewLocalSurfaceId();
-
-  ScreenInfo info = widget_base_->GetScreenInfo();
-  info.device_scale_factor = factor;
-  gfx::Size size_with_dsf = gfx::ScaleToCeiledSize(size_in_dips, factor);
-  widget_base_->UpdateCompositorViewportAndScreenInfo(gfx::Rect(size_with_dsf),
-                                                      info);
-  if (!AutoResizeMode()) {
-    // This picks up the new device scale factor as
-    // UpdateCompositorViewportAndScreenInfo has applied a new value.
-    Resize(widget_base_->DIPsToCeiledBlinkSpace(size_in_dips));
-  }
-}
-
-void WebViewFrameWidget::SetZoomLevel(double zoom_level) {
-  // Override the zoom level with the testing one if necessary
-  if (zoom_level_for_testing_ != -INFINITY)
-    zoom_level = zoom_level_for_testing_;
-  WebFrameWidgetBase::SetZoomLevel(zoom_level);
-}
-
 void WebViewFrameWidget::SetAutoResizeMode(bool auto_resize,
                                            const gfx::Size& min_window_size,
                                            const gfx::Size& max_window_size,
diff --git a/third_party/blink/renderer/core/frame/web_view_frame_widget.h b/third_party/blink/renderer/core/frame/web_view_frame_widget.h
index b1d0cf4..74ecdb6 100644
--- a/third_party/blink/renderer/core/frame/web_view_frame_widget.h
+++ b/third_party/blink/renderer/core/frame/web_view_frame_widget.h
@@ -68,19 +68,13 @@
                        DocumentUpdateReason reason) override;
   void MouseCaptureLost() override;
 
-  // blink::mojom::FrameWidget
-
   // WebFrameWidget overrides:
   bool ScrollFocusedEditableElementIntoView() override;
-  void SetZoomLevelForTesting(double zoom_level) override;
-  void ResetZoomLevelForTesting() override;
-  void SetDeviceScaleFactorForTesting(float factor) override;
 
   // WebFrameWidgetBase overrides:
   bool ForSubframe() const override { return false; }
   bool ForTopLevelFrame() const override { return !is_for_nested_main_frame_; }
   void ZoomToFindInPageRect(const WebRect& rect_in_root_frame) override;
-  void SetZoomLevel(double zoom_level) override;
   void SetAutoResizeMode(bool auto_resize,
                          const gfx::Size& min_size_before_dsf,
                          const gfx::Size& max_size_before_dsf,
@@ -101,7 +95,6 @@
   void ApplyViewportChanges(const cc::ApplyViewportChangesArgs& args) override;
   void RecordManipulationTypeCounts(cc::ManipulationInfo info) override;
   void FocusChanged(bool enabled) override;
-  float GetDeviceScaleFactorForTesting() override;
   void RunPaintBenchmark(int repeat_count,
                          cc::PaintBenchmarkResult& result) override;
 
@@ -126,16 +119,6 @@
 
   scoped_refptr<WebViewImpl> web_view_;
 
-  // Web tests override the zoom factor in the renderer with this. We store it
-  // to keep the override if the browser passes along VisualProperties with the
-  // real device scale factor. A value of -INFINITY means this is ignored.
-  double zoom_level_for_testing_ = -INFINITY;
-
-  // Web tests override the device scale factor in the renderer with this. We
-  // store it to keep the override if the browser passes along VisualProperties
-  // with the real device scale factor. A value of 0.f means this is ignored.
-  float device_scale_factor_for_testing_ = 0;
-
   // This bit is used to tell if this is a nested widget (an "inner web
   // contents") like a <webview> or <portal> widget. If false, the widget is the
   // top level widget.
diff --git a/third_party/blink/renderer/core/paint/compositing/compositing_inputs_updater.cc b/third_party/blink/renderer/core/paint/compositing/compositing_inputs_updater.cc
index b002dbfa..324de371 100644
--- a/third_party/blink/renderer/core/paint/compositing/compositing_inputs_updater.cc
+++ b/third_party/blink/renderer/core/paint/compositing/compositing_inputs_updater.cc
@@ -463,13 +463,17 @@
 
     properties.unclipped_absolute_bounding_box =
         EnclosingIntRect(geometry_map_->AbsoluteRect(
-            layer->BoundingBoxForCompositingOverlapTest()));
+            layer->LocalBoundingBoxForCompositingOverlapTest()));
 
     bool affected_by_scroll = root_layer_->GetScrollableArea() &&
                               layer->IsAffectedByScrollOf(root_layer_);
 
-    // At ths point, |unclipped_absolute_bounding_box| is in viewport space.
+    // At this point, |unclipped_absolute_bounding_box| is in viewport space.
     // To convert to absolute space, add scroll offset for non-fixed layers.
+    // Content that is not affected by scroll, e.g. fixed-pos content and
+    // children of that content, stays in viewport space so we can expand its
+    // bounds during overlap testing without having a dependency on the scroll
+    // offset at the time these properties are calculated.
     if (affected_by_scroll) {
       properties.unclipped_absolute_bounding_box.Move(
           RoundedIntSize(root_layer_->GetScrollableArea()->GetScrollOffset()));
diff --git a/third_party/blink/renderer/core/paint/compositing/compositing_requirements_updater.cc b/third_party/blink/renderer/core/paint/compositing/compositing_requirements_updater.cc
index cb0486ae..17482ee 100644
--- a/third_party/blink/renderer/core/paint/compositing/compositing_requirements_updater.cc
+++ b/third_party/blink/renderer/core/paint/compositing/compositing_requirements_updater.cc
@@ -352,23 +352,8 @@
     unclipped_descendants.push_back(layer);
   }
 
-  IntRect abs_bounds = use_clipped_bounding_rect
-                           ? layer->ClippedAbsoluteBoundingBox()
-                           : layer->UnclippedAbsoluteBoundingBox();
-
-  if (!RuntimeEnabledFeatures::CompositingOptimizationsEnabled()) {
-    PaintLayer* root_layer = layout_view_.Layer();
-    // |abs_bounds| does not include root scroller offset. For the purposes
-    // of overlap, this only matters for fixed-position objects, and their
-    // relative position to other elements. Therefore, it's still correct to,
-    // instead of adding scroll to all non-fixed elements, add a reverse scroll
-    // to ones that are fixed.
-    if (root_layer->GetScrollableArea() &&
-        !layer->IsAffectedByScrollOf(root_layer)) {
-      abs_bounds.Move(
-          RoundedIntSize(root_layer->GetScrollableArea()->GetScrollOffset()));
-    }
-  }
+  IntRect abs_bounds = layer->ExpandedBoundingBoxForCompositingOverlapTest(
+      use_clipped_bounding_rect);
 
   absolute_descendant_bounding_box = abs_bounds;
   if (layer_can_be_composited && current_recursion_data.testing_overlap_ &&
diff --git a/third_party/blink/renderer/core/paint/paint_layer.cc b/third_party/blink/renderer/core/paint/paint_layer.cc
index f171e56d..a9231c68 100644
--- a/third_party/blink/renderer/core/paint/paint_layer.cc
+++ b/third_party/blink/renderer/core/paint/paint_layer.cc
@@ -409,6 +409,30 @@
   return current_layer == ancestor;
 }
 
+bool PaintLayer::IsTopMostNotAffectedByScrollOf(
+    const PaintLayer* ancestor) const {
+  // Returns true if |this| is the top-most fixed-pos layer between |this|
+  // (inclusive) and |ancestor.
+
+  // Should only call this method for layers that we already know are not
+  // affected by the scroll offset of the ancestor (implying this element or
+  // an ancestor must be fixed).
+  DCHECK(!IsAffectedByScrollOf(ancestor));
+
+  // Only fixed-pos elements can be top-most.
+  if (!GetLayoutObject().IsFixedPositioned())
+    return false;
+
+  PaintLayer* curr = Parent();
+  while (curr && curr != ancestor) {
+    if (curr->GetLayoutObject().IsFixedPositioned())
+      return false;
+    curr = curr->Parent();
+  }
+
+  return true;
+}
+
 void PaintLayer::UpdateTransformationMatrix() {
   if (TransformationMatrix* transform = Transform()) {
     LayoutBox* box = GetLayoutBox();
@@ -1079,7 +1103,7 @@
 
 const IntRect PaintLayer::ClippedAbsoluteBoundingBox() const {
   if (RuntimeEnabledFeatures::CompositingOptimizationsEnabled()) {
-    PhysicalRect mapping_rect = BoundingBoxForCompositingOverlapTest();
+    PhysicalRect mapping_rect = LocalBoundingBoxForCompositingOverlapTest();
     GetLayoutObject().MapToVisualRectInAncestorSpace(
         GetLayoutObject().View(), mapping_rect, kUseGeometryMapper);
     return EnclosingIntRect(mapping_rect);
@@ -1091,7 +1115,7 @@
 const IntRect PaintLayer::UnclippedAbsoluteBoundingBox() const {
   if (RuntimeEnabledFeatures::CompositingOptimizationsEnabled()) {
     return EnclosingIntRect(GetLayoutObject().LocalToAbsoluteRect(
-        BoundingBoxForCompositingOverlapTest(),
+        LocalBoundingBoxForCompositingOverlapTest(),
         kUseGeometryMapperMode | kIgnoreScrollOffset));
   } else {
     return GetAncestorDependentCompositingInputs()
@@ -2503,7 +2527,7 @@
     return;
   PhysicalRect result = LocalBoundingBox();
   ExpandRectForStackingChildren(
-      *this, result, PaintLayer::kIncludeTransformsAndCompositedChildLayers);
+      *this, result, kIncludeTransforms | kIncludeCompositedChildLayers);
   FloatRect reference_box = FloatRect(result);
 
   float zoom = GetLayoutObject().StyleRef().EffectiveZoom();
@@ -2645,7 +2669,15 @@
   return result;
 }
 
-PhysicalRect PaintLayer::BoundingBoxForCompositingOverlapTest() const {
+PhysicalRect PaintLayer::LocalBoundingBoxForCompositingOverlapTest() const {
+  // Returns the bounding box, in the local coordinate system for this layer,
+  // for the content that this paint layer is responsible for compositing. This
+  // doesn't include the content painted by self-painting descendants such as
+  // composited absolute positioned children. But the bounds is suitable for
+  // calculations such as squashing sparsity. To get the bounds that includes
+  // the visible extent of this layer and its children for overlap testing, use
+  // ExpandedBoundingBoxForCompositingOverlapTest.
+
   // Apply NeverIncludeTransformForAncestorLayer, because the geometry map in
   // CompositingInputsUpdater will take care of applying the transform of |this|
   // (== the ancestorLayer argument to boundingBoxForCompositing).
@@ -2664,37 +2696,85 @@
         style.BackdropFilter().MapRect(FloatRect(bounding_box)));
   }
 
-  if (FixedToViewport() && !bounding_box.IsEmpty()) {
-    DCHECK_EQ(style.GetPosition(), EPosition::kFixed);
-    // Note that we only expand the bounding box for overlap testing when the
-    // fixed's containing block is the viewport. This keeps us from expanding
-    // the bounds when the fixed is a child of an ancestor with transform,
-    // filters, etc. and the fixed is no longer scroll position dependent.
-
-    // Expand the bounding box by the amount that scrolling the
-    // viewport can expand the area that this fixed-pos element could
-    // cover. Compute how much we could still scroll in each direction.
-    // |max_scroll_delta| is the amount we could still scroll in
-    // increasing offset direction. |min_scroll_delta| is the amount we
-    // can still scroll in a decreasing scroll offset direction.
-    PaintLayerScrollableArea* scrollable_area =
-        GetLayoutObject().View()->GetScrollableArea();
-    ScrollOffset current_scroll_offset = scrollable_area->GetScrollOffset();
-    ScrollOffset max_scroll_delta =
-        scrollable_area->MaximumScrollOffset() - current_scroll_offset;
-    ScrollOffset min_scroll_delta =
-        current_scroll_offset - scrollable_area->MinimumScrollOffset();
-    bounding_box.Expand(
-        LayoutRectOutsets(min_scroll_delta.Height(), max_scroll_delta.Width(),
-                          max_scroll_delta.Height(), min_scroll_delta.Width()));
-  }
   return bounding_box;
 }
 
+IntRect PaintLayer::ExpandedBoundingBoxForCompositingOverlapTest(
+    bool use_clipped_bounding_rect) const {
+  // Returns the bounding box for this layer and self-painted composited
+  // children which are otherwise not included in
+  // LocalBoundingBoxForCompositingOverlapTest. Use the bounds from this layer
+  // for overlap testing that cares about the bounds of this layer and all its
+  // children.
+  IntRect abs_bounds = use_clipped_bounding_rect
+                           ? ClippedAbsoluteBoundingBox()
+                           : UnclippedAbsoluteBoundingBox();
+  PaintLayer* root_layer = GetLayoutObject().View()->Layer();
+  // |abs_bounds| does not include root scroller offset, as in it's in absolute
+  // coordinates, for everything but fixed-pos objects (and their children)
+  // which are in viewport coords. Adjusting these to all be in absolute coords
+  // happens here. This adjustment is delayed until this point in time as doing
+  // it during compositing inputs update would embed the scroll offset at the
+  // time the compositing inputs was ran when converting from viewport to
+  // absolute, making the resulting rects unusable for any other scroll offset.
+  if (root_layer->GetScrollableArea() && !abs_bounds.IsEmpty() &&
+      !IsAffectedByScrollOf(root_layer)) {
+    PaintLayerScrollableArea* scrollable_area = root_layer->GetScrollableArea();
+    ScrollOffset current_scroll_offset = scrollable_area->GetScrollOffset();
+    // Move the bounds to absolute coords
+    if (!RuntimeEnabledFeatures::CompositingOptimizationsEnabled())
+      abs_bounds.Move(RoundedIntSize(current_scroll_offset));
+
+    if (IsTopMostNotAffectedByScrollOf(root_layer)) {
+      // For overlap testing, expand the rect used for fixed-pos content in
+      // two ways. First, include any children such that overlap testing
+      // against the top-most fixed-pos layer is guaranteed to detect any
+      // overlap where a self-painting composited child of the fixed-pos layer
+      // exceeds the fixed-pos layer's bounds. Second, expand the rect to
+      // include the area it could cover if the view were to be scrolled to
+      // its minimum and maximum extents. This allows skipping overlap testing
+      // on scroll offset changes. Note that bounds expansion does not happen
+      // for fixed under a non-view container (under xform or filter for
+      // example) as those fixed still are affected by the view's scroll offset.
+      // This is checked for with the IsAffectedByScrollOf call earlier.
+
+      // Expand the rect to include children that are not already included in
+      // |layer|'s bounds.
+      PhysicalRect children_bounds;
+      if (!GetLayoutObject().ChildPaintBlockedByDisplayLock()) {
+        PaintLayerPaintOrderIterator iterator(*this, kAllChildren);
+        while (PaintLayer* child_layer = iterator.Next()) {
+          // Note that we intentionally include children irrespective of if they
+          // are composited or not.
+          children_bounds.Unite(child_layer->BoundingBoxForCompositingInternal(
+              *this, this,
+              kIncludeClips | kIncludeTransforms |
+                  kIncludeCompositedChildLayers));
+        }
+        if (!children_bounds.IsEmpty()) {
+          GetLayoutObject().MapToVisualRectInAncestorSpace(
+              GetLayoutObject().View(), children_bounds, kUseGeometryMapper);
+          abs_bounds.Unite(EnclosingIntRect(children_bounds));
+        }
+      }
+
+      // Expand bounds to include min/max scroll extents
+      ScrollOffset max_scroll_delta =
+          scrollable_area->MaximumScrollOffset() - current_scroll_offset;
+      ScrollOffset min_scroll_delta =
+          current_scroll_offset - scrollable_area->MinimumScrollOffset();
+      abs_bounds.Expand(
+          IntRectOutsets(min_scroll_delta.Height(), max_scroll_delta.Width(),
+                         max_scroll_delta.Height(), min_scroll_delta.Width()));
+    }
+  }
+  return abs_bounds;
+}
+
 void PaintLayer::ExpandRectForStackingChildren(
     const PaintLayer& composited_layer,
     PhysicalRect& result,
-    PaintLayer::CalculateBoundsOptions options) const {
+    unsigned options) const {
   // If we're locked, th en the subtree does not contribute painted output.
   // Furthermore, we might not have up-to-date sizing and position information
   // in the subtree, so skip recursing into the subtree.
@@ -2708,31 +2788,32 @@
     // for this Layer. For example, the bounds of squashed Layers
     // will be included in the computation of the appropriate squashing
     // GraphicsLayer.
-    if (options != PaintLayer::CalculateBoundsOptions::
-                       kIncludeTransformsAndCompositedChildLayers &&
-        child_layer->GetCompositingState() != kNotComposited)
-      continue;
-    result.Unite(child_layer->BoundingBoxForCompositingInternal(
-        composited_layer, this, options));
+    if ((options & kIncludeCompositedChildLayers) ||
+        child_layer->GetCompositingState() == kNotComposited) {
+      result.Unite(child_layer->BoundingBoxForCompositingInternal(
+          composited_layer, this, options));
+    }
   }
 }
 
 PhysicalRect PaintLayer::BoundingBoxForCompositing() const {
   return BoundingBoxForCompositingInternal(
-      *this, nullptr, kIncludeClipsAndMaybeIncludeTransformForAncestorLayer);
+      *this, nullptr, kIncludeClips | kMaybeIncludeTransformForAncestorLayer);
 }
 
 bool PaintLayer::ShouldApplyTransformToBoundingBox(
     const PaintLayer& composited_layer,
-    CalculateBoundsOptions options) const {
+    unsigned options) const {
+  DCHECK(!(options & kIncludeTransforms) ||
+         !(options & kMaybeIncludeTransformForAncestorLayer));
   if (!Transform())
     return false;
-  if (options == kIncludeTransformsAndCompositedChildLayers)
+  if (options & kIncludeTransforms)
     return true;
   if (PaintsWithTransform(kGlobalPaintNormalPhase)) {
     if (this != &composited_layer)
       return true;
-    if (options == kIncludeClipsAndMaybeIncludeTransformForAncestorLayer)
+    if (options & kMaybeIncludeTransformForAncestorLayer)
       return true;
   }
   return false;
@@ -2741,7 +2822,7 @@
 PhysicalRect PaintLayer::BoundingBoxForCompositingInternal(
     const PaintLayer& composited_layer,
     const PaintLayer* stacking_parent,
-    CalculateBoundsOptions options) const {
+    unsigned options) const {
   DCHECK_GE(GetLayoutObject().GetDocument().Lifecycle().GetState(),
             DocumentLifecycle::kInPrePaint);
   if (!IsSelfPaintingLayer())
@@ -2771,14 +2852,11 @@
     return PhysicalRect();
 
   PhysicalRect result;
-  if (options == kIncludeClipsAndMaybeIncludeTransformForAncestorLayer) {
+  if (options & kIncludeClips) {
     // If there is a clip applied by an ancestor to this PaintLayer but below or
     // equal to |ancestorLayer|, apply that clip. This optimizes the size
     // of the composited layer to exclude clipped-out regions of descendants.
-    result = Clipper((GetLayoutObject().GetDocument().Lifecycle().GetState() ==
-                      DocumentLifecycle::kInCompositingAssignmentsUpdate)
-                         ? GeometryMapperOption::kUseGeometryMapper
-                         : GeometryMapperOption::kUseGeometryMapper)
+    result = Clipper(GeometryMapperOption::kUseGeometryMapper)
                  .LocalClipRect(composited_layer);
 
     result.Intersect(LocalBoundingBox());
diff --git a/third_party/blink/renderer/core/paint/paint_layer.h b/third_party/blink/renderer/core/paint/paint_layer.h
index 2ef4b36..a415f1d8 100644
--- a/third_party/blink/renderer/core/paint/paint_layer.h
+++ b/third_party/blink/renderer/core/paint/paint_layer.h
@@ -461,12 +461,17 @@
                             const PhysicalRect& damage_rect,
                             const PhysicalOffset& offset_from_root) const;
 
-  // MaybeIncludeTransformForAncestorLayer means that a transform on
-  // |ancestorLayer| may be applied to the bounding box, in particular if
-  // paintsWithTransform() is true.
   enum CalculateBoundsOptions {
-    kIncludeClipsAndMaybeIncludeTransformForAncestorLayer,
-    kIncludeTransformsAndCompositedChildLayers,
+    // Include clips between this layer and its ancestor layer (inclusive).
+    kIncludeClips = 0x1,
+    // Include transforms, irrespective of if they are applied via composition
+    // or painting.
+    kIncludeTransforms = 0x2,
+    // Include child layers (recursive) whether composited or not.
+    kIncludeCompositedChildLayers = 0x4,
+    // Include transform for the ancestor layer (|composited_layer| in the
+    // initial call) if |PaintsWithTransform|
+    kMaybeIncludeTransformForAncestorLayer = 0x8
   };
 
   // Bounding box relative to some ancestor layer. Pass offsetFromRoot if known.
@@ -475,10 +480,11 @@
   PhysicalRect PhysicalBoundingBox(const PaintLayer* ancestor_layer) const;
   PhysicalRect FragmentsBoundingBox(const PaintLayer* ancestor_layer) const;
 
-  PhysicalRect BoundingBoxForCompositingOverlapTest() const;
+  PhysicalRect LocalBoundingBoxForCompositingOverlapTest() const;
+  IntRect ExpandedBoundingBoxForCompositingOverlapTest(
+      bool use_clipped_bounding_rect) const;
   PhysicalRect BoundingBoxForCompositing() const;
 
-
   // Static position is set in parent's coordinate space.
   LayoutUnit StaticInlinePosition() const { return static_inline_position_; }
   LayoutUnit StaticBlockPosition() const { return static_block_position_; }
@@ -804,7 +810,14 @@
     const PaintLayer* nearest_contained_layout_layer = nullptr;
 
     // These two boxes do not include any applicable scroll offset of the
-    // root PaintLayer.
+    // root PaintLayer. Note that 'absolute' here is potentially misleading as
+    // the actual coordinate system depends on if this layer is affected by the
+    // viewport's scroll offset or not. For content that is not affected by the
+    // viewport scroll offsets, this ends up being a rect in viewport coords.
+    // For content that is affected by the viewport's scroll offset this
+    // coordinate system is in absolute coords.
+    // Note: This stores LocalBoundingBoxForCompositingOverlapTest and not the
+    // expanded bounds (ExpandedBoundingBoxForCompositingOverlapTest).
     IntRect clipped_absolute_bounding_box;
     IntRect unclipped_absolute_bounding_box;
 
@@ -1274,18 +1287,20 @@
     needs_paint_phase_float_ |= layer.needs_paint_phase_float_;
   }
 
+  bool IsTopMostNotAffectedByScrollOf(const PaintLayer* ancestor) const;
+
   void ExpandRectForStackingChildren(const PaintLayer& composited_layer,
                                      PhysicalRect& result,
-                                     PaintLayer::CalculateBoundsOptions) const;
+                                     unsigned options) const;
 
   // The return value is in the space of |stackingParent|, if non-null, or
   // |this| otherwise.
   PhysicalRect BoundingBoxForCompositingInternal(
       const PaintLayer& composited_layer,
       const PaintLayer* stacking_parent,
-      CalculateBoundsOptions) const;
+      unsigned options) const;
   bool ShouldApplyTransformToBoundingBox(const PaintLayer& composited_layer,
-                                         CalculateBoundsOptions) const;
+                                         unsigned options) const;
 
   AncestorDependentCompositingInputs& EnsureAncestorDependentCompositingInputs()
       const {
diff --git a/third_party/blink/renderer/core/paint/paint_layer_test.cc b/third_party/blink/renderer/core/paint/paint_layer_test.cc
index 4b8d451..49abbec 100644
--- a/third_party/blink/renderer/core/paint/paint_layer_test.cc
+++ b/third_party/blink/renderer/core/paint/paint_layer_test.cc
@@ -2594,211 +2594,6 @@
   EXPECT_NE(nullptr, paint_layer);
 }
 
-TEST_P(PaintLayerTest, FixedUsesExpandedBoundingBoxForOverlap) {
-  SetBodyInnerHTML(R"HTML(
-    <style>
-      * {
-        margin: 0;
-      }
-      body {
-        height: 610px;
-        width: 820px;
-      }
-      #fixed {
-        height: 10px;
-        left: 50px;
-        position: fixed;
-        top: 50px;
-        width: 10px;
-      }
-    </style>
-    <div id=fixed></div>
-  )HTML");
-
-  PaintLayer* fixed = GetPaintLayerByElementId("fixed");
-  EXPECT_EQ(fixed->BoundingBoxForCompositingOverlapTest(),
-            PhysicalRect(0, 0, 30, 20));
-
-  // Modify the viewport scroll offset and ensure that the bounding box is still
-  // adjusted by the new amount the viewport can scroll in any direction.
-  GetDocument().View()->LayoutViewport()->SetScrollOffset(
-      ScrollOffset(10, 10), mojom::blink::ScrollType::kProgrammatic);
-  UpdateAllLifecyclePhasesForTest();
-
-  EXPECT_EQ(fixed->BoundingBoxForCompositingOverlapTest(),
-            PhysicalRect(-10, -10, 30, 20));
-}
-
-TEST_P(PaintLayerTest, FixedInScrollerUsesExpandedBoundingBoxForOverlap) {
-  SetBodyInnerHTML(R"HTML(
-    <style>
-      * {
-        margin: 0;
-      }
-      body {
-        height: 610px;
-        width: 820px;
-      }
-      #scroller {
-        height: 100px;
-        left: 100px;
-        overflow: scroll;
-        position: absolute;
-        top: 100px;
-        width: 100px;
-      }
-      #spacer {
-        height: 500px;
-      }
-      #fixed {
-        height: 10px;
-        left: 50px;
-        position: fixed;
-        top: 50px;
-        width: 10px;
-      }
-    </style>
-    <div id=scroller>
-      <div id=fixed></div>
-      <div id=spacer></div>
-    </div>
-  )HTML");
-
-  PaintLayer* fixed = GetPaintLayerByElementId("fixed");
-  EXPECT_EQ(fixed->BoundingBoxForCompositingOverlapTest(),
-            PhysicalRect(0, 0, 30, 20));
-
-  // Modify the inner scroll offset and ensure that the bounding box is still
-  // the same.
-  PaintLayerScrollableArea* scrollable_area =
-      GetPaintLayerByElementId("scroller")->GetScrollableArea();
-  scrollable_area->ScrollToAbsolutePosition(FloatPoint(10, 10));
-  UpdateAllLifecyclePhasesForTest();
-
-  EXPECT_EQ(fixed->BoundingBoxForCompositingOverlapTest(),
-            PhysicalRect(0, 0, 30, 20));
-
-  // Modify the viewport scroll offset and ensure that the bounding box is still
-  // adjusted by the newamount the viewport can scroll in any direction.
-  GetDocument().View()->LayoutViewport()->SetScrollOffset(
-      ScrollOffset(10, 10), mojom::blink::ScrollType::kProgrammatic);
-  UpdateAllLifecyclePhasesForTest();
-
-  EXPECT_EQ(fixed->BoundingBoxForCompositingOverlapTest(),
-            PhysicalRect(-10, -10, 30, 20));
-}
-
-TEST_P(PaintLayerTest, FixedUnderTransformDoesNotExpandBoundingBoxForOverlap) {
-  SetBodyInnerHTML(R"HTML(
-    <style>
-      .anim {
-        animation: pulse 5s infinite;
-      }
-      @keyframes pulse {
-        0% { opacity: 0.1; }
-        100% { opacity: 0.9; }
-      }
-      .xform {
-        height: 100px;
-        left: 100px;
-        position: absolute;
-        top: 100px;
-        transform: rotate(20deg);
-        width: 100px;
-      }
-      .fixed {
-        height: 50px;
-        left: 25px;
-        position: fixed;
-        top: 25px;
-        width: 50px;
-      }
-      .spacer {
-        height: 2000px;
-      }
-    </style>
-    <div id=fixed-cb class=xform>
-      <div id=fixed class='fixed anim'></div>
-    </div>
-    <div class=spacer></div>
-  )HTML");
-
-  // The animation is to cause the fixed to be composited. However, even with
-  // fixed composited, it shouldn't have expanded bounds because its containing
-  // block isn't the viewport.
-  PaintLayer* fixed = GetPaintLayerByElementId("fixed");
-  EXPECT_EQ(fixed->BoundingBoxForCompositingOverlapTest(),
-            PhysicalRect(0, 0, 50, 50));
-}
-
-TEST_P(PaintLayerTest, NestedFixedUsesExpandedBoundingBoxForOverlap) {
-  SetBodyInnerHTML(R"HTML(
-    <style>
-      * {
-        margin: 0;
-      }
-      body {
-        height: 610px;
-        width: 820px;
-      }
-      #iframe1 {
-        height: 100px;
-        left: 50px;
-        position: fixed;
-        top: 50px;
-        width: 100px;
-      }
-    </style>
-    <iframe id=iframe1></iframe>
-  )HTML");
-  SetChildFrameHTML(R"HTML(
-    <style>
-      * {
-        margin: 0;
-      }
-      body {
-        height: 500px;
-        width: 500px;
-      }
-      #fixed {
-        height: 10px;
-        left: 50px;
-        position: fixed;
-        top: 50px;
-        width: 10px;
-      }
-    </style>
-    <div id=fixed></div>
-  )HTML");
-  UpdateAllLifecyclePhasesForTest();
-
-  PaintLayer* fixed =
-      To<LayoutBoxModelObject>(
-          ChildDocument().getElementById("fixed")->GetLayoutObject())
-          ->Layer();
-  EXPECT_EQ(fixed->BoundingBoxForCompositingOverlapTest(),
-            PhysicalRect(0, 0, 410, 410));
-
-  // Modify the top-most viewport's scroll offset and ensure that the bounding
-  // box is still the same. This shows that we're not considering the wrong
-  // viewport's scroll offset when computing the bounding box.
-  GetDocument().View()->LayoutViewport()->SetScrollOffset(
-      ScrollOffset(10, 10), mojom::blink::ScrollType::kProgrammatic);
-  UpdateAllLifecyclePhasesForTest();
-
-  EXPECT_EQ(fixed->BoundingBoxForCompositingOverlapTest(),
-            PhysicalRect(0, 0, 410, 410));
-
-  // Now modify the iframe's scroll offset. This one should affect the fixed's
-  // bounding box.
-  ChildDocument().View()->LayoutViewport()->SetScrollOffset(
-      ScrollOffset(10, 10), mojom::blink::ScrollType::kProgrammatic);
-  UpdateAllLifecyclePhasesForTest();
-
-  EXPECT_EQ(fixed->BoundingBoxForCompositingOverlapTest(),
-            PhysicalRect(-10, -10, 410, 410));
-}
-
 TEST_P(PaintLayerTest, DirectCompositingReasonsCrossingFrameBoundaries) {
   if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled())
     return;
@@ -2877,7 +2672,6 @@
 }
 
 TEST_P(PaintLayerTest, GlobalRootScrollerHitTest) {
-  USE_NON_OVERLAY_SCROLLBARS();
   SetBodyInnerHTML(R"HTML(
     <style>
       :root {
@@ -2885,7 +2679,6 @@
         background:blue;
         transform: rotate(30deg);
         transform-style: preserve-3d;
-        overflow-x: hidden;
       }
       #perspective {
         perspective:100px;
@@ -2898,7 +2691,6 @@
     <div id="perspective">
       <div id="threedee"></div>
     </div>
-    <div style="height:1000px"></div>
   )HTML");
   GetDocument().GetPage()->SetPageScaleFactor(2);
   UpdateAllLifecyclePhasesForTest();
@@ -2910,13 +2702,12 @@
   EXPECT_EQ(result.InnerNode(), GetDocument().documentElement());
   EXPECT_EQ(result.GetScrollbar(), nullptr);
 
-  const HitTestLocation location_scrollbar(IntPoint(790, 300));
-  HitTestResult result_scrollbar;
-  GetLayoutView().HitTestNoLifecycleUpdate(location_scrollbar,
-                                           result_scrollbar);
-  EXPECT_EQ(result_scrollbar.InnerNode(), GetDocument().documentElement());
-  EXPECT_NE(result_scrollbar.GetScrollbar(), nullptr);
-  EXPECT_EQ(result_scrollbar.LocalPoint(), location_scrollbar.Point());
+  if (GetDocument().GetPage()->GetScrollbarTheme().AllowsHitTest()) {
+    const HitTestLocation location_scrollbar(IntPoint(790, 300));
+    HitTestResult result_scrollbar;
+    EXPECT_EQ(result_scrollbar.InnerNode(), &GetDocument());
+    EXPECT_NE(result_scrollbar.GetScrollbar(), nullptr);
+  }
 }
 
 TEST_P(PaintLayerTest, HasNonEmptyChildLayoutObjectsZeroSizeOverflowVisible) {
@@ -2932,4 +2723,800 @@
   EXPECT_TRUE(layer->HasNonEmptyChildLayoutObjects());
 }
 
+enum { kCompositingOptimizations = 1 << 0 };
+
+// TODO(chrishtr): Remove this test configuration and keep the appropriate
+// variants of the tests when CompositingOptimizations ships or is removed.
+class PaintLayerOverlapTestConfigurations
+    : public testing::WithParamInterface<unsigned>,
+      private ScopedCompositingOptimizationsForTest {
+ public:
+  PaintLayerOverlapTestConfigurations()
+      : ScopedCompositingOptimizationsForTest(GetParam() &
+                                              kCompositingOptimizations) {}
+  ~PaintLayerOverlapTestConfigurations() override {
+    // Must destruct all objects before toggling back feature flags.
+    WebHeap::CollectAllGarbageForTesting();
+  }
+};
+
+class PaintLayerOverlapTest : public PaintLayerOverlapTestConfigurations,
+                              public RenderingTest {
+ public:
+  PaintLayerOverlapTest()
+      : RenderingTest(MakeGarbageCollected<SingleChildLocalFrameClient>()) {}
+
+  void SetUp() override {
+    EnableCompositing();
+    RenderingTest::SetUp();
+  }
+};
+
+INSTANTIATE_TEST_SUITE_P(All,
+                         PaintLayerOverlapTest,
+                         ::testing::Values(0, kCompositingOptimizations));
+
+TEST_P(PaintLayerOverlapTest, FixedUsesExpandedBoundingBoxForOverlap) {
+  SetBodyInnerHTML(R"HTML(
+    <style>
+      * {
+        margin: 0;
+      }
+      body {
+        height: 610px;
+        width: 820px;
+      }
+      #fixed {
+        height: 10px;
+        left: 50px;
+        position: fixed;
+        top: 50px;
+        width: 10px;
+      }
+    </style>
+    <div id=fixed></div>
+  )HTML");
+
+  PaintLayer* fixed = GetPaintLayerByElementId("fixed");
+  EXPECT_EQ(fixed->ExpandedBoundingBoxForCompositingOverlapTest(false),
+            IntRect(50, 50, 30, 20));
+  EXPECT_EQ(fixed->LocalBoundingBoxForCompositingOverlapTest(),
+            PhysicalRect(0, 0, 10, 10));
+  EXPECT_EQ(fixed->UnclippedAbsoluteBoundingBox(), IntRect(50, 50, 10, 10));
+  EXPECT_EQ(fixed->ClippedAbsoluteBoundingBox(), IntRect(50, 50, 10, 10));
+
+  // Modify the viewport scroll offset and ensure that the bounding box is still
+  // adjusted by the new amount the viewport can scroll in any direction.
+  GetDocument().View()->LayoutViewport()->SetScrollOffset(
+      ScrollOffset(10, 10), mojom::blink::ScrollType::kProgrammatic);
+  UpdateAllLifecyclePhasesForTest();
+
+  EXPECT_EQ(fixed->ExpandedBoundingBoxForCompositingOverlapTest(false),
+            IntRect(50, 50, 30, 20));
+  EXPECT_EQ(fixed->LocalBoundingBoxForCompositingOverlapTest(),
+            PhysicalRect(0, 0, 10, 10));
+  if (!RuntimeEnabledFeatures::CompositingOptimizationsEnabled()) {
+    EXPECT_EQ(fixed->UnclippedAbsoluteBoundingBox(), IntRect(50, 50, 10, 10));
+    EXPECT_EQ(fixed->ClippedAbsoluteBoundingBox(), IntRect(50, 50, 10, 10));
+  } else {
+    EXPECT_EQ(fixed->UnclippedAbsoluteBoundingBox(), IntRect(60, 60, 10, 10));
+    EXPECT_EQ(fixed->ClippedAbsoluteBoundingBox(), IntRect(60, 60, 10, 10));
+  }
+}
+
+TEST_P(PaintLayerOverlapTest,
+       FixedInScrollerUsesExpandedBoundingBoxForOverlap) {
+  SetBodyInnerHTML(R"HTML(
+    <style>
+      * {
+        margin: 0;
+      }
+      body {
+        height: 610px;
+        width: 820px;
+      }
+      #scroller {
+        height: 100px;
+        left: 100px;
+        overflow: scroll;
+        position: absolute;
+        top: 100px;
+        width: 100px;
+      }
+      #spacer {
+        height: 500px;
+      }
+      #fixed {
+        height: 10px;
+        left: 50px;
+        position: fixed;
+        top: 50px;
+        width: 10px;
+      }
+    </style>
+    <div id=scroller>
+      <div id=fixed></div>
+      <div id=spacer></div>
+    </div>
+  )HTML");
+
+  PaintLayer* fixed = GetPaintLayerByElementId("fixed");
+  EXPECT_EQ(fixed->ExpandedBoundingBoxForCompositingOverlapTest(false),
+            IntRect(50, 50, 30, 20));
+  EXPECT_EQ(fixed->LocalBoundingBoxForCompositingOverlapTest(),
+            PhysicalRect(0, 0, 10, 10));
+  EXPECT_EQ(fixed->UnclippedAbsoluteBoundingBox(), IntRect(50, 50, 10, 10));
+  EXPECT_EQ(fixed->ClippedAbsoluteBoundingBox(), IntRect(50, 50, 10, 10));
+
+  // Modify the inner scroll offset and ensure that the bounding box is still
+  // the same.
+  PaintLayerScrollableArea* scrollable_area =
+      GetPaintLayerByElementId("scroller")->GetScrollableArea();
+  scrollable_area->ScrollToAbsolutePosition(FloatPoint(10, 10));
+  UpdateAllLifecyclePhasesForTest();
+
+  EXPECT_EQ(fixed->ExpandedBoundingBoxForCompositingOverlapTest(false),
+            IntRect(50, 50, 30, 20));
+  EXPECT_EQ(fixed->LocalBoundingBoxForCompositingOverlapTest(),
+            PhysicalRect(0, 0, 10, 10));
+  EXPECT_EQ(fixed->UnclippedAbsoluteBoundingBox(), IntRect(50, 50, 10, 10));
+  EXPECT_EQ(fixed->ClippedAbsoluteBoundingBox(), IntRect(50, 50, 10, 10));
+
+  // Modify the viewport scroll offset and ensure that the bounding box is still
+  // adjusted by the newamount the viewport can scroll in any direction.
+  GetDocument().View()->LayoutViewport()->SetScrollOffset(
+      ScrollOffset(10, 10), mojom::blink::ScrollType::kProgrammatic);
+  UpdateAllLifecyclePhasesForTest();
+
+  EXPECT_EQ(fixed->ExpandedBoundingBoxForCompositingOverlapTest(false),
+            IntRect(50, 50, 30, 20));
+  EXPECT_EQ(fixed->LocalBoundingBoxForCompositingOverlapTest(),
+            PhysicalRect(0, 0, 10, 10));
+  if (!RuntimeEnabledFeatures::CompositingOptimizationsEnabled()) {
+    EXPECT_EQ(fixed->UnclippedAbsoluteBoundingBox(), IntRect(50, 50, 10, 10));
+    EXPECT_EQ(fixed->ClippedAbsoluteBoundingBox(), IntRect(50, 50, 10, 10));
+  } else {
+    EXPECT_EQ(fixed->UnclippedAbsoluteBoundingBox(), IntRect(60, 60, 10, 10));
+    EXPECT_EQ(fixed->ClippedAbsoluteBoundingBox(), IntRect(60, 60, 10, 10));
+  }
+}
+
+TEST_P(PaintLayerOverlapTest,
+       FixedUnderTransformDoesNotExpandBoundingBoxForOverlap) {
+  SetBodyInnerHTML(R"HTML(
+    <style>
+      .anim {
+        animation: pulse 5s infinite;
+      }
+      @keyframes pulse {
+        0% { opacity: 0.1; }
+        100% { opacity: 0.9; }
+      }
+      .xform {
+        height: 100px;
+        left: 100px;
+        position: absolute;
+        top: 100px;
+        transform: rotate(20deg);
+        width: 100px;
+      }
+      .fixed {
+        height: 50px;
+        left: 25px;
+        position: fixed;
+        top: 25px;
+        width: 50px;
+      }
+      .spacer {
+        height: 2000px;
+      }
+    </style>
+    <div id=fixed-cb class=xform>
+      <div id=fixed class='fixed anim'></div>
+    </div>
+    <div class=spacer></div>
+  )HTML");
+
+  // The animation is to cause the fixed to be composited. However, even with
+  // fixed composited, it shouldn't have expanded bounds because its containing
+  // block isn't the viewport.
+  PaintLayer* fixed = GetPaintLayerByElementId("fixed");
+  EXPECT_EQ(fixed->ExpandedBoundingBoxForCompositingOverlapTest(false),
+            IntRect(117, 117, 66, 66));
+  EXPECT_EQ(fixed->LocalBoundingBoxForCompositingOverlapTest(),
+            PhysicalRect(0, 0, 50, 50));
+  EXPECT_EQ(fixed->UnclippedAbsoluteBoundingBox(), IntRect(117, 117, 66, 66));
+  EXPECT_EQ(fixed->ClippedAbsoluteBoundingBox(), IntRect(117, 117, 66, 66));
+}
+
+TEST_P(PaintLayerOverlapTest, NestedFixedUsesExpandedBoundingBoxForOverlap) {
+  SetBodyInnerHTML(R"HTML(
+    <style>
+      * {
+        margin: 0;
+      }
+      body {
+        height: 610px;
+        width: 820px;
+      }
+      #iframe1 {
+        height: 100px;
+        left: 50px;
+        position: fixed;
+        top: 50px;
+        width: 100px;
+      }
+    </style>
+    <iframe id=iframe1></iframe>
+  )HTML");
+  SetChildFrameHTML(R"HTML(
+    <style>
+      * {
+        margin: 0;
+      }
+      body {
+        height: 500px;
+        width: 500px;
+      }
+      #fixed {
+        height: 10px;
+        left: 50px;
+        position: fixed;
+        top: 50px;
+        width: 10px;
+      }
+    </style>
+    <div id=fixed></div>
+  )HTML");
+  UpdateAllLifecyclePhasesForTest();
+
+  PaintLayer* fixed =
+      To<LayoutBoxModelObject>(
+          ChildDocument().getElementById("fixed")->GetLayoutObject())
+          ->Layer();
+  EXPECT_EQ(fixed->ExpandedBoundingBoxForCompositingOverlapTest(false),
+            IntRect(50, 50, 410, 410));
+  EXPECT_EQ(fixed->LocalBoundingBoxForCompositingOverlapTest(),
+            PhysicalRect(0, 0, 10, 10));
+  EXPECT_EQ(fixed->UnclippedAbsoluteBoundingBox(), IntRect(50, 50, 10, 10));
+  EXPECT_EQ(fixed->ClippedAbsoluteBoundingBox(), IntRect(50, 50, 10, 10));
+
+  // Modify the top-most viewport's scroll offset and ensure that the bounding
+  // box is still the same. This shows that we're not considering the wrong
+  // viewport's scroll offset when computing the bounding box.
+  GetDocument().View()->LayoutViewport()->SetScrollOffset(
+      ScrollOffset(10, 10), mojom::blink::ScrollType::kProgrammatic);
+  UpdateAllLifecyclePhasesForTest();
+
+  EXPECT_EQ(fixed->ExpandedBoundingBoxForCompositingOverlapTest(false),
+            IntRect(50, 50, 410, 410));
+  EXPECT_EQ(fixed->LocalBoundingBoxForCompositingOverlapTest(),
+            PhysicalRect(0, 0, 10, 10));
+  EXPECT_EQ(fixed->UnclippedAbsoluteBoundingBox(), IntRect(50, 50, 10, 10));
+  EXPECT_EQ(fixed->ClippedAbsoluteBoundingBox(), IntRect(50, 50, 10, 10));
+
+  // Now modify the iframe's scroll offset. This one should affect the fixed's
+  // bounding box.
+  ChildDocument().View()->LayoutViewport()->SetScrollOffset(
+      ScrollOffset(10, 10), mojom::blink::ScrollType::kProgrammatic);
+  UpdateAllLifecyclePhasesForTest();
+
+  EXPECT_EQ(fixed->ExpandedBoundingBoxForCompositingOverlapTest(false),
+            IntRect(50, 50, 410, 410));
+  EXPECT_EQ(fixed->LocalBoundingBoxForCompositingOverlapTest(),
+            PhysicalRect(0, 0, 10, 10));
+  if (!RuntimeEnabledFeatures::CompositingOptimizationsEnabled()) {
+    EXPECT_EQ(fixed->UnclippedAbsoluteBoundingBox(), IntRect(50, 50, 10, 10));
+    EXPECT_EQ(fixed->ClippedAbsoluteBoundingBox(), IntRect(50, 50, 10, 10));
+  } else {
+    EXPECT_EQ(fixed->UnclippedAbsoluteBoundingBox(), IntRect(60, 60, 10, 10));
+    EXPECT_EQ(fixed->ClippedAbsoluteBoundingBox(), IntRect(60, 60, 10, 10));
+  }
+}
+
+TEST_P(PaintLayerOverlapTest, FixedWithExpandedBoundsForChild) {
+  SetBodyInnerHTML(R"HTML(
+    <style>
+      * {
+        margin: 0;
+      }
+      body {
+        height: 1000px;
+      }
+      #fixed {
+        height: 50px;
+        left: 25px;
+        position: fixed;
+        top: 25px;
+        width: 50px;
+      }
+      #abs {
+        height: 25px;
+        left: 50px;
+        position: absolute;
+        top: 200px;
+        width: 25px;
+        will-change: transform;
+      }
+    </style>
+    <div id=fixed>
+      <div id=abs></div>
+    </div>
+  )HTML");
+  UpdateAllLifecyclePhasesForTest();
+
+  // The fixed-pos layer should use bounds that have been expanded to include
+  // the absolutely positioned child. Without this expansion, overlap testing
+  // can miss overlap from that child leading to incorrect composition order.
+  PaintLayer* fixed = GetPaintLayerByElementId("fixed");
+  EXPECT_EQ(fixed->ExpandedBoundingBoxForCompositingOverlapTest(false),
+            IntRect(25, 25, 75, 625));
+  EXPECT_EQ(fixed->LocalBoundingBoxForCompositingOverlapTest(),
+            PhysicalRect(0, 0, 50, 50));
+  EXPECT_EQ(fixed->UnclippedAbsoluteBoundingBox(), IntRect(25, 25, 50, 50));
+  EXPECT_EQ(fixed->ClippedAbsoluteBoundingBox(), IntRect(25, 25, 50, 50));
+
+  // Verify that the abs bounds is not expanded even though it is a child of a
+  // fixed-pos layer. Expanding the abs bounds would mean that it could
+  // unnecessarily detect overlap with siblings that it doesn't ever actually
+  // overlap with.
+  PaintLayer* abs = GetPaintLayerByElementId("abs");
+  EXPECT_EQ(abs->ExpandedBoundingBoxForCompositingOverlapTest(false),
+            IntRect(75, 225, 25, 25));
+  EXPECT_EQ(abs->LocalBoundingBoxForCompositingOverlapTest(),
+            PhysicalRect(0, 0, 25, 25));
+  EXPECT_EQ(abs->UnclippedAbsoluteBoundingBox(), IntRect(75, 225, 25, 25));
+  EXPECT_EQ(abs->ClippedAbsoluteBoundingBox(), IntRect(75, 225, 25, 25));
+
+  // Modify the scroll offset and ensure that the bounding box is still the
+  // same. Note that if we get different expanded bounding boxes for overlap
+  // testing with different scroll offsets then it implies that scroll offset is
+  // a part of that calculation and we may get incorrect results as scroll
+  // offsets changes and partial updates happen.
+  GetDocument().View()->LayoutViewport()->SetScrollOffset(
+      ScrollOffset(0, 400), mojom::blink::ScrollType::kProgrammatic);
+  UpdateAllLifecyclePhasesForTest();
+
+  EXPECT_EQ(fixed->ExpandedBoundingBoxForCompositingOverlapTest(false),
+            IntRect(25, 25, 75, 625));
+  EXPECT_EQ(fixed->LocalBoundingBoxForCompositingOverlapTest(),
+            PhysicalRect(0, 0, 50, 50));
+  if (!RuntimeEnabledFeatures::CompositingOptimizationsEnabled()) {
+    EXPECT_EQ(fixed->UnclippedAbsoluteBoundingBox(), IntRect(25, 25, 50, 50));
+    EXPECT_EQ(fixed->ClippedAbsoluteBoundingBox(), IntRect(25, 25, 50, 50));
+  } else {
+    EXPECT_EQ(fixed->UnclippedAbsoluteBoundingBox(), IntRect(25, 425, 50, 50));
+    EXPECT_EQ(fixed->ClippedAbsoluteBoundingBox(), IntRect(25, 425, 50, 50));
+  }
+
+  EXPECT_EQ(abs->ExpandedBoundingBoxForCompositingOverlapTest(false),
+            IntRect(75, 625, 25, 25));
+  EXPECT_EQ(abs->LocalBoundingBoxForCompositingOverlapTest(),
+            PhysicalRect(0, 0, 25, 25));
+  if (!RuntimeEnabledFeatures::CompositingOptimizationsEnabled()) {
+    EXPECT_EQ(abs->UnclippedAbsoluteBoundingBox(), IntRect(75, 225, 25, 25));
+    EXPECT_EQ(abs->ClippedAbsoluteBoundingBox(), IntRect(75, 225, 25, 25));
+  } else {
+    EXPECT_EQ(abs->UnclippedAbsoluteBoundingBox(), IntRect(75, 625, 25, 25));
+    EXPECT_EQ(abs->ClippedAbsoluteBoundingBox(), IntRect(75, 625, 25, 25));
+  }
+}
+
+TEST_P(PaintLayerOverlapTest, FixedWithClippedExpandedBoundsForChild) {
+  SetBodyInnerHTML(R"HTML(
+    <style>
+      * {
+        margin: 0;
+      }
+      body {
+        height: 1000px;
+      }
+      #fixed {
+        height: 50px;
+        left: 25px;
+        position: fixed;
+        top: 25px;
+        width: 50px;
+        overflow: hidden;
+      }
+      #abs {
+        height: 25px;
+        left: 50px;
+        position: absolute;
+        top: 200px;
+        width: 25px;
+        will-change: transform;
+      }
+    </style>
+    <div id=fixed>
+      <div id=abs></div>
+    </div>
+  )HTML");
+  UpdateAllLifecyclePhasesForTest();
+
+  // The fixed-pos layer should use bounds that have been expanded to include
+  // the absolutely positioned child. However, the fixed-pos ancestor also has
+  // clipping which will limit the expansion.
+  PaintLayer* fixed = GetPaintLayerByElementId("fixed");
+  EXPECT_EQ(fixed->ExpandedBoundingBoxForCompositingOverlapTest(false),
+            IntRect(25, 25, 50, 450));
+  EXPECT_EQ(fixed->LocalBoundingBoxForCompositingOverlapTest(),
+            PhysicalRect(0, 0, 50, 50));
+  EXPECT_EQ(fixed->UnclippedAbsoluteBoundingBox(), IntRect(25, 25, 50, 50));
+  EXPECT_EQ(fixed->ClippedAbsoluteBoundingBox(), IntRect(25, 25, 50, 50));
+
+  // Verify that the abs bounds is not expanded even though it is a child of a
+  // fixed-pos layer. Expanding the abs bounds would mean that it could
+  // unnecessarily detect overlap with siblings that it doesn't ever actually
+  // overlap with. Note that the clipped bounds is an empty rect because of the
+  // clipping from the ancestor.
+  PaintLayer* abs = GetPaintLayerByElementId("abs");
+  EXPECT_EQ(abs->ExpandedBoundingBoxForCompositingOverlapTest(false),
+            IntRect(75, 225, 25, 25));
+  EXPECT_EQ(abs->LocalBoundingBoxForCompositingOverlapTest(),
+            PhysicalRect(0, 0, 25, 25));
+  EXPECT_EQ(abs->UnclippedAbsoluteBoundingBox(), IntRect(75, 225, 25, 25));
+  EXPECT_EQ(abs->ClippedAbsoluteBoundingBox(), IntRect(0, 0, 0, 0));
+
+  // Modify the scroll offset and ensure that the bounding box is still the
+  // same. Note that if we get different expanded bounding boxes for overlap
+  // testing with different scroll offsets then it implies that scroll offset is
+  // a part of that calculation and we may get incorrect results as scroll
+  // offsets changes and partial updates happen.
+  GetDocument().View()->LayoutViewport()->SetScrollOffset(
+      ScrollOffset(0, 400), mojom::blink::ScrollType::kProgrammatic);
+  UpdateAllLifecyclePhasesForTest();
+
+  EXPECT_EQ(fixed->ExpandedBoundingBoxForCompositingOverlapTest(false),
+            IntRect(25, 25, 50, 450));
+  EXPECT_EQ(fixed->LocalBoundingBoxForCompositingOverlapTest(),
+            PhysicalRect(0, 0, 50, 50));
+  if (!RuntimeEnabledFeatures::CompositingOptimizationsEnabled()) {
+    EXPECT_EQ(fixed->UnclippedAbsoluteBoundingBox(), IntRect(25, 25, 50, 50));
+    EXPECT_EQ(fixed->ClippedAbsoluteBoundingBox(), IntRect(25, 25, 50, 50));
+  } else {
+    EXPECT_EQ(fixed->UnclippedAbsoluteBoundingBox(), IntRect(25, 425, 50, 50));
+    EXPECT_EQ(fixed->ClippedAbsoluteBoundingBox(), IntRect(25, 425, 50, 50));
+  }
+
+  EXPECT_EQ(abs->ExpandedBoundingBoxForCompositingOverlapTest(false),
+            IntRect(75, 625, 25, 25));
+  EXPECT_EQ(abs->LocalBoundingBoxForCompositingOverlapTest(),
+            PhysicalRect(0, 0, 25, 25));
+  if (!RuntimeEnabledFeatures::CompositingOptimizationsEnabled()) {
+    EXPECT_EQ(abs->UnclippedAbsoluteBoundingBox(), IntRect(75, 225, 25, 25));
+    EXPECT_EQ(abs->ClippedAbsoluteBoundingBox(), IntRect(0, 0, 0, 0));
+  } else {
+    EXPECT_EQ(abs->UnclippedAbsoluteBoundingBox(), IntRect(75, 625, 25, 25));
+    EXPECT_EQ(abs->ClippedAbsoluteBoundingBox(), IntRect(0, 0, 0, 0));
+  }
+}
+
+TEST_P(PaintLayerOverlapTest, FixedWithExpandedBoundsForGrandChild) {
+  SetBodyInnerHTML(R"HTML(
+    <style>
+      * {
+        margin: 0;
+      }
+      body {
+        height: 1000px;
+      }
+      #fixed {
+        height: 50px;
+        left: 25px;
+        position: fixed;
+        top: 25px;
+        width: 50px;
+      }
+      #abs {
+        height: 25px;
+        left: 50px;
+        position: absolute;
+        top: 200px;
+        width: 25px;
+      }
+      #abs2 {
+        height: 25px;
+        left: 50px;
+        position: absolute;
+        top: 100px;
+        width: 25px;
+      }
+    </style>
+    <div id=fixed>
+      <div id=abs>
+        <div id=abs2></div>
+      </div>
+    </div>
+  )HTML");
+  UpdateAllLifecyclePhasesForTest();
+
+  // The fixed-pos layer should use bounds that have been expanded to include
+  // the absolutely positioned grandchild.
+  PaintLayer* fixed = GetPaintLayerByElementId("fixed");
+  EXPECT_EQ(fixed->ExpandedBoundingBoxForCompositingOverlapTest(false),
+            IntRect(25, 25, 125, 725));
+  EXPECT_EQ(fixed->LocalBoundingBoxForCompositingOverlapTest(),
+            PhysicalRect(0, 0, 50, 50));
+  EXPECT_EQ(fixed->UnclippedAbsoluteBoundingBox(), IntRect(25, 25, 50, 50));
+  EXPECT_EQ(fixed->ClippedAbsoluteBoundingBox(), IntRect(25, 25, 50, 50));
+
+  // Verify that the abs bounds is not expanded even though it is a child of a
+  // fixed-pos layer. Additionally, it shouldn't include its child as only
+  // fixed-pos expands to include descendants.
+  PaintLayer* abs = GetPaintLayerByElementId("abs");
+  EXPECT_EQ(abs->ExpandedBoundingBoxForCompositingOverlapTest(false),
+            IntRect(75, 225, 25, 25));
+  EXPECT_EQ(abs->LocalBoundingBoxForCompositingOverlapTest(),
+            PhysicalRect(0, 0, 25, 25));
+  EXPECT_EQ(abs->UnclippedAbsoluteBoundingBox(), IntRect(75, 225, 25, 25));
+  EXPECT_EQ(abs->ClippedAbsoluteBoundingBox(), IntRect(75, 225, 25, 25));
+
+  PaintLayer* abs2 = GetPaintLayerByElementId("abs2");
+  EXPECT_EQ(abs2->ExpandedBoundingBoxForCompositingOverlapTest(false),
+            IntRect(125, 325, 25, 25));
+  EXPECT_EQ(abs2->LocalBoundingBoxForCompositingOverlapTest(),
+            PhysicalRect(0, 0, 25, 25));
+  EXPECT_EQ(abs2->UnclippedAbsoluteBoundingBox(), IntRect(125, 325, 25, 25));
+  EXPECT_EQ(abs2->ClippedAbsoluteBoundingBox(), IntRect(125, 325, 25, 25));
+
+  // Modify the scroll offset and ensure that the bounding box is still the
+  // same. Note that if we get different expanded bounding boxes for overlap
+  // testing with different scroll offsets then it implies that scroll offset is
+  // a part of that calculation and we may get incorrect results as scroll
+  // offsets changes and partial updates happen.
+  GetDocument().View()->LayoutViewport()->SetScrollOffset(
+      ScrollOffset(0, 400), mojom::blink::ScrollType::kProgrammatic);
+  UpdateAllLifecyclePhasesForTest();
+
+  EXPECT_EQ(fixed->ExpandedBoundingBoxForCompositingOverlapTest(false),
+            IntRect(25, 25, 125, 725));
+  EXPECT_EQ(fixed->LocalBoundingBoxForCompositingOverlapTest(),
+            PhysicalRect(0, 0, 50, 50));
+  if (!RuntimeEnabledFeatures::CompositingOptimizationsEnabled()) {
+    EXPECT_EQ(fixed->UnclippedAbsoluteBoundingBox(), IntRect(25, 25, 50, 50));
+    EXPECT_EQ(fixed->ClippedAbsoluteBoundingBox(), IntRect(25, 25, 50, 50));
+  } else {
+    EXPECT_EQ(fixed->UnclippedAbsoluteBoundingBox(), IntRect(25, 425, 50, 50));
+    EXPECT_EQ(fixed->ClippedAbsoluteBoundingBox(), IntRect(25, 425, 50, 50));
+  }
+
+  EXPECT_EQ(abs->ExpandedBoundingBoxForCompositingOverlapTest(false),
+            IntRect(75, 625, 25, 25));
+  EXPECT_EQ(abs->LocalBoundingBoxForCompositingOverlapTest(),
+            PhysicalRect(0, 0, 25, 25));
+  if (!RuntimeEnabledFeatures::CompositingOptimizationsEnabled()) {
+    EXPECT_EQ(abs->UnclippedAbsoluteBoundingBox(), IntRect(75, 225, 25, 25));
+    EXPECT_EQ(abs->ClippedAbsoluteBoundingBox(), IntRect(75, 225, 25, 25));
+  } else {
+    EXPECT_EQ(abs->UnclippedAbsoluteBoundingBox(), IntRect(75, 625, 25, 25));
+    EXPECT_EQ(abs->ClippedAbsoluteBoundingBox(), IntRect(75, 625, 25, 25));
+  }
+
+  EXPECT_EQ(abs2->ExpandedBoundingBoxForCompositingOverlapTest(false),
+            IntRect(125, 725, 25, 25));
+  EXPECT_EQ(abs2->LocalBoundingBoxForCompositingOverlapTest(),
+            PhysicalRect(0, 0, 25, 25));
+  if (!RuntimeEnabledFeatures::CompositingOptimizationsEnabled()) {
+    EXPECT_EQ(abs2->UnclippedAbsoluteBoundingBox(), IntRect(125, 325, 25, 25));
+    EXPECT_EQ(abs2->ClippedAbsoluteBoundingBox(), IntRect(125, 325, 25, 25));
+  } else {
+    EXPECT_EQ(abs2->UnclippedAbsoluteBoundingBox(), IntRect(125, 725, 25, 25));
+    EXPECT_EQ(abs2->ClippedAbsoluteBoundingBox(), IntRect(125, 725, 25, 25));
+  }
+
+  // Add will-change to the middle child to ensure the bounds are still the
+  // same. This helps confirm that the computation of the bounds is agnostic to
+  // if descendants are composited or not.
+  GetDocument().getElementById("abs")->setAttribute(html_names::kStyleAttr,
+                                                    "will-change: transform");
+  UpdateAllLifecyclePhasesForTest();
+
+  EXPECT_EQ(fixed->ExpandedBoundingBoxForCompositingOverlapTest(false),
+            IntRect(25, 25, 125, 725));
+  EXPECT_EQ(fixed->LocalBoundingBoxForCompositingOverlapTest(),
+            PhysicalRect(0, 0, 50, 50));
+  if (!RuntimeEnabledFeatures::CompositingOptimizationsEnabled()) {
+    EXPECT_EQ(fixed->UnclippedAbsoluteBoundingBox(), IntRect(25, 25, 50, 50));
+    EXPECT_EQ(fixed->ClippedAbsoluteBoundingBox(), IntRect(25, 25, 50, 50));
+  } else {
+    EXPECT_EQ(fixed->UnclippedAbsoluteBoundingBox(), IntRect(25, 425, 50, 50));
+    EXPECT_EQ(fixed->ClippedAbsoluteBoundingBox(), IntRect(25, 425, 50, 50));
+  }
+
+  EXPECT_EQ(abs->ExpandedBoundingBoxForCompositingOverlapTest(false),
+            IntRect(75, 625, 25, 25));
+  EXPECT_EQ(abs->LocalBoundingBoxForCompositingOverlapTest(),
+            PhysicalRect(0, 0, 25, 25));
+  if (!RuntimeEnabledFeatures::CompositingOptimizationsEnabled()) {
+    EXPECT_EQ(abs->UnclippedAbsoluteBoundingBox(), IntRect(75, 225, 25, 25));
+    EXPECT_EQ(abs->ClippedAbsoluteBoundingBox(), IntRect(75, 225, 25, 25));
+  } else {
+    EXPECT_EQ(abs->UnclippedAbsoluteBoundingBox(), IntRect(75, 625, 25, 25));
+    EXPECT_EQ(abs->ClippedAbsoluteBoundingBox(), IntRect(75, 625, 25, 25));
+  }
+
+  EXPECT_EQ(abs2->ExpandedBoundingBoxForCompositingOverlapTest(false),
+            IntRect(125, 725, 25, 25));
+  EXPECT_EQ(abs2->LocalBoundingBoxForCompositingOverlapTest(),
+            PhysicalRect(0, 0, 25, 25));
+  if (!RuntimeEnabledFeatures::CompositingOptimizationsEnabled()) {
+    EXPECT_EQ(abs2->UnclippedAbsoluteBoundingBox(), IntRect(125, 325, 25, 25));
+    EXPECT_EQ(abs2->ClippedAbsoluteBoundingBox(), IntRect(125, 325, 25, 25));
+  } else {
+    EXPECT_EQ(abs2->UnclippedAbsoluteBoundingBox(), IntRect(125, 725, 25, 25));
+    EXPECT_EQ(abs2->ClippedAbsoluteBoundingBox(), IntRect(125, 725, 25, 25));
+  }
+
+  // Add will-change to the grandchild and ensure the bounds are still the same.
+  GetDocument().getElementById("abs2")->setAttribute(html_names::kStyleAttr,
+                                                     "will-change: transform");
+  UpdateAllLifecyclePhasesForTest();
+
+  EXPECT_EQ(fixed->ExpandedBoundingBoxForCompositingOverlapTest(false),
+            IntRect(25, 25, 125, 725));
+  EXPECT_EQ(fixed->LocalBoundingBoxForCompositingOverlapTest(),
+            PhysicalRect(0, 0, 50, 50));
+  if (!RuntimeEnabledFeatures::CompositingOptimizationsEnabled()) {
+    EXPECT_EQ(fixed->UnclippedAbsoluteBoundingBox(), IntRect(25, 25, 50, 50));
+    EXPECT_EQ(fixed->ClippedAbsoluteBoundingBox(), IntRect(25, 25, 50, 50));
+  } else {
+    EXPECT_EQ(fixed->UnclippedAbsoluteBoundingBox(), IntRect(25, 425, 50, 50));
+    EXPECT_EQ(fixed->ClippedAbsoluteBoundingBox(), IntRect(25, 425, 50, 50));
+  }
+
+  EXPECT_EQ(abs->ExpandedBoundingBoxForCompositingOverlapTest(false),
+            IntRect(75, 625, 25, 25));
+  EXPECT_EQ(abs->LocalBoundingBoxForCompositingOverlapTest(),
+            PhysicalRect(0, 0, 25, 25));
+  if (!RuntimeEnabledFeatures::CompositingOptimizationsEnabled()) {
+    EXPECT_EQ(abs->UnclippedAbsoluteBoundingBox(), IntRect(75, 225, 25, 25));
+    EXPECT_EQ(abs->ClippedAbsoluteBoundingBox(), IntRect(75, 225, 25, 25));
+  } else {
+    EXPECT_EQ(abs->UnclippedAbsoluteBoundingBox(), IntRect(75, 625, 25, 25));
+    EXPECT_EQ(abs->ClippedAbsoluteBoundingBox(), IntRect(75, 625, 25, 25));
+  }
+
+  EXPECT_EQ(abs2->ExpandedBoundingBoxForCompositingOverlapTest(false),
+            IntRect(125, 725, 25, 25));
+  EXPECT_EQ(abs2->LocalBoundingBoxForCompositingOverlapTest(),
+            PhysicalRect(0, 0, 25, 25));
+  if (!RuntimeEnabledFeatures::CompositingOptimizationsEnabled()) {
+    EXPECT_EQ(abs2->UnclippedAbsoluteBoundingBox(), IntRect(125, 325, 25, 25));
+    EXPECT_EQ(abs2->ClippedAbsoluteBoundingBox(), IntRect(125, 325, 25, 25));
+  } else {
+    EXPECT_EQ(abs2->UnclippedAbsoluteBoundingBox(), IntRect(125, 725, 25, 25));
+    EXPECT_EQ(abs2->ClippedAbsoluteBoundingBox(), IntRect(125, 725, 25, 25));
+  }
+
+  // Remove will-change from the middle child and ensure the bounds are still
+  // the same.
+  GetDocument().getElementById("abs")->setAttribute(html_names::kStyleAttr, "");
+  UpdateAllLifecyclePhasesForTest();
+
+  EXPECT_EQ(fixed->ExpandedBoundingBoxForCompositingOverlapTest(false),
+            IntRect(25, 25, 125, 725));
+  EXPECT_EQ(fixed->LocalBoundingBoxForCompositingOverlapTest(),
+            PhysicalRect(0, 0, 50, 50));
+  if (!RuntimeEnabledFeatures::CompositingOptimizationsEnabled()) {
+    EXPECT_EQ(fixed->UnclippedAbsoluteBoundingBox(), IntRect(25, 25, 50, 50));
+    EXPECT_EQ(fixed->ClippedAbsoluteBoundingBox(), IntRect(25, 25, 50, 50));
+  } else {
+    EXPECT_EQ(fixed->UnclippedAbsoluteBoundingBox(), IntRect(25, 425, 50, 50));
+    EXPECT_EQ(fixed->ClippedAbsoluteBoundingBox(), IntRect(25, 425, 50, 50));
+  }
+
+  EXPECT_EQ(abs->ExpandedBoundingBoxForCompositingOverlapTest(false),
+            IntRect(75, 625, 25, 25));
+  EXPECT_EQ(abs->LocalBoundingBoxForCompositingOverlapTest(),
+            PhysicalRect(0, 0, 25, 25));
+  if (!RuntimeEnabledFeatures::CompositingOptimizationsEnabled()) {
+    EXPECT_EQ(abs->UnclippedAbsoluteBoundingBox(), IntRect(75, 225, 25, 25));
+    EXPECT_EQ(abs->ClippedAbsoluteBoundingBox(), IntRect(75, 225, 25, 25));
+  } else {
+    EXPECT_EQ(abs->UnclippedAbsoluteBoundingBox(), IntRect(75, 625, 25, 25));
+    EXPECT_EQ(abs->ClippedAbsoluteBoundingBox(), IntRect(75, 625, 25, 25));
+  }
+
+  EXPECT_EQ(abs2->ExpandedBoundingBoxForCompositingOverlapTest(false),
+            IntRect(125, 725, 25, 25));
+  EXPECT_EQ(abs2->LocalBoundingBoxForCompositingOverlapTest(),
+            PhysicalRect(0, 0, 25, 25));
+  if (!RuntimeEnabledFeatures::CompositingOptimizationsEnabled()) {
+    EXPECT_EQ(abs2->UnclippedAbsoluteBoundingBox(), IntRect(125, 325, 25, 25));
+    EXPECT_EQ(abs2->ClippedAbsoluteBoundingBox(), IntRect(125, 325, 25, 25));
+  } else {
+    EXPECT_EQ(abs2->UnclippedAbsoluteBoundingBox(), IntRect(125, 725, 25, 25));
+    EXPECT_EQ(abs2->ClippedAbsoluteBoundingBox(), IntRect(125, 725, 25, 25));
+  }
+}
+
+TEST_P(PaintLayerOverlapTest, FixedWithExpandedBoundsForFixedChild) {
+  SetBodyInnerHTML(R"HTML(
+    <style>
+      * {
+        margin: 0;
+      }
+      body {
+        height: 1000px;
+      }
+      #fixed {
+        height: 50px;
+        left: 25px;
+        position: fixed;
+        top: 25px;
+        width: 50px;
+      }
+      #nestedFixed {
+        height: 25px;
+        left: 50px;
+        position: fixed;
+        top: 100px;
+        width: 25px;
+      }
+    </style>
+    <div id=fixed>
+      <div id=nestedFixed></div>
+    </div>
+  )HTML");
+  UpdateAllLifecyclePhasesForTest();
+
+  // The fixed-pos layer should use bounds that have been expanded to include
+  // the absolutely positioned child. Without this expansion, overlap testing
+  // can miss overlap from that child leading to incorrect composition order.
+  PaintLayer* fixed = GetPaintLayerByElementId("fixed");
+  EXPECT_EQ(fixed->ExpandedBoundingBoxForCompositingOverlapTest(false),
+            IntRect(25, 25, 50, 500));
+  EXPECT_EQ(fixed->LocalBoundingBoxForCompositingOverlapTest(),
+            PhysicalRect(0, 0, 50, 50));
+  EXPECT_EQ(fixed->UnclippedAbsoluteBoundingBox(), IntRect(25, 25, 50, 50));
+  EXPECT_EQ(fixed->ClippedAbsoluteBoundingBox(), IntRect(25, 25, 50, 50));
+
+  // Note that the nested fixed should not expand its bounds as it doesn't move
+  // relative to its siblings, fixed-pos or not.
+  PaintLayer* nestedFixed = GetPaintLayerByElementId("nestedFixed");
+  EXPECT_EQ(nestedFixed->ExpandedBoundingBoxForCompositingOverlapTest(false),
+            IntRect(50, 100, 25, 25));
+  EXPECT_EQ(nestedFixed->LocalBoundingBoxForCompositingOverlapTest(),
+            PhysicalRect(0, 0, 25, 25));
+  EXPECT_EQ(nestedFixed->UnclippedAbsoluteBoundingBox(),
+            IntRect(50, 100, 25, 25));
+  EXPECT_EQ(nestedFixed->ClippedAbsoluteBoundingBox(),
+            IntRect(50, 100, 25, 25));
+
+  // Modify the scroll offset and ensure that the bounding box is still the
+  // same.
+  GetDocument().View()->LayoutViewport()->SetScrollOffset(
+      ScrollOffset(0, 400), mojom::blink::ScrollType::kProgrammatic);
+  UpdateAllLifecyclePhasesForTest();
+
+  EXPECT_EQ(fixed->ExpandedBoundingBoxForCompositingOverlapTest(false),
+            IntRect(25, 25, 50, 500));
+  EXPECT_EQ(fixed->LocalBoundingBoxForCompositingOverlapTest(),
+            PhysicalRect(0, 0, 50, 50));
+  if (!RuntimeEnabledFeatures::CompositingOptimizationsEnabled()) {
+    EXPECT_EQ(fixed->UnclippedAbsoluteBoundingBox(), IntRect(25, 25, 50, 50));
+    EXPECT_EQ(fixed->ClippedAbsoluteBoundingBox(), IntRect(25, 25, 50, 50));
+  } else {
+    EXPECT_EQ(fixed->UnclippedAbsoluteBoundingBox(), IntRect(25, 425, 50, 50));
+    EXPECT_EQ(fixed->ClippedAbsoluteBoundingBox(), IntRect(25, 425, 50, 50));
+  }
+
+  EXPECT_EQ(nestedFixed->ExpandedBoundingBoxForCompositingOverlapTest(false),
+            IntRect(50, 500, 25, 25));
+  EXPECT_EQ(nestedFixed->LocalBoundingBoxForCompositingOverlapTest(),
+            PhysicalRect(0, 0, 25, 25));
+  if (!RuntimeEnabledFeatures::CompositingOptimizationsEnabled()) {
+    EXPECT_EQ(nestedFixed->UnclippedAbsoluteBoundingBox(),
+              IntRect(50, 100, 25, 25));
+    EXPECT_EQ(nestedFixed->ClippedAbsoluteBoundingBox(),
+              IntRect(50, 100, 25, 25));
+  } else {
+    EXPECT_EQ(nestedFixed->UnclippedAbsoluteBoundingBox(),
+              IntRect(50, 500, 25, 25));
+    EXPECT_EQ(nestedFixed->ClippedAbsoluteBoundingBox(),
+              IntRect(50, 500, 25, 25));
+  }
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/wake_lock/BUILD.gn b/third_party/blink/renderer/modules/wake_lock/BUILD.gn
index 5a45d1f..6fc9c322 100644
--- a/third_party/blink/renderer/modules/wake_lock/BUILD.gn
+++ b/third_party/blink/renderer/modules/wake_lock/BUILD.gn
@@ -6,8 +6,6 @@
 
 blink_modules_sources("wake_lock") {
   sources = [
-    "navigator_wake_lock.cc",
-    "navigator_wake_lock.h",
     "wake_lock.cc",
     "wake_lock.h",
     "wake_lock_manager.cc",
@@ -16,8 +14,6 @@
     "wake_lock_sentinel.h",
     "wake_lock_type.cc",
     "wake_lock_type.h",
-    "worker_navigator_wake_lock.cc",
-    "worker_navigator_wake_lock.h",
   ]
   deps = [
     "//mojo/public/cpp/bindings",
diff --git a/third_party/blink/renderer/modules/wake_lock/navigator_wake_lock.cc b/third_party/blink/renderer/modules/wake_lock/navigator_wake_lock.cc
deleted file mode 100644
index 35c8053..0000000
--- a/third_party/blink/renderer/modules/wake_lock/navigator_wake_lock.cc
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "third_party/blink/renderer/modules/wake_lock/navigator_wake_lock.h"
-
-#include "third_party/blink/renderer/core/frame/local_dom_window.h"
-#include "third_party/blink/renderer/core/frame/navigator.h"
-#include "third_party/blink/renderer/modules/wake_lock/wake_lock.h"
-
-namespace blink {
-
-NavigatorWakeLock::NavigatorWakeLock(Navigator& navigator)
-    : Supplement<Navigator>(navigator) {}
-
-WakeLock* NavigatorWakeLock::GetWakeLock() {
-  if (!wake_lock_) {
-    if (auto* window = GetSupplementable()->DomWindow())
-      wake_lock_ = MakeGarbageCollected<WakeLock>(*window);
-  }
-  return wake_lock_;
-}
-
-// static
-const char NavigatorWakeLock::kSupplementName[] = "NavigatorWakeLock";
-
-// static
-NavigatorWakeLock& NavigatorWakeLock::From(Navigator& navigator) {
-  NavigatorWakeLock* supplement =
-      Supplement<Navigator>::From<NavigatorWakeLock>(navigator);
-  if (!supplement) {
-    supplement = MakeGarbageCollected<NavigatorWakeLock>(navigator);
-    ProvideTo(navigator, supplement);
-  }
-  return *supplement;
-}
-
-// static
-WakeLock* NavigatorWakeLock::wakeLock(Navigator& navigator) {
-  return NavigatorWakeLock::From(navigator).GetWakeLock();
-}
-
-void NavigatorWakeLock::Trace(Visitor* visitor) const {
-  visitor->Trace(wake_lock_);
-  Supplement<Navigator>::Trace(visitor);
-}
-
-}  // namespace blink
diff --git a/third_party/blink/renderer/modules/wake_lock/navigator_wake_lock.h b/third_party/blink/renderer/modules/wake_lock/navigator_wake_lock.h
deleted file mode 100644
index 6a5df68..0000000
--- a/third_party/blink/renderer/modules/wake_lock/navigator_wake_lock.h
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_WAKE_LOCK_NAVIGATOR_WAKE_LOCK_H_
-#define THIRD_PARTY_BLINK_RENDERER_MODULES_WAKE_LOCK_NAVIGATOR_WAKE_LOCK_H_
-
-#include "third_party/blink/renderer/platform/heap/handle.h"
-#include "third_party/blink/renderer/platform/supplementable.h"
-
-namespace blink {
-
-class Navigator;
-class WakeLock;
-
-class NavigatorWakeLock final : public GarbageCollected<NavigatorWakeLock>,
-                                public Supplement<Navigator> {
- public:
-  static const char kSupplementName[];
-
-  static NavigatorWakeLock& From(Navigator&);
-
-  static WakeLock* wakeLock(Navigator&);
-
-  explicit NavigatorWakeLock(Navigator&);
-
-  void Trace(Visitor*) const override;
-
- private:
-  WakeLock* GetWakeLock();
-
-  Member<WakeLock> wake_lock_;
-};
-
-}  // namespace blink
-
-#endif  // THIRD_PARTY_BLINK_RENDERER_MODULES_WAKE_LOCK_NAVIGATOR_WAKE_LOCK_H_
diff --git a/third_party/blink/renderer/modules/wake_lock/navigator_wake_lock.idl b/third_party/blink/renderer/modules/wake_lock/navigator_wake_lock.idl
index 48ecc6d..55d3b0f 100644
--- a/third_party/blink/renderer/modules/wake_lock/navigator_wake_lock.idl
+++ b/third_party/blink/renderer/modules/wake_lock/navigator_wake_lock.idl
@@ -5,7 +5,7 @@
 // https://w3c.github.io/screen-wake-lock/#extensions-to-the-navigator-interface
 
 [
-  ImplementedAs=NavigatorWakeLock,
+  ImplementedAs=WakeLock,
   RuntimeEnabled=WakeLock,
   SecureContext
 ] partial interface Navigator {
diff --git a/third_party/blink/renderer/modules/wake_lock/wake_lock.cc b/third_party/blink/renderer/modules/wake_lock/wake_lock.cc
index 8b3983f1..942e41d 100644
--- a/third_party/blink/renderer/modules/wake_lock/wake_lock.cc
+++ b/third_party/blink/renderer/modules/wake_lock/wake_lock.cc
@@ -9,6 +9,7 @@
 #include "third_party/blink/renderer/core/dom/document.h"
 #include "third_party/blink/renderer/core/dom/dom_exception.h"
 #include "third_party/blink/renderer/core/execution_context/execution_context.h"
+#include "third_party/blink/renderer/core/execution_context/navigator_base.h"
 #include "third_party/blink/renderer/core/frame/local_dom_window.h"
 #include "third_party/blink/renderer/core/frame/local_frame.h"
 #include "third_party/blink/renderer/core/frame/web_feature.h"
@@ -26,23 +27,39 @@
 using mojom::blink::PermissionService;
 using mojom::blink::PermissionStatus;
 
-WakeLock::WakeLock(LocalDOMWindow& window)
-    : ExecutionContextLifecycleObserver(&window),
-      PageVisibilityObserver(window.GetFrame()->GetPage()),
-      permission_service_(&window),
-      managers_{
-          MakeGarbageCollected<WakeLockManager>(&window, WakeLockType::kScreen),
-          MakeGarbageCollected<WakeLockManager>(&window,
-                                                WakeLockType::kSystem)} {}
+// static
+const char WakeLock::kSupplementName[] = "WakeLock";
 
-WakeLock::WakeLock(DedicatedWorkerGlobalScope& worker_scope)
-    : ExecutionContextLifecycleObserver(&worker_scope),
-      PageVisibilityObserver(nullptr),
-      permission_service_(&worker_scope),
-      managers_{MakeGarbageCollected<WakeLockManager>(&worker_scope,
-                                                      WakeLockType::kScreen),
-                MakeGarbageCollected<WakeLockManager>(&worker_scope,
-                                                      WakeLockType::kSystem)} {}
+// static
+WakeLock* WakeLock::wakeLock(NavigatorBase& navigator) {
+  ExecutionContext* context = navigator.GetExecutionContext();
+  if (!context ||
+      (!context->IsWindow() && !context->IsDedicatedWorkerGlobalScope())) {
+    // TODO(https://crbug.com/839117): Remove this check once the Exposed
+    // attribute is fixed to only expose this property in dedicated workers.
+    return nullptr;
+  }
+
+  WakeLock* supplement = Supplement<NavigatorBase>::From<WakeLock>(navigator);
+  if (!supplement && navigator.GetExecutionContext()) {
+    supplement = MakeGarbageCollected<WakeLock>(navigator);
+    ProvideTo(navigator, supplement);
+  }
+  return supplement;
+}
+
+WakeLock::WakeLock(NavigatorBase& navigator)
+    : Supplement<NavigatorBase>(navigator),
+      ExecutionContextLifecycleObserver(navigator.GetExecutionContext()),
+      PageVisibilityObserver(navigator.DomWindow()
+                                 ? navigator.DomWindow()->GetFrame()->GetPage()
+                                 : nullptr),
+      permission_service_(navigator.GetExecutionContext()),
+      managers_{
+          MakeGarbageCollected<WakeLockManager>(navigator.GetExecutionContext(),
+                                                WakeLockType::kScreen),
+          MakeGarbageCollected<WakeLockManager>(navigator.GetExecutionContext(),
+                                                WakeLockType::kSystem)} {}
 
 ScriptPromise WakeLock::request(ScriptState* script_state,
                                 const String& type,
@@ -278,6 +295,7 @@
   for (const WakeLockManager* manager : managers_)
     visitor->Trace(manager);
   visitor->Trace(permission_service_);
+  Supplement<NavigatorBase>::Trace(visitor);
   PageVisibilityObserver::Trace(visitor);
   ExecutionContextLifecycleObserver::Trace(visitor);
   ScriptWrappable::Trace(visitor);
diff --git a/third_party/blink/renderer/modules/wake_lock/wake_lock.h b/third_party/blink/renderer/modules/wake_lock/wake_lock.h
index 0a8d27e..f405b0b 100644
--- a/third_party/blink/renderer/modules/wake_lock/wake_lock.h
+++ b/third_party/blink/renderer/modules/wake_lock/wake_lock.h
@@ -18,6 +18,7 @@
 #include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/mojo/heap_mojo_remote.h"
 #include "third_party/blink/renderer/platform/mojo/heap_mojo_wrapper_mode.h"
+#include "third_party/blink/renderer/platform/supplementable.h"
 
 namespace WTF {
 
@@ -28,18 +29,23 @@
 namespace blink {
 
 class ExceptionState;
-class LocalDOMWindow;
+class NavigatorBase;
 class ScriptState;
 class WakeLockManager;
 
 class MODULES_EXPORT WakeLock final : public ScriptWrappable,
+                                      public Supplement<NavigatorBase>,
                                       public ExecutionContextLifecycleObserver,
                                       public PageVisibilityObserver {
   DEFINE_WRAPPERTYPEINFO();
 
  public:
-  explicit WakeLock(LocalDOMWindow&);
-  explicit WakeLock(DedicatedWorkerGlobalScope&);
+  static const char kSupplementName[];
+
+  // Getter for navigator.wakelock
+  static WakeLock* wakeLock(NavigatorBase&);
+
+  explicit WakeLock(NavigatorBase&);
 
   ScriptPromise request(ScriptState*,
                         const WTF::String& type,
diff --git a/third_party/blink/renderer/modules/wake_lock/wake_lock_sentinel_test.cc b/third_party/blink/renderer/modules/wake_lock/wake_lock_sentinel_test.cc
index daf4cc2..6275adc 100644
--- a/third_party/blink/renderer/modules/wake_lock/wake_lock_sentinel_test.cc
+++ b/third_party/blink/renderer/modules/wake_lock/wake_lock_sentinel_test.cc
@@ -12,6 +12,7 @@
 #include "third_party/blink/renderer/core/dom/events/native_event_listener.h"
 #include "third_party/blink/renderer/core/frame/local_dom_window.h"
 #include "third_party/blink/renderer/core/frame/local_frame.h"
+#include "third_party/blink/renderer/core/frame/navigator.h"
 #include "third_party/blink/renderer/modules/event_target_modules_names.h"
 #include "third_party/blink/renderer/modules/wake_lock/wake_lock.h"
 #include "third_party/blink/renderer/modules/wake_lock/wake_lock_manager.h"
@@ -114,7 +115,7 @@
       MakeGarbageCollected<ScriptPromiseResolver>(context.GetScriptState());
   ScriptPromise screen_promise = screen_resolver->Promise();
 
-  auto* wake_lock = MakeGarbageCollected<WakeLock>(*context.DomWindow());
+  auto* wake_lock = WakeLock::wakeLock(*context.DomWindow()->navigator());
   wake_lock->DoRequest(WakeLockType::kScreen, screen_resolver);
 
   WakeLockManager* manager =
diff --git a/third_party/blink/renderer/modules/wake_lock/wake_lock_test.cc b/third_party/blink/renderer/modules/wake_lock/wake_lock_test.cc
index 29706f0..ee35774c 100644
--- a/third_party/blink/renderer/modules/wake_lock/wake_lock_test.cc
+++ b/third_party/blink/renderer/modules/wake_lock/wake_lock_test.cc
@@ -10,6 +10,7 @@
 #include "third_party/blink/renderer/core/dom/dom_exception.h"
 #include "third_party/blink/renderer/core/frame/local_dom_window.h"
 #include "third_party/blink/renderer/core/frame/local_frame.h"
+#include "third_party/blink/renderer/core/frame/navigator.h"
 #include "third_party/blink/renderer/core/page/page.h"
 #include "third_party/blink/renderer/modules/wake_lock/wake_lock_test_utils.h"
 #include "third_party/blink/renderer/platform/heap/handle.h"
@@ -28,7 +29,7 @@
       MakeGarbageCollected<ScriptPromiseResolver>(context.GetScriptState());
   ScriptPromise screen_promise = screen_resolver->Promise();
 
-  auto* wake_lock = MakeGarbageCollected<WakeLock>(*context.DomWindow());
+  auto* wake_lock = WakeLock::wakeLock(*context.DomWindow()->navigator());
   wake_lock->DoRequest(WakeLockType::kScreen, screen_resolver);
 
   MockWakeLock& screen_lock =
@@ -55,7 +56,7 @@
       MakeGarbageCollected<ScriptPromiseResolver>(context.GetScriptState());
   ScriptPromise system_promise = system_resolver->Promise();
 
-  auto* wake_lock = MakeGarbageCollected<WakeLock>(*context.DomWindow());
+  auto* wake_lock = WakeLock::wakeLock(*context.DomWindow()->navigator());
   wake_lock->DoRequest(WakeLockType::kSystem, system_resolver);
 
   MockWakeLock& system_lock =
@@ -102,7 +103,7 @@
       MakeGarbageCollected<ScriptPromiseResolver>(context.GetScriptState());
   system_resolver1->Promise();
 
-  auto* wake_lock = MakeGarbageCollected<WakeLock>(*context.DomWindow());
+  auto* wake_lock = WakeLock::wakeLock(*context.DomWindow()->navigator());
   wake_lock->DoRequest(WakeLockType::kScreen, screen_resolver1);
   wake_lock->DoRequest(WakeLockType::kScreen, screen_resolver2);
   screen_lock.WaitForRequest();
@@ -143,7 +144,7 @@
       MakeGarbageCollected<ScriptPromiseResolver>(context.GetScriptState());
   ScriptPromise system_promise = system_resolver->Promise();
 
-  auto* wake_lock = MakeGarbageCollected<WakeLock>(*context.DomWindow());
+  auto* wake_lock = WakeLock::wakeLock(*context.DomWindow()->navigator());
   wake_lock->DoRequest(WakeLockType::kScreen, screen_resolver);
   screen_lock.WaitForRequest();
   wake_lock->DoRequest(WakeLockType::kSystem, system_resolver);
@@ -194,7 +195,7 @@
       MakeGarbageCollected<ScriptPromiseResolver>(context.GetScriptState());
   ScriptPromise system_promise = system_resolver->Promise();
 
-  auto* wake_lock = MakeGarbageCollected<WakeLock>(*context.DomWindow());
+  auto* wake_lock = WakeLock::wakeLock(*context.DomWindow()->navigator());
   wake_lock->DoRequest(WakeLockType::kScreen, screen_resolver);
   wake_lock->DoRequest(WakeLockType::kSystem, system_resolver);
   context.Frame()->GetPage()->SetVisibilityState(
diff --git a/third_party/blink/renderer/modules/wake_lock/worker_navigator_wake_lock.cc b/third_party/blink/renderer/modules/wake_lock/worker_navigator_wake_lock.cc
deleted file mode 100644
index ecd93b8..0000000
--- a/third_party/blink/renderer/modules/wake_lock/worker_navigator_wake_lock.cc
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "third_party/blink/renderer/modules/wake_lock/worker_navigator_wake_lock.h"
-
-#include "third_party/blink/renderer/core/workers/worker_navigator.h"
-#include "third_party/blink/renderer/modules/wake_lock/wake_lock.h"
-
-namespace blink {
-
-WorkerNavigatorWakeLock::WorkerNavigatorWakeLock(WorkerNavigator& navigator)
-    : Supplement<WorkerNavigator>(navigator) {}
-
-// static
-const char WorkerNavigatorWakeLock::kSupplementName[] =
-    "WorkerNavigatorWakeLock";
-
-// static
-WorkerNavigatorWakeLock& WorkerNavigatorWakeLock::From(
-    WorkerNavigator& navigator) {
-  WorkerNavigatorWakeLock* supplement =
-      Supplement<WorkerNavigator>::From<WorkerNavigatorWakeLock>(navigator);
-  if (!supplement) {
-    supplement = MakeGarbageCollected<WorkerNavigatorWakeLock>(navigator);
-    ProvideTo(navigator, supplement);
-  }
-  return *supplement;
-}
-
-// static
-WakeLock* WorkerNavigatorWakeLock::wakeLock(ScriptState* script_state,
-                                            WorkerNavigator& navigator) {
-  return WorkerNavigatorWakeLock::From(navigator).GetWakeLock(script_state);
-}
-
-WakeLock* WorkerNavigatorWakeLock::GetWakeLock(ScriptState* script_state) {
-  if (!wake_lock_) {
-    auto* execution_context = ExecutionContext::From(script_state);
-    DCHECK(execution_context);
-
-    // TODO(https://crbug.com/839117): Remove this check once the Exposed
-    // attribute is fixed to only expose this property in dedicated workers.
-    if (execution_context->IsDedicatedWorkerGlobalScope()) {
-      wake_lock_ = MakeGarbageCollected<WakeLock>(
-          *To<DedicatedWorkerGlobalScope>(execution_context));
-    }
-  }
-  return wake_lock_;
-}
-
-void WorkerNavigatorWakeLock::Trace(Visitor* visitor) const {
-  visitor->Trace(wake_lock_);
-  Supplement<WorkerNavigator>::Trace(visitor);
-}
-
-}  // namespace blink
diff --git a/third_party/blink/renderer/modules/wake_lock/worker_navigator_wake_lock.h b/third_party/blink/renderer/modules/wake_lock/worker_navigator_wake_lock.h
deleted file mode 100644
index 031d054..0000000
--- a/third_party/blink/renderer/modules/wake_lock/worker_navigator_wake_lock.h
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_WAKE_LOCK_WORKER_NAVIGATOR_WAKE_LOCK_H_
-#define THIRD_PARTY_BLINK_RENDERER_MODULES_WAKE_LOCK_WORKER_NAVIGATOR_WAKE_LOCK_H_
-
-#include "third_party/blink/renderer/platform/heap/handle.h"
-#include "third_party/blink/renderer/platform/supplementable.h"
-
-namespace blink {
-
-class ScriptState;
-class WakeLock;
-class WorkerNavigator;
-
-class WorkerNavigatorWakeLock final
-    : public GarbageCollected<WorkerNavigatorWakeLock>,
-      public Supplement<WorkerNavigator> {
- public:
-  static const char kSupplementName[];
-
-  static WorkerNavigatorWakeLock& From(WorkerNavigator&);
-
-  static WakeLock* wakeLock(ScriptState*, WorkerNavigator&);
-
-  explicit WorkerNavigatorWakeLock(WorkerNavigator&);
-
-  void Trace(Visitor*) const override;
-
- private:
-  WakeLock* GetWakeLock(ScriptState*);
-
-  Member<WakeLock> wake_lock_;
-};
-
-}  // namespace blink
-
-#endif  // THIRD_PARTY_BLINK_RENDERER_MODULES_WAKE_LOCK_WORKER_NAVIGATOR_WAKE_LOCK_H_
diff --git a/third_party/blink/renderer/modules/wake_lock/worker_navigator_wake_lock.idl b/third_party/blink/renderer/modules/wake_lock/worker_navigator_wake_lock.idl
index 764e881..9b0dcac 100644
--- a/third_party/blink/renderer/modules/wake_lock/worker_navigator_wake_lock.idl
+++ b/third_party/blink/renderer/modules/wake_lock/worker_navigator_wake_lock.idl
@@ -7,9 +7,9 @@
 // until System Wake Lock API was split from the Screen Wake Lock API.
 
 [
-  ImplementedAs=WorkerNavigatorWakeLock,
+  ImplementedAs=WakeLock,
   RuntimeEnabled=SystemWakeLock,
   SecureContext
 ] partial interface WorkerNavigator {
-  [CallWith=ScriptState, SameObject] readonly attribute WakeLock wakeLock;
+  [SameObject] readonly attribute WakeLock wakeLock;
 };
diff --git a/third_party/blink/renderer/platform/image-decoders/avif/avif_image_decoder.cc b/third_party/blink/renderer/platform/image-decoders/avif/avif_image_decoder.cc
index 22dac65..2ec11fa 100644
--- a/third_party/blink/renderer/platform/image-decoders/avif/avif_image_decoder.cc
+++ b/third_party/blink/renderer/platform/image-decoders/avif/avif_image_decoder.cc
@@ -29,7 +29,6 @@
 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
 #include "third_party/libavif/src/include/avif/avif.h"
 #include "third_party/libyuv/include/libyuv.h"
-#include "third_party/skia/include/core/SkData.h"
 #include "ui/gfx/color_space.h"
 #include "ui/gfx/color_transform.h"
 #include "ui/gfx/half_float.h"
@@ -41,6 +40,11 @@
 
 namespace {
 
+// The maximum AVIF file size we are willing to decode. This helps libavif
+// detect invalid sizes and offsets in an AVIF file before the file size is
+// known.
+constexpr uint64_t kMaxAvifFileSize = 0x10000000;  // 256 MB
+
 // Builds a gfx::ColorSpace from the ITU-T H.273 (CICP) color description in the
 // image. This color space is used to create the gfx::ColorTransform for the
 // YUV-to-RGB conversion. If the image does not have an ICC profile, this color
@@ -271,11 +275,11 @@
 }
 
 void AVIFImageDecoder::OnSetData(SegmentReader* data) {
-  // avifDecoder requires all the data be available before reading and cannot
-  // read incrementally as data comes in. See
-  // https://github.com/AOMediaCodec/libavif/issues/11.
-  if (IsAllDataReceived() && !MaybeCreateDemuxer())
-    SetFailed();
+  have_parsed_current_data_ = false;
+  const bool all_data_received = IsAllDataReceived();
+  avif_io_data_.reader = data_.get();
+  avif_io_data_.all_data_received = all_data_received;
+  avif_io_.sizeHint = all_data_received ? data_->size() : kMaxAvifFileSize;
 }
 
 cc::YUVSubsampling AVIFImageDecoder::GetYUVSubsampling() const {
@@ -355,7 +359,7 @@
   // libavif cannot decode to an external buffer. So we need to copy from
   // libavif's internal buffer to |image_planes_|.
   // TODO(crbug.com/1099825): Enhance libavif to decode to an external buffer.
-  if (!DecodeImage(0)) {
+  if (DecodeImage(0) != AVIF_RESULT_OK) {
     SetFailed();
     return;
   }
@@ -449,6 +453,18 @@
   return decoded_frame_count_ > 1 ? kAnimationLoopInfinite : kAnimationNone;
 }
 
+bool AVIFImageDecoder::FrameIsReceivedAtIndex(size_t index) const {
+  if (!IsDecodedSizeAvailable())
+    return false;
+  if (decoded_frame_count_ == 1)
+    return ImageDecoder::FrameIsReceivedAtIndex(index);
+  // frame_buffer_cache_.size() is equal to the return value of
+  // DecodeFrameCount(). Since DecodeFrameCount() returns the number of frames
+  // whose encoded data have been received, we can return true if |index| is
+  // valid for frame_buffer_cache_. See crbug.com/1148577.
+  return index < frame_buffer_cache_.size();
+}
+
 base::TimeDelta AVIFImageDecoder::FrameDurationAtIndex(size_t index) const {
   return index < frame_buffer_cache_.size()
              ? frame_buffer_cache_[index].Duration()
@@ -496,15 +512,31 @@
   return color_transform_.get();
 }
 
+void AVIFImageDecoder::ParseMetadata() {
+  if (!UpdateDemuxer())
+    SetFailed();
+}
+
 void AVIFImageDecoder::DecodeSize() {
-  // Because avifDecoder cannot read incrementally as data comes in, we cannot
-  // decode the size until all data is received. When all data is received,
-  // OnSetData() decodes the size right away. So DecodeSize() doesn't need to do
-  // anything.
+  ParseMetadata();
 }
 
 size_t AVIFImageDecoder::DecodeFrameCount() {
-  return Failed() ? frame_buffer_cache_.size() : decoded_frame_count_;
+  if (!Failed())
+    ParseMetadata();
+  if (!IsDecodedSizeAvailable())
+    return frame_buffer_cache_.size();
+  if (decoded_frame_count_ == 1 || IsAllDataReceived())
+    return decoded_frame_count_;
+  // For a multi-frame image, Chrome expects DecodeFrameCount() to return the
+  // number of frames whose encoded data have been received.
+  size_t index;
+  for (index = frame_buffer_cache_.size(); index < decoded_frame_count_;
+       ++index) {
+    if (avifDecoderNthImageReady(decoder_.get(), index) != AVIF_RESULT_OK)
+      break;
+  }
+  return index;
 }
 
 void AVIFImageDecoder::InitializeNewFrame(size_t index) {
@@ -522,16 +554,15 @@
 }
 
 void AVIFImageDecoder::Decode(size_t index) {
-  // TODO(dalecurtis): For fragmented AVIF image sequence files we probably want
-  // to allow partial decoding. Depends on if we see frequent use of multi-track
-  // images where there's lots to ignore.
-  if (Failed() || !IsAllDataReceived())
+  if (Failed())
     return;
 
   UpdateAggressivePurging(index);
 
-  if (!DecodeImage(index)) {
-    SetFailed();
+  auto ret = DecodeImage(index);
+  if (ret != AVIF_RESULT_OK) {
+    if (ret != AVIF_RESULT_WAITING_ON_IO)
+      SetFailed();
     return;
   }
 
@@ -587,46 +618,102 @@
   return true;
 }
 
-bool AVIFImageDecoder::MaybeCreateDemuxer() {
-  if (decoder_)
+// static
+avifResult AVIFImageDecoder::ReadFromSegmentReader(avifIO* io,
+                                                   uint32_t read_flags,
+                                                   uint64_t offset,
+                                                   size_t size,
+                                                   avifROData* out) {
+  if (read_flags != 0) {
+    // Unsupported read_flags
+    return AVIF_RESULT_IO_ERROR;
+  }
+
+  AvifIOData* io_data = static_cast<AvifIOData*>(io->data);
+
+  // Sanitize/clamp incoming request
+  if (offset > io_data->reader->size()) {
+    // The offset is past the end of the buffer or available data.
+    return io_data->all_data_received ? AVIF_RESULT_IO_ERROR
+                                      : AVIF_RESULT_WAITING_ON_IO;
+  }
+
+  // It is more convenient to work with a variable of the size_t type. Since
+  // offset <= io_data->reader->size() <= SIZE_MAX, this cast is safe.
+  size_t position = static_cast<size_t>(offset);
+  const size_t available_size = io_data->reader->size() - position;
+  if (size > available_size) {
+    if (!io_data->all_data_received)
+      return AVIF_RESULT_WAITING_ON_IO;
+    size = available_size;
+  }
+
+  out->size = size;
+  const char* data;
+  size_t data_size = io_data->reader->GetSomeData(data, position);
+  if (data_size >= size) {
+    out->data = reinterpret_cast<const uint8_t*>(data);
+    return AVIF_RESULT_OK;
+  }
+
+  io_data->buffer.clear();
+  io_data->buffer.reserve(size);
+  while (size != 0) {
+    data_size = io_data->reader->GetSomeData(data, position);
+    size_t copy_size = std::min(data_size, size);
+    io_data->buffer.insert(io_data->buffer.end(), data, data + copy_size);
+    position += copy_size;
+    size -= copy_size;
+  }
+
+  out->data = io_data->buffer.data();
+  return AVIF_RESULT_OK;
+}
+
+bool AVIFImageDecoder::UpdateDemuxer() {
+  DCHECK(!Failed());
+  if (IsDecodedSizeAvailable())
     return true;
 
-  decoder_ = std::unique_ptr<avifDecoder, void (*)(avifDecoder*)>(
-      avifDecoderCreate(), avifDecoderDestroy);
-  if (!decoder_)
-    return false;
+  if (have_parsed_current_data_)
+    return true;
+  have_parsed_current_data_ = true;
 
-  // TODO(dalecurtis): This may create a second copy of the media data in
-  // memory, which is not great. libavif should provide a read() based API:
-  // https://github.com/AOMediaCodec/libavif/issues/11
-  image_data_ = data_->GetAsSkData();
-  if (!image_data_)
-    return false;
+  if (!decoder_) {
+    decoder_ = std::unique_ptr<avifDecoder, void (*)(avifDecoder*)>(
+        avifDecoderCreate(), avifDecoderDestroy);
+    if (!decoder_)
+      return false;
 
-  // TODO(wtc): Currently libavif always prioritizes the animation, but that's
-  // not correct. It should instead select animation or still image based on the
-  // preferred and major brands listed in the file.
-  if (animation_option_ != AnimationOption::kUnspecified &&
-      avifDecoderSetSource(
-          decoder_.get(), animation_option_ == AnimationOption::kPreferAnimation
-                              ? AVIF_DECODER_SOURCE_TRACKS
-                              : AVIF_DECODER_SOURCE_PRIMARY_ITEM) !=
-          AVIF_RESULT_OK) {
-    return false;
+    // TODO(wtc): Currently libavif always prioritizes the animation, but that's
+    // not correct. It should instead select animation or still image based on
+    // the preferred and major brands listed in the file.
+    if (animation_option_ != AnimationOption::kUnspecified &&
+        avifDecoderSetSource(
+            decoder_.get(),
+            animation_option_ == AnimationOption::kPreferAnimation
+                ? AVIF_DECODER_SOURCE_TRACKS
+                : AVIF_DECODER_SOURCE_PRIMARY_ITEM) != AVIF_RESULT_OK) {
+      return false;
+    }
+
+    // Chrome doesn't use XMP and Exif metadata. Ignoring XMP and Exif will
+    // ensure avifDecoderParse() isn't waiting for some tiny Exif payload hiding
+    // at the end of a file.
+    decoder_->ignoreXMP = AVIF_TRUE;
+    decoder_->ignoreExif = AVIF_TRUE;
+
+    avif_io_.destroy = nullptr;
+    avif_io_.read = ReadFromSegmentReader;
+    avif_io_.write = nullptr;
+    avif_io_.persistent = AVIF_FALSE;
+    avif_io_.data = &avif_io_data_;
+    avifDecoderSetIO(decoder_.get(), &avif_io_);
   }
 
-  // Chrome doesn't use XMP and Exif metadata. Ignoring XMP and Exif will ensure
-  // avifDecoderParse() isn't waiting for some tiny Exif payload hiding at the
-  // end of a file.
-  decoder_->ignoreXMP = AVIF_TRUE;
-  decoder_->ignoreExif = AVIF_TRUE;
-  auto ret = avifDecoderSetIOMemory(decoder_.get(), image_data_->bytes(),
-                                    image_data_->size());
-  if (ret != AVIF_RESULT_OK) {
-    DVLOG(1) << "avifDecoderSetIOMemory failed: " << avifResultToString(ret);
-    return false;
-  }
-  ret = avifDecoderParse(decoder_.get());
+  auto ret = avifDecoderParse(decoder_.get());
+  if (ret == AVIF_RESULT_WAITING_ON_IO)
+    return true;
   if (ret != AVIF_RESULT_OK) {
     DVLOG(1) << "avifDecoderParse failed: " << avifResultToString(ret);
     return false;
@@ -724,12 +811,12 @@
   return SetSize(container->width, container->height);
 }
 
-bool AVIFImageDecoder::DecodeImage(size_t index) {
+avifResult AVIFImageDecoder::DecodeImage(size_t index) {
   const auto ret = avifDecoderNthImage(decoder_.get(), index);
   // |index| should be less than what DecodeFrameCount() returns, so we should
   // not get the AVIF_RESULT_NO_IMAGES_REMAINING error.
   DCHECK_NE(ret, AVIF_RESULT_NO_IMAGES_REMAINING);
-  return ret == AVIF_RESULT_OK;
+  return ret;
 }
 
 void AVIFImageDecoder::UpdateColorTransform(const gfx::ColorSpace& frame_cs,
diff --git a/third_party/blink/renderer/platform/image-decoders/avif/avif_image_decoder.h b/third_party/blink/renderer/platform/image-decoders/avif/avif_image_decoder.h
index b3de5b6..098f8ba 100644
--- a/third_party/blink/renderer/platform/image-decoders/avif/avif_image_decoder.h
+++ b/third_party/blink/renderer/platform/image-decoders/avif/avif_image_decoder.h
@@ -8,14 +8,11 @@
 #include <memory>
 
 #include "third_party/blink/renderer/platform/image-decoders/image_decoder.h"
-#include "third_party/skia/include/core/SkBitmap.h"
-#include "third_party/skia/include/core/SkData.h"
+#include "third_party/libavif/src/include/avif/avif.h"
+#include "third_party/skia/include/core/SkImageInfo.h"
 #include "ui/gfx/color_space.h"
 #include "ui/gfx/color_transform.h"
 
-struct avifDecoder;
-struct avifImage;
-
 namespace blink {
 
 class FastSharedBufferReader;
@@ -42,6 +39,7 @@
   uint8_t GetYUVBitDepth() const override;
   void DecodeToYUV() override;
   int RepetitionCount() const override;
+  bool FrameIsReceivedAtIndex(size_t) const override;
   base::TimeDelta FrameDurationAtIndex(size_t) const override;
   bool ImageHasBothStillAndAnimatedSubImages() const override;
 
@@ -52,6 +50,14 @@
   gfx::ColorTransform* GetColorTransformForTesting();
 
  private:
+  struct AvifIOData {
+    blink::SegmentReader* reader = nullptr;
+    std::vector<uint8_t> buffer;
+    bool all_data_received = false;
+  };
+
+  void ParseMetadata();
+
   // ImageDecoder:
   void DecodeSize() override;
   size_t DecodeFrameCount() override;
@@ -59,12 +65,19 @@
   void Decode(size_t) override;
   bool CanReusePreviousFrameBuffer(size_t) const override;
 
-  // Creates |decoder_| and decodes the size and frame count.
-  bool MaybeCreateDemuxer();
+  // Implements avifIOReadFunc, the |read| function in the avifIO struct.
+  static avifResult ReadFromSegmentReader(avifIO* io,
+                                          uint32_t read_flags,
+                                          uint64_t offset,
+                                          size_t size,
+                                          avifROData* out);
+
+  // Creates |decoder_| if not yet created and decodes the size and frame count.
+  bool UpdateDemuxer();
 
   // Decodes the frame at index |index|. The decoded frame is available in
-  // decoder_->image. Returns whether decoding completed successfully.
-  bool DecodeImage(size_t index);
+  // decoder_->image.
+  avifResult DecodeImage(size_t index);
 
   // Updates or creates |color_transform_| for YUV-to-RGB conversion.
   void UpdateColorTransform(const gfx::ColorSpace& frame_cs, int bit_depth);
@@ -77,24 +90,24 @@
   // desired.
   void ColorCorrectImage(ImageFrame* buffer);
 
+  bool have_parsed_current_data_ = false;
   // The bit depth from the container.
   uint8_t bit_depth_ = 0;
   bool decode_to_half_float_ = false;
-  // The YUV format from the container. Stores an avifPixelFormat enum value.
-  // Declared as uint8_t because we can't forward-declare an enum type in C++.
-  uint8_t avif_yuv_format_ = 0;  // AVIF_PIXEL_FORMAT_NONE
   uint8_t chroma_shift_x_ = 0;
   uint8_t chroma_shift_y_ = 0;
+  // The YUV format from the container.
+  avifPixelFormat avif_yuv_format_ = AVIF_PIXEL_FORMAT_NONE;
   size_t decoded_frame_count_ = 0;
   SkYUVColorSpace yuv_color_space_ = SkYUVColorSpace::kIdentity_SkYUVColorSpace;
   std::unique_ptr<avifDecoder, void (*)(avifDecoder*)> decoder_{nullptr,
                                                                 nullptr};
+  avifIO avif_io_ = {};
+  AvifIOData avif_io_data_;
 
   std::unique_ptr<gfx::ColorTransform> color_transform_;
 
   const AnimationOption animation_option_;
-
-  sk_sp<SkData> image_data_;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/image-decoders/avif/avif_image_decoder_test.cc b/third_party/blink/renderer/platform/image-decoders/avif/avif_image_decoder_test.cc
index 94b7158..53dcac74 100644
--- a/third_party/blink/renderer/platform/image-decoders/avif/avif_image_decoder_test.cc
+++ b/third_party/blink/renderer/platform/image-decoders/avif/avif_image_decoder_test.cc
@@ -480,10 +480,12 @@
   decoder->SetData(data.get(), true);
 
   if (error_phase == ErrorPhase::kParse) {
+    EXPECT_FALSE(decoder->IsSizeAvailable());
     EXPECT_TRUE(decoder->Failed());
     EXPECT_EQ(0u, decoder->FrameCount());
     EXPECT_FALSE(decoder->DecodeFrameBufferAtIndex(0));
   } else {
+    EXPECT_TRUE(decoder->IsSizeAvailable());
     EXPECT_FALSE(decoder->Failed());
     EXPECT_GT(decoder->FrameCount(), 0u);
     ImageFrame* frame = decoder->DecodeFrameBufferAtIndex(0);
diff --git a/third_party/blink/renderer/platform/image-decoders/webp/webp_image_decoder.cc b/third_party/blink/renderer/platform/image-decoders/webp/webp_image_decoder.cc
index 38d5739..34e7f16 100644
--- a/third_party/blink/renderer/platform/image-decoders/webp/webp_image_decoder.cc
+++ b/third_party/blink/renderer/platform/image-decoders/webp/webp_image_decoder.cc
@@ -186,7 +186,7 @@
       frame_background_has_alpha_(false),
       demux_(nullptr),
       demux_state_(WEBP_DEMUX_PARSING_HEADER),
-      have_already_parsed_this_data_(false),
+      have_parsed_current_data_(false),
       repetition_count_(kAnimationLoopOnce),
       decoded_height_(0) {
   blend_function_ = (alpha_option == kAlphaPremultiplied)
@@ -259,7 +259,7 @@
 }
 
 void WEBPImageDecoder::OnSetData(SegmentReader* data) {
-  have_already_parsed_this_data_ = false;
+  have_parsed_current_data_ = false;
   // TODO(crbug.com/943519): Modify this approach for incremental YUV (when
   // we don't require IsAllDataReceived() to be true before decoding).
   if (IsAllDataReceived()) {
@@ -302,10 +302,9 @@
   if (data_->size() < kWebpHeaderSize)
     return IsAllDataReceived() ? SetFailed() : false;
 
-  if (have_already_parsed_this_data_)
+  if (have_parsed_current_data_)
     return true;
-
-  have_already_parsed_this_data_ = true;
+  have_parsed_current_data_ = true;
 
   if (consolidated_data_ && consolidated_data_->size() >= data_->size()) {
     // Less data provided than last time. |consolidated_data_| is guaranteed
diff --git a/third_party/blink/renderer/platform/image-decoders/webp/webp_image_decoder.h b/third_party/blink/renderer/platform/image-decoders/webp/webp_image_decoder.h
index e529ec3..68fdcb0 100644
--- a/third_party/blink/renderer/platform/image-decoders/webp/webp_image_decoder.h
+++ b/third_party/blink/renderer/platform/image-decoders/webp/webp_image_decoder.h
@@ -123,7 +123,7 @@
 
   WebPDemuxer* demux_;
   WebPDemuxState demux_state_;
-  bool have_already_parsed_this_data_;
+  bool have_parsed_current_data_;
   int repetition_count_;
   int decoded_height_;
 
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index 3698073..08a204af 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -445,6 +445,7 @@
     {
       // https://drafts.csswg.org/css-counter-styles-3
       name: "CSSAtRuleCounterStyle",
+      status: "test",
     },
     {
       name: "CSSCalcAsInt",
diff --git a/third_party/blink/renderer/platform/widget/widget_base.cc b/third_party/blink/renderer/platform/widget/widget_base.cc
index 9cd6972..1fe8eb1 100644
--- a/third_party/blink/renderer/platform/widget/widget_base.cc
+++ b/third_party/blink/renderer/platform/widget/widget_base.cc
@@ -353,7 +353,7 @@
 
   blink::VisualProperties visual_properties = visual_properties_from_browser;
   // Web tests can override the device scale factor in the renderer.
-  if (auto scale_factor = client_->GetDeviceScaleFactorForTesting()) {
+  if (auto scale_factor = client_->GetTestingDeviceScaleFactorOverride()) {
     visual_properties.screen_info.device_scale_factor = scale_factor;
     visual_properties.compositor_viewport_pixel_rect =
         gfx::Rect(gfx::ScaleToCeiledSize(
diff --git a/third_party/blink/renderer/platform/widget/widget_base_client.h b/third_party/blink/renderer/platform/widget/widget_base_client.h
index 1012eb159..61ea127 100644
--- a/third_party/blink/renderer/platform/widget/widget_base_client.h
+++ b/third_party/blink/renderer/platform/widget/widget_base_client.h
@@ -182,7 +182,7 @@
   }
 
   // Return the overridden device scale factor for testing.
-  virtual float GetDeviceScaleFactorForTesting() { return 0.f; }
+  virtual float GetTestingDeviceScaleFactorOverride() { return 0.f; }
 
   // Test-specific methods below this point.
   virtual void ScheduleAnimationForWebTests() {}
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 d674ade..ca3a1d05 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
@@ -1023,6 +1023,13 @@
        {}
       ]
      ],
+     "fixed-pos-children.html": [
+      "773439d6f3e9bd29d0e56fe2f06f9b446804e8e1",
+      [
+       null,
+       {}
+      ]
+     ],
      "multicol-on-token-elements.html": [
       "9fc00eb6911b625a7443a3e1ec65ada7daf25105",
       [
@@ -9914,17 +9921,6 @@
        {}
       ]
      ]
-    },
-    "order-of-events": {
-     "focus-events": {
-      "focus-manual.html": [
-       "391daf7028ce7fdff4f78b542caa2dd7d917a0d2",
-       [
-        null,
-        {}
-       ]
-      ]
-     }
     }
    },
    "vibration": {
@@ -66685,6 +66681,19 @@
        {}
       ]
      ],
+     "position-absolute-scrollbar-freeze.html": [
+      "ac510f11e9160b1ed646047ed5aa44d2ebaa8a6f",
+      [
+       null,
+       [
+        [
+         "/css/css-flexbox/position-absolute-scrollbar-freeze-ref.html",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
      "position-fixed-001.html": [
       "a3de19d6d9df95a505c547e48dea3de82af44de5",
       [
@@ -91908,7 +91917,7 @@
       ]
      ],
      "selection-textarea-011.html": [
-      "f21e5e190ff1ed587def22a4ed87de6193dfd762",
+      "4a925bae3389b97bcc03d42c8e6f3fb4190efe5d",
       [
        null,
        [
@@ -183478,6 +183487,10 @@
       "5e1ccdbc8f04119d90c0d7241918bbcd32c5d750",
       []
      ],
+     "position-absolute-scrollbar-freeze-ref.html": [
+      "d0bf617951f3a9b5e3ae8e4b77e6ff3762b3cde1",
+      []
+     ],
      "reference": {
       "align-baseline-ref.html": [
        "d5b88e8c7dae73b57c7f993260d7cce363eeebaa",
@@ -196190,7 +196203,7 @@
        []
       ],
       "selection-textarea-011-ref.html": [
-       "825a4247a202dac2bc3dde257ddff81f2e8eded8",
+       "5c78176d606c8e5619b068f03611f8e0894ab138",
        []
       ]
      },
@@ -207854,10 +207867,6 @@
      ]
     },
     "cssom": {
-     "CSSCounterStyleRule-expected.txt": [
-      "525f5da12305d001d26d00cfff1e3c8f6ebcf7d7",
-      []
-     ],
      "CSSGroupingRule-insertRule-expected.txt": [
       "44e8aaa155eef7b2f14b1d86cee6ec9de789162a",
       []
@@ -241359,7 +241368,7 @@
       []
      ],
      "import-module-scripts.https-expected.txt": [
-      "6bac8b097c273a972583a8d4c453f22915975bb7",
+      "21e4c43a967459857dd8c8e8e05b14ba2a68c6fc",
       []
      ],
      "import-scripts-redirect.https-expected.txt": [
@@ -242103,6 +242112,10 @@
        "19c7a4b8e561b3d95f10496e92a2c7264d693815",
        []
       ],
+      "import-scripts-cross-origin-worker.sub.js": [
+       "b432854db8bc7f8292c43434d8c24a3ffe97dec0",
+       []
+      ],
       "import-scripts-diff-resource-map-worker.js": [
        "0fdcb0fcf80ad7f0dffa284c5b77178bdad95ef6",
        []
@@ -251009,7 +251022,7 @@
       []
      ],
      "requirements.txt": [
-      "766b9bd914391f558de2e28999cbfcc3f16db8ca",
+      "7c134b89b9583f9f0584450f20e9665cb80e6445",
       []
      ],
      "requirements_android_webview.txt": [
@@ -254128,6 +254141,10 @@
         "1972187d21fe1d385f03843b2fe222d2ef3183f7",
         []
        ],
+       "meta-utf8-after-1024-bytes.html": [
+        "b5916148b558e5077338bf77575d3677bdffa764",
+        []
+       ],
        "subframe.html": [
         "2019485529f64c778081b542646d36b55e4c4a39",
         []
@@ -257859,6 +257876,10 @@
        "cb762eff806849df46dc758ef7b98b63f27f54c9",
        []
       ],
+      "export-on-load-script.py": [
+       "6cca411e8a85a7fa0d78ced6b32011bda890b7c3",
+       []
+      ],
       "export-on-static-import-script.js": [
        "3f7174eef0beb68a3f36d66d3ed7a73c9074955c",
        []
@@ -257884,7 +257905,7 @@
        []
       ],
       "import-test-cases.js": [
-       "c5830e97eebc88b12a5ed1f600e810df443204f8",
+       "8ad89e8fda11a3923e29128f4835928f9a8c2cb6",
        []
       ],
       "nested-dynamic-import-worker.js": [
@@ -257900,7 +257921,7 @@
        []
       ],
       "new-worker-window.html": [
-       "a7bd2eedef6ab6d639f28e063ccd50aefbbb0528",
+       "32a89fae0ecd59ba7c84870c86f4251d057c5339",
        []
       ],
       "post-message-on-load-worker.js": [
@@ -257915,6 +257936,10 @@
        "699af684a2366fc0aaa8c1e64abf3240489cf7be",
        []
       ],
+      "redirect.py": [
+       "396bd4c4b2dfbb6091779b6f4375245c1a4604d8",
+       []
+      ],
       "static-import-and-then-dynamic-import-worker.js": [
        "2d857a2e90a11af9f52e5d59d15ee30d225deeaf",
        []
@@ -257927,6 +257952,10 @@
        "16f70e9daf4909605746c6614a509afb3cf71ed1",
        []
       ],
+      "static-import-redirect-worker.js": [
+       "8434c1a34e2837c9fde3709ec3d5bed468deeb7e",
+       []
+      ],
       "static-import-remote-origin-credentials-checker-worker.sub.js": [
        "c543cb49616062dc85fbbe7d885c17ada9646728",
        []
@@ -257936,7 +257965,7 @@
        []
       ],
       "static-import-remote-origin-script-worker.sub.js": [
-       "0221a2b8ba775c672d724a9f5946cdc9915db362",
+       "6432dd5d80d0f1c5c05512658dd240f09fa8ae8e",
        []
       ],
       "static-import-same-origin-credentials-checker-worker.js": [
@@ -335086,7 +335115,7 @@
       ]
      ],
      "script-resource-with-json-parser-breaker.tentative.sub.html": [
-      "d46944727f8ee9fc144caec437a5505be75e3d4b",
+      "1a8095ec658197833cf07f1f08407ac9970f33c4",
       [
        null,
        {}
@@ -405895,6 +405924,13 @@
        {}
       ]
      ],
+     "import-scripts-cross-origin.https.html": [
+      "773708a9fbce32a6b17f22d88b9fb6ebc3553710",
+      [
+       null,
+       {}
+      ]
+     ],
      "import-scripts-mime-types.https.html": [
       "1679831d0f2b98aee22898d820373e80079fbe00",
       [
@@ -406914,7 +406950,7 @@
       ]
      ],
      "getinnerhtml.tentative.html": [
-      "64fd78ba8ed0ca4d465c5a3ef8cb496350c69f7c",
+      "bcf5471b5e9e8748c49fbc899788203ead6fc19a",
       [
        null,
        {}
@@ -406949,7 +406985,7 @@
       ]
      ],
      "setinnerhtml.tentative.html": [
-      "f99863c7c1fabfbaee11d5cc90cc81d3e4acebff",
+      "076fdac2b0dda4fadfb1584a8b7650f736e7a908",
       [
        null,
        {}
@@ -417534,6 +417570,15 @@
          "testdriver": true
         }
        ]
+      ],
+      "focus.html": [
+       "7942674a70b7941cd1c23cee1251c43524362d57",
+       [
+        null,
+        {
+         "testdriver": true
+        }
+       ]
       ]
      },
      "mouse-events": {
@@ -438027,6 +438072,13 @@
        {}
       ]
      ],
+     "dedicated-worker-import-data-url-cross-origin.html": [
+      "37390947b642ebf1ff80434d6e870a0118d3b0be",
+      [
+       null,
+       {}
+      ]
+     ],
      "dedicated-worker-import-data-url.any.js": [
       "0d8510da0c24ad378d1215157a70b4fd615a3d94",
       [
@@ -454253,7 +454305,7 @@
      },
      "navigate_to": {
       "navigate.py": [
-       "289e4321011f3ab245ddc359a787adf1d6eddcab",
+       "b577ecdf96da77174bd4b360fb9a5e385584ff75",
        [
         null,
         {}
diff --git a/third_party/blink/web_tests/external/wpt/css/css-pseudo/reference/selection-textarea-011-ref.html b/third_party/blink/web_tests/external/wpt/css/css-pseudo/reference/selection-textarea-011-ref.html
index 825a424..5c78176 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-pseudo/reference/selection-textarea-011-ref.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-pseudo/reference/selection-textarea-011-ref.html
@@ -18,6 +18,7 @@
       padding: 0px;
       resize: none;
       width: 8ch;
+      outline: none;
     }
   </style>
 
diff --git a/third_party/blink/web_tests/external/wpt/css/css-pseudo/selection-textarea-011.html b/third_party/blink/web_tests/external/wpt/css/css-pseudo/selection-textarea-011.html
index f21e5e1..4a925ba 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-pseudo/selection-textarea-011.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-pseudo/selection-textarea-011.html
@@ -22,6 +22,7 @@
       padding: 0px;
       resize: none;
       width: 8ch;
+      outline: none;
     }
 
   textarea::selection
diff --git a/third_party/blink/web_tests/external/wpt/css/cssom/CSSCounterStyleRule-expected.txt b/third_party/blink/web_tests/external/wpt/css/cssom/CSSCounterStyleRule-expected.txt
deleted file mode 100644
index 525f5da..0000000
--- a/third_party/blink/web_tests/external/wpt/css/cssom/CSSCounterStyleRule-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL CSSCounterStyleRule.cssText doesn't serialize with newlines Cannot read property 'cssText' of undefined
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/fetch/corb/script-resource-with-json-parser-breaker.tentative.sub.html b/third_party/blink/web_tests/external/wpt/fetch/corb/script-resource-with-json-parser-breaker.tentative.sub.html
index d469447..1a8095ec 100644
--- a/third_party/blink/web_tests/external/wpt/fetch/corb/script-resource-with-json-parser-breaker.tentative.sub.html
+++ b/third_party/blink/web_tests/external/wpt/fetch/corb/script-resource-with-json-parser-breaker.tentative.sub.html
@@ -1,4 +1,5 @@
 <!DOCTYPE html>
+<meta charset="utf-8">
 <!-- Test verifies CORB will block responses beginning with a JSON parser
   breaker regardless of their MIME type (excluding text/css - see below).
 
@@ -18,7 +19,6 @@
   style-css-with-json-parser-breaker.sub.html) can parse as valid stylesheet
   even in presence of a JSON parser breaker.
 -->
-<meta charset="utf-8">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <div id=log></div>
diff --git a/third_party/blink/web_tests/external/wpt/mathml/crashtests/fixed-pos-children.html b/third_party/blink/web_tests/external/wpt/mathml/crashtests/fixed-pos-children.html
new file mode 100644
index 0000000..773439d
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/mathml/crashtests/fixed-pos-children.html
@@ -0,0 +1,25 @@
+<style>
+    * {
+        border-bottom: hsla(115.49088416259553deg 12% 15% / 18%) groove thin;
+        -webkit-text-stroke: InactiveCaptionText thin;
+        scale: -121
+    }
+
+    * * {
+        padding-inline: 69%;
+    }
+</style>
+<script>
+  document.addEventListener('DOMContentLoaded', () => {
+    const style = document.createElement('style')
+    document.head.appendChild(style)
+    style.sheet.insertRule('* {-webkit-mask-image:url(', 0)
+  })
+</script>
+<math>
+    <mmultiscripts>
+        <mglyph>
+            <maligngroup style='position:fixed'></maligngroup>
+        </mglyph>
+    </mmultiscripts>
+</math>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/service-workers/service-worker/import-module-scripts.https-expected.txt b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/import-module-scripts.https-expected.txt
index 6bac8b09..21e4c43 100644
--- a/third_party/blink/web_tests/external/wpt/service-workers/service-worker/import-module-scripts.https-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/import-module-scripts.https-expected.txt
@@ -1,5 +1,7 @@
 This is a testharness.js-based test.
 PASS Static import.
+PASS Static import (cross-origin).
+FAIL Static import (redirect). promise_test: Unhandled rejection with value: object "AbortError: Failed to register a ServiceWorker for scope ('https://web-platform.test:8444/workers/modules/resources/static-import-redirect-worker.js') with script ('https://web-platform.test:8444/workers/modules/resources/static-import-redirect-worker.js'): ServiceWorker cannot be started"
 PASS Nested static import.
 FAIL Static import and then dynamic import. assert_array_equals: value is "Failed to do dynamic import: TypeError: Module scripts are not supported on ServiceWorkerGlobalScope yet (see https://crbug.com/824647).", expected array
 FAIL Dynamic import. assert_array_equals: value is "Failed to do dynamic import: TypeError: Module scripts are not supported on ServiceWorkerGlobalScope yet (see https://crbug.com/824647).", expected array
diff --git a/third_party/blink/web_tests/external/wpt/service-workers/service-worker/import-scripts-cross-origin.https.html b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/import-scripts-cross-origin.https.html
new file mode 100644
index 0000000..773708a
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/import-scripts-cross-origin.https.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Tests for importScripts: cross-origin</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<body>
+<script>
+promise_test(async t => {
+    const scope = 'resources/import-scripts-cross-origin';
+    await service_worker_unregister(t, scope);
+    let reg = await navigator.serviceWorker.register(
+      'resources/import-scripts-cross-origin-worker.sub.js', { scope: scope });
+    t.add_cleanup(_ => reg.unregister());
+    assert_not_equals(reg.installing, null, 'worker is installing');
+  }, 'importScripts() supports cross-origin requests');
+</script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/service-workers/service-worker/resources/import-scripts-cross-origin-worker.sub.js b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/resources/import-scripts-cross-origin-worker.sub.js
new file mode 100644
index 0000000..b432854
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/resources/import-scripts-cross-origin-worker.sub.js
@@ -0,0 +1 @@
+importScripts('https://{{domains[www1]}}:{{ports[https][0]}}/service-workers/service-worker/resources/import-scripts-version.py');
diff --git a/third_party/blink/web_tests/external/wpt/tools/wptrunner/requirements.txt b/third_party/blink/web_tests/external/wpt/tools/wptrunner/requirements.txt
index 766b9bd..7c134b8 100644
--- a/third_party/blink/web_tests/external/wpt/tools/wptrunner/requirements.txt
+++ b/third_party/blink/web_tests/external/wpt/tools/wptrunner/requirements.txt
@@ -5,6 +5,6 @@
 # Pillow 7 requires Python 3
 pillow==6.2.2; python_version <= '2.7'  # pyup: <7.0
 pillow==8.0.1; python_version >= '3.0'
-urllib3[secure]==1.26.1
+urllib3[secure]==1.26.2
 requests==2.24.0
 six==1.15.0
diff --git a/third_party/blink/web_tests/external/wpt/webdriver/tests/navigate_to/navigate.py b/third_party/blink/web_tests/external/wpt/webdriver/tests/navigate_to/navigate.py
index 289e432..b577ecd 100644
--- a/third_party/blink/web_tests/external/wpt/webdriver/tests/navigate_to/navigate.py
+++ b/third_party/blink/web_tests/external/wpt/webdriver/tests/navigate_to/navigate.py
@@ -1,3 +1,7 @@
+import time
+
+import pytest
+
 from tests.support import platform_name
 from webdriver.transport import Response
 
@@ -52,3 +56,23 @@
     if session.url.endswith('/'):
         url += '/'
     assert session.url == url
+
+
+@pytest.mark.capabilities({"pageLoadStrategy": "eager"})
+def test_utf8_meta_tag_after_1024_bytes(session, url):
+    page = url("/webdriver/tests/support/html/meta-utf8-after-1024-bytes.html")
+
+    # Loading the page will cause a real parse commencing, and a renavigation
+    # to the same URL getting triggered subsequently. Test that the navigate
+    # command waits long enough.
+    response = navigate_to(session, page)
+    assert_success(response)
+
+    # If the command returns too early the property will be reset due to the
+    # subsequent page load.
+    session.execute_script("window.foo = 'bar'")
+
+    # Use delay to allow a possible missing subsequent navigation to start
+    time.sleep(1)
+
+    assert session.execute_script("return window.foo") == "bar"
diff --git a/third_party/blink/web_tests/external/wpt/webdriver/tests/support/html/meta-utf8-after-1024-bytes.html b/third_party/blink/web_tests/external/wpt/webdriver/tests/support/html/meta-utf8-after-1024-bytes.html
new file mode 100644
index 0000000..b591614
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/webdriver/tests/support/html/meta-utf8-after-1024-bytes.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<!-- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla eu iaculis
+lectus. Quisque ullamcorper est at nunc consectetur suscipit. Aliquam imperdiet
+mauris in nulla ornare, id eleifend turpis placerat. Vestibulum lorem libero,
+sollicitudin in orci suscipit, dictum vestibulum nulla. Ut ac est tincidunt,
+cursus leo vel, pellentesque orci. Sed mattis metus augue, ac tincidunt nunc
+lobortis in. Proin eu ipsum auctor lorem sagittis malesuada. Vivamus maximus,
+eros fringilla vulputate tincidunt, tellus tellus viverra augue, sed iaculis
+ipsum lacus quis tellus. Morbi et enim at ante molestie imperdiet et et nulla.
+Aliquam consequat rhoncus magna, vitae sodales urna maximus eget. Mauris eu
+laoreet turpis, eget condimentum lectus. Maecenas vel lorem vel nulla efficitur
+euismod. Sed lobortis enim ac odio bibendum, id vehicula nibh tempus. Phasellus
+sodales, ipsum feugiat aliquam vehicula, diam leo cursus est, nec varius nunc
+felis vitae est. Curabitur ac purus nisl. Mauris condimentum, magna quis
+consectetur biam. -->
+<meta charset="utf-8">
+<div id="body"></div>
diff --git a/third_party/blink/web_tests/external/wpt/workers/modules/dedicated-worker-import-data-url-cross-origin.html b/third_party/blink/web_tests/external/wpt/workers/modules/dedicated-worker-import-data-url-cross-origin.html
new file mode 100644
index 0000000..37390947b
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/workers/modules/dedicated-worker-import-data-url-cross-origin.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<title>DedicatedWorker: ES modules for data URL workers</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+
+const import_from_data_url_worker_test = (importType, isDataURL, expectation) => {
+  promise_test(async () => {
+    const importURL = new URL(`resources/${importType}-import-` +
+        `${isDataURL ? 'data-url' : 'script'}-block-cross-origin.js`,
+        location.href) + '?pipe=header(Access-Control-Allow-Origin, *)';
+    const dataURL = `data:text/javascript,import "${importURL}";`;
+    const worker = new Worker(dataURL, { type: 'module' });
+    worker.postMessage('Send message for tests from main script.');
+    const msgEvent =
+        await new Promise(resolve => worker.onmessage = resolve);
+    assert_array_equals(msgEvent.data,
+        expectation === 'blocked' ? ['ERROR']
+                                  : ['export-block-cross-origin.js']);
+  }, `${importType} import ${isDataURL ? 'data url' : 'script'} from data: ` +
+     `URL should be ${expectation}.`);
+}
+
+// Static import should obey the outside settings.
+// SecurityOrigin of the outside settings is decided by Window.
+import_from_data_url_worker_test('static', true, 'allowed');
+import_from_data_url_worker_test('static', false, 'allowed');
+
+
+// Dynamic import should obey the inside settings.
+// SecurityOrigin of the inside settings is a unique opaque origin.
+//
+// Data url script is cross-origin to the inside settings' SecurityOrigin, but
+// dynamic importing it is allowed.
+// https://fetch.spec.whatwg.org/#concept-main-fetch
+// Step 5: request’s current URL’s scheme is "data" [spec text]
+import_from_data_url_worker_test('dynamic', true, 'allowed');
+
+// Non-data url script is cross-origin to the inside settings' SecurityOrigin.
+// It should be blocked.
+import_from_data_url_worker_test('dynamic', false, 'blocked');
+
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/workers/modules/resources/export-on-load-script.py b/third_party/blink/web_tests/external/wpt/workers/modules/resources/export-on-load-script.py
new file mode 100644
index 0000000..6cca411e
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/workers/modules/resources/export-on-load-script.py
@@ -0,0 +1,11 @@
+def main(request, response):
+    # This script serves both preflight and main GET request for cross-origin
+    # static imports from module service workers.
+    # According to https://w3c.github.io/ServiceWorker/#update-algorithm,
+    # `Service-Worker: script` request header is added, which triggers CORS
+    # preflight.
+    response_headers = [(b"Content-Type", b"text/javascript"),
+                        (b"Access-Control-Allow-Origin", b"*"),
+                        (b"Access-Control-Allow-Headers", b"Service-Worker")]
+    return (200, response_headers,
+            b"export const importedModules = ['export-on-load-script.js'];")
diff --git a/third_party/blink/web_tests/external/wpt/workers/modules/resources/import-test-cases.js b/third_party/blink/web_tests/external/wpt/workers/modules/resources/import-test-cases.js
index c5830e9..8ad89e8f 100644
--- a/third_party/blink/web_tests/external/wpt/workers/modules/resources/import-test-cases.js
+++ b/third_party/blink/web_tests/external/wpt/workers/modules/resources/import-test-cases.js
@@ -5,6 +5,16 @@
         description: 'Static import.'
     },
     {
+        scriptURL: '/workers/modules/resources/static-import-remote-origin-script-worker.sub.js',
+        expectation: ['export-on-load-script.js'],
+        description: 'Static import (cross-origin).'
+    },
+    {
+        scriptURL: '/workers/modules/resources/static-import-redirect-worker.js',
+        expectation: ['export-on-load-script.js'],
+        description: 'Static import (redirect).'
+    },
+    {
         scriptURL: '/workers/modules/resources/nested-static-import-worker.js',
         expectation: [
             'export-on-static-import-script.js',
diff --git a/third_party/blink/web_tests/external/wpt/workers/modules/resources/new-worker-window.html b/third_party/blink/web_tests/external/wpt/workers/modules/resources/new-worker-window.html
index a7bd2ee..32a89fa 100644
--- a/third_party/blink/web_tests/external/wpt/workers/modules/resources/new-worker-window.html
+++ b/third_party/blink/web_tests/external/wpt/workers/modules/resources/new-worker-window.html
@@ -8,6 +8,7 @@
 // Creates a new dedicated worker for a given script url.
 window.onmessage = e => {
   worker = new Worker(e.data, { type: 'module' });
+  worker.postMessage('start');
   worker.onmessage = msg => window.opener.postMessage(msg.data, '*');
   worker.onerror = err => {
       window.opener.postMessage(['ERROR'], '*');
diff --git a/third_party/blink/web_tests/external/wpt/workers/modules/resources/redirect.py b/third_party/blink/web_tests/external/wpt/workers/modules/resources/redirect.py
new file mode 100644
index 0000000..396bd4c
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/workers/modules/resources/redirect.py
@@ -0,0 +1,8 @@
+def main(request, response):
+    """Simple handler that causes redirection.
+    This is placed here to stay within the same directory during redirects,
+    to avoid issues like https://crbug.com/1136775.
+    """
+    response.status = 302
+    location = request.GET.first(b"location")
+    response.headers.set(b"Location", location)
diff --git a/third_party/blink/web_tests/external/wpt/workers/modules/resources/static-import-redirect-worker.js b/third_party/blink/web_tests/external/wpt/workers/modules/resources/static-import-redirect-worker.js
new file mode 100644
index 0000000..8434c1a3
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/workers/modules/resources/static-import-redirect-worker.js
@@ -0,0 +1,21 @@
+// This script is meant to be imported by a module worker. It receives a
+// message from the worker and responds with the list of imported modules.
+import * as module from './redirect.py?location=/workers/modules/resources/export-on-load-script.js';
+if ('DedicatedWorkerGlobalScope' in self &&
+    self instanceof DedicatedWorkerGlobalScope) {
+  self.onmessage = e => {
+    e.target.postMessage(module.importedModules);
+  };
+} else if (
+    'SharedWorkerGlobalScope' in self &&
+    self instanceof SharedWorkerGlobalScope) {
+  self.onconnect = e => {
+    e.ports[0].postMessage(module.importedModules);
+  };
+} else if (
+    'ServiceWorkerGlobalScope' in self &&
+    self instanceof ServiceWorkerGlobalScope) {
+  self.onmessage = e => {
+    e.source.postMessage(module.importedModules);
+  };
+}
diff --git a/third_party/blink/web_tests/external/wpt/workers/modules/resources/static-import-remote-origin-script-worker.sub.js b/third_party/blink/web_tests/external/wpt/workers/modules/resources/static-import-remote-origin-script-worker.sub.js
index 0221a2b8b..6432dd5 100644
--- a/third_party/blink/web_tests/external/wpt/workers/modules/resources/static-import-remote-origin-script-worker.sub.js
+++ b/third_party/blink/web_tests/external/wpt/workers/modules/resources/static-import-remote-origin-script-worker.sub.js
@@ -1,12 +1,20 @@
 // Import a remote origin script.
-import * as module from 'https://{{domains[www1]}}:{{ports[https][0]}}/workers/modules/resources/export-on-load-script.js';
+import * as module from 'https://{{domains[www1]}}:{{ports[https][0]}}/workers/modules/resources/export-on-load-script.py';
 if ('DedicatedWorkerGlobalScope' in self &&
     self instanceof DedicatedWorkerGlobalScope) {
-  postMessage(module.importedModules);
+  self.onmessage = e => {
+    e.target.postMessage(module.importedModules);
+  };
 } else if (
     'SharedWorkerGlobalScope' in self &&
     self instanceof SharedWorkerGlobalScope) {
-  onconnect = e => {
+  self.onconnect = e => {
     e.ports[0].postMessage(module.importedModules);
   };
+} else if (
+    'ServiceWorkerGlobalScope' in self &&
+    self instanceof ServiceWorkerGlobalScope) {
+  self.onmessage = e => {
+    e.source.postMessage(module.importedModules);
+  };
 }
diff --git a/third_party/blink/web_tests/virtual/link-disabled-old-behavior/external/wpt/css/cssom/CSSCounterStyleRule-expected.txt b/third_party/blink/web_tests/virtual/link-disabled-old-behavior/external/wpt/css/cssom/CSSCounterStyleRule-expected.txt
deleted file mode 100644
index 525f5da..0000000
--- a/third_party/blink/web_tests/virtual/link-disabled-old-behavior/external/wpt/css/cssom/CSSCounterStyleRule-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL CSSCounterStyleRule.cssText doesn't serialize with newlines Cannot read property 'cssText' of undefined
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/webexposed/css-properties-as-js-properties-expected.txt b/third_party/blink/web_tests/webexposed/css-properties-as-js-properties-expected.txt
index a55d90f..c117a7f 100644
--- a/third_party/blink/web_tests/webexposed/css-properties-as-js-properties-expected.txt
+++ b/third_party/blink/web_tests/webexposed/css-properties-as-js-properties-expected.txt
@@ -1,6 +1,7 @@
 This test (crudely) documents Blink's web-exposed CSS properties.  All changes to this list should go through Blink's feature review process: http://www.chromium.org/blink#new-features
 
 
+additiveSymbols
 advanceOverride
 advanceProportionalOverride
 alignContent
@@ -143,6 +144,7 @@
 dominantBaseline
 emptyCells
 end
+fallback
 fill
 fillOpacity
 fillRule
@@ -257,6 +259,7 @@
 minWidth
 minZoom
 mixBlendMode
+negative
 objectFit
 objectPosition
 offset
@@ -287,6 +290,7 @@
 overscrollBehaviorInline
 overscrollBehaviorX
 overscrollBehaviorY
+pad
 padding
 paddingBlock
 paddingBlockEnd
@@ -312,8 +316,10 @@
 placeSelf
 pointerEvents
 position
+prefix
 quotes
 r
+range
 removeProperty
 resize
 right
@@ -359,6 +365,7 @@
 size
 source
 speak
+speakAs
 src
 start
 stopColor
@@ -371,7 +378,10 @@
 strokeMiterlimit
 strokeOpacity
 strokeWidth
+suffix
+symbols
 syntax
+system
 tabSize
 tableLayout
 textAlign
diff --git a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
index 2ceb23b..7e3ad7f 100644
--- a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
+++ b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
@@ -601,6 +601,31 @@
     attribute @@toStringTag
     getter conditionText
     method constructor
+interface CSSCounterStyleRule : CSSRule
+    attribute @@toStringTag
+    getter additiveSymbols
+    getter fallback
+    getter name
+    getter negative
+    getter pad
+    getter prefix
+    getter range
+    getter speakAs
+    getter suffix
+    getter symbols
+    getter system
+    method constructor
+    setter additiveSymbols
+    setter fallback
+    setter name
+    setter negative
+    setter pad
+    setter prefix
+    setter range
+    setter speakAs
+    setter suffix
+    setter symbols
+    setter system
 interface CSSFontFaceRule : CSSRule
     attribute @@toStringTag
     getter style
@@ -748,6 +773,7 @@
 interface CSSRule
     attribute @@toStringTag
     attribute CHARSET_RULE
+    attribute COUNTER_STYLE_RULE
     attribute FONT_FACE_RULE
     attribute IMPORT_RULE
     attribute KEYFRAMES_RULE
diff --git a/third_party/leveldatabase/env_chromium.cc b/third_party/leveldatabase/env_chromium.cc
index 2273630..1e1fe9cb 100644
--- a/third_party/leveldatabase/env_chromium.cc
+++ b/third_party/leveldatabase/env_chromium.cc
@@ -10,7 +10,6 @@
 
 #include "base/bind.h"
 #include "base/check_op.h"
-#include "base/feature_list.h"
 #include "base/files/file_util.h"
 #include "base/format_macros.h"
 #include "base/macros.h"
@@ -50,9 +49,6 @@
 using leveldb::Slice;
 using leveldb::Status;
 
-const base::Feature kLevelDBFileHandleEviction{
-    "LevelDBFileHandleEviction", base::FEATURE_ENABLED_BY_DEFAULT};
-
 namespace leveldb_env {
 namespace {
 
@@ -707,8 +703,7 @@
   DCHECK(filesystem_);
 
   size_t max_open_files = base::GetMaxFds();
-  if (base::FeatureList::IsEnabled(kLevelDBFileHandleEviction) &&
-      max_open_files < kFileLimitToDisableEviction) {
+  if (max_open_files < kFileLimitToDisableEviction) {
     file_cache_.reset(
         leveldb::NewLRUCache(GetLevelDBFileLimit(max_open_files)));
   }
diff --git a/tools/binary_size/generate_commit_size_analysis.py b/tools/binary_size/generate_commit_size_analysis.py
index 25dacde0..9090c17b 100755
--- a/tools/binary_size/generate_commit_size_analysis.py
+++ b/tools/binary_size/generate_commit_size_analysis.py
@@ -91,8 +91,6 @@
   # * supersize_input_file: Main input for SuperSize, and can be {.apk,
   #   .minimal.apks, .ssargs}. If .ssargs, then the file is copied to the
   #   staging dir.
-  # * version: (Unused by this script) Used by build bots to determine whether
-  #   significant binary package restructure has occurred.
 
   # --size-config-json will replace {--apk-name, --mapping-name}.
   parser.add_argument('--size-config-json',
diff --git a/ui/android/java/src/org/chromium/ui/base/WindowAndroid.java b/ui/android/java/src/org/chromium/ui/base/WindowAndroid.java
index 2dbaa08c..55065c4 100644
--- a/ui/android/java/src/org/chromium/ui/base/WindowAndroid.java
+++ b/ui/android/java/src/org/chromium/ui/base/WindowAndroid.java
@@ -978,6 +978,11 @@
         window.setAttributes(params);
     }
 
+    @CalledByNative
+    private boolean applyDisableSurfaceControlWorkaround() {
+        return mDisplayAndroid.applyDisableSurfaceControlWorkaround();
+    }
+
     @SuppressLint("NewApi")
     // mSupportedRefreshRateModes should only be set if Display.Mode is available.
     @TargetApi(Build.VERSION_CODES.M)
diff --git a/ui/android/java/src/org/chromium/ui/display/DisplayAndroid.java b/ui/android/java/src/org/chromium/ui/display/DisplayAndroid.java
index fbed317..2c11f783 100644
--- a/ui/android/java/src/org/chromium/ui/display/DisplayAndroid.java
+++ b/ui/android/java/src/org/chromium/ui/display/DisplayAndroid.java
@@ -20,7 +20,7 @@
  * anywhere, as long as the corresponding WindowAndroids are destroyed. The observers are
  * held weakly so to not lead to leaks.
  */
-public class DisplayAndroid {
+public abstract class DisplayAndroid {
     /**
      * DisplayAndroidObserver interface for changes to this Display.
      */
@@ -254,6 +254,8 @@
         update(null, null, null, null, null, null, isDisplayServerWideColorGamut, null, null, null);
     }
 
+    public abstract boolean applyDisableSurfaceControlWorkaround();
+
     /**
      * Update the display to the provided parameters. Null values leave the parameter unchanged.
      */
diff --git a/ui/android/java/src/org/chromium/ui/display/PhysicalDisplayAndroid.java b/ui/android/java/src/org/chromium/ui/display/PhysicalDisplayAndroid.java
index 6f6f72c..4387946 100644
--- a/ui/android/java/src/org/chromium/ui/display/PhysicalDisplayAndroid.java
+++ b/ui/android/java/src/org/chromium/ui/display/PhysicalDisplayAndroid.java
@@ -23,12 +23,16 @@
  */
 /* package */ class PhysicalDisplayAndroid extends DisplayAndroid {
     private static final String TAG = "DisplayAndroid";
+    private static final String SAMSUNG_DEX_DISPLAY = "Desktop";
 
     // When this object exists, a positive value means that the forced DIP scale is set and
     // the zero means it is not. The non existing object (i.e. null reference) means that
     // the existence and value of the forced DIP scale has not yet been determined.
     private static Float sForcedDIPScale;
 
+    // This is a workaround for crbug.com/1042581.
+    private final boolean mDisableSurfaceControlWorkaround;
+
     private static boolean hasForcedDIPScale() {
         if (sForcedDIPScale == null) {
             String forcedScaleAsString = CommandLine.getInstance().getSwitchValue(
@@ -123,6 +127,7 @@
 
     /* package */ PhysicalDisplayAndroid(Display display) {
         super(display.getDisplayId());
+        mDisableSurfaceControlWorkaround = display.getName().equals(SAMSUNG_DEX_DISPLAY);
     }
 
     @SuppressWarnings("deprecation")
@@ -164,4 +169,9 @@
                 bitsPerComponent(pixelFormatId), display.getRotation(), isWideColorGamut, null,
                 display.getRefreshRate(), currentMode, supportedModes);
     }
+
+    @Override
+    public boolean applyDisableSurfaceControlWorkaround() {
+        return mDisableSurfaceControlWorkaround;
+    }
 }
diff --git a/ui/android/java/src/org/chromium/ui/display/VirtualDisplayAndroid.java b/ui/android/java/src/org/chromium/ui/display/VirtualDisplayAndroid.java
index 5719ba4..46cb4fb 100644
--- a/ui/android/java/src/org/chromium/ui/display/VirtualDisplayAndroid.java
+++ b/ui/android/java/src/org/chromium/ui/display/VirtualDisplayAndroid.java
@@ -57,4 +57,9 @@
     public void destroy() {
         getManager().removeVirtualDisplay(this);
     }
+
+    @Override
+    public boolean applyDisableSurfaceControlWorkaround() {
+        return false;
+    }
 }
diff --git a/ui/android/window_android.cc b/ui/android/window_android.cc
index e37dbeb..b1aa28f 100644
--- a/ui/android/window_android.cc
+++ b/ui/android/window_android.cc
@@ -243,6 +243,12 @@
   Java_WindowAndroid_setPreferredRefreshRate(env, GetJavaObject(), 60.f);
 }
 
+bool WindowAndroid::ApplyDisableSurfaceControlWorkaround() {
+  JNIEnv* env = AttachCurrentThread();
+  return Java_WindowAndroid_applyDisableSurfaceControlWorkaround(
+      env, GetJavaObject());
+}
+
 bool WindowAndroid::HasPermission(const std::string& permission) {
   JNIEnv* env = AttachCurrentThread();
   return Java_WindowAndroid_hasPermission(
diff --git a/ui/android/window_android.h b/ui/android/window_android.h
index dd82231..d6d0ac73 100644
--- a/ui/android/window_android.h
+++ b/ui/android/window_android.h
@@ -106,6 +106,8 @@
 
   void SetForce60HzRefreshRate();
 
+  bool ApplyDisableSurfaceControlWorkaround();
+
   class TestHooks {
    public:
     virtual ~TestHooks() = default;
diff --git a/ui/gfx/android/android_surface_control_compat.cc b/ui/gfx/android/android_surface_control_compat.cc
index 64ca780c..c22ee52 100644
--- a/ui/gfx/android/android_surface_control_compat.cc
+++ b/ui/gfx/android/android_surface_control_compat.cc
@@ -305,14 +305,7 @@
 
 // static
 bool SurfaceControl::IsSupported() {
-  const auto* build_info = base::android::BuildInfo::GetInstance();
-
-  // Disabled on Samsung devices due to a platform bug fixed in R.
-  int min_sdk_version = base::android::SDK_VERSION_Q;
-  if (base::EqualsCaseInsensitiveASCII(build_info->manufacturer(), "samsung"))
-    min_sdk_version = base::android::SDK_VERSION_R;
-
-  if (build_info->sdk_int() < min_sdk_version)
+  if (!base::android::BuildInfo::GetInstance()->is_at_least_q())
     return false;
 
   CHECK(SurfaceControlMethods::Get().supported);
diff --git a/ui/ozone/platform/scenic/scenic_overlay_view.cc b/ui/ozone/platform/scenic/scenic_overlay_view.cc
index e865482..a970990 100644
--- a/ui/ozone/platform/scenic/scenic_overlay_view.cc
+++ b/ui/ozone/platform/scenic/scenic_overlay_view.cc
@@ -8,6 +8,7 @@
 #include <lib/ui/scenic/cpp/view_token_pair.h>
 
 #include "base/fuchsia/fuchsia_logging.h"
+#include "ui/ozone/platform/scenic/scenic_surface_factory.h"
 
 namespace ui {
 
@@ -28,8 +29,10 @@
 }  // namespace
 
 ScenicOverlayView::ScenicOverlayView(
-    scenic::SessionPtrAndListenerRequest session_and_listener_request)
+    scenic::SessionPtrAndListenerRequest session_and_listener_request,
+    ScenicSurfaceFactory* scenic_surface_factory)
     : scenic_session_(std::move(session_and_listener_request)),
+      scenic_surface_factory_(scenic_surface_factory),
       view_(&scenic_session_,
             CreateViewToken(&view_holder_token_),
             kSessionDebugName) {
@@ -40,14 +43,20 @@
 }
 
 ScenicOverlayView::~ScenicOverlayView() {
-  if (surface_)
-    surface_->RemoveOverlayView(buffer_collection_id_);
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+
+  ScenicSurface* surface = scenic_surface_factory_->GetSurface(widget_);
+  if (surface) {
+    surface->AssertBelongsToCurrentThread();
+    surface->RemoveOverlayView(buffer_collection_id_);
+  }
 }
 
 void ScenicOverlayView::Initialize(
     fidl::InterfaceHandle<fuchsia::sysmem::BufferCollectionToken>
         collection_token) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+
   uint32_t image_pipe_id = scenic_session_.AllocResourceId();
   scenic_session_.Enqueue(
       scenic::NewCreateImagePipe2Cmd(image_pipe_id, image_pipe_.NewRequest()));
@@ -78,6 +87,8 @@
 
 bool ScenicOverlayView::AddImages(uint32_t buffer_count,
                                   const gfx::Size& size) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+
   fuchsia::sysmem::ImageFormat_2 image_format = {};
   image_format.coded_width = size.width();
   image_format.coded_height = size.height();
@@ -91,6 +102,8 @@
 bool ScenicOverlayView::PresentImage(uint32_t buffer_index,
                                      std::vector<zx::event> acquire_fences,
                                      std::vector<zx::event> release_fences) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+
   image_pipe_->PresentImage(buffer_index + 1, zx_clock_get_monotonic(),
                             std::move(acquire_fences),
                             std::move(release_fences), [](auto) {});
@@ -98,6 +111,8 @@
 }
 
 void ScenicOverlayView::SetBlendMode(bool enable_blend) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+
   if (enable_blend_ == enable_blend)
     return;
 
@@ -117,10 +132,11 @@
 }
 
 bool ScenicOverlayView::AttachToScenicSurface(
-    ScenicSurface* surface,
     gfx::AcceleratedWidget widget,
     gfx::SysmemBufferCollectionId id) {
-  if (surface_ && surface_ == surface)
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+
+  if (widget_ != gfx::kNullAcceleratedWidget && widget_ == widget)
     return true;
 
   if (!view_holder_token_.value.is_valid()) {
@@ -128,11 +144,13 @@
     return false;
   }
 
-  surface_ = surface;
   buffer_collection_id_ = id;
   widget_ = widget;
-  return surface_->PresentOverlayView(buffer_collection_id_,
-                                      std::move(view_holder_token_));
+
+  ScenicSurface* surface = scenic_surface_factory_->GetSurface(widget_);
+  DCHECK(surface);
+  return surface->PresentOverlayView(buffer_collection_id_,
+                                     std::move(view_holder_token_));
 }
 
 }  // namespace ui
diff --git a/ui/ozone/platform/scenic/scenic_overlay_view.h b/ui/ozone/platform/scenic/scenic_overlay_view.h
index 00220656..c2c25b4 100644
--- a/ui/ozone/platform/scenic/scenic_overlay_view.h
+++ b/ui/ozone/platform/scenic/scenic_overlay_view.h
@@ -24,7 +24,8 @@
 class ScenicOverlayView {
  public:
   ScenicOverlayView(
-      scenic::SessionPtrAndListenerRequest session_and_listener_request);
+      scenic::SessionPtrAndListenerRequest session_and_listener_request,
+      ScenicSurfaceFactory* scenic_surface_factory);
   ~ScenicOverlayView();
   ScenicOverlayView(const ScenicOverlayView&) = delete;
   ScenicOverlayView& operator=(const ScenicOverlayView&) = delete;
@@ -53,21 +54,20 @@
   bool CanAttachToAcceleratedWidget(gfx::AcceleratedWidget widget);
 
   // Return true if |view_holder_token_| is attached to the scene graph of
-  // |surface|.
-  bool AttachToScenicSurface(ScenicSurface* surface,
-                             gfx::AcceleratedWidget widget,
+  // surface corresponding to |widget|.
+  bool AttachToScenicSurface(gfx::AcceleratedWidget widget,
                              gfx::SysmemBufferCollectionId id);
 
  private:
   scenic::Session scenic_session_;
+  ScenicSurfaceFactory* const scenic_surface_factory_;
   fuchsia::ui::views::ViewHolderToken view_holder_token_;
   scenic::View view_;
   fuchsia::images::ImagePipe2Ptr image_pipe_;
   std::unique_ptr<scenic::Material> image_material_;
 
   bool enable_blend_ = false;
-  ScenicSurface* surface_ = nullptr;
-  gfx::AcceleratedWidget widget_;
+  gfx::AcceleratedWidget widget_ = gfx::kNullAcceleratedWidget;
   gfx::SysmemBufferCollectionId buffer_collection_id_;
 
   THREAD_CHECKER(thread_checker_);
diff --git a/ui/ozone/platform/scenic/scenic_surface_factory.cc b/ui/ozone/platform/scenic/scenic_surface_factory.cc
index ba0b5e7a2..d1e03bb 100644
--- a/ui/ozone/platform/scenic/scenic_surface_factory.cc
+++ b/ui/ozone/platform/scenic/scenic_surface_factory.cc
@@ -271,7 +271,9 @@
 ScenicSurface* ScenicSurfaceFactory::GetSurface(gfx::AcceleratedWidget widget) {
   base::AutoLock lock(surface_lock_);
   auto it = surface_map_.find(widget);
-  DCHECK(it != surface_map_.end());
+  if (it == surface_map_.end())
+    return nullptr;
+
   ScenicSurface* surface = it->second;
   surface->AssertBelongsToCurrentThread();
   return surface;
diff --git a/ui/ozone/platform/scenic/sysmem_buffer_collection.cc b/ui/ozone/platform/scenic/sysmem_buffer_collection.cc
index 629045ab..58b0ab35 100644
--- a/ui/ozone/platform/scenic/sysmem_buffer_collection.cc
+++ b/ui/ozone/platform/scenic/sysmem_buffer_collection.cc
@@ -140,7 +140,8 @@
   is_protected_ = force_protected;
 
   if (register_with_image_pipe) {
-    scenic_overlay_view_.emplace(scenic_surface_factory->CreateScenicSession());
+    scenic_overlay_view_.emplace(scenic_surface_factory->CreateScenicSession(),
+                                 scenic_surface_factory);
     surface_factory_ = scenic_surface_factory;
   }
 
diff --git a/ui/ozone/platform/scenic/sysmem_native_pixmap.cc b/ui/ozone/platform/scenic/sysmem_native_pixmap.cc
index 6059a29..10d6c0f 100644
--- a/ui/ozone/platform/scenic/sysmem_native_pixmap.cc
+++ b/ui/ozone/platform/scenic/sysmem_native_pixmap.cc
@@ -106,8 +106,7 @@
   DCHECK(collection_->scenic_overlay_view());
   ScenicOverlayView* overlay_view = collection_->scenic_overlay_view();
   const auto& buffer_collection_id = handle_.buffer_collection_id.value();
-  if (!overlay_view->AttachToScenicSurface(surface, widget,
-                                           buffer_collection_id)) {
+  if (!overlay_view->AttachToScenicSurface(widget, buffer_collection_id)) {
     DLOG(ERROR) << "Failed to attach to surface.";
     return false;
   }
diff --git a/ui/ozone/public/ozone_platform.cc b/ui/ozone/public/ozone_platform.cc
index 898a9c5..29fd78f 100644
--- a/ui/ozone/public/ozone_platform.cc
+++ b/ui/ozone/public/ozone_platform.cc
@@ -87,7 +87,7 @@
 }
 
 // static
-const char* OzonePlatform::GetPlatformName() {
+std::string OzonePlatform::GetPlatformNameForTest() {
   return GetOzonePlatformName();
 }
 
diff --git a/ui/ozone/public/ozone_platform.h b/ui/ozone/public/ozone_platform.h
index 739ffae0..7c99a4e 100644
--- a/ui/ozone/public/ozone_platform.h
+++ b/ui/ozone/public/ozone_platform.h
@@ -6,6 +6,7 @@
 #define UI_OZONE_PUBLIC_OZONE_PLATFORM_H_
 
 #include <memory>
+#include <string>
 #include <vector>
 
 #include "base/callback.h"
@@ -168,12 +169,8 @@
   static OzonePlatform* GetInstance();
 
   // Returns the current ozone platform name.
-  // TODO(crbug.com/1002674): This is temporary and meant to make it possible
-  // for higher level components to take run-time actions depending on the
-  // current ozone platform selected. Which implies in layering violations,
-  // which are tolerated during the X11 migration to Ozone and must be fixed
-  // once it is done.
-  static const char* GetPlatformName();
+  // Some tests may skip based on the platform name.
+  static std::string GetPlatformNameForTest();
 
   // Factory getters to override in subclasses. The returned objects will be
   // injected into the appropriate layer at startup. Subclasses should not
diff --git a/ui/views/bubble/bubble_dialog_delegate_view.cc b/ui/views/bubble/bubble_dialog_delegate_view.cc
index 7c4fb6a5..50b1313a 100644
--- a/ui/views/bubble/bubble_dialog_delegate_view.cc
+++ b/ui/views/bubble/bubble_dialog_delegate_view.cc
@@ -409,17 +409,6 @@
   return client_view_;
 }
 
-bool BubbleDialogDelegateView::AcceleratorPressed(
-    const ui::Accelerator& accelerator) {
-  if (accelerator.key_code() == ui::VKEY_DOWN ||
-      accelerator.key_code() == ui::VKEY_UP) {
-    // Move the focus up or down.
-    GetFocusManager()->AdvanceFocus(accelerator.key_code() != ui::VKEY_DOWN);
-    return true;
-  }
-  return View::AcceleratorPressed(accelerator);
-}
-
 Widget* BubbleDialogDelegateView::GetWidget() {
   return View::GetWidget();
 }
diff --git a/ui/views/bubble/bubble_dialog_delegate_view.h b/ui/views/bubble/bubble_dialog_delegate_view.h
index b588fdb..c88970286 100644
--- a/ui/views/bubble/bubble_dialog_delegate_view.h
+++ b/ui/views/bubble/bubble_dialog_delegate_view.h
@@ -12,7 +12,6 @@
 #include "base/scoped_observer.h"
 #include "build/build_config.h"
 #include "ui/accessibility/ax_enums.mojom-forward.h"
-#include "ui/base/accelerators/accelerator.h"
 #include "ui/base/class_property.h"
 #include "ui/views/bubble/bubble_border.h"
 #include "ui/views/bubble/bubble_frame_view.h"
@@ -31,10 +30,6 @@
 class Rect;
 }
 
-namespace ui {
-class Accelerator;
-}  // namespace ui
-
 namespace ui_devtools {
 class PageAgentViews;
 }
@@ -411,7 +406,6 @@
   Widget* GetWidget() override;
   const Widget* GetWidget() const override;
   void AddedToWidget() override;
-  bool AcceleratorPressed(const ui::Accelerator& accelerator) override;
 
  protected:
   // Disallow overrides of GetMinimumSize and GetMaximumSize(). These would only
diff --git a/ui/views/controls/menu/menu_controller_unittest.cc b/ui/views/controls/menu/menu_controller_unittest.cc
index c58a5701..1869e85 100644
--- a/ui/views/controls/menu/menu_controller_unittest.cc
+++ b/ui/views/controls/menu/menu_controller_unittest.cc
@@ -892,7 +892,7 @@
 TEST_F(MenuControllerTest, TouchIdsReleasedCorrectly) {
   // Run this test only for X11 (either Ozone or non-Ozone).
   if (features::IsUsingOzonePlatform() &&
-      std::strcmp(ui::OzonePlatform::GetPlatformName(), "x11") != 0) {
+      ui::OzonePlatform::GetPlatformNameForTest() != "x11") {
     GTEST_SKIP();
   }
 
diff --git a/ui/views/views_test_suite.h b/ui/views/views_test_suite.h
index c9ab3d6..c9b048b 100644
--- a/ui/views/views_test_suite.h
+++ b/ui/views/views_test_suite.h
@@ -17,6 +17,11 @@
 }
 #endif
 
+#if defined(USE_OZONE)
+#include "ui/base/ui_base_features.h"
+#include "ui/ozone/public/ozone_platform.h"
+#endif
+
 namespace views {
 
 class ViewsTestSuite : public base::TestSuite {
@@ -49,6 +54,17 @@
   DISALLOW_COPY_AND_ASSIGN(ViewsTestSuite);
 };
 
+#if defined(USE_OZONE)
+// Skips the X11-specific test on Ozone if the current platform is not X11.
+#define SKIP_TEST_IF_NOT_OZONE_X11()                          \
+  if (features::IsUsingOzonePlatform() &&                     \
+      ui::OzonePlatform::GetPlatformNameForTest() != "x11") { \
+    GTEST_SKIP() << "This test is X11-only";                  \
+  }
+#else
+#define SKIP_TEST_IF_NOT_OZONE_X11()
+#endif
+
 }  // namespace views
 
 #endif  // UI_VIEWS_VIEWS_TEST_SUITE_H_
diff --git a/ui/views/widget/desktop_aura/desktop_window_tree_host_linux_interactive_uitest.cc b/ui/views/widget/desktop_aura/desktop_window_tree_host_linux_interactive_uitest.cc
index ee8f4254..26f68abe 100644
--- a/ui/views/widget/desktop_aura/desktop_window_tree_host_linux_interactive_uitest.cc
+++ b/ui/views/widget/desktop_aura/desktop_window_tree_host_linux_interactive_uitest.cc
@@ -510,9 +510,6 @@
 // Chrome even if it not possible to deactivate the window wrt to the x server.
 // This behavior is required by several interactive_ui_tests.
 TEST_F(DesktopWindowTreeHostLinuxTest, Deactivate) {
-  // TODO(1109112): enable this test.
-  if (features::IsUsingOzonePlatform())
-    GTEST_SKIP();
   std::unique_ptr<Widget> widget(CreateWidget(gfx::Rect(100, 100, 100, 100)));
 
   ActivationWaiter waiter(static_cast<x11::Window>(
@@ -538,9 +535,6 @@
 // Chrome synchronously switches the window that mouse events are forwarded to
 // when capture is changed.
 TEST_F(DesktopWindowTreeHostLinuxTest, CaptureEventForwarding) {
-  // TODO(1109112): enable this test.
-  if (features::IsUsingOzonePlatform())
-    GTEST_SKIP();
   std::unique_ptr<Widget> widget1(CreateWidget(gfx::Rect(100, 100, 100, 100)));
   aura::Window* window1 = widget1->GetNativeWindow();
   DesktopWindowTreeHostLinux* host1 =
@@ -615,9 +609,6 @@
 }
 
 TEST_F(DesktopWindowTreeHostLinuxTest, InputMethodFocus) {
-  // TODO(1109112): enable this test.
-  if (features::IsUsingOzonePlatform())
-    GTEST_SKIP();
   std::unique_ptr<Widget> widget(CreateWidget(gfx::Rect(100, 100, 100, 100)));
 
   // Waiter should be created as early as possible so that PropertyNotify has
diff --git a/ui/views/widget/desktop_aura/x11_topmost_window_finder_interactive_uitest.cc b/ui/views/widget/desktop_aura/x11_topmost_window_finder_interactive_uitest.cc
index 12f95a3..93ecbdf 100644
--- a/ui/views/widget/desktop_aura/x11_topmost_window_finder_interactive_uitest.cc
+++ b/ui/views/widget/desktop_aura/x11_topmost_window_finder_interactive_uitest.cc
@@ -114,8 +114,7 @@
 #if defined(USE_OZONE)
     // Run tests only for X11 (ozone or not Ozone).
     if (features::IsUsingOzonePlatform() &&
-        std::strcmp(ui::OzonePlatform::GetInstance()->GetPlatformName(),
-                    "x11") != 0) {
+        ui::OzonePlatform::GetPlatformNameForTest() != "x11") {
       // SetUp still is required to be run. Otherwise, ViewsTestBase CHECKs in
       // the dtor.
       DesktopWidgetTestInteractive::SetUp();
diff --git a/ui/views/widget/widget_unittest.cc b/ui/views/widget/widget_unittest.cc
index e0dbbb6..69f0fbe 100644
--- a/ui/views/widget/widget_unittest.cc
+++ b/ui/views/widget/widget_unittest.cc
@@ -34,6 +34,7 @@
 #include "ui/views/test/test_widget_observer.h"
 #include "ui/views/test/widget_test.h"
 #include "ui/views/view_test_api.h"
+#include "ui/views/views_test_suite.h"
 #include "ui/views/widget/native_widget_delegate.h"
 #include "ui/views/widget/native_widget_private.h"
 #include "ui/views/widget/root_view.h"
@@ -1219,10 +1220,8 @@
   if (base::mac::IsOS10_10())
     return;  // Fails when swarmed. http://crbug.com/660582
 #endif
-#if defined(USE_X11)
-  if (features::IsUsingOzonePlatform())
-    return;  // TODO(https://crbug.com/1109112): Will be enabled later.
-#endif
+
+  SKIP_TEST_IF_NOT_OZONE_X11();
 
   WidgetAutoclosePtr widget;
   widget.reset(CreateTopLevelNativeWidget());
diff --git a/ui/webui/resources/cr_elements/cr_dialog/cr_dialog.html b/ui/webui/resources/cr_elements/cr_dialog/cr_dialog.html
index 9bf7aa5..2e14f41d 100644
--- a/ui/webui/resources/cr_elements/cr_dialog/cr_dialog.html
+++ b/ui/webui/resources/cr_elements/cr_dialog/cr_dialog.html
@@ -19,6 +19,8 @@
         box-shadow: 0 0 16px rgba(0, 0, 0, 0.12),
                     0 16px 16px rgba(0, 0, 0, 0.24);
         color: inherit;
+        max-height: initial;
+        max-width: initial;
         overflow-y: hidden;
         padding: 0;
         position: absolute;