diff --git a/DEPS b/DEPS
index b85e1d9..b47566b 100644
--- a/DEPS
+++ b/DEPS
@@ -199,11 +199,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': '88883c4ca259aa1340286c3a0f85fe97a911292d',
+  'skia_revision': '960bd2dbaa6a2130330c5f693e2a07a24aeca392',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
-  'v8_revision': 'b96d7e2a4be85264b9cd7ba26d91f7fb98baa9a5',
+  'v8_revision': 'ab90f8251bdc6bd9dc6033afdd2374af4883794f',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling swarming_client
   # and whatever else without interference from each other.
@@ -211,7 +211,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ANGLE
   # and whatever else without interference from each other.
-  'angle_revision': '4ebcee3eb02ffdd586e20965b9c4bb609876db36',
+  'angle_revision': '4e2b6d6b01028728634d65d0f69523da145420e7',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -250,7 +250,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling freetype
   # and whatever else without interference from each other.
-  'freetype_revision': '7bdf386e758cb7c01392e625f4d723e5abb3f9a6',
+  'freetype_revision': '0d5f1dd37c056b4460a460d16fd1fbb06740e891',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling freetype
   # and whatever else without interference from each other.
@@ -274,7 +274,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling devtools-frontend
   # and whatever else without interference from each other.
-  'devtools_frontend_revision': 'cde5182f7aa484bea3ea9d2ba2a5f49b2cfd738a',
+  'devtools_frontend_revision': '37fb5ece29fd09e5da06b508477b84b8bce58df3',
   # 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.
@@ -314,7 +314,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': '5d4fd88a7403aa255d49919d011af54c691b344a',
+  'dawn_revision': 'ec56b90ceab0c16e3c9f5603cc26562ec7bc2430',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -1262,7 +1262,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + 'c65f224a7259d87bf1f980e7424d73fc0cfca795',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + '8986f3ce67ec65a72629404b45d639ca37e5ea2b',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1340,7 +1340,7 @@
       'packages': [
           {
               'package': 'fuchsia/third_party/aemu/linux-amd64',
-              'version': 'qMq36BPvKEIxjpVFBefO08HoyM51jARe3EuX0vcgzWsC'
+              'version': 'GI8QGXYA44_uicQ1AX9ecXBzIK4dImUA90X-kMlohyIC'
           },
       ],
       'condition': 'host_os == "linux" and checkout_fuchsia',
@@ -1514,7 +1514,7 @@
     Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + '3c2fe3888658d82b47ca831d59a2e07579619c2d',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + 'ef9c4e07a351c9396c72e72efaa1be3df728c440',
+    Var('webrtc_git') + '/src.git' + '@' + '8649e49d10e6e6efb5a98920f0b19a77abe8f070',
 
   'src/third_party/libgifcodec':
      Var('skia_git') + '/libgifcodec' + '@'+  Var('libgifcodec_revision'),
@@ -1586,7 +1586,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@3974e673643526eb5e4050be7da7abcc86e70699',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@ec26b8d2b2f85f4c7f9c4a186daabac73172985f',
     'condition': 'checkout_src_internal',
   },
 
@@ -1594,7 +1594,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/help_app/app',
-        'version': 'Lhu0LG_M8M2Pn2h_RBp-1HjjF6xbNUOUOu6DedtsKlQC',
+        'version': 'ArjDoksx4veFQalheiqMNhRYccNS84igngDuMSvLD-0C',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
@@ -1605,7 +1605,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/media_app/app',
-        'version': '97cSjc_Tjv810DAVmAh-xqhwbphGqNsTmBJluRhxfZ8C',
+        'version': 'JzkQbgu707ehmRMJSwGBX0gMotKpUKiewC8zdP_xHngC',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
@@ -3361,7 +3361,7 @@
       'packages': [
           {
               'package': 'chromium/third_party/android_deps/libs/com_google_guava_guava',
-              'version': 'version:27.1-jre-cr0',
+              'version': 'version:30.1-jre-cr0',
           },
       ],
       'condition': 'checkout_android',
@@ -3372,7 +3372,7 @@
       'packages': [
           {
               'package': 'chromium/third_party/android_deps/libs/com_google_guava_guava_android',
-              'version': 'version:25.1-android-cr0',
+              'version': 'version:30.1-android-cr0',
           },
       ],
       'condition': 'checkout_android',
@@ -3394,7 +3394,7 @@
       'packages': [
           {
               'package': 'chromium/third_party/android_deps/libs/com_google_j2objc_j2objc_annotations',
-              'version': 'version:1.1-cr0',
+              'version': 'version:1.3-cr0',
           },
       ],
       'condition': 'checkout_android',
@@ -3735,7 +3735,7 @@
       'packages': [
           {
               'package': 'chromium/third_party/android_deps/libs/org_checkerframework_checker_compat_qual',
-              'version': 'version:2.5.3-cr0',
+              'version': 'version:2.5.5-cr0',
           },
       ],
       'condition': 'checkout_android',
@@ -3746,7 +3746,7 @@
       'packages': [
           {
               'package': 'chromium/third_party/android_deps/libs/org_checkerframework_checker_qual',
-              'version': 'version:2.10.0-cr0',
+              'version': 'version:3.5.0-cr0',
           },
       ],
       'condition': 'checkout_android',
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index bed617c..11a1d86 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -1678,6 +1678,10 @@
     "wm/window_cycle_event_filter.h",
     "wm/window_cycle_list.cc",
     "wm/window_cycle_list.h",
+    "wm/window_cycle_tab_slider.cc",
+    "wm/window_cycle_tab_slider.h",
+    "wm/window_cycle_tab_slider_button.cc",
+    "wm/window_cycle_tab_slider_button.h",
     "wm/window_dimmer.cc",
     "wm/window_dimmer.h",
     "wm/window_finder.cc",
@@ -1965,6 +1969,7 @@
     "accessibility/touch_exploration_controller_unittest.cc",
     "accessibility/touch_exploration_manager_unittest.cc",
     "ambient/ambient_controller_unittest.cc",
+    "ambient/ambient_photo_cache_unittest.cc",
     "ambient/ambient_photo_controller_unittest.cc",
     "ambient/autotest_ambient_api_unittest.cc",
     "ambient/model/ambient_backend_model_unittest.cc",
diff --git a/ash/accelerators/accelerator_controller_impl.cc b/ash/accelerators/accelerator_controller_impl.cc
index d6f879a..efa0c34 100644
--- a/ash/accelerators/accelerator_controller_impl.cc
+++ b/ash/accelerators/accelerator_controller_impl.cc
@@ -794,7 +794,10 @@
 
 void HandleTakeScreenshot() {
   base::RecordAction(UserMetricsAction("Accel_Take_Screenshot"));
-  Shell::Get()->screenshot_controller()->TakeScreenshotForAllRootWindows();
+  if (!features::IsCaptureModeEnabled())
+    Shell::Get()->screenshot_controller()->TakeScreenshotForAllRootWindows();
+  else
+    CaptureModeController::Get()->CaptureScreenshotsOfAllDisplays();
 }
 
 void HandleToggleSystemTrayBubbleInternal(bool focus_message_center) {
diff --git a/ash/ambient/ambient_controller.cc b/ash/ambient/ambient_controller.cc
index 3079b24..868d0bb 100644
--- a/ash/ambient/ambient_controller.cc
+++ b/ash/ambient/ambient_controller.cc
@@ -509,6 +509,10 @@
 }
 
 void AmbientController::OnEnabledPrefChanged() {
+  // TODO(b/176094707) conditionally create/destroy photo_controller and cache
+  // if Ambient is enabled
+  ambient_photo_controller_.InitCache();
+
   if (IsAmbientModeEnabled()) {
     DVLOG(1) << "Ambient mode enabled";
 
diff --git a/ash/ambient/ambient_photo_cache.cc b/ash/ambient/ambient_photo_cache.cc
index c30b9db8..bdcdad7 100644
--- a/ash/ambient/ambient_photo_cache.cc
+++ b/ash/ambient/ambient_photo_cache.cc
@@ -8,7 +8,10 @@
 #include "ash/public/cpp/ambient/ambient_client.h"
 #include "base/files/file_util.h"
 #include "base/memory/weak_ptr.h"
+#include "base/path_service.h"
 #include "base/sequenced_task_runner.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/system/sys_info.h"
 #include "base/task/task_traits.h"
 #include "base/task/thread_pool.h"
 #include "services/data_decoder/public/cpp/decode_image.h"
@@ -52,11 +55,82 @@
                                           NO_TRAFFIC_ANNOTATION_YET);
 }
 
-// Implementation of |AmbientPhotoCache|.
+bool CreateDirIfNotExists(const base::FilePath& path) {
+  return base::DirectoryExists(path) || base::CreateDirectory(path);
+}
+
+// Writes |data| to |path| if |data| is not nullptr and is not empty. If |data|
+// is nullptr or empty, will delete any existing file at |path|.
+bool WriteOrDeleteFile(const base::FilePath& path,
+                       const std::string* const data) {
+  if (!data || data->empty())
+    return base::DeleteFile(path);
+
+  if (!CreateDirIfNotExists(path.DirName())) {
+    LOG(ERROR) << "Cannot create ambient mode directory.";
+    return false;
+  }
+
+  if (base::SysInfo::AmountOfFreeDiskSpace(path.DirName()) <
+      kMaxReservedAvailableDiskSpaceByte) {
+    LOG(ERROR) << "Not enough disk space left.";
+    return false;
+  }
+
+  // Create a temp file.
+  base::FilePath temp_file;
+  if (!base::CreateTemporaryFileInDir(path.DirName(), &temp_file)) {
+    LOG(ERROR) << "Cannot create a temporary file.";
+    return false;
+  }
+
+  // Write to the tmp file.
+  const int size = data->size();
+  int written_size = base::WriteFile(temp_file, data->data(), size);
+  if (written_size != size) {
+    LOG(ERROR) << "Cannot write the temporary file.";
+    base::DeleteFile(temp_file);
+    return false;
+  }
+
+  // Replace the current file with the temp file.
+  if (!base::ReplaceFile(temp_file, path, /*error=*/nullptr)) {
+    LOG(ERROR) << "Cannot replace the temporary file.";
+    base::DeleteFile(temp_file);
+    return false;
+  }
+
+  return true;
+}
+
+base::FilePath GetPhotoPath(int cache_index,
+                            const base::FilePath& root_path,
+                            bool is_related = false) {
+  std::string file_ext;
+
+  // "_r.img" for related files, ".img" otherwise
+  if (is_related)
+    file_ext += kRelatedPhotoSuffix;
+
+  file_ext += kPhotoFileExt;
+
+  return root_path.Append(base::NumberToString(cache_index) + file_ext);
+}
+
+base::FilePath GetDetailsPath(int cache_index,
+                              const base::FilePath& root_path) {
+  return GetPhotoPath(cache_index, root_path)
+      .RemoveExtension()
+      .AddExtension(kPhotoDetailsFileExt);
+}
+
+// -----------------AmbientPhotoCacheImpl---------------------------------------
+
 class AmbientPhotoCacheImpl : public AmbientPhotoCache {
  public:
-  AmbientPhotoCacheImpl()
-      : task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
+  explicit AmbientPhotoCacheImpl(base::FilePath path)
+      : root_directory_(path),
+        task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
             {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
              base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN})) {}
   ~AmbientPhotoCacheImpl() override = default;
@@ -65,9 +139,12 @@
   void DownloadPhoto(const std::string& url,
                      base::OnceCallback<void(std::unique_ptr<std::string>)>
                          callback) override {
-    auto simple_loader = CreateSimpleURLLoader(url);
+    std::unique_ptr<network::SimpleURLLoader> simple_loader =
+        CreateSimpleURLLoader(url);
+    scoped_refptr<network::SharedURLLoaderFactory> loader_factory =
+        AmbientClient::Get()->GetURLLoaderFactory();
     auto* loader_ptr = simple_loader.get();
-    auto loader_factory = AmbientClient::Get()->GetURLLoaderFactory();
+
     loader_ptr->DownloadToString(
         loader_factory.get(),
         base::BindOnce(&AmbientPhotoCacheImpl::OnUrlDownloaded,
@@ -77,10 +154,120 @@
   }
 
   void DownloadPhotoToFile(const std::string& url,
-                           base::OnceCallback<void(base::FilePath)> callback,
-                           const base::FilePath& file_path) override {
-    auto simple_loader = CreateSimpleURLLoader(url);
-    auto loader_factory = AmbientClient::Get()->GetURLLoaderFactory();
+                           int cache_index,
+                           bool is_related,
+                           base::OnceCallback<void(bool)> callback) override {
+    auto file_path = GetPhotoPath(cache_index, root_directory_, is_related);
+    task_runner_->PostTaskAndReply(
+        FROM_HERE,
+        base::BindOnce(
+            [](const base::FilePath& path) {
+              if (!CreateDirIfNotExists(path))
+                LOG(ERROR) << "Cannot create ambient mode directory";
+            },
+            root_directory_),
+        base::BindOnce(&AmbientPhotoCacheImpl::DownloadPhotoToFileInternal,
+                       weak_factory_.GetWeakPtr(), url, std::move(callback),
+                       file_path));
+  }
+
+  void DecodePhoto(
+      std::unique_ptr<std::string> data,
+      base::OnceCallback<void(const gfx::ImageSkia&)> callback) override {
+    std::vector<uint8_t> image_bytes(data->begin(), data->end());
+    data_decoder::DecodeImageIsolated(
+        image_bytes, data_decoder::mojom::ImageCodec::DEFAULT,
+        /*shrink_to_fit=*/true, data_decoder::kDefaultMaxSizeInBytes,
+        /*desired_image_frame_size=*/gfx::Size(),
+        base::BindOnce(&ToImageSkia, std::move(callback)));
+  }
+
+  void WriteFiles(int cache_index,
+                  const std::string* const image,
+                  const std::string* const details,
+                  const std::string* const related_image,
+                  base::OnceClosure callback) override {
+    DCHECK_LT(cache_index, kMaxNumberOfCachedImages);
+    task_runner_->PostTaskAndReply(
+        FROM_HERE,
+        base::BindOnce(
+            [](int cache_index, const base::FilePath& root_path,
+               const std::string* const image, const std::string* const details,
+               const std::string* const related_image) {
+              bool success = true;
+
+              auto image_path = GetPhotoPath(cache_index, root_path);
+              success = success && WriteOrDeleteFile(image_path, image);
+
+              auto details_path = GetDetailsPath(cache_index, root_path);
+              success = success && WriteOrDeleteFile(details_path, details);
+
+              auto related_image_path =
+                  GetPhotoPath(cache_index, root_path, /*is_related=*/true);
+              success = success &&
+                        WriteOrDeleteFile(related_image_path, related_image);
+
+              if (!success) {
+                LOG(WARNING) << "Error writing files";
+                base::DeleteFile(image_path);
+                base::DeleteFile(details_path);
+                base::DeleteFile(related_image_path);
+              }
+            },
+            cache_index, root_directory_, image, details, related_image),
+        std::move(callback));
+  }
+
+  void ReadFiles(int cache_index,
+                 base::OnceCallback<void(PhotoCacheEntry)> callback) override {
+    task_runner_->PostTaskAndReplyWithResult(
+        FROM_HERE,
+        base::BindOnce(
+            [](int cache_index, const base::FilePath& root_path) {
+              auto image = std::make_unique<std::string>();
+              auto details = std::make_unique<std::string>();
+              auto related_image = std::make_unique<std::string>();
+
+              auto image_path = GetPhotoPath(cache_index, root_path);
+
+              if (!base::ReadFileToString(image_path, image.get()))
+                image->clear();
+
+              auto details_path = GetDetailsPath(cache_index, root_path);
+
+              if (!base::ReadFileToString(details_path, details.get()))
+                details->clear();
+
+              auto related_path =
+                  GetPhotoPath(cache_index, root_path, /*is_related=*/true);
+
+              if (!base::ReadFileToString(related_path, related_image.get()))
+                related_image->clear();
+
+              return PhotoCacheEntry(std::move(image), std::move(details),
+                                     std::move(related_image));
+            },
+            cache_index, root_directory_),
+        std::move(callback));
+  }
+
+  void Clear() override {
+    task_runner_->PostTask(FROM_HERE,
+                           base::BindOnce(
+                               [](const base::FilePath& file_path) {
+                                 base::DeletePathRecursively(file_path);
+                               },
+                               root_directory_));
+  }
+
+ private:
+  void DownloadPhotoToFileInternal(const std::string& url,
+                                   base::OnceCallback<void(bool)> callback,
+                                   const base::FilePath& file_path) {
+    std::unique_ptr<network::SimpleURLLoader> simple_loader =
+        CreateSimpleURLLoader(url);
+    scoped_refptr<network::SharedURLLoaderFactory> loader_factory =
+        AmbientClient::Get()->GetURLLoaderFactory();
     auto* loader_ptr = simple_loader.get();
     auto* loader_factory_ptr = loader_factory.get();
 
@@ -100,18 +287,6 @@
         temp_path);
   }
 
-  void DecodePhoto(
-      std::unique_ptr<std::string> data,
-      base::OnceCallback<void(const gfx::ImageSkia&)> callback) override {
-    std::vector<uint8_t> image_bytes(data->begin(), data->end());
-    data_decoder::DecodeImageIsolated(
-        image_bytes, data_decoder::mojom::ImageCodec::DEFAULT,
-        /*shrink_to_fit=*/true, data_decoder::kDefaultMaxSizeInBytes,
-        /*desired_image_frame_size=*/gfx::Size(),
-        base::BindOnce(&ToImageSkia, std::move(callback)));
-  }
-
- private:
   void OnUrlDownloaded(
       base::OnceCallback<void(std::unique_ptr<std::string>)> callback,
       std::unique_ptr<network::SimpleURLLoader> simple_loader,
@@ -129,7 +304,7 @@
   }
 
   void OnUrlDownloadedToFile(
-      base::OnceCallback<void(base::FilePath)> callback,
+      base::OnceCallback<void(bool)> callback,
       std::unique_ptr<network::SimpleURLLoader> simple_loader,
       scoped_refptr<network::SharedURLLoaderFactory> loader_factory,
       const base::FilePath& desired_path,
@@ -147,7 +322,7 @@
                                               },
                                               temp_path));
       }
-      std::move(callback).Run(base::FilePath());
+      std::move(callback).Run(false);
       return;
     }
 
@@ -163,23 +338,48 @@
                 // Clean up the files.
                 base::DeleteFile(from_path);
                 base::DeleteFile(to_path);
-                return base::FilePath();
+                return false;
               }
-              return to_path;
+              return true;
             },
             desired_path, temp_path),
         std::move(callback));
   }
 
+  const base::FilePath root_directory_;
   scoped_refptr<base::SequencedTaskRunner> task_runner_;
   base::WeakPtrFactory<AmbientPhotoCacheImpl> weak_factory_{this};
 };
 
 }  // namespace
 
+// ---------------- PhotoCacheRead --------------------------------------------
+
+PhotoCacheEntry::PhotoCacheEntry() = default;
+
+PhotoCacheEntry::PhotoCacheEntry(std::unique_ptr<std::string> image,
+                                 std::unique_ptr<std::string> details,
+                                 std::unique_ptr<std::string> related_image)
+    : image(std::move(image)),
+      details(std::move(details)),
+      related_image(std::move(related_image)) {}
+
+PhotoCacheEntry::PhotoCacheEntry(PhotoCacheEntry&&) = default;
+
+PhotoCacheEntry::~PhotoCacheEntry() = default;
+
+void PhotoCacheEntry::reset() {
+  image.reset();
+  details.reset();
+  related_image.reset();
+}
+
+// -------------- AmbientPhotoCache --------------------------------------------
+
 // static
-std::unique_ptr<AmbientPhotoCache> AmbientPhotoCache::Create() {
-  return std::make_unique<AmbientPhotoCacheImpl>();
+std::unique_ptr<AmbientPhotoCache> AmbientPhotoCache::Create(
+    base::FilePath root_path) {
+  return std::make_unique<AmbientPhotoCacheImpl>(root_path);
 }
 
 }  // namespace ash
diff --git a/ash/ambient/ambient_photo_cache.h b/ash/ambient/ambient_photo_cache.h
index 9c3f6cb..56eceb7 100644
--- a/ash/ambient/ambient_photo_cache.h
+++ b/ash/ambient/ambient_photo_cache.h
@@ -8,6 +8,7 @@
 #include <memory>
 #include <string>
 
+#include "ash/ash_export.h"
 #include "base/callback_forward.h"
 #include "base/files/file_path.h"
 
@@ -17,31 +18,77 @@
 
 namespace ash {
 
+// Holds the return value for |AmbientPhotoCache::ReadFiles| to use with
+// |task_runner_->PostTaskAndReplyWithResult|.
+// Represented on disk by a file for each of |image|, |details|, and
+// |related_image|.
+struct ASH_EXPORT PhotoCacheEntry {
+  PhotoCacheEntry();
+
+  PhotoCacheEntry(std::unique_ptr<std::string> image,
+                  std::unique_ptr<std::string> details,
+                  std::unique_ptr<std::string> related_image);
+
+  PhotoCacheEntry(const PhotoCacheEntry&) = delete;
+  PhotoCacheEntry& operator=(const PhotoCacheEntry&) = delete;
+  PhotoCacheEntry(PhotoCacheEntry&&);
+
+  ~PhotoCacheEntry();
+
+  void reset();
+
+  std::unique_ptr<std::string> image;
+  std::unique_ptr<std::string> details;
+  std::unique_ptr<std::string> related_image;
+};
+
 // Interface for downloading and decoding photos for Ambient mode. Mocked for
-// testing to isolate from network.
-class AmbientPhotoCache {
+// testing to isolate from network and file system.
+// Each cache entry is written to disk as three files in the |root_path|, with
+// filenames prefixed by |cache_index|.
+class ASH_EXPORT AmbientPhotoCache {
  public:
   AmbientPhotoCache() = default;
   AmbientPhotoCache(const AmbientPhotoCache&) = delete;
   AmbientPhotoCache& operator=(const AmbientPhotoCache&) = delete;
   virtual ~AmbientPhotoCache() = default;
 
-  static std::unique_ptr<AmbientPhotoCache> Create();
+  static std::unique_ptr<AmbientPhotoCache> Create(base::FilePath root_path);
 
   virtual void DownloadPhoto(
       const std::string& url,
       base::OnceCallback<void(std::unique_ptr<std::string>)> callback) = 0;
 
-  // Saves the photo to |file_path| and calls |callback| when complete. If an
-  // error occurs, will call |callback| with an empty path.
-  virtual void DownloadPhotoToFile(
-      const std::string& url,
-      base::OnceCallback<void(base::FilePath)> callback,
-      const base::FilePath& file_path) = 0;
+  // Saves the photo at |url| to |cache_index| and calls |callback| with a
+  // boolean that indicates success. Setting |is_related| will change the
+  // filename to indicate that this is a paired photo.
+  virtual void DownloadPhotoToFile(const std::string& url,
+                                   int cache_index,
+                                   bool is_related,
+                                   base::OnceCallback<void(bool)> callback) = 0;
 
   virtual void DecodePhoto(
       std::unique_ptr<std::string> data,
       base::OnceCallback<void(const gfx::ImageSkia&)> callback) = 0;
+
+  // Write files to disk at |cache_index| and call |callback| when complete.
+  // |image| and |related_image| are encoded jpg images that must be decoded
+  // with |DecodePhoto| to display. |details| is human readable text.
+  virtual void WriteFiles(int cache_index,
+                          const std::string* const image,
+                          const std::string* const details,
+                          const std::string* const related_image,
+                          base::OnceClosure callback) = 0;
+
+  // Read the files at |cache_index| and call |callback| with a struct
+  // containing the contents of each file. If a particular file fails to be
+  // read, it may be represented as nullptr or empty string.
+  virtual void ReadFiles(
+      int cache_index,
+      base::OnceCallback<void(PhotoCacheEntry)> callback) = 0;
+
+  // Erase all stored files from disk.
+  virtual void Clear() = 0;
 };
 
 }  // namespace ash
diff --git a/ash/ambient/ambient_photo_cache_unittest.cc b/ash/ambient/ambient_photo_cache_unittest.cc
new file mode 100644
index 0000000..bf72c123
--- /dev/null
+++ b/ash/ambient/ambient_photo_cache_unittest.cc
@@ -0,0 +1,135 @@
+// 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/ambient/ambient_photo_cache.h"
+
+#include "ash/ambient/ambient_constants.h"
+#include "base/callback_forward.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/path_service.h"
+#include "base/run_loop.h"
+#include "base/test/task_environment.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace ash {
+
+namespace {
+
+base::FilePath GetTestPath() {
+  base::FilePath path;
+  EXPECT_TRUE(base::PathService::Get(base::DIR_TEMP, &path));
+  path = path.Append(FILE_PATH_LITERAL(kAmbientModeDirectoryName));
+  return path;
+}
+
+}  // namespace
+
+class AmbientPhotoCacheTest : public testing::Test {
+ public:
+  void SetUp() override {
+    auto test_path = GetTestPath();
+    base::DeletePathRecursively(test_path);
+    photo_cache_ = AmbientPhotoCache::Create(test_path);
+  }
+
+  void TearDown() override { base::DeletePathRecursively(GetTestPath()); }
+
+  AmbientPhotoCache* photo_cache() { return photo_cache_.get(); }
+
+ protected:
+  base::test::TaskEnvironment task_environment_;
+
+ private:
+  std::unique_ptr<AmbientPhotoCache> photo_cache_;
+};
+
+TEST_F(AmbientPhotoCacheTest, ReadsBackWrittenFiles) {
+  int cache_index = 0;
+  std::string image("image");
+  std::string details("details");
+  std::string related_image("related image");
+
+  {
+    base::RunLoop loop;
+    photo_cache()->WriteFiles(cache_index, &image, &details, &related_image,
+                              loop.QuitClosure());
+    loop.Run();
+  }
+
+  {
+    base::RunLoop loop;
+    // Read the files back using photo cache.
+    photo_cache()->ReadFiles(
+        cache_index,
+        base::BindOnce(
+            [](base::OnceClosure done, PhotoCacheEntry cache_read) {
+              EXPECT_EQ(*cache_read.image, "image");
+              EXPECT_EQ(*cache_read.details, "details");
+              EXPECT_EQ(*cache_read.related_image, "related image");
+              std::move(done).Run();
+            },
+            loop.QuitClosure()));
+    loop.Run();
+  }
+}
+
+TEST_F(AmbientPhotoCacheTest, WritesFileToDisk) {
+  base::FilePath test_path = GetTestPath();
+
+  int cache_index = 5;
+  std::string image("image 5");
+  std::string details("details 5");
+  std::string related_image("related image 5");
+
+  // Make sure files are not on disk.
+  EXPECT_FALSE(base::PathExists(test_path.Append(FILE_PATH_LITERAL("5.img"))));
+  EXPECT_FALSE(base::PathExists(test_path.Append(FILE_PATH_LITERAL("5.txt"))));
+  EXPECT_FALSE(
+      base::PathExists(test_path.Append(FILE_PATH_LITERAL("5_r.img"))));
+
+  // Write the data to the cache.
+  {
+    base::RunLoop loop;
+    photo_cache()->WriteFiles(cache_index, &image, &details, &related_image,
+                              loop.QuitClosure());
+    loop.Run();
+  }
+
+  // Verify that expected files are written to disk.
+  std::string actual_image;
+  EXPECT_TRUE(base::ReadFileToString(
+      test_path.Append(FILE_PATH_LITERAL("5.img")), &actual_image));
+  EXPECT_EQ(actual_image, image);
+
+  std::string actual_details;
+  EXPECT_TRUE(base::ReadFileToString(
+      test_path.Append(FILE_PATH_LITERAL("5.txt")), &actual_details));
+  EXPECT_EQ(actual_details, details);
+
+  std::string actual_related_image;
+  EXPECT_TRUE(base::ReadFileToString(
+      test_path.Append(FILE_PATH_LITERAL("5_r.img")), &actual_related_image));
+  EXPECT_EQ(actual_related_image, related_image);
+}
+
+TEST_F(AmbientPhotoCacheTest, SetsDataToEmptyStringWhenFilesMissing) {
+  base::FilePath test_path = GetTestPath();
+  EXPECT_FALSE(base::DirectoryExists(test_path));
+  {
+    base::RunLoop loop;
+    photo_cache()->ReadFiles(
+        /*cache_index=*/1,
+        base::BindOnce(
+            [](base::OnceClosure done, PhotoCacheEntry cache_read) {
+              EXPECT_TRUE(cache_read.image->empty());
+              EXPECT_TRUE(cache_read.details->empty());
+              EXPECT_TRUE(cache_read.related_image->empty());
+              std::move(done).Run();
+            },
+            loop.QuitClosure()));
+  }
+}
+
+}  // namespace ash
diff --git a/ash/ambient/ambient_photo_controller.cc b/ash/ambient/ambient_photo_controller.cc
index 767fc69..e414d18 100644
--- a/ash/ambient/ambient_photo_controller.cc
+++ b/ash/ambient/ambient_photo_controller.cc
@@ -85,69 +85,6 @@
           base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN};
 }
 
-// Get the root path for ambient mode.
-base::FilePath GetRootPath() {
-  base::FilePath home_dir;
-  CHECK(base::PathService::Get(base::DIR_HOME, &home_dir));
-  return home_dir.Append(FILE_PATH_LITERAL(kAmbientModeDirectoryName));
-}
-
-base::FilePath GetCachePath() {
-  return GetRootPath().Append(
-      FILE_PATH_LITERAL(kAmbientModeCacheDirectoryName));
-}
-
-base::FilePath GetBackupCachePath() {
-  return GetRootPath().Append(
-      FILE_PATH_LITERAL(kAmbientModeBackupCacheDirectoryName));
-}
-
-base::FilePath GetBackupFilePath(size_t index) {
-  return GetBackupCachePath().Append(base::NumberToString(index) +
-                                     kPhotoFileExt);
-}
-
-base::FilePath GetRelatedFilePath(const std::string& file_name) {
-  return GetCachePath().Append(file_name + kRelatedPhotoSuffix + kPhotoFileExt);
-}
-
-bool CreateDirIfNotExists(const base::FilePath& path) {
-  return base::DirectoryExists(path) || base::CreateDirectory(path);
-}
-
-void WriteFile(const base::FilePath& path, const std::string& data) {
-  if (!CreateDirIfNotExists(GetCachePath())) {
-    LOG(ERROR) << "Cannot create ambient mode directory.";
-    return;
-  }
-
-  if (base::SysInfo::AmountOfFreeDiskSpace(GetRootPath()) <
-      kMaxReservedAvailableDiskSpaceByte) {
-    LOG(WARNING) << "Not enough disk space left.";
-    return;
-  }
-
-  // Create a temp file.
-  base::FilePath temp_file;
-  if (!base::CreateTemporaryFileInDir(path.DirName(), &temp_file)) {
-    LOG(ERROR) << "Cannot create a temporary file.";
-    return;
-  }
-
-  // Write to the tmp file.
-  const int size = data.size();
-  int written_size = base::WriteFile(temp_file, data.data(), size);
-  if (written_size != size) {
-    LOG(ERROR) << "Cannot write the temporary file.";
-    base::DeleteFile(temp_file);
-    return;
-  }
-
-  // Replace the current file with the temp file.
-  if (!base::ReplaceFile(temp_file, path, /*error=*/nullptr))
-    LOG(ERROR) << "Cannot replace the temporary file.";
-}
-
 const std::array<const char*, 2>& GetBackupPhotoUrls() {
   return Shell::Get()
       ->ambient_controller()
@@ -155,12 +92,18 @@
       ->GetBackupPhotoUrls();
 }
 
+// Get the cache root path for ambient mode.
+base::FilePath GetCacheRootPath() {
+  base::FilePath home_dir;
+  CHECK(base::PathService::Get(base::DIR_HOME, &home_dir));
+  return home_dir.Append(FILE_PATH_LITERAL(kAmbientModeDirectoryName));
+}
+
 }  // namespace
 
 AmbientPhotoController::AmbientPhotoController()
     : fetch_topic_retry_backoff_(&kFetchTopicRetryBackoffPolicy),
       resume_fetch_image_backoff_(&kResumeFetchImageBackoffPolicy),
-      photo_cache_(AmbientPhotoCache::Create()),
       task_runner_(
           base::ThreadPool::CreateSequencedTaskRunner(GetTaskTraits())) {
   ambient_backend_model_observer_.Add(&ambient_backend_model_);
@@ -179,7 +122,7 @@
     // Would use |timer_.FireNow()| but this does not execute if screen is
     // locked. Manually call the expected callback instead.
     backup_photo_refresh_timer_.Stop();
-    PrepareFetchBackupImages();
+    FetchBackupImages();
   }
 }
 
@@ -204,7 +147,7 @@
       FROM_HERE,
       std::max(kBackupPhotoRefreshDelay,
                resume_fetch_image_backoff_.GetTimeUntilRelease()),
-      base::BindOnce(&AmbientPhotoController::PrepareFetchBackupImages,
+      base::BindOnce(&AmbientPhotoController::FetchBackupImages,
                      weak_factory_.GetWeakPtr()));
 }
 
@@ -238,12 +181,21 @@
 }
 
 void AmbientPhotoController::ClearCache() {
-  task_runner_->PostTask(FROM_HERE,
-                         base::BindOnce(
-                             [](const base::FilePath& file_path) {
-                               base::DeletePathRecursively(file_path);
-                             },
-                             GetCachePath()));
+  DCHECK(photo_cache_);
+  DCHECK(backup_photo_cache_);
+  photo_cache_->Clear();
+  backup_photo_cache_->Clear();
+}
+
+void AmbientPhotoController::InitCache() {
+  if (!photo_cache_) {
+    photo_cache_ = AmbientPhotoCache::Create(GetCacheRootPath().Append(
+        FILE_PATH_LITERAL(kAmbientModeCacheDirectoryName)));
+  }
+  if (!backup_photo_cache_) {
+    backup_photo_cache_ = AmbientPhotoCache::Create(GetCacheRootPath().Append(
+        FILE_PATH_LITERAL(kAmbientModeBackupCacheDirectoryName)));
+  }
 }
 
 void AmbientPhotoController::ScheduleFetchTopics(bool backoff) {
@@ -265,28 +217,21 @@
                      weak_factory_.GetWeakPtr()));
 }
 
-void AmbientPhotoController::PrepareFetchBackupImages() {
-  task_runner_->PostTaskAndReply(
-      FROM_HERE,
-      base::BindOnce([]() { CreateDirIfNotExists(GetBackupCachePath()); }),
-      base::BindOnce(&AmbientPhotoController::FetchBackupImages,
-                     weak_factory_.GetWeakPtr()));
-}
-
 void AmbientPhotoController::FetchBackupImages() {
   const auto& backup_photo_urls = GetBackupPhotoUrls();
   backup_retries_to_read_from_cache_ = backup_photo_urls.size();
   for (size_t i = 0; i < backup_photo_urls.size(); i++) {
-    photo_cache_->DownloadPhotoToFile(
+    backup_photo_cache_->DownloadPhotoToFile(
         backup_photo_urls.at(i),
+        /*cache_index=*/i,
+        /*is_related=*/false,
         base::BindOnce(&AmbientPhotoController::OnBackupImageFetched,
-                       weak_factory_.GetWeakPtr()),
-        GetBackupFilePath(i));
+                       weak_factory_.GetWeakPtr()));
   }
 }
 
-void AmbientPhotoController::OnBackupImageFetched(base::FilePath file_path) {
-  if (file_path.empty()) {
+void AmbientPhotoController::OnBackupImageFetched(bool success) {
+  if (!success) {
     // TODO(b/169807068) Change to retry individual failed images.
     resume_fetch_image_backoff_.InformOfRequest(/*succeeded=*/false);
     LOG(WARNING) << "Downloading backup image failed.";
@@ -299,10 +244,8 @@
 const AmbientModeTopic* AmbientPhotoController::GetNextTopic() {
   const auto& topics = ambient_backend_model_.topics();
   // If no more topics, will read from cache.
-  if (topic_index_ == topics.size()) {
-    DVLOG(3) << "No more topics";
+  if (topic_index_ == topics.size())
     return nullptr;
-  }
 
   return &topics[topic_index_++];
 }
@@ -328,9 +271,7 @@
 }
 
 void AmbientPhotoController::ResetImageData() {
-  image_data_.reset();
-  related_image_data_.reset();
-  image_details_.reset();
+  cache_entry_.reset();
 
   image_ = gfx::ImageSkia();
   related_image_ = gfx::ImageSkia();
@@ -344,24 +285,21 @@
     const int num_callbacks = (topic->related_image_url) ? 2 : 1;
     auto on_done = base::BarrierClosure(
         num_callbacks,
-        base::BindOnce(&AmbientPhotoController::OnAllPhotoRawDataAvailable,
-                       weak_factory_.GetWeakPtr(),
-                       /*from_downloading=*/true));
+        base::BindOnce(&AmbientPhotoController::OnAllPhotoRawDataDownloaded,
+                       weak_factory_.GetWeakPtr()));
 
     photo_cache_->DownloadPhoto(
         topic->url,
-        base::BindOnce(&AmbientPhotoController::OnPhotoRawDataAvailable,
+        base::BindOnce(&AmbientPhotoController::OnPhotoRawDataDownloaded,
                        weak_factory_.GetWeakPtr(),
-                       /*from_downloading=*/true,
                        /*is_related_image=*/false, on_done,
                        std::make_unique<std::string>(topic->details)));
 
     if (topic->related_image_url) {
       photo_cache_->DownloadPhoto(
           *(topic->related_image_url),
-          base::BindOnce(&AmbientPhotoController::OnPhotoRawDataAvailable,
+          base::BindOnce(&AmbientPhotoController::OnPhotoRawDataDownloaded,
                          weak_factory_.GetWeakPtr(),
-                         /*from_downloading=*/true,
                          /*is_related_image=*/true, on_done,
                          std::make_unique<std::string>(topic->details)));
     }
@@ -400,28 +338,14 @@
     }
 
     --backup_retries_to_read_from_cache_;
-    // Try to read a backup image.
-    auto photo_data = std::make_unique<std::string>();
-    auto* photo_data_ptr = photo_data.get();
-    auto on_done = base::BindRepeating(
-        &AmbientPhotoController::OnAllPhotoRawDataAvailable,
-        weak_factory_.GetWeakPtr(), /*from_downloading=*/false);
 
-    task_runner_->PostTaskAndReply(
-        FROM_HERE,
-        base::BindOnce(
-            [](size_t index, std::string* data) {
-              if (!base::ReadFileToString(GetBackupFilePath(index), data)) {
-                LOG(ERROR) << "Unable to read from backup cache.";
-                data->clear();
-              }
-            },
-            backup_cache_index_for_display_, photo_data_ptr),
-        base::BindOnce(&AmbientPhotoController::OnPhotoRawDataAvailable,
-                       weak_factory_.GetWeakPtr(), /*from_downloading=*/false,
-                       /*is_related_image=*/false, std::move(on_done),
-                       /*details=*/std::make_unique<std::string>(),
-                       std::move(photo_data)));
+    DVLOG(3) << "Read from backup cache index: "
+             << backup_cache_index_for_display_;
+    // Try to read a backup image.
+    backup_photo_cache_->ReadFiles(
+        /*cache_index=*/backup_cache_index_for_display_,
+        base::BindOnce(&AmbientPhotoController::OnAllPhotoRawDataAvailable,
+                       weak_factory_.GetWeakPtr(), /*from_downloading=*/false));
 
     backup_cache_index_for_display_++;
     if (backup_cache_index_for_display_ == GetBackupPhotoUrls().size())
@@ -430,82 +354,43 @@
   }
 
   --retries_to_read_from_cache_;
-  std::string file_name = base::NumberToString(cache_index_for_display_);
+  int current_cache_index = cache_index_for_display_;
 
   ++cache_index_for_display_;
   if (cache_index_for_display_ == kMaxNumberOfCachedImages)
     cache_index_for_display_ = 0;
 
-  auto photo_data = std::make_unique<std::string>();
-  auto photo_details = std::make_unique<std::string>();
-  auto* photo_data_ptr = photo_data.get();
-  auto* photo_details_ptr = photo_details.get();
-
-  auto on_done = base::BarrierClosure(
-      /*num_closures=*/2,
+  DVLOG(3) << "Read from cache index: " << current_cache_index;
+  photo_cache_->ReadFiles(
+      current_cache_index,
       base::BindOnce(&AmbientPhotoController::OnAllPhotoRawDataAvailable,
-                     weak_factory_.GetWeakPtr(),
-                     /*from_downloading=*/false));
-
-  task_runner_->PostTaskAndReply(
-      FROM_HERE,
-      base::BindOnce(
-          [](const std::string& file_name, std::string* photo_data,
-             std::string* photo_details) {
-            if (!base::ReadFileToString(
-                    GetCachePath().Append(file_name + kPhotoFileExt),
-                    photo_data)) {
-              photo_data->clear();
-            }
-            if (!base::ReadFileToString(
-                    GetCachePath().Append(file_name + kPhotoDetailsFileExt),
-                    photo_details)) {
-              photo_details->clear();
-            }
-          },
-          file_name, photo_data_ptr, photo_details_ptr),
-      base::BindOnce(&AmbientPhotoController::OnPhotoRawDataAvailable,
-                     weak_factory_.GetWeakPtr(), /*from_downloading=*/false,
-                     /*is_related_image=*/false, on_done,
-                     std::move(photo_details), std::move(photo_data)));
-
-  auto related_photo_data = std::make_unique<std::string>();
-  auto* related_photo_data_ptr = related_photo_data.get();
-  task_runner_->PostTaskAndReply(
-      FROM_HERE,
-      base::BindOnce(
-          [](const std::string& file_name, std::string* related_photo_data) {
-            const base::FilePath& file = GetRelatedFilePath(file_name);
-            if (!base::PathExists(file) ||
-                !base::ReadFileToString(file, related_photo_data)) {
-              related_photo_data->clear();
-            }
-          },
-          file_name, related_photo_data_ptr),
-      base::BindOnce(&AmbientPhotoController::OnPhotoRawDataAvailable,
-                     weak_factory_.GetWeakPtr(), /*from_downloading=*/false,
-                     /*is_related_image=*/true, on_done,
-                     /*details=*/std::make_unique<std::string>(),
-                     std::move(related_photo_data)));
+                     weak_factory_.GetWeakPtr(), /*from_downloading=*/false));
 }
 
-void AmbientPhotoController::OnPhotoRawDataAvailable(
-    bool from_downloading,
+void AmbientPhotoController::OnPhotoRawDataDownloaded(
     bool is_related_image,
     base::RepeatingClosure on_done,
     std::unique_ptr<std::string> details,
     std::unique_ptr<std::string> data) {
-  if (is_related_image) {
-    related_image_data_ = std::move(data);
-  } else {
-    image_data_ = std::move(data);
-    image_details_ = std::move(details);
-  }
+  cache_entry_.details = std::move(details);
+
+  if (is_related_image)
+    cache_entry_.related_image = std::move(data);
+  else
+    cache_entry_.image = std::move(data);
+
   std::move(on_done).Run();
 }
 
-void AmbientPhotoController::OnAllPhotoRawDataAvailable(bool from_downloading) {
-  if (!image_data_ || image_data_->empty()) {
+void AmbientPhotoController::OnAllPhotoRawDataDownloaded() {
+  OnAllPhotoRawDataAvailable(/*from_downloading=*/true,
+                             std::move(cache_entry_));
+}
+
+void AmbientPhotoController::OnAllPhotoRawDataAvailable(
+    bool from_downloading,
+    PhotoCacheEntry cache_entry) {
+  if (!cache_entry.image || cache_entry.image->empty()) {
     if (from_downloading) {
       LOG(ERROR) << "Failed to download image";
       resume_fetch_image_backoff_.InformOfRequest(/*succeeded=*/false);
@@ -514,57 +399,49 @@
     TryReadPhotoRawData();
     return;
   }
-  DVLOG_IF(3, from_downloading)
-      << "Save photo to cache index: " << cache_index_for_store_;
-  const std::string file_name = base::NumberToString(cache_index_for_store_);
-  // If the data is fetched from downloading, write to disk.
-  // Note: WriteFile() could fail. The saved file name may not be continuous.
-  if (from_downloading)
-    ++cache_index_for_store_;
-  if (cache_index_for_store_ == kMaxNumberOfCachedImages)
-    cache_index_for_store_ = 0;
 
-  const int num_callbacks = related_image_data_ ? 2 : 1;
+  if (from_downloading) {
+    // If the data is fetched from downloading, write to disk.
+    // Note: WriteFiles could fail. The saved file name may not be continuous.
+    DVLOG(3) << "Save photo to cache index: " << cache_index_for_store_;
+    auto current_cache_index = cache_index_for_store_;
+    ++cache_index_for_store_;
+    if (cache_index_for_store_ == kMaxNumberOfCachedImages)
+      cache_index_for_store_ = 0;
+
+    auto* image = cache_entry.image.get();
+    auto* details = cache_entry.details.get();
+    auto* related_image = cache_entry.related_image.get();
+
+    photo_cache_->WriteFiles(
+        /*cache_index=*/current_cache_index, image, details, related_image,
+        base::BindOnce(&AmbientPhotoController::OnPhotoRawDataSaved,
+                       weak_factory_.GetWeakPtr(), from_downloading,
+                       std::move(cache_entry)));
+  } else {
+    OnPhotoRawDataSaved(from_downloading, std::move(cache_entry));
+  }
+}
+
+void AmbientPhotoController::OnPhotoRawDataSaved(bool from_downloading,
+                                                 PhotoCacheEntry cache_entry) {
+  bool has_related =
+      cache_entry.related_image && !cache_entry.related_image->empty();
+  const int num_callbacks = has_related ? 2 : 1;
+
   auto on_done = base::BarrierClosure(
       num_callbacks,
       base::BindOnce(&AmbientPhotoController::OnAllPhotoDecoded,
                      weak_factory_.GetWeakPtr(), from_downloading,
-                     /*hash=*/base::SHA1HashString(*image_data_)));
+                     /*hash=*/base::SHA1HashString(*cache_entry.image)));
 
-  base::Optional<std::string> related_image_data;
-  if (related_image_data_)
-    related_image_data = *related_image_data_;
-
-  task_runner_->PostTaskAndReply(
-      FROM_HERE,
-      base::BindOnce(
-          [](const std::string& file_name, bool need_to_save,
-             const std::string& data, const std::string& details,
-             const base::Optional<std::string>& related_data) {
-            if (need_to_save) {
-              WriteFile(GetCachePath().Append(file_name + kPhotoFileExt), data);
-              WriteFile(GetCachePath().Append(file_name + kPhotoDetailsFileExt),
-                        details);
-              const base::FilePath& related_data_file =
-                  GetRelatedFilePath(file_name);
-              if (related_data) {
-                WriteFile(related_data_file, *related_data);
-              } else {
-                if (base::PathExists(related_data_file))
-                  base::DeleteFile(related_data_file);
-              }
-            }
-          },
-          file_name, from_downloading, *image_data_, *image_details_,
-          std::move(related_image_data)),
-      base::BindOnce(&AmbientPhotoController::DecodePhotoRawData,
-                     weak_factory_.GetWeakPtr(), from_downloading,
+  DecodePhotoRawData(from_downloading,
                      /*is_related_image=*/false, on_done,
-                     std::move(image_data_)));
+                     std::move(cache_entry.image));
 
-  if (related_image_data_) {
+  if (has_related) {
     DecodePhotoRawData(from_downloading, /*is_related_image=*/true, on_done,
-                       std::move(related_image_data_));
+                       std::move(cache_entry.related_image));
   }
 }
 
@@ -617,7 +494,8 @@
   PhotoWithDetails detailed_photo;
   detailed_photo.photo = image_;
   detailed_photo.related_photo = related_image_;
-  detailed_photo.details = *image_details_;
+  if (cache_entry_.details)
+    detailed_photo.details = *cache_entry_.details;
   detailed_photo.hash = hash;
 
   ResetImageData();
@@ -678,7 +556,7 @@
 }
 
 void AmbientPhotoController::FetchBackupImagesForTesting() {
-  PrepareFetchBackupImages();
+  FetchBackupImages();
 }
 
 }  // namespace ash
diff --git a/ash/ambient/ambient_photo_controller.h b/ash/ambient/ambient_photo_controller.h
index 39996e7b..81f1abcf 100644
--- a/ash/ambient/ambient_photo_controller.h
+++ b/ash/ambient/ambient_photo_controller.h
@@ -78,6 +78,8 @@
   // Clear cache when Settings changes.
   void ClearCache();
 
+  void InitCache();
+
  private:
   friend class AmbientAshTestBase;
 
@@ -89,13 +91,10 @@
 
   void ScheduleRefreshImage();
 
-  // Create the backup cache directory and start downloading images.
-  void PrepareFetchBackupImages();
-
   // Download backup cache images.
   void FetchBackupImages();
 
-  void OnBackupImageFetched(base::FilePath file_path);
+  void OnBackupImageFetched(bool success);
 
   void GetScreenUpdateInfo();
 
@@ -114,13 +113,17 @@
   // Try to read photo raw data from cache.
   void TryReadPhotoRawData();
 
-  void OnPhotoRawDataAvailable(bool from_downloading,
-                               bool is_related_image,
-                               base::RepeatingClosure on_done,
-                               std::unique_ptr<std::string> details,
-                               std::unique_ptr<std::string> data);
+  void OnPhotoRawDataDownloaded(bool is_related_image,
+                                base::RepeatingClosure on_done,
+                                std::unique_ptr<std::string> details,
+                                std::unique_ptr<std::string> data);
 
-  void OnAllPhotoRawDataAvailable(bool from_downloading);
+  void OnAllPhotoRawDataDownloaded();
+
+  void OnAllPhotoRawDataAvailable(bool from_downloading,
+                                  PhotoCacheEntry cache_entry);
+
+  void OnPhotoRawDataSaved(bool from_downloading, PhotoCacheEntry cache_entry);
 
   void DecodePhotoRawData(bool from_downloading,
                           bool is_related_image,
@@ -152,6 +155,15 @@
     return photo_cache_.get();
   }
 
+  void set_backup_photo_cache_for_testing(
+      std::unique_ptr<AmbientPhotoCache> photo_cache) {
+    backup_photo_cache_ = std::move(photo_cache);
+  }
+
+  AmbientPhotoCache* get_backup_photo_cache_for_testing() {
+    return backup_photo_cache_.get();
+  }
+
   void FetchTopicsForTesting();
 
   void FetchImageForTesting();
@@ -206,13 +218,12 @@
       ambient_backend_model_observer_{this};
 
   std::unique_ptr<AmbientPhotoCache> photo_cache_;
+  std::unique_ptr<AmbientPhotoCache> backup_photo_cache_;
 
   scoped_refptr<base::SequencedTaskRunner> task_runner_;
 
   // Temporary data store when fetching images and details.
-  std::unique_ptr<std::string> image_data_;
-  std::unique_ptr<std::string> related_image_data_;
-  std::unique_ptr<std::string> image_details_;
+  PhotoCacheEntry cache_entry_;
   gfx::ImageSkia image_;
   gfx::ImageSkia related_image_;
 
diff --git a/ash/ambient/ambient_photo_controller_unittest.cc b/ash/ambient/ambient_photo_controller_unittest.cc
index 0f07d63..748588da9 100644
--- a/ash/ambient/ambient_photo_controller_unittest.cc
+++ b/ash/ambient/ambient_photo_controller_unittest.cc
@@ -26,6 +26,7 @@
 #include "base/path_service.h"
 #include "base/run_loop.h"
 #include "base/scoped_observer.h"
+#include "base/stl_util.h"
 #include "base/system/sys_info.h"
 #include "base/test/bind.h"
 #include "base/timer/timer.h"
@@ -49,44 +50,35 @@
 
 class AmbientPhotoControllerTest : public AmbientAshTestBase {
  public:
-  // AmbientAshTestBase:
-  void SetUp() override {
-    AmbientAshTestBase::SetUp();
-    CleanupAmbientDir();
-  }
-  void TearDown() override {
-    AmbientAshTestBase::TearDown();
-    CleanupAmbientDir();
-  }
-
-  void CleanupAmbientDir() { base::DeletePathRecursively(GetRootDir()); }
-
-  std::vector<base::FilePath> GetFilePathsInDir(const base::FilePath& dir) {
-    std::vector<base::FilePath> result;
-    base::FileEnumerator files(
-        dir, /*recursive=*/false,
-        base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES);
-    for (base::FilePath current = files.Next(); !current.empty();
-         current = files.Next()) {
-      result.emplace_back(current);
+  std::vector<int> GetSavedCacheIndices(bool backup = false) {
+    std::vector<int> result;
+    const auto& map = backup ? GetBackupCachedFiles() : GetCachedFiles();
+    for (auto& it : map) {
+      result.push_back(it.first);
     }
     return result;
   }
 
-  base::FilePath GetRootDir() {
-    base::FilePath home_dir;
-    base::PathService::Get(base::DIR_HOME, &home_dir);
-    return home_dir.Append(FILE_PATH_LITERAL(kAmbientModeDirectoryName));
+  const PhotoCacheEntry* GetCacheEntryAtIndex(int cache_index,
+                                              bool backup = false) {
+    const auto& files = backup ? GetBackupCachedFiles() : GetCachedFiles();
+    auto it = files.find(cache_index);
+    if (it == files.end())
+      return nullptr;
+    else
+      return &(it->second);
   }
 
-  base::FilePath GetCacheDir() {
-    return GetRootDir().Append(
-        FILE_PATH_LITERAL(kAmbientModeCacheDirectoryName));
-  }
-
-  base::FilePath GetBackupCacheDir() {
-    return GetRootDir().Append(
-        FILE_PATH_LITERAL(kAmbientModeBackupCacheDirectoryName));
+  void WriteCacheDataBlocking(int cache_index,
+                              const std::string* image = nullptr,
+                              const std::string* details = nullptr,
+                              const std::string* related_image = nullptr) {
+    base::RunLoop loop;
+    photo_cache()->WriteFiles(/*cache_index=*/cache_index, /*image=*/image,
+                              /*details=*/details,
+                              /*related_image=*/related_image,
+                              loop.QuitClosure());
+    loop.Run();
   }
 };
 
@@ -158,38 +150,27 @@
 
 // Test that image is saved.
 TEST_F(AmbientPhotoControllerTest, ShouldSaveImagesOnDisk) {
-  base::FilePath ambient_image_path = GetCacheDir();
-
   // Start to refresh images. It will download two images immediately and write
   // them in |ambient_image_path|. It will also download one more image after
   // fast forward. It will also download the related images and not cache them.
   photo_controller()->StartScreenUpdate();
   FastForwardToNextImage();
 
-  // Count files and directories in ambient_image_path. There should be six
-  // files that were just created to save image files for this ambient mode
-  // session.
-  EXPECT_TRUE(base::PathExists(ambient_image_path));
-  auto file_paths = GetFilePathsInDir(ambient_image_path);
-  // Three image files, three related image files, and three attribution files.
-  EXPECT_EQ(file_paths.size(), 9u);
-  for (auto& path : file_paths) {
-    // No sub directories.
-    EXPECT_FALSE(base::DirectoryExists(path));
-  }
+  // Count number of writes to cache. There should be three cache writes during
+  // this ambient mode session.
+  auto file_paths = GetSavedCacheIndices();
+  EXPECT_EQ(file_paths.size(), 3u);
 }
 
 // Test that image is save and will not be deleted when stopping ambient mode.
 TEST_F(AmbientPhotoControllerTest, ShouldNotDeleteImagesOnDisk) {
-  base::FilePath ambient_image_path = GetCacheDir();
-
   // Start to refresh images. It will download two images immediately and write
   // them in |ambient_image_path|. It will also download one more image after
   // fast forward. It will also download the related images and not cache them.
   photo_controller()->StartScreenUpdate();
   FastForwardToNextImage();
 
-  EXPECT_TRUE(base::PathExists(ambient_image_path));
+  EXPECT_EQ(GetSavedCacheIndices().size(), 3u);
 
   auto image = photo_controller()->ambient_backend_model()->GetNextImage();
   EXPECT_FALSE(image.IsNull());
@@ -198,29 +179,14 @@
   photo_controller()->StopScreenUpdate();
   FastForwardToNextImage();
 
-  EXPECT_TRUE(base::PathExists(ambient_image_path));
-  EXPECT_FALSE(base::IsDirectoryEmpty(ambient_image_path));
+  EXPECT_EQ(GetSavedCacheIndices().size(), 3u);
 
   image = photo_controller()->ambient_backend_model()->GetNextImage();
   EXPECT_TRUE(image.IsNull());
-
-  // Count files and directories in ambient_image_path. There should be six
-  // files that were just created to save image files for the prior ambient mode
-  // session.
-  EXPECT_TRUE(base::PathExists(ambient_image_path));
-  auto file_paths = GetFilePathsInDir(ambient_image_path);
-  // Three image files, three related image files, and three attribution files.
-  EXPECT_EQ(file_paths.size(), 9u);
-  for (auto& path : file_paths) {
-    // No sub directories.
-    EXPECT_FALSE(base::DirectoryExists(path));
-  }
 }
 
 // Test that image is read from disk when no more topics.
 TEST_F(AmbientPhotoControllerTest, ShouldReadCacheWhenNoMoreTopics) {
-  base::FilePath ambient_image_path = GetCacheDir();
-
   FetchImage();
   FastForwardToNextImage();
   // Topics is empty. Will read from cache, which is empty.
@@ -228,9 +194,8 @@
   EXPECT_TRUE(image.IsNull());
 
   // Save a file to check if it gets read for display.
-  auto cached_image = ambient_image_path.Append("0.img");
-  base::CreateDirectory(ambient_image_path);
-  base::WriteFile(cached_image, "cached image");
+  std::string data("cached image");
+  WriteCacheDataBlocking(/*cache_index=*/0, &data);
 
   // Reset variables in photo controller.
   photo_controller()->StopScreenUpdate();
@@ -238,27 +203,21 @@
   FastForwardToNextImage();
   image = photo_controller()->ambient_backend_model()->GetCurrentImage();
   EXPECT_FALSE(image.IsNull());
-
-  // Clean up.
-  base::DeletePathRecursively(ambient_image_path);
 }
 
 // Test that will try 100 times to read image from disk when no more topics.
 TEST_F(AmbientPhotoControllerTest,
        ShouldTry100TimesToReadCacheWhenNoMoreTopics) {
-  base::FilePath ambient_image_path = GetCacheDir();
-
   FetchImage();
   FastForwardToNextImage();
   // Topics is empty. Will read from cache, which is empty.
   auto image = photo_controller()->ambient_backend_model()->GetCurrentImage();
   EXPECT_TRUE(image.IsNull());
 
-  // The initial file name to be read is 0. Save a file with 99.img to check if
-  // it gets read for display.
-  auto cached_image = ambient_image_path.Append("99.img");
-  base::CreateDirectory(ambient_image_path);
-  base::WriteFile(cached_image, "cached image");
+  // The initial file name to be read is 0. Save a file with 99.img to check
+  // if it gets read for display.
+  std::string data("cached image");
+  WriteCacheDataBlocking(/*cache_index=*/99, &data);
 
   // Reset variables in photo controller.
   photo_controller()->StopScreenUpdate();
@@ -270,8 +229,6 @@
 
 // Test that image is read from disk when image downloading failed.
 TEST_F(AmbientPhotoControllerTest, ShouldReadCacheWhenImageDownloadingFailed) {
-  base::FilePath ambient_image_path = GetCacheDir();
-
   SetDownloadPhotoData("");
   FetchTopics();
   // Forward a little bit time. FetchTopics() will succeed. Downloading should
@@ -281,9 +238,8 @@
   EXPECT_TRUE(image.IsNull());
 
   // Save a file to check if it gets read for display.
-  auto cached_image = ambient_image_path.Append("0.img");
-  base::CreateDirectory(ambient_image_path);
-  base::WriteFile(cached_image, "cached image");
+  std::string data("cached image");
+  WriteCacheDataBlocking(/*cache_index=*/0, &data);
 
   // Reset variables in photo controller.
   photo_controller()->StopScreenUpdate();
@@ -297,8 +253,6 @@
 
 // Test that image is read from disk when image decoding failed.
 TEST_F(AmbientPhotoControllerTest, ShouldReadCacheWhenImageDecodingFailed) {
-  base::FilePath ambient_image_path = GetCacheDir();
-
   SetDecodePhotoImage(gfx::ImageSkia());
   FetchTopics();
   // Forward a little bit time. FetchTopics() will succeed.
@@ -311,8 +265,6 @@
 
 // Test that image will refresh when have more topics.
 TEST_F(AmbientPhotoControllerTest, ShouldResumWhenHaveMoreTopics) {
-  base::FilePath ambient_image_path = GetCacheDir();
-
   FetchImage();
   FastForwardToNextImage();
   // Topics is empty. Will read from cache, which is empty.
@@ -327,37 +279,31 @@
 }
 
 TEST_F(AmbientPhotoControllerTest, ShouldDownloadBackupImagesWhenScheduled) {
-  base::FilePath backup_image_path = GetBackupCacheDir();
-
   std::string expected_data = "backup data";
-  SetDownloadPhotoData(expected_data);
+  SetBackupDownloadPhotoData(expected_data);
 
   photo_controller()->ScheduleFetchBackupImages();
 
   EXPECT_TRUE(
       photo_controller()->backup_photo_refresh_timer_for_testing().IsRunning());
 
-  // TImer is running but download has not started yet.
-  EXPECT_FALSE(base::DirectoryExists(GetBackupCacheDir()));
+  // Timer is running but download has not started yet.
+  EXPECT_TRUE(GetSavedCacheIndices(/*backup=*/true).empty());
   task_environment()->FastForwardBy(kBackupPhotoRefreshDelay);
 
   // Timer should have stopped.
   EXPECT_FALSE(
       photo_controller()->backup_photo_refresh_timer_for_testing().IsRunning());
 
-  // Download has triggered and backup cache directory is created.
-  EXPECT_TRUE(base::DirectoryExists(backup_image_path));
-
-  // Should be two files in backup cache directory.
-  auto paths = GetFilePathsInDir(backup_image_path);
-  std::sort(paths.begin(), paths.end());
-  EXPECT_EQ(paths.size(), 2u);
-  EXPECT_EQ(paths[0].BaseName().value(), "0.img");
-  EXPECT_EQ(paths[1].BaseName().value(), "1.img");
-  for (const auto& path : paths) {
-    std::string data;
-    base::ReadFileToString(path, &data);
-    EXPECT_EQ(data, expected_data);
+  // Should have been two cache writes to backup data.
+  const auto& backup_data = GetBackupCachedFiles();
+  EXPECT_EQ(backup_data.size(), 2u);
+  EXPECT_TRUE(base::Contains(backup_data, 0));
+  EXPECT_TRUE(base::Contains(backup_data, 1));
+  for (const auto& i : backup_data) {
+    EXPECT_EQ(*(i.second.image), expected_data);
+    EXPECT_FALSE(i.second.details);
+    EXPECT_FALSE(i.second.related_image);
   }
 }
 
@@ -371,11 +317,7 @@
   ClearDownloadPhotoData();
   task_environment()->FastForwardBy(kBackupPhotoRefreshDelay);
 
-  // Directory should have been created, but with no files in it.
-  EXPECT_TRUE(base::DirectoryExists(GetBackupCacheDir()));
-
-  auto paths = GetFilePathsInDir(GetBackupCacheDir());
-  EXPECT_EQ(paths.size(), 0u);
+  EXPECT_TRUE(GetBackupCachedFiles().empty());
 
   // Timer should have restarted.
   EXPECT_TRUE(
@@ -389,7 +331,7 @@
   EXPECT_TRUE(
       photo_controller()->backup_photo_refresh_timer_for_testing().IsRunning());
 
-  SetDownloadPhotoData("image data");
+  SetBackupDownloadPhotoData("image data");
 
   photo_controller()->StartScreenUpdate();
 
@@ -399,19 +341,16 @@
 
   task_environment()->RunUntilIdle();
 
-  // Download has triggered and backup cache directory is created.
-  EXPECT_TRUE(base::DirectoryExists(GetBackupCacheDir()));
-
-  // Should be two files in backup cache directory.
-  auto paths = GetFilePathsInDir(GetBackupCacheDir());
-  std::sort(paths.begin(), paths.end());
-  EXPECT_EQ(paths.size(), 2u);
-  EXPECT_EQ(paths[0].BaseName().value(), "0.img");
-  EXPECT_EQ(paths[1].BaseName().value(), "1.img");
-  for (const auto& path : paths) {
-    std::string data;
-    base::ReadFileToString(path, &data);
-    EXPECT_EQ(data, "image data");
+  // Download has triggered and backup cache directory is created. Should be
+  // two cache writes to backup cache.
+  const auto& backup_data = GetBackupCachedFiles();
+  EXPECT_EQ(backup_data.size(), 2u);
+  EXPECT_TRUE(base::Contains(backup_data, 0));
+  EXPECT_TRUE(base::Contains(backup_data, 1));
+  for (const auto& i : backup_data) {
+    EXPECT_EQ(*(i.second.image), "image data");
+    EXPECT_FALSE(i.second.details);
+    EXPECT_FALSE(i.second.related_image);
   }
 }
 
diff --git a/ash/ambient/test/ambient_ash_test_base.cc b/ash/ambient/test/ambient_ash_test_base.cc
index 458182a..ed10e79 100644
--- a/ash/ambient/test/ambient_ash_test_base.cc
+++ b/ash/ambient/test/ambient_ash_test_base.cc
@@ -4,12 +4,14 @@
 
 #include "ash/ambient/test/ambient_ash_test_base.h"
 
+#include <map>
 #include <memory>
 #include <utility>
 #include <vector>
 
 #include "ash/ambient/ambient_access_token_controller.h"
 #include "ash/ambient/ambient_constants.h"
+#include "ash/ambient/ambient_photo_cache.h"
 #include "ash/ambient/ambient_photo_controller.h"
 #include "ash/ambient/test/ambient_ash_test_helper.h"
 #include "ash/ambient/ui/ambient_background_image_view.h"
@@ -24,9 +26,7 @@
 #include "ash/session/session_controller_impl.h"
 #include "ash/shell.h"
 #include "base/callback.h"
-#include "base/files/file_util.h"
 #include "base/memory/ptr_util.h"
-#include "base/notreached.h"
 #include "base/run_loop.h"
 #include "base/sequenced_task_runner.h"
 #include "base/threading/scoped_blocking_call.h"
@@ -35,7 +35,6 @@
 #include "chromeos/dbus/power/fake_power_manager_client.h"
 #include "chromeos/dbus/power/power_manager_client.h"
 #include "chromeos/dbus/power_manager/idle.pb.h"
-#include "ui/display/screen.h"
 #include "ui/gfx/image/image_skia.h"
 #include "ui/gfx/image/image_unittest_util.h"
 #include "ui/views/controls/label.h"
@@ -66,26 +65,30 @@
         FROM_HERE, base::BindOnce(std::move(callback), std::move(data)),
         base::TimeDelta::FromMilliseconds(1));
   }
+
   void DownloadPhotoToFile(const std::string& url,
-                           base::OnceCallback<void(base::FilePath)> callback,
-                           const base::FilePath& file_path) override {
+                           int cache_index,
+                           bool is_related,
+                           base::OnceCallback<void(bool)> callback) override {
     if (!download_data_) {
       base::SequencedTaskRunnerHandle::Get()->PostTask(
-          FROM_HERE, base::BindOnce(std::move(callback), base::FilePath()));
+          FROM_HERE, base::BindOnce(std::move(callback), /*success=*/false));
       return;
     }
 
-    if (!WriteFile(file_path, *download_data_)) {
-      LOG(WARNING) << "error writing file to file_path: " << file_path;
-
-      base::SequencedTaskRunnerHandle::Get()->PostTask(
-          FROM_HERE, base::BindOnce(std::move(callback), base::FilePath()));
-      return;
-    }
+    files_.insert(std::pair<int, PhotoCacheEntry>(
+        cache_index,
+        PhotoCacheEntry(
+            is_related ? nullptr
+                       : std::make_unique<std::string>(*download_data_),
+            /*details=*/nullptr,
+            is_related ? std::make_unique<std::string>(*download_data_)
+                       : nullptr)));
 
     base::SequencedTaskRunnerHandle::Get()->PostTask(
-        FROM_HERE, base::BindOnce(std::move(callback), file_path));
+        FROM_HERE, base::BindOnce(std::move(callback), /*success=*/true));
   }
+
   void DecodePhoto(
       std::unique_ptr<std::string> data,
       base::OnceCallback<void(const gfx::ImageSkia&)> callback) override {
@@ -101,6 +104,47 @@
         FROM_HERE, base::BindOnce(std::move(callback), image));
   }
 
+  void WriteFiles(int cache_index,
+                  const std::string* const image,
+                  const std::string* const details,
+                  const std::string* const related_image,
+                  base::OnceClosure callback) override {
+    files_.insert(std::pair<int, PhotoCacheEntry>(
+        cache_index,
+        PhotoCacheEntry(
+            image ? std::make_unique<std::string>(*image) : nullptr,
+            details ? std::make_unique<std::string>(*details) : nullptr,
+            related_image ? std::make_unique<std::string>(*related_image)
+                          : nullptr)));
+    std::move(callback).Run();
+  }
+
+  void ReadFiles(int cache_index,
+                 base::OnceCallback<void(PhotoCacheEntry)> callback) override {
+    auto it = files_.find(cache_index);
+    if (it == files_.end()) {
+      std::move(callback).Run(PhotoCacheEntry());
+      return;
+    }
+
+    std::move(callback).Run(PhotoCacheEntry(
+        it->second.image ? std::make_unique<std::string>(*(it->second.image))
+                         : nullptr,
+        it->second.details
+            ? std::make_unique<std::string>(*(it->second.details))
+            : nullptr,
+        it->second.related_image
+            ? std::make_unique<std::string>(*(it->second.related_image))
+            : nullptr));
+  }
+  void Clear() override {
+    download_count_ = 0;
+    download_data_.reset();
+    decoded_size_ = gfx::Size(10, 20);
+    decoded_image_.reset();
+    files_.clear();
+  }
+
   void SetDownloadData(std::unique_ptr<std::string> download_data) {
     download_data_ = std::move(download_data);
   }
@@ -112,6 +156,8 @@
 
   void SetDecodedPhoto(const gfx::ImageSkia& image) { decoded_image_ = image; }
 
+  const std::map<int, PhotoCacheEntry>& get_files() { return files_; }
+
  private:
   int download_count_ = 0;
 
@@ -122,6 +168,8 @@
   gfx::Size decoded_size_{10, 20};
   // If set, will replay this image.
   base::Optional<gfx::ImageSkia> decoded_image_;
+
+  std::map<int, PhotoCacheEntry> files_;
 };
 
 AmbientAshTestBase::AmbientAshTestBase()
@@ -139,6 +187,8 @@
       std::make_unique<FakeAmbientBackendControllerImpl>());
   photo_controller()->set_photo_cache_for_testing(
       std::make_unique<TestAmbientPhotoCacheImpl>());
+  photo_controller()->set_backup_photo_cache_for_testing(
+      std::make_unique<TestAmbientPhotoCacheImpl>());
   token_controller()->SetTokenUsageBufferForTesting(
       base::TimeDelta::FromSeconds(30));
   SetAmbientModeEnabled(true);
@@ -380,6 +430,21 @@
   return token_controller()->GetTimeUntilReleaseForTesting();
 }
 
+const std::map<int, PhotoCacheEntry>& AmbientAshTestBase::GetCachedFiles() {
+  auto* photo_cache = static_cast<TestAmbientPhotoCacheImpl*>(
+      photo_controller()->get_photo_cache_for_testing());
+
+  return photo_cache->get_files();
+}
+
+const std::map<int, PhotoCacheEntry>&
+AmbientAshTestBase::GetBackupCachedFiles() {
+  auto* photo_cache = static_cast<TestAmbientPhotoCacheImpl*>(
+      photo_controller()->get_backup_photo_cache_for_testing());
+
+  return photo_cache->get_files();
+}
+
 AmbientController* AmbientAshTestBase::ambient_controller() {
   return Shell::Get()->ambient_controller();
 }
@@ -388,6 +453,10 @@
   return ambient_controller()->ambient_photo_controller();
 }
 
+AmbientPhotoCache* AmbientAshTestBase::photo_cache() {
+  return photo_controller()->get_photo_cache_for_testing();
+}
+
 std::vector<AmbientContainerView*> AmbientAshTestBase::GetContainerViews() {
   std::vector<AmbientContainerView*> result;
   for (auto* ctrl : RootWindowController::root_window_controllers()) {
@@ -449,6 +518,20 @@
   photo_cache->SetDownloadData(nullptr);
 }
 
+void AmbientAshTestBase::SetBackupDownloadPhotoData(std::string data) {
+  auto* backup_cache = static_cast<TestAmbientPhotoCacheImpl*>(
+      photo_controller()->get_backup_photo_cache_for_testing());
+
+  backup_cache->SetDownloadData(std::make_unique<std::string>(std::move(data)));
+}
+
+void AmbientAshTestBase::ClearBackupDownloadPhotoData() {
+  auto* backup_cache = static_cast<TestAmbientPhotoCacheImpl*>(
+      photo_controller()->get_backup_photo_cache_for_testing());
+
+  backup_cache->SetDownloadData(nullptr);
+}
+
 void AmbientAshTestBase::SetDecodePhotoImage(const gfx::ImageSkia& image) {
   auto* photo_cache = static_cast<TestAmbientPhotoCacheImpl*>(
       photo_controller()->get_photo_cache_for_testing());
diff --git a/ash/ambient/test/ambient_ash_test_base.h b/ash/ambient/test/ambient_ash_test_base.h
index f4d69ff3..0828bcc 100644
--- a/ash/ambient/test/ambient_ash_test_base.h
+++ b/ash/ambient/test/ambient_ash_test_base.h
@@ -137,10 +137,15 @@
   // Returns the media string view for the default display.
   MediaStringView* GetMediaStringView();
 
+  const std::map<int, PhotoCacheEntry>& GetCachedFiles();
+  const std::map<int, PhotoCacheEntry>& GetBackupCachedFiles();
+
   AmbientController* ambient_controller();
 
   AmbientPhotoController* photo_controller();
 
+  AmbientPhotoCache* photo_cache();
+
   // Returns the top-level views which contains all the ambient components.
   std::vector<AmbientContainerView*> GetContainerViews();
   // Returns the top level ambient container view for the primary root window.
@@ -160,6 +165,10 @@
 
   void ClearDownloadPhotoData();
 
+  void SetBackupDownloadPhotoData(std::string data);
+
+  void ClearBackupDownloadPhotoData();
+
   void SetDecodePhotoImage(const gfx::ImageSkia& image);
 
  private:
diff --git a/ash/ash_strings.grd b/ash/ash_strings.grd
index d6a5fcab..e8ef0b8f 100644
--- a/ash/ash_strings.grd
+++ b/ash/ash_strings.grd
@@ -554,6 +554,12 @@
       <message name="IDS_DIALOG_MESSAGE_SLOW_BOOT" desc="The body of the Slow Boot Shutdown Confirmation Dialog.">
         Your screen will go blank for longer than usual (up to a minute) during this update. Please don't press the power button while the update is in progress.
       </message>
+      <message name="IDS_UPDATE_NOTIFICATION_TITLE_LACROS" desc="The title of the notification to notify that the user should restart to get an update for the experimental 'Lacros' browser.">
+        Lacros update available
+      </message>
+      <message name="IDS_UPDATE_NOTIFICATION_MESSAGE_LACROS" desc="The body of the notification to notify that the user should restart to get an update for the experimental 'Lacros' browser.">
+        Device restart is required to apply the update.
+      </message>
 
       <message name="IDS_ASH_FLOATING_ACCESSIBILITY_DETAILED_MENU" desc="The name of the quick settings view of floating accessibility menu.">
         Accessibility settings
@@ -3125,6 +3131,14 @@
         Window <ph name="WINDOW_TITLE">$1<ex>Files</ex></ph> moved from Desk <ph name="ACTIVE_DESK">$2</ph> to Desk <ph name="TARGET_DESK">$3</ph>
       </message>
 
+      <!-- Alt Tab -->
+      <message name="IDS_ASH_ALT_TAB_ALL_DESKS_MODE" desc="Alt-tab shows windows from all desks.">
+        All desks
+      </message>
+      <message name="IDS_ASH_ALT_TAB_CURRENT_DESK_MODE" desc="Alt-tab shows only windows from the current desk.">
+        Current desk
+      </message>
+
       <!-- Back gesture -->
       <message name="IDS_ASH_BACK_GESTURE_CONTEXTUAL_NUDGE" desc="Information shown in the suggestion label for the contextual nudge of the back gesture in tablet mode">
         Swipe from the left to go back
diff --git a/ash/ash_strings_grd/IDS_ASH_ALT_TAB_ALL_DESKS_MODE.png.sha1 b/ash/ash_strings_grd/IDS_ASH_ALT_TAB_ALL_DESKS_MODE.png.sha1
new file mode 100644
index 0000000..106a80a
--- /dev/null
+++ b/ash/ash_strings_grd/IDS_ASH_ALT_TAB_ALL_DESKS_MODE.png.sha1
@@ -0,0 +1 @@
+a497fea9619c636ff78f2f5bba3724d5e1ad84d4
\ No newline at end of file
diff --git a/ash/ash_strings_grd/IDS_ASH_ALT_TAB_CURRENT_DESK_MODE.png.sha1 b/ash/ash_strings_grd/IDS_ASH_ALT_TAB_CURRENT_DESK_MODE.png.sha1
new file mode 100644
index 0000000..b29053b
--- /dev/null
+++ b/ash/ash_strings_grd/IDS_ASH_ALT_TAB_CURRENT_DESK_MODE.png.sha1
@@ -0,0 +1 @@
+df4b84a84d9cad8a10afc284876e6a4d279435e0
\ No newline at end of file
diff --git a/ash/ash_strings_grd/IDS_UPDATE_NOTIFICATION_MESSAGE_LACROS.png.sha1 b/ash/ash_strings_grd/IDS_UPDATE_NOTIFICATION_MESSAGE_LACROS.png.sha1
new file mode 100644
index 0000000..0dc2187
--- /dev/null
+++ b/ash/ash_strings_grd/IDS_UPDATE_NOTIFICATION_MESSAGE_LACROS.png.sha1
@@ -0,0 +1 @@
+3a69208c8b754548e419e8c106a0c8df33715c49
\ No newline at end of file
diff --git a/ash/ash_strings_grd/IDS_UPDATE_NOTIFICATION_TITLE_LACROS.png.sha1 b/ash/ash_strings_grd/IDS_UPDATE_NOTIFICATION_TITLE_LACROS.png.sha1
new file mode 100644
index 0000000..0dc2187
--- /dev/null
+++ b/ash/ash_strings_grd/IDS_UPDATE_NOTIFICATION_TITLE_LACROS.png.sha1
@@ -0,0 +1 @@
+3a69208c8b754548e419e8c106a0c8df33715c49
\ No newline at end of file
diff --git a/ash/capture_mode/capture_mode_controller.cc b/ash/capture_mode/capture_mode_controller.cc
index e0ba962..ad92055 100644
--- a/ash/capture_mode/capture_mode_controller.cc
+++ b/ash/capture_mode/capture_mode_controller.cc
@@ -16,6 +16,7 @@
 #include "ash/public/cpp/holding_space/holding_space_controller.h"
 #include "ash/public/cpp/notification_utils.h"
 #include "ash/resources/vector_icons/vector_icons.h"
+#include "ash/root_window_controller.h"
 #include "ash/session/session_controller_impl.h"
 #include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
@@ -68,8 +69,8 @@
 // The format strings of the file names of captured images.
 // TODO(afakhry): Discuss with UX localizing "Screenshot" and "Screen
 // recording".
-constexpr char kScreenshotFileNameFmtStr[] = "Screenshot %s %s.png";
-constexpr char kVideoFileNameFmtStr[] = "Screen recording %s %s.webm";
+constexpr char kScreenshotFileNameFmtStr[] = "Screenshot %s %s";
+constexpr char kVideoFileNameFmtStr[] = "Screen recording %s %s";
 constexpr char kDateFmtStr[] = "%d-%02d-%02d";
 constexpr char k24HourTimeFmtStr[] = "%02d.%02d.%02d";
 constexpr char kAmPmTimeFmtStr[] = "%d.%02d.%02d";
@@ -347,6 +348,29 @@
     last_capture_region_update_time_ = base::TimeTicks::Now();
 }
 
+void CaptureModeController::CaptureScreenshotsOfAllDisplays() {
+  if (delegate_->IsCaptureModeInitRestricted()) {
+    ShowDisabledNotification();
+    return;
+  }
+  // Get a vector of RootWindowControllers with primary root window at first.
+  const std::vector<RootWindowController*> controllers =
+      RootWindowController::root_window_controllers();
+  // Capture screenshot for each individual display.
+  int display_index = 1;
+  for (RootWindowController* controller : controllers) {
+    // TODO(shidi): Check with UX what notification should show if
+    // some (but not all) of the displays have restricted content and
+    // whether we should localize the display name.
+    const CaptureParams capture_params{controller->GetRootWindow(),
+                                       controller->GetRootWindow()->bounds()};
+    CaptureImage(capture_params, controllers.size() == 1
+                                     ? BuildImagePath()
+                                     : BuildImagePathForDisplay(display_index));
+    ++display_index;
+  }
+}
+
 void CaptureModeController::PerformCapture() {
   DCHECK(IsActive());
   const base::Optional<CaptureParams> capture_params = GetCaptureParams();
@@ -362,8 +386,10 @@
   DCHECK(capture_mode_session_);
   capture_mode_session_->ReportSessionHistograms();
 
-  if (type_ == CaptureModeType::kImage)
-    CaptureImage(*capture_params);
+  if (type_ == CaptureModeType::kImage) {
+    CaptureImage(*capture_params, BuildImagePath());
+  }
+
   else
     CaptureVideo(*capture_params);
 }
@@ -608,20 +634,21 @@
   video_recording_watcher_.reset();
 }
 
-void CaptureModeController::CaptureImage(const CaptureParams& capture_params) {
+void CaptureModeController::CaptureImage(const CaptureParams& capture_params,
+                                         const base::FilePath& path) {
   DCHECK_EQ(CaptureModeType::kImage, type_);
   DCHECK(IsCaptureAllowed(capture_params));
 
   // Stop the capture session now, so as not to take a screenshot of the capture
   // bar.
-  Stop();
+  if (IsActive())
+    Stop();
 
   DCHECK(!capture_params.bounds.IsEmpty());
-
   ui::GrabWindowSnapshotAsyncPNG(
       capture_params.window, capture_params.bounds,
       base::BindOnce(&CaptureModeController::OnImageCaptured,
-                     weak_ptr_factory_.GetWeakPtr(), base::Time::Now()));
+                     weak_ptr_factory_.GetWeakPtr(), path));
 
   ++num_screenshots_taken_in_last_day_;
   ++num_screenshots_taken_in_last_week_;
@@ -645,7 +672,7 @@
 }
 
 void CaptureModeController::OnImageCaptured(
-    base::Time timestamp,
+    const base::FilePath& path,
     scoped_refptr<base::RefCountedMemory> png_bytes) {
   if (!png_bytes || !png_bytes->size()) {
     LOG(ERROR) << "Failed to capture image.";
@@ -653,7 +680,6 @@
     return;
   }
 
-  const base::FilePath path = BuildImagePath(timestamp);
   blocking_task_runner_->PostTaskAndReplyWithResult(
       FROM_HERE, base::BindOnce(&SaveFile, png_bytes, path),
       base::BindOnce(&CaptureModeController::OnImageFileSaved,
@@ -677,8 +703,11 @@
   CopyImageToClipboard(image);
   ShowPreviewNotification(path, image, CaptureModeType::kImage);
 
-  if (features::IsTemporaryHoldingSpaceEnabled())
-    HoldingSpaceController::Get()->client()->AddScreenshot(path);
+  if (features::IsTemporaryHoldingSpaceEnabled()) {
+    HoldingSpaceClient* client = HoldingSpaceController::Get()->client();
+    if (client)  // May be `nullptr` in tests.
+      client->AddScreenshot(path);
+  }
 }
 
 void CaptureModeController::OnVideoFileStatus(bool success) {
@@ -703,8 +732,9 @@
         (base::TimeTicks::Now() - recording_start_time_).InSeconds());
 
     if (features::IsTemporaryHoldingSpaceEnabled()) {
-      HoldingSpaceController::Get()->client()->AddScreenRecording(
-          current_video_file_path_);
+      HoldingSpaceClient* client = HoldingSpaceController::Get()->client();
+      if (client)  // May be `nullptr` in tests.
+        client->AddScreenRecording(current_video_file_path_);
     }
   }
 
@@ -757,6 +787,7 @@
   if (!button_index.has_value()) {
     // Show the item in the folder.
     delegate_->ShowScreenCaptureItemInFolder(screen_capture_path);
+    RecordScreenshotNotificationQuickAction(CaptureQuickAction::kFiles);
   } else {
     const int button_index_value = button_index.value();
     if (type == CaptureModeType::kVideo) {
@@ -768,9 +799,12 @@
       switch (button_index_value) {
         case ScreenshotNotificationButtonIndex::BUTTON_EDIT:
           delegate_->OpenScreenshotInImageEditor(screen_capture_path);
+          RecordScreenshotNotificationQuickAction(
+              CaptureQuickAction::kBacklight);
           break;
         case ScreenshotNotificationButtonIndex::BUTTON_DELETE:
           DeleteFileAsync(blocking_task_runner_, screen_capture_path);
+          RecordScreenshotNotificationQuickAction(CaptureQuickAction::kDelete);
           break;
         default:
           NOTREACHED();
@@ -787,18 +821,29 @@
       kScreenCaptureNotificationId, /*by_user=*/false);
 }
 
-base::FilePath CaptureModeController::BuildImagePath(
-    base::Time timestamp) const {
-  return BuildPath(kScreenshotFileNameFmtStr, timestamp);
+base::FilePath CaptureModeController::BuildImagePath() const {
+  return BuildPathNoExtension(kScreenshotFileNameFmtStr, base::Time::Now())
+      .AddExtension("png");
 }
 
-base::FilePath CaptureModeController::BuildVideoPath(
-    base::Time timestamp) const {
-  return BuildPath(kVideoFileNameFmtStr, timestamp);
+base::FilePath CaptureModeController::BuildVideoPath() const {
+  return BuildPathNoExtension(kVideoFileNameFmtStr, base::Time::Now())
+      .AddExtension("webm");
 }
 
-base::FilePath CaptureModeController::BuildPath(const char* const format_string,
-                                                base::Time timestamp) const {
+base::FilePath CaptureModeController::BuildImagePathForDisplay(
+    int display_index) const {
+  auto path_str =
+      BuildPathNoExtension(kScreenshotFileNameFmtStr, base::Time::Now())
+          .value();
+  auto full_path = base::StringPrintf("%s - Display %d.png", path_str.c_str(),
+                                      display_index);
+  return base::FilePath(full_path);
+}
+
+base::FilePath CaptureModeController::BuildPathNoExtension(
+    const char* const format_string,
+    base::Time timestamp) const {
   const base::FilePath path = delegate_->GetActiveUserDownloadsDir();
   base::Time::Exploded exploded_time;
   timestamp.LocalExplode(&exploded_time);
@@ -861,7 +906,7 @@
 
   DCHECK(current_video_file_path_.empty());
   recording_start_time_ = base::TimeTicks::Now();
-  current_video_file_path_ = BuildVideoPath(base::Time::Now());
+  current_video_file_path_ = BuildVideoPath();
   video_file_handler_ = VideoFileHandler::Create(
       blocking_task_runner_, current_video_file_path_,
       kVideoBufferCapacityBytes, kLowDiskSpaceThresholdInBytes,
diff --git a/ash/capture_mode/capture_mode_controller.h b/ash/capture_mode/capture_mode_controller.h
index 84cccffe..80f84c9 100644
--- a/ash/capture_mode/capture_mode_controller.h
+++ b/ash/capture_mode/capture_mode_controller.h
@@ -84,6 +84,11 @@
   // update |last_capture_region_update_time_|.
   void SetUserCaptureRegion(const gfx::Rect& region, bool by_user);
 
+  // Full screen capture for each available display if no restricted
+  // content exists on that display, each capture is saved as an individual
+  // file. Note: this won't start a capture mode session.
+  void CaptureScreenshotsOfAllDisplays();
+
   // Called only while a capture session is in progress to perform the actual
   // capture depending on the current selected |source_| and |type_|, and ends
   // the capture session.
@@ -151,14 +156,15 @@
   // the capture session is still active when called, so they can retrieve the
   // capture parameters they need. They will end the sessions themselves.
   // They should never be called if IsCaptureAllowed() returns false.
-  void CaptureImage(const CaptureParams& capture_params);
+  void CaptureImage(const CaptureParams& capture_params,
+                    const base::FilePath& path);
   void CaptureVideo(const CaptureParams& capture_params);
 
   // Called back when an image has been captured to trigger an attempt to save
   // the image as a file. |timestamp| is the time at which the capture was
-  // triggered, and |png_bytes| is the buffer containing the captured image in a
+  // triggered, |png_bytes| is the buffer containing the captured image in a
   // PNG format.
-  void OnImageCaptured(base::Time timestamp,
+  void OnImageCaptured(const base::FilePath& path,
                        scoped_refptr<base::RefCountedMemory> png_bytes);
 
   // Called back when an attempt to save the image file has been completed, with
@@ -190,13 +196,17 @@
                                  base::Optional<int> button_index);
 
   // Builds a path for a file of an image screenshot, or a video screen
-  // recording, which were taken at |timestamp|.
-  base::FilePath BuildImagePath(base::Time timestamp) const;
-  base::FilePath BuildVideoPath(base::Time timestamp) const;
-  // Used by the above two functions by providing the corresponding file name
-  // |format_string| to a capture type (image or video).
-  base::FilePath BuildPath(const char* const format_string,
-                           base::Time timestamp) const;
+  // recording, builds with display index if there are
+  // multiple displays.
+  base::FilePath BuildImagePath() const;
+  base::FilePath BuildVideoPath() const;
+  base::FilePath BuildImagePathForDisplay(int display_index) const;
+  // Used by the above three functions by providing the corresponding file name
+  // |format_string| to a capture type (image or video). The returned file path
+  // excludes the file extension. The above functions are responsible for adding
+  // it.
+  base::FilePath BuildPathNoExtension(const char* const format_string,
+                                      base::Time timestamp) const;
 
   // Records the number of screenshots taken.
   void RecordAndResetScreenshotsTakenInLastDay();
diff --git a/ash/capture_mode/capture_mode_metrics.cc b/ash/capture_mode/capture_mode_metrics.cc
index 25a8f61..ce9cc2c 100644
--- a/ash/capture_mode/capture_mode_metrics.cc
+++ b/ash/capture_mode/capture_mode_metrics.cc
@@ -22,6 +22,8 @@
 constexpr char kConsecutiveScreenshotHistogramName[] =
     "Ash.CaptureModeController.ConsecutiveScreenshots";
 constexpr char kEntryHistogramName[] = "Ash.CaptureModeController.EntryPoint";
+constexpr char kQuickActionHistogramName[] =
+    "Ash.CaptureModeController.QuickAction";
 constexpr char kRecordTimeHistogramName[] =
     "Ash.CaptureModeController.ScreenRecordingLength";
 constexpr char kScreenshotsPerDayHistogramName[] =
@@ -123,4 +125,8 @@
                               num_screenshots_taken_in_last_week);
 }
 
+void RecordScreenshotNotificationQuickAction(CaptureQuickAction action) {
+  base::UmaHistogramEnumeration(kQuickActionHistogramName, action);
+}
+
 }  // namespace ash
diff --git a/ash/capture_mode/capture_mode_metrics.h b/ash/capture_mode/capture_mode_metrics.h
index 950bf5c..462ad32 100644
--- a/ash/capture_mode/capture_mode_metrics.h
+++ b/ash/capture_mode/capture_mode_metrics.h
@@ -66,6 +66,16 @@
   kMaxValue = kPowerMenu,
 };
 
+// Enumeration of quick actions on screenshot notification. Note that these
+// values are persisted to histograms so existing values should remain
+// unchanged and new values should be added to the end.
+enum class CaptureQuickAction {
+  kBacklight,
+  kFiles,
+  kDelete,
+  kMaxValue = kDelete,
+};
+
 // Records the |reason| for which screen recording was ended.
 void RecordEndRecordingReason(EndRecordingReason reason);
 
@@ -104,6 +114,9 @@
 void RecordNumberOfScreenshotsTakenInLastWeek(
     int num_screenshots_taken_in_last_week);
 
+// Records the action taken on screen notification.
+void RecordScreenshotNotificationQuickAction(CaptureQuickAction action);
+
 }  // namespace ash
 
 #endif  // ASH_CAPTURE_MODE_CAPTURE_MODE_METRICS_H_
diff --git a/ash/capture_mode/capture_mode_unittests.cc b/ash/capture_mode/capture_mode_unittests.cc
index 427fda1..ef3337d 100644
--- a/ash/capture_mode/capture_mode_unittests.cc
+++ b/ash/capture_mode/capture_mode_unittests.cc
@@ -19,6 +19,7 @@
 #include "ash/display/cursor_window_controller.h"
 #include "ash/display/screen_orientation_controller_test_api.h"
 #include "ash/display/window_tree_host_manager.h"
+#include "ash/home_screen/home_screen_controller.h"
 #include "ash/magnifier/magnifier_glass.h"
 #include "ash/public/cpp/ash_features.h"
 #include "ash/root_window_controller.h"
@@ -43,6 +44,10 @@
 #include "ui/gfx/geometry/insets.h"
 #include "ui/gfx/geometry/rect.h"
 #include "ui/gfx/geometry/vector2d.h"
+#include "ui/message_center/message_center.h"
+#include "ui/message_center/message_center_observer.h"
+#include "ui/message_center/public/cpp/notification.h"
+#include "ui/message_center/public/cpp/notification_delegate.h"
 #include "ui/views/view.h"
 #include "ui/views/widget/widget_observer.h"
 
@@ -52,6 +57,7 @@
 
 constexpr char kEndRecordingReasonInClamshellHistogramName[] =
     "Ash.CaptureModeController.EndRecordingReason.ClamshellMode";
+constexpr char kScreenCaptureNotificationId[] = "capture_mode_notification";
 
 // Returns true if the software-composited cursor is enabled.
 bool IsCursorCompositingEnabled() {
@@ -79,6 +85,22 @@
   event_generator->ReleaseKey(key_code, flags);
 }
 
+const message_center::Notification* GetPreviewNotification() {
+  const message_center::NotificationList::Notifications notifications =
+      message_center::MessageCenter::Get()->GetVisibleNotifications();
+  for (const auto* notification : notifications) {
+    if (notification->id() == kScreenCaptureNotificationId)
+      return notification;
+  }
+  return nullptr;
+}
+
+void ClickNotification(base::Optional<int> button_index) {
+  const message_center::Notification* notification = GetPreviewNotification();
+  DCHECK(notification);
+  notification->delegate()->Click(button_index, base::nullopt);
+}
+
 // Moves the mouse and updates the cursor's display manually to imitate what a
 // real mouse move event does in shell.
 void MoveMouseToAndUpdateCursorDisplay(
@@ -324,6 +346,27 @@
   base::ScopedObservation<views::Widget, views::WidgetObserver> observer_{this};
 };
 
+class CaptureNotificationWaiter : public message_center::MessageCenterObserver {
+ public:
+  CaptureNotificationWaiter() {
+    message_center::MessageCenter::Get()->AddObserver(this);
+  }
+  ~CaptureNotificationWaiter() override {
+    message_center::MessageCenter::Get()->RemoveObserver(this);
+  }
+
+  void Wait() { run_loop_.Run(); }
+
+  // message_center::MessageCenterObserver:
+  void OnNotificationAdded(const std::string& notification_id) override {
+    if (notification_id == kScreenCaptureNotificationId)
+      run_loop_.Quit();
+  }
+
+ private:
+  base::RunLoop run_loop_;
+};
+
 TEST_F(CaptureModeTest, StartStop) {
   auto* controller = CaptureModeController::Get();
   controller->Start(CaptureModeEntryType::kQuickSettings);
@@ -1883,9 +1926,10 @@
   event_generator->PressTouch();
   event_generator->ReleaseTouch();
 
-  // There are no windows so the window finder algorithm will find the app list
-  // window and take a picture of that, ending capture mode.
-  EXPECT_FALSE(controller->IsActive());
+  // There are no windows and home screen window is excluded from window capture
+  // mode, so capture mode will still remain active.
+  EXPECT_TRUE(Shell::Get()->home_screen_controller()->IsHomeScreenVisible());
+  EXPECT_TRUE(controller->IsActive());
 }
 
 // Tests that after rotating a display, the capture session widgets are updated
@@ -2145,4 +2189,54 @@
   EXPECT_EQ(1.f, capture_bar_layer->GetTargetOpacity());
 }
 
+// Tests that the quick action histogram is recorded properly.
+TEST_F(CaptureModeTest, QuickActionHistograms) {
+  constexpr char kQuickActionHistogramName[] =
+      "Ash.CaptureModeController.QuickAction";
+  base::HistogramTester histogram_tester;
+
+  auto* controller = StartCaptureSession(CaptureModeSource::kFullscreen,
+                                         CaptureModeType::kImage);
+  EXPECT_TRUE(controller->IsActive());
+  {
+    CaptureNotificationWaiter waiter;
+    controller->PerformCapture();
+    waiter.Wait();
+  }
+  // Verify clicking delete on screenshot notification.
+  const int delete_button = 1;
+  ClickNotification(delete_button);
+  EXPECT_FALSE(GetPreviewNotification());
+  histogram_tester.ExpectBucketCount(kQuickActionHistogramName,
+                                     CaptureQuickAction::kDelete, 1);
+
+  controller = StartCaptureSession(CaptureModeSource::kFullscreen,
+                                   CaptureModeType::kImage);
+  {
+    CaptureNotificationWaiter waiter;
+    controller->PerformCapture();
+    waiter.Wait();
+  }
+  // Click on the notification body. This should take us to the files app.
+  ClickNotification(base::nullopt);
+  EXPECT_FALSE(GetPreviewNotification());
+  histogram_tester.ExpectBucketCount(kQuickActionHistogramName,
+                                     CaptureQuickAction::kFiles, 1);
+
+  controller = StartCaptureSession(CaptureModeSource::kFullscreen,
+                                   CaptureModeType::kImage);
+
+  {
+    CaptureNotificationWaiter waiter;
+    controller->PerformCapture();
+    waiter.Wait();
+  }
+  const int edit_button = 0;
+  // Verify clicking edit on screenshot notification.
+  ClickNotification(edit_button);
+  EXPECT_FALSE(GetPreviewNotification());
+  histogram_tester.ExpectBucketCount(kQuickActionHistogramName,
+                                     CaptureQuickAction::kBacklight, 1);
+}
+
 }  // namespace ash
diff --git a/ash/capture_mode/capture_window_observer.cc b/ash/capture_mode/capture_window_observer.cc
index 5730f37..fe8d9b4 100644
--- a/ash/capture_mode/capture_window_observer.cc
+++ b/ash/capture_mode/capture_window_observer.cc
@@ -5,6 +5,8 @@
 #include "ash/capture_mode/capture_window_observer.h"
 
 #include "ash/capture_mode/capture_mode_session.h"
+#include "ash/home_screen/home_screen_controller.h"
+#include "ash/home_screen/home_screen_delegate.h"
 #include "ash/public/cpp/shell_window_ids.h"
 #include "ash/public/cpp/window_finder.h"
 #include "ash/shell.h"
@@ -40,6 +42,14 @@
     window = nullptr;
   }
 
+  // Don't capture home screen window.
+  if (window && window == Shell::Get()
+                              ->home_screen_controller()
+                              ->delegate()
+                              ->GetHomeScreenWindow()) {
+    window = nullptr;
+  }
+
   // Stop observing the current selected window if there is one.
   StopObserving();
   if (window)
diff --git a/ash/events/event_rewriter_controller_impl.cc b/ash/events/event_rewriter_controller_impl.cc
index 062dd4c..bc1fcca5b1 100644
--- a/ash/events/event_rewriter_controller_impl.cc
+++ b/ash/events/event_rewriter_controller_impl.cc
@@ -60,6 +60,7 @@
       std::make_unique<ui::EventRewriterChromeOS>(
           event_rewriter_delegate, Shell::Get()->sticky_keys_controller(),
           privacy_screen_supported);
+  event_rewriter_chromeos_ = event_rewriter_chromeos.get();
 
   std::unique_ptr<AccessibilityEventRewriter> accessibility_event_rewriter =
       std::make_unique<AccessibilityEventRewriter>(
@@ -112,6 +113,12 @@
   accessibility_event_rewriter_->set_send_mouse_events(value);
 }
 
+void EventRewriterControllerImpl::SetAltLeftClickRemappingEnabled(
+    bool enabled) {
+  if (event_rewriter_chromeos_)
+    event_rewriter_chromeos_->set_alt_left_click_remapping_enabled(enabled);
+}
+
 void EventRewriterControllerImpl::OnHostInitialized(
     aura::WindowTreeHost* host) {
   for (const auto& rewriter : rewriters_)
diff --git a/ash/events/event_rewriter_controller_impl.h b/ash/events/event_rewriter_controller_impl.h
index ab0a828..664abf4 100644
--- a/ash/events/event_rewriter_controller_impl.h
+++ b/ash/events/event_rewriter_controller_impl.h
@@ -45,6 +45,11 @@
   // aura::EnvObserver:
   void OnHostInitialized(aura::WindowTreeHost* host) override;
 
+  // Enable/disable Alt + left click mapping in EventRewriterChromeOS. This
+  // only applies if the feature
+  // `chromeos::features::kUseSearchClickForRightClick` is not enabled.
+  void SetAltLeftClickRemappingEnabled(bool enabled);
+
  private:
   // The |EventRewriter|s managed by this controller.
   std::vector<std::unique_ptr<ui::EventRewriter>> rewriters_;
@@ -52,6 +57,7 @@
   // Owned by |rewriters_|.
   AccessibilityEventRewriter* accessibility_event_rewriter_ = nullptr;
   KeyboardDrivenEventRewriter* keyboard_driven_event_rewriter_ = nullptr;
+  ui::EventRewriterChromeOS* event_rewriter_chromeos_ = nullptr;
 
   DISALLOW_COPY_AND_ASSIGN(EventRewriterControllerImpl);
 };
diff --git a/ash/public/cpp/ash_features.cc b/ash/public/cpp/ash_features.cc
index e96cce3..3dbae26 100644
--- a/ash/public/cpp/ash_features.cc
+++ b/ash/public/cpp/ash_features.cc
@@ -144,7 +144,7 @@
     "NotificationsInContextMenu", base::FEATURE_DISABLED_BY_DEFAULT};
 
 const base::Feature kTemporaryHoldingSpace{"TemporaryHoldingSpace",
-                                           base::FEATURE_DISABLED_BY_DEFAULT};
+                                           base::FEATURE_ENABLED_BY_DEFAULT};
 
 const base::Feature kTemporaryHoldingSpacePreviews{
     "TemporaryHoldingSpacePreviews", base::FEATURE_DISABLED_BY_DEFAULT};
diff --git a/ash/public/cpp/update_types.h b/ash/public/cpp/update_types.h
index f7508fa..3b91972 100644
--- a/ash/public/cpp/update_types.h
+++ b/ash/public/cpp/update_types.h
@@ -27,7 +27,7 @@
 
 // The type of update being applied. Sets the string in the system tray.
 enum class UpdateType {
-  // TODO(https://crbug.com/1154427): Add Lacros update type.
+  kLacros,  // Lacros browser, see //docs/lacros.md
   kSystem,
 };
 
diff --git a/ash/system/model/update_model.h b/ash/system/model/update_model.h
index 6f381e42..d2ca82f 100644
--- a/ash/system/model/update_model.h
+++ b/ash/system/model/update_model.h
@@ -75,7 +75,9 @@
   bool rollback_ = false;
   UpdateType update_type_ = UpdateType::kSystem;
   NotificationStyle notification_style_ = NotificationStyle::kDefault;
+  // Custom title for an OS update, usually due to RelaunchNotification policy.
   base::string16 notification_title_;
+  // Custom body for an OS update, usually due to RelaunchNotification policy.
   base::string16 notification_body_;
   bool update_over_cellular_available_ = false;
 
diff --git a/ash/system/update/update_notification_controller.cc b/ash/system/update/update_notification_controller.cc
index b09e6cb..85855b4 100644
--- a/ash/system/update/update_notification_controller.cc
+++ b/ash/system/update/update_notification_controller.cc
@@ -31,6 +31,8 @@
 
 const char kNotifierId[] = "ash.update";
 
+const char kNotificationId[] = "chrome://update";
+
 bool CheckForSlowBoot(const base::FilePath& slow_boot_file_path) {
   if (base::PathExists(slow_boot_file_path)) {
     return true;
@@ -40,9 +42,6 @@
 
 }  // namespace
 
-// static
-const char UpdateNotificationController::kNotificationId[] = "chrome://update";
-
 UpdateNotificationController::UpdateNotificationController()
     : model_(Shell::Get()->system_tray_model()->update_model()),
       slow_boot_file_path_("/mnt/stateful_partition/etc/slow_boot_required") {
@@ -123,6 +122,9 @@
 }
 
 base::string16 UpdateNotificationController::GetNotificationMessage() const {
+  if (model_->update_type() == UpdateType::kLacros)
+    return l10n_util::GetStringUTF16(IDS_UPDATE_NOTIFICATION_MESSAGE_LACROS);
+
   base::string16 system_app_name =
       l10n_util::GetStringUTF16(IDS_ASH_MESSAGE_CENTER_SYSTEM_APP_NAME);
   if (model_->rollback()) {
@@ -151,6 +153,9 @@
 }
 
 base::string16 UpdateNotificationController::GetNotificationTitle() const {
+  if (model_->update_type() == UpdateType::kLacros)
+    return l10n_util::GetStringUTF16(IDS_UPDATE_NOTIFICATION_TITLE_LACROS);
+
   const base::string16 notification_title = model_->notification_title();
   if (!notification_title.empty())
     return notification_title;
diff --git a/ash/system/update/update_notification_controller.h b/ash/system/update/update_notification_controller.h
index 896869f..e4bfea860 100644
--- a/ash/system/update/update_notification_controller.h
+++ b/ash/system/update/update_notification_controller.h
@@ -39,8 +39,6 @@
   void GenerateUpdateNotification(
       base::Optional<bool> slow_boot_file_path_exists);
 
-  static const char kNotificationId[];
-
   UpdateModel* const model_;
 
   base::FilePath slow_boot_file_path_;
diff --git a/ash/system/update/update_notification_controller_unittest.cc b/ash/system/update/update_notification_controller_unittest.cc
index 8d9ae1a..e7c0925 100644
--- a/ash/system/update/update_notification_controller_unittest.cc
+++ b/ash/system/update/update_notification_controller_unittest.cc
@@ -13,9 +13,11 @@
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
 #include "base/files/scoped_temp_dir.h"
+#include "base/run_loop.h"
 #include "base/strings/utf_string_conversions.h"
 #include "build/branding_buildflags.h"
 #include "ui/message_center/message_center.h"
+#include "ui/message_center/message_center_observer.h"
 
 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
 #define SYSTEM_APP_NAME "Chrome OS"
@@ -24,6 +26,33 @@
 #endif
 
 namespace ash {
+namespace {
+
+const char kNotificationId[] = "chrome://update";
+
+// Waits for the notification to be added. Needed because the controller posts a
+// task to check for slow boot request before showing the notification.
+class AddNotificationWaiter : public message_center::MessageCenterObserver {
+ public:
+  AddNotificationWaiter() {
+    message_center::MessageCenter::Get()->AddObserver(this);
+  }
+  ~AddNotificationWaiter() override {
+    message_center::MessageCenter::Get()->RemoveObserver(this);
+  }
+
+  void Wait() { run_loop_.Run(); }
+
+  // message_center::MessageCenterObserver:
+  void OnNotificationAdded(const std::string& notification_id) override {
+    if (notification_id == kNotificationId)
+      run_loop_.Quit();
+  }
+
+  base::RunLoop run_loop_;
+};
+
+}  // namespace
 
 class UpdateNotificationControllerTest : public AshTestBase {
  public:
@@ -33,47 +62,39 @@
  protected:
   bool HasNotification() {
     return message_center::MessageCenter::Get()->FindVisibleNotificationById(
-        UpdateNotificationController::kNotificationId);
+        kNotificationId);
   }
 
   std::string GetNotificationTitle() {
-    return base::UTF16ToUTF8(
-        message_center::MessageCenter::Get()
-            ->FindVisibleNotificationById(
-                UpdateNotificationController::kNotificationId)
-            ->title());
+    return base::UTF16ToUTF8(message_center::MessageCenter::Get()
+                                 ->FindVisibleNotificationById(kNotificationId)
+                                 ->title());
   }
 
   std::string GetNotificationMessage() {
-    return base::UTF16ToUTF8(
-        message_center::MessageCenter::Get()
-            ->FindVisibleNotificationById(
-                UpdateNotificationController::kNotificationId)
-            ->message());
+    return base::UTF16ToUTF8(message_center::MessageCenter::Get()
+                                 ->FindVisibleNotificationById(kNotificationId)
+                                 ->message());
   }
 
   std::string GetNotificationButton(int index) {
-    return base::UTF16ToUTF8(
-        message_center::MessageCenter::Get()
-            ->FindVisibleNotificationById(
-                UpdateNotificationController::kNotificationId)
-            ->buttons()
-            .at(index)
-            .title);
+    return base::UTF16ToUTF8(message_center::MessageCenter::Get()
+                                 ->FindVisibleNotificationById(kNotificationId)
+                                 ->buttons()
+                                 .at(index)
+                                 .title);
   }
 
   int GetNotificationButtonCount() {
     return message_center::MessageCenter::Get()
-        ->FindVisibleNotificationById(
-            UpdateNotificationController::kNotificationId)
+        ->FindVisibleNotificationById(kNotificationId)
         ->buttons()
         .size();
   }
 
   int GetNotificationPriority() {
     return message_center::MessageCenter::Get()
-        ->FindVisibleNotificationById(
-            UpdateNotificationController::kNotificationId)
+        ->FindVisibleNotificationById(kNotificationId)
         ->priority();
   }
 
@@ -85,10 +106,6 @@
         ->update_->slow_boot_file_path_ = file_path;
   }
 
-  const char* GetUpdateNotificationId() {
-    return UpdateNotificationController::kNotificationId;
-  }
-
   ShutdownConfirmationDialog* GetSlowBootConfirmationDialog() {
     return Shell::Get()
         ->system_notification_controller()
@@ -158,7 +175,7 @@
 
   // Trigger Click on "Restart to Update" button in Notification.
   message_center::MessageCenter::Get()->ClickOnNotificationButton(
-      GetUpdateNotificationId(), 0);
+      kNotificationId, 0);
 
   // Ensure Slow Boot Dialog is open and notification is removed.
   ASSERT_TRUE(GetSlowBootConfirmationDialog());
@@ -350,4 +367,23 @@
             GetNotificationPriority());
 }
 
+TEST_F(UpdateNotificationControllerTest, VisibilityAfterLacrosUpdate) {
+  // The system starts with no update pending, so the notification isn't
+  // visible.
+  EXPECT_FALSE(HasNotification());
+
+  // Simulate an update.
+  AddNotificationWaiter waiter;
+  Shell::Get()->system_tray_model()->ShowUpdateIcon(UpdateSeverity::kLow, false,
+                                                    false, UpdateType::kLacros);
+  waiter.Wait();
+
+  // The notification is now visible.
+  ASSERT_TRUE(HasNotification());
+  EXPECT_EQ("Lacros update available", GetNotificationTitle());
+  EXPECT_EQ("Device restart is required to apply the update.",
+            GetNotificationMessage());
+  EXPECT_EQ("Restart to update", GetNotificationButton(0));
+}
+
 }  // namespace ash
diff --git a/ash/wm/desks/desks_controller.cc b/ash/wm/desks/desks_controller.cc
index 4f68cb7..23973bd2 100644
--- a/ash/wm/desks/desks_controller.cc
+++ b/ash/wm/desks/desks_controller.cc
@@ -697,10 +697,9 @@
   // If in the middle of a window cycle gesture, reset the window cycle list
   // contents so it contains the new active desk's windows.
   auto* shell = Shell::Get();
-  if (features::IsAltTabLimitedToActiveDesk()) {
-    auto* window_cycle_controller = shell->window_cycle_controller();
+  auto* window_cycle_controller = shell->window_cycle_controller();
+  if (window_cycle_controller->IsAltTabPerActiveDesk())
     window_cycle_controller->MaybeResetCycleList();
-  }
 
   for (auto& observer : observers_)
     observer.OnDeskActivationChanged(active_desk_, old_active);
diff --git a/ash/wm/window_cycle_controller.cc b/ash/wm/window_cycle_controller.cc
index e88a2ba3..0a6423f 100644
--- a/ash/wm/window_cycle_controller.cc
+++ b/ash/wm/window_cycle_controller.cc
@@ -4,6 +4,7 @@
 
 #include "ash/wm/window_cycle_controller.h"
 
+#include "ash/events/event_rewriter_controller_impl.h"
 #include "ash/metrics/task_switch_metrics_recorder.h"
 #include "ash/metrics/task_switch_source.h"
 #include "ash/metrics/user_metrics_recorder.h"
@@ -88,6 +89,8 @@
   if (!IsCycling())
     StartCycling();
 
+  // TODO(crbug.com/1157100): Handle window highlighting after switching
+  // alt-tab mode.
   Step(direction);
 }
 
@@ -107,6 +110,8 @@
   // the window view item for the preview is transparent
   // (http://crbug.com/895265).
   Shell::Get()->wallpaper_controller()->MaybeClosePreviewWallpaper();
+  Shell::Get()->event_rewriter_controller()->SetAltLeftClickRemappingEnabled(
+      false);
 
   WindowCycleController::WindowList window_list = CreateWindowList();
   SaveCurrentActiveDeskAndWindow(window_list);
@@ -160,7 +165,7 @@
 WindowCycleController::WindowList WindowCycleController::CreateWindowList() {
   WindowCycleController::WindowList window_list =
       Shell::Get()->mru_window_tracker()->BuildWindowForCycleWithPipList(
-          features::IsAltTabLimitedToActiveDesk() ? kActiveDesk : kAllDesks);
+          IsAltTabPerActiveDesk() ? kActiveDesk : kAllDesks);
   // Window cycle list windows will handle showing their transient related
   // windows, so if a window in |window_list| has a transient root also in
   // |window_list|, we can remove it as the transient root will handle showing
@@ -169,6 +174,18 @@
   return window_list;
 }
 
+void WindowCycleController::SetAltTabMode(DesksMruType alt_tab_mode) {
+  if (alt_tab_mode_ == alt_tab_mode)
+    return;
+  alt_tab_mode_ = alt_tab_mode;
+  MaybeResetCycleList();
+}
+
+bool WindowCycleController::IsAltTabPerActiveDesk() {
+  return features::IsBentoEnabled() ? alt_tab_mode_ == kActiveDesk
+                                    : features::IsAltTabLimitedToActiveDesk();
+}
+
 void WindowCycleController::SaveCurrentActiveDeskAndWindow(
     const WindowCycleController::WindowList& window_list) {
   active_desk_container_id_before_cycle_ =
@@ -204,6 +221,8 @@
 
   active_window_before_window_cycle_ = nullptr;
   active_desk_container_id_before_cycle_ = kShellWindowId_Invalid;
+  Shell::Get()->event_rewriter_controller()->SetAltLeftClickRemappingEnabled(
+      true);
 }
 
 }  // namespace ash
diff --git a/ash/wm/window_cycle_controller.h b/ash/wm/window_cycle_controller.h
index 8ba6efd9..d24fd15a 100644
--- a/ash/wm/window_cycle_controller.h
+++ b/ash/wm/window_cycle_controller.h
@@ -9,6 +9,7 @@
 
 #include "ash/ash_export.h"
 #include "ash/public/cpp/shell_window_ids.h"
+#include "ash/wm/mru_window_tracker.h"
 #include "base/macros.h"
 #include "base/time/time.h"
 
@@ -86,6 +87,14 @@
     return window_cycle_list_.get();
   }
 
+  // Sets alt-tab mode to all desks (default) or active desk.
+  void SetAltTabMode(DesksMruType alt_tab_mode_);
+
+  // Checks if alt-tab should be per active desk. If Bento is enabled, alt-tab
+  // mode depends on users' alt_tab_mode_ selection. Otherwise, it'll default
+  // to all desk unless LimitAltTabToActiveDesk flag is explicitly enabled.
+  bool IsAltTabPerActiveDesk();
+
  private:
   // Gets a list of windows from the currently open windows, removing windows
   // with transient roots already in the list. The returned list of windows
@@ -115,6 +124,12 @@
   // Non-null while actively cycling.
   std::unique_ptr<WindowCycleEventFilter> event_filter_;
 
+  // Tracks alt-tab mode to display all windows from all desks by default.
+  // An alternative mode is active desk where only windows from the current desk
+  // are shown in alt-tab.
+  // TODO(crbug.com/1157105): Store per-desk mode in primary user pref.
+  DesksMruType alt_tab_mode_ = DesksMruType::kAllDesks;
+
   DISALLOW_COPY_AND_ASSIGN(WindowCycleController);
 };
 
diff --git a/ash/wm/window_cycle_controller_unittest.cc b/ash/wm/window_cycle_controller_unittest.cc
index bc67c14..ad53fcb 100644
--- a/ash/wm/window_cycle_controller_unittest.cc
+++ b/ash/wm/window_cycle_controller_unittest.cc
@@ -124,6 +124,20 @@
         ->GetWindowCycleItemViewsForTesting();
   }
 
+  const views::View::Views& GetWindowCycleTabSliderViews() const {
+    return Shell::Get()
+        ->window_cycle_controller()
+        ->window_cycle_list()
+        ->GetWindowCycleTabSliderViewsForTesting();
+  }
+
+  const views::Label* GetWindowCycleNoRecentItemsLabel() const {
+    return Shell::Get()
+        ->window_cycle_controller()
+        ->window_cycle_list()
+        ->GetWindowCycleNoRecentItemsLabelForTesting();
+  }
+
   const aura::Window* GetTargetWindow() const {
     return Shell::Get()
         ->window_cycle_controller()
@@ -1337,4 +1351,189 @@
   EXPECT_EQ(win0.get(), window_util::GetActiveWindow());
 }
 
+class ModeSelectionWindowCycleControllerTest
+    : public WindowCycleControllerTest {
+ public:
+  ModeSelectionWindowCycleControllerTest() = default;
+  ModeSelectionWindowCycleControllerTest(
+      const ModeSelectionWindowCycleControllerTest&) = delete;
+  ModeSelectionWindowCycleControllerTest& operator=(
+      const ModeSelectionWindowCycleControllerTest&) = delete;
+  ~ModeSelectionWindowCycleControllerTest() override = default;
+
+  // WindowCycleControllerTest:
+  void SetUp() override {
+    scoped_feature_list_.InitAndEnableFeature(features::kBento);
+    WindowCycleControllerTest::SetUp();
+    generator_ = GetEventGenerator();
+  }
+
+  void SwitchPerDeskAltTabMode(bool per_desk_mode) {
+    gfx::Point button_center =
+        GetWindowCycleTabSliderViews()[per_desk_mode ? 1 : 0]
+            ->GetBoundsInScreen()
+            .CenterPoint();
+    generator_->MoveMouseTo(button_center);
+    generator_->ClickLeftButton();
+    EXPECT_EQ(per_desk_mode,
+              Shell::Get()->window_cycle_controller()->IsAltTabPerActiveDesk());
+  }
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+  ui::test::EventGenerator* generator_;
+};
+
+// Tests that alt-tab shows all windows in an all-desk mode by default and
+// shows only windows in the current desk in a current-desk mode. Switching
+// between two modes should refresh the window list, while re-entering alt-tab
+// should display the most recently selected mode.
+TEST_F(ModeSelectionWindowCycleControllerTest, CycleShowsWindowsPerMode) {
+  WindowCycleController* cycle_controller =
+      Shell::Get()->window_cycle_controller();
+
+  // Create two windows for desk1 and three windows for desk2.
+  auto win0 = CreateAppWindow(gfx::Rect(0, 0, 250, 100));
+  auto win1 = CreateAppWindow(gfx::Rect(50, 50, 200, 200));
+  auto* desks_controller = DesksController::Get();
+  desks_controller->NewDesk(DesksCreationRemovalSource::kButton);
+  ASSERT_EQ(2u, desks_controller->desks().size());
+  const Desk* desk_2 = desks_controller->desks()[1].get();
+  ActivateDesk(desk_2);
+  EXPECT_EQ(desk_2, desks_controller->active_desk());
+  auto win2 = CreateAppWindow(gfx::Rect(0, 0, 300, 200));
+  auto win3 = CreateAppWindow(gfx::Rect(10, 30, 400, 200));
+  auto win4 = CreateAppWindow(gfx::Rect(10, 30, 400, 200));
+
+  // By default should contain windows from all desks.
+  auto* generator = GetEventGenerator();
+  // Press and hold an alt key to test that alt + left clicking a button works.
+  generator->PressKey(ui::VKEY_MENU, ui::EF_NONE);
+  cycle_controller->StartCycling();
+  EXPECT_FALSE(cycle_controller->IsAltTabPerActiveDesk());
+  auto cycle_windows = GetWindows(cycle_controller);
+  EXPECT_EQ(5u, cycle_windows.size());
+  EXPECT_EQ(cycle_windows.size(), GetWindowCycleItemViews().size());
+  EXPECT_TRUE(base::Contains(cycle_windows, win0.get()));
+  EXPECT_TRUE(base::Contains(cycle_windows, win1.get()));
+  EXPECT_TRUE(base::Contains(cycle_windows, win2.get()));
+  EXPECT_TRUE(base::Contains(cycle_windows, win3.get()));
+  EXPECT_TRUE(base::Contains(cycle_windows, win4.get()));
+
+  // Switching alt-tab to the current-desk mode should show windows in the
+  // active desk.
+  SwitchPerDeskAltTabMode(true);
+  cycle_windows = GetWindows(cycle_controller);
+  EXPECT_EQ(3u, GetWindowCycleItemViews().size());
+  EXPECT_EQ(cycle_windows.size(), GetWindowCycleItemViews().size());
+  EXPECT_TRUE(base::Contains(cycle_windows, win2.get()));
+  EXPECT_TRUE(base::Contains(cycle_windows, win3.get()));
+  EXPECT_TRUE(base::Contains(cycle_windows, win4.get()));
+  cycle_controller->CompleteCycling();
+
+  // Activate desk1 and start alt-tab.
+  const Desk* desk_1 = desks_controller->desks()[0].get();
+  ActivateDesk(desk_1);
+  cycle_controller->StartCycling();
+  // Should start alt-tab with the current-desk mode and show only two windows
+  // from desk1.
+  EXPECT_TRUE(cycle_controller->IsAltTabPerActiveDesk());
+  cycle_windows = GetWindows(cycle_controller);
+  EXPECT_EQ(2u, GetWindowCycleItemViews().size());
+  EXPECT_EQ(cycle_windows.size(), GetWindowCycleItemViews().size());
+  EXPECT_TRUE(base::Contains(cycle_windows, win0.get()));
+  EXPECT_TRUE(base::Contains(cycle_windows, win1.get()));
+
+  // Switch to the all-desks mode, check and stop alt-tab.
+  SwitchPerDeskAltTabMode(false);
+  cycle_windows = GetWindows(cycle_controller);
+  EXPECT_EQ(5u, cycle_windows.size());
+  EXPECT_EQ(cycle_windows.size(), GetWindowCycleItemViews().size());
+  cycle_controller->CompleteCycling();
+  generator->ReleaseKey(ui::VKEY_MENU, ui::EF_NONE);
+}
+
+// For one window display, tests that alt-tab does not show up if there is only
+// one window to be shown, but would continue to show a window in alt-tab if
+// switching from the all-desks mode with multiple windows.
+TEST_F(ModeSelectionWindowCycleControllerTest, OneWindowInActiveDesk) {
+  WindowCycleController* cycle_controller =
+      Shell::Get()->window_cycle_controller();
+
+  // Create two windows for desk1 and one window for desk2.
+  auto win0 = CreateAppWindow(gfx::Rect(0, 0, 250, 100));
+  auto win1 = CreateAppWindow(gfx::Rect(50, 50, 200, 200));
+  auto* desks_controller = DesksController::Get();
+  desks_controller->NewDesk(DesksCreationRemovalSource::kButton);
+  ASSERT_EQ(2u, desks_controller->desks().size());
+  const Desk* desk_2 = desks_controller->desks()[1].get();
+  ActivateDesk(desk_2);
+  EXPECT_EQ(desk_2, desks_controller->active_desk());
+  auto win2 = CreateAppWindow(gfx::Rect(0, 0, 300, 200));
+
+  // Starting alt-tab should shows all desks.
+  cycle_controller->StartCycling();
+  auto cycle_windows = GetWindows(cycle_controller);
+  EXPECT_EQ(3u, GetWindowCycleItemViews().size());
+  EXPECT_EQ(cycle_windows.size(), GetWindowCycleItemViews().size());
+
+  // Switching to an active desk mode should shows a single window in desk2.
+  SwitchPerDeskAltTabMode(true);
+  EXPECT_TRUE(cycle_controller->IsCycling());
+  cycle_windows = GetWindows(cycle_controller);
+  EXPECT_EQ(1u, GetWindowCycleItemViews().size());
+  EXPECT_EQ(cycle_windows.size(), GetWindowCycleItemViews().size());
+  EXPECT_TRUE(base::Contains(cycle_windows, win2.get()));
+  cycle_controller->CompleteCycling();
+
+  // Closing alt-tab and trying to re-open again in the current-desk mode
+  // should not work because there's only one window.
+  cycle_controller->StartCycling();
+  EXPECT_TRUE(cycle_controller->IsCycling());
+  EXPECT_FALSE(CycleViewExists());
+  cycle_controller->CompleteCycling();
+}
+
+// Similar to OneWindowInActiveDesk, tests that alt-tab will not work if there
+// is no window to be shown. However, if users already open alt-tab with
+// multiple windows in the all-desks mode and later switch to the current-
+// desk mode, which has no window, it'll display "No recent items".
+TEST_F(ModeSelectionWindowCycleControllerTest, NoWindowInActiveDesk) {
+  WindowCycleController* cycle_controller =
+      Shell::Get()->window_cycle_controller();
+
+  // Create two desks with all two windows in desk1.
+  auto win0 = CreateAppWindow(gfx::Rect(0, 0, 250, 100));
+  auto win1 = CreateAppWindow(gfx::Rect(50, 50, 200, 200));
+  auto* desks_controller = DesksController::Get();
+  desks_controller->NewDesk(DesksCreationRemovalSource::kButton);
+  ASSERT_EQ(2u, desks_controller->desks().size());
+  const Desk* desk_2 = desks_controller->desks()[1].get();
+
+  // Activate desk2.
+  ActivateDesk(desk_2);
+  EXPECT_EQ(desk_2, desks_controller->active_desk());
+
+  // Starting alt-tab should show all windows from all desks.
+  cycle_controller->StartCycling();
+  auto cycle_windows = GetWindows(cycle_controller);
+  EXPECT_EQ(2u, GetWindowCycleItemViews().size());
+  EXPECT_EQ(cycle_windows.size(), GetWindowCycleItemViews().size());
+  EXPECT_FALSE(GetWindowCycleNoRecentItemsLabel()->GetVisible());
+
+  // Switching to an active-desk mode should not show any mirror window
+  // and should display "no recent items" label.
+  SwitchPerDeskAltTabMode(true);
+  EXPECT_TRUE(cycle_controller->IsCycling());
+  cycle_windows = GetWindows(cycle_controller);
+  EXPECT_EQ(0u, GetWindowCycleItemViews().size());
+  EXPECT_EQ(cycle_windows.size(), GetWindowCycleItemViews().size());
+  EXPECT_TRUE(GetWindowCycleNoRecentItemsLabel()->GetVisible());
+
+  // Switching back to an all-desks mode should hide the label.
+  SwitchPerDeskAltTabMode(false);
+  EXPECT_FALSE(GetWindowCycleNoRecentItemsLabel()->GetVisible());
+  cycle_controller->CompleteCycling();
+}
+
 }  // namespace ash
diff --git a/ash/wm/window_cycle_list.cc b/ash/wm/window_cycle_list.cc
index 993898c..95c443b5 100644
--- a/ash/wm/window_cycle_list.cc
+++ b/ash/wm/window_cycle_list.cc
@@ -17,7 +17,11 @@
 #include "ash/public/cpp/window_properties.h"
 #include "ash/shell.h"
 #include "ash/shell_delegate.h"
+#include "ash/strings/grit/ash_strings.h"
 #include "ash/style/ash_color_provider.h"
+#include "ash/style/default_colors.h"
+#include "ash/wm/window_cycle_tab_slider.h"
+#include "ash/wm/window_cycle_tab_slider_button.h"
 #include "ash/wm/window_mini_view.h"
 #include "ash/wm/window_preview_view.h"
 #include "ash/wm/window_state.h"
@@ -29,12 +33,15 @@
 #include "ui/aura/scoped_window_targeter.h"
 #include "ui/aura/window.h"
 #include "ui/aura/window_targeter.h"
+#include "ui/base/l10n/l10n_util.h"
 #include "ui/compositor/animation_throughput_reporter.h"
 #include "ui/compositor/layer_animation_sequence.h"
 #include "ui/compositor/scoped_layer_animation_settings.h"
 #include "ui/display/display.h"
 #include "ui/gfx/geometry/insets.h"
 #include "ui/views/animation/bounds_animator.h"
+#include "ui/views/background.h"
+#include "ui/views/controls/button/label_button.h"
 #include "ui/views/layout/box_layout.h"
 #include "ui/views/view.h"
 #include "ui/views/widget/widget.h"
@@ -70,9 +77,19 @@
 constexpr int kInsideBorderHorizontalPaddingDp = 64;
 constexpr int kInsideBorderVerticalPaddingDp = 60;
 
+// Padding between the alt-tab bandshield and the tab slider container.
+constexpr int kMirrorContainerVerticalPaddingDp = 24;
+
 // Padding between the window previews within the alt-tab bandshield.
 constexpr int kBetweenChildPaddingDp = 10;
 
+// Padding between the tab slider button and the tab slider container.
+constexpr int kTabSliderContainerVerticalPaddingDp = 32;
+
+// The font size of "No recent items" string when there's no window in the
+// window cycle list.
+constexpr int kNoRecentItemsLabelFontSizeDp = 14;
+
 // The UMA histogram that logs smoothness of the fade-in animation.
 constexpr char kShowAnimationSmoothness[] =
     "Ash.WindowCycleView.AnimationSmoothness.Show";
@@ -262,6 +279,30 @@
     layer->SetBackdropFilterQuality(kBackgroundBlurQuality);
     layer->SetName("WindowCycleView");
 
+    if (features::IsBentoEnabled()) {
+      tab_slider_container_ =
+          AddChildView(std::make_unique<WindowCycleTabSlider>());
+
+      no_recent_items_label_ = AddChildView(std::make_unique<views::Label>(
+          l10n_util::GetStringUTF16(IDS_ASH_OVERVIEW_NO_RECENT_ITEMS)));
+
+      no_recent_items_label_->SetPaintToLayer();
+      no_recent_items_label_->layer()->SetFillsBoundsOpaquely(false);
+      no_recent_items_label_->SetHorizontalAlignment(gfx::ALIGN_CENTER);
+      no_recent_items_label_->SetVerticalAlignment(gfx::ALIGN_MIDDLE);
+
+      no_recent_items_label_->SetEnabledColor(
+          AshColorProvider::Get()->GetContentLayerColor(
+              AshColorProvider::ContentLayerType::kIconColorSecondary));
+      no_recent_items_label_->SetFontList(
+          no_recent_items_label_->font_list()
+              .DeriveWithSizeDelta(
+                  kNoRecentItemsLabelFontSizeDp -
+                  no_recent_items_label_->font_list().GetFontSize())
+              .DeriveWithWeight(gfx::Font::Weight::NORMAL));
+      no_recent_items_label_->SetVisible(false);
+    }
+
     // |mirror_container_| may be larger than |this|. In this case, it will be
     // shifted along the x-axis when the user tabs through. It is a container
     // for the previews and has no rendered content.
@@ -271,7 +312,11 @@
     views::BoxLayout* layout =
         mirror_container_->SetLayoutManager(std::make_unique<views::BoxLayout>(
             views::BoxLayout::Orientation::kHorizontal,
-            gfx::Insets(kInsideBorderVerticalPaddingDp,
+            gfx::Insets(features::IsBentoEnabled()
+                            ? kMirrorContainerVerticalPaddingDp
+                            : kInsideBorderVerticalPaddingDp,
+                        kInsideBorderHorizontalPaddingDp,
+                        kInsideBorderVerticalPaddingDp,
                         kInsideBorderHorizontalPaddingDp),
             kBetweenChildPaddingDp));
     layout->set_cross_axis_alignment(
@@ -301,6 +346,15 @@
   ~WindowCycleView() override = default;
 
   void UpdateWindows(const WindowCycleList::WindowList& windows) {
+    const bool no_windows = windows.empty();
+    if (features::IsBentoEnabled()) {
+      no_recent_items_label_->SetVisible(no_windows);
+    }
+    if (no_windows)
+      return;
+
+    if (features::IsBentoEnabled())
+      no_recent_items_label_->SetVisible(false);
     for (auto* window : windows) {
       auto* view = mirror_container_->AddChildView(
           std::make_unique<WindowCycleItemView>(window));
@@ -394,7 +448,12 @@
 
   // views::WidgetDelegateView:
   gfx::Size CalculatePreferredSize() const override {
-    return mirror_container_->GetPreferredSize();
+    gfx::Size size = mirror_container_->GetPreferredSize();
+    if (features::IsBentoEnabled()) {
+      size.Enlarge(0, tab_slider_container_->GetPreferredSize().height() +
+                          kTabSliderContainerVerticalPaddingDp);
+    }
+    return size;
   }
 
   void Layout() override {
@@ -436,6 +495,32 @@
     }
     container_bounds.set_x(x_offset);
 
+    // Layout a tab slider if Bento is enabled.
+    if (features::IsBentoEnabled()) {
+      // Layout the tab slider.
+      const gfx::Size tab_slider_size =
+          tab_slider_container_->GetPreferredSize();
+      const gfx::Rect tab_slider_container_bounds(
+          (width() - tab_slider_size.width()) / 2,
+          kTabSliderContainerVerticalPaddingDp, tab_slider_size.width(),
+          tab_slider_size.height());
+      tab_slider_container_->SetBoundsRect(tab_slider_container_bounds);
+
+      // Move window cycle container down.
+      container_bounds.set_y(tab_slider_container_->y() +
+                             tab_slider_container_->height());
+
+      // Unlike the bounds of scrollable mirror container, the bounds of label
+      // should not overflow out of the screen.
+      if (no_previews_set_.empty()) {
+        const gfx::Rect no_recent_item_bounds_(
+            std::max(0, container_bounds.x()), container_bounds.y(),
+            std::min(width(), container_bounds.width()),
+            container_bounds.height());
+        no_recent_items_label_->SetBoundsRect(no_recent_item_bounds_);
+      }
+    }
+
     // Enable animations only after the first Layout() pass.
     std::unique_ptr<ui::ScopedLayerAnimationSettings> settings;
     base::Optional<ui::AnimationThroughputReporter> reporter;
@@ -478,6 +563,13 @@
     return mirror_container_->children();
   }
 
+  const views::View::Views& GetTabSliderViewsForTesting() const {
+    return tab_slider_container_->children();
+  }
+
+  const views::Label* GetNoRecentItemsLabelForTesting() const {
+    return no_recent_items_label_;
+  }
   const aura::Window* GetTargetWindowForTesting() const {
     return target_window_;
   }
@@ -491,6 +583,10 @@
   std::map<aura::Window*, WindowCycleItemView*> window_view_map_;
   views::View* mirror_container_ = nullptr;
 
+  // Tab slider and no recent items are only used when Bento is enabled.
+  views::View* tab_slider_container_ = nullptr;
+  views::Label* no_recent_items_label_ = nullptr;
+
   // The |target_window_| is the window that has the focus ring. When the user
   // completes cycling the |target_window_| is activated.
   aura::Window* target_window_ = nullptr;
@@ -572,8 +668,6 @@
 }
 
 void WindowCycleList::ReplaceWindows(const WindowList& windows) {
-  if (windows.empty())
-    return;
 
   RemoveAllWindows();
   windows_ = windows;
@@ -581,7 +675,7 @@
   for (auto* new_window : windows_)
     new_window->AddObserver(this);
 
-  if (ShouldShowUi() && cycle_view_)
+  if (cycle_view_)
     cycle_view_->UpdateWindows(windows_);
 }
 
@@ -819,11 +913,21 @@
 
 const views::View::Views& WindowCycleList::GetWindowCycleItemViewsForTesting()
     const {
-  return cycle_view_->GetPreviewViewsForTesting();
+  return cycle_view_->GetPreviewViewsForTesting();  // IN-TEST
+}
+
+const views::View::Views&
+WindowCycleList::GetWindowCycleTabSliderViewsForTesting() const {
+  return cycle_view_->GetTabSliderViewsForTesting();  // IN-TEST
+}
+
+const views::Label*
+WindowCycleList::GetWindowCycleNoRecentItemsLabelForTesting() const {
+  return cycle_view_->GetNoRecentItemsLabelForTesting();  // IN-TEST
 }
 
 const aura::Window* WindowCycleList::GetTargetWindowForTesting() const {
-  return cycle_view_->GetTargetWindowForTesting();
+  return cycle_view_->GetTargetWindowForTesting();  // IN-TEST
 }
 
 }  // namespace ash
diff --git a/ash/wm/window_cycle_list.h b/ash/wm/window_cycle_list.h
index b31fd413..d97e0a2 100644
--- a/ash/wm/window_cycle_list.h
+++ b/ash/wm/window_cycle_list.h
@@ -15,6 +15,7 @@
 #include "ui/aura/window_observer.h"
 #include "ui/display/display_observer.h"
 #include "ui/display/screen.h"
+#include "ui/views/controls/label.h"
 #include "ui/views/view.h"
 
 namespace aura {
@@ -115,6 +116,12 @@
   // Returns the views for the window cycle list.
   const views::View::Views& GetWindowCycleItemViewsForTesting() const;
 
+  // Returns the views for the window cycle tab slider buttons.
+  const views::View::Views& GetWindowCycleTabSliderViewsForTesting() const;
+
+  // Returns no recent items label.
+  const views::Label* GetWindowCycleNoRecentItemsLabelForTesting() const;
+
   // Returns the window cycle list's target window.
   const aura::Window* GetTargetWindowForTesting() const;
 
diff --git a/ash/wm/window_cycle_tab_slider.cc b/ash/wm/window_cycle_tab_slider.cc
new file mode 100644
index 0000000..d6726c9
--- /dev/null
+++ b/ash/wm/window_cycle_tab_slider.cc
@@ -0,0 +1,72 @@
+// 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/wm/window_cycle_tab_slider.h"
+
+#include "ash/shell.h"
+#include "ash/strings/grit/ash_strings.h"
+#include "ash/style/ash_color_provider.h"
+#include "ash/style/default_colors.h"
+#include "ash/wm/mru_window_tracker.h"
+#include "ash/wm/window_cycle_controller.h"
+#include "base/strings/utf_string_conversions.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/gfx/canvas.h"
+#include "ui/views/background.h"
+#include "ui/views/layout/box_layout.h"
+#include "ui/views/metadata/metadata_impl_macros.h"
+
+namespace ash {
+
+WindowCycleTabSlider::WindowCycleTabSlider()
+    : all_desks_tab_slider_button_(
+          AddChildView(std::make_unique<WindowCycleTabSliderButton>(
+              base::BindRepeating(&WindowCycleTabSlider::OnModeChanged,
+                                  base::Unretained(this),
+                                  false),
+              l10n_util::GetStringUTF16(IDS_ASH_ALT_TAB_ALL_DESKS_MODE)))),
+      current_desk_tab_slider_button_(
+          AddChildView(std::make_unique<WindowCycleTabSliderButton>(
+              base::BindRepeating(&WindowCycleTabSlider::OnModeChanged,
+                                  base::Unretained(this),
+                                  true),
+              l10n_util::GetStringUTF16(IDS_ASH_ALT_TAB_CURRENT_DESK_MODE)))) {
+  SetPaintToLayer();
+  layer()->SetFillsBoundsOpaquely(false);
+  SetLayoutManager(std::make_unique<views::BoxLayout>(
+      views::BoxLayout::Orientation::kHorizontal, gfx::Insets(0, 0), 0));
+
+  // All buttons should have the same width and height.
+  gfx::Size common_size = all_desks_tab_slider_button_->GetPreferredSize();
+  common_size.SetToMax(current_desk_tab_slider_button_->GetPreferredSize());
+  all_desks_tab_slider_button_->SetPreferredSize(common_size);
+  current_desk_tab_slider_button_->SetPreferredSize(common_size);
+
+  const int tab_slider_round_radius = int{common_size.height() / 2};
+  SetBackground(views::CreateRoundedRectBackground(
+      AshColorProvider::Get()->GetControlsLayerColor(
+          AshColorProvider::ControlsLayerType::kControlBackgroundColorInactive),
+      tab_slider_round_radius));
+
+  per_desk_mode_ =
+      Shell::Get()->window_cycle_controller()->IsAltTabPerActiveDesk();
+
+  all_desks_tab_slider_button_->SetToggled(!per_desk_mode_);
+  current_desk_tab_slider_button_->SetToggled(per_desk_mode_);
+}
+
+void WindowCycleTabSlider::OnModeChanged(bool per_desk) {
+  if (per_desk_mode_ == per_desk)
+    return;
+  per_desk_mode_ = per_desk;
+  all_desks_tab_slider_button_->SetToggled(!per_desk_mode_);
+  current_desk_tab_slider_button_->SetToggled(per_desk_mode_);
+  Shell::Get()->window_cycle_controller()->SetAltTabMode(
+      per_desk_mode_ ? DesksMruType::kActiveDesk : DesksMruType::kAllDesks);
+}
+
+BEGIN_METADATA(WindowCycleTabSlider, views::View)
+END_METADATA
+
+}  // namespace ash
diff --git a/ash/wm/window_cycle_tab_slider.h b/ash/wm/window_cycle_tab_slider.h
new file mode 100644
index 0000000..17d25e3
--- /dev/null
+++ b/ash/wm/window_cycle_tab_slider.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 ASH_WM_WINDOW_CYCLE_TAB_SLIDER_H_
+#define ASH_WM_WINDOW_CYCLE_TAB_SLIDER_H_
+
+#include "ash/ash_export.h"
+#include "ash/wm/window_cycle_tab_slider_button.h"
+#include "ui/views/metadata/metadata_header_macros.h"
+
+namespace ash {
+
+// A WindowCycleTabSlider containing two buttons to switch between
+// all desks and current desks mode.
+class ASH_EXPORT WindowCycleTabSlider : public views::View {
+ public:
+  METADATA_HEADER(WindowCycleTabSlider);
+
+  WindowCycleTabSlider();
+  WindowCycleTabSlider(const WindowCycleTabSlider&) = delete;
+  WindowCycleTabSlider& operator=(const WindowCycleTabSlider&) = delete;
+  ~WindowCycleTabSlider() override = default;
+
+  void OnModeChanged(bool per_desk);
+
+  // TODO(crbug.com/1157087): Add tab slider animation.
+
+ private:
+  bool per_desk_mode_ = false;
+
+  WindowCycleTabSliderButton* all_desks_tab_slider_button_;
+  WindowCycleTabSliderButton* current_desk_tab_slider_button_;
+};
+
+}  // namespace ash
+
+#endif  // ASH_WM_WINDOW_CYCLE_TAB_SLIDER_H_
diff --git a/ash/wm/window_cycle_tab_slider_button.cc b/ash/wm/window_cycle_tab_slider_button.cc
new file mode 100644
index 0000000..887f7c9
--- /dev/null
+++ b/ash/wm/window_cycle_tab_slider_button.cc
@@ -0,0 +1,84 @@
+// 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/wm/window_cycle_tab_slider_button.h"
+
+#include "ash/style/ash_color_provider.h"
+#include "base/strings/utf_string_conversions.h"
+#include "ui/gfx/canvas.h"
+#include "ui/views/background.h"
+#include "ui/views/controls/label.h"
+#include "ui/views/layout/box_layout.h"
+#include "ui/views/metadata/metadata_impl_macros.h"
+
+namespace ash {
+
+namespace {
+
+// The height of the tab slider button.
+constexpr int kTabSliderButtonHeight = 32;
+
+// The round radius of the slider, which is half of its diameter (height).
+constexpr int kTabSliderRoundRadius = int{kTabSliderButtonHeight / 2};
+
+// The horizontal insets between the label and the button.
+constexpr int kTabSliderButtonHorizontalInsets = 20;
+
+// The font size of the button label.
+constexpr int kTabSliderButtonLabelFontSizeDp = 13;
+
+}  // namespace
+
+WindowCycleTabSliderButton::WindowCycleTabSliderButton(
+    views::Button::PressedCallback callback,
+    const base::string16& label_text)
+    : LabelButton(std::move(callback), label_text) {
+  SetHorizontalAlignment(gfx::ALIGN_CENTER);
+
+  label()->SetFontList(
+      label()
+          ->font_list()
+          .DeriveWithSizeDelta(kTabSliderButtonLabelFontSizeDp -
+                               label()->font_list().GetFontSize())
+          .DeriveWithWeight(gfx::Font::Weight::MEDIUM));
+
+  SetEnabledTextColors(AshColorProvider::Get()->GetContentLayerColor(
+      AshColorProvider::ContentLayerType::kTextColorPrimary));
+
+  SetBorder(views::CreateEmptyBorder(gfx::Insets()));
+}
+
+void WindowCycleTabSliderButton::SetToggled(bool is_toggled) {
+  if (is_toggled == toggled_)
+    return;
+  toggled_ = is_toggled;
+  SetEnabledTextColors(AshColorProvider::Get()->GetContentLayerColor(
+      toggled_ ? AshColorProvider::ContentLayerType::kButtonLabelColorPrimary
+               : AshColorProvider::ContentLayerType::kTextColorPrimary));
+  // SchedulePaint triggers OnPaintBackground
+  SchedulePaint();
+}
+
+void WindowCycleTabSliderButton::OnPaintBackground(gfx::Canvas* canvas) {
+  if (!toggled_)
+    return;
+
+  cc::PaintFlags flags;
+  flags.setAntiAlias(true);
+  flags.setStyle(cc::PaintFlags::kFill_Style);
+  flags.setColor(AshColorProvider::Get()->GetControlsLayerColor(
+      AshColorProvider::ControlsLayerType::kControlBackgroundColorActive));
+  canvas->DrawRoundRect(GetContentsBounds(), kTabSliderRoundRadius, flags);
+}
+
+gfx::Size WindowCycleTabSliderButton::CalculatePreferredSize() const {
+  return gfx::Size(label()->GetPreferredSize().width() +
+                       2 * kTabSliderButtonHorizontalInsets,
+                   kTabSliderButtonHeight);
+}
+
+BEGIN_METADATA(WindowCycleTabSliderButton, views::LabelButton)
+END_METADATA
+
+}  // namespace ash
diff --git a/ash/wm/window_cycle_tab_slider_button.h b/ash/wm/window_cycle_tab_slider_button.h
new file mode 100644
index 0000000..b94f607
--- /dev/null
+++ b/ash/wm/window_cycle_tab_slider_button.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 ASH_WM_WINDOW_CYCLE_TAB_SLIDER_BUTTON_H_
+#define ASH_WM_WINDOW_CYCLE_TAB_SLIDER_BUTTON_H_
+
+#include "ash/ash_export.h"
+#include "ui/views/controls/button/label_button.h"
+#include "ui/views/metadata/metadata_header_macros.h"
+
+namespace ash {
+
+// A button used in the WindowCycleTabSlider to choose between
+// all desks and current desks mode.
+class ASH_EXPORT WindowCycleTabSliderButton : public views::LabelButton {
+ public:
+  METADATA_HEADER(WindowCycleTabSliderButton);
+
+  WindowCycleTabSliderButton(views::Button::PressedCallback callback,
+                             const base::string16& label);
+  WindowCycleTabSliderButton(const WindowCycleTabSliderButton&) = delete;
+  WindowCycleTabSliderButton& operator=(const WindowCycleTabSliderButton&) =
+      delete;
+  ~WindowCycleTabSliderButton() override = default;
+
+  // views::View:
+  void OnPaintBackground(gfx::Canvas* canvas) override;
+  gfx::Size CalculatePreferredSize() const override;
+
+  void SetToggled(bool is_toggled);
+
+ private:
+  bool toggled_ = false;
+};
+
+}  // namespace ash
+
+#endif  // ASH_WM_WINDOW_CYCLE_TAB_SLIDER_BUTTON_H_
diff --git a/base/fuchsia/startup_context.cc b/base/fuchsia/startup_context.cc
index 7d1e0c2..3655845c 100644
--- a/base/fuchsia/startup_context.cc
+++ b/base/fuchsia/startup_context.cc
@@ -13,7 +13,6 @@
 #include "base/macros.h"
 
 namespace base {
-namespace fuchsia {
 
 StartupContext::StartupContext(::fuchsia::sys::StartupInfo startup_info) {
   std::unique_ptr<sys::ServiceDirectory> incoming_services;
@@ -98,5 +97,4 @@
   component_context_->outgoing()->Serve(std::move(outgoing_directory_request_));
 }
 
-}  // namespace fuchsia
 }  // namespace base
diff --git a/base/fuchsia/startup_context.h b/base/fuchsia/startup_context.h
index a2d1f5c..eeffd4f 100644
--- a/base/fuchsia/startup_context.h
+++ b/base/fuchsia/startup_context.h
@@ -19,7 +19,6 @@
 }  // namespace sys
 
 namespace base {
-namespace fuchsia {
 
 // Helper for unpacking a fuchsia.sys.StartupInfo and creating convenience
 // wrappers for the various fields (e.g. the incoming & outgoing service
@@ -68,7 +67,6 @@
   zx::channel outgoing_directory_request_;
 };
 
-}  // namespace fuchsia
 }  // namespace base
 
 #endif  // BASE_FUCHSIA_STARTUP_CONTEXT_H_
diff --git a/base/win/windows_types.h b/base/win/windows_types.h
index d1f3e8d..17a20aad 100644
--- a/base/win/windows_types.h
+++ b/base/win/windows_types.h
@@ -236,6 +236,9 @@
 
 WINBASEAPI VOID WINAPI SetLastError(_In_ DWORD dwErrCode);
 
+WINBASEAPI BOOL WINAPI TerminateProcess(_In_ HANDLE hProcess,
+                                        _In_ UINT uExitCode);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/build/fuchsia/linux.sdk.sha1 b/build/fuchsia/linux.sdk.sha1
index a0251159..f075674e 100644
--- a/build/fuchsia/linux.sdk.sha1
+++ b/build/fuchsia/linux.sdk.sha1
@@ -1 +1 @@
-0.20201221.0.1
+0.20201221.2.1
diff --git a/build/fuchsia/mac.sdk.sha1 b/build/fuchsia/mac.sdk.sha1
index a0251159..f075674e 100644
--- a/build/fuchsia/mac.sdk.sha1
+++ b/build/fuchsia/mac.sdk.sha1
@@ -1 +1 @@
-0.20201221.0.1
+0.20201221.2.1
diff --git a/chrome/VERSION b/chrome/VERSION
index 95be3f3..5b0aabc 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
 MAJOR=89
 MINOR=0
-BUILD=4363
+BUILD=4364
 PATCH=0
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManager.java b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManager.java
index 736b2c4..8166ec2 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManager.java
@@ -1057,7 +1057,7 @@
                 }
                 if (mSearchRequest != null
                         && (!mDidStartLoadingResolvedSearchRequest || mShouldLoadDelayedSearch)) {
-                    // mShouldLoadDelayedSearch is used in the long-press case to load content.
+                    // mShouldLoadDelayedSearch is used in the non-preloading case to load content.
                     // Since content is now created and destroyed for each request, was impossible
                     // to know if content was already loaded or recently needed to be; this is for
                     // the case where it needed to be.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchPolicy.java b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchPolicy.java
index cb33f42..07d93a9 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchPolicy.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchPolicy.java
@@ -142,10 +142,7 @@
 
         // We never preload unless we have sent page context (done through a Resolve request).
         // Only some gestures can resolve, and only when resolve privacy rules are met.
-        return (mSelectionController.getSelectionType() == SelectionType.TAP
-                       || mSelectionController.getSelectionType()
-                               == SelectionType.RESOLVING_LONG_PRESS)
-                && shouldPreviousGestureResolve();
+        return isResolvingGesture() && shouldPreviousGestureResolve();
     }
 
     /**
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/browserservices/TrustedWebActivityClientTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/browserservices/TrustedWebActivityClientTest.java
index 668e76e..732f1b04 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/browserservices/TrustedWebActivityClientTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/browserservices/TrustedWebActivityClientTest.java
@@ -8,7 +8,6 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -23,7 +22,7 @@
 import androidx.browser.trusted.TrustedWebActivityServiceConnection;
 import androidx.browser.trusted.TrustedWebActivityServiceConnectionPool;
 
-import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.SettableFuture;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -31,7 +30,6 @@
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
-import org.mockito.stubbing.Answer;
 import org.robolectric.RuntimeEnvironment;
 import org.robolectric.annotation.Config;
 
@@ -55,9 +53,12 @@
     private static final int SERVICE_SMALL_ICON_ID = 1;
     private static final String CLIENT_PACKAGE_NAME = "com.example.app";
 
-    @Mock private TrustedWebActivityServiceConnectionPool mConnectionPool;
+    private SettableFuture<TrustedWebActivityServiceConnection> mServiceFuture =
+            SettableFuture.create();
+
+    @Mock
+    private TrustedWebActivityServiceConnectionPool mConnectionPool;
     @Mock private TrustedWebActivityServiceConnection mService;
-    @Mock private ListenableFuture<TrustedWebActivityServiceConnection> mServiceFuture;
     @Mock private NotificationBuilderBase mNotificationBuilder;
     @Mock private TrustedWebActivityUmaRecorder mRecorder;
     @Mock private NotificationUmaTracker mNotificationUmaTracker;
@@ -73,12 +74,7 @@
     public void setUp() throws ExecutionException, InterruptedException, RemoteException {
         MockitoAnnotations.initMocks(this);
 
-        when(mServiceFuture.get()).thenReturn(mService);
-        doAnswer((Answer<Void>) invocation -> {
-            Runnable runnable = invocation.getArgument(0);
-            runnable.run();
-            return null;
-        }).when(mServiceFuture).addListener(any(), any());
+        mServiceFuture.set(mService);
         when(mConnectionPool.connect(any(), any(), any())).thenReturn(mServiceFuture);
 
         when(mService.getSmallIconId()).thenReturn(SERVICE_SMALL_ICON_ID);
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 5777322..d139e1b 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -4191,6 +4191,7 @@
       "//components/web_modal",
       "//components/zoom",
       "//courgette:courgette_lib",
+      "//services/device/public/cpp/hid",
       "//third_party/sqlite",
     ]
     if (is_chromeos_ash) {
@@ -4424,8 +4425,6 @@
       "nearby_sharing/share_target_info.h",
       "nearby_sharing/sharesheet/nearby_share_action.cc",
       "nearby_sharing/sharesheet/nearby_share_action.h",
-      "nearby_sharing/sharesheet/nearby_share_web_view.cc",
-      "nearby_sharing/sharesheet/nearby_share_web_view.h",
       "nearby_sharing/transfer_metadata.cc",
       "nearby_sharing/transfer_metadata.h",
       "nearby_sharing/transfer_metadata_builder.cc",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 6c85d11..f5d1ab7 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -2834,6 +2834,9 @@
     {"bluetooth-wbs-dogfood", flag_descriptions::kBluetoothWbsDogfoodName,
      flag_descriptions::kBluetoothWbsDogfoodDescription, kOsCrOS,
      FEATURE_VALUE_TYPE(chromeos::features::kBluetoothWbsDogfood)},
+    {"cellular-use-attach-apn", flag_descriptions::kCellularUseAttachApnName,
+     flag_descriptions::kCellularUseAttachApnDescription, kOsCrOS,
+     FEATURE_VALUE_TYPE(chromeos::features::kCellularUseAttachApn)},
     {"cryptauth-v2-device-activity-status",
      flag_descriptions::kCryptAuthV2DeviceActivityStatusName,
      flag_descriptions::kCryptAuthV2DeviceActivityStatusDescription, kOsCrOS,
diff --git a/chrome/browser/browser_commands_unittest.cc b/chrome/browser/browser_commands_unittest.cc
index fdc962a..139e2d7 100644
--- a/chrome/browser/browser_commands_unittest.cc
+++ b/chrome/browser/browser_commands_unittest.cc
@@ -503,18 +503,34 @@
   }
 }
 
-TEST_F(BrowserCommandsTest, TabSearchDisabled) {
-  EXPECT_FALSE(chrome::IsCommandEnabled(browser(), IDC_TAB_SEARCH));
-  EXPECT_FALSE(chrome::IsCommandEnabled(browser(), IDC_TAB_SEARCH_CLOSE));
+class TabSearchCommandTest : public BrowserCommandsTest,
+                             public ::testing::WithParamInterface<bool> {
+ public:
+  void SetUp() override {
+    if (GetParam()) {
+      scoped_feature_list_.InitWithFeatures({features::kTabSearch}, {});
+    } else {
+      scoped_feature_list_.InitWithFeatures({}, {features::kTabSearch});
+    }
+    BrowserCommandsTest::SetUp();
+  }
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+TEST_P(TabSearchCommandTest, TabSearchCommandStatus) {
+  if (base::FeatureList::IsEnabled(features::kTabSearch)) {
+    EXPECT_TRUE(chrome::IsCommandEnabled(browser(), IDC_TAB_SEARCH));
+    EXPECT_TRUE(chrome::IsCommandEnabled(browser(), IDC_TAB_SEARCH_CLOSE));
+  } else {
+    EXPECT_FALSE(chrome::IsCommandEnabled(browser(), IDC_TAB_SEARCH));
+    EXPECT_FALSE(chrome::IsCommandEnabled(browser(), IDC_TAB_SEARCH_CLOSE));
+  }
 }
 
-TEST_F(BrowserCommandsTest, TabSearchEnabled) {
-  base::test::ScopedFeatureList feature_list;
-  feature_list.InitAndEnableFeature(features::kTabSearch);
-  auto browser =
-      CreateBrowser(profile(), Browser::TYPE_NORMAL, false, window());
-  EXPECT_TRUE(chrome::IsCommandEnabled(browser.get(), IDC_TAB_SEARCH));
-  EXPECT_TRUE(chrome::IsCommandEnabled(browser.get(), IDC_TAB_SEARCH_CLOSE));
-}
+INSTANTIATE_TEST_SUITE_P(All,
+                         TabSearchCommandTest,
+                         ::testing::Values(true, false));
 
 }  // namespace
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index c744f28..aa521b5 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -3489,6 +3489,7 @@
     "chrome_content_browser_client_chromeos_part_unittest.cc",
     "concierge_helper_service_unittest.cc",
     "crosapi/account_manager_ash_unittest.cc",
+    "crosapi/browser_loader_unittest.cc",
     "crosapi/browser_util_unittest.cc",
     "crosapi/message_center_ash_unittest.cc",
     "crosapi/metrics_reporting_ash_unittest.cc",
diff --git a/chrome/browser/chromeos/cert_provisioning/cert_provisioning_worker.cc b/chrome/browser/chromeos/cert_provisioning/cert_provisioning_worker.cc
index ed81e120..485aca8 100644
--- a/chrome/browser/chromeos/cert_provisioning/cert_provisioning_worker.cc
+++ b/chrome/browser/chromeos/cert_provisioning/cert_provisioning_worker.cc
@@ -70,6 +70,10 @@
       *output_algo =
           chromeos::platform_keys::HashAlgorithm::HASH_ALGORITHM_SHA256;
       return true;
+    case em::HashingAlgorithm::NO_HASH:
+      *output_algo =
+          chromeos::platform_keys::HashAlgorithm::HASH_ALGORITHM_NONE;
+      return true;
     case em::HashingAlgorithm::HASHING_ALGORITHM_UNSPECIFIED:
       return false;
   }
@@ -566,6 +570,15 @@
     return;
   }
 
+  if (hashing_algorithm_ ==
+      chromeos::platform_keys::HashAlgorithm::HASH_ALGORITHM_NONE) {
+    platform_keys_service_->SignRSAPKCS1Raw(
+        GetPlatformKeysTokenId(cert_scope_), csr_, public_key_,
+        base::BindRepeating(&CertProvisioningWorkerImpl::OnSignCsrDone,
+                            weak_factory_.GetWeakPtr(),
+                            base::TimeTicks::Now()));
+    return;
+  }
   platform_keys_service_->SignRSAPKCS1Digest(
       GetPlatformKeysTokenId(cert_scope_), csr_, public_key_,
       hashing_algorithm_.value(),
diff --git a/chrome/browser/chromeos/cert_provisioning/cert_provisioning_worker_unittest.cc b/chrome/browser/chromeos/cert_provisioning/cert_provisioning_worker_unittest.cc
index 14202f6..24bb88a 100644
--- a/chrome/browser/chromeos/cert_provisioning/cert_provisioning_worker_unittest.cc
+++ b/chrome/browser/chromeos/cert_provisioning/cert_provisioning_worker_unittest.cc
@@ -99,9 +99,6 @@
 constexpr char kCertScopeStrDevice[] = "google/chromeos/device";
 constexpr char kInvalidationTopic[] = "fake_invalidation_topic_1";
 constexpr char kDataToSign[] = "fake_data_to_sign_1";
-constexpr em::HashingAlgorithm kProtoHashAlgo = em::HashingAlgorithm::SHA256;
-constexpr platform_keys::HashAlgorithm kPkHashAlgo =
-    platform_keys::HashAlgorithm::HASH_ALGORITHM_SHA256;
 constexpr char kChallenge[] = "fake_va_challenge_1";
 constexpr char kChallengeResponse[] = "fake_va_challenge_response_1";
 constexpr char kSignature[] = "fake_signature_1";
@@ -162,7 +159,7 @@
         .WillOnce(RunOnceCallback<0>(register_key_result));               \
   }
 
-#define EXPECT_START_CSR_OK(START_CSR_FUNC)                           \
+#define EXPECT_START_CSR_OK(START_CSR_FUNC, HASHING_ALGO)             \
   {                                                                   \
     EXPECT_CALL(cloud_policy_client_, START_CSR_FUNC)                 \
         .Times(1)                                                     \
@@ -170,10 +167,10 @@
             policy::DeviceManagementStatus::DM_STATUS_SUCCESS,        \
             /*response_error=*/base::nullopt,                         \
             /*try_again_later_ms=*/base::nullopt, kInvalidationTopic, \
-            kChallenge, kProtoHashAlgo, kDataToSign));                \
+            kChallenge, HASHING_ALGO, kDataToSign));                  \
   }
 
-#define EXPECT_START_CSR_OK_WITHOUT_VA(START_CSR_FUNC)                \
+#define EXPECT_START_CSR_OK_WITHOUT_VA(START_CSR_FUNC, HASHING_ALGO)  \
   {                                                                   \
     EXPECT_CALL(cloud_policy_client_, START_CSR_FUNC)                 \
         .Times(1)                                                     \
@@ -181,7 +178,7 @@
             policy::DeviceManagementStatus::DM_STATUS_SUCCESS,        \
             /*response_error=*/base::nullopt,                         \
             /*try_again_later_ms=*/base::nullopt, kInvalidationTopic, \
-            /*va_challenge=*/"", kProtoHashAlgo, kDataToSign));       \
+            /*va_challenge=*/"", HASHING_ALGO, kDataToSign));         \
   }
 
 #define EXPECT_START_CSR_TRY_LATER(START_CSR_FUNC, DELAY_MS)       \
@@ -318,6 +315,14 @@
             RunOnceCallback<4>(kSignature, platform_keys::Status::kSuccess)); \
   }
 
+#define EXPECT_SIGN_RSAPKC1_RAW_OK(SIGN_FUNC)                                 \
+  {                                                                           \
+    EXPECT_CALL(*platform_keys_service_, SIGN_FUNC)                           \
+        .Times(1)                                                             \
+        .WillOnce(                                                            \
+            RunOnceCallback<3>(kSignature, platform_keys::Status::kSuccess)); \
+  }
+
 #define EXPECT_SIGN_RSAPKC1_DIGEST_FAIL(SIGN_FUNC)                            \
   {                                                                           \
     EXPECT_CALL(*platform_keys_service_, SIGN_FUNC)                           \
@@ -492,9 +497,11 @@
                             /*callback=*/_));
     EXPECT_CALL(state_change_callback_observer_, StateChangeCallback());
 
-    EXPECT_START_CSR_OK(ClientCertProvisioningStartCsr(
-        kCertScopeStrUser, kCertProfileId, kCertProfileVersion, GetPublicKey(),
-        /*callback=*/_));
+    EXPECT_START_CSR_OK(
+        ClientCertProvisioningStartCsr(kCertScopeStrUser, kCertProfileId,
+                                       kCertProfileVersion, GetPublicKey(),
+                                       /*callback=*/_),
+        em::HashingAlgorithm::SHA256);
     EXPECT_CALL(state_change_callback_observer_, StateChangeCallback());
 
     EXPECT_CALL(*mock_invalidator, Register(kInvalidationTopic, _)).Times(1);
@@ -520,7 +527,8 @@
 
     EXPECT_SIGN_RSAPKC1_DIGEST_OK(SignRSAPKCS1Digest(
         ::testing::Optional(platform_keys::TokenId::kUser), kDataToSign,
-        GetPublicKey(), kPkHashAlgo, /*callback=*/_));
+        GetPublicKey(), platform_keys::HashAlgorithm::HASH_ALGORITHM_SHA256,
+        /*callback=*/_));
     EXPECT_CALL(state_change_callback_observer_, StateChangeCallback());
 
     EXPECT_FINISH_CSR_OK(ClientCertProvisioningFinishCsr(
@@ -582,9 +590,11 @@
         .WillOnce(RunOnceCallback<2>(GetPublicKey(),
                                      platform_keys::Status::kSuccess));
 
-    EXPECT_START_CSR_OK_WITHOUT_VA(ClientCertProvisioningStartCsr(
-        kCertScopeStrUser, kCertProfileId, kCertProfileVersion, GetPublicKey(),
-        /*callback=*/_));
+    EXPECT_START_CSR_OK_WITHOUT_VA(
+        ClientCertProvisioningStartCsr(kCertScopeStrUser, kCertProfileId,
+                                       kCertProfileVersion, GetPublicKey(),
+                                       /*callback=*/_),
+        em::HashingAlgorithm::SHA256);
 
     EXPECT_CALL(
         *key_permissions_manager_,
@@ -598,7 +608,8 @@
 
     EXPECT_SIGN_RSAPKC1_DIGEST_OK(SignRSAPKCS1Digest(
         ::testing::Optional(platform_keys::TokenId::kUser), kDataToSign,
-        GetPublicKey(), kPkHashAlgo, /*callback=*/_));
+        GetPublicKey(), platform_keys::HashAlgorithm::HASH_ALGORITHM_SHA256,
+        /*callback=*/_));
 
     EXPECT_FINISH_CSR_OK(ClientCertProvisioningFinishCsr(
         kCertScopeStrUser, kCertProfileId, kCertProfileVersion, GetPublicKey(),
@@ -619,6 +630,88 @@
   worker.DoStep();
 }
 
+// Checks that the worker correctly forwards a request with
+// hashing_algorithm=NO_HASH to platform_keys.
+TEST_F(CertProvisioningWorkerTest, NoHashInStartCsr) {
+  CertProfile cert_profile(kCertProfileId, kCertProfileName,
+                           kCertProfileVersion,
+                           /*is_va_enabled=*/true, kCertProfileRenewalPeriod);
+
+  MockTpmChallengeKeySubtle* mock_tpm_challenge_key = PrepareTpmChallengeKey();
+  MockCertProvisioningInvalidator* mock_invalidator = nullptr;
+  CertProvisioningWorkerImpl worker(
+      CertScope::kUser, GetProfile(), &testing_pref_service_, cert_profile,
+      &cloud_policy_client_, MakeInvalidator(&mock_invalidator),
+      GetStateChangeCallback(), GetResultCallback());
+
+  {
+    testing::InSequence seq;
+
+    EXPECT_PREPARE_KEY_OK(
+        *mock_tpm_challenge_key,
+        StartPrepareKeyStep(attestation::AttestationKeyType::KEY_USER,
+                            /*will_register_key=*/true,
+                            GetKeyName(kCertProfileId),
+                            /*profile=*/_,
+                            /*callback=*/_));
+    EXPECT_CALL(state_change_callback_observer_, StateChangeCallback());
+
+    EXPECT_START_CSR_OK(
+        ClientCertProvisioningStartCsr(kCertScopeStrUser, kCertProfileId,
+                                       kCertProfileVersion, GetPublicKey(),
+                                       /*callback=*/_),
+        em::HashingAlgorithm::NO_HASH);
+    EXPECT_CALL(state_change_callback_observer_, StateChangeCallback());
+
+    EXPECT_CALL(*mock_invalidator, Register(kInvalidationTopic, _)).Times(1);
+
+    EXPECT_SIGN_CHALLENGE_OK(*mock_tpm_challenge_key,
+                             StartSignChallengeStep(kChallenge,
+                                                    /*callback=*/_));
+    EXPECT_CALL(state_change_callback_observer_, StateChangeCallback());
+
+    EXPECT_REGISTER_KEY_OK(*mock_tpm_challenge_key, StartRegisterKeyStep);
+    EXPECT_CALL(state_change_callback_observer_, StateChangeCallback());
+
+    EXPECT_CALL(
+        *key_permissions_manager_,
+        AllowKeyForUsage(/*callback=*/_, platform_keys::KeyUsage::kCorporate,
+                         GetPublicKey()));
+
+    EXPECT_SET_ATTRIBUTE_FOR_KEY_OK(SetAttributeForKey(
+        platform_keys::TokenId::kUser, GetPublicKey(),
+        platform_keys::KeyAttributeType::kCertificateProvisioningId,
+        kCertProfileId, _));
+    EXPECT_CALL(state_change_callback_observer_, StateChangeCallback());
+
+    EXPECT_SIGN_RSAPKC1_RAW_OK(
+        SignRSAPKCS1Raw(::testing::Optional(platform_keys::TokenId::kUser),
+                        kDataToSign, GetPublicKey(), /*callback=*/_));
+    EXPECT_CALL(state_change_callback_observer_, StateChangeCallback());
+
+    EXPECT_FINISH_CSR_OK(ClientCertProvisioningFinishCsr(
+        kCertScopeStrUser, kCertProfileId, kCertProfileVersion, GetPublicKey(),
+        kChallengeResponse, kSignature, /*callback=*/_));
+    EXPECT_CALL(state_change_callback_observer_, StateChangeCallback());
+
+    EXPECT_DOWNLOAD_CERT_OK(ClientCertProvisioningDownloadCert(
+        kCertScopeStrUser, kCertProfileId, kCertProfileVersion, GetPublicKey(),
+        /*callback=*/_));
+
+    EXPECT_IMPORT_CERTIFICATE_OK(ImportCertificate(
+        platform_keys::TokenId::kUser, /*certificate=*/_, /*callback=*/_));
+    EXPECT_CALL(state_change_callback_observer_, StateChangeCallback());
+
+    EXPECT_CALL(*mock_invalidator, Unregister()).Times(1);
+
+    EXPECT_CALL(callback_observer_,
+                Callback(cert_profile, CertProvisioningWorkerState::kSucceeded))
+        .Times(1);
+  }
+
+  worker.DoStep();
+}
+
 // Checks that when the server returns try_again_later field, the worker will
 // retry a request when it asked to continue the provisioning.
 TEST_F(CertProvisioningWorkerTest, TryLaterManualRetry) {
@@ -663,7 +756,8 @@
     EXPECT_START_CSR_OK(
         ClientCertProvisioningStartCsr(kCertScopeStrDevice, kCertProfileId,
                                        kCertProfileVersion, GetPublicKey(),
-                                       /*callback=*/_));
+                                       /*callback=*/_),
+        em::HashingAlgorithm::SHA256);
 
     EXPECT_SIGN_CHALLENGE_OK(*mock_tpm_challenge_key,
                              StartSignChallengeStep(kChallenge,
@@ -777,9 +871,11 @@
   {
     testing::InSequence seq;
 
-    EXPECT_START_CSR_OK(ClientCertProvisioningStartCsr(
-        kCertScopeStrUser, kCertProfileId, kCertProfileVersion, GetPublicKey(),
-        /*callback=*/_));
+    EXPECT_START_CSR_OK(
+        ClientCertProvisioningStartCsr(kCertScopeStrUser, kCertProfileId,
+                                       kCertProfileVersion, GetPublicKey(),
+                                       /*callback=*/_),
+        em::HashingAlgorithm::SHA256);
 
     EXPECT_SIGN_CHALLENGE_OK(*mock_tpm_challenge_key,
                              StartSignChallengeStep(kChallenge,
@@ -799,7 +895,8 @@
 
     EXPECT_SIGN_RSAPKC1_DIGEST_OK(SignRSAPKCS1Digest(
         ::testing::Optional(platform_keys::TokenId::kUser), kDataToSign,
-        GetPublicKey(), kPkHashAlgo, /*callback=*/_));
+        GetPublicKey(), platform_keys::HashAlgorithm::HASH_ALGORITHM_SHA256,
+        /*callback=*/_));
 
     EXPECT_FINISH_CSR_TRY_LATER(
         ClientCertProvisioningFinishCsr(
@@ -898,9 +995,11 @@
   {
     testing::InSequence seq;
 
-    EXPECT_START_CSR_OK(ClientCertProvisioningStartCsr(
-        kCertScopeStrUser, kCertProfileId, kCertProfileVersion, GetPublicKey(),
-        /*callback=*/_));
+    EXPECT_START_CSR_OK(
+        ClientCertProvisioningStartCsr(kCertScopeStrUser, kCertProfileId,
+                                       kCertProfileVersion, GetPublicKey(),
+                                       /*callback=*/_),
+        em::HashingAlgorithm::SHA256);
     EXPECT_CALL(*mock_invalidator, Register(kInvalidationTopic, _))
         .WillOnce(SaveArg<1>(&on_invalidation_callback));
 
@@ -922,7 +1021,8 @@
 
     EXPECT_SIGN_RSAPKC1_DIGEST_OK(SignRSAPKCS1Digest(
         ::testing::Optional(platform_keys::TokenId::kUser), kDataToSign,
-        GetPublicKey(), kPkHashAlgo, /*callback=*/_));
+        GetPublicKey(), platform_keys::HashAlgorithm::HASH_ALGORITHM_SHA256,
+        /*callback=*/_));
 
     EXPECT_FINISH_CSR_TRY_LATER(
         ClientCertProvisioningFinishCsr(
@@ -1198,9 +1298,11 @@
                             /*profile=*/_,
                             /*callback=*/_));
 
-    EXPECT_START_CSR_OK(ClientCertProvisioningStartCsr(
-        kCertScopeStrUser, kCertProfileId, kCertProfileVersion, GetPublicKey(),
-        /*callback=*/_));
+    EXPECT_START_CSR_OK(
+        ClientCertProvisioningStartCsr(kCertScopeStrUser, kCertProfileId,
+                                       kCertProfileVersion, GetPublicKey(),
+                                       /*callback=*/_),
+        em::HashingAlgorithm::SHA256);
 
     EXPECT_CALL(*mock_invalidator, Register(kInvalidationTopic, _)).Times(1);
 
@@ -1357,9 +1459,11 @@
   {
     testing::InSequence seq;
 
-    EXPECT_START_CSR_OK(ClientCertProvisioningStartCsr(
-        kCertScopeStrUser, kCertProfileId, kCertProfileVersion, GetPublicKey(),
-        /*callback=*/_));
+    EXPECT_START_CSR_OK(
+        ClientCertProvisioningStartCsr(kCertScopeStrUser, kCertProfileId,
+                                       kCertProfileVersion, GetPublicKey(),
+                                       /*callback=*/_),
+        em::HashingAlgorithm::SHA256);
 
     pref_val = ParseJson("{}");
     EXPECT_CALL(pref_observer, OnPrefValueUpdated(IsJson(pref_val))).Times(1);
@@ -1384,7 +1488,8 @@
 
     EXPECT_SIGN_RSAPKC1_DIGEST_OK(SignRSAPKCS1Digest(
         ::testing::Optional(platform_keys::TokenId::kUser), kDataToSign,
-        GetPublicKey(), kPkHashAlgo, /*callback=*/_));
+        GetPublicKey(), platform_keys::HashAlgorithm::HASH_ALGORITHM_SHA256,
+        /*callback=*/_));
 
     EXPECT_FINISH_CSR_OK(ClientCertProvisioningFinishCsr(
         kCertScopeStrUser, kCertProfileId, kCertProfileVersion, GetPublicKey(),
diff --git a/chrome/browser/chromeos/crosapi/browser_loader.cc b/chrome/browser/chromeos/crosapi/browser_loader.cc
index a6693f7..f586d832 100644
--- a/chrome/browser/chromeos/crosapi/browser_loader.cc
+++ b/chrome/browser/chromeos/crosapi/browser_loader.cc
@@ -8,15 +8,19 @@
 
 #include "base/bind.h"
 #include "base/command_line.h"
+#include "base/feature_list.h"
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
 #include "base/logging.h"
 #include "base/task/task_traits.h"
 #include "base/task/thread_pool.h"
+#include "chrome/browser/browser_process.h"
 #include "chrome/browser/chromeos/crosapi/browser_util.h"
+#include "chrome/browser/ui/ash/system_tray_client.h"
 #include "chromeos/constants/chromeos_features.h"
 #include "chromeos/constants/chromeos_switches.h"
 #include "chromeos/cryptohome/system_salt_getter.h"
+#include "components/component_updater/component_updater_service.h"
 
 namespace crosapi {
 
@@ -31,22 +35,52 @@
 const base::Feature kLacrosPreferDogfoodOverFishfood{
     "LacrosPreferDogfoodOverFishfood", base::FEATURE_DISABLED_BY_DEFAULT};
 
-std::string GetLacrosComponentName() {
+// Emergency kill switch in case the notification code doesn't work properly.
+const base::Feature kLacrosShowUpdateNotifications{
+    "LacrosShowUpdateNotifications", base::FEATURE_ENABLED_BY_DEFAULT};
+
+struct ComponentInfo {
+  // The client-side component name.
+  const char* const name;
+  // The CRX "extension" ID for component updater.
+  // Must match the Omaha console.
+  const char* const crx_id;
+};
+
+// NOTE: If you change the lacros component names, you must also update
+// chrome/browser/component_updater/cros_component_installer_chromeos.cc
+constexpr ComponentInfo kLacrosFishfoodInfo = {
+    "lacros-fishfood", "hkifppleldbgkdlijbdfkdpedggaopda"};
+constexpr ComponentInfo kLacrosDogfoodDevInfo = {
+    "lacros-dogfood-dev", "ldobopbhiamakmncndpkeelenhdmgfhk"};
+constexpr ComponentInfo kLacrosDogfoodStableInfo = {
+    "lacros-dogfood-stable", "hnfmbeciphpghlfgpjfbcdifbknombnk"};
+
+ComponentInfo GetLacrosComponentInfo() {
   if (!base::FeatureList::IsEnabled(kLacrosPreferDogfoodOverFishfood))
-    return "lacros-fishfood";
+    return kLacrosFishfoodInfo;
 
   const base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess();
   if (cmdline->HasSwitch(browser_util::kLacrosStabilitySwitch)) {
     std::string value =
         cmdline->GetSwitchValueASCII(browser_util::kLacrosStabilitySwitch);
     if (value == browser_util::kLacrosStabilityLessStable) {
-      return "lacros-dogfood-dev";
+      return kLacrosDogfoodDevInfo;
     } else if (value == browser_util::kLacrosStabilityMoreStable) {
-      return "lacros-dogfood-stable";
+      return kLacrosDogfoodStableInfo;
     }
   }
   // Use more frequent updates by default.
-  return "lacros-dogfood-dev";
+  return kLacrosDogfoodDevInfo;
+}
+
+std::string GetLacrosComponentName() {
+  return GetLacrosComponentInfo().name;
+}
+
+// Returns the CRX "extension" ID for a lacros component.
+std::string GetLacrosComponentCrxId() {
+  return GetLacrosComponentInfo().crx_id;
 }
 
 // Returns whether lacros-fishfood component is already installed.
@@ -67,15 +101,46 @@
   return true;
 }
 
+// Production delegate implementation.
+class DelegateImpl : public BrowserLoader::Delegate {
+ public:
+  DelegateImpl() = default;
+  DelegateImpl(const DelegateImpl&) = delete;
+  DelegateImpl& operator=(const DelegateImpl&) = delete;
+  ~DelegateImpl() override = default;
+
+  // BrowserLoader::Delegate:
+  void SetLacrosUpdateAvailable() override {
+    if (base::FeatureList::IsEnabled(kLacrosShowUpdateNotifications)) {
+      // Show the update notification in ash.
+      SystemTrayClient::Get()->SetLacrosUpdateAvailable();
+    }
+  }
+};
+
 }  // namespace
 
 BrowserLoader::BrowserLoader(
     scoped_refptr<component_updater::CrOSComponentManager> manager)
-    : component_manager_(manager) {
+    : BrowserLoader(std::make_unique<DelegateImpl>(), manager) {}
+
+BrowserLoader::BrowserLoader(
+    std::unique_ptr<Delegate> delegate,
+    scoped_refptr<component_updater::CrOSComponentManager> manager)
+    : delegate_(std::move(delegate)),
+      component_manager_(manager),
+      component_update_service_(g_browser_process->component_updater()) {
+  DCHECK(delegate_);
   DCHECK(component_manager_);
 }
 
-BrowserLoader::~BrowserLoader() = default;
+BrowserLoader::~BrowserLoader() {
+  // May be null in tests.
+  if (component_update_service_) {
+    // Removing an observer is a no-op if the observer wasn't added.
+    component_update_service_->RemoveObserver(this);
+  }
+}
 
 void BrowserLoader::Load(LoadCompletionCallback callback) {
   DCHECK(browser_util::IsLacrosEnabled());
@@ -113,19 +178,35 @@
                      weak_factory_.GetWeakPtr()));
 }
 
+void BrowserLoader::OnEvent(Events event, const std::string& id) {
+  // Check for the Lacros component being updated.
+  if (event == Events::COMPONENT_UPDATED && id == GetLacrosComponentCrxId()) {
+    delegate_->SetLacrosUpdateAvailable();
+  }
+}
+
 void BrowserLoader::OnLoadComplete(
     LoadCompletionCallback callback,
     component_updater::CrOSComponentManager::Error error,
     const base::FilePath& path) {
-  bool success =
-      (error == component_updater::CrOSComponentManager::Error::NONE);
-  if (success) {
-    LOG(WARNING) << "Loaded lacros image at " << path.MaybeAsASCII();
-  } else {
+  // Bail out on error.
+  if (error != component_updater::CrOSComponentManager::Error::NONE) {
     LOG(WARNING) << "Error loading lacros component image: "
                  << static_cast<int>(error);
+    std::move(callback).Run(base::FilePath());
+    return;
   }
-  std::move(callback).Run(success ? path : base::FilePath());
+  // Log the path on success.
+  LOG(WARNING) << "Loaded lacros image at " << path.MaybeAsASCII();
+  std::move(callback).Run(path);
+
+  // May be null in tests.
+  if (component_update_service_) {
+    // Now that we have the initial component download, start observing for
+    // future updates. We don't do this in the constructor because we don't want
+    // to show the "update available" notification for the initial load.
+    component_update_service_->AddObserver(this);
+  }
 }
 
 void BrowserLoader::OnCheckInstalled(bool was_installed) {
diff --git a/chrome/browser/chromeos/crosapi/browser_loader.h b/chrome/browser/chromeos/crosapi/browser_loader.h
index d06d065..296263cc 100644
--- a/chrome/browser/chromeos/crosapi/browser_loader.h
+++ b/chrome/browser/chromeos/crosapi/browser_loader.h
@@ -10,20 +10,34 @@
 #include "base/memory/scoped_refptr.h"
 #include "base/memory/weak_ptr.h"
 #include "chrome/browser/component_updater/cros_component_manager.h"
+#include "components/component_updater/component_updater_service.h"
 
 namespace crosapi {
 
-// Manages download of the lacros-chrome binary. This class is a part of
-// ash-chrome.
-class BrowserLoader {
+// Manages download of the lacros-chrome binary. After the initial component is
+// downloaded and mounted, observes the component updater for future updates.
+// If it detects a new update, triggers a user-visible notification.
+// This class is a part of ash-chrome.
+class BrowserLoader
+    : public component_updater::ComponentUpdateService::Observer {
  public:
+  // Delete for testing.
+  class Delegate {
+   public:
+    virtual void SetLacrosUpdateAvailable() = 0;
+    virtual ~Delegate() = default;
+  };
+  // Contructor for production.
   explicit BrowserLoader(
       scoped_refptr<component_updater::CrOSComponentManager> manager);
+  // Constructor for testing.
+  BrowserLoader(std::unique_ptr<Delegate> delegate,
+                scoped_refptr<component_updater::CrOSComponentManager> manager);
 
   BrowserLoader(const BrowserLoader&) = delete;
   BrowserLoader& operator=(const BrowserLoader&) = delete;
 
-  ~BrowserLoader();
+  ~BrowserLoader() override;
 
   // Starts to load lacros-chrome binary.
   // |callback| is called on completion with the path to the lacros-chrome on
@@ -36,6 +50,9 @@
   // Note that this triggers to remove the user directory for lacros-chrome.
   void Unload();
 
+  // component_updater::ComponentUpdateService::Observer:
+  void OnEvent(Events event, const std::string& id) override;
+
  private:
   // Called on the completion of loading.
   void OnLoadComplete(LoadCompletionCallback callback,
@@ -49,9 +66,14 @@
   // Unloads the component. Called after system salt is available.
   void UnloadAfterCleanUp(const std::string& ignored_salt);
 
-  // May be null in tests.
+  // Allows stubbing out some methods for testing.
+  std::unique_ptr<Delegate> delegate_;
+
   scoped_refptr<component_updater::CrOSComponentManager> component_manager_;
 
+  // May be null in tests.
+  component_updater::ComponentUpdateService* const component_update_service_;
+
   base::WeakPtrFactory<BrowserLoader> weak_factory_{this};
 };
 
diff --git a/chrome/browser/chromeos/crosapi/browser_loader_unittest.cc b/chrome/browser/chromeos/crosapi/browser_loader_unittest.cc
new file mode 100644
index 0000000..ea66094
--- /dev/null
+++ b/chrome/browser/chromeos/crosapi/browser_loader_unittest.cc
@@ -0,0 +1,102 @@
+// 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/crosapi/browser_loader.h"
+
+#include "base/run_loop.h"
+#include "base/test/bind.h"
+#include "base/test/scoped_feature_list.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/chromeos/crosapi/browser_util.h"
+#include "chrome/browser/component_updater/fake_cros_component_manager.h"
+#include "chrome/test/base/browser_process_platform_part_test_api_chromeos.h"
+#include "chromeos/constants/chromeos_features.h"
+#include "components/update_client/update_client.h"
+#include "content/public/test/browser_task_environment.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using update_client::UpdateClient;
+
+namespace crosapi {
+namespace {
+
+// Delegate for testing.
+class DelegateImpl : public BrowserLoader::Delegate {
+ public:
+  DelegateImpl() = default;
+  DelegateImpl(const DelegateImpl&) = delete;
+  DelegateImpl& operator=(const DelegateImpl&) = delete;
+  ~DelegateImpl() override = default;
+
+  // BrowserLoader::Delegate:
+  void SetLacrosUpdateAvailable() override { ++set_lacros_update_available_; }
+
+  // Public because this is test code.
+  int set_lacros_update_available_ = 0;
+};
+
+class BrowserLoaderTest : public testing::Test {
+ public:
+  BrowserLoaderTest() { browser_util::SetLacrosEnabledForTest(true); }
+
+  ~BrowserLoaderTest() override {
+    browser_util::SetLacrosEnabledForTest(false);
+  }
+
+  // Public because this is test code.
+  content::BrowserTaskEnvironment task_environment_;
+};
+
+TEST_F(BrowserLoaderTest, ShowUpdateNotification) {
+  // Create dependencies for object under test.
+  scoped_refptr<component_updater::FakeCrOSComponentManager> component_manager =
+      base::MakeRefCounted<component_updater::FakeCrOSComponentManager>();
+  component_manager->set_supported_components({"lacros-fishfood"});
+  component_manager->ResetComponentState(
+      "lacros-fishfood",
+      component_updater::FakeCrOSComponentManager::ComponentInfo(
+          component_updater::CrOSComponentManager::Error::NONE,
+          base::FilePath("/install/path"), base::FilePath("/mount/path")));
+  BrowserProcessPlatformPartTestApi browser_part(
+      g_browser_process->platform_part());
+  browser_part.InitializeCrosComponentManager(component_manager);
+
+  // Create object under test.
+  auto delegate_ptr = std::make_unique<DelegateImpl>();
+  DelegateImpl* delegate = delegate_ptr.get();
+  BrowserLoader browser_loader(std::move(delegate_ptr), component_manager);
+
+  // Creating the loader does not trigger an update notification.
+  EXPECT_EQ(0, delegate->set_lacros_update_available_);
+
+  // The initial load of the component does not trigger an update notification.
+  base::RunLoop run_loop;
+  browser_loader.Load(base::BindLambdaForTesting(
+      [&](const base::FilePath&) { run_loop.Quit(); }));
+  run_loop.Run();
+  EXPECT_EQ(0, delegate->set_lacros_update_available_);
+
+  // Update check does not trigger an update notification.
+  constexpr char kLacrosFishfoodId[] = "hkifppleldbgkdlijbdfkdpedggaopda";
+  browser_loader.OnEvent(
+      UpdateClient::Observer::Events::COMPONENT_CHECKING_FOR_UPDATES,
+      kLacrosFishfoodId);
+  EXPECT_EQ(0, delegate->set_lacros_update_available_);
+
+  // Update download does not trigger an update notification.
+  browser_loader.OnEvent(
+      UpdateClient::Observer::Events::COMPONENT_UPDATE_DOWNLOADING,
+      kLacrosFishfoodId);
+  EXPECT_EQ(0, delegate->set_lacros_update_available_);
+
+  // Update completion trigger the notification.
+  browser_loader.OnEvent(UpdateClient::Observer::Events::COMPONENT_UPDATED,
+                         kLacrosFishfoodId);
+  EXPECT_EQ(1, delegate->set_lacros_update_available_);
+
+  browser_part.ShutdownCrosComponentManager();
+}
+
+}  // namespace
+}  // namespace crosapi
diff --git a/chrome/browser/chromeos/crosapi/browser_util.cc b/chrome/browser/chromeos/crosapi/browser_util.cc
index 4542404..47b3046 100644
--- a/chrome/browser/chromeos/crosapi/browser_util.cc
+++ b/chrome/browser/chromeos/crosapi/browser_util.cc
@@ -48,6 +48,8 @@
 namespace browser_util {
 namespace {
 
+bool g_lacros_enabled_for_test = false;
+
 // Some account types require features that aren't yet supported by lacros.
 // See https://crbug.com/1080693
 bool IsUserTypeAllowed(const User* user) {
@@ -135,6 +137,11 @@
 }
 
 bool IsLacrosEnabled(Channel channel) {
+  // Allows tests to avoid enabling the flag, constructing a fake user manager,
+  // creating g_browser_process->local_state(), etc.
+  if (g_lacros_enabled_for_test)
+    return true;
+
   if (!base::FeatureList::IsEnabled(chromeos::features::kLacrosSupport)) {
     LOG(WARNING) << "Lacros-chrome is not supported";
     return false;
@@ -175,6 +182,10 @@
   }
 }
 
+void SetLacrosEnabledForTest(bool force_enabled) {
+  g_lacros_enabled_for_test = force_enabled;
+}
+
 bool IsLacrosWindow(const aura::Window* window) {
   const std::string* app_id = exo::GetShellApplicationId(window);
   if (!app_id)
diff --git a/chrome/browser/chromeos/crosapi/browser_util.h b/chrome/browser/chromeos/crosapi/browser_util.h
index fa7b983f..668b272 100644
--- a/chrome/browser/chromeos/crosapi/browser_util.h
+++ b/chrome/browser/chromeos/crosapi/browser_util.h
@@ -60,6 +60,9 @@
 // As above, but takes a channel. Exposed for testing.
 bool IsLacrosEnabled(version_info::Channel channel);
 
+// Forces IsLacrosEnabled() to return true for testing.
+void SetLacrosEnabledForTest(bool force_enabled);
+
 // Returns true if |window| is an exo ShellSurface window representing a Lacros
 // browser.
 bool IsLacrosWindow(const aura::Window* window);
diff --git a/chrome/browser/chromeos/crostini/termina_installer.cc b/chrome/browser/chromeos/crostini/termina_installer.cc
index cd8d6de..7a37f262 100644
--- a/chrome/browser/chromeos/crostini/termina_installer.cc
+++ b/chrome/browser/chromeos/crostini/termina_installer.cc
@@ -137,6 +137,12 @@
     LOG(ERROR)
         << "Failed to install the cros-termina component with error code: "
         << static_cast<int>(error);
+
+    if (error ==
+        component_updater::CrOSComponentManager::Error::MOUNT_FAILURE) {
+      ReinstallComponent(std::move(callback));
+      return;
+    }
     if (is_update_checked) {
       scoped_refptr<component_updater::CrOSComponentManager> component_manager =
           g_browser_process->platform_part()->cros_component_manager();
@@ -188,6 +194,38 @@
   std::move(callback).Run(result);
 }
 
+void TerminaInstaller::ReinstallComponent(
+    base::OnceCallback<void(InstallResult)> callback) {
+  scoped_refptr<component_updater::CrOSComponentManager> component_manager =
+      g_browser_process->platform_part()->cros_component_manager();
+  if (component_manager->Unload(imageloader::kTerminaComponentName)) {
+    component_manager->Load(
+        imageloader::kTerminaComponentName,
+        component_updater::CrOSComponentManager::MountPolicy::kMount,
+        UpdatePolicy::kDontForce,
+        base::BindOnce(&TerminaInstaller::OnReinstallComponent,
+                       weak_ptr_factory_.GetWeakPtr(), std::move(callback),
+                       false));
+  } else {
+    std::move(callback).Run(InstallResult::Failure);
+  }
+}
+
+void TerminaInstaller::OnReinstallComponent(
+    base::OnceCallback<void(InstallResult)> callback,
+    bool is_update_checked,
+    component_updater::CrOSComponentManager::Error error,
+    const base::FilePath& path) {
+  LOG(ERROR) << "Attempting to re-install cros-termina component.";
+  if (error != component_updater::CrOSComponentManager::Error::MOUNT_FAILURE) {
+    OnInstallComponent(std::move(callback), is_update_checked, error, path);
+    return;
+  }
+  // Give up with a permanent failure. The newly downloaded component failed to
+  // mount.
+  std::move(callback).Run(InstallResult::Failure);
+}
+
 void TerminaInstaller::Uninstall(base::OnceCallback<void(bool)> callback) {
   // Unset |termina_location_| now since it will become invalid at some point
   // soon.
diff --git a/chrome/browser/chromeos/crostini/termina_installer.h b/chrome/browser/chromeos/crostini/termina_installer.h
index b60676f..742a4de 100644
--- a/chrome/browser/chromeos/crostini/termina_installer.h
+++ b/chrome/browser/chromeos/crostini/termina_installer.h
@@ -67,6 +67,12 @@
                           bool is_update_checked,
                           component_updater::CrOSComponentManager::Error error,
                           const base::FilePath& path);
+  void ReinstallComponent(base::OnceCallback<void(InstallResult)> callback);
+  void OnReinstallComponent(
+      base::OnceCallback<void(InstallResult)> callback,
+      bool is_update_checked,
+      component_updater::CrOSComponentManager::Error error,
+      const base::FilePath& path);
 
   void RemoveComponentIfPresent(base::OnceCallback<void()> callback,
                                 UninstallResult* result);
diff --git a/chrome/browser/chromeos/login/session/user_session_initializer.cc b/chrome/browser/chromeos/login/session/user_session_initializer.cc
index 27ee924..753687d 100644
--- a/chrome/browser/chromeos/login/session/user_session_initializer.cc
+++ b/chrome/browser/chromeos/login/session/user_session_initializer.cc
@@ -30,7 +30,9 @@
 #include "chrome/browser/component_updater/sth_set_component_remover.h"
 #include "chrome/browser/google/google_brand_chromeos.h"
 #include "chrome/browser/net/nss_context.h"
+#include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/ui/ash/clipboard_image_model_factory_impl.h"
+#include "chrome/browser/ui/ash/holding_space/holding_space_keyed_service_factory.h"
 #include "chrome/browser/ui/ash/media_client_impl.h"
 #include "chrome/common/pref_names.h"
 #include "chromeos/constants/chromeos_features.h"
@@ -205,8 +207,14 @@
 }
 
 void UserSessionInitializer::OnUserSessionStarted(bool is_primary_user) {
+  Profile* profile = ProfileManager::GetActiveUserProfile();
+  DCHECK(profile);
+
+  // Ensure that the `HoldingSpaceKeyedService` for `profile` is created.
+  ash::HoldingSpaceKeyedServiceFactory::GetInstance()->GetService(profile);
+
   if (is_primary_user) {
-    DCHECK_NE(primary_profile_, nullptr);
+    DCHECK_EQ(primary_profile_, profile);
 
     plugin_vm::PluginVmManager* plugin_vm_manager =
         plugin_vm::PluginVmManagerFactory::GetForProfile(primary_profile_);
diff --git a/chrome/browser/chromeos/platform_keys/platform_keys.cc b/chrome/browser/chromeos/platform_keys/platform_keys.cc
index 00dffe9..e06264d 100644
--- a/chrome/browser/chromeos/platform_keys/platform_keys.cc
+++ b/chrome/browser/chromeos/platform_keys/platform_keys.cc
@@ -55,6 +55,8 @@
       return "Algorithm not supported.";
     case Status::kErrorCertificateNotFound:
       return "Certificate could not be found.";
+    case Status::kErrorInputTooLong:
+      return "Input too long.";
     case Status::kErrorGrantKeyPermissionForExtension:
       return "Tried to grant permission for a key although prohibited (either "
              "key is a corporate key or this account is managed).";
diff --git a/chrome/browser/chromeos/platform_keys/platform_keys.h b/chrome/browser/chromeos/platform_keys/platform_keys.h
index 66fabc46..880c1cfa 100644
--- a/chrome/browser/chromeos/platform_keys/platform_keys.h
+++ b/chrome/browser/chromeos/platform_keys/platform_keys.h
@@ -43,6 +43,7 @@
   kSuccess,
   kErrorAlgorithmNotSupported,
   kErrorCertificateNotFound,
+  kErrorInputTooLong,
   kErrorGrantKeyPermissionForExtension,
   kErrorInternal,
   kErrorKeyAttributeRetrievalFailed,
diff --git a/chrome/browser/chromeos/platform_keys/platform_keys_service.h b/chrome/browser/chromeos/platform_keys/platform_keys_service.h
index 2d95482..3f1feb5 100644
--- a/chrome/browser/chromeos/platform_keys/platform_keys_service.h
+++ b/chrome/browser/chromeos/platform_keys/platform_keys_service.h
@@ -156,11 +156,12 @@
                                   SignCallback callback) = 0;
 
   // Applies PKCS1 padding and afterwards signs the data with the private key
-  // matching |public_key_spki_der|. |data| is not digested. If the key is not
-  // found in that |token_id| (or in none of the available tokens if |token_id|
-  // is not specified), the operation aborts. The size of |data| (number of
-  // octets) must be smaller than k - 11, where k is the key size in octets.
-  // |callback| will be invoked with the signature or an error status.
+  // matching |public_key_spki_der|. |data| is not digested, PKCS1 DigestInfo is
+  // not prepended. If the key is not found in that |token_id| (or in none of
+  // the available tokens if |token_id| is not specified), the operation aborts.
+  // The size of |data| (number of octets) must be smaller than k - 11, where k
+  // is the key size in octets. |callback| will be invoked with the signature or
+  // an error status.
   virtual void SignRSAPKCS1Raw(base::Optional<TokenId> token_id,
                                const std::string& data,
                                const std::string& public_key_spki_der,
diff --git a/chrome/browser/chromeos/platform_keys/platform_keys_service_browsertest.cc b/chrome/browser/chromeos/platform_keys/platform_keys_service_browsertest.cc
index dfabd7a..8512e375 100644
--- a/chrome/browser/chromeos/platform_keys/platform_keys_service_browsertest.cc
+++ b/chrome/browser/chromeos/platform_keys/platform_keys_service_browsertest.cc
@@ -16,7 +16,10 @@
 #include "base/memory/weak_ptr.h"
 #include "base/optional.h"
 #include "base/run_loop.h"
+#include "base/stl_util.h"
+#include "base/strings/strcat.h"
 #include "base/strings/string_number_conversions.h"
+#include "base/strings/string_piece.h"
 #include "base/strings/stringprintf.h"
 #include "base/task/post_task.h"
 #include "base/threading/thread_restrictions.h"
@@ -47,6 +50,7 @@
 #include "content/public/test/browser_test.h"
 #include "crypto/nss_key_util.h"
 #include "crypto/scoped_nss_types.h"
+#include "crypto/sha2.h"
 #include "crypto/signature_verifier.h"
 #include "net/cert/nss_cert_database.h"
 #include "net/cert/x509_certificate.h"
@@ -95,6 +99,24 @@
   TokenId token_id;
 };
 
+// Returns |hash| prefixed with DER-encoded PKCS#1 DigestInfo with
+// AlgorithmIdentifier=id-sha256.
+// This is useful for testing PlatformKeysService::SignRSAPKCS1Raw which only
+// appends PKCS#1 v1.5 padding before signing.
+std::string PrependSHA256DigestInfo(base::StringPiece hash) {
+  // DER-encoded PKCS#1 DigestInfo "prefix" with
+  // AlgorithmIdentifier=id-sha256.
+  // The encoding is taken from https://tools.ietf.org/html/rfc3447#page-43
+  const uint8_t kDigestInfoSha256DerData[] = {
+      0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01,
+      0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20};
+  const base::StringPiece kDigestInfoSha256Der(
+      reinterpret_cast<const char*>(kDigestInfoSha256DerData),
+      base::size(kDigestInfoSha256DerData));
+
+  return base::StrCat({kDigestInfoSha256Der, hash});
+}
+
 }  // namespace
 
 class PlatformKeysServiceBrowserTestBase
@@ -171,17 +193,23 @@
 
   // Generates a key pair in the given |token_id| using platform keys service
   // and returns the SubjectPublicKeyInfo string encoded in DER format.
-  std::string GenerateKeyPair(TokenId token_id) {
-    const unsigned int kKeySize = 2048;
-
+  std::string GenerateKeyPair(TokenId token_id, unsigned int key_size) {
     test_util::GenerateKeyExecutionWaiter generate_key_waiter;
-    platform_keys_service()->GenerateRSAKey(token_id, kKeySize,
+    platform_keys_service()->GenerateRSAKey(token_id, key_size,
                                             generate_key_waiter.GetCallback());
     generate_key_waiter.Wait();
 
     return generate_key_waiter.public_key_spki_der();
   }
 
+  // Generates a key pair with a default size in the given |token_id| using
+  // platform keys service and returns the SubjectPublicKeyInfo string encoded
+  // in DER format.
+  std::string GenerateKeyPair(TokenId token_id) {
+    const unsigned int kDefaultKeySize = 2048;
+    return GenerateKeyPair(token_id, kDefaultKeySize);
+  }
+
   // Imports the certificate and key described by the |cert_filename| and
   // |key_filename| files in |source_dir| into the Token |token_id|, then stores
   // the resulting certificate in *|out_cert| and the SPKI of the public key in
@@ -426,6 +454,79 @@
   EXPECT_TRUE(signature_verifier.VerifyFinal());
 }
 
+// Generates a Rsa key pair and tests signing using the SignRSAPKCS1Raw
+// function.
+IN_PROC_BROWSER_TEST_P(PlatformKeysServicePerTokenBrowserTest,
+                       GenerateRsaAndSignRaw) {
+  const unsigned int kKeySize = 2048;
+  const TokenId token_id = GetParam().token_id;
+
+  // SignRSAPKCS1Raw only performs PKCS#1.5 padding. To get a correct PKCS#1
+  // signature of |kDataToSign|, it is necessary to pass
+  // (DigestInfo + hash(kDataToSign)) to SignRSAPKCS1Raw, where DigestInfo
+  // describes the hash function.
+  const std::string kDataToSign = "test";
+  const std::string kDataToSignHash = crypto::SHA256HashString(kDataToSign);
+  const std::string kDigestInfoAndDataToSignHash =
+      PrependSHA256DigestInfo(kDataToSignHash);
+
+  const crypto::SignatureVerifier::SignatureAlgorithm kSignatureAlgorithm =
+      crypto::SignatureVerifier::RSA_PKCS1_SHA256;
+
+  const std::string public_key_spki_der = GenerateKeyPair(token_id, kKeySize);
+
+  test_util::SignExecutionWaiter sign_waiter;
+  platform_keys_service()->SignRSAPKCS1Raw(
+      token_id, kDigestInfoAndDataToSignHash, public_key_spki_der,
+      sign_waiter.GetCallback());
+  sign_waiter.Wait();
+  EXPECT_EQ(sign_waiter.status(), Status::kSuccess);
+
+  crypto::SignatureVerifier signature_verifier;
+  ASSERT_TRUE(signature_verifier.VerifyInit(
+      kSignatureAlgorithm,
+      base::as_bytes(base::make_span(sign_waiter.signature())),
+      base::as_bytes(base::make_span(public_key_spki_der))));
+  signature_verifier.VerifyUpdate(base::as_bytes(base::make_span(kDataToSign)));
+  EXPECT_TRUE(signature_verifier.VerifyFinal());
+}
+
+// Generates a Rsa key pair and tests expected limits of the input length of the
+// SignRSAPKCS1Raw function.
+IN_PROC_BROWSER_TEST_P(PlatformKeysServicePerTokenBrowserTest,
+                       SignRawInputTooLong) {
+  const unsigned int kKeySize = 2048;
+  const TokenId token_id = GetParam().token_id;
+
+  const std::string public_key_spki_der = GenerateKeyPair(token_id, kKeySize);
+
+  // SignRSAPKCS1Raw performs PKCS#11 padding which adds at least 11 bytes.
+  {
+    // An input of |kKeySize in bytes - 11| should be fine.
+    std::string data_to_sign;
+    data_to_sign.resize(kKeySize / 8 - 11);
+
+    test_util::SignExecutionWaiter sign_waiter;
+    platform_keys_service()->SignRSAPKCS1Raw(
+        token_id, data_to_sign, public_key_spki_der, sign_waiter.GetCallback());
+    sign_waiter.Wait();
+    EXPECT_EQ(sign_waiter.status(), Status::kSuccess);
+  }
+
+  {
+    // An input of |kKeySize in bytes - 10| should be too long.
+    std::string data_to_sign_too_long;
+    data_to_sign_too_long.resize(kKeySize / 8 - 10);
+
+    test_util::SignExecutionWaiter sign_waiter;
+    platform_keys_service()->SignRSAPKCS1Raw(token_id, data_to_sign_too_long,
+                                             public_key_spki_der,
+                                             sign_waiter.GetCallback());
+    sign_waiter.Wait();
+    EXPECT_EQ(sign_waiter.status(), Status::kErrorInputTooLong);
+  }
+}
+
 IN_PROC_BROWSER_TEST_P(PlatformKeysServicePerTokenBrowserTest,
                        SetAndGetKeyAttribute) {
   // The attribute type to be set and retrieved using platform keys service.
diff --git a/chrome/browser/chromeos/platform_keys/platform_keys_service_nss.cc b/chrome/browser/chromeos/platform_keys/platform_keys_service_nss.cc
index a559c17..2a816b96 100644
--- a/chrome/browser/chromeos/platform_keys/platform_keys_service_nss.cc
+++ b/chrome/browser/chromeos/platform_keys/platform_keys_service_nss.cc
@@ -826,6 +826,29 @@
       base::BindOnce(&GenerateECKeyOnWorkerThread, std::move(state)));
 }
 
+// Checks whether |input_length| is lower or equal to the maximum input length
+// for a RSA PKCS#1 v1.5 signature generated using |private_key| with PK11_Sign.
+// Returns false if |input_length| is too large.
+// If the maximum input length can not be determined (which is possible because
+// it queries the PKCS#11 module), returns true and logs a warning.
+bool CheckRSAPKCS1SignRawInputLength(SECKEYPrivateKey* private_key,
+                                     size_t input_length) {
+  // For RSA keys, PK11_Sign will perform PKCS#1 v1.5 padding, which needs at
+  // least 11 bytes. RSA Sign can process an input of max. modulus length.
+  // Thus the maximum input length for the sign operation is
+  // |modulus_length - 11|.
+  int modulus_length_bytes = PK11_GetPrivateModulusLen(private_key);
+  if (modulus_length_bytes <= 0) {
+    LOG(WARNING) << "Could not determine modulus length";
+    return true;
+  }
+  size_t max_input_length_after_padding =
+      static_cast<size_t>(modulus_length_bytes);
+  // PKCS#1 v1.5 Padding needs at least this many bytes.
+  size_t kMinPaddingLength = 11u;
+  return input_length + kMinPaddingLength <= max_input_length_after_padding;
+}
+
 // Performs "raw" PKCS1 v1.5 padding + signing by calling PK11_Sign on
 // |rsa_key|.
 void SignRSAPKCS1RawOnWorkerThread(std::unique_ptr<SignState> state,
@@ -848,6 +871,17 @@
   std::vector<unsigned char> signature(signature_len);
   SECItem signature_output = {siBuffer, signature.data(), signature.size()};
   if (PK11_Sign(rsa_key.get(), &signature_output, &input) != SECSuccess) {
+    // Input size is checked after a failure - obtaining max input size
+    // involves extracting key modulus length which is not a free operation, so
+    // don't bother if signing succeeded.
+    // Note: It would be better if this could be determined from some library
+    // return code (e.g. PORT_GetError), but this was not possible with
+    // NSS+chaps at this point.
+    if (!CheckRSAPKCS1SignRawInputLength(rsa_key.get(), state->data_.size())) {
+      LOG(ERROR) << "Couldn't sign - input too long.";
+      state->OnError(FROM_HERE, Status::kErrorInputTooLong);
+      return;
+    }
     LOG(ERROR) << "Couldn't sign.";
     state->OnError(FROM_HERE, Status::kErrorInternal);
     return;
diff --git a/chrome/browser/component_updater/cros_component_installer_chromeos.cc b/chrome/browser/component_updater/cros_component_installer_chromeos.cc
index ab0f0d5..a43b245e 100644
--- a/chrome/browser/component_updater/cros_component_installer_chromeos.cc
+++ b/chrome/browser/component_updater/cros_component_installer_chromeos.cc
@@ -48,6 +48,8 @@
      "5714811c04f0a63aac96b39096faa759ace4c04e9b68291e7c9716128f5a2722"},
     {"demo-mode-resources", "1.0",
      "93c093ebac788581389015e9c59c5af111d2fa5174d206eb795042e6376cbd10"},
+    // NOTE: If you change the lacros component names, you must also update
+    // chrome/browser/chromeos/crosapi/browser_loader.cc.
     {"lacros-fishfood", "",
      "7a85ffb4b316a3b89135a3f43660ef3049950a61a2f8df4237e1ec213852b848"},
     {"lacros-dogfood-dev", "",
diff --git a/chrome/browser/diagnostics/sqlite_diagnostics.cc b/chrome/browser/diagnostics/sqlite_diagnostics.cc
index 8c3d45c..1511c83e 100644
--- a/chrome/browser/diagnostics/sqlite_diagnostics.cc
+++ b/chrome/browser/diagnostics/sqlite_diagnostics.cc
@@ -103,8 +103,8 @@
 
     int errors = 0;
     {  // Scope the statement and database so they close properly.
-      sql::Database database;
-      database.set_exclusive_locking();
+      sql::Database database(
+          {.exclusive_locking = true, .page_size = 4096, .cache_size = 500});
       scoped_refptr<ErrorRecorder> recorder(new ErrorRecorder);
 
       // Set the error callback so that we can get useful results in a debug
diff --git a/chrome/browser/download/download_browsertest.cc b/chrome/browser/download/download_browsertest.cc
index fbc815a..0ffebfa 100644
--- a/chrome/browser/download/download_browsertest.cc
+++ b/chrome/browser/download/download_browsertest.cc
@@ -4268,7 +4268,50 @@
 // Testing the behavior of resuming with only in-progress download manager.
 class InProgressDownloadTest : public DownloadTest {
  public:
-  void SetUpOnMainThread() override { EXPECT_TRUE(CheckTestDir()); }
+  InProgressDownloadTest() {
+    // The in progress download manager will be released from
+    // `DownloadManagerUtils` during creation of the `DownloadManagerImpl`. As
+    // the `DownloadManagerImpl` may be created before test bodies can run,
+    // register a callback to cache a pointer before release occurs.
+    DownloadManagerUtils::
+        SetRetrieveInProgressDownloadManagerCallbackForTesting(
+            base::BindRepeating(
+                &InProgressDownloadTest::set_in_progress_manager,
+                base::Unretained(this)));
+  }
+
+  // DownloadTest:
+  void SetUpOnMainThread() override {
+    EXPECT_TRUE(CheckTestDir());
+
+    if (!in_progress_manager_) {
+      // This will only occur if `DownloadManagerImpl` has not already been
+      // created in which case the in progress download manager has not yet been
+      // released from `DownloadManagerUtils`.
+      set_in_progress_manager(
+          DownloadManagerUtils::GetInProgressDownloadManager(
+              browser()->profile()->GetProfileKey()));
+    }
+
+    // As a pointer to the in progress download manager has now been cached,
+    // watching for release from `DownloadManagerUtils` (if it has not already
+    // occurred) is no longer necessary.
+    DownloadManagerUtils::
+        SetRetrieveInProgressDownloadManagerCallbackForTesting(
+            base::NullCallback());
+  }
+
+  download::InProgressDownloadManager* in_progress_manager() {
+    return in_progress_manager_;
+  }
+
+  void set_in_progress_manager(
+      download::InProgressDownloadManager* in_progress_manager) {
+    in_progress_manager_ = in_progress_manager;
+  }
+
+ private:
+  download::InProgressDownloadManager* in_progress_manager_ = nullptr;
 };
 
 // Check that if a download exists in both in-progress and history DB,
@@ -4290,9 +4333,6 @@
   std::string guid = base::GenerateGUID();
 
   // Wait for in-progress download manager to initialize.
-  download::InProgressDownloadManager* in_progress_manager =
-      DownloadManagerUtils::GetInProgressDownloadManager(
-          browser()->profile()->GetProfileKey());
   download::SimpleDownloadManagerCoordinator* coordinator =
       SimpleDownloadManagerCoordinatorFactory::GetForKey(
           browser()->profile()->GetProfileKey());
@@ -4307,9 +4347,9 @@
   std::vector<GURL> url_chain;
   url_chain.emplace_back(url);
   base::Time current_time = base::Time::Now();
-  in_progress_manager->AddInProgressDownloadForTest(
+  in_progress_manager()->AddInProgressDownloadForTest(
       std::make_unique<download::DownloadItemImpl>(
-          in_progress_manager, guid, 1 /* id */,
+          in_progress_manager(), guid, 1 /* id */,
           target_path.AddExtensionASCII("crdownload"), target_path, url_chain,
           GURL() /* referrer_url */, GURL() /* site_url */,
           GURL() /* tab_url */, GURL() /* tab_referrer_url */,
@@ -4359,9 +4399,6 @@
   std::string guid = base::GenerateGUID();
 
   // Wait for in-progress download manager to initialize.
-  download::InProgressDownloadManager* in_progress_manager =
-      DownloadManagerUtils::GetInProgressDownloadManager(
-          browser()->profile()->GetProfileKey());
   download::SimpleDownloadManagerCoordinator* coordinator =
       SimpleDownloadManagerCoordinatorFactory::GetForKey(
           browser()->profile()->GetProfileKey());
@@ -4382,14 +4419,14 @@
   params->set_file_path(target_path);
   params->set_transient(true);
   params->set_require_safety_checks(false);
-  in_progress_manager->DownloadUrl(std::move(params));
+  in_progress_manager()->DownloadUrl(std::move(params));
   auto params2 = std::make_unique<DownloadUrlParameters>(
       url, TRAFFIC_ANNOTATION_FOR_TESTS);
   params2->set_guid(guid);
   params2->set_file_path(target_path);
   params2->set_transient(true);
   params2->set_require_safety_checks(false);
-  in_progress_manager->DownloadUrl(std::move(params2));
+  in_progress_manager()->DownloadUrl(std::move(params2));
   coordinator_waiter.WaitForDownloadCreation(1);
   download::DownloadItem* download = coordinator->GetDownloadByGuid(guid);
   ASSERT_TRUE(download);
diff --git a/chrome/browser/download/download_manager_utils.cc b/chrome/browser/download/download_manager_utils.cc
index c0f0485..6fe38b5 100644
--- a/chrome/browser/download/download_manager_utils.cc
+++ b/chrome/browser/download/download_manager_utils.cc
@@ -43,6 +43,17 @@
   return *map;
 }
 
+// Returns a callback to be invoked during `RetrieveInProgressDownloadManager()`
+// to provide an opportunity to cache a pointer to the in progress download
+// manager being released.
+base::RepeatingCallback<void(download::InProgressDownloadManager*)>&
+GetRetrieveInProgressDownloadManagerCallback() {
+  static base::NoDestructor<
+      base::RepeatingCallback<void(download::InProgressDownloadManager*)>>
+      callback;
+  return *callback;
+}
+
 // Ignores origin security check. DownloadManagerImpl will provide its own
 // implementation when InProgressDownloadManager object is passed to it.
 bool IgnoreOriginSecurityCheck(const GURL& url) {
@@ -71,6 +82,8 @@
   ProfileKey* key = profile->GetProfileKey();
   GetInProgressDownloadManager(key);
   auto& map = GetInProgressManagerMap();
+  if (GetRetrieveInProgressDownloadManagerCallback())
+    GetRetrieveInProgressDownloadManagerCallback().Run(map[key].get());
   return map[key].release();
 }
 
@@ -129,3 +142,11 @@
   }
   return map[key].get();
 }
+
+// static
+void DownloadManagerUtils::
+    SetRetrieveInProgressDownloadManagerCallbackForTesting(
+        base::RepeatingCallback<void(download::InProgressDownloadManager*)>
+            callback) {
+  GetRetrieveInProgressDownloadManagerCallback() = callback;
+}
diff --git a/chrome/browser/download/download_manager_utils.h b/chrome/browser/download/download_manager_utils.h
index 79dc4a1..7d23ad32 100644
--- a/chrome/browser/download/download_manager_utils.h
+++ b/chrome/browser/download/download_manager_utils.h
@@ -5,6 +5,7 @@
 #ifndef CHROME_BROWSER_DOWNLOAD_DOWNLOAD_MANAGER_UTILS_H_
 #define CHROME_BROWSER_DOWNLOAD_DOWNLOAD_MANAGER_UTILS_H_
 
+#include "base/callback_forward.h"
 #include "base/macros.h"
 
 class Profile;
@@ -29,6 +30,13 @@
   static download::InProgressDownloadManager* GetInProgressDownloadManager(
       ProfileKey* key);
 
+  // Registers a `callback` to be run during subsequent invocations of
+  // `RetrieveInProgressDownloadManager()`, providing an opportunity to cache
+  // a pointer to the in progress download manager being released.
+  static void SetRetrieveInProgressDownloadManagerCallbackForTesting(
+      base::RepeatingCallback<void(download::InProgressDownloadManager*)>
+          callback);
+
  private:
   DISALLOW_COPY_AND_ASSIGN(DownloadManagerUtils);
 };
diff --git a/chrome/browser/download/save_page_browsertest.cc b/chrome/browser/download/save_page_browsertest.cc
index 0a48741..a5bcd21 100644
--- a/chrome/browser/download/save_page_browsertest.cc
+++ b/chrome/browser/download/save_page_browsertest.cc
@@ -802,15 +802,13 @@
 #endif
 
   // Save the file as MHTML. Run until save completes.
+  base::RunLoop run_loop;
+  content::SavePackageFinishedObserver observer(
+      content::BrowserContext::GetDownloadManager(browser()->profile()),
+      run_loop.QuitClosure());
   ASSERT_TRUE(select_file_dialog_factory->GetLastDialog()->CallFileSelected(
       full_file_name, "mhtml"));
-  {
-    base::RunLoop run_loop;
-    content::SavePackageFinishedObserver observer(
-        content::BrowserContext::GetDownloadManager(browser()->profile()),
-        run_loop.QuitClosure());
-    run_loop.Run();
-  }
+  run_loop.Run();
 
   ASSERT_TRUE(VerifySavePackageExpectations(browser(), url));
   persisted.WaitForPersisted();
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index d5a5f37d..1b92f5e 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -478,6 +478,11 @@
     "expiry_milestone": 91
   },
   {
+    "name": "cellular-use-attach-apn",
+    "owners": [ "vpalatin", "ejcaruso", "chromeos-cellular-platform@google.com" ],
+    "expiry_milestone": 95
+  },
+  {
     "name": "change-password-affiliation",
     "owners": [ "vsemeniuk", "vasilii"],
     "expiry_milestone": 90
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 40e1f3b..a33443a 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -3829,6 +3829,12 @@
 const char kCdmFactoryDaemonDescription[] =
     "Use the CDM daemon instead of the library CDM";
 
+const char kCellularUseAttachApnName[] = "Cellular use Attach APN";
+const char kCellularUseAttachApnDescription[] =
+    "Use the mobile operator database to set explicitly an Attach APN "
+    "for the LTE connections rather than letting the modem decide which "
+    "attach APN to use or retrieve it from the network";
+
 const char kConnectivityDiagnosticsWebUiName[] =
     "Connectivity Diagnostics WebUI";
 const char kConnectivityDiagnosticsWebUiDescription[] =
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 6847b214..1abf9c5 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -2214,6 +2214,9 @@
 extern const char kCdmFactoryDaemonName[];
 extern const char kCdmFactoryDaemonDescription[];
 
+extern const char kCellularUseAttachApnName[];
+extern const char kCellularUseAttachApnDescription[];
+
 extern const char kConnectivityDiagnosticsWebUiName[];
 extern const char kConnectivityDiagnosticsWebUiDescription[];
 
diff --git a/chrome/browser/hid/chrome_hid_delegate.cc b/chrome/browser/hid/chrome_hid_delegate.cc
index 9ed76ebc..78c22ad 100644
--- a/chrome/browser/hid/chrome_hid_delegate.cc
+++ b/chrome/browser/hid/chrome_hid_delegate.cc
@@ -13,7 +13,6 @@
 #include "chrome/browser/ui/browser_finder.h"
 #include "chrome/browser/ui/hid/hid_chooser.h"
 #include "chrome/browser/ui/hid/hid_chooser_controller.h"
-#include "chrome/browser/usb/usb_blocklist.h"
 #include "content/public/browser/web_contents.h"
 
 namespace {
diff --git a/chrome/browser/hid/hid_chooser_context.cc b/chrome/browser/hid/hid_chooser_context.cc
index a015303..3173e0f 100644
--- a/chrome/browser/hid/hid_chooser_context.cc
+++ b/chrome/browser/hid/hid_chooser_context.cc
@@ -11,10 +11,10 @@
 #include "base/values.h"
 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/usb/usb_blocklist.h"
 #include "chrome/grit/generated_resources.h"
 #include "components/content_settings/core/common/content_settings_types.h"
 #include "content/public/browser/device_service.h"
+#include "services/device/public/cpp/hid/hid_blocklist.h"
 #include "ui/base/l10n/l10n_util.h"
 
 namespace {
@@ -215,10 +215,8 @@
     const url::Origin& requesting_origin,
     const url::Origin& embedding_origin,
     const device::mojom::HidDeviceInfo& device) {
-  if (UsbBlocklist::Get().IsExcluded(
-          {device.vendor_id, device.product_id, 0})) {
+  if (device::HidBlocklist::IsDeviceExcluded(device))
     return false;
-  }
 
   if (!CanRequestObjectPermission(requesting_origin, embedding_origin))
     return false;
diff --git a/chrome/browser/media/router/BUILD.gn b/chrome/browser/media/router/BUILD.gn
index 51b0562..f908520 100644
--- a/chrome/browser/media/router/BUILD.gn
+++ b/chrome/browser/media/router/BUILD.gn
@@ -250,7 +250,12 @@
       "providers/extension/extension_media_route_provider_proxy_unittest.cc",
       "providers/wired_display/wired_display_media_route_provider_unittest.cc",
     ]
-    deps += [ ":test_support" ]
+    deps += [
+      ":test_support",
+      "//chrome/test:test_support",
+      "//components/sync_preferences:test_support",
+      "//content/test:test_support",
+    ]
   }
 
   if (enable_openscreen) {
diff --git a/chrome/browser/media/router/media_router_feature.cc b/chrome/browser/media/router/media_router_feature.cc
index 52716b2..4875422 100644
--- a/chrome/browser/media/router/media_router_feature.cc
+++ b/chrome/browser/media/router/media_router_feature.cc
@@ -4,8 +4,12 @@
 
 #include "chrome/browser/media/router/media_router_feature.h"
 
+#include <utility>
+
 #include "base/base64.h"
+#include "base/containers/flat_map.h"
 #include "base/feature_list.h"
+#include "base/no_destructor.h"
 #include "base/strings/string_util.h"
 #include "build/build_config.h"
 #include "chrome/browser/profiles/profile.h"
@@ -66,11 +70,22 @@
 #endif  // !defined(OFFICIAL_BUILD) && !defined(OS_ANDROID)
 
 #if defined(OS_ANDROID) || BUILDFLAG(ENABLE_EXTENSIONS)
+  static base::NoDestructor<base::flat_map<content::BrowserContext*, bool>>
+      stored_pref_values;
+
+  // If the Media Router was already enabled or disabled for |context|, then it
+  // must remain so.  The Media Router does not support dynamic
+  // enabling/disabling.
+  auto const it = stored_pref_values->find(context);
+  if (it != stored_pref_values->end())
+    return it->second;
+
+  // Check the enterprise policy.
   const PrefService::Preference* pref = GetMediaRouterPref(context);
-  // Only use the pref value if it set from a mandatory policy.
   if (pref->IsManaged() && !pref->IsDefaultValue()) {
-    bool allowed = false;
+    bool allowed;
     CHECK(pref->GetValue()->GetAsBoolean(&allowed));
+    stored_pref_values->insert(std::make_pair(context, allowed));
     return allowed;
   }
 
diff --git a/chrome/browser/media/router/media_router_feature_unittest.cc b/chrome/browser/media/router/media_router_feature_unittest.cc
index e61e9d4..d81a9e4 100644
--- a/chrome/browser/media/router/media_router_feature_unittest.cc
+++ b/chrome/browser/media/router/media_router_feature_unittest.cc
@@ -4,11 +4,23 @@
 
 #include "chrome/browser/media/router/media_router_feature.h"
 
+#include <memory>
+
 #include "base/test/scoped_feature_list.h"
+#include "build/build_config.h"
 #include "components/prefs/pref_registry_simple.h"
 #include "components/prefs/testing_pref_service.h"
+#include "extensions/buildflags/buildflags.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
+#if defined(OS_ANDROID) || BUILDFLAG(ENABLE_EXTENSIONS)
+#include "base/values.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/test/base/testing_profile.h"
+#include "components/sync_preferences/testing_pref_service_syncable.h"
+#include "content/public/test/browser_task_environment.h"
+#endif  // defined(OS_ANDROID) || BUILDFLAG(ENABLE_EXTENSIONS)
+
 namespace media_router {
 
 TEST(MediaRouterFeatureTest, GetCastAllowAllIPsPref) {
@@ -42,4 +54,41 @@
   EXPECT_EQ(token, GetReceiverIdHashToken(pref_service.get()));
 }
 
+#if defined(OS_ANDROID) || BUILDFLAG(ENABLE_EXTENSIONS)
+class MediaRouterEnabledTest : public ::testing::Test {
+ public:
+  MediaRouterEnabledTest() = default;
+  MediaRouterEnabledTest(const MediaRouterEnabledTest&) = delete;
+  ~MediaRouterEnabledTest() override = default;
+  MediaRouterEnabledTest& operator=(const MediaRouterEnabledTest&) = delete;
+
+ protected:
+  content::BrowserTaskEnvironment test_environment;
+  TestingProfile enabled_profile;
+  TestingProfile disabled_profile;
+};
+
+TEST_F(MediaRouterEnabledTest, TestEnabledByPolicy) {
+  enabled_profile.GetTestingPrefService()->SetManagedPref(
+      ::prefs::kEnableMediaRouter, std::make_unique<base::Value>(true));
+  EXPECT_TRUE(MediaRouterEnabled(&enabled_profile));
+
+  enabled_profile.GetTestingPrefService()->SetManagedPref(
+      ::prefs::kEnableMediaRouter, std::make_unique<base::Value>(false));
+  // Runtime changes are not supported.
+  EXPECT_TRUE(MediaRouterEnabled(&enabled_profile));
+}
+
+TEST_F(MediaRouterEnabledTest, TestDisabledByPolicy) {
+  disabled_profile.GetTestingPrefService()->SetManagedPref(
+      ::prefs::kEnableMediaRouter, std::make_unique<base::Value>(false));
+  EXPECT_FALSE(MediaRouterEnabled(&disabled_profile));
+
+  disabled_profile.GetTestingPrefService()->SetManagedPref(
+      ::prefs::kEnableMediaRouter, std::make_unique<base::Value>(true));
+  // Runtime changes are not supported.
+  EXPECT_FALSE(MediaRouterEnabled(&disabled_profile));
+}
+#endif  // defined(OS_ANDROID) || BUILDFLAG(ENABLE_EXTENSIONS)
+
 }  // namespace media_router
diff --git a/chrome/browser/media_galleries/fileapi/media_file_validator_browsertest.cc b/chrome/browser/media_galleries/fileapi/media_file_validator_browsertest.cc
index 28e4938..082f309 100644
--- a/chrome/browser/media_galleries/fileapi/media_file_validator_browsertest.cc
+++ b/chrome/browser/media_galleries/fileapi/media_file_validator_browsertest.cc
@@ -47,22 +47,18 @@
 const int64_t kNoFileSize = -1;
 
 void HandleCheckFileResult(int64_t expected_size,
-                           const base::Callback<void(bool success)>& callback,
+                           base::OnceCallback<void(bool success)> callback,
                            base::File::Error result,
                            const base::File::Info& file_info) {
   if (result == base::File::FILE_OK) {
     if (!file_info.is_directory && expected_size != kNoFileSize &&
         file_info.size == expected_size) {
-      callback.Run(true);
-      return;
-    }
-  } else {
-    if (expected_size == kNoFileSize) {
-      callback.Run(true);
+      std::move(callback).Run(true);
       return;
     }
   }
-  callback.Run(false);
+
+  std::move(callback).Run(expected_size == kNoFileSize);
 }
 
 base::FilePath GetMediaTestDir() {
@@ -165,10 +161,10 @@
 
     content::GetIOThreadTaskRunner({})->PostTask(
         FROM_HERE,
-        base::BindOnce(&MediaFileValidatorTest::CheckFiles,
-                       base::Unretained(this), true,
-                       base::Bind(&MediaFileValidatorTest::OnTestFilesReady,
-                                  base::Unretained(this), expected_result)));
+        base::BindOnce(
+            &MediaFileValidatorTest::CheckFiles, base::Unretained(this), true,
+            base::BindOnce(&MediaFileValidatorTest::OnTestFilesReady,
+                           base::Unretained(this), expected_result)));
   }
 
   void SetupFromFileBlocking(const std::string& filename,
@@ -183,10 +179,11 @@
   // |src_expected| indicates which one should exist.  When complete,
   // |callback| is called with success/failure.
   void CheckFiles(bool src_expected,
-                  const base::Callback<void(bool success)>& callback) {
+                  base::OnceCallback<void(bool success)> callback) {
     CheckFile(move_src_, src_expected ? test_file_size_ : kNoFileSize,
-              base::Bind(&MediaFileValidatorTest::OnCheckFilesFirstResult,
-                         base::Unretained(this), !src_expected, callback));
+              base::BindOnce(&MediaFileValidatorTest::OnCheckFilesFirstResult,
+                             base::Unretained(this), !src_expected,
+                             std::move(callback)));
   }
 
   // Helper that checks a file has the |expected_size|, which may be
@@ -194,24 +191,25 @@
   // with success/failure.
   void CheckFile(storage::FileSystemURL url,
                  int64_t expected_size,
-                 const base::Callback<void(bool success)>& callback) {
+                 base::OnceCallback<void(bool success)> callback) {
     operation_runner()->GetMetadata(
         url, storage::FileSystemOperation::GET_METADATA_FIELD_SIZE,
-        base::BindOnce(&HandleCheckFileResult, expected_size, callback));
+        base::BindOnce(&HandleCheckFileResult, expected_size,
+                       std::move(callback)));
   }
 
   // Helper that checks the result of |move_src_| lookup and then checks
   // |move_dest_| if all is as expected.
   void OnCheckFilesFirstResult(bool dest_expected,
-                               const base::Callback<void(bool)>& callback,
+                               base::OnceCallback<void(bool)> callback,
                                bool src_result) {
     EXPECT_TRUE(src_result);
     if (!src_result) {
-      callback.Run(false);
+      std::move(callback).Run(false);
       return;
     }
     CheckFile(move_dest_, dest_expected ? test_file_size_ : kNoFileSize,
-              callback);
+              std::move(callback));
   }
 
   // Assert |test_files_ready| and then do the actual test of moving
@@ -232,8 +230,8 @@
     else
       EXPECT_EQ(base::File::FILE_ERROR_SECURITY, result);
     CheckFiles(!expected_result,
-               base::Bind(&MediaFileValidatorTest::OnTestFilesCheckResult,
-                          base::Unretained(this)));
+               base::BindOnce(&MediaFileValidatorTest::OnTestFilesCheckResult,
+                              base::Unretained(this)));
   }
 
   // Check that the correct test file exists and then allow the main-thread
diff --git a/chrome/browser/nearby_sharing/sharesheet/nearby_share_action.cc b/chrome/browser/nearby_sharing/sharesheet/nearby_share_action.cc
index 9ab2ba3..dfbe112 100644
--- a/chrome/browser/nearby_sharing/sharesheet/nearby_share_action.cc
+++ b/chrome/browser/nearby_sharing/sharesheet/nearby_share_action.cc
@@ -16,9 +16,11 @@
 #include "chrome/browser/nearby_sharing/file_attachment.h"
 #include "chrome/browser/nearby_sharing/nearby_sharing_service.h"
 #include "chrome/browser/nearby_sharing/nearby_sharing_service_factory.h"
-#include "chrome/browser/nearby_sharing/sharesheet/nearby_share_web_view.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/sharesheet/sharesheet_types.h"
+#include "chrome/browser/ui/browser_navigator.h"
+#include "chrome/browser/ui/browser_navigator_params.h"
+#include "chrome/browser/ui/scoped_tabbed_browser_displayer.h"
 #include "chrome/browser/ui/webui/nearby_share/nearby_share_dialog_ui.h"
 #include "chrome/common/webui_url_constants.h"
 #include "chrome/grit/generated_resources.h"
@@ -96,21 +98,22 @@
   controller->SetSharesheetSize(size.width(), size.height());
 
   auto* profile = controller->GetProfile();
-  auto view = std::make_unique<NearbyShareWebView>(profile);
+  auto view = std::make_unique<views::WebView>(profile);
   // If this is not done, we don't see anything in our view.
   view->SetPreferredSize(size);
-  views::WebView* web_view = root_view->AddChildView(std::move(view));
+  web_view_ = root_view->AddChildView(std::move(view));
+  web_view_->GetWebContents()->SetDelegate(this);
   // TODO(vecore): Query this from the container view
-  web_view->holder()->SetCornerRadii(gfx::RoundedCornersF(kCornerRadius));
+  web_view_->holder()->SetCornerRadii(gfx::RoundedCornersF(kCornerRadius));
 
   // load chrome://nearby into the webview
-  web_view->LoadInitialURL(GURL(chrome::kChromeUINearbyShareURL));
+  web_view_->LoadInitialURL(GURL(chrome::kChromeUINearbyShareURL));
 
   // Without requesting focus, the sharesheet will launch in an unfocused state
   // which raises accessibility issues with the "Device name" input.
-  web_view->RequestFocus();
+  web_view_->RequestFocus();
 
-  auto* webui = web_view->GetWebContents()->GetWebUI();
+  auto* webui = web_view_->GetWebContents()->GetWebUI();
   DCHECK(webui != nullptr);
 
   nearby_ui_ =
@@ -154,3 +157,24 @@
     nearby_ui_ = nullptr;
   }
 }
+
+bool NearbyShareAction::HandleKeyboardEvent(
+    content::WebContents* source,
+    const content::NativeWebKeyboardEvent& event) {
+  return unhandled_keyboard_event_handler_.HandleKeyboardEvent(
+      event, web_view_->GetFocusManager());
+}
+
+void NearbyShareAction::WebContentsCreated(
+    content::WebContents* source_contents,
+    int opener_render_process_id,
+    int opener_render_frame_id,
+    const std::string& frame_name,
+    const GURL& target_url,
+    content::WebContents* new_contents) {
+  chrome::ScopedTabbedBrowserDisplayer displayer(
+      Profile::FromBrowserContext(web_view_->GetBrowserContext()));
+  NavigateParams nav_params(displayer.browser(), target_url,
+                            ui::PageTransition::PAGE_TRANSITION_LINK);
+  Navigate(&nav_params);
+}
diff --git a/chrome/browser/nearby_sharing/sharesheet/nearby_share_action.h b/chrome/browser/nearby_sharing/sharesheet/nearby_share_action.h
index c29437b..7b5ad19 100644
--- a/chrome/browser/nearby_sharing/sharesheet/nearby_share_action.h
+++ b/chrome/browser/nearby_sharing/sharesheet/nearby_share_action.h
@@ -7,9 +7,16 @@
 
 #include "chrome/browser/sharesheet/share_action.h"
 #include "chrome/browser/ui/webui/nearby_share/nearby_share_dialog_ui.h"
+#include "content/public/browser/web_contents_delegate.h"
+#include "ui/views/controls/webview/unhandled_keyboard_event_handler.h"
+
+namespace views {
+class WebView;
+}  // namespace views
 
 class NearbyShareAction : public sharesheet::ShareAction,
-                          nearby_share::NearbyShareDialogUI::Observer {
+                          nearby_share::NearbyShareDialogUI::Observer,
+                          content::WebContentsDelegate {
  public:
   NearbyShareAction();
   ~NearbyShareAction() override;
@@ -29,9 +36,22 @@
   // nearby_share::NearbyShareDialogUI::Observer:
   void OnClose() override;
 
+  // content::WebContentsDelegate:
+  bool HandleKeyboardEvent(
+      content::WebContents* source,
+      const content::NativeWebKeyboardEvent& event) override;
+  void WebContentsCreated(content::WebContents* source_contents,
+                          int opener_render_process_id,
+                          int opener_render_frame_id,
+                          const std::string& frame_name,
+                          const GURL& target_url,
+                          content::WebContents* new_contents) override;
+
  private:
   sharesheet::SharesheetController* controller_ = nullptr;
   nearby_share::NearbyShareDialogUI* nearby_ui_ = nullptr;
+  views::WebView* web_view_;
+  views::UnhandledKeyboardEventHandler unhandled_keyboard_event_handler_;
 };
 
 #endif  // CHROME_BROWSER_NEARBY_SHARING_SHARESHEET_NEARBY_SHARE_ACTION_H_
diff --git a/chrome/browser/nearby_sharing/sharesheet/nearby_share_web_view.cc b/chrome/browser/nearby_sharing/sharesheet/nearby_share_web_view.cc
deleted file mode 100644
index 17291d1..0000000
--- a/chrome/browser/nearby_sharing/sharesheet/nearby_share_web_view.cc
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/nearby_sharing/sharesheet/nearby_share_web_view.h"
-
-#include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/ui/browser_navigator.h"
-#include "chrome/browser/ui/browser_navigator_params.h"
-#include "chrome/browser/ui/scoped_tabbed_browser_displayer.h"
-
-NearbyShareWebView::NearbyShareWebView(content::BrowserContext* browser_context)
-    : WebView(browser_context) {}
-
-void NearbyShareWebView::WebContentsCreated(
-    content::WebContents* source_contents,
-    int opener_render_process_id,
-    int opener_render_frame_id,
-    const std::string& frame_name,
-    const GURL& target_url,
-    content::WebContents* new_contents) {
-  chrome::ScopedTabbedBrowserDisplayer displayer(
-      Profile::FromBrowserContext(GetBrowserContext()));
-  NavigateParams nav_params(displayer.browser(), target_url,
-                            ui::PageTransition::PAGE_TRANSITION_LINK);
-  Navigate(&nav_params);
-}
diff --git a/chrome/browser/nearby_sharing/sharesheet/nearby_share_web_view.h b/chrome/browser/nearby_sharing/sharesheet/nearby_share_web_view.h
deleted file mode 100644
index 70768cb..0000000
--- a/chrome/browser/nearby_sharing/sharesheet/nearby_share_web_view.h
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_NEARBY_SHARING_SHARESHEET_NEARBY_SHARE_WEB_VIEW_H_
-#define CHROME_BROWSER_NEARBY_SHARING_SHARESHEET_NEARBY_SHARE_WEB_VIEW_H_
-
-#include "ui/views/controls/webview/webview.h"
-
-namespace content {
-class BrowserContext;
-class WebContents;
-}  // namespace content
-
-// NearbyShareWebView is used in place of the general views::WebView when
-// creating the UI for the sharesheet action so that we can handle navigation
-// to open a new tab when a link is clicked.
-class NearbyShareWebView : public views::WebView {
- public:
-  explicit NearbyShareWebView(content::BrowserContext* browser_context);
-  ~NearbyShareWebView() override = default;
-
-  // content::WebContentsDelegate:
-  void WebContentsCreated(content::WebContents* source_contents,
-                          int opener_render_process_id,
-                          int opener_render_frame_id,
-                          const std::string& frame_name,
-                          const GURL& target_url,
-                          content::WebContents* new_contents) override;
-};
-
-#endif  // CHROME_BROWSER_NEARBY_SHARING_SHARESHEET_NEARBY_SHARE_WEB_VIEW_H_
diff --git a/chrome/browser/optimization_guide/optimization_guide_keyed_service_browsertest.cc b/chrome/browser/optimization_guide/optimization_guide_keyed_service_browsertest.cc
index fdb5e5d..457b3ba 100644
--- a/chrome/browser/optimization_guide/optimization_guide_keyed_service_browsertest.cc
+++ b/chrome/browser/optimization_guide/optimization_guide_keyed_service_browsertest.cc
@@ -483,7 +483,6 @@
   PushHintsComponentAndWaitForCompletion();
   RegisterWithKeyedService();
 
-  ukm::TestAutoSetUkmRecorder ukm_recorder;
   base::HistogramTester histogram_tester;
 
   ui_test_utils::NavigateToURL(browser(), url_that_redirects_to_hints());
@@ -505,7 +504,6 @@
   PushHintsComponentAndWaitForCompletion();
   RegisterWithKeyedService();
 
-  ukm::TestAutoSetUkmRecorder ukm_recorder;
   base::HistogramTester histogram_tester;
 
   ui_test_utils::NavigateToURL(browser(), GURL("https://nohints.com/"));
@@ -526,6 +524,49 @@
       1);
 }
 
+IN_PROC_BROWSER_TEST_F(OptimizationGuideKeyedServiceBrowserTest,
+                       CheckForBlocklistFilter) {
+  PushHintsComponentAndWaitForCompletion();
+
+  OptimizationGuideKeyedService* ogks =
+      OptimizationGuideKeyedServiceFactory::GetForProfile(browser()->profile());
+
+  // Register an optimization type with an optimization filter.
+  ogks->RegisterOptimizationTypes({optimization_guide::proto::FAST_HOST_HINTS});
+  // Wait until filter is loaded.
+  base::RunLoop().RunUntilIdle();
+
+  base::HistogramTester histogram_tester;
+
+  EXPECT_EQ(optimization_guide::OptimizationGuideDecision::kFalse,
+            ogks->CanApplyOptimization(
+                GURL("https://blockedhost.com/whatever"),
+                optimization_guide::proto::FAST_HOST_HINTS, nullptr));
+  histogram_tester.ExpectUniqueSample(
+      "OptimizationGuide.ApplyDecision.FastHostHints",
+      static_cast<int>(optimization_guide::OptimizationTypeDecision::
+                           kNotAllowedByOptimizationFilter),
+      1);
+
+  // Register another type with optimization filter.
+  ogks->RegisterOptimizationTypes(
+      {optimization_guide::proto::LITE_PAGE_REDIRECT});
+  // Wait until filter is loaded.
+  base::RunLoop().RunUntilIdle();
+
+  // The previously loaded filter should still be loaded and give the same
+  // result.
+  EXPECT_EQ(optimization_guide::OptimizationGuideDecision::kFalse,
+            ogks->CanApplyOptimization(
+                GURL("https://blockedhost.com/whatever"),
+                optimization_guide::proto::FAST_HOST_HINTS, nullptr));
+  histogram_tester.ExpectUniqueSample(
+      "OptimizationGuide.ApplyDecision.FastHostHints",
+      static_cast<int>(optimization_guide::OptimizationTypeDecision::
+                           kNotAllowedByOptimizationFilter),
+      2);
+}
+
 class OptimizationGuideKeyedServiceDataSaverUserWithInfobarShownTest
     : public OptimizationGuideKeyedServiceBrowserTest {
  public:
diff --git a/chrome/browser/policy/messaging_layer/upload/record_handler_impl.cc b/chrome/browser/policy/messaging_layer/upload/record_handler_impl.cc
index 64af268..c2cb82b9 100644
--- a/chrome/browser/policy/messaging_layer/upload/record_handler_impl.cc
+++ b/chrome/browser/policy/messaging_layer/upload/record_handler_impl.cc
@@ -33,6 +33,27 @@
 #include "content/public/browser/browser_thread.h"
 
 namespace reporting {
+namespace {
+
+// Priority could come back as an int or as a std::string, this function handles
+// both situations.
+base::Optional<Priority> GetPriorityProtoFromSequencingInformationValue(
+    const base::Value& sequencing_information) {
+  const base::Optional<int> int_priority_result =
+      sequencing_information.FindIntKey("priority");
+  if (int_priority_result.has_value()) {
+    return Priority(int_priority_result.value());
+  }
+
+  const std::string* str_priority_result =
+      sequencing_information.FindStringKey("priority");
+  Priority priority;
+  if (!Priority_Parse(*str_priority_result, &priority)) {
+    return base::nullopt;
+  }
+  return priority;
+}
+}  // namespace
 
 // ReportUploader handles enqueuing events on the |report_queue_|,
 // and uploading those events with the |client_|.
@@ -344,14 +365,15 @@
     const base::Value& value) {
   const std::string* sequencing_id = value.FindStringKey("sequencingId");
   const std::string* generation_id = value.FindStringKey("generationId");
-  const auto priority = value.FindIntKey("priority");
+  const auto priority_result =
+      GetPriorityProtoFromSequencingInformationValue(value);
 
   // If any of the previous values don't exist, or are malformed, return error.
   int64_t seq_id;
   int64_t gen_id;
   if (!sequencing_id || sequencing_id->empty() || !generation_id ||
-      generation_id->empty() || !priority.has_value() ||
-      !Priority_IsValid(priority.value()) ||
+      generation_id->empty() || !priority_result.has_value() ||
+      !Priority_IsValid(priority_result.value()) ||
       !base::StringToInt64(*sequencing_id, &seq_id) ||
       !base::StringToInt64(*generation_id, &gen_id)) {
     return Status(error::INVALID_ARGUMENT,
@@ -363,7 +385,7 @@
   SequencingInformation proto;
   proto.set_sequencing_id(seq_id);
   proto.set_generation_id(gen_id);
-  proto.set_priority(Priority(priority.value()));
+  proto.set_priority(Priority(priority_result.value()));
   return proto;
 }
 
diff --git a/chrome/browser/policy/messaging_layer/upload/record_handler_impl_unittest.cc b/chrome/browser/policy/messaging_layer/upload/record_handler_impl_unittest.cc
index 9ad1094..42f6580 100644
--- a/chrome/browser/policy/messaging_layer/upload/record_handler_impl_unittest.cc
+++ b/chrome/browser/policy/messaging_layer/upload/record_handler_impl_unittest.cc
@@ -86,8 +86,8 @@
 
 // Helper function for retrieving and processing the SequencingInformation from
 // a request.
-void RetrieveFinalSequencingInforamation(const base::Value& request,
-                                         base::Value& sequencing_info) {
+void RetrieveFinalSequencingInformation(const base::Value& request,
+                                        base::Value& sequencing_info) {
   ASSERT_TRUE(request.is_dict());
 
   // Retrieve and process sequencing information
@@ -98,10 +98,22 @@
   const auto* seq_info = encrypted_record_list->GetList().rbegin()->FindDictKey(
       "sequencingInformation");
   ASSERT_TRUE(seq_info != nullptr);
-  ASSERT_TRUE(seq_info->FindStringKey("sequencingId"));
-  ASSERT_TRUE(seq_info->FindStringKey("generationId"));
+  ASSERT_TRUE(!seq_info->FindStringKey("sequencingId")->empty());
+  ASSERT_TRUE(!seq_info->FindStringKey("generationId")->empty());
   ASSERT_TRUE(seq_info->FindIntKey("priority"));
+
   sequencing_info.MergeDictionary(seq_info);
+  // Set half of sequencing information to return a string instead of an int for
+  // priority.
+  int64_t sequencing_id;
+  ASSERT_TRUE(base::StringToInt64(
+      *sequencing_info.FindStringKey("sequencingId"), &sequencing_id));
+  if (sequencing_id % 2) {
+    const auto int_result = sequencing_info.FindIntKey("priority");
+    ASSERT_TRUE(int_result.has_value());
+    sequencing_info.RemoveKey("priority");
+    sequencing_info.SetStringKey("priority", Priority_Name(int_result.value()));
+  }
 }
 
 base::Optional<base::Value> BuildEncryptionSettingsFromRequest(
@@ -132,7 +144,7 @@
 void SucceedResponseFromRequest(const base::Value& request,
                                 base::Value& response) {
   base::Value seq_info{base::Value::Type::DICTIONARY};
-  RetrieveFinalSequencingInforamation(request, seq_info);
+  RetrieveFinalSequencingInformation(request, seq_info);
   response.SetPath("lastSucceedUploadedRecord", std::move(seq_info));
 
   // If attach_encryption_settings it true, process that.
@@ -149,21 +161,17 @@
 void FailedResponseFromRequest(const base::Value& request,
                                base::Value& response) {
   base::Value seq_info{base::Value::Type::DICTIONARY};
-  RetrieveFinalSequencingInforamation(request, seq_info);
+  RetrieveFinalSequencingInformation(request, seq_info);
 
-  // |seq_info| has been built by RetrieveFinalSequencingInforamation and is
-  // guaranteed to have these keys.
+  response.SetPath("lastSucceedUploadedRecord", seq_info.Clone());
+  // The lastSucceedUploadedRecord should be the record before the one indicated
+  // in seq_info. |seq_info| has been built by
+  // RetrieveFinalSequencingInforamation and is guaranteed to have this key.
   int64_t sequencing_id;
   ASSERT_TRUE(base::StringToInt64(*seq_info.FindStringKey("sequencingId"),
                                   &sequencing_id));
-  // The lastSucceedUploadedRecord should be the record before the one
-  // indicated in seq_info.
   response.SetStringPath("lastSucceedUploadedRecord.sequencingId",
                          base::NumberToString(sequencing_id - 1));
-  response.SetStringPath("lastSucceedUploadedRecord.generationId",
-                         *seq_info.FindStringKey("generationId"));
-  response.SetIntPath("lastSucceedUploadedRecord.priority",
-                      seq_info.FindIntKey("priority").value());
 
   // The firstFailedUploadedRecord.failedUploadedRecord should be the one
   // indicated in seq_info.
diff --git a/chrome/browser/prefetch/no_state_prefetch/prerender_unittest.cc b/chrome/browser/prefetch/no_state_prefetch/prerender_unittest.cc
index 92c664b..a600c09 100644
--- a/chrome/browser/prefetch/no_state_prefetch/prerender_unittest.cc
+++ b/chrome/browser/prefetch/no_state_prefetch/prerender_unittest.cc
@@ -538,6 +538,20 @@
                          url, nullptr, gfx::Size()));
 }
 
+// Verify that link-rel:next URLs are not prefetched.
+TEST_F(PrerenderTest, LinkRelNextWithNSPDisabled) {
+  GURL url("http://www.notgoogle.com/");
+  prerender_manager()->CreateNextPrerenderContents(
+      url, url::Origin::Create(GURL("www.notgoogle.com")), ORIGIN_LINK_REL_NEXT,
+      FINAL_STATUS_PROFILE_DESTROYED);
+  EXPECT_EQ(nullptr,
+            prerender_manager()->AddPrerenderWithPreconnectFallbackForTesting(
+                ORIGIN_LINK_REL_NEXT, url,
+                url::Origin::Create(GURL("www.notgoogle.com"))));
+  histogram_tester().ExpectUniqueSample(
+      "Prerender.FinalStatus", FINAL_STATUS_LINK_REL_NEXT_NOT_ALLOWED, 1);
+}
+
 TEST_F(PrerenderTest, PredictorPrefetchHoldbackOffNonPredictorReferrer) {
   GURL url("http://www.notgoogle.com/");
   base::test::ScopedFeatureList scoped_feature_list;
diff --git a/chrome/browser/renderer_host/chrome_render_widget_host_view_mac_history_swiper_browsertest.mm b/chrome/browser/renderer_host/chrome_render_widget_host_view_mac_history_swiper_browsertest.mm
index 1457565..6af002a 100644
--- a/chrome/browser/renderer_host/chrome_render_widget_host_view_mac_history_swiper_browsertest.mm
+++ b/chrome/browser/renderer_host/chrome_render_widget_host_view_mac_history_swiper_browsertest.mm
@@ -744,7 +744,7 @@
 // Initial movements are vertical, and scroll the iframe. Subsequent movements
 // are horizontal, and should not trigger history swiping.
 IN_PROC_BROWSER_TEST_F(ChromeRenderWidgetHostViewMacHistorySwiperTest,
-                       TestIframeHistorySwiping) {
+                       DISABLED_TestIframeHistorySwiping) {
   if (!IsHistorySwipingSupported())
     return;
 
diff --git a/chrome/browser/resources/tab_search/tab_search_resources.grd b/chrome/browser/resources/tab_search/tab_search_resources.grd
deleted file mode 100644
index 8c32d63..0000000
--- a/chrome/browser/resources/tab_search/tab_search_resources.grd
+++ /dev/null
@@ -1,48 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<grit latest_public_release="0" current_release="1" output_all_resource_defines="false">
-  <outputs>
-    <output filename="grit/tab_search_resources.h" type="rc_header">
-      <emit emit_type='prepend'></emit>
-    </output>
-    <output filename="grit/tab_search_resources_map.cc"
-            type="resource_file_map_source" />
-    <output filename="grit/tab_search_resources_map.h"
-            type="resource_map_header" />
-    <output filename="tab_search_resources.pak" type="data_package" />
-  </outputs>
-  <release seq="1">
-    <includes>
-      <include name="IDR_APP_JS"
-               file="${root_gen_dir}/chrome/browser/resources/tab_search/app.js"
-               type="BINDATA"
-               use_base_dir="false" />
-      <include name="IDR_FUSE_JS"
-               file="../../../../third_party/fusejs/dist/fuse.basic.esm.min.js"
-               type="BINDATA"/>
-      <include name="IDR_FUZZY_SEARCH_JS"
-               file="fuzzy_search.js"
-               type="BINDATA"/>
-      <include name="IDR_TAB_DATA_JS"
-               file="tab_data.js"
-               type="BINDATA" />
-      <include name="IDR_TAB_SEARCH_API_PROXY_JS"
-               file="tab_search_api_proxy.js"
-               type="BINDATA" />
-      <include name="IDR_TAB_SEARCH_ITEM_JS"
-               file="${root_gen_dir}/chrome/browser/resources/tab_search/tab_search_item.js"
-               type="BINDATA"
-               use_base_dir="false"/>
-      <include name="IDR_TAB_SEARCH_MOJOM_WEBUI_JS"
-               file="${root_gen_dir}/mojom-webui/chrome/browser/ui/webui/tab_search/tab_search.mojom-webui.js"
-               type="BINDATA"
-               use_base_dir="false"/>
-      <include name="IDR_TAB_SEARCH_PAGE_HTML"
-               file="tab_search_page.html"
-               type="BINDATA" />
-      <include name="IDR_TAB_SEARCH_SEARCH_FIELD_JS"
-               file="${root_gen_dir}/chrome/browser/resources/tab_search/tab_search_search_field.js"
-               type="BINDATA"
-               use_base_dir="false" />
-    </includes>
-  </release>
-</grit>
diff --git a/chrome/browser/subresource_redirect/https_image_compression_infobar_decider.cc b/chrome/browser/subresource_redirect/https_image_compression_infobar_decider.cc
index 10b1d83..bc875d4 100644
--- a/chrome/browser/subresource_redirect/https_image_compression_infobar_decider.cc
+++ b/chrome/browser/subresource_redirect/https_image_compression_infobar_decider.cc
@@ -31,9 +31,9 @@
 
 // The time used to compare and identify recent LiteMode users. Users who
 // enabled LiteMode before this time are treated as non-recent and the one-time
-// https image compression InfoBar is shown for them. Set approximate as M85
-// release date, which is the target for https image compression feature.
-constexpr char kRecentLiteModeUserEnableTime[] = "2020-08-25T00:00:01Z";
+// https image compression InfoBar is shown for them. Set approximate as M88
+// release date, which is the target for https image compression V2 feature.
+constexpr char kRecentLiteModeUserEnableTime[] = "2021-01-19T00:00:01Z";
 
 }  // namespace
 
diff --git a/chrome/browser/subresource_redirect/https_image_compression_infobar_decider_unittest.cc b/chrome/browser/subresource_redirect/https_image_compression_infobar_decider_unittest.cc
index 0c30b0f..9e1e8d3a 100644
--- a/chrome/browser/subresource_redirect/https_image_compression_infobar_decider_unittest.cc
+++ b/chrome/browser/subresource_redirect/https_image_compression_infobar_decider_unittest.cc
@@ -130,7 +130,7 @@
 }
 
 TEST_F(HttpsImageCompressionInfoBarDeciderPrefTest, TestRecentLiteModeUser) {
-  SetLiteModeLastEnableDate("2020-12-01T00:00:01Z");
+  SetLiteModeLastEnableDate("2021-12-01T00:00:01Z");
   HttpsImageCompressionInfoBarDecider* decider = GetDeciderWithDRPEnabled(true);
   EXPECT_FALSE(decider->NeedToShowInfoBar());
 
@@ -143,7 +143,7 @@
 
 TEST_F(HttpsImageCompressionInfoBarDeciderPrefTest, TestNonRecentLiteModeUser) {
   HttpsImageCompressionInfoBarDecider* decider = GetDeciderWithDRPEnabled(true);
-  SetLiteModeLastEnableDate("2020-01-01T00:00:01Z");
+  SetLiteModeLastEnableDate("2021-01-01T00:00:01Z");
   EXPECT_TRUE(decider->NeedToShowInfoBar());
   decider->SetUserHasSeenInfoBar();
   EXPECT_FALSE(decider->NeedToShowInfoBar());
diff --git a/chrome/browser/sync/device_info_sync_service_factory.cc b/chrome/browser/sync/device_info_sync_service_factory.cc
index 2eb956e..56fcb64 100644
--- a/chrome/browser/sync/device_info_sync_service_factory.cc
+++ b/chrome/browser/sync/device_info_sync_service_factory.cc
@@ -72,22 +72,28 @@
   }
 
   // syncer::DeviceInfoSyncClient:
-  std::string GetFCMRegistrationToken() const override {
+  base::Optional<std::string> GetFCMRegistrationToken() const override {
     syncer::SyncInvalidationsService* service =
         SyncInvalidationsServiceFactory::GetForProfile(profile_);
     if (service) {
       return service->GetFCMRegistrationToken();
     }
+    // If the service is not enabled, then the registration token must be empty,
+    // not unknown (base::nullopt). This is needed to reset previous token if
+    // the invalidations have been turned off.
     return std::string();
   }
 
   // syncer::DeviceInfoSyncClient:
-  syncer::ModelTypeSet GetInterestedDataTypes() const override {
+  base::Optional<syncer::ModelTypeSet> GetInterestedDataTypes() const override {
     syncer::SyncInvalidationsService* service =
         SyncInvalidationsServiceFactory::GetForProfile(profile_);
     if (service) {
       return service->GetInterestedDataTypes();
     }
+    // If the service is not enabled, then the list of types must be empty, not
+    // unknown (base::nullopt). This is needed to reset previous types if the
+    // invalidations have been turned off.
     return syncer::ModelTypeSet();
   }
 
diff --git a/chrome/browser/sync/test/integration/single_client_device_info_sync_test.cc b/chrome/browser/sync/test/integration/single_client_device_info_sync_test.cc
index d5347f0..9af51e72 100644
--- a/chrome/browser/sync/test/integration/single_client_device_info_sync_test.cc
+++ b/chrome/browser/sync/test/integration/single_client_device_info_sync_test.cc
@@ -5,6 +5,7 @@
 #include <string>
 
 #include "base/strings/stringprintf.h"
+#include "base/test/bind.h"
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
 #include "chrome/browser/sync/test/integration/device_info_helper.h"
@@ -15,6 +16,7 @@
 #include "components/sync/base/sync_prefs.h"
 #include "components/sync/base/time.h"
 #include "components/sync/driver/sync_driver_switches.h"
+#include "components/sync/invalidations/switches.h"
 #include "components/sync/protocol/proto_value_conversions.h"
 #include "components/sync/protocol/sync.pb.h"
 #include "components/sync/test/fake_server/fake_server.h"
@@ -201,4 +203,46 @@
   EXPECT_FALSE(message.commit().config_params().single_client());
 }
 
+IN_PROC_BROWSER_TEST_F(SingleClientDeviceInfoSyncTest,
+                       PRE_ShouldNotSendDeviceInfoAfterBrowserRestart) {
+  ASSERT_TRUE(SetupSync());
+  EXPECT_TRUE(
+      ServerDeviceInfoMatchChecker(
+          GetFakeServer(), ElementsAre(HasCacheGuid(GetLocalCacheGuid())))
+          .Wait());
+}
+
+IN_PROC_BROWSER_TEST_F(SingleClientDeviceInfoSyncTest,
+                       ShouldNotSendDeviceInfoAfterBrowserRestart) {
+  const std::vector<sync_pb::SyncEntity> entities_before =
+      fake_server_->GetSyncEntitiesByModelType(syncer::DEVICE_INFO);
+  ASSERT_TRUE(SetupClients());
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  // signin::SetRefreshTokenForPrimaryAccount() is needed on ChromeOS in order
+  // to get a non-empty refresh token on startup.
+  GetClient(0)->SignInPrimaryAccount();
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+  ASSERT_TRUE(GetClient(0)->AwaitEngineInitialization());
+  ASSERT_TRUE(GetClient(0)->AwaitSyncSetupCompletion());
+
+  bool has_local_changes = false;
+  base::RunLoop run_loop;
+  GetSyncService(0)->HasUnsyncedItemsForTest(
+      base::BindLambdaForTesting([&has_local_changes, &run_loop](bool result) {
+        has_local_changes = result;
+        run_loop.Quit();
+      }));
+  run_loop.Run();
+
+  const std::vector<sync_pb::SyncEntity> entities_after =
+      fake_server_->GetSyncEntitiesByModelType(syncer::DEVICE_INFO);
+  ASSERT_EQ(1U, entities_before.size());
+  ASSERT_EQ(1U, entities_after.size());
+
+  // Check that there are no local changes and nothing has been committed to the
+  // server.
+  EXPECT_FALSE(has_local_changes);
+  EXPECT_EQ(entities_before.front().mtime(), entities_after.front().mtime());
+}
+
 }  // namespace
diff --git a/chrome/browser/sync/test/integration/single_client_sync_invalidations_test.cc b/chrome/browser/sync/test/integration/single_client_sync_invalidations_test.cc
index 55de306..f048331 100644
--- a/chrome/browser/sync/test/integration/single_client_sync_invalidations_test.cc
+++ b/chrome/browser/sync/test/integration/single_client_sync_invalidations_test.cc
@@ -100,8 +100,9 @@
   syncer::SyncInvalidationsService* sync_invalidations_service =
       SyncInvalidationsServiceFactory::GetForProfile(GetProfile(0));
   ASSERT_THAT(sync_invalidations_service, NotNull());
-  syncer::ModelTypeSet interested_data_types =
-      sync_invalidations_service->GetInterestedDataTypes();
+  ASSERT_TRUE(sync_invalidations_service->GetInterestedDataTypes());
+  const syncer::ModelTypeSet interested_data_types =
+      *sync_invalidations_service->GetInterestedDataTypes();
 
   // Check that some "standard" data types are included.
   EXPECT_TRUE(
@@ -146,9 +147,12 @@
   syncer::SyncInvalidationsService* sync_invalidations_service =
       SyncInvalidationsServiceFactory::GetForProfile(GetProfile(0));
   ASSERT_THAT(sync_invalidations_service, NotNull());
-  syncer::ModelTypeSet interested_data_types =
-      sync_invalidations_service->GetInterestedDataTypes();
-  std::string fcm_token = sync_invalidations_service->GetFCMRegistrationToken();
+  ASSERT_TRUE(sync_invalidations_service->GetInterestedDataTypes());
+  ASSERT_TRUE(sync_invalidations_service->GetFCMRegistrationToken());
+  const syncer::ModelTypeSet interested_data_types =
+      *sync_invalidations_service->GetInterestedDataTypes();
+  const std::string fcm_token =
+      *sync_invalidations_service->GetFCMRegistrationToken();
 
   // Check that some "standard" data types are included.
   EXPECT_TRUE(
@@ -205,9 +209,12 @@
   syncer::SyncInvalidationsService* sync_invalidations_service =
       SyncInvalidationsServiceFactory::GetForProfile(GetProfile(0));
   ASSERT_THAT(sync_invalidations_service, NotNull());
-  syncer::ModelTypeSet interested_data_types =
-      sync_invalidations_service->GetInterestedDataTypes();
-  std::string fcm_token = sync_invalidations_service->GetFCMRegistrationToken();
+  ASSERT_TRUE(sync_invalidations_service->GetInterestedDataTypes());
+  ASSERT_TRUE(sync_invalidations_service->GetFCMRegistrationToken());
+  const syncer::ModelTypeSet interested_data_types =
+      *sync_invalidations_service->GetInterestedDataTypes();
+  const std::string fcm_token =
+      *sync_invalidations_service->GetFCMRegistrationToken();
 
   // Check that some "standard" data types are included.
   EXPECT_TRUE(
@@ -274,24 +281,30 @@
 
   // The local device should eventually be committed to the server. The FCM
   // token should be present in device info.
-  std::string old_token =
-      SyncInvalidationsServiceFactory::GetForProfile(GetProfile(0))
-          ->GetFCMRegistrationToken();
+  ASSERT_TRUE(SyncInvalidationsServiceFactory::GetForProfile(GetProfile(0))
+                  ->GetFCMRegistrationToken());
+  const std::string old_token =
+      *SyncInvalidationsServiceFactory::GetForProfile(GetProfile(0))
+           ->GetFCMRegistrationToken();
   EXPECT_TRUE(ServerDeviceInfoMatchChecker(
                   GetFakeServer(), ElementsAre(HasInstanceIdToken(old_token)))
                   .Wait());
 
   // Sign out. The FCM token should be cleared.
   GetClient(0)->SignOutPrimaryAccount();
+  ASSERT_TRUE(SyncInvalidationsServiceFactory::GetForProfile(GetProfile(0))
+                  ->GetFCMRegistrationToken());
   EXPECT_TRUE(SyncInvalidationsServiceFactory::GetForProfile(GetProfile(0))
                   ->GetFCMRegistrationToken()
-                  .empty());
+                  ->empty());
 
   // Sign in again.
   ASSERT_TRUE(GetClient(0)->SignInPrimaryAccount());
-  std::string new_token =
-      SyncInvalidationsServiceFactory::GetForProfile(GetProfile(0))
-          ->GetFCMRegistrationToken();
+  ASSERT_TRUE(SyncInvalidationsServiceFactory::GetForProfile(GetProfile(0))
+                  ->GetFCMRegistrationToken());
+  const std::string new_token =
+      *SyncInvalidationsServiceFactory::GetForProfile(GetProfile(0))
+           ->GetFCMRegistrationToken();
   EXPECT_NE(new_token, old_token);
   EXPECT_FALSE(new_token.empty());
   // New device info should eventually be committed to the server (but the old
diff --git a/chrome/browser/themes/theme_syncable_service.cc b/chrome/browser/themes/theme_syncable_service.cc
index 757c3da..e95832e 100644
--- a/chrome/browser/themes/theme_syncable_service.cc
+++ b/chrome/browser/themes/theme_syncable_service.cc
@@ -43,7 +43,6 @@
     : profile_(profile),
       theme_service_(theme_service),
       use_system_theme_by_default_(false) {
-  DCHECK(profile_);
   DCHECK(theme_service_);
 }
 
@@ -61,6 +60,22 @@
   }
 }
 
+void ThemeSyncableService::AddObserver(
+    ThemeSyncableService::Observer* observer) {
+  observer_list_.AddObserver(observer);
+  if (sync_processor_)
+    observer->OnThemeSyncStarted();
+}
+
+void ThemeSyncableService::RemoveObserver(
+    ThemeSyncableService::Observer* observer) {
+  observer_list_.RemoveObserver(observer);
+}
+
+void ThemeSyncableService::NotifyOnSyncStartedForTesting() {
+  NotifyOnSyncStarted();
+}
+
 void ThemeSyncableService::WaitUntilReadyToSync(base::OnceClosure done) {
   extensions::ExtensionSystem::Get(profile_)->ready().Post(FROM_HERE,
                                                            std::move(done));
@@ -91,6 +106,7 @@
   if (!GetThemeSpecificsFromCurrentTheme(&current_specifics)) {
     // Current theme is unsyncable - don't overwrite from sync data, and don't
     // save the unsyncable theme to sync data.
+    NotifyOnSyncStarted();
     return base::nullopt;
   }
 
@@ -103,13 +119,17 @@
       if (!HasNonDefaultTheme(current_specifics) ||
           HasNonDefaultTheme(sync_data->GetSpecifics().theme())) {
         MaybeSetTheme(current_specifics, *sync_data);
+        NotifyOnSyncStarted();
         return base::nullopt;
       }
     }
   }
 
   // No theme specifics are found. Create one according to current theme.
-  return ProcessNewTheme(syncer::SyncChange::ACTION_ADD, current_specifics);
+  base::Optional<syncer::ModelError> error =
+      ProcessNewTheme(syncer::SyncChange::ACTION_ADD, current_specifics);
+  NotifyOnSyncStarted();
+  return error;
 }
 
 void ThemeSyncableService::StopSyncing(syncer::ModelType type) {
@@ -369,3 +389,8 @@
 
   return sync_processor_->ProcessSyncChanges(FROM_HERE, changes);
 }
+
+void ThemeSyncableService::NotifyOnSyncStarted() {
+  for (Observer& observer : observer_list_)
+    observer.OnThemeSyncStarted();
+}
diff --git a/chrome/browser/themes/theme_syncable_service.h b/chrome/browser/themes/theme_syncable_service.h
index 083d8c1..a43d4d7 100644
--- a/chrome/browser/themes/theme_syncable_service.h
+++ b/chrome/browser/themes/theme_syncable_service.h
@@ -9,6 +9,8 @@
 
 #include "base/gtest_prod_util.h"
 #include "base/macros.h"
+#include "base/observer_list.h"
+#include "base/observer_list_types.h"
 #include "base/threading/thread_checker.h"
 #include "components/sync/model/sync_change.h"
 #include "components/sync/model/sync_data.h"
@@ -26,8 +28,16 @@
 
 class ThemeSyncableService : public syncer::SyncableService {
  public:
-  ThemeSyncableService(Profile* profile,  // Same profile used by theme_service.
-                       ThemeService* theme_service);
+  class Observer : public base::CheckedObserver {
+   public:
+    // Called when theme sync gets started. Observers that register after theme
+    // sync gets started are called right away when they register.
+    virtual void OnThemeSyncStarted() = 0;
+  };
+
+  // `profile` may be nullptr in tests (and is the one used by theme_service,
+  // otherwise).
+  ThemeSyncableService(Profile* profile, ThemeService* theme_service);
   ~ThemeSyncableService() override;
 
   static syncer::ModelType model_type() { return syncer::THEMES; }
@@ -35,6 +45,10 @@
   // Called by ThemeService when user changes theme.
   void OnThemeChange();
 
+  void AddObserver(Observer* observer);
+  void RemoveObserver(Observer* observer);
+  void NotifyOnSyncStartedForTesting();
+
   // syncer::SyncableService implementation.
   void WaitUntilReadyToSync(base::OnceClosure done) override;
   base::Optional<syncer::ModelError> MergeDataAndStartSyncing(
@@ -81,9 +95,13 @@
       syncer::SyncChange::SyncChangeType change_type,
       const sync_pb::ThemeSpecifics& theme_specifics);
 
+  void NotifyOnSyncStarted();
+
   Profile* const profile_;
   ThemeService* const theme_service_;
 
+  base::ObserverList<Observer> observer_list_;
+
   std::unique_ptr<syncer::SyncChangeProcessor> sync_processor_;
   std::unique_ptr<syncer::SyncErrorFactory> sync_error_handler_;
 
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index 549167e..e6a3c9ab 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -1570,6 +1570,7 @@
       "//device/bluetooth/strings:strings_grit",
       "//device/fido",
       "//ppapi/c",
+      "//services/device/public/cpp/hid",
       "//services/device/public/mojom",
       "//services/metrics/public/cpp:metrics_cpp",
       "//services/preferences/public/mojom:mojom",
@@ -2954,6 +2955,8 @@
         "signin/dice_web_signin_interceptor_delegate.h",
         "views/profiles/dice_web_signin_interception_bubble_view.cc",
         "views/profiles/dice_web_signin_interception_bubble_view.h",
+        "views/profiles/profile_customization_bubble_sync_controller.cc",
+        "views/profiles/profile_customization_bubble_sync_controller.h",
         "views/profiles/profile_customization_bubble_view.cc",
         "views/profiles/profile_customization_bubble_view.h",
         "webui/signin/dice_turn_sync_on_helper.cc",
diff --git a/chrome/browser/ui/ash/holding_space/holding_space_keyed_service.cc b/chrome/browser/ui/ash/holding_space/holding_space_keyed_service.cc
index 9f3fc9f..b783985f1 100644
--- a/chrome/browser/ui/ash/holding_space/holding_space_keyed_service.cc
+++ b/chrome/browser/ui/ash/holding_space/holding_space_keyed_service.cc
@@ -90,9 +90,12 @@
   // the first time that holding space became available, this will no-op.
   holding_space_prefs::MarkTimeOfFirstAvailability(profile_->GetPrefs());
 
+  ProfileManager* const profile_manager = GetProfileManager();
+  if (!profile_manager)  // May be `nullptr` in tests.
+    return;
+
   // The associated profile may not be ready yet. If it is, we can immediately
   // proceed with profile dependent initialization.
-  ProfileManager* const profile_manager = GetProfileManager();
   if (profile_manager->IsValidProfile(profile)) {
     OnProfileReady();
     return;
@@ -103,9 +106,10 @@
 }
 
 HoldingSpaceKeyedService::~HoldingSpaceKeyedService() {
-  if (HoldingSpaceController::Get()) {
-    // For BrowserWithTestWindowTest that releases profile and its keyed
-    // services before ash Shell.
+  if (chromeos::PowerManagerClient::Get())
+    chromeos::PowerManagerClient::Get()->RemoveObserver(this);
+
+  if (HoldingSpaceController::Get()) {  // May be `nullptr` in tests.
     HoldingSpaceController::Get()->RegisterClientAndModelForUser(
         account_id_, /*client=*/nullptr, /*model=*/nullptr);
   }
@@ -286,12 +290,14 @@
 void HoldingSpaceKeyedService::OnProfileReady() {
   // Observe suspend status - the delegates will be shutdown during suspend.
   if (chromeos::PowerManagerClient::Get())
-    power_manager_observer_.Observe(chromeos::PowerManagerClient::Get());
+    chromeos::PowerManagerClient::Get()->AddObserver(this);
 
   InitializeDelegates();
 
-  HoldingSpaceController::Get()->RegisterClientAndModelForUser(
-      account_id_, &holding_space_client_, &holding_space_model_);
+  if (HoldingSpaceController::Get()) {  // May be `nullptr` in tests.
+    HoldingSpaceController::Get()->RegisterClientAndModelForUser(
+        account_id_, &holding_space_client_, &holding_space_model_);
+  }
 }
 
 void HoldingSpaceKeyedService::SuspendImminent(
diff --git a/chrome/browser/ui/ash/holding_space/holding_space_keyed_service.h b/chrome/browser/ui/ash/holding_space/holding_space_keyed_service.h
index 4d1eb2a..e0130a8 100644
--- a/chrome/browser/ui/ash/holding_space/holding_space_keyed_service.h
+++ b/chrome/browser/ui/ash/holding_space/holding_space_keyed_service.h
@@ -143,10 +143,6 @@
   base::ScopedObservation<ProfileManager, ProfileManagerObserver>
       profile_manager_observer_{this};
 
-  base::ScopedObservation<chromeos::PowerManagerClient,
-                          chromeos::PowerManagerClient::Observer>
-      power_manager_observer_{this};
-
   base::WeakPtrFactory<HoldingSpaceKeyedService> weak_factory_{this};
 };
 
diff --git a/chrome/browser/ui/ash/holding_space/holding_space_keyed_service_factory.cc b/chrome/browser/ui/ash/holding_space/holding_space_keyed_service_factory.cc
index 9ea5631..4479580 100644
--- a/chrome/browser/ui/ash/holding_space/holding_space_keyed_service_factory.cc
+++ b/chrome/browser/ui/ash/holding_space/holding_space_keyed_service_factory.cc
@@ -54,11 +54,6 @@
   return new HoldingSpaceKeyedService(profile, user->GetAccountId());
 }
 
-bool HoldingSpaceKeyedServiceFactory::ServiceIsCreatedWithBrowserContext()
-    const {
-  return true;
-}
-
 void HoldingSpaceKeyedServiceFactory::RegisterProfilePrefs(
     user_prefs::PrefRegistrySyncable* registry) {
   HoldingSpaceKeyedService::RegisterProfilePrefs(registry);
diff --git a/chrome/browser/ui/ash/holding_space/holding_space_keyed_service_factory.h b/chrome/browser/ui/ash/holding_space/holding_space_keyed_service_factory.h
index beb004d6..ac91afa 100644
--- a/chrome/browser/ui/ash/holding_space/holding_space_keyed_service_factory.h
+++ b/chrome/browser/ui/ash/holding_space/holding_space_keyed_service_factory.h
@@ -28,7 +28,6 @@
   // BrowserContextKeyedServiceFactory:
   KeyedService* BuildServiceInstanceFor(
       content::BrowserContext* context) const override;
-  bool ServiceIsCreatedWithBrowserContext() const override;
   void RegisterProfilePrefs(
       user_prefs::PrefRegistrySyncable* registry) override;
 
diff --git a/chrome/browser/ui/ash/holding_space/holding_space_keyed_service_unittest.cc b/chrome/browser/ui/ash/holding_space/holding_space_keyed_service_unittest.cc
index bd6e27ea..56b2ae5 100644
--- a/chrome/browser/ui/ash/holding_space/holding_space_keyed_service_unittest.cc
+++ b/chrome/browser/ui/ash/holding_space/holding_space_keyed_service_unittest.cc
@@ -1359,6 +1359,8 @@
 
 TEST_F(HoldingSpaceKeyedServiceTest, AddDownloadItem) {
   TestingProfile* profile = GetProfile();
+  HoldingSpaceModelAttachedWaiter(profile).Wait();
+
   // Create a test downloads mount point.
   std::unique_ptr<ScopedTestMountPoint> downloads_mount =
       ScopedTestMountPoint::CreateAndMountDownloads(profile);
diff --git a/chrome/browser/ui/ash/system_tray_client.cc b/chrome/browser/ui/ash/system_tray_client.cc
index 76098303..642e2eb6 100644
--- a/chrome/browser/ui/ash/system_tray_client.cc
+++ b/chrome/browser/ui/ash/system_tray_client.cc
@@ -75,8 +75,14 @@
       ProfileManager::GetActiveUserProfile(), sub_page);
 }
 
-// Returns the severity of a pending Chrome / Chrome OS update.
-ash::UpdateSeverity GetUpdateSeverity(UpgradeDetector* detector) {
+// Returns the severity of a pending update.
+ash::UpdateSeverity GetUpdateSeverity(ash::UpdateType update_type,
+                                      UpgradeDetector* detector) {
+  // Lacros is always "low", which is the same severity OS updates start with.
+  if (update_type == ash::UpdateType::kLacros)
+    return ash::UpdateSeverity::kLow;
+
+  // OS updates use UpgradeDetector's severity mapping.
   switch (detector->upgrade_notification_stage()) {
     case UpgradeDetector::UPGRADE_ANNOYANCE_NONE:
       return ash::UpdateSeverity::kNone;
@@ -89,11 +95,8 @@
     case UpgradeDetector::UPGRADE_ANNOYANCE_HIGH:
       return ash::UpdateSeverity::kHigh;
     case UpgradeDetector::UPGRADE_ANNOYANCE_CRITICAL:
-      break;
+      return ash::UpdateSeverity::kCritical;
   }
-  DCHECK_EQ(detector->upgrade_notification_stage(),
-            UpgradeDetector::UPGRADE_ANNOYANCE_CRITICAL);
-  return ash::UpdateSeverity::kCritical;
 }
 
 const chromeos::NetworkState* GetNetworkState(const std::string& network_id) {
@@ -124,7 +127,7 @@
 
   // If an upgrade is available at startup then tell ash about it.
   if (UpgradeDetector::GetInstance()->notify_upgrade())
-    HandleUpdateAvailable();
+    HandleUpdateAvailable(ash::UpdateType::kSystem);
 
   // If the device is enterprise managed then send ash the enterprise domain.
   policy::BrowserPolicyConnectorChromeOS* policy_connector =
@@ -171,7 +174,11 @@
   update_notification_style_ = style;
   update_notification_title_ = notification_title;
   update_notification_body_ = notification_body;
-  HandleUpdateAvailable();
+  HandleUpdateAvailable(ash::UpdateType::kSystem);
+}
+
+void SystemTrayClient::SetLacrosUpdateAvailable() {
+  HandleUpdateAvailable(ash::UpdateType::kLacros);
 }
 
 void SystemTrayClient::SetPrimaryTrayEnabled(bool enabled) {
@@ -460,20 +467,15 @@
   chrome::AttemptUserExit();
 }
 
-void SystemTrayClient::HandleUpdateAvailable() {
-  // Show an update icon for Chrome OS updates.
+void SystemTrayClient::HandleUpdateAvailable(ash::UpdateType update_type) {
   UpgradeDetector* detector = UpgradeDetector::GetInstance();
-  bool update_available = detector->notify_upgrade();
-  DCHECK(update_available);
-  if (!update_available)
+  if (update_type == ash::UpdateType::kSystem && !detector->notify_upgrade()) {
+    LOG(ERROR) << "Tried to show update notification when no update available";
     return;
+  }
 
-  // Get the Chrome update severity.
-  ash::UpdateSeverity severity = GetUpdateSeverity(detector);
-
-  // TODO(https://crbug.com/1154427): Add Lacros update type.
-  ash::UpdateType update_type = ash::UpdateType::kSystem;
-
+  // Show the system tray icon.
+  ash::UpdateSeverity severity = GetUpdateSeverity(update_type, detector);
   system_tray_->ShowUpdateIcon(severity, detector->is_factory_reset_required(),
                                detector->is_rollback(), update_type);
 
@@ -506,7 +508,7 @@
 }
 
 void SystemTrayClient::OnUpgradeRecommended() {
-  HandleUpdateAvailable();
+  HandleUpdateAvailable(ash::UpdateType::kSystem);
 }
 
 ////////////////////////////////////////////////////////////////////////////////
diff --git a/chrome/browser/ui/ash/system_tray_client.h b/chrome/browser/ui/ash/system_tray_client.h
index 97e9237..5d426ec3 100644
--- a/chrome/browser/ui/ash/system_tray_client.h
+++ b/chrome/browser/ui/ash/system_tray_client.h
@@ -16,6 +16,7 @@
 class SystemTray;
 enum class LoginStatus;
 enum class NotificationStyle;
+enum class UpdateType;
 }  // namespace ash
 
 // Handles method calls delegated back to chrome from ash. Also notifies ash of
@@ -33,10 +34,14 @@
 
   // Specifies if notification is recommended or required by administrator and
   // triggers the notification to be shown with the given body and title.
+  // Only applies to OS updates.
   void SetUpdateNotificationState(ash::NotificationStyle style,
                                   const base::string16& notification_title,
                                   const base::string16& notification_body);
 
+  // Shows a notification that a Lacros browser update is available.
+  void SetLacrosUpdateAvailable();
+
   // Wrappers around ash::mojom::SystemTray interface:
   void SetPrimaryTrayEnabled(bool enabled);
   void SetPrimaryTrayVisible(bool visible);
@@ -83,7 +88,7 @@
                                  bool show_configure);
 
   // Requests that ash show the update available icon.
-  void HandleUpdateAvailable();
+  void HandleUpdateAvailable(ash::UpdateType update_type);
 
   // chromeos::system::SystemClockObserver:
   void OnSystemClockChanged(chromeos::system::SystemClock* clock) override;
diff --git a/chrome/browser/ui/hid/hid_chooser_controller.cc b/chrome/browser/ui/hid/hid_chooser_controller.cc
index a8e836f..e7a8322 100644
--- a/chrome/browser/ui/hid/hid_chooser_controller.cc
+++ b/chrome/browser/ui/hid/hid_chooser_controller.cc
@@ -7,14 +7,12 @@
 #include <utility>
 
 #include "base/bind.h"
-#include "base/strings/stringprintf.h"
-#include "base/strings/utf_string_conversions.h"
 #include "chrome/browser/hid/hid_chooser_context.h"
 #include "chrome/browser/hid/hid_chooser_context_factory.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/usb/usb_blocklist.h"
 #include "chrome/grit/generated_resources.h"
 #include "content/public/browser/web_contents.h"
+#include "services/device/public/cpp/hid/hid_blocklist.h"
 #include "ui/base/l10n/l10n_util.h"
 
 namespace {
@@ -193,8 +191,8 @@
 
 bool HidChooserController::DisplayDevice(
     const device::mojom::HidDeviceInfo& device) const {
-  // Do not pass the device to the chooser if it is on the USB blocklist.
-  if (UsbBlocklist::Get().IsExcluded({device.vendor_id, device.product_id, 0}))
+  // Do not pass the device to the chooser if it is excluded by the blocklist.
+  if (device::HidBlocklist::IsDeviceExcluded(device))
     return false;
 
   // Do not pass the device to the chooser if it has a top-level collection with
diff --git a/chrome/browser/ui/startup/bad_flags_prompt.cc b/chrome/browser/ui/startup/bad_flags_prompt.cc
index 5a40403..6c831a8 100644
--- a/chrome/browser/ui/startup/bad_flags_prompt.cc
+++ b/chrome/browser/ui/startup/bad_flags_prompt.cc
@@ -38,6 +38,7 @@
 #include "media/base/media_switches.h"
 #include "media/media_buildflags.h"
 #include "sandbox/policy/switches.h"
+#include "services/device/public/cpp/hid/hid_switches.h"
 #include "services/network/public/cpp/network_switches.h"
 #include "third_party/blink/public/common/features.h"
 #include "ui/base/l10n/l10n_util.h"
@@ -143,6 +144,9 @@
     // A flag to support local file based WebBundle loading, only for testing
     // purpose.
     switches::kTrustableWebBundleFileUrl,
+
+    // A flag to bypass the WebHID blocklist for testing purposes.
+    switches::kDisableHidBlocklist,
 };
 #endif  // OS_ANDROID
 
diff --git a/chrome/browser/ui/views/extensions/extensions_menu_view.cc b/chrome/browser/ui/views/extensions/extensions_menu_view.cc
index 91bd7c3..1f8b20e 100644
--- a/chrome/browser/ui/views/extensions/extensions_menu_view.cc
+++ b/chrome/browser/ui/views/extensions/extensions_menu_view.cc
@@ -79,6 +79,11 @@
                   IDS_EXTENSIONS_MENU_ACCESSING_SITE_DATA_SHORT,
                   IDS_EXTENSIONS_MENU_ACCESSING_SITE_DATA,
                   ToolbarActionViewController::PageInteractionStatus::kActive} {
+  // Ensure layer masking is used for the extensions menu to ensure buttons with
+  // layer effects sitting flush with the bottom of the bubble are clipped
+  // appropriately.
+  SetPaintClientToLayer(true);
+
   toolbar_model_observation_.Observe(toolbar_model_);
   browser_->tab_strip_model()->AddObserver(this);
   set_margins(gfx::Insets(0));
diff --git a/chrome/browser/ui/views/frame/tab_strip_region_view.cc b/chrome/browser/ui/views/frame/tab_strip_region_view.cc
index a058f50..1fd4387 100644
--- a/chrome/browser/ui/views/frame/tab_strip_region_view.cc
+++ b/chrome/browser/ui/views/frame/tab_strip_region_view.cc
@@ -132,8 +132,9 @@
       views::FlexSpecification(views::MinimumFlexSizeRule::kPreferred,
                                views::MaximumFlexSizeRule::kUnbounded));
 
-  if (base::FeatureList::IsEnabled(features::kTabSearch) &&
-      tab_strip_->controller()->GetBrowser()->is_type_normal()) {
+  const Browser* browser = tab_strip_->controller()->GetBrowser();
+  if (base::FeatureList::IsEnabled(features::kTabSearch) && browser &&
+      browser->is_type_normal()) {
     auto tab_search_button = std::make_unique<TabSearchButton>(tab_strip_);
     tab_search_button->SetTooltipText(
         l10n_util::GetStringUTF16(IDS_TOOLTIP_TAB_SEARCH));
diff --git a/chrome/browser/ui/views/profiles/profile_customization_bubble_sync_controller.cc b/chrome/browser/ui/views/profiles/profile_customization_bubble_sync_controller.cc
new file mode 100644
index 0000000..83f00cf6
--- /dev/null
+++ b/chrome/browser/ui/views/profiles/profile_customization_bubble_sync_controller.cc
@@ -0,0 +1,139 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/views/profiles/profile_customization_bubble_sync_controller.h"
+
+#include "chrome/browser/sync/profile_sync_service_factory.h"
+#include "chrome/browser/themes/theme_service.h"
+#include "chrome/browser/themes/theme_service_factory.h"
+#include "chrome/browser/ui/views/profiles/profile_customization_bubble_view.h"
+#include "components/sync/driver/sync_user_settings.h"
+
+namespace {
+
+bool CanSyncStart(syncer::SyncService* sync_service) {
+  if (!sync_service || !sync_service->CanSyncFeatureStart())
+    return false;
+  if (!sync_service->GetUserSettings()->GetSelectedTypes().Has(
+          syncer::UserSelectableType::kThemes)) {
+    return false;
+  }
+  return true;
+}
+
+void ShowBubble(Profile* profile, views::View* anchor_view, bool should_show) {
+  if (!should_show)
+    return;
+  ProfileCustomizationBubbleView::CreateBubble(profile, anchor_view);
+}
+
+}  // namespace
+
+// static
+void ProfileCustomizationBubbleSyncController::
+    ApplyColorAndShowBubbleWhenNoValueSynced(Profile* profile,
+                                             views::View* anchor_view,
+                                             SkColor suggested_profile_color) {
+  // The controller is owned by itself.
+  auto* controller = new ProfileCustomizationBubbleSyncController(
+      ProfileSyncServiceFactory::GetForProfile(profile),
+      ThemeServiceFactory::GetForProfile(profile),
+      base::BindOnce(&ShowBubble, profile, anchor_view),
+      suggested_profile_color);
+  controller->Init();
+}
+
+// static
+void ProfileCustomizationBubbleSyncController::
+    ApplyColorAndShowBubbleWhenNoValueSyncedForTesting(
+        syncer::SyncService* sync_service,
+        ThemeService* theme_service,
+        base::OnceCallback<void(bool)> show_bubble_callback,
+        SkColor suggested_profile_color) {
+  // The controller is owned by itself.
+  auto* controller = new ProfileCustomizationBubbleSyncController(
+      sync_service, theme_service, std::move(show_bubble_callback),
+      suggested_profile_color);
+  controller->Init();
+}
+
+// static
+bool ProfileCustomizationBubbleSyncController::CanThemeSyncStart(
+    Profile* profile) {
+  syncer::SyncService* sync_service =
+      ProfileSyncServiceFactory::GetForProfile(profile);
+  return CanSyncStart(sync_service);
+}
+
+ProfileCustomizationBubbleSyncController::
+    ProfileCustomizationBubbleSyncController(
+        syncer::SyncService* sync_service,
+        ThemeService* theme_service,
+        base::OnceCallback<void(bool)> show_bubble_callback,
+        SkColor suggested_profile_color)
+    : sync_service_(sync_service),
+      theme_service_(theme_service),
+      show_bubble_callback_(std::move(show_bubble_callback)),
+      suggested_profile_color_(suggested_profile_color) {
+  DCHECK(sync_service_);
+  DCHECK(theme_service_);
+  DCHECK(show_bubble_callback_);
+}
+
+ProfileCustomizationBubbleSyncController::
+    ~ProfileCustomizationBubbleSyncController() = default;
+
+void ProfileCustomizationBubbleSyncController::Init() {
+  if (!CanSyncStart(sync_service_)) {
+    ApplyDefaultColorAndShowBubble();
+    return;
+  }
+
+  theme_observation_.Observe(theme_service_->GetThemeSyncableService());
+
+  // Observe also the sync service to abort waiting for theme sync if the user
+  // hits any error or if custom passphrase is needed.
+  sync_observation_.Observe(sync_service_);
+}
+
+void ProfileCustomizationBubbleSyncController::OnStateChanged(
+    syncer::SyncService* sync) {
+  // If we figure out sync cannot start (soon), skip the check and show the
+  // bubble.
+  if (!CanSyncStart(sync)) {
+    ApplyDefaultColorAndShowBubble();
+    return;
+  }
+
+  if (sync->GetUserSettings()->IsPassphraseRequired()) {
+    // Keep the default color and do not show the bubble. The reason is that the
+    // custom passphrase user may have a color in their sync but Chrome will
+    // figure that out later (once the user enter their passphrase) so no prior
+    // customization makes sense.
+    SkipBubble();
+  }
+}
+
+void ProfileCustomizationBubbleSyncController::OnThemeSyncStarted() {
+  // Skip the bubble (and not use the default color) if the user got a
+  // non-default value from sync.
+  if (!theme_service_->UsingDefaultTheme() &&
+      !theme_service_->UsingSystemTheme()) {
+    SkipBubble();
+    return;
+  }
+  ApplyDefaultColorAndShowBubble();
+}
+
+void ProfileCustomizationBubbleSyncController::
+    ApplyDefaultColorAndShowBubble() {
+  theme_service_->BuildAutogeneratedThemeFromColor(suggested_profile_color_);
+  std::move(show_bubble_callback_).Run(true);
+  delete this;
+}
+
+void ProfileCustomizationBubbleSyncController::SkipBubble() {
+  std::move(show_bubble_callback_).Run(false);
+  delete this;
+}
diff --git a/chrome/browser/ui/views/profiles/profile_customization_bubble_sync_controller.h b/chrome/browser/ui/views/profiles/profile_customization_bubble_sync_controller.h
new file mode 100644
index 0000000..2b97d833
--- /dev/null
+++ b/chrome/browser/ui/views/profiles/profile_customization_bubble_sync_controller.h
@@ -0,0 +1,90 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_VIEWS_PROFILES_PROFILE_CUSTOMIZATION_BUBBLE_SYNC_CONTROLLER_H_
+#define CHROME_BROWSER_UI_VIEWS_PROFILES_PROFILE_CUSTOMIZATION_BUBBLE_SYNC_CONTROLLER_H_
+
+#include "base/callback.h"
+#include "base/scoped_observation.h"
+#include "chrome/browser/themes/theme_syncable_service.h"
+#include "components/sync/driver/sync_service.h"
+#include "components/sync/driver/sync_service_observer.h"
+#include "third_party/skia/include/core/SkColor.h"
+
+namespace views {
+class View;
+}  // namespace views
+
+class Profile;
+
+// Helper class for logic to show / delay showing the profile customization
+// bubble. Owns itself.
+class ProfileCustomizationBubbleSyncController
+    : public syncer::SyncServiceObserver,
+      public ThemeSyncableService::Observer {
+ public:
+  ~ProfileCustomizationBubbleSyncController() override;
+
+  ProfileCustomizationBubbleSyncController(
+      const ProfileCustomizationBubbleSyncController& other) = delete;
+  ProfileCustomizationBubbleSyncController& operator=(
+      const ProfileCustomizationBubbleSyncController& other) = delete;
+
+  // Applies `suggested_profile_color` and shows the profile customization
+  // bubble if either (a) theme sync cannot start (see `CanThemeSyncStart()`) or
+  // (b) theme sync is successful and results in the default theme in this
+  // profile (either no value was synced before or the default theme was
+  // synced). In all other cases, the call has no visible impact. This also
+  // includes the case when sync can start but is blocked on the user to enter
+  // the custom passphrase.
+  static void ApplyColorAndShowBubbleWhenNoValueSynced(
+      Profile* profile,
+      views::View* anchor_view,
+      SkColor suggested_profile_color);
+
+  // A version of ApplyColorAndShowBubbleWhenNoValueSynced() that allows simpler
+  // mocking.
+  static void ApplyColorAndShowBubbleWhenNoValueSyncedForTesting(
+      syncer::SyncService* sync_service,
+      ThemeService* theme_service,
+      base::OnceCallback<void(bool)> show_bubble_callback,
+      SkColor suggested_profile_color);
+
+  // Returns whether theme sync can start (i.e. is not disabled by policy,
+  // theme sync is enabled, ...).
+  static bool CanThemeSyncStart(Profile* profile);
+
+ private:
+  ProfileCustomizationBubbleSyncController(
+      syncer::SyncService* sync_service,
+      ThemeService* theme_service,
+      base::OnceCallback<void(bool)> show_bubble_callback,
+      SkColor suggested_profile_color);
+
+  // SyncServiceObserver:
+  void OnStateChanged(syncer::SyncService* sync) override;
+
+  // ThemeSyncableService::Observer:
+  void OnThemeSyncStarted() override;
+
+  // This function may delete the object.
+  void Init();
+
+  // Functions that finalize the control logic by either showing or skipping the
+  // bubble and deleting itself.
+  void ApplyDefaultColorAndShowBubble();
+  void SkipBubble();
+
+  syncer::SyncService* const sync_service_;
+  ThemeService* const theme_service_;
+  base::OnceCallback<void(bool)> show_bubble_callback_;
+  SkColor const suggested_profile_color_;
+
+  base::ScopedObservation<syncer::SyncService, syncer::SyncServiceObserver>
+      sync_observation_{this};
+  base::ScopedObservation<ThemeSyncableService, ThemeSyncableService::Observer>
+      theme_observation_{this};
+};
+
+#endif  // CHROME_BROWSER_UI_VIEWS_PROFILES_PROFILE_CUSTOMIZATION_BUBBLE_SYNC_CONTROLLER_H_
diff --git a/chrome/browser/ui/views/profiles/profile_customization_bubble_sync_controller_unittest.cc b/chrome/browser/ui/views/profiles/profile_customization_bubble_sync_controller_unittest.cc
new file mode 100644
index 0000000..4908272
--- /dev/null
+++ b/chrome/browser/ui/views/profiles/profile_customization_bubble_sync_controller_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 "chrome/browser/ui/views/profiles/profile_customization_bubble_sync_controller.h"
+
+#include "base/test/mock_callback.h"
+#include "chrome/browser/themes/theme_service.h"
+#include "chrome/browser/themes/theme_syncable_service.h"
+#include "components/sync/driver/sync_service.h"
+#include "components/sync/driver/test_sync_service.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/skia/include/core/SkColor.h"
+
+namespace {
+
+constexpr SkColor kNewProfileColor = SK_ColorRED;
+constexpr SkColor kSyncedProfileColor = SK_ColorBLUE;
+
+class FakeThemeService : public ThemeService {
+ public:
+  explicit FakeThemeService(const ThemeHelper& theme_helper)
+      : ThemeService(nullptr, theme_helper) {}
+
+  void SetThemeSyncableService(ThemeSyncableService* theme_syncable_service) {
+    theme_syncable_service_ = theme_syncable_service;
+  }
+
+  // ThemeService:
+  void DoSetTheme(const extensions::Extension* extension,
+                  bool suppress_infobar) override {
+    using_default_theme_ = false;
+    color_ = 0;
+  }
+
+  void BuildAutogeneratedThemeFromColor(SkColor color) override {
+    color_ = color;
+    using_default_theme_ = false;
+  }
+
+  void UseDefaultTheme() override {
+    using_default_theme_ = true;
+    color_ = 0;
+  }
+
+  bool UsingDefaultTheme() const override { return using_default_theme_; }
+
+  SkColor GetAutogeneratedThemeColor() const override { return color_; }
+
+  ThemeSyncableService* GetThemeSyncableService() const override {
+    return theme_syncable_service_;
+  }
+
+ private:
+  ThemeSyncableService* theme_syncable_service_ = nullptr;
+  bool using_default_theme_ = true;
+  SkColor color_ = 0;
+};
+
+class ProfileCustomizationBubbleSyncControllerTest : public testing::Test {
+ public:
+  ProfileCustomizationBubbleSyncControllerTest()
+      : fake_theme_service_(theme_helper_),
+        theme_syncable_service_(nullptr, &fake_theme_service_) {
+    fake_theme_service_.SetThemeSyncableService(&theme_syncable_service_);
+  }
+
+  void ApplyColorAndShowBubbleWhenNoValueSynced(
+      base::OnceCallback<void(bool)> show_bubble_callback) {
+    ProfileCustomizationBubbleSyncController::
+        ApplyColorAndShowBubbleWhenNoValueSyncedForTesting(
+            &test_sync_service_, &fake_theme_service_,
+            std::move(show_bubble_callback), kNewProfileColor);
+  }
+
+  void SetSyncedProfileColor() {
+    fake_theme_service_.BuildAutogeneratedThemeFromColor(kSyncedProfileColor);
+  }
+
+  void SetSyncedProfileTheme() {
+    fake_theme_service_.DoSetTheme(nullptr, false);
+  }
+
+  syncer::TestSyncService* test_sync_service() { return &test_sync_service_; }
+
+  void NotifyOnSyncStarted() {
+    theme_syncable_service_.NotifyOnSyncStartedForTesting();
+  }
+
+ protected:
+  syncer::TestSyncService test_sync_service_;
+
+ private:
+  FakeThemeService fake_theme_service_;
+  ThemeSyncableService theme_syncable_service_;
+  ThemeHelper theme_helper_;
+};
+
+TEST_F(ProfileCustomizationBubbleSyncControllerTest,
+       ShouldShowWhenSyncGetsDefaultTheme) {
+  base::MockCallback<base::OnceCallback<void(bool)>> show_bubble;
+  EXPECT_CALL(show_bubble, Run(true));
+
+  ApplyColorAndShowBubbleWhenNoValueSynced(show_bubble.Get());
+  NotifyOnSyncStarted();
+}
+
+TEST_F(ProfileCustomizationBubbleSyncControllerTest,
+       ShouldShowWhenSyncDisabled) {
+  base::MockCallback<base::OnceCallback<void(bool)>> show_bubble;
+  EXPECT_CALL(show_bubble, Run(true));
+
+  test_sync_service_.SetDisableReasons(
+      syncer::SyncService::DISABLE_REASON_ENTERPRISE_POLICY);
+  ApplyColorAndShowBubbleWhenNoValueSynced(show_bubble.Get());
+}
+
+TEST_F(ProfileCustomizationBubbleSyncControllerTest,
+       ShouldNotShowWhenSyncGetsCustomColor) {
+  base::MockCallback<base::OnceCallback<void(bool)>> show_bubble;
+  EXPECT_CALL(show_bubble, Run(false));
+
+  ApplyColorAndShowBubbleWhenNoValueSynced(show_bubble.Get());
+  SetSyncedProfileColor();
+  NotifyOnSyncStarted();
+}
+
+TEST_F(ProfileCustomizationBubbleSyncControllerTest,
+       ShouldNotShowWhenSyncGetsCustomTheme) {
+  base::MockCallback<base::OnceCallback<void(bool)>> show_bubble;
+  EXPECT_CALL(show_bubble, Run(false));
+
+  ApplyColorAndShowBubbleWhenNoValueSynced(show_bubble.Get());
+  SetSyncedProfileTheme();
+  NotifyOnSyncStarted();
+}
+
+TEST_F(ProfileCustomizationBubbleSyncControllerTest,
+       ShouldNotShowWhenSyncHasCustomPasshrase) {
+  base::MockCallback<base::OnceCallback<void(bool)>> show_bubble;
+  EXPECT_CALL(show_bubble, Run(false));
+
+  test_sync_service_.SetPassphraseRequired(true);
+  ApplyColorAndShowBubbleWhenNoValueSynced(show_bubble.Get());
+  test_sync_service_.FireStateChanged();
+}
+
+}  // namespace
diff --git a/chrome/browser/ui/views/profiles/profile_picker_view.cc b/chrome/browser/ui/views/profiles/profile_picker_view.cc
index c59791d..94662f8 100644
--- a/chrome/browser/ui/views/profiles/profile_picker_view.cc
+++ b/chrome/browser/ui/views/profiles/profile_picker_view.cc
@@ -30,6 +30,11 @@
 #include "chrome/browser/ui/browser_navigator_params.h"
 #include "chrome/browser/ui/user_manager.h"
 #include "chrome/browser/ui/views/accelerator_table.h"
+#include "chrome/browser/ui/views/frame/browser_view.h"
+#include "chrome/browser/ui/views/frame/toolbar_button_provider.h"
+#include "chrome/browser/ui/views/profiles/avatar_toolbar_button.h"
+#include "chrome/browser/ui/views/profiles/profile_customization_bubble_sync_controller.h"
+#include "chrome/browser/ui/views/profiles/profile_customization_bubble_view.h"
 #include "chrome/browser/ui/views/profiles/profile_picker_view_sync_delegate.h"
 #include "chrome/browser/ui/webui/signin/dice_turn_sync_on_helper.h"
 #include "chrome/browser/ui/webui/signin/profile_picker_ui.h"
@@ -81,6 +86,28 @@
     IDC_CLOSE_TAB, IDC_CLOSE_WINDOW, IDC_EXIT, IDC_FULLSCREEN,
     IDC_MINIMIZE_WINDOW};
 
+void ShowCustomizationBubble(SkColor new_profile_color, Browser* browser) {
+  views::View* anchor_view = BrowserView::GetBrowserViewForBrowser(browser)
+                                 ->toolbar_button_provider()
+                                 ->GetAvatarToolbarButton();
+  DCHECK(anchor_view);
+
+  if (ProfileCustomizationBubbleSyncController::CanThemeSyncStart(
+          browser->profile())) {
+    // For sync users, their profile color has not been applied yet. Call a
+    // helper class that applies the color and shows the bubble only if there is
+    // no conflict with a synced theme / color.
+    ProfileCustomizationBubbleSyncController::
+        ApplyColorAndShowBubbleWhenNoValueSynced(
+            browser->profile(), anchor_view,
+            /*suggested_profile_color=*/new_profile_color);
+  } else {
+    // For non syncing users, simply show the bubble.
+    ProfileCustomizationBubbleView::CreateBubble(browser->profile(),
+                                                 anchor_view);
+  }
+}
+
 GURL CreateURLForEntryPoint(ProfilePicker::EntryPoint entry_point) {
   GURL base_url = GURL(chrome::kChromeUIProfilePickerUrl);
   switch (entry_point) {
@@ -298,8 +325,11 @@
 void ProfilePickerView::SwitchToSignIn(
     SkColor profile_color,
     base::OnceCallback<void(bool)> switch_finished_callback) {
+  profile_color_ = profile_color;
+
   DCHECK(!switch_finished_callback_);
   switch_finished_callback_ = std::move(switch_finished_callback);
+
   size_t icon_index = profiles::GetPlaceholderAvatarIndex();
   // Silently create the new profile for browsing on GAIA (so that the sign-in
   // cookies are stored in the right profile).
@@ -309,11 +339,10 @@
           .ChooseNameForNewProfile(icon_index),
       profiles::GetDefaultAvatarIconUrl(icon_index),
       base::BindRepeating(&ProfilePickerView::OnProfileForSigninCreated,
-                          weak_ptr_factory_.GetWeakPtr(), profile_color));
+                          weak_ptr_factory_.GetWeakPtr()));
 }
 
 void ProfilePickerView::OnProfileForSigninCreated(
-    SkColor profile_color,
     Profile* profile,
     Profile::CreateStatus status) {
   if (status == Profile::CREATE_STATUS_LOCAL_FAIL) {
@@ -335,10 +364,6 @@
     return;
   }
 
-  // Apply a new color to the profile.
-  auto* theme_service = ThemeServiceFactory::GetForProfile(profile);
-  theme_service->BuildAutogeneratedThemeFromColor(profile_color);
-
   if (signin_util::IsForceSigninEnabled()) {
     // Show the embedded sign-in flow if the force signin is enabled.
     // TODO(https://crbug.com/1156096): set the local profile name at the end of
@@ -539,7 +564,9 @@
 
   base::OnceClosure sync_consent_completed_closure =
       base::BindOnce(&ProfilePickerView::FinishSignedInCreationFlow,
-                     weak_ptr_factory_.GetWeakPtr(), BrowserOpenedCallback());
+                     weak_ptr_factory_.GetWeakPtr(),
+                     base::BindOnce(&ShowCustomizationBubble, profile_color_),
+                     /*enterprise_sync_consent_needed=*/false);
 
   // Stop with the sign-in navigation, it is not needed any more and this avoids
   // any glitches of the redirect page getting displayed. This is needed because
@@ -606,7 +633,8 @@
 }
 
 void ProfilePickerView::FinishSignedInCreationFlow(
-    BrowserOpenedCallback callback) {
+    BrowserOpenedCallback callback,
+    bool enterprise_sync_consent_needed) {
   // This can get called first time from a special case handling (such as the
   // Settings link) and than second time when the consent flow finishes. We need
   // to make sure only the first call gets handled.
@@ -617,15 +645,18 @@
   if (name_for_signed_in_profile_.empty()) {
     on_profile_name_available_ =
         base::BindOnce(&ProfilePickerView::FinishSignedInCreationFlowImpl,
-                       weak_ptr_factory_.GetWeakPtr(), std::move(callback));
+                       weak_ptr_factory_.GetWeakPtr(), std::move(callback),
+                       enterprise_sync_consent_needed);
     return;
   }
 
-  FinishSignedInCreationFlowImpl(std::move(callback));
+  FinishSignedInCreationFlowImpl(std::move(callback),
+                                 enterprise_sync_consent_needed);
 }
 
 void ProfilePickerView::FinishSignedInCreationFlowImpl(
-    BrowserOpenedCallback callback) {
+    BrowserOpenedCallback callback,
+    bool enterprise_sync_consent_needed) {
   DCHECK(!name_for_signed_in_profile_.empty());
 
   ProfileAttributesEntry* entry = nullptr;
@@ -641,6 +672,17 @@
   entry->SetIsEphemeral(false);
   entry->SetLocalProfileName(name_for_signed_in_profile_);
 
+  // If sync is not enabled (and will not likely be enabled with an enterprise
+  // consent), apply a new color to the profile (otherwise, a more complicated
+  // logic gets triggered in ShowCustomizationBubble()).
+  if (!enterprise_sync_consent_needed &&
+      !ProfileCustomizationBubbleSyncController::CanThemeSyncStart(
+          signed_in_profile_being_created_)) {
+    auto* theme_service =
+        ThemeServiceFactory::GetForProfile(signed_in_profile_being_created_);
+    theme_service->BuildAutogeneratedThemeFromColor(profile_color_);
+  }
+
   // Skip the FRE for this profile as it's replaced by profile creation flow.
   signed_in_profile_being_created_->GetPrefs()->SetBoolean(
       prefs::kHasSeenWelcomePage, true);
diff --git a/chrome/browser/ui/views/profiles/profile_picker_view.h b/chrome/browser/ui/views/profiles/profile_picker_view.h
index 79943de..3028b636 100644
--- a/chrome/browser/ui/views/profiles/profile_picker_view.h
+++ b/chrome/browser/ui/views/profiles/profile_picker_view.h
@@ -68,8 +68,7 @@
   void SwitchToSignIn(SkColor profile_color,
                       base::OnceCallback<void(bool)> switch_finished_callback);
   // On creation success for the sign-in profile, it rebuilds the view.
-  void OnProfileForSigninCreated(SkColor profile_color,
-                                 Profile* new_profile,
+  void OnProfileForSigninCreated(Profile* new_profile,
                                  Profile::CreateStatus status);
   // Switches the layout to the sync confirmation screen.
   void SwitchToSyncConfirmation();
@@ -116,8 +115,10 @@
 
   // Finishes the creation flow by marking `profile_being_created_` as fully
   // created, opening a browser window for this profile and calling `callback`.
-  void FinishSignedInCreationFlow(BrowserOpenedCallback callback);
-  void FinishSignedInCreationFlowImpl(BrowserOpenedCallback callback);
+  void FinishSignedInCreationFlow(BrowserOpenedCallback callback,
+                                  bool enterprise_sync_consent_needed);
+  void FinishSignedInCreationFlowImpl(BrowserOpenedCallback callback,
+                                      bool enterprise_sync_consent_needed);
 
   // Internal callback to finish the last steps of the signed-in creation flow.
   void OnBrowserOpened(BrowserOpenedCallback finish_flow_callback,
@@ -171,6 +172,11 @@
   // same lifetime. This is used for displaying the sign-in flow.
   std::unique_ptr<content::WebContents> new_profile_contents_;
 
+  // Assigned a value at the beginning of a signed-in profile creation, set for
+  // the profile at the very end to avoid coloring the simple toolbar for GAIA
+  // sign-in (that uses the ThemeProvider of the current profile).
+  SkColor profile_color_;
+
   base::string16 name_for_signed_in_profile_;
   base::OnceClosure on_profile_name_available_;
   base::TimeDelta extended_account_info_timeout_;
diff --git a/chrome/browser/ui/views/profiles/profile_picker_view_browsertest.cc b/chrome/browser/ui/views/profiles/profile_picker_view_browsertest.cc
index e4414e1..2672a0ad 100644
--- a/chrome/browser/ui/views/profiles/profile_picker_view_browsertest.cc
+++ b/chrome/browser/ui/views/profiles/profile_picker_view_browsertest.cc
@@ -271,8 +271,6 @@
   // Add an account - simulate a successful Gaia sign-in.
   Profile* profile_being_created =
       static_cast<Profile*>(web_contents()->GetBrowserContext());
-  syncer::SyncService* sync_service =
-      ProfileSyncServiceFactory::GetForProfile(profile_being_created);
   signin::IdentityManager* identity_manager =
       IdentityManagerFactory::GetForProfile(profile_being_created);
   CoreAccountInfo core_account_info =
@@ -287,9 +285,9 @@
   // confirmation screen getting displayed.
   WaitForFirstPaint(web_contents(), GURL("chrome://sync-confirmation/"));
 
-  // Simulate closing the UI with "Yes, I'm in".
+  // Simulate closing the UI with "No, thanks".
   LoginUIServiceFactory::GetForProfile(profile_being_created)
-      ->SyncConfirmationUIClosed(LoginUIService::SYNC_WITH_DEFAULT_SETTINGS);
+      ->SyncConfirmationUIClosed(LoginUIService::ABORT_SYNC);
   Browser* new_browser = BrowserAddedWaiter(2u).Wait();
   WaitForFirstPaint(new_browser->tab_strip_model()->GetActiveWebContents(),
                     GURL("chrome://newtab/"));
@@ -303,13 +301,12 @@
                   .GetProfileAttributesWithPath(
                       profile_being_created->GetPath(), &entry));
   EXPECT_FALSE(entry->IsEphemeral());
-  EXPECT_TRUE(entry->IsAuthenticated());
+  EXPECT_FALSE(entry->IsAuthenticated());
   EXPECT_EQ(entry->GetLocalProfileName(), base::UTF8ToUTF16("Joe"));
+
   EXPECT_EQ(ThemeServiceFactory::GetForProfile(profile_being_created)
                 ->GetAutogeneratedThemeColor(),
             kProfileColor);
-  EXPECT_TRUE(sync_service->GetUserSettings()->IsSyncRequested());
-  EXPECT_TRUE(sync_service->GetUserSettings()->IsFirstSetupComplete());
 }
 
 IN_PROC_BROWSER_TEST_F(ProfilePickerCreationFlowBrowserTest,
@@ -432,9 +429,9 @@
   // cannot be told.
   EXPECT_TRUE(entry->IsAuthenticated());
   EXPECT_EQ(entry->GetLocalProfileName(), base::UTF8ToUTF16("Joe"));
-  EXPECT_EQ(ThemeServiceFactory::GetForProfile(profile_being_created)
-                ->GetAutogeneratedThemeColor(),
-            kProfileColor);
+  // The color is not applied if the user enters settings.
+  EXPECT_FALSE(ThemeServiceFactory::GetForProfile(profile_being_created)
+                   ->UsingAutogeneratedTheme());
 }
 
 IN_PROC_BROWSER_TEST_F(ProfilePickerCreationFlowBrowserTest,
@@ -586,7 +583,7 @@
 
   // Simulate closing the UI with "Yes, I'm in".
   LoginUIServiceFactory::GetForProfile(profile_being_created)
-      ->SyncConfirmationUIClosed(LoginUIService::SYNC_WITH_DEFAULT_SETTINGS);
+      ->SyncConfirmationUIClosed(LoginUIService::ABORT_SYNC);
   Browser* new_browser = BrowserAddedWaiter(2u).Wait();
   WaitForFirstPaint(new_browser->tab_strip_model()->GetActiveWebContents(),
                     GURL("chrome://newtab/"));
@@ -600,7 +597,7 @@
                   .GetProfileAttributesWithPath(
                       profile_being_created->GetPath(), &entry));
   EXPECT_FALSE(entry->IsEphemeral());
-  EXPECT_TRUE(entry->IsAuthenticated());
+  EXPECT_FALSE(entry->IsAuthenticated());
   // Since the given name is not provided, the email address is used instead as
   // a profile name.
   EXPECT_EQ(entry->GetLocalProfileName(),
@@ -676,10 +673,10 @@
                     GURL("chrome://newtab/"));
   EXPECT_FALSE(ProfilePicker::IsOpen());
 
-  // Now the sync consent screen is shown, simulate closing the UI with "Yes,
-  // I'm in".
+  // Now the sync consent screen is shown, simulate closing the UI with "No,
+  // thanks".
   LoginUIServiceFactory::GetForProfile(profile_being_created)
-      ->SyncConfirmationUIClosed(LoginUIService::SYNC_WITH_DEFAULT_SETTINGS);
+      ->SyncConfirmationUIClosed(LoginUIService::ABORT_SYNC);
 
   // Check expectations when the profile creation flow is done.
   ProfileAttributesEntry* entry = nullptr;
@@ -689,9 +686,10 @@
                       profile_being_created->GetPath(), &entry));
   EXPECT_FALSE(entry->IsEphemeral());
   EXPECT_EQ(entry->GetLocalProfileName(), base::UTF8ToUTF16(kWork));
-  EXPECT_EQ(ThemeServiceFactory::GetForProfile(profile_being_created)
-                ->GetAutogeneratedThemeColor(),
-            kProfileColor);
+
+  // The color is not applied for enterprise users.
+  EXPECT_FALSE(ThemeServiceFactory::GetForProfile(profile_being_created)
+                   ->UsingAutogeneratedTheme());
 }
 
 IN_PROC_BROWSER_TEST_F(ProfilePickerEnterpriseCreationFlowBrowserTest,
@@ -756,9 +754,9 @@
                       profile_being_created->GetPath(), &entry));
   EXPECT_FALSE(entry->IsEphemeral());
   EXPECT_EQ(entry->GetLocalProfileName(), base::UTF8ToUTF16(kWork));
-  EXPECT_EQ(ThemeServiceFactory::GetForProfile(profile_being_created)
-                ->GetAutogeneratedThemeColor(),
-            kProfileColor);
+  // The color is not applied if the user enters settings.
+  EXPECT_FALSE(ThemeServiceFactory::GetForProfile(profile_being_created)
+                   ->UsingAutogeneratedTheme());
 }
 
 }  // namespace
diff --git a/chrome/browser/ui/views/profiles/profile_picker_view_sync_delegate.cc b/chrome/browser/ui/views/profiles/profile_picker_view_sync_delegate.cc
index a42a42bc..30c29da 100644
--- a/chrome/browser/ui/views/profiles/profile_picker_view_sync_delegate.cc
+++ b/chrome/browser/ui/views/profiles/profile_picker_view_sync_delegate.cc
@@ -49,8 +49,9 @@
   // switch or to start sign-in once again.
   std::move(open_browser_callback_)
       .Run(base::BindOnce(
-          &DiceTurnSyncOnHelper::Delegate::ShowLoginErrorForBrowser, email,
-          error_message));
+               &DiceTurnSyncOnHelper::Delegate::ShowLoginErrorForBrowser, email,
+               error_message),
+           /*enterprise_sync_consent_needed=*/false);
 }
 
 void ProfilePickerViewSyncDelegate::ShowMergeSyncDataConfirmation(
@@ -69,7 +70,8 @@
   std::move(open_browser_callback_)
       .Run(base::BindOnce(&DiceTurnSyncOnHelper::Delegate::
                               ShowEnterpriseAccountConfirmationForBrowser,
-                          email, std::move(callback)));
+                          email, std::move(callback)),
+           /*enterprise_sync_consent_needed=*/true);
 }
 
 void ProfilePickerViewSyncDelegate::ShowSyncConfirmation(
@@ -101,7 +103,8 @@
 
   // Open the browser and when it's done, show the confirmation dialog.
   std::move(open_browser_callback_)
-      .Run(base::BindOnce(&OpenSyncConfirmationDialogInBrowser));
+      .Run(base::BindOnce(&OpenSyncConfirmationDialogInBrowser),
+           /*enterprise_sync_consent_needed=*/false);
 }
 
 void ProfilePickerViewSyncDelegate::ShowSyncSettings() {
@@ -114,7 +117,9 @@
   }
 
   // Open the browser and when it's done, open settings in the browser.
-  std::move(open_browser_callback_).Run(base::BindOnce(&OpenSettingsInBrowser));
+  std::move(open_browser_callback_)
+      .Run(base::BindOnce(&OpenSettingsInBrowser),
+           /*enterprise_sync_consent_needed=*/false);
 }
 
 void ProfilePickerViewSyncDelegate::SwitchToProfile(Profile* new_profile) {
diff --git a/chrome/browser/ui/views/profiles/profile_picker_view_sync_delegate.h b/chrome/browser/ui/views/profiles/profile_picker_view_sync_delegate.h
index 69aad8e..87197fc 100644
--- a/chrome/browser/ui/views/profiles/profile_picker_view_sync_delegate.h
+++ b/chrome/browser/ui/views/profiles/profile_picker_view_sync_delegate.h
@@ -18,7 +18,8 @@
                                       public LoginUIService::Observer {
  public:
   using OpenBrowserCallback =
-      base::OnceCallback<void(ProfilePickerView::BrowserOpenedCallback)>;
+      base::OnceCallback<void(ProfilePickerView::BrowserOpenedCallback,
+                              bool enterprise_sync_consent_needed)>;
 
   ProfilePickerViewSyncDelegate(Profile* profile,
                                 OpenBrowserCallback open_browser_callback);
diff --git a/chrome/browser/ui/views/tabs/tab_drag_controller_interactive_uitest.cc b/chrome/browser/ui/views/tabs/tab_drag_controller_interactive_uitest.cc
index 4b6e0e4..0ff04054 100644
--- a/chrome/browser/ui/views/tabs/tab_drag_controller_interactive_uitest.cc
+++ b/chrome/browser/ui/views/tabs/tab_drag_controller_interactive_uitest.cc
@@ -2052,8 +2052,10 @@
 
 }  // namespace
 
-#if defined(OS_MAC) && defined(ARCH_CPU_ARM64)
+#if defined(OS_MAC) /* && defined(ARCH_CPU_ARM64) */
 // Bulk-disabled for arm64 bot stabilization: https://crbug.com/1154345
+// These were flaking on all macs, so commented out ARCH_ above for
+// crbug.com/1160917 too.
 #define MAYBE_DragAllToSeparateWindow DISABLED_DragAllToSeparateWindow
 #else
 #define MAYBE_DragAllToSeparateWindow DragAllToSeparateWindow
@@ -2561,8 +2563,10 @@
 
 }  // namespace
 
-#if defined(OS_MAC) && defined(ARCH_CPU_ARM64)
+#if defined(OS_MAC) /* && defined(ARCH_CPU_ARM64) */
 // Bulk-disabled for arm64 bot stabilization: https://crbug.com/1154345
+// These were flaking on all macs, so commented out ARCH_ above for
+// crbug.com/1160917 too.
 #define MAYBE_DragAllToSeparateWindowAndCancel \
   DISABLED_DragAllToSeparateWindowAndCancel
 #else
diff --git a/chrome/browser/ui/webui/help/test_version_updater.cc b/chrome/browser/ui/webui/help/test_version_updater.cc
index b159078..32e2acd2 100644
--- a/chrome/browser/ui/webui/help/test_version_updater.cc
+++ b/chrome/browser/ui/webui/help/test_version_updater.cc
@@ -8,7 +8,7 @@
 
 TestVersionUpdater::~TestVersionUpdater() = default;
 
-void TestVersionUpdater::CheckForUpdate(const StatusCallback& callback,
+void TestVersionUpdater::CheckForUpdate(StatusCallback callback,
                                         const PromoteCallback&) {
   callback.Run(status_, progress_, rollback_, powerwash_, version_,
                update_size_, message_);
diff --git a/chrome/browser/ui/webui/help/test_version_updater.h b/chrome/browser/ui/webui/help/test_version_updater.h
index 8a521dd4..6de58a7a 100644
--- a/chrome/browser/ui/webui/help/test_version_updater.h
+++ b/chrome/browser/ui/webui/help/test_version_updater.h
@@ -19,8 +19,7 @@
   TestVersionUpdater();
   ~TestVersionUpdater() override;
 
-  void CheckForUpdate(const StatusCallback& callback,
-                      const PromoteCallback&) override;
+  void CheckForUpdate(StatusCallback callback, const PromoteCallback&) override;
 
   void SetReturnedStatus(Status status) { status_ = status; }
 
@@ -34,7 +33,7 @@
   void GetChannel(bool get_current_channel,
                   const ChannelCallback& callback) override {}
   void GetEolInfo(EolInfoCallback callback) override {}
-  void SetUpdateOverCellularOneTimePermission(const StatusCallback& callback,
+  void SetUpdateOverCellularOneTimePermission(StatusCallback callback,
                                               const std::string& update_version,
                                               int64_t update_size) override {}
 #endif
diff --git a/chrome/browser/ui/webui/help/version_updater.h b/chrome/browser/ui/webui/help/version_updater.h
index 937a28b3..1c1277b 100644
--- a/chrome/browser/ui/webui/help/version_updater.h
+++ b/chrome/browser/ui/webui/help/version_updater.h
@@ -65,13 +65,13 @@
   // |update_size| is the size of the available update in bytes and should be 0
   //     when update is not available.
   // |message| is a message explaining a failure.
-  typedef base::Callback<void(Status status,
-                              int progress,
-                              bool rollback,
-                              bool powerwash,
-                              const std::string& version,
-                              int64_t update_size,
-                              const base::string16& message)>
+  typedef base::RepeatingCallback<void(Status status,
+                                       int progress,
+                                       bool rollback,
+                                       bool powerwash,
+                                       const std::string& version,
+                                       int64_t update_size,
+                                       const base::string16& message)>
       StatusCallback;
 
   // Used to show or hide the promote UI elements. Mac-only.
@@ -89,7 +89,7 @@
   // |status_callback| is called for each status update. |promote_callback|
   // (which is only used on the Mac) can be used to show or hide the promote UI
   // elements.
-  virtual void CheckForUpdate(const StatusCallback& status_callback,
+  virtual void CheckForUpdate(StatusCallback status_callback,
                               const PromoteCallback& promote_callback) = 0;
 
 #if defined(OS_MAC)
@@ -115,7 +115,7 @@
   // there's a new update available or a delta update becomes a full update with
   // a larger size.
   virtual void SetUpdateOverCellularOneTimePermission(
-      const StatusCallback& callback,
+      StatusCallback callback,
       const std::string& update_version,
       int64_t update_size) = 0;
 #endif
diff --git a/chrome/browser/ui/webui/help/version_updater_basic.cc b/chrome/browser/ui/webui/help/version_updater_basic.cc
index 2a4c334..0b0a8c2 100644
--- a/chrome/browser/ui/webui/help/version_updater_basic.cc
+++ b/chrome/browser/ui/webui/help/version_updater_basic.cc
@@ -7,9 +7,8 @@
 #include "base/strings/string16.h"
 #include "chrome/browser/upgrade_detector/upgrade_detector.h"
 
-void VersionUpdaterBasic::CheckForUpdate(
-    const StatusCallback& status_callback,
-    const PromoteCallback&) {
+void VersionUpdaterBasic::CheckForUpdate(StatusCallback status_callback,
+                                         const PromoteCallback&) {
   const Status status = UpgradeDetector::GetInstance()->notify_upgrade()
                             ? NEARLY_UPDATED
                             : DISABLED;
diff --git a/chrome/browser/ui/webui/help/version_updater_basic.h b/chrome/browser/ui/webui/help/version_updater_basic.h
index 8fbb47a..d5cc437 100644
--- a/chrome/browser/ui/webui/help/version_updater_basic.h
+++ b/chrome/browser/ui/webui/help/version_updater_basic.h
@@ -13,8 +13,8 @@
 class VersionUpdaterBasic : public VersionUpdater {
  public:
   // VersionUpdater implementation.
-  void CheckForUpdate(const StatusCallback& callback,
-                      const PromoteCallback&) override;
+  void CheckForUpdate(StatusCallback callback, const PromoteCallback&) override;
+
  protected:
   friend class VersionUpdater;
 
diff --git a/chrome/browser/ui/webui/help/version_updater_chromeos.cc b/chrome/browser/ui/webui/help/version_updater_chromeos.cc
index 3651443..bf58f02 100644
--- a/chrome/browser/ui/webui/help/version_updater_chromeos.cc
+++ b/chrome/browser/ui/webui/help/version_updater_chromeos.cc
@@ -136,11 +136,11 @@
   return new VersionUpdaterCros(web_contents);
 }
 
-void VersionUpdaterCros::GetUpdateStatus(const StatusCallback& callback) {
-  callback_ = callback;
+void VersionUpdaterCros::GetUpdateStatus(StatusCallback callback) {
+  callback_ = std::move(callback);
 
   // User is not actively checking for updates.
-  if (!EnsureCanUpdate(false /* interactive */, callback))
+  if (!EnsureCanUpdate(false /* interactive */, callback_))
     return;
 
   UpdateEngineClient* update_engine_client =
@@ -152,12 +152,12 @@
       DBusThreadManager::Get()->GetUpdateEngineClient()->GetLastStatus());
 }
 
-void VersionUpdaterCros::CheckForUpdate(const StatusCallback& callback,
+void VersionUpdaterCros::CheckForUpdate(StatusCallback callback,
                                         const PromoteCallback&) {
-  callback_ = callback;
+  callback_ = std::move(callback);
 
   // User is actively checking for updates.
-  if (!EnsureCanUpdate(true /* interactive */, callback))
+  if (!EnsureCanUpdate(true /* interactive */, callback_))
     return;
 
   UpdateEngineClient* update_engine_client =
@@ -195,10 +195,10 @@
 }
 
 void VersionUpdaterCros::SetUpdateOverCellularOneTimePermission(
-    const StatusCallback& callback,
+    StatusCallback callback,
     const std::string& update_version,
     int64_t update_size) {
-  callback_ = callback;
+  callback_ = std::move(callback);
   DBusThreadManager::Get()
       ->GetUpdateEngineClient()
       ->SetUpdateOverCellularOneTimePermission(
diff --git a/chrome/browser/ui/webui/help/version_updater_chromeos.h b/chrome/browser/ui/webui/help/version_updater_chromeos.h
index 0578815..39ac0f4 100644
--- a/chrome/browser/ui/webui/help/version_updater_chromeos.h
+++ b/chrome/browser/ui/webui/help/version_updater_chromeos.h
@@ -19,19 +19,18 @@
                            public chromeos::UpdateEngineClient::Observer {
  public:
   // VersionUpdater implementation.
-  void CheckForUpdate(const StatusCallback& callback,
-                      const PromoteCallback&) override;
+  void CheckForUpdate(StatusCallback callback, const PromoteCallback&) override;
   void SetChannel(const std::string& channel,
                   bool is_powerwash_allowed) override;
   void GetChannel(bool get_current_channel,
                   const ChannelCallback& callback) override;
   void GetEolInfo(EolInfoCallback callback) override;
-  void SetUpdateOverCellularOneTimePermission(const StatusCallback& callback,
+  void SetUpdateOverCellularOneTimePermission(StatusCallback callback,
                                               const std::string& update_version,
                                               int64_t update_size) override;
 
   // Gets the last update status, without triggering a new check or download.
-  void GetUpdateStatus(const StatusCallback& callback);
+  void GetUpdateStatus(StatusCallback callback);
 
  protected:
   friend class VersionUpdater;
diff --git a/chrome/browser/ui/webui/help/version_updater_chromeos_unittest.cc b/chrome/browser/ui/webui/help/version_updater_chromeos_unittest.cc
index 999a1a6..c0b1623 100644
--- a/chrome/browser/ui/webui/help/version_updater_chromeos_unittest.cc
+++ b/chrome/browser/ui/webui/help/version_updater_chromeos_unittest.cc
@@ -126,7 +126,7 @@
   EXPECT_EQ(0, fake_update_engine_client_->request_update_check_call_count());
 
   // IDLE -> DOWNLOADING transition after update check.
-  version_updater_->CheckForUpdate(base::Bind(&CheckNotification),
+  version_updater_->CheckForUpdate(base::BindRepeating(&CheckNotification),
                                    VersionUpdater::PromoteCallback());
   EXPECT_EQ(1, fake_update_engine_client_->request_update_check_call_count());
 
@@ -150,7 +150,7 @@
     fake_update_engine_client_->NotifyObserversThatStatusChanged(status);
   }
 
-  version_updater_->CheckForUpdate(base::Bind(&CheckNotification),
+  version_updater_->CheckForUpdate(base::BindRepeating(&CheckNotification),
                                    VersionUpdater::PromoteCallback());
   EXPECT_EQ(1, fake_update_engine_client_->request_update_check_call_count());
 
@@ -172,7 +172,7 @@
 TEST_F(VersionUpdaterCrosTest, InteractiveCellularUpdateAllowed) {
   SetCellularService();
   EXPECT_EQ(0, fake_update_engine_client_->request_update_check_call_count());
-  version_updater_->CheckForUpdate(base::Bind(&CheckNotification),
+  version_updater_->CheckForUpdate(base::BindRepeating(&CheckNotification),
                                    VersionUpdater::PromoteCallback());
   EXPECT_EQ(1, fake_update_engine_client_->request_update_check_call_count());
 }
@@ -185,7 +185,7 @@
   const std::string& update_version = "9999.0.0";
   const int64_t update_size = 99999;
   version_updater_->SetUpdateOverCellularOneTimePermission(
-      base::Bind(&CheckNotification), update_version, update_size);
+      base::BindRepeating(&CheckNotification), update_version, update_size);
   EXPECT_EQ(1, fake_update_engine_client_->request_update_check_call_count());
 }
 
diff --git a/chrome/browser/ui/webui/help/version_updater_mac.h b/chrome/browser/ui/webui/help/version_updater_mac.h
index f00e08b..eab161d 100644
--- a/chrome/browser/ui/webui/help/version_updater_mac.h
+++ b/chrome/browser/ui/webui/help/version_updater_mac.h
@@ -27,7 +27,7 @@
 class VersionUpdaterMac : public VersionUpdater {
  public:
   // VersionUpdater implementation.
-  void CheckForUpdate(const StatusCallback& status_callback,
+  void CheckForUpdate(StatusCallback status_callback,
                       const PromoteCallback& promote_callback) override;
   void PromoteUpdater() const override;
 
diff --git a/chrome/browser/ui/webui/help/version_updater_mac.mm b/chrome/browser/ui/webui/help/version_updater_mac.mm
index 0cdd1f8..5818db0d 100644
--- a/chrome/browser/ui/webui/help/version_updater_mac.mm
+++ b/chrome/browser/ui/webui/help/version_updater_mac.mm
@@ -78,7 +78,7 @@
 }
 
 void UpdateStatusFromChromiumUpdater(
-    const VersionUpdater::StatusCallback& status_callback,
+    VersionUpdater::StatusCallback status_callback,
     updater::UpdateService::UpdateState update_state) {
   VersionUpdater::Status status = VersionUpdater::Status::CHECKING;
   int progress = 0;
@@ -136,17 +136,17 @@
 VersionUpdaterMac::~VersionUpdaterMac() {}
 
 void VersionUpdaterMac::CheckForUpdate(
-    const StatusCallback& status_callback,
+    StatusCallback status_callback,
     const PromoteCallback& promote_callback) {
 #if BUILDFLAG(ENABLE_CHROMIUM_UPDATER)
   if (!update_client_)
     update_client_ = BrowserUpdaterClient::Create();
 
-  update_client_->CheckForUpdate(
-      base::BindRepeating(&UpdateStatusFromChromiumUpdater, status_callback));
+  update_client_->CheckForUpdate(base::BindRepeating(
+      &UpdateStatusFromChromiumUpdater, std::move(status_callback)));
   return;
 #else
-  status_callback_ = status_callback;
+  status_callback_ = std::move(status_callback);
   promote_callback_ = promote_callback;
 
   KeystoneGlue* keystone_glue = [KeystoneGlue defaultKeystoneGlue];
diff --git a/chrome/browser/ui/webui/help/version_updater_win.cc b/chrome/browser/ui/webui/help/version_updater_win.cc
index be910614..9101776 100644
--- a/chrome/browser/ui/webui/help/version_updater_win.cc
+++ b/chrome/browser/ui/webui/help/version_updater_win.cc
@@ -26,10 +26,10 @@
 VersionUpdaterWin::~VersionUpdaterWin() {
 }
 
-void VersionUpdaterWin::CheckForUpdate(const StatusCallback& callback,
+void VersionUpdaterWin::CheckForUpdate(StatusCallback callback,
                                        const PromoteCallback&) {
   // There is no supported integration with Google Update for Chromium.
-  callback_ = callback;
+  callback_ = std::move(callback);
 
   callback_.Run(CHECKING, 0, false, false, std::string(), 0, base::string16());
   DoBeginUpdateCheck(false /* !install_update_if_possible */);
diff --git a/chrome/browser/ui/webui/help/version_updater_win.h b/chrome/browser/ui/webui/help/version_updater_win.h
index f225a23..94398857 100644
--- a/chrome/browser/ui/webui/help/version_updater_win.h
+++ b/chrome/browser/ui/webui/help/version_updater_win.h
@@ -25,8 +25,7 @@
   ~VersionUpdaterWin() override;
 
   // VersionUpdater:
-  void CheckForUpdate(const StatusCallback& callback,
-                      const PromoteCallback&) override;
+  void CheckForUpdate(StatusCallback callback, const PromoteCallback&) override;
 
   // UpdateCheckDelegate:
   void OnUpdateCheckComplete(const base::string16& new_version) override;
diff --git a/chrome/browser/ui/webui/settings/about_handler.cc b/chrome/browser/ui/webui/settings/about_handler.cc
index d4522c4e..9a55940 100644
--- a/chrome/browser/ui/webui/settings/about_handler.cc
+++ b/chrome/browser/ui/webui/settings/about_handler.cc
@@ -470,7 +470,8 @@
   if (user_manager::UserManager::Get()->IsCurrentUserOwner()) {
     // Check for update after switching release channel.
     version_updater_->CheckForUpdate(
-        base::Bind(&AboutHandler::SetUpdateStatus, base::Unretained(this)),
+        base::BindRepeating(&AboutHandler::SetUpdateStatus,
+                            base::Unretained(this)),
         VersionUpdater::PromoteCallback());
   }
 }
@@ -570,7 +571,8 @@
 void AboutHandler::RequestUpdateOverCellular(const std::string& update_version,
                                              int64_t update_size) {
   version_updater_->SetUpdateOverCellularOneTimePermission(
-      base::Bind(&AboutHandler::SetUpdateStatus, base::Unretained(this)),
+      base::BindRepeating(&AboutHandler::SetUpdateStatus,
+                          base::Unretained(this)),
       update_version, update_size);
 }
 
@@ -625,9 +627,11 @@
 
 void AboutHandler::RequestUpdate() {
   version_updater_->CheckForUpdate(
-      base::Bind(&AboutHandler::SetUpdateStatus, base::Unretained(this)),
+      base::BindRepeating(&AboutHandler::SetUpdateStatus,
+                          base::Unretained(this)),
 #if defined(OS_MAC)
-      base::Bind(&AboutHandler::SetPromotionState, base::Unretained(this)));
+      base::BindRepeating(&AboutHandler::SetPromotionState,
+                          base::Unretained(this)));
 #else
       VersionUpdater::PromoteCallback());
 #endif  // OS_MAC
diff --git a/chrome/browser/ui/webui/settings/safety_check_handler.cc b/chrome/browser/ui/webui/settings/safety_check_handler.cc
index 0d00091..39e80de 100644
--- a/chrome/browser/ui/webui/settings/safety_check_handler.cc
+++ b/chrome/browser/ui/webui/settings/safety_check_handler.cc
@@ -388,8 +388,8 @@
 void SafetyCheckHandler::CheckUpdates() {
   // Usage of base::Unretained(this) is safe, because we own `version_updater_`.
   version_updater_->CheckForUpdate(
-      base::Bind(&SafetyCheckHandler::OnVersionUpdaterResult,
-                 base::Unretained(this)),
+      base::BindRepeating(&SafetyCheckHandler::OnVersionUpdaterResult,
+                          base::Unretained(this)),
       VersionUpdater::PromoteCallback());
 }
 
diff --git a/chrome/browser/ui/webui/settings/safety_check_handler_unittest.cc b/chrome/browser/ui/webui/settings/safety_check_handler_unittest.cc
index 6e291145..a8eb9e7 100644
--- a/chrome/browser/ui/webui/settings/safety_check_handler_unittest.cc
+++ b/chrome/browser/ui/webui/settings/safety_check_handler_unittest.cc
@@ -99,7 +99,7 @@
  public:
   ~TestDestructionVersionUpdater() override { destructor_invoked_ = true; }
 
-  void CheckForUpdate(const StatusCallback& callback,
+  void CheckForUpdate(StatusCallback callback,
                       const PromoteCallback&) override {}
 
   static bool GetDestructorInvoked() { return destructor_invoked_; }
diff --git a/chrome/build/linux.pgo.txt b/chrome/build/linux.pgo.txt
index 19f2c9b..b5faf77 100644
--- a/chrome/build/linux.pgo.txt
+++ b/chrome/build/linux.pgo.txt
@@ -1 +1 @@
-chrome-linux-master-1608332413-e74bf19c468b4f240b918ee99bd718a9ec1e581b.profdata
+chrome-linux-master-1608573074-24f850c78c572e5bf0ca93d4771b7966907fde32.profdata
diff --git a/chrome/build/mac.pgo.txt b/chrome/build/mac.pgo.txt
index b391599..9d84b146 100644
--- a/chrome/build/mac.pgo.txt
+++ b/chrome/build/mac.pgo.txt
@@ -1 +1 @@
-chrome-mac-master-1608529993-82d6d40e289e60063d3bd5c2eebe97dc9bb53838.profdata
+chrome-mac-master-1608573074-c037592ba2f8332cc5c933010a8eab8414fcad34.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index 41d839e..afce3112 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-master-1608508387-093abbef3f1d2d94b454a0cdd869bb44780245e6.profdata
+chrome-win64-master-1608560380-1035bf8a36b405614508f1a548443341a120ef85.profdata
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index c4ce2aa3..a4ee1117 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -4309,6 +4309,7 @@
       "../browser/signin/process_dice_header_delegate_impl_unittest.cc",
       "../browser/signin/signin_manager_unittest.cc",
       "../browser/ui/views/profiles/dice_web_signin_interception_bubble_view_unittest.cc",
+      "../browser/ui/views/profiles/profile_customization_bubble_sync_controller_unittest.cc",
       "../browser/ui/webui/signin/dice_turn_sync_on_helper_unittest.cc",
     ]
   }
diff --git a/chromeos/components/diagnostics_ui/backend/system_routine_controller.cc b/chromeos/components/diagnostics_ui/backend/system_routine_controller.cc
index c389ede..49d14b2 100644
--- a/chromeos/components/diagnostics_ui/backend/system_routine_controller.cc
+++ b/chromeos/components/diagnostics_ui/backend/system_routine_controller.cc
@@ -34,6 +34,7 @@
 constexpr uint32_t kRoutineResultRefreshIntervalInSeconds = 1;
 
 constexpr char kChargePercentKey[] = "chargePercent";
+constexpr char kDischargePercentKey[] = "dischargePercent";
 constexpr char kResultDetailsKey[] = "resultDetails";
 
 mojom::RoutineResultInfoPtr ConstructStandardRoutineResultInfoPtr(
@@ -197,7 +198,19 @@
   inflight_routine_timer_ = std::make_unique<base::OneShotTimer>();
 }
 
-SystemRoutineController::~SystemRoutineController() = default;
+SystemRoutineController::~SystemRoutineController() {
+  if (!inflight_routine_runner_) {
+    return;
+  }
+
+  // Since SystemRoutineController is torn down at the same time as the
+  // frontend, there's no guarantee that the disconnect handler will be called.
+  // If there's a routine inflight, cancel it but do not pass a callback.
+  BindCrosHealthdDiagnosticsServiceIfNeccessary();
+  diagnostics_service_->GetRoutineUpdate(
+      inflight_routine_id_, healthd::DiagnosticRoutineCommandEnum::kCancel,
+      /*should_include_output=*/false, base::DoNothing());
+}
 
 void SystemRoutineController::RunRoutine(
     mojom::RoutineType type,
@@ -214,6 +227,9 @@
 
   inflight_routine_runner_ =
       mojo::Remote<mojom::RoutineRunner>(std::move(runner));
+  inflight_routine_runner_.set_disconnect_handler(base::BindOnce(
+      &SystemRoutineController::OnInflightRoutineRunnerDisconnected,
+      base::Unretained(this)));
   ExecuteRoutine(type);
 }
 
@@ -566,7 +582,9 @@
   }
 
   base::Optional<double> charge_percent_opt =
-      result_details_dict->FindDoubleKey(kChargePercentKey);
+      routine_type == mojom::RoutineType::kBatteryCharge
+          ? result_details_dict->FindDoubleKey(kChargePercentKey)
+          : result_details_dict->FindDoubleKey(kDischargePercentKey);
   if (!charge_percent_opt.has_value()) {
     OnPowerRoutineResult(routine_type,
                          mojom::StandardRoutineResult::kExecutionError,
@@ -621,8 +639,29 @@
 }
 
 void SystemRoutineController::OnInflightRoutineRunnerDisconnected() {
+  // Reset `inflight_routine_runner_` since the other side of the pipe is
+  // already disconnected.
   inflight_routine_runner_.reset();
-  // TODO(baileyberro): Implement routine cancellation.
+
+  // Make a best effort attempt to remove the routine.
+  BindCrosHealthdDiagnosticsServiceIfNeccessary();
+  diagnostics_service_->GetRoutineUpdate(
+      inflight_routine_id_, healthd::DiagnosticRoutineCommandEnum::kCancel,
+      /*should_include_output=*/false,
+      base::BindOnce(&SystemRoutineController::OnRoutineCancelAttempted,
+                     base::Unretained(this)));
+}
+
+void SystemRoutineController::OnRoutineCancelAttempted(
+    healthd::RoutineUpdatePtr update_ptr) {
+  const healthd::NonInteractiveRoutineUpdate* update =
+      GetNonInteractiveRoutineUpdate(*update_ptr);
+
+  if (!update ||
+      update->status != healthd::DiagnosticRoutineStatusEnum::kCancelled) {
+    DVLOG(2) << "Failed to cancel routine.";
+    return;
+  }
 }
 
 }  // namespace diagnostics
diff --git a/chromeos/components/diagnostics_ui/backend/system_routine_controller.h b/chromeos/components/diagnostics_ui/backend/system_routine_controller.h
index 9c80f33..77adbfd 100644
--- a/chromeos/components/diagnostics_ui/backend/system_routine_controller.h
+++ b/chromeos/components/diagnostics_ui/backend/system_routine_controller.h
@@ -112,6 +112,9 @@
 
   void OnInflightRoutineRunnerDisconnected();
 
+  void OnRoutineCancelAttempted(
+      cros_healthd::mojom::RoutineUpdatePtr update_ptr);
+
   // Keeps track of the id created by CrosHealthd for the currently running
   // routine.
   int32_t inflight_routine_id_ = kInvalidRoutineId;
diff --git a/chromeos/components/diagnostics_ui/backend/system_routine_controller_unittest.cc b/chromeos/components/diagnostics_ui/backend/system_routine_controller_unittest.cc
index 625c822..6d9ecc5e 100644
--- a/chromeos/components/diagnostics_ui/backend/system_routine_controller_unittest.cc
+++ b/chromeos/components/diagnostics_ui/backend/system_routine_controller_unittest.cc
@@ -13,6 +13,7 @@
 #include "base/test/bind.h"
 #include "base/test/task_environment.h"
 #include "chromeos/dbus/cros_healthd/fake_cros_healthd_client.h"
+#include "chromeos/dbus/cros_healthd/fake_cros_healthd_service.h"
 #include "chromeos/services/cros_healthd/public/mojom/cros_healthd.mojom.h"
 #include "chromeos/services/cros_healthd/public/mojom/cros_healthd_diagnostics.mojom.h"
 #include "mojo/public/cpp/system/platform_handle.h"
@@ -26,6 +27,7 @@
 namespace healthd = cros_healthd::mojom;
 
 constexpr char kChargePercentKey[] = "chargePercent";
+constexpr char kDischargePercentKey[] = "dischargePercent";
 constexpr char kResultDetailsKey[] = "resultDetails";
 
 void SetCrosHealthdRunRoutineResponse(
@@ -96,9 +98,17 @@
                                         time_elapsed_seconds);
 }
 
-std::string ConstructPowerRoutineResultJson(double charge_percent) {
+// Constructs the Power Routine Result json. If `charge_percent` is negative,
+// the discharge field will be used.
+std::string ConstructPowerRoutineResultJson(double charge_percent,
+                                            bool charge) {
   base::Value result_dict(base::Value::Type::DICTIONARY);
-  result_dict.SetKey(kChargePercentKey, base::Value(charge_percent));
+  if (charge) {
+    result_dict.SetKey(kChargePercentKey, base::Value(charge_percent));
+
+  } else {
+    result_dict.SetKey(kDischargePercentKey, base::Value(charge_percent));
+  }
 
   base::Value output_dict(base::Value::Type::DICTIONARY);
   output_dict.SetKey(kResultDetailsKey, std::move(result_dict));
@@ -144,8 +154,10 @@
   }
 
  protected:
-  mojo::ScopedHandle CreateMojoHandleForPowerRoutine(double charge_percent) {
-    return CreateMojoHandle(ConstructPowerRoutineResultJson(charge_percent));
+  mojo::ScopedHandle CreateMojoHandleForPowerRoutine(double charge_percent,
+                                                     bool charge) {
+    return CreateMojoHandle(
+        ConstructPowerRoutineResultJson(charge_percent, charge));
   }
 
   base::test::TaskEnvironment task_environment_{
@@ -422,7 +434,8 @@
 
   SetNonInteractiveRoutineUpdateResponse(
       /*percent_complete=*/100, healthd::DiagnosticRoutineStatusEnum::kPassed,
-      CreateMojoHandleForPowerRoutine(expected_percent_charge));
+      CreateMojoHandleForPowerRoutine(expected_percent_charge,
+                                      /*charge=*/true));
   task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(31));
 
   EXPECT_FALSE(routine_runner.result.is_null());
@@ -433,6 +446,39 @@
                                   expected_time_elapsed_seconds));
 }
 
+TEST_F(SystemRoutineControllerTest, DischargeRoutineSuccess) {
+  SetRunRoutineResponse(/*id=*/1,
+                        healthd::DiagnosticRoutineStatusEnum::kWaiting);
+  SetNonInteractiveRoutineUpdateResponse(
+      /*percent_complete=*/10, healthd::DiagnosticRoutineStatusEnum::kRunning,
+      mojo::ScopedHandle());
+
+  FakeRoutineRunner routine_runner;
+  system_routine_controller_->RunRoutine(
+      mojom::RoutineType::kBatteryDischarge,
+      routine_runner.receiver.BindNewPipeAndPassRemote());
+  base::RunLoop().RunUntilIdle();
+
+  // Assert that the routine is not complete.
+  EXPECT_TRUE(routine_runner.result.is_null());
+
+  const uint8_t expected_percent_discharge = 5;
+  const uint32_t expected_time_elapsed_seconds = 30;
+
+  SetNonInteractiveRoutineUpdateResponse(
+      /*percent_complete=*/100, healthd::DiagnosticRoutineStatusEnum::kPassed,
+      CreateMojoHandleForPowerRoutine(expected_percent_discharge,
+                                      /*charge=*/false));
+  task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(31));
+
+  EXPECT_FALSE(routine_runner.result.is_null());
+  VerifyRoutineResult(
+      *routine_runner.result, mojom::RoutineType::kBatteryDischarge,
+      ConstructPowerRoutineResult(mojom::StandardRoutineResult::kTestPassed,
+                                  expected_percent_discharge,
+                                  expected_time_elapsed_seconds));
+}
+
 TEST_F(SystemRoutineControllerTest, AvailableRoutines) {
   SetAvailableRoutines({healthd::DiagnosticRoutineEnum::kFloatingPointAccuracy,
                         healthd::DiagnosticRoutineEnum::kMemory,
@@ -464,5 +510,75 @@
   run_loop.Run();
 }
 
+TEST_F(SystemRoutineControllerTest, CancelRoutine) {
+  const int32_t expected_id = 1;
+  SetRunRoutineResponse(expected_id,
+                        healthd::DiagnosticRoutineStatusEnum::kRunning);
+
+  std::unique_ptr<FakeRoutineRunner> routine_runner =
+      std::make_unique<FakeRoutineRunner>();
+  system_routine_controller_->RunRoutine(
+      mojom::RoutineType::kCpuStress,
+      routine_runner->receiver.BindNewPipeAndPassRemote());
+  base::RunLoop().RunUntilIdle();
+
+  // Assert that the first routine is not complete.
+  EXPECT_TRUE(routine_runner->result.is_null());
+
+  // Update the status on cros_healthd.
+  SetNonInteractiveRoutineUpdateResponse(
+      /*percent_complete=*/0, healthd::DiagnosticRoutineStatusEnum::kCancelled,
+      mojo::ScopedHandle());
+
+  // Close the routine_runner
+  routine_runner.reset();
+  base::RunLoop().RunUntilIdle();
+
+  // Verify that CrosHealthd is called with the correct parameters.
+  base::Optional<cros_healthd::FakeCrosHealthdService::RoutineUpdateParams>
+      update_params =
+          cros_healthd::FakeCrosHealthdClient::Get()->GetRoutineUpdateParams();
+
+  ASSERT_TRUE(update_params.has_value());
+  EXPECT_EQ(expected_id, update_params->id);
+  EXPECT_EQ(healthd::DiagnosticRoutineCommandEnum::kCancel,
+            update_params->command);
+}
+
+TEST_F(SystemRoutineControllerTest, CancelRoutineDtor) {
+  const int32_t expected_id = 2;
+  SetRunRoutineResponse(expected_id,
+                        healthd::DiagnosticRoutineStatusEnum::kRunning);
+
+  std::unique_ptr<FakeRoutineRunner> routine_runner =
+      std::make_unique<FakeRoutineRunner>();
+  system_routine_controller_->RunRoutine(
+      mojom::RoutineType::kCpuStress,
+      routine_runner->receiver.BindNewPipeAndPassRemote());
+  base::RunLoop().RunUntilIdle();
+
+  // Assert that the first routine is not complete.
+  EXPECT_TRUE(routine_runner->result.is_null());
+
+  // Update the status on cros_healthd.
+  SetNonInteractiveRoutineUpdateResponse(
+      /*percent_complete=*/0, healthd::DiagnosticRoutineStatusEnum::kCancelled,
+      mojo::ScopedHandle());
+
+  // Destroy the SystemRoutineController
+  system_routine_controller_.reset();
+  base::RunLoop().RunUntilIdle();
+
+  // Verify that CrosHealthd is called with the correct parameters.
+  base::Optional<cros_healthd::FakeCrosHealthdService::RoutineUpdateParams>
+      update_params =
+          cros_healthd::FakeCrosHealthdClient::Get()->GetRoutineUpdateParams();
+
+  ASSERT_TRUE(update_params.has_value());
+  EXPECT_EQ(expected_id, update_params->id);
+  EXPECT_EQ(healthd::DiagnosticRoutineCommandEnum::kCancel,
+            update_params->command);
+}
+
 }  // namespace diagnostics
 }  // namespace chromeos
diff --git a/chromeos/constants/chromeos_features.cc b/chromeos/constants/chromeos_features.cc
index 6ebbe211..ea0a86f3 100644
--- a/chromeos/constants/chromeos_features.cc
+++ b/chromeos/constants/chromeos_features.cc
@@ -168,6 +168,10 @@
 const base::Feature kCdmFactoryDaemon{"CdmFactoryDaemon",
                                       base::FEATURE_DISABLED_BY_DEFAULT};
 
+// If enabled, send the LTE attach APN configuration to the modem.
+const base::Feature kCellularUseAttachApn{"CellularUseAttachApn",
+                                          base::FEATURE_DISABLED_BY_DEFAULT};
+
 // Enables or disables entry point for child account sign in or creation.
 const base::Feature kChildSpecificSignin{"ChildSpecificSignin",
                                          base::FEATURE_ENABLED_BY_DEFAULT};
diff --git a/chromeos/constants/chromeos_features.h b/chromeos/constants/chromeos_features.h
index fedbf41..b7fda07 100644
--- a/chromeos/constants/chromeos_features.h
+++ b/chromeos/constants/chromeos_features.h
@@ -90,6 +90,8 @@
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
 extern const base::Feature kCdmFactoryDaemon;
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
+extern const base::Feature kCellularUseAttachApn;
+COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
 extern const base::Feature kChildSpecificSignin;
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
 extern const base::Feature kConnectivityDiagnosticsWebUi;
diff --git a/chromeos/network/network_device_handler_impl.cc b/chromeos/network/network_device_handler_impl.cc
index 01aacf6..2ef4687 100644
--- a/chromeos/network/network_device_handler_impl.cc
+++ b/chromeos/network/network_device_handler_impl.cc
@@ -22,6 +22,7 @@
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/time/time.h"
 #include "base/values.h"
+#include "chromeos/constants/chromeos_features.h"
 #include "chromeos/dbus/shill/shill_device_client.h"
 #include "chromeos/dbus/shill/shill_ipconfig_client.h"
 #include "chromeos/network/device_state.h"
@@ -332,6 +333,7 @@
   ApplyCellularAllowRoamingToShill();
   ApplyMACAddressRandomizationToShill();
   ApplyUsbEthernetMacAddressSourceToShill();
+  ApplyUseAttachApnToShill();
 }
 
 void NetworkDeviceHandlerImpl::DevicePropertiesUpdated(
@@ -439,6 +441,27 @@
           usb_ethernet_mac_address_source_, network_handler::ErrorCallback()));
 }
 
+void NetworkDeviceHandlerImpl::ApplyUseAttachApnToShill() {
+  NetworkStateHandler::DeviceStateList list;
+  bool use_attach_apn =
+      base::FeatureList::IsEnabled(chromeos::features::kCellularUseAttachApn);
+  network_state_handler_->GetDeviceListByType(NetworkTypePattern::Cellular(),
+                                              &list);
+  if (list.empty()) {
+    NET_LOG(DEBUG) << "No cellular device available.";
+    return;
+  }
+  for (NetworkStateHandler::DeviceStateList::const_iterator it = list.begin();
+       it != list.end(); ++it) {
+    const DeviceState* device_state = *it;
+
+    SetDevicePropertyInternal(device_state->path(),
+                              shill::kUseAttachAPNProperty,
+                              base::Value(use_attach_apn), base::DoNothing(),
+                              network_handler::ErrorCallback());
+  }
+}
+
 void NetworkDeviceHandlerImpl::OnSetUsbEthernetMacAddressSourceError(
     const std::string& device_path,
     const std::string& device_mac_address,
diff --git a/chromeos/network/network_device_handler_impl.h b/chromeos/network/network_device_handler_impl.h
index acae3b6..386ddf2 100644
--- a/chromeos/network/network_device_handler_impl.h
+++ b/chromeos/network/network_device_handler_impl.h
@@ -138,6 +138,10 @@
   // specified yet.
   void ApplyUsbEthernetMacAddressSourceToShill();
 
+  // Applies the current value of the |cellular-use-attach-apn| flag to all
+  // existing cellular devices of Shill.
+  void ApplyUseAttachApnToShill();
+
   // Callback to be called on MAC address source change request failure.
   // The request was called on device with |device_path| path and
   // |device_mac_address| MAC address to change MAC address source to the new
diff --git a/chromeos/services/assistant/BUILD.gn b/chromeos/services/assistant/BUILD.gn
index 7334fd360..680d850 100644
--- a/chromeos/services/assistant/BUILD.gn
+++ b/chromeos/services/assistant/BUILD.gn
@@ -124,7 +124,6 @@
       "//chromeos/resources",
       "//chromeos/services/assistant/proxy",
       "//chromeos/services/assistant/public/cpp/migration",
-      "//chromeos/services/assistant/public/cpp/migration",
       "//chromeos/services/libassistant",
       "//chromeos/services/network_config/public/mojom",
       "//chromeos/strings",
@@ -235,6 +234,10 @@
     "//testing/gmock",
     "//testing/gtest",
   ]
+
+  if (enable_cros_libassistant) {
+    deps += [ "//chromeos/services/assistant/public/cpp/migration" ]
+  }
 }
 
 buildflag_header("buildflags") {
diff --git a/chromeos/services/assistant/assistant_manager_service_impl_unittest.cc b/chromeos/services/assistant/assistant_manager_service_impl_unittest.cc
index a95b9b3..13d4b23d 100644
--- a/chromeos/services/assistant/assistant_manager_service_impl_unittest.cc
+++ b/chromeos/services/assistant/assistant_manager_service_impl_unittest.cc
@@ -196,6 +196,13 @@
   }
   void Stop() override { service_->Unbind(); }
 
+  void SetInitializeCallback(
+      base::OnceCallback<void(assistant_client::AssistantManager*,
+                              assistant_client::AssistantManagerInternal*)>
+          callback) override {
+    service_->service_controller().SetInitializeCallback(std::move(callback));
+  }
+
  private:
   FakeLibassistantService* service_;
 };
diff --git a/chromeos/services/assistant/libassistant_service_host_impl.cc b/chromeos/services/assistant/libassistant_service_host_impl.cc
index 68a2d4d..d1fc222 100644
--- a/chromeos/services/assistant/libassistant_service_host_impl.cc
+++ b/chromeos/services/assistant/libassistant_service_host_impl.cc
@@ -5,6 +5,7 @@
 #include "chromeos/services/assistant/libassistant_service_host_impl.h"
 
 #include "base/check.h"
+#include "base/synchronization/lock.h"
 #include "chromeos/services/libassistant/libassistant_service.h"
 
 namespace chromeos {
@@ -23,14 +24,37 @@
 void LibassistantServiceHostImpl::Launch(
     mojo::PendingReceiver<LibassistantServiceMojom> receiver) {
   DCHECK_EQ(libassistant_service_, nullptr);
+
+  base::AutoLock lock(libassistant_service_lock_);
   libassistant_service_ =
       std::make_unique<chromeos::libassistant::LibassistantService>(
           std::move(receiver), platform_api_, delegate_);
+
+  if (pending_initialize_callback_) {
+    libassistant_service_->SetInitializeCallback(
+        std::move(pending_initialize_callback_));
+  }
 }
 
 void LibassistantServiceHostImpl::Stop() {
+  base::AutoLock lock(libassistant_service_lock_);
   libassistant_service_ = nullptr;
 }
 
+void LibassistantServiceHostImpl::SetInitializeCallback(
+    InitializeCallback callback) {
+  base::AutoLock lock(libassistant_service_lock_);
+
+  if (libassistant_service_) {
+    libassistant_service_->SetInitializeCallback(std::move(callback));
+  } else {
+    // Launch() is called on the background thread and SetInitializeCallback()
+    // on the main thread, so it is possible we come here before Launch() has
+    // had a chance to run. If that happens we remember the callback and pass
+    // it to the service in Launch().
+    pending_initialize_callback_ = std::move(callback);
+  }
+}
+
 }  // namespace assistant
 }  // namespace chromeos
diff --git a/chromeos/services/assistant/libassistant_service_host_impl.h b/chromeos/services/assistant/libassistant_service_host_impl.h
index eb115ff..6337092 100644
--- a/chromeos/services/assistant/libassistant_service_host_impl.h
+++ b/chromeos/services/assistant/libassistant_service_host_impl.h
@@ -7,6 +7,7 @@
 
 #include <memory>
 
+#include "base/synchronization/lock.h"
 #include "chromeos/services/assistant/proxy/libassistant_service_host.h"
 
 namespace assistant_client {
@@ -14,12 +15,22 @@
 }  // namespace assistant_client
 
 namespace chromeos {
+namespace libassistant {
+class LibassistantService;
+}  // namespace libassistant
+}  // namespace chromeos
+
+namespace chromeos {
 namespace assistant {
 
 class AssistantManagerServiceDelegate;
 
 class LibassistantServiceHostImpl : public LibassistantServiceHost {
  public:
+  using InitializeCallback =
+      base::OnceCallback<void(assistant_client::AssistantManager*,
+                              assistant_client::AssistantManagerInternal*)>;
+
   LibassistantServiceHostImpl(assistant_client::PlatformApi* platform_api,
                               AssistantManagerServiceDelegate* delegate);
   LibassistantServiceHostImpl(LibassistantServiceHostImpl&) = delete;
@@ -30,6 +41,7 @@
   void Launch(
       mojo::PendingReceiver<LibassistantServiceMojom> receiver) override;
   void Stop() override;
+  void SetInitializeCallback(InitializeCallback) override;
 
  private:
   // Owned by |AssistantManagerServiceImpl| which also owns |this|.
@@ -37,7 +49,14 @@
   // Owned by |AssistantManagerServiceImpl| which also owns |this|.
   AssistantManagerServiceDelegate* const delegate_;
 
-  std::unique_ptr<LibassistantServiceMojom> libassistant_service_;
+  // Protects access to |libassistant_service_|. This is required because the
+  // service will be launched/stopped on a background thread, where the other
+  // methods will be called from the main thread.
+  base::Lock libassistant_service_lock_;
+  std::unique_ptr<chromeos::libassistant::LibassistantService>
+      libassistant_service_;
+  // Used when SetInitializeCallback() is called before Launch().
+  InitializeCallback pending_initialize_callback_;
 };
 
 }  // namespace assistant
diff --git a/chromeos/services/assistant/proxy/assistant_proxy.cc b/chromeos/services/assistant/proxy/assistant_proxy.cc
index a3feb582..6cee87f 100644
--- a/chromeos/services/assistant/proxy/assistant_proxy.cc
+++ b/chromeos/services/assistant/proxy/assistant_proxy.cc
@@ -28,8 +28,8 @@
   libassistant_service_host_ = host;
   LaunchLibassistantService();
 
-  service_controller_proxy_ = std::make_unique<ServiceControllerProxy>(
-      background_task_runner(), BindServiceController());
+  service_controller_proxy_ =
+      std::make_unique<ServiceControllerProxy>(host, BindServiceController());
 }
 
 void AssistantProxy::LaunchLibassistantService() {
diff --git a/chromeos/services/assistant/proxy/libassistant_service_host.h b/chromeos/services/assistant/proxy/libassistant_service_host.h
index 7097cff5..6191d37d 100644
--- a/chromeos/services/assistant/proxy/libassistant_service_host.h
+++ b/chromeos/services/assistant/proxy/libassistant_service_host.h
@@ -9,6 +9,11 @@
 
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 
+namespace assistant_client {
+class AssistantManager;
+class AssistantManagerInternal;
+}  // namespace assistant_client
+
 namespace chromeos {
 namespace libassistant {
 namespace mojom {
@@ -36,9 +41,18 @@
   // |Stop| is called.
   virtual void Launch(
       mojo::PendingReceiver<LibassistantServiceMojom> receiver) = 0;
-  // Stop the mojom service.
 
+  // Stop the mojom service.
   virtual void Stop() = 0;
+
+  // Set a callback to initialize |AssistantManager| and
+  // |AssistantManagerInternal|. This callback will be invoked before
+  // AssistantManager::Start() is called. This is temporary until we've migrated
+  // all initialization code to the mojom service.
+  virtual void SetInitializeCallback(
+      base::OnceCallback<
+          void(assistant_client::AssistantManager*,
+               assistant_client::AssistantManagerInternal*)>) = 0;
 };
 
 }  // namespace assistant
diff --git a/chromeos/services/assistant/proxy/service_controller_proxy.cc b/chromeos/services/assistant/proxy/service_controller_proxy.cc
index 0dc1660..8390fc7 100644
--- a/chromeos/services/assistant/proxy/service_controller_proxy.cc
+++ b/chromeos/services/assistant/proxy/service_controller_proxy.cc
@@ -11,6 +11,7 @@
 #include "chromeos/assistant/internal/cros_display_connection.h"
 #include "chromeos/assistant/internal/internal_util.h"
 #include "chromeos/constants/chromeos_features.h"
+#include "chromeos/services/assistant/proxy/libassistant_service_host.h"
 #include "chromeos/services/assistant/public/cpp/features.h"
 #include "chromeos/services/assistant/public/cpp/migration/assistant_manager_service_delegate.h"
 #include "chromeos/services/assistant/public/cpp/migration/libassistant_v1_api.h"
@@ -36,6 +37,25 @@
 constexpr char kServersideOpenAppExperimentId[] = "39651593";
 constexpr char kServersideResponseProcessingV2ExperimentId[] = "1793869";
 
+struct StartArguments {
+  StartArguments() = default;
+  StartArguments(StartArguments&&) = default;
+  StartArguments& operator=(StartArguments&&) = default;
+  ~StartArguments() = default;
+
+  assistant_client::ActionModule* action_module;
+  assistant_client::FuchsiaApiDelegate* fuchsia_api_delegate;
+  assistant_client::AssistantManagerDelegate* assistant_manager_delegate;
+  assistant_client::ConversationStateListener* conversation_state_listener;
+  assistant_client::DeviceStateListener* device_state_listener;
+  CrosDisplayConnection* display_connection;
+  std::string libassistant_config;
+  std::string locale;
+  std::string locale_override;
+  bool spoken_feedback_enabled;
+  ServiceControllerProxy::AuthTokens auth_tokens;
+};
+
 void FillServerExperimentIds(std::vector<std::string>* server_experiment_ids) {
   if (base::FeatureList::IsEnabled(kChromeOSAssistantDogfood)) {
     server_experiment_ids->emplace_back(kServersideDogfoodExperimentId);
@@ -58,17 +78,59 @@
   }
 }
 
+void SetInternalOptions(
+    assistant_client::AssistantManagerInternal* assistant_manager_internal,
+    const std::string& locale,
+    bool spoken_feedback_enabled) {
+  auto* internal_options =
+      assistant_manager_internal->CreateDefaultInternalOptions();
+  SetAssistantOptions(internal_options, locale, spoken_feedback_enabled);
+
+  internal_options->SetClientControlEnabled(
+      assistant::features::IsRoutinesEnabled());
+
+  if (!features::IsVoiceMatchDisabled())
+    internal_options->EnableRequireVoiceMatchVerification();
+
+  assistant_manager_internal->SetOptions(*internal_options, [](bool success) {
+    DVLOG(2) << "set options: " << success;
+  });
+}
+
+// TODO(b/171748795): This should all be migrated to the mojom service, which
+// should be responsible for the complete creation of the Libassistant
+// objects.
+// Note: this method will be called from the mojom (background) thread.
+void InitializeAssistantManager(
+    StartArguments arguments,
+    assistant_client::AssistantManager* assistant_manager,
+    assistant_client::AssistantManagerInternal* assistant_manager_internal) {
+  SetInternalOptions(assistant_manager_internal, arguments.locale,
+                     arguments.spoken_feedback_enabled);
+  assistant_manager_internal->SetDisplayConnection(
+      arguments.display_connection);
+  assistant_manager_internal->SetLocaleOverride(arguments.locale_override);
+  assistant_manager_internal->RegisterActionModule(arguments.action_module);
+  assistant_manager_internal->SetAssistantManagerDelegate(
+      arguments.assistant_manager_delegate);
+  assistant_manager_internal->GetFuchsiaApiHelperOrDie()->SetFuchsiaApiDelegate(
+      arguments.fuchsia_api_delegate);
+  assistant_manager->AddConversationStateListener(
+      arguments.conversation_state_listener);
+  assistant_manager->AddDeviceStateListener(arguments.device_state_listener);
+  SetServerExperiments(assistant_manager_internal);
+  assistant_manager->SetAuthTokens(arguments.auth_tokens);
+}
+
 }  // namespace
 
 ServiceControllerProxy::ServiceControllerProxy(
-    scoped_refptr<base::SingleThreadTaskRunner> background_task_runner,
+    LibassistantServiceHost* host,
     mojo::PendingRemote<chromeos::libassistant::mojom::ServiceController>
         client)
-    : background_task_runner_(std::move(background_task_runner)),
+    : host_(host),
       service_controller_remote_(std::move(client)),
       state_observer_receiver_(this) {
-  DCHECK(background_task_runner_);
-
   service_controller_remote_->AddAndFireStateObserver(
       state_observer_receiver_.BindNewPipeAndPassRemote());
 }
@@ -92,24 +154,31 @@
   DCHECK_EQ(state_, State::kStopped);
   state_ = State::kStarting;
 
+  pending_display_connection_ = std::make_unique<CrosDisplayConnection>(
+      event_observer, /*feedback_ui_enabled=*/true,
+      assistant::features::IsMediaSessionIntegrationEnabled());
+
   // The mojom service will create the |AssistantManager|.
   service_controller_remote_->Start(libassistant_config);
 
-  // We need to finalize (and start) the |AssistantManager| once it's created,
-  // so we have to store all the required arguments for that.
+  // We need to initialize the |AssistantManager| once it's created and before
+  // it's started, so we register a callback to do just that.
   StartArguments arguments;
   arguments.action_module = action_module;
   arguments.fuchsia_api_delegate = fuchsia_api_delegate;
   arguments.assistant_manager_delegate = assistant_manager_delegate;
   arguments.conversation_state_listener = conversation_state_listener;
   arguments.device_state_listener = device_state_listener;
-  arguments.event_observer = event_observer;
+  arguments.display_connection = pending_display_connection_.get();
   arguments.locale = locale;
   arguments.locale_override = locale_override;
   arguments.spoken_feedback_enabled = spoken_feedback_enabled;
   arguments.auth_tokens = auth_tokens;
-  arguments.done_callback = std::move(done_callback);
-  pending_start_argument_ = std::move(arguments);
+
+  host_->SetInitializeCallback(
+      base::BindOnce(InitializeAssistantManager, std::move(arguments)));
+
+  on_start_done_callback_ = std::move(done_callback);
 }
 
 void ServiceControllerProxy::Stop() {
@@ -126,21 +195,8 @@
 void ServiceControllerProxy::UpdateInternalOptions(
     const std::string& locale,
     bool spoken_feedback_enabled) {
-  // NOTE: this method is called on multiple threads, it needs to be
-  // thread-safe.
-  auto* internal_options =
-      assistant_manager_internal()->CreateDefaultInternalOptions();
-  SetAssistantOptions(internal_options, locale, spoken_feedback_enabled);
-
-  internal_options->SetClientControlEnabled(
-      assistant::features::IsRoutinesEnabled());
-
-  if (!features::IsVoiceMatchDisabled())
-    internal_options->EnableRequireVoiceMatchVerification();
-
-  assistant_manager_internal()->SetOptions(*internal_options, [](bool success) {
-    DVLOG(2) << "set options: " << success;
-  });
+  SetInternalOptions(assistant_manager_internal(), locale,
+                     spoken_feedback_enabled);
 }
 
 void ServiceControllerProxy::SetAuthTokens(const AuthTokens& tokens) {
@@ -181,45 +237,13 @@
 }
 
 void ServiceControllerProxy::FinishCreatingAssistant() {
-  // TODO(b/171748795): This should all be migrated to the mojom service, which
-  // should be responsible for the complete creation of the Libassistant
-  // objects.
-  DCHECK(pending_start_argument_.has_value());
-
-  auto arguments = std::move(pending_start_argument_.value());
-
-  display_connection_ = std::make_unique<CrosDisplayConnection>(
-      arguments.event_observer, /*feedback_ui_enabled=*/true,
-      assistant::features::IsMediaSessionIntegrationEnabled());
-
-  UpdateInternalOptions(arguments.locale, arguments.spoken_feedback_enabled);
-
-  assistant_manager_internal()->SetDisplayConnection(display_connection());
-  assistant_manager_internal()->SetLocaleOverride(arguments.locale_override);
-  assistant_manager_internal()->RegisterActionModule(arguments.action_module);
-  assistant_manager_internal()->SetAssistantManagerDelegate(
-      arguments.assistant_manager_delegate);
-  assistant_manager_internal()
-      ->GetFuchsiaApiHelperOrDie()
-      ->SetFuchsiaApiDelegate(arguments.fuchsia_api_delegate);
-  assistant_manager()->AddConversationStateListener(
-      arguments.conversation_state_listener);
-  assistant_manager()->AddDeviceStateListener(arguments.device_state_listener);
-  SetServerExperiments(assistant_manager_internal());
-  SetAuthTokens(arguments.auth_tokens);
-
-  assistant_manager()->Start();
+  DCHECK(on_start_done_callback_.has_value());
+  DCHECK_NE(pending_display_connection_, nullptr);
 
   state_ = State::kStarted;
-  std::move(arguments.done_callback).Run();
+  display_connection_ = std::move(pending_display_connection_);
+  std::move(on_start_done_callback_.value()).Run();
 }
 
-ServiceControllerProxy::StartArguments::StartArguments() = default;
-ServiceControllerProxy::StartArguments::StartArguments(StartArguments&&) =
-    default;
-ServiceControllerProxy::StartArguments&
-ServiceControllerProxy::StartArguments::operator=(StartArguments&&) = default;
-ServiceControllerProxy::StartArguments::~StartArguments() = default;
-
 }  // namespace assistant
 }  // namespace chromeos
diff --git a/chromeos/services/assistant/proxy/service_controller_proxy.h b/chromeos/services/assistant/proxy/service_controller_proxy.h
index d98f1a1..83a1a2b 100644
--- a/chromeos/services/assistant/proxy/service_controller_proxy.h
+++ b/chromeos/services/assistant/proxy/service_controller_proxy.h
@@ -11,9 +11,7 @@
 #include <vector>
 
 #include "base/check.h"
-#include "base/memory/scoped_refptr.h"
 #include "base/memory/weak_ptr.h"
-#include "base/single_thread_task_runner.h"
 #include "chromeos/services/libassistant/public/mojom/service_controller.mojom.h"
 #include "mojo/public/cpp/bindings/remote.h"
 
@@ -34,6 +32,7 @@
 
 class AssistantEventObserver;
 class CrosDisplayConnection;
+class LibassistantServiceHost;
 
 // Component managing the lifecycle of Libassistant,
 // exposing methods to start/stop and configure Libassistant.
@@ -43,7 +42,7 @@
   using AuthTokens = std::vector<std::pair<std::string, std::string>>;
 
   ServiceControllerProxy(
-      scoped_refptr<base::SingleThreadTaskRunner> background_task_runner,
+      LibassistantServiceHost* host,
       mojo::PendingRemote<chromeos::libassistant::mojom::ServiceController>
           client);
 
@@ -52,8 +51,6 @@
   ~ServiceControllerProxy() override;
 
   // Can not be invoked before Start() has finished.
-  // Both LibAssistant and Chrome threads may access |display_connection|.
-  // |display_connection| is thread safe.
   CrosDisplayConnection* display_connection() {
     DCHECK(display_connection_);
     return display_connection_.get();
@@ -112,25 +109,6 @@
   // Can not be invoked before Start() has finished.
   assistant_client::AssistantManagerInternal* assistant_manager_internal();
 
-  struct StartArguments {
-    StartArguments();
-    StartArguments(StartArguments&&);
-    StartArguments& operator=(StartArguments&&);
-    ~StartArguments();
-    assistant_client::ActionModule* action_module;
-    assistant_client::FuchsiaApiDelegate* fuchsia_api_delegate;
-    assistant_client::AssistantManagerDelegate* assistant_manager_delegate;
-    assistant_client::ConversationStateListener* conversation_state_listener;
-    assistant_client::DeviceStateListener* device_state_listener;
-    AssistantEventObserver* event_observer;
-    std::string libassistant_config;
-    std::string locale;
-    std::string locale_override;
-    bool spoken_feedback_enabled;
-    AuthTokens auth_tokens;
-    base::OnceClosure done_callback;
-  };
-
   void FinishCreatingAssistant();
 
   // libassistant::mojom::StateObserver implementation:
@@ -141,20 +119,22 @@
   // Used internally for consistency checks.
   State state_ = State::kStopped;
 
-  scoped_refptr<base::SingleThreadTaskRunner> background_task_runner_;
+  // Owned by |AssistantManagerServiceImpl| which (indirectly) also owns us.
+  LibassistantServiceHost* const host_;
 
   mojo::Remote<chromeos::libassistant::mojom::ServiceController>
       service_controller_remote_;
   mojo::Receiver<chromeos::libassistant::mojom::StateObserver>
       state_observer_receiver_;
 
-  // Arguments passed to the last Start() call.
-  // Used to finish starting Libassistant after the Libassistant mojom service
-  // signals it has created the required objects.
-  // Unset once we've finished starting.
-  base::Optional<StartArguments> pending_start_argument_;
+  // Callback passed to Start(). Will be invoked once the Libassistant service
+  // has started.
+  base::Optional<base::OnceClosure> on_start_done_callback_;
 
   std::unique_ptr<CrosDisplayConnection> display_connection_;
+  // Populated when we're starting but not started yet, so after Start() has
+  // been called but before the mojom service signalled it has started.
+  std::unique_ptr<CrosDisplayConnection> pending_display_connection_;
 
   base::WeakPtrFactory<ServiceControllerProxy> weak_factory_{this};
 };
diff --git a/chromeos/services/assistant/test_support/fake_service_controller.cc b/chromeos/services/assistant/test_support/fake_service_controller.cc
index 6feaaff..235d54b 100644
--- a/chromeos/services/assistant/test_support/fake_service_controller.cc
+++ b/chromeos/services/assistant/test_support/fake_service_controller.cc
@@ -4,6 +4,7 @@
 
 #include "chromeos/services/assistant/test_support/fake_service_controller.h"
 
+#include "chromeos/services/assistant/public/cpp/migration/libassistant_v1_api.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace chromeos {
@@ -35,6 +36,10 @@
   state_observers_.Clear();
 }
 
+void FakeServiceController::SetInitializeCallback(InitializeCallback callback) {
+  initialize_callback_ = std::move(callback);
+}
+
 void FakeServiceController::BlockStartCalls() {
   // This lock will be release in |UnblockStartCalls|.
   start_mutex_.lock();
@@ -50,6 +55,12 @@
   // Will block if |BlockStartCalls| was invoked.
   std::lock_guard<std::mutex> lock(start_mutex_);
 
+  if (initialize_callback_) {
+    std::move(initialize_callback_)
+        .Run(LibassistantV1Api::Get()->assistant_manager(),
+             LibassistantV1Api::Get()->assistant_manager_internal());
+  }
+
   SetState(State::kStarted);
 }
 
diff --git a/chromeos/services/assistant/test_support/fake_service_controller.h b/chromeos/services/assistant/test_support/fake_service_controller.h
index 59f2bae..9dc561a 100644
--- a/chromeos/services/assistant/test_support/fake_service_controller.h
+++ b/chromeos/services/assistant/test_support/fake_service_controller.h
@@ -12,6 +12,11 @@
 #include "mojo/public/cpp/bindings/receiver.h"
 #include "mojo/public/cpp/bindings/remote_set.h"
 
+namespace assistant_client {
+class AssistantManager;
+class AssistantManagerInternal;
+}  // namespace assistant_client
+
 namespace chromeos {
 namespace assistant {
 
@@ -21,6 +26,9 @@
 class FakeServiceController : public libassistant::mojom::ServiceController {
  public:
   using State = libassistant::mojom::ServiceState;
+  using InitializeCallback =
+      base::OnceCallback<void(assistant_client::AssistantManager*,
+                              assistant_client::AssistantManagerInternal*)>;
 
   FakeServiceController();
   FakeServiceController(FakeServiceController&) = delete;
@@ -39,6 +47,8 @@
                 pending_receiver);
   void Unbind();
 
+  void SetInitializeCallback(InitializeCallback callback);
+
   // Call this to block any call to |Start|. The observers will not be invoked
   // as long as the start call is blocked. Unblock these calls using
   // |UnblockStartCalls|. This is not enabled by default, so unless you call
@@ -61,6 +71,8 @@
   // Config passed to LibAssistant when it was started.
   std::string libassistant_config_;
 
+  InitializeCallback initialize_callback_;
+
   State state_ = State::kStopped;
   mojo::Receiver<libassistant::mojom::ServiceController> receiver_;
   mojo::RemoteSet<libassistant::mojom::StateObserver> state_observers_;
diff --git a/chromeos/services/assistant/utils.cc b/chromeos/services/assistant/utils.cc
index 82bbf877..ace2adf 100644
--- a/chromeos/services/assistant/utils.cc
+++ b/chromeos/services/assistant/utils.cc
@@ -148,10 +148,8 @@
                          GetBaseAssistantDir().AsUTF8Unsafe());
   }
 
-  if (features::IsLibAssistantBetaBackendEnabled() ||
-      features::IsAssistantDebuggingEnabled()) {
+  if (features::IsLibAssistantBetaBackendEnabled())
     config.SetStringPath("internal.backend_type", "BETA_DOGFOOD");
-  }
 
   // Use http unless we're using the fake s3 server, which requires grpc.
   if (s3_server_uri_override)
diff --git a/chromeos/services/libassistant/libassistant_service.cc b/chromeos/services/libassistant/libassistant_service.cc
index 6bf0ea1..481fb6d 100644
--- a/chromeos/services/libassistant/libassistant_service.cc
+++ b/chromeos/services/libassistant/libassistant_service.cc
@@ -29,5 +29,9 @@
   service_controller_->Bind(std::move(receiver));
 }
 
+void LibassistantService::SetInitializeCallback(InitializeCallback callback) {
+  service_controller().SetInitializeCallback(std::move(callback));
+}
+
 }  // namespace libassistant
 }  // namespace chromeos
diff --git a/chromeos/services/libassistant/libassistant_service.h b/chromeos/services/libassistant/libassistant_service.h
index fed2dfa..38c5c05 100644
--- a/chromeos/services/libassistant/libassistant_service.h
+++ b/chromeos/services/libassistant/libassistant_service.h
@@ -12,6 +12,8 @@
 #include "mojo/public/cpp/bindings/receiver.h"
 
 namespace assistant_client {
+class AssistantManager;
+class AssistantManagerInternal;
 class PlatformApi;
 }  // namespace assistant_client
 
@@ -29,6 +31,10 @@
 class COMPONENT_EXPORT(LIBASSISTANT_SERVICE) LibassistantService
     : public mojom::LibassistantService {
  public:
+  using InitializeCallback =
+      base::OnceCallback<void(assistant_client::AssistantManager*,
+                              assistant_client::AssistantManagerInternal*)>;
+
   explicit LibassistantService(
       mojo::PendingReceiver<mojom::LibassistantService> receiver,
       assistant_client::PlatformApi* platform_api,
@@ -37,7 +43,11 @@
   LibassistantService& operator=(LibassistantService&) = delete;
   ~LibassistantService() override;
 
+  void SetInitializeCallback(InitializeCallback callback);
+
  private:
+  ServiceController& service_controller() { return *service_controller_; }
+
   // mojom::LibassistantService implementation:
   void BindServiceController(
       mojo::PendingReceiver<mojom::ServiceController> receiver) override;
diff --git a/chromeos/services/libassistant/service_controller.cc b/chromeos/services/libassistant/service_controller.cc
index 68ceb36..24a235e 100644
--- a/chromeos/services/libassistant/service_controller.cc
+++ b/chromeos/services/libassistant/service_controller.cc
@@ -32,6 +32,10 @@
   receiver_.Bind(std::move(receiver));
 }
 
+void ServiceController::SetInitializeCallback(InitializeCallback callback) {
+  initialize_callback_ = std::move(callback);
+}
+
 void ServiceController::Start(const std::string& libassistant_config) {
   if (state_ != ServiceState::kStopped)
     return;
@@ -43,6 +47,13 @@
   libassistant_v1_api_ = std::make_unique<assistant::LibassistantV1Api>(
       assistant_manager_.get(), assistant_manager_internal_);
 
+  if (initialize_callback_) {
+    std::move(initialize_callback_)
+        .Run(assistant_manager(), assistant_manager_internal());
+  }
+
+  assistant_manager()->Start();
+
   SetStateAndInformObservers(ServiceState::kStarted);
 
   for (auto& observer : assistant_manager_observers_) {
diff --git a/chromeos/services/libassistant/service_controller.h b/chromeos/services/libassistant/service_controller.h
index 26ffd61..c93bfba 100644
--- a/chromeos/services/libassistant/service_controller.h
+++ b/chromeos/services/libassistant/service_controller.h
@@ -43,6 +43,10 @@
 class COMPONENT_EXPORT(LIBASSISTANT_SERVICE) ServiceController
     : public mojom::ServiceController {
  public:
+  using InitializeCallback =
+      base::OnceCallback<void(assistant_client::AssistantManager*,
+                              assistant_client::AssistantManagerInternal*)>;
+
   ServiceController(assistant::AssistantManagerServiceDelegate* delegate,
                     assistant_client::PlatformApi* platform_api);
   ServiceController(ServiceController&) = delete;
@@ -51,6 +55,12 @@
 
   void Bind(mojo::PendingReceiver<mojom::ServiceController> receiver);
 
+  // Set a callback to initialize |AssistantManager| and
+  // |AssistantManagerInternal|. This callback will be invoked before
+  // AssistantManager::Start() is called. This is temporary until we've migrated
+  // all initialization code to this class.
+  void SetInitializeCallback(InitializeCallback callback);
+
   // mojom::ServiceController implementation:
   void Start(const std::string& libassistant_config) override;
   void Stop() override;
@@ -77,6 +87,9 @@
   // Owned by |AssistantManagerServiceImpl| which indirectly owns us.
   assistant_client::PlatformApi* const platform_api_;
 
+  // Callback called to initialize |AssistantManager| before it's started.
+  InitializeCallback initialize_callback_;
+
   std::unique_ptr<assistant_client::AssistantManager> assistant_manager_;
   assistant_client::AssistantManagerInternal* assistant_manager_internal_ =
       nullptr;
diff --git a/components/download/internal/common/in_progress_download_manager.cc b/components/download/internal/common/in_progress_download_manager.cc
index fdd22a1..c8a3830 100644
--- a/components/download/internal/common/in_progress_download_manager.cc
+++ b/components/download/internal/common/in_progress_download_manager.cc
@@ -680,6 +680,7 @@
 void InProgressDownloadManager::AddInProgressDownloadForTest(
     std::unique_ptr<download::DownloadItemImpl> download) {
   in_progress_downloads_.push_back(std::move(download));
+  NotifyDownloadsInitialized();
 }
 
 void InProgressDownloadManager::CancelUrlDownload(
diff --git a/components/exo/data_device.cc b/components/exo/data_device.cc
index 3787f7d..091ebfb 100644
--- a/components/exo/data_device.cc
+++ b/components/exo/data_device.cc
@@ -124,14 +124,18 @@
 
   delegate_->OnDrop();
 
-  // TODO(tetsui): Avoid using nested loop by adding asynchronous callback to
-  // aura::client::DragDropDelegate.
+  // TODO(crbug.com/1160925): Avoid using nested loop by adding asynchronous
+  // callback to aura::client::DragDropDelegate.
+  base::WeakPtr<DataDevice> alive(weak_factory_.GetWeakPtr());
   base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed);
   base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
       FROM_HERE, run_loop.QuitClosure(), kDataOfferDestructionTimeout);
   quit_closure_ = run_loop.QuitClosure();
   run_loop.Run();
 
+  if (!alive)
+    return ui::DragDropTypes::DRAG_NONE;
+
   if (quit_closure_) {
     // DataOffer not destroyed by the client until the timeout.
     quit_closure_.Reset();
diff --git a/components/exo/data_device.h b/components/exo/data_device.h
index b0b647b..cf66a18 100644
--- a/components/exo/data_device.h
+++ b/components/exo/data_device.h
@@ -8,6 +8,7 @@
 #include <cstdint>
 
 #include "base/macros.h"
+#include "base/memory/weak_ptr.h"
 #include "components/exo/data_offer_observer.h"
 #include "components/exo/seat_observer.h"
 #include "components/exo/surface.h"
@@ -87,6 +88,7 @@
 
   base::OnceClosure quit_closure_;
   bool drop_succeeded_;
+  base::WeakPtrFactory<DataDevice> weak_factory_{this};
 
   DISALLOW_COPY_AND_ASSIGN(DataDevice);
 };
diff --git a/components/exo/data_device_unittest.cc b/components/exo/data_device_unittest.cc
index 09c94d8..fbe7d49f 100644
--- a/components/exo/data_device_unittest.cc
+++ b/components/exo/data_device_unittest.cc
@@ -11,6 +11,7 @@
 #include "ash/shell.h"
 #include "base/strings/string16.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/test/bind.h"
 #include "components/exo/data_device_delegate.h"
 #include "components/exo/data_exchange_delegate.h"
 #include "components/exo/data_offer.h"
@@ -199,6 +200,17 @@
   EXPECT_EQ(DataEvent::kLeave, events[0]);
 }
 
+TEST_F(DataDeviceTest, DeleteDataDeviceDuringDrop) {
+  ui::DropTargetEvent event(data_, gfx::PointF(), gfx::PointF(),
+                            ui::DragDropTypes::DRAG_MOVE);
+  ui::Event::DispatcherApi(&event).set_target(surface_->window());
+  device_->OnDragEntered(event);
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, base::BindLambdaForTesting([&]() { device_.reset(); }));
+  int result = device_->OnPerformDrop(event);
+  EXPECT_EQ(ui::DragDropTypes::DRAG_NONE, result);
+}
+
 TEST_F(DataDeviceTest, DeleteDataOfferDuringDrag) {
   ui::DropTargetEvent event(data_, gfx::PointF(), gfx::PointF(),
                             ui::DragDropTypes::DRAG_MOVE);
diff --git a/components/no_state_prefetch/browser/prerender_manager.cc b/components/no_state_prefetch/browser/prerender_manager.cc
index 76fbf6b0..ae248992 100644
--- a/components/no_state_prefetch/browser/prerender_manager.cc
+++ b/components/no_state_prefetch/browser/prerender_manager.cc
@@ -499,6 +499,14 @@
     return nullptr;
   }
 
+  // Disallow NSPing link-rel:next URLs.
+  // See https://bugs.chromium.org/p/chromium/issues/detail?id=1158209.
+  if (origin == ORIGIN_LINK_REL_NEXT) {
+    SkipPrerenderContentsAndMaybePreconnect(
+        url_arg, origin, FINAL_STATUS_LINK_REL_NEXT_NOT_ALLOWED);
+    return nullptr;
+  }
+
   // Disallow prerendering on low end devices.
   if (IsLowEndDevice()) {
     SkipPrerenderContentsAndMaybePreconnect(url_arg, origin,
@@ -923,6 +931,9 @@
     return;
   }
 
+  if (origin == ORIGIN_LINK_REL_NEXT)
+    return;
+
   if (final_status == FINAL_STATUS_LOW_END_DEVICE ||
       final_status == FINAL_STATUS_CELLULAR_NETWORK ||
       final_status == FINAL_STATUS_DUPLICATE ||
@@ -931,7 +942,7 @@
   }
 
   static_assert(
-      FINAL_STATUS_MAX == FINAL_STATUS_SINGLE_PROCESS + 1,
+      FINAL_STATUS_MAX == FINAL_STATUS_LINK_REL_NEXT_NOT_ALLOWED + 1,
       "Consider whether a failed prerender should fallback to preconnect");
 }
 
@@ -977,6 +988,15 @@
   prefetches_.clear();
 }
 
+std::unique_ptr<PrerenderHandle>
+PrerenderManager::AddPrerenderWithPreconnectFallbackForTesting(
+    Origin origin,
+    const GURL& url,
+    const base::Optional<url::Origin>& initiator_origin) {
+  return AddPrerenderWithPreconnectFallback(
+      origin, url, content::Referrer(), initiator_origin, gfx::Rect(), nullptr);
+}
+
 void PrerenderManager::SetPrerenderContentsFactoryForTest(
     PrerenderContents::Factory* prerender_contents_factory) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
diff --git a/components/no_state_prefetch/browser/prerender_manager.h b/components/no_state_prefetch/browser/prerender_manager.h
index 1795bc89..5921099 100644
--- a/components/no_state_prefetch/browser/prerender_manager.h
+++ b/components/no_state_prefetch/browser/prerender_manager.h
@@ -266,6 +266,14 @@
   // Returns true iff the |url| is found in the list of recent prefetches.
   bool HasRecentlyPrefetchedUrlForTesting(const GURL& url);
 
+  // Adds a prerender for |url| from |initiator_origin|. The |origin| specifies
+  // how the prerender was added. Returns a PrerenderHandle or nullptr. Only for
+  // testing.
+  std::unique_ptr<PrerenderHandle> AddPrerenderWithPreconnectFallbackForTesting(
+      Origin origin,
+      const GURL& url,
+      const base::Optional<url::Origin>& initiator_origin);
+
  protected:
   class PrerenderData : public base::SupportsWeakPtr<PrerenderData> {
    public:
diff --git a/components/no_state_prefetch/common/prerender_final_status.cc b/components/no_state_prefetch/common/prerender_final_status.cc
index 0029e8c..be22d0f 100644
--- a/components/no_state_prefetch/common/prerender_final_status.cc
+++ b/components/no_state_prefetch/common/prerender_final_status.cc
@@ -75,6 +75,7 @@
     "Unknown",
     "Navigation Predictor Holdback",
     "Single Process Mode",
+    "Link Rel Next Not Allowed",
     "Max",
 };
 static_assert(base::size(kFinalStatusNames) == FINAL_STATUS_MAX + 1,
diff --git a/components/no_state_prefetch/common/prerender_final_status.h b/components/no_state_prefetch/common/prerender_final_status.h
index e827c2d..f21979b 100644
--- a/components/no_state_prefetch/common/prerender_final_status.h
+++ b/components/no_state_prefetch/common/prerender_final_status.h
@@ -80,6 +80,7 @@
   FINAL_STATUS_UNKNOWN = 60,
   FINAL_STATUS_NAVIGATION_PREDICTOR_HOLDBACK = 61,
   FINAL_STATUS_SINGLE_PROCESS = 62,
+  FINAL_STATUS_LINK_REL_NEXT_NOT_ALLOWED = 63,
   FINAL_STATUS_MAX,
 };
 
diff --git a/components/optimization_guide/bloom_filter.cc b/components/optimization_guide/bloom_filter.cc
index 20e6fb19..33155175 100644
--- a/components/optimization_guide/bloom_filter.cc
+++ b/components/optimization_guide/bloom_filter.cc
@@ -51,9 +51,7 @@
   memcpy(&bytes_[0], filter_data.data(), filter_data.size());
 }
 
-BloomFilter::~BloomFilter() {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-}
+BloomFilter::~BloomFilter() = default;
 
 bool BloomFilter::Contains(const std::string& str) const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
diff --git a/components/optimization_guide/optimization_filter.cc b/components/optimization_guide/optimization_filter.cc
index f977fc1..a67ae4e0 100644
--- a/components/optimization_guide/optimization_filter.cc
+++ b/components/optimization_guide/optimization_filter.cc
@@ -28,11 +28,10 @@
   DETACH_FROM_SEQUENCE(sequence_checker_);
 }
 
-OptimizationFilter::~OptimizationFilter() {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-}
+OptimizationFilter::~OptimizationFilter() = default;
 
 bool OptimizationFilter::Matches(const GURL& url) const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   return ContainsHostSuffix(url) || MatchesRegexp(url);
 }
 
diff --git a/components/optimization_guide/test_hints_component_creator.cc b/components/optimization_guide/test_hints_component_creator.cc
index 6edfb2c3..bfcde05 100644
--- a/components/optimization_guide/test_hints_component_creator.cc
+++ b/components/optimization_guide/test_hints_component_creator.cc
@@ -11,6 +11,7 @@
 #include "base/strings/string_number_conversions.h"
 #include "base/threading/thread_restrictions.h"
 #include "base/version.h"
+#include "components/optimization_guide/bloom_filter.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace {
@@ -58,7 +59,6 @@
     optimization_guide::proto::Optimization* optimization =
         page_hint->add_whitelisted_optimizations();
     optimization->set_optimization_type(optimization_type);
-
   }
 
   // Always stick something with no hint version in here.
@@ -73,6 +73,37 @@
   bad_version_hint->set_version("notaversion");
   bad_version_hint->add_page_hints()->set_page_pattern("*");
 
+  // Always stick an allowlist optimization filter in here.
+  optimization_guide::BloomFilter allowlist_bloom_filter(7, 511);
+  allowlist_bloom_filter.Add("allowedhost.com");
+  std::string allowlist_bloom_filter_data(
+      reinterpret_cast<const char*>(&allowlist_bloom_filter.bytes()[0]),
+      allowlist_bloom_filter.bytes().size());
+  optimization_guide::proto::OptimizationFilter* allowlist_optimization_filter =
+      config.add_optimization_allowlists();
+  allowlist_optimization_filter->set_optimization_type(
+      optimization_guide::proto::LITE_PAGE_REDIRECT);
+  allowlist_optimization_filter->mutable_bloom_filter()->set_num_hash_functions(
+      7);
+  allowlist_optimization_filter->mutable_bloom_filter()->set_num_bits(511);
+  allowlist_optimization_filter->mutable_bloom_filter()->set_data(
+      allowlist_bloom_filter_data);
+  // Always stick a blocklist optimization filter in here.
+  optimization_guide::BloomFilter blocklist_bloom_filter(7, 511);
+  blocklist_bloom_filter.Add("blockedhost.com");
+  std::string blocklist_bloom_filter_data(
+      reinterpret_cast<const char*>(&blocklist_bloom_filter.bytes()[0]),
+      blocklist_bloom_filter.bytes().size());
+  optimization_guide::proto::OptimizationFilter* blocklist_optimization_filter =
+      config.add_optimization_blacklists();
+  blocklist_optimization_filter->set_optimization_type(
+      optimization_guide::proto::FAST_HOST_HINTS);
+  blocklist_optimization_filter->mutable_bloom_filter()->set_num_hash_functions(
+      7);
+  blocklist_optimization_filter->mutable_bloom_filter()->set_num_bits(511);
+  blocklist_optimization_filter->mutable_bloom_filter()->set_data(
+      blocklist_bloom_filter_data);
+
   return WriteConfigToFileAndReturnHintsComponentInfo(config);
 }
 
diff --git a/components/password_manager/core/browser/sync/password_sync_bridge.cc b/components/password_manager/core/browser/sync/password_sync_bridge.cc
index 35dd2d8..d015c0f 100644
--- a/components/password_manager/core/browser/sync/password_sync_bridge.cc
+++ b/components/password_manager/core/browser/sync/password_sync_bridge.cc
@@ -15,6 +15,7 @@
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/utf_string_conversions.h"
 #include "build/build_config.h"
+#include "components/password_manager/core/browser/insecure_credentials_table.h"
 #include "components/password_manager/core/browser/password_form.h"
 #include "components/password_manager/core/browser/password_manager_metrics_util.h"
 #include "components/password_manager/core/common/password_manager_features.h"
@@ -55,6 +56,13 @@
          net::EscapePath(password_data.signon_realm());
 }
 
+base::Time ConvertToBaseTime(uint64_t time) {
+  return base::Time::FromDeltaSinceWindowsEpoch(
+      // Use FromDeltaSinceWindowsEpoch because create_time_us has
+      // always used the Windows epoch.
+      base::TimeDelta::FromMicroseconds(time));
+}
+
 sync_pb::PasswordSpecifics SpecificsFromPassword(
     const PasswordForm& password_form) {
   sync_pb::PasswordSpecifics specifics;
@@ -107,18 +115,14 @@
   password.username_value = base::UTF8ToUTF16(password_data.username_value());
   password.password_value = base::UTF8ToUTF16(password_data.password_value());
   if (password_data.has_date_last_used()) {
-    password.date_last_used = base::Time::FromDeltaSinceWindowsEpoch(
-        base::TimeDelta::FromMicroseconds(password_data.date_last_used()));
+    password.date_last_used = ConvertToBaseTime(password_data.date_last_used());
   } else if (password_data.preferred()) {
     // For legacy passwords that don't have the |date_last_used| field set, we
     // should it similar to the logic in login database migration.
     password.date_last_used =
         base::Time::FromDeltaSinceWindowsEpoch(base::TimeDelta::FromDays(1));
   }
-  password.date_created = base::Time::FromDeltaSinceWindowsEpoch(
-      // Use FromDeltaSinceWindowsEpoch because create_time_us has
-      // always used the Windows epoch.
-      base::TimeDelta::FromMicroseconds(password_data.date_created()));
+  password.date_created = ConvertToBaseTime(password_data.date_created());
   password.blocked_by_user = password_data.blacklisted();
   password.type = static_cast<PasswordForm::Type>(password_data.type());
   password.times_used = password_data.times_used();
@@ -130,6 +134,58 @@
   return password;
 }
 
+CompromisedCredentials CreateCompromisedCredentials(
+    const std::string& signon_realm,
+    const base::string16& username,
+    CompromiseType compromise_type,
+    const sync_pb::PasswordSpecificsData::PasswordIssues::PasswordIssue&
+        issue) {
+  return CompromisedCredentials(
+      signon_realm, username,
+      ConvertToBaseTime(issue.date_first_detection_microseconds()),
+      compromise_type, IsMuted(issue.is_muted()));
+}
+
+std::vector<CompromisedCredentials> CompromisedCredentialsFromEntityChange(
+    const syncer::EntityChange& entity_change) {
+  DCHECK(entity_change.data().specifics.has_password());
+
+  const sync_pb::PasswordSpecificsData& password_data =
+      entity_change.data().specifics.password().client_only_encrypted_data();
+
+  std::vector<CompromisedCredentials> issues;
+
+  if (!password_data.has_password_issues())
+    return issues;
+
+  const std::string& signon_realm = password_data.signon_realm();
+  const base::string16& username =
+      base::UTF8ToUTF16(password_data.username_value());
+
+  const auto& password_issues = password_data.password_issues();
+  if (password_issues.has_leaked_password_issue()) {
+    issues.emplace_back(CreateCompromisedCredentials(
+        signon_realm, username, CompromiseType::kLeaked,
+        password_issues.leaked_password_issue()));
+  }
+  if (password_issues.has_reused_password_issue()) {
+    issues.emplace_back(CreateCompromisedCredentials(
+        signon_realm, username, CompromiseType::kReused,
+        password_issues.reused_password_issue()));
+  }
+  if (password_issues.has_weak_password_issue()) {
+    issues.emplace_back(CreateCompromisedCredentials(
+        signon_realm, username, CompromiseType::kWeak,
+        password_issues.weak_password_issue()));
+  }
+  if (password_issues.has_phished_password_issue()) {
+    issues.emplace_back(CreateCompromisedCredentials(
+        signon_realm, username, CompromiseType::kPhished,
+        password_issues.phished_password_issue()));
+  }
+  return issues;
+}
+
 std::unique_ptr<syncer::EntityData> CreateEntityData(const PasswordForm& form) {
   auto entity_data = std::make_unique<syncer::EntityData>();
   *entity_data->specifics.mutable_password() = SpecificsFromPassword(form);
@@ -165,13 +221,9 @@
           base::UTF16ToUTF8(password_form.password_value) ==
               password_specifics.password_value() &&
           password_form.date_last_used ==
-              base::Time::FromDeltaSinceWindowsEpoch(
-                  base::TimeDelta::FromMicroseconds(
-                      password_specifics.date_last_used())) &&
+              ConvertToBaseTime(password_specifics.date_last_used()) &&
           password_form.date_created ==
-              base::Time::FromDeltaSinceWindowsEpoch(
-                  base::TimeDelta::FromMicroseconds(
-                      password_specifics.date_created())) &&
+              ConvertToBaseTime(password_specifics.date_created()) &&
           password_form.blocked_by_user == password_specifics.blacklisted() &&
           static_cast<int>(password_form.type) == password_specifics.type() &&
           password_form.times_used == password_specifics.times_used() &&
@@ -422,9 +474,7 @@
       }
 
       // Passwords aren't identical, pick the most recently created one.
-      if (base::Time::FromDeltaSinceWindowsEpoch(
-              base::TimeDelta::FromMicroseconds(
-                  remote_password_specifics.date_created())) <
+      if (ConvertToBaseTime(remote_password_specifics.date_created()) <
           local_password_form.date_created) {
         // The local password is more recent, update the processor.
         change_processor()->Put(
@@ -476,6 +526,8 @@
       PasswordStoreChangeList changes = password_store_sync_->AddLoginSync(
           PasswordFromEntityChange(*entity_change, /*sync_time=*/time_now),
           &add_login_error);
+      password_store_sync_->AddCompromisedCredentialsSync(
+          CompromisedCredentialsFromEntityChange(*entity_change));
       base::UmaHistogramEnumeration(
           "PasswordManager.MergeSyncData.AddLoginSyncError", add_login_error);
 
@@ -590,6 +642,8 @@
           changes = password_store_sync_->AddLoginSync(
               PasswordFromEntityChange(*entity_change, /*sync_time=*/time_now),
               &add_login_error);
+          password_store_sync_->AddCompromisedCredentialsSync(
+              CompromisedCredentialsFromEntityChange(*entity_change));
           base::UmaHistogramEnumeration(
               "PasswordManager.ApplySyncChanges.AddLoginSyncError",
               add_login_error);
diff --git a/components/password_manager/core/browser/sync/password_sync_bridge_unittest.cc b/components/password_manager/core/browser/sync/password_sync_bridge_unittest.cc
index 4c8845a..4fdcf023 100644
--- a/components/password_manager/core/browser/sync/password_sync_bridge_unittest.cc
+++ b/components/password_manager/core/browser/sync/password_sync_bridge_unittest.cc
@@ -39,6 +39,7 @@
 using testing::NotNull;
 using testing::Return;
 using testing::UnorderedElementsAre;
+using testing::UnorderedElementsAreArray;
 
 constexpr char kSignonRealm1[] = "abc";
 constexpr char kSignonRealm2[] = "def";
@@ -67,11 +68,39 @@
              ->GetMetadataStoreForTesting() == expected_metadata_store;
 }
 
-sync_pb::PasswordSpecifics CreateSpecifics(const std::string& origin,
-                                           const std::string& username_element,
-                                           const std::string& username_value,
-                                           const std::string& password_element,
-                                           const std::string& signon_realm) {
+sync_pb::PasswordSpecificsData_PasswordIssues CreateSpecificsIssues(
+    const std::vector<CompromiseType>& issue_types) {
+  sync_pb::PasswordSpecificsData_PasswordIssues remote_issues;
+  for (auto type : issue_types) {
+    sync_pb::PasswordSpecificsData_PasswordIssues_PasswordIssue remote_issue;
+    remote_issue.set_date_first_detection_microseconds(
+        base::Time().ToDeltaSinceWindowsEpoch().InMicroseconds());
+    remote_issue.set_is_muted(false);
+    switch (type) {
+      case CompromiseType::kLeaked:
+        *remote_issues.mutable_leaked_password_issue() = remote_issue;
+        break;
+      case CompromiseType::kPhished:
+        *remote_issues.mutable_phished_password_issue() = remote_issue;
+        break;
+      case CompromiseType::kWeak:
+        *remote_issues.mutable_weak_password_issue() = remote_issue;
+        break;
+      case CompromiseType::kReused:
+        *remote_issues.mutable_reused_password_issue() = remote_issue;
+        break;
+    }
+  }
+  return remote_issues;
+}
+
+sync_pb::PasswordSpecifics CreateSpecifics(
+    const std::string& origin,
+    const std::string& username_element,
+    const std::string& username_value,
+    const std::string& password_element,
+    const std::string& signon_realm,
+    const std::vector<CompromiseType>& issue_types) {
   sync_pb::EntitySpecifics password_specifics;
   sync_pb::PasswordSpecificsData* password_data =
       password_specifics.mutable_password()
@@ -81,13 +110,25 @@
   password_data->set_username_value(username_value);
   password_data->set_password_element(password_element);
   password_data->set_signon_realm(signon_realm);
+  if (!issue_types.empty())
+    *password_data->mutable_password_issues() =
+        CreateSpecificsIssues(issue_types);
   return password_specifics.password();
 }
 
 sync_pb::PasswordSpecifics CreateSpecificsWithSignonRealm(
     const std::string& signon_realm) {
   return CreateSpecifics("http://www.origin.com", "username_element",
-                         "username_value", "password_element", signon_realm);
+                         "username_value", "password_element", signon_realm,
+                         {});
+}
+
+sync_pb::PasswordSpecifics CreateSpecificsWithSignonRealmAndIssues(
+    const std::string& signon_realm,
+    const std::vector<CompromiseType>& issue_types) {
+  return CreateSpecifics("http://www.origin.com", "username_element",
+                         "username_value", "password_element", signon_realm,
+                         issue_types);
 }
 
 PasswordForm MakePasswordForm(const std::string& signon_realm) {
@@ -108,6 +149,19 @@
   return form;
 }
 
+std::vector<CompromisedCredentials> MakeCompromisedCredentials(
+    const PasswordForm& form,
+    const std::vector<CompromiseType>& types) {
+  std::vector<CompromisedCredentials> issues;
+
+  for (auto type : types) {
+    issues.emplace_back(
+        CompromisedCredentials(form.signon_realm, form.username_value,
+                               base::Time(), type, IsMuted(false)));
+  }
+  return issues;
+}
+
 // A mini database class the supports Add/Update/Remove functionality. It also
 // supports an auto increment primary key that starts from 1. It will be used to
 // empower the MockPasswordStoreSync be forwarding all database calls to an
@@ -358,9 +412,9 @@
 
 TEST_F(PasswordSyncBridgeTest, ShouldComputeClientTagHash) {
   syncer::EntityData data;
-  *data.specifics.mutable_password() =
-      CreateSpecifics("http://www.origin.com", "username_element",
-                      "username_value", "password_element", "signon_realm");
+  *data.specifics.mutable_password() = CreateSpecifics(
+      "http://www.origin.com", "username_element", "username_value",
+      "password_element", "signon_realm", /*issue_types=*/{});
 
   EXPECT_THAT(
       bridge()->GetClientTag(data),
@@ -1078,4 +1132,69 @@
       "PasswordManager.AccountStoreBlocklistedEntriesAfterOptIn", 1, 1);
 }
 
+TEST_F(PasswordSyncBridgeTest,
+       ShouldAddRemoteCompromisedCredentilasUponRemoteCreation) {
+  ON_CALL(mock_processor(), IsTrackingMetadata()).WillByDefault(Return(true));
+  const std::vector<CompromiseType> kIssuesTypes = {CompromiseType::kLeaked,
+                                                    CompromiseType::kWeak};
+  const std::vector<CompromisedCredentials> kExpectedIssues =
+      MakeCompromisedCredentials(MakePasswordForm(kSignonRealm1), kIssuesTypes);
+
+  sync_pb::PasswordSpecifics specifics =
+      CreateSpecificsWithSignonRealmAndIssues(kSignonRealm1, kIssuesTypes);
+
+  testing::InSequence in_sequence;
+  EXPECT_CALL(*mock_password_store_sync(), BeginTransaction());
+  EXPECT_CALL(*mock_password_store_sync(),
+              AddLoginSync(FormHasSignonRealm(kSignonRealm1), _));
+
+  EXPECT_CALL(*mock_password_store_sync(),
+              AddCompromisedCredentialsSync(
+                  UnorderedElementsAreArray(kExpectedIssues)));
+
+  EXPECT_CALL(*mock_password_store_sync(), CommitTransaction());
+
+  syncer::EntityChangeList entity_change_list;
+  entity_change_list.push_back(syncer::EntityChange::CreateAdd(
+      /*storage_key=*/"", SpecificsToEntity(specifics)));
+  base::Optional<syncer::ModelError> error = bridge()->ApplySyncChanges(
+      bridge()->CreateMetadataChangeList(), std::move(entity_change_list));
+  EXPECT_FALSE(error);
+}
+
+TEST_F(PasswordSyncBridgeTest,
+       ShouldAddRemoteCompromisedCredentilasDuringInitialMerge) {
+  ON_CALL(mock_processor(), IsTrackingMetadata()).WillByDefault(Return(true));
+  const std::vector<CompromiseType> kIssuesTypes = {CompromiseType::kLeaked,
+                                                    CompromiseType::kReused};
+  const std::vector<CompromisedCredentials> kIssues =
+      MakeCompromisedCredentials(MakePasswordForm(kSignonRealm1), kIssuesTypes);
+  sync_pb::PasswordSpecifics specifics =
+      CreateSpecificsWithSignonRealmAndIssues(kSignonRealm1, kIssuesTypes);
+
+  // Form will be added to the password store sync. We use sequence since the
+  // order is important. The form itself should be added before we add the
+  // compromised credentials.
+
+  testing::Sequence in_sequence;
+  EXPECT_CALL(*mock_password_store_sync(), BeginTransaction());
+
+  EXPECT_CALL(*mock_password_store_sync(),
+              AddLoginSync(FormHasSignonRealm(kSignonRealm1), _));
+
+  EXPECT_CALL(
+      *mock_password_store_sync(),
+      AddCompromisedCredentialsSync(UnorderedElementsAreArray(kIssues)));
+
+  EXPECT_CALL(*mock_password_store_sync(), CommitTransaction());
+
+  syncer::EntityChangeList entity_change_list;
+  entity_change_list.push_back(syncer::EntityChange::CreateAdd(
+      /*storage_key=*/"", SpecificsToEntity(specifics)));
+
+  base::Optional<syncer::ModelError> error = bridge()->MergeSyncData(
+      bridge()->CreateMetadataChangeList(), std::move(entity_change_list));
+  EXPECT_EQ(error, base::nullopt);
+}
+
 }  // namespace password_manager
diff --git a/components/policy/proto/device_management_backend.proto b/components/policy/proto/device_management_backend.proto
index 4cf5991..34e189b 100644
--- a/components/policy/proto/device_management_backend.proto
+++ b/components/policy/proto/device_management_backend.proto
@@ -3485,6 +3485,17 @@
 
   SHA1 = 1;
   SHA256 = 2;
+
+  // Do not hash the input data - assume it is input to the signature algorithm.
+  // The client supports this starting with M-89.
+  //
+  // For SigningAlgorithm RSA_PKCS1_V1_5, the client will perform PKCS#1 v1.5
+  // padding on |data_to_sign|. |data_to_sign| is expected to already contain a
+  // PKCS#1 DigestInfo prefix - the client will not attempt to add such a
+  // prefix. Also |data_to_sign| must be shorter than (key_size-11) bytes. If no
+  // other key size was specified in the
+  // RequiredClientCertificateFor{User,Device} policy, 2048 bytes is assumed.
+  NO_HASH = 3;
 }
 
 // Signing Algorithm for Client Certificate Provisioning Flow.
diff --git a/components/signin/core/browser/account_reconcilor.cc b/components/signin/core/browser/account_reconcilor.cc
index 7aab3de..c5e4ef4 100644
--- a/components/signin/core/browser/account_reconcilor.cc
+++ b/components/signin/core/browser/account_reconcilor.cc
@@ -236,6 +236,7 @@
 AccountReconcilor::~AccountReconcilor() {
   VLOG(1) << "AccountReconcilor::~AccountReconcilor";
   // Make sure shutdown was called first.
+  DCHECK(WasShutDown());
   DCHECK(!registered_with_identity_manager_);
 }
 
@@ -280,8 +281,10 @@
 
 void AccountReconcilor::Shutdown() {
   VLOG(1) << "AccountReconcilor::Shutdown";
+  was_shut_down_ = true;
   DisableReconcile(false /* logout_all_accounts */);
   delegate_.reset();
+  DCHECK(WasShutDown());
 }
 
 void AccountReconcilor::RegisterWithContentSettings() {
@@ -434,6 +437,9 @@
 }
 
 void AccountReconcilor::StartReconcile() {
+  if (WasShutDown())
+    return;
+
   if (is_reconcile_started_)
     return;
 
@@ -1083,3 +1089,7 @@
   for (auto& observer : observer_list_)
     observer.OnStateChanged(state_);
 }
+
+bool AccountReconcilor::WasShutDown() const {
+  return was_shut_down_;
+}
diff --git a/components/signin/core/browser/account_reconcilor.h b/components/signin/core/browser/account_reconcilor.h
index 5a1a867c..8aeb6f9b 100644
--- a/components/signin/core/browser/account_reconcilor.h
+++ b/components/signin/core/browser/account_reconcilor.h
@@ -221,6 +221,8 @@
                            TableRowTestMergeSession);
   FRIEND_TEST_ALL_PREFIXES(AccountReconcilorTestActiveDirectory,
                            TableRowTestMultilogin);
+  FRIEND_TEST_ALL_PREFIXES(AccountReconcilorTest, ReconcileAfterShutdown);
+  FRIEND_TEST_ALL_PREFIXES(AccountReconcilorTest, UnlockAfterShutdown);
 
   void set_timer_for_testing(std::unique_ptr<base::OneShotTimer> timer);
 
@@ -309,6 +311,9 @@
   // Sets the reconcilor state and calls Observer::OnStateChanged() if needed.
   void SetState(signin_metrics::AccountReconcilorState state);
 
+  // Returns whether Shutdown() was called.
+  bool WasShutDown() const;
+
   std::unique_ptr<signin::AccountReconcilorDelegate> delegate_;
 
   // The IdentityManager associated with this reconcilor.
@@ -373,6 +378,9 @@
 
   signin_metrics::AccountReconcilorState state_;
 
+  // Set to true when Shutdown() is called.
+  bool was_shut_down_ = false;
+
   base::WeakPtrFactory<AccountReconcilor> weak_factory_{this};
 
   DISALLOW_COPY_AND_ASSIGN(AccountReconcilor);
diff --git a/components/signin/core/browser/account_reconcilor_unittest.cc b/components/signin/core/browser/account_reconcilor_unittest.cc
index 83e91923..48c603e 100644
--- a/components/signin/core/browser/account_reconcilor_unittest.cc
+++ b/components/signin/core/browser/account_reconcilor_unittest.cc
@@ -245,7 +245,7 @@
   base::HistogramTester* histogram_tester() { return &histogram_tester_; }
 
   MockAccountReconcilor* GetMockReconcilor();
-  MockAccountReconcilor* GetMockReconcilor(
+  MockAccountReconcilor* CreateMockReconcilor(
       std::unique_ptr<signin::AccountReconcilorDelegate> delegate);
 
   AccountInfo ConnectProfileToAccount(const std::string& email);
@@ -275,7 +275,11 @@
 
   PrefService* pref_service() { return &pref_service_; }
 
-  void DeleteReconcilor() { mock_reconcilor_.reset(); }
+  void DeleteReconcilor() {
+    if (mock_reconcilor_)
+      mock_reconcilor_->Shutdown();
+    mock_reconcilor_.reset();
+  }
 
   network::TestURLLoaderFactory test_url_loader_factory_;
 
@@ -347,12 +351,12 @@
   return mock_reconcilor_.get();
 }
 
-MockAccountReconcilor* AccountReconcilorTest::GetMockReconcilor(
+MockAccountReconcilor* AccountReconcilorTest::CreateMockReconcilor(
     std::unique_ptr<signin::AccountReconcilorDelegate> delegate) {
+  DCHECK(!mock_reconcilor_);
   mock_reconcilor_ = std::make_unique<MockAccountReconcilor>(
       identity_test_env_.identity_manager(), &test_signin_client_,
       std::move(delegate));
-
   return mock_reconcilor_.get();
 }
 
@@ -1809,7 +1813,8 @@
   SetupTokens(GetParam().tokens);
 
   testing::InSequence mock_sequence;
-  MockAccountReconcilor* reconcilor = GetMockReconcilor(
+  DeleteReconcilor();
+  MockAccountReconcilor* reconcilor = CreateMockReconcilor(
       std::make_unique<signin::ActiveDirectoryAccountReconcilorDelegate>());
 
   // Setup expectations.
@@ -2716,7 +2721,8 @@
   AccountInfo account_info = ConnectProfileToAccount("user@gmail.com");
   auto spy_delegate0 = std::make_unique<SpyReconcilorDelegate>();
   SpyReconcilorDelegate* spy_delegate = spy_delegate0.get();
-  AccountReconcilor* reconcilor = GetMockReconcilor(std::move(spy_delegate0));
+  AccountReconcilor* reconcilor =
+      CreateMockReconcilor(std::move(spy_delegate0));
   ASSERT_TRUE(reconcilor);
   auto timer0 = std::make_unique<base::MockOneShotTimer>();
   base::MockOneShotTimer* timer = timer0.get();
@@ -2756,7 +2762,8 @@
       account_info.email, account_info.gaia, &test_url_loader_factory_);
   auto spy_delegate0 = std::make_unique<SpyReconcilorDelegate>();
   SpyReconcilorDelegate* spy_delegate = spy_delegate0.get();
-  AccountReconcilor* reconcilor = GetMockReconcilor(std::move(spy_delegate0));
+  AccountReconcilor* reconcilor =
+      CreateMockReconcilor(std::move(spy_delegate0));
   ASSERT_TRUE(reconcilor);
   auto timer0 = std::make_unique<base::MockOneShotTimer>();
   base::MockOneShotTimer* timer = timer0.get();
@@ -2815,7 +2822,7 @@
   };
 
   MockAccountReconcilor* reconcilor =
-      GetMockReconcilor(std::make_unique<MultiloginLogoutDelegate>());
+      CreateMockReconcilor(std::make_unique<MultiloginLogoutDelegate>());
   signin::SetListAccountsResponseOneAccount("user@gmail.com", "123456",
                                             &test_url_loader_factory_);
 
@@ -2830,3 +2837,33 @@
   EXPECT_FALSE(reconcilor->is_reconcile_started_);
   ASSERT_EQ(signin_metrics::ACCOUNT_RECONCILOR_OK, reconcilor->GetState());
 }
+
+// Reconcilor does not start after being shutdown. Regression test for
+// https://crbug.com/923094
+TEST_F(AccountReconcilorTest, ReconcileAfterShutdown) {
+  AccountReconcilor* reconcilor = GetMockReconcilor();
+  ASSERT_TRUE(reconcilor);
+  EXPECT_FALSE(reconcilor->WasShutDown());
+  reconcilor->Shutdown();
+  EXPECT_TRUE(reconcilor->WasShutDown());
+  reconcilor->StartReconcile();  // This should not crash.
+  EXPECT_FALSE(reconcilor->is_reconcile_started_);
+}
+
+// Reconcilor does not unlock after being shutdown. Regression test for
+// https://crbug.com/923094
+TEST_F(AccountReconcilorTest, UnlockAfterShutdown) {
+  AccountReconcilor* reconcilor = GetMockReconcilor();
+  ASSERT_TRUE(reconcilor);
+  std::unique_ptr<AccountReconcilor::Lock> lock =
+      std::make_unique<AccountReconcilor::Lock>(reconcilor);
+
+  // Reconcile does not start now because of the Lock, but is scheduled to start
+  // when the lock is released.
+  reconcilor->StartReconcile();
+  EXPECT_FALSE(reconcilor->is_reconcile_started_);
+
+  reconcilor->Shutdown();
+  lock.reset();  // This should not crash.
+  EXPECT_FALSE(reconcilor->is_reconcile_started_);
+}
diff --git a/components/signin/ios/browser/account_consistency_service.h b/components/signin/ios/browser/account_consistency_service.h
index 798460a..3722472 100644
--- a/components/signin/ios/browser/account_consistency_service.h
+++ b/components/signin/ios/browser/account_consistency_service.h
@@ -30,7 +30,6 @@
 }
 
 class AccountReconcilor;
-class PrefService;
 
 // Handles actions necessary for keeping the list of Google accounts available
 // on the web and those available on the iOS device from first-party Google apps
@@ -48,21 +47,13 @@
   // Name of the Google authentication cookie.
   static const char kGaiaCookieName[];
 
-  // Name of the preference property that persists the domains that have a
-  // CHROME_CONNECTED cookie set by this service.
-  static const char kDomainsWithCookiePref[];
-
   AccountConsistencyService(
       web::BrowserState* browser_state,
-      PrefService* prefs,
       AccountReconcilor* account_reconcilor,
       scoped_refptr<content_settings::CookieSettings> cookie_settings,
       signin::IdentityManager* identity_manager);
   ~AccountConsistencyService() override;
 
-  // Registers the preferences used by AccountConsistencyService.
-  static void RegisterPrefs(PrefRegistrySimple* registry);
-
   // Sets the handler for |web_state| that reacts on Gaia responses with the
   // X-Chrome-Manage-Accounts header and notifies |delegate|.
   void SetWebStateHandler(web::WebState* web_state,
@@ -99,9 +90,6 @@
  private:
   friend class AccountConsistencyServiceTest;
 
-  // Loads the domains with a CHROME_CONNECTED cookie from the prefs.
-  void LoadFromPrefs();
-
   // KeyedService implementation.
   void Shutdown() override;
 
@@ -110,7 +98,6 @@
 
   // Called when the request to set CHROME_CONNECTED cookie is done.
   void OnChromeConnectedCookieFinished(
-      const std::string& domain,
       net::CookieAccessResult cookie_access_result);
 
   // Called when cookie deletion is completed with the number of cookies that
@@ -118,21 +105,6 @@
   void OnDeleteCookiesFinished(base::OnceClosure callback,
                                uint32_t num_cookies_deleted);
 
-  // Returns whether the CHROME_CONNECTED cookie should be added to |domain|.
-  // If the cookie is not already on |domain|, it will return true. If the
-  // cookie is time constrained, |cookie_refresh_interval| is present, then a
-  // cookie older than |cookie_refresh_interval| returns true.
-  bool ShouldSetChromeConnectedCookieToDomain(
-      const std::string& domain,
-      const base::TimeDelta& cookie_refresh_interval);
-
-  // Enqueues a request to set the CHROME_CONNECTED cookie for the |url|'s
-  // domain. The cookie is set if it is not already on domain or if it is too
-  // old compared to the given |cookie_refresh_interval|.
-  void SetChromeConnectedCookieWithUrls(
-      const std::vector<const GURL>& urls,
-      const base::TimeDelta& cookie_refresh_interval);
-
   // Triggers a Gaia cookie update on the Google domain. Calls
   // |cookies_restored_callback| if the Gaia cookies were restored.
   void TriggerGaiaCookieChangeIfDeleted(
@@ -153,8 +125,6 @@
 
   // Browser state associated with the service.
   web::BrowserState* browser_state_;
-  // Used to update kDomainsWithCookiePref.
-  PrefService* prefs_;
   // Service managing accounts reconciliation, notified of GAIA responses with
   // the X-Chrome-Manage-Accounts header
   AccountReconcilor* account_reconcilor_;
@@ -168,9 +138,6 @@
   // The number of cookie manager requests that are being processed.
   // Used for testing purposes only.
   int64_t active_cookie_manager_requests_for_testing_;
-  // The map between domains where a CHROME_CONNECTED cookie is present and
-  // the time when the cookie was last updated.
-  std::map<std::string, base::Time> last_cookie_update_map_;
 
   // Last time Gaia cookie was updated for the Google domain.
   base::Time last_gaia_cookie_verification_time_;
diff --git a/components/signin/ios/browser/account_consistency_service.mm b/components/signin/ios/browser/account_consistency_service.mm
index 1aaae8f..537a327 100644
--- a/components/signin/ios/browser/account_consistency_service.mm
+++ b/components/signin/ios/browser/account_consistency_service.mm
@@ -16,9 +16,6 @@
 #include "base/strings/sys_string_conversions.h"
 #include "components/content_settings/core/browser/cookie_settings.h"
 #include "components/google/core/common/google_util.h"
-#include "components/prefs/pref_registry_simple.h"
-#include "components/prefs/pref_service.h"
-#include "components/prefs/scoped_user_pref_update.h"
 #include "components/signin/core/browser/account_reconcilor.h"
 #include "components/signin/core/browser/signin_header_helper.h"
 #include "components/signin/ios/browser/features.h"
@@ -42,11 +39,6 @@
 
 namespace {
 
-// The validity of CHROME_CONNECTED cookies is one day maximum as a
-// precaution to ensure that the cookie is regenerated in the case that it
-// is removed or invalidated.
-constexpr base::TimeDelta kDelayThresholdToUpdateChromeConnectedCookie =
-    base::TimeDelta::FromHours(24);
 // The validity of the Gaia cookie on the Google domain is one hour to
 // ensure that Mirror account consistency is respected in light of the more
 // restrictive Intelligent Tracking Prevention (ITP) guidelines in iOS 14
@@ -364,23 +356,17 @@
 
 const char AccountConsistencyService::kGaiaCookieName[] = "SAPISID";
 
-const char AccountConsistencyService::kDomainsWithCookiePref[] =
-    "signin.domains_with_cookie";
-
 AccountConsistencyService::AccountConsistencyService(
     web::BrowserState* browser_state,
-    PrefService* prefs,
     AccountReconcilor* account_reconcilor,
     scoped_refptr<content_settings::CookieSettings> cookie_settings,
     signin::IdentityManager* identity_manager)
     : browser_state_(browser_state),
-      prefs_(prefs),
       account_reconcilor_(account_reconcilor),
       cookie_settings_(cookie_settings),
       identity_manager_(identity_manager),
       active_cookie_manager_requests_for_testing_(0) {
   identity_manager_->AddObserver(this);
-  LoadFromPrefs();
   if (identity_manager_->HasPrimaryAccount()) {
     AddChromeConnectedCookies();
   } else {
@@ -391,12 +377,6 @@
 AccountConsistencyService::~AccountConsistencyService() {
 }
 
-// static
-void AccountConsistencyService::RegisterPrefs(PrefRegistrySimple* registry) {
-  registry->RegisterDictionaryPref(
-      AccountConsistencyService::kDomainsWithCookiePref);
-}
-
 void AccountConsistencyService::SetWebStateHandler(
     web::WebState* web_state,
     id<ManageAccountsDelegate> delegate) {
@@ -470,11 +450,6 @@
 void AccountConsistencyService::RemoveAllChromeConnectedCookies(
     base::OnceClosure callback) {
   DCHECK(!browser_state_->IsOffTheRecord());
-  if (last_cookie_update_map_.empty()) {
-    if (!callback.is_null())
-      std::move(callback).Run();
-    return;
-  }
 
   network::mojom::CookieManager* cookie_manager =
       browser_state_->GetCookieManager();
@@ -488,7 +463,7 @@
       std::move(filter),
       base::BindOnce(&AccountConsistencyService::OnDeleteCookiesFinished,
                      base::Unretained(this), std::move(callback)));
-  ResetInternalState();
+  last_gaia_cookie_verification_time_ = base::Time();
 }
 
 void AccountConsistencyService::OnDeleteCookiesFinished(
@@ -502,41 +477,11 @@
 
 void AccountConsistencyService::SetChromeConnectedCookieWithUrls(
     const std::vector<const GURL>& urls) {
-  SetChromeConnectedCookieWithUrls(
-      urls, kDelayThresholdToUpdateChromeConnectedCookie);
-}
-
-void AccountConsistencyService::SetChromeConnectedCookieWithUrls(
-    const std::vector<const GURL>& urls,
-    const base::TimeDelta& cookie_refresh_interval) {
   for (const GURL& url : urls) {
-    const std::string domain = GetDomainFromUrl(url);
-    if (!ShouldSetChromeConnectedCookieToDomain(domain,
-                                                cookie_refresh_interval)) {
-      continue;
-    }
-    last_cookie_update_map_[domain] = base::Time::Now();
     SetChromeConnectedCookieWithUrl(url);
   }
 }
 
-bool AccountConsistencyService::ShouldSetChromeConnectedCookieToDomain(
-    const std::string& domain,
-    const base::TimeDelta& cookie_refresh_interval) {
-  auto domain_iterator = last_cookie_update_map_.find(domain);
-  bool domain_not_found = domain_iterator == last_cookie_update_map_.end();
-  return domain_not_found || ((base::Time::Now() - domain_iterator->second) >
-                              cookie_refresh_interval);
-}
-
-void AccountConsistencyService::LoadFromPrefs() {
-  const base::DictionaryValue* dict =
-      prefs_->GetDictionary(kDomainsWithCookiePref);
-  for (base::DictionaryValue::Iterator it(*dict); !it.IsAtEnd(); it.Advance()) {
-    last_cookie_update_map_[it.key()] = base::Time();
-  }
-}
-
 void AccountConsistencyService::Shutdown() {
   identity_manager_->RemoveObserver(this);
   web_state_handlers_.clear();
@@ -550,7 +495,6 @@
       signin::AccountConsistencyMethod::kMirror, cookie_settings_.get(),
       signin::PROFILE_MODE_DEFAULT);
   if (cookie_value.empty()) {
-    last_cookie_update_map_.erase(domain);
     return;
   }
 
@@ -582,40 +526,27 @@
       *cookie, url, options,
       base::BindOnce(
           &AccountConsistencyService::OnChromeConnectedCookieFinished,
-          base::Unretained(this), domain));
+          base::Unretained(this)));
 }
 
 void AccountConsistencyService::OnChromeConnectedCookieFinished(
-    const std::string& domain,
     net::CookieAccessResult cookie_access_result) {
   DCHECK(cookie_access_result.status.IsInclude());
-  DictionaryPrefUpdate update(
-      prefs_, AccountConsistencyService::kDomainsWithCookiePref);
-  // Add request.domain to prefs, use |true| as a dummy value (that is
-  // never used), as the dictionary is used as a set.
-  update->SetKey(domain, base::Value(true));
   --active_cookie_manager_requests_for_testing_;
 }
 
 void AccountConsistencyService::AddChromeConnectedCookies() {
   DCHECK(!browser_state_->IsOffTheRecord());
-  // These cookie requests are preventive and not a strong signal (unlike
-  // navigation to a domain). Don't force update the old cookies in this case.
-  SetChromeConnectedCookieWithUrls({GURL(kGoogleUrl), GURL(kYoutubeUrl)},
-                                   base::TimeDelta::Max());
-}
-
-void AccountConsistencyService::ResetInternalState() {
-  last_cookie_update_map_.clear();
-  last_gaia_cookie_verification_time_ = base::Time();
-  base::DictionaryValue dict;
-  prefs_->Set(kDomainsWithCookiePref, dict);
+  // These cookie requests are preventive. Chrome cannot be sure that
+  // CHROME_CONNECTED cookies are set on google.com and youtube.com domains due
+  // to ITP restrictions.
+  SetChromeConnectedCookieWithUrls({GURL(kGoogleUrl), GURL(kYoutubeUrl)});
 }
 
 void AccountConsistencyService::OnBrowsingDataRemoved() {
   // CHROME_CONNECTED cookies have been removed, update internal state
   // accordingly.
-  ResetInternalState();
+  last_gaia_cookie_verification_time_ = base::Time();
 
   // SAPISID cookie has been removed, notify the GCMS.
   // TODO(https://crbug.com/930582) : Remove the need to expose this method
diff --git a/components/signin/ios/browser/account_consistency_service_unittest.mm b/components/signin/ios/browser/account_consistency_service_unittest.mm
index d984bb4..385562e 100644
--- a/components/signin/ios/browser/account_consistency_service_unittest.mm
+++ b/components/signin/ios/browser/account_consistency_service_unittest.mm
@@ -142,7 +142,7 @@
  protected:
   void SetUp() override {
     PlatformTest::SetUp();
-    AccountConsistencyService::RegisterPrefs(prefs_.registry());
+
     content_settings::CookieSettings::RegisterProfilePrefs(prefs_.registry());
     HostContentSettingsMap::RegisterProfilePrefs(prefs_.registry());
 
@@ -168,6 +168,7 @@
 
     account_consistency_service_->Shutdown();
     settings_map_->ShutdownOnUIThread();
+    account_reconcilor_->Shutdown();
     identity_test_env_.reset();
     PlatformTest::TearDown();
   }
@@ -177,7 +178,7 @@
       account_consistency_service_->Shutdown();
     }
     account_consistency_service_.reset(new AccountConsistencyService(
-        &browser_state_, &prefs_, account_reconcilor_.get(), cookie_settings_,
+        &browser_state_, account_reconcilor_.get(), cookie_settings_,
         identity_test_env_->identity_manager()));
   }
 
@@ -215,14 +216,6 @@
                        /*domain=*/std::string()));
   }
 
-  void CheckDomainHasChromeConnectedCookieWithUpdateTime(
-      const std::string& domain,
-      base::Time time) {
-    CheckDomainHasChromeConnectedCookie(domain);
-    EXPECT_EQ(time,
-              account_consistency_service_->last_cookie_update_map_[domain]);
-  }
-
   // Verifies the time that the Gaia cookie was last updated for google.com.
   void CheckGaiaCookieWithUpdateTime(base::Time time) {
     EXPECT_EQ(
@@ -348,12 +341,6 @@
   SignIn();
   CheckDomainHasChromeConnectedCookie(kGoogleDomain);
   CheckDomainHasChromeConnectedCookie(kYoutubeDomain);
-
-  const base::DictionaryValue* dict =
-      prefs_.GetDictionary(AccountConsistencyService::kDomainsWithCookiePref);
-  EXPECT_EQ(2u, dict->size());
-  EXPECT_TRUE(dict->GetBooleanWithoutPathExpansion(kGoogleDomain, nullptr));
-  EXPECT_TRUE(dict->GetBooleanWithoutPathExpansion(kYoutubeDomain, nullptr));
 }
 
 // Tests that cookies that are added during SignIn and subsequent navigations
@@ -611,10 +598,6 @@
   SignIn();
   CheckDomainHasChromeConnectedCookie(kGoogleDomain);
   CheckDomainHasChromeConnectedCookie(kYoutubeDomain);
-  EXPECT_EQ(
-      2u,
-      prefs_.GetDictionary(AccountConsistencyService::kDomainsWithCookiePref)
-          ->size());
 
   // Sets Response to get IdentityManager::Observer::OnAccountsInCookieUpdated
   // through GaiaCookieManagerService::OnCookieChange.
@@ -627,57 +610,34 @@
   // AccountsCookieMutator::ForceTriggerOnCookieChange and finally
   // IdentityManager::Observer::OnAccountsInCookieUpdated is called.
   account_consistency_service_->OnBrowsingDataRemoved();
-  EXPECT_EQ(
-      0u,
-      prefs_.GetDictionary(AccountConsistencyService::kDomainsWithCookiePref)
-          ->size());
+
   run_loop.Run();
 
   // AccountConsistency service is supposed to rebuild the CHROME_CONNECTED
   // cookies when browsing data is removed.
   CheckDomainHasChromeConnectedCookie(kGoogleDomain);
   CheckDomainHasChromeConnectedCookie(kYoutubeDomain);
-  EXPECT_EQ(
-      2u,
-      prefs_.GetDictionary(AccountConsistencyService::kDomainsWithCookiePref)
-          ->size());
 }
 
-// Tests that the CHROME_CONNECTED cookie is set on Google-associated domains,
-// but not on Google domains if the account consistency service runs before the
-// scheduled cookie update time.
+// Tests that google.com domain cookies can be regenerated after an external
+// source removes these cookies.
 TEST_F(AccountConsistencyServiceTest,
-       SetChromeConnectedCookieBeforeUpdateTime) {
+       AddChromeConnectedCookiesOnCookiesRemoved) {
   SignIn();
+  CheckDomainHasChromeConnectedCookie(kGoogleDomain);
 
-  id delegate =
-      [OCMockObject mockForProtocol:@protocol(ManageAccountsDelegate)];
-  NSDictionary* headers = [NSDictionary dictionary];
-
-  // HTTP response URL is eligible for Mirror (the test does not use google.com
-  // since the CHROME_CONNECTED cookie is generated for it by default.
-  NSHTTPURLResponse* response = [[NSHTTPURLResponse alloc]
-       initWithURL:[NSURL URLWithString:@"https://youtube.com"]
-        statusCode:200
-       HTTPVersion:@"HTTP/1.1"
-      headerFields:headers];
-
-  SimulateNavigateToURL(response, delegate);
   SimulateExternalSourceRemovesAllGoogleDomainCookies();
-
-  // Advance clock before 24-hour CHROME_CONNECTED update time.
-  task_environment_.FastForwardBy(base::TimeDelta::FromHours(2));
-  SimulateNavigateToURL(response, delegate);
-
-  CheckDomainHasChromeConnectedCookieWithUpdateTime(
-      kYoutubeDomain, base::Time::Now() - base::TimeDelta::FromHours(2));
   CheckNoChromeConnectedCookieForDomain(kGoogleDomain);
+
+  // Forcibly rebuild the CHROME_CONNECTED cookies.
+  account_consistency_service_->AddChromeConnectedCookies();
+
+  CheckDomainHasChromeConnectedCookie(kGoogleDomain);
 }
 
 // Tests that the CHROME_CONNECTED cookie is set on Google and Google-associated
-// domains when the
-// account consistency service runs at the scheduled cookie update time.
-TEST_F(AccountConsistencyServiceTest, SetChromeConnectedCookieAtUpdateTime) {
+// domains when the account consistency service runs.
+TEST_F(AccountConsistencyServiceTest, SetChromeConnectedCookie) {
   SignIn();
 
   id delegate =
@@ -695,14 +655,10 @@
   SimulateNavigateToURL(response, delegate);
   SimulateExternalSourceRemovesAllGoogleDomainCookies();
 
-  // Advance clock past 24-hour CHROME_CONNECTED update time.
-  task_environment_.FastForwardBy(base::TimeDelta::FromDays(2));
   SimulateNavigateToURL(response, delegate);
 
-  CheckDomainHasChromeConnectedCookieWithUpdateTime(kGoogleDomain,
-                                                    base::Time::Now());
-  CheckDomainHasChromeConnectedCookieWithUpdateTime(kYoutubeDomain,
-                                                    base::Time::Now());
+  CheckDomainHasChromeConnectedCookie(kGoogleDomain);
+  CheckDomainHasChromeConnectedCookie(kYoutubeDomain);
 }
 
 // Tests that the GAIA cookie update time is not updated before the scheduled
diff --git a/components/sync/invalidations/fcm_handler.cc b/components/sync/invalidations/fcm_handler.cc
index 32f9b10..31268e5 100644
--- a/components/sync/invalidations/fcm_handler.cc
+++ b/components/sync/invalidations/fcm_handler.cc
@@ -42,12 +42,16 @@
   DCHECK(!IsListening());
   DCHECK(base::FeatureList::IsEnabled(switches::kUseSyncInvalidations));
   gcm_driver_->AddAppHandler(app_id_, this);
+  waiting_for_token_ = true;
   StartTokenFetch(base::BindOnce(&FCMHandler::DidRetrieveToken,
                                  weak_ptr_factory_.GetWeakPtr()));
 }
 
 void FCMHandler::StopListening() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  // StopListening() may be called after StartListening() right away and
+  // DidRetrieveToken() won't be called.
+  waiting_for_token_ = false;
   if (IsListening()) {
     gcm_driver_->RemoveAppHandler(app_id_);
     token_validation_timer_.AbandonAndStop();
@@ -72,6 +76,11 @@
   return fcm_registration_token_;
 }
 
+bool FCMHandler::IsWaitingForToken() const {
+  DCHECK(!waiting_for_token_ || IsListening());
+  return waiting_for_token_;
+}
+
 void FCMHandler::ShutdownHandler() {
   // Shutdown() should come before and it removes us from the list of app
   // handlers of gcm::GCMDriver so this shouldn't ever been called.
@@ -141,6 +150,8 @@
 void FCMHandler::DidRetrieveToken(const std::string& subscription_token,
                                   instance_id::InstanceID::Result result) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  waiting_for_token_ = false;
+
   if (!IsListening()) {
     // After we requested the token, |StopListening| has been called. Thus,
     // ignore the token.
diff --git a/components/sync/invalidations/fcm_handler.h b/components/sync/invalidations/fcm_handler.h
index 1786d9e..1894b6c 100644
--- a/components/sync/invalidations/fcm_handler.h
+++ b/components/sync/invalidations/fcm_handler.h
@@ -71,6 +71,10 @@
   // been received yet, or if the handler has stopped listening permanently.
   const std::string& GetFCMRegistrationToken() const;
 
+  // Returns true if an FCM registration token has never been retreived after
+  // the last StartListening() call.
+  bool IsWaitingForToken() const;
+
   // GCMAppHandler overrides.
   void ShutdownHandler() override;
   void OnStoreReset() override;
@@ -105,6 +109,8 @@
 
   base::OneShotTimer token_validation_timer_;
 
+  bool waiting_for_token_ = false;
+
   // Contains all listeners to notify about each incoming message in OnMessage
   // method.
   base::ObserverList<InvalidationsListener,
diff --git a/components/sync/invalidations/fcm_handler_unittest.cc b/components/sync/invalidations/fcm_handler_unittest.cc
index 3a305e8..c02505af 100644
--- a/components/sync/invalidations/fcm_handler_unittest.cc
+++ b/components/sync/invalidations/fcm_handler_unittest.cc
@@ -132,13 +132,16 @@
 TEST_F(FCMHandlerTest, ShouldReturnValidToken) {
   // Check that the handler gets the token through GetToken.
   EXPECT_CALL(mock_instance_id_, GetToken)
-      .WillOnce(WithArg<4>(Invoke([](InstanceID::GetTokenCallback callback) {
-        std::move(callback).Run("token", InstanceID::Result::SUCCESS);
-      })));
+      .WillOnce(
+          WithArg<4>(Invoke([this](InstanceID::GetTokenCallback callback) {
+            EXPECT_TRUE(fcm_handler_.IsWaitingForToken());
+            std::move(callback).Run("token", InstanceID::Result::SUCCESS);
+          })));
 
   fcm_handler_.StartListening();
 
   EXPECT_EQ("token", fcm_handler_.GetFCMRegistrationToken());
+  EXPECT_FALSE(fcm_handler_.IsWaitingForToken());
 }
 
 TEST_F(FCMHandlerTest, ShouldPropagatePayloadToListener) {
diff --git a/components/sync/invalidations/interested_data_types_manager.cc b/components/sync/invalidations/interested_data_types_manager.cc
index d83d8152..4d30ce5 100644
--- a/components/sync/invalidations/interested_data_types_manager.cc
+++ b/components/sync/invalidations/interested_data_types_manager.cc
@@ -37,6 +37,11 @@
     interested_data_types_handler_->OnInterestedDataTypesChanged(
         base::BindOnce(std::move(callback), new_data_types));
   }
+  initialized_ = true;
+}
+
+bool InterestedDataTypesManager::IsInitialized() const {
+  return initialized_;
 }
 
 }  // namespace syncer
diff --git a/components/sync/invalidations/interested_data_types_manager.h b/components/sync/invalidations/interested_data_types_manager.h
index a0796f1..864f5dd5 100644
--- a/components/sync/invalidations/interested_data_types_manager.h
+++ b/components/sync/invalidations/interested_data_types_manager.h
@@ -24,15 +24,24 @@
   // unregister any existing handler. There can be at most one handler.
   void SetInterestedDataTypesHandler(InterestedDataTypesHandler* handler);
 
-  // Get or set the interested data types.
+  // Get the interested data types.
   const ModelTypeSet& GetInterestedDataTypes() const;
+
+  // Set interested data types. The first call of the method initializes this
+  // object.
   void SetInterestedDataTypes(
       const ModelTypeSet& data_types,
       SyncInvalidationsService::InterestedDataTypesAppliedCallback callback);
 
+  // Returns true if SetInterestedDataTypes() has been called at least once.
+  // Before that this object is considered to be uninitialized.
+  bool IsInitialized() const;
+
  private:
   InterestedDataTypesHandler* interested_data_types_handler_ = nullptr;
 
+  bool initialized_ = false;
+
   ModelTypeSet data_types_;
 };
 
diff --git a/components/sync/invalidations/interested_data_types_manager_unittest.cc b/components/sync/invalidations/interested_data_types_manager_unittest.cc
index 6e890c8..4d2aef1e 100644
--- a/components/sync/invalidations/interested_data_types_manager_unittest.cc
+++ b/components/sync/invalidations/interested_data_types_manager_unittest.cc
@@ -47,5 +47,16 @@
   manager_.SetInterestedDataTypesHandler(nullptr);
 }
 
+TEST_F(InterestedDataTypesManagerTest,
+       ShouldInitializeOnFirstSetInterestedDataTypes) {
+  EXPECT_FALSE(manager_.IsInitialized());
+  manager_.SetInterestedDataTypes(ModelTypeSet(BOOKMARKS, PREFERENCES),
+                                  base::DoNothing());
+  EXPECT_TRUE(manager_.IsInitialized());
+  manager_.SetInterestedDataTypes(ModelTypeSet(BOOKMARKS, PREFERENCES, NIGORI),
+                                  base::DoNothing());
+  EXPECT_TRUE(manager_.IsInitialized());
+}
+
 }  // namespace
 }  // namespace syncer
diff --git a/components/sync/invalidations/mock_sync_invalidations_service.h b/components/sync/invalidations/mock_sync_invalidations_service.h
index 742f581f..406cb05 100644
--- a/components/sync/invalidations/mock_sync_invalidations_service.h
+++ b/components/sync/invalidations/mock_sync_invalidations_service.h
@@ -26,11 +26,17 @@
   MOCK_METHOD(void,
               RemoveTokenObserver,
               (FCMRegistrationTokenObserver * observer));
-  MOCK_METHOD(const std::string&, GetFCMRegistrationToken, (), (const));
+  MOCK_METHOD(base::Optional<std::string>,
+              GetFCMRegistrationToken,
+              (),
+              (const));
   MOCK_METHOD(void,
               SetInterestedDataTypesHandler,
               (InterestedDataTypesHandler * handler));
-  MOCK_METHOD(const ModelTypeSet&, GetInterestedDataTypes, (), (const));
+  MOCK_METHOD(base::Optional<ModelTypeSet>,
+              GetInterestedDataTypes,
+              (),
+              (const));
   MOCK_METHOD(void,
               SetInterestedDataTypes,
               (const ModelTypeSet& data_types,
diff --git a/components/sync/invalidations/sync_invalidations_service.h b/components/sync/invalidations/sync_invalidations_service.h
index 730565f..58b8619e 100644
--- a/components/sync/invalidations/sync_invalidations_service.h
+++ b/components/sync/invalidations/sync_invalidations_service.h
@@ -8,6 +8,7 @@
 #include <string>
 
 #include "base/callback.h"
+#include "base/optional.h"
 #include "components/keyed_service/core/keyed_service.h"
 #include "components/sync/base/model_type.h"
 
@@ -41,9 +42,10 @@
   virtual void AddTokenObserver(FCMRegistrationTokenObserver* observer) = 0;
   virtual void RemoveTokenObserver(FCMRegistrationTokenObserver* observer) = 0;
 
-  // Used to get an obtained FCM token. Returns empty string if it hasn't been
-  // received yet, or if the device has stopped listening to invalidations.
-  virtual const std::string& GetFCMRegistrationToken() const = 0;
+  // Used to get an obtained FCM token. base::nullopt is returned if the token
+  // has been requested but hasn't been received yet. Returns an empty string if
+  // the device is not listening to invalidations.
+  virtual base::Optional<std::string> GetFCMRegistrationToken() const = 0;
 
   // Set the interested data types change handler. |handler| can be nullptr to
   // unregister any existing handler. There can be at most one handler.
@@ -51,7 +53,9 @@
       InterestedDataTypesHandler* handler) = 0;
 
   // Get or set for which data types should the device receive invalidations.
-  virtual const ModelTypeSet& GetInterestedDataTypes() const = 0;
+  // GetInterestedDataTypes() will return base::nullptr until
+  // SetInterestedDataTypes() has been called at least once.
+  virtual base::Optional<ModelTypeSet> GetInterestedDataTypes() const = 0;
   virtual void SetInterestedDataTypes(
       const ModelTypeSet& data_types,
       InterestedDataTypesAppliedCallback callback) = 0;
diff --git a/components/sync/invalidations/sync_invalidations_service_impl.cc b/components/sync/invalidations/sync_invalidations_service_impl.cc
index dc6c526..dd83d0f2 100644
--- a/components/sync/invalidations/sync_invalidations_service_impl.cc
+++ b/components/sync/invalidations/sync_invalidations_service_impl.cc
@@ -61,8 +61,11 @@
   fcm_handler_->RemoveTokenObserver(observer);
 }
 
-const std::string& SyncInvalidationsServiceImpl::GetFCMRegistrationToken()
-    const {
+base::Optional<std::string>
+SyncInvalidationsServiceImpl::GetFCMRegistrationToken() const {
+  if (fcm_handler_->IsWaitingForToken()) {
+    return base::nullopt;
+  }
   return fcm_handler_->GetFCMRegistrationToken();
 }
 
@@ -71,9 +74,12 @@
   data_types_manager_.SetInterestedDataTypesHandler(handler);
 }
 
-const ModelTypeSet& SyncInvalidationsServiceImpl::GetInterestedDataTypes()
-    const {
-  return data_types_manager_.GetInterestedDataTypes();
+base::Optional<ModelTypeSet>
+SyncInvalidationsServiceImpl::GetInterestedDataTypes() const {
+  if (data_types_manager_.IsInitialized()) {
+    return data_types_manager_.GetInterestedDataTypes();
+  }
+  return base::nullopt;
 }
 
 void SyncInvalidationsServiceImpl::SetInterestedDataTypes(
diff --git a/components/sync/invalidations/sync_invalidations_service_impl.h b/components/sync/invalidations/sync_invalidations_service_impl.h
index 9cdc52e..ad8722cf 100644
--- a/components/sync/invalidations/sync_invalidations_service_impl.h
+++ b/components/sync/invalidations/sync_invalidations_service_impl.h
@@ -37,10 +37,10 @@
   void RemoveListener(InvalidationsListener* listener) override;
   void AddTokenObserver(FCMRegistrationTokenObserver* observer) override;
   void RemoveTokenObserver(FCMRegistrationTokenObserver* observer) override;
-  const std::string& GetFCMRegistrationToken() const override;
+  base::Optional<std::string> GetFCMRegistrationToken() const override;
   void SetInterestedDataTypesHandler(
       InterestedDataTypesHandler* handler) override;
-  const ModelTypeSet& GetInterestedDataTypes() const override;
+  base::Optional<ModelTypeSet> GetInterestedDataTypes() const override;
   void SetInterestedDataTypes(
       const ModelTypeSet& data_types,
       InterestedDataTypesAppliedCallback callback) override;
diff --git a/components/sync_device_info/device_info_sync_bridge.cc b/components/sync_device_info/device_info_sync_bridge.cc
index de1454a..4be3d8d 100644
--- a/components/sync_device_info/device_info_sync_bridge.cc
+++ b/components/sync_device_info/device_info_sync_bridge.cc
@@ -199,6 +199,22 @@
   return base::nullopt;
 }
 
+ModelTypeSet ExtractInterestedDataTypes(const DeviceInfoSpecifics& specifics) {
+  ModelTypeSet interested_data_types;
+  for (int data_type_id :
+       specifics.invalidation_fields().interested_data_type_ids()) {
+    const ModelType model_type =
+        GetModelTypeFromSpecificsFieldNumber(data_type_id);
+
+    // This is possible if the browser has been updated and a data type has been
+    // removed.
+    if (model_type != ModelType::UNSPECIFIED) {
+      interested_data_types.Put(model_type);
+    }
+  }
+  return interested_data_types;
+}
+
 }  // namespace
 
 DeviceInfoSyncBridge::DeviceInfoSyncBridge(
@@ -226,15 +242,22 @@
   return local_device_info_provider_.get();
 }
 
-void DeviceInfoSyncBridge::RefreshLocalDeviceInfo(base::OnceClosure callback) {
-  if (!callback.is_null()) {
-    device_info_synced_callback_list_.push_back(std::move(callback));
-  }
-
+void DeviceInfoSyncBridge::RefreshLocalDeviceInfoIfNeeded(
+    base::OnceClosure callback) {
   // Device info cannot be synced if the provider is not initialized. When it
   // gets initialized, local device info will be sent.
-  if (local_device_info_provider_->GetLocalDeviceInfo()) {
-    SendLocalData();
+  if (!local_device_info_provider_->GetLocalDeviceInfo()) {
+    if (!callback.is_null()) {
+      device_info_synced_callback_list_.push_back(std::move(callback));
+    }
+    return;
+  }
+
+  if (ReconcileLocalAndStored()) {
+    // The device info has been changed.
+    if (!callback.is_null()) {
+      device_info_synced_callback_list_.push_back(std::move(callback));
+    }
   }
 }
 
@@ -275,7 +298,8 @@
   local_device_info_provider_->Initialize(
       local_cache_guid_, GetLocalClientName(),
       local_device_name_info_.manufacturer_name,
-      local_device_name_info_.model_name);
+      local_device_name_info_.model_name,
+      /*last_fcm_registration_token=*/std::string(), ModelTypeSet());
 
   std::unique_ptr<WriteBatch> batch = store_->CreateWriteBatch();
   for (const auto& change : entity_data) {
@@ -571,10 +595,19 @@
   // If sync already enabled (usual case without data corruption), we can
   // initialize the provider immediately.
   local_cache_guid_ = local_cache_guid_in_metadata;
+
+  // Get stored sync invalidation fields to initialize local device info. This
+  // is needed to prevent an unnecessary DeviceInfo commit on browser startup
+  // when the SyncInvalidationsService is not initialized.
+  auto iter = all_data_.find(local_cache_guid_);
+  DCHECK(iter != all_data_.end());
+
   local_device_info_provider_->Initialize(
       local_cache_guid_, GetLocalClientName(),
       local_device_name_info_.manufacturer_name,
-      local_device_name_info_.model_name);
+      local_device_name_info_.model_name,
+      iter->second->invalidation_fields().instance_id_token(),
+      ExtractInterestedDataTypes(*iter->second));
 
   // This probably isn't strictly needed, but in case the cache_guid has changed
   // we save the new one to prefs.
@@ -590,7 +623,7 @@
   }
 }
 
-void DeviceInfoSyncBridge::ReconcileLocalAndStored() {
+bool DeviceInfoSyncBridge::ReconcileLocalAndStored() {
   const DeviceInfo* current_info =
       local_device_info_provider_->GetLocalDeviceInfo();
   DCHECK(current_info);
@@ -600,16 +633,24 @@
 
   // Convert to DeviceInfo for Equals function.
   if (current_info->Equals(*SpecificsToModel(*iter->second))) {
+    if (pulse_timer_.IsRunning()) {
+      // No need to update the |pulse_timer| since nothing has changed.
+      return false;
+    }
+
     const TimeDelta pulse_delay(DeviceInfoUtil::CalculatePulseDelay(
         GetLastUpdateTime(*iter->second), Time::Now()));
     if (!pulse_delay.is_zero()) {
       pulse_timer_.Start(FROM_HERE, pulse_delay,
                          base::BindOnce(&DeviceInfoSyncBridge::SendLocalData,
                                         base::Unretained(this)));
-      return;
+      return false;
     }
   }
+
+  // Either the local data was updated, or it's time for a pulse update.
   SendLocalData();
+  return true;
 }
 
 void DeviceInfoSyncBridge::SendLocalData() {
diff --git a/components/sync_device_info/device_info_sync_bridge.h b/components/sync_device_info/device_info_sync_bridge.h
index a89b6c3..1211d682 100644
--- a/components/sync_device_info/device_info_sync_bridge.h
+++ b/components/sync_device_info/device_info_sync_bridge.h
@@ -52,8 +52,10 @@
   // change. Used when the caller knows a property of local device info has
   // changed (e.g. SharingInfo), and must be sync-ed to other devices as soon as
   // possible, without waiting for the periodic commits. |callback| will be
-  // called when device info is synced.
-  void RefreshLocalDeviceInfo(base::OnceClosure callback);
+  // called when device info is synced. The device info will be compared with
+  // the local copy. If the data has been updated, then it will be committed.
+  // Otherwise nothing happens and the |callback| will be never called.
+  void RefreshLocalDeviceInfoIfNeeded(base::OnceClosure callback);
 
   // ModelTypeSyncBridge implementation.
   void OnSyncStarting(const DataTypeActivationRequest& request) override;
@@ -113,14 +115,16 @@
       LocalDeviceNameInfo local_device_name_info);
   void OnReadAllData(std::unique_ptr<ClientIdToSpecifics> all_data,
                      const base::Optional<syncer::ModelError>& error);
+  void OnSyncInvalidationsInitialized();
   void OnReadAllMetadata(const base::Optional<syncer::ModelError>& error,
                          std::unique_ptr<MetadataBatch> metadata_batch);
   void OnCommit(const base::Optional<syncer::ModelError>& error);
 
   // Performs reconciliation between the locally provided device info and the
   // stored device info data. If the sets of data differ, then we consider this
-  // a local change and we send it to the processor.
-  void ReconcileLocalAndStored();
+  // a local change and we send it to the processor. Returns true if the local
+  // data has been changed and sent to the processor.
+  bool ReconcileLocalAndStored();
 
   // Stores the updated version of the local copy of device info in durable
   // storage, in memory, and informs sync of the change. Must not be called
diff --git a/components/sync_device_info/device_info_sync_bridge_unittest.cc b/components/sync_device_info/device_info_sync_bridge_unittest.cc
index 028b64c..67e7bcd 100644
--- a/components/sync_device_info/device_info_sync_bridge_unittest.cc
+++ b/components/sync_device_info/device_info_sync_bridge_unittest.cc
@@ -44,6 +44,7 @@
 using testing::IsEmpty;
 using testing::IsNull;
 using testing::Matcher;
+using testing::NiceMock;
 using testing::NotNull;
 using testing::Pair;
 using testing::Return;
@@ -338,7 +339,9 @@
   void Initialize(const std::string& cache_guid,
                   const std::string& session_name,
                   const std::string& manufacturer_name,
-                  const std::string& model_name) override {
+                  const std::string& model_name,
+                  const std::string& last_fcm_registration_token,
+                  const ModelTypeSet& last_interested_data_types) override {
     std::set<sync_pb::SharingSpecificFields::EnabledFeatures>
         sharing_enabled_features{SharingEnabledFeaturesForSuffix(kLocalSuffix)};
     local_device_info_ = std::make_unique<DeviceInfo>(
@@ -356,8 +359,7 @@
              SharingSenderIdP256dhForSuffix(kLocalSuffix),
              SharingSenderIdAuthSecretForSuffix(kLocalSuffix)},
             sharing_enabled_features),
-        SyncInvalidationsInstanceIdTokenForSuffix(kLocalSuffix),
-        SyncInvalidationsInterestedDataTypes());
+        last_fcm_registration_token, last_interested_data_types);
   }
 
   void Clear() override { local_device_info_.reset(); }
@@ -372,6 +374,15 @@
   }
 
   const DeviceInfo* GetLocalDeviceInfo() const override {
+    if (local_device_info_) {
+      if (fcm_registration_token_) {
+        local_device_info_->set_fcm_registration_token(
+            *fcm_registration_token_);
+      }
+      if (interested_data_types_) {
+        local_device_info_->set_interested_data_types(*interested_data_types_);
+      }
+    }
     return local_device_info_.get();
   }
 
@@ -381,8 +392,18 @@
     return {};
   }
 
+  void UpdateFCMRegistrationToken(const std::string& fcm_registration_token) {
+    fcm_registration_token_ = fcm_registration_token;
+  }
+
+  void UpdateInterestedDataTypes(const ModelTypeSet& data_types) {
+    interested_data_types_ = data_types;
+  }
+
  private:
   std::unique_ptr<DeviceInfo> local_device_info_;
+  base::Optional<std::string> fcm_registration_token_;
+  base::Optional<ModelTypeSet> interested_data_types_;
 
   DISALLOW_COPY_AND_ASSIGN(TestLocalDeviceInfoProvider);
 };  // namespace
@@ -425,8 +446,12 @@
 
   // Initialized the bridge based on the current local device and store.
   void InitializeBridge() {
+    auto local_device_info_provider =
+        std::make_unique<TestLocalDeviceInfoProvider>();
+    // Store a pointer to be able to update the local device info fields.
+    local_device_info_provider_ = local_device_info_provider.get();
     bridge_ = std::make_unique<DeviceInfoSyncBridge>(
-        std::make_unique<TestLocalDeviceInfoProvider>(),
+        std::move(local_device_info_provider),
         ModelTypeStoreTestUtil::FactoryForForwardingStore(store_.get()),
         mock_processor_.CreateForwardingProcessor(),
         std::make_unique<DeviceInfoPrefs>(&pref_service_, &clock_));
@@ -440,10 +465,10 @@
     task_environment_.RunUntilIdle();
   }
 
-  // Creates the bridge with no prior data on the store, and mimics sync being
-  // enabled by the user with no remote data.
-  void InitializeAndMergeInitialData(SyncMode sync_mode) {
-    InitializeAndPump();
+  // Mimics sync being enabled by the user with no remote data. Must be called
+  // when the bridge is initialized.
+  void EnableSyncAndMergeInitialData(SyncMode sync_mode) {
+    DCHECK(bridge_);
     bridge()->OnSyncStarting(TestDataTypeActivationRequest(sync_mode));
 
     std::unique_ptr<MetadataChangeList> metadata_change_list =
@@ -455,6 +480,13 @@
                             EntityChangeList());
   }
 
+  // Creates the bridge with no prior data on the store, and mimics sync being
+  // enabled by the user with no remote data.
+  void InitializeAndMergeInitialData(SyncMode sync_mode) {
+    InitializeAndPump();
+    EnableSyncAndMergeInitialData(sync_mode);
+  }
+
   // Allows access to the store before that will ultimately be used to
   // initialize the bridge.
   ModelTypeStore* store() {
@@ -465,8 +497,8 @@
   // Get the number of times the bridge notifies observers of changes.
   int change_count() { return change_count_; }
 
-  LocalDeviceInfoProvider* local_device() {
-    return bridge_->GetLocalDeviceInfoProvider();
+  TestLocalDeviceInfoProvider* local_device() {
+    return local_device_info_provider_;
   }
 
   // Allows access to the bridge after InitializeBridge() is called.
@@ -493,7 +525,7 @@
   void ForcePulse() { bridge()->ForcePulseForTest(); }
 
   void RefreshLocalDeviceInfo() {
-    bridge()->RefreshLocalDeviceInfo(base::DoNothing());
+    bridge()->RefreshLocalDeviceInfoIfNeeded(base::DoNothing());
   }
 
   void CommitToStoreAndWait(std::unique_ptr<WriteBatch> batch) {
@@ -602,7 +634,7 @@
   // In memory model type store needs to be able to post tasks.
   base::test::TaskEnvironment task_environment_;
 
-  testing::NiceMock<MockModelTypeChangeProcessor> mock_processor_;
+  NiceMock<MockModelTypeChangeProcessor> mock_processor_;
 
   // Holds the store.
   const std::unique_ptr<ModelTypeStore> store_;
@@ -611,9 +643,12 @@
   const LocalDeviceNameInfo local_device_name_info_;
 
   TestingPrefServiceSimple pref_service_;
+
   // Not initialized immediately (upon test's constructor). This allows each
   // test case to modify the dependencies the bridge will be constructed with.
   std::unique_ptr<DeviceInfoSyncBridge> bridge_;
+
+  TestLocalDeviceInfoProvider* local_device_info_provider_ = nullptr;
 };
 
 TEST_F(DeviceInfoSyncBridgeTest, BeforeSyncEnabled) {
@@ -1193,8 +1228,16 @@
   EXPECT_EQ(1, change_count());
   testing::Mock::VerifyAndClearExpectations(processor());
 
+  // Check that the device is not updated if nothing has been changed.
+  RefreshLocalDeviceInfo();
+  EXPECT_EQ(1, change_count());
+
   // Ensure |last_updated| is about now, plus or minus a little bit.
   EXPECT_CALL(*processor(), Put(_, HasSpecifics(HasLastUpdatedAboutNow()), _));
+  ASSERT_THAT(local_device()->GetLocalDeviceInfo()->fcm_registration_token(),
+              IsEmpty());
+  local_device()->UpdateFCMRegistrationToken(
+      SyncInvalidationsInstanceIdTokenForSuffix(kLocalSuffix));
   RefreshLocalDeviceInfo();
   EXPECT_EQ(2, change_count());
 }
@@ -1292,14 +1335,23 @@
                   HasSpecifics(
                       AllOf(HasInstanceIdToken(), HasAnyInterestedDataTypes())),
                   _));
-  InitializeAndMergeInitialData(SyncMode::kFull);
+  InitializeAndPump();
+  local_device()->UpdateFCMRegistrationToken(
+      SyncInvalidationsInstanceIdTokenForSuffix(kLocalSuffix));
+  local_device()->UpdateInterestedDataTypes(
+      SyncInvalidationsInterestedDataTypes());
+  EnableSyncAndMergeInitialData(SyncMode::kFull);
 }
 
 TEST_F(DeviceInfoSyncBridgeTest, ShouldNotifyWhenDeviceInfoIsSynced) {
   InitializeAndMergeInitialData(SyncMode::kFull);
 
   base::MockOnceClosure callback;
-  bridge()->RefreshLocalDeviceInfo(callback.Get());
+  ASSERT_THAT(local_device()->GetLocalDeviceInfo()->fcm_registration_token(),
+              IsEmpty());
+  local_device()->UpdateFCMRegistrationToken(
+      SyncInvalidationsInstanceIdTokenForSuffix(kLocalSuffix));
+  bridge()->RefreshLocalDeviceInfoIfNeeded(callback.Get());
 
   std::string guid = local_device()->GetLocalDeviceInfo()->guid();
   EXPECT_CALL(*processor(), IsEntityUnsynced(guid)).WillOnce(Return(true));
@@ -1313,6 +1365,19 @@
                              EntityChangeList());
 }
 
+TEST_F(DeviceInfoSyncBridgeTest, ShouldNotNotifyWhenDeviceInfoIsUnchanged) {
+  InitializeAndMergeInitialData(SyncMode::kFull);
+
+  base::MockOnceClosure callback;
+  bridge()->RefreshLocalDeviceInfoIfNeeded(callback.Get());
+
+  std::string guid = local_device()->GetLocalDeviceInfo()->guid();
+  EXPECT_CALL(*processor(), IsEntityUnsynced(guid)).WillOnce(Return(false));
+  EXPECT_CALL(callback, Run()).Times(0);
+  bridge()->ApplySyncChanges(bridge()->CreateMetadataChangeList(),
+                             EntityChangeList());
+}
+
 // This test mimics the case when OnSyncStarting is called before the metadata
 // is loaded from the storage.
 TEST_F(DeviceInfoSyncBridgeTest,
diff --git a/components/sync_device_info/device_info_sync_client.h b/components/sync_device_info/device_info_sync_client.h
index 230539e..1f9a094 100644
--- a/components/sync_device_info/device_info_sync_client.h
+++ b/components/sync_device_info/device_info_sync_client.h
@@ -23,8 +23,15 @@
   virtual bool GetSendTabToSelfReceivingEnabled() const = 0;
   virtual base::Optional<DeviceInfo::SharingInfo> GetLocalSharingInfo()
       const = 0;
-  virtual std::string GetFCMRegistrationToken() const = 0;
-  virtual ModelTypeSet GetInterestedDataTypes() const = 0;
+
+  // Returns current FCM registration token if known, empty if the invalidation
+  // service is not enabled. base::nullopt will be returned if the token has
+  // been requested but hasn't been retrieved yet.
+  virtual base::Optional<std::string> GetFCMRegistrationToken() const = 0;
+
+  // A list of enabled data types, base::nullopt if the invalidation service is
+  // not initialized yet.
+  virtual base::Optional<ModelTypeSet> GetInterestedDataTypes() const = 0;
 
  private:
   DISALLOW_COPY_AND_ASSIGN(DeviceInfoSyncClient);
diff --git a/components/sync_device_info/device_info_sync_service.h b/components/sync_device_info/device_info_sync_service.h
index d76c7d4..6ca64fc 100644
--- a/components/sync_device_info/device_info_sync_service.h
+++ b/components/sync_device_info/device_info_sync_service.h
@@ -39,8 +39,11 @@
   // Interface to refresh local copy of device info in memory, and informs sync
   // of the change. Used when the caller knows a property of local device info
   // has changed (e.g. SharingInfo), and must be sync-ed to other devices as
-  // soon as possible, without waiting for the periodic commits. |callback| will
-  // be called when device info is synced.
+  // soon as possible, without waiting for the periodic commits. The device info
+  // will be compared to the local copy. If the device info has been actually
+  // changed, then it will be committed and the |callback| will be called when
+  // device info is synced. Otherwise nothing happens and the |callback| will
+  // never be called.
   virtual void RefreshLocalDeviceInfo(
       base::OnceClosure callback = base::DoNothing()) = 0;
 };
diff --git a/components/sync_device_info/device_info_sync_service_impl.cc b/components/sync_device_info/device_info_sync_service_impl.cc
index a24ee0c..cf97cc9 100644
--- a/components/sync_device_info/device_info_sync_service_impl.cc
+++ b/components/sync_device_info/device_info_sync_service_impl.cc
@@ -68,7 +68,7 @@
 
 void DeviceInfoSyncServiceImpl::RefreshLocalDeviceInfo(
     base::OnceClosure callback) {
-  bridge_->RefreshLocalDeviceInfo(std::move(callback));
+  bridge_->RefreshLocalDeviceInfoIfNeeded(std::move(callback));
 }
 
 void DeviceInfoSyncServiceImpl::OnFCMRegistrationTokenChanged() {
diff --git a/components/sync_device_info/local_device_info_provider.h b/components/sync_device_info/local_device_info_provider.h
index abf9c29..f4c4e28b 100644
--- a/components/sync_device_info/local_device_info_provider.h
+++ b/components/sync_device_info/local_device_info_provider.h
@@ -9,6 +9,7 @@
 #include <string>
 
 #include "base/callback_list.h"
+#include "components/sync/base/model_type.h"
 #include "components/version_info/version_info.h"
 
 namespace syncer {
@@ -41,7 +42,9 @@
   virtual void Initialize(const std::string& cache_guid,
                           const std::string& client_name,
                           const std::string& manufacturer_name,
-                          const std::string& model_name) = 0;
+                          const std::string& model_name,
+                          const std::string& last_fcm_registration_token,
+                          const ModelTypeSet& last_interested_data_types) = 0;
   virtual void Clear() = 0;
 
   // Updates the local device's client name. Initialize() must be called before
diff --git a/components/sync_device_info/local_device_info_provider_impl.cc b/components/sync_device_info/local_device_info_provider_impl.cc
index 8baf8b1..28fa7c0 100644
--- a/components/sync_device_info/local_device_info_provider_impl.cc
+++ b/components/sync_device_info/local_device_info_provider_impl.cc
@@ -37,13 +37,25 @@
     return nullptr;
   }
 
+  // Pull new values for settings that aren't automatically updated.
   local_device_info_->set_send_tab_to_self_receiving_enabled(
       sync_client_->GetSendTabToSelfReceivingEnabled());
   local_device_info_->set_sharing_info(sync_client_->GetLocalSharingInfo());
-  local_device_info_->set_fcm_registration_token(
-      sync_client_->GetFCMRegistrationToken());
-  local_device_info_->set_interested_data_types(
-      sync_client_->GetInterestedDataTypes());
+
+  // Do not update previous values if the service is not fully initialized.
+  // base::nullopt means that the value is unknown yet and the previous value
+  // should be kept.
+  const base::Optional<std::string> fcm_token =
+      sync_client_->GetFCMRegistrationToken();
+  if (fcm_token) {
+    local_device_info_->set_fcm_registration_token(*fcm_token);
+  }
+
+  const base::Optional<ModelTypeSet> interested_data_types =
+      sync_client_->GetInterestedDataTypes();
+  if (interested_data_types) {
+    local_device_info_->set_interested_data_types(*interested_data_types);
+  }
 
   return local_device_info_.get();
 }
@@ -60,7 +72,9 @@
     const std::string& cache_guid,
     const std::string& client_name,
     const std::string& manufacturer_name,
-    const std::string& model_name) {
+    const std::string& model_name,
+    const std::string& last_fcm_registration_token,
+    const ModelTypeSet& last_interested_data_types) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(!cache_guid.empty());
 
@@ -73,9 +87,8 @@
       /*last_updated_timestamp=*/base::Time(),
       DeviceInfoUtil::GetPulseInterval(),
       sync_client_->GetSendTabToSelfReceivingEnabled(),
-      sync_client_->GetLocalSharingInfo(),
-      sync_client_->GetFCMRegistrationToken(),
-      sync_client_->GetInterestedDataTypes());
+      sync_client_->GetLocalSharingInfo(), last_fcm_registration_token,
+      last_interested_data_types);
 
   // Notify observers.
   callback_list_.Notify();
diff --git a/components/sync_device_info/local_device_info_provider_impl.h b/components/sync_device_info/local_device_info_provider_impl.h
index 08a023e..d6e12fff 100644
--- a/components/sync_device_info/local_device_info_provider_impl.h
+++ b/components/sync_device_info/local_device_info_provider_impl.h
@@ -32,7 +32,9 @@
   void Initialize(const std::string& cache_guid,
                   const std::string& client_name,
                   const std::string& manufacturer_name,
-                  const std::string& model_name) override;
+                  const std::string& model_name,
+                  const std::string& last_fcm_registration_token,
+                  const ModelTypeSet& last_interested_data_types) override;
   void Clear() override;
   void UpdateClientName(const std::string& client_name) override;
   version_info::Channel GetChannel() const override;
diff --git a/components/sync_device_info/local_device_info_provider_impl_unittest.cc b/components/sync_device_info/local_device_info_provider_impl_unittest.cc
index 68f5207..55227c1e 100644
--- a/components/sync_device_info/local_device_info_provider_impl_unittest.cc
+++ b/components/sync_device_info/local_device_info_provider_impl_unittest.cc
@@ -47,8 +47,14 @@
               GetLocalSharingInfo,
               (),
               (const override));
-  MOCK_METHOD(std::string, GetFCMRegistrationToken, (), (const override));
-  MOCK_METHOD(ModelTypeSet, GetInterestedDataTypes, (), (const override));
+  MOCK_METHOD(base::Optional<std::string>,
+              GetFCMRegistrationToken,
+              (),
+              (const override));
+  MOCK_METHOD(base::Optional<ModelTypeSet>,
+              GetInterestedDataTypes,
+              (),
+              (const override));
 
  private:
   DISALLOW_COPY_AND_ASSIGN(MockDeviceInfoSyncClient);
@@ -73,7 +79,9 @@
 
   void InitializeProvider(const std::string& guid) {
     provider_->Initialize(guid, kLocalDeviceClientName,
-                          kLocalDeviceManufacturerName, kLocalDeviceModelName);
+                          kLocalDeviceManufacturerName, kLocalDeviceModelName,
+                          /*last_fcm_registration_token=*/std::string(),
+                          ModelTypeSet());
   }
 
   testing::NiceMock<MockDeviceInfoSyncClient> device_info_sync_client_;
@@ -179,7 +187,7 @@
 
   const std::string kFCMRegistrationToken = "token";
   EXPECT_CALL(device_info_sync_client_, GetFCMRegistrationToken())
-      .WillOnce(Return(kFCMRegistrationToken));
+      .WillRepeatedly(Return(kFCMRegistrationToken));
 
   EXPECT_EQ(provider_->GetLocalDeviceInfo()->fcm_registration_token(),
             kFCMRegistrationToken);
@@ -192,10 +200,27 @@
 
   const ModelTypeSet kTypes = ModelTypeSet(BOOKMARKS);
   EXPECT_CALL(device_info_sync_client_, GetInterestedDataTypes())
-      .WillOnce(Return(kTypes));
+      .WillRepeatedly(Return(kTypes));
 
   EXPECT_EQ(provider_->GetLocalDeviceInfo()->interested_data_types(), kTypes);
 }
 
+TEST_F(LocalDeviceInfoProviderImplTest, ShouldKeepStoredInvalidationFields) {
+  const std::string kFCMRegistrationToken = "fcm_token";
+  const ModelTypeSet kInterestedDataTypes(BOOKMARKS);
+  provider_->Initialize(kLocalDeviceGuid, kLocalDeviceClientName,
+                        kLocalDeviceManufacturerName, kLocalDeviceModelName,
+                        kFCMRegistrationToken, kInterestedDataTypes);
+
+  EXPECT_CALL(device_info_sync_client_, GetFCMRegistrationToken())
+      .WillOnce(Return(base::nullopt));
+  EXPECT_CALL(device_info_sync_client_, GetInterestedDataTypes())
+      .WillOnce(Return(base::nullopt));
+
+  const DeviceInfo* local_device_info = provider_->GetLocalDeviceInfo();
+  EXPECT_EQ(local_device_info->interested_data_types(), kInterestedDataTypes);
+  EXPECT_EQ(local_device_info->fcm_registration_token(), kFCMRegistrationToken);
+}
+
 }  // namespace
 }  // namespace syncer
diff --git a/components/viz/service/display_embedder/output_presenter_x11.cc b/components/viz/service/display_embedder/output_presenter_x11.cc
index 59ee389..e16a2b4 100644
--- a/components/viz/service/display_embedder/output_presenter_x11.cc
+++ b/components/viz/service/display_embedder/output_presenter_x11.cc
@@ -423,7 +423,7 @@
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   auto* connection = x11::Connection::Get();
   event_source_ = std::make_unique<ui::X11EventSource>(connection);
-  connection->RemoveEventObserver(this);
+  connection->AddEventObserver(this);
 
   auto* present = &connection->present();
   event_id_ = connection->GenerateId<uint32_t>();
diff --git a/content/browser/accessibility/accessibility_tree_formatter_mac.h b/content/browser/accessibility/accessibility_tree_formatter_mac.h
index bbcd197..5500a598 100644
--- a/content/browser/accessibility/accessibility_tree_formatter_mac.h
+++ b/content/browser/accessibility/accessibility_tree_formatter_mac.h
@@ -54,7 +54,6 @@
       const ui::AXPropertyNode& property_node,
       const a11y::LineIndexer* line_indexer) const;
 
-  base::Value PopulateSize(const BrowserAccessibilityCocoa*) const;
   base::Value PopulatePosition(const BrowserAccessibilityCocoa*) const;
   base::Value PopulatePoint(NSPoint) const;
   base::Value PopulateSize(NSSize) const;
diff --git a/content/browser/accessibility/accessibility_tree_formatter_mac.mm b/content/browser/accessibility/accessibility_tree_formatter_mac.mm
index 8d3b32c..ed70b7dd 100644
--- a/content/browser/accessibility/accessibility_tree_formatter_mac.mm
+++ b/content/browser/accessibility/accessibility_tree_formatter_mac.mm
@@ -45,9 +45,6 @@
 const char kPositionDictAttr[] = "position";
 const char kXCoordDictAttr[] = "x";
 const char kYCoordDictAttr[] = "y";
-const char kSizeDictAttr[] = "size";
-const char kWidthDictAttr[] = "width";
-const char kHeightDictAttr[] = "height";
 const char kRangeLocDictAttr[] = "loc";
 const char kRangeLenDictAttr[] = "len";
 
@@ -171,9 +168,8 @@
     dict->SetKey("id",
                  base::Value(base::NumberToString16(owner_node->GetId())));
 
-    // Position and size
+    // Position (no size since it's exposed as standard AXSize attribute)
     dict->SetPath(kPositionDictAttr, PopulatePosition(cocoa_node));
-    dict->SetPath(kSizeDictAttr, PopulateSize(cocoa_node));
   }
 
   // Dump all attributes if match-all filter is specified.
@@ -206,15 +202,6 @@
   }
 }
 
-base::Value AccessibilityTreeFormatterMac::PopulateSize(
-    const BrowserAccessibilityCocoa* cocoa_node) const {
-  base::Value size(base::Value::Type::DICTIONARY);
-  NSSize node_size = [[cocoa_node size] sizeValue];
-  size.SetIntPath(kHeightDictAttr, static_cast<int>(node_size.height));
-  size.SetIntPath(kWidthDictAttr, static_cast<int>(node_size.width));
-  return size;
-}
-
 base::Value AccessibilityTreeFormatterMac::PopulatePosition(
     const BrowserAccessibilityCocoa* cocoa_node) const {
   BrowserAccessibility* node = [cocoa_node owner];
@@ -261,10 +248,14 @@
     return base::Value([value intValue]);
   }
 
-  // NSRange
-  if ([value isKindOfClass:[NSValue class]] &&
-      0 == strcmp([value objCType], @encode(NSRange))) {
-    return PopulateRange([value rangeValue]);
+  // NSRange, NSSize
+  if ([value isKindOfClass:[NSValue class]]) {
+    if (0 == strcmp([value objCType], @encode(NSRange))) {
+      return PopulateRange([value rangeValue]);
+    }
+    if (0 == strcmp([value objCType], @encode(NSSize))) {
+      return PopulateSize([value sizeValue]);
+    }
   }
 
   // AXTextMarker
@@ -465,14 +456,6 @@
                      &line);
       continue;
     }
-    // Special case: size.
-    if (item.first == kSizeDictAttr) {
-      WriteAttribute(false,
-                     FormatCoordinates(item.second, kSizeDictAttr,
-                                       kWidthDictAttr, kHeightDictAttr),
-                     &line);
-      continue;
-    }
 
     // Write formatted value.
     std::string formatted_value = FormatAttributeValue(item.second);
diff --git a/content/browser/accessibility/browser_accessibility_android.cc b/content/browser/accessibility/browser_accessibility_android.cc
index 1e02e70..55a236e0 100644
--- a/content/browser/accessibility/browser_accessibility_android.cc
+++ b/content/browser/accessibility/browser_accessibility_android.cc
@@ -181,6 +181,7 @@
   return (ui::IsTableLike(GetRole()) || GetRole() == ax::mojom::Role::kList ||
           GetRole() == ax::mojom::Role::kListBox ||
           GetRole() == ax::mojom::Role::kDescriptionList ||
+          GetRole() == ax::mojom::Role::kDirectory ||
           GetRole() == ax::mojom::Role::kTree);
 }
 
diff --git a/content/browser/accessibility/browser_accessibility_cocoa.mm b/content/browser/accessibility/browser_accessibility_cocoa.mm
index 6ad3a7d..b30b7d4c 100644
--- a/content/browser/accessibility/browser_accessibility_cocoa.mm
+++ b/content/browser/accessibility/browser_accessibility_cocoa.mm
@@ -2374,8 +2374,10 @@
   if ([self internalRole] == ax::mojom::Role::kDescriptionList)
     return NSAccessibilityDefinitionListSubrole;
 
-  if ([self internalRole] == ax::mojom::Role::kList)
+  if ([self internalRole] == ax::mojom::Role::kDirectory ||
+      [self internalRole] == ax::mojom::Role::kList) {
     return NSAccessibilityContentListSubrole;
+  }
 
   return [AXPlatformNodeCocoa nativeSubroleFromAXRole:[self internalRole]];
 }
diff --git a/content/browser/hid/hid_browsertest.cc b/content/browser/hid/hid_browsertest.cc
index 3a6c6af..0b426177 100644
--- a/content/browser/hid/hid_browsertest.cc
+++ b/content/browser/hid/hid_browsertest.cc
@@ -23,7 +23,6 @@
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/mojom/hid/hid.mojom.h"
 
-using testing::_;
 using testing::ByMove;
 using testing::Exactly;
 using testing::Return;
@@ -32,6 +31,22 @@
 
 namespace {
 
+// Create a device with a single collection containing an input report and an
+// output report. Both reports have report ID 0.
+device::mojom::HidDeviceInfoPtr CreateTestDeviceWithInputAndOutputReports() {
+  auto collection = device::mojom::HidCollectionInfo::New();
+  collection->usage = device::mojom::HidUsageAndPage::New(0x0001, 0xff00);
+  collection->input_reports.push_back(
+      device::mojom::HidReportDescription::New());
+  collection->output_reports.push_back(
+      device::mojom::HidReportDescription::New());
+
+  auto device = device::mojom::HidDeviceInfo::New();
+  device->guid = "test-guid";
+  device->collections.push_back(std::move(collection));
+  return device;
+}
+
 class HidTest : public ContentBrowserTest {
  public:
   HidTest() {
@@ -71,12 +86,12 @@
 
   // Three devices are added but only two will have permission granted.
   for (int i = 0; i < 3; i++) {
-    auto device = device::mojom::HidDeviceInfo::New();
+    auto device = CreateTestDeviceWithInputAndOutputReports();
     device->guid = base::StringPrintf("test-guid-%02d", i);
     hid_manager()->AddDevice(std::move(device));
   }
 
-  EXPECT_CALL(delegate(), HasDevicePermission(_, _, _))
+  EXPECT_CALL(delegate(), HasDevicePermission)
       .WillOnce(Return(true))
       .WillOnce(Return(false))
       .WillOnce(Return(true));
@@ -90,13 +105,10 @@
 IN_PROC_BROWSER_TEST_F(HidTest, RequestDevice) {
   EXPECT_TRUE(NavigateToURL(shell(), GetTestUrl(nullptr, "simple_page.html")));
 
-  EXPECT_CALL(delegate(), CanRequestDevicePermission(_, _))
-      .WillOnce(Return(true));
+  EXPECT_CALL(delegate(), CanRequestDevicePermission).WillOnce(Return(true));
 
-  auto device = device::mojom::HidDeviceInfo::New();
-  device->guid = "test-guid";
   std::vector<device::mojom::HidDeviceInfoPtr> devices;
-  devices.push_back(std::move(device));
+  devices.push_back(CreateTestDeviceWithInputAndOutputReports());
   EXPECT_CALL(delegate(), RunChooserInternal)
       .WillOnce(Return(ByMove(std::move(devices))));
 
@@ -112,8 +124,7 @@
 IN_PROC_BROWSER_TEST_F(HidTest, DisallowRequestDevice) {
   EXPECT_TRUE(NavigateToURL(shell(), GetTestUrl(nullptr, "simple_page.html")));
 
-  EXPECT_CALL(delegate(), CanRequestDevicePermission(_, _))
-      .WillOnce(Return(false));
+  EXPECT_CALL(delegate(), CanRequestDevicePermission).WillOnce(Return(false));
   EXPECT_CALL(delegate(), RunChooserInternal).Times(Exactly(0));
 
   EXPECT_EQ(0, EvalJs(shell(),
@@ -123,4 +134,50 @@
              })())"));
 }
 
+IN_PROC_BROWSER_TEST_F(HidTest, ProtectedReportsAreFiltered) {
+  LOG(ERROR) << "HidTest.ProtectedReportsAreFiltered";
+  EXPECT_TRUE(NavigateToURL(shell(), GetTestUrl(nullptr, "simple_page.html")));
+
+  auto device = CreateTestDeviceWithInputAndOutputReports();
+
+  // Mark the input report as protected.
+  device->protected_input_report_ids = std::vector<uint8_t>{0};
+
+  hid_manager()->AddDevice(std::move(device));
+
+  EXPECT_CALL(delegate(), HasDevicePermission).WillOnce(Return(true));
+
+  EXPECT_EQ(true, EvalJs(shell(),
+                         R"((async () => {
+             let devices = await navigator.hid.getDevices();
+             return devices instanceof Array
+                    && devices.length == 1
+                    && devices[0] instanceof HIDDevice
+                    && devices[0].collections instanceof Array
+                    && devices[0].collections.length == 1
+                    && devices[0].collections[0].inputReports instanceof Array
+                    && devices[0].collections[0].inputReports.length == 0
+                    && devices[0].collections[0].outputReports instanceof Array
+                    && devices[0].collections[0].outputReports.length == 1;
+           })())"));
+}
+
+IN_PROC_BROWSER_TEST_F(HidTest, DeviceWithAllProtectedReportsIsExcluded) {
+  EXPECT_TRUE(NavigateToURL(shell(), GetTestUrl(nullptr, "simple_page.html")));
+
+  auto device = CreateTestDeviceWithInputAndOutputReports();
+
+  // Mark both the input and output reports as protected.
+  device->protected_input_report_ids = std::vector<uint8_t>{0};
+  device->protected_output_report_ids = std::vector<uint8_t>{0};
+
+  hid_manager()->AddDevice(std::move(device));
+
+  EXPECT_EQ(true, EvalJs(shell(),
+                         R"((async () => {
+               let devices = await navigator.hid.getDevices();
+               return devices instanceof Array && devices.length == 0;
+             })())"));
+}
+
 }  // namespace content
diff --git a/content/browser/hid/hid_service.cc b/content/browser/hid/hid_service.cc
index c99b249..eb42cccc 100644
--- a/content/browser/hid/hid_service.cc
+++ b/content/browser/hid/hid_service.cc
@@ -21,6 +21,52 @@
 
 namespace content {
 
+namespace {
+
+// Removes reports from |device| if the report IDs match the IDs in the
+// protected report ID lists. If all of the reports are removed from a
+// collection, the collection is also removed.
+void RemoveProtectedReports(device::mojom::HidDeviceInfo& device) {
+  std::vector<device::mojom::HidCollectionInfoPtr> collections;
+  for (auto& collection : device.collections) {
+    std::vector<device::mojom::HidReportDescriptionPtr> input_reports;
+    for (auto& report : collection->input_reports) {
+      if (!device.protected_input_report_ids.has_value() ||
+          !base::Contains(*device.protected_input_report_ids,
+                          report->report_id)) {
+        input_reports.push_back(std::move(report));
+      }
+    }
+    std::vector<device::mojom::HidReportDescriptionPtr> output_reports;
+    for (auto& report : collection->output_reports) {
+      if (!device.protected_output_report_ids.has_value() ||
+          !base::Contains(*device.protected_output_report_ids,
+                          report->report_id)) {
+        output_reports.push_back(std::move(report));
+      }
+    }
+    std::vector<device::mojom::HidReportDescriptionPtr> feature_reports;
+    for (auto& report : collection->feature_reports) {
+      if (!device.protected_feature_report_ids.has_value() ||
+          !base::Contains(*device.protected_feature_report_ids,
+                          report->report_id)) {
+        feature_reports.push_back(std::move(report));
+      }
+    }
+    // Only keep the collection if it has at least one report.
+    if (!input_reports.empty() || !output_reports.empty() ||
+        !feature_reports.empty()) {
+      collection->input_reports = std::move(input_reports);
+      collection->output_reports = std::move(output_reports);
+      collection->feature_reports = std::move(feature_reports);
+      collections.push_back(std::move(collection));
+    }
+  }
+  device.collections = std::move(collections);
+}
+
+}  // namespace
+
 HidService::HidService(RenderFrameHost* render_frame_host,
                        mojo::PendingReceiver<blink::mojom::HidService> receiver)
     : FrameServiceBase(render_frame_host, std::move(receiver)),
@@ -121,6 +167,7 @@
       ->GetHidManager(WebContents::FromRenderFrameHost(render_frame_host()))
       ->Connect(
           device_guid, std::move(client), std::move(watcher),
+          /*allow_protected_reports=*/false,
           base::BindOnce(&HidService::FinishConnect, weak_factory_.GetWeakPtr(),
                          std::move(callback)));
 }
@@ -151,8 +198,13 @@
     return;
   }
 
+  auto filtered_device_info = device_info.Clone();
+  RemoveProtectedReports(*filtered_device_info);
+  if (filtered_device_info->collections.empty())
+    return;
+
   for (auto& client : clients_)
-    client->DeviceAdded(device_info.Clone());
+    client->DeviceAdded(filtered_device_info->Clone());
 }
 
 void HidService::OnDeviceRemoved(
@@ -163,8 +215,13 @@
     return;
   }
 
+  auto filtered_device_info = device_info.Clone();
+  RemoveProtectedReports(*filtered_device_info);
+  if (filtered_device_info->collections.empty())
+    return;
+
   for (auto& client : clients_)
-    client->DeviceRemoved(device_info.Clone());
+    client->DeviceRemoved(filtered_device_info->Clone());
 }
 
 void HidService::OnHidManagerConnectionError() {
@@ -210,6 +267,10 @@
   std::vector<device::mojom::HidDeviceInfoPtr> result;
   HidDelegate* delegate = GetContentClient()->browser()->GetHidDelegate();
   for (auto& device : devices) {
+    RemoveProtectedReports(*device);
+    if (device->collections.empty())
+      continue;
+
     if (delegate->HasDevicePermission(
             WebContents::FromRenderFrameHost(render_frame_host()), origin(),
             *device))
diff --git a/content/browser/hid/hid_service_unittest.cc b/content/browser/hid/hid_service_unittest.cc
index 7c4b9c9..8ca4b56 100644
--- a/content/browser/hid/hid_service_unittest.cc
+++ b/content/browser/hid/hid_service_unittest.cc
@@ -128,8 +128,13 @@
   contents()->GetMainFrame()->GetHidService(
       service.BindNewPipeAndPassReceiver());
 
+  auto collection = device::mojom::HidCollectionInfo::New();
+  collection->usage = device::mojom::HidUsageAndPage::New(0xff00, 0x0001);
+  collection->input_reports.push_back(
+      device::mojom::HidReportDescription::New());
   auto device_info = device::mojom::HidDeviceInfo::New();
   device_info->guid = kTestGuid;
+  device_info->collections.push_back(std::move(collection));
   ConnectDevice(*device_info);
 
   EXPECT_CALL(hid_delegate(), HasDevicePermission).WillOnce(Return(true));
@@ -152,8 +157,13 @@
   contents()->GetMainFrame()->GetHidService(
       service.BindNewPipeAndPassReceiver());
 
+  auto collection = device::mojom::HidCollectionInfo::New();
+  collection->usage = device::mojom::HidUsageAndPage::New(0xff00, 0x0001);
+  collection->input_reports.push_back(
+      device::mojom::HidReportDescription::New());
   auto device_info = device::mojom::HidDeviceInfo::New();
   device_info->guid = kTestGuid;
+  device_info->collections.push_back(std::move(collection));
   ConnectDevice(*device_info);
 
   EXPECT_CALL(hid_delegate(), HasDevicePermission).WillOnce(Return(false));
@@ -326,8 +336,13 @@
   EXPECT_TRUE(devices.empty());
 
   // 2. Connect a device and wait for DeviceAdded.
+  auto collection = device::mojom::HidCollectionInfo::New();
+  collection->usage = device::mojom::HidUsageAndPage::New(0xff00, 0x0001);
+  collection->input_reports.push_back(
+      device::mojom::HidReportDescription::New());
   auto device_info = device::mojom::HidDeviceInfo::New();
   device_info->guid = kTestGuid;
+  device_info->collections.push_back(std::move(collection));
   ConnectDevice(*device_info);
   device_added_loop.Run();
 
diff --git a/content/browser/media/media_web_contents_observer.cc b/content/browser/media/media_web_contents_observer.cc
index e144c94..4172a48 100644
--- a/content/browser/media/media_web_contents_observer.cc
+++ b/content/browser/media/media_web_contents_observer.cc
@@ -599,19 +599,6 @@
       std::move(player_receiver));
 }
 
-void MediaWebContentsObserver::SetMediaPlayerObserverForMediaPlayer(
-    const MediaPlayerId& player_id) {
-  if (!media_player_observer_hosts_.contains(player_id)) {
-    media_player_observer_hosts_[player_id] =
-        std::make_unique<MediaPlayerObserverHostImpl>(player_id, this);
-  }
-
-  DCHECK(media_player_remotes_[player_id]);
-  media_player_remotes_[player_id]->SetMediaPlayerObserver(
-      media_player_observer_hosts_[player_id]
-          ->BindMediaPlayerObserverReceiverAndPassRemote());
-}
-
 void MediaWebContentsObserver::OnMediaPlayerAdded(
     mojo::PendingRemote<media::mojom::MediaPlayer> player_remote,
     MediaPlayerId player_id) {
@@ -628,9 +615,16 @@
       },
       base::Unretained(this), player_id));
 
-  // Create a new MediaPlayerObserverHostImpl to be able to receive messages
-  // from the renderer process via the MediaPlayerObserver mojo interface.
-  SetMediaPlayerObserverForMediaPlayer(player_id);
+  // Create a new MediaPlayerObserverHostImpl for |player_id|, implementing the
+  // media::mojom::MediaPlayerObserver mojo interface, to handle messages sent
+  // from the MediaPlayer element in the renderer process.
+  if (!media_player_observer_hosts_.contains(player_id)) {
+    media_player_observer_hosts_[player_id] =
+        std::make_unique<MediaPlayerObserverHostImpl>(player_id, this);
+  }
+  media_player_remotes_[player_id]->AddMediaPlayerObserver(
+      media_player_observer_hosts_[player_id]
+          ->BindMediaPlayerObserverReceiverAndPassRemote());
 }
 
 #if defined(OS_ANDROID)
diff --git a/content/browser/media/media_web_contents_observer.h b/content/browser/media/media_web_contents_observer.h
index db237acd..7795fc7 100644
--- a/content/browser/media/media_web_contents_observer.h
+++ b/content/browser/media/media_web_contents_observer.h
@@ -122,10 +122,6 @@
       RenderFrameHost* host,
       mojo::PendingReceiver<media::mojom::MediaPlayerHost> player_receiver);
 
-  // Establishes a MediaPlayerObserver for |player_id|, allowing the MediaPlayer
-  // element in the renderer process to communicate back with the browser.
-  void SetMediaPlayerObserverForMediaPlayer(const MediaPlayerId& player_id);
-
   // Communicates with the MediaSessionControllerManager to find or create (if
   // needed) a MediaSessionController identified by |player_id|, in order to
   // bind its mojo remote for media::mojom::MediaPlayer.
diff --git a/content/browser/media/session/media_session_controller_unittest.cc b/content/browser/media/session/media_session_controller_unittest.cc
index b40d4cb..ef2931e 100644
--- a/content/browser/media/session/media_session_controller_unittest.cc
+++ b/content/browser/media/session/media_session_controller_unittest.cc
@@ -82,7 +82,7 @@
   }
 
   // media::mojom::MediaPlayer implementation.
-  void SetMediaPlayerObserver(
+  void AddMediaPlayerObserver(
       mojo::PendingRemote<media::mojom::MediaPlayerObserver>) override {}
 
   void RequestPlay() override {
diff --git a/content/browser/picture_in_picture/picture_in_picture_service_impl_unittest.cc b/content/browser/picture_in_picture/picture_in_picture_service_impl_unittest.cc
index fd64054b..c7bffe79 100644
--- a/content/browser/picture_in_picture/picture_in_picture_service_impl_unittest.cc
+++ b/content/browser/picture_in_picture/picture_in_picture_service_impl_unittest.cc
@@ -113,7 +113,7 @@
   }
 
   // media::mojom::MediaPlayer implementation.
-  void SetMediaPlayerObserver(
+  void AddMediaPlayerObserver(
       mojo::PendingRemote<media::mojom::MediaPlayerObserver>) override {}
   void RequestPlay() override {}
   void RequestPause(bool triggered_by_user) override {}
diff --git a/content/test/data/accessibility/aria/aria-directory-expected-android.txt b/content/test/data/accessibility/aria/aria-directory-expected-android.txt
index 53c5958..0780a21 100644
--- a/content/test/data/accessibility/aria/aria-directory-expected-android.txt
+++ b/content/test/data/accessibility/aria/aria-directory-expected-android.txt
@@ -1,2 +1,2 @@
 android.webkit.WebView focusable focused scrollable
-++android.widget.ListView collection
+++android.widget.ListView role_description='directory' collection
diff --git a/content/test/data/accessibility/aria/aria-directory-expected-blink.txt b/content/test/data/accessibility/aria/aria-directory-expected-blink.txt
index 063e3ab..909bafc 100644
--- a/content/test/data/accessibility/aria/aria-directory-expected-blink.txt
+++ b/content/test/data/accessibility/aria/aria-directory-expected-blink.txt
@@ -1,4 +1,4 @@
 rootWebArea
 ++genericContainer ignored
 ++++genericContainer ignored
-++++++list
+++++++directory
diff --git a/content/test/data/accessibility/aria/aria-expanded-roles-supported-expected-blink.txt b/content/test/data/accessibility/aria/aria-expanded-roles-supported-expected-blink.txt
index f1974bc..e43859fd 100644
--- a/content/test/data/accessibility/aria/aria-expanded-roles-supported-expected-blink.txt
+++ b/content/test/data/accessibility/aria/aria-expanded-roles-supported-expected-blink.txt
@@ -16,7 +16,7 @@
 ++++++definition
 ++++++contentDeletion
 ++++++dialog
-++++++list
+++++++directory
 ++++++document
 ++++++emphasis
 ++++++feed
@@ -83,4 +83,4 @@
 ++++++tooltip
 ++++++tree
 ++++++treeGrid
-++++++treeItem expanded
+++++++treeItem expanded
\ No newline at end of file
diff --git a/content/test/data/accessibility/aria/aria-owns-list-expected-mac.txt b/content/test/data/accessibility/aria/aria-owns-list-expected-mac.txt
index a2f7408..74bd6ac 100644
--- a/content/test/data/accessibility/aria/aria-owns-list-expected-mac.txt
+++ b/content/test/data/accessibility/aria/aria-owns-list-expected-mac.txt
@@ -1,6 +1,6 @@
 AXWebArea
-++AXList size=(400, 400)
-++++AXGroup size=(400, 200)
+++AXList AXSize={h: 400, w: 400}
+++++AXGroup AXSize={h: 200, w: 400}
 ++++++AXStaticText AXValue='One'
-++++AXGroup size=(400, 200)
+++++AXGroup AXSize={h: 200, w: 400}
 ++++++AXStaticText AXValue='Two'
diff --git a/content/test/data/accessibility/aria/aria-owns-list.html b/content/test/data/accessibility/aria/aria-owns-list.html
index b8587971..3c4ad3c 100644
--- a/content/test/data/accessibility/aria/aria-owns-list.html
+++ b/content/test/data/accessibility/aria/aria-owns-list.html
@@ -1,5 +1,5 @@
 <!--
-@MAC-ALLOW:size=(400*
+@MAC-ALLOW:AXSize={h: *, w: 400}
 @BLINK-ALLOW:pageSize=(400*
 -->
 <html>
diff --git a/content/test/data/accessibility/html/iframe-coordinates-cross-process-expected-mac.txt b/content/test/data/accessibility/html/iframe-coordinates-cross-process-expected-mac.txt
index 5466623..6a54699 100644
--- a/content/test/data/accessibility/html/iframe-coordinates-cross-process-expected-mac.txt
+++ b/content/test/data/accessibility/html/iframe-coordinates-cross-process-expected-mac.txt
@@ -1,15 +1,15 @@
-AXWebArea position=(0, 0) size=(800, 600)
-++AXGroup position=(0, 0) size=(300, 150)
-++++AXButton position=(25, 25) size=(250, 50)
-++AXGroup position=(0, 150) size=(300, 150)
-++++AXButton position=(25, 175) size=(250, 50)
-++AXGroup position=(0, 300) size=(300, 150)
-++++AXGroup position=(0, 300) size=(300, 100)
-++++++AXWebArea position=(0, 300) size=(300, 100)
-++++++++AXGroup position=(0, 300) size=(300, 100)
-++++++++++AXButton position=(25, 325) size=(250, 50)
-++AXGroup position=(0, 450) size=(300, 150)
-++++AXGroup position=(0, 450) size=(150, 50)
-++++++AXWebArea position=(0, 450) size=(150, 50)
-++++++++AXGroup position=(0, 450) size=(150, 50)
-++++++++++AXButton position=(0, 450) size=(125, 25)
\ No newline at end of file
+AXWebArea position=(0, 0) AXSize={h: 600, w: 800}
+++AXGroup position=(0, 0) AXSize={h: 150, w: 300}
+++++AXButton position=(25, 25) AXSize={h: 50, w: 250}
+++AXGroup position=(0, 150) AXSize={h: 150, w: 300}
+++++AXButton position=(25, 175) AXSize={h: 50, w: 250}
+++AXGroup position=(0, 300) AXSize={h: 150, w: 300}
+++++AXGroup position=(0, 300) AXSize={h: 100, w: 300}
+++++++AXWebArea position=(0, 300) AXSize={h: 100, w: 300}
+++++++++AXGroup position=(0, 300) AXSize={h: 100, w: 300}
+++++++++++AXButton position=(25, 325) AXSize={h: 50, w: 250}
+++AXGroup position=(0, 450) AXSize={h: 150, w: 300}
+++++AXGroup position=(0, 450) AXSize={h: 50, w: 150}
+++++++AXWebArea position=(0, 450) AXSize={h: 50, w: 150}
+++++++++AXGroup position=(0, 450) AXSize={h: 50, w: 150}
+++++++++++AXButton position=(0, 450) AXSize={h: 25, w: 125}
\ No newline at end of file
diff --git a/content/test/data/accessibility/html/iframe-coordinates-cross-process.html b/content/test/data/accessibility/html/iframe-coordinates-cross-process.html
index ce07e220..d2b2491 100644
--- a/content/test/data/accessibility/html/iframe-coordinates-cross-process.html
+++ b/content/test/data/accessibility/html/iframe-coordinates-cross-process.html
@@ -2,7 +2,7 @@
 @MAC-DENY:AXTitle
 @MAC-DENY:AXValue
 @MAC-ALLOW:position*
-@MAC-ALLOW:size*
+@MAC-ALLOW:AXSize
 
 @WIN-DENY:title*
 @WIN-ALLOW:location*
diff --git a/content/test/data/accessibility/html/iframe-coordinates-expected-mac.txt b/content/test/data/accessibility/html/iframe-coordinates-expected-mac.txt
index 6b37577..205cd1b 100644
--- a/content/test/data/accessibility/html/iframe-coordinates-expected-mac.txt
+++ b/content/test/data/accessibility/html/iframe-coordinates-expected-mac.txt
@@ -1,15 +1,15 @@
-AXWebArea position=(0, 0) size=(800, 600)
-++AXGroup position=(0, 0) size=(300, 150)
-++++AXButton position=(25, 25) size=(250, 50)
-++AXGroup position=(0, 150) size=(300, 150)
-++++AXButton position=(25, 175) size=(250, 50)
-++AXGroup position=(0, 300) size=(300, 150)
-++++AXGroup position=(0, 300) size=(300, 100)
-++++++AXWebArea position=(0, 300) size=(300, 100)
-++++++++AXGroup position=(0, 300) size=(300, 100)
-++++++++++AXButton position=(25, 325) size=(250, 50)
-++AXGroup position=(0, 450) size=(300, 150)
-++++AXGroup position=(0, 450) size=(150, 50)
-++++++AXWebArea position=(0, 450) size=(150, 50)
-++++++++AXGroup position=(0, 450) size=(150, 50)
-++++++++++AXButton position=(0, 450) size=(125, 25)
+AXWebArea AXSize={h: 600, w: 800} position=(0, 0)
+++AXGroup AXSize={h: 150, w: 300} position=(0, 0)
+++++AXButton AXSize={h: 50, w: 250} position=(25, 25)
+++AXGroup AXSize={h: 150, w: 300} position=(0, 150)
+++++AXButton AXSize={h: 50, w: 250} position=(25, 175)
+++AXGroup AXSize={h: 150, w: 300} position=(0, 300)
+++++AXGroup AXSize={h: 100, w: 300} position=(0, 300)
+++++++AXWebArea AXSize={h: 100, w: 300} position=(0, 300)
+++++++++AXGroup AXSize={h: 100, w: 300} position=(0, 300)
+++++++++++AXButton AXSize={h: 50, w: 250} position=(25, 325)
+++AXGroup AXSize={h: 150, w: 300} position=(0, 450)
+++++AXGroup AXSize={h: 50, w: 150} position=(0, 450)
+++++++AXWebArea AXSize={h: 50, w: 150} position=(0, 450)
+++++++++AXGroup AXSize={h: 50, w: 150} position=(0, 450)
+++++++++++AXButton AXSize={h: 25, w: 125} position=(0, 450)
diff --git a/content/test/data/accessibility/html/iframe-coordinates.html b/content/test/data/accessibility/html/iframe-coordinates.html
index aefb22d0..3632d213 100644
--- a/content/test/data/accessibility/html/iframe-coordinates.html
+++ b/content/test/data/accessibility/html/iframe-coordinates.html
@@ -2,7 +2,7 @@
 @MAC-DENY:AXTitle
 @MAC-DENY:AXValue
 @MAC-ALLOW:position*
-@MAC-ALLOW:size*
+@MAC-ALLOW:AXSize
 
 @WIN-DENY:title*
 @WIN-ALLOW:location*
diff --git a/content/test/data/accessibility/html/transition-expected-mac.txt b/content/test/data/accessibility/html/transition-expected-mac.txt
index f23daed2..fc0cbe5 100644
--- a/content/test/data/accessibility/html/transition-expected-mac.txt
+++ b/content/test/data/accessibility/html/transition-expected-mac.txt
@@ -1,4 +1,4 @@
 AXWebArea
 ++AXGroup
-++++AXButton AXTitle='GrowButton' size=(600, 300)
+++++AXButton AXTitle='GrowButton' AXSize={h: 300, w: 600}
 ++++AXStaticText AXValue='Done'
diff --git a/content/test/data/accessibility/html/transition.html b/content/test/data/accessibility/html/transition.html
index 68a36b3..6cf08d8 100644
--- a/content/test/data/accessibility/html/transition.html
+++ b/content/test/data/accessibility/html/transition.html
@@ -7,8 +7,7 @@
 not diff the dump against the expectations until the text "Done"
 appears in the dump.
 
-@MAC-ALLOW:size=(400, 200)
-@MAC-ALLOW:size=(600, 300)
+@MAC-ALLOW:AXSize={h: 300, w: 600}
 @WIN-ALLOW:size=(400, 200)
 @WIN-ALLOW:size=(600, 300)
 @WAIT-FOR:Done
diff --git a/device/fido/BUILD.gn b/device/fido/BUILD.gn
index 69e2279..d2d153b 100644
--- a/device/fido/BUILD.gn
+++ b/device/fido/BUILD.gn
@@ -422,6 +422,7 @@
       "hid/fake_hid_impl_for_testing.cc",
       "hid/fake_hid_impl_for_testing.h",
     ]
+    deps += [ "//services/device/public/cpp/hid" ]
   }
 
   if (!is_android) {
diff --git a/device/fido/hid/fake_hid_impl_for_testing.cc b/device/fido/hid/fake_hid_impl_for_testing.cc
index 794cd4f..911da13 100644
--- a/device/fido/hid/fake_hid_impl_for_testing.cc
+++ b/device/fido/hid/fake_hid_impl_for_testing.cc
@@ -11,6 +11,7 @@
 #include "device/fido/hid/fido_hid_discovery.h"
 #include "mojo/public/cpp/bindings/pending_associated_remote.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "services/device/public/cpp/hid/hid_blocklist.h"
 #include "services/device/public/mojom/hid.mojom.h"
 
 namespace device {
@@ -136,6 +137,7 @@
 void FakeFidoHidManager::AddFidoHidDevice(std::string guid) {
   auto c_info = device::mojom::HidCollectionInfo::New();
   c_info->usage = device::mojom::HidUsageAndPage::New(1, 0xf1d0);
+  c_info->input_reports.push_back(device::mojom::HidReportDescription::New());
   auto device = device::mojom::HidDeviceInfo::New();
   device->guid = std::move(guid);
   device->product_name = "Test Fido Device";
@@ -144,6 +146,18 @@
   device->collections.push_back(std::move(c_info));
   device->max_input_report_size = 64;
   device->max_output_report_size = 64;
+  device->protected_input_report_ids =
+      HidBlocklist::Get().GetProtectedReportIds(
+          HidBlocklist::kReportTypeInput, device->vendor_id, device->product_id,
+          device->collections);
+  device->protected_output_report_ids =
+      HidBlocklist::Get().GetProtectedReportIds(
+          HidBlocklist::kReportTypeOutput, device->vendor_id,
+          device->product_id, device->collections);
+  device->protected_feature_report_ids =
+      HidBlocklist::Get().GetProtectedReportIds(
+          HidBlocklist::kReportTypeFeature, device->vendor_id,
+          device->product_id, device->collections);
   AddDevice(std::move(device));
 }
 
@@ -167,6 +181,7 @@
     const std::string& device_guid,
     mojo::PendingRemote<mojom::HidConnectionClient> connection_client,
     mojo::PendingRemote<mojom::HidConnectionWatcher> watcher,
+    bool allow_protected_reports,
     ConnectCallback callback) {
   auto device_it = devices_.find(device_guid);
   auto connection_it = connections_.find(device_guid);
diff --git a/device/fido/hid/fake_hid_impl_for_testing.h b/device/fido/hid/fake_hid_impl_for_testing.h
index 894efab..34ba5bd 100644
--- a/device/fido/hid/fake_hid_impl_for_testing.h
+++ b/device/fido/hid/fake_hid_impl_for_testing.h
@@ -110,6 +110,7 @@
       const std::string& device_guid,
       mojo::PendingRemote<mojom::HidConnectionClient> connection_client,
       mojo::PendingRemote<mojom::HidConnectionWatcher> watcher,
+      bool allow_protected_reports,
       ConnectCallback callback) override;
   void AddReceiver(
       mojo::PendingReceiver<device::mojom::HidManager> receiver) override;
diff --git a/device/fido/hid/fido_hid_device.cc b/device/fido/hid/fido_hid_device.cc
index 885a8cd..72f938ac 100644
--- a/device/fido/hid/fido_hid_device.cc
+++ b/device/fido/hid/fido_hid_device.cc
@@ -177,7 +177,8 @@
   DCHECK(hid_manager_);
   hid_manager_->Connect(device_info_->guid,
                         /*connection_client=*/mojo::NullRemote(),
-                        /*watcher=*/mojo::NullRemote(), std::move(callback));
+                        /*watcher=*/mojo::NullRemote(),
+                        /*allow_protected_reports=*/true, std::move(callback));
 }
 
 void FidoHidDevice::OnConnect(
diff --git a/device/gamepad/nintendo_controller.cc b/device/gamepad/nintendo_controller.cc
index 66c0a0c..f18fdfa 100644
--- a/device/gamepad/nintendo_controller.cc
+++ b/device/gamepad/nintendo_controller.cc
@@ -1188,7 +1188,8 @@
   DCHECK(hid_manager_);
   hid_manager_->Connect(device_info_->guid,
                         /*connection_client=*/mojo::NullRemote(),
-                        /*watcher=*/mojo::NullRemote(), std::move(callback));
+                        /*watcher=*/mojo::NullRemote(),
+                        /*allow_protected_reports=*/false, std::move(callback));
 }
 
 void NintendoController::OnConnect(
diff --git a/docs/mac_lld.md b/docs/mac_lld.md
index 923f030b..6268ddc0 100644
--- a/docs/mac_lld.md
+++ b/docs/mac_lld.md
@@ -60,18 +60,18 @@
   ([bug](https://llvm.org/PR48395), fixed upstream)
 - LLD-linked `protoc` crashes when it runs as part of the build
   ([bug](https://llvm.org/PR48491), fixed upstream)
+- LLD cannot yet link swiftshader binaries
+  ([bug](https://llvm.org/PR48332), fixed upstream)
 - LLD-linked `v8_context_snapshot_generator` crashes when it runs as part of
-  the build ([bug](https://llvm.org/PR48511),
-  [in-progress patch](https://reviews.llvm.org/D93369))
-- verify\_framework\_order fails in LLD builds (FIXME: file bug)
+  the build ([bug](https://llvm.org/PR48511), fixed upstream)
+- verify\_framework\_order fails in LLD builds
+  ([bug](https://llvm.org/PR48536),
+  [in-progress patch](https://reviews.llvm.org/D93609))
 - LLD-built Chromium.app crashes with `malloc: *** error for object
   0x7fa46941f20a: pointer being freed was not allocated` at starutp (FIXME:
   file bug)
 - LLD does not yet have any ARM support
   ([in-progress patch](https://reviews.llvm.org/D88629))
-- LLD cannot yet link swiftshader binaries ([bug](https://llvm.org/PR48332),
-  [in-progress patch](https://reviews.llvm.org/D93267)) --
-  need to locally hack up LLD to warn instead of error on this for now
 - LLD likely produces bad debug info, and LLD-linked binaries likely don't
   yet work in a debugger
 - LLD-linked base\_unittests fails some unwind-related tests
diff --git a/extensions/browser/api/device_permissions_prompt.cc b/extensions/browser/api/device_permissions_prompt.cc
index 74de730a..463ca83 100644
--- a/extensions/browser/api/device_permissions_prompt.cc
+++ b/extensions/browser/api/device_permissions_prompt.cc
@@ -290,7 +290,7 @@
 
   bool HasUnprotectedCollections(const device::mojom::HidDeviceInfo& device) {
     for (const auto& collection : device.collections) {
-      if (!device::IsProtected(*collection->usage)) {
+      if (!device::IsAlwaysProtected(*collection->usage)) {
         return true;
       }
     }
diff --git a/extensions/browser/api/hid/hid_apitest.cc b/extensions/browser/api/hid/hid_apitest.cc
index 27fe98dc..3b0f109 100644
--- a/extensions/browser/api/hid/hid_apitest.cc
+++ b/extensions/browser/api/hid/hid_apitest.cc
@@ -164,7 +164,10 @@
         serial_number, device::mojom::HidBusType::kHIDBusTypeUSB,
         report_descriptor, std::move(collections), has_report_id,
         max_input_report_size, max_output_report_size, max_feature_report_size,
-        "");
+        /*device_path=*/"",
+        /*protected_input_report_ids=*/std::vector<uint8_t>{},
+        /*protected_output_report_ids=*/std::vector<uint8_t>{},
+        /*protected_feature_report_ids=*/std::vector<uint8_t>{});
 
     fake_hid_manager_->AddDevice(std::move(device));
   }
diff --git a/extensions/browser/api/hid/hid_device_manager.cc b/extensions/browser/api/hid/hid_device_manager.cc
index 82c8db94..0abb3962 100644
--- a/extensions/browser/api/hid/hid_device_manager.cc
+++ b/extensions/browser/api/hid/hid_device_manager.cc
@@ -49,7 +49,7 @@
 
   for (const auto& collection : input.collections) {
     // Don't expose sensitive data.
-    if (device::IsProtected(*collection->usage)) {
+    if (device::IsAlwaysProtected(*collection->usage)) {
       continue;
     }
 
@@ -178,6 +178,7 @@
 
   hid_manager_->Connect(device_guid, /*connection_client=*/mojo::NullRemote(),
                         /*watcher=*/mojo::NullRemote(),
+                        /*allow_protected_reports=*/true,
                         mojo::WrapCallbackWithDefaultInvokeIfNotRun(
                             std::move(callback), mojo::NullRemote()));
 }
diff --git a/fuchsia/runners/cast/cast_component.h b/fuchsia/runners/cast/cast_component.h
index 0a127d8..e575e04 100644
--- a/fuchsia/runners/cast/cast_component.h
+++ b/fuchsia/runners/cast/cast_component.h
@@ -40,7 +40,7 @@
     bool AreComplete() const;
 
     // Parameters populated directly from the StartComponent() arguments.
-    std::unique_ptr<base::fuchsia::StartupContext> startup_context;
+    std::unique_ptr<base::StartupContext> startup_context;
     fidl::InterfaceRequest<fuchsia::sys::ComponentController>
         controller_request;
 
diff --git a/fuchsia/runners/cast/cast_runner.cc b/fuchsia/runners/cast/cast_runner.cc
index cd2db638..b84b3ffc 100644
--- a/fuchsia/runners/cast/cast_runner.cc
+++ b/fuchsia/runners/cast/cast_runner.cc
@@ -182,7 +182,7 @@
   // it to connect to the MetricsRecorder.
   static base::WeakPtr<const sys::ServiceDirectory>
   StartAndReturnIncomingServiceDirectory(
-      std::unique_ptr<base::fuchsia::StartupContext> startup_context,
+      std::unique_ptr<base::StartupContext> startup_context,
       fidl::InterfaceRequest<fuchsia::sys::ComponentController>
           controller_request,
       fuchsia::web::FrameHost* const frame_host_impl) {
@@ -194,11 +194,10 @@
   }
 
  private:
-  FrameHostComponent(
-      std::unique_ptr<base::fuchsia::StartupContext> startup_context,
-      fidl::InterfaceRequest<fuchsia::sys::ComponentController>
-          controller_request,
-      fuchsia::web::FrameHost* const frame_host_impl)
+  FrameHostComponent(std::unique_ptr<base::StartupContext> startup_context,
+                     fidl::InterfaceRequest<fuchsia::sys::ComponentController>
+                         controller_request,
+                     fuchsia::web::FrameHost* const frame_host_impl)
       : startup_context_(std::move(startup_context)),
         frame_host_binding_(startup_context_->outgoing(), frame_host_impl),
         weak_incoming_services_(startup_context_->svc()) {
@@ -215,7 +214,7 @@
     delete this;
   }
 
-  const std::unique_ptr<base::fuchsia::StartupContext> startup_context_;
+  const std::unique_ptr<base::StartupContext> startup_context_;
   const base::fuchsia::ScopedServiceBinding<fuchsia::web::FrameHost>
       frame_host_binding_;
   fidl::Binding<fuchsia::sys::ComponentController> binding_{this};
@@ -229,22 +228,20 @@
                            public chromium::cast::DataReset {
  public:
   // Creates a DataResetComponent with lifetime managed by |controller_request|.
-  static void Start(
-      base::OnceCallback<bool()> delete_persistent_data,
-      std::unique_ptr<base::fuchsia::StartupContext> startup_context,
-      fidl::InterfaceRequest<fuchsia::sys::ComponentController>
-          controller_request) {
+  static void Start(base::OnceCallback<bool()> delete_persistent_data,
+                    std::unique_ptr<base::StartupContext> startup_context,
+                    fidl::InterfaceRequest<fuchsia::sys::ComponentController>
+                        controller_request) {
     new DataResetComponent(std::move(delete_persistent_data),
                            std::move(startup_context),
                            std::move(controller_request));
   }
 
  private:
-  DataResetComponent(
-      base::OnceCallback<bool()> delete_persistent_data,
-      std::unique_ptr<base::fuchsia::StartupContext> startup_context,
-      fidl::InterfaceRequest<fuchsia::sys::ComponentController>
-          controller_request)
+  DataResetComponent(base::OnceCallback<bool()> delete_persistent_data,
+                     std::unique_ptr<base::StartupContext> startup_context,
+                     fidl::InterfaceRequest<fuchsia::sys::ComponentController>
+                         controller_request)
       : delete_persistent_data_(std::move(delete_persistent_data)),
         startup_context_(std::move(startup_context)),
         data_reset_handler_binding_(startup_context_->outgoing(), this) {
@@ -273,7 +270,7 @@
   }
 
   base::OnceCallback<bool()> delete_persistent_data_;
-  std::unique_ptr<base::fuchsia::StartupContext> startup_context_;
+  std::unique_ptr<base::StartupContext> startup_context_;
   const base::fuchsia::ScopedServiceBinding<chromium::cast::DataReset>
       data_reset_handler_binding_;
   fidl::Binding<fuchsia::sys::ComponentController> binding_{this};
@@ -336,7 +333,7 @@
   }
 
   auto startup_context =
-      std::make_unique<base::fuchsia::StartupContext>(std::move(startup_info));
+      std::make_unique<base::StartupContext>(std::move(startup_info));
 
   if (cors_exempt_headers_) {
     StartComponentInternal(cast_url, std::move(startup_context),
@@ -675,7 +672,7 @@
 
 void CastRunner::StartComponentInternal(
     const GURL& url,
-    std::unique_ptr<base::fuchsia::StartupContext> startup_context,
+    std::unique_ptr<base::StartupContext> startup_context,
     fidl::InterfaceRequest<fuchsia::sys::ComponentController>
         controller_request) {
   // TODO(crbug.com/1120914): Remove this once Component Framework v2 can be
diff --git a/fuchsia/runners/cast/cast_runner.h b/fuchsia/runners/cast/cast_runner.h
index 5159d95..e92b9dc 100644
--- a/fuchsia/runners/cast/cast_runner.h
+++ b/fuchsia/runners/cast/cast_runner.h
@@ -107,7 +107,7 @@
   // component URL and ensuring that CORS-exempt headers have been fetched.
   void StartComponentInternal(
       const GURL& url,
-      std::unique_ptr<base::fuchsia::StartupContext> startup_context,
+      std::unique_ptr<base::StartupContext> startup_context,
       fidl::InterfaceRequest<fuchsia::sys::ComponentController>
           controller_request);
 
diff --git a/fuchsia/runners/cast/pending_cast_component.cc b/fuchsia/runners/cast/pending_cast_component.cc
index f15ac9e0..aaf6b80 100644
--- a/fuchsia/runners/cast/pending_cast_component.cc
+++ b/fuchsia/runners/cast/pending_cast_component.cc
@@ -11,7 +11,7 @@
 
 PendingCastComponent::PendingCastComponent(
     Delegate* delegate,
-    std::unique_ptr<base::fuchsia::StartupContext> startup_context,
+    std::unique_ptr<base::StartupContext> startup_context,
     fidl::InterfaceRequest<fuchsia::sys::ComponentController>
         controller_request,
     base::StringPiece app_id)
diff --git a/fuchsia/runners/cast/pending_cast_component.h b/fuchsia/runners/cast/pending_cast_component.h
index 28c0735..dca6f7af 100644
--- a/fuchsia/runners/cast/pending_cast_component.h
+++ b/fuchsia/runners/cast/pending_cast_component.h
@@ -39,12 +39,11 @@
     virtual void CancelPendingComponent(PendingCastComponent* component) = 0;
   };
 
-  PendingCastComponent(
-      Delegate* delegate,
-      std::unique_ptr<base::fuchsia::StartupContext> startup_context,
-      fidl::InterfaceRequest<fuchsia::sys::ComponentController>
-          controller_request,
-      base::StringPiece app_id);
+  PendingCastComponent(Delegate* delegate,
+                       std::unique_ptr<base::StartupContext> startup_context,
+                       fidl::InterfaceRequest<fuchsia::sys::ComponentController>
+                           controller_request,
+                       base::StringPiece app_id);
   ~PendingCastComponent();
 
   PendingCastComponent(const PendingCastComponent&) = delete;
diff --git a/fuchsia/runners/common/web_component.cc b/fuchsia/runners/common/web_component.cc
index 8e5000a..73b7ad6 100644
--- a/fuchsia/runners/common/web_component.cc
+++ b/fuchsia/runners/common/web_component.cc
@@ -21,7 +21,7 @@
 WebComponent::WebComponent(
     base::StringPiece debug_name,
     WebContentRunner* runner,
-    std::unique_ptr<base::fuchsia::StartupContext> context,
+    std::unique_ptr<base::StartupContext> context,
     fidl::InterfaceRequest<fuchsia::sys::ComponentController>
         controller_request)
     : debug_name_(debug_name.as_string()),
diff --git a/fuchsia/runners/common/web_component.h b/fuchsia/runners/common/web_component.h
index 9216637f..97cd53b 100644
--- a/fuchsia/runners/common/web_component.h
+++ b/fuchsia/runners/common/web_component.h
@@ -43,7 +43,7 @@
   //   lifetime of this component instance.
   WebComponent(base::StringPiece debug_name,
                WebContentRunner* runner,
-               std::unique_ptr<base::fuchsia::StartupContext> context,
+               std::unique_ptr<base::StartupContext> context,
                fidl::InterfaceRequest<fuchsia::sys::ComponentController>
                    controller_request);
 
@@ -67,7 +67,7 @@
 
   // Returns the component's startup context (e.g. incoming services, public
   // service directory, etc).
-  base::fuchsia::StartupContext* startup_context() const {
+  base::StartupContext* startup_context() const {
     return startup_context_.get();
   }
 
@@ -108,7 +108,7 @@
   WebContentRunner* const runner_ = nullptr;
 
   // Component context for this instance, including incoming services.
-  const std::unique_ptr<base::fuchsia::StartupContext> startup_context_;
+  const std::unique_ptr<base::StartupContext> startup_context_;
 
   fuchsia::web::FramePtr frame_;
 
diff --git a/fuchsia/runners/common/web_content_runner.cc b/fuchsia/runners/common/web_content_runner.cc
index da13c89..9fdd51b 100644
--- a/fuchsia/runners/common/web_content_runner.cc
+++ b/fuchsia/runners/common/web_content_runner.cc
@@ -78,7 +78,7 @@
 
   std::unique_ptr<WebComponent> component = std::make_unique<WebComponent>(
       std::string(), this,
-      std::make_unique<base::fuchsia::StartupContext>(std::move(startup_info)),
+      std::make_unique<base::StartupContext>(std::move(startup_info)),
       std::move(controller_request));
 #if BUILDFLAG(WEB_RUNNER_REMOTE_DEBUGGING_PORT) != 0
   component->EnableRemoteDebugging();
diff --git a/gpu/ipc/service/pass_through_image_transport_surface.cc b/gpu/ipc/service/pass_through_image_transport_surface.cc
index 8db6374..6863844 100644
--- a/gpu/ipc/service/pass_through_image_transport_surface.cc
+++ b/gpu/ipc/service/pass_through_image_transport_surface.cc
@@ -8,14 +8,13 @@
 
 #include "base/bind.h"
 #include "base/callback_helpers.h"
-#include "base/command_line.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/metrics/histogram_macros_local.h"
 #include "build/build_config.h"
 #include "gpu/command_buffer/common/swap_buffers_complete_params.h"
 #include "ui/gfx/vsync_provider.h"
 #include "ui/gl/gl_context.h"
-#include "ui/gl/gl_switches.h"
+#include "ui/gl/gl_features.h"
 
 namespace gpu {
 
@@ -28,10 +27,6 @@
 int g_num_swaps_in_current_swap_generation_ = 0;
 int g_last_multi_window_swap_generation_ = 0;
 
-bool HasSwitch(const char switch_constant[]) {
-  return base::CommandLine::ForCurrentProcess()->HasSwitch(switch_constant);
-}
-
 }  // anonymous namespace
 
 PassThroughImageTransportSurface::PassThroughImageTransportSurface(
@@ -39,13 +34,12 @@
     gl::GLSurface* surface,
     bool override_vsync_for_multi_window_swap)
     : GLSurfaceAdapter(surface),
-      is_gpu_vsync_disabled_(HasSwitch(switches::kDisableGpuVsync)),
+      is_gpu_vsync_disabled_(!features::UseGpuVsync()),
       is_multi_window_swap_vsync_override_enabled_(
           override_vsync_for_multi_window_swap),
       delegate_(delegate) {}
 
-PassThroughImageTransportSurface::~PassThroughImageTransportSurface() {
-}
+PassThroughImageTransportSurface::~PassThroughImageTransportSurface() = default;
 
 bool PassThroughImageTransportSurface::Initialize(gl::GLSurfaceFormat format) {
   // The surface is assumed to have already been initialized.
diff --git a/headless/app/headless_shell.cc b/headless/app/headless_shell.cc
index 17e23c3..af0460d 100644
--- a/headless/app/headless_shell.cc
+++ b/headless/app/headless_shell.cc
@@ -51,6 +51,7 @@
 #include "ui/gfx/geometry/size.h"
 
 #if defined(OS_WIN)
+#include <base/win/windows_types.h>
 #include "components/crash/core/app/crash_switches.h"
 #include "components/crash/core/app/run_as_crashpad_handler_win.h"
 #include "sandbox/win/src/sandbox_types.h"
@@ -836,8 +837,15 @@
       builder.SetUserAgent(ua);
   }
 
-  exit(RunContentMain(builder.Build(),
-                      base::OnceCallback<void(HeadlessBrowser*)>()));
+  int rc = RunContentMain(builder.Build(),
+                          base::OnceCallback<void(HeadlessBrowser*)>());
+#if defined(OS_WIN)
+  // Use TerminateProcess instead of exit to avoid shutdown crashes and
+  // slowdowns on shutdown.
+  ::TerminateProcess(::GetCurrentProcess(), rc);
+#else   // defined(OS_WIN)
+  exit(rc);
+#endif  // defined(OS_WIN)
 }
 
 int HeadlessBrowserMain(
diff --git a/ios/chrome/browser/application_context_impl.h b/ios/chrome/browser/application_context_impl.h
index 15217f9..db51baea 100644
--- a/ios/chrome/browser/application_context_impl.h
+++ b/ios/chrome/browser/application_context_impl.h
@@ -95,10 +95,6 @@
   // Logger which observers and logs application wide events to
   // |breadcrumb_manager_|. Will be null if breadcrumbs feature is not enabled.
   std::unique_ptr<ApplicationBreadcrumbsLogger> application_breadcrumbs_logger_;
-  // Persistent storage manager to write breadcrumbs to disk for storage
-  // between sessions. Will be null if breadcrumbs feature is not enabled.
-  std::unique_ptr<BreadcrumbPersistentStorageManager>
-      breadcrumb_persistent_storage_manager_;
 
   // Must be destroyed after |local_state_|. BrowserStatePolicyConnector isn't a
   // keyed service because the pref service, which isn't a keyed service, has a
diff --git a/ios/chrome/browser/application_context_impl.mm b/ios/chrome/browser/application_context_impl.mm
index eb58be1..eef21df 100644
--- a/ios/chrome/browser/application_context_impl.mm
+++ b/ios/chrome/browser/application_context_impl.mm
@@ -165,11 +165,12 @@
     base::FilePath storage_dir;
     bool result = base::PathService::Get(ios::DIR_USER_DATA, &storage_dir);
     DCHECK(result);
-    breadcrumb_persistent_storage_manager_ =
+
+    auto breadcrumb_persistent_storage_manager =
         std::make_unique<BreadcrumbPersistentStorageManager>(storage_dir);
 
     application_breadcrumbs_logger_->SetPersistentStorageManager(
-        breadcrumb_persistent_storage_manager_.get());
+        std::move(breadcrumb_persistent_storage_manager));
   }
 }
 
@@ -207,6 +208,12 @@
     sessions::SessionIdGenerator::GetInstance()->Shutdown();
   }
 
+  // The ApplicationBreadcrumbsLogger tries to log event via a task when it
+  // is destroyed, so it needs to be notified of the app tear down now when
+  // the task tracker is still valid (will be destroyed after StartTearDown
+  // returns).
+  application_breadcrumbs_logger_.reset();
+
   ios_chrome_io_thread_->NetworkTearDown();
 }
 
@@ -466,7 +473,9 @@
 BreadcrumbPersistentStorageManager*
 ApplicationContextImpl::GetBreadcrumbPersistentStorageManager() {
   DCHECK(thread_checker_.CalledOnValidThread());
-  return breadcrumb_persistent_storage_manager_.get();
+  return application_breadcrumbs_logger_
+             ? application_breadcrumbs_logger_->GetPersistentStorageManager()
+             : nullptr;
 }
 
 void ApplicationContextImpl::SetApplicationLocale(const std::string& locale) {
diff --git a/ios/chrome/browser/crash_report/breadcrumbs/application_breadcrumbs_logger.h b/ios/chrome/browser/crash_report/breadcrumbs/application_breadcrumbs_logger.h
index c3d5004..08f7769 100644
--- a/ios/chrome/browser/crash_report/breadcrumbs/application_breadcrumbs_logger.h
+++ b/ios/chrome/browser/crash_report/breadcrumbs/application_breadcrumbs_logger.h
@@ -32,7 +32,12 @@
   // Sets a BreadcrumbPersistentStorageManager to persist application breadcrumb
   // events logged by this ApplicationBreadcrumbsLogger instance.
   void SetPersistentStorageManager(
-      BreadcrumbPersistentStorageManager* persistent_storage_manager);
+      std::unique_ptr<BreadcrumbPersistentStorageManager>
+          persistent_storage_manager);
+
+  // Returns a pointer to the BreadcrumbPersistentStorageManager owned by this
+  // instance. May be null.
+  BreadcrumbPersistentStorageManager* GetPersistentStorageManager() const;
 
  private:
   ApplicationBreadcrumbsLogger(const ApplicationBreadcrumbsLogger&) = delete;
@@ -58,9 +63,10 @@
   // Observes device orientation.
   id<NSObject> orientation_observer_;
 
-  // A weak reference to the persistent breadcrumb manager listening for events
+  // A strong pointer to the persistent breadcrumb manager listening for events
   // from |breadcrumb_manager_| to store to disk.
-  BreadcrumbPersistentStorageManager* persistent_storage_manager_ = nullptr;
+  std::unique_ptr<BreadcrumbPersistentStorageManager>
+      persistent_storage_manager_;
 
   // Used to avoid logging the same orientation twice.
   base::Optional<UIDeviceOrientation> last_orientation_;
diff --git a/ios/chrome/browser/crash_report/breadcrumbs/application_breadcrumbs_logger.mm b/ios/chrome/browser/crash_report/breadcrumbs/application_breadcrumbs_logger.mm
index ef8ae51..4698402 100644
--- a/ios/chrome/browser/crash_report/breadcrumbs/application_breadcrumbs_logger.mm
+++ b/ios/chrome/browser/crash_report/breadcrumbs/application_breadcrumbs_logger.mm
@@ -81,14 +81,20 @@
 }
 
 void ApplicationBreadcrumbsLogger::SetPersistentStorageManager(
-    BreadcrumbPersistentStorageManager* persistent_storage_manager) {
+    std::unique_ptr<BreadcrumbPersistentStorageManager>
+        persistent_storage_manager) {
   if (persistent_storage_manager_) {
     persistent_storage_manager_->StopMonitoringBreadcrumbManager(
         breadcrumb_manager_);
   }
 
-  persistent_storage_manager_ = persistent_storage_manager;
-  persistent_storage_manager->MonitorBreadcrumbManager(breadcrumb_manager_);
+  persistent_storage_manager_ = std::move(persistent_storage_manager);
+  persistent_storage_manager_->MonitorBreadcrumbManager(breadcrumb_manager_);
+}
+
+BreadcrumbPersistentStorageManager*
+ApplicationBreadcrumbsLogger::GetPersistentStorageManager() const {
+  return persistent_storage_manager_.get();
 }
 
 void ApplicationBreadcrumbsLogger::OnUserAction(const std::string& action,
diff --git a/ios/chrome/browser/prefs/browser_prefs.mm b/ios/chrome/browser/prefs/browser_prefs.mm
index 840deeac9..801b133 100644
--- a/ios/chrome/browser/prefs/browser_prefs.mm
+++ b/ios/chrome/browser/prefs/browser_prefs.mm
@@ -105,6 +105,9 @@
     "profile.was_pwm_onboarding_feature_checked_before";
 }
 
+// Deprecated 12/2020
+const char kDomainsWithCookiePref[] = "signin.domains_with_cookie";
+
 void RegisterLocalStatePrefs(PrefRegistrySimple* registry) {
   BrowserStateInfoCache::RegisterPrefs(registry);
   flags_ui::PrefServiceFlagsStorage::RegisterPrefs(registry);
@@ -244,6 +247,7 @@
 
   registry->RegisterIntegerPref(kPasswordManagerOnboardingState, 0);
   registry->RegisterBooleanPref(kWasOnboardingFeatureCheckedBefore, false);
+  registry->RegisterDictionaryPref(kDomainsWithCookiePref);
 }
 
 // This method should be periodically pruned of year+ old migrations.
@@ -293,4 +297,7 @@
   prefs->ClearPref(kPasswordManagerOnboardingState);
   prefs->ClearPref(kWasOnboardingFeatureCheckedBefore);
   prerender_prefs::MigrateNetworkPredictionPrefs(prefs);
+
+  // Added 12/2020.
+  prefs->ClearPref(kDomainsWithCookiePref);
 }
diff --git a/ios/chrome/browser/signin/account_consistency_service_factory.h b/ios/chrome/browser/signin/account_consistency_service_factory.h
index 248a99d..fe6513f 100644
--- a/ios/chrome/browser/signin/account_consistency_service_factory.h
+++ b/ios/chrome/browser/signin/account_consistency_service_factory.h
@@ -37,8 +37,6 @@
   ~AccountConsistencyServiceFactory() override;
 
   // BrowserStateKeyedServiceFactory:
-  void RegisterBrowserStatePrefs(
-      user_prefs::PrefRegistrySyncable* registry) override;
   std::unique_ptr<KeyedService> BuildServiceInstanceFor(
       web::BrowserState* context) const override;
 
diff --git a/ios/chrome/browser/signin/account_consistency_service_factory.mm b/ios/chrome/browser/signin/account_consistency_service_factory.mm
index 5eb675ac..be30317 100644
--- a/ios/chrome/browser/signin/account_consistency_service_factory.mm
+++ b/ios/chrome/browser/signin/account_consistency_service_factory.mm
@@ -7,7 +7,6 @@
 #include "base/no_destructor.h"
 #include "components/content_settings/core/browser/cookie_settings.h"
 #include "components/keyed_service/ios/browser_state_dependency_manager.h"
-#include "components/pref_registry/pref_registry_syncable.h"
 #include "components/signin/ios/browser/account_consistency_service.h"
 #include "ios/chrome/browser/browser_state/chrome_browser_state.h"
 #include "ios/chrome/browser/content_settings/cookie_settings_factory.h"
@@ -45,18 +44,13 @@
   return instance.get();
 }
 
-void AccountConsistencyServiceFactory::RegisterBrowserStatePrefs(
-    user_prefs::PrefRegistrySyncable* registry) {
-  AccountConsistencyService::RegisterPrefs(registry);
-}
-
 std::unique_ptr<KeyedService>
 AccountConsistencyServiceFactory::BuildServiceInstanceFor(
     web::BrowserState* context) const {
   ChromeBrowserState* chrome_browser_state =
       ChromeBrowserState::FromBrowserState(context);
   return std::make_unique<AccountConsistencyService>(
-      chrome_browser_state, chrome_browser_state->GetPrefs(),
+      chrome_browser_state,
       ios::AccountReconcilorFactory::GetForBrowserState(chrome_browser_state),
       ios::CookieSettingsFactory::GetForBrowserState(chrome_browser_state),
       IdentityManagerFactory::GetForBrowserState(chrome_browser_state));
diff --git a/ios/chrome/browser/sync/device_info_sync_service_factory.mm b/ios/chrome/browser/sync/device_info_sync_service_factory.mm
index b310d58f..db957af 100644
--- a/ios/chrome/browser/sync/device_info_sync_service_factory.mm
+++ b/ios/chrome/browser/sync/device_info_sync_service_factory.mm
@@ -52,10 +52,12 @@
   }
 
   // syncer::DeviceInfoSyncClient:
-  std::string GetFCMRegistrationToken() const override { return std::string(); }
+  base::Optional<std::string> GetFCMRegistrationToken() const override {
+    return std::string();
+  }
 
   // syncer::DeviceInfoSyncClient:
-  syncer::ModelTypeSet GetInterestedDataTypes() const override {
+  base::Optional<syncer::ModelTypeSet> GetInterestedDataTypes() const override {
     return syncer::ModelTypeSet();
   }
 
diff --git a/ios/chrome/browser/ui/main/scene_controller.mm b/ios/chrome/browser/ui/main/scene_controller.mm
index 0b811564..8f9184bf 100644
--- a/ios/chrome/browser/ui/main/scene_controller.mm
+++ b/ios/chrome/browser/ui/main/scene_controller.mm
@@ -1582,6 +1582,10 @@
   [self finishActivatingBrowserDismissingTabSwitcher:YES];
 }
 
+- (TabGridPage)activePageForTabGrid:(TabGridCoordinator*)tabGrid {
+  return self.activePage;
+}
+
 // Begins the process of activating the given current model, switching which BVC
 // is suspended if necessary. If |dismissTabSwitcher| is set, the tab switcher
 // will also be dismissed. Note that this means that a browser can be activated
diff --git a/ios/chrome/browser/ui/settings/settings_table_view_controller.mm b/ios/chrome/browser/ui/settings/settings_table_view_controller.mm
index 296d228..84c4b0f 100644
--- a/ios/chrome/browser/ui/settings/settings_table_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/settings_table_view_controller.mm
@@ -1438,7 +1438,8 @@
             [self.tableViewModel itemAtIndexPath:googleServicesCellIndexPath]);
     DCHECK(googleServicesItem);
     [self updateSyncAndGoogleServicesItem:googleServicesItem];
-    [self reconfigureCellsForItems:@[ googleServicesItem ]];
+    [self reloadCellsForItems:@[ googleServicesItem ]
+             withRowAnimation:UITableViewRowAnimationNone];
     return;
   }
 
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_coordinator.mm b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_coordinator.mm
index 161da22..32a1f03d 100644
--- a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_coordinator.mm
+++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_coordinator.mm
@@ -60,7 +60,8 @@
                                   RecentTabsContextMenuDelegate,
                                   RecentTabsPresentationDelegate,
                                   TabGridMediatorDelegate,
-                                  TabPresentationDelegate> {
+                                  TabPresentationDelegate,
+                                  TabGridViewControllerDelegate> {
   // Use an explicit ivar instead of synthesizing as the setter isn't using the
   // ivar.
   Browser* _incognitoBrowser;
@@ -252,7 +253,6 @@
     if (shouldCloseTabGrid) {
       [self.thumbStripCoordinator.panHandler setState:ViewRevealState::Hidden
                                              animated:YES];
-      [self.delegate tabGridDismissTransitionDidEnd:self];
 
       // Record when the tab switcher is dismissed.
       base::RecordAction(base::UserMetricsAction("MobileTabGridExited"));
@@ -334,6 +334,7 @@
   baseViewController.reauthHandler =
       HandlerForProtocol(self.dispatcher, IncognitoReauthCommands);
   baseViewController.tabPresentationDelegate = self;
+  baseViewController.delegate = self;
   _baseViewController = baseViewController;
 
   self.regularTabsMediator = [[TabGridMediator alloc]
@@ -559,6 +560,18 @@
   [self.actionSheetCoordinator start];
 }
 
+#pragma mark - TabGridViewControllerDelegate
+
+- (TabGridPage)activePageForTabGridViewController:
+    (TabGridViewController*)tabGridViewController {
+  return [self.delegate activePageForTabGrid:self];
+}
+
+- (void)tabGridViewControllerDidDismiss:
+    (TabGridViewController*)tabGridViewController {
+  [self.delegate tabGridDismissTransitionDidEnd:self];
+}
+
 #pragma mark - RecentTabsPresentationDelegate
 
 - (void)showHistoryFromRecentTabs {
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_coordinator_delegate.h b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_coordinator_delegate.h
index 7a198bd..f24c1c0 100644
--- a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_coordinator_delegate.h
+++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_coordinator_delegate.h
@@ -7,6 +7,8 @@
 
 #import <Foundation/Foundation.h>
 
+#import "ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_paging.h"
+
 class Browser;
 @class TabGridCoordinator;
 
@@ -26,6 +28,9 @@
 // Informs the delegate that the tab switcher is done and should be dismissed.
 - (void)tabGridDismissTransitionDidEnd:(TabGridCoordinator*)tabGrid;
 
+// Asks the delegate for the page that should currently be active.
+- (TabGridPage)activePageForTabGrid:(TabGridCoordinator*)tabGrid;
+
 @end
 
 #endif  // IOS_CHROME_BROWSER_UI_TAB_SWITCHER_TAB_GRID_TAB_GRID_COORDINATOR_DELEGATE_H_
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_coordinator_unittest.mm b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_coordinator_unittest.mm
index 921053e5..26cdc76 100644
--- a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_coordinator_unittest.mm
+++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_coordinator_unittest.mm
@@ -55,6 +55,10 @@
 - (void)tabGridDismissTransitionDidEnd:(TabGridCoordinator*)tabGrid {
   self.didEndCalled = YES;
 }
+
+- (TabGridPage)activePageForTabGrid:(TabGridCoordinator*)tabGrid {
+  return TabGridPageRegularTabs;
+}
 @end
 
 namespace {
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_view_controller.h b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_view_controller.h
index b8b6bddb..6e26c32 100644
--- a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_view_controller.h
+++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_view_controller.h
@@ -21,6 +21,7 @@
 @protocol GridImageDataSource;
 @protocol RecentTabsConsumer;
 @class RecentTabsTableViewController;
+@class TabGridViewController;
 
 // Delegate protocol for an object that can handle presenting ("opening") tabs
 // from the tab grid.
@@ -36,6 +37,19 @@
                closeTabGrid:(BOOL)closeTabGrid;
 @end
 
+@protocol TabGridViewControllerDelegate <NSObject>
+
+// Asks the delegate for the page that should currently be active.
+- (TabGridPage)activePageForTabGridViewController:
+    (TabGridViewController*)tabGridViewController;
+
+// Notifies the delegate that the tab grid was dismissed via the
+// ViewRevealingAnimatee.
+- (void)tabGridViewControllerDidDismiss:
+    (TabGridViewController*)tabGridViewController;
+
+@end
+
 // View controller representing a tab switcher. The tab switcher has an
 // incognito tab grid, regular tab grid, and remote tabs.
 @interface TabGridViewController
@@ -50,6 +64,8 @@
 // Delegate for this view controller to handle presenting tab UI.
 @property(nonatomic, weak) id<TabPresentationDelegate> tabPresentationDelegate;
 
+@property(nonatomic, weak) id<TabGridViewControllerDelegate> delegate;
+
 // Consumers send updates from the model layer to the UI layer.
 @property(nonatomic, readonly) id<GridConsumer> regularTabsConsumer;
 @property(nonatomic, readonly) id<GridConsumer, IncognitoReauthConsumer>
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_view_controller.mm b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_view_controller.mm
index 8cd42b9..28478ab 100644
--- a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_view_controller.mm
+++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_view_controller.mm
@@ -501,10 +501,20 @@
 #pragma mark - ViewRevealingAnimatee
 
 - (void)willAnimateViewReveal:(ViewRevealState)currentViewRevealState {
-  DCHECK_NE(TabGridPageRemoteTabs, self.currentPage);
   self.scrollView.scrollEnabled = NO;
   switch (currentViewRevealState) {
     case ViewRevealState::Hidden: {
+      // If the tab grid is just showing up, make sure that the active page is
+      // current. This can happen when the user closes the tab grid using the
+      // done button on RecentTabs. The current page would stay RecentTabs, but
+      // the active page comes from the currently displayed BVC.
+      if (self.delegate) {
+        self.activePage =
+            [self.delegate activePageForTabGridViewController:self];
+      }
+      if (self.currentPage != self.activePage) {
+        [self scrollToPage:self.activePage animated:NO];
+      }
       self.topToolbar.transform = CGAffineTransformMakeTranslation(
           0, [self hiddenTopToolbarYTranslation]);
       GridViewController* regularViewController =
@@ -580,9 +590,17 @@
 }
 
 - (void)didAnimateViewReveal:(ViewRevealState)viewRevealState {
-  if (viewRevealState == ViewRevealState::Revealed) {
-    self.scrollView.scrollEnabled = YES;
-    [self setInsetForRemoteTabs];
+  switch (viewRevealState) {
+    case ViewRevealState::Hidden:
+      [self.delegate tabGridViewControllerDidDismiss:self];
+      break;
+    case ViewRevealState::Peeked:
+      // No-op.
+      break;
+    case ViewRevealState::Revealed:
+      self.scrollView.scrollEnabled = YES;
+      [self setInsetForRemoteTabs];
+      break;
   }
 }
 
diff --git a/ios/chrome/credential_provider_extension/BUILD.gn b/ios/chrome/credential_provider_extension/BUILD.gn
index dece710..20e5944 100644
--- a/ios/chrome/credential_provider_extension/BUILD.gn
+++ b/ios/chrome/credential_provider_extension/BUILD.gn
@@ -64,6 +64,7 @@
     ":password_util",
     ":reauthentication_handler",
     ":system_strings",
+    "//base",
     "//ios/chrome/common/app_group",
     "//ios/chrome/common/app_group:client",
     "//ios/chrome/common/credential_provider",
diff --git a/ios/chrome/credential_provider_extension/credential_provider_view_controller.mm b/ios/chrome/credential_provider_extension/credential_provider_view_controller.mm
index 4fd6f3e..487da1b 100644
--- a/ios/chrome/credential_provider_extension/credential_provider_view_controller.mm
+++ b/ios/chrome/credential_provider_extension/credential_provider_view_controller.mm
@@ -6,11 +6,13 @@
 
 #import <Foundation/Foundation.h>
 
+#include "base/check.h"
 #include "ios/chrome/common/app_group/app_group_constants.h"
 #include "ios/chrome/common/app_group/app_group_metrics.h"
 #import "ios/chrome/common/credential_provider/archivable_credential_store.h"
 #import "ios/chrome/common/credential_provider/constants.h"
 #import "ios/chrome/common/credential_provider/credential.h"
+#import "ios/chrome/common/ui/colors/semantic_color_names.h"
 #import "ios/chrome/common/ui/confirmation_alert/confirmation_alert_action_handler.h"
 #import "ios/chrome/common/ui/reauthentication/reauthentication_module.h"
 #import "ios/chrome/credential_provider_extension/account_verification_provider.h"
@@ -51,6 +53,9 @@
 // Interface for verified that accounts are still valid.
 @property(nonatomic, strong) AccountVerificationProvider* accountVerificator;
 
+// Loading indicator used for user validation, which APIs can take a long time.
+@property(nonatomic, strong) UIActivityIndicatorView* activityIndicatorView;
+
 @end
 
 @implementation CredentialProviderViewController
@@ -191,11 +196,36 @@
   [self exitWithErrorCode:ASExtensionErrorCodeCredentialIdentityNotFound];
 }
 
+// Shows a loading indicator,
+- (void)showLoadingIndicator {
+  DCHECK(!self.activityIndicatorView);
+  self.activityIndicatorView = [[UIActivityIndicatorView alloc] init];
+  UIActivityIndicatorView* activityView = self.activityIndicatorView;
+  activityView.translatesAutoresizingMaskIntoConstraints = NO;
+  [self.view addSubview:activityView];
+  [NSLayoutConstraint activateConstraints:@[
+    [activityView.centerXAnchor
+        constraintEqualToAnchor:self.view.centerXAnchor],
+    [activityView.centerYAnchor
+        constraintEqualToAnchor:self.view.centerYAnchor],
+  ]];
+  [activityView startAnimating];
+  activityView.color = [UIColor colorNamed:kBlueColor];
+}
+
+// Hides the loading indicator.
+- (void)hideLoadingIndicator {
+  [self.activityIndicatorView removeFromSuperview];
+  self.activityIndicatorView = nil;
+}
+
 // Verifies that the user is still signed in.
 // Return NO in the completion when the user is no longer valid. YES otherwise.
 - (void)validateUserWithCompletion:(void (^)(BOOL))completion {
+  [self showLoadingIndicator];
   auto handler = ^(BOOL isValid) {
     dispatch_async(dispatch_get_main_queue(), ^{
+      [self hideLoadingIndicator];
       if (completion) {
         completion(isValid);
       }
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 1aa246cf..d9271f4 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 @@
-cfb6628631b0eade9a89c3a9e026153bcb0ff25b
\ No newline at end of file
+b5dedb7cb4c7611d82b69f2e7843b409f61a9279
\ 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 e60d062..6479cfe 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 @@
-f9b2d3ca7121aa9d58a0638b109902715c95f902
\ No newline at end of file
+3c61e68c3cfe577c7365ee95c69f0ae0b2dfe434
\ 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 77bd28f..21d7861 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 @@
-b307b9973183217fa47e4d6705850225c2f43b14
\ No newline at end of file
+9ecccb872f8243b3415ebc5935a7b4c826400eda
\ 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 eed9afe..29eb6f1 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 @@
-17af97771d1bcb3e9ccc18a0fbc691aed670c264
\ No newline at end of file
+e1a9bc6da2d1da66607e246664c0f15c6e9474b9
\ 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 09d7252..bf56d02 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 @@
-133c69e75363e84ce3c453776561f8a552355eee
\ No newline at end of file
+38f8bbfa89bb81a804a352261def2757c7991205
\ 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 3465aa5..37275356 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 @@
-0bc5985329576385fb0f8ba74334dea1f8a857f1
\ No newline at end of file
+df76f9fa52c23dafee812abd39a0a2f88b445e5a
\ 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 a925d08..2373179 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 @@
-f6564d72809e153e72815297ba2b55b54db3de19
\ No newline at end of file
+f0dc53a17470c060787852161291c153161d87f0
\ 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 5b1b32ef..04d4fc8 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 @@
-f3eeaa520ee1c07670e2a1c5ab83f938ed15d086
\ No newline at end of file
+6edc0e1e73546ecccc87603382627f8a29b3198b
\ No newline at end of file
diff --git a/ios/web_view/internal/sync/web_view_device_info_sync_service_factory.mm b/ios/web_view/internal/sync/web_view_device_info_sync_service_factory.mm
index 5cb3324..d095d046 100644
--- a/ios/web_view/internal/sync/web_view_device_info_sync_service_factory.mm
+++ b/ios/web_view/internal/sync/web_view_device_info_sync_service_factory.mm
@@ -46,10 +46,12 @@
   }
 
   // syncer::DeviceInfoSyncClient:
-  std::string GetFCMRegistrationToken() const override { return std::string(); }
+  base::Optional<std::string> GetFCMRegistrationToken() const override {
+    return std::string();
+  }
 
   // syncer::DeviceInfoSyncClient:
-  syncer::ModelTypeSet GetInterestedDataTypes() const override {
+  base::Optional<syncer::ModelTypeSet> GetInterestedDataTypes() const override {
     return syncer::ModelTypeSet();
   }
 
diff --git a/media/mojo/mojom/media_player.mojom b/media/mojo/mojom/media_player.mojom
index 1bd8dbc8..5e62b21f 100644
--- a/media/mojo/mojom/media_player.mojom
+++ b/media/mojo/mojom/media_player.mojom
@@ -12,9 +12,7 @@
 interface MediaPlayer {
   // Sends |observer| to the renderer process so that it can be established a
   // communication channel with the implementor of MediaPlayerObserver.
-  // TODO(crbug.com/1039252): Make this an AddMediaPlayerObserver method as soon
-  // as there is a HeapMojoRemoteSet class available for its use in Blink.
-  SetMediaPlayerObserver(pending_remote<MediaPlayerObserver> observer);
+  AddMediaPlayerObserver(pending_remote<MediaPlayerObserver> observer);
 
   // Requests the media player to start or resume media playback.
   RequestPlay();
diff --git a/mojo/public/cpp/bindings/remote_set.h b/mojo/public/cpp/bindings/remote_set.h
index fd6f9bbd..2e2c325 100644
--- a/mojo/public/cpp/bindings/remote_set.h
+++ b/mojo/public/cpp/bindings/remote_set.h
@@ -12,6 +12,8 @@
 #include "base/bind.h"
 #include "base/callback.h"
 #include "base/macros.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/sequenced_task_runner.h"
 #include "base/stl_util.h"
 #include "base/util/type_safety/id_type.h"
 #include "mojo/public/cpp/bindings/associated_remote.h"
@@ -99,9 +101,14 @@
     return id;
   }
 
-  // Same as above but for the equivalent pending remote type, for convenience.
-  RemoteSetElementId Add(PendingRemoteType<Interface> remote) {
-    return Add(RemoteType<Interface>(std::move(remote)));
+  // Same as above but for the equivalent pending remote type. If |task_runner|
+  // is null, the value of |base::SequencedTaskRunnerHandle::Get()| at the time
+  // of the |Add()| call will be used to run scheduled tasks for the remote.
+  RemoteSetElementId Add(
+      PendingRemoteType<Interface> remote,
+      scoped_refptr<base::SequencedTaskRunner> task_runner = nullptr) {
+    return Add(
+        RemoteType<Interface>(std::move(remote), std::move(task_runner)));
   }
 
   // Removes a remote from the set given |id|, if present.
diff --git a/pdf/out_of_process_instance.cc b/pdf/out_of_process_instance.cc
index 59c5a81..a30b1a6 100644
--- a/pdf/out_of_process_instance.cc
+++ b/pdf/out_of_process_instance.cc
@@ -18,6 +18,7 @@
 #include "base/bind.h"
 #include "base/callback.h"
 #include "base/feature_list.h"
+#include "base/location.h"
 #include "base/logging.h"
 #include "base/memory/weak_ptr.h"
 #include "base/metrics/histogram_functions.h"
@@ -27,6 +28,7 @@
 #include "base/strings/string_split.h"
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
 #include "base/values.h"
 #include "build/chromeos_buildflags.h"
 #include "net/base/escape.h"
@@ -243,7 +245,8 @@
 constexpr char kJSSetReadOnlyType[] = "setReadOnly";
 constexpr char kJSEnableReadOnly[] = "enableReadOnly";
 
-constexpr int kFindResultCooldownMs = 100;
+constexpr base::TimeDelta kFindResultCooldown =
+    base::TimeDelta::FromMilliseconds(100);
 
 // Do not save files with over 100 MB. This cap should be kept in sync with and
 // is also enforced in chrome/browser/resources/pdf/pdf_viewer.js.
@@ -256,7 +259,8 @@
 
 // A delay to wait between each accessibility page to keep the system
 // responsive.
-constexpr int kAccessibilityPageDelayMs = 100;
+constexpr base::TimeDelta kAccessibilityPageDelay =
+    base::TimeDelta::FromMilliseconds(100);
 
 constexpr double kMinZoom = 0.01;
 
@@ -528,10 +532,7 @@
 }  // namespace
 
 OutOfProcessInstance::OutOfProcessInstance(PP_Instance instance)
-    : pp::Instance(instance),
-      pp::Find_Private(this),
-      pp::Printing_Dev(this),
-      paint_manager_(this) {
+    : pp::Instance(instance), pp::Find_Private(this), pp::Printing_Dev(this) {
   pp::Module::Get()->AddPluginInterface(kPPPPdfInterface, &ppp_private);
   AddPerInstanceObject(kPPPPdfInterface, this);
 
@@ -767,7 +768,7 @@
     plugin_size_ = view_device_size;
     plugin_offset_ = view_rect.point();
 
-    paint_manager_.SetSize(SizeFromPPSize(view_device_size), device_scale_);
+    paint_manager().SetSize(SizeFromPPSize(view_device_size), device_scale_);
 
     const gfx::Size old_image_data_size = SizeFromPPSize(image_data_.size());
     gfx::Size new_image_data_size = PaintManager::GetNewContextSize(
@@ -867,11 +868,10 @@
   SendAccessibilityViewportInfo();
 
   // Schedule loading the first page.
-  pp::Module::Get()->core()->CallOnMainThread(
-      kAccessibilityPageDelayMs,
-      PPCompletionCallbackFromResultCallback(
-          base::BindOnce(&OutOfProcessInstance::SendNextAccessibilityPage,
-                         weak_factory_.GetWeakPtr())),
+  ScheduleTaskOnMainThread(
+      kAccessibilityPageDelay,
+      base::BindOnce(&OutOfProcessInstance::SendNextAccessibilityPage,
+                     weak_factory_.GetWeakPtr()),
       0);
 }
 
@@ -901,11 +901,10 @@
                                     pp_text_runs, pp_chars, page_objects);
 
   // Schedule loading the next page.
-  pp::Module::Get()->core()->CallOnMainThread(
-      kAccessibilityPageDelayMs,
-      PPCompletionCallbackFromResultCallback(
-          base::BindOnce(&OutOfProcessInstance::SendNextAccessibilityPage,
-                         weak_factory_.GetWeakPtr())),
+  ScheduleTaskOnMainThread(
+      kAccessibilityPageDelay,
+      base::BindOnce(&OutOfProcessInstance::SendNextAccessibilityPage,
+                     weak_factory_.GetWeakPtr()),
       page_index + 1);
 }
 
@@ -1210,12 +1209,12 @@
 
   gfx::Rect offset_rect(rect);
   offset_rect.Offset(VectorFromPPPoint(available_area_.point()));
-  paint_manager_.InvalidateRect(offset_rect);
+  paint_manager().InvalidateRect(offset_rect);
 }
 
 void OutOfProcessInstance::DidScroll(const gfx::Vector2d& offset) {
   if (!image_data_.is_null())
-    paint_manager_.ScrollRect(RectFromPPRect(available_area_), offset);
+    paint_manager().ScrollRect(RectFromPPRect(available_area_), offset);
 }
 
 void OutOfProcessInstance::ScrollToX(int x_in_screen_coords) {
@@ -1327,11 +1326,10 @@
   NumberOfFindResultsChanged(total, final_result);
   SetTickmarks(tickmarks_);
   recently_sent_find_update_ = true;
-  pp::Module::Get()->core()->CallOnMainThread(
-      kFindResultCooldownMs,
-      PPCompletionCallbackFromResultCallback(
-          base::BindOnce(&OutOfProcessInstance::ResetRecentlySentFindUpdate,
-                         weak_factory_.GetWeakPtr())),
+  ScheduleTaskOnMainThread(
+      kFindResultCooldown,
+      base::BindOnce(&OutOfProcessInstance::ResetRecentlySentFindUpdate,
+                     weak_factory_.GetWeakPtr()),
       0);
 }
 
@@ -1468,9 +1466,10 @@
     return;
   }
 
-  pp::Module::Get()->core()->CallOnMainThread(
-      0, PPCompletionCallbackFromResultCallback(base::BindOnce(
-             &OutOfProcessInstance::OnPrint, weak_factory_.GetWeakPtr())));
+  ScheduleTaskOnMainThread(base::TimeDelta(),
+                           base::BindOnce(&OutOfProcessInstance::OnPrint,
+                                          weak_factory_.GetWeakPtr()),
+                           0);
 }
 
 void OutOfProcessInstance::SubmitForm(const std::string& url,
@@ -1763,7 +1762,7 @@
   engine()->SetGrayscale(dict.Get(pp::Var(kJSPrintPreviewGrayscale)).AsBool());
   engine()->New(url_.c_str(), /*headers=*/nullptr);
 
-  paint_manager_.InvalidateRect(gfx::Rect(SizeFromPPSize(plugin_size_)));
+  paint_manager().InvalidateRect(gfx::Rect(SizeFromPPSize(plugin_size_)));
 }
 
 void OutOfProcessInstance::HandleSaveAttachmentMessage(
@@ -1961,9 +1960,9 @@
           (scroll_offset.y() - scroll_offset_at_last_raster_.y() * zoom_ratio));
     }
 
-    paint_manager_.SetTransform(zoom_ratio, PointFromPPPoint(pinch_center),
-                                pinch_vector + paint_offset + scroll_delta,
-                                true);
+    paint_manager().SetTransform(zoom_ratio, PointFromPPPoint(pinch_center),
+                                 pinch_vector + paint_offset + scroll_delta,
+                                 true);
     needs_reraster_ = false;
     return;
   }
@@ -1973,7 +1972,7 @@
     // that appear after zooming out.
     // On pinch end the scale is again 1.f and we request a reraster
     // in the new position.
-    paint_manager_.ClearTransform();
+    paint_manager().ClearTransform();
     last_bitmap_smaller_ = false;
     needs_reraster_ = true;
 
@@ -2021,7 +2020,7 @@
   }
 
   document_load_state_ = LOAD_STATE_FAILED;
-  paint_manager_.InvalidateRect(gfx::Rect(SizeFromPPSize(plugin_size_)));
+  paint_manager().InvalidateRect(gfx::Rect(SizeFromPPSize(plugin_size_)));
 
   // Send a progress value of -1 to indicate a failure.
   SendLoadingProgress(-1);
@@ -2130,7 +2129,7 @@
 
   if (document_size_.IsEmpty())
     return;
-  paint_manager_.InvalidateRect(gfx::Rect(SizeFromPPSize(plugin_size_)));
+  paint_manager().InvalidateRect(gfx::Rect(SizeFromPPSize(plugin_size_)));
 
   if (accessibility_state_ == ACCESSIBILITY_STATE_LOADED)
     SendAccessibilityViewportInfo();
@@ -2287,13 +2286,26 @@
   engine()->PostPaint();
 
   if (!deferred_invalidates_.empty()) {
-    pp::Module::Get()->core()->CallOnMainThread(
-        0, PPCompletionCallbackFromResultCallback(
-               base::BindOnce(&OutOfProcessInstance::InvalidateAfterPaintDone,
-                              weak_factory_.GetWeakPtr())));
+    ScheduleTaskOnMainThread(
+        base::TimeDelta(),
+        base::BindOnce(&OutOfProcessInstance::InvalidateAfterPaintDone,
+                       weak_factory_.GetWeakPtr()),
+        0);
   }
 }
 
+void OutOfProcessInstance::ScheduleTaskOnMainThread(
+    base::TimeDelta delay,
+    ResultCallback callback,
+    int32_t result,
+    const base::Location& from_here) {
+  int64_t delay_in_msec = delay.InMilliseconds();
+  DCHECK(delay_in_msec <= INT32_MAX);
+  pp::Module::Get()->core()->CallOnMainThread(
+      static_cast<int32_t>(delay_in_msec),
+      PPCompletionCallbackFromResultCallback(std::move(callback)), result);
+}
+
 void OutOfProcessInstance::ProcessPreviewPageInfo(const std::string& url,
                                                   int dest_page_index) {
   DCHECK(IsPrintPreview());
diff --git a/pdf/out_of_process_instance.h b/pdf/out_of_process_instance.h
index e3d94324..0a59798 100644
--- a/pdf/out_of_process_instance.h
+++ b/pdf/out_of_process_instance.h
@@ -16,8 +16,8 @@
 
 #include "base/callback.h"
 #include "base/containers/queue.h"
+#include "base/location.h"
 #include "base/memory/weak_ptr.h"
-#include "pdf/paint_manager.h"
 #include "pdf/pdf_view_plugin_base.h"
 #include "pdf/preview_mode_client.h"
 #include "ppapi/c/private/ppp_pdf.h"
@@ -163,6 +163,11 @@
   void OnPaint(const std::vector<gfx::Rect>& paint_rects,
                std::vector<PaintReadyRect>* ready,
                std::vector<gfx::Rect>* pending) override;
+  void ScheduleTaskOnMainThread(
+      base::TimeDelta delay,
+      ResultCallback callback,
+      int32_t result,
+      const base::Location& from_here = base::Location::Current()) override;
 
   // PreviewModeClient::Client:
   void PreviewDocumentLoadComplete() override;
@@ -390,8 +395,6 @@
   // True if the plugin is full-page.
   bool full_ = false;
 
-  PaintManager paint_manager_;
-
   // True if we haven't painted the plugin viewport yet.
   bool first_paint_ = true;
   // Whether OnPaint() is in progress or not.
diff --git a/pdf/paint_manager.cc b/pdf/paint_manager.cc
index 47d896a..fe08ac9 100644
--- a/pdf/paint_manager.cc
+++ b/pdf/paint_manager.cc
@@ -13,6 +13,7 @@
 #include "base/bind.h"
 #include "base/callback.h"
 #include "base/check.h"
+#include "base/time/time.h"
 #include "pdf/paint_ready_rect.h"
 #include "pdf/ppapi_migration/callback.h"
 #include "pdf/ppapi_migration/geometry_conversions.h"
@@ -161,10 +162,10 @@
   if (manual_callback_pending_)
     return;
 
-  pp::Module::Get()->core()->CallOnMainThread(
-      0,
-      PPCompletionCallbackFromResultCallback(base::BindOnce(
-          &PaintManager::OnManualCallbackComplete, weak_factory_.GetWeakPtr())),
+  client_->ScheduleTaskOnMainThread(
+      base::TimeDelta(),
+      base::BindOnce(&PaintManager::OnManualCallbackComplete,
+                     weak_factory_.GetWeakPtr()),
       0);
   manual_callback_pending_ = true;
 }
diff --git a/pdf/paint_manager.h b/pdf/paint_manager.h
index a630811..06e91f3 100644
--- a/pdf/paint_manager.h
+++ b/pdf/paint_manager.h
@@ -10,10 +10,16 @@
 #include <memory>
 #include <vector>
 
+#include "base/location.h"
 #include "base/memory/weak_ptr.h"
 #include "pdf/paint_aggregator.h"
+#include "pdf/ppapi_migration/callback.h"
 #include "ui/gfx/geometry/size.h"
 
+namespace base {
+class TimeDelta;
+}  // namespace base
+
 namespace gfx {
 class Point;
 class Rect;
@@ -66,6 +72,18 @@
                          std::vector<PaintReadyRect>* ready,
                          std::vector<gfx::Rect>* pending) = 0;
 
+    // Schedules work to be executed on a main thread after a specific delay.
+    // The `result` parameter will be passed as the argument to the `callback`.
+    // `result` is needed sometimes to emulate calls of some callbacks, but it's
+    // not always needed. `delay` should be no longer than `INT32_MAX`
+    // milliseconds for the Pepper plugin implementation to prevent integer
+    // overflow.
+    virtual void ScheduleTaskOnMainThread(
+        base::TimeDelta delay,
+        ResultCallback callback,
+        int32_t result,
+        const base::Location& from_here = base::Location::Current()) = 0;
+
    protected:
     // You shouldn't be doing deleting through this interface.
     ~Client() = default;
diff --git a/pdf/pdf_engine.h b/pdf/pdf_engine.h
index 392c0576..9017dee0 100644
--- a/pdf/pdf_engine.h
+++ b/pdf/pdf_engine.h
@@ -13,6 +13,7 @@
 
 #include "base/callback.h"
 #include "base/containers/span.h"
+#include "base/location.h"
 #include "base/optional.h"
 #include "base/strings/string16.h"
 #include "base/time/time.h"
@@ -20,6 +21,7 @@
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
 #include "pdf/document_layout.h"
+#include "pdf/ppapi_migration/callback.h"
 #include "ppapi/c/dev/pp_cursor_type_dev.h"
 #include "ppapi/c/dev/ppp_printing_dev.h"
 #include "ppapi/cpp/completion_callback.h"
@@ -286,6 +288,18 @@
     // viewers.
     // See https://crbug.com/312882 for an example.
     virtual bool IsValidLink(const std::string& url) = 0;
+
+    // Schedules work to be executed on a main thread after a specific delay.
+    // The `result` parameter will be passed as the argument to the `callback`.
+    // `result` is needed sometimes to emulate calls of some callbacks, but it's
+    // not always needed. `delay` should be no longer than `INT32_MAX`
+    // milliseconds for the Pepper plugin implementation to prevent integer
+    // overflow.
+    virtual void ScheduleTaskOnMainThread(
+        base::TimeDelta delay,
+        ResultCallback callback,
+        int32_t result,
+        const base::Location& from_here = base::Location::Current()) = 0;
   };
 
   struct AccessibilityLinkInfo {
diff --git a/pdf/pdf_view_plugin_base.h b/pdf/pdf_view_plugin_base.h
index dd5677f..e9ecfa0b 100644
--- a/pdf/pdf_view_plugin_base.h
+++ b/pdf/pdf_view_plugin_base.h
@@ -42,6 +42,8 @@
 
   PDFiumEngine* engine() { return engine_.get(); }
 
+  PaintManager& paint_manager() { return paint_manager_; }
+
   // Starts loading `url`. If `is_print_preview` is `true`, load for print
   // preview instead of normal PDF viewing.
   void LoadUrl(const std::string& url, bool is_print_preview);
@@ -62,6 +64,7 @@
 
  private:
   std::unique_ptr<PDFiumEngine> engine_;
+  PaintManager paint_manager_{this};
 };
 
 }  // namespace chrome_pdf
diff --git a/pdf/pdf_view_web_plugin.cc b/pdf/pdf_view_web_plugin.cc
index 4143faa..8c246c4 100644
--- a/pdf/pdf_view_web_plugin.cc
+++ b/pdf/pdf_view_web_plugin.cc
@@ -12,9 +12,11 @@
 #include <vector>
 
 #include "base/check_op.h"
+#include "base/location.h"
 #include "base/notreached.h"
 #include "base/thread_annotations.h"
 #include "base/threading/thread_checker.h"
+#include "base/time/time.h"
 #include "cc/paint/paint_canvas.h"
 #include "net/cookies/site_for_cookies.h"
 #include "pdf/pdf_engine.h"
@@ -329,6 +331,15 @@
   NOTIMPLEMENTED_LOG_ONCE();
 }
 
+void PdfViewWebPlugin::ScheduleTaskOnMainThread(
+    base::TimeDelta delay,
+    ResultCallback callback,
+    int32_t result,
+    const base::Location& from_here) {
+  base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+      from_here, base::BindOnce(std::move(callback), result), delay);
+}
+
 bool PdfViewWebPlugin::IsValid() const {
   return container_ && container_->GetDocument().GetFrame();
 }
diff --git a/pdf/pdf_view_web_plugin.h b/pdf/pdf_view_web_plugin.h
index 00ecc52..ff329c4 100644
--- a/pdf/pdf_view_web_plugin.h
+++ b/pdf/pdf_view_web_plugin.h
@@ -5,8 +5,8 @@
 #ifndef PDF_PDF_VIEW_WEB_PLUGIN_H_
 #define PDF_PDF_VIEW_WEB_PLUGIN_H_
 
+#include "base/location.h"
 #include "base/memory/weak_ptr.h"
-#include "pdf/paint_manager.h"
 #include "pdf/pdf_view_plugin_base.h"
 #include "pdf/ppapi_migration/url_loader.h"
 #include "third_party/blink/public/web/web_plugin.h"
@@ -109,6 +109,11 @@
   void OnPaint(const std::vector<gfx::Rect>& paint_rects,
                std::vector<PaintReadyRect>* ready,
                std::vector<gfx::Rect>* pending) override;
+  void ScheduleTaskOnMainThread(
+      base::TimeDelta delay,
+      ResultCallback callback,
+      int32_t result,
+      const base::Location& from_here = base::Location::Current()) override;
 
   // BlinkUrlLoader::Client:
   bool IsValid() const override;
@@ -134,8 +139,6 @@
   blink::WebPluginParams initial_params_;
   blink::WebPluginContainer* container_ = nullptr;
 
-  PaintManager paint_manager_{this};
-
   // The background color of the PDF viewer.
   uint32_t background_color_ = 0;
   // The blank space above the first page of the document reserved for the
diff --git a/pdf/pdfium/pdfium_engine.cc b/pdf/pdfium/pdfium_engine.cc
index 28e3ae9..baa08e3 100644
--- a/pdf/pdfium/pdfium_engine.cc
+++ b/pdf/pdfium/pdfium_engine.cc
@@ -26,6 +26,7 @@
 #include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/threading/thread_task_runner_handle.h"
+#include "base/time/time.h"
 #include "build/build_config.h"
 #include "gin/array_buffer.h"
 #include "gin/public/gin_embedders.h"
@@ -1745,10 +1746,10 @@
   if (doc_loader_set_for_testing_) {
     ContinueFind(case_sensitive ? 1 : 0);
   } else {
-    pp::Module::Get()->core()->CallOnMainThread(
-        0,
-        PPCompletionCallbackFromResultCallback(base::BindOnce(
-            &PDFiumEngine::ContinueFind, find_weak_factory_.GetWeakPtr())),
+    client_->ScheduleTaskOnMainThread(
+        base::TimeDelta(),
+        base::BindOnce(&PDFiumEngine::ContinueFind,
+                       find_weak_factory_.GetWeakPtr()),
         case_sensitive ? 1 : 0);
   }
 }
diff --git a/pdf/preview_mode_client.cc b/pdf/preview_mode_client.cc
index bdafe06..850ef83 100644
--- a/pdf/preview_mode_client.cc
+++ b/pdf/preview_mode_client.cc
@@ -11,7 +11,9 @@
 #include <utility>
 
 #include "base/callback.h"
+#include "base/location.h"
 #include "base/notreached.h"
+#include "base/time/time.h"
 #include "pdf/document_layout.h"
 #include "pdf/ppapi_migration/url_loader.h"
 
@@ -178,4 +180,12 @@
   return false;
 }
 
+void PreviewModeClient::ScheduleTaskOnMainThread(
+    base::TimeDelta delay,
+    ResultCallback callback,
+    int32_t result,
+    const base::Location& from_here) {
+  NOTREACHED();
+}
+
 }  // namespace chrome_pdf
diff --git a/pdf/preview_mode_client.h b/pdf/preview_mode_client.h
index a5250c4..cc8b289 100644
--- a/pdf/preview_mode_client.h
+++ b/pdf/preview_mode_client.h
@@ -11,6 +11,7 @@
 #include <vector>
 
 #include "base/callback_forward.h"
+#include "base/location.h"
 #include "pdf/pdf_engine.h"
 
 namespace gfx {
@@ -78,6 +79,11 @@
   void SetSelectedText(const std::string& selected_text) override;
   void SetLinkUnderCursor(const std::string& link_under_cursor) override;
   bool IsValidLink(const std::string& url) override;
+  void ScheduleTaskOnMainThread(
+      base::TimeDelta delay,
+      ResultCallback callback,
+      int32_t result,
+      const base::Location& from_here = base::Location::Current()) override;
 
  private:
   Client* const client_;
diff --git a/pdf/test/test_client.cc b/pdf/test/test_client.cc
index 2fd87a42..55b8e40 100644
--- a/pdf/test/test_client.cc
+++ b/pdf/test/test_client.cc
@@ -6,6 +6,8 @@
 
 #include <memory>
 
+#include "base/location.h"
+#include "base/time/time.h"
 #include "pdf/document_layout.h"
 #include "pdf/ppapi_migration/url_loader.h"
 
@@ -71,4 +73,9 @@
   return !url.empty();
 }
 
+void TestClient::ScheduleTaskOnMainThread(base::TimeDelta delay,
+                                          ResultCallback callback,
+                                          int32_t result,
+                                          const base::Location& from_here) {}
+
 }  // namespace chrome_pdf
diff --git a/pdf/test/test_client.h b/pdf/test/test_client.h
index 6f62f17d..bd0a9c0 100644
--- a/pdf/test/test_client.h
+++ b/pdf/test/test_client.h
@@ -8,6 +8,7 @@
 #include <string>
 #include <vector>
 
+#include "base/location.h"
 #include "pdf/pdf_engine.h"
 
 namespace chrome_pdf {
@@ -41,6 +42,11 @@
   void SetSelectedText(const std::string& selected_text) override;
   void SetLinkUnderCursor(const std::string& link_under_cursor) override;
   bool IsValidLink(const std::string& url) override;
+  void ScheduleTaskOnMainThread(
+      base::TimeDelta delay,
+      ResultCallback callback,
+      int32_t result,
+      const base::Location& from_here = base::Location::Current()) override;
 
  private:
   // Not owned. Expected to dangle briefly, as the engine usually is destroyed
diff --git a/remoting/host/linux/linux_me2me_host.py b/remoting/host/linux/linux_me2me_host.py
index 42d62f3..9b49a9f 100755
--- a/remoting/host/linux/linux_me2me_host.py
+++ b/remoting/host/linux/linux_me2me_host.py
@@ -101,6 +101,7 @@
 CONFIG_DIR = os.path.join(HOME_DIR, ".config/chrome-remote-desktop")
 SESSION_FILE_PATH = os.path.join(HOME_DIR, ".chrome-remote-desktop-session")
 SYSTEM_SESSION_FILE_PATH = "/etc/chrome-remote-desktop-session"
+SYSTEM_PRE_SESSION_FILE_PATH = "/etc/chrome-remote-desktop-pre-session"
 
 DEBIAN_XSESSION_PATH = "/etc/X11/Xsession"
 
@@ -388,13 +389,16 @@
 
 
 class SessionOutputFilterThread(threading.Thread):
-  """Reads session log from a pipe and logs the output for amount of time
-  defined by SESSION_OUTPUT_TIME_LIMIT_SECONDS."""
+  """Reads session log from a pipe and logs the output with the provided prefix
+  for amount of time defined by time_limit, or indefinitely if time_limit is
+  None."""
 
-  def __init__(self, stream):
+  def __init__(self, stream, prefix, time_limit):
     threading.Thread.__init__(self)
     self.stream = stream
     self.daemon = True
+    self.prefix = prefix
+    self.time_limit = time_limit
 
   def run(self):
     started_time = time.time()
@@ -413,13 +417,12 @@
       if not is_logging:
         continue
 
-      if time.time() - started_time >= SESSION_OUTPUT_TIME_LIMIT_SECONDS:
+      if self.time_limit and time.time() - started_time >= self.time_limit:
         is_logging = False
         print("Suppressing rest of the session output.", flush=True)
       else:
         # Pass stream bytes through as is instead of decoding and encoding.
-        sys.stdout.buffer.write(
-            "Session output: ".encode(sys.stdout.encoding) + line);
+        sys.stdout.buffer.write(self.prefix.encode(sys.stdout.encoding) + line);
         sys.stdout.flush()
 
 
@@ -428,6 +431,7 @@
 
   def __init__(self, sizes):
     self.x_proc = None
+    self.pre_session_proc = None
     self.session_proc = None
     self.host_proc = None
     self.child_env = None
@@ -719,7 +723,32 @@
     subprocess.Popen(args, env=self.child_env, stdout=subprocess.DEVNULL,
                      stderr=subprocess.DEVNULL)
 
-  def _launch_x_session(self):
+  def _launch_pre_session(self):
+    # Launch the pre-session script, if it exists. Returns true if the script
+    # was launched, false if it didn't exist.
+    if os.path.exists(SYSTEM_PRE_SESSION_FILE_PATH):
+      pre_session_command = bash_invocation_for_script(
+          SYSTEM_PRE_SESSION_FILE_PATH)
+
+      logging.info("Launching pre-session: %s" % pre_session_command)
+      self.pre_session_proc = subprocess.Popen(pre_session_command,
+                                               stdin=subprocess.DEVNULL,
+                                               stdout=subprocess.PIPE,
+                                               stderr=subprocess.STDOUT,
+                                               cwd=HOME_DIR,
+                                               env=self.child_env)
+
+      if not self.pre_session_proc.pid:
+        raise Exception("Could not start pre-session")
+
+      output_filter_thread = SessionOutputFilterThread(
+          self.pre_session_proc.stdout, "Pre-session output: ", None)
+      output_filter_thread.start()
+
+      return True
+    return False
+
+  def launch_x_session(self):
     # Start desktop session.
     # The /dev/null input redirection is necessary to prevent the X session
     # reading from stdin.  If this code runs as a shell background job in a
@@ -738,18 +767,21 @@
                                          cwd=HOME_DIR,
                                          env=self.child_env)
 
-    output_filter_thread = SessionOutputFilterThread(self.session_proc.stdout)
-    output_filter_thread.start()
-
     if not self.session_proc.pid:
       raise Exception("Could not start X session")
 
+    output_filter_thread = SessionOutputFilterThread(self.session_proc.stdout,
+        "Session output: ", SESSION_OUTPUT_TIME_LIMIT_SECONDS)
+    output_filter_thread.start()
+
   def launch_session(self, x_args):
     self._init_child_env()
     self._setup_pulseaudio()
     self._setup_gnubby()
     self._launch_x_server(x_args)
-    self._launch_x_session()
+    if not self._launch_pre_session():
+      # If there was no pre-session script, launch the session immediately.
+      self.launch_x_session()
 
   def launch_host(self, host_config, extra_start_host_args):
     # Start remoting host
@@ -797,6 +829,7 @@
     SIGKILL if a process doesn't exit within 10 seconds.
     """
     for proc, name in [(self.x_proc, "X server"),
+                       (self.pre_session_proc, "pre-session"),
                        (self.session_proc, "session"),
                        (self.host_proc, "host")]:
       if proc is not None:
@@ -814,6 +847,7 @@
         except psutil.Error:
           logging.error("Error terminating process")
     self.x_proc = None
+    self.pre_session_proc = None
     self.session_proc = None
     self.host_proc = None
 
@@ -926,6 +960,20 @@
   return non_child_process if not require_child_process else None
 
 
+def bash_invocation_for_script(script):
+  """Chooses the appropriate bash command to run the provided script."""
+  if os.path.exists(script):
+    if os.access(script, os.X_OK):
+      # "/bin/sh -c" is smart about how to execute the session script and
+      # works in cases where plain exec() fails (for example, if the file is
+      # marked executable, but is a plain script with no shebang line).
+      return ["/bin/sh", "-c", pipes.quote(script)]
+    else:
+      # If this is a system-wide session script, it should be run using the
+      # system shell, ignoring any login shell that might be set for the
+      # current user.
+      return ["/bin/sh", script]
+
 def choose_x_session():
   """Chooses the most appropriate X session command for this system.
 
@@ -941,16 +989,7 @@
   for startup_file in XSESSION_FILES:
     startup_file = os.path.expanduser(startup_file)
     if os.path.exists(startup_file):
-      if os.access(startup_file, os.X_OK):
-        # "/bin/sh -c" is smart about how to execute the session script and
-        # works in cases where plain exec() fails (for example, if the file is
-        # marked executable, but is a plain script with no shebang line).
-        return ["/bin/sh", "-c", pipes.quote(startup_file)]
-      else:
-        # If this is a system-wide session script, it should be run using the
-        # system shell, ignoring any login shell that might be set for the
-        # current user.
-        return ["/bin/sh", startup_file]
+      return bash_invocation_for_script(startup_file)
 
   # If there's no configuration, show the user a session chooser.
   return [HOST_BINARY_PATH, "--type=xsession_chooser"]
@@ -1708,7 +1747,8 @@
       # launching things in the wrong order due to differing relaunch times.
       logging.info("Waiting before relaunching")
     else:
-      if desktop.x_proc is None and desktop.session_proc is None:
+      if (desktop.x_proc is None and desktop.pre_session_proc is None and
+          desktop.session_proc is None):
         logging.info("Launching X server and X session.")
         desktop.launch_session(options.args)
         x_server_inhibitor.record_started(MINIMUM_PROCESS_LIFETIME,
@@ -1742,6 +1782,25 @@
       x_server_inhibitor.record_stopped(False)
       tear_down = True
 
+    if (desktop.pre_session_proc is not None and
+        pid == desktop.pre_session_proc.pid):
+      desktop.pre_session_proc = None
+      if status == 0:
+        logging.info("Pre-session terminated successfully. Starting session.")
+        desktop.launch_x_session()
+      else:
+        logging.info("Pre-session failed. Tearing down.")
+        # The pre-session may have exited on its own or been brought down by the
+        # X server dying. Check if the X server is still running so we know whom
+        # to penalize.
+        if desktop.check_x_responding():
+          # Pre-session and session use the same inhibitor.
+          session_inhibitor.record_stopped(False)
+        else:
+          x_server_inhibitor.record_stopped(False)
+        # Either way, we want to tear down the session.
+        tear_down = True
+
     if desktop.session_proc is not None and pid == desktop.session_proc.pid:
       logging.info("Session process terminated")
       desktop.session_proc = None
diff --git a/remoting/protocol/webrtc_transport_unittest.cc b/remoting/protocol/webrtc_transport_unittest.cc
index edc2fef..10ff018 100644
--- a/remoting/protocol/webrtc_transport_unittest.cc
+++ b/remoting/protocol/webrtc_transport_unittest.cc
@@ -495,7 +495,8 @@
 }
 
 TEST_F(WebrtcTransportTest,
-       ThreadJoinNotBlockedDuringConnectionTeardown_WatchdogNotFired) {
+       DISABLED_ThreadJoinNotBlockedDuringConnectionTeardown_WatchdogNotFired) {
+  // Test disabled for crbug.com/1160702
   InitializeConnection();
 
   int counter = 2;
diff --git a/services/device/BUILD.gn b/services/device/BUILD.gn
index 7c36810c..2f56d1d 100644
--- a/services/device/BUILD.gn
+++ b/services/device/BUILD.gn
@@ -162,6 +162,7 @@
     "//base/test:test_support",
     "//base/third_party/dynamic_annotations",
     "//build:chromeos_buildflags",
+    "//components/variations",
     "//device/base/synchronization",
     "//mojo/public/cpp/bindings",
     "//net",
@@ -245,8 +246,10 @@
     sources += [
       "battery/battery_monitor_impl_unittest.cc",
       "hid/hid_manager_unittest.cc",
+      "public/cpp/hid/hid_blocklist_unittest.cc",
     ]
     deps += [
+      "//components/variations:test_support",
       "//services/device/battery",
       "//services/device/hid:mocks",
       "//services/device/public/cpp/hid",
diff --git a/services/device/DEPS b/services/device/DEPS
index 243aae8..12e8777 100644
--- a/services/device/DEPS
+++ b/services/device/DEPS
@@ -1,6 +1,7 @@
 include_rules = [
   "+chromeos/crosapi",
   "+chromeos/lacros",
+  "+components/variations",
   "+device",
   "+services/device/usb/jni_headers",
   "+services/network/public/cpp",
diff --git a/services/device/hid/hid_connection.cc b/services/device/hid/hid_connection.cc
index c513132..8319493 100644
--- a/services/device/hid/hid_connection.cc
+++ b/services/device/hid/hid_connection.cc
@@ -36,9 +36,9 @@
 };
 
 // Functor returning true if collection has a protected usage.
-struct CollectionIsProtected {
+struct CollectionIsAlwaysProtected {
   bool operator()(const mojom::HidCollectionInfoPtr& info) const {
-    return IsProtected(*info->usage);
+    return IsAlwaysProtected(*info->usage);
   }
 };
 
@@ -55,18 +55,21 @@
   return nullptr;
 }
 
-bool HasProtectedCollection(
+bool HasAlwaysProtectedCollection(
     const std::vector<mojom::HidCollectionInfoPtr>& collections) {
   return std::find_if(collections.begin(), collections.end(),
-                      CollectionIsProtected()) != collections.end();
+                      CollectionIsAlwaysProtected()) != collections.end();
 }
 
 }  // namespace
 
-HidConnection::HidConnection(scoped_refptr<HidDeviceInfo> device_info)
-    : device_info_(device_info), closed_(false) {
-  has_protected_collection_ =
-      HasProtectedCollection(device_info->collections());
+HidConnection::HidConnection(scoped_refptr<HidDeviceInfo> device_info,
+                             bool allow_protected_reports)
+    : device_info_(device_info),
+      allow_protected_reports_(allow_protected_reports),
+      closed_(false) {
+  has_always_protected_collection_ =
+      HasAlwaysProtectedCollection(device_info->collections());
 }
 
 HidConnection::~HidConnection() {
@@ -125,7 +128,7 @@
     std::move(callback).Run(false);
     return;
   }
-  if (IsReportIdProtected(report_id)) {
+  if (IsReportIdProtected(report_id, kOutput)) {
     HID_LOG(USER) << "Attempt to set a protected output report.";
     std::move(callback).Run(false);
     return;
@@ -146,7 +149,7 @@
     std::move(callback).Run(false, nullptr, 0);
     return;
   }
-  if (IsReportIdProtected(report_id)) {
+  if (IsReportIdProtected(report_id, kFeature)) {
     HID_LOG(USER) << "Attempt to get a protected feature report.";
     std::move(callback).Run(false, nullptr, 0);
     return;
@@ -171,7 +174,7 @@
     std::move(callback).Run(false);
     return;
   }
-  if (IsReportIdProtected(report_id)) {
+  if (IsReportIdProtected(report_id, kFeature)) {
     HID_LOG(USER) << "Attempt to set a protected feature report.";
     std::move(callback).Run(false);
     return;
@@ -180,14 +183,40 @@
   PlatformSendFeatureReport(buffer, std::move(callback));
 }
 
-bool HidConnection::IsReportIdProtected(uint8_t report_id) {
+bool HidConnection::IsReportIdProtected(uint8_t report_id,
+                                        HidReportType report_type) {
+  if (!allow_protected_reports_) {
+    // Deny access to reports that match HID blocklist rules.
+    if (report_type == kInput) {
+      if (device_info_->device()->protected_input_report_ids.has_value() &&
+          base::Contains(*device_info_->device()->protected_input_report_ids,
+                         report_id)) {
+        return true;
+      }
+    } else if (report_type == kOutput) {
+      if (device_info_->device()->protected_output_report_ids.has_value() &&
+          base::Contains(*device_info_->device()->protected_output_report_ids,
+                         report_id)) {
+        return true;
+      }
+    } else if (report_type == kFeature) {
+      if (device_info_->device()->protected_feature_report_ids.has_value() &&
+          base::Contains(*device_info_->device()->protected_feature_report_ids,
+                         report_id)) {
+        return true;
+      }
+    }
+  }
+
+  // Some types of reports are always blocked regardless of
+  // |allow_protected_reports_|.
   auto* collection_info =
       FindCollectionByReportId(device_info_->collections(), report_id);
   if (collection_info) {
-    return IsProtected(*collection_info->usage);
+    return IsAlwaysProtected(*collection_info->usage);
   }
 
-  return has_protected_collection();
+  return has_always_protected_collection();
 }
 
 void HidConnection::ProcessInputReport(
@@ -197,7 +226,7 @@
   DCHECK_GE(size, 1u);
 
   uint8_t report_id = buffer->data()[0];
-  if (IsReportIdProtected(report_id))
+  if (IsReportIdProtected(report_id, kInput))
     return;
 
   if (client_) {
diff --git a/services/device/hid/hid_connection.h b/services/device/hid/hid_connection.h
index ac6a788..f244cb4 100644
--- a/services/device/hid/hid_connection.h
+++ b/services/device/hid/hid_connection.h
@@ -49,7 +49,9 @@
   void SetClient(Client* client);
 
   scoped_refptr<HidDeviceInfo> device_info() const { return device_info_; }
-  bool has_protected_collection() const { return has_protected_collection_; }
+  bool has_always_protected_collection() const {
+    return has_always_protected_collection_;
+  }
   bool closed() const { return closed_; }
 
   // Closes the connection. This must be called before the object is freed.
@@ -75,9 +77,16 @@
                          WriteCallback callback);
 
  protected:
+  enum HidReportType {
+    kInput,
+    kOutput,
+    kFeature,
+  };
+
   friend class base::RefCountedThreadSafe<HidConnection>;
 
-  explicit HidConnection(scoped_refptr<HidDeviceInfo> device_info);
+  HidConnection(scoped_refptr<HidDeviceInfo> device_info,
+                bool allow_protected_reports);
   virtual ~HidConnection();
 
   virtual void PlatformClose() = 0;
@@ -89,15 +98,16 @@
       scoped_refptr<base::RefCountedBytes> buffer,
       WriteCallback callback) = 0;
 
-  bool IsReportIdProtected(uint8_t report_id);
+  bool IsReportIdProtected(uint8_t report_id, HidReportType report_type);
   void ProcessInputReport(scoped_refptr<base::RefCountedBytes> buffer,
                           size_t size);
   void ProcessReadQueue();
 
  private:
   scoped_refptr<HidDeviceInfo> device_info_;
+  bool allow_protected_reports_;
   Client* client_ = nullptr;
-  bool has_protected_collection_;
+  bool has_always_protected_collection_;
   bool closed_;
 
   base::queue<std::tuple<scoped_refptr<base::RefCountedBytes>, size_t>>
diff --git a/services/device/hid/hid_connection_impl_unittest.cc b/services/device/hid/hid_connection_impl_unittest.cc
index 2cea116c..edd52b1 100644
--- a/services/device/hid/hid_connection_impl_unittest.cc
+++ b/services/device/hid/hid_connection_impl_unittest.cc
@@ -37,8 +37,8 @@
 // input report.
 class FakeHidConnection : public HidConnection {
  public:
-  FakeHidConnection(scoped_refptr<HidDeviceInfo> device)
-      : HidConnection(device) {}
+  explicit FakeHidConnection(scoped_refptr<HidDeviceInfo> device)
+      : HidConnection(device, /*allow_protected_reports=*/false) {}
 
   // HidConnection implementation.
   void PlatformClose() override {}
diff --git a/services/device/hid/hid_connection_linux.cc b/services/device/hid/hid_connection_linux.cc
index 208bdec..870f5d22 100644
--- a/services/device/hid/hid_connection_linux.cc
+++ b/services/device/hid/hid_connection_linux.cc
@@ -184,8 +184,9 @@
 HidConnectionLinux::HidConnectionLinux(
     scoped_refptr<HidDeviceInfo> device_info,
     base::ScopedFD fd,
-    scoped_refptr<base::SequencedTaskRunner> blocking_task_runner)
-    : HidConnection(device_info),
+    scoped_refptr<base::SequencedTaskRunner> blocking_task_runner,
+    bool allow_protected_reports)
+    : HidConnection(device_info, allow_protected_reports),
       helper_(nullptr, base::OnTaskRunnerDeleter(blocking_task_runner)),
       blocking_task_runner_(std::move(blocking_task_runner)) {
   helper_.reset(new BlockingTaskRunnerHelper(std::move(fd), device_info,
diff --git a/services/device/hid/hid_connection_linux.h b/services/device/hid/hid_connection_linux.h
index ce5df85..6db774a7b 100644
--- a/services/device/hid/hid_connection_linux.h
+++ b/services/device/hid/hid_connection_linux.h
@@ -25,7 +25,8 @@
   HidConnectionLinux(
       scoped_refptr<HidDeviceInfo> device_info,
       base::ScopedFD fd,
-      scoped_refptr<base::SequencedTaskRunner> blocking_task_runner);
+      scoped_refptr<base::SequencedTaskRunner> blocking_task_runner,
+      bool allow_protected_reports);
 
  private:
   friend class base::RefCountedThreadSafe<HidConnectionLinux>;
diff --git a/services/device/hid/hid_connection_mac.cc b/services/device/hid/hid_connection_mac.cc
index 9e6cecf..84d25ab 100644
--- a/services/device/hid/hid_connection_mac.cc
+++ b/services/device/hid/hid_connection_mac.cc
@@ -28,8 +28,9 @@
 }  // namespace
 
 HidConnectionMac::HidConnectionMac(base::ScopedCFTypeRef<IOHIDDeviceRef> device,
-                                   scoped_refptr<HidDeviceInfo> device_info)
-    : HidConnection(device_info),
+                                   scoped_refptr<HidDeviceInfo> device_info,
+                                   bool allow_protected_reports)
+    : HidConnection(device_info, allow_protected_reports),
       device_(std::move(device)),
       task_runner_(base::ThreadTaskRunnerHandle::Get()),
       blocking_task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
diff --git a/services/device/hid/hid_connection_mac.h b/services/device/hid/hid_connection_mac.h
index f891e4ee..06af473 100644
--- a/services/device/hid/hid_connection_mac.h
+++ b/services/device/hid/hid_connection_mac.h
@@ -24,7 +24,8 @@
 class HidConnectionMac : public HidConnection {
  public:
   HidConnectionMac(base::ScopedCFTypeRef<IOHIDDeviceRef> device,
-                   scoped_refptr<HidDeviceInfo> device_info);
+                   scoped_refptr<HidDeviceInfo> device_info,
+                   bool allow_protected_reports);
 
  private:
   ~HidConnectionMac() override;
diff --git a/services/device/hid/hid_connection_unittest.cc b/services/device/hid/hid_connection_unittest.cc
index ed9a18c..a78c298 100644
--- a/services/device/hid/hid_connection_unittest.cc
+++ b/services/device/hid/hid_connection_unittest.cc
@@ -185,7 +185,8 @@
     return;
 
   TestConnectCallback connect_callback;
-  service_->Connect(device_guid_, connect_callback.GetCallback());
+  service_->Connect(device_guid_, /*allow_protected_reports=*/false,
+                    connect_callback.GetCallback());
   scoped_refptr<HidConnection> conn = connect_callback.WaitForConnection();
   ASSERT_TRUE(conn.get());
 
diff --git a/services/device/hid/hid_connection_win.cc b/services/device/hid/hid_connection_win.cc
index c1a5210e..98f7fbe 100644
--- a/services/device/hid/hid_connection_win.cc
+++ b/services/device/hid/hid_connection_win.cc
@@ -89,16 +89,19 @@
 // static
 scoped_refptr<HidConnection> HidConnectionWin::Create(
     scoped_refptr<HidDeviceInfo> device_info,
-    base::win::ScopedHandle file) {
-  scoped_refptr<HidConnectionWin> connection(
-      new HidConnectionWin(std::move(device_info), std::move(file)));
+    base::win::ScopedHandle file,
+    bool allow_protected_reports) {
+  scoped_refptr<HidConnectionWin> connection(new HidConnectionWin(
+      std::move(device_info), std::move(file), allow_protected_reports));
   connection->ReadNextInputReport();
   return std::move(connection);
 }
 
 HidConnectionWin::HidConnectionWin(scoped_refptr<HidDeviceInfo> device_info,
-                                   base::win::ScopedHandle file)
-    : HidConnection(std::move(device_info)), file_(std::move(file)) {}
+                                   base::win::ScopedHandle file,
+                                   bool allow_protected_reports)
+    : HidConnection(std::move(device_info), allow_protected_reports),
+      file_(std::move(file)) {}
 
 HidConnectionWin::~HidConnectionWin() {
   DCHECK(!file_.IsValid());
@@ -194,7 +197,7 @@
   }
 
   uint8_t report_id = buffer->data()[0];
-  if (IsReportIdProtected(report_id)) {
+  if (IsReportIdProtected(report_id, HidReportType::kInput)) {
     ReadNextInputReport();
     return;
   }
diff --git a/services/device/hid/hid_connection_win.h b/services/device/hid/hid_connection_win.h
index ecdd7c2..e068cac 100644
--- a/services/device/hid/hid_connection_win.h
+++ b/services/device/hid/hid_connection_win.h
@@ -23,14 +23,16 @@
  public:
   static scoped_refptr<HidConnection> Create(
       scoped_refptr<HidDeviceInfo> device_info,
-      base::win::ScopedHandle file);
+      base::win::ScopedHandle file,
+      bool allow_protected_reports);
 
  private:
   friend class HidServiceWin;
   friend class PendingHidTransfer;
 
   HidConnectionWin(scoped_refptr<HidDeviceInfo> device_info,
-                   base::win::ScopedHandle file);
+                   base::win::ScopedHandle file,
+                   bool allow_protected_reports);
   ~HidConnectionWin() override;
 
   // HidConnection implementation.
diff --git a/services/device/hid/hid_device_info.cc b/services/device/hid/hid_device_info.cc
index e58a902..93cf696 100644
--- a/services/device/hid/hid_device_info.cc
+++ b/services/device/hid/hid_device_info.cc
@@ -6,6 +6,7 @@
 
 #include "base/guid.h"
 #include "build/build_config.h"
+#include "services/device/public/cpp/hid/hid_blocklist.h"
 #include "services/device/public/cpp/hid/hid_report_descriptor.h"
 
 namespace device {
@@ -31,11 +32,20 @@
                                &max_input_report_size, &max_output_report_size,
                                &max_feature_report_size);
 
+  auto protected_input_report_ids = HidBlocklist::Get().GetProtectedReportIds(
+      HidBlocklist::kReportTypeInput, vendor_id, product_id, collections);
+  auto protected_output_report_ids = HidBlocklist::Get().GetProtectedReportIds(
+      HidBlocklist::kReportTypeOutput, vendor_id, product_id, collections);
+  auto protected_feature_report_ids = HidBlocklist::Get().GetProtectedReportIds(
+      HidBlocklist::kReportTypeFeature, vendor_id, product_id, collections);
+
   device_ = mojom::HidDeviceInfo::New(
       base::GenerateGUID(), physical_device_id, vendor_id, product_id,
       product_name, serial_number, bus_type, report_descriptor,
       std::move(collections), has_report_id, max_input_report_size,
-      max_output_report_size, max_feature_report_size, device_node);
+      max_output_report_size, max_feature_report_size, device_node,
+      protected_input_report_ids, protected_output_report_ids,
+      protected_feature_report_ids);
 }
 
 HidDeviceInfo::HidDeviceInfo(const HidPlatformDeviceId& platform_device_id,
@@ -54,14 +64,23 @@
   bool has_report_id = !collection->report_ids.empty();
   collections.push_back(std::move(collection));
 
+  auto protected_input_report_ids = HidBlocklist::Get().GetProtectedReportIds(
+      HidBlocklist::kReportTypeInput, vendor_id, product_id, collections);
+  auto protected_output_report_ids = HidBlocklist::Get().GetProtectedReportIds(
+      HidBlocklist::kReportTypeOutput, vendor_id, product_id, collections);
+  auto protected_feature_report_ids = HidBlocklist::Get().GetProtectedReportIds(
+      HidBlocklist::kReportTypeFeature, vendor_id, product_id, collections);
+
   std::vector<uint8_t> report_descriptor;
   device_ = mojom::HidDeviceInfo::New(
       base::GenerateGUID(), physical_device_id, vendor_id, product_id,
       product_name, serial_number, bus_type, report_descriptor,
       std::move(collections), has_report_id, max_input_report_size,
-      max_output_report_size, max_feature_report_size, "");
+      max_output_report_size, max_feature_report_size, "",
+      protected_input_report_ids, protected_output_report_ids,
+      protected_feature_report_ids);
 }
 
-HidDeviceInfo::~HidDeviceInfo() {}
+HidDeviceInfo::~HidDeviceInfo() = default;
 
 }  // namespace device
diff --git a/services/device/hid/hid_manager_impl.cc b/services/device/hid/hid_manager_impl.cc
index bcdb44d..003d3d9 100644
--- a/services/device/hid/hid_manager_impl.cc
+++ b/services/device/hid/hid_manager_impl.cc
@@ -78,8 +78,9 @@
     const std::string& device_guid,
     mojo::PendingRemote<mojom::HidConnectionClient> connection_client,
     mojo::PendingRemote<mojom::HidConnectionWatcher> watcher,
+    bool allow_protected_reports,
     ConnectCallback callback) {
-  hid_service_->Connect(device_guid,
+  hid_service_->Connect(device_guid, allow_protected_reports,
                         base::AdaptCallbackForRepeating(base::BindOnce(
                             &HidManagerImpl::CreateConnection,
                             weak_factory_.GetWeakPtr(), std::move(callback),
diff --git a/services/device/hid/hid_manager_impl.h b/services/device/hid/hid_manager_impl.h
index a9cfaba..cc9be19 100644
--- a/services/device/hid/hid_manager_impl.h
+++ b/services/device/hid/hid_manager_impl.h
@@ -48,6 +48,7 @@
       const std::string& device_guid,
       mojo::PendingRemote<mojom::HidConnectionClient> connection_client,
       mojo::PendingRemote<mojom::HidConnectionWatcher> watcher,
+      bool allow_protected_reports,
       ConnectCallback callback) override;
 
  private:
diff --git a/services/device/hid/hid_manager_unittest.cc b/services/device/hid/hid_manager_unittest.cc
index 8040dcf..ad3a5728 100644
--- a/services/device/hid/hid_manager_unittest.cc
+++ b/services/device/hid/hid_manager_unittest.cc
@@ -264,6 +264,7 @@
         device->device_guid(),
         /*connection_client=*/mojo::NullRemote(),
         /*watcher=*/mojo::NullRemote(),
+        /*allow_protected_reports=*/false,
         base::BindOnce(&OnConnect, run_loop.QuitClosure(), client.get()));
     run_loop.Run();
   }
diff --git a/services/device/hid/hid_service.h b/services/device/hid/hid_service.h
index 0784fd4..a6c4790b 100644
--- a/services/device/hid/hid_service.h
+++ b/services/device/hid/hid_service.h
@@ -68,6 +68,7 @@
   // Opens a connection to a device. The callback will be run with null on
   // failure.
   virtual void Connect(const std::string& device_guid,
+                       bool allow_protected_reports,
                        ConnectCallback callback) = 0;
 
  protected:
diff --git a/services/device/hid/hid_service_linux.cc b/services/device/hid/hid_service_linux.cc
index ba0a694..de3637e 100644
--- a/services/device/hid/hid_service_linux.cc
+++ b/services/device/hid/hid_service_linux.cc
@@ -176,8 +176,10 @@
 
 struct HidServiceLinux::ConnectParams {
   ConnectParams(scoped_refptr<HidDeviceInfo> device_info,
+                bool allow_protected_reports,
                 ConnectCallback callback)
       : device_info(std::move(device_info)),
+        allow_protected_reports(allow_protected_reports),
         callback(std::move(callback)),
         task_runner(base::SequencedTaskRunnerHandle::Get()),
         blocking_task_runner(
@@ -185,6 +187,7 @@
   ~ConnectParams() {}
 
   scoped_refptr<HidDeviceInfo> device_info;
+  bool allow_protected_reports;
   ConnectCallback callback;
   scoped_refptr<base::SequencedTaskRunner> task_runner;
   scoped_refptr<base::SequencedTaskRunner> blocking_task_runner;
@@ -344,6 +347,7 @@
 }
 
 void HidServiceLinux::Connect(const std::string& device_guid,
+                              bool allow_protected_reports,
                               ConnectCallback callback) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
@@ -363,12 +367,13 @@
       device_info->device_node(),
       base::BindOnce(
           &HidServiceLinux::OnPathOpenComplete,
-          std::make_unique<ConnectParams>(device_info, copyable_callback)),
+          std::make_unique<ConnectParams>(device_info, allow_protected_reports,
+                                          copyable_callback)),
       base::BindOnce(&HidServiceLinux::OnPathOpenError,
                      device_info->device_node(), copyable_callback));
 #else
-  auto params =
-      std::make_unique<ConnectParams>(device_info, std::move(callback));
+  auto params = std::make_unique<ConnectParams>(
+      device_info, allow_protected_reports, std::move(callback));
   scoped_refptr<base::SequencedTaskRunner> blocking_task_runner =
       params->blocking_task_runner;
   blocking_task_runner->PostTask(
@@ -449,7 +454,8 @@
   std::move(params->callback)
       .Run(base::MakeRefCounted<HidConnectionLinux>(
           std::move(params->device_info), std::move(params->fd),
-          std::move(params->blocking_task_runner)));
+          std::move(params->blocking_task_runner),
+          params->allow_protected_reports));
 }
 
 }  // namespace device
diff --git a/services/device/hid/hid_service_linux.h b/services/device/hid/hid_service_linux.h
index 5239016b..5b05a04c 100644
--- a/services/device/hid/hid_service_linux.h
+++ b/services/device/hid/hid_service_linux.h
@@ -25,7 +25,9 @@
   ~HidServiceLinux() override;
 
   // HidService:
-  void Connect(const std::string& device_id, ConnectCallback callback) override;
+  void Connect(const std::string& device_id,
+               bool allow_protected_reports,
+               ConnectCallback callback) override;
   base::WeakPtr<HidService> GetWeakPtr() override;
 
  private:
diff --git a/services/device/hid/hid_service_mac.cc b/services/device/hid/hid_service_mac.cc
index 5ef38b1..72ba607 100644
--- a/services/device/hid/hid_service_mac.cc
+++ b/services/device/hid/hid_service_mac.cc
@@ -131,6 +131,7 @@
 HidServiceMac::~HidServiceMac() {}
 
 void HidServiceMac::Connect(const std::string& device_guid,
+                            bool allow_protected_reports,
                             ConnectCallback callback) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
@@ -145,7 +146,8 @@
       FROM_HERE, kBlockingTaskTraits,
       base::BindOnce(&HidServiceMac::OpenOnBlockingThread, map_entry->second),
       base::BindOnce(&HidServiceMac::DeviceOpened, weak_factory_.GetWeakPtr(),
-                     map_entry->second, std::move(callback)));
+                     map_entry->second, allow_protected_reports,
+                     std::move(callback)));
 }
 
 base::WeakPtr<HidService> HidServiceMac::GetWeakPtr() {
@@ -191,11 +193,13 @@
 
 void HidServiceMac::DeviceOpened(
     scoped_refptr<HidDeviceInfo> device_info,
+    bool allow_protected_reports,
     ConnectCallback callback,
     base::ScopedCFTypeRef<IOHIDDeviceRef> hid_device) {
   if (hid_device) {
     std::move(callback).Run(base::MakeRefCounted<HidConnectionMac>(
-        std::move(hid_device), std::move(device_info)));
+        std::move(hid_device), std::move(device_info),
+        allow_protected_reports));
   } else {
     std::move(callback).Run(nullptr);
   }
diff --git a/services/device/hid/hid_service_mac.h b/services/device/hid/hid_service_mac.h
index abe4e33..3fd9f06 100644
--- a/services/device/hid/hid_service_mac.h
+++ b/services/device/hid/hid_service_mac.h
@@ -26,13 +26,16 @@
   HidServiceMac();
   ~HidServiceMac() override;
 
-  void Connect(const std::string& device_id, ConnectCallback connect) override;
+  void Connect(const std::string& device_id,
+               bool allow_protected_reports,
+               ConnectCallback connect) override;
   base::WeakPtr<HidService> GetWeakPtr() override;
 
  private:
   static base::ScopedCFTypeRef<IOHIDDeviceRef> OpenOnBlockingThread(
       scoped_refptr<HidDeviceInfo> device_info);
   void DeviceOpened(scoped_refptr<HidDeviceInfo> device_info,
+                    bool allow_protected_reports,
                     ConnectCallback callback,
                     base::ScopedCFTypeRef<IOHIDDeviceRef> hid_device);
 
diff --git a/services/device/hid/hid_service_win.cc b/services/device/hid/hid_service_win.cc
index f0d7e939..34a2000 100644
--- a/services/device/hid/hid_service_win.cc
+++ b/services/device/hid/hid_service_win.cc
@@ -353,6 +353,7 @@
 HidServiceWin::~HidServiceWin() = default;
 
 void HidServiceWin::Connect(const std::string& device_guid,
+                            bool allow_protected_reports,
                             ConnectCallback callback) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   const auto& map_entry = devices().find(device_guid);
@@ -374,7 +375,8 @@
   task_runner_->PostTask(
       FROM_HERE,
       base::BindOnce(std::move(callback),
-                     HidConnectionWin::Create(device_info, std::move(file))));
+                     HidConnectionWin::Create(device_info, std::move(file),
+                                              allow_protected_reports)));
 }
 
 base::WeakPtr<HidService> HidServiceWin::GetWeakPtr() {
diff --git a/services/device/hid/hid_service_win.h b/services/device/hid/hid_service_win.h
index 0e99fe41..2ff89c3 100644
--- a/services/device/hid/hid_service_win.h
+++ b/services/device/hid/hid_service_win.h
@@ -129,7 +129,9 @@
   HidServiceWin& operator=(const HidServiceWin&) = delete;
   ~HidServiceWin() override;
 
-  void Connect(const std::string& device_id, ConnectCallback callback) override;
+  void Connect(const std::string& device_id,
+               bool allow_protected_reports,
+               ConnectCallback callback) override;
   base::WeakPtr<HidService> GetWeakPtr() override;
 
  private:
diff --git a/services/device/hid/mock_hid_connection.cc b/services/device/hid/mock_hid_connection.cc
index 40093cec..90b450f 100644
--- a/services/device/hid/mock_hid_connection.cc
+++ b/services/device/hid/mock_hid_connection.cc
@@ -10,7 +10,7 @@
 namespace device {
 
 MockHidConnection::MockHidConnection(scoped_refptr<HidDeviceInfo> device)
-    : HidConnection(device) {}
+    : HidConnection(device, /*allow_protected_reports=*/false) {}
 
 MockHidConnection::~MockHidConnection() {}
 
diff --git a/services/device/hid/mock_hid_service.cc b/services/device/hid/mock_hid_service.cc
index 364fc82..b58259d 100644
--- a/services/device/hid/mock_hid_service.cc
+++ b/services/device/hid/mock_hid_service.cc
@@ -32,6 +32,7 @@
 }
 
 void MockHidService::Connect(const std::string& device_id,
+                             bool allow_protected_reports,
                              ConnectCallback callback) {
   const auto& map_entry = devices().find(device_id);
   if (map_entry == devices().end()) {
diff --git a/services/device/hid/mock_hid_service.h b/services/device/hid/mock_hid_service.h
index 1f35cfb..c06a129 100644
--- a/services/device/hid/mock_hid_service.h
+++ b/services/device/hid/mock_hid_service.h
@@ -20,7 +20,9 @@
   void FirstEnumerationComplete();
   const std::map<std::string, scoped_refptr<HidDeviceInfo>>& devices() const;
 
-  void Connect(const std::string& device_id, ConnectCallback callback) override;
+  void Connect(const std::string& device_id,
+               bool allow_protected_reports,
+               ConnectCallback callback) override;
 
  private:
   base::WeakPtr<HidService> GetWeakPtr() override;
diff --git a/services/device/public/cpp/hid/BUILD.gn b/services/device/public/cpp/hid/BUILD.gn
index d246bd0..fe1a873f 100644
--- a/services/device/public/cpp/hid/BUILD.gn
+++ b/services/device/public/cpp/hid/BUILD.gn
@@ -11,6 +11,8 @@
   assert(!is_android)
 
   sources = [
+    "hid_blocklist.cc",
+    "hid_blocklist.h",
     "hid_collection.cc",
     "hid_collection.h",
     "hid_device_filter.cc",
@@ -23,11 +25,14 @@
     "hid_report_descriptor_item.h",
     "hid_report_item.cc",
     "hid_report_item.h",
+    "hid_switches.cc",
+    "hid_switches.h",
     "hid_usage_and_page.cc",
     "hid_usage_and_page.h",
   ]
 
   deps = [
+    "//components/variations",
     "//services/device/public/mojom",
     "//services/service_manager/public/cpp",
   ]
@@ -50,6 +55,7 @@
 
   public_deps = [
     "//base",
+    "//services/device/public/cpp/hid",
     "//services/device/public/mojom",
   ]
 }
diff --git a/services/device/public/cpp/hid/fake_hid_manager.cc b/services/device/public/cpp/hid/fake_hid_manager.cc
index 69617c8..bcde2a69 100644
--- a/services/device/public/cpp/hid/fake_hid_manager.cc
+++ b/services/device/public/cpp/hid/fake_hid_manager.cc
@@ -10,6 +10,7 @@
 #include "base/guid.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/self_owned_receiver.h"
+#include "services/device/public/cpp/hid/hid_blocklist.h"
 
 namespace device {
 
@@ -143,6 +144,7 @@
     const std::string& device_guid,
     mojo::PendingRemote<mojom::HidConnectionClient> connection_client,
     mojo::PendingRemote<mojom::HidConnectionWatcher> watcher,
+    bool allow_protected_reports,
     ConnectCallback callback) {
   if (!base::Contains(devices_, device_guid)) {
     std::move(callback).Run(mojo::NullRemote());
@@ -164,16 +166,10 @@
     const std::string& product_name,
     const std::string& serial_number,
     mojom::HidBusType bus_type) {
-  mojom::HidDeviceInfoPtr device = mojom::HidDeviceInfo::New();
-  device->guid = base::GenerateGUID();
-  device->physical_device_id = physical_device_id;
-  device->vendor_id = vendor_id;
-  device->product_id = product_id;
-  device->product_name = product_name;
-  device->serial_number = serial_number;
-  device->bus_type = bus_type;
-  AddDevice(device.Clone());
-  return device;
+  return CreateAndAddDeviceWithTopLevelUsage(
+      physical_device_id, vendor_id, product_id, product_name, serial_number,
+      bus_type, /*usage_page=*/0xff00,
+      /*usage=*/0x0001);
 }
 
 mojom::HidDeviceInfoPtr FakeHidManager::CreateAndAddDeviceWithTopLevelUsage(
@@ -185,7 +181,12 @@
     mojom::HidBusType bus_type,
     uint16_t usage_page,
     uint16_t usage) {
-  mojom::HidDeviceInfoPtr device = mojom::HidDeviceInfo::New();
+  auto collection = mojom::HidCollectionInfo::New();
+  collection->usage = mojom::HidUsageAndPage::New(usage, usage_page);
+  collection->collection_type = mojom::kHIDCollectionTypeApplication;
+  collection->input_reports.push_back(mojom::HidReportDescription::New());
+
+  auto device = mojom::HidDeviceInfo::New();
   device->guid = base::GenerateGUID();
   device->physical_device_id = physical_device_id;
   device->vendor_id = vendor_id;
@@ -193,16 +194,19 @@
   device->product_name = product_name;
   device->serial_number = serial_number;
   device->bus_type = bus_type;
-
-  std::vector<mojom::HidReportDescriptionPtr> input_reports;
-  std::vector<mojom::HidReportDescriptionPtr> output_reports;
-  std::vector<mojom::HidReportDescriptionPtr> feature_reports;
-  std::vector<mojom::HidCollectionInfoPtr> children;
-  device->collections.push_back(mojom::HidCollectionInfo::New(
-      mojom::HidUsageAndPage::New(usage, usage_page), std::vector<uint8_t>(),
-      mojom::kHIDCollectionTypeApplication, std::move(input_reports),
-      std::move(output_reports), std::move(feature_reports),
-      std::move(children)));
+  device->collections.push_back(std::move(collection));
+  device->protected_input_report_ids =
+      HidBlocklist::Get().GetProtectedReportIds(HidBlocklist::kReportTypeInput,
+                                                vendor_id, product_id,
+                                                device->collections);
+  device->protected_output_report_ids =
+      HidBlocklist::Get().GetProtectedReportIds(HidBlocklist::kReportTypeOutput,
+                                                vendor_id, product_id,
+                                                device->collections);
+  device->protected_feature_report_ids =
+      HidBlocklist::Get().GetProtectedReportIds(
+          HidBlocklist::kReportTypeFeature, vendor_id, product_id,
+          device->collections);
   AddDevice(device.Clone());
   return device;
 }
diff --git a/services/device/public/cpp/hid/fake_hid_manager.h b/services/device/public/cpp/hid/fake_hid_manager.h
index 623efab4..ba17870 100644
--- a/services/device/public/cpp/hid/fake_hid_manager.h
+++ b/services/device/public/cpp/hid/fake_hid_manager.h
@@ -66,6 +66,7 @@
       const std::string& device_guid,
       mojo::PendingRemote<mojom::HidConnectionClient> connection_client,
       mojo::PendingRemote<mojom::HidConnectionWatcher> watcher,
+      bool allow_protected_reports,
       ConnectCallback callback) override;
 
   mojom::HidDeviceInfoPtr CreateAndAddDevice(
diff --git a/services/device/public/cpp/hid/hid_blocklist.cc b/services/device/public/cpp/hid/hid_blocklist.cc
new file mode 100644
index 0000000..053dbc6c
--- /dev/null
+++ b/services/device/public/cpp/hid/hid_blocklist.cc
@@ -0,0 +1,357 @@
+// 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 "services/device/public/cpp/hid/hid_blocklist.h"
+
+#include "base/command_line.h"
+#include "base/no_destructor.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "components/variations/variations_associated_data.h"
+#include "services/device/public/cpp/hid/hid_switches.h"
+
+namespace device {
+
+namespace {
+
+#define VENDOR_PRODUCT_RULE(vid, pid)                       \
+  {                                                         \
+    true, (vid), true, (pid), false, 0, false, 0, false, 0, \
+        HidBlocklist::ReportType::kReportTypeAny            \
+  }
+
+constexpr HidBlocklist::Entry kStaticEntries[] = {
+    // Block all top-level collections with the FIDO usage page.
+    {false, 0, false, 0, true, mojom::kPageFido, false, 0, false, 0,
+     HidBlocklist::ReportType::kReportTypeAny},
+
+    // KEY-ID
+    VENDOR_PRODUCT_RULE(0x096e, 0x0850),
+    // Feitian devices
+    VENDOR_PRODUCT_RULE(0x096e, 0x0852),
+    VENDOR_PRODUCT_RULE(0x096e, 0x0853),
+    VENDOR_PRODUCT_RULE(0x096e, 0x0854),
+    VENDOR_PRODUCT_RULE(0x096e, 0x0856),
+    VENDOR_PRODUCT_RULE(0x096e, 0x0858),
+    VENDOR_PRODUCT_RULE(0x096e, 0x085a),
+    VENDOR_PRODUCT_RULE(0x096e, 0x085b),
+    // HyperFIDO
+    VENDOR_PRODUCT_RULE(0x096e, 0x0880),
+    // HID Global BlueTrust Token
+    VENDOR_PRODUCT_RULE(0x09c3, 0x0023),
+    // Yubikey devices
+    VENDOR_PRODUCT_RULE(0x1050, 0x0010),
+    VENDOR_PRODUCT_RULE(0x1050, 0x0018),
+    VENDOR_PRODUCT_RULE(0x1050, 0x0030),
+    VENDOR_PRODUCT_RULE(0x1050, 0x0110),
+    VENDOR_PRODUCT_RULE(0x1050, 0x0111),
+    VENDOR_PRODUCT_RULE(0x1050, 0x0112),
+    VENDOR_PRODUCT_RULE(0x1050, 0x0113),
+    VENDOR_PRODUCT_RULE(0x1050, 0x0114),
+    VENDOR_PRODUCT_RULE(0x1050, 0x0115),
+    VENDOR_PRODUCT_RULE(0x1050, 0x0116),
+    VENDOR_PRODUCT_RULE(0x1050, 0x0120),
+    VENDOR_PRODUCT_RULE(0x1050, 0x0200),
+    VENDOR_PRODUCT_RULE(0x1050, 0x0211),
+    VENDOR_PRODUCT_RULE(0x1050, 0x0401),
+    VENDOR_PRODUCT_RULE(0x1050, 0x0402),
+    VENDOR_PRODUCT_RULE(0x1050, 0x0403),
+    VENDOR_PRODUCT_RULE(0x1050, 0x0404),
+    VENDOR_PRODUCT_RULE(0x1050, 0x0405),
+    VENDOR_PRODUCT_RULE(0x1050, 0x0406),
+    VENDOR_PRODUCT_RULE(0x1050, 0x0407),
+    VENDOR_PRODUCT_RULE(0x1050, 0x0410),
+    // U2F Zero
+    VENDOR_PRODUCT_RULE(0x10c4, 0x8acf),
+    // Mooltipass Mini-BLE
+    VENDOR_PRODUCT_RULE(0x1209, 0x4321),
+    // Mooltipass Arduino sketch
+    VENDOR_PRODUCT_RULE(0x1209, 0x4322),
+    // Titan
+    VENDOR_PRODUCT_RULE(0x18d1, 0x5026),
+    // VASCO
+    VENDOR_PRODUCT_RULE(0x1a44, 0x00bb),
+    // Keydo AES
+    VENDOR_PRODUCT_RULE(0x1e0d, 0xf1ae),
+    // Neowave Keydo
+    VENDOR_PRODUCT_RULE(0x1e0d, 0xf1d0),
+    // Thetis
+    VENDOR_PRODUCT_RULE(0x1ea8, 0xf025),
+    // Nitrokey
+    VENDOR_PRODUCT_RULE(0x20a0, 0x4287),
+    // JaCarta
+    VENDOR_PRODUCT_RULE(0x24dc, 0x0101),
+    // Happlink
+    VENDOR_PRODUCT_RULE(0x2581, 0xf1d0),
+    // Bluink
+    VENDOR_PRODUCT_RULE(0x2abe, 0x1002),
+    // Feitian USB, HyperFIDO
+    VENDOR_PRODUCT_RULE(0x2ccf, 0x0880),
+};
+
+bool IsValidBlocklistEntry(const HidBlocklist::Entry& entry) {
+  // An entry with a product ID parameter must also specify a vendor ID.
+  if (!entry.has_vendor_id && entry.has_product_id)
+    return false;
+
+  // An entry with a usage ID parameter must also specify a usage page.
+  if (!entry.has_usage_page && entry.has_usage)
+    return false;
+
+  return true;
+}
+
+const std::vector<mojom::HidReportDescriptionPtr>& GetReportsForType(
+    HidBlocklist::ReportType report_type,
+    const mojom::HidCollectionInfo& collection) {
+  switch (report_type) {
+    case HidBlocklist::kReportTypeInput:
+      return collection.input_reports;
+    case HidBlocklist::kReportTypeOutput:
+      return collection.output_reports;
+    case HidBlocklist::kReportTypeFeature:
+      return collection.feature_reports;
+    case HidBlocklist::kReportTypeAny:
+      NOTREACHED();
+      return collection.input_reports;
+  }
+}
+
+// Iterates over |collections| to find reports of type |report_type| that should
+// be protected according to the blocklist rule |entry|. |vendor_id| and
+// |product_id| are the vendor and product IDs of the device with these reports.
+// The report IDs of the protected reports are inserted into |protected_ids|.
+void CheckBlocklistEntry(
+    const HidBlocklist::Entry& entry,
+    HidBlocklist::ReportType report_type,
+    uint16_t vendor_id,
+    uint16_t product_id,
+    const std::vector<mojom::HidCollectionInfoPtr>& collections,
+    std::set<uint8_t>& protected_ids) {
+  DCHECK_NE(report_type, HidBlocklist::kReportTypeAny);
+  if (entry.report_type != HidBlocklist::kReportTypeAny &&
+      entry.report_type != report_type) {
+    return;
+  }
+
+  if (entry.has_vendor_id) {
+    if (entry.vendor_id != vendor_id)
+      return;
+
+    if (entry.has_product_id && entry.product_id != product_id)
+      return;
+  }
+
+  for (const auto& collection : collections) {
+    if (entry.has_usage_page) {
+      if (entry.usage_page != collection->usage->usage_page)
+        continue;
+
+      if (entry.has_usage && entry.usage != collection->usage->usage)
+        continue;
+    }
+
+    const auto& reports = GetReportsForType(report_type, *collection);
+    for (const auto& report : reports) {
+      if (!entry.has_report_id || entry.report_id == report->report_id)
+        protected_ids.insert(report->report_id);
+    }
+  }
+}
+
+// Returns true if the passed string is exactly |digits| digits long and only
+// contains valid hexadecimal characters (no leading 0x).
+bool IsHexComponent(base::StringPiece string, size_t digits) {
+  if (string.length() != digits)
+    return false;
+
+  // This is necessary because base::HexStringToUInt allows whitespace and the
+  // "0x" prefix in its input.
+  for (char c : string) {
+    if (c >= '0' && c <= '9')
+      continue;
+    if (c >= 'a' && c <= 'f')
+      continue;
+    if (c >= 'A' && c <= 'F')
+      continue;
+    return false;
+  }
+  return true;
+}
+
+// Returns true if the passed string is "I" (input report), "O" (output report),
+// "F" (feature report), or "" (any report type).
+bool IsReportTypeComponent(base::StringPiece string) {
+  return string.empty() ||
+         (string.length() == 1 &&
+          (string[0] == 'I' || string[0] == 'O' || string[0] == 'F'));
+}
+
+}  // namespace
+
+// static
+HidBlocklist& HidBlocklist::Get() {
+  static base::NoDestructor<HidBlocklist> instance;
+  return *instance;
+}
+
+// static
+bool HidBlocklist::IsDeviceExcluded(const mojom::HidDeviceInfo& device_info) {
+  // A device should only be excluded if all its reports are protected.
+  for (const auto& collection : device_info.collections) {
+    if (device_info.protected_input_report_ids) {
+      for (const auto& report : collection->input_reports) {
+        if (!base::Contains(*device_info.protected_input_report_ids,
+                            report->report_id)) {
+          return false;
+        }
+      }
+    } else if (!collection->input_reports.empty()) {
+      return false;
+    }
+    if (device_info.protected_output_report_ids) {
+      for (const auto& report : collection->output_reports) {
+        if (!base::Contains(*device_info.protected_output_report_ids,
+                            report->report_id)) {
+          return false;
+        }
+      }
+    } else if (!collection->output_reports.empty()) {
+      return false;
+    }
+    if (device_info.protected_feature_report_ids) {
+      for (const auto& report : collection->feature_reports) {
+        if (!base::Contains(*device_info.protected_feature_report_ids,
+                            report->report_id)) {
+          return false;
+        }
+      }
+    } else if (!collection->feature_reports.empty()) {
+      return false;
+    }
+  }
+  return true;
+}
+
+std::vector<uint8_t> HidBlocklist::GetProtectedReportIds(
+    HidBlocklist::ReportType report_type,
+    uint16_t vendor_id,
+    uint16_t product_id,
+    const std::vector<mojom::HidCollectionInfoPtr>& collections) {
+  DCHECK_NE(report_type, ReportType::kReportTypeAny);
+  if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+          switches::kDisableHidBlocklist)) {
+    return {};
+  }
+
+  std::set<uint8_t> protected_ids;
+  for (const auto& entry : kStaticEntries) {
+    CheckBlocklistEntry(entry, report_type, vendor_id, product_id, collections,
+                        protected_ids);
+  }
+  for (const auto& entry : dynamic_entries_) {
+    CheckBlocklistEntry(entry, report_type, vendor_id, product_id, collections,
+                        protected_ids);
+  }
+  return std::vector<uint8_t>(protected_ids.begin(), protected_ids.end());
+}
+
+void HidBlocklist::PopulateWithServerProvidedValues() {
+  std::string blocklist_string = variations::GetVariationParamValue(
+      "WebHIDBlocklist", "blocklist_additions");
+  DLOG(WARNING) << "HID blocklist additions: " << blocklist_string;
+  for (const auto& blocklist_rule :
+       base::SplitStringPiece(blocklist_string, ",", base::TRIM_WHITESPACE,
+                              base::SPLIT_WANT_NONEMPTY)) {
+    std::vector<base::StringPiece> components = base::SplitStringPiece(
+        blocklist_rule, ":", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
+    if (components.size() != 6) {
+      DLOG(WARNING) << "Wrong number of components in HID blocklist rule: "
+                    << blocklist_rule;
+      continue;
+    }
+
+    // The vendor ID, product ID, usage page, and usage must be specified as
+    // either an empty string or a 16-bit hexadecimal value.
+    if ((!components[0].empty() && !IsHexComponent(components[0], 4)) ||
+        (!components[1].empty() && !IsHexComponent(components[1], 4)) ||
+        (!components[2].empty() && !IsHexComponent(components[2], 4)) ||
+        (!components[3].empty() && !IsHexComponent(components[3], 4))) {
+      DLOG(WARNING) << "Bad component format in HID blocklist rule: "
+                    << blocklist_rule;
+      continue;
+    }
+
+    // The report ID must be specified as either an empty string or an 8-bit
+    // hexadecimal value.
+    if (!components[4].empty() && !IsHexComponent(components[4], 2)) {
+      DLOG(WARNING) << "Bad component format in HID blocklist rule: "
+                    << blocklist_rule;
+      continue;
+    }
+
+    // The report type must be specified as either an empty string or a single
+    // character 'I', 'O', or 'F'.
+    if (!components[5].empty() && !IsReportTypeComponent(components[5])) {
+      DLOG(WARNING) << "Bad component format in HID blocklist rule: "
+                    << blocklist_rule;
+      continue;
+    }
+
+    Entry entry = {};
+    uint32_t int_value;
+    if (!components[0].empty()) {
+      base::HexStringToUInt(components[0], &int_value);
+      entry.has_vendor_id = true;
+      entry.vendor_id = static_cast<uint16_t>(int_value);
+    }
+    if (!components[1].empty()) {
+      base::HexStringToUInt(components[1], &int_value);
+      entry.has_product_id = true;
+      entry.product_id = static_cast<uint16_t>(int_value);
+    }
+    if (!components[2].empty()) {
+      base::HexStringToUInt(components[2], &int_value);
+      entry.has_usage_page = true;
+      entry.usage_page = static_cast<uint16_t>(int_value);
+    }
+    if (!components[3].empty()) {
+      base::HexStringToUInt(components[3], &int_value);
+      entry.has_usage = true;
+      entry.usage = static_cast<uint16_t>(int_value);
+    }
+    if (!components[4].empty()) {
+      base::HexStringToUInt(components[4], &int_value);
+      entry.has_report_id = true;
+      entry.report_id = static_cast<uint16_t>(int_value);
+    }
+    if (components[5] == "I")
+      entry.report_type = HidBlocklist::kReportTypeInput;
+    else if (components[5] == "O")
+      entry.report_type = HidBlocklist::kReportTypeOutput;
+    else if (components[5] == "F")
+      entry.report_type = HidBlocklist::kReportTypeFeature;
+
+    if (!IsValidBlocklistEntry(entry)) {
+      DLOG(WARNING) << "Ivalid HID blocklist rule: " << blocklist_rule;
+      continue;
+    }
+
+    dynamic_entries_.push_back(entry);
+  }
+}
+
+void HidBlocklist::ResetToDefaultValuesForTest() {
+  dynamic_entries_.clear();
+  PopulateWithServerProvidedValues();
+}
+
+HidBlocklist::HidBlocklist() {
+#if DCHECK_IS_ON()
+  for (const auto& entry : kStaticEntries)
+    DCHECK(IsValidBlocklistEntry(entry));
+#endif
+}
+
+}  // namespace device
diff --git a/services/device/public/cpp/hid/hid_blocklist.h b/services/device/public/cpp/hid/hid_blocklist.h
new file mode 100644
index 0000000..e0d145f
--- /dev/null
+++ b/services/device/public/cpp/hid/hid_blocklist.h
@@ -0,0 +1,103 @@
+// 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 SERVICES_DEVICE_PUBLIC_CPP_HID_HID_BLOCKLIST_H_
+#define SERVICES_DEVICE_PUBLIC_CPP_HID_HID_BLOCKLIST_H_
+
+#include "services/device/public/mojom/hid.mojom.h"
+
+namespace base {
+template <typename T>
+class NoDestructor;
+}  // namespace base
+
+namespace device {
+
+class HidBlocklist final {
+ public:
+  enum ReportType {
+    kReportTypeAny = 0,
+    kReportTypeInput,
+    kReportTypeOutput,
+    kReportTypeFeature,
+  };
+
+  struct Entry {
+    bool has_vendor_id;
+    uint16_t vendor_id;
+
+    bool has_product_id;
+    uint16_t product_id;
+
+    bool has_usage_page;
+    uint16_t usage_page;
+
+    bool has_usage;
+    uint16_t usage;
+
+    bool has_report_id;
+    uint8_t report_id;
+
+    ReportType report_type;
+  };
+
+  HidBlocklist(const HidBlocklist&) = delete;
+  HidBlocklist& operator=(const HidBlocklist&) = delete;
+  ~HidBlocklist();
+
+  // Returns a singleton instance of the blocklist.
+  static HidBlocklist& Get();
+
+  // Returns true if a device is excluded from access. A device is excluded if
+  // all of its reports are blocked.
+  static bool IsDeviceExcluded(const device::mojom::HidDeviceInfo& device_info);
+
+  // Given the |vendor_id|, |product_id|, and |collections| for a HID device,
+  // returns a vector of protected report IDs for reports of type |report_type|.
+  std::vector<uint8_t> GetProtectedReportIds(
+      ReportType report_type,
+      uint16_t vendor_id,
+      uint16_t product_id,
+      const std::vector<mojom::HidCollectionInfoPtr>& collections);
+
+  // Returns the number of dynamic blocklist entries.
+  size_t GetDynamicEntryCountForTest() const { return dynamic_entries_.size(); }
+
+  // Reloads the blocklist for testing purposes.
+  void ResetToDefaultValuesForTest();
+
+ private:
+  // Friend NoDestructor to permit access to the private constructor.
+  friend class base::NoDestructor<HidBlocklist>;
+
+  HidBlocklist();
+
+  // Populates the blocklist with values set via a Finch experiment which allows
+  // the set of blocked devices to be updated without shipping new executable
+  // versions.
+  //
+  // The variation string must be a comma-separated list of blocklist rules,
+  // where each rule is composed of six properties of the form:
+  //
+  //     vendor_id:product_id:usage_page:usage:report_id:report_type
+  //
+  // Each property may be empty, indicating that the rule should match any value
+  // for that property. When vendor_id, product_id, usage_page, or usage are
+  // specified, they must be a 16-bit integer written as exactly 4 hexadecimal
+  // digits. When report_id is specified, it must be an 8-bit integer written as
+  // exactly 2 hexadecimal digits. When report_type is specified, it must be a
+  // single character I, O, or F.
+  //
+  // Invalid entries in the comma-separated list will be ignored.
+  //
+  // Example:
+  //     "::f1d0:::, 1234:5678::::, abcd:0001:::01:I"
+  void PopulateWithServerProvidedValues();
+
+  std::vector<Entry> dynamic_entries_;
+};
+
+}  // namespace device
+
+#endif  // SERVICES_DEVICE_PUBLIC_CPP_HID_HID_BLOCKLIST_H_
diff --git a/services/device/public/cpp/hid/hid_blocklist_unittest.cc b/services/device/public/cpp/hid/hid_blocklist_unittest.cc
new file mode 100644
index 0000000..46bae57b
--- /dev/null
+++ b/services/device/public/cpp/hid/hid_blocklist_unittest.cc
@@ -0,0 +1,417 @@
+// 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 "services/device/public/cpp/hid/hid_blocklist.h"
+
+#include "base/command_line.h"
+#include "base/guid.h"
+#include "components/variations/variations_params_manager.h"
+#include "services/device/public/cpp/hid/hid_switches.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace device {
+
+namespace {
+
+using ::testing::ElementsAre;
+
+constexpr uint16_t kTestVendorId = 0x1234;
+constexpr uint16_t kTestProductId = 0x0001;
+constexpr uint16_t kTestUsagePage = 0xff00;
+constexpr uint16_t kTestUsage = 0x0001;
+constexpr uint8_t kNoReportId = 0x00;
+constexpr uint8_t kTestReportId = 0x01;
+
+class HidBlocklistTest : public testing::Test {
+ public:
+  HidBlocklistTest() : blocklist_(HidBlocklist::Get()) {}
+
+  const HidBlocklist& list() { return blocklist_; }
+
+  void SetDynamicBlocklist(base::StringPiece list) {
+    params_manager_.ClearAllVariationParams();
+
+    std::map<std::string, std::string> params;
+    params["blocklist_additions"] = list.as_string();
+    params_manager_.SetVariationParams("WebHIDBlocklist", params);
+
+    blocklist_.ResetToDefaultValuesForTest();
+  }
+
+  mojom::HidDeviceInfoPtr CreateTestDeviceWithOneReport(
+      uint16_t vendor_id,
+      uint16_t product_id,
+      uint16_t usage_page,
+      uint16_t usage,
+      uint8_t report_id,
+      HidBlocklist::ReportType report_type) {
+    const bool has_report_id = (report_id != 0);
+    auto report = mojom::HidReportDescription::New();
+    report->report_id = report_id;
+
+    auto collection = mojom::HidCollectionInfo::New();
+    collection->usage = mojom::HidUsageAndPage::New(usage, usage_page);
+    collection->collection_type = mojom::kHIDCollectionTypeApplication;
+    if (has_report_id)
+      collection->report_ids.push_back(report_id);
+    if (report_type == HidBlocklist::kReportTypeInput)
+      collection->input_reports.push_back(std::move(report));
+    else if (report_type == HidBlocklist::kReportTypeOutput)
+      collection->output_reports.push_back(std::move(report));
+    else if (report_type == HidBlocklist::kReportTypeFeature)
+      collection->feature_reports.push_back(std::move(report));
+
+    auto device = mojom::HidDeviceInfo::New();
+    device->guid = base::GenerateGUID();
+    device->vendor_id = vendor_id;
+    device->product_id = product_id;
+    device->has_report_id = has_report_id;
+    device->collections.push_back(std::move(collection));
+    device->protected_input_report_ids = blocklist_.GetProtectedReportIds(
+        HidBlocklist::kReportTypeInput, vendor_id, product_id,
+        device->collections);
+    device->protected_output_report_ids = blocklist_.GetProtectedReportIds(
+        HidBlocklist::kReportTypeOutput, vendor_id, product_id,
+        device->collections);
+    device->protected_feature_report_ids = blocklist_.GetProtectedReportIds(
+        HidBlocklist::kReportTypeFeature, vendor_id, product_id,
+        device->collections);
+    return device;
+  }
+
+  // Returns a mojom::HidDeviceInfoPtr with |num_collections| collections, each
+  // with different usage pages and report IDs. Each collection contains one
+  // report of each type (input/output/feature).
+  mojom::HidDeviceInfoPtr CreateTestDeviceWithMultipleCollections(
+      uint16_t vendor_id,
+      uint16_t product_id,
+      size_t num_collections) {
+    std::vector<mojom::HidCollectionInfoPtr> collections;
+    uint16_t usage_page = kTestUsagePage;
+    uint8_t report_id = kTestReportId;
+    for (size_t i = 0; i < num_collections; ++i) {
+      auto collection = mojom::HidCollectionInfo::New();
+      collection->usage = mojom::HidUsageAndPage::New(kTestUsage, usage_page++);
+      collection->collection_type = mojom::kHIDCollectionTypeApplication;
+
+      auto input_report = mojom::HidReportDescription::New();
+      collection->report_ids.push_back(report_id);
+      input_report->report_id = report_id++;
+      collection->input_reports.push_back(std::move(input_report));
+
+      auto output_report = mojom::HidReportDescription::New();
+      collection->report_ids.push_back(report_id);
+      output_report->report_id = report_id++;
+      collection->output_reports.push_back(std::move(output_report));
+
+      auto feature_report = mojom::HidReportDescription::New();
+      collection->report_ids.push_back(report_id);
+      feature_report->report_id = report_id++;
+      collection->feature_reports.push_back(std::move(feature_report));
+
+      collections.push_back(std::move(collection));
+    }
+
+    auto device = mojom::HidDeviceInfo::New();
+    device->guid = base::GenerateGUID();
+    device->vendor_id = vendor_id;
+    device->product_id = product_id;
+    device->has_report_id = true;
+    device->collections = std::move(collections);
+    device->protected_input_report_ids = blocklist_.GetProtectedReportIds(
+        HidBlocklist::kReportTypeInput, vendor_id, product_id,
+        device->collections);
+    device->protected_output_report_ids = blocklist_.GetProtectedReportIds(
+        HidBlocklist::kReportTypeOutput, vendor_id, product_id,
+        device->collections);
+    device->protected_feature_report_ids = blocklist_.GetProtectedReportIds(
+        HidBlocklist::kReportTypeFeature, vendor_id, product_id,
+        device->collections);
+    return device;
+  }
+
+ private:
+  void TearDown() override {
+    // Because HidBlocklist is a singleton it must be cleared after tests run
+    // to prevent leakage between tests.
+    params_manager_.ClearAllVariationParams();
+    blocklist_.ResetToDefaultValuesForTest();
+  }
+
+  variations::testing::VariationParamsManager params_manager_;
+  HidBlocklist& blocklist_;
+};
+
+}  // namespace
+
+TEST_F(HidBlocklistTest, StringsWithNoValidEntries) {
+  SetDynamicBlocklist("");
+  EXPECT_EQ(0u, list().GetDynamicEntryCountForTest());
+
+  SetDynamicBlocklist("~!@#$%^&*()-_=+[]{}/*-");
+  EXPECT_EQ(0u, list().GetDynamicEntryCountForTest());
+
+  SetDynamicBlocklist(":");
+  EXPECT_EQ(0u, list().GetDynamicEntryCountForTest());
+
+  SetDynamicBlocklist(",");
+  EXPECT_EQ(0u, list().GetDynamicEntryCountForTest());
+
+  SetDynamicBlocklist(",,");
+  EXPECT_EQ(0u, list().GetDynamicEntryCountForTest());
+
+  SetDynamicBlocklist("1:2:3:4:5:I");
+  EXPECT_EQ(0u, list().GetDynamicEntryCountForTest());
+
+  SetDynamicBlocklist("18d1:2:ff00:::");
+  EXPECT_EQ(0u, list().GetDynamicEntryCountForTest());
+
+  SetDynamicBlocklist("0x18d1:0x0000::::");
+  EXPECT_EQ(0u, list().GetDynamicEntryCountForTest());
+
+  SetDynamicBlocklist("0000:   0::::");
+  EXPECT_EQ(0u, list().GetDynamicEntryCountForTest());
+
+  SetDynamicBlocklist("000g:0000::::");
+  EXPECT_EQ(0u, list().GetDynamicEntryCountForTest());
+
+  SetDynamicBlocklist("::::255:I");
+  EXPECT_EQ(0u, list().GetDynamicEntryCountForTest());
+
+  SetDynamicBlocklist("::::0xff:I");
+  EXPECT_EQ(0u, list().GetDynamicEntryCountForTest());
+
+  SetDynamicBlocklist(":::::i");
+  EXPECT_EQ(0u, list().GetDynamicEntryCountForTest());
+
+  SetDynamicBlocklist(":::::o");
+  EXPECT_EQ(0u, list().GetDynamicEntryCountForTest());
+
+  SetDynamicBlocklist(":::::f");
+  EXPECT_EQ(0u, list().GetDynamicEntryCountForTest());
+
+  SetDynamicBlocklist(":::::A");
+  EXPECT_EQ(0u, list().GetDynamicEntryCountForTest());
+
+  SetDynamicBlocklist("☯");
+  EXPECT_EQ(0u, list().GetDynamicEntryCountForTest());
+}
+
+TEST_F(HidBlocklistTest, UnexcludedDevice) {
+  auto device = CreateTestDeviceWithOneReport(
+      kTestVendorId, kTestProductId, kTestUsagePage, kTestUsage, kTestReportId,
+      HidBlocklist::kReportTypeInput);
+  EXPECT_FALSE(HidBlocklist::IsDeviceExcluded(*device));
+  EXPECT_TRUE(device->protected_input_report_ids->empty());
+  EXPECT_TRUE(device->protected_output_report_ids->empty());
+  EXPECT_TRUE(device->protected_feature_report_ids->empty());
+}
+
+TEST_F(HidBlocklistTest, VendorRule) {
+  // Exclude all devices with matching vendor ID.
+  SetDynamicBlocklist("1234:::::");
+
+  // A device with matching vendor IDs is excluded.
+  auto device1 = CreateTestDeviceWithOneReport(
+      kTestVendorId, kTestProductId, kTestUsagePage, kTestUsage, kTestReportId,
+      HidBlocklist::kReportTypeInput);
+  EXPECT_TRUE(HidBlocklist::IsDeviceExcluded(*device1));
+  EXPECT_THAT(*device1->protected_input_report_ids, ElementsAre(kTestReportId));
+
+  // A device with a different vendor ID is not excluded.
+  auto device2 = CreateTestDeviceWithOneReport(
+      kTestVendorId + 1, kTestProductId, kTestUsagePage, kTestUsage,
+      kTestReportId, HidBlocklist::kReportTypeInput);
+  EXPECT_FALSE(HidBlocklist::IsDeviceExcluded(*device2));
+  EXPECT_TRUE(device2->protected_input_report_ids->empty());
+}
+
+TEST_F(HidBlocklistTest, VendorProductRule) {
+  // Exclude devices with matching vendor and product ID.
+  SetDynamicBlocklist("1234:0001::::");
+
+  // A device with matching vendor/product IDs is excluded.
+  auto device1 = CreateTestDeviceWithOneReport(
+      kTestVendorId, kTestProductId, kTestUsagePage, kTestUsage, kTestReportId,
+      HidBlocklist::kReportTypeInput);
+  EXPECT_TRUE(HidBlocklist::IsDeviceExcluded(*device1));
+  EXPECT_THAT(*device1->protected_input_report_ids, ElementsAre(kTestReportId));
+
+  // A device with matching vendor ID but different product ID is not excluded.
+  auto device2 = CreateTestDeviceWithOneReport(
+      kTestVendorId, kTestProductId + 1, kTestUsagePage, kTestUsage,
+      kTestReportId, HidBlocklist::kReportTypeInput);
+  EXPECT_FALSE(HidBlocklist::IsDeviceExcluded(*device2));
+  EXPECT_TRUE(device2->protected_input_report_ids->empty());
+}
+
+TEST_F(HidBlocklistTest, ExcludedDeviceAllowedWithFlag) {
+  base::CommandLine::ForCurrentProcess()->AppendSwitch(
+      switches::kDisableHidBlocklist);
+
+  // Exclude devices with matching vendor and product ID.
+  SetDynamicBlocklist("1234:0001::::");
+
+  // A device with matching vendor/product IDs is not excluded because the
+  // blocklist is disabled.
+  auto device = CreateTestDeviceWithOneReport(
+      kTestVendorId, kTestProductId, kTestUsagePage, kTestUsage, kTestReportId,
+      HidBlocklist::kReportTypeInput);
+  EXPECT_FALSE(HidBlocklist::IsDeviceExcluded(*device));
+  EXPECT_TRUE(device->protected_input_report_ids->empty());
+  EXPECT_TRUE(device->protected_output_report_ids->empty());
+  EXPECT_TRUE(device->protected_feature_report_ids->empty());
+}
+
+TEST_F(HidBlocklistTest, ProductRuleWithoutVendorDoesNothing) {
+  // Add an invalid rule with a product ID but no vendor ID.
+  SetDynamicBlocklist(":0001::::");
+
+  // A device with matching product ID is not excluded.
+  auto device = CreateTestDeviceWithOneReport(
+      kTestVendorId, kTestProductId, kTestUsagePage, kTestUsage, kTestReportId,
+      HidBlocklist::kReportTypeInput);
+  EXPECT_FALSE(HidBlocklist::IsDeviceExcluded(*device));
+  EXPECT_TRUE(device->protected_input_report_ids->empty());
+}
+
+TEST_F(HidBlocklistTest, UsagePageRule) {
+  // Protect reports by the usage page of the top-level collection.
+  SetDynamicBlocklist("::ff00:::");
+
+  // A device with matching usage page is excluded.
+  auto device1 = CreateTestDeviceWithOneReport(
+      kTestVendorId, kTestProductId, kTestUsagePage, kTestUsage, kTestReportId,
+      HidBlocklist::kReportTypeInput);
+  EXPECT_TRUE(HidBlocklist::IsDeviceExcluded(*device1));
+  EXPECT_THAT(*device1->protected_input_report_ids, ElementsAre(kTestReportId));
+
+  // A device with a different usage page is not excluded.
+  auto device2 = CreateTestDeviceWithOneReport(
+      kTestVendorId, kTestProductId, kTestUsagePage + 1, kTestUsage,
+      kTestReportId, HidBlocklist::kReportTypeInput);
+  EXPECT_FALSE(HidBlocklist::IsDeviceExcluded(*device2));
+  EXPECT_TRUE(device2->protected_input_report_ids->empty());
+}
+
+TEST_F(HidBlocklistTest, UsagePageAndUsageRule) {
+  // Protect reports by the usage page and usage ID of the top-level collection.
+  SetDynamicBlocklist("::ff00:0001::");
+
+  // A device with matching usage page is excluded.
+  auto device1 = CreateTestDeviceWithOneReport(
+      kTestVendorId, kTestProductId, kTestUsagePage, kTestUsage, kTestReportId,
+      HidBlocklist::kReportTypeInput);
+  EXPECT_TRUE(HidBlocklist::IsDeviceExcluded(*device1));
+  EXPECT_THAT(*device1->protected_input_report_ids, ElementsAre(kTestReportId));
+
+  // A device with a different usage page is not excluded.
+  auto device2 = CreateTestDeviceWithOneReport(
+      kTestVendorId, kTestProductId, kTestUsagePage, kTestUsage + 1,
+      kTestReportId, HidBlocklist::kReportTypeInput);
+  EXPECT_FALSE(HidBlocklist::IsDeviceExcluded(*device2));
+  EXPECT_TRUE(device2->protected_input_report_ids->empty());
+}
+
+TEST_F(HidBlocklistTest, UsageRuleWithoutUsagePageDoesNothing) {
+  // Add an invalid rule with a usage ID but no usage page.
+  SetDynamicBlocklist(":::0001::");
+
+  // A device with matching usage ID is not excluded.
+  auto device = CreateTestDeviceWithOneReport(
+      kTestVendorId, kTestProductId, kTestUsagePage, kTestUsage, kTestReportId,
+      HidBlocklist::kReportTypeInput);
+  EXPECT_FALSE(HidBlocklist::IsDeviceExcluded(*device));
+  EXPECT_TRUE(device->protected_input_report_ids->empty());
+}
+
+TEST_F(HidBlocklistTest, NonZeroReportIdRule) {
+  // Protect reports by report ID.
+  SetDynamicBlocklist("::::01:");
+
+  // A device with matching report ID is excluded.
+  auto device1 = CreateTestDeviceWithOneReport(
+      kTestVendorId, kTestProductId, kTestUsagePage, kTestUsage, kTestReportId,
+      HidBlocklist::kReportTypeInput);
+  EXPECT_TRUE(HidBlocklist::IsDeviceExcluded(*device1));
+  EXPECT_THAT(*device1->protected_input_report_ids, ElementsAre(kTestReportId));
+
+  // A device with a different report ID is not excluded.
+  auto device2 = CreateTestDeviceWithOneReport(
+      kTestVendorId, kTestProductId, kTestUsagePage, kTestUsage,
+      kTestReportId + 1, HidBlocklist::kReportTypeInput);
+  EXPECT_FALSE(HidBlocklist::IsDeviceExcluded(*device2));
+  EXPECT_TRUE(device2->protected_input_report_ids->empty());
+}
+
+TEST_F(HidBlocklistTest, ZeroReportIdRule) {
+  // Protect reports from devices that do not use report IDs.
+  SetDynamicBlocklist("::::00:");
+
+  // A device that does not use report IDs is excluded.
+  auto device1 = CreateTestDeviceWithOneReport(
+      kTestVendorId, kTestProductId, kTestUsagePage, kTestUsage, kNoReportId,
+      HidBlocklist::kReportTypeInput);
+  EXPECT_TRUE(HidBlocklist::IsDeviceExcluded(*device1));
+  EXPECT_THAT(*device1->protected_input_report_ids, ElementsAre(kNoReportId));
+
+  // A device that uses report IDs is not excluded.
+  auto device2 = CreateTestDeviceWithOneReport(
+      kTestVendorId, kTestProductId, kTestUsagePage, kTestUsage, kTestReportId,
+      HidBlocklist::kReportTypeInput);
+  EXPECT_FALSE(HidBlocklist::IsDeviceExcluded(*device2));
+  EXPECT_TRUE(device2->protected_input_report_ids->empty());
+}
+
+TEST_F(HidBlocklistTest, ReportTypeRule) {
+  // Protect reports by report type.
+  SetDynamicBlocklist(":::::I");
+
+  // A device with only an input report is excluded.
+  auto device1 = CreateTestDeviceWithOneReport(
+      kTestVendorId, kTestProductId, kTestUsagePage, kTestUsage, kTestReportId,
+      HidBlocklist::kReportTypeInput);
+  EXPECT_TRUE(HidBlocklist::IsDeviceExcluded(*device1));
+  EXPECT_THAT(*device1->protected_input_report_ids, ElementsAre(kTestReportId));
+
+  // A device with an output report is not excluded.
+  auto device2 = CreateTestDeviceWithOneReport(
+      kTestVendorId, kTestProductId, kTestUsagePage, kTestUsage, kTestReportId,
+      HidBlocklist::kReportTypeOutput);
+  EXPECT_FALSE(HidBlocklist::IsDeviceExcluded(*device2));
+  EXPECT_TRUE(device2->protected_output_report_ids->empty());
+}
+
+TEST_F(HidBlocklistTest, DeviceWithAnyUnprotectedReportsNotExcluded) {
+  // Protect input report 0x01.
+  SetDynamicBlocklist("::::01:I");
+
+  // Create a device with six reports divided into two collections. One of the
+  // reports matches the blocklist entry and should be protected, but the other
+  // reports should not be protected.
+  auto device =
+      CreateTestDeviceWithMultipleCollections(kTestVendorId, kTestProductId, 2);
+  EXPECT_FALSE(HidBlocklist::IsDeviceExcluded(*device));
+  EXPECT_THAT(*device->protected_input_report_ids, ElementsAre(0x01));
+  EXPECT_TRUE(device->protected_output_report_ids->empty());
+  EXPECT_TRUE(device->protected_feature_report_ids->empty());
+}
+
+TEST_F(HidBlocklistTest, DeviceWithAllProtectedReportsIsExcluded) {
+  // Protect six reports by report ID and report type.
+  SetDynamicBlocklist(
+      "::::01:I, ::::02:O, ::::03:F, ::::04:I, ::::05:O, ::::06:F");
+
+  // Create a device with six reports divided into two collections. All of the
+  // reports match the above blocklist rules and should be protected.
+  auto device =
+      CreateTestDeviceWithMultipleCollections(kTestVendorId, kTestProductId, 2);
+  EXPECT_TRUE(HidBlocklist::IsDeviceExcluded(*device));
+  EXPECT_THAT(*device->protected_input_report_ids, ElementsAre(0x01, 0x04));
+  EXPECT_THAT(*device->protected_output_report_ids, ElementsAre(0x02, 0x05));
+  EXPECT_THAT(*device->protected_feature_report_ids, ElementsAre(0x03, 0x06));
+}
+
+}  // namespace device
diff --git a/services/device/public/cpp/hid/hid_report_descriptor_unittest.cc b/services/device/public/cpp/hid/hid_report_descriptor_unittest.cc
index 766aac13..f2d3d61 100644
--- a/services/device/public/cpp/hid/hid_report_descriptor_unittest.cc
+++ b/services/device/public/cpp/hid/hid_report_descriptor_unittest.cc
@@ -458,7 +458,7 @@
 TEST_F(HidReportDescriptorTest, ValidateDetails_Digitizer) {
   auto digitizer = HidCollectionInfo::New();
   digitizer->usage = HidUsageAndPage::New(0x01, mojom::kPageDigitizer);
-  ASSERT_EQ(IsProtected(*digitizer->usage), false);
+  ASSERT_FALSE(IsAlwaysProtected(*digitizer->usage));
   digitizer->report_ids = {0x01, 0x02, 0x03};
   AddTopCollectionInfo(std::move(digitizer));
   ValidateDetails(true, 6, 0, 0, kDigitizer, kDigitizerSize);
@@ -527,7 +527,7 @@
   auto keyboard = HidCollectionInfo::New();
   keyboard->usage = HidUsageAndPage::New(mojom::kGenericDesktopKeyboard,
                                          mojom::kPageGenericDesktop);
-  ASSERT_EQ(IsProtected(*keyboard->usage), true);
+  ASSERT_TRUE(IsAlwaysProtected(*keyboard->usage));
   AddTopCollectionInfo(std::move(keyboard));
   ValidateDetails(false, 8, 1, 0, kKeyboard, kKeyboardSize);
 }
@@ -556,7 +556,7 @@
 TEST_F(HidReportDescriptorTest, ValidateDetails_Monitor) {
   auto monitor = HidCollectionInfo::New();
   monitor->usage = HidUsageAndPage::New(0x01, mojom::kPageMonitor0);
-  ASSERT_EQ(IsProtected(*monitor->usage), false);
+  ASSERT_FALSE(IsAlwaysProtected(*monitor->usage));
   monitor->report_ids = {0x01, 0x02, 0x03, 0x04, 0x05};
   AddTopCollectionInfo(std::move(monitor));
   ValidateDetails(true, 0, 0, 243, kMonitor, kMonitorSize);
@@ -600,7 +600,7 @@
   auto mouse = HidCollectionInfo::New();
   mouse->usage = HidUsageAndPage::New(mojom::kGenericDesktopMouse,
                                       mojom::kPageGenericDesktop);
-  ASSERT_EQ(IsProtected(*mouse->usage), true);
+  ASSERT_TRUE(IsAlwaysProtected(*mouse->usage));
   AddTopCollectionInfo(std::move(mouse));
   ValidateDetails(false, 3, 0, 0, kMouse, kMouseSize);
 }
@@ -626,15 +626,15 @@
 TEST_F(HidReportDescriptorTest, ValidateDetails_LogitechUnifyingReceiver) {
   auto hidpp_short = HidCollectionInfo::New();
   hidpp_short->usage = HidUsageAndPage::New(0x01, mojom::kPageVendor);
-  ASSERT_EQ(IsProtected(*hidpp_short->usage), false);
+  ASSERT_FALSE(IsAlwaysProtected(*hidpp_short->usage));
   hidpp_short->report_ids = {0x10};
   auto hidpp_long = HidCollectionInfo::New();
   hidpp_long->usage = HidUsageAndPage::New(0x02, mojom::kPageVendor);
-  ASSERT_EQ(IsProtected(*hidpp_long->usage), false);
+  ASSERT_FALSE(IsAlwaysProtected(*hidpp_long->usage));
   hidpp_long->report_ids = {0x11};
   auto hidpp_dj = HidCollectionInfo::New();
   hidpp_dj->usage = HidUsageAndPage::New(0x04, mojom::kPageVendor);
-  ASSERT_EQ(IsProtected(*hidpp_dj->usage), false);
+  ASSERT_FALSE(IsAlwaysProtected(*hidpp_dj->usage));
   hidpp_dj->report_ids = {0x20, 0x21};
   AddTopCollectionInfo(std::move(hidpp_short));
   AddTopCollectionInfo(std::move(hidpp_long));
@@ -682,7 +682,7 @@
   auto top_info = HidCollectionInfo::New();
   top_info->usage = HidUsageAndPage::New(mojom::kGenericDesktopJoystick,
                                          mojom::kPageGenericDesktop);
-  ASSERT_EQ(IsProtected(*top_info->usage), false);
+  ASSERT_FALSE(IsAlwaysProtected(*top_info->usage));
   top_info->report_ids = {0x01, 0x02, 0xee, 0xef};
   AddTopCollectionInfo(std::move(top_info));
   ValidateDetails(true, 48, 48, 48, kSonyDualshock3, kSonyDualshock3Size);
@@ -739,7 +739,7 @@
   auto top_info = HidCollectionInfo::New();
   top_info->usage = HidUsageAndPage::New(mojom::kGenericDesktopGamePad,
                                          mojom::kPageGenericDesktop);
-  ASSERT_EQ(IsProtected(*top_info->usage), false);
+  ASSERT_FALSE(IsAlwaysProtected(*top_info->usage));
   top_info->report_ids = {0x01, 0x05, 0x04, 0x02, 0x08, 0x10, 0x11, 0x12, 0x13,
                           0x14, 0x15, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86,
                           0x87, 0x88, 0x89, 0x90, 0x91, 0x92, 0x93, 0xa0, 0xa1,
@@ -907,7 +907,7 @@
   auto top_info = HidCollectionInfo::New();
   top_info->usage = HidUsageAndPage::New(mojom::kGenericDesktopGamePad,
                                          mojom::kPageGenericDesktop);
-  ASSERT_EQ(IsProtected(*top_info->usage), false);
+  ASSERT_FALSE(IsAlwaysProtected(*top_info->usage));
   top_info->report_ids = {0x01, 0x02, 0x03, 0x04};
   AddTopCollectionInfo(std::move(top_info));
   ValidateDetails(true, 15, 8, 0, kMicrosoftXboxWirelessController,
@@ -1000,7 +1000,7 @@
   auto top_info = HidCollectionInfo::New();
   top_info->usage = HidUsageAndPage::New(mojom::kGenericDesktopJoystick,
                                          mojom::kPageGenericDesktop);
-  ASSERT_EQ(IsProtected(*top_info->usage), false);
+  ASSERT_FALSE(IsAlwaysProtected(*top_info->usage));
   top_info->report_ids = {0x30, 0x21, 0x81, 0x01, 0x10, 0x80, 0x82};
   AddTopCollectionInfo(std::move(top_info));
   ValidateDetails(true, 63, 63, 0, kNintendoSwitchProController,
@@ -1061,13 +1061,13 @@
   auto gamepad_info = HidCollectionInfo::New();
   gamepad_info->usage = HidUsageAndPage::New(mojom::kGenericDesktopGamePad,
                                              mojom::kPageGenericDesktop);
-  ASSERT_EQ(IsProtected(*gamepad_info->usage), false);
+  ASSERT_FALSE(IsAlwaysProtected(*gamepad_info->usage));
   gamepad_info->report_ids = {0x01, 0x02, 0x03, 0x04, 0x06,
                               0x07, 0x08, 0x09, 0x0a, 0x0b};
   auto keyboard_info = HidCollectionInfo::New();
   keyboard_info->usage = HidUsageAndPage::New(mojom::kGenericDesktopKeyboard,
                                               mojom::kPageGenericDesktop);
-  ASSERT_EQ(IsProtected(*keyboard_info->usage), true);
+  ASSERT_TRUE(IsAlwaysProtected(*keyboard_info->usage));
   keyboard_info->report_ids = {0x05};
   AddTopCollectionInfo(std::move(gamepad_info));
   AddTopCollectionInfo(std::move(keyboard_info));
@@ -1356,12 +1356,12 @@
   auto gamepad_info = HidCollectionInfo::New();
   gamepad_info->usage = HidUsageAndPage::New(mojom::kGenericDesktopGamePad,
                                              mojom::kPageGenericDesktop);
-  ASSERT_EQ(IsProtected(*gamepad_info->usage), false);
+  ASSERT_FALSE(IsAlwaysProtected(*gamepad_info->usage));
   gamepad_info->report_ids = {0x01, 0x02};
   auto status_info = HidCollectionInfo::New();
   status_info->usage = HidUsageAndPage::New(mojom::kGenericDesktopGamePad,
                                             mojom::kPageGenericDesktop);
-  ASSERT_EQ(IsProtected(*status_info->usage), false);
+  ASSERT_FALSE(IsAlwaysProtected(*status_info->usage));
   status_info->report_ids = {0x03};
   AddTopCollectionInfo(std::move(gamepad_info));
   AddTopCollectionInfo(std::move(status_info));
@@ -1420,7 +1420,7 @@
   auto info = HidCollectionInfo::New();
   info->usage = HidUsageAndPage::New(mojom::kGenericDesktopKeyboard,
                                      mojom::kPageGenericDesktop);
-  ASSERT_EQ(IsProtected(*info->usage), true);
+  ASSERT_TRUE(IsAlwaysProtected(*info->usage));
   AddTopCollectionInfo(std::move(info));
   ValidateDetails(false, 8, 1, 0, kSteamControllerKeyboard,
                   kSteamControllerKeyboardSize);
@@ -1451,7 +1451,7 @@
   auto info = HidCollectionInfo::New();
   info->usage = HidUsageAndPage::New(mojom::kGenericDesktopMouse,
                                      mojom::kPageGenericDesktop);
-  ASSERT_EQ(IsProtected(*info->usage), true);
+  ASSERT_TRUE(IsAlwaysProtected(*info->usage));
   AddTopCollectionInfo(std::move(info));
   ValidateDetails(false, 4, 0, 0, kSteamControllerMouse,
                   kSteamControllerMouseSize);
@@ -1479,7 +1479,7 @@
 TEST_F(HidReportDescriptorTest, ValidateDetails_SteamControllerVendor) {
   auto info = HidCollectionInfo::New();
   info->usage = HidUsageAndPage::New(0x01, mojom::kPageVendor);
-  ASSERT_EQ(IsProtected(*info->usage), false);
+  ASSERT_FALSE(IsAlwaysProtected(*info->usage));
   AddTopCollectionInfo(std::move(info));
   ValidateDetails(false, 64, 64, 64, kSteamControllerVendor,
                   kSteamControllerVendorSize);
@@ -1499,7 +1499,7 @@
   auto info = HidCollectionInfo::New();
   info->usage = HidUsageAndPage::New(mojom::kGenericDesktopJoystick,
                                      mojom::kPageGenericDesktop);
-  ASSERT_EQ(IsProtected(*info->usage), false);
+  ASSERT_FALSE(IsAlwaysProtected(*info->usage));
   AddTopCollectionInfo(std::move(info));
   ValidateDetails(false, 7, 4, 0, kXSkillsUsbAdapter, kXSkillsUsbAdapterSize);
 }
@@ -1532,7 +1532,7 @@
   auto info = HidCollectionInfo::New();
   info->usage = HidUsageAndPage::New(mojom::kGenericDesktopKeyboard,
                                      mojom::kPageGenericDesktop);
-  ASSERT_EQ(IsProtected(*info->usage), true);
+  ASSERT_TRUE(IsAlwaysProtected(*info->usage));
   AddTopCollectionInfo(std::move(info));
   ValidateDetails(false, 8, 0, 0, kBelkinNostromoKeyboard,
                   kBelkinNostromoKeyboardSize);
@@ -1558,7 +1558,7 @@
   auto info = HidCollectionInfo::New();
   info->usage = HidUsageAndPage::New(mojom::kGenericDesktopMouse,
                                      mojom::kPageGenericDesktop);
-  ASSERT_EQ(IsProtected(*info->usage), true);
+  ASSERT_TRUE(IsAlwaysProtected(*info->usage));
   AddTopCollectionInfo(std::move(info));
   ValidateDetails(false, 4, 1, 0, kBelkinNostromoMouseAndExtra,
                   kBelkinNostromoMouseAndExtraSize);
diff --git a/services/device/public/cpp/hid/hid_switches.cc b/services/device/public/cpp/hid/hid_switches.cc
new file mode 100644
index 0000000..e4d5b7ab0
--- /dev/null
+++ b/services/device/public/cpp/hid/hid_switches.cc
@@ -0,0 +1,12 @@
+// 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 "services/device/public/cpp/hid/hid_switches.h"
+
+namespace switches {
+
+// Disable the HID blocklist.
+const char kDisableHidBlocklist[] = "disable-hid-blocklist";
+
+}  // namespace switches
diff --git a/services/device/public/cpp/hid/hid_switches.h b/services/device/public/cpp/hid/hid_switches.h
new file mode 100644
index 0000000..1aa505e
--- /dev/null
+++ b/services/device/public/cpp/hid/hid_switches.h
@@ -0,0 +1,14 @@
+// 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 SERVICES_DEVICE_PUBLIC_CPP_HID_HID_SWITCHES_H_
+#define SERVICES_DEVICE_PUBLIC_CPP_HID_HID_SWITCHES_H_
+
+namespace switches {
+
+extern const char kDisableHidBlocklist[];
+
+}  // namespace switches
+
+#endif  // SERVICES_DEVICE_PUBLIC_CPP_HID_HID_SWITCHES_H_
diff --git a/services/device/public/cpp/hid/hid_usage_and_page.cc b/services/device/public/cpp/hid/hid_usage_and_page.cc
index c3b6178..9ce7f520 100644
--- a/services/device/public/cpp/hid/hid_usage_and_page.cc
+++ b/services/device/public/cpp/hid/hid_usage_and_page.cc
@@ -6,7 +6,7 @@
 
 namespace device {
 
-bool IsProtected(const mojom::HidUsageAndPage& hid_usage_and_page) {
+bool IsAlwaysProtected(const mojom::HidUsageAndPage& hid_usage_and_page) {
   const uint16_t usage = hid_usage_and_page.usage;
   const uint16_t usage_page = hid_usage_and_page.usage_page;
 
diff --git a/services/device/public/cpp/hid/hid_usage_and_page.h b/services/device/public/cpp/hid/hid_usage_and_page.h
index b080797..422c6a5 100644
--- a/services/device/public/cpp/hid/hid_usage_and_page.h
+++ b/services/device/public/cpp/hid/hid_usage_and_page.h
@@ -9,8 +9,8 @@
 
 namespace device {
 
-// Indicates whether this usage is protected by Chrome.
-bool IsProtected(const mojom::HidUsageAndPage& hid_usage_and_page);
+// Indicates whether this usage is always protected by Chrome.
+bool IsAlwaysProtected(const mojom::HidUsageAndPage& hid_usage_and_page);
 
 }  // namespace device
 
diff --git a/services/device/public/mojom/hid.mojom b/services/device/public/mojom/hid.mojom
index b93b66c..ca840837 100644
--- a/services/device/public/mojom/hid.mojom
+++ b/services/device/public/mojom/hid.mojom
@@ -341,6 +341,11 @@
 
   // A platform-specific string identifier for the logical device.
   string device_node@13;
+
+  // Reports that should not be accessible from Javascript.
+  [MinVersion=1] array<uint8>? protected_input_report_ids@14;
+  [MinVersion=1] array<uint8>? protected_output_report_ids@15;
+  [MinVersion=1] array<uint8>? protected_feature_report_ids@16;
 };
 
 // A client interface for receiving a notification when HID devices are
@@ -379,9 +384,13 @@
   // open. When the HID connection is closed, the watcher is also closed. This
   // is useful when the connection closure should be handled somewhere other
   // than where the |connection| and |connection_client| are held.
+  //
+  // If |allow_protected_reports| is true, this connection is exempted from
+  // the HID blocklist so that protected reports may be sent and received.
   Connect@2(string device_guid,
           pending_remote<HidConnectionClient>? connection_client,
-          pending_remote<HidConnectionWatcher>? watcher)
+          pending_remote<HidConnectionWatcher>? watcher,
+          [MinVersion=1] bool allow_protected_reports)
       => (pending_remote<HidConnection>? connection);
 
   // Binds a HidManager endpoint.
diff --git a/services/network/resource_scheduler/resource_scheduler.cc b/services/network/resource_scheduler/resource_scheduler.cc
index c5831e4..7609fb9 100644
--- a/services/network/resource_scheduler/resource_scheduler.cc
+++ b/services/network/resource_scheduler/resource_scheduler.cc
@@ -16,6 +16,7 @@
 #include "base/metrics/field_trial_params.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/metrics/histogram_macros.h"
+#include "base/metrics/histogram_macros_local.h"
 #include "base/optional.h"
 #include "base/sequenced_task_runner.h"
 #include "base/strings/string_number_conversions.h"
@@ -823,10 +824,12 @@
         }
       } else {
         if (last_non_delayable_request_end_) {
-          UMA_HISTOGRAM_MEDIUM_TIMES(
+          LOCAL_HISTOGRAM_CUSTOM_TIMES(
               "ResourceScheduler.NonDelayableLastEndToNonDelayableStart."
               "NonDelayableNotInFlight",
-              ticks_now - last_non_delayable_request_end_.value());
+              ticks_now - last_non_delayable_request_end_.value(),
+              base::TimeDelta::FromMilliseconds(10),
+              base::TimeDelta::FromMinutes(3), 50);
         }
       }
 
@@ -839,9 +842,11 @@
             ticks_now - last_non_delayable_request_start_.value());
       }
       if (last_non_delayable_request_end_.has_value()) {
-        UMA_HISTOGRAM_MEDIUM_TIMES(
+        LOCAL_HISTOGRAM_CUSTOM_TIMES(
             "ResourceScheduler.NonDelayableLastEndToNonDelayableStart",
-            ticks_now - last_non_delayable_request_end_.value());
+            ticks_now - last_non_delayable_request_end_.value(),
+            base::TimeDelta::FromMilliseconds(10),
+            base::TimeDelta::FromMinutes(3), 50);
       }
 
       // Record time since last non-delayable request start or end, whichever
@@ -1264,10 +1269,11 @@
                                request.url_request()->creation_time();
     }
 
-    UMA_HISTOGRAM_MEDIUM_TIMES(
+    LOCAL_HISTOGRAM_CUSTOM_TIMES(
         "ResourceScheduler.DelayableRequests."
         "WaitTimeToAvoidContentionWithNonDelayableRequest",
-        ideal_duration_to_wait);
+        ideal_duration_to_wait, base::TimeDelta::FromMilliseconds(10),
+        base::TimeDelta::FromMinutes(3), 50);
   }
 
   RequestQueue pending_requests_;
diff --git a/testing/buildbot/chromium.android.fyi.json b/testing/buildbot/chromium.android.fyi.json
index 023a480..d075b1ea 100644
--- a/testing/buildbot/chromium.android.fyi.json
+++ b/testing/buildbot/chromium.android.fyi.json
@@ -240,11 +240,11 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "weblayer_instrumentation_test_versions_apk_Client Tests For 87.0.4280.123"
+            "weblayer_instrumentation_test_versions_apk_Client Tests For 87.0.4280.125"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
-        "name": "weblayer_instrumentation_test_versions_apk_Client Tests For 87.0.4280.123",
+        "name": "weblayer_instrumentation_test_versions_apk_Client Tests For 87.0.4280.125",
         "resultdb": {
           "enable": true
         },
@@ -254,7 +254,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M87",
-              "revision": "version:87.0.4280.123"
+              "revision": "version:87.0.4280.125"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -317,11 +317,11 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "weblayer_instrumentation_test_versions_apk_Client Tests For 88.0.4324.56"
+            "weblayer_instrumentation_test_versions_apk_Client Tests For 88.0.4324.58"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
-        "name": "weblayer_instrumentation_test_versions_apk_Client Tests For 88.0.4324.56",
+        "name": "weblayer_instrumentation_test_versions_apk_Client Tests For 88.0.4324.58",
         "resultdb": {
           "enable": true
         },
@@ -331,7 +331,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M88",
-              "revision": "version:88.0.4324.56"
+              "revision": "version:88.0.4324.58"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -394,11 +394,11 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "weblayer_instrumentation_test_versions_apk_Implementation Tests For 87.0.4280.123"
+            "weblayer_instrumentation_test_versions_apk_Implementation Tests For 87.0.4280.125"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
-        "name": "weblayer_instrumentation_test_versions_apk_Implementation Tests For 87.0.4280.123",
+        "name": "weblayer_instrumentation_test_versions_apk_Implementation Tests For 87.0.4280.125",
         "resultdb": {
           "enable": true
         },
@@ -408,7 +408,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M87",
-              "revision": "version:87.0.4280.123"
+              "revision": "version:87.0.4280.125"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -471,11 +471,11 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "weblayer_instrumentation_test_versions_apk_Implementation Tests For 88.0.4324.56"
+            "weblayer_instrumentation_test_versions_apk_Implementation Tests For 88.0.4324.58"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
-        "name": "weblayer_instrumentation_test_versions_apk_Implementation Tests For 88.0.4324.56",
+        "name": "weblayer_instrumentation_test_versions_apk_Implementation Tests For 88.0.4324.58",
         "resultdb": {
           "enable": true
         },
@@ -485,7 +485,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M88",
-              "revision": "version:88.0.4324.56"
+              "revision": "version:88.0.4324.58"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -769,11 +769,11 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "weblayer_instrumentation_test_versions_apk_Client Tests For 87.0.4280.123"
+            "weblayer_instrumentation_test_versions_apk_Client Tests For 87.0.4280.125"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
-        "name": "weblayer_instrumentation_test_versions_apk_Client Tests For 87.0.4280.123",
+        "name": "weblayer_instrumentation_test_versions_apk_Client Tests For 87.0.4280.125",
         "resultdb": {
           "enable": true
         },
@@ -783,7 +783,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M87",
-              "revision": "version:87.0.4280.123"
+              "revision": "version:87.0.4280.125"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -846,11 +846,11 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "weblayer_instrumentation_test_versions_apk_Client Tests For 88.0.4324.56"
+            "weblayer_instrumentation_test_versions_apk_Client Tests For 88.0.4324.58"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
-        "name": "weblayer_instrumentation_test_versions_apk_Client Tests For 88.0.4324.56",
+        "name": "weblayer_instrumentation_test_versions_apk_Client Tests For 88.0.4324.58",
         "resultdb": {
           "enable": true
         },
@@ -860,7 +860,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M88",
-              "revision": "version:88.0.4324.56"
+              "revision": "version:88.0.4324.58"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -923,11 +923,11 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "weblayer_instrumentation_test_versions_apk_Implementation Tests For 87.0.4280.123"
+            "weblayer_instrumentation_test_versions_apk_Implementation Tests For 87.0.4280.125"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
-        "name": "weblayer_instrumentation_test_versions_apk_Implementation Tests For 87.0.4280.123",
+        "name": "weblayer_instrumentation_test_versions_apk_Implementation Tests For 87.0.4280.125",
         "resultdb": {
           "enable": true
         },
@@ -937,7 +937,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M87",
-              "revision": "version:87.0.4280.123"
+              "revision": "version:87.0.4280.125"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -1000,11 +1000,11 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "weblayer_instrumentation_test_versions_apk_Implementation Tests For 88.0.4324.56"
+            "weblayer_instrumentation_test_versions_apk_Implementation Tests For 88.0.4324.58"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
-        "name": "weblayer_instrumentation_test_versions_apk_Implementation Tests For 88.0.4324.56",
+        "name": "weblayer_instrumentation_test_versions_apk_Implementation Tests For 88.0.4324.58",
         "resultdb": {
           "enable": true
         },
@@ -1014,7 +1014,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M88",
-              "revision": "version:88.0.4324.56"
+              "revision": "version:88.0.4324.58"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -1298,11 +1298,11 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "weblayer_instrumentation_test_versions_apk_Client Tests For 87.0.4280.123"
+            "weblayer_instrumentation_test_versions_apk_Client Tests For 87.0.4280.125"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
-        "name": "weblayer_instrumentation_test_versions_apk_Client Tests For 87.0.4280.123",
+        "name": "weblayer_instrumentation_test_versions_apk_Client Tests For 87.0.4280.125",
         "resultdb": {
           "enable": true
         },
@@ -1312,7 +1312,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M87",
-              "revision": "version:87.0.4280.123"
+              "revision": "version:87.0.4280.125"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -1375,11 +1375,11 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "weblayer_instrumentation_test_versions_apk_Client Tests For 88.0.4324.56"
+            "weblayer_instrumentation_test_versions_apk_Client Tests For 88.0.4324.58"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
-        "name": "weblayer_instrumentation_test_versions_apk_Client Tests For 88.0.4324.56",
+        "name": "weblayer_instrumentation_test_versions_apk_Client Tests For 88.0.4324.58",
         "resultdb": {
           "enable": true
         },
@@ -1389,7 +1389,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M88",
-              "revision": "version:88.0.4324.56"
+              "revision": "version:88.0.4324.58"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -1452,11 +1452,11 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "weblayer_instrumentation_test_versions_apk_Implementation Tests For 87.0.4280.123"
+            "weblayer_instrumentation_test_versions_apk_Implementation Tests For 87.0.4280.125"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
-        "name": "weblayer_instrumentation_test_versions_apk_Implementation Tests For 87.0.4280.123",
+        "name": "weblayer_instrumentation_test_versions_apk_Implementation Tests For 87.0.4280.125",
         "resultdb": {
           "enable": true
         },
@@ -1466,7 +1466,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M87",
-              "revision": "version:87.0.4280.123"
+              "revision": "version:87.0.4280.125"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -1529,11 +1529,11 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "weblayer_instrumentation_test_versions_apk_Implementation Tests For 88.0.4324.56"
+            "weblayer_instrumentation_test_versions_apk_Implementation Tests For 88.0.4324.58"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
-        "name": "weblayer_instrumentation_test_versions_apk_Implementation Tests For 88.0.4324.56",
+        "name": "weblayer_instrumentation_test_versions_apk_Implementation Tests For 88.0.4324.58",
         "resultdb": {
           "enable": true
         },
@@ -1543,7 +1543,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M88",
-              "revision": "version:88.0.4324.56"
+              "revision": "version:88.0.4324.58"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -1827,11 +1827,11 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "weblayer_instrumentation_test_versions_apk_Client Tests For 87.0.4280.123"
+            "weblayer_instrumentation_test_versions_apk_Client Tests For 87.0.4280.125"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
-        "name": "weblayer_instrumentation_test_versions_apk_Client Tests For 87.0.4280.123",
+        "name": "weblayer_instrumentation_test_versions_apk_Client Tests For 87.0.4280.125",
         "resultdb": {
           "enable": true
         },
@@ -1841,7 +1841,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M87",
-              "revision": "version:87.0.4280.123"
+              "revision": "version:87.0.4280.125"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -1904,11 +1904,11 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "weblayer_instrumentation_test_versions_apk_Client Tests For 88.0.4324.56"
+            "weblayer_instrumentation_test_versions_apk_Client Tests For 88.0.4324.58"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
-        "name": "weblayer_instrumentation_test_versions_apk_Client Tests For 88.0.4324.56",
+        "name": "weblayer_instrumentation_test_versions_apk_Client Tests For 88.0.4324.58",
         "resultdb": {
           "enable": true
         },
@@ -1918,7 +1918,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M88",
-              "revision": "version:88.0.4324.56"
+              "revision": "version:88.0.4324.58"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -1981,11 +1981,11 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "weblayer_instrumentation_test_versions_apk_Implementation Tests For 87.0.4280.123"
+            "weblayer_instrumentation_test_versions_apk_Implementation Tests For 87.0.4280.125"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
-        "name": "weblayer_instrumentation_test_versions_apk_Implementation Tests For 87.0.4280.123",
+        "name": "weblayer_instrumentation_test_versions_apk_Implementation Tests For 87.0.4280.125",
         "resultdb": {
           "enable": true
         },
@@ -1995,7 +1995,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M87",
-              "revision": "version:87.0.4280.123"
+              "revision": "version:87.0.4280.125"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -2058,11 +2058,11 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "weblayer_instrumentation_test_versions_apk_Implementation Tests For 88.0.4324.56"
+            "weblayer_instrumentation_test_versions_apk_Implementation Tests For 88.0.4324.58"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
-        "name": "weblayer_instrumentation_test_versions_apk_Implementation Tests For 88.0.4324.56",
+        "name": "weblayer_instrumentation_test_versions_apk_Implementation Tests For 88.0.4324.58",
         "resultdb": {
           "enable": true
         },
@@ -2072,7 +2072,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M88",
-              "revision": "version:88.0.4324.56"
+              "revision": "version:88.0.4324.58"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
diff --git a/testing/buildbot/variants.pyl b/testing/buildbot/variants.pyl
index 54ec3921..eed5a67e 100644
--- a/testing/buildbot/variants.pyl
+++ b/testing/buildbot/variants.pyl
@@ -319,13 +319,13 @@
       '../../weblayer/browser/android/javatests/skew/expectations.txt',
       '--impl-version=88',
     ],
-    'identifier': 'Implementation Tests For 88.0.4324.56',
+    'identifier': 'Implementation Tests For 88.0.4324.58',
     'swarming': {
       'cipd_packages': [
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M88',
-          'revision': 'version:88.0.4324.56',
+          'revision': 'version:88.0.4324.58',
         }
       ],
     },
@@ -342,13 +342,13 @@
       '../../weblayer/browser/android/javatests/skew/expectations.txt',
       '--impl-version=87',
     ],
-    'identifier': 'Implementation Tests For 87.0.4280.123',
+    'identifier': 'Implementation Tests For 87.0.4280.125',
     'swarming': {
       'cipd_packages': [
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M87',
-          'revision': 'version:87.0.4280.123',
+          'revision': 'version:87.0.4280.125',
         }
       ],
     },
@@ -388,13 +388,13 @@
       '../../weblayer/browser/android/javatests/skew/expectations.txt',
       '--client-version=88',
     ],
-    'identifier': 'Client Tests For 88.0.4324.56',
+    'identifier': 'Client Tests For 88.0.4324.58',
     'swarming': {
       'cipd_packages': [
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M88',
-          'revision': 'version:88.0.4324.56',
+          'revision': 'version:88.0.4324.58',
         }
       ],
     },
@@ -411,13 +411,13 @@
       '../../weblayer/browser/android/javatests/skew/expectations.txt',
       '--client-version=87',
     ],
-    'identifier': 'Client Tests For 87.0.4280.123',
+    'identifier': 'Client Tests For 87.0.4280.125',
     'swarming': {
       'cipd_packages': [
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M87',
-          'revision': 'version:87.0.4280.123',
+          'revision': 'version:87.0.4280.125',
         }
       ],
     },
diff --git a/third_party/android_deps/BUILD.gn b/third_party/android_deps/BUILD.gn
index 64ef5f0..0ce9667 100644
--- a/third_party/android_deps/BUILD.gn
+++ b/third_party/android_deps/BUILD.gn
@@ -1568,7 +1568,7 @@
 
 # This is generated, do not edit. Update BuildConfigGenerator.groovy instead.
 java_prebuilt("com_google_guava_guava_java") {
-  jar_path = "libs/com_google_guava_guava/guava-27.1-jre.jar"
+  jar_path = "libs/com_google_guava_guava/guava-30.1-jre.jar"
   output_name = "com_google_guava_guava"
   enable_bytecode_checks = false
   deps = [
@@ -1578,7 +1578,6 @@
     ":com_google_guava_listenablefuture_java",
     ":com_google_j2objc_j2objc_annotations_java",
     ":org_checkerframework_checker_qual_java",
-    ":org_codehaus_mojo_animal_sniffer_annotations_java",
   ]
 
   # Need to exclude class and replace it with class library as
@@ -1589,15 +1588,16 @@
 
 # This is generated, do not edit. Update BuildConfigGenerator.groovy instead.
 java_prebuilt("com_google_guava_guava_android_java") {
-  jar_path = "libs/com_google_guava_guava_android/guava-25.1-android.jar"
+  jar_path = "libs/com_google_guava_guava_android/guava-30.1-android.jar"
   output_name = "com_google_guava_guava_android"
   supports_android = true
   deps = [
     ":com_google_code_findbugs_jsr305_java",
     ":com_google_errorprone_error_prone_annotations_java",
+    ":com_google_guava_failureaccess_java",
+    ":com_google_guava_listenablefuture_java",
     ":com_google_j2objc_j2objc_annotations_java",
     ":org_checkerframework_checker_compat_qual_java",
-    ":org_codehaus_mojo_animal_sniffer_annotations_java",
   ]
 
   # Add a dep to com_google_guava_listenablefuture_java
@@ -1620,7 +1620,7 @@
 # This is generated, do not edit. Update BuildConfigGenerator.groovy instead.
 java_prebuilt("com_google_j2objc_j2objc_annotations_java") {
   jar_path =
-      "libs/com_google_j2objc_j2objc_annotations/j2objc-annotations-1.1.jar"
+      "libs/com_google_j2objc_j2objc_annotations/j2objc-annotations-1.3.jar"
   output_name = "com_google_j2objc_j2objc_annotations"
   supports_android = true
 
@@ -1715,7 +1715,7 @@
 
 # This is generated, do not edit. Update BuildConfigGenerator.groovy instead.
 java_prebuilt("org_checkerframework_checker_compat_qual_java") {
-  jar_path = "libs/org_checkerframework_checker_compat_qual/checker-compat-qual-2.5.3.jar"
+  jar_path = "libs/org_checkerframework_checker_compat_qual/checker-compat-qual-2.5.5.jar"
   output_name = "org_checkerframework_checker_compat_qual"
   supports_android = true
 }
@@ -2896,7 +2896,7 @@
 
 # This is generated, do not edit. Update BuildConfigGenerator.groovy instead.
 java_prebuilt("org_checkerframework_checker_qual_java") {
-  jar_path = "libs/org_checkerframework_checker_qual/checker-qual-2.10.0.jar"
+  jar_path = "libs/org_checkerframework_checker_qual/checker-qual-3.5.0.jar"
   output_name = "org_checkerframework_checker_qual"
   enable_bytecode_checks = false
 
diff --git a/third_party/android_deps/build.gradle b/third_party/android_deps/build.gradle
index ca71391..9004c61 100644
--- a/third_party/android_deps/build.gradle
+++ b/third_party/android_deps/build.gradle
@@ -156,7 +156,6 @@
 
     compile "com.google.code.findbugs:jsr305:3.0.2"
     compile "com.google.guava:failureaccess:1.0.1"
-    compile "com.google.guava:listenablefuture:1.0"
     compile "com.google.j2objc:j2objc-annotations:1.1"
     compile "com.google.protobuf:protobuf-javalite:3.13.0"
     compile "javax.annotation:javax.annotation-api:1.3.2"
@@ -174,10 +173,10 @@
     // Upstream guava introduced versions with -android suffix starting with version
     // 22 to remove incompatible code with android. Thus we keep two jars, one for
     // the full guava and one that supports android.
-    compile "com.google.guava:guava:25.1-android"
+    compile "com.google.guava:guava:30.1-android"
 
     // buildCompile targets have supports_android = false.
-    buildCompile "com.google.guava:guava:27.0.1-jre"
+    buildCompile "com.google.guava:guava:30.1-jre"
 
     def daggerVersion = '2.30'
     compile "com.google.dagger:dagger:${daggerVersion}"
@@ -192,6 +191,9 @@
     compile "org.checkerframework:checker-compat-qual:2.5.3"
     compile "org.codehaus.mojo:animal-sniffer-annotations:1.17"
 
+    // Dedicated configuration to avoid using higher version number. The 9999 version is empty.
+    compileListenableFuture "com.google.guava:listenablefuture:1.0"
+
     buildCompile "com.google.errorprone:error_prone_core:${errorproneVersion}"
     buildCompile "com.google.errorprone:error_prone_check_api:${errorproneVersion}"
     buildCompile "com.google.errorprone:error_prone_annotation:${errorproneVersion}"
diff --git a/third_party/android_deps/buildSrc/src/main/groovy/ChromiumDepGraph.groovy b/third_party/android_deps/buildSrc/src/main/groovy/ChromiumDepGraph.groovy
index f00aeaff..4ccf3f72 100644
--- a/third_party/android_deps/buildSrc/src/main/groovy/ChromiumDepGraph.groovy
+++ b/third_party/android_deps/buildSrc/src/main/groovy/ChromiumDepGraph.groovy
@@ -19,12 +19,8 @@
  */
 class ChromiumDepGraph {
     final def dependencies = new HashMap<String, DependencyDescription>()
+    final def lowerVersionOverride = new HashSet<String>()
 
-    // Override to use the lower version of the library when
-    // resolving which library version to use.
-    final def LOWER_VERSION_OVERRIDE = [
-         'com_google_guava_listenablefuture',
-    ]
     // Some libraries don't properly fill their POM with the appropriate licensing information.
     // It is provided here from manual lookups. Note that licenseUrl must provide textual content
     // rather than be an html page.
@@ -290,6 +286,8 @@
 
     void collectDependencies() {
         def compileConfig = project.configurations.getByName('compile').resolvedConfiguration
+        def compileListenableFutureConfig = project.configurations.getByName(
+            'compileListenableFuture').resolvedConfiguration
         def buildCompileConfig = project.configurations.getByName('buildCompile').resolvedConfiguration
         def testCompileConfig = project.configurations.getByName('testCompile').resolvedConfiguration
         def androidTestCompileConfig = project.configurations.getByName(
@@ -297,10 +295,15 @@
         List<String> topLevelIds = []
         Set<ResolvedConfiguration> deps = []
         deps += compileConfig.firstLevelModuleDependencies
+        deps += compileListenableFutureConfig.firstLevelModuleDependencies
         deps += buildCompileConfig.firstLevelModuleDependencies
         deps += testCompileConfig.firstLevelModuleDependencies
         deps += androidTestCompileConfig.firstLevelModuleDependencies
 
+        compileListenableFutureConfig.firstLevelModuleDependencies.each { dependency ->
+            lowerVersionOverride.add(makeModuleId(dependency.module))
+        }
+
         deps.each { dependency ->
             topLevelIds.add(makeModuleId(dependency.module))
             collectDependenciesInternal(dependency)
@@ -329,7 +332,9 @@
             dep.testOnly = false
         }
 
-        compileConfig.resolvedArtifacts.each { artifact ->
+        def compileResolvedArtifacts = compileConfig.resolvedArtifacts
+        compileResolvedArtifacts += compileListenableFutureConfig.resolvedArtifacts
+        compileResolvedArtifacts.each { artifact ->
             def id = makeModuleId(artifact)
             def dep = dependencies.get(id)
             assert dep != null : "No dependency collected for artifact ${artifact.name}"
@@ -362,13 +367,16 @@
     private void collectDependenciesInternal(ResolvedDependency dependency) {
         def id = makeModuleId(dependency.module)
         if (dependencies.containsKey(id)) {
-            if (id in LOWER_VERSION_OVERRIDE &&
-                   dependencies.get(id).version <= dependency.module.id.version) {
+            if (dependencies.get(id).version == dependency.module.id.version) return
+
+            // Default to using largest version for version conflict resolution. See
+            // crbug.com/1040958
+            // https://docs.gradle.org/current/userguide/dependency_resolution.html#sec:version-conflict
+            def useLowerVersion = (id in lowerVersionOverride)
+            def versionIsLower = dependency.module.id.version < dependencies.get(id).version 
+            if (useLowerVersion != versionIsLower) {
                 return
             }
-            // Use largest version for version conflict resolution. See crbug.com/1040958
-            // https://docs.gradle.org/current/userguide/dependency_resolution.html#sec:version-conflict
-            if (dependencies.get(id).version >= dependency.module.id.version) return
         }
 
         def childModules = []
diff --git a/third_party/android_deps/buildSrc/src/main/groovy/ChromiumPlugin.groovy b/third_party/android_deps/buildSrc/src/main/groovy/ChromiumPlugin.groovy
index c19885c..0814b69 100644
--- a/third_party/android_deps/buildSrc/src/main/groovy/ChromiumPlugin.groovy
+++ b/third_party/android_deps/buildSrc/src/main/groovy/ChromiumPlugin.groovy
@@ -17,6 +17,12 @@
             /** Main type of configuration, use it for libraries that the APK depends on. */
             compile
 
+            /**
+             * Dedicated com_google_guava_listenablefuture configuration so that other libraries do
+             * not affect the resolved listenablefuture version.
+             */
+            compileListenableFuture
+
             /** Libraries that are for testing only. */
             testCompile
 
diff --git a/third_party/android_deps/libs/com_google_guava_guava/README.chromium b/third_party/android_deps/libs/com_google_guava_guava/README.chromium
index 11a90e8..cea878f 100644
--- a/third_party/android_deps/libs/com_google_guava_guava/README.chromium
+++ b/third_party/android_deps/libs/com_google_guava_guava/README.chromium
@@ -1,13 +1,13 @@
 Name: Guava: Google Core Libraries for Java
 Short Name: guava
 URL: https://github.com/google/guava
-Version: 27.1-jre
+Version: 30.1-jre
 License: Apache 2.0
 License File: NOT_SHIPPED
 Security Critical: no
 
 Description:
-Guava is a suite of core and expanded libraries that include utility classes, google's collections, io classes, and much much more.
+Guava is a suite of core and expanded libraries that include utility classes, Google's collections, I/O classes, and much more.
 
 Local Modifications:
 No modifications.
diff --git a/third_party/android_deps/libs/com_google_guava_guava/cipd.yaml b/third_party/android_deps/libs/com_google_guava_guava/cipd.yaml
index b64d6c9..9d267da 100644
--- a/third_party/android_deps/libs/com_google_guava_guava/cipd.yaml
+++ b/third_party/android_deps/libs/com_google_guava_guava/cipd.yaml
@@ -3,8 +3,8 @@
 # found in the LICENSE file.
 
 # To create CIPD package run the following command.
-# cipd create --pkg-def cipd.yaml -tag version:27.1-jre-cr0
+# cipd create --pkg-def cipd.yaml -tag version:30.1-jre-cr0
 package: chromium/third_party/android_deps/libs/com_google_guava_guava
 description: "Guava: Google Core Libraries for Java"
 data:
-- file: guava-27.1-jre.jar
+- file: guava-30.1-jre.jar
diff --git a/third_party/android_deps/libs/com_google_j2objc_j2objc_annotations/README.chromium b/third_party/android_deps/libs/com_google_j2objc_j2objc_annotations/README.chromium
index f3fd069..d084704 100644
--- a/third_party/android_deps/libs/com_google_j2objc_j2objc_annotations/README.chromium
+++ b/third_party/android_deps/libs/com_google_j2objc_j2objc_annotations/README.chromium
@@ -1,7 +1,7 @@
 Name: J2ObjC Annotations
 Short Name: j2objc-annotations
 URL: https://github.com/google/j2objc/
-Version: 1.1
+Version: 1.3
 License: Apache Version 2.0
 License File: LICENSE
 Security Critical: yes
diff --git a/third_party/android_deps/libs/com_google_j2objc_j2objc_annotations/cipd.yaml b/third_party/android_deps/libs/com_google_j2objc_j2objc_annotations/cipd.yaml
index fea1c80..aaa9c53 100644
--- a/third_party/android_deps/libs/com_google_j2objc_j2objc_annotations/cipd.yaml
+++ b/third_party/android_deps/libs/com_google_j2objc_j2objc_annotations/cipd.yaml
@@ -3,8 +3,8 @@
 # found in the LICENSE file.
 
 # To create CIPD package run the following command.
-# cipd create --pkg-def cipd.yaml -tag version:1.1-cr0
+# cipd create --pkg-def cipd.yaml -tag version:1.3-cr0
 package: chromium/third_party/android_deps/libs/com_google_j2objc_j2objc_annotations
 description: "J2ObjC Annotations"
 data:
-- file: j2objc-annotations-1.1.jar
+- file: j2objc-annotations-1.3.jar
diff --git a/third_party/android_deps/libs/org_checkerframework_checker_compat_qual/LICENSE b/third_party/android_deps/libs/org_checkerframework_checker_compat_qual/LICENSE
index 70d6a70f..0e64188 100644
--- a/third_party/android_deps/libs/org_checkerframework_checker_compat_qual/LICENSE
+++ b/third_party/android_deps/libs/org_checkerframework_checker_compat_qual/LICENSE
@@ -16,8 +16,9 @@
    I18nFormatUtil.java, NullnessUtil.java, Opt.java, PurityUnqualified.java,
    RegexUtil.java, SignednessUtil.java, SignednessUtilExtra.java, and
    UnitsTools.java.  It also applies to the cleanroom implementations of
-   third-party annotations (in checker/src/testannotations/ and in
-   framework/src/main/java/org/jmlspecs/).
+   third-party annotations (in checker/src/testannotations/,
+   framework/src/main/java/org/jmlspecs/, and
+   framework/src/main/java/com/google/).
 
 The Checker Framework includes annotations for some libraries.  Those in
 .astub files use the MIT License.  Those in https://github.com/typetools/jdk
diff --git a/third_party/android_deps/libs/org_checkerframework_checker_compat_qual/README.chromium b/third_party/android_deps/libs/org_checkerframework_checker_compat_qual/README.chromium
index d3995a2..180ea276 100644
--- a/third_party/android_deps/libs/org_checkerframework_checker_compat_qual/README.chromium
+++ b/third_party/android_deps/libs/org_checkerframework_checker_compat_qual/README.chromium
@@ -1,7 +1,7 @@
 Name: Checker Qual
 Short Name: checker-compat-qual
 URL: https://checkerframework.org
-Version: 2.5.3
+Version: 2.5.5
 License: GPL v2 with the classpath exception
 License File: LICENSE
 Security Critical: yes
diff --git a/third_party/android_deps/libs/org_checkerframework_checker_compat_qual/cipd.yaml b/third_party/android_deps/libs/org_checkerframework_checker_compat_qual/cipd.yaml
index 6b0f4f9..33fdfdd 100644
--- a/third_party/android_deps/libs/org_checkerframework_checker_compat_qual/cipd.yaml
+++ b/third_party/android_deps/libs/org_checkerframework_checker_compat_qual/cipd.yaml
@@ -3,8 +3,8 @@
 # found in the LICENSE file.
 
 # To create CIPD package run the following command.
-# cipd create --pkg-def cipd.yaml -tag version:2.5.3-cr0
+# cipd create --pkg-def cipd.yaml -tag version:2.5.5-cr0
 package: chromium/third_party/android_deps/libs/org_checkerframework_checker_compat_qual
 description: "Checker Qual"
 data:
-- file: checker-compat-qual-2.5.3.jar
+- file: checker-compat-qual-2.5.5.jar
diff --git a/third_party/android_deps/libs/org_checkerframework_checker_qual/LICENSE b/third_party/android_deps/libs/org_checkerframework_checker_qual/LICENSE
index 70d6a70f..0e64188 100644
--- a/third_party/android_deps/libs/org_checkerframework_checker_qual/LICENSE
+++ b/third_party/android_deps/libs/org_checkerframework_checker_qual/LICENSE
@@ -16,8 +16,9 @@
    I18nFormatUtil.java, NullnessUtil.java, Opt.java, PurityUnqualified.java,
    RegexUtil.java, SignednessUtil.java, SignednessUtilExtra.java, and
    UnitsTools.java.  It also applies to the cleanroom implementations of
-   third-party annotations (in checker/src/testannotations/ and in
-   framework/src/main/java/org/jmlspecs/).
+   third-party annotations (in checker/src/testannotations/,
+   framework/src/main/java/org/jmlspecs/, and
+   framework/src/main/java/com/google/).
 
 The Checker Framework includes annotations for some libraries.  Those in
 .astub files use the MIT License.  Those in https://github.com/typetools/jdk
diff --git a/third_party/android_deps/libs/org_checkerframework_checker_qual/README.chromium b/third_party/android_deps/libs/org_checkerframework_checker_qual/README.chromium
index daf9719..6a38eb4 100644
--- a/third_party/android_deps/libs/org_checkerframework_checker_qual/README.chromium
+++ b/third_party/android_deps/libs/org_checkerframework_checker_qual/README.chromium
@@ -1,7 +1,7 @@
 Name: Checker Qual
 Short Name: checker-qual
 URL: https://checkerframework.org
-Version: 2.10.0
+Version: 3.5.0
 License: GPL v2 with the classpath exception
 License File: NOT_SHIPPED
 Security Critical: no
diff --git a/third_party/android_deps/libs/org_checkerframework_checker_qual/cipd.yaml b/third_party/android_deps/libs/org_checkerframework_checker_qual/cipd.yaml
index dc14dc8..2068dba 100644
--- a/third_party/android_deps/libs/org_checkerframework_checker_qual/cipd.yaml
+++ b/third_party/android_deps/libs/org_checkerframework_checker_qual/cipd.yaml
@@ -3,8 +3,8 @@
 # found in the LICENSE file.
 
 # To create CIPD package run the following command.
-# cipd create --pkg-def cipd.yaml -tag version:2.10.0-cr0
+# cipd create --pkg-def cipd.yaml -tag version:3.5.0-cr0
 package: chromium/third_party/android_deps/libs/org_checkerframework_checker_qual
 description: "Checker Qual"
 data:
-- file: checker-qual-2.10.0.jar
+- file: checker-qual-3.5.0.jar
diff --git a/third_party/android_deps/libs/org_checkerframework_dataflow_shaded/LICENSE b/third_party/android_deps/libs/org_checkerframework_dataflow_shaded/LICENSE
index 70d6a70f..0e64188 100644
--- a/third_party/android_deps/libs/org_checkerframework_dataflow_shaded/LICENSE
+++ b/third_party/android_deps/libs/org_checkerframework_dataflow_shaded/LICENSE
@@ -16,8 +16,9 @@
    I18nFormatUtil.java, NullnessUtil.java, Opt.java, PurityUnqualified.java,
    RegexUtil.java, SignednessUtil.java, SignednessUtilExtra.java, and
    UnitsTools.java.  It also applies to the cleanroom implementations of
-   third-party annotations (in checker/src/testannotations/ and in
-   framework/src/main/java/org/jmlspecs/).
+   third-party annotations (in checker/src/testannotations/,
+   framework/src/main/java/org/jmlspecs/, and
+   framework/src/main/java/com/google/).
 
 The Checker Framework includes annotations for some libraries.  Those in
 .astub files use the MIT License.  Those in https://github.com/typetools/jdk
diff --git a/third_party/android_deps/util/org/chromium/gms/ChromiumPlayServicesAvailability.java b/third_party/android_deps/util/org/chromium/gms/ChromiumPlayServicesAvailability.java
index 8d61f6d..b0e2815 100644
--- a/third_party/android_deps/util/org/chromium/gms/ChromiumPlayServicesAvailability.java
+++ b/third_party/android_deps/util/org/chromium/gms/ChromiumPlayServicesAvailability.java
@@ -8,33 +8,19 @@
 import com.google.android.gms.common.ConnectionResult;
 import com.google.android.gms.common.GoogleApiAvailability;
 
+// Refer to go/doubledown-play-services#new-apis for more detail.
 public final class ChromiumPlayServicesAvailability {
     /**
      * The minimum GMS version we're requesting. isGooglePlayServicesAvailable will fail if the
-     * found version on the devices is lower than this number. This number should only be updated
-     * when using an API introduced in a newer GMS version, and that API usage is gated by this
-     * class. To see how this number originated, see
+     * found version on the devices is lower than this number. This number should never be updated;
+     * if you need to check for a higher number, use
+     * {@link GoogleApiAvailability#isGooglePlayServicesAvailable(Context, int))} instead.
+     * To see how this number originated, see
      * https://bugs.chromium.org/p/chromium/issues/detail?id=1145211#c3.
      */
     public static final int GMS_VERSION_NUMBER = 20415000;
 
     /**
-     * Checks, with an appropriate version number, if Play Services is available in this context.
-     * This is intended to replace anyone manually calling isGooglePlayServicesAvailable(context),
-     * as it causes bugs without an appropriate version number (crbug.com/1145211).
-     *
-     * If at all possible, do not use this. From a GMSCore team member: "we would not recommend
-     * checking availability upfront. You should be able to just call the API directly, and it
-     * should handle the availability for you. If the API is not available, it should either prompt
-     * the user to update GMS Core or fail with exception." If in doubt, please consult with your
-     * PM/UX.
-     */
-    public static int getGooglePlayServicesConnectionResult(final Context context) {
-        return GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(
-                context, GMS_VERSION_NUMBER);
-    }
-
-    /**
      * Checks if Play Services is available in this context.
      *
      * If at all possible, do not use this. From a GMSCore team member: "we would not recommend
@@ -48,4 +34,23 @@
                        context, GMS_VERSION_NUMBER)
                 == ConnectionResult.SUCCESS;
     }
+
+    /**
+     * Gets Play Services connection result, to be used to see if Play Services are available.
+     *
+     * This is intended to replace anyone manually calling
+     * {@link GoogleApiAvailability#isGooglePlayServicesAvailable(context)},
+     * as it causes bugs without an appropriate version number (crbug.com/1145211). Use
+     * isGooglePlayServicesAvailable if you don't explicitly need the connection result.
+     *
+     * If at all possible, do not use this. From a GMSCore team member: "we would not recommend
+     * checking availability upfront. You should be able to just call the API directly, and it
+     * should handle the availability for you. If the API is not available, it should either prompt
+     * the user to update GMS Core or fail with exception." If in doubt, please consult with your
+     * PM/UX.
+     */
+    public static int getGooglePlayServicesConnectionResult(final Context context) {
+        return GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(
+                context, GMS_VERSION_NUMBER);
+    }
 }
diff --git a/third_party/android_deps/vulnerability_supressions.xml b/third_party/android_deps/vulnerability_supressions.xml
index 1043b90..5212fd3 100644
--- a/third_party/android_deps/vulnerability_supressions.xml
+++ b/third_party/android_deps/vulnerability_supressions.xml
@@ -14,4 +14,20 @@
     <packageUrl regex="true">^pkg:maven/org\.jetbrains\.kotlin/kotlin\-stdlib\-common@.*$</packageUrl>
     <cve>CVE-2020-15824</cve>
   </suppress>
+  <suppress until="2021-12-01Z">
+    <notes><![CDATA[
+      https://nvd.nist.gov/vuln/detail/CVE-2020-8908 should not apply to all guava libraries.
+      ]]></notes>
+    <packageUrl regex="true">^pkg:maven/com\.google\.guava/failureaccess@.*$</packageUrl>
+    <cpe>cpe:/a:google:guava</cpe>
+
+  </suppress>
+  <suppress until="2021-12-01Z">
+    <notes><![CDATA[
+      https://nvd.nist.gov/vuln/detail/CVE-2020-8908 should not apply to all guava libraries.
+      ]]></notes>
+    <packageUrl regex="true">^pkg:maven/com\.google\.guava/listenablefuture@1.0$</packageUrl>
+    <cpe>cpe:/a:google:guava</cpe>
+
+  </suppress>
 </suppressions>
diff --git a/third_party/blink/renderer/core/html/canvas/image_data.cc b/third_party/blink/renderer/core/html/canvas/image_data.cc
index 19593e7..a061ae6 100644
--- a/third_party/blink/renderer/core/html/canvas/image_data.cc
+++ b/third_party/blink/renderer/core/html/canvas/image_data.cc
@@ -42,69 +42,76 @@
 // required to describe a pixel, namely, red, green, blue and alpha.
 namespace {
 
-bool RaiseDOMExceptionAndReturnFalse(ExceptionState* exception_state,
-                                     DOMExceptionCode exception_code,
-                                     const char* message) {
+ImageData* RaiseDOMExceptionAndReturnNull(ExceptionState* exception_state,
+                                          DOMExceptionCode exception_code,
+                                          const char* message) {
   if (exception_state)
     exception_state->ThrowDOMException(exception_code, message);
-  return false;
+  return nullptr;
 }
 
 }  // namespace
 
-bool ImageData::ValidateConstructorArguments(
-    const IntSize* size,
-    const unsigned* width,
-    const unsigned* height,
-    const NotShared<DOMArrayBufferView>* data,
-    const ImageDataSettings* settings,
-    ExceptionState* exception_state) {
-  // We accept all the combinations of colorSpace and storageFormat in an
-  // ImageDataSettings to be stored in an ImageData. Therefore, we don't
-  // check the color settings in this function.
-
-  if (width && !*width) {
-    return RaiseDOMExceptionAndReturnFalse(
-        exception_state, DOMExceptionCode::kIndexSizeError,
-        "The source width is zero or not a number.");
-  }
-
-  if (height && !*height) {
-    return RaiseDOMExceptionAndReturnFalse(
-        exception_state, DOMExceptionCode::kIndexSizeError,
-        "The source height is zero or not a number.");
-  }
-
-  if (width || height) {
-    base::CheckedNumeric<unsigned> data_size =
-        StorageFormatBytesPerPixel(kUint8ClampedArrayStorageFormatName);
-    if (settings) {
-      data_size = StorageFormatBytesPerPixel(settings->storageFormat());
+ImageData* ImageData::ValidateAndCreate(const IntSize* input_size,
+                                        const unsigned* width,
+                                        const unsigned* height,
+                                        NotShared<DOMArrayBufferView>* data,
+                                        const ImageDataSettings* settings,
+                                        ExceptionState* exception_state) {
+  IntSize size;
+  if (width) {
+    DCHECK(!input_size);
+    if (!*width) {
+      return RaiseDOMExceptionAndReturnNull(
+          exception_state, DOMExceptionCode::kIndexSizeError,
+          "The source width is zero or not a number.");
     }
-    data_size *= width ? *width : 0;
-    data_size *= height ? *height : 0;
-    if (!data_size.IsValid()) {
-      return RaiseDOMExceptionAndReturnFalse(
+    size.SetWidth(*width);
+  }
+  if (height) {
+    DCHECK(width);
+    if (!*height) {
+      return RaiseDOMExceptionAndReturnNull(
+          exception_state, DOMExceptionCode::kIndexSizeError,
+          "The source height is zero or not a number.");
+    }
+    size.SetHeight(*height);
+  }
+
+  // TODO(https://crbug.com/1160105): An |input_size| of 0x0 is accepted, but
+  // |width| of 0 or |height| of 0 is not. Is this intentional?
+  if (input_size)
+    size = *input_size;
+
+  // Ensure the size does not overflow.
+  unsigned size_in_elements = 0;
+  {
+    base::CheckedNumeric<unsigned> size_in_elements_checked = 4;
+    size_in_elements_checked *= size.Width();
+    size_in_elements_checked *= size.Height();
+    if (!size_in_elements_checked.IsValid()) {
+      return RaiseDOMExceptionAndReturnNull(
           exception_state, DOMExceptionCode::kIndexSizeError,
           "The requested image size exceeds the supported range.");
     }
-
-    if (data_size.ValueOrDie() > v8::TypedArray::kMaxLength) {
+    if (size_in_elements_checked.ValueOrDie() > v8::TypedArray::kMaxLength) {
       if (exception_state) {
         exception_state->ThrowRangeError(
             "Out of memory at ImageData creation.");
       }
-      return false;
+      return nullptr;
     }
+    size_in_elements = size_in_elements_checked.ValueOrDie();
   }
 
-  unsigned data_length = 0;
+  // If |data| is provided, ensure it is a reasonable format, and that it can
+  // work with |size|.
   if (data) {
     DCHECK(data);
     if ((*data)->GetType() != DOMArrayBufferView::ViewType::kTypeUint8Clamped &&
         (*data)->GetType() != DOMArrayBufferView::ViewType::kTypeUint16 &&
         (*data)->GetType() != DOMArrayBufferView::ViewType::kTypeFloat32) {
-      return RaiseDOMExceptionAndReturnFalse(
+      return RaiseDOMExceptionAndReturnNull(
           exception_state, DOMExceptionCode::kNotSupportedError,
           "The input data type is not supported.");
     }
@@ -114,52 +121,71 @@
             std::numeric_limits<uint32_t>::max(),
         "We use UINT32_MAX as the upper bound of the input size and expect "
         "that the result fits into an `unsigned`.");
+
+    unsigned data_length_in_bytes = 0;
     if (!base::CheckedNumeric<uint32_t>((*data)->byteLength())
-             .AssignIfValid(&data_length)) {
-      return RaiseDOMExceptionAndReturnFalse(
+             .AssignIfValid(&data_length_in_bytes)) {
+      return RaiseDOMExceptionAndReturnNull(
           exception_state, DOMExceptionCode::kNotSupportedError,
           "The input data is too large. The maximum size is 4294967295.");
     }
-    if (!data_length) {
-      return RaiseDOMExceptionAndReturnFalse(
+    if (!data_length_in_bytes) {
+      return RaiseDOMExceptionAndReturnNull(
           exception_state, DOMExceptionCode::kInvalidStateError,
           "The input data has zero elements.");
     }
-    data_length /= (*data)->TypeSize();
-    if (data_length % 4) {
-      return RaiseDOMExceptionAndReturnFalse(
+
+    const unsigned data_length_in_elements =
+        data_length_in_bytes / (*data)->TypeSize();
+    if (data_length_in_elements % 4) {
+      return RaiseDOMExceptionAndReturnNull(
           exception_state, DOMExceptionCode::kInvalidStateError,
           "The input data length is not a multiple of 4.");
     }
 
-    if (width && (data_length / 4) % *width) {
-      return RaiseDOMExceptionAndReturnFalse(
-          exception_state, DOMExceptionCode::kIndexSizeError,
-          "The input data length is not a multiple of (4 * width).");
+    const unsigned data_length_in_pixels = data_length_in_elements / 4;
+    // TODO(https://crbug.com/1160105): This code historically does not ensure
+    // that |size| satisfy the same requirements when specified by |input_size|
+    // as compared when when it is specified by |width| and |height|.
+    if (width) {
+      if (data_length_in_pixels % *width) {
+        return RaiseDOMExceptionAndReturnNull(
+            exception_state, DOMExceptionCode::kIndexSizeError,
+            "The input data length is not a multiple of (4 * width).");
+      }
+
+      unsigned expected_height = data_length_in_pixels / *width;
+      if (height) {
+        if (*height != expected_height) {
+          return RaiseDOMExceptionAndReturnNull(
+              exception_state, DOMExceptionCode::kIndexSizeError,
+              "The input data length is not equal to (4 * width * height).");
+        }
+      } else {
+        size.SetHeight(expected_height);
+      }
     }
-
-    if (width && height && *height != data_length / (4 * *width))
-      return RaiseDOMExceptionAndReturnFalse(
-          exception_state, DOMExceptionCode::kIndexSizeError,
-          "The input data length is not equal to (4 * width * height).");
-  }
-
-  if (size) {
-    if (size->Width() <= 0 || size->Height() <= 0)
-      return false;
-    base::CheckedNumeric<unsigned> data_size = 4;
-    data_size *= size->Width();
-    data_size *= size->Height();
-    if (!data_size.IsValid() ||
-        data_size.ValueOrDie() > v8::TypedArray::kMaxLength)
-      return false;
-    if (data) {
-      if (data_size.ValueOrDie() > data_length)
-        return false;
+    // As referenced above, this is is the only check that has been made when
+    // size is specified by |input_size|.
+    if (input_size) {
+      if (size_in_elements > data_length_in_elements)
+        return nullptr;
     }
   }
 
-  return true;
+  NotShared<DOMArrayBufferView> allocated_data;
+  if (!data) {
+    ImageDataStorageFormat storage_format =
+        settings ? GetImageDataStorageFormat(settings->storageFormat())
+                 : kUint8ClampedArrayStorageFormat;
+    allocated_data = AllocateAndValidateDataArray(
+        size_in_elements, storage_format, exception_state);
+    if (!allocated_data)
+      return nullptr;
+  }
+
+  return MakeGarbageCollected<ImageData>(size, data ? *data : allocated_data,
+                                         settings);
 }
 
 NotShared<DOMArrayBufferView> ImageData::AllocateAndValidateDataArray(
@@ -201,20 +227,7 @@
 
 ImageData* ImageData::Create(const IntSize& size,
                              const ImageDataSettings* settings) {
-  if (!ValidateConstructorArguments(&size, nullptr, nullptr, nullptr, settings))
-    return nullptr;
-  ImageDataStorageFormat storage_format = kUint8ClampedArrayStorageFormat;
-  if (settings) {
-    storage_format =
-        ImageData::GetImageDataStorageFormat(settings->storageFormat());
-  }
-  NotShared<DOMArrayBufferView> data_array =
-      AllocateAndValidateDataArray(4 * static_cast<unsigned>(size.Width()) *
-                                       static_cast<unsigned>(size.Height()),
-                                   storage_format);
-  return data_array
-             ? MakeGarbageCollected<ImageData>(size, data_array, settings)
-             : nullptr;
+  return ValidateAndCreate(&size, nullptr, nullptr, nullptr, settings, nullptr);
 }
 
 ImageData* ImageData::Create(const IntSize& size,
@@ -252,36 +265,23 @@
                              NotShared<DOMArrayBufferView> data_array,
                              const ImageDataSettings* settings) {
   NotShared<DOMArrayBufferView> buffer_view = data_array;
-  if (!ValidateConstructorArguments(&size, nullptr, nullptr, &buffer_view,
-                                    settings))
-    return nullptr;
-  return MakeGarbageCollected<ImageData>(size, data_array, settings);
+  return ValidateAndCreate(&size, nullptr, nullptr, &buffer_view, settings,
+                           nullptr);
 }
 
 ImageData* ImageData::Create(unsigned width,
                              unsigned height,
                              ExceptionState& exception_state) {
-  if (!ValidateConstructorArguments(nullptr, &width, &height, nullptr, nullptr,
-                                    &exception_state))
-    return nullptr;
-
-  NotShared<DOMArrayBufferView> byte_array = AllocateAndValidateDataArray(
-      4 * width * height, kUint8ClampedArrayStorageFormat, &exception_state);
-  return byte_array ? MakeGarbageCollected<ImageData>(IntSize(width, height),
-                                                      byte_array)
-                    : nullptr;
+  return ValidateAndCreate(nullptr, &width, &height, nullptr, nullptr,
+                           &exception_state);
 }
 
 ImageData* ImageData::Create(NotShared<DOMUint8ClampedArray> data,
                              unsigned width,
                              ExceptionState& exception_state) {
   NotShared<DOMArrayBufferView> buffer_view = data;
-  if (!ValidateConstructorArguments(nullptr, &width, nullptr, &buffer_view,
-                                    nullptr, &exception_state))
-    return nullptr;
-
-  unsigned height = base::checked_cast<unsigned>(data->length()) / (width * 4);
-  return MakeGarbageCollected<ImageData>(IntSize(width, height), data);
+  return ValidateAndCreate(nullptr, &width, nullptr, &buffer_view, nullptr,
+                           &exception_state);
 }
 
 ImageData* ImageData::Create(NotShared<DOMUint8ClampedArray> data,
@@ -289,31 +289,16 @@
                              unsigned height,
                              ExceptionState& exception_state) {
   NotShared<DOMArrayBufferView> buffer_view = data;
-  if (!ValidateConstructorArguments(nullptr, &width, &height, &buffer_view,
-                                    nullptr, &exception_state))
-    return nullptr;
-
-  return MakeGarbageCollected<ImageData>(IntSize(width, height), data);
+  return ValidateAndCreate(nullptr, &width, &height, &buffer_view, nullptr,
+                           &exception_state);
 }
 
 ImageData* ImageData::CreateImageData(unsigned width,
                                       unsigned height,
                                       const ImageDataSettings* settings,
                                       ExceptionState& exception_state) {
-  if (!ValidateConstructorArguments(nullptr, &width, &height, nullptr, settings,
-                                    &exception_state))
-    return nullptr;
-
-  ImageDataStorageFormat storage_format =
-      GetImageDataStorageFormat(settings->storageFormat());
-  NotShared<DOMArrayBufferView> buffer_view = AllocateAndValidateDataArray(
-      4 * width * height, storage_format, &exception_state);
-
-  if (!buffer_view)
-    return nullptr;
-
-  return MakeGarbageCollected<ImageData>(IntSize(width, height), buffer_view,
-                                         settings);
+  return ValidateAndCreate(nullptr, &width, &height, nullptr, settings,
+                           &exception_state);
 }
 
 ImageData* ImageData::CreateImageData(ImageDataArray& data,
@@ -344,12 +329,8 @@
   if (settings->storageFormat() != storage_format_name)
     settings->setStorageFormat(storage_format_name);
 
-  if (!ValidateConstructorArguments(nullptr, &width, &height, &buffer_view,
-                                    settings, &exception_state))
-    return nullptr;
-
-  return MakeGarbageCollected<ImageData>(IntSize(width, height), buffer_view,
-                                         settings);
+  return ValidateAndCreate(nullptr, &width, &height, &buffer_view, settings,
+                           &exception_state);
 }
 
 // This function accepts size (0, 0) and always returns the ImageData in
diff --git a/third_party/blink/renderer/core/html/canvas/image_data.h b/third_party/blink/renderer/core/html/canvas/image_data.h
index 8f0977b..92ad9d8 100644
--- a/third_party/blink/renderer/core/html/canvas/image_data.h
+++ b/third_party/blink/renderer/core/html/canvas/image_data.h
@@ -151,13 +151,12 @@
   NotShared<DOMUint16Array> data_u16_;
   NotShared<DOMFloat32Array> data_f32_;
 
-  static bool ValidateConstructorArguments(
-      const IntSize* size,
-      const unsigned* width,
-      const unsigned* height,
-      const NotShared<DOMArrayBufferView>* data,
-      const ImageDataSettings* settings,
-      ExceptionState* = nullptr);
+  static ImageData* ValidateAndCreate(const IntSize* size,
+                                      const unsigned* width,
+                                      const unsigned* height,
+                                      NotShared<DOMArrayBufferView>* data,
+                                      const ImageDataSettings* settings,
+                                      ExceptionState*);
 
   static NotShared<DOMArrayBufferView> AllocateAndValidateDataArray(
       const unsigned&,
diff --git a/third_party/blink/renderer/core/html/media/html_media_element.cc b/third_party/blink/renderer/core/html/media/html_media_element.cc
index d2f5a72f..10d1703 100644
--- a/third_party/blink/renderer/core/html/media/html_media_element.cc
+++ b/third_party/blink/renderer/core/html/media/html_media_element.cc
@@ -536,7 +536,7 @@
       controls_list_(MakeGarbageCollected<HTMLMediaElementControlsList>(this)),
       lazy_load_intersection_observer_(nullptr),
       media_player_host_remote_(GetExecutionContext()),
-      media_player_observer_remote_(GetExecutionContext()),
+      media_player_observer_remote_set_(GetExecutionContext()),
       media_player_receiver_set_(this, GetExecutionContext()) {
   DVLOG(1) << "HTMLMediaElement(" << *this << ")";
 
@@ -635,13 +635,6 @@
   return !IsFullscreen() && SupportsFocus();
 }
 
-media::mojom::blink::MediaPlayerObserver*
-HTMLMediaElement::GetMediaPlayerObserverRemote() {
-  if (!media_player_observer_remote_.is_bound())
-    return nullptr;
-  return media_player_observer_remote_.get();
-}
-
 void HTMLMediaElement::ParseAttribute(
     const AttributeModificationParams& params) {
   const QualifiedName& name = params.name;
@@ -1469,9 +1462,9 @@
          !GetWebMediaPlayer()->PausedWhenHidden();
 }
 
-void HTMLMediaElement::SetMediaPlayerObserverForTesting(
+void HTMLMediaElement::AddMediaPlayerObserverForTesting(
     mojo::PendingRemote<media::mojom::blink::MediaPlayerObserver> observer) {
-  SetMediaPlayerObserver(std::move(observer));
+  AddMediaPlayerObserver(std::move(observer));
 }
 
 bool HTMLMediaElement::TextTracksAreReady() const {
@@ -3674,7 +3667,7 @@
     // The lifetime of the mojo endpoints are tied to the WebMediaPlayer's, so
     // we need to reset those as well.
     media_player_receiver_set_.Clear();
-    media_player_observer_remote_.reset();
+    media_player_observer_remote_set_.Clear();
   }
   OnWebMediaPlayerCleared();
 }
@@ -4150,7 +4143,7 @@
   visitor->Trace(controls_list_);
   visitor->Trace(lazy_load_intersection_observer_);
   visitor->Trace(media_player_host_remote_);
-  visitor->Trace(media_player_observer_remote_);
+  visitor->Trace(media_player_observer_remote_set_);
   visitor->Trace(media_player_receiver_set_);
   Supplementable<HTMLMediaElement>::Trace(visitor);
   HTMLElement::Trace(visitor);
@@ -4392,61 +4385,61 @@
 }
 
 void HTMLMediaElement::DidPlayerMutedStatusChange(bool muted) {
-  // The remote to the MediaPlayerObserver could be not set yet.
-  if (!media_player_observer_remote_.is_bound())
-    return;
-
-  media_player_observer_remote_->OnMutedStatusChanged(muted);
+  for (auto& observer : media_player_observer_remote_set_) {
+    if (!observer.is_bound())
+      continue;
+    observer->OnMutedStatusChanged(muted);
+  }
 }
 
 void HTMLMediaElement::DidPlayerMediaPositionStateChange(
     double playback_rate,
     base::TimeDelta duration,
     base::TimeDelta position) {
-  // The remote to the MediaPlayerObserver could be not set yet.
-  if (!media_player_observer_remote_.is_bound())
-    return;
-
-  media_player_observer_remote_->OnMediaPositionStateChanged(
-      media_session::mojom::blink::MediaPosition::New(
-          playback_rate, duration, position, base::TimeTicks::Now()));
+  for (auto& observer : media_player_observer_remote_set_) {
+    if (!observer.is_bound())
+      continue;
+    observer->OnMediaPositionStateChanged(
+        media_session::mojom::blink::MediaPosition::New(
+            playback_rate, duration, position, base::TimeTicks::Now()));
+  }
 }
 
 void HTMLMediaElement::DidDisableAudioOutputSinkChanges() {
-  // The remote to the MediaPlayerObserver could be not set yet.
-  if (!media_player_observer_remote_.is_bound())
-    return;
-
-  media_player_observer_remote_->OnAudioOutputSinkChangingDisabled();
+  for (auto& observer : media_player_observer_remote_set_) {
+    if (!observer.is_bound())
+      continue;
+    observer->OnAudioOutputSinkChangingDisabled();
+  }
 }
 
 void HTMLMediaElement::DidPlayerSizeChange(const gfx::Size& size) {
-  // The remote to the MediaPlayerObserver could be not set yet.
-  if (!media_player_observer_remote_.is_bound())
-    return;
-
-  media_player_observer_remote_->OnMediaSizeChanged(size);
+  for (auto& observer : media_player_observer_remote_set_) {
+    if (!observer.is_bound())
+      continue;
+    observer->OnMediaSizeChanged(size);
+  }
 }
 
 void HTMLMediaElement::DidBufferUnderflow() {
-  // The remote to the MediaPlayerObserver could be not set yet.
-  if (!media_player_observer_remote_.is_bound())
-    return;
-
-  media_player_observer_remote_->OnBufferUnderflow();
+  for (auto& observer : media_player_observer_remote_set_) {
+    if (!observer.is_bound())
+      continue;
+    observer->OnBufferUnderflow();
+  }
 }
 
 void HTMLMediaElement::DidSeek() {
-  // The remote to the MediaPlayerObserver could be not set yet.
-  if (!media_player_observer_remote_.is_bound())
-    return;
-
   // Send the seek updates to the browser process only once per second.
   if (last_seek_update_time_.is_null() ||
       (base::TimeTicks::Now() - last_seek_update_time_ >=
        base::TimeDelta::FromSeconds(1))) {
     last_seek_update_time_ = base::TimeTicks::Now();
-    media_player_observer_remote_->OnSeek();
+    for (auto& observer : media_player_observer_remote_set_) {
+      if (!observer.is_bound())
+        continue;
+      observer->OnSeek();
+    }
   }
 }
 
@@ -4462,12 +4455,18 @@
   return *media_player_host_remote_.get();
 }
 
-void HTMLMediaElement::SetMediaPlayerObserver(
+void HTMLMediaElement::AddMediaPlayerObserver(
     mojo::PendingRemote<media::mojom::blink::MediaPlayerObserver> observer) {
-  DCHECK(!media_player_observer_remote_.is_bound());
-  media_player_observer_remote_.Bind(
+  media_player_observer_remote_set_.Add(
       std::move(observer),
       GetDocument().GetTaskRunner(TaskType::kInternalMedia));
+
+  media_player_observer_remote_set_.set_disconnect_handler(WTF::BindRepeating(
+      [](HTMLMediaElement* html_media_element,
+         mojo::RemoteSetElementId remote_id) {
+        html_media_element->media_player_observer_remote_set_.Remove(remote_id);
+      },
+      WrapWeakPersistent(this)));
 }
 
 void HTMLMediaElement::RequestPlay() {
diff --git a/third_party/blink/renderer/core/html/media/html_media_element.h b/third_party/blink/renderer/core/html/media/html_media_element.h
index 56896b6..1587116b 100644
--- a/third_party/blink/renderer/core/html/media/html_media_element.h
+++ b/third_party/blink/renderer/core/html/media/html_media_element.h
@@ -48,6 +48,7 @@
 #include "third_party/blink/renderer/platform/media/web_audio_source_provider_client.h"
 #include "third_party/blink/renderer/platform/mojo/heap_mojo_receiver_set.h"
 #include "third_party/blink/renderer/platform/mojo/heap_mojo_remote.h"
+#include "third_party/blink/renderer/platform/mojo/heap_mojo_remote_set.h"
 #include "third_party/blink/renderer/platform/network/mime/mime_type_registry.h"
 #include "third_party/blink/renderer/platform/scheduler/public/post_cancellable_task.h"
 #include "third_party/blink/renderer/platform/supplementable.h"
@@ -349,7 +350,7 @@
   void SetCcLayerForTesting(cc::Layer* layer) { SetCcLayer(layer); }
 
   // Required by tests set mock receivers to check that messages are delivered.
-  void SetMediaPlayerObserverForTesting(
+  void AddMediaPlayerObserverForTesting(
       mojo::PendingRemote<media::mojom::blink::MediaPlayerObserver> observer);
 
   bool IsShowPosterFlagSet() const { return show_poster_flag_; }
@@ -362,9 +363,13 @@
   ~HTMLMediaElement() override;
   void Dispose();
 
-  // Returns a pointer to the media::mojom::blink::MediaPlayerObserver remote if
-  // already bound, or nullptr otherwise. Used from subclasses as well.
-  media::mojom::blink::MediaPlayerObserver* GetMediaPlayerObserverRemote();
+  // Returns a constant reference to the HeapMojoRemoteSet holding all the bound
+  // remotes for the media::mojom::blink::MediaPlayerObserver interface. Needed
+  // to allow sending messages directly from HTMLMediaElement's subclasses.
+  const HeapMojoRemoteSet<media::mojom::blink::MediaPlayerObserver>&
+  GetMediaPlayerObserverRemoteSet() {
+    return media_player_observer_remote_set_;
+  }
 
   void ParseAttribute(const AttributeModificationParams&) override;
   void FinishParsingChildren() final;
@@ -486,7 +491,7 @@
   media::mojom::blink::MediaPlayerHost& GetMediaPlayerHostRemote();
 
   // media::mojom::MediaPlayer  implementation.
-  void SetMediaPlayerObserver(
+  void AddMediaPlayerObserver(
       mojo::PendingRemote<media::mojom::blink::MediaPlayerObserver> observer)
       override;
   void RequestPlay() override;
@@ -817,8 +822,11 @@
 
   HeapMojoRemote<media::mojom::blink::MediaPlayerHost>
       media_player_host_remote_;
-  HeapMojoRemote<media::mojom::blink::MediaPlayerObserver>
-      media_player_observer_remote_;
+
+  // Multiple objects outside of the renderer process can register as observers,
+  // so we need to store the remotes in a set here.
+  HeapMojoRemoteSet<media::mojom::blink::MediaPlayerObserver>
+      media_player_observer_remote_set_;
 
   // A receiver set is needed here as there will be different objects in the
   // browser communicating with this object. This is done this way to avoid
diff --git a/third_party/blink/renderer/core/html/media/html_media_element_test.cc b/third_party/blink/renderer/core/html/media/html_media_element_test.cc
index d194090..8e66871 100644
--- a/third_party/blink/renderer/core/html/media/html_media_element_test.cc
+++ b/third_party/blink/renderer/core/html/media/html_media_element_test.cc
@@ -85,7 +85,7 @@
 // interface to allow checking that messages sent over mojo are received with
 // the right values in the other end.
 //
-// Note this relies on HTMLMediaElement::SetMediaPlayerObserverForTesting() to
+// Note this relies on HTMLMediaElement::AddMediaPlayerObserverForTesting() to
 // provide the HTMLMediaElement instance owned by the test with a valid mojo
 // remote, that will be bound to the mojo receiver provided by this class
 // instead of the real one used in production that would be owned by
@@ -97,7 +97,7 @@
       HTMLMediaElement* html_media_element) {
     // Bind the remote to the receiver, so that we can intercept incoming
     // messages sent via the different methods that use the remote.
-    html_media_element->SetMediaPlayerObserverForTesting(
+    html_media_element->AddMediaPlayerObserverForTesting(
         receiver_.BindNewPipeAndPassRemote());
   }
 
diff --git a/third_party/blink/renderer/core/html/media/html_video_element.cc b/third_party/blink/renderer/core/html/media/html_video_element.cc
index d79da0f..57b8048 100644
--- a/third_party/blink/renderer/core/html/media/html_video_element.cc
+++ b/third_party/blink/renderer/core/html/media/html_video_element.cc
@@ -259,10 +259,10 @@
   if (!web_media_player_)
     return;
 
-  auto* media_player_observer_remote = GetMediaPlayerObserverRemote();
-  if (media_player_observer_remote) {
-    media_player_observer_remote->OnPictureInPictureAvailabilityChanged(
-        SupportsPictureInPicture());
+  for (auto& observer : GetMediaPlayerObserverRemoteSet()) {
+    if (!observer.is_bound())
+      continue;
+    observer->OnPictureInPictureAvailabilityChanged(SupportsPictureInPicture());
   }
 }
 
diff --git a/third_party/blink/renderer/core/layout/svg/svg_layout_tree_as_text.cc b/third_party/blink/renderer/core/layout/svg/svg_layout_tree_as_text.cc
index 785956e..6980d18 100644
--- a/third_party/blink/renderer/core/layout/svg/svg_layout_tree_as_text.cc
+++ b/third_party/blink/renderer/core/layout/svg/svg_layout_tree_as_text.cc
@@ -685,7 +685,6 @@
   WriteStandardPrefix(ts, text, indent);
   WritePositionAndStyle(ts, text);
   ts << "\n";
-  WriteResources(ts, text, indent);
   WriteSVGInlineTextBoxes(ts, text, indent);
 }
 
diff --git a/third_party/blink/renderer/core/loader/modulescript/module_script_creation_params.h b/third_party/blink/renderer/core/loader/modulescript/module_script_creation_params.h
index 8fdfb2a..e8972b8 100644
--- a/third_party/blink/renderer/core/loader/modulescript/module_script_creation_params.h
+++ b/third_party/blink/renderer/core/loader/modulescript/module_script_creation_params.h
@@ -79,8 +79,10 @@
 
   ModuleScriptCreationParams CopyWithClearedSourceText() const {
     return ModuleScriptCreationParams(
-        source_url_, base_url_, module_type_, ParkableString(), cache_handler_,
-        credentials_mode_, script_streamer_, not_streaming_reason_);
+        source_url_, base_url_, module_type_, ParkableString(),
+        /*cache_handler=*/nullptr, credentials_mode_,
+        /*script_streamer=*/nullptr,
+        ScriptStreamer::NotStreamingReason::kStreamingDisabled);
   }
 
   SingleCachedMetadataHandler* CacheHandler() const { return cache_handler_; }
diff --git a/third_party/blink/renderer/modules/accessibility/BUILD.gn b/third_party/blink/renderer/modules/accessibility/BUILD.gn
index c9b2ea07..e7dabc4 100644
--- a/third_party/blink/renderer/modules/accessibility/BUILD.gn
+++ b/third_party/blink/renderer/modules/accessibility/BUILD.gn
@@ -14,8 +14,6 @@
     "ax_inline_text_box.h",
     "ax_layout_object.cc",
     "ax_layout_object.h",
-    "ax_list.cc",
-    "ax_list.h",
     "ax_list_box.cc",
     "ax_list_box.h",
     "ax_list_box_option.cc",
diff --git a/third_party/blink/renderer/modules/accessibility/ax_list.cc b/third_party/blink/renderer/modules/accessibility/ax_list.cc
deleted file mode 100644
index 576bea1..0000000
--- a/third_party/blink/renderer/modules/accessibility/ax_list.cc
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2008 Apple Inc. All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * 1.  Redistributions of source code must retain the above copyright
- *     notice, this list of conditions and the following disclaimer.
- * 2.  Redistributions in binary form must reproduce the above copyright
- *     notice, this list of conditions and the following disclaimer in the
- *     documentation and/or other materials provided with the distribution.
- * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
- *     its contributors may be used to endorse or promote products derived
- *     from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
- * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
- * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-#include "third_party/blink/renderer/modules/accessibility/ax_list.h"
-
-#include "third_party/blink/renderer/core/html/html_ulist_element.h"
-#include "third_party/blink/renderer/core/layout/layout_object.h"
-#include "third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.h"
-
-namespace blink {
-
-AXList::AXList(LayoutObject* layout_object, AXObjectCacheImpl& ax_object_cache)
-    : AXLayoutObject(layout_object, ax_object_cache) {}
-
-AXList::~AXList() = default;
-
-bool AXList::ComputeAccessibilityIsIgnored(
-    IgnoredReasons* ignored_reasons) const {
-  return AccessibilityIsIgnoredByDefault(ignored_reasons);
-}
-
-bool AXList::IsDescriptionList() const {
-  if (!layout_object_)
-    return false;
-
-  Node* node = layout_object_->GetNode();
-  return node && node->HasTagName(html_names::kDlTag);
-}
-
-ax::mojom::Role AXList::RoleValue() const {
-  if (IsDescriptionList())
-    return ax::mojom::Role::kDescriptionList;
-
-  return ax::mojom::Role::kList;
-}
-}  // namespace blink
diff --git a/third_party/blink/renderer/modules/accessibility/ax_list.h b/third_party/blink/renderer/modules/accessibility/ax_list.h
deleted file mode 100644
index 3fb9a7f..0000000
--- a/third_party/blink/renderer/modules/accessibility/ax_list.h
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2008 Apple Inc. All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * 1.  Redistributions of source code must retain the above copyright
- *     notice, this list of conditions and the following disclaimer.
- * 2.  Redistributions in binary form must reproduce the above copyright
- *     notice, this list of conditions and the following disclaimer in the
- *     documentation and/or other materials provided with the distribution.
- * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
- *     its contributors may be used to endorse or promote products derived
- *     from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
- * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
- * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-#ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_ACCESSIBILITY_AX_LIST_H_
-#define THIRD_PARTY_BLINK_RENDERER_MODULES_ACCESSIBILITY_AX_LIST_H_
-
-#include "base/macros.h"
-#include "third_party/blink/renderer/modules/accessibility/ax_layout_object.h"
-
-namespace blink {
-
-class AXObjectCacheImpl;
-
-class AXList final : public AXLayoutObject {
- public:
-  AXList(LayoutObject*, AXObjectCacheImpl&);
-  ~AXList() override;
-
-  bool IsList() const override { return true; }
-
-  ax::mojom::Role RoleValue() const final;
-
- private:
-  bool IsDescriptionList() const;
-  bool ComputeAccessibilityIsIgnored(IgnoredReasons* = nullptr) const override;
-
-  DISALLOW_COPY_AND_ASSIGN(AXList);
-};
-
-}  // namespace blink
-
-#endif  // THIRD_PARTY_BLINK_RENDERER_MODULES_ACCESSIBILITY_AX_LIST_H_
diff --git a/third_party/blink/renderer/modules/accessibility/ax_node_object.cc b/third_party/blink/renderer/modules/accessibility/ax_node_object.cc
index f056d5a..a405cb7 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_node_object.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_node_object.cc
@@ -367,6 +367,9 @@
           ax::mojom::blink::Role::kContentDeletion,
           ax::mojom::blink::Role::kContentInsertion,
           ax::mojom::blink::Role::kDetails,
+          ax::mojom::blink::Role::kDescriptionList,
+          ax::mojom::blink::Role::kDescriptionListDetail,
+          ax::mojom::blink::Role::kDescriptionListTerm,
           ax::mojom::blink::Role::kDialog,
           ax::mojom::blink::Role::kFigcaption,
           ax::mojom::blink::Role::kFigure,
@@ -856,7 +859,8 @@
   if (IsA<HTMLDivElement>(*GetNode()))
     return RoleFromLayoutObject(ax::mojom::blink::Role::kGenericContainer);
 
-  if (IsA<HTMLMenuElement>(*GetNode())) {
+  if (IsA<HTMLMenuElement>(*GetNode()) || IsA<HTMLUListElement>(*GetNode()) ||
+      IsA<HTMLOListElement>(*GetNode())) {
     // <menu> is a deprecated feature of HTML 5, but is included for semantic
     // compatibility with HTML3, and may contain list items. Exposing it as an
     // unordered list works better than the current HTML-AAM recommendaton of
diff --git a/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc b/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc
index 9b0611f..5201d0d 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc
@@ -76,7 +76,6 @@
 #include "third_party/blink/renderer/modules/accessibility/ax_image_map_link.h"
 #include "third_party/blink/renderer/modules/accessibility/ax_inline_text_box.h"
 #include "third_party/blink/renderer/modules/accessibility/ax_layout_object.h"
-#include "third_party/blink/renderer/modules/accessibility/ax_list.h"
 #include "third_party/blink/renderer/modules/accessibility/ax_list_box.h"
 #include "third_party/blink/renderer/modules/accessibility/ax_list_box_option.h"
 #include "third_party/blink/renderer/modules/accessibility/ax_media_element.h"
@@ -416,30 +415,10 @@
   return objects_.at(ax_id);
 }
 
-// FIXME: This probably belongs on Node.
-// FIXME: This should take a const char*, but one caller passes g_null_atom.
-static bool NodeHasRole(Node* node, const String& role) {
-  auto* element = DynamicTo<Element>(node);
-  if (!element)
-    return false;
-
-  // TODO(accessibility) support role strings with multiple roles.
-  return EqualIgnoringASCIICase(
-      element->FastGetAttribute(html_names::kRoleAttr), role);
-}
-
 AXObject* AXObjectCacheImpl::CreateFromRenderer(LayoutObject* layout_object) {
   // FIXME: How could layoutObject->node() ever not be an Element?
   Node* node = layout_object->GetNode();
 
-  // If the node is aria role="list" or the aria role is empty and its a
-  // ul/ol/dl type (it shouldn't be a list if aria says otherwise).
-  if (NodeHasRole(node, "list") || NodeHasRole(node, "directory") ||
-      (NodeHasRole(node, g_null_atom) &&
-       (IsA<HTMLUListElement>(node) || IsA<HTMLOListElement>(node) ||
-        IsA<HTMLDListElement>(node))))
-    return MakeGarbageCollected<AXList>(layout_object, *this);
-
   // media element
   if (node && node->IsMediaElement())
     return AccessibilityMediaElement::Create(layout_object, *this);
diff --git a/third_party/blink/renderer/platform/BUILD.gn b/third_party/blink/renderer/platform/BUILD.gn
index 8c7a7c4..2f13be3b 100644
--- a/third_party/blink/renderer/platform/BUILD.gn
+++ b/third_party/blink/renderer/platform/BUILD.gn
@@ -1243,6 +1243,7 @@
     "mojo/heap_mojo_receiver.h",
     "mojo/heap_mojo_receiver_set.h",
     "mojo/heap_mojo_remote.h",
+    "mojo/heap_mojo_remote_set.h",
     "mojo/heap_mojo_unique_receiver_set.h",
     "mojo/heap_mojo_wrapper_mode.h",
     "mojo/kurl_mojom_traits.h",
@@ -2056,6 +2057,7 @@
     "mojo/heap_mojo_associated_remote_test.cc",
     "mojo/heap_mojo_receiver_set_test.cc",
     "mojo/heap_mojo_receiver_test.cc",
+    "mojo/heap_mojo_remote_set_test.cc",
     "mojo/heap_mojo_remote_test.cc",
     "mojo/heap_mojo_unique_receiver_set_test.cc",
     "mojo/kurl_security_origin_test.cc",
diff --git a/third_party/blink/renderer/platform/fonts/shaping/shaping_line_breaker.cc b/third_party/blink/renderer/platform/fonts/shaping/shaping_line_breaker.cc
index 45d7563..76b953d1 100644
--- a/third_party/blink/renderer/platform/fonts/shaping/shaping_line_breaker.cc
+++ b/third_party/blink/renderer/platform/fonts/shaping/shaping_line_breaker.cc
@@ -157,8 +157,11 @@
     for (;; offset--) {
       offset = break_iterator_->PreviousBreakOpportunity(offset, start);
       if (offset <= start || offset >= text.length() ||
-          text[offset - 1] != kSoftHyphenCharacter)
+          text[offset - 1] != kSoftHyphenCharacter) {
+        if (IsBreakableSpace(text[offset - 1]))
+          return {offset, FindNonHangableEnd(text, offset - 1), false};
         return {offset, false};
+      }
     }
   }
 
@@ -183,8 +186,11 @@
   if (UNLIKELY(!IsSoftHyphenEnabled())) {
     for (;; offset++) {
       offset = break_iterator_->NextBreakOpportunity(offset);
-      if (offset >= text.length() || text[offset - 1] != kSoftHyphenCharacter)
+      if (offset >= text.length() || text[offset - 1] != kSoftHyphenCharacter) {
+        if (IsBreakableSpace(text[offset - 1]))
+          return {offset, FindNonHangableEnd(text, offset - 1), false};
         return {offset, false};
+      }
     }
   }
 
diff --git a/third_party/blink/renderer/platform/loader/fetch/resource.cc b/third_party/blink/renderer/platform/loader/fetch/resource.cc
index 3dd5655..b59317f 100644
--- a/third_party/blink/renderer/platform/loader/fetch/resource.cc
+++ b/third_party/blink/renderer/platform/loader/fetch/resource.cc
@@ -661,11 +661,9 @@
     // from volatile storage as promptly as possible"
     // "... History buffers MAY store such responses as part of their normal
     // operation."
-    // We allow non-secure content to be reused in history, but we do not allow
-    // secure content to be reused.
-    if (HasCacheControlNoStoreHeader() && Url().ProtocolIs("https") &&
-        IsMainThread())
+    if (HasCacheControlNoStoreHeader() && IsMainThread()) {
       GetMemoryCache()->Remove(this);
+    }
   }
 }
 
diff --git a/third_party/blink/renderer/platform/mojo/heap_mojo_remote_set.h b/third_party/blink/renderer/platform/mojo/heap_mojo_remote_set.h
new file mode 100644
index 0000000..0fcf98eb
--- /dev/null
+++ b/third_party/blink/renderer/platform/mojo/heap_mojo_remote_set.h
@@ -0,0 +1,122 @@
+// 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 THIRD_PARTY_BLINK_RENDERER_PLATFORM_MOJO_HEAP_MOJO_REMOTE_SET_H_
+#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_MOJO_HEAP_MOJO_REMOTE_SET_H_
+
+#include <utility>
+
+#include "base/memory/scoped_refptr.h"
+#include "base/sequenced_task_runner.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "mojo/public/cpp/bindings/remote_set.h"
+#include "third_party/blink/renderer/platform/context_lifecycle_observer.h"
+#include "third_party/blink/renderer/platform/heap/heap.h"
+#include "third_party/blink/renderer/platform/mojo/features.h"
+#include "third_party/blink/renderer/platform/mojo/heap_mojo_wrapper_mode.h"
+
+namespace blink {
+
+// HeapMojoRemoteSet is a wrapper for mojo::RemoteSet to be owned by a
+// garbage-collected object. Blink is expected to use HeapMojoRemoteSet by
+// default. HeapMojoRemoteSet must be associated with context.
+// HeapMojoRemoteSet's constructor takes context as a mandatory parameter.
+// HeapMojoRemoteSet resets the mojo connection when the associated
+// ExecutionContext is detached.
+
+// TODO(crbug.com/1058076) HeapMojoWrapperMode should be removed once we ensure
+// that the interface is not used after ContextDestroyed().
+template <typename Interface,
+          HeapMojoWrapperMode Mode = HeapMojoWrapperMode::kWithContextObserver>
+class HeapMojoRemoteSet {
+  DISALLOW_NEW();
+
+ public:
+  using DisconnectHandler =
+      typename mojo::RemoteSet<Interface>::DisconnectHandler;
+  using Iterator = typename mojo::RemoteSet<Interface>::Iterator;
+
+  explicit HeapMojoRemoteSet(ContextLifecycleNotifier* notifier)
+      : wrapper_(MakeGarbageCollected<Wrapper>(notifier)) {}
+  HeapMojoRemoteSet(const HeapMojoRemoteSet&) = delete;
+  HeapMojoRemoteSet& operator=(const HeapMojoRemoteSet&) = delete;
+  HeapMojoRemoteSet(HeapMojoRemoteSet&&) = default;
+  HeapMojoRemoteSet& operator=(HeapMojoRemoteSet&&) = default;
+
+  // Methods to redirect to mojo::RemoteSet:
+  void set_disconnect_handler(DisconnectHandler handler) {
+    wrapper_->remote_set().set_disconnect_handler(std::move(handler));
+  }
+
+  mojo::RemoteSetElementId Add(mojo::Remote<Interface> remote) {
+    return wrapper_->remote_set().Add(std::move(remote));
+  }
+
+  mojo::RemoteSetElementId Add(
+      mojo::PendingRemote<Interface> remote,
+      scoped_refptr<base::SequencedTaskRunner> task_runner) {
+    DCHECK(task_runner);
+    return wrapper_->remote_set().Add(std::move(remote), task_runner);
+  }
+
+  void Remove(mojo::RemoteSetElementId id) {
+    wrapper_->remote_set().Remove(id);
+  }
+
+  bool Contains(mojo::RemoteSetElementId id) {
+    return wrapper_->remote_set().Contains(id);
+  }
+
+  void Clear() { wrapper_->remote_set().Clear(); }
+
+  bool empty() const { return wrapper_->remote_set().empty(); }
+  size_t size() const { return wrapper_->remote_set().size(); }
+
+  Iterator begin() { return wrapper_->remote_set().begin(); }
+  Iterator begin() const { return wrapper_->remote_set().begin(); }
+  Iterator end() { return wrapper_->remote_set().end(); }
+  Iterator end() const { return wrapper_->remote_set().end(); }
+
+  void Trace(Visitor* visitor) const { visitor->Trace(wrapper_); }
+
+ private:
+  FRIEND_TEST_ALL_PREFIXES(HeapMojoRemoteSetGCWithContextObserverTest,
+                           NoClearOnConservativeGC);
+
+  // Garbage collected wrapper class to add ContextLifecycleObserver.
+  class Wrapper final : public GarbageCollected<Wrapper>,
+                        public ContextLifecycleObserver {
+   public:
+    explicit Wrapper(ContextLifecycleNotifier* notifier) {
+      SetContextLifecycleNotifier(notifier);
+    }
+    Wrapper(const Wrapper&) = delete;
+    Wrapper& operator=(const Wrapper&) = delete;
+    Wrapper(Wrapper&&) = default;
+    Wrapper& operator=(Wrapper&&) = default;
+
+    void Trace(Visitor* visitor) const override {
+      ContextLifecycleObserver::Trace(visitor);
+    }
+
+    mojo::RemoteSet<Interface>& remote_set() { return remote_set_; }
+
+    // ContextLifecycleObserver methods
+    void ContextDestroyed() override {
+      if (Mode == HeapMojoWrapperMode::kWithContextObserver ||
+          (Mode == HeapMojoWrapperMode::kWithoutContextObserver &&
+           base::FeatureList::IsEnabled(kHeapMojoUseContextObserver)))
+        remote_set_.Clear();
+    }
+
+   private:
+    mojo::RemoteSet<Interface> remote_set_;
+  };
+
+  Member<Wrapper> wrapper_;
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_MOJO_HEAP_MOJO_REMOTE_SET_H_
diff --git a/third_party/blink/renderer/platform/mojo/heap_mojo_remote_set_test.cc b/third_party/blink/renderer/platform/mojo/heap_mojo_remote_set_test.cc
new file mode 100644
index 0000000..609eabb
--- /dev/null
+++ b/third_party/blink/renderer/platform/mojo/heap_mojo_remote_set_test.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 "third_party/blink/renderer/platform/mojo/heap_mojo_remote_set.h"
+
+#include <utility>
+
+#include <string>
+#include "base/test/null_task_runner.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/remote_set.h"
+#include "mojo/public/interfaces/bindings/tests/sample_service.mojom-blink.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/renderer/platform/context_lifecycle_notifier.h"
+#include "third_party/blink/renderer/platform/heap/heap_test_utilities.h"
+#include "third_party/blink/renderer/platform/heap/persistent.h"
+#include "third_party/blink/renderer/platform/heap_observer_set.h"
+#include "third_party/blink/renderer/platform/mojo/heap_mojo_wrapper_mode.h"
+
+namespace blink {
+
+namespace {
+
+class FakeContextNotifier final : public GarbageCollected<FakeContextNotifier>,
+                                  public ContextLifecycleNotifier {
+ public:
+  FakeContextNotifier() = default;
+
+  void AddContextLifecycleObserver(
+      ContextLifecycleObserver* observer) override {
+    observers_.AddObserver(observer);
+  }
+  void RemoveContextLifecycleObserver(
+      ContextLifecycleObserver* observer) override {
+    observers_.RemoveObserver(observer);
+  }
+
+  void NotifyContextDestroyed() {
+    observers_.ForEachObserver([](ContextLifecycleObserver* observer) {
+      observer->ContextDestroyed();
+    });
+  }
+
+  void Trace(Visitor* visitor) const override {
+    visitor->Trace(observers_);
+    ContextLifecycleNotifier::Trace(visitor);
+  }
+
+ private:
+  HeapObserverSet<ContextLifecycleObserver> observers_;
+};
+
+template <HeapMojoWrapperMode Mode>
+class HeapMojoRemoteSetGCBaseTest;
+
+template <HeapMojoWrapperMode Mode>
+class GCOwner : public GarbageCollected<GCOwner<Mode>> {
+ public:
+  explicit GCOwner(FakeContextNotifier* context,
+                   HeapMojoRemoteSetGCBaseTest<Mode>* test)
+      : remote_set_(context), test_(test) {
+    test_->set_is_owner_alive(true);
+  }
+  void Dispose() { test_->set_is_owner_alive(false); }
+  void Trace(Visitor* visitor) const { visitor->Trace(remote_set_); }
+
+  HeapMojoRemoteSet<sample::blink::Service, Mode>& remote_set() {
+    return remote_set_;
+  }
+
+ private:
+  HeapMojoRemoteSet<sample::blink::Service, Mode> remote_set_;
+  HeapMojoRemoteSetGCBaseTest<Mode>* test_;
+};
+
+template <HeapMojoWrapperMode Mode>
+class HeapMojoRemoteSetGCBaseTest : public TestSupportingGC {
+ public:
+  FakeContextNotifier* context() { return context_; }
+  scoped_refptr<base::NullTaskRunner> task_runner() {
+    return null_task_runner_;
+  }
+  GCOwner<Mode>* owner() { return owner_; }
+  void set_is_owner_alive(bool alive) { is_owner_alive_ = alive; }
+
+  void ClearOwner() { owner_ = nullptr; }
+
+ protected:
+  void SetUp() override {
+    context_ = MakeGarbageCollected<FakeContextNotifier>();
+    owner_ = MakeGarbageCollected<GCOwner<Mode>>(context(), this);
+  }
+  void TearDown() override {
+    owner_ = nullptr;
+    PreciselyCollectGarbage();
+  }
+
+  Persistent<FakeContextNotifier> context_;
+  Persistent<GCOwner<Mode>> owner_;
+  bool is_owner_alive_ = false;
+  scoped_refptr<base::NullTaskRunner> null_task_runner_ =
+      base::MakeRefCounted<base::NullTaskRunner>();
+};
+
+}  // namespace
+
+class HeapMojoRemoteSetGCWithContextObserverTest
+    : public HeapMojoRemoteSetGCBaseTest<
+          HeapMojoWrapperMode::kWithContextObserver> {};
+class HeapMojoRemoteSetGCWithoutContextObserverTest
+    : public HeapMojoRemoteSetGCBaseTest<
+          HeapMojoWrapperMode::kForceWithoutContextObserver> {};
+
+// GC the HeapMojoRemoteSet with context observer and verify that the remote
+// is no longer part of the set, and that the service was deleted.
+TEST_F(HeapMojoRemoteSetGCWithContextObserverTest, RemovesRemote) {
+  auto& remote_set = owner()->remote_set();
+  auto remote = mojo::PendingRemote<sample::blink::Service>(
+      mojo::MessagePipe().handle0, 0);
+
+  mojo::RemoteSetElementId rid =
+      remote_set.Add(std::move(remote), task_runner());
+  EXPECT_TRUE(remote_set.Contains(rid));
+
+  remote_set.Remove(rid);
+
+  EXPECT_FALSE(remote_set.Contains(rid));
+}
+
+// Check that the wrapper does not outlive the owner when ConservativeGC finds
+// the wrapper.
+TEST_F(HeapMojoRemoteSetGCWithContextObserverTest, NoClearOnConservativeGC) {
+  auto* wrapper = owner_->remote_set().wrapper_.Get();
+
+  auto remote = mojo::PendingRemote<sample::blink::Service>(
+      mojo::MessagePipe().handle0, 0);
+
+  mojo::RemoteSetElementId rid =
+      owner()->remote_set().Add(std::move(remote), task_runner());
+  EXPECT_TRUE(wrapper->remote_set().Contains(rid));
+
+  ClearOwner();
+  EXPECT_TRUE(is_owner_alive_);
+
+  ConservativelyCollectGarbage();
+
+  EXPECT_TRUE(wrapper->remote_set().Contains(rid));
+  EXPECT_TRUE(is_owner_alive_);
+}
+
+// GC the HeapMojoRemoteSet without context observer and verify that the
+// remote is no longer part of the set, and that the service was deleted.
+TEST_F(HeapMojoRemoteSetGCWithoutContextObserverTest, RemovesRemote) {
+  auto& remote_set = owner()->remote_set();
+  auto remote = mojo::PendingRemote<sample::blink::Service>(
+      mojo::MessagePipe().handle0, 0);
+
+  mojo::RemoteSetElementId rid =
+      remote_set.Add(std::move(remote), task_runner());
+  EXPECT_TRUE(remote_set.Contains(rid));
+
+  remote_set.Remove(rid);
+
+  EXPECT_FALSE(remote_set.Contains(rid));
+}
+
+// GC the HeapMojoRemoteSet with context observer and verify that the remote
+// is no longer part of the set, and that the service was deleted.
+TEST_F(HeapMojoRemoteSetGCWithContextObserverTest, ClearLeavesSetEmpty) {
+  auto& remote_set = owner()->remote_set();
+  auto remote = mojo::PendingRemote<sample::blink::Service>(
+      mojo::MessagePipe().handle0, 0);
+
+  mojo::RemoteSetElementId rid =
+      remote_set.Add(std::move(remote), task_runner());
+  EXPECT_TRUE(remote_set.Contains(rid));
+
+  remote_set.Clear();
+
+  EXPECT_FALSE(remote_set.Contains(rid));
+}
+
+// GC the HeapMojoRemoteSet without context observer and verify that the
+// remote is no longer part of the set, and that the service was deleted.
+TEST_F(HeapMojoRemoteSetGCWithoutContextObserverTest, ClearLeavesSetEmpty) {
+  auto& remote_set = owner()->remote_set();
+  auto remote = mojo::PendingRemote<sample::blink::Service>(
+      mojo::MessagePipe().handle0, 0);
+
+  mojo::RemoteSetElementId rid =
+      remote_set.Add(std::move(remote), task_runner());
+  EXPECT_TRUE(remote_set.Contains(rid));
+
+  remote_set.Clear();
+
+  EXPECT_FALSE(remote_set.Contains(rid));
+}
+
+// Add several remote and confirm that remote_set holds properly.
+TEST_F(HeapMojoRemoteSetGCWithContextObserverTest, AddSeveralRemoteSet) {
+  auto& remote_set = owner()->remote_set();
+
+  EXPECT_TRUE(remote_set.empty());
+  EXPECT_EQ(remote_set.size(), 0u);
+
+  auto remote_1 = mojo::PendingRemote<sample::blink::Service>(
+      mojo::MessagePipe().handle0, 0);
+  mojo::RemoteSetElementId rid_1 =
+      remote_set.Add(std::move(remote_1), task_runner());
+  EXPECT_TRUE(remote_set.Contains(rid_1));
+  EXPECT_FALSE(remote_set.empty());
+  EXPECT_EQ(remote_set.size(), 1u);
+
+  auto remote_2 = mojo::PendingRemote<sample::blink::Service>(
+      mojo::MessagePipe().handle0, 0);
+  mojo::RemoteSetElementId rid_2 =
+      remote_set.Add(std::move(remote_2), task_runner());
+  EXPECT_TRUE(remote_set.Contains(rid_1));
+  EXPECT_TRUE(remote_set.Contains(rid_2));
+  EXPECT_FALSE(remote_set.empty());
+  EXPECT_EQ(remote_set.size(), 2u);
+
+  remote_set.Clear();
+
+  EXPECT_FALSE(remote_set.Contains(rid_1));
+  EXPECT_FALSE(remote_set.Contains(rid_2));
+  EXPECT_TRUE(remote_set.empty());
+  EXPECT_EQ(remote_set.size(), 0u);
+}
+
+}  // namespace blink
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 8153f40..6e9e6f79 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -2471,11 +2471,7 @@
 
 # ====== New tests from wpt-importer added here ======
 crbug.com/626703 external/wpt/html/interaction/focus/document-level-focus-apis/document-has-system-focus.html [ Timeout ]
-crbug.com/626703 external/wpt/css/css-backgrounds/box-shadow-radius-000.html [ Failure ]
-crbug.com/626703 virtual/composite-bgcolor-animation/external/wpt/css/css-backgrounds/box-shadow-radius-000.html [ Failure ]
-crbug.com/626703 virtual/composite-bgcolor-animation/external/wpt/css/css-backgrounds/box-shadow-radius-001.html [ Failure ]
 crbug.com/626703 [ Mac11.0 ] virtual/threaded/external/wpt/animation-worklet/worklet-animation-set-keyframes.https.html [ Failure ]
-crbug.com/626703 external/wpt/css/css-backgrounds/box-shadow-radius-001.html [ Failure ]
 crbug.com/626703 external/wpt/css/css-images/image-orientation/image-orientation-exif-png.html [ Failure ]
 crbug.com/626703 external/wpt/css/css-conditional/css-supports-040.xht [ Failure ]
 crbug.com/626703 external/wpt/css/css-conditional/css-supports-042.xht [ Failure ]
@@ -3233,13 +3229,22 @@
 crbug.com/697971 [ Mac10.12 ] virtual/text-antialias/selection/flexbox-selection-nested.html [ Skip ]
 crbug.com/697971 [ Mac10.12 ] virtual/text-antialias/selection/flexbox-selection.html [ Skip ]
 
-# [composite-bgcolor-animation]
-crbug.com/1148369 virtual/composite-bgcolor-animation/external/wpt/css/css-backgrounds/border-bottom-left-radius-010.xht [ Failure Pass ]
-crbug.com/1148369 virtual/composite-bgcolor-animation/external/wpt/css/css-backgrounds/border-bottom-right-radius-010.xht [ Failure Pass ]
-crbug.com/1148369 virtual/composite-bgcolor-animation/external/wpt/css/css-backgrounds/border-radius-clip-001.html [ Failure Pass ]
-crbug.com/1148369 virtual/composite-bgcolor-animation/external/wpt/css/css-backgrounds/border-radius-clip-002.htm [ Failure Pass ]
-crbug.com/1148369 virtual/composite-bgcolor-animation/external/wpt/css/css-backgrounds/border-top-left-radius-010.xht [ Failure Pass ]
-crbug.com/1148369 virtual/composite-bgcolor-animation/external/wpt/css/css-backgrounds/border-top-right-radius-010.xht [ Failure Pass ]
+crbug.com/1160916 virtual/composite-bgcolor-animation/external/wpt/css/css-backgrounds/border-bottom-left-radius-010.xht [ Failure Pass ]
+crbug.com/1160916 external/wpt/css/css-backgrounds/border-bottom-left-radius-010.xht [ Failure Pass ]
+crbug.com/1160916 virtual/composite-bgcolor-animation/external/wpt/css/css-backgrounds/border-bottom-right-radius-010.xht [ Failure Pass ]
+crbug.com/1160916 external/wpt/css/css-backgrounds/border-bottom-right-radius-010.xht [ Failure Pass ]
+crbug.com/1160916 virtual/composite-bgcolor-animation/external/wpt/css/css-backgrounds/border-radius-clip-001.html [ Failure Pass ]
+crbug.com/1160916 external/wpt/css/css-backgrounds/border-radius-clip-001.html [ Failure Pass ]
+crbug.com/1160916 virtual/composite-bgcolor-animation/external/wpt/css/css-backgrounds/border-radius-clip-002.htm [ Failure Pass ]
+crbug.com/1160916 external/wpt/css/css-backgrounds/border-radius-clip-002.htm [ Failure Pass ]
+crbug.com/1160916 virtual/composite-bgcolor-animation/external/wpt/css/css-backgrounds/border-top-left-radius-010.xht [ Failure Pass ]
+crbug.com/1160916 external/wpt/css/css-backgrounds/border-top-left-radius-010.xht [ Failure Pass ]
+crbug.com/1160916 virtual/composite-bgcolor-animation/external/wpt/css/css-backgrounds/border-top-right-radius-010.xht [ Failure Pass ]
+crbug.com/1160916 external/wpt/css/css-backgrounds/border-top-right-radius-010.xht [ Failure Pass ]
+crbug.com/1160916 virtual/composite-bgcolor-animation/external/wpt/css/css-backgrounds/box-shadow-radius-000.html [ Failure ]
+crbug.com/1160916 external/wpt/css/css-backgrounds/box-shadow-radius-000.html [ Failure ]
+crbug.com/1160916 virtual/composite-bgcolor-animation/external/wpt/css/css-backgrounds/box-shadow-radius-001.html [ Failure ]
+crbug.com/1160916 external/wpt/css/css-backgrounds/box-shadow-radius-001.html [ Failure ]
 
 # [css-grid]
 crbug.com/921722 external/wpt/css/css-grid/abspos/descendant-static-position-001.html [ Failure ]
@@ -5884,10 +5889,6 @@
 crbug.com/681468 fast/forms/suggestion-picker/date-suggestion-picker-appearance-zoom125.html [ Failure Pass ]
 crbug.com/681468 fast/forms/suggestion-picker/date-suggestion-picker-appearance-zoom200.html [ Failure Pass ]
 
-# Other fast/forms/suggestion-picker/time-suggestion-picker-appearance.html
-# failures on Win7 caused by focus appearing on the wrong element.
-crbug.com/1160594 [ Win7 ] fast/forms/suggestion-picker/time-suggestion-picker-appearance.html [ Failure ]
-
 # These tests will only run in the virtual test suite where the frequency
 # capping for overlay popup detection is disabled. This eliminates the need
 # for waitings in web tests to trigger a detection event.
@@ -5920,5 +5921,3 @@
 
 # Sheriff 2020-12-14
 crbug.com/1046784 http/tests/devtools/console/console-context-selector.js [ Pass Timeout ]
-
-
diff --git a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
index 968f3e6..997b5f4 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
@@ -6376,6 +6376,20 @@
      ]
     },
     "selectors": {
+     "focus-visible-006-manual.html": [
+      "974abc0024e3153008e944ff706156efcb9948e2",
+      [
+       null,
+       {}
+      ]
+     ],
+     "focus-visible-008-manual.html": [
+      "53306a90bbcae67ca65feefce3e4d3758d1a4ecd",
+      [
+       null,
+       {}
+      ]
+     ],
      "hover-001-manual.html": [
       "87d7af91b6994371b72ff33923ea1cacc65c8b5b",
       [
@@ -185743,6 +185757,14 @@
       "2b0f294c3441f1cf25a6678b9ce0748258582ea1",
       []
      ],
+     "flex-item-compressible-001-expected.txt": [
+      "4ddf85ea6cc7a830f0afc262097795e35b13ef4d",
+      []
+     ],
+     "flex-item-compressible-002-expected.txt": [
+      "cd5115045af41b8997e8261e2923b4c8affcfabe",
+      []
+     ],
      "flex-lines": {
       "multi-line-wrap-reverse-column-reverse-ref.html": [
        "38366a62f7e6b7df30b2da814cf178ebba5a83fa",
@@ -222355,6 +222377,10 @@
         "a3ffdd005afadb784b1b64a12caa190a66271018",
         []
        ],
+       "location-prevent-extensions-expected.txt": [
+        "9f79369fbc41bf564547199727d8a4b8256b7247",
+        []
+       ],
        "location-protocol-setter-non-broken-expected.txt": [
         "4d1f8a9abd4d46f445ceb66ec1f65fe65425aabd",
         []
@@ -263878,10 +263904,6 @@
        "905836ca6bc3388a39e073ec478bf50db6982407",
        []
       ],
-      "002.worker-expected.txt": [
-       "d73864c4701f9428e561ccbf2b64702ba87fe845",
-       []
-      ],
       "003-expected.txt": [
        "e72a68ecc1b03d21f201b48630cea1a481978c8d",
        []
@@ -263893,10 +263915,6 @@
       "004-expected.txt": [
        "d834acc47ce940bf6d1c9eeb9c36bd76d49b2a33",
        []
-      ],
-      "004.any.sharedworker-expected.txt": [
-       "ba2bc24d5f5458cc472838cd8698f2a3117a1f5c",
-       []
       ]
      },
      "multiple-workers": {
@@ -264085,6 +264103,10 @@
       "aae86a0b055346f8439cbec4f98df8d08d472368",
       []
      ],
+     "Worker-run-forever-during-top-level-await.js": [
+      "a1c8b32b619753941999c7f41e9169816c396d36",
+      []
+     ],
      "Worker-run-forever.js": [
       "2d822d407f4c83fe7321cbd4cb1168626de05eea",
       []
@@ -293155,6 +293177,20 @@
        {}
       ]
      ],
+     "flex-item-compressible-001.html": [
+      "9725512cd8027b8b8fe4bd966f96db1b5242456e",
+      [
+       null,
+       {}
+      ]
+     ],
+     "flex-item-compressible-002.html": [
+      "d1a14b9bfe3cbb05f598dee987ccd41ec5d4013e",
+      [
+       null,
+       {}
+      ]
+     ],
      "flex-item-contains-strict.html": [
       "25849cc64b2f10cf2d319e1a2f750f32d64359b5",
       [
@@ -315005,15 +315041,6 @@
        }
       ]
      ],
-     "focus-visible-006.html": [
-      "4b034b2e27962810bd9357ff88e5fe4587e56d25",
-      [
-       null,
-       {
-        "testdriver": true
-       }
-      ]
-     ],
      "focus-visible-007.html": [
       "95a27006e62076909f63f9946e1f2d7f8331732a",
       [
@@ -315023,15 +315050,6 @@
        }
       ]
      ],
-     "focus-visible-008.html": [
-      "75f676f53f3319cb672195343c77cc7ebd94eee4",
-      [
-       null,
-       {
-        "testdriver": true
-       }
-      ]
-     ],
      "focus-visible-009.html": [
       "4dae6407adac3971db6064822643332cf7f1722d",
       [
@@ -346182,6 +346200,13 @@
          {}
         ]
        ],
+       "location-non-configurable-toString-valueOf.html": [
+        "80760ac9e44e2513017b583329e1a9cddeccdb60",
+        [
+         null,
+         {}
+        ]
+       ],
        "location-origin-idna.sub.window.js": [
         "83b030f88651aeb66998b724ffcec31ad72801bc",
         [
@@ -346196,6 +346221,13 @@
          {}
         ]
        ],
+       "location-prevent-extensions.html": [
+        "a8c777aded82c384e8106db824b2b175bfe15774",
+        [
+         null,
+         {}
+        ]
+       ],
        "location-protocol-setter-non-broken-weird.html": [
         "78ba5f6e401dadf43034c772118bba466e678131",
         [
@@ -346224,6 +346256,13 @@
          {}
         ]
        ],
+       "location-prototype-no-toString-valueOf.html": [
+        "56316320af59dd1531c661fb854643736cf3ef6e",
+        [
+         null,
+         {}
+        ]
+       ],
        "location-prototype-setting-cross-origin-domain.sub.html": [
         "1e677b03653a7d391653865a02ab45096fcff4e9",
         [
@@ -346604,7 +346643,7 @@
         ]
        ],
        "cross-origin-objects.html": [
-        "3b7c7832d7e7f215ccc810ae583f8cfdf925d497",
+        "577129502426989888a1791f78df46b6ed23c55e",
         [
          null,
          {
@@ -347284,6 +347323,13 @@
       ]
      },
      "the-windowproxy-exotic-object": {
+      "windowproxy-prevent-extensions.html": [
+       "97a156290a1c5ad15a36f24b0009d9f599c8be3e",
+       [
+        null,
+        {}
+       ]
+      ],
       "windowproxy-prototype-setting-cross-origin-domain.sub.html": [
        "a5ae78cc87dc8698e819b5132fb1514ac4f25860",
        [
@@ -446148,7 +446194,7 @@
      ]
     ],
     "Worker-terminate-forever-during-evaluation.html": [
-     "4513c5b53790d11b4e26b0fd38a4e9bee9f1c9ad",
+     "ab66f29289f21d6821a3edccdec9ff3512f86919",
      [
       null,
       {}
@@ -448176,7 +448222,7 @@
        ]
       ],
       "002.worker.js": [
-       "065cc6e6f1705fc0157ee80ccd8daee4af4ac534",
+       "8eb41c23fdcfc2a81a135582648b93ebfde40d22",
        [
         "workers/semantics/interface-objects/002.worker.html",
         {}
@@ -448197,7 +448243,7 @@
        ]
       ],
       "004.any.js": [
-       "2cbb4b54256b4587a9bdc139e03f5e7b07756915",
+       "963a9962d11f36f2a7fc01854b30d3a7477b7ef8",
        [
         "workers/semantics/interface-objects/004.any.sharedworker.html",
         {
diff --git a/third_party/blink/web_tests/external/wpt/css/css-flexbox/flex-item-compressible-001-expected.txt b/third_party/blink/web_tests/external/wpt/css/css-flexbox/flex-item-compressible-001-expected.txt
new file mode 100644
index 0000000..4ddf85e
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-flexbox/flex-item-compressible-001-expected.txt
@@ -0,0 +1,28 @@
+This is a testharness.js-based test.
+PASS .flexbox 1
+PASS .flexbox 2
+PASS .flexbox 3
+PASS .flexbox 4
+PASS .flexbox 5
+PASS .flexbox 6
+PASS .flexbox 7
+PASS .flexbox 8
+PASS .flexbox 9
+PASS .flexbox 10
+FAIL .flexbox 11 assert_equals: 
+<div class="flexbox">
+      <div class="spacer"></div>
+      <input type="text" class="test3" data-expected-width="140">
+    </div>
+width expected 140 but got 100
+FAIL .flexbox 12 assert_equals: 
+<div class="flexbox">
+      <div class="spacer"></div>
+      <input type="range" class="test3" data-expected-width="140">
+    </div>
+width expected 140 but got 100
+PASS .flexbox 13
+PASS .flexbox 14
+PASS .flexbox 15
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/css/css-flexbox/flex-item-compressible-001.html b/third_party/blink/web_tests/external/wpt/css/css-flexbox/flex-item-compressible-001.html
new file mode 100644
index 0000000..9725512c
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-flexbox/flex-item-compressible-001.html
@@ -0,0 +1,149 @@
+<!DOCTYPE html>
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+  <meta charset="utf-8">
+  <title>CSS Flexbox Test: Testing automatic minimun size of &lt;input&gt; flex items in a row flex container</title>
+  <link rel="author" title="Ting-Yu Lin" href="mailto:tlin@mozilla.com">
+  <link rel="author" title="Mozilla" href="https://www.mozilla.org/">
+  <link rel="help" href="https://drafts.csswg.org/css-flexbox-1/#min-size-auto">
+  <link rel="help" href="https://drafts.csswg.org/css-sizing-3/#replaced-percentage-min-contribution">
+  <link rel="help" href="https://drafts.csswg.org/css-sizing-3/#min-content-zero">
+  <link rel="stylesheet" href="/fonts/ahem.css">
+  <meta name="assert" content="This test verifies that an <input> flex item should resolve its percentage part of main size to zero when computing specified size suggestion.">
+
+  <script src="/resources/testharness.js"></script>
+  <script src="/resources/testharnessreport.js"></script>
+  <script src="/resources/check-layout-th.js"></script>
+
+  <style>
+  .flexbox {
+    display: flex;
+    width: 300px;
+    height: 40px;
+    border: 1px solid black;
+    margin-bottom: 5px;
+  }
+  .spacer {
+    /* Just to occupy some space, so that the flex algorithm will try to shrink
+       the <input> element below its percentage specified width. */
+    flex: 0 0 200px;
+    background: lightgray;
+  }
+  input {
+    font: 20px/1 Ahem;
+    background: lightblue;
+    /* Get rid of native theming and UA default styles. */
+    appearance: none;
+    border: 0;
+    padding: 0;
+    margin: 0;
+  }
+  .test1 {
+    width: 100%;
+  }
+  .test2 {
+    width: calc(100%);
+  }
+  .test3 {
+    width: calc(140px + 100%);
+  }
+  </style>
+
+  <body onload="checkLayout('.flexbox')">
+    <p>Test1: "width: 100%"</p>
+    <div class="flexbox">
+      <div class="spacer"></div>
+      <input type="text"
+             class="test1" data-expected-width="100">
+    </div>
+
+    <div class="flexbox">
+      <div class="spacer"></div>
+      <input type="range"
+             class="test1" data-expected-width="100">
+    </div>
+
+    <div class="flexbox">
+      <div class="spacer"></div>
+      <input type="button" value="XXXXXXX"
+             class="test1" data-expected-width="140">
+    </div>
+
+    <div class="flexbox">
+      <div class="spacer"></div>
+      <input type="submit" value="XXXXXXX"
+             class="test1" data-expected-width="140">
+    </div>
+
+    <div class="flexbox">
+      <div class="spacer"></div>
+      <input type="reset" value="XXXXXXX"
+             class="test1" data-expected-width="140">
+    </div>
+
+    <p>Test2: "width: calc(100%)"</p>
+    <div class="flexbox">
+      <div class="spacer"></div>
+      <input type="text"
+             class="test2" data-expected-width="100">
+    </div>
+
+    <div class="flexbox">
+      <div class="spacer"></div>
+      <input type="range"
+             class="test2" data-expected-width="100">
+    </div>
+
+    <div class="flexbox">
+      <div class="spacer"></div>
+      <input type="button" value="XXXXXXX"
+             class="test2" data-expected-width="140">
+    </div>
+
+    <div class="flexbox">
+      <div class="spacer"></div>
+      <input type="submit" value="XXXXXXX"
+             class="test2" data-expected-width="140">
+    </div>
+
+    <div class="flexbox">
+      <div class="spacer"></div>
+      <input type="reset" value="XXXXXXX"
+             class="test2" data-expected-width="140">
+    </div>
+
+    <p>Test3: "width: calc(140px + 100%)"</p>
+    <div class="flexbox">
+      <div class="spacer"></div>
+      <input type="text"
+             class="test3" data-expected-width="140">
+    </div>
+
+    <div class="flexbox">
+      <div class="spacer"></div>
+      <input type="range"
+             class="test3" data-expected-width="140">
+    </div>
+
+    <div class="flexbox">
+      <div class="spacer"></div>
+      <input type="button" value="XXXXXXX"
+             class="test3" data-expected-width="140">
+    </div>
+
+    <div class="flexbox">
+      <div class="spacer"></div>
+      <input type="submit" value="XXXXXXX"
+             class="test3" data-expected-width="140">
+    </div>
+
+    <div class="flexbox">
+      <div class="spacer"></div>
+      <input type="reset" value="XXXXXXX"
+             class="test3" data-expected-width="140">
+    </div>
+  </body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-flexbox/flex-item-compressible-002-expected.txt b/third_party/blink/web_tests/external/wpt/css/css-flexbox/flex-item-compressible-002-expected.txt
new file mode 100644
index 0000000..cd51150
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-flexbox/flex-item-compressible-002-expected.txt
@@ -0,0 +1,73 @@
+This is a testharness.js-based test.
+PASS .flexbox 1
+PASS .flexbox 2
+FAIL .flexbox 3 assert_equals: 
+<div class="flexbox">
+      <div class="spacer"></div>
+      <input type="button" value="XXXXXXX" class="test1" data-expected-height="140">
+    </div>
+height expected 140 but got 100
+FAIL .flexbox 4 assert_equals: 
+<div class="flexbox">
+      <div class="spacer"></div>
+      <input type="submit" value="XXXXXXX" class="test1" data-expected-height="140">
+    </div>
+height expected 140 but got 100
+FAIL .flexbox 5 assert_equals: 
+<div class="flexbox">
+      <div class="spacer"></div>
+      <input type="reset" value="XXXXXXX" class="test1" data-expected-height="140">
+    </div>
+height expected 140 but got 100
+PASS .flexbox 6
+PASS .flexbox 7
+FAIL .flexbox 8 assert_equals: 
+<div class="flexbox">
+      <div class="spacer"></div>
+      <input type="button" value="XXXXXXX" class="test2" data-expected-height="140">
+    </div>
+height expected 140 but got 100
+FAIL .flexbox 9 assert_equals: 
+<div class="flexbox">
+      <div class="spacer"></div>
+      <input type="submit" value="XXXXXXX" class="test2" data-expected-height="140">
+    </div>
+height expected 140 but got 100
+FAIL .flexbox 10 assert_equals: 
+<div class="flexbox">
+      <div class="spacer"></div>
+      <input type="reset" value="XXXXXXX" class="test2" data-expected-height="140">
+    </div>
+height expected 140 but got 100
+FAIL .flexbox 11 assert_equals: 
+<div class="flexbox">
+      <div class="spacer"></div>
+      <input type="text" class="test3" data-expected-height="140">
+    </div>
+height expected 140 but got 100
+FAIL .flexbox 12 assert_equals: 
+<div class="flexbox">
+      <div class="spacer"></div>
+      <input type="range" class="test3" data-expected-height="140">
+    </div>
+height expected 140 but got 100
+FAIL .flexbox 13 assert_equals: 
+<div class="flexbox">
+      <div class="spacer"></div>
+      <input type="button" value="XXXXXXX" class="test3" data-expected-height="140">
+    </div>
+height expected 140 but got 100
+FAIL .flexbox 14 assert_equals: 
+<div class="flexbox">
+      <div class="spacer"></div>
+      <input type="submit" value="XXXXXXX" class="test3" data-expected-height="140">
+    </div>
+height expected 140 but got 100
+FAIL .flexbox 15 assert_equals: 
+<div class="flexbox">
+      <div class="spacer"></div>
+      <input type="reset" value="XXXXXXX" class="test3" data-expected-height="140">
+    </div>
+height expected 140 but got 100
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/css/css-flexbox/flex-item-compressible-002.html b/third_party/blink/web_tests/external/wpt/css/css-flexbox/flex-item-compressible-002.html
new file mode 100644
index 0000000..d1a14b9
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-flexbox/flex-item-compressible-002.html
@@ -0,0 +1,151 @@
+<!DOCTYPE html>
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+  <meta charset="utf-8">
+  <title>CSS Flexbox Test: Testing automatic minimun size of &lt;input&gt; flex items in a column flex container</title>
+  <link rel="author" title="Ting-Yu Lin" href="mailto:tlin@mozilla.com">
+  <link rel="author" title="Mozilla" href="https://www.mozilla.org/">
+  <link rel="help" href="https://drafts.csswg.org/css-flexbox-1/#min-size-auto">
+  <link rel="help" href="https://drafts.csswg.org/css-sizing-3/#replaced-percentage-min-contribution">
+  <link rel="help" href="https://drafts.csswg.org/css-sizing-3/#min-content-zero">
+  <link rel="stylesheet" href="/fonts/ahem.css">
+  <meta name="assert" content="This test verifies that an <input> flex item should resolve its percentage part of main size to zero when computing specified size suggestion.">
+
+  <script src="/resources/testharness.js"></script>
+  <script src="/resources/testharnessreport.js"></script>
+  <script src="/resources/check-layout-th.js"></script>
+
+  <style>
+  .flexbox {
+    display: inline-flex;
+    flex-direction: column;
+    width: 40px;
+    height: 300px;
+    border: 1px solid black;
+    margin-bottom: 40px;
+  }
+  .spacer {
+    /* Just to occupy some space, so that the flex algorithm will try to shrink
+       the <input> element below its percentage specified height. */
+    flex: 0 0 200px;
+    background: lightgray;
+  }
+  input {
+    writing-mode: vertical-lr;
+    font: 20px/1 Ahem;
+    background: lightblue;
+    /* Get rid of native theming and UA default styles. */
+    appearance: none;
+    border: 0;
+    padding: 0;
+    margin: 0;
+  }
+  .test1 {
+    height: 100%;
+  }
+  .test2 {
+    height: calc(100%);
+  }
+  .test3 {
+    height: calc(140px + 100%);
+  }
+  </style>
+
+  <body onload="checkLayout('.flexbox')">
+    <p>Test1: "height: 100%"</p>
+    <div class="flexbox">
+      <div class="spacer"></div>
+      <input type="text"
+             class="test1" data-expected-height="100">
+    </div>
+
+    <div class="flexbox">
+      <div class="spacer"></div>
+      <input type="range"
+             class="test1" data-expected-height="100">
+    </div>
+
+    <div class="flexbox">
+      <div class="spacer"></div>
+      <input type="button" value="XXXXXXX"
+             class="test1" data-expected-height="140">
+    </div>
+
+    <div class="flexbox">
+      <div class="spacer"></div>
+      <input type="submit" value="XXXXXXX"
+             class="test1" data-expected-height="140">
+    </div>
+
+    <div class="flexbox">
+      <div class="spacer"></div>
+      <input type="reset" value="XXXXXXX"
+             class="test1" data-expected-height="140">
+    </div>
+
+    <p>Test2: "height: calc(100%)"</p>
+    <div class="flexbox">
+      <div class="spacer"></div>
+      <input type="text"
+             class="test2" data-expected-height="100">
+    </div>
+
+    <div class="flexbox">
+      <div class="spacer"></div>
+      <input type="range"
+             class="test2" data-expected-height="100">
+    </div>
+
+    <div class="flexbox">
+      <div class="spacer"></div>
+      <input type="button" value="XXXXXXX"
+             class="test2" data-expected-height="140">
+    </div>
+
+    <div class="flexbox">
+      <div class="spacer"></div>
+      <input type="submit" value="XXXXXXX"
+             class="test2" data-expected-height="140">
+    </div>
+
+    <div class="flexbox">
+      <div class="spacer"></div>
+      <input type="reset" value="XXXXXXX"
+             class="test2" data-expected-height="140">
+    </div>
+
+    <p>Test3: "height: calc(140px + 100%)"</p>
+    <div class="flexbox">
+      <div class="spacer"></div>
+      <input type="text"
+             class="test3" data-expected-height="140">
+    </div>
+
+    <div class="flexbox">
+      <div class="spacer"></div>
+      <input type="range"
+             class="test3" data-expected-height="140">
+    </div>
+
+    <div class="flexbox">
+      <div class="spacer"></div>
+      <input type="button" value="XXXXXXX"
+             class="test3" data-expected-height="140">
+    </div>
+
+    <div class="flexbox">
+      <div class="spacer"></div>
+      <input type="submit" value="XXXXXXX"
+             class="test3" data-expected-height="140">
+    </div>
+
+    <div class="flexbox">
+      <div class="spacer"></div>
+      <input type="reset" value="XXXXXXX"
+             class="test3" data-expected-height="140">
+    </div>
+  </body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/hyphens/hyphens-none-014.html b/third_party/blink/web_tests/external/wpt/css/css-text/hyphens/hyphens-none-014.html
new file mode 100644
index 0000000..9f5c688
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/hyphens/hyphens-none-014.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Text level 3 Test: Line breaking with floats and disabled hyphenation </title>
+<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com"/>
+<link rel="help" title="5.4. Hyphenation: the hyphens property" href="https://drafts.csswg.org/css-text-3/#hyphenation">
+<link rel="help" href="https://drafts.csswg.org/css-text-3/#valdef-hyphens-none">
+<link rel="match" href="../../reference/ref-filled-green-100px-square-only.html">
+<meta name="assert" content="A span with hypens 'none' is wrapped based on the available space left by a float image.">
+<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
+<style>
+div { font: 20px/1 Ahem; }
+img { float:right; }
+.test {
+    max-width: 100px;
+    color: green;
+}
+span { hyphens: none; }
+.ref {
+    position: absolute;
+    background: green linear-gradient(red, red) 2ch 0/3ch 3ch no-repeat;
+    color: red;
+    width: 100px;
+    height: 100px;
+    z-index: -1;
+}
+</style>
+<p>Test passes if there is a filled green square.</p>
+<div class="ref">XX<br>X</br></div>
+<div class="test">
+  <img src="/css/support/60x60-green.png" alt="">
+  <span>XX X</span>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/hyphens/hyphens-none-015.html b/third_party/blink/web_tests/external/wpt/css/css-text/hyphens/hyphens-none-015.html
new file mode 100644
index 0000000..10c90b1
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/hyphens/hyphens-none-015.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Text level 3 Test: Line breaking with floats and disabled hyphenation </title>
+<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com"/>
+<link rel="help" title="5.4. Hyphenation: the hyphens property" href="https://drafts.csswg.org/css-text-3/#hyphenation">
+<link rel="help" href="https://drafts.csswg.org/css-text-3/#valdef-hyphens-manual">
+<link rel="match" href="../../reference/ref-filled-green-200px-square.html">
+<meta name="assert" content="A span with hypens 'none' is wrapped in multiple lines based on the available space left by a float image.">
+<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
+<style>
+div { font: 20px/1 Ahem; }
+img { float:right; }
+.test {
+    max-width: 200px;
+    color: green;
+}
+span { hyphens: none; }
+.ref {
+    position: absolute;
+    background: green linear-gradient(red, red) 7ch 0/3ch 3ch no-repeat;
+    color: red;
+    width: 200px;
+    height: 200px;
+    z-index: -1;
+}
+</style>
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div class="ref">XX X<br>XXX</br>XXXX XX<br>XXX</div>
+<div class="test">
+  <img src="/css/support/60x60-green.png" alt="">
+  <span>XX X XXX XXXX XX XXX</span>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/history/the-location-interface/location-non-configurable-toString-valueOf.html b/third_party/blink/web_tests/external/wpt/html/browsers/history/the-location-interface/location-non-configurable-toString-valueOf.html
new file mode 100644
index 0000000..80760ac9
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/history/the-location-interface/location-non-configurable-toString-valueOf.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Same-origin Location objects have non-configurable "toString" and "valueOf" properties</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/history.html#location-defineownproperty">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+"use strict";
+
+test(() => {
+  assert_own_property(location, "toString");
+  const origToString = location.toString;
+
+  assert_throws_js(TypeError, () => {
+    Object.defineProperty(location, "toString", {
+      get() {},
+      set(_v) {},
+      enumerable: true,
+      configurable: true,
+    });
+  });
+
+  assert_equals(location.toString, origToString);
+}, "'toString' redefinition with accessor fails");
+
+test(() => {
+  assert_own_property(location, "valueOf");
+  const origValueOf = location.valueOf;
+
+  assert_throws_js(TypeError, () => {
+    Object.defineProperty(location, "valueOf", {
+      get() {},
+      enumerable: false,
+      configurable: true,
+    });
+  });
+
+  assert_equals(location.valueOf, origValueOf);
+}, "'valueOf' redefinition with accessor fails");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/history/the-location-interface/location-prevent-extensions-expected.txt b/third_party/blink/web_tests/external/wpt/html/browsers/history/the-location-interface/location-prevent-extensions-expected.txt
new file mode 100644
index 0000000..9f79369
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/history/the-location-interface/location-prevent-extensions-expected.txt
@@ -0,0 +1,7 @@
+This is a testharness.js-based test.
+FAIL Object.preventExtensions throws a TypeError assert_throws_js: function "() => {
+    Object.preventExtensions(location);
+  }" did not throw
+FAIL Reflect.preventExtensions returns false assert_false: expected false got true
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/history/the-location-interface/location-prevent-extensions.html b/third_party/blink/web_tests/external/wpt/html/browsers/history/the-location-interface/location-prevent-extensions.html
new file mode 100644
index 0000000..a8c777a
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/history/the-location-interface/location-prevent-extensions.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>[[PreventExtensions]] on a Location object should return false</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/history.html#location-preventextensions">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+"use strict";
+
+test(() => {
+  assert_throws_js(TypeError, () => {
+    Object.preventExtensions(location);
+  });
+}, "Object.preventExtensions throws a TypeError");
+
+test(() => {
+  assert_false(Reflect.preventExtensions(location));
+}, "Reflect.preventExtensions returns false");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/history/the-location-interface/location-prototype-no-toString-valueOf.html b/third_party/blink/web_tests/external/wpt/html/browsers/history/the-location-interface/location-prototype-no-toString-valueOf.html
new file mode 100644
index 0000000..5631632
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/history/the-location-interface/location-prototype-no-toString-valueOf.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Location.prototype objects don't have own "toString" and "valueOf" properties</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+"use strict";
+
+test(t => {
+  assert_not_own_property(Location.prototype, "toString");
+  t.add_cleanup(() => { delete Location.prototype.toString; });
+
+  let val;
+  Object.defineProperty(Location.prototype, "toString", {
+    get: () => val,
+    set: newVal => { val = newVal; },
+    enumerable: false,
+    configurable: true,
+  });
+
+  Location.prototype.toString = 2;
+  assert_equals(Location.prototype.toString, 2);
+}, "'toString' accessor property is defined");
+
+test(t => {
+  assert_not_own_property(Location.prototype, "toString");
+  t.add_cleanup(() => { delete Location.prototype.toString; });
+
+  Location.prototype.toString = 4;
+  assert_equals(Location.prototype.toString, 4);
+}, "'toString' data property is created via [[Set]]");
+
+test(t => {
+  assert_not_own_property(Location.prototype, "valueOf");
+  t.add_cleanup(() => { delete Location.prototype.valueOf; });
+
+  Object.defineProperty(Location.prototype, "valueOf", {
+    get: () => 6,
+    enumerable: true,
+    configurable: true,
+  });
+
+  assert_equals(Location.prototype.valueOf, 6);
+}, "'valueOf' accessor property is defined");
+
+test(t => {
+  assert_not_own_property(Location.prototype, "valueOf");
+  t.add_cleanup(() => { delete Location.prototype.valueOf; });
+
+  Location.prototype.valueOf = 8;
+  assert_equals(Object.getOwnPropertyDescriptor(Location.prototype, "valueOf").value, 8);
+}, "'valueOf' data property is created via [[Set]]");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/origin/cross-origin-objects/cross-origin-objects.html b/third_party/blink/web_tests/external/wpt/html/browsers/origin/cross-origin-objects/cross-origin-objects.html
index 3b7c783..57712950 100644
--- a/third_party/blink/web_tests/external/wpt/html/browsers/origin/cross-origin-objects/cross-origin-objects.html
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/origin/cross-origin-objects/cross-origin-objects.html
@@ -379,7 +379,11 @@
                    "preventExtensions on cross-origin Window should throw");
   assert_throws_js(TypeError, function() { Object.preventExtensions(win.location) },
                    "preventExtensions on cross-origin Location should throw");
-}, "[[PreventExtensions]] should throw for cross-origin objects");
+  assert_false(Reflect.preventExtensions(win),
+              "Reflect.preventExtensions on cross-origin Window");
+  assert_false(Reflect.preventExtensions(win.location),
+              "Reflect.preventExtensions on cross-origin Location");
+}, "[[PreventExtensions]] should return false cross-origin objects");
 
 /*
  * [[GetOwnProperty]]
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/the-windowproxy-exotic-object/windowproxy-prevent-extensions.html b/third_party/blink/web_tests/external/wpt/html/browsers/the-windowproxy-exotic-object/windowproxy-prevent-extensions.html
new file mode 100644
index 0000000..97a1562
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/the-windowproxy-exotic-object/windowproxy-prevent-extensions.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>[[PreventExtensions]] on a WindowProxy object should return false</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-preventextensions">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+"use strict";
+
+test(() => {
+  assert_throws_js(TypeError, () => {
+    Object.preventExtensions(window);
+  });
+}, "Object.preventExtensions throws a TypeError");
+
+test(() => {
+  assert_false(Reflect.preventExtensions(window));
+}, "Reflect.preventExtensions returns false");
+</script>
diff --git a/third_party/blink/web_tests/http/tests/cache/history-only-cached-subresource-loads-expected.txt b/third_party/blink/web_tests/http/tests/cache/history-only-cached-subresource-loads-expected.txt
index 1125eff..d1a264e 100644
--- a/third_party/blink/web_tests/http/tests/cache/history-only-cached-subresource-loads-expected.txt
+++ b/third_party/blink/web_tests/http/tests/cache/history-only-cached-subresource-loads-expected.txt
@@ -1,8 +1,6 @@
-This test checks that loading a subresource with "Cache-Control: no-store" is cached and reused in back navigation when the page is not in the page cache.
+This test checks that a subresource with "Cache-Control: no-store" is not cached.
 
-We then test that loading the same subresource is refetched when used in non-stale loads such as refreshes or normal navigation.
-
-PASS - no-store subresource was cached and used for a back navigation
+PASS - no-store subresource was refetched for a back navigation
 PASS - no-store subresource was refetched with a reload
 PASS - no-store subresource was refetched with a normal navigation
 
diff --git a/third_party/blink/web_tests/http/tests/cache/history-only-cached-subresource-loads.html b/third_party/blink/web_tests/http/tests/cache/history-only-cached-subresource-loads.html
index d248f70..9ed5494 100644
--- a/third_party/blink/web_tests/http/tests/cache/history-only-cached-subresource-loads.html
+++ b/third_party/blink/web_tests/http/tests/cache/history-only-cached-subresource-loads.html
@@ -5,12 +5,7 @@
 </head>
 <body>
     <p>
-        This test checks that loading a subresource with "Cache-Control: no-store" is
-        cached and reused in back navigation when the page is not in the page cache.
-    </p>
-    <p>
-        We then test that loading the same subresource is refetched when used in
-        non-stale loads such as refreshes or normal navigation.
+        This test checks that a subresource with "Cache-Control: no-store" is not cached.
     </p>
     <pre id="console"></pre>
     <script>
@@ -46,9 +41,9 @@
             backLoadRandomNumber = event.data;
             var wasCached = (backLoadRandomNumber === originalRandomNumber);
             if (wasCached)
-                pre.appendChild(document.createTextNode('PASS - no-store subresource was cached and used for a back navigation\n'));
+                pre.appendChild(document.createTextNode('FAIL - no-store subresource should have been refetched for a back navigation\n'));
             else
-                pre.appendChild(document.createTextNode('FAIL - no-store subresource should have been cached and used in a back navigation\n'));
+                pre.appendChild(document.createTextNode('PASS - no-store subresource was refetched for a back navigation\n'));
             target.postMessage('reload', '*');
             return;
         }
diff --git a/third_party/blink/web_tests/platform/win7/fast/forms/suggestion-picker/time-suggestion-picker-appearance-expected.png b/third_party/blink/web_tests/platform/win7/fast/forms/suggestion-picker/time-suggestion-picker-appearance-expected.png
deleted file mode 100644
index a26bc7a..0000000
--- a/third_party/blink/web_tests/platform/win7/fast/forms/suggestion-picker/time-suggestion-picker-appearance-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/freetype/README.chromium b/third_party/freetype/README.chromium
index f75c27f..b6f8922 100644
--- a/third_party/freetype/README.chromium
+++ b/third_party/freetype/README.chromium
@@ -1,7 +1,7 @@
 Name: FreeType
 URL: http://www.freetype.org/
-Version: VER-2-10-4-52-g7bdf386e7
-Revision: 7bdf386e758cb7c01392e625f4d723e5abb3f9a6
+Version: VER-2-10-4-53-g0d5f1dd37
+Revision: 0d5f1dd37c056b4460a460d16fd1fbb06740e891
 CPEPrefix: cpe:/a:freetype:freetype:2.10.1
 License: Custom license "inspired by the BSD, Artistic, and IJG (Independent
          JPEG Group) licenses"
diff --git a/tools/binary_size/generate_milestone_reports.py b/tools/binary_size/generate_milestone_reports.py
index e31306f..9d18ab2e 100755
--- a/tools/binary_size/generate_milestone_reports.py
+++ b/tools/binary_size/generate_milestone_reports.py
@@ -74,8 +74,9 @@
     '83.0.4103.60',
     '84.0.4147.89',
     '85.0.4183.81',
-    '86.0.4240.11',  # Canary
-    '87.0.4280.13',  # Canary
+    '86.0.4240.198',
+    '87.0.4280.66',
+    '88.0.4324.51',  # Beta
 ]
 
 
diff --git a/tools/binary_size/milestone_apk_sizes.py b/tools/binary_size/milestone_apk_sizes.py
index e98b06d..6d6efc7 100755
--- a/tools/binary_size/milestone_apk_sizes.py
+++ b/tools/binary_size/milestone_apk_sizes.py
@@ -96,7 +96,7 @@
 
   def AddDfmSizes(self, metrics):
     for k, v in sorted(self._resource_sizes_json['charts'].items()):
-      if k.startswith('DFM_'):
+      if k.startswith('DFM_') and k != 'DFM_base':
         metrics['DFM: ' + k[4:]] = v['Size with hindi']['value']
 
   def PrintLibraryCompression(self):
diff --git a/tools/clang/scripts/build.py b/tools/clang/scripts/build.py
index 527e118..ee3396e3 100755
--- a/tools/clang/scripts/build.py
+++ b/tools/clang/scripts/build.py
@@ -571,8 +571,6 @@
         '-DLIBCXX_INCLUDE_TESTS=OFF',
         '-DLIBCXX_ENABLE_EXPERIMENTAL_LIBRARY=OFF',
     ])
-    # Prefer Python 2. TODO(crbug.com/1076834): Remove this.
-    base_cmake_args.append('-DPython3_EXECUTABLE=/nonexistent')
 
   if args.gcc_toolchain:
     # Force compiler-rt tests to use our gcc toolchain (including libstdc++.so)
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 21c65a8..6d21e337 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -9416,6 +9416,12 @@
   <int value="9" label="TEXTURE"/>
 </enum>
 
+<enum name="CaptureQuickAction">
+  <int value="0" label="Edit file in backlight"/>
+  <int value="1" label="Go to file location"/>
+  <int value="2" label="Delete file"/>
+</enum>
+
 <enum name="CaptureStartupResult">
   <obsolete>
     Deprecated as of 02/2017.
@@ -44481,6 +44487,7 @@
       label="SafeBrowsingEnhancedProtectionMessageInInterstitials:enabled"/>
   <int value="151022756" label="ArcAvailableForChildAccount:disabled"/>
   <int value="151101719" label="HtmlBaseUsernameDetector:enabled"/>
+  <int value="151173516" label="CellularUseAttachApn:enabled"/>
   <int value="151630887" label="WebUIA11yEnhancements:disabled"/>
   <int value="153347646" label="SmartDimModelV3:disabled"/>
   <int value="155977192" label="EnableFileManagerFormatDialog:disabled"/>
@@ -45491,6 +45498,7 @@
   <int value="1140541604" label="WinrtGeolocationImplementation:enabled"/>
   <int value="1141918949" label="ContextualSearchLongpressPanelHelp:enabled"/>
   <int value="1142515376" label="enable-nacl"/>
+  <int value="1142752111" label="CellularUseAttachApn:disabled"/>
   <int value="1142788238" label="FontCacheScaling:disabled"/>
   <int value="1142970266"
       label="SignedExchangePrefetchCacheForNavigations:disabled"/>
@@ -59947,6 +59955,8 @@
   <int value="59" label="FINAL_STATUS_GWS_HOLDBACK"/>
   <int value="60" label="FINAL_STATUS_UNKNOWN"/>
   <int value="61" label="FINAL_STATUS_NAVIGATION_PREDICTOR_HOLDBACK"/>
+  <int value="62" label="SINGLE PROCESS"/>
+  <int value="63" label="LINK REL NEXT NOT ALLOWED"/>
 </enum>
 
 <enum name="PrerenderHoverEvent">
diff --git a/tools/metrics/histograms/histograms_xml/ash/histograms.xml b/tools/metrics/histograms/histograms_xml/ash/histograms.xml
index c21483ca..7760ac2 100644
--- a/tools/metrics/histograms/histograms_xml/ash/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/ash/histograms.xml
@@ -279,6 +279,16 @@
   <token key="TabletOrClamshell" variants="DisplayModes"/>
 </histogram>
 
+<histogram name="Ash.CaptureModeController.QuickAction"
+    enum="CaptureQuickAction" expires_after="2021-11-19">
+  <owner>shidi@chromium.org</owner>
+  <owner>chinsenj@chromium.org</owner>
+  <summary>
+    Track all quick actions on screenshot notification. Including: Edit in
+    backlight, Go to Files, Delete File.
+  </summary>
+</histogram>
+
 <histogram name="Ash.CaptureModeController.ScreenRecordingLength"
     units="seconds" expires_after="2021-11-04">
   <owner>afakhry@chromium.org</owner>
diff --git a/tools/metrics/histograms/histograms_xml/others/histograms.xml b/tools/metrics/histograms/histograms_xml/others/histograms.xml
index 7aa55e3..194c8f5 100644
--- a/tools/metrics/histograms/histograms_xml/others/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/others/histograms.xml
@@ -12899,6 +12899,9 @@
 <histogram
     name="ResourceScheduler.DelayableRequests.WaitTimeToAvoidContentionWithNonDelayableRequest"
     units="ms" expires_after="M85">
+  <obsolete>
+    Obsoleted M85.
+  </obsolete>
   <owner>tbansal@chromium.org</owner>
   <summary>
     Records how long after the start of a delayable resource request, a
@@ -12910,6 +12913,9 @@
 
 <histogram name="ResourceScheduler.NonDelayableLastEndToNonDelayableStart"
     units="ms" expires_after="M85">
+  <obsolete>
+    Obsoleted M85.
+  </obsolete>
   <owner>tbansal@chromium.org</owner>
   <owner>dougarnett@chromium.org</owner>
   <summary>
@@ -12922,7 +12928,10 @@
 
 <histogram
     name="ResourceScheduler.NonDelayableLastEndToNonDelayableStart.NonDelayableNotInFlight"
-    units="ms" expires_after="2021-09-01">
+    units="ms" expires_after="2020-12-14">
+  <obsolete>
+    Obsoleted M89.
+  </obsolete>
   <owner>tbansal@chromium.org</owner>
   <owner>dougarnett@chromium.org</owner>
   <summary>
@@ -14641,7 +14650,7 @@
 </histogram>
 
 <histogram name="SpellCheck.SpellingService.RequestDuration" units="ms"
-    expires_after="2021-01-31">
+    expires_after="2021-04-04">
   <owner>yyushkina@google.com</owner>
   <owner>gujen@google.com</owner>
   <owner>chrome-language@google.com</owner>
@@ -14653,7 +14662,7 @@
 </histogram>
 
 <histogram name="SpellCheck.SpellingService.RequestHttpResponseCode"
-    enum="HttpResponseCode" expires_after="2021-04-11">
+    enum="HttpResponseCode" expires_after="2021-04-04">
   <owner>yyushkina@google.com</owner>
   <owner>gujen@google.com</owner>
   <owner>chrome-language@google.com</owner>
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json
index 17b15d9..a87ef188 100644
--- a/tools/perf/core/perfetto_binary_roller/binary_deps.json
+++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -1,12 +1,12 @@
 {
     "trace_processor_shell": {
         "win": {
-            "hash": "338c190945851de759c1aa74422592480695a218",
-            "remote_path": "perfetto_binaries/trace_processor_shell/win/0d23e521cc40b7a795ad98247a5d5b20121a5cf4/trace_processor_shell.exe"
+            "hash": "23fc83b6e05c1e0608bdc0e182a199f449d240f0",
+            "remote_path": "perfetto_binaries/trace_processor_shell/win/c65f224a7259d87bf1f980e7424d73fc0cfca795/trace_processor_shell.exe"
         },
         "mac": {
-            "hash": "607eed89d7285563dbfdddc37e6e3e2642b7eb8c",
-            "remote_path": "perfetto_binaries/trace_processor_shell/mac/0d23e521cc40b7a795ad98247a5d5b20121a5cf4/trace_processor_shell"
+            "hash": "e70767fa3e8f88563e8bb4e50672468b9d759ac7",
+            "remote_path": "perfetto_binaries/trace_processor_shell/mac/c65f224a7259d87bf1f980e7424d73fc0cfca795/trace_processor_shell"
         },
         "linux": {
             "hash": "72c98fdee445bb652f16c2965bddc2d8cf1f0325",
diff --git a/tools/perf/expectations.config b/tools/perf/expectations.config
index b493412..8405487 100644
--- a/tools/perf/expectations.config
+++ b/tools/perf/expectations.config
@@ -246,6 +246,11 @@
 crbug.com/1124237 [ android-nexus-5x android-webview ] rendering.mobile/toBlob_duration.html [ Skip ]
 crbug.com/1150490 [ android-nexus-5x android-webview ] rendering.mobile/microgame_fps [ Skip ]
 crbug.com/1153607 [ android-pixel-2 ] rendering.mobile/many_images [ Skip ]
+crbug.com/1160628 [ android-pixel-2 ] rendering.mobile/idle_power_animated_gif [ Skip ]
+crbug.com/1160628 [ android-pixel-2 ] rendering.mobile/idle_power_css_animation [ Skip ]
+crbug.com/1160628 [ android-pixel-2 ] rendering.mobile/idle_power_request_animation_frame [ Skip ]
+crbug.com/1160628 [ android-pixel-2 ] rendering.mobile/idle_power_set_timeout_long [ Skip ]
+crbug.com/1160628 [ android-pixel-2 ] rendering.mobile/idle_power_set_timetout [ Skip ]
 
 # Benchmark: rasterize_and_record_micro.top_25
 crbug.com/764543 rasterize_and_record_micro.top_25/file://static_top_25/wikipedia.html [ Skip ]
@@ -462,6 +467,7 @@
 crbug.com/1036143 [ android-pixel-2 ] v8.browsing_mobile-future/browse:chrome:omnibox:2019 [ Skip ]
 crbug.com/1064658 [ android ] v8.browsing_mobile-future/browse:tech:discourse_infinite_scroll:2018 [ Skip ]
 crbug.com/1112337 [ android ] v8.browsing_mobile-future/browse:news:cricbuzz:2019 [ Skip ]
+crbug.com/1160630 [ android-pixel-2 ] v8.browsing_mobile-future/browse:social:pinterest_infinite_scroll:2019 [ Skip ]
 
 # Benchmark: v8.runtime_stats.top_25
 crbug.com/954229 [ mac ] v8.runtime_stats.top_25/* [ Skip ]
diff --git a/ui/accessibility/ax_assistant_structure.cc b/ui/accessibility/ax_assistant_structure.cc
index 7cef392..de239c5e 100644
--- a/ui/accessibility/ax_assistant_structure.cc
+++ b/ui/accessibility/ax_assistant_structure.cc
@@ -456,6 +456,7 @@
     case ax::mojom::Role::kList:
     case ax::mojom::Role::kListBox:
     case ax::mojom::Role::kDescriptionList:
+    case ax::mojom::Role::kDirectory:
       return kAXListViewClassname;
     case ax::mojom::Role::kDialog:
       return kAXDialogClassname;
diff --git a/ui/accessibility/ax_role_properties.cc b/ui/accessibility/ax_role_properties.cc
index 36b48d5..c09f6364 100644
--- a/ui/accessibility/ax_role_properties.cc
+++ b/ui/accessibility/ax_role_properties.cc
@@ -729,6 +729,7 @@
     case ax::mojom::Role::kDefinition:
     case ax::mojom::Role::kDescriptionList:
     case ax::mojom::Role::kDescriptionListTerm:
+    case ax::mojom::Role::kDirectory:
     case ax::mojom::Role::kDocument:
     case ax::mojom::Role::kGraphicsDocument:
     case ax::mojom::Role::kImage:
diff --git a/ui/accessibility/test_ax_tree_manager.cc b/ui/accessibility/test_ax_tree_manager.cc
index 70d872d8..3acce8f 100644
--- a/ui/accessibility/test_ax_tree_manager.cc
+++ b/ui/accessibility/test_ax_tree_manager.cc
@@ -14,7 +14,27 @@
 
 TestAXTreeManager::TestAXTreeManager(std::unique_ptr<AXTree> tree)
     : tree_(std::move(tree)) {
-  AXTreeManagerMap::GetInstance().AddTreeManager(GetTreeID(), this);
+  if (tree_)
+    AXTreeManagerMap::GetInstance().AddTreeManager(GetTreeID(), this);
+}
+
+TestAXTreeManager::TestAXTreeManager(TestAXTreeManager&& manager)
+    : tree_(std::move(manager.tree_)) {
+  if (tree_) {
+    AXTreeManagerMap::GetInstance().RemoveTreeManager(GetTreeID());
+    AXTreeManagerMap::GetInstance().AddTreeManager(GetTreeID(), this);
+  }
+}
+
+TestAXTreeManager& TestAXTreeManager::operator=(TestAXTreeManager&& manager) {
+  if (this == &manager)
+    return *this;
+  if (manager.tree_)
+    AXTreeManagerMap::GetInstance().RemoveTreeManager(manager.GetTreeID());
+  // std::move(nullptr) == nullptr, so no need to check if `manager.tree_` is
+  // assigned.
+  SetTree(std::move(manager.tree_));
+  return *this;
 }
 
 TestAXTreeManager::~TestAXTreeManager() {
@@ -40,7 +60,8 @@
     AXTreeManagerMap::GetInstance().RemoveTreeManager(GetTreeID());
 
   tree_ = std::move(tree);
-  AXTreeManagerMap::GetInstance().AddTreeManager(GetTreeID(), this);
+  if (tree_)
+    AXTreeManagerMap::GetInstance().AddTreeManager(GetTreeID(), this);
 }
 
 AXNode* TestAXTreeManager::GetNodeFromTree(const AXTreeID tree_id,
diff --git a/ui/accessibility/test_ax_tree_manager.h b/ui/accessibility/test_ax_tree_manager.h
index 117f020..5fec7c7 100644
--- a/ui/accessibility/test_ax_tree_manager.h
+++ b/ui/accessibility/test_ax_tree_manager.h
@@ -34,6 +34,9 @@
   TestAXTreeManager(const TestAXTreeManager& manager) = delete;
   TestAXTreeManager& operator=(const TestAXTreeManager& manager) = delete;
 
+  TestAXTreeManager(TestAXTreeManager&& manager);
+  TestAXTreeManager& operator=(TestAXTreeManager&& manager);
+
   void DestroyTree();
   AXTree* GetTree() const;
   // Takes ownership of |tree|.
diff --git a/ui/chromeos/events/event_rewriter_chromeos.cc b/ui/chromeos/events/event_rewriter_chromeos.cc
index f759593f..a13542f 100644
--- a/ui/chromeos/events/event_rewriter_chromeos.cc
+++ b/ui/chromeos/events/event_rewriter_chromeos.cc
@@ -965,7 +965,8 @@
       *matched_mask = kSearchLeftButton;
     }
   } else {
-    if (AreFlagsSet(flags, kAltLeftButton)) {
+    if (AreFlagsSet(flags, kAltLeftButton) &&
+        is_alt_left_click_remapping_enabled_) {
       *matched_mask = kAltLeftButton;
     }
   }
diff --git a/ui/chromeos/events/event_rewriter_chromeos.h b/ui/chromeos/events/event_rewriter_chromeos.h
index cef089aa..68bb1e2 100644
--- a/ui/chromeos/events/event_rewriter_chromeos.h
+++ b/ui/chromeos/events/event_rewriter_chromeos.h
@@ -150,6 +150,13 @@
     privacy_screen_supported_ = supported;
   }
 
+  // Enable/disable alt + left click mapping to a right click. This only
+  // applies if the feature `chromeos::features::kUseSearchClickForRightClick`
+  // is not enabled.
+  void set_alt_left_click_remapping_enabled(bool enabled) {
+    is_alt_left_click_remapping_enabled_ = enabled;
+  }
+
   // EventRewriter overrides:
   EventDispatchDetails RewriteEvent(const Event& event,
                                     const Continuation continuation) override;
@@ -215,7 +222,8 @@
   // Returns true if this event should be remapped to a right-click.
   // |matched_mask| will be set to the variant (Alt+Click or Search+Click)
   // that was used to match based on flag/feature settings. |matched_mask|
-  // only has a valid value when returning true.
+  // only has a valid value when returning true. However, Alt+Click will not
+  // be remapped if |is_alt_left_click_remapping_enabled_| is false.
   bool ShouldRemapToRightClick(const MouseEvent& mouse_event,
                                int flags,
                                int* matched_mask) const;
@@ -325,6 +333,11 @@
   int latched_modifier_latches_;
   int used_modifier_latches_;
 
+  // Alt + left click maps to a right click by default. In some scenario, such
+  // as clicking a button in the Alt-Tab UI, this remapping undesirably prevents
+  // button clicking.
+  bool is_alt_left_click_remapping_enabled_ = true;
+
   DISALLOW_COPY_AND_ASSIGN(EventRewriterChromeOS);
 };
 
diff --git a/ui/gl/direct_composition_surface_win.cc b/ui/gl/direct_composition_surface_win.cc
index 8dbb0a7..9b36519 100644
--- a/ui/gl/direct_composition_surface_win.cc
+++ b/ui/gl/direct_composition_surface_win.cc
@@ -20,6 +20,7 @@
 #include "ui/gl/dc_layer_tree.h"
 #include "ui/gl/direct_composition_child_surface_win.h"
 #include "ui/gl/gl_angle_util_win.h"
+#include "ui/gl/gl_features.h"
 #include "ui/gl/gl_implementation.h"
 #include "ui/gl/gl_switches.h"
 #include "ui/gl/gpu_switching_manager.h"
@@ -652,8 +653,7 @@
 // static
 bool DirectCompositionSurfaceWin::AllowTearing() {
   // Swap chain tearing is used only if vsync is disabled explicitly.
-  return base::CommandLine::ForCurrentProcess()->HasSwitch(
-             switches::kDisableGpuVsync) &&
+  return features::UseGpuVsync() &&
          DirectCompositionSurfaceWin::IsSwapChainTearingSupported();
 }
 
diff --git a/ui/gl/gl_features.cc b/ui/gl/gl_features.cc
index cbae797..c71e70c 100644
--- a/ui/gl/gl_features.cc
+++ b/ui/gl/gl_features.cc
@@ -4,9 +4,17 @@
 
 #include "ui/gl/gl_features.h"
 
+#include "base/command_line.h"
+#include "base/feature_list.h"
 #include "build/chromeos_buildflags.h"
+#include "ui/gl/gl_switches.h"
 
 namespace features {
+namespace {
+
+const base::Feature kGpuVsync{"GpuVsync", base::FEATURE_ENABLED_BY_DEFAULT};
+
+}  // namespace
 
 // Use the passthrough command decoder by default.  This can be overridden with
 // the --use-cmd-decoder=passthrough or --use-cmd-decoder=validating flags.
@@ -24,4 +32,10 @@
 #endif
 };
 
+bool UseGpuVsync() {
+  return !base::CommandLine::ForCurrentProcess()->HasSwitch(
+             switches::kDisableGpuVsync) &&
+         base::FeatureList::IsEnabled(kGpuVsync);
+}
+
 }  // namespace features
diff --git a/ui/gl/gl_features.h b/ui/gl/gl_features.h
index 3513df1..e84a953c 100644
--- a/ui/gl/gl_features.h
+++ b/ui/gl/gl_features.h
@@ -11,6 +11,9 @@
 
 namespace features {
 
+// Controls if GPU should synchronize presentation with vsync.
+GL_EXPORT bool UseGpuVsync();
+
 // All features in alphabetical order. The features should be documented
 // alongside the definition of their values in the .cc file.
 GL_EXPORT extern const base::Feature kDefaultPassthroughCommandDecoder;
diff --git a/ui/webui/resources/cr_components/customize_themes/customize_themes.html b/ui/webui/resources/cr_components/customize_themes/customize_themes.html
index 6ff12c4..1b9005b 100644
--- a/ui/webui/resources/cr_components/customize_themes/customize_themes.html
+++ b/ui/webui/resources/cr_components/customize_themes/customize_themes.html
@@ -157,9 +157,9 @@
       [[i18n('colorPickerLabel')]]
     </paper-tooltip>
   </div>
-  <div aria-label="[[i18n('defaultThemeLabel')]]" tabindex="0">
+  <div aria-label="[[i18n('defaultThemeLabel')]]"
+      tabindex="0" on-click="onDefaultThemeClick_">
     <cr-theme-icon id="defaultTheme"
-        on-click="onDefaultThemeClick_"
         selected$="[[isThemeIconSelected_('default', selectedTheme)]]">
     </cr-theme-icon>
     <paper-tooltip offset="0" fit-to-visible-bounds>
@@ -167,8 +167,9 @@
     </paper-tooltip>
   </div>
   <template is="dom-repeat" id="themes" items="[[chromeThemes_]]">
-    <div aria-label="[[item.label]]" tabindex="0" class="chrome-theme-wrapper">
-      <cr-theme-icon on-click="onChromeThemeClick_"
+    <div aria-label="[[item.label]]" tabindex="0"
+        on-click="onChromeThemeClick_" class="chrome-theme-wrapper">
+      <cr-theme-icon
           style="--cr-theme-icon-frame-color:
               [[skColorToRgba_(item.colors.frame)]];
               --cr-theme-icon-active-tab-color:
diff --git a/weblayer/browser/no_state_prefetch/no_state_prefetch_browsertest.cc b/weblayer/browser/no_state_prefetch/no_state_prefetch_browsertest.cc
index 56424da..539ba42 100644
--- a/weblayer/browser/no_state_prefetch/no_state_prefetch_browsertest.cc
+++ b/weblayer/browser/no_state_prefetch/no_state_prefetch_browsertest.cc
@@ -197,14 +197,12 @@
   prerendered_page_fetched_->Run();
 }
 
-// link-rel="next" happens even when NoStatePrefetch has been disabled.
+// link-rel="next" URLs should not be prefetched.
 IN_PROC_BROWSER_TEST_F(NoStatePrefetchBrowserTest, LinkRelNextWithNSPDisabled) {
-  GetProfile()->SetBooleanSetting(SettingType::NETWORK_PREDICTION_ENABLED,
-                                  false);
   NavigateAndWaitForCompletion(
       GURL(https_server_->GetURL("/link_rel_next_parent.html")), shell());
-
-  prerendered_page_fetched_->Run();
+  base::RunLoop().RunUntilIdle();
+  EXPECT_FALSE(prerendered_page_was_fetched_);
 }
 
 // Non-web initiated prerender succeeds and subsequent navigations reuse