diff --git a/DEPS b/DEPS
index b93476c..f0844a446 100644
--- a/DEPS
+++ b/DEPS
@@ -167,11 +167,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': '1ebaa15a4c62453f5eeac71a5ba33e4dd6da566e',
+  'skia_revision': 'cf0a9a628490ccb52f7257e8c22e1bbf432252f2',
   # 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': '23dacd6b32b6767f9bde088e80e70c054c825c22',
+  'v8_revision': 'f45ddb9b49eab6d363a7ffa52fdd265fe14984a7',
   # 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.
@@ -179,7 +179,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': 'f22f16d3c0a80ce7bbc4a8eeff81fde63aa6ad40',
+  'angle_revision': 'f03259ad720556908d46ea139b99804b1039550e',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -230,7 +230,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling catapult
   # and whatever else without interference from each other.
-  'catapult_revision': '017b54db6bea7cae9c65896a4f4846209485212d',
+  'catapult_revision': 'dbfa96532ab0841252db9df65ef2d7fe3ce41393',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -286,7 +286,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.
-  'spv_tools_revision': '85c67b5e08eea1818767f174515612464740a53c',
+  'spv_tools_revision': '44b32176ee48936dc212de0624b3d848510338a9',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -302,7 +302,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': 'dc9f1e68069917f29d552d9cb0b7993fcb7c4059',
+  'dawn_revision': '867f72058a4f6b901524efe484611e1e319c80fb',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -862,7 +862,7 @@
 
   # Build tools for Chrome OS. Note: This depends on third_party/pyelftools.
   'src/third_party/chromite': {
-      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '98592f6c5dbe68b65645be7d0f0277757f0a09c7',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '929f3ba81e6e0047f04f51b143efe5f3f974137d',
       'condition': 'checkout_linux',
   },
 
@@ -887,7 +887,7 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '5eac9d301390c5ff8afdaa95f46e68bb84e20575',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '2458b31208f87c2bd54befb7d701ecf60b9f75e9',
 
   'src/third_party/devtools-node-modules':
     Var('chromium_git') + '/external/github.com/ChromeDevTools/devtools-node-modules' + '@' + Var('devtools_node_modules_revision'),
@@ -1269,7 +1269,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + 'fa5193a394a7597a3a52c6cb05a71183b45fa170',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + '3ad1e2151d99792d5a2d1280cd4a1bbddd475edd',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1459,7 +1459,7 @@
     Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + '7c4e67ff117d6c640e6dd17989afe2fb7da7eecb',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + 'f4e0c29ed1d74dd192e745c7b2a7d6806b5d8e4f',
+    Var('webrtc_git') + '/src.git' + '@' + 'f1e97b9ebd23c12d12ffd6b18bdf3eb4951153b4',
 
   'src/third_party/xdg-utils': {
       'url': Var('chromium_git') + '/chromium/deps/xdg-utils.git' + '@' + 'd80274d5869b17b8c9067a1022e4416ee7ed5e0d',
@@ -1521,7 +1521,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@eb06828e0343cd99de93721890618db0490ab8cd',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@9bdb26527d654e36041b0926eee626c4b0fdab00',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/android_webview/browser/js_java_interaction/js_java_configurator_host.cc b/android_webview/browser/js_java_interaction/js_java_configurator_host.cc
index aecc3a2..8b4c33b2 100644
--- a/android_webview/browser/js_java_interaction/js_java_configurator_host.cc
+++ b/android_webview/browser/js_java_interaction/js_java_configurator_host.cc
@@ -128,9 +128,9 @@
             render_frame_host,
             pending_remote.InitWithNewEndpointAndPassReceiver(),
             js_object.listener_ref_, js_object.allowed_origin_rules_));
-    js_objects.push_back(std::move(
-        mojom::JsObject::New(js_object.name_, std::move(pending_remote),
-                             js_object.allowed_origin_rules_)));
+    js_objects.push_back(mojom::JsObject::New(js_object.name_,
+                                              std::move(pending_remote),
+                                              js_object.allowed_origin_rules_));
   }
   configurator_remote->SetJsObjects(std::move(js_objects));
 }
diff --git a/ash/app_list/views/search_result_page_view.cc b/ash/app_list/views/search_result_page_view.cc
index eb14c81..e28addf9 100644
--- a/ash/app_list/views/search_result_page_view.cc
+++ b/ash/app_list/views/search_result_page_view.cc
@@ -182,7 +182,8 @@
   // contents' size. Using zeroes doesn't prevent it from scrolling and sizing
   // correctly.
   scroller->ClipHeightTo(0, 0);
-  scroller->SetVerticalScrollBar(new ZeroWidthVerticalScrollBar());
+  scroller->SetVerticalScrollBar(
+      std::make_unique<ZeroWidthVerticalScrollBar>());
   scroller->SetBackgroundColor(SK_ColorTRANSPARENT);
   AddChildView(std::move(scroller));
 
diff --git a/ash/assistant/ui/base/assistant_scroll_view.cc b/ash/assistant/ui/base/assistant_scroll_view.cc
index 9f65819..e0b18a5 100644
--- a/ash/assistant/ui/base/assistant_scroll_view.cc
+++ b/ash/assistant/ui/base/assistant_scroll_view.cc
@@ -88,11 +88,11 @@
   content_view_ = SetContents(std::move(content_view));
 
   // Scroll bars.
-  horizontal_scroll_bar_ = new InvisibleScrollBar(/*horizontal=*/true);
-  SetHorizontalScrollBar(horizontal_scroll_bar_);
+  horizontal_scroll_bar_ = SetHorizontalScrollBar(
+      std::make_unique<InvisibleScrollBar>(/*horizontal=*/true));
 
-  vertical_scroll_bar_ = new InvisibleScrollBar(/*horizontal=*/false);
-  SetVerticalScrollBar(vertical_scroll_bar_);
+  vertical_scroll_bar_ = SetVerticalScrollBar(
+      std::make_unique<InvisibleScrollBar>(/*horizontal=*/false));
 }
 
 }  // namespace ash
diff --git a/ash/login/ui/lock_debug_view.cc b/ash/login/ui/lock_debug_view.cc
index 69e1568a..382ad699 100644
--- a/ash/login/ui/lock_debug_view.cc
+++ b/ash/login/ui/lock_debug_view.cc
@@ -752,8 +752,10 @@
     scroll->SetPreferredSize(gfx::Size(600, height));
     scroll->SetContents(base::WrapUnique(content));
     scroll->SetBackgroundColor(SK_ColorTRANSPARENT);
-    scroll->SetVerticalScrollBar(new views::OverlayScrollBar(false));
-    scroll->SetHorizontalScrollBar(new views::OverlayScrollBar(true));
+    scroll->SetVerticalScrollBar(
+        std::make_unique<views::OverlayScrollBar>(false));
+    scroll->SetHorizontalScrollBar(
+        std::make_unique<views::OverlayScrollBar>(true));
     return scroll;
   };
   container_->AddChildView(make_scroll(global_action_view_container_, 110));
diff --git a/ash/login/ui/login_menu_view.cc b/ash/login/ui/login_menu_view.cc
index aa2b28b8..da16546 100644
--- a/ash/login/ui/login_menu_view.cc
+++ b/ash/login/ui/login_menu_view.cc
@@ -41,19 +41,19 @@
         views::BoxLayout::Orientation::kHorizontal));
     SetPreferredSize(gfx::Size(kMenuItemWidthDp, kMenuItemHeightDp));
 
-    auto* spacing = new NonAccessibleView();
+    auto spacing = std::make_unique<NonAccessibleView>();
     spacing->SetPreferredSize(gfx::Size(item.is_group
                                             ? kRegularMenuItemLeftPaddingDp
                                             : kGroupMenuItemLeftPaddingDp,
                                         kNonEmptyHeight));
-    AddChildView(spacing);
+    AddChildView(std::move(spacing));
 
-    views::Label* label = new views::Label(base::UTF8ToUTF16(item.title));
+    auto label = std::make_unique<views::Label>(base::UTF8ToUTF16(item.title));
     label->SetEnabledColor(SK_ColorWHITE);
     label->SetSubpixelRenderingEnabled(false);
     label->SetAutoColorReadabilityEnabled(false);
     label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
-    AddChildView(label);
+    AddChildView(std::move(label));
 
     if (item.selected)
       SetBackground(views::CreateSolidBackground(SK_ColorGRAY));
@@ -163,7 +163,7 @@
       selected_index_ = i;
   }
   contents_ = scroller_->SetContents(std::move(contents));
-  scroller_->SetVerticalScrollBar(new LoginScrollBar());
+  scroller_->SetVerticalScrollBar(std::make_unique<LoginScrollBar>());
 }
 
 LoginMenuView::~LoginMenuView() = default;
diff --git a/ash/login/ui/scrollable_users_list_view.cc b/ash/login/ui/scrollable_users_list_view.cc
index 2c61bb4b..28d58e9 100644
--- a/ash/login/ui/scrollable_users_list_view.cc
+++ b/ash/login/ui/scrollable_users_list_view.cc
@@ -334,8 +334,8 @@
   SetBackgroundColor(SK_ColorTRANSPARENT);
   SetDrawOverflowIndicator(false);
 
-  SetVerticalScrollBar(new UsersListScrollBar(false));
-  SetHorizontalScrollBar(new UsersListScrollBar(true));
+  SetVerticalScrollBar(std::make_unique<UsersListScrollBar>(false));
+  SetHorizontalScrollBar(std::make_unique<UsersListScrollBar>(true));
 
   observer_.Add(Shell::Get()->wallpaper_controller());
 }
diff --git a/ash/system/message_center/notifier_settings_view.cc b/ash/system/message_center/notifier_settings_view.cc
index c446042f..b928183e 100644
--- a/ash/system/message_center/notifier_settings_view.cc
+++ b/ash/system/message_center/notifier_settings_view.cc
@@ -414,14 +414,7 @@
 
 // NotifierSettingsView -------------------------------------------------------
 
-NotifierSettingsView::NotifierSettingsView()
-    : quiet_mode_icon_(nullptr),
-      quiet_mode_toggle_(nullptr),
-      header_view_(nullptr),
-      top_label_(nullptr),
-      scroll_bar_(nullptr),
-      scroller_(nullptr),
-      no_notifiers_view_(nullptr) {
+NotifierSettingsView::NotifierSettingsView() {
   SetFocusBehavior(FocusBehavior::ACCESSIBLE_ONLY);
   SetPaintToLayer();
   layer()->SetFillsBoundsOpaquely(false);
@@ -487,11 +480,10 @@
 
   header_view_ = AddChildView(std::move(header_view));
 
-  scroll_bar_ = new views::OverlayScrollBar(/*horizontal=*/false);
-
   auto scroller = std::make_unique<views::ScrollView>();
   scroller->SetBackgroundColor(SK_ColorTRANSPARENT);
-  scroller->SetVerticalScrollBar(scroll_bar_);
+  scroll_bar_ = scroller->SetVerticalScrollBar(
+      std::make_unique<views::OverlayScrollBar>(/*horizontal=*/false));
   scroller->SetDrawOverflowIndicator(false);
   scroller_ = AddChildView(std::move(scroller));
 
diff --git a/ash/system/message_center/notifier_settings_view.h b/ash/system/message_center/notifier_settings_view.h
index 124e66b..d61886e 100644
--- a/ash/system/message_center/notifier_settings_view.h
+++ b/ash/system/message_center/notifier_settings_view.h
@@ -96,13 +96,13 @@
   // Overridden from views::ButtonListener:
   void ButtonPressed(views::Button* sender, const ui::Event& event) override;
 
-  views::ImageView* quiet_mode_icon_;
-  views::ToggleButton* quiet_mode_toggle_;
-  views::View* header_view_;
-  views::Label* top_label_;
-  views::ScrollBar* scroll_bar_;
-  views::ScrollView* scroller_;
-  views::View* no_notifiers_view_;
+  views::ImageView* quiet_mode_icon_ = nullptr;
+  views::ToggleButton* quiet_mode_toggle_ = nullptr;
+  views::View* header_view_ = nullptr;
+  views::Label* top_label_ = nullptr;
+  views::ScrollBar* scroll_bar_ = nullptr;
+  views::ScrollView* scroller_ = nullptr;
+  views::View* no_notifiers_view_ = nullptr;
   std::set<NotifierButton*> buttons_;
 
   DISALLOW_COPY_AND_ASSIGN(NotifierSettingsView);
diff --git a/ash/system/message_center/unified_message_center_view.cc b/ash/system/message_center/unified_message_center_view.cc
index 41ed6624..166fde6 100644
--- a/ash/system/message_center/unified_message_center_view.cc
+++ b/ash/system/message_center/unified_message_center_view.cc
@@ -23,6 +23,7 @@
 #include "ash/system/unified/unified_system_tray_model.h"
 #include "ash/system/unified/unified_system_tray_view.h"
 #include "base/i18n/rtl.h"
+#include "base/memory/ptr_util.h"
 #include "base/metrics/user_metrics.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/gfx/animation/linear_animation.h"
@@ -302,7 +303,7 @@
   scroller_->SetContents(
       std::make_unique<ScrollerContentsView>(message_list_view_, this));
   scroller_->SetBackgroundColor(SK_ColorTRANSPARENT);
-  scroller_->SetVerticalScrollBar(scroll_bar_);
+  scroller_->SetVerticalScrollBar(base::WrapUnique(scroll_bar_));
   scroller_->SetDrawOverflowIndicator(false);
   AddChildView(scroller_);
 }
diff --git a/base/BUILD.gn b/base/BUILD.gn
index b1cb70e1..27dbe412 100644
--- a/base/BUILD.gn
+++ b/base/BUILD.gn
@@ -815,6 +815,8 @@
     "task/sequence_manager/work_queue.h",
     "task/sequence_manager/work_queue_sets.cc",
     "task/sequence_manager/work_queue_sets.h",
+    "task/simple_task_executor.cc",
+    "task/simple_task_executor.h",
     "task/single_thread_task_executor.cc",
     "task/single_thread_task_executor.h",
     "task/single_thread_task_runner_thread_mode.h",
@@ -2711,6 +2713,7 @@
     "task/sequence_manager/work_deduplicator_unittest.cc",
     "task/sequence_manager/work_queue_sets_unittest.cc",
     "task/sequence_manager/work_queue_unittest.cc",
+    "task/single_thread_task_executor_unittest.cc",
     "task/task_traits_extension_unittest.cc",
     "task/task_traits_unittest.cc",
     "task/thread_pool/can_run_policy_test.h",
diff --git a/base/file_version_info_win.cc b/base/file_version_info_win.cc
index c45765d..1572d0c 100644
--- a/base/file_version_info_win.cc
+++ b/base/file_version_info_win.cc
@@ -183,6 +183,14 @@
     return base::string16();
 }
 
+base::Version FileVersionInfoWin::GetFileVersion() const {
+  return base::Version(
+      std::vector<uint32_t>{HIWORD(fixed_file_info_->dwFileVersionMS),
+                            LOWORD(fixed_file_info_->dwFileVersionMS),
+                            HIWORD(fixed_file_info_->dwFileVersionLS),
+                            LOWORD(fixed_file_info_->dwFileVersionLS)});
+}
+
 FileVersionInfoWin::FileVersionInfoWin(std::vector<uint8_t>&& data,
                                        WORD language,
                                        WORD code_page)
diff --git a/base/file_version_info_win.h b/base/file_version_info_win.h
index 7c83daf..30d4756c 100644
--- a/base/file_version_info_win.h
+++ b/base/file_version_info_win.h
@@ -16,6 +16,7 @@
 #include "base/base_export.h"
 #include "base/file_version_info.h"
 #include "base/macros.h"
+#include "base/version.h"
 
 struct tagVS_FIXEDFILEINFO;
 typedef tagVS_FIXEDFILEINFO VS_FIXEDFILEINFO;
@@ -44,8 +45,8 @@
   // does not exist).
   base::string16 GetStringValue(const base::char16* name);
 
-  // Get the fixed file info if it exists. Otherwise NULL
-  const VS_FIXEDFILEINFO* fixed_file_info() const { return fixed_file_info_; }
+  // Get file version number in dotted version format.
+  base::Version GetFileVersion() const;
 
   // Behaves like CreateFileVersionInfo, but returns a FileVersionInfoWin.
   static std::unique_ptr<FileVersionInfoWin> CreateFileVersionInfoWin(
diff --git a/base/file_version_info_win_unittest.cc b/base/file_version_info_win_unittest.cc
index 7a761ab..36bf4e6 100644
--- a/base/file_version_info_win_unittest.cc
+++ b/base/file_version_info_win_unittest.cc
@@ -152,4 +152,7 @@
       version_info_win->GetValue(STRING16_LITERAL("Unknown property"), &str));
   EXPECT_EQ(base::string16(), version_info_win->GetStringValue(
                                   STRING16_LITERAL("Unknown property")));
+
+  EXPECT_EQ(base::Version(std::vector<uint32_t>{1, 0, 0, 1}),
+            version_info_win->GetFileVersion());
 }
diff --git a/base/task/simple_task_executor.cc b/base/task/simple_task_executor.cc
new file mode 100644
index 0000000..ffccf52
--- /dev/null
+++ b/base/task/simple_task_executor.cc
@@ -0,0 +1,62 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/task/simple_task_executor.h"
+
+namespace base {
+
+SimpleTaskExecutor::SimpleTaskExecutor(
+    scoped_refptr<SingleThreadTaskRunner> task_queue)
+    : task_queue_(std::move(task_queue)),
+      previous_task_executor_(GetTaskExecutorForCurrentThread()) {
+  DCHECK(task_queue_);
+  // The TaskExecutor API does not expect nesting, but this can happen in tests
+  // so we have to work around it here.
+  if (previous_task_executor_)
+    SetTaskExecutorForCurrentThread(nullptr);
+  SetTaskExecutorForCurrentThread(this);
+}
+
+SimpleTaskExecutor::~SimpleTaskExecutor() {
+  if (previous_task_executor_)
+    SetTaskExecutorForCurrentThread(nullptr);
+  SetTaskExecutorForCurrentThread(previous_task_executor_);
+}
+
+bool SimpleTaskExecutor::PostDelayedTask(const Location& from_here,
+                                         const TaskTraits& traits,
+                                         OnceClosure task,
+                                         TimeDelta delay) {
+  return task_queue_->PostDelayedTask(from_here, std::move(task), delay);
+}
+
+scoped_refptr<TaskRunner> SimpleTaskExecutor::CreateTaskRunner(
+    const TaskTraits& traits) {
+  return task_queue_;
+}
+
+scoped_refptr<SequencedTaskRunner>
+SimpleTaskExecutor::CreateSequencedTaskRunner(const TaskTraits& traits) {
+  return task_queue_;
+}
+
+scoped_refptr<SingleThreadTaskRunner>
+SimpleTaskExecutor::CreateSingleThreadTaskRunner(
+    const TaskTraits& traits,
+    SingleThreadTaskRunnerThreadMode thread_mode) {
+  return task_queue_;
+}
+
+#if defined(OS_WIN)
+scoped_refptr<SingleThreadTaskRunner>
+SimpleTaskExecutor::CreateCOMSTATaskRunner(
+    const TaskTraits& traits,
+    SingleThreadTaskRunnerThreadMode thread_mode) {
+  // It seems pretty unlikely this will be used on a comsta task thread.
+  NOTREACHED();
+  return task_queue_;
+}
+#endif  // defined(OS_WIN)
+
+}  // namespace base
diff --git a/base/task/simple_task_executor.h b/base/task/simple_task_executor.h
new file mode 100644
index 0000000..7d9a74dc
--- /dev/null
+++ b/base/task/simple_task_executor.h
@@ -0,0 +1,52 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TASK_SIMPLE_TASK_EXECUTOR_H_
+#define BASE_TASK_SIMPLE_TASK_EXECUTOR_H_
+
+#include "base/task/task_executor.h"
+#include "build/build_config.h"
+
+namespace base {
+
+// A simple TaskExecutor with exactly one SingleThreadTaskRunner.
+// Must be instantiated and destroyed on the thread that runs tasks for the
+// SingleThreadTaskRunner.
+class BASE_EXPORT SimpleTaskExecutor : public TaskExecutor {
+ public:
+  explicit SimpleTaskExecutor(scoped_refptr<SingleThreadTaskRunner> task_queue);
+
+  ~SimpleTaskExecutor() override;
+
+  bool PostDelayedTask(const Location& from_here,
+                       const TaskTraits& traits,
+                       OnceClosure task,
+                       TimeDelta delay) override;
+
+  scoped_refptr<TaskRunner> CreateTaskRunner(const TaskTraits& traits) override;
+
+  scoped_refptr<SequencedTaskRunner> CreateSequencedTaskRunner(
+      const TaskTraits& traits) override;
+
+  scoped_refptr<SingleThreadTaskRunner> CreateSingleThreadTaskRunner(
+      const TaskTraits& traits,
+      SingleThreadTaskRunnerThreadMode thread_mode) override;
+
+#if defined(OS_WIN)
+  scoped_refptr<SingleThreadTaskRunner> CreateCOMSTATaskRunner(
+      const TaskTraits& traits,
+      SingleThreadTaskRunnerThreadMode thread_mode) override;
+#endif  // defined(OS_WIN)
+
+ private:
+  const scoped_refptr<SingleThreadTaskRunner> task_queue_;
+
+  // In tests there may already be a TaskExecutor registered for the thread, we
+  // keep tack of the previous TaskExecutor and restored it upon destruction.
+  TaskExecutor* const previous_task_executor_;
+};
+
+}  // namespace base
+
+#endif  // BASE_TASK_SIMPLE_TASK_EXECUTOR_H_
diff --git a/base/task/single_thread_task_executor.cc b/base/task/single_thread_task_executor.cc
index 7e12b78..01d993b 100644
--- a/base/task/single_thread_task_executor.cc
+++ b/base/task/single_thread_task_executor.cc
@@ -17,7 +17,8 @@
               .Build())),
       default_task_queue_(sequence_manager_->CreateTaskQueue(
           sequence_manager::TaskQueue::Spec("default_tq"))),
-      type_(type) {
+      type_(type),
+      simple_task_executor_(task_runner()) {
   sequence_manager_->SetDefaultTaskRunner(default_task_queue_->task_runner());
   sequence_manager_->BindToMessagePump(MessagePump::Create(type));
 
diff --git a/base/task/single_thread_task_executor.h b/base/task/single_thread_task_executor.h
index d350420..c048a83 100644
--- a/base/task/single_thread_task_executor.h
+++ b/base/task/single_thread_task_executor.h
@@ -11,6 +11,7 @@
 #include "base/memory/scoped_refptr.h"
 #include "base/message_loop/message_pump_type.h"
 #include "base/single_thread_task_runner.h"
+#include "base/task/simple_task_executor.h"
 
 namespace base {
 
@@ -40,6 +41,7 @@
   std::unique_ptr<sequence_manager::SequenceManager> sequence_manager_;
   scoped_refptr<sequence_manager::TaskQueue> default_task_queue_;
   MessagePumpType type_;
+  SimpleTaskExecutor simple_task_executor_;
 
   DISALLOW_COPY_AND_ASSIGN(SingleThreadTaskExecutor);
 };
diff --git a/base/task/single_thread_task_executor_unittest.cc b/base/task/single_thread_task_executor_unittest.cc
new file mode 100644
index 0000000..8919298b
--- /dev/null
+++ b/base/task/single_thread_task_executor_unittest.cc
@@ -0,0 +1,45 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/task/single_thread_task_executor.h"
+
+#include "base/run_loop.h"
+#include "base/test/bind_test_util.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::IsNull;
+using ::testing::NotNull;
+
+namespace base {
+
+TEST(SingleThreadTaskExecutorTest, GetTaskExecutorForCurrentThread) {
+  EXPECT_THAT(GetTaskExecutorForCurrentThread(), IsNull());
+
+  {
+    SingleThreadTaskExecutor single_thread_task_executor;
+    EXPECT_THAT(GetTaskExecutorForCurrentThread(), NotNull());
+  }
+
+  EXPECT_THAT(GetTaskExecutorForCurrentThread(), IsNull());
+}
+
+TEST(SingleThreadTaskExecutorTest,
+     GetTaskExecutorForCurrentThreadInPostedTask) {
+  SingleThreadTaskExecutor single_thread_task_executor;
+  TaskExecutor* task_executor = GetTaskExecutorForCurrentThread();
+
+  EXPECT_THAT(task_executor, NotNull());
+
+  RunLoop run_loop;
+  single_thread_task_executor.task_runner()->PostTask(
+      FROM_HERE, BindLambdaForTesting([&]() {
+        EXPECT_EQ(GetTaskExecutorForCurrentThread(), task_executor);
+        run_loop.Quit();
+      }));
+
+  run_loop.Run();
+}
+
+}  // namespace base
diff --git a/base/threading/thread.cc b/base/threading/thread.cc
index 6fc752f..eecd3491 100644
--- a/base/threading/thread.cc
+++ b/base/threading/thread.cc
@@ -19,6 +19,7 @@
 #include "base/synchronization/waitable_event.h"
 #include "base/task/sequence_manager/sequence_manager_impl.h"
 #include "base/task/sequence_manager/task_queue.h"
+#include "base/task/simple_task_executor.h"
 #include "base/third_party/dynamic_annotations/dynamic_annotations.h"
 #include "base/threading/thread_id_name_manager.h"
 #include "base/threading/thread_local.h"
@@ -86,6 +87,7 @@
     sequence_manager_->BindToMessagePump(
         std::move(message_pump_factory_).Run());
     sequence_manager_->SetTimerSlack(timer_slack);
+    simple_task_executor_.emplace(GetDefaultTaskRunner());
   }
 
  private:
@@ -93,6 +95,7 @@
       sequence_manager_;
   scoped_refptr<sequence_manager::TaskQueue> default_task_queue_;
   OnceCallback<std::unique_ptr<MessagePump>()> message_pump_factory_;
+  base::Optional<SimpleTaskExecutor> simple_task_executor_;
 };
 
 }  // namespace
diff --git a/base/threading/thread_unittest.cc b/base/threading/thread_unittest.cc
index bdb57cc..ab986326 100644
--- a/base/threading/thread_unittest.cc
+++ b/base/threading/thread_unittest.cc
@@ -18,15 +18,19 @@
 #include "base/single_thread_task_runner.h"
 #include "base/synchronization/waitable_event.h"
 #include "base/task/sequence_manager/sequence_manager_impl.h"
+#include "base/task/task_executor.h"
+#include "base/test/bind_test_util.h"
 #include "base/test/gtest_util.h"
 #include "base/third_party/dynamic_annotations/dynamic_annotations.h"
 #include "base/threading/platform_thread.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
+#include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "testing/platform_test.h"
 
 using base::Thread;
+using ::testing::NotNull;
 
 typedef PlatformTest ThreadTest;
 
@@ -522,6 +526,23 @@
   a.FlushForTesting();
 }
 
+TEST_F(ThreadTest, GetTaskExecutorForCurrentThread) {
+  Thread a("GetTaskExecutorForCurrentThread");
+  ASSERT_TRUE(a.Start());
+
+  base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL,
+                            base::WaitableEvent::InitialState::NOT_SIGNALED);
+
+  a.task_runner()->PostTask(
+      FROM_HERE, base::BindLambdaForTesting([&]() {
+        EXPECT_THAT(base::GetTaskExecutorForCurrentThread(), NotNull());
+        event.Signal();
+      }));
+
+  event.Wait();
+  a.Stop();
+}
+
 namespace {
 
 class SequenceManagerThreadDelegate : public Thread::Delegate {
diff --git a/base/win/windows_version.cc b/base/win/windows_version.cc
index 88974fa..476744b 100644
--- a/base/win/windows_version.cc
+++ b/base/win/windows_version.cc
@@ -214,15 +214,7 @@
           FilePath(FILE_PATH_LITERAL("kernelbase.dll")));
     }
     CHECK(file_version_info);
-    const int major =
-        HIWORD(file_version_info->fixed_file_info()->dwFileVersionMS);
-    const int minor =
-        LOWORD(file_version_info->fixed_file_info()->dwFileVersionMS);
-    const int build =
-        HIWORD(file_version_info->fixed_file_info()->dwFileVersionLS);
-    const int patch =
-        LOWORD(file_version_info->fixed_file_info()->dwFileVersionLS);
-    return base::Version(std::vector<uint32_t>{major, minor, build, patch});
+    return file_version_info->GetFileVersion();
   }());
   return *version;
 }
diff --git a/build/config/android/rules.gni b/build/config/android/rules.gni
index 2d5d634..e20e766 100644
--- a/build/config/android/rules.gni
+++ b/build/config/android/rules.gni
@@ -373,14 +373,16 @@
   template("generate_jni_registration") {
     action_with_pydeps(target_name) {
       forward_variables_from(invoker, [ "testonly" ])
-      _build_config = get_label_info(invoker.target, "target_gen_dir") + "/" +
-                      get_label_info(invoker.target, "name") + ".build_config"
+      _build_config = get_label_info("${invoker.target}($default_toolchain)",
+                                     "target_gen_dir") + "/" +
+                      get_label_info("${invoker.target}($default_toolchain)",
+                                     "name") + ".build_config"
       _rebased_build_config = rebase_path(_build_config, root_build_dir)
       _srcjar_output = "$target_gen_dir/$target_name.srcjar"
 
       script = "//base/android/jni_generator/jni_registration_generator.py"
       deps = [
-        "${invoker.target}$build_config_target_suffix",
+        "${invoker.target}$build_config_target_suffix($default_toolchain)",
       ]
       inputs = [
         _build_config,
diff --git a/build/config/compiler/BUILD.gn b/build/config/compiler/BUILD.gn
index e65eee2..2ef8771 100644
--- a/build/config/compiler/BUILD.gn
+++ b/build/config/compiler/BUILD.gn
@@ -1546,6 +1546,12 @@
           cflags += [
             # TODO(https://crbug.com/1010111): Evaluate and possible enable.
             "-Wno-enum-compare-conditional",
+
+            # TODO(https://crbug.com/1010458): Figure out plan forward,
+            # see bug. Depending on how many instances there are, either
+            # turn off warning when rolling this in, then fix and turn back on,
+            # or fix before the roll.
+            "-Wno-error=pessimizing-move",
           ]
         }
       }
diff --git a/build/fuchsia/linux.sdk.sha1 b/build/fuchsia/linux.sdk.sha1
index d2f4a60..a3cab05 100644
--- a/build/fuchsia/linux.sdk.sha1
+++ b/build/fuchsia/linux.sdk.sha1
@@ -1 +1 @@
-8900743524103453968
\ No newline at end of file
+8900716279085139440
\ No newline at end of file
diff --git a/build/fuchsia/mac.sdk.sha1 b/build/fuchsia/mac.sdk.sha1
index bc98830..fbe4b5d 100644
--- a/build/fuchsia/mac.sdk.sha1
+++ b/build/fuchsia/mac.sdk.sha1
@@ -1 +1 @@
-8900744692874110736
\ No newline at end of file
+8900715419223164800
\ No newline at end of file
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/metrics/VariationsSession.java b/chrome/android/java/src/org/chromium/chrome/browser/metrics/VariationsSession.java
index 6f3dd638..a1fa3e1 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/metrics/VariationsSession.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/metrics/VariationsSession.java
@@ -4,9 +4,8 @@
 
 package org.chromium.chrome.browser.metrics;
 
-import android.content.Context;
-
 import org.chromium.base.Callback;
+import org.chromium.base.annotations.NativeMethods;
 
 /**
  * Sets up communication with the VariationsService. This is primarily used for
@@ -20,14 +19,9 @@
      * Triggers to the native VariationsService that the application has entered the foreground.
      */
     public void start() {
-        start(null);
-    }
-
-    // TODO(agrieve): Delete after updating downstream.
-    public void start(Context unused) {
         // If |mRestrictModeFetchStarted| is true and |mRestrictMode| is null, then async
-        // initializationn is in progress and nativeStartVariationsSession() will be called
-        // when it completes.
+        // initialization is in progress and VariationsSessionJni.get().startVariationsSession()
+        // will be called when it completes.
         if (mRestrictModeFetchStarted && mRestrictMode == null) {
             return;
         }
@@ -36,7 +30,8 @@
         getRestrictModeValue(new Callback<String>() {
             @Override
             public void onResult(String restrictMode) {
-                nativeStartVariationsSession(mRestrictMode);
+                VariationsSessionJni.get().startVariationsSession(
+                        VariationsSession.this, mRestrictMode);
             }
         });
     }
@@ -71,11 +66,6 @@
      * restrict values, which must not be null.
      */
     protected void getRestrictMode(Callback<String> callback) {
-        // TODO(agrieve): Refactor downstream.
-        getRestrictMode(null, callback);
-    }
-
-    protected void getRestrictMode(Context unused, Callback<String> callback) {
         callback.onResult("");
     }
 
@@ -83,9 +73,12 @@
      * @return The latest country according to the current variations state. Null if not known.
      */
     public String getLatestCountry() {
-        return nativeGetLatestCountry();
+        return VariationsSessionJni.get().getLatestCountry(this);
     }
 
-    protected native void nativeStartVariationsSession(String restrictMode);
-    protected native String nativeGetLatestCountry();
+    @NativeMethods
+    interface Natives {
+        void startVariationsSession(VariationsSession caller, String restrictMode);
+        String getLatestCountry(VariationsSession caller);
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/preferences/sync/ManageSyncPreferences.java b/chrome/android/java/src/org/chromium/chrome/browser/preferences/sync/ManageSyncPreferences.java
index 64848db8..353debc89 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/preferences/sync/ManageSyncPreferences.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/preferences/sync/ManageSyncPreferences.java
@@ -263,10 +263,10 @@
             closeDialogIfOpen(FRAGMENT_ENTER_PASSPHRASE);
             return;
         }
-        if (!mProfileSyncService.isPassphraseRequiredForDecryption()) {
+        if (!mProfileSyncService.isPassphraseRequiredForPreferredDataTypes()) {
             closeDialogIfOpen(FRAGMENT_ENTER_PASSPHRASE);
         }
-        if (mProfileSyncService.isPassphraseRequiredForDecryption() && isAdded()) {
+        if (mProfileSyncService.isPassphraseRequiredForPreferredDataTypes() && isAdded()) {
             mSyncEncryption.setSummary(
                     errorSummary(getString(R.string.sync_need_passphrase), getActivity()));
         }
@@ -344,7 +344,7 @@
     @Override
     public boolean onPassphraseEntered(String passphrase) {
         if (!mProfileSyncService.isEngineInitialized()
-                || !mProfileSyncService.isPassphraseRequiredForDecryption()) {
+                || !mProfileSyncService.isPassphraseRequiredForPreferredDataTypes()) {
             // If the engine was shut down since the dialog was opened, or the passphrase isn't
             // required anymore, do nothing.
             return false;
@@ -397,7 +397,7 @@
     private void onSyncEncryptionClicked() {
         if (!mProfileSyncService.isEngineInitialized()) return;
 
-        if (mProfileSyncService.isPassphraseRequiredForDecryption()) {
+        if (mProfileSyncService.isPassphraseRequiredForPreferredDataTypes()) {
             displayPassphraseDialog();
         } else {
             displayPassphraseTypeDialog();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/preferences/sync/SyncAndServicesPreferences.java b/chrome/android/java/src/org/chromium/chrome/browser/preferences/sync/SyncAndServicesPreferences.java
index d03d866f..13f4f5c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/preferences/sync/SyncAndServicesPreferences.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/preferences/sync/SyncAndServicesPreferences.java
@@ -401,7 +401,7 @@
     @Override
     public boolean onPassphraseEntered(String passphrase) {
         if (!mProfileSyncService.isEngineInitialized()
-                || !mProfileSyncService.isPassphraseRequiredForDecryption()) {
+                || !mProfileSyncService.isPassphraseRequiredForPreferredDataTypes()) {
             // If the engine was shut down since the dialog was opened, or the passphrase isn't
             // required anymore, do nothing.
             return false;
@@ -438,7 +438,7 @@
         }
 
         if (mProfileSyncService.isEngineInitialized()
-                && mProfileSyncService.isPassphraseRequiredForDecryption()) {
+                && mProfileSyncService.isPassphraseRequiredForPreferredDataTypes()) {
             return SyncError.PASSPHRASE_REQUIRED;
         }
 
@@ -557,7 +557,7 @@
 
     private void updateSyncPreferences() {
         if (!mProfileSyncService.isEngineInitialized()
-                || !mProfileSyncService.isPassphraseRequiredForDecryption()) {
+                || !mProfileSyncService.isPassphraseRequiredForPreferredDataTypes()) {
             closeDialogIfOpen(FRAGMENT_ENTER_PASSPHRASE);
         }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/preferences/sync/SyncPreferenceUtils.java b/chrome/android/java/src/org/chromium/chrome/browser/preferences/sync/SyncPreferenceUtils.java
index 0539e9e0..bee34685 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/preferences/sync/SyncPreferenceUtils.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/preferences/sync/SyncPreferenceUtils.java
@@ -60,7 +60,7 @@
             }
 
             if (profileSyncService.isSyncActive()
-                    && profileSyncService.isPassphraseRequiredForDecryption()) {
+                    && profileSyncService.isPassphraseRequiredForPreferredDataTypes()) {
                 return true;
             }
         }
@@ -115,7 +115,7 @@
                 return res.getString(R.string.sync_setup_progress);
             }
 
-            if (profileSyncService.isPassphraseRequiredForDecryption()) {
+            if (profileSyncService.isPassphraseRequiredForPreferredDataTypes()) {
                 return res.getString(R.string.sync_need_passphrase);
             }
             return context.getString(R.string.sync_and_services_summary_sync_on);
@@ -149,7 +149,7 @@
         if (profileSyncService.isEngineInitialized()
                 && (profileSyncService.hasUnrecoverableError()
                         || profileSyncService.getAuthError() != GoogleServiceAuthError.State.NONE
-                        || profileSyncService.isPassphraseRequiredForDecryption())) {
+                        || profileSyncService.isPassphraseRequiredForPreferredDataTypes())) {
             return UiUtils.getTintedDrawable(
                     context, R.drawable.ic_sync_error_40dp, R.color.default_red);
         }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/sync/ProfileSyncService.java b/chrome/android/java/src/org/chromium/chrome/browser/sync/ProfileSyncService.java
index 4d31065..78e2d95 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/sync/ProfileSyncService.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/sync/ProfileSyncService.java
@@ -371,7 +371,7 @@
      * running (isEngineInitialized() returns true) before calling this function.
      * <p/>
      * This method should only be used if you want to know the raw value. For checking whether
-     * we should ask the user for a passphrase, use isPassphraseRequiredForDecryption().
+     * we should ask the user for a passphrase, use isPassphraseRequiredForPreferredDataTypes().
      */
     public @Passphrase.Type int getPassphraseType() {
         assert isEngineInitialized();
@@ -442,9 +442,9 @@
      *
      * @return true if we need a passphrase.
      */
-    public boolean isPassphraseRequiredForDecryption() {
+    public boolean isPassphraseRequiredForPreferredDataTypes() {
         assert isEngineInitialized();
-        return ProfileSyncServiceJni.get().isPassphraseRequiredForDecryption(
+        return ProfileSyncServiceJni.get().isPassphraseRequiredForPreferredDataTypes(
                 mNativeProfileSyncServiceAndroid, ProfileSyncService.this);
     }
 
@@ -646,7 +646,7 @@
                 long nativeProfileSyncServiceAndroid, ProfileSyncService caller);
         void enableEncryptEverything(
                 long nativeProfileSyncServiceAndroid, ProfileSyncService caller);
-        boolean isPassphraseRequiredForDecryption(
+        boolean isPassphraseRequiredForPreferredDataTypes(
                 long nativeProfileSyncServiceAndroid, ProfileSyncService caller);
         boolean isUsingSecondaryPassphrase(
                 long nativeProfileSyncServiceAndroid, ProfileSyncService caller);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/sync/SyncNotificationController.java b/chrome/android/java/src/org/chromium/chrome/browser/sync/SyncNotificationController.java
index 7a1eee0..a12fbbbd 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/sync/SyncNotificationController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/sync/SyncNotificationController.java
@@ -62,7 +62,7 @@
                     GoogleServiceAuthError.getMessageID(mProfileSyncService.getAuthError()),
                     createSettingsIntent());
         } else if (mProfileSyncService.isEngineInitialized()
-                && mProfileSyncService.isPassphraseRequiredForDecryption()) {
+                && mProfileSyncService.isPassphraseRequiredForPreferredDataTypes()) {
             if (mProfileSyncService.isPassphrasePrompted()) {
                 return;
             }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/sync/FakeProfileSyncService.java b/chrome/android/javatests/src/org/chromium/chrome/browser/sync/FakeProfileSyncService.java
index 8ae0ee6..846514f 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/sync/FakeProfileSyncService.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/sync/FakeProfileSyncService.java
@@ -56,7 +56,7 @@
     }
 
     @Override
-    public boolean isPassphraseRequiredForDecryption() {
+    public boolean isPassphraseRequiredForPreferredDataTypes() {
         return mPassphraseRequiredForDecryption;
     }
 
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/metrics/VariationsSessionTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/metrics/VariationsSessionTest.java
index 3bfbf8c..71dea3cf 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/metrics/VariationsSessionTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/metrics/VariationsSessionTest.java
@@ -4,19 +4,23 @@
 
 package org.chromium.chrome.browser.metrics;
 
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 import org.robolectric.annotation.Config;
 
 import org.chromium.base.Callback;
 import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.base.test.util.JniMocker;
 
 /**
  * Tests for VariationsSession
@@ -24,6 +28,11 @@
 @RunWith(BaseRobolectricTestRunner.class)
 @Config(manifest = Config.NONE)
 public class VariationsSessionTest {
+    @Rule
+    public JniMocker mocker = new JniMocker();
+    @Mock
+    private VariationsSession.Natives mVariationsSessionJniMock;
+
     private TestVariationsSession mSession;
 
     private static class TestVariationsSession extends VariationsSession {
@@ -37,26 +46,24 @@
         public void runCallback(String value) {
             mCallback.onResult(value);
         }
-
-        @Override
-        protected void nativeStartVariationsSession(String restrictMode) {
-            // No-op for tests.
-        }
     }
 
     @Before
     public void setUp() {
-        mSession = spy(new TestVariationsSession());
+        MockitoAnnotations.initMocks(this);
+        mocker.mock(VariationsSessionJni.TEST_HOOKS, mVariationsSessionJniMock);
+        mSession = new TestVariationsSession();
     }
 
     @Test
     public void testStart() {
         mSession.start();
-        verify(mSession, never()).nativeStartVariationsSession(any(String.class));
+        verify(mVariationsSessionJniMock, never())
+                .startVariationsSession(eq(mSession), any(String.class));
 
         String restrictValue = "test";
         mSession.runCallback(restrictValue);
-        verify(mSession, times(1)).nativeStartVariationsSession(restrictValue);
+        verify(mVariationsSessionJniMock, times(1)).startVariationsSession(mSession, restrictValue);
     }
 
     @Test
@@ -67,9 +74,10 @@
         });
         String restrictValue = "test";
         mSession.runCallback(restrictValue);
-        verify(mSession, never()).nativeStartVariationsSession(any(String.class));
+        verify(mVariationsSessionJniMock, never())
+                .startVariationsSession(eq(mSession), any(String.class));
 
         mSession.start();
-        verify(mSession, times(1)).nativeStartVariationsSession(restrictValue);
+        verify(mVariationsSessionJniMock, times(1)).startVariationsSession(mSession, restrictValue);
     }
 }
diff --git a/chrome/android/modules/test_dummy/internal/BUILD.gn b/chrome/android/modules/test_dummy/internal/BUILD.gn
index a361216..0547ad5 100644
--- a/chrome/android/modules/test_dummy/internal/BUILD.gn
+++ b/chrome/android/modules/test_dummy/internal/BUILD.gn
@@ -30,6 +30,7 @@
     "entrypoints.cc",
   ]
   deps = [
+    ":jni_registration",
     "//base",
     "//chrome/android/features/test_dummy/internal:native",
   ]
@@ -38,12 +39,6 @@
   if (use_native_modules) {
     cflags = [ "-fsymbol-partition=libtest_dummy.so" ]
   }
-
-  if (current_toolchain != default_toolchain) {
-    deps += [ ":jni_registration_secondary($default_toolchain)" ]
-  } else {
-    deps += [ ":jni_registration($default_toolchain)" ]
-  }
 }
 
 # TODO(https://crbug.com/1008109): Adapt JNI registration to point at a Java
@@ -55,26 +50,9 @@
 # including a registration stub, as Chrome's base library does. That requires
 # two sets of registration targets, so that the feature module template can
 # selectively pull in the real version or a stub.
-if (current_toolchain == default_toolchain) {
-  generate_jni_registration("jni_registration") {
-    target =
-        "//chrome/android:chrome_modern_public_bundle__test_dummy_bundle_module"
-    header_output = "$target_gen_dir/jni_registration.h"
-    namespace = "test_dummy"
-  }
-
-  # Note also: We cannot currently build JNI registration on secondary ABI
-  # toolchain (dependency issues). Therefore, for Monochrome's 32-bit library,
-  # we need to use the 64-bit side registration header that ChromeModern uses.
-  # However, it's in the 64-bit output directory. We need to also generate a copy
-  # for the 32-bit directory.
-  if (android_64bit_target_cpu) {
-    generate_jni_registration("jni_registration_secondary") {
-      target = "//chrome/android:chrome_modern_public_bundle__test_dummy_bundle_module"
-      header_output =
-          get_label_info(":native($android_secondary_abi_toolchain)",
-                         "target_gen_dir") + "/jni_registration.h"
-      namespace = "test_dummy"
-    }
-  }
+generate_jni_registration("jni_registration") {
+  target =
+      "//chrome/android:chrome_modern_public_bundle__test_dummy_bundle_module"
+  header_output = "$target_gen_dir/jni_registration.h"
+  namespace = "test_dummy"
 }
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index 56ceb04..2d5043e6 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -8490,21 +8490,6 @@
       <message name="IDS_MANAGE_PASSWORDS_AUTO_SIGNIN_TITLE_MD" desc="The title of the auto-signin toast in Material Design mode.">
         Signing in as
       </message>
-      <message name="IDS_CREDENTIAL_LEAK_TITLE" desc="The title of the credential leak dialog.">
-        Data breach reported
-      </message>
-      <message name="IDS_CREDENTIAL_LEAK_CHECK_PASSWORDS_MESSAGE" desc="The text that is used in credential leak detection dialog when saved credentials were used on multiple sites.">
-        A data breach on a site or app you use exposed your password. Chrome recommends checking your saved passwords now.
-      </message>
-      <message name="IDS_CREDENTIAL_LEAK_CHANGE_PASSWORD_MESSAGE" desc="The text that is used in credential leak detection dialog when credentials were leaked on current site only.">
-        A data breach on a site or app you use exposed your password. Chrome recommends <ph name="BOLD">$1<ex>changing your password</ex></ph> on <ph name="ORIGIN">$2<ex>example.com</ex></ph> now.
-      </message>
-      <message name="IDS_CREDENTIAL_LEAK_CHANGE_PASSWORD_BOLD_MESSAGE" desc="The text that is written in bold in leak dialog message when credentials were leaked on current site only.">
-        changing your password
-      </message>
-      <message name="IDS_CREDENTIAL_LEAK_CHANGE_AND_CHECK_PASSWORDS_MESSAGE" desc="The text that is used in credential leak detection dialog when credentials were not saved in chrome, but used on multiple sites.">
-        A data breach on a site or app you use exposed your password. Chrome recommends checking your saved passwords and changing your password on <ph name="ORIGIN">$1<ex>example.com</ex></ph>.
-      </message>
       <message name="IDS_AUTO_SIGNIN_FIRST_RUN_TITLE_MANY_DEVICES" desc="The title of the dialog during the autosign-in first run experience for the Chrome syncing users.">
         Sign in easily across devices
       </message>
@@ -8524,16 +8509,6 @@
           OK, got it
         </message>
       </if>
-      <if expr="use_titlecase">
-        <message name="IDS_LEAK_CHECK_CREDENTIALS" desc="The text of the OK button in the dialog for credentials leaked on multiple sites.">
-          Check Passwords
-        </message>
-      </if>
-      <if expr="not use_titlecase">
-        <message name="IDS_LEAK_CHECK_CREDENTIALS" desc="The text of the OK button in the dialog for credentials leaked on multiple sites.">
-          Check passwords
-        </message>
-      </if>
 
       <!-- Extra Mac UI Strings -->
       <if expr="is_macosx">
diff --git a/chrome/browser/android/chrome_feature_list.cc b/chrome/browser/android/chrome_feature_list.cc
index ea130fa..59e12d6 100644
--- a/chrome/browser/android/chrome_feature_list.cc
+++ b/chrome/browser/android/chrome_feature_list.cc
@@ -35,7 +35,6 @@
 #include "components/signin/public/base/account_consistency_method.h"
 #include "components/subresource_filter/core/browser/subresource_filter_features.h"
 #include "components/sync/driver/sync_driver_switches.h"
-#include "components/unified_consent/feature.h"
 #include "content/public/common/content_features.h"
 #include "media/base/media_switches.h"
 #include "services/device/public/cpp/device_features.h"
@@ -223,7 +222,6 @@
     &safe_browsing::kCaptureSafetyNetId,
     &signin::kMiceFeature,
     &switches::kSyncManualStartAndroid,
-    &unified_consent::kUnifiedConsent,
     &subresource_filter::kSafeBrowsingSubresourceFilter,
 };
 
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index 2629898..b37e3f4 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -672,6 +672,8 @@
     "attestation/platform_verification_dialog.h",
     "attestation/platform_verification_flow.cc",
     "attestation/platform_verification_flow.h",
+    "attestation/tpm_challenge_key.cc",
+    "attestation/tpm_challenge_key.h",
     "authpolicy/auth_policy_credentials_manager.cc",
     "authpolicy/auth_policy_credentials_manager.h",
     "authpolicy/authpolicy_helper.cc",
@@ -2487,7 +2489,10 @@
     "attestation/fake_certificate.cc",
     "attestation/fake_certificate.h",
     "attestation/machine_certificate_uploader_impl_unittest.cc",
+    "attestation/mock_tpm_challenge_key.cc",
+    "attestation/mock_tpm_challenge_key.h",
     "attestation/platform_verification_flow_unittest.cc",
+    "attestation/tpm_challenge_key_unittest.cc",
     "authpolicy/auth_policy_credentials_manager_unittest.cc",
     "authpolicy/authpolicy_helper.unittest.cc",
     "base/file_flusher_unittest.cc",
diff --git a/chrome/browser/chromeos/account_manager/account_manager_util.cc b/chrome/browser/chromeos/account_manager/account_manager_util.cc
index 93cd26c..597e19ce 100644
--- a/chrome/browser/chromeos/account_manager/account_manager_util.cc
+++ b/chrome/browser/chromeos/account_manager/account_manager_util.cc
@@ -13,16 +13,12 @@
 #include "chrome/browser/profiles/profiles_state.h"
 #include "chromeos/components/account_manager/account_manager.h"
 #include "chromeos/components/account_manager/account_manager_factory.h"
-#include "chromeos/constants/chromeos_features.h"
 #include "chromeos/tpm/install_attributes.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 
 namespace chromeos {
 
 bool IsAccountManagerAvailable(const Profile* const profile) {
-  if (!features::IsAccountManagerEnabled())
-    return false;
-
   // Signin Profile does not have any accounts associated with it.
   if (chromeos::ProfileHelper::IsSigninProfile(profile))
     return false;
diff --git a/chrome/browser/chromeos/attestation/OWNERS b/chrome/browser/chromeos/attestation/OWNERS
index 9c8d156..57c540d2 100644
--- a/chrome/browser/chromeos/attestation/OWNERS
+++ b/chrome/browser/chromeos/attestation/OWNERS
@@ -3,3 +3,7 @@
 dkrahn@chromium.org
 mnissler@chromium.org
 bartfab@chromium.org
+
+per-file *tpm_challenge_key*=pmarko@chromium.org
+per-file *tpm_challenge_key*=emaxx@chromium.org
+per-file *tpm_challenge_key*=drcrash@chromium.org
diff --git a/chrome/browser/chromeos/attestation/mock_tpm_challenge_key.cc b/chrome/browser/chromeos/attestation/mock_tpm_challenge_key.cc
new file mode 100644
index 0000000..b862be8
--- /dev/null
+++ b/chrome/browser/chromeos/attestation/mock_tpm_challenge_key.cc
@@ -0,0 +1,28 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/chromeos/attestation/mock_tpm_challenge_key.h"
+#include <utility>
+
+using ::testing::Invoke;
+using ::testing::WithArgs;
+
+namespace chromeos {
+namespace attestation {
+
+MockTpmChallengeKey::MockTpmChallengeKey() = default;
+MockTpmChallengeKey::~MockTpmChallengeKey() = default;
+
+void MockTpmChallengeKey::EnableFake() {
+  ON_CALL(*this, Run)
+      .WillByDefault(
+          WithArgs<2>(Invoke(this, &MockTpmChallengeKey::FakeRunSuccess)));
+}
+
+void MockTpmChallengeKey::FakeRunSuccess(TpmChallengeKeyCallback callback) {
+  std::move(callback).Run(TpmChallengeKeyResult::MakeResult("response"));
+}
+
+}  // namespace attestation
+}  // namespace chromeos
diff --git a/chrome/browser/chromeos/attestation/mock_tpm_challenge_key.h b/chrome/browser/chromeos/attestation/mock_tpm_challenge_key.h
new file mode 100644
index 0000000..f69e55b
--- /dev/null
+++ b/chrome/browser/chromeos/attestation/mock_tpm_challenge_key.h
@@ -0,0 +1,40 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_CHROMEOS_ATTESTATION_MOCK_TPM_CHALLENGE_KEY_H_
+#define CHROME_BROWSER_CHROMEOS_ATTESTATION_MOCK_TPM_CHALLENGE_KEY_H_
+
+#include <string>
+
+#include "chrome/browser/chromeos/attestation/tpm_challenge_key.h"
+#include "chromeos/dbus/constants/attestation_constants.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace chromeos {
+namespace attestation {
+
+class MockTpmChallengeKey : public TpmChallengeKey {
+ public:
+  MockTpmChallengeKey();
+  ~MockTpmChallengeKey() override;
+
+  void EnableFake();
+
+  MOCK_METHOD(void,
+              Run,
+              (chromeos::attestation::AttestationKeyType key_type,
+               Profile* profile,
+               TpmChallengeKeyCallback callback,
+               const std::string& challenge,
+               bool register_key,
+               const std::string& key_name_for_spkac),
+              (override));
+
+  void FakeRunSuccess(TpmChallengeKeyCallback callback);
+};
+
+}  // namespace attestation
+}  // namespace chromeos
+
+#endif  // CHROME_BROWSER_CHROMEOS_ATTESTATION_MOCK_TPM_CHALLENGE_KEY_H_
diff --git a/chrome/browser/chromeos/attestation/tpm_challenge_key.cc b/chrome/browser/chromeos/attestation/tpm_challenge_key.cc
new file mode 100644
index 0000000..c2c8725
--- /dev/null
+++ b/chrome/browser/chromeos/attestation/tpm_challenge_key.cc
@@ -0,0 +1,528 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/chromeos/attestation/tpm_challenge_key.h"
+
+#include "base/base64.h"
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/strings/stringprintf.h"
+#include "base/values.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/chromeos/attestation/attestation_ca_client.h"
+#include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h"
+#include "chrome/browser/chromeos/profiles/profile_helper.h"
+#include "chrome/browser/chromeos/settings/cros_settings.h"
+#include "chrome/browser/extensions/chrome_extension_function_details.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/common/pref_names.h"
+#include "chromeos/cryptohome/async_method_caller.h"
+#include "chromeos/cryptohome/cryptohome_parameters.h"
+#include "chromeos/settings/cros_settings_names.h"
+#include "chromeos/tpm/install_attributes.h"
+#include "components/pref_registry/pref_registry_syncable.h"
+#include "components/prefs/pref_service.h"
+
+namespace chromeos {
+namespace attestation {
+
+//========================= TpmChallengeKeyFactory =============================
+
+TpmChallengeKey* TpmChallengeKeyFactory::next_result_for_testing_ = nullptr;
+
+// static
+std::unique_ptr<TpmChallengeKey> TpmChallengeKeyFactory::Create() {
+  if (LIKELY(!next_result_for_testing_)) {
+    return std::make_unique<TpmChallengeKeyImpl>();
+  }
+
+  std::unique_ptr<TpmChallengeKey> result(next_result_for_testing_);
+  next_result_for_testing_ = nullptr;
+  return result;
+}
+
+// static
+void TpmChallengeKeyFactory::SetForTesting(
+    std::unique_ptr<TpmChallengeKey> next_result) {
+  // unique_ptr itself cannot be stored in a static variable because of its
+  // complex destructor.
+  next_result_for_testing_ = next_result.release();
+}
+
+//============================ TpmChallengeKeyResult ===========================
+
+// static
+TpmChallengeKeyResult TpmChallengeKeyResult::MakeResult(
+    const std::string& success_result) {
+  return TpmChallengeKeyResult{/*is_success=*/true,
+                               /*data=*/success_result,
+                               /*error_message=*/""};
+}
+
+// static
+TpmChallengeKeyResult TpmChallengeKeyResult::MakeError(
+    const std::string& error_message) {
+  return TpmChallengeKeyResult{/*is_success=*/false,
+                               /*data=*/"",
+                               /*error_message=*/error_message};
+}
+
+//=========================== TpmChallengeKeyImpl ==============================
+
+namespace {
+// Returns true if the device is enterprise managed.
+bool IsEnterpriseDevice() {
+  return InstallAttributes::Get()->IsEnterpriseManaged();
+}
+
+// For personal devices, we don't need to check if remote attestation is
+// enabled in the device, but we need to ask for user consent if the key
+// does not exist.
+bool IsUserConsentRequired() {
+  return !IsEnterpriseDevice();
+}
+}  // namespace
+
+const char TpmChallengeKeyImpl::kDevicePolicyDisabledError[] =
+    "Remote attestation is not enabled for your device.";
+const char TpmChallengeKeyImpl::kSignChallengeFailedError[] =
+    "Failed to sign the challenge.";
+const char TpmChallengeKeyImpl::kUserNotManaged[] =
+    "The user account is not enterprise managed.";
+const char TpmChallengeKeyImpl::kKeyRegistrationFailedError[] =
+    "Key registration failed.";
+const char TpmChallengeKeyImpl::kGetCertificateFailedError[] =
+    "Failed to get Enterprise certificate. Error code = %d";
+const char TpmChallengeKeyImpl::kUserPolicyDisabledError[] =
+    "Remote attestation is not enabled for your account.";
+const char TpmChallengeKeyImpl::kUserKeyNotAvailable[] =
+    "User keys cannot be challenged in this profile.";
+const char TpmChallengeKeyImpl::kNonEnterpriseDeviceError[] =
+    "The device is not enterprise enrolled.";
+
+void TpmChallengeKey::RegisterProfilePrefs(
+    user_prefs::PrefRegistrySyncable* registry) {
+  registry->RegisterBooleanPref(prefs::kAttestationEnabled, false);
+}
+
+TpmChallengeKeyImpl::TpmChallengeKeyImpl()
+    : default_attestation_flow_(std::make_unique<AttestationFlow>(
+          cryptohome::AsyncMethodCaller::GetInstance(),
+          CryptohomeClient::Get(),
+          std::make_unique<AttestationCAClient>())),
+      attestation_flow_(default_attestation_flow_.get()) {}
+
+TpmChallengeKeyImpl::TpmChallengeKeyImpl(
+    AttestationFlow* attestation_flow_for_testing)
+    : attestation_flow_(attestation_flow_for_testing) {}
+
+TpmChallengeKeyImpl::~TpmChallengeKeyImpl() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+}
+
+void TpmChallengeKeyImpl::Run(AttestationKeyType key_type,
+                              Profile* profile,
+                              TpmChallengeKeyCallback callback,
+                              const std::string& challenge,
+                              bool register_key,
+                              const std::string& key_name_for_spkac) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  // |key_name_for_spkac| was designed to only be used with KEY_DEVICE.
+  DCHECK((key_type != KEY_USER) || key_name_for_spkac.empty())
+      << "Key name for SPKAC will be unused.";
+
+  // For device key: if |register_key| is true, |key_name_for_spkac| should not
+  // be empty; if |register_key| is false, |key_name_for_spkac| is not used.
+  DCHECK((key_type != KEY_DEVICE) ||
+         (register_key == !key_name_for_spkac.empty()))
+      << "Invalid arguments: " << register_key << " "
+      << !key_name_for_spkac.empty();
+
+  challenge_ = challenge;
+  profile_ = profile;
+  callback_ = std::move(callback);
+  key_type_ = key_type;
+  register_key_ = register_key;
+  key_name_for_spkac_ = key_name_for_spkac;
+
+  switch (key_type_) {
+    case KEY_DEVICE:
+      ChallengeMachineKey();
+      return;
+    case KEY_USER:
+      ChallengeUserKey();
+      return;
+  }
+  NOTREACHED();
+}
+
+void TpmChallengeKeyImpl::ChallengeMachineKey() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  // Check if the device is enterprise enrolled.
+  if (!IsEnterpriseDevice()) {
+    std::move(callback_).Run(
+        TpmChallengeKeyResult::MakeError(kNonEnterpriseDeviceError));
+    return;
+  }
+
+  // Check whether the user is managed unless the signin profile is used.
+  if (GetUser() && !IsUserAffiliated()) {
+    std::move(callback_).Run(TpmChallengeKeyResult::MakeError(kUserNotManaged));
+    return;
+  }
+
+  // Check if remote attestation is enabled in the device policy.
+  GetDeviceAttestationEnabled(base::BindRepeating(
+      &TpmChallengeKeyImpl::GetDeviceAttestationEnabledCallback,
+      weak_factory_.GetWeakPtr()));
+}
+
+void TpmChallengeKeyImpl::ChallengeUserKey() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  // Check if user keys are available in this profile.
+  if (!GetUser()) {
+    std::move(callback_).Run(
+        TpmChallengeKeyResult::MakeError(kUserKeyNotAvailable));
+    return;
+  }
+
+  if (!IsRemoteAttestationEnabledForUser()) {
+    std::move(callback_).Run(
+        TpmChallengeKeyResult::MakeError(kUserPolicyDisabledError));
+    return;
+  }
+
+  if (IsEnterpriseDevice()) {
+    if (!IsUserAffiliated()) {
+      std::move(callback_).Run(
+          TpmChallengeKeyResult::MakeError(kUserNotManaged));
+      return;
+    }
+
+    // Check if remote attestation is enabled in the device policy.
+    GetDeviceAttestationEnabled(base::BindRepeating(
+        &TpmChallengeKeyImpl::GetDeviceAttestationEnabledCallback,
+        weak_factory_.GetWeakPtr()));
+  } else {
+    GetDeviceAttestationEnabledCallback(true);
+  }
+}
+
+bool TpmChallengeKeyImpl::IsUserAffiliated() const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  const user_manager::User* const user = GetUser();
+  if (user) {
+    return user->IsAffiliated();
+  }
+  return false;
+}
+
+bool TpmChallengeKeyImpl::IsRemoteAttestationEnabledForUser() const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  PrefService* prefs = profile_->GetPrefs();
+  if (prefs && prefs->IsManagedPreference(prefs::kAttestationEnabled)) {
+    return prefs->GetBoolean(prefs::kAttestationEnabled);
+  }
+  return false;
+}
+
+std::string TpmChallengeKeyImpl::GetEmail() const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  switch (key_type_) {
+    case KEY_DEVICE:
+      return InstallAttributes::Get()->GetDomain();
+    case KEY_USER:
+      return GetAccountId().GetUserEmail();
+  }
+  NOTREACHED();
+}
+
+const char* TpmChallengeKeyImpl::GetKeyName() const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  switch (key_type_) {
+    case KEY_DEVICE:
+      return kEnterpriseMachineKey;
+    case KEY_USER:
+      return kEnterpriseUserKey;
+  }
+  NOTREACHED();
+}
+
+AttestationCertificateProfile TpmChallengeKeyImpl::GetCertificateProfile()
+    const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  switch (key_type_) {
+    case KEY_DEVICE:
+      return PROFILE_ENTERPRISE_MACHINE_CERTIFICATE;
+    case KEY_USER:
+      return PROFILE_ENTERPRISE_USER_CERTIFICATE;
+  }
+  NOTREACHED();
+}
+
+std::string TpmChallengeKeyImpl::GetKeyNameForRegister() const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  switch (key_type_) {
+    case KEY_DEVICE:
+      return key_name_for_spkac_;
+    case KEY_USER:
+      return GetKeyName();
+  }
+  NOTREACHED();
+}
+
+const user_manager::User* TpmChallengeKeyImpl::GetUser() const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  return ProfileHelper::Get()->GetUserByProfile(profile_);
+}
+
+AccountId TpmChallengeKeyImpl::GetAccountId() const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  const user_manager::User* user = GetUser();
+  if (user) {
+    return user->GetAccountId();
+  }
+  // Signin profile doesn't have associated user.
+  return EmptyAccountId();
+}
+
+void TpmChallengeKeyImpl::GetDeviceAttestationEnabled(
+    const base::RepeatingCallback<void(bool)>& callback) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  CrosSettings* settings = CrosSettings::Get();
+  CrosSettingsProvider::TrustedStatus status = settings->PrepareTrustedValues(
+      base::BindRepeating(&TpmChallengeKeyImpl::GetDeviceAttestationEnabled,
+                          weak_factory_.GetWeakPtr(), callback));
+
+  bool value = false;
+  switch (status) {
+    case CrosSettingsProvider::TRUSTED:
+      if (!settings->GetBoolean(kDeviceAttestationEnabled, &value)) {
+        value = false;
+      }
+      break;
+    case CrosSettingsProvider::TEMPORARILY_UNTRUSTED:
+      // Do nothing. This function will be called again when the values are
+      // ready.
+      return;
+    case CrosSettingsProvider::PERMANENTLY_UNTRUSTED:
+      // If the value cannot be trusted, we assume that the device attestation
+      // is false to be on the safe side.
+      break;
+  }
+
+  callback.Run(value);
+}
+
+void TpmChallengeKeyImpl::GetDeviceAttestationEnabledCallback(bool enabled) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  if (!enabled) {
+    std::move(callback_).Run(
+        TpmChallengeKeyResult::MakeError(kDevicePolicyDisabledError));
+    return;
+  }
+
+  PrepareKey();
+}
+
+void TpmChallengeKeyImpl::PrepareKey() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  CryptohomeClient::Get()->TpmAttestationIsPrepared(
+      base::BindOnce(&TpmChallengeKeyImpl::IsAttestationPreparedCallback,
+                     weak_factory_.GetWeakPtr()));
+}
+
+void TpmChallengeKeyImpl::IsAttestationPreparedCallback(
+    base::Optional<bool> result) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  if (!result.has_value()) {
+    PrepareKeyFinished(PrepareKeyResult::kDbusError);
+    return;
+  }
+  if (!result.value()) {
+    CryptohomeClient::Get()->TpmIsEnabled(
+        base::BindOnce(&TpmChallengeKeyImpl::PrepareKeyErrorHandlerCallback,
+                       weak_factory_.GetWeakPtr()));
+    return;
+  }
+
+  if (!key_name_for_spkac_.empty()) {
+    // Generate a new key and have it signed by PCA.
+    attestation_flow_->GetCertificate(
+        GetCertificateProfile(), GetAccountId(),
+        std::string(),  // Not used.
+        true,           // Force a new key to be generated.
+        key_name_for_spkac_,
+        base::BindRepeating(&TpmChallengeKeyImpl::GetCertificateCallback,
+                            weak_factory_.GetWeakPtr()));
+    return;
+  }
+
+  // Attestation is available, see if the key we need already exists.
+  CryptohomeClient::Get()->TpmAttestationDoesKeyExist(
+      key_type_,
+      cryptohome::CreateAccountIdentifierFromAccountId(GetAccountId()),
+      GetKeyName(),
+      base::BindRepeating(&TpmChallengeKeyImpl::DoesKeyExistCallback,
+                          weak_factory_.GetWeakPtr()));
+}
+
+void TpmChallengeKeyImpl::PrepareKeyErrorHandlerCallback(
+    base::Optional<bool> is_tpm_enabled) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  if (!is_tpm_enabled.has_value()) {
+    PrepareKeyFinished(PrepareKeyResult::kDbusError);
+    return;
+  }
+
+  if (is_tpm_enabled.value()) {
+    PrepareKeyFinished(PrepareKeyResult::kResetRequired);
+  } else {
+    PrepareKeyFinished(PrepareKeyResult::kAttestationUnsupported);
+  }
+}
+
+void TpmChallengeKeyImpl::DoesKeyExistCallback(base::Optional<bool> result) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  if (!result.has_value()) {
+    PrepareKeyFinished(PrepareKeyResult::kDbusError);
+    return;
+  }
+
+  if (result.value()) {
+    // The key exists. Do nothing more.
+    PrepareKeyFinished(PrepareKeyResult::kOk);
+    return;
+  }
+
+  // The key does not exist. Create a new key and have it signed by PCA.
+  if (IsUserConsentRequired()) {
+    // We should ask the user explicitly before sending any private
+    // information to PCA.
+    AskForUserConsent(
+        base::BindOnce(&TpmChallengeKeyImpl::AskForUserConsentCallback,
+                       weak_factory_.GetWeakPtr()));
+  } else {
+    // User consent is not required. Skip to the next step.
+    AskForUserConsentCallback(true);
+  }
+}
+
+void TpmChallengeKeyImpl::AskForUserConsent(
+    base::OnceCallback<void(bool)> callback) const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  // TODO(davidyu): right now we just simply reject the request before we have
+  // a way to ask for user consent.
+  std::move(callback).Run(false);
+}
+
+void TpmChallengeKeyImpl::AskForUserConsentCallback(bool result) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  if (!result) {
+    // The user rejects the request.
+    PrepareKeyFinished(PrepareKeyResult::kUserRejected);
+    return;
+  }
+
+  // Generate a new key and have it signed by PCA.
+  attestation_flow_->GetCertificate(
+      GetCertificateProfile(), GetAccountId(),
+      std::string(),  // Not used.
+      true,           // Force a new key to be generated.
+      std::string(),  // Leave key name empty to generate a default name.
+      base::BindRepeating(&TpmChallengeKeyImpl::GetCertificateCallback,
+                          weak_factory_.GetWeakPtr()));
+}
+
+void TpmChallengeKeyImpl::GetCertificateCallback(
+    AttestationStatus status,
+    const std::string& pem_certificate_chain) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  if (status != ATTESTATION_SUCCESS) {
+    PrepareKeyFinished(PrepareKeyResult::kGetCertificateFailed);
+    return;
+  }
+
+  PrepareKeyFinished(PrepareKeyResult::kOk);
+}
+
+void TpmChallengeKeyImpl::PrepareKeyFinished(PrepareKeyResult result) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  if (result != PrepareKeyResult::kOk) {
+    std::move(callback_).Run(TpmChallengeKeyResult::MakeError(
+        base::StringPrintf(kGetCertificateFailedError, result)));
+    return;
+  }
+
+  // Everything is checked. Sign the challenge.
+  cryptohome::AsyncMethodCaller::GetInstance()
+      ->TpmAttestationSignEnterpriseChallenge(
+          key_type_, cryptohome::Identification(GetAccountId()), GetKeyName(),
+          GetEmail(), InstallAttributes::Get()->GetDeviceId(),
+          register_key_ ? CHALLENGE_INCLUDE_SIGNED_PUBLIC_KEY
+                        : CHALLENGE_OPTION_NONE,
+          challenge_, key_name_for_spkac_,
+          base::BindRepeating(&TpmChallengeKeyImpl::SignChallengeCallback,
+                              weak_factory_.GetWeakPtr(), register_key_));
+}
+
+void TpmChallengeKeyImpl::SignChallengeCallback(bool register_key,
+                                                bool success,
+                                                const std::string& response) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  if (!success) {
+    std::move(callback_).Run(
+        TpmChallengeKeyResult::MakeError(kSignChallengeFailedError));
+    return;
+  }
+
+  if (register_key) {
+    cryptohome::AsyncMethodCaller::GetInstance()->TpmAttestationRegisterKey(
+        key_type_, cryptohome::Identification(GetAccountId()),
+        GetKeyNameForRegister(),
+        base::BindRepeating(&TpmChallengeKeyImpl::RegisterKeyCallback,
+                            weak_factory_.GetWeakPtr(), response));
+  } else {
+    RegisterKeyCallback(response, true, cryptohome::MOUNT_ERROR_NONE);
+  }
+}
+
+void TpmChallengeKeyImpl::RegisterKeyCallback(
+    const std::string& response,
+    bool success,
+    cryptohome::MountError return_code) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  if (!success || return_code != cryptohome::MOUNT_ERROR_NONE) {
+    std::move(callback_).Run(
+        TpmChallengeKeyResult::MakeError(kKeyRegistrationFailedError));
+    return;
+  }
+  std::move(callback_).Run(TpmChallengeKeyResult::MakeResult(response));
+}
+
+}  // namespace attestation
+}  // namespace chromeos
diff --git a/chrome/browser/chromeos/attestation/tpm_challenge_key.h b/chrome/browser/chromeos/attestation/tpm_challenge_key.h
new file mode 100644
index 0000000..aaf154c
--- /dev/null
+++ b/chrome/browser/chromeos/attestation/tpm_challenge_key.h
@@ -0,0 +1,196 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_CHROMEOS_ATTESTATION_TPM_CHALLENGE_KEY_H_
+#define CHROME_BROWSER_CHROMEOS_ATTESTATION_TPM_CHALLENGE_KEY_H_
+
+#include <memory>
+#include <string>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/optional.h"
+#include "base/sequence_checker.h"
+#include "chromeos/attestation/attestation_flow.h"
+#include "chromeos/dbus/constants/attestation_constants.h"
+#include "chromeos/dbus/cryptohome/cryptohome_client.h"
+#include "components/account_id/account_id.h"
+#include "components/user_manager/user.h"
+#include "third_party/cros_system_api/dbus/cryptohome/dbus-constants.h"
+
+class Profile;
+
+namespace user_prefs {
+class PrefRegistrySyncable;
+}
+
+namespace chromeos {
+namespace attestation {
+
+//========================= TpmChallengeKeyFactory =============================
+
+class TpmChallengeKey;
+
+class TpmChallengeKeyFactory {
+ public:
+  static std::unique_ptr<TpmChallengeKey> Create();
+  static void SetForTesting(std::unique_ptr<TpmChallengeKey> next_result);
+
+ private:
+  static TpmChallengeKey* next_result_for_testing_;
+};
+
+//========================= TpmChallengeKeyResult ==============================
+
+// If |is_success| is true then |data| contains a challenge response and
+// |error_message| is empty. If |is_success| is false then |error_message|
+// contains an error message and |data| is empty.
+struct TpmChallengeKeyResult {
+  static TpmChallengeKeyResult MakeResult(const std::string& success_result);
+  static TpmChallengeKeyResult MakeError(const std::string& error_message);
+
+  bool is_success = false;
+  std::string data;
+  std::string error_message;
+};
+
+//=========================== TpmChallengeKey ==================================
+
+using TpmChallengeKeyCallback =
+    base::OnceCallback<void(const TpmChallengeKeyResult& result)>;
+
+// Asynchronously run the flow to challenge a key in the caller context.
+class TpmChallengeKey {
+ public:
+  TpmChallengeKey(const TpmChallengeKey&) = delete;
+  TpmChallengeKey& operator=(const TpmChallengeKey&) = delete;
+  virtual ~TpmChallengeKey() = default;
+
+  static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry);
+
+  // Should be called only once for every instance. |TpmChallengeKey| object
+  // should live as long as response from |Run| function via |callback| is
+  // expected. On destruction it stops challenge process and silently discards
+  // callback. |key_name_for_spkac| the name of the key used for
+  // SignedPublicKeyAndChallenge when sending a challenge machine key request
+  // with |registerKey|=true.
+  virtual void Run(AttestationKeyType key_type,
+                   Profile* profile,
+                   TpmChallengeKeyCallback callback,
+                   const std::string& challenge,
+                   bool register_key,
+                   const std::string& key_name_for_spkac) = 0;
+
+ protected:
+  // Use TpmChallengeKeyFactory for creation.
+  TpmChallengeKey() = default;
+};
+
+//=========================== TpmChallengeKeyImpl ==============================
+
+class TpmChallengeKeyImpl : public TpmChallengeKey {
+ public:
+  static const char kDevicePolicyDisabledError[];
+  static const char kSignChallengeFailedError[];
+  static const char kUserNotManaged[];
+  static const char kKeyRegistrationFailedError[];
+  static const char kGetCertificateFailedError[];
+  static const char kUserKeyNotAvailable[];
+  static const char kUserPolicyDisabledError[];
+  static const char kNonEnterpriseDeviceError[];
+
+  // Use TpmChallengeKeyFactory for creation.
+  TpmChallengeKeyImpl();
+  // Use only for testing.
+  explicit TpmChallengeKeyImpl(AttestationFlow* attestation_flow_for_testing);
+  TpmChallengeKeyImpl(const TpmChallengeKeyImpl&) = delete;
+  TpmChallengeKeyImpl& operator=(const TpmChallengeKeyImpl&) = delete;
+  ~TpmChallengeKeyImpl() override;
+
+  // TpmChallengeKey
+  void Run(AttestationKeyType key_type,
+           Profile* profile,
+           TpmChallengeKeyCallback callback,
+           const std::string& challenge,
+           bool register_key,
+           const std::string& key_name_for_spkac) override;
+
+ private:
+  enum class PrepareKeyResult {
+    kOk,
+    kDbusError,
+    kUserRejected,
+    kGetCertificateFailed,
+    kResetRequired,
+    kAttestationUnsupported
+  };
+
+  void ChallengeUserKey();
+  void ChallengeMachineKey();
+
+  // Returns true if the user is managed and is affiliated with the domain the
+  // device is enrolled to.
+  bool IsUserAffiliated() const;
+  // Returns true if remote attestation is allowed and the setting is managed.
+  bool IsRemoteAttestationEnabledForUser() const;
+
+  // Returns the enterprise domain the device is enrolled to or user email.
+  std::string GetEmail() const;
+  const char* GetKeyName() const;
+  AttestationCertificateProfile GetCertificateProfile() const;
+  std::string GetKeyNameForRegister() const;
+  const user_manager::User* GetUser() const;
+  AccountId GetAccountId() const;
+
+  // Prepares the key for signing. It will first check if a new key should be
+  // generated, i.e. |key_name_for_spkac_| is not empty or the key doesn't exist
+  // and, if necessary, call AttestationFlow::GetCertificate() to get a new one.
+  // If |IsUserConsentRequired()| is true, it will explicitly ask for user
+  // consent before calling GetCertificate(). Ends up calling
+  // |PrepareKeyFinished| function.
+  void PrepareKey();
+  void PrepareKeyFinished(PrepareKeyResult result);
+
+  void SignChallengeCallback(bool register_key,
+                             bool success,
+                             const std::string& response);
+  void RegisterKeyCallback(const std::string& response,
+                           bool success,
+                           cryptohome::MountError return_code);
+  // Returns a trusted value from CrosSettings indicating if the device
+  // attestation is enabled.
+  void GetDeviceAttestationEnabled(
+      const base::RepeatingCallback<void(bool)>& callback);
+  void GetDeviceAttestationEnabledCallback(bool enabled);
+
+  void IsAttestationPreparedCallback(base::Optional<bool> result);
+  void PrepareKeyErrorHandlerCallback(base::Optional<bool> is_tpm_enabled);
+  void DoesKeyExistCallback(base::Optional<bool> result);
+  void AskForUserConsent(base::OnceCallback<void(bool)> callback) const;
+  void AskForUserConsentCallback(bool result);
+  void GetCertificateCallback(AttestationStatus status,
+                              const std::string& pem_certificate_chain);
+
+  std::unique_ptr<AttestationFlow> default_attestation_flow_;
+  AttestationFlow* attestation_flow_ = nullptr;
+
+  TpmChallengeKeyCallback callback_;
+  Profile* profile_ = nullptr;
+
+  AttestationKeyType key_type_ = AttestationKeyType::KEY_DEVICE;
+  std::string challenge_;
+
+  bool register_key_ = false;
+  std::string key_name_for_spkac_;
+
+  SEQUENCE_CHECKER(sequence_checker_);
+
+  base::WeakPtrFactory<TpmChallengeKeyImpl> weak_factory_{this};
+};
+
+}  // namespace attestation
+}  // namespace chromeos
+
+#endif  // CHROME_BROWSER_CHROMEOS_ATTESTATION_TPM_CHALLENGE_KEY_H_
diff --git a/chrome/browser/chromeos/attestation/tpm_challenge_key_unittest.cc b/chrome/browser/chromeos/attestation/tpm_challenge_key_unittest.cc
new file mode 100644
index 0000000..be0ec53
--- /dev/null
+++ b/chrome/browser/chromeos/attestation/tpm_challenge_key_unittest.cc
@@ -0,0 +1,746 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/chromeos/attestation/tpm_challenge_key.h"
+
+#include <mutex>
+#include <string>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/memory/ptr_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/values.h"
+#include "chrome/browser/chromeos/login/users/fake_chrome_user_manager.h"
+#include "chrome/browser/chromeos/profiles/profile_helper.h"
+#include "chrome/browser/chromeos/settings/cros_settings.h"
+#include "chrome/browser/chromeos/settings/scoped_cros_settings_test_helper.h"
+#include "chrome/browser/extensions/extension_function_test_utils.h"
+#include "chrome/browser/signin/identity_manager_factory.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/common/chrome_constants.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/test/base/browser_with_test_window_test.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "chrome/test/base/testing_profile_manager.h"
+#include "chromeos/attestation/mock_attestation_flow.h"
+#include "chromeos/cryptohome/async_method_caller.h"
+#include "chromeos/cryptohome/cryptohome_parameters.h"
+#include "chromeos/cryptohome/mock_async_method_caller.h"
+#include "chromeos/dbus/constants/attestation_constants.h"
+#include "chromeos/dbus/cryptohome/fake_cryptohome_client.h"
+#include "chromeos/tpm/stub_install_attributes.h"
+#include "components/account_id/account_id.h"
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#include "components/prefs/pref_service.h"
+#include "components/signin/public/identity_manager/identity_manager.h"
+#include "components/signin/public/identity_manager/identity_test_utils.h"
+#include "components/sync_preferences/testing_pref_service_syncable.h"
+#include "components/user_manager/scoped_user_manager.h"
+#include "extensions/common/extension_builder.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/cros_system_api/dbus/service_constants.h"
+
+using testing::_;
+using testing::Invoke;
+using testing::NiceMock;
+using testing::Return;
+using testing::WithArgs;
+
+namespace utils = extension_function_test_utils;
+
+namespace chromeos {
+namespace attestation {
+
+namespace {
+
+// Certificate errors as reported to the calling extension.
+const int kDBusError = 1;
+const int kUserRejected = 2;
+const int kGetCertificateFailed = 3;
+const int kResetRequired = 4;
+const int kPrepareKeyAttestationUnsupported = 5;
+
+const char kUserEmail[] = "test@google.com";
+const char kChallenge[] = "challenge";
+const char kResponse[] = "response";
+const char kKeyNameForSpkac[] = "attest-ent-machine-123456";
+
+void RegisterKeyCallbackTrue(
+    chromeos::attestation::AttestationKeyType key_type,
+    const cryptohome::Identification& user_id,
+    const std::string& key_name,
+    const cryptohome::AsyncMethodCaller::Callback& callback) {
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, base::BindOnce(callback, true, cryptohome::MOUNT_ERROR_NONE));
+}
+
+void RegisterKeyCallbackFalse(
+    chromeos::attestation::AttestationKeyType key_type,
+    const cryptohome::Identification& user_id,
+    const std::string& key_name,
+    const cryptohome::AsyncMethodCaller::Callback& callback) {
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, base::BindOnce(callback, false, cryptohome::MOUNT_ERROR_NONE));
+}
+
+void SignChallengeCallbackTrue(
+    chromeos::attestation::AttestationKeyType key_type,
+    const cryptohome::Identification& user_id,
+    const std::string& key_name,
+    const std::string& domain,
+    const std::string& device_id,
+    chromeos::attestation::AttestationChallengeOptions options,
+    const std::string& challenge,
+    const std::string& key_name_for_spkac,
+    const cryptohome::AsyncMethodCaller::DataCallback& callback) {
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, base::BindOnce(callback, true, "response"));
+}
+
+void SignChallengeCallbackFalse(
+    chromeos::attestation::AttestationKeyType key_type,
+    const cryptohome::Identification& user_id,
+    const std::string& key_name,
+    const std::string& domain,
+    const std::string& device_id,
+    chromeos::attestation::AttestationChallengeOptions options,
+    const std::string& challenge,
+    const std::string& key_name_for_spkac,
+    const cryptohome::AsyncMethodCaller::DataCallback& callback) {
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, base::BindOnce(callback, false, ""));
+}
+
+void GetCertificateCallbackTrue(
+    chromeos::attestation::AttestationCertificateProfile certificate_profile,
+    const AccountId& account_id,
+    const std::string& request_origin,
+    bool force_new_key,
+    const std::string& key_name,
+    const chromeos::attestation::AttestationFlow::CertificateCallback&
+        callback) {
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE,
+      base::BindRepeating(callback, chromeos::attestation::ATTESTATION_SUCCESS,
+                          "certificate"));
+}
+
+void GetCertificateCallbackUnspecifiedFailure(
+    chromeos::attestation::AttestationCertificateProfile certificate_profile,
+    const AccountId& account_id,
+    const std::string& request_origin,
+    bool force_new_key,
+    const std::string& key_name,
+    const chromeos::attestation::AttestationFlow::CertificateCallback&
+        callback) {
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE,
+      base::BindRepeating(
+          callback, chromeos::attestation::ATTESTATION_UNSPECIFIED_FAILURE,
+          ""));
+}
+
+void GetCertificateCallbackBadRequestFailure(
+    chromeos::attestation::AttestationCertificateProfile certificate_profile,
+    const AccountId& account_id,
+    const std::string& request_origin,
+    bool force_new_key,
+    const std::string& key_name,
+    const chromeos::attestation::AttestationFlow::CertificateCallback&
+        callback) {
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE,
+      base::BindRepeating(
+          callback,
+          chromeos::attestation::ATTESTATION_SERVER_BAD_REQUEST_FAILURE, ""));
+}
+
+class TpmChallengeKeyTestBase : public BrowserWithTestWindowTest {
+ public:
+  enum class ProfileType { kUserProfile, kSigninProfile };
+
+ protected:
+  TpmChallengeKeyTestBase(ProfileType profile_type,
+                          chromeos::attestation::AttestationKeyType key_type)
+      : profile_type_(profile_type),
+        fake_user_manager_(new chromeos::FakeChromeUserManager()),
+        user_manager_enabler_(base::WrapUnique(fake_user_manager_)),
+        key_type_(key_type) {
+    mock_async_method_caller_ =
+        new NiceMock<cryptohome::MockAsyncMethodCaller>();
+    // Ownership of mock_async_method_caller_ is transferred to
+    // AsyncMethodCaller::InitializeForTesting.
+    cryptohome::AsyncMethodCaller::InitializeForTesting(
+        mock_async_method_caller_);
+
+    challenge_key_impl_ =
+        std::make_unique<TpmChallengeKeyImpl>(&mock_attestation_flow_);
+
+    // Set up the default behavior of mocks.
+    ON_CALL(*mock_async_method_caller_, TpmAttestationRegisterKey)
+        .WillByDefault(Invoke(RegisterKeyCallbackTrue));
+    ON_CALL(*mock_async_method_caller_, TpmAttestationSignEnterpriseChallenge)
+        .WillByDefault(Invoke(SignChallengeCallbackTrue));
+    ON_CALL(mock_attestation_flow_, GetCertificate)
+        .WillByDefault(Invoke(GetCertificateCallbackTrue));
+
+    GetInstallAttributes()->SetCloudManaged("google.com", "device_id");
+
+    GetCrosSettingsHelper()->ReplaceDeviceSettingsProviderWithStub();
+    GetCrosSettingsHelper()->SetBoolean(chromeos::kDeviceAttestationEnabled,
+                                        true);
+  }
+
+  ~TpmChallengeKeyTestBase() { cryptohome::AsyncMethodCaller::Shutdown(); }
+
+  void SetUp() override {
+    BrowserWithTestWindowTest::SetUp();
+    if (profile_type_ == ProfileType::kUserProfile) {
+      prefs_ = GetProfile()->GetPrefs();
+      SetAuthenticatedUser();
+    }
+  }
+
+  void TearDown() override { BrowserWithTestWindowTest::TearDown(); }
+
+  // This will be called by BrowserWithTestWindowTest::SetUp();
+  TestingProfile* CreateProfile() override {
+    switch (profile_type_) {
+      case ProfileType::kUserProfile:
+        fake_user_manager_->AddUserWithAffiliation(
+            AccountId::FromUserEmail(kUserEmail), true);
+        return profile_manager()->CreateTestingProfile(kUserEmail);
+
+      case ProfileType::kSigninProfile:
+        return profile_manager()->CreateTestingProfile(chrome::kInitialProfile);
+    }
+    NOTREACHED() << "Invalid profile type: " << static_cast<int>(profile_type_);
+  }
+
+  // Derived classes can override this method to set the required authenticated
+  // user in the IdentityManager class.
+  virtual void SetAuthenticatedUser() {
+    auto* identity_manager =
+        IdentityManagerFactory::GetForProfile(GetProfile());
+    signin::MakePrimaryAccountAvailable(identity_manager, kUserEmail);
+  }
+
+  // Returns an error string for the given code.
+  static std::string GetCertificateError(int error_code) {
+    return base::StringPrintf(TpmChallengeKeyImpl::kGetCertificateFailedError,
+                              error_code);
+  }
+
+  void RunFunc(const std::string& challenge,
+               bool register_key,
+               const std::string& key_name_for_spkac,
+               TpmChallengeKeyResult* res) {
+    auto callback = [](base::OnceClosure done_closure,
+                       TpmChallengeKeyResult* res,
+                       const TpmChallengeKeyResult& tpm_result) {
+      *res = tpm_result;
+      std::move(done_closure).Run();
+    };
+
+    base::RunLoop loop;
+    challenge_key_impl_->Run(key_type_, GetProfile(),
+                             base::Bind(callback, loop.QuitClosure(), res),
+                             challenge, register_key, key_name_for_spkac);
+    loop.Run();
+  }
+
+  chromeos::FakeCryptohomeClient cryptohome_client_;
+  cryptohome::MockAsyncMethodCaller* mock_async_method_caller_ = nullptr;
+  NiceMock<chromeos::attestation::MockAttestationFlow> mock_attestation_flow_;
+  ProfileType profile_type_;
+  // fake_user_manager_ is owned by user_manager_enabler_.
+  chromeos::FakeChromeUserManager* fake_user_manager_;
+  user_manager::ScopedUserManager user_manager_enabler_;
+  PrefService* prefs_ = nullptr;
+  std::unique_ptr<TpmChallengeKey> challenge_key_impl_;
+  chromeos::attestation::AttestationKeyType key_type_;
+};
+
+class TpmChallengeMachineKeyTest : public TpmChallengeKeyTestBase {
+ protected:
+  explicit TpmChallengeMachineKeyTest(
+      ProfileType profile_type = ProfileType::kUserProfile)
+      : TpmChallengeKeyTestBase(profile_type,
+                                chromeos::attestation::KEY_DEVICE) {}
+};
+
+TEST_F(TpmChallengeMachineKeyTest, NonEnterpriseDevice) {
+  GetInstallAttributes()->SetConsumerOwned();
+
+  TpmChallengeKeyResult res;
+  RunFunc(kChallenge, /*register_key=*/false, "", &res);
+
+  EXPECT_FALSE(res.is_success);
+  EXPECT_EQ("", res.data);
+  EXPECT_EQ(TpmChallengeKeyImpl::kNonEnterpriseDeviceError, res.error_message);
+}
+
+TEST_F(TpmChallengeMachineKeyTest, DevicePolicyDisabled) {
+  GetCrosSettingsHelper()->SetBoolean(chromeos::kDeviceAttestationEnabled,
+                                      false);
+
+  TpmChallengeKeyResult res;
+  RunFunc(kChallenge, /*register_key=*/false, "", &res);
+
+  EXPECT_FALSE(res.is_success);
+  EXPECT_EQ("", res.data);
+  EXPECT_EQ(TpmChallengeKeyImpl::kDevicePolicyDisabledError, res.error_message);
+}
+
+TEST_F(TpmChallengeMachineKeyTest, DoesKeyExistDbusFailed) {
+  cryptohome_client_.set_tpm_attestation_does_key_exist_should_succeed(false);
+
+  TpmChallengeKeyResult res;
+  RunFunc(kChallenge, /*register_key=*/false, "", &res);
+
+  EXPECT_FALSE(res.is_success);
+  EXPECT_EQ("", res.data);
+  EXPECT_EQ(GetCertificateError(kDBusError), res.error_message);
+}
+
+TEST_F(TpmChallengeMachineKeyTest, GetCertificateFailed) {
+  EXPECT_CALL(mock_attestation_flow_, GetCertificate)
+      .WillRepeatedly(Invoke(GetCertificateCallbackUnspecifiedFailure));
+
+  TpmChallengeKeyResult res;
+  RunFunc(kChallenge, /*register_key=*/false, "", &res);
+
+  EXPECT_FALSE(res.is_success);
+  EXPECT_EQ("", res.data);
+  EXPECT_EQ(GetCertificateError(kGetCertificateFailed), res.error_message);
+}
+
+TEST_F(TpmChallengeMachineKeyTest, SignChallengeFailed) {
+  EXPECT_CALL(*mock_async_method_caller_, TpmAttestationSignEnterpriseChallenge)
+      .WillRepeatedly(Invoke(SignChallengeCallbackFalse));
+
+  TpmChallengeKeyResult res;
+  RunFunc(kChallenge, /*register_key=*/false, "", &res);
+
+  EXPECT_FALSE(res.is_success);
+  EXPECT_EQ("", res.data);
+  EXPECT_EQ(TpmChallengeKeyImpl::kSignChallengeFailedError, res.error_message);
+}
+
+TEST_F(TpmChallengeMachineKeyTest, KeyExists) {
+  cryptohome_client_.SetTpmAttestationDeviceCertificate("attest-ent-machine",
+                                                        std::string());
+  // GetCertificate must not be called if the key exists.
+  EXPECT_CALL(mock_attestation_flow_, GetCertificate).Times(0);
+
+  TpmChallengeKeyResult res;
+  RunFunc(kChallenge, /*register_key=*/false, "", &res);
+
+  EXPECT_TRUE(res.is_success);
+  EXPECT_EQ(kResponse, res.data);
+  EXPECT_EQ("", res.error_message);
+}
+
+TEST_F(TpmChallengeMachineKeyTest, AttestationNotPrepared) {
+  cryptohome_client_.set_tpm_attestation_is_prepared(false);
+
+  TpmChallengeKeyResult res;
+  RunFunc(kChallenge, /*register_key=*/false, "", &res);
+
+  EXPECT_FALSE(res.is_success);
+  EXPECT_EQ("", res.data);
+  EXPECT_EQ(GetCertificateError(kResetRequired), res.error_message);
+}
+
+// Test that we get proper error message in case we don't have TPM.
+TEST_F(TpmChallengeMachineKeyTest, AttestationUnsupported) {
+  cryptohome_client_.set_tpm_attestation_is_prepared(false);
+  cryptohome_client_.set_tpm_is_enabled(false);
+
+  TpmChallengeKeyResult res;
+  RunFunc(kChallenge, /*register_key=*/false, "", &res);
+
+  EXPECT_FALSE(res.is_success);
+  EXPECT_EQ("", res.data);
+  EXPECT_EQ(GetCertificateError(kPrepareKeyAttestationUnsupported),
+            res.error_message);
+}
+
+TEST_F(TpmChallengeMachineKeyTest, AttestationPreparedDbusFailed) {
+  cryptohome_client_.SetServiceIsAvailable(false);
+
+  TpmChallengeKeyResult res;
+  RunFunc(kChallenge, /*register_key=*/false, "", &res);
+
+  EXPECT_FALSE(res.is_success);
+  EXPECT_EQ("", res.data);
+  EXPECT_EQ(GetCertificateError(kDBusError), res.error_message);
+}
+
+TEST_F(TpmChallengeMachineKeyTest, KeyRegistrationFailed) {
+  EXPECT_CALL(*mock_async_method_caller_, TpmAttestationRegisterKey)
+      .WillRepeatedly(Invoke(RegisterKeyCallbackFalse));
+
+  TpmChallengeKeyResult res;
+  RunFunc(kChallenge, /*register_key=*/true, kKeyNameForSpkac, &res);
+
+  EXPECT_FALSE(res.is_success);
+  EXPECT_EQ("", res.data);
+  EXPECT_EQ(TpmChallengeKeyImpl::kKeyRegistrationFailedError,
+            res.error_message);
+}
+
+TEST_F(TpmChallengeMachineKeyTest, KeyNotRegisteredSuccess) {
+  EXPECT_CALL(*mock_async_method_caller_, TpmAttestationRegisterKey).Times(0);
+
+  TpmChallengeKeyResult res;
+  RunFunc(kChallenge, /*register_key=*/false, "", &res);
+
+  EXPECT_TRUE(res.is_success);
+  EXPECT_EQ(kResponse, res.data);
+  EXPECT_EQ("", res.error_message);
+}
+
+TEST_F(TpmChallengeMachineKeyTest, KeyRegisteredSuccess) {
+  // GetCertificate must be called exactly once.
+  EXPECT_CALL(mock_attestation_flow_,
+              GetCertificate(
+                  chromeos::attestation::PROFILE_ENTERPRISE_MACHINE_CERTIFICATE,
+                  _, _, _, _, _))
+      .Times(1);
+  // TpmAttestationRegisterKey must be called exactly once.
+  EXPECT_CALL(*mock_async_method_caller_,
+              TpmAttestationRegisterKey(chromeos::attestation::KEY_DEVICE,
+                                        _ /* Unused by the API. */,
+                                        kKeyNameForSpkac, _))
+      .Times(1);
+  // SignEnterpriseChallenge must be called exactly once.
+  EXPECT_CALL(
+      *mock_async_method_caller_,
+      TpmAttestationSignEnterpriseChallenge(
+          chromeos::attestation::KEY_DEVICE, _, "attest-ent-machine",
+          "google.com", "device_id", _, "challenge", kKeyNameForSpkac, _))
+      .Times(1);
+
+  TpmChallengeKeyResult res;
+  RunFunc(kChallenge, /*register_key=*/true, kKeyNameForSpkac, &res);
+
+  EXPECT_TRUE(res.is_success);
+  EXPECT_EQ(kResponse, res.data);
+  EXPECT_EQ("", res.error_message);
+}
+
+// Tests the API with all profiles types as determined by the test parameter.
+class TpmChallengeMachineKeyAllProfilesTest
+    : public TpmChallengeMachineKeyTest,
+      public ::testing::WithParamInterface<
+          TpmChallengeKeyTestBase::ProfileType> {
+ protected:
+  TpmChallengeMachineKeyAllProfilesTest()
+      : TpmChallengeMachineKeyTest(GetParam()) {}
+};
+
+TEST_P(TpmChallengeMachineKeyAllProfilesTest, Success) {
+  // GetCertificate must be called exactly once.
+  EXPECT_CALL(mock_attestation_flow_,
+              GetCertificate(
+                  chromeos::attestation::PROFILE_ENTERPRISE_MACHINE_CERTIFICATE,
+                  _, _, _, _, _))
+      .Times(1);
+  // SignEnterpriseChallenge must be called exactly once.
+  EXPECT_CALL(*mock_async_method_caller_,
+              TpmAttestationSignEnterpriseChallenge(
+                  chromeos::attestation::KEY_DEVICE, _, "attest-ent-machine",
+                  "google.com", "device_id", _, "challenge", _, _))
+      .Times(1);
+
+  TpmChallengeKeyResult res;
+  RunFunc(kChallenge, /*register_key=*/false, "", &res);
+
+  EXPECT_TRUE(res.is_success);
+  EXPECT_EQ(kResponse, res.data);
+  EXPECT_EQ("", res.error_message);
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    AllProfiles,
+    TpmChallengeMachineKeyAllProfilesTest,
+    ::testing::Values(TpmChallengeKeyTestBase::ProfileType::kUserProfile,
+                      TpmChallengeKeyTestBase::ProfileType::kSigninProfile));
+
+class TpmChallengeUserKeyTest : public TpmChallengeKeyTestBase {
+ protected:
+  explicit TpmChallengeUserKeyTest(
+      ProfileType profile_type = ProfileType::kUserProfile)
+      : TpmChallengeKeyTestBase(profile_type, chromeos::attestation::KEY_USER) {
+  }
+
+  void SetUp() override {
+    TpmChallengeKeyTestBase::SetUp();
+
+    if (profile_type_ == ProfileType::kUserProfile) {
+      GetProfile()->GetTestingPrefService()->SetManagedPref(
+          prefs::kAttestationEnabled, std::make_unique<base::Value>(true));
+    }
+  }
+};
+
+TEST_F(TpmChallengeUserKeyTest, UserPolicyDisabled) {
+  GetProfile()->GetTestingPrefService()->SetManagedPref(
+      prefs::kAttestationEnabled, std::make_unique<base::Value>(false));
+
+  TpmChallengeKeyResult res;
+  RunFunc(kChallenge, /*register_key=*/true, "", &res);
+
+  EXPECT_FALSE(res.is_success);
+  EXPECT_EQ("", res.data);
+  EXPECT_EQ(TpmChallengeKeyImpl::kUserPolicyDisabledError, res.error_message);
+}
+
+TEST_F(TpmChallengeUserKeyTest, DevicePolicyDisabled) {
+  GetCrosSettingsHelper()->SetBoolean(chromeos::kDeviceAttestationEnabled,
+                                      false);
+
+  TpmChallengeKeyResult res;
+  RunFunc(kChallenge, /*register_key=*/true, "", &res);
+
+  EXPECT_FALSE(res.is_success);
+  EXPECT_EQ("", res.data);
+  EXPECT_EQ(TpmChallengeKeyImpl::kDevicePolicyDisabledError, res.error_message);
+}
+
+TEST_F(TpmChallengeUserKeyTest, DoesKeyExistDbusFailed) {
+  cryptohome_client_.set_tpm_attestation_does_key_exist_should_succeed(false);
+
+  TpmChallengeKeyResult res;
+  RunFunc(kChallenge, /*register_key=*/true, "", &res);
+
+  EXPECT_FALSE(res.is_success);
+  EXPECT_EQ("", res.data);
+  EXPECT_EQ(GetCertificateError(kDBusError), res.error_message);
+}
+
+TEST_F(TpmChallengeUserKeyTest, GetCertificateFailedWithUnspecifiedFailure) {
+  EXPECT_CALL(mock_attestation_flow_, GetCertificate)
+      .WillRepeatedly(Invoke(GetCertificateCallbackUnspecifiedFailure));
+
+  TpmChallengeKeyResult res;
+  RunFunc(kChallenge, /*register_key=*/true, "", &res);
+
+  EXPECT_FALSE(res.is_success);
+  EXPECT_EQ("", res.data);
+  EXPECT_EQ(GetCertificateError(kGetCertificateFailed), res.error_message);
+}
+
+TEST_F(TpmChallengeUserKeyTest, GetCertificateFailedWithBadRequestFailure) {
+  EXPECT_CALL(mock_attestation_flow_, GetCertificate)
+      .WillRepeatedly(Invoke(GetCertificateCallbackBadRequestFailure));
+
+  TpmChallengeKeyResult res;
+  RunFunc(kChallenge, /*register_key=*/true, "", &res);
+
+  EXPECT_FALSE(res.is_success);
+  EXPECT_EQ("", res.data);
+  EXPECT_EQ(GetCertificateError(kGetCertificateFailed), res.error_message);
+}
+
+TEST_F(TpmChallengeUserKeyTest, SignChallengeFailed) {
+  EXPECT_CALL(*mock_async_method_caller_, TpmAttestationSignEnterpriseChallenge)
+      .WillRepeatedly(Invoke(SignChallengeCallbackFalse));
+
+  TpmChallengeKeyResult res;
+  RunFunc(kChallenge, /*register_key=*/true, "", &res);
+
+  EXPECT_FALSE(res.is_success);
+  EXPECT_EQ("", res.data);
+  EXPECT_EQ(TpmChallengeKeyImpl::kSignChallengeFailedError, res.error_message);
+}
+
+TEST_F(TpmChallengeUserKeyTest, KeyRegistrationFailed) {
+  EXPECT_CALL(*mock_async_method_caller_, TpmAttestationRegisterKey)
+      .WillRepeatedly(Invoke(RegisterKeyCallbackFalse));
+
+  TpmChallengeKeyResult res;
+  RunFunc(kChallenge, /*register_key=*/true, "", &res);
+
+  EXPECT_FALSE(res.is_success);
+  EXPECT_EQ("", res.data);
+  EXPECT_EQ(TpmChallengeKeyImpl::kKeyRegistrationFailedError,
+            res.error_message);
+}
+
+TEST_F(TpmChallengeUserKeyTest, KeyExists) {
+  cryptohome_client_.SetTpmAttestationUserCertificate(
+      cryptohome::CreateAccountIdentifierFromAccountId(
+          AccountId::FromUserEmail(kUserEmail)),
+      "attest-ent-user", std::string());
+  // GetCertificate must not be called if the key exists.
+  EXPECT_CALL(mock_attestation_flow_, GetCertificate).Times(0);
+
+  TpmChallengeKeyResult res;
+  RunFunc(kChallenge, /*register_key=*/true, "", &res);
+
+  EXPECT_TRUE(res.is_success);
+  EXPECT_EQ(kResponse, res.data);
+  EXPECT_EQ("", res.error_message);
+}
+
+TEST_F(TpmChallengeUserKeyTest, KeyNotRegisteredSuccess) {
+  EXPECT_CALL(*mock_async_method_caller_, TpmAttestationRegisterKey).Times(0);
+
+  TpmChallengeKeyResult res;
+  RunFunc(kChallenge, /*register_key=*/false, "", &res);
+
+  EXPECT_TRUE(res.is_success);
+  EXPECT_EQ(kResponse, res.data);
+  EXPECT_EQ("", res.error_message);
+}
+
+TEST_F(TpmChallengeUserKeyTest, PersonalDevice) {
+  GetInstallAttributes()->SetConsumerOwned();
+
+  TpmChallengeKeyResult res;
+  RunFunc(kChallenge, /*register_key=*/true, "", &res);
+
+  // Currently personal devices are not supported.
+  EXPECT_FALSE(res.is_success);
+  EXPECT_EQ("", res.data);
+  EXPECT_EQ(GetCertificateError(kUserRejected), res.error_message);
+}
+
+TEST_F(TpmChallengeUserKeyTest, Success) {
+  // GetCertificate must be called exactly once.
+  EXPECT_CALL(
+      mock_attestation_flow_,
+      GetCertificate(chromeos::attestation::PROFILE_ENTERPRISE_USER_CERTIFICATE,
+                     _, _, _, _, _))
+      .Times(1);
+  const AccountId account_id = AccountId::FromUserEmail(kUserEmail);
+  // SignEnterpriseChallenge must be called exactly once.
+  EXPECT_CALL(*mock_async_method_caller_,
+              TpmAttestationSignEnterpriseChallenge(
+                  chromeos::attestation::KEY_USER,
+                  cryptohome::Identification(account_id), "attest-ent-user",
+                  cryptohome::Identification(account_id).id(), "device_id", _,
+                  "challenge", _, _))
+      .Times(1);
+  // RegisterKey must be called exactly once.
+  EXPECT_CALL(*mock_async_method_caller_,
+              TpmAttestationRegisterKey(chromeos::attestation::KEY_USER,
+                                        cryptohome::Identification(account_id),
+                                        "attest-ent-user", _))
+      .Times(1);
+
+  TpmChallengeKeyResult res;
+  RunFunc(kChallenge, /*register_key=*/true, "", &res);
+
+  EXPECT_TRUE(res.is_success);
+  EXPECT_EQ(kResponse, res.data);
+  EXPECT_EQ("", res.error_message);
+}
+
+TEST_F(TpmChallengeUserKeyTest, AttestationNotPrepared) {
+  cryptohome_client_.set_tpm_attestation_is_prepared(false);
+
+  TpmChallengeKeyResult res;
+  RunFunc(kChallenge, /*register_key=*/true, "", &res);
+
+  EXPECT_FALSE(res.is_success);
+  EXPECT_EQ("", res.data);
+  EXPECT_EQ(GetCertificateError(kResetRequired), res.error_message);
+}
+
+TEST_F(TpmChallengeUserKeyTest, AttestationPreparedDbusFailed) {
+  cryptohome_client_.SetServiceIsAvailable(false);
+
+  TpmChallengeKeyResult res;
+  RunFunc(kChallenge, /*register_key=*/true, "", &res);
+
+  EXPECT_FALSE(res.is_success);
+  EXPECT_EQ("", res.data);
+  EXPECT_EQ(GetCertificateError(kDBusError), res.error_message);
+}
+
+class TpmChallengeUserKeySigninProfileTest : public TpmChallengeUserKeyTest {
+ protected:
+  TpmChallengeUserKeySigninProfileTest()
+      : TpmChallengeUserKeyTest(ProfileType::kSigninProfile) {}
+};
+
+TEST_F(TpmChallengeUserKeySigninProfileTest, UserKeyNotAvailable) {
+  GetCrosSettingsHelper()->SetBoolean(chromeos::kDeviceAttestationEnabled,
+                                      false);
+
+  TpmChallengeKeyResult res;
+  RunFunc(kChallenge, /*register_key=*/true, "", &res);
+
+  EXPECT_FALSE(res.is_success);
+  EXPECT_EQ("", res.data);
+  EXPECT_EQ(TpmChallengeKeyImpl::kUserKeyNotAvailable, res.error_message);
+}
+
+class TpmChallengeMachineKeyUnmanagedUserTest
+    : public TpmChallengeMachineKeyTest {
+ protected:
+  void SetAuthenticatedUser() override {
+    signin::MakePrimaryAccountAvailable(
+        IdentityManagerFactory::GetForProfile(GetProfile()),
+        account_id_.GetUserEmail());
+  }
+
+  TestingProfile* CreateProfile() override {
+    fake_user_manager_->AddUser(account_id_);
+    return profile_manager()->CreateTestingProfile(account_id_.GetUserEmail());
+  }
+
+  const std::string email = "test@chromium.com";
+  const AccountId account_id_ =
+      AccountId::FromUserEmailGaiaId(email,
+                                     signin::GetTestGaiaIdForEmail(email));
+};
+
+TEST_F(TpmChallengeMachineKeyUnmanagedUserTest, UserNotManaged) {
+  TpmChallengeKeyResult res;
+  RunFunc(kChallenge, /*register_key=*/false, "", &res);
+
+  EXPECT_FALSE(res.is_success);
+  EXPECT_EQ("", res.data);
+  EXPECT_EQ(TpmChallengeKeyImpl::kUserNotManaged, res.error_message);
+}
+
+class TpmChallengeUserKeyUnmanagedUserTest : public TpmChallengeUserKeyTest {
+ protected:
+  void SetAuthenticatedUser() override {
+    signin::MakePrimaryAccountAvailable(
+        IdentityManagerFactory::GetForProfile(GetProfile()),
+        account_id_.GetUserEmail());
+  }
+
+  TestingProfile* CreateProfile() override {
+    fake_user_manager_->AddUser(account_id_);
+    return profile_manager()->CreateTestingProfile(account_id_.GetUserEmail());
+  }
+
+  const std::string email = "test@chromium.com";
+  const AccountId account_id_ =
+      AccountId::FromUserEmailGaiaId(email,
+                                     signin::GetTestGaiaIdForEmail(email));
+};
+
+TEST_F(TpmChallengeUserKeyUnmanagedUserTest, UserNotManaged) {
+  TpmChallengeKeyResult res;
+  RunFunc(kChallenge, /*register_key=*/true, "", &res);
+
+  EXPECT_FALSE(res.is_success);
+  EXPECT_EQ("", res.data);
+  EXPECT_EQ(TpmChallengeKeyImpl::kUserNotManaged, res.error_message);
+}
+
+}  // namespace
+}  // namespace attestation
+}  // namespace chromeos
diff --git a/chrome/browser/extensions/api/certificate_provider/certificate_provider_api.cc b/chrome/browser/extensions/api/certificate_provider/certificate_provider_api.cc
index 533bdee..cdc5786 100644
--- a/chrome/browser/extensions/api/certificate_provider/certificate_provider_api.cc
+++ b/chrome/browser/extensions/api/certificate_provider/certificate_provider_api.cc
@@ -77,7 +77,8 @@
 
 }  // namespace
 
-const int api::certificate_provider::kMaxClosedDialogsPer10Mins = 2;
+const int api::certificate_provider::kMaxClosedDialogsPerMinute = 10;
+const int api::certificate_provider::kMaxClosedDialogsPer10Minutes = 30;
 
 CertificateProviderInternalReportCertificatesFunction::
     ~CertificateProviderInternalReportCertificatesFunction() {}
@@ -258,10 +259,17 @@
 void CertificateProviderRequestPinFunction::GetQuotaLimitHeuristics(
     extensions::QuotaLimitHeuristics* heuristics) const {
   QuotaLimitHeuristic::Config short_limit_config = {
-      api::certificate_provider::kMaxClosedDialogsPer10Mins,
-      base::TimeDelta::FromMinutes(10)};
+      api::certificate_provider::kMaxClosedDialogsPerMinute,
+      base::TimeDelta::FromMinutes(1)};
   heuristics->push_back(std::make_unique<QuotaService::TimedLimit>(
       short_limit_config, new QuotaLimitHeuristic::SingletonBucketMapper(),
+      "MAX_PIN_DIALOGS_CLOSED_PER_MINUTE"));
+
+  QuotaLimitHeuristic::Config long_limit_config = {
+      api::certificate_provider::kMaxClosedDialogsPer10Minutes,
+      base::TimeDelta::FromMinutes(10)};
+  heuristics->push_back(std::make_unique<QuotaService::TimedLimit>(
+      long_limit_config, new QuotaLimitHeuristic::SingletonBucketMapper(),
       "MAX_PIN_DIALOGS_CLOSED_PER_10_MINUTES"));
 }
 
diff --git a/chrome/browser/extensions/api/certificate_provider/certificate_provider_api.h b/chrome/browser/extensions/api/certificate_provider/certificate_provider_api.h
index b61e947..623b666 100644
--- a/chrome/browser/extensions/api/certificate_provider/certificate_provider_api.h
+++ b/chrome/browser/extensions/api/certificate_provider/certificate_provider_api.h
@@ -19,9 +19,10 @@
 
 namespace api {
 namespace certificate_provider {
-// The maximum number of times per 10 minutes, extension is allowed to show PIN
-// dialog again after user closed the previous one.
-extern const int kMaxClosedDialogsPer10Mins;
+// The maximum number of times in the given interval the extension is allowed to
+// show the PIN dialog again after user closed the previous one.
+extern const int kMaxClosedDialogsPerMinute;
+extern const int kMaxClosedDialogsPer10Minutes;
 
 struct CertificateInfo;
 }
diff --git a/chrome/browser/extensions/api/certificate_provider/certificate_provider_apitest.cc b/chrome/browser/extensions/api/certificate_provider/certificate_provider_apitest.cc
index 1611eb4a..6a95de1 100644
--- a/chrome/browser/extensions/api/certificate_provider/certificate_provider_apitest.cc
+++ b/chrome/browser/extensions/api/certificate_provider/certificate_provider_apitest.cc
@@ -382,14 +382,14 @@
   EXPECT_FALSE(GetActivePinDialogView());
 }
 
-// User closes the dialog kMaxClosedDialogsPer10Mins times, and the extension
+// User closes the dialog kMaxClosedDialogsPerMinute times, and the extension
 // should be blocked from showing it again.
 IN_PROC_BROWSER_TEST_F(CertificateProviderRequestPinTest, ShowPinDialogClose) {
   AddFakeSignRequest();
   NavigateTo("basic.html");
 
   for (int i = 0;
-       i < extensions::api::certificate_provider::kMaxClosedDialogsPer10Mins;
+       i < extensions::api::certificate_provider::kMaxClosedDialogsPerMinute;
        i++) {
     ExtensionTestMessageListener listener("User closed the dialog", false);
     GetActivePinDialogWindow()->Close();
@@ -401,7 +401,7 @@
   ASSERT_TRUE(close_listener.WaitUntilSatisfied());
   close_listener.Reply("GetLastError");
   ExtensionTestMessageListener last_error_listener(
-      "This request exceeds the MAX_PIN_DIALOGS_CLOSED_PER_10_MINUTES quota.",
+      "This request exceeds the MAX_PIN_DIALOGS_CLOSED_PER_MINUTE quota.",
       false);
   ASSERT_TRUE(last_error_listener.WaitUntilSatisfied());
   EXPECT_FALSE(GetActivePinDialogView());
@@ -462,7 +462,7 @@
   EXPECT_FALSE(GetActivePinDialogView());
 }
 
-// Extension closes the dialog kMaxClosedDialogsPer10Mins times after the user
+// Extension closes the dialog kMaxClosedDialogsPerMinute times after the user
 // inputs some value, and it should be blocked from showing it again.
 IN_PROC_BROWSER_TEST_F(CertificateProviderRequestPinTest,
                        RepeatedProgrammaticCloseAfterInput) {
@@ -470,7 +470,7 @@
 
   for (int i = 0;
        i <
-       extensions::api::certificate_provider::kMaxClosedDialogsPer10Mins + 1;
+       extensions::api::certificate_provider::kMaxClosedDialogsPerMinute + 1;
        i++) {
     AddFakeSignRequest();
     EXPECT_TRUE(SendCommandAndWaitForMessage(
@@ -487,8 +487,8 @@
       "Request",
       base::StringPrintf(
           "request%d:error:This request exceeds the "
-          "MAX_PIN_DIALOGS_CLOSED_PER_10_MINUTES quota.",
-          extensions::api::certificate_provider::kMaxClosedDialogsPer10Mins +
+          "MAX_PIN_DIALOGS_CLOSED_PER_MINUTE quota.",
+          extensions::api::certificate_provider::kMaxClosedDialogsPerMinute +
               2)));
   EXPECT_FALSE(GetActivePinDialogView());
 }
@@ -505,7 +505,7 @@
   EXPECT_FALSE(GetActivePinDialogView());
 }
 
-// Extension closes the dialog kMaxClosedDialogsPer10Mins times before the user
+// Extension closes the dialog kMaxClosedDialogsPerMinute times before the user
 // inputs anything, and it should be blocked from showing it again.
 IN_PROC_BROWSER_TEST_F(CertificateProviderRequestPinTest,
                        RepeatedProgrammaticCloseBeforeInput) {
@@ -513,7 +513,7 @@
 
   for (int i = 0;
        i <
-       extensions::api::certificate_provider::kMaxClosedDialogsPer10Mins + 1;
+       extensions::api::certificate_provider::kMaxClosedDialogsPerMinute + 1;
        i++) {
     AddFakeSignRequest();
     EXPECT_TRUE(SendCommand("Request"));
@@ -527,8 +527,8 @@
       "Request",
       base::StringPrintf(
           "request%d:error:This request exceeds the "
-          "MAX_PIN_DIALOGS_CLOSED_PER_10_MINUTES quota.",
-          extensions::api::certificate_provider::kMaxClosedDialogsPer10Mins +
+          "MAX_PIN_DIALOGS_CLOSED_PER_MINUTE quota.",
+          extensions::api::certificate_provider::kMaxClosedDialogsPerMinute +
               2)));
   EXPECT_FALSE(GetActivePinDialogView());
 }
diff --git a/chrome/browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_api.cc b/chrome/browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_api.cc
index 2932c70..dfcc812 100644
--- a/chrome/browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_api.cc
+++ b/chrome/browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_api.cc
@@ -45,8 +45,7 @@
 }  // namespace
 
 EnterprisePlatformKeysInternalGenerateKeyFunction::
-    ~EnterprisePlatformKeysInternalGenerateKeyFunction() {
-}
+    ~EnterprisePlatformKeysInternalGenerateKeyFunction() = default;
 
 ExtensionFunction::ResponseAction
 EnterprisePlatformKeysInternalGenerateKeyFunction::Run() {
@@ -64,9 +63,7 @@
   DCHECK(service);
 
   service->GenerateRSAKey(
-      platform_keys_token_id,
-      params->modulus_length,
-      extension_id(),
+      platform_keys_token_id, params->modulus_length, extension_id(),
       base::Bind(
           &EnterprisePlatformKeysInternalGenerateKeyFunction::OnGeneratedKey,
           this));
@@ -86,8 +83,7 @@
 }
 
 EnterprisePlatformKeysGetCertificatesFunction::
-    ~EnterprisePlatformKeysGetCertificatesFunction() {
-}
+    ~EnterprisePlatformKeysGetCertificatesFunction() {}
 
 ExtensionFunction::ResponseAction
 EnterprisePlatformKeysGetCertificatesFunction::Run() {
@@ -118,8 +114,7 @@
 
   std::unique_ptr<base::ListValue> client_certs(new base::ListValue());
   for (net::CertificateList::const_iterator it = certs->begin();
-       it != certs->end();
-       ++it) {
+       it != certs->end(); ++it) {
     base::StringPiece cert_der =
         net::x509_util::CryptoBufferAsStringPiece((*it)->cert_buffer());
     client_certs->Append(std::make_unique<base::Value>(
@@ -132,8 +127,7 @@
 }
 
 EnterprisePlatformKeysImportCertificateFunction::
-    ~EnterprisePlatformKeysImportCertificateFunction() {
-}
+    ~EnterprisePlatformKeysImportCertificateFunction() {}
 
 ExtensionFunction::ResponseAction
 EnterprisePlatformKeysImportCertificateFunction::Run() {
@@ -157,8 +151,7 @@
     return RespondNow(Error(kEnterprisePlatformErrorInvalidX509Cert));
 
   chromeos::platform_keys::ImportCertificate(
-      platform_keys_token_id,
-      cert_x509,
+      platform_keys_token_id, cert_x509,
       base::Bind(&EnterprisePlatformKeysImportCertificateFunction::
                      OnImportedCertificate,
                  this),
@@ -176,8 +169,7 @@
 }
 
 EnterprisePlatformKeysRemoveCertificateFunction::
-    ~EnterprisePlatformKeysRemoveCertificateFunction() {
-}
+    ~EnterprisePlatformKeysRemoveCertificateFunction() {}
 
 ExtensionFunction::ResponseAction
 EnterprisePlatformKeysRemoveCertificateFunction::Run() {
@@ -201,8 +193,7 @@
     return RespondNow(Error(kEnterprisePlatformErrorInvalidX509Cert));
 
   chromeos::platform_keys::RemoveCertificate(
-      platform_keys_token_id,
-      cert_x509,
+      platform_keys_token_id, cert_x509,
       base::Bind(&EnterprisePlatformKeysRemoveCertificateFunction::
                      OnRemovedCertificate,
                  this),
@@ -220,8 +211,7 @@
 }
 
 EnterprisePlatformKeysInternalGetTokensFunction::
-    ~EnterprisePlatformKeysInternalGetTokensFunction() {
-}
+    ~EnterprisePlatformKeysInternalGetTokensFunction() {}
 
 ExtensionFunction::ResponseAction
 EnterprisePlatformKeysInternalGetTokensFunction::Run() {
@@ -246,8 +236,7 @@
   std::vector<std::string> token_ids;
   for (std::vector<std::string>::const_iterator it =
            platform_keys_token_ids->begin();
-       it != platform_keys_token_ids->end();
-       ++it) {
+       it != platform_keys_token_ids->end(); ++it) {
     std::string token_id = platform_keys::PlatformKeysTokenIdToApiId(*it);
     if (token_id.empty()) {
       Respond(Error(kEnterprisePlatformErrorInternal));
@@ -260,13 +249,7 @@
 }
 
 EnterprisePlatformKeysChallengeMachineKeyFunction::
-    EnterprisePlatformKeysChallengeMachineKeyFunction()
-    : default_impl_(new EPKPChallengeMachineKey), impl_(default_impl_.get()) {}
-
-EnterprisePlatformKeysChallengeMachineKeyFunction::
-    EnterprisePlatformKeysChallengeMachineKeyFunction(
-        EPKPChallengeMachineKey* impl_for_testing)
-    : impl_(impl_for_testing) {}
+    EnterprisePlatformKeysChallengeMachineKeyFunction() = default;
 
 EnterprisePlatformKeysChallengeMachineKeyFunction::
     ~EnterprisePlatformKeysChallengeMachineKeyFunction() = default;
@@ -276,39 +259,32 @@
   std::unique_ptr<api_epk::ChallengeMachineKey::Params> params(
       api_epk::ChallengeMachineKey::Params::Create(*args_));
   EXTENSION_FUNCTION_VALIDATE(params);
-  ChallengeKeyCallback callback = base::Bind(
+  chromeos::attestation::TpmChallengeKeyCallback callback = base::BindOnce(
       &EnterprisePlatformKeysChallengeMachineKeyFunction::OnChallengedKey,
       this);
   // base::Unretained is safe on impl_ since its life-cycle matches |this| and
   // |callback| holds a reference to |this|.
-  base::Closure task =
-      base::Bind(&EPKPChallengeMachineKey::Run, base::Unretained(impl_),
-                 scoped_refptr<ExtensionFunction>(this), callback,
-                 StringFromVector(params->challenge),
-                 params->register_key ? *params->register_key : false);
-  base::PostTask(FROM_HERE, {content::BrowserThread::UI}, task);
+  base::OnceClosure task = base::BindOnce(
+      &EPKPChallengeKey::Run, base::Unretained(&impl_),
+      chromeos::attestation::KEY_DEVICE, scoped_refptr<ExtensionFunction>(this),
+      std::move(callback), StringFromVector(params->challenge),
+      params->register_key ? *params->register_key : false);
+  base::PostTask(FROM_HERE, {content::BrowserThread::UI}, std::move(task));
   return RespondLater();
 }
 
 void EnterprisePlatformKeysChallengeMachineKeyFunction::OnChallengedKey(
-    bool success,
-    const std::string& data) {
-  if (success) {
-    Respond(ArgumentList(
-        api_epk::ChallengeMachineKey::Results::Create(VectorFromString(data))));
+    const chromeos::attestation::TpmChallengeKeyResult& result) {
+  if (result.is_success) {
+    Respond(ArgumentList(api_epk::ChallengeMachineKey::Results::Create(
+        VectorFromString(result.data))));
   } else {
-    Respond(Error(data));
+    Respond(Error(result.error_message));
   }
 }
 
 EnterprisePlatformKeysChallengeUserKeyFunction::
-    EnterprisePlatformKeysChallengeUserKeyFunction()
-    : default_impl_(new EPKPChallengeUserKey), impl_(default_impl_.get()) {}
-
-EnterprisePlatformKeysChallengeUserKeyFunction::
-    EnterprisePlatformKeysChallengeUserKeyFunction(
-        EPKPChallengeUserKey* impl_for_testing)
-    : impl_(impl_for_testing) {}
+    EnterprisePlatformKeysChallengeUserKeyFunction() = default;
 
 EnterprisePlatformKeysChallengeUserKeyFunction::
     ~EnterprisePlatformKeysChallengeUserKeyFunction() = default;
@@ -318,26 +294,26 @@
   std::unique_ptr<api_epk::ChallengeUserKey::Params> params(
       api_epk::ChallengeUserKey::Params::Create(*args_));
   EXTENSION_FUNCTION_VALIDATE(params);
-  ChallengeKeyCallback callback = base::Bind(
+  chromeos::attestation::TpmChallengeKeyCallback callback = base::Bind(
       &EnterprisePlatformKeysChallengeUserKeyFunction::OnChallengedKey, this);
   // base::Unretained is safe on impl_ since its life-cycle matches |this| and
   // |callback| holds a reference to |this|.
-  base::Closure task =
-      base::Bind(&EPKPChallengeUserKey::Run, base::Unretained(impl_),
-                 scoped_refptr<ExtensionFunction>(this), callback,
-                 StringFromVector(params->challenge), params->register_key);
-  base::PostTask(FROM_HERE, {content::BrowserThread::UI}, task);
+  base::OnceClosure task = base::BindOnce(
+      &EPKPChallengeKey::Run, base::Unretained(&impl_),
+      chromeos::attestation::KEY_USER, scoped_refptr<ExtensionFunction>(this),
+      std::move(callback), StringFromVector(params->challenge),
+      params->register_key);
+  base::PostTask(FROM_HERE, {content::BrowserThread::UI}, std::move(task));
   return RespondLater();
 }
 
 void EnterprisePlatformKeysChallengeUserKeyFunction::OnChallengedKey(
-    bool success,
-    const std::string& data) {
-  if (success) {
-    Respond(ArgumentList(
-        api_epk::ChallengeUserKey::Results::Create(VectorFromString(data))));
+    const chromeos::attestation::TpmChallengeKeyResult& result) {
+  if (result.is_success) {
+    Respond(ArgumentList(api_epk::ChallengeUserKey::Results::Create(
+        VectorFromString(result.data))));
   } else {
-    Respond(Error(data));
+    Respond(Error(result.error_message));
   }
 }
 
diff --git a/chrome/browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_api.h b/chrome/browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_api.h
index 9ec384a2..598990b1 100644
--- a/chrome/browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_api.h
+++ b/chrome/browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_api.h
@@ -15,8 +15,8 @@
 
 namespace net {
 class X509Certificate;
-typedef std::vector<scoped_refptr<X509Certificate> > CertificateList;
-}
+using CertificateList = std::vector<scoped_refptr<X509Certificate>>;
+}  // namespace net
 
 namespace extensions {
 
@@ -96,20 +96,16 @@
     : public ExtensionFunction {
  public:
   EnterprisePlatformKeysChallengeMachineKeyFunction();
-  explicit EnterprisePlatformKeysChallengeMachineKeyFunction(
-      EPKPChallengeMachineKey* impl_for_testing);
 
  private:
   ~EnterprisePlatformKeysChallengeMachineKeyFunction() override;
   ResponseAction Run() override;
 
-  // Called when the challenge operation is complete. If the operation succeeded
-  // |success| will be true and |data| will contain the challenge response data.
-  // Otherwise |success| will be false and |data| is an error message.
-  void OnChallengedKey(bool success, const std::string& data);
+  // Called when the challenge operation is complete.
+  void OnChallengedKey(
+      const chromeos::attestation::TpmChallengeKeyResult& result);
 
-  std::unique_ptr<EPKPChallengeMachineKey> default_impl_;
-  EPKPChallengeMachineKey* impl_;
+  EPKPChallengeKey impl_;
 
   DECLARE_EXTENSION_FUNCTION("enterprise.platformKeys.challengeMachineKey",
                              ENTERPRISE_PLATFORMKEYS_CHALLENGEMACHINEKEY)
@@ -119,20 +115,16 @@
     : public ExtensionFunction {
  public:
   EnterprisePlatformKeysChallengeUserKeyFunction();
-  explicit EnterprisePlatformKeysChallengeUserKeyFunction(
-      EPKPChallengeUserKey* impl_for_testing);
 
  private:
   ~EnterprisePlatformKeysChallengeUserKeyFunction() override;
   ResponseAction Run() override;
 
-  // Called when the challenge operation is complete. If the operation succeeded
-  // |success| will be true and |data| will contain the challenge response data.
-  // Otherwise |success| will be false and |data| is an error message.
-  void OnChallengedKey(bool success, const std::string& data);
+  // Called when the challenge operation is complete.
+  void OnChallengedKey(
+      const chromeos::attestation::TpmChallengeKeyResult& result);
 
-  std::unique_ptr<EPKPChallengeUserKey> default_impl_;
-  EPKPChallengeUserKey* impl_;
+  EPKPChallengeKey impl_;
 
   DECLARE_EXTENSION_FUNCTION("enterprise.platformKeys.challengeUserKey",
                              ENTERPRISE_PLATFORMKEYS_CHALLENGEUSERKEY)
diff --git a/chrome/browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_api_unittest.cc b/chrome/browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_api_unittest.cc
index bffca7db..386feaea 100644
--- a/chrome/browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_api_unittest.cc
+++ b/chrome/browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_api_unittest.cc
@@ -4,170 +4,71 @@
 
 #include "chrome/browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_api.h"
 
-#include <string>
+#include <utility>
 
-#include "base/bind.h"
-#include "base/location.h"
-#include "base/memory/ptr_util.h"
-#include "base/strings/stringprintf.h"
-#include "base/threading/thread_task_runner_handle.h"
 #include "base/values.h"
+#include "chrome/browser/chromeos/attestation/mock_tpm_challenge_key.h"
 #include "chrome/browser/chromeos/login/users/fake_chrome_user_manager.h"
-#include "chrome/browser/chromeos/settings/cros_settings.h"
-#include "chrome/browser/chromeos/settings/scoped_cros_settings_test_helper.h"
 #include "chrome/browser/extensions/extension_function_test_utils.h"
 #include "chrome/browser/signin/identity_manager_factory.h"
-#include "chrome/browser/ui/browser.h"
 #include "chrome/common/pref_names.h"
 #include "chrome/test/base/browser_with_test_window_test.h"
-#include "chrome/test/base/testing_browser_process.h"
 #include "chrome/test/base/testing_profile_manager.h"
-#include "chromeos/attestation/mock_attestation_flow.h"
-#include "chromeos/cryptohome/async_method_caller.h"
-#include "chromeos/cryptohome/cryptohome_parameters.h"
-#include "chromeos/cryptohome/mock_async_method_caller.h"
-#include "chromeos/dbus/constants/attestation_constants.h"
-#include "chromeos/dbus/cryptohome/fake_cryptohome_client.h"
-#include "chromeos/dbus/dbus_method_call_status.h"
-#include "chromeos/tpm/stub_install_attributes.h"
-#include "components/account_id/account_id.h"
-#include "components/policy/core/common/cloud/cloud_policy_constants.h"
-#include "components/prefs/pref_service.h"
+#include "components/signin/public/identity_manager/identity_manager.h"
 #include "components/signin/public/identity_manager/identity_test_utils.h"
 #include "components/user_manager/scoped_user_manager.h"
 #include "extensions/common/extension_builder.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
-#include "third_party/cros_system_api/dbus/service_constants.h"
 
-using testing::_;
 using testing::Invoke;
 using testing::NiceMock;
-using testing::Return;
-using testing::WithArgs;
 
 namespace utils = extension_function_test_utils;
 
 namespace extensions {
 namespace {
 
-// Certificate errors as reported to the calling extension.
-const int kDBusError = 1;
-const int kUserRejected = 2;
-const int kGetCertificateFailed = 3;
-const int kResetRequired = 4;
-
 const char kUserEmail[] = "test@google.com";
 
-void RegisterKeyCallbackTrue(
+void FakeRunCheckNotRegister(
     chromeos::attestation::AttestationKeyType key_type,
-    const cryptohome::Identification& user_id,
-    const std::string& key_name,
-    const cryptohome::AsyncMethodCaller::Callback& callback) {
-  base::ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE, base::BindOnce(callback, true, cryptohome::MOUNT_ERROR_NONE));
-}
-
-void RegisterKeyCallbackFalse(
-    chromeos::attestation::AttestationKeyType key_type,
-    const cryptohome::Identification& user_id,
-    const std::string& key_name,
-    const cryptohome::AsyncMethodCaller::Callback& callback) {
-  base::ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE, base::BindOnce(callback, false, cryptohome::MOUNT_ERROR_NONE));
-}
-
-void SignChallengeCallbackTrue(
-    chromeos::attestation::AttestationKeyType key_type,
-    const cryptohome::Identification& user_id,
-    const std::string& key_name,
-    const std::string& domain,
-    const std::string& device_id,
-    chromeos::attestation::AttestationChallengeOptions options,
+    Profile* profile,
+    chromeos::attestation::TpmChallengeKeyCallback callback,
     const std::string& challenge,
-    const std::string& key_name_for_spkac,
-    const cryptohome::AsyncMethodCaller::DataCallback& callback) {
-  base::ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE, base::BindOnce(callback, true, "response"));
-}
-
-void SignChallengeCallbackFalse(
-    chromeos::attestation::AttestationKeyType key_type,
-    const cryptohome::Identification& user_id,
-    const std::string& key_name,
-    const std::string& domain,
-    const std::string& device_id,
-    chromeos::attestation::AttestationChallengeOptions options,
-    const std::string& challenge,
-    const std::string& key_name_for_spkac,
-    const cryptohome::AsyncMethodCaller::DataCallback& callback) {
-  base::ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE, base::BindOnce(callback, false, ""));
-}
-
-void GetCertificateCallbackTrue(
-    chromeos::attestation::AttestationCertificateProfile certificate_profile,
-    const AccountId& account_id,
-    const std::string& request_origin,
-    bool force_new_key,
-    const std::string& key_name,
-    const chromeos::attestation::AttestationFlow::CertificateCallback&
-        callback) {
-  base::ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE,
-      base::BindOnce(callback, chromeos::attestation::ATTESTATION_SUCCESS,
-                     "certificate"));
-}
-
-void GetCertificateCallbackFalse(
-    chromeos::attestation::AttestationCertificateProfile certificate_profile,
-    const AccountId& account_id,
-    const std::string& request_origin,
-    bool force_new_key,
-    const std::string& key_name,
-    const chromeos::attestation::AttestationFlow::CertificateCallback&
-        callback) {
-  base::ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE,
-      base::BindOnce(callback,
-                     chromeos::attestation::ATTESTATION_UNSPECIFIED_FAILURE,
-                     ""));
+    bool register_key,
+    const std::string& key_name_for_spkac) {
+  EXPECT_FALSE(register_key);
+  std::move(callback).Run(
+      chromeos::attestation::TpmChallengeKeyResult::MakeResult("response"));
 }
 
 class EPKChallengeKeyTestBase : public BrowserWithTestWindowTest {
  protected:
   EPKChallengeKeyTestBase()
-      : settings_helper_(false),
-        extension_(ExtensionBuilder("Test").Build()),
+      : extension_(ExtensionBuilder("Test").Build()),
         fake_user_manager_(new chromeos::FakeChromeUserManager),
         user_manager_enabler_(base::WrapUnique(fake_user_manager_)) {
-    // Set up the default behavior of mocks.
-    ON_CALL(mock_async_method_caller_, TpmAttestationRegisterKey(_, _, _, _))
-        .WillByDefault(Invoke(RegisterKeyCallbackTrue));
-    ON_CALL(mock_async_method_caller_,
-            TpmAttestationSignEnterpriseChallenge(_, _, _, _, _, _, _, _, _))
-        .WillByDefault(Invoke(SignChallengeCallbackTrue));
-    ON_CALL(mock_attestation_flow_, GetCertificate(_, _, _, _, _, _))
-        .WillByDefault(Invoke(GetCertificateCallbackTrue));
-
     stub_install_attributes_.SetCloudManaged("google.com", "device_id");
-
-    settings_helper_.ReplaceDeviceSettingsProviderWithStub();
-    settings_helper_.SetBoolean(chromeos::kDeviceAttestationEnabled, true);
   }
 
   void SetUp() override {
     BrowserWithTestWindowTest::SetUp();
-
-    // Set the user preferences.
     prefs_ = browser()->profile()->GetPrefs();
-    base::ListValue whitelist;
-    whitelist.AppendString(extension_->id());
-    prefs_->Set(prefs::kAttestationExtensionWhitelist, whitelist);
-
     SetAuthenticatedUser();
   }
 
+  void SetMockTpmChallenger() {
+    auto mock_tpm_challenge_key = std::make_unique<
+        NiceMock<chromeos::attestation::MockTpmChallengeKey>>();
+    // Will be used with EXPECT_CALL.
+    mock_tpm_challenge_key_ = mock_tpm_challenge_key.get();
+    mock_tpm_challenge_key->EnableFake();
+    // transfer ownership inside factory
+    chromeos::attestation::TpmChallengeKeyFactory::SetForTesting(
+        std::move(mock_tpm_challenge_key));
+  }
+
   // This will be called by BrowserWithTestWindowTest::SetUp();
   TestingProfile* CreateProfile() override {
     fake_user_manager_->AddUserWithAffiliation(
@@ -205,8 +106,8 @@
     function->set_has_callback(true);
     utils::RunFunction(function, std::move(args), browser,
                        extensions::api_test_utils::NONE);
-    EXPECT_TRUE(function->GetError().empty()) << "Unexpected error: "
-                                              << function->GetError();
+    EXPECT_TRUE(function->GetError().empty())
+        << "Unexpected error: " << function->GetError();
     const base::Value* single_result = NULL;
     if (function->GetResultList() != NULL &&
         function->GetResultList()->Get(0, &single_result)) {
@@ -215,33 +116,27 @@
     return NULL;
   }
 
-  chromeos::FakeCryptohomeClient cryptohome_client_;
-  NiceMock<cryptohome::MockAsyncMethodCaller> mock_async_method_caller_;
-  NiceMock<chromeos::attestation::MockAttestationFlow> mock_attestation_flow_;
-  chromeos::ScopedCrosSettingsTestHelper settings_helper_;
   scoped_refptr<const extensions::Extension> extension_;
   chromeos::StubInstallAttributes stub_install_attributes_;
   // fake_user_manager_ is owned by user_manager_enabler_.
-  chromeos::FakeChromeUserManager* fake_user_manager_;
+  chromeos::FakeChromeUserManager* fake_user_manager_ = nullptr;
   user_manager::ScopedUserManager user_manager_enabler_;
   PrefService* prefs_ = nullptr;
+  chromeos::attestation::MockTpmChallengeKey* mock_tpm_challenge_key_ = nullptr;
 };
 
 class EPKChallengeMachineKeyTest : public EPKChallengeKeyTestBase {
  protected:
   EPKChallengeMachineKeyTest()
-      : impl_(&cryptohome_client_,
-              &mock_async_method_caller_,
-              &mock_attestation_flow_,
-              &stub_install_attributes_),
-        func_(new EnterprisePlatformKeysChallengeMachineKeyFunction(&impl_)) {
+      : func_(new EnterprisePlatformKeysChallengeMachineKeyFunction()) {
     func_->set_extension(extension_.get());
   }
 
   // Returns an error string for the given code.
   std::string GetCertificateError(int error_code) {
     return base::StringPrintf(
-        EPKPChallengeMachineKey::kGetCertificateFailedError, error_code);
+        chromeos::attestation::TpmChallengeKeyImpl::kGetCertificateFailedError,
+        error_code);
   }
 
   std::unique_ptr<base::ListValue> CreateArgs() {
@@ -266,170 +161,51 @@
     return args;
   }
 
-  EPKPChallengeMachineKey impl_;
   scoped_refptr<EnterprisePlatformKeysChallengeMachineKeyFunction> func_;
   base::ListValue args_;
 };
 
-TEST_F(EPKChallengeMachineKeyTest, NonEnterpriseDevice) {
-  stub_install_attributes_.SetConsumerOwned();
-
-  EXPECT_EQ(EPKPChallengeMachineKey::kNonEnterpriseDeviceError,
-            RunFunctionAndReturnError(func_.get(), CreateArgs(), browser()));
-}
-
 TEST_F(EPKChallengeMachineKeyTest, ExtensionNotWhitelisted) {
   base::ListValue empty_whitelist;
   prefs_->Set(prefs::kAttestationExtensionWhitelist, empty_whitelist);
 
-  EXPECT_EQ(EPKPChallengeKeyBase::kExtensionNotWhitelistedError,
+  EXPECT_EQ(EPKPChallengeKey::kExtensionNotWhitelistedError,
             RunFunctionAndReturnError(func_.get(), CreateArgs(), browser()));
 }
 
-TEST_F(EPKChallengeMachineKeyTest, DevicePolicyDisabled) {
-  settings_helper_.SetBoolean(chromeos::kDeviceAttestationEnabled, false);
-
-  EXPECT_EQ(EPKPChallengeKeyBase::kDevicePolicyDisabledError,
-            RunFunctionAndReturnError(func_.get(), CreateArgs(), browser()));
-}
-
-TEST_F(EPKChallengeMachineKeyTest, DoesKeyExistDbusFailed) {
-  cryptohome_client_.set_tpm_attestation_does_key_exist_should_succeed(false);
-
-  EXPECT_EQ(GetCertificateError(kDBusError),
-            RunFunctionAndReturnError(func_.get(), CreateArgs(), browser()));
-}
-
-TEST_F(EPKChallengeMachineKeyTest, GetCertificateFailed) {
-  EXPECT_CALL(mock_attestation_flow_, GetCertificate(_, _, _, _, _, _))
-      .WillRepeatedly(Invoke(GetCertificateCallbackFalse));
-
-  EXPECT_EQ(GetCertificateError(kGetCertificateFailed),
-            RunFunctionAndReturnError(func_.get(), CreateArgs(), browser()));
-}
-
-TEST_F(EPKChallengeMachineKeyTest, SignChallengeFailed) {
-  EXPECT_CALL(mock_async_method_caller_,
-              TpmAttestationSignEnterpriseChallenge(_, _, _, _, _, _, _, _, _))
-      .WillRepeatedly(Invoke(SignChallengeCallbackFalse));
-
-  EXPECT_EQ(EPKPChallengeKeyBase::kSignChallengeFailedError,
-            RunFunctionAndReturnError(func_.get(), CreateArgs(), browser()));
-}
-
-TEST_F(EPKChallengeMachineKeyTest, KeyRegistrationFailed) {
-  EXPECT_CALL(mock_async_method_caller_, TpmAttestationRegisterKey(_, _, _, _))
-      .WillRepeatedly(Invoke(RegisterKeyCallbackFalse));
-
-  EXPECT_EQ(
-      EPKPChallengeMachineKey::kKeyRegistrationFailedError,
-      RunFunctionAndReturnError(func_.get(), CreateArgsRegister(), browser()));
-}
-
-TEST_F(EPKChallengeMachineKeyTest, KeyExists) {
-  cryptohome_client_.SetTpmAttestationDeviceCertificate("attest-ent-machine",
-                                                        std::string());
-
-  // GetCertificate must not be called if the key exists.
-  EXPECT_CALL(mock_attestation_flow_, GetCertificate(_, _, _, _, _, _))
-      .Times(0);
-
-  EXPECT_TRUE(utils::RunFunction(func_.get(), CreateArgs(), browser(),
-                                 extensions::api_test_utils::NONE));
-}
-
-TEST_F(EPKChallengeMachineKeyTest, KeyNotRegisteredByDefault) {
-  EXPECT_CALL(mock_async_method_caller_, TpmAttestationRegisterKey(_, _, _, _))
-      .Times(0);
-
-  EXPECT_TRUE(utils::RunFunction(func_.get(), CreateArgs(), browser(),
-                                 extensions::api_test_utils::NONE));
-}
-
-TEST_F(EPKChallengeMachineKeyTest, KeyNotRegistered) {
-  EXPECT_CALL(mock_async_method_caller_, TpmAttestationRegisterKey(_, _, _, _))
-      .Times(0);
-
-  EXPECT_TRUE(utils::RunFunction(func_.get(), CreateArgsNoRegister(), browser(),
-                                 extensions::api_test_utils::NONE));
-}
-
 TEST_F(EPKChallengeMachineKeyTest, Success) {
-  // GetCertificate must be called exactly once.
-  EXPECT_CALL(mock_attestation_flow_,
-              GetCertificate(
-                  chromeos::attestation::PROFILE_ENTERPRISE_MACHINE_CERTIFICATE,
-                  _, _, _, _, _))
-      .Times(1);
-  // SignEnterpriseChallenge must be called exactly once.
-  EXPECT_CALL(mock_async_method_caller_,
-              TpmAttestationSignEnterpriseChallenge(
-                  chromeos::attestation::KEY_DEVICE,
-                  cryptohome::Identification(), "attest-ent-machine",
-                  "google.com", "device_id", _, "challenge", _, _))
-      .Times(1);
+  SetMockTpmChallenger();
+
+  base::Value whitelist(base::Value::Type::LIST);
+  whitelist.Append(extension_->id());
+  prefs_->Set(prefs::kAttestationExtensionWhitelist, whitelist);
 
   std::unique_ptr<base::Value> value(
       RunFunctionAndReturnSingleResult(func_.get(), CreateArgs(), browser()));
 
   ASSERT_TRUE(value->is_blob());
-  EXPECT_EQ("response",
-            std::string(value->GetBlob().begin(), value->GetBlob().end()));
+  std::string response(value->GetBlob().begin(), value->GetBlob().end());
+  EXPECT_EQ("response", response);
 }
 
-TEST_F(EPKChallengeMachineKeyTest, KeyRegisteredSuccess) {
-  std::string key_name_for_spkac = "attest-ent-machine-" + extension_->id();
-  // GetCertificate must be called exactly once.
-  EXPECT_CALL(mock_attestation_flow_,
-              GetCertificate(
-                  chromeos::attestation::PROFILE_ENTERPRISE_MACHINE_CERTIFICATE,
-                  _, _, _, _, _))
-      .Times(1);
-  // TpmAttestationRegisterKey must be called exactly once.
-  EXPECT_CALL(mock_async_method_caller_,
-              TpmAttestationRegisterKey(chromeos::attestation::KEY_DEVICE,
-                                        _ /* Unused by the API. */,
-                                        key_name_for_spkac, _))
-      .Times(1);
-  // SignEnterpriseChallenge must be called exactly once.
-  EXPECT_CALL(
-      mock_async_method_caller_,
-      TpmAttestationSignEnterpriseChallenge(
-          chromeos::attestation::KEY_DEVICE, cryptohome::Identification(),
-          "attest-ent-machine", "google.com", "device_id", _, "challenge",
-          key_name_for_spkac, _))
-      .Times(1);
+TEST_F(EPKChallengeMachineKeyTest, KeyNotRegisteredByDefault) {
+  SetMockTpmChallenger();
 
-  std::unique_ptr<base::Value> value(RunFunctionAndReturnSingleResult(
-      func_.get(), CreateArgsRegister(), browser()));
+  base::ListValue whitelist;
+  whitelist.AppendString(extension_->id());
+  prefs_->Set(prefs::kAttestationExtensionWhitelist, whitelist);
 
-  ASSERT_TRUE(value->is_blob());
-  EXPECT_EQ("response",
-            std::string(value->GetBlob().begin(), value->GetBlob().end()));
-}
+  EXPECT_CALL(*mock_tpm_challenge_key_, Run)
+      .WillOnce(Invoke(FakeRunCheckNotRegister));
 
-TEST_F(EPKChallengeMachineKeyTest, AttestationNotPrepared) {
-  cryptohome_client_.set_tpm_attestation_is_prepared(false);
-
-  EXPECT_EQ(GetCertificateError(kResetRequired),
-            RunFunctionAndReturnError(func_.get(), CreateArgs(), browser()));
-}
-
-TEST_F(EPKChallengeMachineKeyTest, AttestationPreparedDbusFailed) {
-  cryptohome_client_.SetServiceIsAvailable(false);
-
-  EXPECT_EQ(GetCertificateError(kDBusError),
-            RunFunctionAndReturnError(func_.get(), CreateArgs(), browser()));
+  EXPECT_TRUE(utils::RunFunction(func_.get(), CreateArgs(), browser(),
+                                 extensions::api_test_utils::NONE));
 }
 
 class EPKChallengeUserKeyTest : public EPKChallengeKeyTestBase {
  protected:
   EPKChallengeUserKeyTest()
-      : impl_(&cryptohome_client_,
-              &mock_async_method_caller_,
-              &mock_attestation_flow_,
-              &stub_install_attributes_),
-        func_(new EnterprisePlatformKeysChallengeUserKeyFunction(&impl_)) {
+      : func_(new EnterprisePlatformKeysChallengeUserKeyFunction()) {
     func_->set_extension(extension_.get());
   }
 
@@ -440,12 +216,6 @@
     prefs_->SetBoolean(prefs::kAttestationEnabled, true);
   }
 
-  // Returns an error string for the given code.
-  std::string GetCertificateError(int error_code) {
-    return base::StringPrintf(EPKPChallengeUserKey::kGetCertificateFailedError,
-                              error_code);
-  }
-
   std::unique_ptr<base::ListValue> CreateArgs() {
     return CreateArgsInternal(true);
   }
@@ -461,183 +231,15 @@
     return args;
   }
 
-  EPKPChallengeUserKey impl_;
+  EPKPChallengeKey impl_;
   scoped_refptr<EnterprisePlatformKeysChallengeUserKeyFunction> func_;
 };
 
-TEST_F(EPKChallengeUserKeyTest, UserPolicyDisabled) {
-  prefs_->SetBoolean(prefs::kAttestationEnabled, false);
-
-  EXPECT_EQ(EPKPChallengeUserKey::kUserPolicyDisabledError,
-            RunFunctionAndReturnError(func_.get(), CreateArgs(), browser()));
-}
-
 TEST_F(EPKChallengeUserKeyTest, ExtensionNotWhitelisted) {
   base::ListValue empty_whitelist;
   prefs_->Set(prefs::kAttestationExtensionWhitelist, empty_whitelist);
 
-  EXPECT_EQ(EPKPChallengeKeyBase::kExtensionNotWhitelistedError,
-            RunFunctionAndReturnError(func_.get(), CreateArgs(), browser()));
-}
-
-TEST_F(EPKChallengeUserKeyTest, DevicePolicyDisabled) {
-  settings_helper_.SetBoolean(chromeos::kDeviceAttestationEnabled, false);
-
-  EXPECT_EQ(EPKPChallengeKeyBase::kDevicePolicyDisabledError,
-            RunFunctionAndReturnError(func_.get(), CreateArgs(), browser()));
-}
-
-TEST_F(EPKChallengeUserKeyTest, DoesKeyExistDbusFailed) {
-  cryptohome_client_.set_tpm_attestation_does_key_exist_should_succeed(false);
-
-  EXPECT_EQ(GetCertificateError(kDBusError),
-            RunFunctionAndReturnError(func_.get(), CreateArgs(), browser()));
-}
-
-TEST_F(EPKChallengeUserKeyTest, GetCertificateFailed) {
-  EXPECT_CALL(mock_attestation_flow_, GetCertificate(_, _, _, _, _, _))
-      .WillRepeatedly(Invoke(GetCertificateCallbackFalse));
-
-  EXPECT_EQ(GetCertificateError(kGetCertificateFailed),
-            RunFunctionAndReturnError(func_.get(), CreateArgs(), browser()));
-}
-
-TEST_F(EPKChallengeUserKeyTest, SignChallengeFailed) {
-  EXPECT_CALL(mock_async_method_caller_,
-              TpmAttestationSignEnterpriseChallenge(_, _, _, _, _, _, _, _, _))
-      .WillRepeatedly(Invoke(SignChallengeCallbackFalse));
-
-  EXPECT_EQ(EPKPChallengeKeyBase::kSignChallengeFailedError,
-            RunFunctionAndReturnError(func_.get(), CreateArgs(), browser()));
-}
-
-TEST_F(EPKChallengeUserKeyTest, KeyRegistrationFailed) {
-  EXPECT_CALL(mock_async_method_caller_, TpmAttestationRegisterKey(_, _, _, _))
-      .WillRepeatedly(Invoke(RegisterKeyCallbackFalse));
-
-  EXPECT_EQ(EPKPChallengeUserKey::kKeyRegistrationFailedError,
-            RunFunctionAndReturnError(func_.get(), CreateArgs(), browser()));
-}
-
-TEST_F(EPKChallengeUserKeyTest, KeyExists) {
-  cryptohome_client_.SetTpmAttestationUserCertificate(
-      cryptohome::CreateAccountIdentifierFromAccountId(
-          AccountId::FromUserEmail(kUserEmail)),
-      "attest-ent-user", std::string());
-  // GetCertificate must not be called if the key exists.
-  EXPECT_CALL(mock_attestation_flow_, GetCertificate(_, _, _, _, _, _))
-      .Times(0);
-
-  EXPECT_TRUE(utils::RunFunction(func_.get(), CreateArgs(), browser(),
-                                 extensions::api_test_utils::NONE));
-}
-
-TEST_F(EPKChallengeUserKeyTest, KeyNotRegistered) {
-  EXPECT_CALL(mock_async_method_caller_, TpmAttestationRegisterKey(_, _, _, _))
-      .Times(0);
-
-  EXPECT_TRUE(utils::RunFunction(func_.get(), CreateArgsNoRegister(), browser(),
-                                 extensions::api_test_utils::NONE));
-}
-
-TEST_F(EPKChallengeUserKeyTest, PersonalDevice) {
-  stub_install_attributes_.SetConsumerOwned();
-
-  // Currently personal devices are not supported.
-  EXPECT_EQ(GetCertificateError(kUserRejected),
-            RunFunctionAndReturnError(func_.get(), CreateArgs(), browser()));
-}
-
-TEST_F(EPKChallengeUserKeyTest, Success) {
-  // GetCertificate must be called exactly once.
-  EXPECT_CALL(
-      mock_attestation_flow_,
-      GetCertificate(chromeos::attestation::PROFILE_ENTERPRISE_USER_CERTIFICATE,
-                     _, _, _, _, _))
-      .Times(1);
-  const cryptohome::Identification cryptohome_id(
-      AccountId::FromUserEmail(kUserEmail));
-  // SignEnterpriseChallenge must be called exactly once.
-  EXPECT_CALL(
-      mock_async_method_caller_,
-      TpmAttestationSignEnterpriseChallenge(
-          chromeos::attestation::KEY_USER, cryptohome_id, "attest-ent-user",
-          kUserEmail, "device_id", _, "challenge", _, _))
-      .Times(1);
-  // RegisterKey must be called exactly once.
-  EXPECT_CALL(mock_async_method_caller_,
-              TpmAttestationRegisterKey(chromeos::attestation::KEY_USER,
-                                        cryptohome_id, "attest-ent-user", _))
-      .Times(1);
-
-  std::unique_ptr<base::Value> value(
-      RunFunctionAndReturnSingleResult(func_.get(), CreateArgs(), browser()));
-
-  ASSERT_TRUE(value->is_blob());
-  EXPECT_EQ("response",
-            std::string(value->GetBlob().begin(), value->GetBlob().end()));
-}
-
-TEST_F(EPKChallengeUserKeyTest, AttestationNotPrepared) {
-  cryptohome_client_.set_tpm_attestation_is_prepared(false);
-
-  EXPECT_EQ(GetCertificateError(kResetRequired),
-            RunFunctionAndReturnError(func_.get(), CreateArgs(), browser()));
-}
-
-TEST_F(EPKChallengeUserKeyTest, AttestationPreparedDbusFailed) {
-  cryptohome_client_.SetServiceIsAvailable(false);
-
-  EXPECT_EQ(GetCertificateError(kDBusError),
-            RunFunctionAndReturnError(func_.get(), CreateArgs(), browser()));
-}
-
-class EPKChallengeMachineKeyUnmanagedUserTest
-    : public EPKChallengeMachineKeyTest {
- protected:
-  void SetAuthenticatedUser() override {
-    signin::MakePrimaryAccountAvailable(
-        IdentityManagerFactory::GetForProfile(browser()->profile()),
-        account_id_.GetUserEmail());
-  }
-
-  TestingProfile* CreateProfile() override {
-    fake_user_manager_->AddUser(account_id_);
-    return profile_manager()->CreateTestingProfile(account_id_.GetUserEmail());
-  }
-
-  const std::string email = "test@chromium.com";
-  const AccountId account_id_ =
-      AccountId::FromUserEmailGaiaId(email,
-                                     signin::GetTestGaiaIdForEmail(email));
-};
-
-TEST_F(EPKChallengeMachineKeyUnmanagedUserTest, UserNotManaged) {
-  EXPECT_EQ(EPKPChallengeKeyBase::kUserNotManaged,
-            RunFunctionAndReturnError(func_.get(), CreateArgs(), browser()));
-}
-
-class EPKChallengeUserKeyUnmanagedUserTest : public EPKChallengeUserKeyTest {
- protected:
-  void SetAuthenticatedUser() override {
-    signin::MakePrimaryAccountAvailable(
-        IdentityManagerFactory::GetForProfile(browser()->profile()),
-        account_id_.GetUserEmail());
-  }
-
-  TestingProfile* CreateProfile() override {
-    fake_user_manager_->AddUser(account_id_);
-    return profile_manager()->CreateTestingProfile(account_id_.GetUserEmail());
-  }
-
-  const std::string email = "test@chromium.com";
-  const AccountId account_id_ =
-      AccountId::FromUserEmailGaiaId(email,
-                                     signin::GetTestGaiaIdForEmail(email));
-};
-
-TEST_F(EPKChallengeUserKeyUnmanagedUserTest, UserNotManaged) {
-  EXPECT_EQ(EPKPChallengeKeyBase::kUserNotManaged,
+  EXPECT_EQ(EPKPChallengeKey::kExtensionNotWhitelistedError,
             RunFunctionAndReturnError(func_.get(), CreateArgs(), browser()));
 }
 
diff --git a/chrome/browser/extensions/api/enterprise_platform_keys_private/enterprise_platform_keys_private_api.cc b/chrome/browser/extensions/api/enterprise_platform_keys_private/enterprise_platform_keys_private_api.cc
index c68aaa5..a7dfd04 100644
--- a/chrome/browser/extensions/api/enterprise_platform_keys_private/enterprise_platform_keys_private_api.cc
+++ b/chrome/browser/extensions/api/enterprise_platform_keys_private/enterprise_platform_keys_private_api.cc
@@ -4,44 +4,20 @@
 
 #include "chrome/browser/extensions/api/enterprise_platform_keys_private/enterprise_platform_keys_private_api.h"
 
-#include <string>
-#include <utility>
-
 #include "base/base64.h"
 #include "base/bind.h"
-#include "base/callback.h"
-#include "base/strings/stringprintf.h"
 #include "base/task/post_task.h"
 #include "base/values.h"
-#include "chrome/browser/browser_process.h"
-#include "chrome/browser/chromeos/attestation/attestation_ca_client.h"
-#include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h"
 #include "chrome/browser/chromeos/profiles/profile_helper.h"
-#include "chrome/browser/chromeos/settings/cros_settings.h"
 #include "chrome/browser/extensions/chrome_extension_function_details.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/common/extensions/api/enterprise_platform_keys_private.h"
 #include "chrome/common/pref_names.h"
-#include "chromeos/attestation/attestation_flow.h"
-#include "chromeos/cryptohome/async_method_caller.h"
-#include "chromeos/cryptohome/cryptohome_parameters.h"
-#include "chromeos/dbus/constants/attestation_constants.h"
-#include "chromeos/dbus/cryptohome/cryptohome_client.h"
-#include "chromeos/dbus/dbus_method_call_status.h"
-#include "chromeos/dbus/dbus_thread_manager.h"
-#include "chromeos/settings/cros_settings_names.h"
-#include "chromeos/tpm/install_attributes.h"
-#include "components/account_id/account_id.h"
 #include "components/pref_registry/pref_registry_syncable.h"
 #include "components/prefs/pref_service.h"
-#include "components/user_manager/known_user.h"
-#include "components/user_manager/user.h"
-#include "components/user_manager/user_manager.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
 #include "extensions/common/manifest.h"
-#include "google_apis/gaia/gaia_auth_util.h"
-#include "third_party/cros_system_api/dbus/service_constants.h"
 
 namespace {
 // Prefix for naming machine keys used for SignedPublicKeyAndChallenge when
@@ -53,615 +29,68 @@
 
 namespace api_epkp = api::enterprise_platform_keys_private;
 
-// Base class
+EPKPChallengeKey::EPKPChallengeKey() = default;
+EPKPChallengeKey::~EPKPChallengeKey() = default;
 
-const char EPKPChallengeKeyBase::kChallengeBadBase64Error[] =
-    "Challenge is not base64 encoded.";
-const char EPKPChallengeKeyBase::kDevicePolicyDisabledError[] =
-    "Remote attestation is not enabled for your device.";
-const char EPKPChallengeKeyBase::kExtensionNotWhitelistedError[] =
+void EPKPChallengeKey::RegisterProfilePrefs(
+    user_prefs::PrefRegistrySyncable* registry) {
+  registry->RegisterListPref(prefs::kAttestationExtensionWhitelist);
+}
+
+const char EPKPChallengeKey::kExtensionNotWhitelistedError[] =
     "The extension does not have permission to call this function.";
-const char EPKPChallengeKeyBase::kResponseBadBase64Error[] =
-    "Response cannot be encoded in base64.";
-const char EPKPChallengeKeyBase::kSignChallengeFailedError[] =
-    "Failed to sign the challenge.";
-const char EPKPChallengeKeyBase::kUserNotManaged[] =
-    "The user account is not enterprise managed.";
+const char EPKPChallengeKey::kChallengeBadBase64Error[] =
+    "Challenge is not base64 encoded.";
 
-EPKPChallengeKeyBase::PrepareKeyContext::PrepareKeyContext(
-    chromeos::attestation::AttestationKeyType key_type,
-    const AccountId& account_id,
-    const std::string& key_name,
-    chromeos::attestation::AttestationCertificateProfile certificate_profile,
-    bool require_user_consent,
-    const std::string& key_name_for_spkac,
-    const base::Callback<void(PrepareKeyResult)>& callback)
-    : key_type(key_type),
-      account_id(account_id),
-      key_name(key_name),
-      certificate_profile(certificate_profile),
-      require_user_consent(require_user_consent),
-      key_name_for_spkac(key_name_for_spkac),
-      callback(callback) {}
-
-EPKPChallengeKeyBase::PrepareKeyContext::PrepareKeyContext(
-    const PrepareKeyContext& other) = default;
-
-EPKPChallengeKeyBase::PrepareKeyContext::~PrepareKeyContext() {
-}
-
-EPKPChallengeKeyBase::EPKPChallengeKeyBase()
-    : cryptohome_client_(chromeos::CryptohomeClient::Get()),
-      async_caller_(cryptohome::AsyncMethodCaller::GetInstance()),
-      install_attributes_(g_browser_process->platform_part()
-                              ->browser_policy_connector_chromeos()
-                              ->GetInstallAttributes()) {
-  std::unique_ptr<chromeos::attestation::ServerProxy> ca_client(
-      new chromeos::attestation::AttestationCAClient());
-  default_attestation_flow_.reset(new chromeos::attestation::AttestationFlow(
-      async_caller_, cryptohome_client_, std::move(ca_client)));
-  attestation_flow_ = default_attestation_flow_.get();
-}
-
-EPKPChallengeKeyBase::EPKPChallengeKeyBase(
-    chromeos::CryptohomeClient* cryptohome_client,
-    cryptohome::AsyncMethodCaller* async_caller,
-    chromeos::attestation::AttestationFlow* attestation_flow,
-    chromeos::InstallAttributes* install_attributes) :
-    cryptohome_client_(cryptohome_client),
-    async_caller_(async_caller),
-    attestation_flow_(attestation_flow),
-    install_attributes_(install_attributes) {
-}
-
-EPKPChallengeKeyBase::~EPKPChallengeKeyBase() {
-}
-
-void EPKPChallengeKeyBase::GetDeviceAttestationEnabled(
-    const base::Callback<void(bool)>& callback) const {
-  chromeos::CrosSettings* settings = chromeos::CrosSettings::Get();
-  chromeos::CrosSettingsProvider::TrustedStatus status =
-      settings->PrepareTrustedValues(
-          base::Bind(&EPKPChallengeKeyBase::GetDeviceAttestationEnabled,
-                     base::Unretained(this), callback));
-
-  bool value = false;
-  switch (status) {
-    case chromeos::CrosSettingsProvider::TRUSTED:
-      if (!settings->GetBoolean(chromeos::kDeviceAttestationEnabled, &value))
-        value = false;
-      break;
-    case chromeos::CrosSettingsProvider::TEMPORARILY_UNTRUSTED:
-      // Do nothing. This function will be called again when the values are
-      // ready.
-      return;
-    case chromeos::CrosSettingsProvider::PERMANENTLY_UNTRUSTED:
-      // If the value cannot be trusted, we assume that the device attestation
-      // is false to be on the safe side.
-      break;
-  }
-
-  callback.Run(value);
-}
-
-bool EPKPChallengeKeyBase::IsEnterpriseDevice() const {
-  return install_attributes_->IsEnterpriseManaged();
-}
-
-bool EPKPChallengeKeyBase::IsExtensionWhitelisted() const {
-  if (!chromeos::ProfileHelper::Get()->GetUserByProfile(profile_)) {
+// Check if the extension is whitelisted in the user policy.
+bool EPKPChallengeKey::IsExtensionWhitelisted(
+    Profile* profile,
+    scoped_refptr<const Extension> extension) {
+  if (!chromeos::ProfileHelper::Get()->GetUserByProfile(profile)) {
     // Only allow remote attestation for apps that were force-installed on the
     // login/signin screen.
     // TODO(drcrash): Use a separate device-wide policy for the API.
-    return Manifest::IsPolicyLocation(extension_->location());
+    return Manifest::IsPolicyLocation(extension->location());
   }
-  if (Manifest::IsComponentLocation(extension_->location())) {
+  if (Manifest::IsComponentLocation(extension->location())) {
     // Note: For this to even be called, the component extension must also be
     // whitelisted in chrome/common/extensions/api/_permission_features.json
     return true;
   }
   const base::ListValue* list =
-      profile_->GetPrefs()->GetList(prefs::kAttestationExtensionWhitelist);
-  base::Value value(extension_->id());
+      profile->GetPrefs()->GetList(prefs::kAttestationExtensionWhitelist);
+  base::Value value(extension->id());
   return list->Find(value) != list->end();
 }
 
-AccountId EPKPChallengeKeyBase::GetAccountId() const {
-  const user_manager::User* user =
-      chromeos::ProfileHelper::Get()->GetUserByProfile(profile_);
-
-  // Signin profile doesn't have associated user.
-  if (!user) {
-    return EmptyAccountId();
-  }
-
-  return user->GetAccountId();
-}
-
-bool EPKPChallengeKeyBase::IsUserAffiliated() const {
-  const user_manager::User* const user =
-      user_manager::UserManager::Get()->FindUser(GetAccountId());
-
-  if (user) {
-    return user->IsAffiliated();
-  }
-
-  return false;
-}
-
-std::string EPKPChallengeKeyBase::GetEnterpriseDomain() const {
-  return install_attributes_->GetDomain();
-}
-
-std::string EPKPChallengeKeyBase::GetUserEmail() const {
-  return GetAccountId().GetUserEmail();
-}
-
-std::string EPKPChallengeKeyBase::GetDeviceId() const {
-  return install_attributes_->GetDeviceId();
-}
-
-void EPKPChallengeKeyBase::PrepareKey(
-    chromeos::attestation::AttestationKeyType key_type,
-    const AccountId& account_id,
-    const std::string& key_name,
-    chromeos::attestation::AttestationCertificateProfile certificate_profile,
-    bool require_user_consent,
-    const std::string& key_name_for_spkac,
-    const base::Callback<void(PrepareKeyResult)>& callback) {
-  const PrepareKeyContext context =
-      PrepareKeyContext(key_type, account_id, key_name, certificate_profile,
-                        require_user_consent, key_name_for_spkac, callback);
-  cryptohome_client_->TpmAttestationIsPrepared(
-      base::BindOnce(&EPKPChallengeKeyBase::IsAttestationPreparedCallback,
-                     base::Unretained(this), context));
-}
-
-void EPKPChallengeKeyBase::IsAttestationPreparedCallback(
-    const PrepareKeyContext& context,
-    base::Optional<bool> result) {
-  if (!result.has_value()) {
-    context.callback.Run(PREPARE_KEY_DBUS_ERROR);
-    return;
-  }
-  if (!result.value()) {
-    cryptohome_client_->TpmIsEnabled(
-        base::BindOnce(&EPKPChallengeKeyBase::PrepareKeyErrorHandlerCallback,
-                       base::Unretained(this), context));
-    return;
-  }
-
-  if (!context.key_name_for_spkac.empty()) {
-    // Generate a new key and have it signed by PCA.
-    attestation_flow_->GetCertificate(
-        context.certificate_profile, context.account_id,
-        std::string(),  // Not used.
-        true,           // Force a new key to be generated.
-        context.key_name_for_spkac,
-        base::Bind(&EPKPChallengeKeyBase::GetCertificateCallback,
-                   base::Unretained(this), context.callback));
-    return;
-  }
-  // Attestation is available, see if the key we need already exists.
-  cryptohome_client_->TpmAttestationDoesKeyExist(
-      context.key_type,
-      cryptohome::CreateAccountIdentifierFromAccountId(context.account_id),
-      context.key_name,
-      base::BindOnce(&EPKPChallengeKeyBase::DoesKeyExistCallback,
-                     base::Unretained(this), context));
-}
-
-void EPKPChallengeKeyBase::PrepareKeyErrorHandlerCallback(
-    const PrepareKeyContext& context,
-    base::Optional<bool> is_tpm_enabled) {
-  if (!is_tpm_enabled.has_value()) {
-    context.callback.Run(PREPARE_KEY_DBUS_ERROR);
-    return;
-  }
-
-  if (is_tpm_enabled.value()) {
-    context.callback.Run(PREPARE_KEY_RESET_REQUIRED);
-  } else {
-    context.callback.Run(PREPARE_KEY_ATTESTATION_UNSUPPORTED);
-  }
-}
-
-void EPKPChallengeKeyBase::DoesKeyExistCallback(
-    const PrepareKeyContext& context,
-    base::Optional<bool> result) {
-  if (!result.has_value()) {
-    context.callback.Run(PREPARE_KEY_DBUS_ERROR);
-    return;
-  }
-
-  if (result.value()) {
-    // The key exists. Do nothing more.
-    context.callback.Run(PREPARE_KEY_OK);
-  } else {
-    // The key does not exist. Create a new key and have it signed by PCA.
-    if (context.require_user_consent) {
-      // We should ask the user explicitly before sending any private
-      // information to PCA.
-      AskForUserConsent(
-          base::Bind(&EPKPChallengeKeyBase::AskForUserConsentCallback,
-                     base::Unretained(this), context));
-    } else {
-      // User consent is not required. Skip to the next step.
-      AskForUserConsentCallback(context, true);
-    }
-  }
-}
-
-void EPKPChallengeKeyBase::AskForUserConsent(
-    const base::Callback<void(bool)>& callback) const {
-  // TODO(davidyu): right now we just simply reject the request before we have
-  // a way to ask for user consent.
-  callback.Run(false);
-}
-
-void EPKPChallengeKeyBase::AskForUserConsentCallback(
-    const PrepareKeyContext& context,
-    bool result) {
-  if (!result) {
-    // The user rejects the request.
-    context.callback.Run(PREPARE_KEY_USER_REJECTED);
-    return;
-  }
-
-  // Generate a new key and have it signed by PCA.
-  attestation_flow_->GetCertificate(
-      context.certificate_profile, context.account_id,
-      std::string(),  // Not used.
-      true,           // Force a new key to be generated.
-      std::string(),  // Leave key name empty to generate a default name.
-      base::Bind(&EPKPChallengeKeyBase::GetCertificateCallback,
-                 base::Unretained(this), context.callback));
-}
-
-void EPKPChallengeKeyBase::GetCertificateCallback(
-    const base::Callback<void(PrepareKeyResult)>& callback,
-    chromeos::attestation::AttestationStatus status,
-    const std::string& pem_certificate_chain) {
-  if (status != chromeos::attestation::ATTESTATION_SUCCESS) {
-    callback.Run(PREPARE_KEY_GET_CERTIFICATE_FAILED);
-    return;
-  }
-
-  callback.Run(PREPARE_KEY_OK);
-}
-
-// Implementation of ChallengeMachineKey()
-
-const char EPKPChallengeMachineKey::kGetCertificateFailedError[] =
-    "Failed to get Enterprise machine certificate. Error code = %d";
-const char EPKPChallengeMachineKey::kKeyRegistrationFailedError[] =
-    "Machine key registration failed.";
-const char EPKPChallengeMachineKey::kNonEnterpriseDeviceError[] =
-    "The device is not enterprise enrolled.";
-
-EPKPChallengeMachineKey::EPKPChallengeMachineKey() : EPKPChallengeKeyBase() {
-}
-
-EPKPChallengeMachineKey::EPKPChallengeMachineKey(
-    chromeos::CryptohomeClient* cryptohome_client,
-    cryptohome::AsyncMethodCaller* async_caller,
-    chromeos::attestation::AttestationFlow* attestation_flow,
-    chromeos::InstallAttributes* install_attributes) :
-    EPKPChallengeKeyBase(cryptohome_client,
-                         async_caller,
-                         attestation_flow,
-                         install_attributes) {
-}
-
-EPKPChallengeMachineKey::~EPKPChallengeMachineKey() {
-}
-
-void EPKPChallengeMachineKey::Run(scoped_refptr<ExtensionFunction> caller,
-                                  const ChallengeKeyCallback& callback,
-                                  const std::string& challenge,
-                                  bool register_key) {
-  callback_ = callback;
-  profile_ = ChromeExtensionFunctionDetails(caller.get()).GetProfile();
-  extension_ = scoped_refptr<const Extension>(caller->extension());
-
-  // Check if the device is enterprise enrolled.
-  if (!IsEnterpriseDevice()) {
-    callback_.Run(false, kNonEnterpriseDeviceError);
-    return;
-  }
-
-  // Check if the extension is whitelisted in the user policy.
-  if (!IsExtensionWhitelisted()) {
-    callback_.Run(false, kExtensionNotWhitelistedError);
-    return;
-  }
-
-  // Check whether the user is managed unless the signin profile is used.
-  if (chromeos::ProfileHelper::Get()->GetUserByProfile(profile_) &&
-      !IsUserAffiliated()) {
-    callback_.Run(false, kUserNotManaged);
-    return;
-  }
-
-  // Check if RA is enabled in the device policy.
-  GetDeviceAttestationEnabled(
-      base::Bind(&EPKPChallengeMachineKey::GetDeviceAttestationEnabledCallback,
-                 base::Unretained(this), challenge, register_key));
-}
-
-void EPKPChallengeMachineKey::DecodeAndRun(
+void EPKPChallengeKey::Run(
+    chromeos::attestation::AttestationKeyType type,
     scoped_refptr<ExtensionFunction> caller,
-    const ChallengeKeyCallback& callback,
-    const std::string& encoded_challenge,
+    chromeos::attestation::TpmChallengeKeyCallback callback,
+    const std::string& challenge,
     bool register_key) {
-  std::string challenge;
-  if (!base::Base64Decode(encoded_challenge, &challenge)) {
-    callback.Run(false, kChallengeBadBase64Error);
-    return;
-  }
-  Run(caller, callback, challenge, register_key);
-}
+  Profile* profile = ChromeExtensionFunctionDetails(caller.get()).GetProfile();
 
-void EPKPChallengeMachineKey::GetDeviceAttestationEnabledCallback(
-    const std::string& challenge,
-    bool register_key,
-    bool enabled) {
-  if (!enabled) {
-    callback_.Run(false, kDevicePolicyDisabledError);
+  if (!IsExtensionWhitelisted(profile, caller->extension())) {
+    std::move(callback).Run(
+        chromeos::attestation::TpmChallengeKeyResult::MakeError(
+            kExtensionNotWhitelistedError));
     return;
   }
 
-  // The EMK cannot be registered as that would relinquish it and the DMServer
-  // relies on it to remain stable. If register_key = true, generate a new
-  // machine key to side-load into the system-wide token. This key will be
-  // used for SignedPublicKeyAndChallenge but the challenge response will still
-  // be singed using the stable EMK.
   std::string key_name_for_spkac;
-  if (register_key) {
-    key_name_for_spkac = kEnterpriseMachineKeyForSpkacPrefix + extension_->id();
-  }
-  PrepareKey(chromeos::attestation::KEY_DEVICE,
-             EmptyAccountId(),  // Not used.
-             chromeos::attestation::kEnterpriseMachineKey,
-             chromeos::attestation::PROFILE_ENTERPRISE_MACHINE_CERTIFICATE,
-             false,  // user consent is not required.
-             key_name_for_spkac,
-             base::Bind(&EPKPChallengeMachineKey::PrepareKeyCallback,
-                        base::Unretained(this), challenge, register_key,
-                        key_name_for_spkac));
-}
-
-void EPKPChallengeMachineKey::PrepareKeyCallback(
-    const std::string& challenge,
-    bool register_key,
-    const std::string& key_name_for_spkac,
-    PrepareKeyResult result) {
-  if (result != PREPARE_KEY_OK) {
-    callback_.Run(false,
-                  base::StringPrintf(kGetCertificateFailedError, result));
-    return;
+  if (register_key && (type == chromeos::attestation::KEY_DEVICE)) {
+    key_name_for_spkac =
+        kEnterpriseMachineKeyForSpkacPrefix + caller->extension()->id();
   }
 
-  // Everything is checked. Sign the challenge.
-  async_caller_->TpmAttestationSignEnterpriseChallenge(
-      chromeos::attestation::KEY_DEVICE,
-      cryptohome::Identification(),  // Not used.
-      chromeos::attestation::kEnterpriseMachineKey, GetEnterpriseDomain(),
-      GetDeviceId(),
-      register_key ? chromeos::attestation::CHALLENGE_INCLUDE_SIGNED_PUBLIC_KEY
-                   : chromeos::attestation::CHALLENGE_OPTION_NONE,
-      challenge, key_name_for_spkac,
-      base::Bind(&EPKPChallengeMachineKey::SignChallengeCallback,
-                 base::Unretained(this), register_key));
-}
-
-void EPKPChallengeMachineKey::SignChallengeCallback(
-    bool register_key,
-    bool success,
-    const std::string& response) {
-  if (!success) {
-    callback_.Run(false, kSignChallengeFailedError);
-    return;
-  }
-  if (register_key) {
-    std::string key_name_for_spkac =
-        kEnterpriseMachineKeyForSpkacPrefix + extension_->id();
-    async_caller_->TpmAttestationRegisterKey(
-        chromeos::attestation::KEY_DEVICE,
-        cryptohome::Identification(),  // Not used.
-        key_name_for_spkac,
-        base::Bind(&EPKPChallengeMachineKey::RegisterKeyCallback,
-                   base::Unretained(this), response));
-  } else {
-    RegisterKeyCallback(response, true, cryptohome::MOUNT_ERROR_NONE);
-  }
-}
-
-void EPKPChallengeMachineKey::RegisterKeyCallback(
-    const std::string& response,
-    bool success,
-    cryptohome::MountError return_code) {
-  if (!success || return_code != cryptohome::MOUNT_ERROR_NONE) {
-    callback_.Run(false, kKeyRegistrationFailedError);
-    return;
-  }
-  callback_.Run(true, response);
-}
-
-// Implementation of ChallengeUserKey()
-
-const char EPKPChallengeUserKey::kGetCertificateFailedError[] =
-    "Failed to get Enterprise user certificate. Error code = %d";
-const char EPKPChallengeUserKey::kKeyRegistrationFailedError[] =
-    "Key registration failed.";
-const char EPKPChallengeUserKey::kUserPolicyDisabledError[] =
-    "Remote attestation is not enabled for your account.";
-const char EPKPChallengeUserKey::kUserKeyNotAvailable[] =
-    "User keys cannot be challenged in this profile.";
-
-EPKPChallengeUserKey::EPKPChallengeUserKey() : EPKPChallengeKeyBase() {
-}
-
-EPKPChallengeUserKey::EPKPChallengeUserKey(
-    chromeos::CryptohomeClient* cryptohome_client,
-    cryptohome::AsyncMethodCaller* async_caller,
-    chromeos::attestation::AttestationFlow* attestation_flow,
-    chromeos::InstallAttributes* install_attributes) :
-    EPKPChallengeKeyBase(cryptohome_client,
-                         async_caller,
-                         attestation_flow,
-                         install_attributes) {
-}
-
-EPKPChallengeUserKey::~EPKPChallengeUserKey() {
-}
-
-void EPKPChallengeUserKey::RegisterProfilePrefs(
-    user_prefs::PrefRegistrySyncable* registry) {
-  registry->RegisterBooleanPref(prefs::kAttestationEnabled, false);
-  registry->RegisterListPref(prefs::kAttestationExtensionWhitelist);
-}
-
-void EPKPChallengeUserKey::Run(scoped_refptr<ExtensionFunction> caller,
-                               const ChallengeKeyCallback& callback,
-                               const std::string& challenge,
-                               bool register_key) {
-  callback_ = callback;
-  profile_ = ChromeExtensionFunctionDetails(caller.get()).GetProfile();
-  extension_ = scoped_refptr<const Extension>(caller->extension());
-
-  // Check if user keys are available in this profile.
-  if (!chromeos::ProfileHelper::Get()->GetUserByProfile(profile_)) {
-    callback_.Run(false, EPKPChallengeUserKey::kUserKeyNotAvailable);
-    return;
-  }
-
-  // Check if RA is enabled in the user policy.
-  if (!IsRemoteAttestationEnabledForUser()) {
-    callback_.Run(false, kUserPolicyDisabledError);
-    return;
-  }
-
-  // Check if the extension is whitelisted in the user policy.
-  if (!IsExtensionWhitelisted()) {
-    callback_.Run(false, kExtensionNotWhitelistedError);
-    return;
-  }
-
-  if (IsEnterpriseDevice()) {
-    if (!IsUserAffiliated()) {
-      callback_.Run(false, kUserNotManaged);
-      return;
-    }
-
-    // Check if RA is enabled in the device policy.
-    GetDeviceAttestationEnabled(
-        base::Bind(&EPKPChallengeUserKey::GetDeviceAttestationEnabledCallback,
-                   base::Unretained(this), challenge, register_key,
-                   false));  // user consent is not required.
-  } else {
-    // For personal devices, we don't need to check if RA is enabled in the
-    // device, but we need to ask for user consent if the key does not exist.
-    GetDeviceAttestationEnabledCallback(challenge, register_key,
-                                        true,   // user consent is required.
-                                        true);  // attestation is enabled.
-  }
-}
-
-void EPKPChallengeUserKey::DecodeAndRun(scoped_refptr<ExtensionFunction> caller,
-                                        const ChallengeKeyCallback& callback,
-                                        const std::string& encoded_challenge,
-                                        bool register_key) {
-  std::string challenge;
-  if (!base::Base64Decode(encoded_challenge, &challenge)) {
-    callback.Run(false, kChallengeBadBase64Error);
-    return;
-  }
-  Run(caller, callback, challenge, register_key);
-}
-
-void EPKPChallengeUserKey::GetDeviceAttestationEnabledCallback(
-    const std::string& challenge,
-    bool register_key,
-    bool require_user_consent,
-    bool enabled) {
-  if (!enabled) {
-    callback_.Run(false, kDevicePolicyDisabledError);
-    return;
-  }
-
-  PrepareKey(chromeos::attestation::KEY_USER, GetAccountId(),
-             chromeos::attestation::kEnterpriseUserKey,
-             chromeos::attestation::PROFILE_ENTERPRISE_USER_CERTIFICATE,
-             require_user_consent, std::string() /* key_name_for_spkac */,
-             base::Bind(&EPKPChallengeUserKey::PrepareKeyCallback,
-                        base::Unretained(this), challenge, register_key));
-}
-
-void EPKPChallengeUserKey::PrepareKeyCallback(const std::string& challenge,
-                                              bool register_key,
-                                              PrepareKeyResult result) {
-  if (result != PREPARE_KEY_OK) {
-    callback_.Run(false,
-                  base::StringPrintf(kGetCertificateFailedError, result));
-    return;
-  }
-
-  // Everything is checked. Sign the challenge.
-  async_caller_->TpmAttestationSignEnterpriseChallenge(
-      chromeos::attestation::KEY_USER,
-      cryptohome::Identification(GetAccountId()),
-      chromeos::attestation::kEnterpriseUserKey, GetUserEmail(), GetDeviceId(),
-      register_key ? chromeos::attestation::CHALLENGE_INCLUDE_SIGNED_PUBLIC_KEY
-                   : chromeos::attestation::CHALLENGE_OPTION_NONE,
-      challenge, std::string() /* key_name_for_spkac */,
-      base::Bind(&EPKPChallengeUserKey::SignChallengeCallback,
-                 base::Unretained(this), register_key));
-}
-
-void EPKPChallengeUserKey::SignChallengeCallback(bool register_key,
-                                                 bool success,
-                                                 const std::string& response) {
-  if (!success) {
-    callback_.Run(false, kSignChallengeFailedError);
-    return;
-  }
-
-  if (register_key) {
-    async_caller_->TpmAttestationRegisterKey(
-        chromeos::attestation::KEY_USER,
-        cryptohome::Identification(GetAccountId()),
-        chromeos::attestation::kEnterpriseUserKey,
-        base::Bind(&EPKPChallengeUserKey::RegisterKeyCallback,
-                   base::Unretained(this), response));
-  } else {
-    RegisterKeyCallback(response, true, cryptohome::MOUNT_ERROR_NONE);
-  }
-}
-
-void EPKPChallengeUserKey::RegisterKeyCallback(
-    const std::string& response,
-    bool success,
-    cryptohome::MountError return_code) {
-  if (!success || return_code != cryptohome::MOUNT_ERROR_NONE) {
-    callback_.Run(false, kKeyRegistrationFailedError);
-    return;
-  }
-  callback_.Run(true, response);
-}
-
-bool EPKPChallengeUserKey::IsRemoteAttestationEnabledForUser() const {
-  return profile_->GetPrefs()->GetBoolean(prefs::kAttestationEnabled);
+  impl_ = chromeos::attestation::TpmChallengeKeyFactory::Create();
+  impl_->Run(type, profile, std::move(callback), challenge, register_key,
+             key_name_for_spkac);
 }
 
 EnterprisePlatformKeysPrivateChallengeMachineKeyFunction::
-    EnterprisePlatformKeysPrivateChallengeMachineKeyFunction()
-    : default_impl_(new EPKPChallengeMachineKey), impl_(default_impl_.get()) {}
-
-EnterprisePlatformKeysPrivateChallengeMachineKeyFunction::
-    EnterprisePlatformKeysPrivateChallengeMachineKeyFunction(
-        EPKPChallengeMachineKey* impl_for_testing)
-    : impl_(impl_for_testing) {}
+    EnterprisePlatformKeysPrivateChallengeMachineKeyFunction() = default;
 
 EnterprisePlatformKeysPrivateChallengeMachineKeyFunction::
     ~EnterprisePlatformKeysPrivateChallengeMachineKeyFunction() = default;
@@ -671,41 +100,41 @@
   std::unique_ptr<api_epkp::ChallengeMachineKey::Params> params(
       api_epkp::ChallengeMachineKey::Params::Create(*args_));
   EXTENSION_FUNCTION_VALIDATE(params);
-  ChallengeKeyCallback callback =
+  chromeos::attestation::TpmChallengeKeyCallback callback =
       base::Bind(&EnterprisePlatformKeysPrivateChallengeMachineKeyFunction::
                      OnChallengedKey,
                  this);
+
+  std::string challenge;
+  if (!base::Base64Decode(params->challenge, &challenge)) {
+    return RespondNow(Error(EPKPChallengeKey::kChallengeBadBase64Error));
+  }
+
   // base::Unretained is safe on impl_ since its life-cycle matches |this| and
   // |callback| holds a reference to |this|.
-  base::Closure task = base::Bind(&EPKPChallengeMachineKey::DecodeAndRun,
-                                  base::Unretained(impl_),
-                                  scoped_refptr<ExtensionFunction>(this),
-                                  callback, params->challenge, false);
-  base::PostTask(FROM_HERE, {content::BrowserThread::UI}, task);
+  base::OnceClosure task = base::BindOnce(
+      &EPKPChallengeKey::Run, base::Unretained(&impl_),
+      chromeos::attestation::KEY_DEVICE, scoped_refptr<ExtensionFunction>(this),
+      std::move(callback), challenge,
+      /*register_key=*/false);
+  base::PostTask(FROM_HERE, {content::BrowserThread::UI}, std::move(task));
   return RespondLater();
 }
 
 void EnterprisePlatformKeysPrivateChallengeMachineKeyFunction::OnChallengedKey(
-    bool success,
-    const std::string& data) {
-  if (success) {
+    const chromeos::attestation::TpmChallengeKeyResult& result) {
+  if (result.is_success) {
     std::string encoded_response;
-    base::Base64Encode(data, &encoded_response);
+    base::Base64Encode(result.data, &encoded_response);
     Respond(ArgumentList(
         api_epkp::ChallengeMachineKey::Results::Create(encoded_response)));
   } else {
-    Respond(Error(data));
+    Respond(Error(result.error_message));
   }
 }
 
 EnterprisePlatformKeysPrivateChallengeUserKeyFunction::
-    EnterprisePlatformKeysPrivateChallengeUserKeyFunction()
-    : default_impl_(new EPKPChallengeUserKey), impl_(default_impl_.get()) {}
-
-EnterprisePlatformKeysPrivateChallengeUserKeyFunction::
-    EnterprisePlatformKeysPrivateChallengeUserKeyFunction(
-        EPKPChallengeUserKey* impl_for_testing)
-    : impl_(impl_for_testing) {}
+    EnterprisePlatformKeysPrivateChallengeUserKeyFunction() = default;
 
 EnterprisePlatformKeysPrivateChallengeUserKeyFunction::
     ~EnterprisePlatformKeysPrivateChallengeUserKeyFunction() = default;
@@ -715,29 +144,34 @@
   std::unique_ptr<api_epkp::ChallengeUserKey::Params> params(
       api_epkp::ChallengeUserKey::Params::Create(*args_));
   EXTENSION_FUNCTION_VALIDATE(params);
-  ChallengeKeyCallback callback = base::Bind(
+  chromeos::attestation::TpmChallengeKeyCallback callback = base::Bind(
       &EnterprisePlatformKeysPrivateChallengeUserKeyFunction::OnChallengedKey,
       this);
+
+  std::string challenge;
+  if (!base::Base64Decode(params->challenge, &challenge)) {
+    return RespondNow(Error(EPKPChallengeKey::kChallengeBadBase64Error));
+  }
+
   // base::Unretained is safe on impl_ since its life-cycle matches |this| and
   // |callback| holds a reference to |this|.
-  base::Closure task =
-      base::Bind(&EPKPChallengeUserKey::DecodeAndRun, base::Unretained(impl_),
-                 scoped_refptr<ExtensionFunction>(this), callback,
-                 params->challenge, params->register_key);
-  base::PostTask(FROM_HERE, {content::BrowserThread::UI}, task);
+  base::OnceClosure task = base::BindOnce(
+      &EPKPChallengeKey::Run, base::Unretained(&impl_),
+      chromeos::attestation::KEY_USER, scoped_refptr<ExtensionFunction>(this),
+      std::move(callback), challenge, params->register_key);
+  base::PostTask(FROM_HERE, {content::BrowserThread::UI}, std::move(task));
   return RespondLater();
 }
 
 void EnterprisePlatformKeysPrivateChallengeUserKeyFunction::OnChallengedKey(
-    bool success,
-    const std::string& data) {
-  if (success) {
+    const chromeos::attestation::TpmChallengeKeyResult& result) {
+  if (result.is_success) {
     std::string encoded_response;
-    base::Base64Encode(data, &encoded_response);
+    base::Base64Encode(result.data, &encoded_response);
     Respond(ArgumentList(
         api_epkp::ChallengeUserKey::Results::Create(encoded_response)));
   } else {
-    Respond(Error(data));
+    Respond(Error(result.error_message));
   }
 }
 
diff --git a/chrome/browser/extensions/api/enterprise_platform_keys_private/enterprise_platform_keys_private_api.h b/chrome/browser/extensions/api/enterprise_platform_keys_private/enterprise_platform_keys_private_api.h
index e8e9fa0..8223117 100644
--- a/chrome/browser/extensions/api/enterprise_platform_keys_private/enterprise_platform_keys_private_api.h
+++ b/chrome/browser/extensions/api/enterprise_platform_keys_private/enterprise_platform_keys_private_api.h
@@ -11,272 +11,60 @@
 #include <memory>
 #include <string>
 
-#include "base/callback.h"
-#include "base/compiler_specific.h"
-#include "base/optional.h"
-#include "chrome/common/extensions/api/enterprise_platform_keys_private.h"
-#include "chromeos/attestation/attestation_flow.h"
+#include "chrome/browser/chromeos/attestation/tpm_challenge_key.h"
 #include "chromeos/dbus/constants/attestation_constants.h"
-#include "chromeos/dbus/cryptohome/cryptohome_client.h"
-#include "components/account_id/account_id.h"
 #include "extensions/browser/extension_function.h"
 #include "extensions/common/extension.h"
-#include "third_party/cros_system_api/dbus/service_constants.h"
 
 class Profile;
 
-namespace chromeos {
-class CryptohomeClient;
-class InstallAttributes;
-}
-
-namespace cryptohome {
-class AsyncMethodCaller;
-}
-
 namespace user_prefs {
 class PrefRegistrySyncable;
 }
 
 namespace extensions {
 
-// A callback for challenge key operations. If the operation succeeded,
-// |success| is true and |data| is the challenge response. Otherwise, |success|
-// is false and |data| is an error message.
-using ChallengeKeyCallback =
-    base::Callback<void(bool success, const std::string& data)>;
-
-class EPKPChallengeKeyBase {
+class EPKPChallengeKey {
  public:
-  static const char kChallengeBadBase64Error[];
-  static const char kDevicePolicyDisabledError[];
   static const char kExtensionNotWhitelistedError[];
-  static const char kResponseBadBase64Error[];
-  static const char kSignChallengeFailedError[];
-  static const char kUserNotManaged[];
-
- protected:
-  enum PrepareKeyResult {
-    PREPARE_KEY_OK = 0,
-    PREPARE_KEY_DBUS_ERROR,
-    PREPARE_KEY_USER_REJECTED,
-    PREPARE_KEY_GET_CERTIFICATE_FAILED,
-    PREPARE_KEY_RESET_REQUIRED,
-    PREPARE_KEY_ATTESTATION_UNSUPPORTED
-  };
-
-  EPKPChallengeKeyBase();
-  EPKPChallengeKeyBase(
-      chromeos::CryptohomeClient* cryptohome_client,
-      cryptohome::AsyncMethodCaller* async_caller,
-      chromeos::attestation::AttestationFlow* attestation_flow,
-      chromeos::InstallAttributes* install_attributes);
-  virtual ~EPKPChallengeKeyBase();
-
-  // Returns a trusted value from CroSettings indicating if the device
-  // attestation is enabled.
-  void GetDeviceAttestationEnabled(
-      const base::Callback<void(bool)>& callback) const;
-
-  // Returns true if the device is enterprise managed.
-  bool IsEnterpriseDevice() const;
-
-  // Returns true if the extension is white-listed in the user policy.
-  bool IsExtensionWhitelisted() const;
-
-  // Returns true if the user is managed and is affiliated with the domain the
-  // device is enrolled to.
-  bool IsUserAffiliated() const;
-
-  // Returns the enterprise domain the device is enrolled to.
-  std::string GetEnterpriseDomain() const;
-
-  // Returns the user email.
-  std::string GetUserEmail() const;
-
-  // Returns account id.
-  AccountId GetAccountId() const;
-
-  // Returns the enterprise virtual device ID.
-  std::string GetDeviceId() const;
-
-  // Prepares the key for signing. It will first check if a new key should be
-  // generated, i.e. |key_name_for_spkac| is not empty or the key doesn't
-  // exist and, if necessary, call AttestationFlow::GetCertificate() to get a
-  // new one. If require_user_consent is true, it will explicitly ask for user
-  // consent before calling GetCertificate().
-  void PrepareKey(
-      chromeos::attestation::AttestationKeyType key_type,
-      const AccountId& account_id,
-      const std::string& key_name,
-      chromeos::attestation::AttestationCertificateProfile certificate_profile,
-      bool require_user_consent,
-      const std::string& key_name_for_spkac,
-      const base::Callback<void(PrepareKeyResult)>& callback);
-
-  chromeos::CryptohomeClient* cryptohome_client_;
-  cryptohome::AsyncMethodCaller* async_caller_;
-  chromeos::attestation::AttestationFlow* attestation_flow_;
-  std::unique_ptr<chromeos::attestation::AttestationFlow>
-      default_attestation_flow_;
-  ChallengeKeyCallback callback_;
-  Profile* profile_;
-  scoped_refptr<const Extension> extension_;
-
- private:
-  // Holds the context of a PrepareKey() operation.
-  struct PrepareKeyContext {
-    PrepareKeyContext(chromeos::attestation::AttestationKeyType key_type,
-                      const AccountId& account_id,
-                      const std::string& key_name,
-                      chromeos::attestation::AttestationCertificateProfile
-                          certificate_profile,
-                      bool require_user_consent,
-                      const std::string& key_name_for_spkac,
-                      const base::Callback<void(PrepareKeyResult)>& callback);
-    PrepareKeyContext(const PrepareKeyContext& other);
-    ~PrepareKeyContext();
-
-    chromeos::attestation::AttestationKeyType key_type;
-    const AccountId account_id;
-    const std::string key_name;
-    chromeos::attestation::AttestationCertificateProfile certificate_profile;
-    bool require_user_consent;
-    std::string key_name_for_spkac;
-    const base::Callback<void(PrepareKeyResult)> callback;
-  };
-
-  void IsAttestationPreparedCallback(const PrepareKeyContext& context,
-                                     base::Optional<bool> result);
-  void PrepareKeyErrorHandlerCallback(const PrepareKeyContext& context,
-                                      base::Optional<bool> is_tpm_enabled);
-  void DoesKeyExistCallback(const PrepareKeyContext& context,
-                            base::Optional<bool> result);
-  void AskForUserConsent(const base::Callback<void(bool)>& callback) const;
-  void AskForUserConsentCallback(
-      const PrepareKeyContext& context,
-      bool result);
-  void GetCertificateCallback(
-      const base::Callback<void(PrepareKeyResult)>& callback,
-      chromeos::attestation::AttestationStatus status,
-      const std::string& pem_certificate_chain);
-
-  chromeos::InstallAttributes* install_attributes_;
-};
-
-class EPKPChallengeMachineKey : public EPKPChallengeKeyBase {
- public:
-  static const char kGetCertificateFailedError[];
-  static const char kKeyRegistrationFailedError[];
-  static const char kNonEnterpriseDeviceError[];
-
-  EPKPChallengeMachineKey();
-  EPKPChallengeMachineKey(
-      chromeos::CryptohomeClient* cryptohome_client,
-      cryptohome::AsyncMethodCaller* async_caller,
-      chromeos::attestation::AttestationFlow* attestation_flow,
-      chromeos::InstallAttributes* install_attributes);
-  ~EPKPChallengeMachineKey() override;
-
-  // Asynchronously run the flow to challenge a machine key in the |caller|
-  // context.
-  void Run(scoped_refptr<ExtensionFunction> caller,
-           const ChallengeKeyCallback& callback,
-           const std::string& encoded_challenge,
-           bool register_key);
-
-  // Like |Run| but expects a Base64 |encoded_challenge|.
-  void DecodeAndRun(scoped_refptr<ExtensionFunction> caller,
-                    const ChallengeKeyCallback& callback,
-                    const std::string& encoded_challenge,
-                    bool register_key);
-
- private:
-  static const char kKeyName[];
-
-  void GetDeviceAttestationEnabledCallback(const std::string& challenge,
-                                           bool register_key,
-                                           bool enabled);
-  void PrepareKeyCallback(const std::string& challenge,
-                          bool register_key,
-                          const std::string& key_name_for_spkac,
-                          PrepareKeyResult result);
-  void SignChallengeCallback(bool register_key,
-                             bool success,
-                             const std::string& response);
-  void RegisterKeyCallback(const std::string& response,
-                           bool success,
-                           cryptohome::MountError return_code);
-};
-
-class EPKPChallengeUserKey : public EPKPChallengeKeyBase {
- public:
-  static const char kGetCertificateFailedError[];
-  static const char kKeyRegistrationFailedError[];
-  static const char kUserKeyNotAvailable[];
-  static const char kUserPolicyDisabledError[];
-
-  EPKPChallengeUserKey();
-  EPKPChallengeUserKey(
-      chromeos::CryptohomeClient* cryptohome_client,
-      cryptohome::AsyncMethodCaller* async_caller,
-      chromeos::attestation::AttestationFlow* attestation_flow,
-      chromeos::InstallAttributes* install_attributes);
-  ~EPKPChallengeUserKey() override;
+  static const char kChallengeBadBase64Error[];
+  EPKPChallengeKey();
+  EPKPChallengeKey(const EPKPChallengeKey&) = delete;
+  EPKPChallengeKey& operator=(const EPKPChallengeKey&) = delete;
+  ~EPKPChallengeKey();
 
   static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry);
 
-  // Asynchronously run the flow to challenge a user key in the |caller|
+  // Asynchronously run the flow to challenge a key in the |caller|
   // context.
-  void Run(scoped_refptr<ExtensionFunction> caller,
-           const ChallengeKeyCallback& callback,
+  void Run(chromeos::attestation::AttestationKeyType type,
+           scoped_refptr<ExtensionFunction> caller,
+           chromeos::attestation::TpmChallengeKeyCallback callback,
            const std::string& challenge,
            bool register_key);
 
-  // Like |Run| but expects a Base64 |encoded_challenge|.
-  void DecodeAndRun(scoped_refptr<ExtensionFunction> caller,
-                    const ChallengeKeyCallback& callback,
-                    const std::string& encoded_challenge,
-                    bool register_key);
-
  private:
-  static const char kKeyName[];
+  // Check if the extension is whitelisted in the user policy.
+  bool IsExtensionWhitelisted(Profile* profile,
+                              scoped_refptr<const Extension> extension);
 
-  void GetDeviceAttestationEnabledCallback(const std::string& challenge,
-                                           bool register_key,
-                                           bool require_user_consent,
-                                           bool enabled);
-  void PrepareKeyCallback(const std::string& challenge,
-                          bool register_key,
-                          PrepareKeyResult result);
-  void SignChallengeCallback(bool register_key,
-                             bool success,
-                             const std::string& response);
-  void RegisterKeyCallback(const std::string& response,
-                           bool success,
-                           cryptohome::MountError return_code);
-
-  bool IsRemoteAttestationEnabledForUser() const;
+  std::unique_ptr<chromeos::attestation::TpmChallengeKey> impl_;
 };
 
 class EnterprisePlatformKeysPrivateChallengeMachineKeyFunction
     : public ExtensionFunction {
  public:
   EnterprisePlatformKeysPrivateChallengeMachineKeyFunction();
-  explicit EnterprisePlatformKeysPrivateChallengeMachineKeyFunction(
-      EPKPChallengeMachineKey* impl_for_testing);
 
  private:
   ~EnterprisePlatformKeysPrivateChallengeMachineKeyFunction() override;
   ResponseAction Run() override;
 
-  // Called when the challenge operation is complete. If the operation succeeded
-  // |success| will be true and |data| will contain the challenge response data.
-  // Otherwise |success| will be false and |data| is an error message.
-  void OnChallengedKey(bool success, const std::string& data);
+  // Called when the challenge operation is complete.
+  void OnChallengedKey(
+      const chromeos::attestation::TpmChallengeKeyResult& result);
 
-  std::unique_ptr<EPKPChallengeMachineKey> default_impl_;
-  EPKPChallengeMachineKey* impl_;
+  EPKPChallengeKey impl_;
 
   DECLARE_EXTENSION_FUNCTION(
       "enterprise.platformKeysPrivate.challengeMachineKey",
@@ -287,20 +75,16 @@
     : public ExtensionFunction {
  public:
   EnterprisePlatformKeysPrivateChallengeUserKeyFunction();
-  explicit EnterprisePlatformKeysPrivateChallengeUserKeyFunction(
-      EPKPChallengeUserKey* impl_for_testing);
 
  private:
   ~EnterprisePlatformKeysPrivateChallengeUserKeyFunction() override;
   ResponseAction Run() override;
 
-  // Called when the challenge operation is complete. If the operation succeeded
-  // |success| will be true and |data| will contain the challenge response data.
-  // Otherwise |success| will be false and |data| is an error message.
-  void OnChallengedKey(bool success, const std::string& data);
+  // Called when the challenge operation is complete.
+  void OnChallengedKey(
+      const chromeos::attestation::TpmChallengeKeyResult& result);
 
-  std::unique_ptr<EPKPChallengeUserKey> default_impl_;
-  EPKPChallengeUserKey* impl_;
+  EPKPChallengeKey impl_;
 
   DECLARE_EXTENSION_FUNCTION("enterprise.platformKeysPrivate.challengeUserKey",
                              ENTERPRISE_PLATFORMKEYSPRIVATE_CHALLENGEUSERKEY)
diff --git a/chrome/browser/extensions/api/enterprise_platform_keys_private/enterprise_platform_keys_private_api_unittest.cc b/chrome/browser/extensions/api/enterprise_platform_keys_private/enterprise_platform_keys_private_api_unittest.cc
index 6230610..db219faef 100644
--- a/chrome/browser/extensions/api/enterprise_platform_keys_private/enterprise_platform_keys_private_api_unittest.cc
+++ b/chrome/browser/extensions/api/enterprise_platform_keys_private/enterprise_platform_keys_private_api_unittest.cc
@@ -4,206 +4,59 @@
 
 #include "chrome/browser/extensions/api/enterprise_platform_keys_private/enterprise_platform_keys_private_api.h"
 
-#include <string>
+#include <utility>
 
-#include "base/bind.h"
-#include "base/location.h"
-#include "base/memory/ptr_util.h"
-#include "base/strings/stringprintf.h"
-#include "base/threading/thread_task_runner_handle.h"
 #include "base/values.h"
+#include "chrome/browser/chromeos/attestation/mock_tpm_challenge_key.h"
 #include "chrome/browser/chromeos/login/users/fake_chrome_user_manager.h"
-#include "chrome/browser/chromeos/profiles/profile_helper.h"
-#include "chrome/browser/chromeos/settings/cros_settings.h"
-#include "chrome/browser/chromeos/settings/scoped_cros_settings_test_helper.h"
 #include "chrome/browser/extensions/extension_function_test_utils.h"
 #include "chrome/browser/signin/identity_manager_factory.h"
-#include "chrome/browser/ui/browser.h"
-#include "chrome/common/chrome_constants.h"
 #include "chrome/common/pref_names.h"
 #include "chrome/test/base/browser_with_test_window_test.h"
-#include "chrome/test/base/testing_browser_process.h"
 #include "chrome/test/base/testing_profile_manager.h"
-#include "chromeos/attestation/mock_attestation_flow.h"
-#include "chromeos/cryptohome/async_method_caller.h"
-#include "chromeos/cryptohome/cryptohome_parameters.h"
-#include "chromeos/cryptohome/mock_async_method_caller.h"
-#include "chromeos/dbus/constants/attestation_constants.h"
-#include "chromeos/dbus/cryptohome/fake_cryptohome_client.h"
-#include "chromeos/tpm/stub_install_attributes.h"
-#include "components/account_id/account_id.h"
-#include "components/policy/core/common/cloud/cloud_policy_constants.h"
-#include "components/prefs/pref_service.h"
 #include "components/signin/public/identity_manager/identity_manager.h"
 #include "components/signin/public/identity_manager/identity_test_utils.h"
 #include "components/user_manager/scoped_user_manager.h"
 #include "extensions/common/extension_builder.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
-#include "third_party/cros_system_api/dbus/service_constants.h"
 
-using testing::_;
-using testing::Invoke;
 using testing::NiceMock;
-using testing::Return;
-using testing::WithArgs;
 
 namespace utils = extension_function_test_utils;
 
 namespace extensions {
 namespace {
 
-// Certificate errors as reported to the calling extension.
-const int kDBusError = 1;
-const int kUserRejected = 2;
-const int kGetCertificateFailed = 3;
-const int kResetRequired = 4;
-const int kPrepareKeyAttestationUnsupported = 5;
-
 const char kUserEmail[] = "test@google.com";
 
-void RegisterKeyCallbackTrue(
-    chromeos::attestation::AttestationKeyType key_type,
-    const cryptohome::Identification& user_id,
-    const std::string& key_name,
-    const cryptohome::AsyncMethodCaller::Callback& callback) {
-  base::ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE, base::BindOnce(callback, true, cryptohome::MOUNT_ERROR_NONE));
-}
-
-void RegisterKeyCallbackFalse(
-    chromeos::attestation::AttestationKeyType key_type,
-    const cryptohome::Identification& user_id,
-    const std::string& key_name,
-    const cryptohome::AsyncMethodCaller::Callback& callback) {
-  base::ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE, base::BindOnce(callback, false, cryptohome::MOUNT_ERROR_NONE));
-}
-
-void SignChallengeCallbackTrue(
-    chromeos::attestation::AttestationKeyType key_type,
-    const cryptohome::Identification& user_id,
-    const std::string& key_name,
-    const std::string& domain,
-    const std::string& device_id,
-    chromeos::attestation::AttestationChallengeOptions options,
-    const std::string& challenge,
-    const std::string& key_name_for_spkac,
-    const cryptohome::AsyncMethodCaller::DataCallback& callback) {
-  base::ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE, base::BindOnce(callback, true, "response"));
-}
-
-void SignChallengeCallbackFalse(
-    chromeos::attestation::AttestationKeyType key_type,
-    const cryptohome::Identification& user_id,
-    const std::string& key_name,
-    const std::string& domain,
-    const std::string& device_id,
-    chromeos::attestation::AttestationChallengeOptions options,
-    const std::string& challenge,
-    const std::string& key_name_for_spkac,
-    const cryptohome::AsyncMethodCaller::DataCallback& callback) {
-  base::ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE, base::BindOnce(callback, false, ""));
-}
-
-void GetCertificateCallbackTrue(
-    chromeos::attestation::AttestationCertificateProfile certificate_profile,
-    const AccountId& account_id,
-    const std::string& request_origin,
-    bool force_new_key,
-    const std::string& key_name,
-    const chromeos::attestation::AttestationFlow::CertificateCallback&
-        callback) {
-  base::ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE,
-      base::BindRepeating(callback, chromeos::attestation::ATTESTATION_SUCCESS,
-                          "certificate"));
-}
-
-void GetCertificateCallbackUnspecifiedFailure(
-    chromeos::attestation::AttestationCertificateProfile certificate_profile,
-    const AccountId& account_id,
-    const std::string& request_origin,
-    bool force_new_key,
-    const std::string& key_name,
-    const chromeos::attestation::AttestationFlow::CertificateCallback&
-        callback) {
-  base::ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE,
-      base::BindRepeating(
-          callback, chromeos::attestation::ATTESTATION_UNSPECIFIED_FAILURE,
-          ""));
-}
-
-void GetCertificateCallbackBadRequestFailure(
-    chromeos::attestation::AttestationCertificateProfile certificate_profile,
-    const AccountId& account_id,
-    const std::string& request_origin,
-    bool force_new_key,
-    const std::string& key_name,
-    const chromeos::attestation::AttestationFlow::CertificateCallback&
-        callback) {
-  base::ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE,
-      base::BindRepeating(
-          callback,
-          chromeos::attestation::ATTESTATION_SERVER_BAD_REQUEST_FAILURE, ""));
-}
-
 class EPKPChallengeKeyTestBase : public BrowserWithTestWindowTest {
- public:
-  enum class ProfileType { USER_PROFILE, SIGNIN_PROFILE };
-
  protected:
-  explicit EPKPChallengeKeyTestBase(ProfileType profile_type)
-      : settings_helper_(false),
-        profile_type_(profile_type),
-        fake_user_manager_(new chromeos::FakeChromeUserManager),
+  EPKPChallengeKeyTestBase()
+      : fake_user_manager_(new chromeos::FakeChromeUserManager()),
         user_manager_enabler_(base::WrapUnique(fake_user_manager_)) {
-    // Create the extension.
-    extension_ = CreateExtension();
-
-    // Set up the default behavior of mocks.
-    ON_CALL(mock_async_method_caller_, TpmAttestationRegisterKey(_, _, _, _))
-        .WillByDefault(Invoke(RegisterKeyCallbackTrue));
-    ON_CALL(mock_async_method_caller_,
-            TpmAttestationSignEnterpriseChallenge(_, _, _, _, _, _, _, _, _))
-        .WillByDefault(Invoke(SignChallengeCallbackTrue));
-    ON_CALL(mock_attestation_flow_, GetCertificate(_, _, _, _, _, _))
-        .WillByDefault(Invoke(GetCertificateCallbackTrue));
-
-    stub_install_attributes_.SetCloudManaged("google.com", "device_id");
-
-    settings_helper_.ReplaceDeviceSettingsProviderWithStub();
-    settings_helper_.SetBoolean(chromeos::kDeviceAttestationEnabled, true);
+    extension_ = ExtensionBuilder("Test").Build();
   }
 
   void SetUp() override {
     BrowserWithTestWindowTest::SetUp();
-    if (profile_type_ == ProfileType::USER_PROFILE) {
-      // Set the user preferences.
-      prefs_ = browser()->profile()->GetPrefs();
-      base::ListValue whitelist;
-      whitelist.AppendString(extension_->id());
-      prefs_->Set(prefs::kAttestationExtensionWhitelist, whitelist);
+    prefs_ = browser()->profile()->GetPrefs();
+    SetAuthenticatedUser();
+  }
 
-      SetAuthenticatedUser();
-    }
+  void SetMockTpmChallenger() {
+    auto mock_tpm_challenge_key = std::make_unique<
+        NiceMock<chromeos::attestation::MockTpmChallengeKey>>();
+    mock_tpm_challenge_key->EnableFake();
+    chromeos::attestation::TpmChallengeKeyFactory::SetForTesting(
+        std::move(mock_tpm_challenge_key));
   }
 
   // This will be called by BrowserWithTestWindowTest::SetUp();
   TestingProfile* CreateProfile() override {
-    switch (profile_type_) {
-      case ProfileType::USER_PROFILE:
-        fake_user_manager_->AddUserWithAffiliation(
-            AccountId::FromUserEmail(kUserEmail), true);
-        return profile_manager()->CreateTestingProfile(kUserEmail);
-
-      case ProfileType::SIGNIN_PROFILE:
-        return profile_manager()->CreateTestingProfile(chrome::kInitialProfile);
-    }
+    fake_user_manager_->AddUserWithAffiliation(
+        AccountId::FromUserEmail(kUserEmail), true);
+    return profile_manager()->CreateTestingProfile(kUserEmail);
   }
 
   // Derived classes can override this method to set the required authenticated
@@ -214,432 +67,90 @@
     signin::MakePrimaryAccountAvailable(identity_manager, kUserEmail);
   }
 
-  chromeos::FakeCryptohomeClient cryptohome_client_;
-  NiceMock<cryptohome::MockAsyncMethodCaller> mock_async_method_caller_;
-  NiceMock<chromeos::attestation::MockAttestationFlow> mock_attestation_flow_;
-  chromeos::ScopedCrosSettingsTestHelper settings_helper_;
   scoped_refptr<const Extension> extension_;
-  chromeos::StubInstallAttributes stub_install_attributes_;
-  ProfileType profile_type_;
   // fake_user_manager_ is owned by user_manager_enabler_.
-  chromeos::FakeChromeUserManager* fake_user_manager_;
+  chromeos::FakeChromeUserManager* fake_user_manager_ = nullptr;
   user_manager::ScopedUserManager user_manager_enabler_;
   PrefService* prefs_ = nullptr;
-
- private:
-  scoped_refptr<const Extension> CreateExtension() {
-    switch (profile_type_) {
-      case ProfileType::USER_PROFILE:
-        return ExtensionBuilder("Test").Build();
-
-      case ProfileType::SIGNIN_PROFILE:
-        return ExtensionBuilder("Test", ExtensionBuilder::Type::PLATFORM_APP)
-            .SetLocation(Manifest::Location::EXTERNAL_POLICY)
-            .Build();
-    }
-  }
 };
 
 class EPKPChallengeMachineKeyTest : public EPKPChallengeKeyTestBase {
  protected:
-  static const char kArgs[];
+  static const char kFuncArgs[];
 
-  explicit EPKPChallengeMachineKeyTest(
-      ProfileType profile_type = ProfileType::USER_PROFILE)
-      : EPKPChallengeKeyTestBase(profile_type),
-        impl_(&cryptohome_client_,
-              &mock_async_method_caller_,
-              &mock_attestation_flow_,
-              &stub_install_attributes_),
-        func_(new EnterprisePlatformKeysPrivateChallengeMachineKeyFunction(
-            &impl_)) {
+  EPKPChallengeMachineKeyTest()
+      : func_(new EnterprisePlatformKeysPrivateChallengeMachineKeyFunction()) {
     func_->set_extension(extension_.get());
   }
 
-  // Returns an error string for the given code.
-  std::string GetCertificateError(int error_code) {
-    return base::StringPrintf(
-        EPKPChallengeMachineKey::kGetCertificateFailedError,
-        error_code);
-  }
-
-  EPKPChallengeMachineKey impl_;
   scoped_refptr<EnterprisePlatformKeysPrivateChallengeMachineKeyFunction> func_;
 };
 
 // Base 64 encoding of 'challenge'.
-const char EPKPChallengeMachineKeyTest::kArgs[] = "[\"Y2hhbGxlbmdl\"]";
-
-TEST_F(EPKPChallengeMachineKeyTest, ChallengeBadBase64) {
-  EXPECT_EQ(EPKPChallengeKeyBase::kChallengeBadBase64Error,
-            utils::RunFunctionAndReturnError(
-                func_.get(), "[\"****\"]", browser()));
-}
-
-TEST_F(EPKPChallengeMachineKeyTest, NonEnterpriseDevice) {
-  stub_install_attributes_.SetConsumerOwned();
-
-  EXPECT_EQ(EPKPChallengeMachineKey::kNonEnterpriseDeviceError,
-            utils::RunFunctionAndReturnError(func_.get(), kArgs, browser()));
-}
+const char EPKPChallengeMachineKeyTest::kFuncArgs[] = "[\"Y2hhbGxlbmdl\"]";
 
 TEST_F(EPKPChallengeMachineKeyTest, ExtensionNotWhitelisted) {
   base::ListValue empty_whitelist;
   prefs_->Set(prefs::kAttestationExtensionWhitelist, empty_whitelist);
 
-  EXPECT_EQ(EPKPChallengeKeyBase::kExtensionNotWhitelistedError,
-            utils::RunFunctionAndReturnError(func_.get(), kArgs, browser()));
+  EXPECT_EQ(
+      EPKPChallengeKey::kExtensionNotWhitelistedError,
+      utils::RunFunctionAndReturnError(func_.get(), kFuncArgs, browser()));
 }
 
-TEST_F(EPKPChallengeMachineKeyTest, DevicePolicyDisabled) {
-  settings_helper_.SetBoolean(chromeos::kDeviceAttestationEnabled, false);
+TEST_F(EPKPChallengeMachineKeyTest, Success) {
+  SetMockTpmChallenger();
 
-  EXPECT_EQ(EPKPChallengeKeyBase::kDevicePolicyDisabledError,
-            utils::RunFunctionAndReturnError(func_.get(), kArgs, browser()));
-}
-
-TEST_F(EPKPChallengeMachineKeyTest, DoesKeyExistDbusFailed) {
-  cryptohome_client_.set_tpm_attestation_does_key_exist_should_succeed(false);
-
-  EXPECT_EQ(GetCertificateError(kDBusError),
-            utils::RunFunctionAndReturnError(func_.get(), kArgs, browser()));
-}
-
-TEST_F(EPKPChallengeMachineKeyTest, GetCertificateFailed) {
-  EXPECT_CALL(mock_attestation_flow_, GetCertificate(_, _, _, _, _, _))
-      .WillRepeatedly(Invoke(GetCertificateCallbackUnspecifiedFailure));
-
-  EXPECT_EQ(GetCertificateError(kGetCertificateFailed),
-            utils::RunFunctionAndReturnError(func_.get(), kArgs, browser()));
-}
-
-TEST_F(EPKPChallengeMachineKeyTest, SignChallengeFailed) {
-  EXPECT_CALL(mock_async_method_caller_,
-              TpmAttestationSignEnterpriseChallenge(_, _, _, _, _, _, _, _, _))
-      .WillRepeatedly(Invoke(SignChallengeCallbackFalse));
-
-  EXPECT_EQ(EPKPChallengeKeyBase::kSignChallengeFailedError,
-            utils::RunFunctionAndReturnError(func_.get(), kArgs, browser()));
-}
-
-TEST_F(EPKPChallengeMachineKeyTest, KeyExists) {
-  cryptohome_client_.SetTpmAttestationDeviceCertificate("attest-ent-machine",
-                                                        std::string());
-  // GetCertificate must not be called if the key exists.
-  EXPECT_CALL(mock_attestation_flow_, GetCertificate(_, _, _, _, _, _))
-      .Times(0);
-
-  EXPECT_TRUE(utils::RunFunction(func_.get(), kArgs, browser(),
-                                 extensions::api_test_utils::NONE));
-}
-
-TEST_F(EPKPChallengeMachineKeyTest, AttestationNotPrepared) {
-  cryptohome_client_.set_tpm_attestation_is_prepared(false);
-
-  EXPECT_EQ(GetCertificateError(kResetRequired),
-            utils::RunFunctionAndReturnError(func_.get(), kArgs, browser()));
-}
-
-// Test that we get proper error message in case we don't have TPM.
-TEST_F(EPKPChallengeMachineKeyTest, AttestationUnsupported) {
-  cryptohome_client_.set_tpm_attestation_is_prepared(false);
-  cryptohome_client_.set_tpm_is_enabled(false);
-
-  EXPECT_EQ(GetCertificateError(kPrepareKeyAttestationUnsupported),
-            utils::RunFunctionAndReturnError(func_.get(), kArgs, browser()));
-}
-
-TEST_F(EPKPChallengeMachineKeyTest, AttestationPreparedDbusFailed) {
-  cryptohome_client_.SetServiceIsAvailable(false);
-
-  EXPECT_EQ(GetCertificateError(kDBusError),
-            utils::RunFunctionAndReturnError(func_.get(), kArgs, browser()));
-}
-
-// Tests the API with all profiles types as determined by the test parameter.
-class EPKPChallengeMachineKeyAllProfilesTest
-    : public EPKPChallengeMachineKeyTest,
-      public ::testing::WithParamInterface<
-          EPKPChallengeKeyTestBase::ProfileType> {
- protected:
-  EPKPChallengeMachineKeyAllProfilesTest()
-      : EPKPChallengeMachineKeyTest(GetParam()) {}
-};
-
-TEST_P(EPKPChallengeMachineKeyAllProfilesTest, Success) {
-  // GetCertificate must be called exactly once.
-  EXPECT_CALL(mock_attestation_flow_,
-              GetCertificate(
-                  chromeos::attestation::PROFILE_ENTERPRISE_MACHINE_CERTIFICATE,
-                  _, _, _, _, _))
-      .Times(1);
-  // SignEnterpriseChallenge must be called exactly once.
-  EXPECT_CALL(mock_async_method_caller_,
-              TpmAttestationSignEnterpriseChallenge(
-                  chromeos::attestation::KEY_DEVICE,
-                  cryptohome::Identification(), "attest-ent-machine",
-                  "google.com", "device_id", _, "challenge", _, _))
-      .Times(1);
+  base::ListValue whitelist;
+  whitelist.AppendString(extension_->id());
+  prefs_->Set(prefs::kAttestationExtensionWhitelist, whitelist);
 
   std::unique_ptr<base::Value> value(utils::RunFunctionAndReturnSingleResult(
-      func_.get(), kArgs, browser(), extensions::api_test_utils::NONE));
+      func_.get(), kFuncArgs, browser(), extensions::api_test_utils::NONE));
 
   std::string response;
   value->GetAsString(&response);
   EXPECT_EQ("cmVzcG9uc2U=" /* Base64 encoding of 'response' */, response);
 }
 
-INSTANTIATE_TEST_SUITE_P(
-    AllProfiles,
-    EPKPChallengeMachineKeyAllProfilesTest,
-    ::testing::Values(EPKPChallengeKeyTestBase::ProfileType::USER_PROFILE,
-                      EPKPChallengeKeyTestBase::ProfileType::SIGNIN_PROFILE));
-
 class EPKPChallengeUserKeyTest : public EPKPChallengeKeyTestBase {
  protected:
-  static const char kArgs[];
+  static const char kFuncArgs[];
 
-  explicit EPKPChallengeUserKeyTest(
-      ProfileType profile_type = ProfileType::USER_PROFILE)
-      : EPKPChallengeKeyTestBase(profile_type),
-        impl_(&cryptohome_client_,
-              &mock_async_method_caller_,
-              &mock_attestation_flow_,
-              &stub_install_attributes_),
-        func_(
-            new EnterprisePlatformKeysPrivateChallengeUserKeyFunction(&impl_)) {
+  EPKPChallengeUserKeyTest()
+      : func_(new EnterprisePlatformKeysPrivateChallengeUserKeyFunction()) {
     func_->set_extension(extension_.get());
   }
 
-  void SetUp() override {
-    EPKPChallengeKeyTestBase::SetUp();
-
-    if (profile_type_ == ProfileType::USER_PROFILE) {
-      // Set the user preferences.
-      prefs_->SetBoolean(prefs::kAttestationEnabled, true);
-    }
-  }
-
-  // Returns an error string for the given code.
-  std::string GetCertificateError(int error_code) {
-    return base::StringPrintf(EPKPChallengeUserKey::kGetCertificateFailedError,
-                              error_code);
-  }
-
-  EPKPChallengeUserKey impl_;
   scoped_refptr<EnterprisePlatformKeysPrivateChallengeUserKeyFunction> func_;
 };
 
-// Base 64 encoding of 'challenge'
-const char EPKPChallengeUserKeyTest::kArgs[] = "[\"Y2hhbGxlbmdl\", true]";
-
-TEST_F(EPKPChallengeUserKeyTest, ChallengeBadBase64) {
-  EXPECT_EQ(EPKPChallengeKeyBase::kChallengeBadBase64Error,
-            utils::RunFunctionAndReturnError(
-                func_.get(), "[\"****\", true]", browser()));
-}
-
-TEST_F(EPKPChallengeUserKeyTest, UserPolicyDisabled) {
-  prefs_->SetBoolean(prefs::kAttestationEnabled, false);
-
-  EXPECT_EQ(EPKPChallengeUserKey::kUserPolicyDisabledError,
-            utils::RunFunctionAndReturnError(func_.get(), kArgs, browser()));
-}
+// Base 64 encoding of 'challenge', register_key required.
+const char EPKPChallengeUserKeyTest::kFuncArgs[] = "[\"Y2hhbGxlbmdl\", true]";
 
 TEST_F(EPKPChallengeUserKeyTest, ExtensionNotWhitelisted) {
   base::ListValue empty_whitelist;
   prefs_->Set(prefs::kAttestationExtensionWhitelist, empty_whitelist);
 
-  EXPECT_EQ(EPKPChallengeKeyBase::kExtensionNotWhitelistedError,
-            utils::RunFunctionAndReturnError(func_.get(), kArgs, browser()));
-}
-
-TEST_F(EPKPChallengeUserKeyTest, DevicePolicyDisabled) {
-  settings_helper_.SetBoolean(chromeos::kDeviceAttestationEnabled, false);
-
-  EXPECT_EQ(EPKPChallengeKeyBase::kDevicePolicyDisabledError,
-            utils::RunFunctionAndReturnError(func_.get(), kArgs, browser()));
-}
-
-TEST_F(EPKPChallengeUserKeyTest, DoesKeyExistDbusFailed) {
-  cryptohome_client_.set_tpm_attestation_does_key_exist_should_succeed(false);
-
-  EXPECT_EQ(GetCertificateError(kDBusError),
-            utils::RunFunctionAndReturnError(func_.get(), kArgs, browser()));
-}
-
-TEST_F(EPKPChallengeUserKeyTest, GetCertificateFailedWithUnspecifiedFailure) {
-  EXPECT_CALL(mock_attestation_flow_, GetCertificate(_, _, _, _, _, _))
-      .WillRepeatedly(Invoke(GetCertificateCallbackUnspecifiedFailure));
-
-  EXPECT_EQ(GetCertificateError(kGetCertificateFailed),
-            utils::RunFunctionAndReturnError(func_.get(), kArgs, browser()));
-}
-
-TEST_F(EPKPChallengeUserKeyTest, GetCertificateFailedWithBadRequestFailure) {
-  EXPECT_CALL(mock_attestation_flow_, GetCertificate(_, _, _, _, _, _))
-      .WillRepeatedly(Invoke(GetCertificateCallbackBadRequestFailure));
-
-  EXPECT_EQ(GetCertificateError(kGetCertificateFailed),
-            utils::RunFunctionAndReturnError(func_.get(), kArgs, browser()));
-}
-
-TEST_F(EPKPChallengeUserKeyTest, SignChallengeFailed) {
-  EXPECT_CALL(mock_async_method_caller_,
-              TpmAttestationSignEnterpriseChallenge(_, _, _, _, _, _, _, _, _))
-      .WillRepeatedly(Invoke(SignChallengeCallbackFalse));
-
-  EXPECT_EQ(EPKPChallengeKeyBase::kSignChallengeFailedError,
-            utils::RunFunctionAndReturnError(func_.get(), kArgs, browser()));
-}
-
-TEST_F(EPKPChallengeUserKeyTest, KeyRegistrationFailed) {
-  EXPECT_CALL(mock_async_method_caller_, TpmAttestationRegisterKey(_, _, _, _))
-      .WillRepeatedly(Invoke(RegisterKeyCallbackFalse));
-
-  EXPECT_EQ(EPKPChallengeUserKey::kKeyRegistrationFailedError,
-            utils::RunFunctionAndReturnError(func_.get(), kArgs, browser()));
-}
-
-TEST_F(EPKPChallengeUserKeyTest, KeyExists) {
-  cryptohome_client_.SetTpmAttestationUserCertificate(
-      cryptohome::CreateAccountIdentifierFromAccountId(
-          AccountId::FromUserEmail(kUserEmail)),
-      "attest-ent-user", std::string());
-  // GetCertificate must not be called if the key exists.
-  EXPECT_CALL(mock_attestation_flow_, GetCertificate(_, _, _, _, _, _))
-      .Times(0);
-
-  EXPECT_TRUE(utils::RunFunction(func_.get(), kArgs, browser(),
-                                 extensions::api_test_utils::NONE));
-}
-
-TEST_F(EPKPChallengeUserKeyTest, KeyNotRegistered) {
-  EXPECT_CALL(mock_async_method_caller_, TpmAttestationRegisterKey(_, _, _, _))
-      .Times(0);
-
-  EXPECT_TRUE(utils::RunFunction(func_.get(), "[\"Y2hhbGxlbmdl\", false]",
-                                 browser(), extensions::api_test_utils::NONE));
-}
-
-TEST_F(EPKPChallengeUserKeyTest, PersonalDevice) {
-  stub_install_attributes_.SetConsumerOwned();
-
-  // Currently personal devices are not supported.
-  EXPECT_EQ(GetCertificateError(kUserRejected),
-            utils::RunFunctionAndReturnError(func_.get(), kArgs, browser()));
+  EXPECT_EQ(
+      EPKPChallengeKey::kExtensionNotWhitelistedError,
+      utils::RunFunctionAndReturnError(func_.get(), kFuncArgs, browser()));
 }
 
 TEST_F(EPKPChallengeUserKeyTest, Success) {
-  // GetCertificate must be called exactly once.
-  EXPECT_CALL(
-      mock_attestation_flow_,
-      GetCertificate(chromeos::attestation::PROFILE_ENTERPRISE_USER_CERTIFICATE,
-                     _, _, _, _, _))
-      .Times(1);
-  const AccountId account_id = AccountId::FromUserEmail(kUserEmail);
-  // SignEnterpriseChallenge must be called exactly once.
-  EXPECT_CALL(mock_async_method_caller_,
-              TpmAttestationSignEnterpriseChallenge(
-                  chromeos::attestation::KEY_USER,
-                  cryptohome::Identification(account_id), "attest-ent-user",
-                  cryptohome::Identification(account_id).id(), "device_id", _,
-                  "challenge", _, _))
-      .Times(1);
-  // RegisterKey must be called exactly once.
-  EXPECT_CALL(mock_async_method_caller_,
-              TpmAttestationRegisterKey(chromeos::attestation::KEY_USER,
-                                        cryptohome::Identification(account_id),
-                                        "attest-ent-user", _))
-      .Times(1);
+  SetMockTpmChallenger();
+
+  base::ListValue whitelist;
+  whitelist.AppendString(extension_->id());
+  prefs_->Set(prefs::kAttestationExtensionWhitelist, whitelist);
 
   std::unique_ptr<base::Value> value(utils::RunFunctionAndReturnSingleResult(
-      func_.get(), kArgs, browser(), extensions::api_test_utils::NONE));
+      func_.get(), kFuncArgs, browser(), extensions::api_test_utils::NONE));
 
   std::string response;
   value->GetAsString(&response);
   EXPECT_EQ("cmVzcG9uc2U=" /* Base64 encoding of 'response' */, response);
 }
 
-TEST_F(EPKPChallengeUserKeyTest, AttestationNotPrepared) {
-  cryptohome_client_.set_tpm_attestation_is_prepared(false);
-
-  EXPECT_EQ(GetCertificateError(kResetRequired),
-            utils::RunFunctionAndReturnError(func_.get(), kArgs, browser()));
-}
-
-TEST_F(EPKPChallengeUserKeyTest, AttestationPreparedDbusFailed) {
-  cryptohome_client_.SetServiceIsAvailable(false);
-
-  EXPECT_EQ(GetCertificateError(kDBusError),
-            utils::RunFunctionAndReturnError(func_.get(), kArgs, browser()));
-}
-
-class EPKPChallengeUserKeySigninProfileTest : public EPKPChallengeUserKeyTest {
- protected:
-  EPKPChallengeUserKeySigninProfileTest()
-      : EPKPChallengeUserKeyTest(ProfileType::SIGNIN_PROFILE) {}
-};
-
-TEST_F(EPKPChallengeUserKeySigninProfileTest, UserKeyNotAvailable) {
-  settings_helper_.SetBoolean(chromeos::kDeviceAttestationEnabled, false);
-
-  EXPECT_EQ(EPKPChallengeUserKey::kUserKeyNotAvailable,
-            utils::RunFunctionAndReturnError(func_.get(), kArgs, browser()));
-}
-
-class EPKPChallengeMachineKeyUnmanagedUserTest
-    : public EPKPChallengeMachineKeyTest {
- protected:
-  void SetAuthenticatedUser() override {
-    auto* identity_manager =
-        IdentityManagerFactory::GetForProfile(browser()->profile());
-    signin::MakePrimaryAccountAvailable(identity_manager,
-                                        account_id_.GetUserEmail());
-  }
-
-  TestingProfile* CreateProfile() override {
-    fake_user_manager_->AddUser(account_id_);
-    return profile_manager()->CreateTestingProfile(account_id_.GetUserEmail());
-  }
-
- private:
-  const std::string kOtherEmail = "test@chromium.com";
-  const AccountId account_id_ = AccountId::FromUserEmailGaiaId(
-      kOtherEmail,
-      signin::GetTestGaiaIdForEmail(kOtherEmail));
-};
-
-TEST_F(EPKPChallengeMachineKeyUnmanagedUserTest, UserNotManaged) {
-  EXPECT_EQ(EPKPChallengeKeyBase::kUserNotManaged,
-            utils::RunFunctionAndReturnError(func_.get(), kArgs, browser()));
-}
-
-class EPKPChallengeUserKeyUnmanagedUserTest : public EPKPChallengeUserKeyTest {
- protected:
-  void SetAuthenticatedUser() override {
-    auto* identity_manager =
-        IdentityManagerFactory::GetForProfile(browser()->profile());
-    signin::MakePrimaryAccountAvailable(identity_manager,
-                                        account_id_.GetUserEmail());
-  }
-
-  TestingProfile* CreateProfile() override {
-    fake_user_manager_->AddUser(account_id_);
-    return profile_manager()->CreateTestingProfile(account_id_.GetUserEmail());
-  }
-
- private:
-  const std::string kOtherEmail = "test@chromium.com";
-  const AccountId account_id_ = AccountId::FromUserEmailGaiaId(
-      kOtherEmail,
-      signin::GetTestGaiaIdForEmail(kOtherEmail));
-};
-
-TEST_F(EPKPChallengeUserKeyUnmanagedUserTest, UserNotManaged) {
-  EXPECT_EQ(EPKPChallengeKeyBase::kUserNotManaged,
-            utils::RunFunctionAndReturnError(func_.get(), kArgs, browser()));
-}
-
 }  // namespace
 }  // namespace extensions
diff --git a/chrome/browser/extensions/api/input_ime/input_ime_api.cc b/chrome/browser/extensions/api/input_ime/input_ime_api.cc
index 006075d..f4157fa 100644
--- a/chrome/browser/extensions/api/input_ime/input_ime_api.cc
+++ b/chrome/browser/extensions/api/input_ime/input_ime_api.cc
@@ -80,7 +80,7 @@
 void ImeObserver::OnKeyEvent(
     const std::string& component_id,
     const InputMethodEngineBase::KeyboardEvent& event,
-    IMEEngineHandlerInterface::KeyEventDoneCallback key_data) {
+    IMEEngineHandlerInterface::KeyEventDoneCallback callback) {
   if (extension_id_.empty())
     return;
 
@@ -89,7 +89,7 @@
   if (!ShouldForwardKeyEvent()) {
     // Continue processing the key event so that the physical keyboard can
     // still work.
-    std::move(key_data).Run(false);
+    std::move(callback).Run(false);
     return;
   }
 
@@ -99,7 +99,7 @@
     return;
   const std::string request_id =
       event_router->GetEngineIfActive(extension_id_)
-          ->AddRequest(component_id, std::move(key_data));
+          ->AddPendingKeyEvent(component_id, std::move(callback));
 
   input_ime::KeyboardEvent key_data_value;
   key_data_value.type = input_ime::ParseKeyboardEventType(event.type);
diff --git a/chrome/browser/page_load_metrics/observers/core_page_load_metrics_observer.cc b/chrome/browser/page_load_metrics/observers/core_page_load_metrics_observer.cc
index b5d850a4..c0ebc7ef 100644
--- a/chrome/browser/page_load_metrics/observers/core_page_load_metrics_observer.cc
+++ b/chrome/browser/page_load_metrics/observers/core_page_load_metrics_observer.cc
@@ -120,9 +120,9 @@
 const char kHistogramFirstInputTimestampSkipFilteringComparison[] =
     "PageLoad.InteractiveTiming.FirstInputTimestamp.SkipFilteringComparison";
 const char kHistogramLongestInputDelay[] =
-    "PageLoad.InteractiveTiming.LongestInputDelay3";
+    "PageLoad.InteractiveTiming.LongestInputDelay4";
 const char kHistogramLongestInputTimestamp[] =
-    "PageLoad.InteractiveTiming.LongestInputTimestamp3";
+    "PageLoad.InteractiveTiming.LongestInputTimestamp4";
 const char kHistogramParseStartToFirstMeaningfulPaint[] =
     "PageLoad.Experimental.PaintTiming.ParseStartToFirstMeaningfulPaint";
 const char kHistogramParseStartToFirstContentfulPaint[] =
diff --git a/chrome/browser/page_load_metrics/observers/ukm_page_load_metrics_observer.cc b/chrome/browser/page_load_metrics/observers/ukm_page_load_metrics_observer.cc
index 3849662..afb4d9e 100644
--- a/chrome/browser/page_load_metrics/observers/ukm_page_load_metrics_observer.cc
+++ b/chrome/browser/page_load_metrics/observers/ukm_page_load_metrics_observer.cc
@@ -378,13 +378,13 @@
   if (timing.interactive_timing->longest_input_delay) {
     base::TimeDelta longest_input_delay =
         timing.interactive_timing->longest_input_delay.value();
-    builder.SetInteractiveTiming_LongestInputDelay3(
+    builder.SetInteractiveTiming_LongestInputDelay4(
         longest_input_delay.InMilliseconds());
   }
   if (timing.interactive_timing->longest_input_timestamp) {
     base::TimeDelta longest_input_timestamp =
         timing.interactive_timing->longest_input_timestamp.value();
-    builder.SetInteractiveTiming_LongestInputTimestamp3(
+    builder.SetInteractiveTiming_LongestInputTimestamp4(
         longest_input_timestamp.InMilliseconds());
   }
 
diff --git a/chrome/browser/page_load_metrics/observers/ukm_page_load_metrics_observer_unittest.cc b/chrome/browser/page_load_metrics/observers/ukm_page_load_metrics_observer_unittest.cc
index d05d9f2..4137ad85 100644
--- a/chrome/browser/page_load_metrics/observers/ukm_page_load_metrics_observer_unittest.cc
+++ b/chrome/browser/page_load_metrics/observers/ukm_page_load_metrics_observer_unittest.cc
@@ -863,11 +863,11 @@
     test_ukm_recorder().ExpectEntrySourceHasUrl(kv.second.get(),
                                                 GURL(kTestUrl1));
     test_ukm_recorder().ExpectEntryMetric(
-        kv.second.get(), PageLoad::kInteractiveTiming_LongestInputDelay3Name,
+        kv.second.get(), PageLoad::kInteractiveTiming_LongestInputDelay4Name,
         50);
     test_ukm_recorder().ExpectEntryMetric(
         kv.second.get(),
-        PageLoad::kInteractiveTiming_LongestInputTimestamp3Name, 712);
+        PageLoad::kInteractiveTiming_LongestInputTimestamp4Name, 712);
   }
 }
 
diff --git a/chrome/browser/password_manager/credential_leak_controller_android.cc b/chrome/browser/password_manager/credential_leak_controller_android.cc
index 46efed6f..ce79541 100644
--- a/chrome/browser/password_manager/credential_leak_controller_android.cc
+++ b/chrome/browser/password_manager/credential_leak_controller_android.cc
@@ -8,8 +8,8 @@
 #include "base/android/jni_string.h"
 #include "chrome/android/chrome_jni_headers/PasswordCheckupLauncher_jni.h"
 #include "chrome/browser/ui/android/passwords/credential_leak_dialog_view_android.h"
-#include "chrome/browser/ui/passwords/credential_leak_dialog_utils.h"
 #include "chrome/common/url_constants.h"
+#include "components/password_manager/core/browser/leak_detection_dialog_utils.h"
 #include "ui/android/window_android.h"
 
 using password_manager::metrics_util::LeakDialogDismissalReason;
@@ -30,14 +30,14 @@
 
 void CredentialLeakControllerAndroid::OnCancelDialog() {
   LogLeakDialogTypeAndDismissalReason(
-      leak_dialog_utils::GetLeakDialogType(leak_type_),
+      password_manager::GetLeakDialogType(leak_type_),
       LeakDialogDismissalReason::kClickedClose);
   delete this;
 }
 
 void CredentialLeakControllerAndroid::OnAcceptDialog() {
   LogLeakDialogTypeAndDismissalReason(
-      leak_dialog_utils::GetLeakDialogType(leak_type_),
+      password_manager::GetLeakDialogType(leak_type_),
       ShouldCheckPasswords() ? LeakDialogDismissalReason::kClickedCheckPasswords
                              : LeakDialogDismissalReason::kClickedOk);
 
@@ -47,7 +47,7 @@
     Java_PasswordCheckupLauncher_launchCheckup(
         env,
         base::android::ConvertUTF8ToJavaString(
-            env, leak_dialog_utils::GetPasswordCheckupURL().spec()),
+            env, password_manager::GetPasswordCheckupURL().spec()),
         window_android_->GetJavaObject());
   }
 
@@ -56,35 +56,35 @@
 
 void CredentialLeakControllerAndroid::OnCloseDialog() {
   LogLeakDialogTypeAndDismissalReason(
-      leak_dialog_utils::GetLeakDialogType(leak_type_),
+      password_manager::GetLeakDialogType(leak_type_),
       LeakDialogDismissalReason::kNoDirectInteraction);
   delete this;
 }
 
 base::string16 CredentialLeakControllerAndroid::GetAcceptButtonLabel() const {
-  return leak_dialog_utils::GetAcceptButtonLabel(leak_type_);
+  return password_manager::GetAcceptButtonLabel(leak_type_);
 }
 
 base::string16 CredentialLeakControllerAndroid::GetCancelButtonLabel() const {
-  return leak_dialog_utils::GetCancelButtonLabel();
+  return password_manager::GetCancelButtonLabel();
 }
 
 base::string16 CredentialLeakControllerAndroid::GetDescription() const {
-  return leak_dialog_utils::GetDescription(leak_type_, origin_);
+  return password_manager::GetDescription(leak_type_, origin_);
 }
 
 base::string16 CredentialLeakControllerAndroid::GetTitle() const {
-  return leak_dialog_utils::GetTitle(leak_type_);
+  return password_manager::GetTitle(leak_type_);
 }
 
 gfx::Range CredentialLeakControllerAndroid::GetDescriptionBoldRange() const {
-  return leak_dialog_utils::GetChangePasswordBoldRange(leak_type_, origin_);
+  return password_manager::GetChangePasswordBoldRange(leak_type_, origin_);
 }
 
 bool CredentialLeakControllerAndroid::ShouldCheckPasswords() const {
-  return leak_dialog_utils::ShouldCheckPasswords(leak_type_);
+  return password_manager::ShouldCheckPasswords(leak_type_);
 }
 
 bool CredentialLeakControllerAndroid::ShouldShowCancelButton() const {
-  return leak_dialog_utils::ShouldShowCancelButton(leak_type_);
+  return password_manager::ShouldShowCancelButton(leak_type_);
 }
diff --git a/chrome/browser/performance_manager/decorators/process_metrics_decorator_unittest.cc b/chrome/browser/performance_manager/decorators/process_metrics_decorator_unittest.cc
index b6fa075..cdcbe66 100644
--- a/chrome/browser/performance_manager/decorators/process_metrics_decorator_unittest.cc
+++ b/chrome/browser/performance_manager/decorators/process_metrics_decorator_unittest.cc
@@ -134,11 +134,11 @@
   MockSystemNodeObserver sys_node_observer;
 
   graph()->AddSystemNodeObserver(&sys_node_observer);
-  auto memory_dump = base::make_optional(std::move(
+  auto memory_dump = base::make_optional(
       GenerateMemoryDump({{mock_graph()->process->process_id(),
                            kFakeResidentSetKb, kFakePrivateFootprintKb},
                           {mock_graph()->other_process->process_id(),
-                           kFakeResidentSetKb, kFakePrivateFootprintKb}})));
+                           kFakeResidentSetKb, kFakePrivateFootprintKb}}));
 
   EXPECT_CALL(*decorator(), GetMemoryDump())
       .WillOnce(testing::Return(testing::ByMove(std::move(memory_dump))));
diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc
index bafc4d06..086c510 100644
--- a/chrome/browser/prefs/browser_prefs.cc
+++ b/chrome/browser/prefs/browser_prefs.cc
@@ -931,7 +931,8 @@
   chromeos::ServicesCustomizationDocument::RegisterProfilePrefs(registry);
   chromeos::UserImageSyncObserver::RegisterProfilePrefs(registry);
   crostini::prefs::RegisterProfilePrefs(registry);
-  extensions::EPKPChallengeUserKey::RegisterProfilePrefs(registry);
+  chromeos::attestation::TpmChallengeKey::RegisterProfilePrefs(registry);
+  extensions::EPKPChallengeKey::RegisterProfilePrefs(registry);
   flags_ui::PrefServiceFlagsStorage::RegisterProfilePrefs(registry);
   guest_os::prefs::RegisterProfilePrefs(registry);
   lock_screen_apps::StateController::RegisterProfilePrefs(registry);
diff --git a/chrome/browser/resources/chromeos/camera/BUILD.gn b/chrome/browser/resources/chromeos/camera/BUILD.gn
index 83a2fb78..01cfbca1 100644
--- a/chrome/browser/resources/chromeos/camera/BUILD.gn
+++ b/chrome/browser/resources/chromeos/camera/BUILD.gn
@@ -79,6 +79,9 @@
     "src/images/camera_button_timer_on_10s.svg",
     "src/images/camera_button_timer_on_3s.svg",
     "src/images/camera_focus_aim.svg",
+    "src/images/camera_intent_play_video.svg",
+    "src/images/camera_intent_result_cancel.svg",
+    "src/images/camera_intent_result_confirm.svg",
     "src/images/camera_mode_photo.svg",
     "src/images/camera_mode_portrait.svg",
     "src/images/camera_mode_square.svg",
@@ -145,6 +148,7 @@
     "src/js/device/camera3_device_info.js",
     "src/js/device/constraints_preferrer.js",
     "src/js/device/device_info_updater.js",
+    "src/js/device/error.js",
   ]
 
   outputs = [
@@ -154,11 +158,13 @@
 
 copy("chrome_camera_app_js_models") {
   sources = [
+    "src/js/models/file_video_saver.js",
     "src/js/models/filenamer.js",
     "src/js/models/filesystem.js",
     "src/js/models/gallery.js",
+    "src/js/models/intent_video_saver.js",
     "src/js/models/result_saver.js",
-    "src/js/models/video_saver.js",
+    "src/js/models/video_saver_interface.js",
   ]
 
   outputs = [
@@ -182,6 +188,7 @@
   sources = [
     "src/js/views/browser.js",
     "src/js/views/camera.js",
+    "src/js/views/camera_intent.js",
     "src/js/views/dialog.js",
     "src/js/views/gallery_base.js",
     "src/js/views/settings.js",
@@ -201,6 +208,7 @@
     "src/js/views/camera/options.js",
     "src/js/views/camera/preview.js",
     "src/js/views/camera/recordtime.js",
+    "src/js/views/camera/review_result.js",
     "src/js/views/camera/timertick.js",
   ]
 
diff --git a/chrome/browser/resources/chromeos/camera/Makefile b/chrome/browser/resources/chromeos/camera/Makefile
index 721778c..9956660 100644
--- a/chrome/browser/resources/chromeos/camera/Makefile
+++ b/chrome/browser/resources/chromeos/camera/Makefile
@@ -93,6 +93,9 @@
 	src/images/camera_button_timer_on_10s.svg \
 	src/images/camera_button_timer_on_3s.svg \
 	src/images/camera_focus_aim.svg \
+	src/images/camera_intent_play_video.svg \
+	src/images/camera_intent_result_cancel.svg \
+	src/images/camera_intent_result_confirm.svg \
 	src/images/camera_mode_photo.svg \
 	src/images/camera_mode_portrait.svg \
 	src/images/camera_mode_square.svg \
@@ -119,16 +122,19 @@
 	src/js/device/camera3_device_info.js \
 	src/js/device/constraints_preferrer.js \
 	src/js/device/device_info_updater.js \
+	src/js/device/error.js \
 	src/js/gallerybutton.js \
 	src/js/google-analytics-bundle.js \
 	src/js/intent.js \
 	src/js/main.js \
 	src/js/metrics.js \
+	src/js/models/file_video_saver.js \
 	src/js/models/filenamer.js \
 	src/js/models/filesystem.js \
 	src/js/models/gallery.js \
+	src/js/models/intent_video_saver.js \
 	src/js/models/result_saver.js \
-	src/js/models/video_saver.js \
+	src/js/models/video_saver_interface.js \
 	src/js/mojo/chrome_helper.js \
 	src/js/mojo/device_operator.js \
 	src/js/mojo/image_capture.js \
@@ -142,11 +148,13 @@
 	src/js/util.js \
 	src/js/views/browser.js \
 	src/js/views/camera.js \
+	src/js/views/camera_intent.js \
 	src/js/views/camera/layout.js \
 	src/js/views/camera/modes.js \
 	src/js/views/camera/options.js \
 	src/js/views/camera/preview.js \
 	src/js/views/camera/recordtime.js \
+	src/js/views/camera/review_result.js \
 	src/js/views/camera/timertick.js \
 	src/js/views/dialog.js \
 	src/js/views/gallery_base.js \
diff --git a/chrome/browser/resources/chromeos/camera/camera_resources.grd b/chrome/browser/resources/chromeos/camera/camera_resources.grd
index 59f7a2a..947be1d 100644
--- a/chrome/browser/resources/chromeos/camera/camera_resources.grd
+++ b/chrome/browser/resources/chromeos/camera/camera_resources.grd
@@ -17,11 +17,13 @@
       <structure name="IDR_CAMERA_BUNDLE_JS" file="src/js/google-analytics-bundle.js" type="chrome_html" />
       <structure name="IDR_CAMERA_CAMERA3_DEVICE_INFO_JS" file="src/js/device/camera3_device_info.js" type="chrome_html" />
       <structure name="IDR_CAMERA_CAMERA_JS" file="src/js/views/camera.js" type="chrome_html" />
+      <structure name="IDR_CAMERA_CAMERA_INTENT_JS" file="src/js/views/camera_intent.js" type="chrome_html" />
       <structure name="IDR_CAMERA_CONSTRAINTS_PREFERRER_JS" file="src/js/device/constraints_preferrer.js" type="chrome_html" />
       <structure name="IDR_CAMERA_CHROME_HELPER_JS" file="src/js/mojo/chrome_helper.js" type="chrome_html" />
       <structure name="IDR_CAMERA_DEVICE_OPERATOR_JS" file="src/js/mojo/device_operator.js" type="chrome_html" />
       <structure name="IDR_CAMERA_DEVICE_INFO_UPDATER_JS" file="src/js/device/device_info_updater.js" type="chrome_html" />
       <structure name="IDR_CAMERA_DIALOG_JS" file="src/js/views/dialog.js" type="chrome_html" />
+      <structure name="IDR_CAMERA_ERROR_JS" file="src/js/device/error.js" type="chrome_html" />
       <structure name="IDR_CAMERA_FILENAMER_JS" file="src/js/models/filenamer.js" type="chrome_html" />
       <structure name="IDR_CAMERA_FILESYSTEM_JS" file="src/js/models/filesystem.js" type="chrome_html" />
       <structure name="IDR_CAMERA_GALLERY_BASE_JS" file="src/js/views/gallery_base.js" type="chrome_html" />
@@ -42,6 +44,7 @@
       <structure name="IDR_CAMERA_RECORDTIME_JS" file="src/js/views/camera/recordtime.js" type="chrome_html" />
       <structure name="IDR_CAMERA_RESOLUTION_EVENT_BROKER_JS" file="src/js/resolution_event_broker.js" type="chrome_html" />
       <structure name="IDR_CAMERA_RESULT_SAVER_JS" file="src/js/models/result_saver.js" type="chrome_html" />
+      <structure name="IDR_CAMERA_REVIEW_RESULT_JS" file="src/js/views/camera/review_result.js" type="chrome_html" />
       <structure name="IDR_CAMERA_SCROLLBAR_JS" file="src/js/scrollbar.js" type="chrome_html" />
       <structure name="IDR_CAMERA_SETTINGS_JS" file="src/js/views/settings.js" type="chrome_html" />
       <structure name="IDR_CAMERA_SOUND_JS" file="src/js/sound.js" type="chrome_html" />
@@ -50,7 +53,9 @@
       <structure name="IDR_CAMERA_TOAST_JS" file="src/js/toast.js" type="chrome_html" />
       <structure name="IDR_CAMERA_TOOLTIP_JS" file="src/js/tooltip.js" type="chrome_html" />
       <structure name="IDR_CAMERA_UTIL_JS" file="src/js/util.js" type="chrome_html" />
-      <structure name="IDR_CAMERA_VIDEO_SAVER_JS" file="src/js/models/video_saver.js" type="chrome_html" />
+      <structure name="IDR_CAMERA_VIDEO_SAVER_INTERFACE_JS" file="src/js/models/video_saver_interface.js" type="chrome_html" />
+      <structure name="IDR_CAMERA_INTENT_VIDEO_SAVER_JS" file="src/js/models/file_video_saver.js" type="chrome_html" />
+      <structure name="IDR_CAMERA_FILE_VIDEO_SAVER_JS" file="src/js/models/intent_video_saver.js" type="chrome_html" />
       <structure name="IDR_CAMERA_VIEW_JS" file="src/js/views/view.js" type="chrome_html" />
       <structure name="IDR_CAMERA_WARNING_JS" file="src/js/views/warning.js" type="chrome_html" />
       <structure name="IDR_CAMERA_WEBUI_BROWSER_PROXY" file="src/js/browser_proxy/webui_browser_proxy.js" type="chrome_html" />
diff --git a/chrome/browser/resources/chromeos/camera/src/css/main.css b/chrome/browser/resources/chromeos/camera/src/css/main.css
index b8ba077..9b5deb8 100644
--- a/chrome/browser/resources/chromeos/camera/src/css/main.css
+++ b/chrome/browser/resources/chromeos/camera/src/css/main.css
@@ -214,6 +214,14 @@
   bottom: calc((var(--modes-bottom) + var(--modes-height)) + 16px);
 }
 
+body.review-result #shutters-group {
+  display: none;
+}
+
+body.should-handle-intent-result #shutters-group {
+  bottom: var(--modes-bottom);
+}
+
 body.tablet-landscape .actions-group {
   flex-direction: column-reverse;
 }
@@ -239,6 +247,52 @@
   padding: var(--modes-gradient-padding) 8px;
 }
 
+body.should-handle-intent-result #modes-group {
+  display: none;
+}
+
+.preview-content {
+  height: 0; /* Calculate at runtime. */
+  width: 0; /* Calculate at runtime. */
+}
+
+#play-result-video {
+  background-image: url(../images/camera_intent_play_video.svg);
+  height: 80px;
+  width: 80px;
+}
+
+#confirm-result-groups {
+  bottom: calc(var(--bottom-line) - 36px);
+  display: flex;
+  flex-direction: column;
+}
+
+#confirm-result-groups>button {
+  flex: 0 0 76px;
+  height: 72px;
+  width: 72px;
+}
+
+body.review-result #preview-video,
+body:not(.review-result) #review-photo-result,
+body:not(.review-result) #review-video-result,
+body:not(.review-photo-result) #review-photo-result,
+body.review-photo-result #review-video-result,
+body.review-photo-result #play-result-video,
+body.playing-result-video #play-result-video,
+body:not(.review-result) #confirm-result-groups {
+  display: none;
+}
+
+#confirm-result {
+  background-image: url(../images/camera_intent_result_confirm.svg);
+}
+
+#cancel-result {
+  background-image: url(../images/camera_intent_result_cancel.svg);
+}
+
 .mode-item {
   flex: 0 0 var(--mode-item-height);
   position: relative;
@@ -415,6 +469,10 @@
   width: var(--big-icon);
 }
 
+body.should-handle-intent-result #gallery-enter {
+  display: none;
+}
+
 .centered-overlay {
   left: 50%;
   position: absolute;
@@ -495,6 +553,10 @@
   background-image: url(../images/camera_button_settings.svg);
 }
 
+body.should-handle-intent-result #open-settings {
+  display: none;
+}
+
 #camera,
 #settings,
 #gridsettings,
@@ -606,7 +668,7 @@
 }
 
 #preview-wrapper,
-#preview-video {
+.preview-content {
   flex-shrink: 0;
   pointer-events: none;
   position: relative;
@@ -625,17 +687,17 @@
   width: 0; /* Calculate at runtime. */
 }
 
-body.square-preview #preview-video {
+body.square-preview .preview-content {
   left: 0; /* Calculate at runtime. */
   position: absolute;
   top: 0; /* Calculate at runtime. */
 }
 
-body.streaming #preview-video {
+body.streaming .preview-content {
   pointer-events: auto;
 }
 
-body.mirror #preview-video,
+body.mirror .preview-content ,
 body.mirror #preview-focus {
   transform: scaleX(-1);
 }
@@ -1270,6 +1332,6 @@
   z-index: 1;
 }
 
-body:not(.mode-switching):not(.streaming) #spinner {
+body:not(.mode-switching):not(.streaming):not(.review-result) #spinner {
   visibility: visible;
 }
diff --git a/chrome/browser/resources/chromeos/camera/src/images/camera_intent_play_video.svg b/chrome/browser/resources/chromeos/camera/src/images/camera_intent_play_video.svg
new file mode 100644
index 0000000..e57b29fe
--- /dev/null
+++ b/chrome/browser/resources/chromeos/camera/src/images/camera_intent_play_video.svg
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="72px" height="72px" viewBox="0 0 72 72" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <!-- Generator: Sketch 57.1 (83088) - https://sketch.com -->
+    <title>btn_play_video_1x</title>
+    <desc>Created with Sketch.</desc>
+    <g id="btn_play_video_1x" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="camera-button" transform="translate(6.127660, 6.127660)" opacity="0.9">
+            <circle id="Oval-3" fill="#FFFFFF" cx="30.0154866" cy="30.0154866" r="30.0154866"></circle>
+            <circle id="Oval-3" fill-opacity="0.1" fill="#5F6368" cx="30.0154866" cy="30.0154866" r="30.0154866"></circle>
+        </g>
+        <g id="dense/av/play" transform="translate(29.412766, 24.817021)" fill="#5F6368">
+            <polygon id="↳Color-fill" points="0 22.7345051 19.4867186 11.3672525 0 0"></polygon>
+        </g>
+    </g>
+</svg>
\ No newline at end of file
diff --git a/chrome/browser/resources/chromeos/camera/src/images/camera_intent_result_cancel.svg b/chrome/browser/resources/chromeos/camera/src/images/camera_intent_result_cancel.svg
new file mode 100644
index 0000000..367f1617
--- /dev/null
+++ b/chrome/browser/resources/chromeos/camera/src/images/camera_intent_result_cancel.svg
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="72px" height="72px" viewBox="0 0 72 72" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <!-- Generator: Sketch 57.1 (83088) - https://sketch.com -->
+    <title>btn_stop_timer_1x</title>
+    <desc>Created with Sketch.</desc>
+    <defs>
+        <polygon id="path-1" points="44 29.6114286 42.3885714 28 36 34.3885714 29.6114286 28 28 29.6114286 34.3885714 36 28 42.3885714 29.6114286 44 36 37.6114286 42.3885714 44 44 42.3885714 37.6114286 36"></polygon>
+    </defs>
+    <g id="btn_stop_timer_1x" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <circle id="Oval-3" fill="#BDC1C6" opacity="0.9" cx="36" cy="36" r="30"></circle>
+        <mask id="mask-2" fill="white">
+            <use xlink:href="#path-1"></use>
+        </mask>
+        <use id="ic_close_24px" fill="#FFFFFF" fill-rule="nonzero" xlink:href="#path-1"></use>
+    </g>
+</svg>
\ No newline at end of file
diff --git a/chrome/browser/resources/chromeos/camera/src/images/camera_intent_result_confirm.svg b/chrome/browser/resources/chromeos/camera/src/images/camera_intent_result_confirm.svg
new file mode 100644
index 0000000..ae2c4f9
--- /dev/null
+++ b/chrome/browser/resources/chromeos/camera/src/images/camera_intent_result_confirm.svg
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="72px" height="72px" viewBox="0 0 72 72" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <!-- Generator: Sketch 57.1 (83088) - https://sketch.com -->
+    <title>btn_confirm_1x</title>
+    <desc>Created with Sketch.</desc>
+    <defs>
+        <polygon id="path-1" points="14.3166667 25.9666667 7.36666667 19.0166667 5 21.3666667 14.3166667 30.6833333 34.3166667 10.6833333 31.9666667 8.33333333"></polygon>
+    </defs>
+    <g id="btn_confirm_1x" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="camera-button" transform="translate(6.127660, 6.127660)" fill="#1A73E8" opacity="0.9">
+            <circle id="Oval-3" cx="30.0154866" cy="30.0154866" r="30.0154866"></circle>
+            <circle id="Oval-3" cx="30.0154866" cy="30.0154866" r="30.0154866"></circle>
+        </g>
+        <g id="ic_check_24px" transform="translate(16.000000, 16.000000)">
+            <mask id="mask-2" fill="white">
+                <use xlink:href="#path-1"></use>
+            </mask>
+            <use fill="#FFFFFF" fill-rule="nonzero" xlink:href="#path-1"></use>
+        </g>
+    </g>
+</svg>
\ No newline at end of file
diff --git a/chrome/browser/resources/chromeos/camera/src/js/device/BUILD.gn b/chrome/browser/resources/chromeos/camera/src/js/device/BUILD.gn
index cc01ba9b3..a54df96 100644
--- a/chrome/browser/resources/chromeos/camera/src/js/device/BUILD.gn
+++ b/chrome/browser/resources/chromeos/camera/src/js/device/BUILD.gn
@@ -9,6 +9,7 @@
     ":camera3_device_info",
     ":constraints_preferrer",
     ":device_info_updater",
+    ":error",
   ]
 }
 
@@ -31,6 +32,10 @@
   deps = [
     ":camera3_device_info",
     ":constraints_preferrer",
+    ":error",
     "..:state",
   ]
 }
+
+js_library("error") {
+}
diff --git a/chrome/browser/resources/chromeos/camera/src/js/device/device_info_updater.js b/chrome/browser/resources/chromeos/camera/src/js/device/device_info_updater.js
index eb055b6..949bb3f 100644
--- a/chrome/browser/resources/chromeos/camera/src/js/device/device_info_updater.js
+++ b/chrome/browser/resources/chromeos/camera/src/js/device/device_info_updater.js
@@ -231,7 +231,7 @@
   async getDeviceResolutions(deviceId) {
     const devices = await this.getCamera3DevicesInfo();
     if (!devices) {
-      throw new Error('HALv1-api');
+      throw new cca.device.LegacyVCDError();
     }
     const info = devices.find((info) => info.deviceId === deviceId);
     return [info.photoResols, info.videoResols];
diff --git a/chrome/browser/resources/chromeos/camera/src/js/device/error.js b/chrome/browser/resources/chromeos/camera/src/js/device/error.js
new file mode 100644
index 0000000..3e817dec
--- /dev/null
+++ b/chrome/browser/resources/chromeos/camera/src/js/device/error.js
@@ -0,0 +1,32 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+'use strict';
+
+/**
+ * Namespace for the Camera app.
+ */
+var cca = cca || {};
+
+/**
+ * Namespace for device.
+ */
+cca.device = cca.device || {};
+
+/**
+ * Throws from calls to methods requiring mojo supporting VCD on HALv1 device
+ * equipped with legacy VCD implementation.
+ */
+cca.device.LegacyVCDError = class extends Error {
+  /**
+   * @param {string=} message
+   * @public
+   */
+  constructor(
+      message =
+          'Call to unsupported mojo operation on legacy VCD implementation.') {
+    super(message);
+    this.name = 'LegacyVCDError';
+  }
+};
diff --git a/chrome/browser/resources/chromeos/camera/src/js/main.js b/chrome/browser/resources/chromeos/camera/src/js/main.js
index 71539b16..af99ae9 100644
--- a/chrome/browser/resources/chromeos/camera/src/js/main.js
+++ b/chrome/browser/resources/chromeos/camera/src/js/main.js
@@ -14,6 +14,10 @@
  * @constructor
  */
 cca.App = function() {
+  const shouldHandleIntentResult =
+      window.intent !== null && window.intent.shouldHandleResult;
+  cca.state.set('should-handle-intent-result', shouldHandleIntentResult);
+
   /**
    * @type {cca.models.Gallery}
    * @private
@@ -63,9 +67,13 @@
    * @type {cca.views.Camera}
    * @private
    */
-  this.cameraView_ = new cca.views.Camera(
-      this.gallery_, this.infoUpdater_, this.photoPreferrer_,
-      this.videoPreferrer_);
+  this.cameraView_ = shouldHandleIntentResult ?
+      new cca.views.CameraIntent(
+          window.intent, this.infoUpdater_, this.photoPreferrer_,
+          this.videoPreferrer_) :
+      new cca.views.Camera(
+          this.gallery_, this.infoUpdater_, this.photoPreferrer_,
+          this.videoPreferrer_);
 
   // End of properties. Seal the object.
   Object.seal(this);
@@ -110,8 +118,10 @@
   cca.proxy.browserProxy.localStorageGet(
       {expert: false}, ({expert}) => cca.state.set('expert', expert));
   document.querySelectorAll('input').forEach((element) => {
-    element.addEventListener('keypress', (event) =>
-        cca.util.getShortcutIdentifier(event) == 'Enter' && element.click());
+    element.addEventListener(
+        'keypress',
+        (event) => cca.util.getShortcutIdentifier(event) == 'Enter' &&
+            element.click());
 
     var css = element.getAttribute('data-state');
     var key = element.getAttribute('data-key');
@@ -129,14 +139,15 @@
         if (element.type == 'radio' && element.checked) {
           // Handle unchecked grouped sibling radios.
           var grouped = `input[type=radio][name=${element.name}]:not(:checked)`;
-          document.querySelectorAll(grouped).forEach((radio) =>
-              radio.dispatchEvent(new Event('change')) && radio.save());
+          document.querySelectorAll(grouped).forEach(
+              (radio) =>
+                  radio.dispatchEvent(new Event('change')) && radio.save());
         }
       }
     });
     element.toggleChecked = (checked) => {
       element.checked = checked;
-      element.dispatchEvent(new Event('change')); // Trigger toggling css.
+      element.dispatchEvent(new Event('change'));  // Trigger toggling css.
     };
     element.save = () => {
       return key && cca.proxy.browserProxy.localStorageSet(payload());
@@ -154,34 +165,38 @@
  */
 cca.App.prototype.start = function() {
   var ackMigrate = false;
-  cca.models.FileSystem.initialize(() => {
-    // Prompt to migrate pictures if needed.
-    var message = chrome.i18n.getMessage('migrate_pictures_msg');
-    return cca.nav.open('message-dialog', {message, cancellable: false})
-        .then((acked) => {
-          if (!acked) {
-            throw new Error('no-migrate');
-          }
-          ackMigrate = true;
-        });
-  }).then((external) => {
-    cca.state.set('ext-fs', external);
-    this.gallery_.addObserver(this.galleryButton_);
-    if (!cca.App.useGalleryApp()) {
-      this.gallery_.addObserver(this.browserView_);
-    }
-    this.gallery_.load();
-    cca.nav.open('camera');
-  }).catch((error) => {
-    console.error(error);
-    if (error && error.message == 'no-migrate') {
-      chrome.app.window.current().close();
-      return;
-    }
-    cca.nav.open('warning', 'filesystem-failure');
-  }).finally(() => {
-    cca.metrics.log(cca.metrics.Type.LAUNCH, ackMigrate);
-  });
+  cca.models.FileSystem
+      .initialize(() => {
+        // Prompt to migrate pictures if needed.
+        var message = chrome.i18n.getMessage('migrate_pictures_msg');
+        return cca.nav.open('message-dialog', {message, cancellable: false})
+            .then((acked) => {
+              if (!acked) {
+                throw new Error('no-migrate');
+              }
+              ackMigrate = true;
+            });
+      })
+      .then((external) => {
+        cca.state.set('ext-fs', external);
+        this.gallery_.addObserver(this.galleryButton_);
+        if (!cca.App.useGalleryApp()) {
+          this.gallery_.addObserver(this.browserView_);
+        }
+        this.gallery_.load();
+        cca.nav.open('camera');
+      })
+      .catch((error) => {
+        console.error(error);
+        if (error && error.message == 'no-migrate') {
+          chrome.app.window.current().close();
+          return;
+        }
+        cca.nav.open('warning', 'filesystem-failure');
+      })
+      .finally(() => {
+        cca.metrics.log(cca.metrics.Type.LAUNCH, ackMigrate);
+      });
 };
 
 /**
@@ -190,7 +205,7 @@
  * @private
  */
 cca.App.prototype.onKeyPressed_ = function(event) {
-  cca.tooltip.hide(); // Hide shown tooltip on any keypress.
+  cca.tooltip.hide();  // Hide shown tooltip on any keypress.
   cca.nav.onKeyPressed(event);
 };
 
diff --git a/chrome/browser/resources/chromeos/camera/src/js/models/BUILD.gn b/chrome/browser/resources/chromeos/camera/src/js/models/BUILD.gn
index db0f77e3..39e6022 100644
--- a/chrome/browser/resources/chromeos/camera/src/js/models/BUILD.gn
+++ b/chrome/browser/resources/chromeos/camera/src/js/models/BUILD.gn
@@ -33,4 +33,12 @@
 }
 
 js_library("video_saver") {
+  sources = [
+    "file_video_saver.js",
+    "intent_video_saver.js",
+    "video_saver_interface.js",
+  ]
+  deps = [
+    "..:intent",
+  ]
 }
diff --git a/chrome/browser/resources/chromeos/camera/src/js/models/video_saver.js b/chrome/browser/resources/chromeos/camera/src/js/models/file_video_saver.js
similarity index 71%
rename from chrome/browser/resources/chromeos/camera/src/js/models/video_saver.js
rename to chrome/browser/resources/chromeos/camera/src/js/models/file_video_saver.js
index d4ade9a..91a724e 100644
--- a/chrome/browser/resources/chromeos/camera/src/js/models/video_saver.js
+++ b/chrome/browser/resources/chromeos/camera/src/js/models/file_video_saver.js
@@ -16,8 +16,9 @@
 
 /**
  * Used to save captured video.
+ * @implements {cca.models.VideoSaver}
  */
-cca.models.VideoSaver = class {
+cca.models.FileVideoSaver = class {
   /**
    * @param {!FileEntry} file
    * @param {!FileWriter} writer
@@ -42,9 +43,7 @@
   }
 
   /**
-   * Writes video data to result video.
-   * @param {!Blob} blob Video data to be written.
-   * @return {!Promise}
+   * @override
    */
   async write(blob) {
     this.curWrite_ = (async () => {
@@ -58,8 +57,7 @@
   }
 
   /**
-   * Finishes the write of video data parts and returns result video file.
-   * @return {!Promise<!FileEntry>} Result video file.
+   * @override
    */
   async endWrite() {
     await this.curWrite_;
@@ -67,14 +65,14 @@
   }
 
   /**
-   * Create VideoSaver.
-   * @param {!FileEntry} file The file which VideoSaver saves the result video
-   *     into.
-   * @return {!Promise<!cca.models.VideoSaver>}
+   * Creates FileVideoSaver.
+   * @param {!FileEntry} file The file which FileVideoSaver saves the result
+   *     video into.
+   * @return {!Promise<!cca.models.FileVideoSaver>}
    */
   static async create(file) {
     const writer = await new Promise(
         (resolve, reject) => file.createWriter(resolve, reject));
-    return new cca.models.VideoSaver(file, writer);
+    return new cca.models.FileVideoSaver(file, writer);
   }
 };
diff --git a/chrome/browser/resources/chromeos/camera/src/js/models/filesystem.js b/chrome/browser/resources/chromeos/camera/src/js/models/filesystem.js
index eee6686..1db6dde 100644
--- a/chrome/browser/resources/chromeos/camera/src/js/models/filesystem.js
+++ b/chrome/browser/resources/chromeos/camera/src/js/models/filesystem.js
@@ -37,6 +37,12 @@
 cca.models.FileSystem.internalDir = null;
 
 /**
+ * Temporary directory in the internal file system.
+ * @type {DirectoryEntry}
+ */
+cca.models.FileSystem.internalTempDir = null;
+
+/**
  * Directory in the external file system.
  * @type {DirectoryEntry}
  */
@@ -49,7 +55,21 @@
  */
 cca.models.FileSystem.initInternalDir_ = function() {
   return new Promise((resolve, reject) => {
-    webkitRequestFileSystem(window.PERSISTENT, 768 * 1024 * 1024 /* 768MB */,
+    webkitRequestFileSystem(
+        window.PERSISTENT, 768 * 1024 * 1024 /* 768MB */,
+        (fs) => resolve(fs.root), reject);
+  });
+};
+
+/**
+ * Initializes the temporary directory in the internal file system.
+ * @return {!Promise<DirectoryEntry>} Promise for the directory result.
+ * @private
+ */
+cca.models.FileSystem.initInternalTempDir_ = function() {
+  return new Promise((resolve, reject) => {
+    webkitRequestFileSystem(
+        window.TEMPORARY, 768 * 1024 * 1024 /* 768MB */,
         (fs) => resolve(fs.root), reject);
   });
 };
@@ -118,48 +138,59 @@
   var doneMigrate = () => chrome.chromeosInfoPrivate &&
       chrome.chromeosInfoPrivate.set('cameraMediaConsolidated', true);
 
-  return Promise.all([
-    cca.models.FileSystem.initInternalDir_(),
-    cca.models.FileSystem.initExternalDir_(),
-    checkAcked,
-    checkMigrated,
-  ]).then(([internalDir, externalDir, acked, migrated]) => {
-    cca.models.FileSystem.internalDir = internalDir;
-    cca.models.FileSystem.externalDir = externalDir;
-    if (migrated && !externalDir) {
-      throw new Error('External file system should be available.');
-    }
-    // Check if acknowledge-prompt and migrate-pictures are needed.
-    if (migrated || !cca.models.FileSystem.externalDir) {
-      return [false, false];
-    }
-    // Check if any internal picture other than thumbnail needs migration.
-    // Pictures taken by old Camera App may not have IMG_ or VID_ prefix.
-    var dir = cca.models.FileSystem.internalDir;
-    return cca.models.FileSystem.readDir_(dir).then((entries) => {
-      return entries.some(
-          (entry) => !cca.models.FileSystem.hasThumbnailPrefix_(entry));
-    }).then((migrateNeeded) => {
-      if (migrateNeeded) {
-        return [!acked, true];
-      }
-      // If the external file system is supported and there is already no
-      // picture in the internal file system, it implies done migration and
-      // then doesn't need acknowledge-prompt.
-      ackMigrate();
-      doneMigrate();
-      return [false, false];
-    });
-  }).then(([promptNeeded, migrateNeeded]) => { // Prompt to migrate if needed.
-    return !promptNeeded ? migrateNeeded : promptMigrate().then(() => {
-      ackMigrate();
-      return migrateNeeded;
-    });
-  }).then((migrateNeeded) => { // Migrate pictures if needed.
-    const external = cca.models.FileSystem.externalDir != null;
-    return !migrateNeeded ? external : cca.models.FileSystem.migratePictures()
-        .then(doneMigrate).then(() => external);
-  });
+  return Promise
+      .all([
+        cca.models.FileSystem.initInternalDir_(),
+        cca.models.FileSystem.initInternalTempDir_(),
+        cca.models.FileSystem.initExternalDir_(),
+        checkAcked,
+        checkMigrated,
+      ])
+      .then(([internalDir, internalTempDir, externalDir, acked, migrated]) => {
+        cca.models.FileSystem.internalDir = internalDir;
+        cca.models.FileSystem.internalTempDir = internalTempDir;
+        cca.models.FileSystem.externalDir = externalDir;
+        if (migrated && !externalDir) {
+          throw new Error('External file system should be available.');
+        }
+        // Check if acknowledge-prompt and migrate-pictures are needed.
+        if (migrated || !cca.models.FileSystem.externalDir) {
+          return [false, false];
+        }
+        // Check if any internal picture other than thumbnail needs migration.
+        // Pictures taken by old Camera App may not have IMG_ or VID_ prefix.
+        var dir = cca.models.FileSystem.internalDir;
+        return cca.models.FileSystem.readDir_(dir)
+            .then((entries) => {
+              return entries.some(
+                  (entry) => !cca.models.FileSystem.hasThumbnailPrefix_(entry));
+            })
+            .then((migrateNeeded) => {
+              if (migrateNeeded) {
+                return [!acked, true];
+              }
+              // If the external file system is supported and there is already
+              // no picture in the internal file system, it implies done
+              // migration and then doesn't need acknowledge-prompt.
+              ackMigrate();
+              doneMigrate();
+              return [false, false];
+            });
+      })
+      .then(
+          ([promptNeeded, migrateNeeded]) => {  // Prompt to migrate if needed.
+            return !promptNeeded ? migrateNeeded : promptMigrate().then(() => {
+              ackMigrate();
+              return migrateNeeded;
+            });
+          })
+      .then((migrateNeeded) => {  // Migrate pictures if needed.
+        const external = cca.models.FileSystem.externalDir != null;
+        return !migrateNeeded ? external :
+                                cca.models.FileSystem.migratePictures()
+                                    .then(doneMigrate)
+                                    .then(() => external);
+      });
 };
 
 /**
@@ -303,6 +334,26 @@
 };
 
 /**
+ * @const {string}
+ */
+cca.models.FileSystem.PRIVATE_TEMPFILE_NAME = 'video-intent.mkv';
+
+/**
+ * @return {!Promise<!FileEntry>} Newly created temporary file.
+ * @throws {Error} If failed to create video temp file.
+ */
+cca.models.FileSystem.createPrivateTempVideoFile = async function() {
+  // TODO(inker): Handles running out of space case.
+  const dir = cca.models.FileSystem.internalTempDir;
+  const file = await cca.models.FileSystem.getFile(
+      dir, cca.models.FileSystem.PRIVATE_TEMPFILE_NAME, true);
+  if (file === null) {
+    throw new Error('Failed to create private video temp file.');
+  }
+  return file;
+};
+
+/**
  * Saves temporary video file to predefined default location.
  * @param {FileEntry} tempfile Temporary video file to be saved.
  * @param {string} filename Filename to be saved.
diff --git a/chrome/browser/resources/chromeos/camera/src/js/models/gallery.js b/chrome/browser/resources/chromeos/camera/src/js/models/gallery.js
index 8edc3e0..d3ac1a7d 100644
--- a/chrome/browser/resources/chromeos/camera/src/js/models/gallery.js
+++ b/chrome/browser/resources/chromeos/camera/src/js/models/gallery.js
@@ -88,11 +88,15 @@
   var name = cca.models.FileSystem.regulatePictureName(pictureEntry);
   // Match numeric parts from filenames, e.g. IMG_'yyyyMMdd_HHmmss (n)'.jpg.
   // Assume no more than one picture taken within one millisecond.
+  // Use String.raw instead of /...regex.../ here to avoid breaking syntax
+  // highlight on gerrit.
   var match = name.match(
-      /_(\d{4})(\d{2})(\d{2})_(\d{2})(\d{2})(\d{2})(?: \((\d+)\))?/);
-  return match ? new Date(num(match[1]), num(match[2]) - 1, num(match[3]),
-      num(match[4]), num(match[5]), num(match[6]),
-      match[7] ? num(match[7]) : 0) : new Date(0);
+      String.raw`_(\d{4})(\d{2})(\d{2})_(\d{2})(\d{2})(\d{2})(?: \((\d+)\))?/`);
+  return match ?
+      new Date(
+          num(match[1]), num(match[2]) - 1, num(match[3]), num(match[4]),
+          num(match[5]), num(match[6]), match[7] ? num(match[7]) : 0) :
+      new Date(0);
 };
 
 cca.models.Gallery.Picture.prototype = {
@@ -313,7 +317,7 @@
  */
 cca.models.Gallery.prototype.startSaveVideo = async function() {
   const tempFile = await cca.models.FileSystem.createTempVideoFile();
-  return cca.models.VideoSaver.create(tempFile);
+  return cca.models.FileVideoSaver.create(tempFile);
 };
 
 /**
diff --git a/chrome/browser/resources/chromeos/camera/src/js/models/intent_video_saver.js b/chrome/browser/resources/chromeos/camera/src/js/models/intent_video_saver.js
new file mode 100644
index 0000000..d7c5e91
--- /dev/null
+++ b/chrome/browser/resources/chromeos/camera/src/js/models/intent_video_saver.js
@@ -0,0 +1,67 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+'use strict';
+
+/**
+ * Namespace for the Camera app.
+ */
+var cca = cca || {};
+
+/**
+ * Namespace for models.
+ */
+cca.models = cca.models || {};
+
+/**
+ * Used to save captured video into a preview file and forward to intent result.
+ * @implements {cca.models.VideoSaver}
+ */
+cca.models.IntentVideoSaver = class {
+  /**
+   * @param {!cca.intent.Intent} intent
+   * @param {!cca.models.FileVideoSaver} fileSaver
+   * @private
+   */
+  constructor(intent, fileSaver) {
+    /**
+     * @const {!cca.intent.Intent} intent
+     * @private
+     */
+    this.intent_ = intent;
+
+    /**
+     * @const {!cca.models.FileVideoSaver}
+     * @private
+     */
+    this.fileSaver_ = fileSaver;
+  }
+
+  /**
+   * @override
+   */
+  async write(blob) {
+    await this.fileSaver_.write(blob);
+    const arrayBuffer = await blob.arrayBuffer();
+    this.intent_.appendData(new Uint8Array(arrayBuffer));
+  }
+
+  /**
+   * @override
+   */
+  async endWrite() {
+    return this.fileSaver_.endWrite();
+  }
+
+  /**
+   * Creates IntentVideoSaver.
+   * @param {!cca.intent.Intent} intent
+   * @return {!Promise<!cca.models.IntentVideoSaver>}
+   */
+  static async create(intent) {
+    const tmpFile = await cca.models.FileSystem.createPrivateTempVideoFile();
+    const fileSaver = await cca.models.FileVideoSaver.create(tmpFile);
+    return new cca.models.IntentVideoSaver(intent, fileSaver);
+  }
+};
diff --git a/chrome/browser/resources/chromeos/camera/src/js/models/video_saver_interface.js b/chrome/browser/resources/chromeos/camera/src/js/models/video_saver_interface.js
new file mode 100644
index 0000000..7b56956
--- /dev/null
+++ b/chrome/browser/resources/chromeos/camera/src/js/models/video_saver_interface.js
@@ -0,0 +1,34 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+'use strict';
+
+/**
+ * Namespace for the Camera app.
+ */
+var cca = cca || {};
+
+/**
+ * Namespace for models.
+ */
+cca.models = cca.models || {};
+
+/**
+ * Used to save captured video.
+ * @interface
+ */
+cca.models.VideoSaver = class {
+  /**
+   * Writes video data to result video.
+   * @param {!Blob} blob Video data to be written.
+   * @return {!Promise}
+   */
+  async write(blob) {}
+
+  /**
+   * Finishes the write of video data parts and returns result video file.
+   * @return {!Promise<!FileEntry>} Result video file.
+   */
+  async endWrite() {}
+};
diff --git a/chrome/browser/resources/chromeos/camera/src/js/views/BUILD.gn b/chrome/browser/resources/chromeos/camera/src/js/views/BUILD.gn
index 84560373..d52516bb 100644
--- a/chrome/browser/resources/chromeos/camera/src/js/views/BUILD.gn
+++ b/chrome/browser/resources/chromeos/camera/src/js/views/BUILD.gn
@@ -7,6 +7,7 @@
 group("closure_compile") {
   deps = [
     ":compile_resources",
+    "camera:compile_resources",
   ]
 }
 
diff --git a/chrome/browser/resources/chromeos/camera/src/js/views/camera.js b/chrome/browser/resources/chromeos/camera/src/js/views/camera.js
index bee9b77a..d21b3f9 100644
--- a/chrome/browser/resources/chromeos/camera/src/js/views/camera.js
+++ b/chrome/browser/resources/chromeos/camera/src/js/views/camera.js
@@ -129,13 +129,10 @@
   /**
    * Promise for the current take of photo or recording.
    * @type {?Promise}
-   * @private
+   * @protected
    */
   this.take_ = null;
 
-  // End of properties, seal the object.
-  Object.seal(this);
-
   document.querySelectorAll('#start-takephoto, #start-recordvideo')
       .forEach((btn) => btn.addEventListener('click', () => this.beginTake_()));
 
@@ -189,11 +186,13 @@
 
 /**
  * Begins to take photo or recording with the current options, e.g. timer.
- * @private
+ * @return {?Promise} Promise resolved when take action completes. Returns null
+ *     if CCA can't start take action.
+ * @protected
  */
 cca.views.Camera.prototype.beginTake_ = function() {
   if (!cca.state.get('streaming') || cca.state.get('taking')) {
-    return;
+    return null;
   }
 
   cca.state.set('taking', true);
@@ -214,6 +213,7 @@
       this.focus();  // Refocus the visible shutter button for ChromeVox.
     }
   })();
+  return this.take_;
 };
 
 /**
@@ -273,69 +273,70 @@
 };
 
 /**
- * Try start stream reconfiguration with specified device id.
- * @async
+ * Try start stream reconfiguration with specified mode and device id.
  * @param {?string} deviceId
- * @return {boolean} If found suitable stream and reconfigure successfully.
+ * @param {string} mode
+ * @return {!Promise<boolean>} If found suitable stream and reconfigure
+ *     successfully.
  */
-cca.views.Camera.prototype.startWithDevice_ = async function(deviceId) {
-  let supportedModes = null;
-  for (const mode of this.modes_.getModeCandidates()) {
-    try {
-      if (!deviceId) {
-        // Null for requesting default camera on HALv1.
-        throw new Error('HALv1-api');
-      }
+cca.views.Camera.prototype.startWithMode_ = async function(deviceId, mode) {
+  const deviceOperator = await cca.mojo.DeviceOperator.getInstance();
+  let resolCandidates = null;
+  if (deviceOperator !== null) {
+    if (deviceId !== null) {
       const previewRs =
           (await this.infoUpdater_.getDeviceResolutions(deviceId))[1];
-      var resolCandidates =
+      resolCandidates =
           this.modes_.getResolutionCandidates(mode, deviceId, previewRs);
-    } catch (e) {
-      // Assume the exception here is thrown from error of HALv1 not support
-      // resolution query, fallback to use v1 constraints-candidates.
-      if (e.message == 'HALv1-api') {
-        resolCandidates =
-            await this.modes_.getResolutionCandidatesV1(mode, deviceId);
-      } else {
-        throw e;
+    } else {
+      console.error('Null device id present on HALv3 device. Fallback to v1.');
+    }
+  }
+  if (resolCandidates === null) {
+    resolCandidates =
+        await this.modes_.getResolutionCandidatesV1(mode, deviceId);
+  }
+  for (const [captureResolution, previewCandidates] of resolCandidates) {
+    for (const constraints of previewCandidates) {
+      if (this.suspended) {
+        throw new cca.views.CameraSuspendedError();
+      }
+      try {
+        if (deviceOperator !== null) {
+          await deviceOperator.setFpsRange(deviceId, constraints);
+          await deviceOperator.setCaptureIntent(
+              deviceId, this.modes_.getCaptureIntent(mode));
+        }
+        const stream = await navigator.mediaDevices.getUserMedia(constraints);
+        await this.preview_.start(stream);
+        this.facingMode_ =
+            await this.options_.updateValues(constraints, stream);
+        await this.modes_.updateModeSelectionUI(deviceId);
+        await this.modes_.updateMode(mode, stream, deviceId, captureResolution);
+        cca.nav.close('warning', 'no-camera');
+        return true;
+      } catch (e) {
+        this.preview_.stop();
+        console.error(e);
       }
     }
-    for (const [captureResolution, previewCandidates] of resolCandidates) {
-      if (supportedModes && !supportedModes.includes(mode)) {
-        break;
-      }
-      for (const constraints of previewCandidates) {
-        if (this.suspended) {
-          throw new cca.views.CameraSuspendedError();
-        }
-        try {
-          const deviceOperator = await cca.mojo.DeviceOperator.getInstance();
-          if (deviceOperator) {
-            await deviceOperator.setFpsRange(deviceId, constraints);
-            await deviceOperator.setCaptureIntent(
-                deviceId, this.modes_.getCaptureIntent(mode));
-          }
-          const stream = await navigator.mediaDevices.getUserMedia(constraints);
-          if (!supportedModes) {
-            supportedModes = await this.modes_.getSupportedModes(stream);
-            if (!supportedModes.includes(mode)) {
-              stream.getTracks()[0].stop();
-              break;
-            }
-          }
-          await this.preview_.start(stream);
-          this.facingMode_ =
-              await this.options_.updateValues(constraints, stream);
-          await this.modes_.updateModeSelectionUI(supportedModes);
-          await this.modes_.updateMode(
-              mode, stream, deviceId, captureResolution);
-          cca.nav.close('warning', 'no-camera');
-          return true;
-        } catch (e) {
-          this.preview_.stop();
-          console.error(e);
-        }
-      }
+  }
+  return false;
+};
+
+/**
+ * Try start stream reconfiguration with specified device id.
+ * @param {?string} deviceId
+ * @return {!Promise<boolean>} If found suitable stream and reconfigure
+ *     successfully.
+ */
+cca.views.Camera.prototype.startWithDevice_ = async function(deviceId) {
+  const supportedModes = await this.modes_.getSupportedModes(deviceId);
+  const modes =
+      this.modes_.getModeCandidates().filter((m) => supportedModes.includes(m));
+  for (const mode of modes) {
+    if (await this.startWithMode_(deviceId, mode)) {
+      return true;
     }
   }
   return false;
diff --git a/chrome/browser/resources/chromeos/camera/src/js/views/camera/BUILD.gn b/chrome/browser/resources/chromeos/camera/src/js/views/camera/BUILD.gn
new file mode 100644
index 0000000..38a22c26
--- /dev/null
+++ b/chrome/browser/resources/chromeos/camera/src/js/views/camera/BUILD.gn
@@ -0,0 +1,24 @@
+# Copyright 2019 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//third_party/closure_compiler/compile_js.gni")
+
+group("closure_compile") {
+  deps = [
+    ":compile_resources",
+  ]
+}
+
+js_type_check("compile_resources") {
+  deps = [
+    ":review_result",
+  ]
+}
+
+js_library("review_result") {
+  deps = [
+    "../..:state",
+    "../..:util",
+  ]
+}
diff --git a/chrome/browser/resources/chromeos/camera/src/js/views/camera/layout.js b/chrome/browser/resources/chromeos/camera/src/js/views/camera/layout.js
index 43afec7..f6fd00d 100644
--- a/chrome/browser/resources/chromeos/camera/src/js/views/camera/layout.js
+++ b/chrome/browser/resources/chromeos/camera/src/js/views/camera/layout.js
@@ -38,7 +38,14 @@
    * @private
    */
   this.squareVideo_ =
-      cca.views.camera.Layout.cssStyle_('body.square-preview #preview-video');
+      cca.views.camera.Layout.cssStyle_('body.square-preview .preview-content');
+
+  /**
+   * CSS style of what is currently put as camera preview.
+   * @type {CSSStyleDeclaration}
+   * @private
+   */
+  this.previewContent_ = cca.views.camera.Layout.cssStyle_('.preview-content');
 
   // End of properties, seal the object.
   Object.seal(this);
@@ -73,7 +80,9 @@
   // it may fill up the window or be letterboxed when fullscreen/maximized.
   // Don't use app-window.innerBounds' width/height properties during resizing
   // as they are not updated immediately.
-  var video = document.querySelector('#preview-video');
+  const video = document.querySelector('#preview-video');
+  let contentWidth = 0;
+  let contentHeight = 0;
   if (video.videoHeight) {
     var scale = cca.state.get('square-mode') ?
         Math.min(window.innerHeight, window.innerWidth) /
@@ -81,15 +90,19 @@
         Math.min(
             window.innerHeight / video.videoHeight,
             window.innerWidth / video.videoWidth);
-    video.width = scale * video.videoWidth;
-    video.height = scale * video.videoHeight;
+    contentWidth = scale * video.videoWidth;
+    contentHeight = scale * video.videoHeight;
+    this.previewContent_.setProperty('width', `${contentWidth}px`);
+    this.previewContent_.setProperty('height', `${contentHeight}px`);
   }
-  var [viewportW, viewportH] = [video.width, video.height];
+  var [viewportW, viewportH] = [contentWidth, contentHeight];
   cca.state.set('square-preview', cca.state.get('square-mode'));
   if (cca.state.get('square-mode')) {
-    viewportW = viewportH = Math.min(video.width, video.height);
-    this.squareVideo_.setProperty('left', `${(viewportW - video.width) / 2}px`);
-    this.squareVideo_.setProperty('top', `${(viewportH - video.height) / 2}px`);
+    viewportW = viewportH = Math.min(contentWidth, contentHeight);
+    this.squareVideo_.setProperty(
+        'left', `${(viewportW - contentWidth) / 2}px`);
+    this.squareVideo_.setProperty(
+        'top', `${(viewportH - contentHeight) / 2}px`);
     this.squareViewport_.setProperty('width', `${viewportW}px`);
     this.squareViewport_.setProperty('height', `${viewportH}px`);
   }
diff --git a/chrome/browser/resources/chromeos/camera/src/js/views/camera/modes.js b/chrome/browser/resources/chromeos/camera/src/js/views/camera/modes.js
index 43693e5..59ddb8a 100644
--- a/chrome/browser/resources/chromeos/camera/src/js/views/camera/modes.js
+++ b/chrome/browser/resources/chromeos/camera/src/js/views/camera/modes.js
@@ -162,12 +162,14 @@
     'portrait-mode': {
       captureFactory: () => new cca.views.camera.Portrait(
           this.stream_, doSavePhoto, this.captureResolution_),
-      isSupported: async (stream) => {
-        const deviceOperator = await cca.mojo.DeviceOperator.getInstance();
-        if (!deviceOperator) {
+      isSupported: async (deviceId) => {
+        if (deviceId === null) {
           return false;
         }
-        const deviceId = stream.getVideoTracks()[0].getSettings().deviceId;
+        const deviceOperator = await cca.mojo.DeviceOperator.getInstance();
+        if (deviceOperator === null) {
+          return false;
+        }
         return await deviceOperator.isPortraitModeSupported(deviceId);
       },
       resolutionConfig: photoResolPreferrer,
@@ -273,7 +275,7 @@
 /**
  * Gets all mode candidates. Desired trying sequence of candidate modes is
  * reflected in the order of the returned array.
- * @return {Array<string>} Mode candidates to be tried out.
+ * @return {!Array<string>} Mode candidates to be tried out.
  */
 cca.views.camera.Modes.prototype.getModeCandidates = function() {
   const tried = {};
@@ -326,14 +328,15 @@
 };
 
 /**
- * Gets supported modes for video device of the given stream.
- * @param {MediaStream} stream Stream of the video device.
- * @return {Array<string>} Names of all supported mode for the video device.
+ * Gets supported modes for video device of given device id.
+ * @param {?string} deviceId Device id of the video device.
+ * @return {!Promise<!Array<cca.views.camera.Mode>>} All supported mode for the
+ *     video device.
  */
-cca.views.camera.Modes.prototype.getSupportedModes = async function(stream) {
+cca.views.camera.Modes.prototype.getSupportedModes = async function(deviceId) {
   let supportedModes = [];
   for (const [mode, obj] of Object.entries(this.allModes_)) {
-    if (await obj.isSupported(stream)) {
+    if (await obj.isSupported(deviceId)) {
       supportedModes.push(mode);
     }
   }
@@ -341,12 +344,13 @@
 };
 
 /**
- * Updates mode selection UI according to given supported modes.
- * @param {Array<string>} supportedModes Supported mode names to be updated
- *     with.
+ * Updates mode selection UI according to given device id.
+ * @param {?string} deviceId
+ * @return {!Promise}
  */
-cca.views.camera.Modes.prototype.updateModeSelectionUI = function(
-    supportedModes) {
+cca.views.camera.Modes.prototype.updateModeSelectionUI =
+    async function(deviceId) {
+  const supportedModes = await this.getSupportedModes(deviceId);
   document.querySelectorAll('.mode-item').forEach((element) => {
     const radio = element.querySelector('input[type=radio]');
     element.classList.toggle(
diff --git a/chrome/browser/resources/chromeos/camera/src/js/views/camera/preview.js b/chrome/browser/resources/chromeos/camera/src/js/views/camera/preview.js
index d0317660..da40f94 100644
--- a/chrome/browser/resources/chromeos/camera/src/js/views/camera/preview.js
+++ b/chrome/browser/resources/chromeos/camera/src/js/views/camera/preview.js
@@ -123,6 +123,7 @@
 cca.views.camera.Preview.prototype.setSource_ = function(stream) {
   var video = document.createElement('video');
   video.id = 'preview-video';
+  video.classList = this.video_.classList;
   video.muted = true; // Mute to avoid echo from the captured audio.
   return new Promise((resolve) => {
     var handler = () => {
@@ -187,27 +188,6 @@
 };
 
 /**
- * Creates an image blob of the current frame.
- * @return {!Promise<Blob>} Promise for the result.
- */
-cca.views.camera.Preview.prototype.toImage = function() {
-  var canvas = document.createElement('canvas');
-  var ctx = canvas.getContext('2d');
-  canvas.width = this.video_.videoWidth;
-  canvas.height = this.video_.videoHeight;
-  ctx.drawImage(this.video_, 0, 0);
-  return new Promise((resolve, reject) => {
-    canvas.toBlob((blob) => {
-      if (blob) {
-        resolve(blob);
-      } else {
-        reject(new Error('Photo blob error.'));
-      }
-    }, 'image/jpeg');
-  });
-};
-
-/**
  * Checks preview whether to show preview metadata or not.
  * @private
  */
diff --git a/chrome/browser/resources/chromeos/camera/src/js/views/camera/review_result.js b/chrome/browser/resources/chromeos/camera/src/js/views/camera/review_result.js
new file mode 100644
index 0000000..64b6412
--- /dev/null
+++ b/chrome/browser/resources/chromeos/camera/src/js/views/camera/review_result.js
@@ -0,0 +1,126 @@
+// Copyright (c) 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+'use strict';
+
+/**
+ * Namespace for the Camera app.
+ */
+var cca = cca || {};
+
+/**
+ * Namespace for views.
+ */
+cca.views = cca.views || {};
+
+/**
+ * Namespace for Camera view.
+ */
+cca.views.camera = cca.views.camera || {};
+
+/**
+ * Creates a controller for reviewing intent result in Camera view.
+ */
+cca.views.camera.ReviewResult = class {
+  /**
+   * @public
+   */
+  constructor() {
+    /**
+     * @const {!HTMLImageElement}
+     * @private
+     */
+    this.reviewPhotoResult_ = /** @type {!HTMLImageElement} */ (
+        document.querySelector('#review-photo-result'));
+
+    /**
+     * @const {!HTMLVideoElement}
+     * @private
+     */
+    this.reviewVideoResult_ = /** @type {!HTMLVideoElement} */ (
+        document.querySelector('#review-video-result'));
+
+    /**
+     * Function resolving open result call called with whether user confirms
+     * after reviewing intent result.
+     * @type {?function(boolean)}
+     * @private
+     */
+    this.resolveOpen_ = null;
+
+    this.reviewVideoResult_.onended = () => {
+      this.reviewVideoResult_.currentTime = 0;
+      cca.state.set('playing-result-video', false);
+    };
+
+    const addClickListener = (selector, handler) =>
+        document.querySelector(selector).addEventListener('click', handler);
+    addClickListener('#confirm-result', () => this.close_(true));
+    addClickListener('#cancel-result', () => this.close_(false));
+    addClickListener('#play-result-video', () => this.playResultVideo_());
+  }
+
+  /**
+   * Starts playing result video.
+   * @private
+   */
+  playResultVideo_() {
+    if (cca.state.get('playing-result-video')) {
+      return;
+    }
+    cca.state.set('playing-result-video', true);
+    this.reviewVideoResult_.play();
+  }
+
+  /**
+   * Closes review result UI and resolves its open promise with whether user
+   * confirms after reviewing the result.
+   * @param {boolean} confirmed
+   * @private
+   */
+  close_(confirmed) {
+    if (this.resolveOpen_ === null) {
+      console.error('Close review result with no unresolved open.');
+      return;
+    }
+    const resolve = this.resolveOpen_;
+    this.resolveOpen_ = null;
+    cca.state.set('review-result', false);
+    cca.state.set('playing-result-video', false);
+    this.reviewPhotoResult_.src = '';
+    this.reviewVideoResult_.src = '';
+    resolve(confirmed);
+  }
+
+  /**
+   * Opens photo result blob and shows photo on review result UI.
+   * @param {!Blob} blob Photo result blob.
+   * @return {!Promise<boolean>} Promise resolved with whether user confirms
+   *     with the photo result.
+   */
+  async openPhoto(blob) {
+    const img = await cca.util.blobToImage(blob);
+    this.reviewPhotoResult_.src = img.src;
+    cca.state.set('review-photo-result', true);
+    cca.state.set('review-result', true);
+    return new Promise((resolve) => {
+      this.resolveOpen_ = resolve;
+    });
+  }
+
+  /**
+   * Opens video result file and shows video on review result UI.
+   * @param {!FileEntry} fileEntry Video result file.
+   * @return {!Promise<boolean>} Promise resolved with whether user confirms
+   *     with the video result.
+   */
+  async openVideo(fileEntry) {
+    this.reviewVideoResult_.src = fileEntry.toURL();
+    cca.state.set('review-photo-result', false);
+    cca.state.set('review-result', true);
+    return new Promise((resolve) => {
+      this.resolveOpen_ = resolve;
+    });
+  }
+};
diff --git a/chrome/browser/resources/chromeos/camera/src/js/views/camera_intent.js b/chrome/browser/resources/chromeos/camera/src/js/views/camera_intent.js
new file mode 100644
index 0000000..bb3baca4
--- /dev/null
+++ b/chrome/browser/resources/chromeos/camera/src/js/views/camera_intent.js
@@ -0,0 +1,112 @@
+// Copyright (c) 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+'use strict';
+
+/**
+ * Namespace for the Camera app.
+ */
+var cca = cca || {};
+
+/**
+ * Namespace for views.
+ */
+cca.views = cca.views || {};
+
+/**
+ * Creates the camera-intent-view controller.
+ */
+cca.views.CameraIntent = class extends cca.views.Camera {
+  /**
+   * @param {!cca.intent.Intent} intent
+   * @param {!cca.device.DeviceInfoUpdater} infoUpdater
+   * @param {!cca.device.PhotoResolPreferrer} photoPreferrer
+   * @param {!cca.device.VideoConstraintsPreferrer} videoPreferrer
+   */
+  constructor(intent, infoUpdater, photoPreferrer, videoPreferrer) {
+    const resultSaver = {
+      savePhoto: async (blob) => {
+        this.photoResult_ = blob;
+        const buf = await blob.arrayBuffer();
+        await this.intent_.appendData(new Uint8Array(buf));
+      },
+      startSaveVideo: async () => {
+        return await cca.models.IntentVideoSaver.create(intent);
+      },
+      finishSaveVideo: async (video, savedName) => {
+        this.videoResult_ = await video.endWrite();
+      },
+    };
+    super(resultSaver, infoUpdater, photoPreferrer, videoPreferrer);
+
+    /**
+     * @type {!cca.intent.Intent}
+     * @private
+     */
+    this.intent_ = intent;
+
+    /**
+     * @type {?Blob}
+     * @private
+     */
+    this.photoResult_ = null;
+
+    /**
+     * @type {?FileEntry}
+     * @private
+     */
+    this.videoResult_ = null;
+
+    /**
+     * @type {!cca.views.camera.ReviewResult}
+     * @private
+     */
+    this.reviewResult_ = new cca.views.camera.ReviewResult();
+  }
+
+  /**
+   * @override
+   */
+  beginTake_() {
+    if (this.photoResult_ !== null) {
+      URL.revokeObjectURL(this.photoResult_);
+    }
+    this.photoResult_ = null;
+    this.videoResult_ = null;
+
+    const take = super.beginTake_();
+    if (take === null) {
+      return null;
+    }
+    return (async () => {
+      await take;
+
+      if (this.photoResult_ === null && this.videoResult_ === null) {
+        console.warn('End take without intent result.');
+        return;
+      }
+      cca.state.set('suspend', true);
+      await this.restart();
+      const confirmed = await (
+          this.photoResult_ !== null ?
+              this.reviewResult_.openPhoto(this.photoResult_) :
+              this.reviewResult_.openVideo(this.videoResult_));
+      if (confirmed) {
+        await this.intent_.finish();
+        window.close();
+        return;
+      }
+      cca.state.set('suspend', false);
+      await this.intent_.clearData();
+      await this.restart();
+    })();
+  }
+
+  /**
+   * @override
+   */
+  async startWithDevice_(deviceId) {
+    return this.startWithMode_(deviceId, this.defaultMode);
+  }
+};
diff --git a/chrome/browser/resources/chromeos/camera/src/views/main.html b/chrome/browser/resources/chromeos/camera/src/views/main.html
index fc78682..3f36521 100644
--- a/chrome/browser/resources/chromeos/camera/src/views/main.html
+++ b/chrome/browser/resources/chromeos/camera/src/views/main.html
@@ -21,6 +21,7 @@
     <script src="../js/sound.js"></script>
     <script src="../js/scrollbar.js"></script>
     <script src="../js/gallerybutton.js"></script>
+    <script src="../js/device/error.js"></script>
     <script src="../js/device/camera3_device_info.js"></script>
     <script src="../js/device/constraints_preferrer.js"></script>
     <script src="../js/device/device_info_updater.js"></script>
@@ -28,7 +29,9 @@
     <script src="../js/models/gallery.js"></script>
     <script src="../js/models/filesystem.js"></script>
     <script src="../js/models/result_saver.js"></script>
-    <script src="../js/models/video_saver.js"></script>
+    <script src="../js/models/video_saver_interface.js"></script>
+    <script src="../js/models/file_video_saver.js"></script>
+    <script src="../js/models/intent_video_saver.js"></script>
     <script src="../js/mojo/mojo_bindings_lite.js"></script>
     <script src="../js/mojo/camera_metadata_tags.mojom-lite.js"></script>
     <script src="../js/mojo/camera_metadata.mojom-lite.js"></script>
@@ -45,10 +48,12 @@
     <script src="../js/views/view.js"></script>
     <script src="../js/views/gallery_base.js"></script>
     <script src="../js/views/camera.js"></script>
+    <script src="../js/views/camera_intent.js"></script>
     <script src="../js/views/camera/layout.js"></script>
     <script src="../js/views/camera/options.js"></script>
     <script src="../js/views/camera/preview.js"></script>
     <script src="../js/views/camera/recordtime.js"></script>
+    <script src="../js/views/camera/review_result.js"></script>
     <script src="../js/views/camera/timertick.js"></script>
     <script src="../js/views/camera/modes.js"></script>
     <script src="../js/views/dialog.js"></script>
@@ -61,7 +66,12 @@
   <body class="sound mirror mic _3x3">
     <div id="camera">
       <div id="preview-wrapper" aria-hidden="true">
-        <video id="preview-video"></video>
+        <img id="review-photo-result" class="preview-content">
+        <video id="review-video-result" class="preview-content"></video>
+        <div class="buttons circle centered-overlay">
+          <button id="play-result-video"></button>
+        </div>
+        <video id="preview-video" class="preview-content"></video>
         <div id="preview-metadata">
           <div id="preview-stat" class="metadata-row mode-on">
             <span class="metadata-category">Stat</span>
@@ -140,6 +150,10 @@
         <button id="gallery-enter" tabindex="0"
                 i18n-label="gallery_button" hidden></button>
       </div>
+      <div id="confirm-result-groups" class="buttons right-stripe circle">
+        <button id="confirm-result"></button>
+        <button id="cancel-result"></button>
+      </div>
       <div class="bottom-stripe left-stripe buttons circle">
         <button id="switch-device" tabindex="0"
                 i18n-label="switch_camera_button"></button>
diff --git a/chrome/browser/resources/chromeos/login/md_screen_container.css b/chrome/browser/resources/chromeos/login/md_screen_container.css
index 1d559ae..2c6f3493 100644
--- a/chrome/browser/resources/chromeos/login/md_screen_container.css
+++ b/chrome/browser/resources/chromeos/login/md_screen_container.css
@@ -14,8 +14,6 @@
   display: flex;
   justify-content: center;
   left: 0;
-  /* This enables scrolling. Min resolution: 1024x768 */
-  min-height: calc(768px - var(--shelf-area-height));
   perspective: 1px; /* Workaround, see http://crbug.com/360567 */
   position: absolute;
   right: 0;
diff --git a/chrome/browser/resources/print_preview/ui/pin_settings.js b/chrome/browser/resources/print_preview/ui/pin_settings.js
index e6c1c40..8e56e89 100644
--- a/chrome/browser/resources/print_preview/ui/pin_settings.js
+++ b/chrome/browser/resources/print_preview/ui/pin_settings.js
@@ -66,7 +66,7 @@
   /** @private */
   onCollapseChanged_: function() {
     if (this.pinEnabled_) {
-      /** @type {!CrInputElement} */ (this.$.pinValue).inputElement.focus();
+      /** @type {!CrInputElement} */ (this.$.pinValue).focusInput();
     }
   },
 
diff --git a/chrome/browser/resources/settings/people_page/sync_page.js b/chrome/browser/resources/settings/people_page/sync_page.js
index fd28dad6..e65b7ef 100644
--- a/chrome/browser/resources/settings/people_page/sync_page.js
+++ b/chrome/browser/resources/settings/people_page/sync_page.js
@@ -506,7 +506,10 @@
       case settings.PageStatus.PASSPHRASE_FAILED:
         if (this.pageStatus_ == this.pages_.CONFIGURE && this.syncPrefs &&
             this.syncPrefs.passphraseRequired) {
-          this.$$('#existingPassphraseInput').invalid = true;
+          const passphraseInput = /** @type {!CrInputElement} */ (
+              this.$$('#existingPassphraseInput'));
+          passphraseInput.invalid = true;
+          passphraseInput.focusInput();
         }
         return;
     }
diff --git a/chrome/browser/resources/settings/site_settings/site_data.html b/chrome/browser/resources/settings/site_settings/site_data.html
index 9fc485d..f1a028c9 100644
--- a/chrome/browser/resources/settings/site_settings/site_data.html
+++ b/chrome/browser/resources/settings/site_settings/site_data.html
@@ -44,7 +44,7 @@
       </cr-button>
       <cr-button disabled$="[[isLoading_]]" id="removeThirdPartyCookies"
           on-click="onRemoveThirdPartyCookiesTap_"
-          hidden$="[[!enableRemovingAllThirdPartyCookies_]]">
+          hidden$="[[!showRemoveThirdPartyCookies_(sites.length, filter)]]">
           $i18n{siteSettingsCookieRemoveAllThirdParty}
       </cr-button>
     </div>
diff --git a/chrome/browser/resources/settings/site_settings/site_data.js b/chrome/browser/resources/settings/site_settings/site_data.js
index 644aedc4..cd6370aa 100644
--- a/chrome/browser/resources/settings/site_settings/site_data.js
+++ b/chrome/browser/resources/settings/site_settings/site_data.js
@@ -75,15 +75,6 @@
 
     /** @private */
     listBlurred_: Boolean,
-
-    /** @private */
-    enableRemovingAllThirdPartyCookies_: {
-      type: Boolean,
-      value: function() {
-        return loadTimeData.getBoolean('enableRemovingAllThirdPartyCookies') &&
-            (this.sites.length > 0);
-      }
-    },
   },
 
   /** @private {settings.LocalDataBrowserProxy} */
@@ -287,4 +278,13 @@
         new URLSearchParams('site=' + event.model.item.site));
     this.lastSelected_ = event.model;
   },
+
+  /**
+   * @private
+   * @return {boolean}
+   */
+  showRemoveThirdPartyCookies_: function() {
+    return loadTimeData.getBoolean('enableRemovingAllThirdPartyCookies') &&
+        this.sites.length > 0 && this.filter.length == 0;
+  },
 });
diff --git a/chrome/browser/sharing/sharing_sync_preference.cc b/chrome/browser/sharing/sharing_sync_preference.cc
index cf535b02..0b6d3d9 100644
--- a/chrome/browser/sharing/sharing_sync_preference.cc
+++ b/chrome/browser/sharing/sharing_sync_preference.cc
@@ -22,7 +22,6 @@
 const char kDeviceP256dh[] = "device_p256dh";
 const char kDeviceAuthSecret[] = "device_auth_secret";
 const char kDeviceCapabilities[] = "device_capabilities";
-const char kDeviceLastUpdated[] = "device_last_updated";
 
 const char kRegistrationAuthorizedEntity[] = "registration_authorized_entity";
 const char kRegistrationFcmToken[] = "registration_fcm_token";
@@ -162,16 +161,12 @@
 void SharingSyncPreference::SetSyncDevice(const std::string& guid,
                                           const Device& device) {
   DictionaryPrefUpdate update(prefs_, prefs::kSharingSyncedDevices);
-  update->SetKey(guid, DeviceToValue(device, base::Time::Now()));
+  update->SetKey(guid, DeviceToValue(device));
 }
 
 void SharingSyncPreference::RemoveDevice(const std::string& guid) {
   DictionaryPrefUpdate update(prefs_, prefs::kSharingSyncedDevices);
-  // Clear all values of device with |guid| by setting its value to an empty
-  // entry that only contains a timestamp so other devices can merge it.
-  base::Value cleared(base::Value::Type::DICTIONARY);
-  cleared.SetKey(kDeviceLastUpdated, base::CreateTimeValue(base::Time::Now()));
-  update->SetKey(guid, std::move(cleared));
+  update->RemoveKey(guid);
 }
 
 base::Optional<SharingSyncPreference::FCMRegistration>
@@ -226,8 +221,7 @@
 }
 
 // static
-base::Value SharingSyncPreference::DeviceToValue(const Device& device,
-                                                 base::Time timestamp) {
+base::Value SharingSyncPreference::DeviceToValue(const Device& device) {
   std::string base64_p256dh, base64_auth_secret;
   base::Base64Encode(device.p256dh, &base64_p256dh);
   base::Base64Encode(device.auth_secret, &base64_auth_secret);
@@ -243,7 +237,6 @@
   result.SetStringKey(kDeviceP256dh, base64_p256dh);
   result.SetStringKey(kDeviceAuthSecret, base64_auth_secret);
   result.SetIntKey(kDeviceCapabilities, capabilities);
-  result.SetKey(kDeviceLastUpdated, base::CreateTimeValue(timestamp));
   return result;
 }
 
diff --git a/chrome/browser/sharing/sharing_sync_preference.h b/chrome/browser/sharing/sharing_sync_preference.h
index 01fbbdef..9240fb76 100644
--- a/chrome/browser/sharing/sharing_sync_preference.h
+++ b/chrome/browser/sharing/sharing_sync_preference.h
@@ -125,7 +125,7 @@
  private:
   friend class SharingSyncPreferenceTest;
 
-  static base::Value DeviceToValue(const Device& device, base::Time timestamp);
+  static base::Value DeviceToValue(const Device& device);
 
   static base::Optional<Device> ValueToDevice(const base::Value& value);
 
diff --git a/chrome/browser/sharing/sharing_sync_preference_unittest.cc b/chrome/browser/sharing/sharing_sync_preference_unittest.cc
index 6d51d49..4b7164f 100644
--- a/chrome/browser/sharing/sharing_sync_preference_unittest.cc
+++ b/chrome/browser/sharing/sharing_sync_preference_unittest.cc
@@ -44,13 +44,6 @@
                                       kDeviceAuthToken, kClickToCallEnabled));
   }
 
-  static base::Value CreateRandomDevice(base::Time timestamp) {
-    return SharingSyncPreference::DeviceToValue(
-        {base::GenerateGUID(), kDeviceP256dh, kDeviceAuthToken,
-         kClickToCallEnabled},
-        timestamp);
-  }
-
   sync_preferences::TestingPrefServiceSyncable prefs_;
   SharingSyncPreference sharing_sync_preference_;
 };
diff --git a/chrome/browser/signin/account_reconcilor_factory.cc b/chrome/browser/signin/account_reconcilor_factory.cc
index 61fd5f7a..115dfd4 100644
--- a/chrome/browser/signin/account_reconcilor_factory.cc
+++ b/chrome/browser/signin/account_reconcilor_factory.cc
@@ -29,7 +29,6 @@
 #include "chrome/browser/chromeos/account_manager/account_manager_util.h"
 #include "chrome/browser/chromeos/account_manager/account_migration_runner.h"
 #include "chrome/browser/lifetime/application_lifetime.h"
-#include "chromeos/constants/chromeos_features.h"
 #include "chromeos/tpm/install_attributes.h"
 #include "components/signin/core/browser/active_directory_account_reconcilor_delegate.h"
 #include "components/user_manager/user_manager.h"
@@ -181,15 +180,12 @@
             signin::ActiveDirectoryAccountReconcilorDelegate>();
       }
 
-      // TODO(sinhak): Remove the if-condition (and use
-      // |MirrorAccountReconcilorDelegate|) when all Chrome OS users have been
-      // migrated to Account Manager.
-      if (chromeos::features::IsAccountManagerEnabled()) {
-        return std::make_unique<ChromeOSAccountReconcilorDelegate>(
-            IdentityManagerFactory::GetForProfile(profile),
-            chromeos::AccountManagerMigratorFactory::GetForBrowserContext(
-                profile));
-      }
+      // TODO(sinhak): Use |MirrorAccountReconcilorDelegate|) when all Chrome OS
+      // users have been migrated to Account Manager.
+      return std::make_unique<ChromeOSAccountReconcilorDelegate>(
+          IdentityManagerFactory::GetForProfile(profile),
+          chromeos::AccountManagerMigratorFactory::GetForBrowserContext(
+              profile));
 #elif defined(OS_ANDROID)
       if (base::FeatureList::IsEnabled(signin::kMiceFeature))
         return std::make_unique<signin::MiceAccountReconcilorDelegate>();
diff --git a/chrome/browser/signin/chrome_signin_helper.cc b/chrome/browser/signin/chrome_signin_helper.cc
index 703983c5..c276278 100644
--- a/chrome/browser/signin/chrome_signin_helper.cc
+++ b/chrome/browser/signin/chrome_signin_helper.cc
@@ -58,7 +58,6 @@
 #include "chrome/browser/supervised_user/supervised_user_service_factory.h"
 #include "chrome/browser/ui/settings_window_manager_chromeos.h"
 #include "chrome/browser/ui/webui/signin/inline_login_handler_dialog_chromeos.h"
-#include "chromeos/constants/chromeos_features.h"
 #endif
 
 namespace signin {
@@ -185,58 +184,45 @@
       AccountReconcilorFactory::GetForProfile(profile);
   account_reconcilor->OnReceivedManageAccountsResponse(service_type);
 #if defined(OS_CHROMEOS)
-  if (chrome::FindBrowserWithWebContents(web_contents) &&
-      service_type == GAIA_SERVICE_TYPE_INCOGNITO) {
-    chrome::NewIncognitoWindow(profile);
-    return;
-  }
   signin_metrics::LogAccountReconcilorStateOnGaiaResponse(
       account_reconcilor->GetState());
 
-  if (chromeos::features::IsAccountManagerEnabled()) {
-    // Chrome OS Account Manager is available. The only allowed operations
-    // are:
-    //
-    // - Going Incognito (already handled in above switch-case).
-    // - Displaying the Account Manager for managing accounts.
-    // - Displaying a reauthentication window: Enterprise GSuite Accounts could
-    // have been forced through an online in-browser sign-in for sensitive
-    // webpages, thereby decreasing their session validity. After their session
-    // expires, they will receive a "Mirror" re-authentication request for all
-    // Google web properties.
+  // Do not do anything if the navigation happened in the "background".
+  if (!chrome::FindBrowserWithWebContents(web_contents))
+    return;
 
-    // Do not display Account Manager if the navigation happened in the
-    // "background".
-    if (!chrome::FindBrowserWithWebContents(web_contents))
-      return;
+  // The only allowed operations are:
+  // - Going Incognito.
+  // - Displaying the Account Manager for managing accounts.
+  // - Displaying a reauthentication window: Enterprise GSuite Accounts could
+  // have been forced through an online in-browser sign-in for sensitive
+  // webpages, thereby decreasing their session validity. After their session
+  // expires, they will receive a "Mirror" re-authentication request for all
+  // Google web properties.
 
-    if (manage_accounts_params.email.empty()) {
-      // Display Account Manager for managing accounts.
-      chrome::SettingsWindowManager::GetInstance()->ShowOSSettings(
-          profile, chrome::kAccountManagerSubPage);
-    } else {
-      // Do not display the re-authentication dialog if this event was triggered
-      // by supervision being enabled for an account.  In this situation, a
-      // complete signout is required.
-      SupervisedUserService* service =
-          SupervisedUserServiceFactory::GetForProfile(profile);
-      if (service && service->signout_required_after_supervision_enabled()) {
-        return;
-      }
-
-      // Display a re-authentication dialog.
-      chromeos::InlineLoginHandlerDialogChromeOS::Show(
-          manage_accounts_params.email);
-    }
+  if (service_type == GAIA_SERVICE_TYPE_INCOGNITO) {
+    chrome::NewIncognitoWindow(profile);
     return;
   }
 
-  // TODO(sinhak): Remove this when Chrome OS Account Manager is released.
-  // Chrome OS does not have an account picker right now. To fix
-  // https://crbug.com/807568, this is a no-op here. This is OK because in
-  // the limited cases where Mirror is available on Chrome OS, 1:1 account
-  // consistency is enforced and adding/removing accounts is not allowed,
-  // GAIA_SERVICE_TYPE_INCOGNITO may be allowed though.
+  if (manage_accounts_params.email.empty()) {
+    // Display Account Manager for managing accounts.
+    chrome::SettingsWindowManager::GetInstance()->ShowOSSettings(
+        profile, chrome::kAccountManagerSubPage);
+  } else {
+    // Do not display the re-authentication dialog if this event was triggered
+    // by supervision being enabled for an account.  In this situation, a
+    // complete signout is required.
+    SupervisedUserService* service =
+        SupervisedUserServiceFactory::GetForProfile(profile);
+    if (service && service->signout_required_after_supervision_enabled()) {
+      return;
+    }
+
+    // Display a re-authentication dialog.
+    chromeos::InlineLoginHandlerDialogChromeOS::Show(
+        manage_accounts_params.email);
+  }
   return;
 
 #else   // !defined(OS_CHROMEOS)
diff --git a/chrome/browser/sync/profile_sync_service_android.cc b/chrome/browser/sync/profile_sync_service_android.cc
index 30863e6e8..e0195f0f 100644
--- a/chrome/browser/sync/profile_sync_service_android.cc
+++ b/chrome/browser/sync/profile_sync_service_android.cc
@@ -278,11 +278,12 @@
   sync_service_->GetUserSettings()->EnableEncryptEverything();
 }
 
-jboolean ProfileSyncServiceAndroid::IsPassphraseRequiredForDecryption(
+jboolean ProfileSyncServiceAndroid::IsPassphraseRequiredForPreferredDataTypes(
     JNIEnv* env,
     const JavaParamRef<jobject>& obj) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
-  return sync_service_->GetUserSettings()->IsPassphraseRequiredForDecryption();
+  return sync_service_->GetUserSettings()
+      ->IsPassphraseRequiredForPreferredDataTypes();
 }
 
 jboolean ProfileSyncServiceAndroid::IsUsingSecondaryPassphrase(
diff --git a/chrome/browser/sync/profile_sync_service_android.h b/chrome/browser/sync/profile_sync_service_android.h
index e5cbed0..69edabe 100644
--- a/chrome/browser/sync/profile_sync_service_android.h
+++ b/chrome/browser/sync/profile_sync_service_android.h
@@ -85,7 +85,7 @@
       const base::android::JavaParamRef<jobject>& obj);
   void EnableEncryptEverything(JNIEnv* env,
                                const base::android::JavaParamRef<jobject>& obj);
-  jboolean IsPassphraseRequiredForDecryption(
+  jboolean IsPassphraseRequiredForPreferredDataTypes(
       JNIEnv* env,
       const base::android::JavaParamRef<jobject>& obj);
   jboolean IsUsingSecondaryPassphrase(
diff --git a/chrome/browser/sync/sync_ui_util.cc b/chrome/browser/sync/sync_ui_util.cc
index 7ee628d..5014a8de 100644
--- a/chrome/browser/sync/sync_ui_util.cc
+++ b/chrome/browser/sync/sync_ui_util.cc
@@ -161,7 +161,8 @@
 
   if (service->GetUserSettings()->IsFirstSetupComplete()) {
     // Check for a passphrase error.
-    if (service->GetUserSettings()->IsPassphraseRequiredForDecryption()) {
+    if (service->GetUserSettings()
+            ->IsPassphraseRequiredForPreferredDataTypes()) {
       if (status_label) {
         *status_label =
             l10n_util::GetStringUTF16(IDS_SYNC_STATUS_NEEDS_PASSWORD);
@@ -344,7 +345,8 @@
 
 bool ShouldShowPassphraseError(const syncer::SyncService* service) {
   return service->GetUserSettings()->IsFirstSetupComplete() &&
-         service->GetUserSettings()->IsPassphraseRequiredForDecryption();
+         service->GetUserSettings()
+             ->IsPassphraseRequiredForPreferredDataTypes();
 }
 
 }  // namespace sync_ui_util
diff --git a/chrome/browser/sync/test/integration/encryption_helper.cc b/chrome/browser/sync/test/integration/encryption_helper.cc
index 6205c96..497cddd 100644
--- a/chrome/browser/sync/test/integration/encryption_helper.cc
+++ b/chrome/browser/sync/test/integration/encryption_helper.cc
@@ -169,8 +169,9 @@
     : SingleClientStatusChangeChecker(service), desired_state_(desired_state) {}
 
 bool PassphraseRequiredStateChecker::IsExitConditionSatisfied() {
-  return service()->GetUserSettings()->IsPassphraseRequiredForDecryption() ==
-         desired_state_;
+  return service()
+             ->GetUserSettings()
+             ->IsPassphraseRequiredForPreferredDataTypes() == desired_state_;
 }
 
 std::string PassphraseRequiredStateChecker::GetDebugMessage() const {
diff --git a/chrome/browser/sync/test/integration/profile_sync_service_harness.cc b/chrome/browser/sync/test/integration/profile_sync_service_harness.cc
index 514365f05..edc13b0 100644
--- a/chrome/browser/sync/test/integration/profile_sync_service_harness.cc
+++ b/chrome/browser/sync/test/integration/profile_sync_service_harness.cc
@@ -6,6 +6,7 @@
 
 #include <cstddef>
 #include <sstream>
+#include <utility>
 
 #include "base/command_line.h"
 #include "base/json/json_writer.h"
@@ -98,8 +99,13 @@
     if (HasAuthError(service())) {
       return true;
     }
-    if (service()->GetPassphraseRequiredReasonForTest() ==
-        syncer::REASON_DECRYPTION) {
+    // TODO(crbug.com/1010397): The verification of INITIALIZING is only needed
+    // due to SyncEncryptionHandlerImpl issuing an unnecessary
+    // OnPassphraseRequired() during initialization.
+    if (service()
+            ->GetUserSettings()
+            ->IsPassphraseRequiredForPreferredDataTypes() &&
+        transport_state != syncer::SyncService::TransportState::INITIALIZING) {
       LOG(FATAL)
           << "A passphrase is required for decryption but was not provided. "
              "Waiting for sync to become available won't succeed. Make sure "
@@ -658,9 +664,8 @@
        << ", server conflicts: " << snap.num_server_conflicts()
        << ", num_updates_downloaded : "
        << snap.model_neutral_state().num_updates_downloaded_total
-       << ", passphrase_required_reason: "
-       << syncer::PassphraseRequiredReasonToString(
-              service()->GetPassphraseRequiredReasonForTest())
+       << ", passphrase_required: "
+       << service()->GetUserSettings()->IsPassphraseRequired()
        << ", notifications_enabled: " << status.notifications_enabled
        << ", service_is_active: " << service()->IsSyncFeatureActive();
   } else {
diff --git a/chrome/browser/sync/test/integration/two_client_custom_passphrase_sync_test.cc b/chrome/browser/sync/test/integration/two_client_custom_passphrase_sync_test.cc
index 7833d79..6fbdee3 100644
--- a/chrome/browser/sync/test/integration/two_client_custom_passphrase_sync_test.cc
+++ b/chrome/browser/sync/test/integration/two_client_custom_passphrase_sync_test.cc
@@ -67,7 +67,7 @@
                    ->SetDecryptionPassphrase("incorrect passphrase"));
   EXPECT_TRUE(GetSyncService(kDecryptingClientId)
                   ->GetUserSettings()
-                  ->IsPassphraseRequiredForDecryption());
+                  ->IsPassphraseRequiredForPreferredDataTypes());
 }
 
 IN_PROC_BROWSER_TEST_F(TwoClientCustomPassphraseSyncTest, ClientsCanSyncData) {
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index 08271819..f4656df9 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -135,8 +135,6 @@
     "page_info/page_info_ui.h",
     "passwords/account_avatar_fetcher.cc",
     "passwords/account_avatar_fetcher.h",
-    "passwords/credential_leak_dialog_utils.cc",
-    "passwords/credential_leak_dialog_utils.h",
     "passwords/manage_passwords_state.cc",
     "passwords/manage_passwords_state.h",
     "passwords/manage_passwords_view_utils.cc",
diff --git a/chrome/browser/ui/input_method/input_method_engine_base.cc b/chrome/browser/ui/input_method/input_method_engine_base.cc
index 0736114..10e5bed8 100644
--- a/chrome/browser/ui/input_method/input_method_engine_base.cc
+++ b/chrome/browser/ui/input_method/input_method_engine_base.cc
@@ -151,7 +151,6 @@
       context_id_(0),
       next_context_id_(1),
       profile_(nullptr),
-      next_request_id_(1),
       composition_changed_(false),
       text_(""),
       commit_text_changed_(false),
@@ -458,27 +457,38 @@
     composition_changed_ = false;
   }
 
-  auto request = request_map_.find(request_id);
-  if (request == request_map_.end()) {
+  const auto it = pending_key_events_.find(request_id);
+  if (it == pending_key_events_.end()) {
     LOG(ERROR) << "Request ID not found: " << request_id;
     return;
   }
 
-  std::move(request->second.second).Run(handled);
-  request_map_.erase(request);
+  std::move(it->second.callback).Run(handled);
+  pending_key_events_.erase(it);
 }
 
-std::string InputMethodEngineBase::AddRequest(
+std::string InputMethodEngineBase::AddPendingKeyEvent(
     const std::string& component_id,
-    ui::IMEEngineHandlerInterface::KeyEventDoneCallback key_data) {
+    ui::IMEEngineHandlerInterface::KeyEventDoneCallback callback) {
   std::string request_id = base::NumberToString(next_request_id_);
   ++next_request_id_;
 
-  request_map_[request_id] = std::make_pair(component_id, std::move(key_data));
+  pending_key_events_.emplace(
+      request_id, PendingKeyEvent(component_id, std::move(callback)));
 
   return request_id;
 }
 
+InputMethodEngineBase::PendingKeyEvent::PendingKeyEvent(
+    const std::string& component_id,
+    ui::IMEEngineHandlerInterface::KeyEventDoneCallback callback)
+    : component_id(component_id), callback(std::move(callback)) {}
+
+InputMethodEngineBase::PendingKeyEvent::PendingKeyEvent(
+    PendingKeyEvent&& other) = default;
+
+InputMethodEngineBase::PendingKeyEvent::~PendingKeyEvent() = default;
+
 void InputMethodEngineBase::DeleteSurroundingTextToInputContext(
     int offset,
     size_t number_of_chars) {
diff --git a/chrome/browser/ui/input_method/input_method_engine_base.h b/chrome/browser/ui/input_method/input_method_engine_base.h
index ac7b934..2a7aff5 100644
--- a/chrome/browser/ui/input_method/input_method_engine_base.h
+++ b/chrome/browser/ui/input_method/input_method_engine_base.h
@@ -197,10 +197,10 @@
                        const std::string& request_id,
                        bool handled);
 
-  // Adds unprocessed key event to |request_map_|.
-  std::string AddRequest(
+  // Returns the request ID for this key event.
+  std::string AddPendingKeyEvent(
       const std::string& component_id,
-      ui::IMEEngineHandlerInterface::KeyEventDoneCallback key_data);
+      ui::IMEEngineHandlerInterface::KeyEventDoneCallback callback);
 
   int GetContextIdForTesting() const { return context_id_; }
 
@@ -210,6 +210,20 @@
   }
 
  protected:
+  struct PendingKeyEvent {
+    PendingKeyEvent(
+        const std::string& component_id,
+        ui::IMEEngineHandlerInterface::KeyEventDoneCallback callback);
+    PendingKeyEvent(PendingKeyEvent&& other);
+    ~PendingKeyEvent();
+
+    std::string component_id;
+    ui::IMEEngineHandlerInterface::KeyEventDoneCallback callback;
+
+   private:
+    DISALLOW_COPY_AND_ASSIGN(PendingKeyEvent);
+  };
+
   // Returns true if this IME is active, false if not.
   virtual bool IsActive() const = 0;
 
@@ -253,13 +267,8 @@
 
   Profile* profile_;
 
-  using RequestMap =
-      std::map<std::string,
-               std::pair<std::string,
-                         ui::IMEEngineHandlerInterface::KeyEventDoneCallback>>;
-
-  unsigned int next_request_id_;
-  RequestMap request_map_;
+  unsigned int next_request_id_ = 1;
+  std::map<std::string, PendingKeyEvent> pending_key_events_;
 
   // The composition text to be set from calling input.ime.setComposition API.
   ui::CompositionText composition_;
diff --git a/chrome/browser/ui/passwords/credential_leak_dialog_controller_impl.cc b/chrome/browser/ui/passwords/credential_leak_dialog_controller_impl.cc
index b029f07d..6f0ddad 100644
--- a/chrome/browser/ui/passwords/credential_leak_dialog_controller_impl.cc
+++ b/chrome/browser/ui/passwords/credential_leak_dialog_controller_impl.cc
@@ -4,9 +4,9 @@
 
 #include "chrome/browser/ui/passwords/credential_leak_dialog_controller_impl.h"
 
-#include "chrome/browser/ui/passwords/credential_leak_dialog_utils.h"
 #include "chrome/browser/ui/passwords/password_dialog_prompts.h"
 #include "chrome/browser/ui/passwords/passwords_leak_dialog_delegate.h"
+#include "components/password_manager/core/browser/leak_detection_dialog_utils.h"
 #include "components/password_manager/core/browser/password_manager_metrics_util.h"
 
 using password_manager::CredentialLeakFlags;
@@ -37,7 +37,7 @@
 
 void CredentialLeakDialogControllerImpl::OnCancelDialog() {
   LogLeakDialogTypeAndDismissalReason(
-      leak_dialog_utils::GetLeakDialogType(leak_type_),
+      password_manager::GetLeakDialogType(leak_type_),
       LeakDialogDismissalReason::kClickedClose);
   delegate_->OnLeakDialogHidden();
 }
@@ -45,12 +45,12 @@
 void CredentialLeakDialogControllerImpl::OnAcceptDialog() {
   if (ShouldCheckPasswords()) {
     LogLeakDialogTypeAndDismissalReason(
-        leak_dialog_utils::GetLeakDialogType(leak_type_),
+        password_manager::GetLeakDialogType(leak_type_),
         LeakDialogDismissalReason::kClickedCheckPasswords);
     delegate_->NavigateToPasswordCheckup();
   } else {
     LogLeakDialogTypeAndDismissalReason(
-        leak_dialog_utils::GetLeakDialogType(leak_type_),
+        password_manager::GetLeakDialogType(leak_type_),
         LeakDialogDismissalReason::kClickedOk);
   }
   delegate_->OnLeakDialogHidden();
@@ -58,40 +58,40 @@
 
 void CredentialLeakDialogControllerImpl::OnCloseDialog() {
   LogLeakDialogTypeAndDismissalReason(
-      leak_dialog_utils::GetLeakDialogType(leak_type_),
+      password_manager::GetLeakDialogType(leak_type_),
       LeakDialogDismissalReason::kNoDirectInteraction);
   delegate_->OnLeakDialogHidden();
 }
 
 base::string16 CredentialLeakDialogControllerImpl::GetAcceptButtonLabel()
     const {
-  return leak_dialog_utils::GetAcceptButtonLabel(leak_type_);
+  return password_manager::GetAcceptButtonLabel(leak_type_);
 }
 
 base::string16 CredentialLeakDialogControllerImpl::GetCancelButtonLabel()
     const {
-  return leak_dialog_utils::GetCancelButtonLabel();
+  return password_manager::GetCancelButtonLabel();
 }
 
 base::string16 CredentialLeakDialogControllerImpl::GetDescription() const {
-  return leak_dialog_utils::GetDescription(leak_type_, origin_);
+  return password_manager::GetDescription(leak_type_, origin_);
 }
 
 base::string16 CredentialLeakDialogControllerImpl::GetTitle() const {
-  return leak_dialog_utils::GetTitle(leak_type_);
+  return password_manager::GetTitle(leak_type_);
 }
 
 bool CredentialLeakDialogControllerImpl::ShouldCheckPasswords() const {
-  return leak_dialog_utils::ShouldCheckPasswords(leak_type_);
+  return password_manager::ShouldCheckPasswords(leak_type_);
 }
 
 bool CredentialLeakDialogControllerImpl::ShouldShowCancelButton() const {
-  return leak_dialog_utils::ShouldShowCancelButton(leak_type_);
+  return password_manager::ShouldShowCancelButton(leak_type_);
 }
 
 gfx::Range CredentialLeakDialogControllerImpl::GetChangePasswordBoldRange()
     const {
-  return leak_dialog_utils::GetChangePasswordBoldRange(leak_type_, origin_);
+  return password_manager::GetChangePasswordBoldRange(leak_type_, origin_);
 }
 
 void CredentialLeakDialogControllerImpl::ResetDialog() {
diff --git a/chrome/browser/ui/passwords/credential_leak_dialog_utils.cc b/chrome/browser/ui/passwords/credential_leak_dialog_utils.cc
deleted file mode 100644
index 6ed1e39..0000000
--- a/chrome/browser/ui/passwords/credential_leak_dialog_utils.cc
+++ /dev/null
@@ -1,108 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/ui/passwords/credential_leak_dialog_utils.h"
-
-#include "base/metrics/field_trial_params.h"
-#include "base/strings/utf_string_conversions.h"
-#include "chrome/common/url_constants.h"
-#include "chrome/grit/generated_resources.h"
-#include "components/password_manager/core/browser/leak_detection_dialog_utils.h"
-#include "components/password_manager/core/common/password_manager_features.h"
-#include "components/strings/grit/components_strings.h"
-#include "components/url_formatter/elide_url.h"
-#include "ui/base/l10n/l10n_util.h"
-#include "url/gurl.h"
-#include "url/origin.h"
-
-using password_manager::CredentialLeakFlags;
-using password_manager::CredentialLeakType;
-using password_manager::metrics_util::LeakDialogType;
-
-namespace leak_dialog_utils {
-namespace {
-
-// Formats the |origin| to a human-friendly url string.
-base::string16 GetFormattedUrl(const GURL& origin) {
-  return url_formatter::FormatUrlForSecurityDisplay(
-      origin, url_formatter::SchemeDisplay::OMIT_HTTP_AND_HTTPS);
-}
-
-}  // namespace
-
-base::string16 GetAcceptButtonLabel(CredentialLeakType leak_type) {
-  return l10n_util::GetStringUTF16(
-      ShouldCheckPasswords(leak_type) ? IDS_LEAK_CHECK_CREDENTIALS : IDS_OK);
-}
-
-base::string16 GetCancelButtonLabel() {
-  return l10n_util::GetStringUTF16(IDS_CLOSE);
-}
-
-base::string16 GetDescription(CredentialLeakType leak_type,
-                              const GURL& origin) {
-  const base::string16 formatted = GetFormattedUrl(origin);
-  if (!ShouldCheckPasswords(leak_type)) {
-    std::vector<size_t> offsets;
-    base::string16 bold_message = l10n_util::GetStringUTF16(
-        IDS_CREDENTIAL_LEAK_CHANGE_PASSWORD_BOLD_MESSAGE);
-    return l10n_util::GetStringFUTF16(
-        IDS_CREDENTIAL_LEAK_CHANGE_PASSWORD_MESSAGE, bold_message, formatted,
-        &offsets);
-  } else if (password_manager::IsPasswordSaved(leak_type)) {
-    return l10n_util::GetStringUTF16(
-        IDS_CREDENTIAL_LEAK_CHECK_PASSWORDS_MESSAGE);
-  } else {
-    return l10n_util::GetStringFUTF16(
-        IDS_CREDENTIAL_LEAK_CHANGE_AND_CHECK_PASSWORDS_MESSAGE, formatted);
-  }
-}
-
-base::string16 GetTitle(CredentialLeakType leak_type) {
-  return l10n_util::GetStringUTF16(IDS_CREDENTIAL_LEAK_TITLE);
-}
-
-bool ShouldCheckPasswords(CredentialLeakType leak_type) {
-  return password_manager::IsPasswordUsedOnOtherSites(leak_type) &&
-         password_manager::IsSyncingPasswordsNormally(leak_type);
-}
-
-bool ShouldShowCancelButton(CredentialLeakType leak_type) {
-  return ShouldCheckPasswords(leak_type);
-}
-
-LeakDialogType GetLeakDialogType(CredentialLeakType leak_type) {
-  if (!ShouldCheckPasswords(leak_type))
-    return LeakDialogType::kChange;
-
-  return password_manager::IsPasswordSaved(leak_type)
-             ? LeakDialogType::kCheckup
-             : LeakDialogType::kCheckupAndChange;
-}
-
-GURL GetPasswordCheckupURL() {
-  std::string value = base::GetFieldTrialParamValueByFeature(
-      password_manager::features::kLeakDetection, "leak-check-url");
-  if (value.empty())
-    return GURL(chrome::kPasswordCheckupURL);
-  return GURL(value);
-}
-
-gfx::Range GetChangePasswordBoldRange(CredentialLeakType leak_type,
-                                      const GURL& origin) {
-  if (ShouldCheckPasswords(leak_type))
-    return gfx::Range();
-
-  std::vector<size_t> offsets;
-  const base::string16 bold_message = l10n_util::GetStringUTF16(
-      IDS_CREDENTIAL_LEAK_CHANGE_PASSWORD_BOLD_MESSAGE);
-  const base::string16 change_password_message = l10n_util::GetStringFUTF16(
-      IDS_CREDENTIAL_LEAK_CHANGE_PASSWORD_MESSAGE, bold_message,
-      GetFormattedUrl(origin), &offsets);
-  return offsets.empty()
-             ? gfx::Range()
-             : gfx::Range(offsets[0], offsets[0] + bold_message.length());
-}
-
-}  // namespace leak_dialog_utils
diff --git a/chrome/browser/ui/passwords/credential_leak_dialog_utils.h b/chrome/browser/ui/passwords/credential_leak_dialog_utils.h
deleted file mode 100644
index 54bd37b..0000000
--- a/chrome/browser/ui/passwords/credential_leak_dialog_utils.h
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_UI_PASSWORDS_CREDENTIAL_LEAK_DIALOG_UTILS_H_
-#define CHROME_BROWSER_UI_PASSWORDS_CREDENTIAL_LEAK_DIALOG_UTILS_H_
-
-#include "base/macros.h"
-#include "base/strings/string16.h"
-#include "components/password_manager/core/browser/leak_detection_dialog_utils.h"
-#include "components/password_manager/core/browser/password_manager_metrics_util.h"
-#include "ui/gfx/range/range.h"
-#include "url/gurl.h"
-
-namespace leak_dialog_utils {
-
-// Returns the label for the leak dialog accept button.
-base::string16 GetAcceptButtonLabel(
-    password_manager::CredentialLeakType leak_type);
-
-// Returns the label for the leak dialog cancel button.
-base::string16 GetCancelButtonLabel();
-
-// Returns the leak dialog message based on leak type.
-base::string16 GetDescription(password_manager::CredentialLeakType leak_type,
-                              const GURL& origin);
-
-// Returns the leak dialog title based on leak type.
-base::string16 GetTitle(password_manager::CredentialLeakType leak_type);
-
-// Checks whether the leak dialog should prompt user to password checkup.
-bool ShouldCheckPasswords(password_manager::CredentialLeakType leak_type);
-
-// Checks whether the leak dialog should show cancel button.
-bool ShouldShowCancelButton(password_manager::CredentialLeakType leak_type);
-
-// Returns the LeakDialogType corresponding to |leak_type|.
-password_manager::metrics_util::LeakDialogType GetLeakDialogType(
-    password_manager::CredentialLeakType leak_type);
-
-// Returns the URL used to launch the password checkup.
-GURL GetPasswordCheckupURL();
-
-// Returns the range of the bold part of the leak dialog message when
-// credentials were leaked only on current site.
-gfx::Range GetChangePasswordBoldRange(
-    password_manager::CredentialLeakType leak_type,
-    const GURL& origin);
-
-}  // namespace leak_dialog_utils
-
-#endif  // CHROME_BROWSER_UI_PASSWORDS_CREDENTIAL_LEAK_DIALOG_UTILS_H_
diff --git a/chrome/browser/ui/passwords/manage_passwords_view_utils.cc b/chrome/browser/ui/passwords/manage_passwords_view_utils.cc
index dfbd6c20..6baae5e 100644
--- a/chrome/browser/ui/passwords/manage_passwords_view_utils.cc
+++ b/chrome/browser/ui/passwords/manage_passwords_view_utils.cc
@@ -17,13 +17,13 @@
 #include "chrome/browser/ui/browser_navigator.h"
 #include "chrome/browser/ui/browser_navigator_params.h"
 #include "chrome/browser/ui/chrome_pages.h"
-#include "chrome/browser/ui/passwords/credential_leak_dialog_utils.h"
 #include "chrome/common/url_constants.h"
 #include "chrome/common/webui_url_constants.h"
 #include "chrome/grit/chromium_strings.h"
 #include "chrome/grit/generated_resources.h"
 #include "components/autofill/core/common/password_form.h"
 #include "components/password_manager/core/browser/android_affiliation/affiliation_utils.h"
+#include "components/password_manager/core/browser/leak_detection_dialog_utils.h"
 #include "components/password_manager/core/browser/password_manager_client.h"
 #include "components/password_manager/core/browser/password_manager_constants.h"
 #include "components/password_manager/core/browser/password_manager_util.h"
@@ -257,7 +257,7 @@
 }
 
 void NavigateToPasswordCheckupPage(Profile* profile) {
-  NavigateParams params(profile, leak_dialog_utils::GetPasswordCheckupURL(),
+  NavigateParams params(profile, password_manager::GetPasswordCheckupURL(),
                         ui::PAGE_TRANSITION_LINK);
   params.disposition = WindowOpenDisposition::NEW_FOREGROUND_TAB;
   Navigate(&params);
diff --git a/chrome/browser/ui/views/frame/browser_non_client_frame_view_unittest.cc b/chrome/browser/ui/views/frame/browser_non_client_frame_view_unittest.cc
index 56273c0..6587564 100644
--- a/chrome/browser/ui/views/frame/browser_non_client_frame_view_unittest.cc
+++ b/chrome/browser/ui/views/frame/browser_non_client_frame_view_unittest.cc
@@ -51,14 +51,7 @@
       : BrowserNonClientFrameViewTest(Browser::TYPE_POPUP) {}
 };
 
-// TODO(crbug.com/998369): Flaky on Linux TSAN and ASAN.
-#if defined(OS_LINUX) && \
-    (defined(ADDRESS_SANITIZER) || defined(THREAD_SANITIZER))
-#define MAYBE_HitTestPopupTopChrome DISABLED_HitTestPopupTopChrome
-#else
-#define MAYBE_HitTestPopupTopChrome HitTestPopupTopChrome
-#endif
-TEST_F(BrowserNonClientFrameViewPopupTest, MAYBE_HitTestPopupTopChrome) {
+TEST_F(BrowserNonClientFrameViewPopupTest, HitTestPopupTopChrome) {
   EXPECT_FALSE(frame_view_->HitTestRect(gfx::Rect(-1, 4, 1, 1)));
   EXPECT_FALSE(frame_view_->HitTestRect(gfx::Rect(4, -1, 1, 1)));
   const int top_inset = frame_view_->GetTopInset(false);
diff --git a/chrome/browser/ui/views/global_media_controls/media_notification_list_view.cc b/chrome/browser/ui/views/global_media_controls/media_notification_list_view.cc
index d29747b..8139413e 100644
--- a/chrome/browser/ui/views/global_media_controls/media_notification_list_view.cc
+++ b/chrome/browser/ui/views/global_media_controls/media_notification_list_view.cc
@@ -21,8 +21,10 @@
       views::BoxLayout::Orientation::kVertical));
   ClipHeightTo(0, kMediaListMaxHeight);
 
-  SetVerticalScrollBar(new views::OverlayScrollBar(/*horizontal=*/false));
-  SetHorizontalScrollBar(new views::OverlayScrollBar(/*horizontal=*/true));
+  SetVerticalScrollBar(
+      std::make_unique<views::OverlayScrollBar>(/*horizontal=*/false));
+  SetHorizontalScrollBar(
+      std::make_unique<views::OverlayScrollBar>(/*horizontal=*/true));
 }
 
 MediaNotificationListView::~MediaNotificationListView() = default;
diff --git a/chrome/browser/ui/webui/settings/people_handler.cc b/chrome/browser/ui/webui/settings/people_handler.cc
index 176db09..3ec355f 100644
--- a/chrome/browser/ui/webui/settings/people_handler.cc
+++ b/chrome/browser/ui/webui/settings/people_handler.cc
@@ -619,8 +619,8 @@
   bool passphrase_failed = false;
   if (!configuration.passphrase.empty()) {
     // We call IsPassphraseRequired() here (instead of
-    // IsPassphraseRequiredForDecryption()) because the user may try to enter
-    // a passphrase even though no encrypted data types are enabled.
+    // IsPassphraseRequiredForPreferredDataTypes()) because the user may try to
+    // enter a passphrase even though no encrypted data types are enabled.
     if (service->GetUserSettings()->IsPassphraseRequired()) {
       // If we have pending keys, try to decrypt them with the provided
       // passphrase. We track if this succeeds or fails because a failed
@@ -642,7 +642,7 @@
   }
 
   if (passphrase_failed ||
-      service->GetUserSettings()->IsPassphraseRequiredForDecryption()) {
+      service->GetUserSettings()->IsPassphraseRequiredForPreferredDataTypes()) {
     // If the user doesn't enter any passphrase, we won't call
     // SetDecryptionPassphrase() (passphrase_failed == false), but we still
     // want to display an error message to let the user know that their blank
@@ -1097,8 +1097,8 @@
                   sync_user_settings->IsEncryptEverythingAllowed());
 
   // We call IsPassphraseRequired() here, instead of calling
-  // IsPassphraseRequiredForDecryption(), because we want to show the passphrase
-  // UI even if no encrypted data types are enabled.
+  // IsPassphraseRequiredForPreferredDataTypes(), because we want to show the
+  // passphrase UI even if no encrypted data types are enabled.
   args.SetBoolean("passphraseRequired",
                   sync_user_settings->IsPassphraseRequired());
 
diff --git a/chrome/browser/ui/webui/settings/people_handler_unittest.cc b/chrome/browser/ui/webui/settings/people_handler_unittest.cc
index d3dee7a..833ac7b 100644
--- a/chrome/browser/ui/webui/settings/people_handler_unittest.cc
+++ b/chrome/browser/ui/webui/settings/people_handler_unittest.cc
@@ -816,7 +816,7 @@
   list_args.AppendString(kTestCallbackId);
   list_args.AppendString(args);
   ON_CALL(*mock_sync_service_->GetMockUserSettings(),
-          IsPassphraseRequiredForDecryption())
+          IsPassphraseRequiredForPreferredDataTypes())
       .WillByDefault(Return(false));
   ON_CALL(*mock_sync_service_->GetMockUserSettings(), IsPassphraseRequired())
       .WillByDefault(Return(false));
@@ -835,7 +835,7 @@
   list_args.AppendString(kTestCallbackId);
   list_args.AppendString(args);
   ON_CALL(*mock_sync_service_->GetMockUserSettings(),
-          IsPassphraseRequiredForDecryption())
+          IsPassphraseRequiredForPreferredDataTypes())
       .WillByDefault(Return(true));
   ON_CALL(*mock_sync_service_->GetMockUserSettings(), IsPassphraseRequired())
       .WillByDefault(Return(true));
@@ -864,7 +864,7 @@
               IsPassphraseRequired())
       .WillOnce(Return(true));
   ON_CALL(*mock_sync_service_->GetMockUserSettings(),
-          IsPassphraseRequiredForDecryption())
+          IsPassphraseRequiredForPreferredDataTypes())
       .WillByDefault(Return(false));
   ON_CALL(*mock_sync_service_->GetMockUserSettings(),
           IsUsingSecondaryPassphrase())
@@ -890,7 +890,7 @@
           IsEncryptEverythingAllowed())
       .WillByDefault(Return(true));
   ON_CALL(*mock_sync_service_->GetMockUserSettings(),
-          IsPassphraseRequiredForDecryption())
+          IsPassphraseRequiredForPreferredDataTypes())
       .WillByDefault(Return(false));
   ON_CALL(*mock_sync_service_->GetMockUserSettings(), IsPassphraseRequired())
       .WillByDefault(Return(false));
@@ -914,7 +914,7 @@
   list_args.AppendString(kTestCallbackId);
   list_args.AppendString(args);
   ON_CALL(*mock_sync_service_->GetMockUserSettings(),
-          IsPassphraseRequiredForDecryption())
+          IsPassphraseRequiredForPreferredDataTypes())
       .WillByDefault(Return(true));
   ON_CALL(*mock_sync_service_->GetMockUserSettings(), IsPassphraseRequired())
       .WillByDefault(Return(true));
@@ -945,7 +945,7 @@
   list_args.AppendString(kTestCallbackId);
   list_args.AppendString(args);
   ON_CALL(*mock_sync_service_->GetMockUserSettings(),
-          IsPassphraseRequiredForDecryption())
+          IsPassphraseRequiredForPreferredDataTypes())
       .WillByDefault(Return(true));
   ON_CALL(*mock_sync_service_->GetMockUserSettings(), IsPassphraseRequired())
       .WillByDefault(Return(true));
@@ -976,7 +976,7 @@
     list_args.AppendString(kTestCallbackId);
     list_args.AppendString(args);
     ON_CALL(*mock_sync_service_->GetMockUserSettings(),
-            IsPassphraseRequiredForDecryption())
+            IsPassphraseRequiredForPreferredDataTypes())
         .WillByDefault(Return(false));
     ON_CALL(*mock_sync_service_->GetMockUserSettings(), IsPassphraseRequired())
         .WillByDefault(Return(false));
@@ -1001,7 +1001,7 @@
   list_args.AppendString(kTestCallbackId);
   list_args.AppendString(args);
   ON_CALL(*mock_sync_service_->GetMockUserSettings(),
-          IsPassphraseRequiredForDecryption())
+          IsPassphraseRequiredForPreferredDataTypes())
       .WillByDefault(Return(false));
   ON_CALL(*mock_sync_service_->GetMockUserSettings(), IsPassphraseRequired())
       .WillByDefault(Return(false));
@@ -1240,7 +1240,7 @@
 
 TEST_F(PeopleHandlerTest, TurnOnEncryptAllDisallowed) {
   ON_CALL(*mock_sync_service_->GetMockUserSettings(),
-          IsPassphraseRequiredForDecryption())
+          IsPassphraseRequiredForPreferredDataTypes())
       .WillByDefault(Return(false));
   ON_CALL(*mock_sync_service_->GetMockUserSettings(), IsPassphraseRequired())
       .WillByDefault(Return(false));
diff --git a/chrome/browser/web_applications/components/app_registrar.cc b/chrome/browser/web_applications/components/app_registrar.cc
index 9025044..eb4df2b 100644
--- a/chrome/browser/web_applications/components/app_registrar.cc
+++ b/chrome/browser/web_applications/components/app_registrar.cc
@@ -24,6 +24,11 @@
   return IsLocallyInstalled(GenerateAppIdFromURL(start_url));
 }
 
+bool AppRegistrar::IsPlaceholderApp(const AppId& app_id) const {
+  return ExternallyInstalledWebAppPrefs(profile_->GetPrefs())
+      .IsPlaceholderApp(app_id);
+}
+
 void AppRegistrar::AddObserver(AppRegistrarObserver* observer) {
   observers_.AddObserver(observer);
 }
diff --git a/chrome/browser/web_applications/components/app_registrar.h b/chrome/browser/web_applications/components/app_registrar.h
index 06597c1..f7564e66 100644
--- a/chrome/browser/web_applications/components/app_registrar.h
+++ b/chrome/browser/web_applications/components/app_registrar.h
@@ -93,6 +93,10 @@
   // that fall within the scope.
   bool IsLocallyInstalled(const GURL& start_url) const;
 
+  // Returns whether the app is pending successful navigation in order to
+  // complete installation via the PendingAppManager.
+  bool IsPlaceholderApp(const AppId& app_id) const;
+
   void AddObserver(AppRegistrarObserver* observer);
   void RemoveObserver(AppRegistrarObserver* observer);
 
diff --git a/chrome/browser/web_applications/components/externally_installed_web_app_prefs.cc b/chrome/browser/web_applications/components/externally_installed_web_app_prefs.cc
index a398fc3..6872183 100644
--- a/chrome/browser/web_applications/components/externally_installed_web_app_prefs.cc
+++ b/chrome/browser/web_applications/components/externally_installed_web_app_prefs.cc
@@ -210,4 +210,14 @@
   app_entry->SetBoolKey(kIsPlaceholder, is_placeholder);
 }
 
+bool ExternallyInstalledWebAppPrefs::IsPlaceholderApp(
+    const AppId& app_id) const {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+  const base::Value* app_prefs = GetPreferenceValue(pref_service_, app_id);
+  if (!app_prefs || !app_prefs->is_dict())
+    return false;
+  return app_prefs->FindBoolKey(kIsPlaceholder).value_or(false);
+}
+
 }  // namespace web_app
diff --git a/chrome/browser/web_applications/components/externally_installed_web_app_prefs.h b/chrome/browser/web_applications/components/externally_installed_web_app_prefs.h
index a2cb288..c871a08 100644
--- a/chrome/browser/web_applications/components/externally_installed_web_app_prefs.h
+++ b/chrome/browser/web_applications/components/externally_installed_web_app_prefs.h
@@ -57,6 +57,7 @@
   // *placeholder app*.
   base::Optional<AppId> LookupPlaceholderAppId(const GURL& url) const;
   void SetIsPlaceholder(const GURL& url, bool is_placeholder);
+  bool IsPlaceholderApp(const AppId& app_id) const;
 
  private:
   PrefService* pref_service_;
diff --git a/chrome/browser/web_applications/components/manifest_update_manager.cc b/chrome/browser/web_applications/components/manifest_update_manager.cc
index 1f8849e3..1bedfdb 100644
--- a/chrome/browser/web_applications/components/manifest_update_manager.cc
+++ b/chrome/browser/web_applications/components/manifest_update_manager.cc
@@ -40,8 +40,12 @@
     return;
   }
 
-  std::unique_ptr<ManifestUpdateTask>& current_task = tasks_[app_id];
-  if (current_task)
+  if (registrar_->IsPlaceholderApp(app_id)) {
+    NotifyResult(url, ManifestUpdateResult::kAppIsPlaceholder);
+    return;
+  }
+
+  if (base::Contains(tasks_, app_id))
     return;
 
   if (!MaybeConsumeUpdateCheck(app_id)) {
@@ -49,12 +53,13 @@
     return;
   }
 
-  current_task = std::make_unique<ManifestUpdateTask>(
-      url, app_id, web_contents,
-      base::Bind(&ManifestUpdateManager::OnUpdateStopped,
-                 base::Unretained(this)),
-      hang_update_checks_for_testing_, *registrar_, ui_manager_,
-      install_manager_);
+  tasks_.insert_or_assign(
+      app_id, std::make_unique<ManifestUpdateTask>(
+                  url, app_id, web_contents,
+                  base::Bind(&ManifestUpdateManager::OnUpdateStopped,
+                             base::Unretained(this)),
+                  hang_update_checks_for_testing_, *registrar_, ui_manager_,
+                  install_manager_));
 }
 
 // AppRegistrarObserver:
diff --git a/chrome/browser/web_applications/components/manifest_update_manager_browsertest.cc b/chrome/browser/web_applications/components/manifest_update_manager_browsertest.cc
index d4473909..f1ebd46 100644
--- a/chrome/browser/web_applications/components/manifest_update_manager_browsertest.cc
+++ b/chrome/browser/web_applications/components/manifest_update_manager_browsertest.cc
@@ -17,6 +17,7 @@
 #include "chrome/browser/web_applications/components/app_registry_controller.h"
 #include "chrome/browser/web_applications/components/install_finalizer.h"
 #include "chrome/browser/web_applications/components/install_manager.h"
+#include "chrome/browser/web_applications/components/pending_app_manager.h"
 #include "chrome/browser/web_applications/components/web_app_provider_base.h"
 #include "chrome/browser/web_applications/components/web_app_tab_helper.h"
 #include "chrome/common/chrome_features.h"
@@ -111,20 +112,27 @@
 
   std::unique_ptr<net::test_server::HttpResponse> RequestHandlerOverride(
       const net::test_server::HttpRequest& request) {
-    if (request.GetURL() != override_url_)
-      return nullptr;
-    auto http_response =
-        std::make_unique<net::test_server::BasicHttpResponse>();
-    http_response->set_code(net::HTTP_FOUND);
-    http_response->set_content(override_content_);
-    return std::move(http_response);
+    if (request_override_)
+      return request_override_.Run(request);
+    return nullptr;
   }
 
   void OverrideManifest(const char* manifest_template,
                         const std::vector<std::string>& substitutions) {
-    override_url_ = GetManifestURL();
-    override_content_ = base::ReplaceStringPlaceholders(manifest_template,
-                                                        substitutions, nullptr);
+    std::string content = base::ReplaceStringPlaceholders(
+        manifest_template, substitutions, nullptr);
+    request_override_ = base::BindLambdaForTesting(
+        [this, content = std::move(content)](
+            const net::test_server::HttpRequest& request)
+            -> std::unique_ptr<net::test_server::HttpResponse> {
+          if (request.GetURL() != GetManifestURL())
+            return nullptr;
+          auto http_response =
+              std::make_unique<net::test_server::BasicHttpResponse>();
+          http_response->set_code(net::HTTP_FOUND);
+          http_response->set_content(content);
+          return std::move(http_response);
+        });
   }
 
   GURL GetAppURL() const {
@@ -176,12 +184,12 @@
     return *WebAppProviderBase::GetProviderBase(browser()->profile());
   }
 
+  net::EmbeddedTestServer::HandleRequestCallback request_override_;
+
  private:
   base::test::ScopedFeatureList scoped_feature_list_;
 
   net::EmbeddedTestServer http_server_;
-  GURL override_url_;
-  std::string override_content_;
 
   DISALLOW_COPY_AND_ASSIGN(ManifestUpdateManagerBrowserTest);
 };
@@ -396,6 +404,61 @@
 }
 
 IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTest,
+                       CheckIgnoresPlaceholderApps) {
+  // Set up app URL to redirect to force placeholder app to install.
+  const GURL app_url = GetAppURL();
+  request_override_ = base::BindLambdaForTesting(
+      [&app_url](const net::test_server::HttpRequest& request)
+          -> std::unique_ptr<net::test_server::HttpResponse> {
+        if (request.GetURL() != app_url)
+          return nullptr;
+        auto http_response =
+            std::make_unique<net::test_server::BasicHttpResponse>();
+        http_response->set_code(net::HTTP_TEMPORARY_REDIRECT);
+        http_response->AddCustomHeader("Location", "/defaultresponse");
+        http_response->set_content("redirect page");
+        return std::move(http_response);
+      });
+
+  // Install via PendingAppManager, the redirect should cause it to install a
+  // placeholder app.
+  base::RunLoop run_loop;
+  ExternalInstallOptions install_options(
+      app_url, LaunchContainer::kWindow,
+      ExternalInstallSource::kExternalPolicy);
+  install_options.add_to_applications_menu = false;
+  install_options.add_to_desktop = false;
+  install_options.add_to_quick_launch_bar = false;
+  install_options.install_placeholder = true;
+  GetProvider().pending_app_manager().Install(
+      std::move(install_options),
+      base::BindLambdaForTesting(
+          [&](const GURL& installed_app_url, InstallResultCode code) {
+            EXPECT_EQ(installed_app_url, app_url);
+            EXPECT_EQ(code, InstallResultCode::kSuccessNewInstall);
+            run_loop.Quit();
+          }));
+  run_loop.Run();
+  AppId app_id = GetProvider().registrar().LookupExternalAppId(app_url).value();
+  EXPECT_TRUE(GetProvider().registrar().IsPlaceholderApp(app_id));
+
+  // Manifest updating should ignore non-redirect loads for placeholder apps
+  // because the PendingAppManager will handle these.
+  const char* manifest_template = R"(
+    {
+      "name": "Test app name",
+      "start_url": ".",
+      "scope": "/",
+      "display": "standalone",
+      "icons": $1
+    }
+  )";
+  OverrideManifest(manifest_template, {kInstallableIconList});
+  EXPECT_EQ(GetResultAfterPageLoad(app_url, &app_id),
+            ManifestUpdateResult::kAppIsPlaceholder);
+}
+
+IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTest,
                        CheckFindsThemeColorChange) {
   const char* manifest_template = R"(
     {
diff --git a/chrome/browser/web_applications/components/manifest_update_task.h b/chrome/browser/web_applications/components/manifest_update_task.h
index aa9f53a..5d9e783 100644
--- a/chrome/browser/web_applications/components/manifest_update_task.h
+++ b/chrome/browser/web_applications/components/manifest_update_task.h
@@ -25,6 +25,7 @@
   kThrottled,
   kWebContentsDestroyed,
   kAppUninstalled,
+  kAppIsPlaceholder,
   kAppUpToDate,
   kAppDataInvalid,
   kAppUpdateFailed,
diff --git a/chrome/browser/web_applications/extensions/externally_installed_web_app_prefs_unittest.cc b/chrome/browser/web_applications/extensions/externally_installed_web_app_prefs_unittest.cc
index 79b6bd2a..27848e5 100644
--- a/chrome/browser/web_applications/extensions/externally_installed_web_app_prefs_unittest.cc
+++ b/chrome/browser/web_applications/extensions/externally_installed_web_app_prefs_unittest.cc
@@ -176,4 +176,26 @@
             GetAppUrls(ExternalInstallSource::kExternalPolicy));
 }
 
+TEST_F(ExternallyInstalledWebAppPrefsTest, IsPlaceholderApp) {
+  const GURL url("https://example.com");
+  const AppId app_id = "app_id_string";
+  ExternallyInstalledWebAppPrefs prefs(profile()->GetPrefs());
+  prefs.Insert(url, app_id, ExternalInstallSource::kExternalPolicy);
+  EXPECT_FALSE(ExternallyInstalledWebAppPrefs(profile()->GetPrefs())
+                   .IsPlaceholderApp(app_id));
+  prefs.SetIsPlaceholder(url, true);
+  EXPECT_TRUE(ExternallyInstalledWebAppPrefs(profile()->GetPrefs())
+                  .IsPlaceholderApp(app_id));
+}
+
+TEST_F(ExternallyInstalledWebAppPrefsTest, OldPrefFormat) {
+  // Set up the old format for this pref {url -> app_id}.
+  DictionaryPrefUpdate update(profile()->GetPrefs(),
+                              prefs::kWebAppsExtensionIDs);
+  update->SetKey("https://example.com", base::Value("add_id_string"));
+  // This should not crash on invalid pref data.
+  EXPECT_FALSE(ExternallyInstalledWebAppPrefs(profile()->GetPrefs())
+                   .IsPlaceholderApp("app_id_string"));
+}
+
 }  // namespace web_app
diff --git a/chrome/common/url_constants.cc b/chrome/common/url_constants.cc
index 3b3a967..fab12ef 100644
--- a/chrome/common/url_constants.cc
+++ b/chrome/common/url_constants.cc
@@ -156,17 +156,6 @@
 
 const char kGooglePasswordManagerURL[] = "https://passwords.google.com";
 
-const char kPasswordCheckupURL[] =
-#if defined(OS_ANDROID)
-    "https://passwords.google.com/checkup/"
-    "start?utm_source=chrome&utm_medium=android&utm_campaign=leak_dialog&crch="
-    "true";
-#else
-    "https://passwords.google.com/checkup/"
-    "start?utm_source=chrome&utm_medium=desktop&utm_campaign=leak_dialog&crch="
-    "true";
-#endif
-
 const char kLearnMoreReportingURL[] =
     "https://support.google.com/chrome/?p=ui_usagestat";
 
diff --git a/chrome/common/url_constants.h b/chrome/common/url_constants.h
index 51445591..d91e2b7 100644
--- a/chrome/common/url_constants.h
+++ b/chrome/common/url_constants.h
@@ -150,9 +150,6 @@
 // URL of the Google Password Manager.
 extern const char kGooglePasswordManagerURL[];
 
-// URL for Password Checkup.
-extern const char kPasswordCheckupURL[];
-
 // The URL for the "Learn more" page for the usage/crash reporting option in the
 // first run dialog.
 extern const char kLearnMoreReportingURL[];
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 76f6649..8e65b99 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -3283,7 +3283,6 @@
     "../browser/ui/find_bar/find_backend_unittest.cc",
     "../browser/ui/login/login_handler_unittest.cc",
     "../browser/ui/page_info/page_info_unittest.cc",
-    "../browser/ui/passwords/credential_leak_dialog_utils_unittest.cc",
     "../browser/ui/passwords/manage_passwords_state_unittest.cc",
     "../browser/ui/passwords/manage_passwords_view_utils_unittest.cc",
     "../browser/ui/passwords/password_generation_popup_controller_impl_unittest.cc",
diff --git a/chrome/test/base/browser_with_test_window_test.cc b/chrome/test/base/browser_with_test_window_test.cc
index b54a78f..737123f85 100644
--- a/chrome/test/base/browser_with_test_window_test.cc
+++ b/chrome/test/base/browser_with_test_window_test.cc
@@ -212,6 +212,18 @@
   return std::make_unique<Browser>(params);
 }
 
+#if defined(OS_CHROMEOS)
+chromeos::ScopedCrosSettingsTestHelper*
+BrowserWithTestWindowTest::GetCrosSettingsHelper() {
+  return &cros_settings_test_helper_;
+}
+
+chromeos::StubInstallAttributes*
+BrowserWithTestWindowTest::GetInstallAttributes() {
+  return GetCrosSettingsHelper()->InstallAttributes();
+}
+#endif  // defined(OS_CHROMEOS)
+
 BrowserWithTestWindowTest::BrowserWithTestWindowTest(
     std::unique_ptr<content::BrowserTaskEnvironment> task_environment,
     Browser::Type browser_type,
diff --git a/chrome/test/base/browser_with_test_window_test.h b/chrome/test/base/browser_with_test_window_test.h
index a8226f86..4fed95c 100644
--- a/chrome/test/base/browser_with_test_window_test.h
+++ b/chrome/test/base/browser_with_test_window_test.h
@@ -24,6 +24,7 @@
 #include "ash/test/ash_test_views_delegate.h"
 #include "chrome/browser/chromeos/login/users/scoped_test_user_manager.h"
 #include "chrome/browser/chromeos/settings/scoped_cros_settings_test_helper.h"
+#include "chromeos/tpm/stub_install_attributes.h"
 #else
 #include "ui/views/test/scoped_views_test_helper.h"
 #endif
@@ -192,6 +193,11 @@
   }
 #endif
 
+#if defined(OS_CHROMEOS)
+  chromeos::ScopedCrosSettingsTestHelper* GetCrosSettingsHelper();
+  chromeos::StubInstallAttributes* GetInstallAttributes();
+#endif
+
  private:
   // The template constructor has to be in the header but it delegates to this
   // constructor to initialize all other members out-of-line.
diff --git a/chromecast/media/audio/cast_audio_device_factory.cc b/chromecast/media/audio/cast_audio_device_factory.cc
index d8eddb4..898f085 100644
--- a/chromecast/media/audio/cast_audio_device_factory.cc
+++ b/chromecast/media/audio/cast_audio_device_factory.cc
@@ -65,7 +65,7 @@
   void Flush() override { output_device_->Flush(); }
 
  protected:
-  ~NonSwitchableAudioRendererSink() override = default;
+  ~NonSwitchableAudioRendererSink() override { output_device_->Stop(); }
 
  private:
   scoped_refptr<::media::AudioOutputDevice> output_device_;
diff --git a/chromeos/CHROMEOS_LKGM b/chromeos/CHROMEOS_LKGM
index 00ef50e1..b1014158 100644
--- a/chromeos/CHROMEOS_LKGM
+++ b/chromeos/CHROMEOS_LKGM
@@ -1 +1 @@
-12557.0.0
\ No newline at end of file
+12560.0.0
\ No newline at end of file
diff --git a/components/bookmarks/browser/bookmark_node_data_unittest.cc b/components/bookmarks/browser/bookmark_node_data_unittest.cc
index 86be83c2..3dd0f0a 100644
--- a/components/bookmarks/browser/bookmark_node_data_unittest.cc
+++ b/components/bookmarks/browser/bookmark_node_data_unittest.cc
@@ -367,7 +367,9 @@
   EXPECT_EQ(base::ASCIIToUTF16("g1"), clipboard_result);
 }
 
-TEST_F(BookmarkNodeDataTest, WriteToClipboardFolderAndURL) {
+// TODO(https://crbug.com/1010415): This test is flaky on various platforms, fix
+// and re-enable it.
+TEST_F(BookmarkNodeDataTest, DISABLED_WriteToClipboardFolderAndURL) {
   BookmarkNodeData data;
   GURL url(GURL("http://foo.com"));
   const base::string16 title(ASCIIToUTF16("blah"));
diff --git a/components/bookmarks/browser/bookmark_utils_unittest.cc b/components/bookmarks/browser/bookmark_utils_unittest.cc
index a6777e0..394bba5f 100644
--- a/components/bookmarks/browser/bookmark_utils_unittest.cc
+++ b/components/bookmarks/browser/bookmark_utils_unittest.cc
@@ -291,7 +291,13 @@
             ASCIIToUTF16(new_folder->children().front()->url().spec()));
 }
 
-TEST_F(BookmarkUtilsTest, CopyPaste) {
+// TODO(https://crbug.com/1010182): Fix flakes and re-enable this test.
+#if defined(OS_WIN) || defined(OS_MACOSX)
+#define MAYBE_CopyPaste DISABLED_CopyPaste
+#else
+#define MAYBE_CopyPaste CopyPaste
+#endif
+TEST_F(BookmarkUtilsTest, MAYBE_CopyPaste) {
   std::unique_ptr<BookmarkModel> model(TestBookmarkClient::CreateModel());
   const BookmarkNode* node = model->AddURL(model->other_node(),
                                            0,
diff --git a/components/content_capture/browser/content_capture_receiver_test.cc b/components/content_capture/browser/content_capture_receiver_test.cc
index 2bb600c..b88f710 100644
--- a/components/content_capture/browser/content_capture_receiver_test.cc
+++ b/components/content_capture/browser/content_capture_receiver_test.cc
@@ -9,6 +9,7 @@
 
 #include "base/strings/utf_string_conversions.h"
 #include "base/task/post_task.h"
+#include "build/build_config.h"
 #include "components/content_capture/browser/content_capture_receiver_manager.h"
 #include "content/public/browser/content_browser_client.h"
 #include "content/public/browser/web_contents.h"
@@ -492,7 +493,15 @@
   DidRemoveContent(expected_removed_ids());
 }
 
-TEST_F(ContentCaptureReceiverTest, ChildFrameCaptureContentFirst) {
+// TODO(https://crbug.com/1010416): Fix flakes on win10_chromium_x64_rel_ng and
+// re-enable this test.
+#if defined(OS_WIN)
+#define MAYBE_ChildFrameCaptureContentFirst \
+  DISABLED_ChildFrameCaptureContentFirst
+#else
+#define MAYBE_ChildFrameCaptureContentFirst ChildFrameCaptureContentFirst
+#endif
+TEST_F(ContentCaptureReceiverTest, MAYBE_ChildFrameCaptureContentFirst) {
   // Simulate add child frame.
   SetupChildFrame();
   // Simulate to capture the content from child frame.
@@ -635,8 +644,16 @@
   void TearDown() override { content::RenderViewHostTestHarness::TearDown(); }
 };
 
+// TODO(https://crbug.com/1010417): Fix flakes on win10_chromium_x64_rel_ng and
+// re-enable this test.
+#if defined(OS_WIN)
+#define MAYBE_ReceiverCreatedForExistingFrame \
+  DISABLED_ReceiverCreatedForExistingFrame
+#else
+#define MAYBE_ReceiverCreatedForExistingFrame ReceiverCreatedForExistingFrame
+#endif
 TEST_F(ContentCaptureReceiverMultipleFrameTest,
-       ReceiverCreatedForExistingFrame) {
+       MAYBE_ReceiverCreatedForExistingFrame) {
   EXPECT_EQ(
       2u,
       content_capture_receiver_manager_helper()->GetFrameMapSizeForTesting());
diff --git a/components/favicon/core/favicon_handler.cc b/components/favicon/core/favicon_handler.cc
index 08c9c16..95a979c8 100644
--- a/components/favicon/core/favicon_handler.cc
+++ b/components/favicon/core/favicon_handler.cc
@@ -226,8 +226,9 @@
   // we get <link rel="icon"> candidates (FaviconHandler::OnUpdateCandidates()).
   service_->GetFaviconForPageURL(
       last_page_url_, icon_types_, preferred_icon_size(),
-      base::Bind(&FaviconHandler::OnFaviconDataForInitialURLFromFaviconService,
-                 base::Unretained(this)),
+      base::BindOnce(
+          &FaviconHandler::OnFaviconDataForInitialURLFromFaviconService,
+          base::Unretained(this)),
       &cancelable_task_tracker_for_page_url_);
 }
 
@@ -360,8 +361,9 @@
   // mappings only if the manifest URL is cached.
   GetFaviconAndUpdateMappingsUnlessIncognito(
       /*icon_url=*/manifest_url_, favicon_base::IconType::kWebManifestIcon,
-      base::Bind(&FaviconHandler::OnFaviconDataForManifestFromFaviconService,
-                 base::Unretained(this)));
+      base::BindOnce(
+          &FaviconHandler::OnFaviconDataForManifestFromFaviconService,
+          base::Unretained(this)));
 }
 
 void FaviconHandler::OnFaviconDataForManifestFromFaviconService(
@@ -621,7 +623,7 @@
   } else {
     GetFaviconAndUpdateMappingsUnlessIncognito(
         icon_url, icon_type,
-        base::Bind(&FaviconHandler::OnFaviconData, base::Unretained(this)));
+        base::BindOnce(&FaviconHandler::OnFaviconData, base::Unretained(this)));
   }
 }
 
diff --git a/components/favicon/ios/web_favicon_driver.mm b/components/favicon/ios/web_favicon_driver.mm
index 37bc123..6efaf65 100644
--- a/components/favicon/ios/web_favicon_driver.mm
+++ b/components/favicon/ios/web_favicon_driver.mm
@@ -23,14 +23,6 @@
 #error "This file requires ARC support."
 #endif
 
-// Callback for the download of favicon.
-using ImageDownloadCallback =
-    base::Callback<void(int image_id,
-                        int http_status_code,
-                        const GURL& image_url,
-                        const std::vector<SkBitmap>& bitmaps,
-                        const std::vector<gfx::Size>& sizes)>;
-
 namespace favicon {
 
 // static
diff --git a/components/flags_ui/resources/flags.css b/components/flags_ui/resources/flags.css
index 0481846..58482c95 100644
--- a/components/flags_ui/resources/flags.css
+++ b/components/flags_ui/resources/flags.css
@@ -37,7 +37,6 @@
   --warning-color: var(--google-red-700);
 
   --input-background: var(--google-grey-100);
-  --input-placeholder-color: rgb(117, 117, 117);
   --keyboard-focus-ring: rgba(var(--google-blue-500-rgb), 0.4);
   --link-color: var(--google-blue-700);
   --separator-color: rgba(0, 0, 0, .06);
@@ -179,10 +178,6 @@
   width: 100%;
 }
 
-#search::placeholder {
-  color: var(--input-placeholder-color);
-}
-
 @media (prefers-color-scheme: dark) {
   #search {
     background-image: url(../../../ui/webui/resources/images/dark/icon_search.svg);
diff --git a/components/neterror/resources/offline.js b/components/neterror/resources/offline.js
index 504b2a89..f170440 100644
--- a/components/neterror/resources/offline.js
+++ b/components/neterror/resources/offline.js
@@ -93,7 +93,14 @@
 var IS_HIDPI = window.devicePixelRatio > 1;
 
 /** @const */
-var IS_IOS = /iPad|iPhone|iPod/.test(window.navigator.platform);
+// iPads are returning "MacIntel" for iOS 13 (devices & simulators).
+// Chrome on macOS also returns "MacIntel" for navigator.platform,
+// but navigator.userAgent includes /Safari/.
+// TODO(crbug.com/998999): Fix navigator.userAgent such that it reliably
+// returns an agent string containing "CriOS".
+var IS_IOS = /iPad|iPhone|iPod|MacIntel/.test(navigator.platform) &&
+    !(/Safari/.test(navigator.userAgent));
+
 
 /** @const */
 var IS_MOBILE = /Android/.test(window.navigator.userAgent) || IS_IOS;
diff --git a/components/omnibox/browser/location_bar_model_impl_unittest.cc b/components/omnibox/browser/location_bar_model_impl_unittest.cc
index 5b9ec65a..32fdc79c 100644
--- a/components/omnibox/browser/location_bar_model_impl_unittest.cc
+++ b/components/omnibox/browser/location_bar_model_impl_unittest.cc
@@ -7,6 +7,7 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/test/task_environment.h"
+#include "build/build_config.h"
 #include "components/omnibox/browser/location_bar_model_delegate.h"
 #include "components/omnibox/browser/test_omnibox_client.h"
 #include "components/omnibox/common/omnibox_features.h"
@@ -110,7 +111,14 @@
             model()->GetURLForDisplay());
 }
 
-TEST_F(LocationBarModelImplTest, PreventElisionWorks) {
+// TODO(https://crbug.com/1010418): Fix flakes on linux_chromium_asan_rel_ng and
+// re-enable this test.
+#if defined(OS_LINUX)
+#define MAYBE_PreventElisionWorks DISABLED_PreventElisionWorks
+#else
+#define MAYBE_PreventElisionWorks PreventElisionWorks
+#endif
+TEST_F(LocationBarModelImplTest, MAYBE_PreventElisionWorks) {
   base::test::ScopedFeatureList feature_list;
   feature_list.InitWithFeatures(
       {omnibox::kHideSteadyStateUrlScheme,
diff --git a/components/password_manager/core/browser/BUILD.gn b/components/password_manager/core/browser/BUILD.gn
index 97a1dad6..b7dd6d7 100644
--- a/components/password_manager/core/browser/BUILD.gn
+++ b/components/password_manager/core/browser/BUILD.gn
@@ -102,6 +102,8 @@
     "import/password_csv_reader.h",
     "import/password_importer.cc",
     "import/password_importer.h",
+    "leak_detection_dialog_utils.cc",
+    "leak_detection_dialog_utils.h",
     "leaked_credentials_table.cc",
     "leaked_credentials_table.h",
     "login_database.cc",
@@ -231,6 +233,7 @@
     ":password_generator",
     ":password_hash_data",
     ":proto",
+    "//base",
     "//base:i18n",
     "//components/autofill/core/browser",
     "//components/autofill/core/browser/proto",
@@ -247,6 +250,7 @@
     "//components/security_state/core",
     "//components/signin/public/identity_manager",
     "//components/strings",
+    "//components/strings:components_strings",
     "//components/sync",
     "//components/sync_preferences",
     "//components/url_formatter",
@@ -262,6 +266,7 @@
     "//third_party/re2",
     "//ui/base",
     "//ui/gfx",
+    "//ui/gfx/range",
     "//url",
   ]
 
@@ -275,8 +280,6 @@
       "leak_detection_delegate.h",
       "leak_detection_delegate_helper.cc",
       "leak_detection_delegate_helper.h",
-      "leak_detection_dialog_utils.cc",
-      "leak_detection_dialog_utils.h",
     ]
     deps += [
       "//components/password_manager/core/browser/leak_detection:leak_detection",
@@ -514,6 +517,7 @@
     "import/csv_reader_unittest.cc",
     "import/password_csv_reader_unittest.cc",
     "import/password_importer_unittest.cc",
+    "leak_detection_dialog_utils_unittest.cc",
     "leaked_credentials_table_unittest.cc",
     "login_database_unittest.cc",
     "multi_store_form_fetcher_unittest.cc",
@@ -603,6 +607,7 @@
     "//components/sync:test_support",
     "//components/sync_preferences:test_support",
     "//components/ukm:test_support",
+    "//components/url_formatter",
     "//components/variations",
     "//google_apis:google_apis",
     "//net:test_support",
diff --git a/components/password_manager/core/browser/leak_detection_dialog_utils.cc b/components/password_manager/core/browser/leak_detection_dialog_utils.cc
index 654f5a5..40c89fcb 100644
--- a/components/password_manager/core/browser/leak_detection_dialog_utils.cc
+++ b/components/password_manager/core/browser/leak_detection_dialog_utils.cc
@@ -4,8 +4,36 @@
 
 #include "components/password_manager/core/browser/leak_detection_dialog_utils.h"
 
+#include "base/metrics/field_trial_params.h"
+#include "base/strings/utf_string_conversions.h"
+#include "build/build_config.h"
+#include "components/password_manager/core/browser/leak_detection_dialog_utils.h"
+#include "components/password_manager/core/common/password_manager_features.h"
+#include "components/strings/grit/components_strings.h"
+#include "components/url_formatter/elide_url.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "url/gurl.h"
+#include "url/origin.h"
+
 namespace password_manager {
 
+using metrics_util::LeakDialogType;
+
+constexpr char kPasswordCheckupURL[] =
+#if defined(OS_ANDROID)
+    "https://passwords.google.com/checkup/"
+    "start?utm_source=chrome&utm_medium=android&utm_campaign=leak_dialog&crch="
+    "true";
+#elif defined(OS_IOS)
+    "https://passwords.google.com/checkup/"
+    "start?utm_source=chrome&utm_medium=ios&utm_campaign=leak_dialog&crch="
+    "true";
+#else
+    "https://passwords.google.com/checkup/"
+    "start?utm_source=chrome&utm_medium=desktop&utm_campaign=leak_dialog&crch="
+    "true";
+#endif
+
 CredentialLeakType CreateLeakType(IsSaved is_saved,
                                   IsReused is_reused,
                                   IsSyncing is_syncing) {
@@ -31,4 +59,84 @@
   return leak_type & CredentialLeakFlags::kSyncingPasswordsNormally;
 }
 
+// Formats the |origin| to a human-friendly url string.
+base::string16 GetFormattedUrl(const GURL& origin) {
+  return url_formatter::FormatUrlForSecurityDisplay(
+      origin, url_formatter::SchemeDisplay::OMIT_HTTP_AND_HTTPS);
+}
+
+base::string16 GetAcceptButtonLabel(CredentialLeakType leak_type) {
+  return l10n_util::GetStringUTF16(
+      ShouldCheckPasswords(leak_type) ? IDS_LEAK_CHECK_CREDENTIALS : IDS_OK);
+}
+
+base::string16 GetCancelButtonLabel() {
+  return l10n_util::GetStringUTF16(IDS_CLOSE);
+}
+
+base::string16 GetDescription(CredentialLeakType leak_type,
+                              const GURL& origin) {
+  const base::string16 formatted = GetFormattedUrl(origin);
+  if (!ShouldCheckPasswords(leak_type)) {
+    std::vector<size_t> offsets;
+    base::string16 bold_message = l10n_util::GetStringUTF16(
+        IDS_CREDENTIAL_LEAK_CHANGE_PASSWORD_BOLD_MESSAGE);
+    return l10n_util::GetStringFUTF16(
+        IDS_CREDENTIAL_LEAK_CHANGE_PASSWORD_MESSAGE, bold_message, formatted,
+        &offsets);
+  } else if (password_manager::IsPasswordSaved(leak_type)) {
+    return l10n_util::GetStringUTF16(
+        IDS_CREDENTIAL_LEAK_CHECK_PASSWORDS_MESSAGE);
+  } else {
+    return l10n_util::GetStringFUTF16(
+        IDS_CREDENTIAL_LEAK_CHANGE_AND_CHECK_PASSWORDS_MESSAGE, formatted);
+  }
+}
+
+base::string16 GetTitle(CredentialLeakType leak_type) {
+  return l10n_util::GetStringUTF16(IDS_CREDENTIAL_LEAK_TITLE);
+}
+
+bool ShouldCheckPasswords(CredentialLeakType leak_type) {
+  return password_manager::IsPasswordUsedOnOtherSites(leak_type) &&
+         password_manager::IsSyncingPasswordsNormally(leak_type);
+}
+
+bool ShouldShowCancelButton(CredentialLeakType leak_type) {
+  return ShouldCheckPasswords(leak_type);
+}
+
+LeakDialogType GetLeakDialogType(CredentialLeakType leak_type) {
+  if (!ShouldCheckPasswords(leak_type))
+    return LeakDialogType::kChange;
+
+  return password_manager::IsPasswordSaved(leak_type)
+             ? LeakDialogType::kCheckup
+             : LeakDialogType::kCheckupAndChange;
+}
+
+GURL GetPasswordCheckupURL() {
+  std::string value = base::GetFieldTrialParamValueByFeature(
+      password_manager::features::kLeakDetection, "leak-check-url");
+  if (value.empty())
+    return GURL(password_manager::kPasswordCheckupURL);
+  return GURL(value);
+}
+
+gfx::Range GetChangePasswordBoldRange(CredentialLeakType leak_type,
+                                      const GURL& origin) {
+  if (ShouldCheckPasswords(leak_type))
+    return gfx::Range();
+
+  std::vector<size_t> offsets;
+  const base::string16 bold_message = l10n_util::GetStringUTF16(
+      IDS_CREDENTIAL_LEAK_CHANGE_PASSWORD_BOLD_MESSAGE);
+  const base::string16 change_password_message = l10n_util::GetStringFUTF16(
+      IDS_CREDENTIAL_LEAK_CHANGE_PASSWORD_MESSAGE, bold_message,
+      GetFormattedUrl(origin), &offsets);
+  return offsets.empty()
+             ? gfx::Range()
+             : gfx::Range(offsets[0], offsets[0] + bold_message.length());
+}
+
 }  // namespace password_manager
diff --git a/components/password_manager/core/browser/leak_detection_dialog_utils.h b/components/password_manager/core/browser/leak_detection_dialog_utils.h
index 867d864c..6b0a574 100644
--- a/components/password_manager/core/browser/leak_detection_dialog_utils.h
+++ b/components/password_manager/core/browser/leak_detection_dialog_utils.h
@@ -7,7 +7,13 @@
 
 #include <type_traits>
 
+#include "base/macros.h"
+#include "base/strings/string16.h"
 #include "base/util/type_safety/strong_alias.h"
+#include "components/password_manager/core/browser/leak_detection_dialog_utils.h"
+#include "components/password_manager/core/browser/password_manager_metrics_util.h"
+#include "ui/gfx/range/range.h"
+#include "url/gurl.h"
 
 namespace password_manager {
 
@@ -42,6 +48,39 @@
 // Checks whether user is syncing passwords with normal encryption.
 bool IsSyncingPasswordsNormally(CredentialLeakType leak_type);
 
+// Returns the label for the leak dialog accept button.
+base::string16 GetAcceptButtonLabel(
+    password_manager::CredentialLeakType leak_type);
+
+// Returns the label for the leak dialog cancel button.
+base::string16 GetCancelButtonLabel();
+
+// Returns the leak dialog message based on leak type.
+base::string16 GetDescription(password_manager::CredentialLeakType leak_type,
+                              const GURL& origin);
+
+// Returns the leak dialog title based on leak type.
+base::string16 GetTitle(password_manager::CredentialLeakType leak_type);
+
+// Checks whether the leak dialog should prompt user to password checkup.
+bool ShouldCheckPasswords(password_manager::CredentialLeakType leak_type);
+
+// Checks whether the leak dialog should show cancel button.
+bool ShouldShowCancelButton(password_manager::CredentialLeakType leak_type);
+
+// Returns the LeakDialogType corresponding to |leak_type|.
+password_manager::metrics_util::LeakDialogType GetLeakDialogType(
+    password_manager::CredentialLeakType leak_type);
+
+// Returns the URL used to launch the password checkup.
+GURL GetPasswordCheckupURL();
+
+// Returns the range of the bold part of the leak dialog message when
+// credentials were leaked only on current site.
+gfx::Range GetChangePasswordBoldRange(
+    password_manager::CredentialLeakType leak_type,
+    const GURL& origin);
+
 }  // namespace password_manager
 
 #endif  // COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_LEAK_DETECTION_DIALOG_UTILS_H_
diff --git a/chrome/browser/ui/passwords/credential_leak_dialog_utils_unittest.cc b/components/password_manager/core/browser/leak_detection_dialog_utils_unittest.cc
similarity index 96%
rename from chrome/browser/ui/passwords/credential_leak_dialog_utils_unittest.cc
rename to components/password_manager/core/browser/leak_detection_dialog_utils_unittest.cc
index e79239f..7da6287 100644
--- a/chrome/browser/ui/passwords/credential_leak_dialog_utils_unittest.cc
+++ b/components/password_manager/core/browser/leak_detection_dialog_utils_unittest.cc
@@ -2,10 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/ui/passwords/credential_leak_dialog_utils.h"
-#include "base/strings/utf_string_conversions.h"
-#include "chrome/grit/generated_resources.h"
 #include "components/password_manager/core/browser/leak_detection_dialog_utils.h"
+#include "base/strings/utf_string_conversions.h"
 #include "components/strings/grit/components_strings.h"
 #include "components/url_formatter/elide_url.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -20,7 +18,7 @@
 using password_manager::IsSaved;
 using password_manager::IsSyncing;
 
-namespace leak_dialog_utils {
+namespace password_manager {
 
 namespace {
 
@@ -153,4 +151,4 @@
   }
 }
 
-}  // namespace leak_dialog_utils
+}  // namespace password_manager
diff --git a/components/password_manager_strings.grdp b/components/password_manager_strings.grdp
index c7985c1..0a89046 100644
--- a/components/password_manager_strings.grdp
+++ b/components/password_manager_strings.grdp
@@ -1,6 +1,30 @@
 <?xml version="1.0" encoding="utf-8"?>
 <grit-part>
-
+  <if expr="use_titlecase">
+    <message name="IDS_LEAK_CHECK_CREDENTIALS" desc="The text of the OK button in the dialog for credentials leaked on multiple sites.">
+      Check Passwords
+    </message>
+  </if>
+  <if expr="not use_titlecase">
+    <message name="IDS_LEAK_CHECK_CREDENTIALS" desc="The text of the OK button in the dialog for credentials leaked on multiple sites.">
+      Check passwords
+    </message>
+  </if>
+  <message name="IDS_CREDENTIAL_LEAK_TITLE" desc="The title of the credential leak dialog.">
+    Data breach reported
+  </message>
+  <message name="IDS_CREDENTIAL_LEAK_CHECK_PASSWORDS_MESSAGE" desc="The text that is used in credential leak detection dialog when saved credentials were used on multiple sites.">
+    A data breach on a site or app you use exposed your password. Chrome recommends checking your saved passwords now.
+  </message>
+  <message name="IDS_CREDENTIAL_LEAK_CHANGE_PASSWORD_MESSAGE" desc="The text that is used in credential leak detection dialog when credentials were leaked on current site only.">
+    A data breach on a site or app you use exposed your password. Chrome recommends <ph name="BOLD">$1<ex>changing your password</ex></ph> on <ph name="ORIGIN">$2<ex>example.com</ex></ph> now.
+  </message>
+  <message name="IDS_CREDENTIAL_LEAK_CHANGE_PASSWORD_BOLD_MESSAGE" desc="The text that is written in bold in leak dialog message when credentials were leaked on current site only.">
+    changing your password
+  </message>
+  <message name="IDS_CREDENTIAL_LEAK_CHANGE_AND_CHECK_PASSWORDS_MESSAGE" desc="The text that is used in credential leak detection dialog when credentials were not saved in chrome, but used on multiple sites.">
+    A data breach on a site or app you use exposed your password. Chrome recommends checking your saved passwords and changing your password on <ph name="ORIGIN">$1<ex>example.com</ex></ph>.
+  </message>
   <message name="IDS_MANAGE_PASSWORDS_AUTO_SIGNIN_TITLE" desc="The title of the auto-signin toast.">
     Signing in as <ph name="username">$1<ex>chef@google.com</ex></ph>
   </message>
@@ -35,5 +59,4 @@
   <message name="IDS_PASSWORD_MANAGER_DEFAULT_EXPORT_FILENAME" desc="Chrome suggests this file name when user chooses to export their passwords saved with Chrome.">
     Chrome Passwords
   </message>
-
 </grit-part>
diff --git a/components/policy/core/common/cloud/cloud_policy_client_unittest.cc b/components/policy/core/common/cloud/cloud_policy_client_unittest.cc
index 8caff10..16ef0349 100644
--- a/components/policy/core/common/cloud/cloud_policy_client_unittest.cc
+++ b/components/policy/core/common/cloud/cloud_policy_client_unittest.cc
@@ -1473,8 +1473,8 @@
   event_list.Append(std::move(event));
 
   client_->UploadRealtimeReport(
-      std::move(policy::RealtimeReportingJobConfiguration::BuildReport(
-          std::move(event_list), std::move(context))),
+      policy::RealtimeReportingJobConfiguration::BuildReport(
+          std::move(event_list), std::move(context)),
       callback);
   base::RunLoop().RunUntilIdle();
   EXPECT_EQ(
diff --git a/components/security_interstitials/core/browser/resources/interstitial_safebrowsing.css b/components/security_interstitials/core/browser/resources/interstitial_safebrowsing.css
index 98d78624..cfc256e 100644
--- a/components/security_interstitials/core/browser/resources/interstitial_safebrowsing.css
+++ b/components/security_interstitials/core/browser/resources/interstitial_safebrowsing.css
@@ -64,7 +64,7 @@
        (max-height: 560px) {
   body.safe-browsing .nav-wrapper {
     background: var(--google-red-600);
-    box-shadow: 0 -22px 40px var(--google-red-600);
+    box-shadow: 0 -12px 24px var(--google-red-600);
   }
 }
 
diff --git a/components/security_interstitials/core/common/resources/interstitial_common.css b/components/security_interstitials/core/common/resources/interstitial_common.css
index f43f92c..0ca3c2ba 100644
--- a/components/security_interstitials/core/common/resources/interstitial_common.css
+++ b/components/security_interstitials/core/common/resources/interstitial_common.css
@@ -132,7 +132,7 @@
 
 #extended-reporting-opt-in {
   font-size: .875em;
-  margin-top: 39px;
+  margin-top: 32px;
 }
 
 #extended-reporting-opt-in label {
@@ -167,7 +167,7 @@
   display: block;
   height: 1em;
   left: -1em;
-  padding: var(--padding);
+  padding-inline-start: var(--padding);
   position: absolute;
   right: 0;
   top: -.5em;
@@ -293,7 +293,7 @@
   body .nav-wrapper {
     background: var(--background-color);
     bottom: 0;
-    box-shadow: 0 -22px 40px var(--background-color);
+    box-shadow: 0 -12px 24px var(--background-color);
     left: 0;
     margin: 0 auto;
     max-width: 736px;
@@ -331,7 +331,8 @@
 
   button,
   [dir='rtl'] button,
-  button.small-link {
+  button.small-link,
+  .nav-wrapper .secondary-button {
     font-family: Roboto-Regular,Helvetica;
     font-size: .933em;
     margin: 6px 0;
@@ -418,6 +419,12 @@
   }
 }
 
+@media (max-height: 560px) and (min-height: 240px) and (orientation:landscape) {
+  .extended-reporting-has-checkbox #details {
+    padding-bottom: 80px;
+  }
+}
+
 @media (min-height: 500px) and (max-height: 650px) and (max-width: 414px) and
        (orientation: portrait) {
   .interstitial-wrapper {
@@ -461,7 +468,8 @@
     width: 100%;
   }
 
-  button {
+  button,
+  .nav-wrapper .secondary-button {
     padding: 16px 24px;
   }
 
diff --git a/components/signin/internal/identity_manager/profile_oauth2_token_service_builder.cc b/components/signin/internal/identity_manager/profile_oauth2_token_service_builder.cc
index 4ccaa74..1a3546be 100644
--- a/components/signin/internal/identity_manager/profile_oauth2_token_service_builder.cc
+++ b/components/signin/internal/identity_manager/profile_oauth2_token_service_builder.cc
@@ -23,7 +23,6 @@
 
 #if defined(OS_CHROMEOS)
 #include "chromeos/components/account_manager/account_manager.h"
-#include "chromeos/constants/chromeos_features.h"
 #include "components/signin/internal/identity_manager/profile_oauth2_token_service_delegate_chromeos.h"
 #include "components/user_manager/user_manager.h"
 #endif  // defined(OS_CHROMEOS)
@@ -57,8 +56,7 @@
       signin_client, std::move(device_accounts_provider),
       account_tracker_service);
 }
-#else  // !defined(OS_ANDROID) && !defined(OS_IOS)
-#if defined(OS_CHROMEOS)
+#elif defined(OS_CHROMEOS)
 std::unique_ptr<signin::ProfileOAuth2TokenServiceDelegateChromeOS>
 CreateCrOsOAuthDelegate(
     AccountTrackerService* account_tracker_service,
@@ -70,25 +68,7 @@
       account_tracker_service, network_connection_tracker, account_manager,
       is_regular_profile);
 }
-#endif  // defined(OS_CHROMEOS)
-
-// Supervised users cannot revoke credentials.
-bool CanRevokeCredentials() {
-#if defined(OS_CHROMEOS)
-  // UserManager may not exist in unit_tests.
-  if (user_manager::UserManager::IsInitialized() &&
-      user_manager::UserManager::Get()->IsLoggedInAsSupervisedUser()) {
-    // Don't allow revoking credentials for Chrome OS supervised users.
-    // See http://crbug.com/332032
-    LOG(ERROR) << "Attempt to revoke supervised user refresh "
-               << "token detected, ignoring.";
-    return false;
-  }
-#endif
-
-  return true;
-}
-
+#else
 std::unique_ptr<MutableProfileOAuth2TokenServiceDelegate>
 CreateMutableProfileOAuthDelegate(
     AccountTrackerService* account_tracker_service,
@@ -110,7 +90,7 @@
   return std::make_unique<MutableProfileOAuth2TokenServiceDelegate>(
       signin_client, account_tracker_service, network_connection_tracker,
       token_web_data, account_consistency, revoke_all_tokens_on_load,
-      CanRevokeCredentials(),
+      true /* can_revoke_credentials */,
 #if defined(OS_WIN)
       reauth_callback
 #else
@@ -147,24 +127,19 @@
   return CreateIOSOAuthDelegate(signin_client,
                                 std::move(device_accounts_provider),
                                 account_tracker_service);
-#else  // !defined(OS_ANDROID) && !defined(OS_IOS)
-#if defined(OS_CHROMEOS)
-  if (chromeos::features::IsAccountManagerEnabled()) {
-    return CreateCrOsOAuthDelegate(account_tracker_service,
-                                   network_connection_tracker, account_manager,
-                                   is_regular_profile);
-  }
-#endif  // defined(OS_CHROMEOS)
-  // Fall back to |MutableProfileOAuth2TokenServiceDelegate|:
-  // 1. On all platforms other than Android and Chrome OS.
-  // 2. On Chrome OS, if Account Manager has not been switched on yet
-  // (chromeos::features::IsAccountManagerEnabled).
+#elif defined(OS_CHROMEOS)
+  return CreateCrOsOAuthDelegate(account_tracker_service,
+                                 network_connection_tracker, account_manager,
+                                 is_regular_profile);
+#else
+  // Fall back to |MutableProfileOAuth2TokenServiceDelegate| on all platforms
+  // other than Android, iOS, and Chrome OS.
   return CreateMutableProfileOAuthDelegate(
       account_tracker_service, account_consistency,
       delete_signin_cookies_on_exit, token_web_data, signin_client,
 #if defined(OS_WIN)
       reauth_callback,
-#endif
+#endif  // defined(OS_WIN)
       network_connection_tracker);
 
 #endif  // defined(OS_ANDROID)
diff --git a/components/sync/driver/glue/sync_engine_backend.cc b/components/sync/driver/glue/sync_engine_backend.cc
index 99269df2..9e76e8d 100644
--- a/components/sync/driver/glue/sync_engine_backend.cc
+++ b/components/sync/driver/glue/sync_engine_backend.cc
@@ -433,6 +433,12 @@
   sync_manager_->GetEncryptionHandler()->SetEncryptionPassphrase(passphrase);
 }
 
+void SyncEngineBackend::DoAddTrustedVaultDecryptionKeys(
+    const std::vector<std::string>& keys) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  sync_manager_->GetEncryptionHandler()->AddTrustedVaultDecryptionKeys(keys);
+}
+
 void SyncEngineBackend::DoInitialProcessControlTypes() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
diff --git a/components/sync/driver/glue/sync_engine_backend.h b/components/sync/driver/glue/sync_engine_backend.h
index ade6f25..2b4ac88 100644
--- a/components/sync/driver/glue/sync_engine_backend.h
+++ b/components/sync/driver/glue/sync_engine_backend.h
@@ -116,9 +116,12 @@
   // Called to set the passphrase for encryption.
   void DoSetEncryptionPassphrase(const std::string& passphrase);
 
-  // Called to decrypt the pending keys.
+  // Called to decrypt the pending keys using user-entered passphrases.
   void DoSetDecryptionPassphrase(const std::string& passphrase);
 
+  // Called to decrypt the pending keys using trusted vault keys.
+  void DoAddTrustedVaultDecryptionKeys(const std::vector<std::string>& keys);
+
   // Called to turn on encryption of all sync data as well as
   // reencrypt everything.
   void DoEnableEncryptEverything();
diff --git a/components/sync/driver/glue/sync_engine_impl.cc b/components/sync/driver/glue/sync_engine_impl.cc
index fd54656..60c3bd7 100644
--- a/components/sync/driver/glue/sync_engine_impl.cc
+++ b/components/sync/driver/glue/sync_engine_impl.cc
@@ -119,6 +119,15 @@
                                 backend_, passphrase));
 }
 
+void SyncEngineImpl::AddTrustedVaultDecryptionKeys(
+    const std::vector<std::string>& keys) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  sync_task_runner_->PostTask(
+      FROM_HERE,
+      base::BindOnce(&SyncEngineBackend::DoAddTrustedVaultDecryptionKeys,
+                     backend_, keys));
+}
+
 void SyncEngineImpl::StopSyncingForShutdown() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
diff --git a/components/sync/driver/glue/sync_engine_impl.h b/components/sync/driver/glue/sync_engine_impl.h
index e7a9c1f..71d2db1b 100644
--- a/components/sync/driver/glue/sync_engine_impl.h
+++ b/components/sync/driver/glue/sync_engine_impl.h
@@ -64,6 +64,8 @@
   void StartSyncingWithServer() override;
   void SetEncryptionPassphrase(const std::string& passphrase) override;
   void SetDecryptionPassphrase(const std::string& passphrase) override;
+  void AddTrustedVaultDecryptionKeys(
+      const std::vector<std::string>& keys) override;
   void StopSyncingForShutdown() override;
   void Shutdown(ShutdownReason reason) override;
   void ConfigureDataTypes(ConfigureParams params) override;
diff --git a/components/sync/driver/profile_sync_service.cc b/components/sync/driver/profile_sync_service.cc
index 42e3b29..19c3d8f0 100644
--- a/components/sync/driver/profile_sync_service.cc
+++ b/components/sync/driver/profile_sync_service.cc
@@ -1045,7 +1045,7 @@
 
   // We should never get in a state where we have no encrypted datatypes
   // enabled, and yet we still think we require a passphrase for decryption.
-  DCHECK(!user_settings_->IsPassphraseRequiredForDecryption() ||
+  DCHECK(!user_settings_->IsPassphraseRequiredForPreferredDataTypes() ||
          user_settings_->IsEncryptedDatatypeEnabled());
 
   // Notify listeners that configuration is done.
@@ -1334,11 +1334,6 @@
   return last_snapshot_;
 }
 
-PassphraseRequiredReason
-ProfileSyncService::GetPassphraseRequiredReasonForTest() const {
-  return crypto_.passphrase_required_reason();
-}
-
 void ProfileSyncService::HasUnsyncedItemsForTest(
     base::OnceCallback<void(bool)> cb) const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
diff --git a/components/sync/driver/profile_sync_service.h b/components/sync/driver/profile_sync_service.h
index 19694857..4e68e16 100644
--- a/components/sync/driver/profile_sync_service.h
+++ b/components/sync/driver/profile_sync_service.h
@@ -222,8 +222,6 @@
   bool IsPassphrasePrompted() const;
   void SetPassphrasePrompted(bool prompted);
 
-  PassphraseRequiredReason GetPassphraseRequiredReasonForTest() const;
-
   // Returns whether or not the underlying sync engine has made any
   // local changes to items that have not yet been synced with the
   // server.
diff --git a/components/sync/driver/sync_service_crypto.cc b/components/sync/driver/sync_service_crypto.cc
index 799e52e..7e0838f 100644
--- a/components/sync/driver/sync_service_crypto.cc
+++ b/components/sync/driver/sync_service_crypto.cc
@@ -48,6 +48,22 @@
                        observer_));
   }
 
+  void OnTrustedVaultKeyRequired() override {
+    task_runner_->PostTask(
+        FROM_HERE,
+        base::BindOnce(
+            &SyncEncryptionHandler::Observer::OnTrustedVaultKeyRequired,
+            observer_));
+  }
+
+  void OnTrustedVaultKeyAccepted() override {
+    task_runner_->PostTask(
+        FROM_HERE,
+        base::BindOnce(
+            &SyncEncryptionHandler::Observer::OnTrustedVaultKeyAccepted,
+            observer_));
+  }
+
   void OnBootstrapTokenUpdated(const std::string& bootstrap_token,
                                BootstrapTokenType type) override {
     task_runner_->PostTask(
@@ -151,6 +167,22 @@
   return state_.cached_explicit_passphrase_time;
 }
 
+bool SyncServiceCrypto::IsPassphraseRequired() const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  switch (state_.required_user_action) {
+    case RequiredUserAction::kNone:
+      break;
+    case RequiredUserAction::kPassphraseRequiredForDecryption:
+    case RequiredUserAction::kPassphraseRequiredForEncryption:
+      return true;
+    case RequiredUserAction::kTrustedVaultKeyRequired:
+      // TODO(crbug.com/1010189): This should return false and get exposed
+      // differently to upper layers.
+      return true;
+  }
+  return false;
+}
+
 bool SyncServiceCrypto::IsUsingSecondaryPassphrase() const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   return IsExplicitPassphrase(state_.cached_passphrase_type);
@@ -176,17 +208,20 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   // This should only be called when the engine has been initialized.
   DCHECK(state_.engine);
-  DCHECK(state_.passphrase_required_reason != REASON_DECRYPTION)
+  DCHECK_NE(state_.required_user_action,
+            RequiredUserAction::kPassphraseRequiredForDecryption)
       << "Can not set explicit passphrase when decryption is needed.";
 
   DVLOG(1) << "Setting explicit passphrase for encryption.";
-  if (state_.passphrase_required_reason == REASON_ENCRYPTION) {
-    // REASON_ENCRYPTION implies that the cryptographer does not have pending
-    // keys. Hence, as long as we're not trying to do an invalid passphrase
-    // change (e.g. explicit -> explicit or explicit -> implicit), we know this
-    // will succeed. If for some reason a new encryption key arrives via
-    // sync later, the SBH will trigger another OnPassphraseRequired().
-    state_.passphrase_required_reason = REASON_PASSPHRASE_NOT_REQUIRED;
+  if (state_.required_user_action ==
+      RequiredUserAction::kPassphraseRequiredForEncryption) {
+    // |kPassphraseRequiredForEncryption| implies that the cryptographer does
+    // not have pending keys. Hence, as long as we're not trying to do an
+    // invalid passphrase change (e.g. explicit -> explicit or explicit ->
+    // implicit), we know this will succeed. If for some reason a new
+    // encryption key arrives via sync later, the SyncEncryptionHandler will
+    // trigger another OnPassphraseRequired().
+    state_.required_user_action = RequiredUserAction::kNone;
     notify_observers_.Run();
   }
 
@@ -203,6 +238,13 @@
 bool SyncServiceCrypto::SetDecryptionPassphrase(const std::string& passphrase) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
+  // TODO(crbug.com/1010189): Move this logic to a separate function.
+  if (state_.required_user_action ==
+      RequiredUserAction::kTrustedVaultKeyRequired) {
+    state_.engine->AddTrustedVaultDecryptionKeys({passphrase});
+    return true;
+  }
+
   // We should never be called with an empty passphrase.
   DCHECK(!passphrase.empty());
 
@@ -270,7 +312,17 @@
 
   DVLOG(1) << "Passphrase required with reason: "
            << PassphraseRequiredReasonToString(reason);
-  state_.passphrase_required_reason = reason;
+
+  switch (reason) {
+    case REASON_ENCRYPTION:
+      state_.required_user_action =
+          RequiredUserAction::kPassphraseRequiredForEncryption;
+      break;
+    case REASON_DECRYPTION:
+      state_.required_user_action =
+          RequiredUserAction::kPassphraseRequiredForDecryption;
+      break;
+  }
 
   // Reconfigure without the encrypted types (excluded implicitly via the
   // failed datatypes handler).
@@ -283,15 +335,46 @@
   // Clear our cache of the cryptographer's pending keys.
   state_.cached_pending_keys.clear_blob();
 
-  // Reset |passphrase_required_reason| since we know we no longer require the
+  // Reset |required_user_action| since we know we no longer require the
   // passphrase.
-  state_.passphrase_required_reason = REASON_PASSPHRASE_NOT_REQUIRED;
+  state_.required_user_action = RequiredUserAction::kNone;
 
   // Make sure the data types that depend on the passphrase are started at
   // this time.
   reconfigure_.Run(CONFIGURE_REASON_CRYPTO);
 }
 
+void SyncServiceCrypto::OnTrustedVaultKeyRequired() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  // To be on the safe since, if a passphrase is required, we avoid overriding
+  // |state_.required_user_action|.
+  if (state_.required_user_action != RequiredUserAction::kNone) {
+    return;
+  }
+
+  state_.required_user_action = RequiredUserAction::kTrustedVaultKeyRequired;
+
+  // Reconfigure without the encrypted types (excluded implicitly via the
+  // failed datatypes handler).
+  reconfigure_.Run(CONFIGURE_REASON_CRYPTO);
+}
+
+void SyncServiceCrypto::OnTrustedVaultKeyAccepted() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  if (state_.required_user_action !=
+      RequiredUserAction::kTrustedVaultKeyRequired) {
+    return;
+  }
+
+  state_.required_user_action = RequiredUserAction::kNone;
+
+  // Make sure the data types that depend on the decryption key are started at
+  // this time.
+  reconfigure_.Run(CONFIGURE_REASON_CRYPTO);
+}
+
 void SyncServiceCrypto::OnBootstrapTokenUpdated(
     const std::string& bootstrap_token,
     BootstrapTokenType type) {
diff --git a/components/sync/driver/sync_service_crypto.h b/components/sync/driver/sync_service_crypto.h
index a9f08fdd..576a7060 100644
--- a/components/sync/driver/sync_service_crypto.h
+++ b/components/sync/driver/sync_service_crypto.h
@@ -7,6 +7,7 @@
 
 #include <memory>
 #include <string>
+#include <vector>
 
 #include "base/callback.h"
 #include "base/memory/weak_ptr.h"
@@ -35,11 +36,13 @@
 
   // See the SyncService header.
   base::Time GetExplicitPassphraseTime() const;
+  bool IsPassphraseRequired() const;
   bool IsUsingSecondaryPassphrase() const;
   void EnableEncryptEverything();
   bool IsEncryptEverythingEnabled() const;
   void SetEncryptionPassphrase(const std::string& passphrase);
   bool SetDecryptionPassphrase(const std::string& passphrase);
+  void AddTrustedVaultDecryptionKeys(const std::vector<std::string>& keys);
 
   // Returns the actual passphrase type being used for encryption.
   PassphraseType GetPassphraseType() const;
@@ -53,6 +56,8 @@
       const KeyDerivationParams& key_derivation_params,
       const sync_pb::EncryptedData& pending_keys) override;
   void OnPassphraseAccepted() override;
+  void OnTrustedVaultKeyRequired() override;
+  void OnTrustedVaultKeyAccepted() override;
   void OnBootstrapTokenUpdated(const std::string& bootstrap_token,
                                BootstrapTokenType type) override;
   void OnEncryptedTypesChanged(ModelTypeSet encrypted_types,
@@ -69,12 +74,16 @@
   // Creates a proxy observer object that will post calls to this thread.
   std::unique_ptr<SyncEncryptionHandler::Observer> GetEncryptionObserverProxy();
 
-  PassphraseRequiredReason passphrase_required_reason() const {
-    return state_.passphrase_required_reason;
-  }
   bool encryption_pending() const { return state_.encryption_pending; }
 
  private:
+  enum class RequiredUserAction {
+    kNone,
+    kPassphraseRequiredForDecryption,
+    kPassphraseRequiredForEncryption,
+    kTrustedVaultKeyRequired,
+  };
+
   // Calls SyncServiceBase::NotifyObservers(). Never null.
   const base::RepeatingClosure notify_observers_;
 
@@ -95,11 +104,7 @@
     // Not-null when the engine is initialized.
     SyncEngine* engine = nullptr;
 
-    // Was the last SYNC_PASSPHRASE_REQUIRED notification sent because it
-    // was required for encryption, decryption with a cached passphrase, or
-    // because a new passphrase is required?
-    PassphraseRequiredReason passphrase_required_reason =
-        REASON_PASSPHRASE_NOT_REQUIRED;
+    RequiredUserAction required_user_action = RequiredUserAction::kNone;
 
     // The current set of encrypted types. Always a superset of
     // Cryptographer::SensitiveTypes().
diff --git a/components/sync/driver/sync_user_settings.h b/components/sync/driver/sync_user_settings.h
index d099c4d..0b05ede 100644
--- a/components/sync/driver/sync_user_settings.h
+++ b/components/sync/driver/sync_user_settings.h
@@ -93,7 +93,7 @@
   bool IsPassphraseRequired() const override = 0;
   // Whether a passphrase is required to decrypt the data for any currently
   // enabled data type.
-  virtual bool IsPassphraseRequiredForDecryption() const = 0;
+  virtual bool IsPassphraseRequiredForPreferredDataTypes() const = 0;
   // Whether a "secondary" passphrase is in use (aka explicit passphrase), which
   // means either a custom or a frozen implicit passphrase.
   virtual bool IsUsingSecondaryPassphrase() const = 0;
@@ -108,6 +108,8 @@
   // Asynchronously decrypts pending keys using |passphrase|. Returns false
   // immediately if the passphrase could not be used to decrypt a locally cached
   // copy of encrypted keys; returns true otherwise.
+  // TODO(crbug.com/1010189): Introduce a dedicated API for trusted vault
+  // decryption keys.
   virtual bool SetDecryptionPassphrase(const std::string& passphrase)
       WARN_UNUSED_RESULT = 0;
 };
diff --git a/components/sync/driver/sync_user_settings_impl.cc b/components/sync/driver/sync_user_settings_impl.cc
index 30c1ea9..695d6d0 100644
--- a/components/sync/driver/sync_user_settings_impl.cc
+++ b/components/sync/driver/sync_user_settings_impl.cc
@@ -125,11 +125,10 @@
 }
 
 bool SyncUserSettingsImpl::IsPassphraseRequired() const {
-  return crypto_->passphrase_required_reason() !=
-         REASON_PASSPHRASE_NOT_REQUIRED;
+  return crypto_->IsPassphraseRequired();
 }
 
-bool SyncUserSettingsImpl::IsPassphraseRequiredForDecryption() const {
+bool SyncUserSettingsImpl::IsPassphraseRequiredForPreferredDataTypes() const {
   // If there is an encrypted datatype enabled and we don't have the proper
   // passphrase, we must prompt the user for a passphrase. The only way for the
   // user to avoid entering their passphrase is to disable the encrypted types.
diff --git a/components/sync/driver/sync_user_settings_impl.h b/components/sync/driver/sync_user_settings_impl.h
index 56252b4..6fe27b3e 100644
--- a/components/sync/driver/sync_user_settings_impl.h
+++ b/components/sync/driver/sync_user_settings_impl.h
@@ -53,7 +53,7 @@
 
   ModelTypeSet GetEncryptedDataTypes() const override;
   bool IsPassphraseRequired() const override;
-  bool IsPassphraseRequiredForDecryption() const override;
+  bool IsPassphraseRequiredForPreferredDataTypes() const override;
   bool IsUsingSecondaryPassphrase() const override;
   base::Time GetExplicitPassphraseTime() const override;
   PassphraseType GetPassphraseType() const override;
diff --git a/components/sync/driver/sync_user_settings_mock.h b/components/sync/driver/sync_user_settings_mock.h
index fd72dd3..96dc21a 100644
--- a/components/sync/driver/sync_user_settings_mock.h
+++ b/components/sync/driver/sync_user_settings_mock.h
@@ -38,7 +38,7 @@
 
   MOCK_CONST_METHOD0(GetEncryptedDataTypes, ModelTypeSet());
   MOCK_CONST_METHOD0(IsPassphraseRequired, bool());
-  MOCK_CONST_METHOD0(IsPassphraseRequiredForDecryption, bool());
+  MOCK_CONST_METHOD0(IsPassphraseRequiredForPreferredDataTypes, bool());
   MOCK_CONST_METHOD0(IsUsingSecondaryPassphrase, bool());
   MOCK_CONST_METHOD0(GetExplicitPassphraseTime, base::Time());
   MOCK_CONST_METHOD0(GetPassphraseType, PassphraseType());
diff --git a/components/sync/driver/test_sync_user_settings.cc b/components/sync/driver/test_sync_user_settings.cc
index ce94b10..12c9a57 100644
--- a/components/sync/driver/test_sync_user_settings.cc
+++ b/components/sync/driver/test_sync_user_settings.cc
@@ -122,7 +122,7 @@
   return passphrase_required_;
 }
 
-bool TestSyncUserSettings::IsPassphraseRequiredForDecryption() const {
+bool TestSyncUserSettings::IsPassphraseRequiredForPreferredDataTypes() const {
   return passphrase_required_for_decryption_;
 }
 
diff --git a/components/sync/driver/test_sync_user_settings.h b/components/sync/driver/test_sync_user_settings.h
index ba387ae..7019410 100644
--- a/components/sync/driver/test_sync_user_settings.h
+++ b/components/sync/driver/test_sync_user_settings.h
@@ -42,7 +42,7 @@
 
   syncer::ModelTypeSet GetEncryptedDataTypes() const override;
   bool IsPassphraseRequired() const override;
-  bool IsPassphraseRequiredForDecryption() const override;
+  bool IsPassphraseRequiredForPreferredDataTypes() const override;
   bool IsUsingSecondaryPassphrase() const override;
   base::Time GetExplicitPassphraseTime() const override;
   PassphraseType GetPassphraseType() const override;
diff --git a/components/sync/engine/fake_sync_engine.cc b/components/sync/engine/fake_sync_engine.cc
index 9fee2c2..9318db8 100644
--- a/components/sync/engine/fake_sync_engine.cc
+++ b/components/sync/engine/fake_sync_engine.cc
@@ -46,6 +46,9 @@
 
 void FakeSyncEngine::SetDecryptionPassphrase(const std::string& passphrase) {}
 
+void FakeSyncEngine::AddTrustedVaultDecryptionKeys(
+    const std::vector<std::string>& keys) {}
+
 void FakeSyncEngine::StopSyncingForShutdown() {}
 
 void FakeSyncEngine::Shutdown(ShutdownReason reason) {}
diff --git a/components/sync/engine/fake_sync_engine.h b/components/sync/engine/fake_sync_engine.h
index 6f858a8..0665e80 100644
--- a/components/sync/engine/fake_sync_engine.h
+++ b/components/sync/engine/fake_sync_engine.h
@@ -7,6 +7,7 @@
 
 #include <memory>
 #include <string>
+#include <vector>
 
 #include "base/callback.h"
 #include "base/compiler_specific.h"
@@ -45,6 +46,9 @@
 
   void SetDecryptionPassphrase(const std::string& passphrase) override;
 
+  void AddTrustedVaultDecryptionKeys(
+      const std::vector<std::string>& keys) override;
+
   void StopSyncingForShutdown() override;
 
   void Shutdown(ShutdownReason reason) override;
diff --git a/components/sync/engine/mock_sync_engine.h b/components/sync/engine/mock_sync_engine.h
index 57705236..808207d 100644
--- a/components/sync/engine/mock_sync_engine.h
+++ b/components/sync/engine/mock_sync_engine.h
@@ -7,6 +7,7 @@
 
 #include <memory>
 #include <string>
+#include <vector>
 
 #include "components/sync/engine/data_type_activation_response.h"
 #include "components/sync/engine/sync_engine.h"
@@ -44,6 +45,8 @@
   MOCK_METHOD0(StartSyncingWithServer, void());
   MOCK_METHOD1(SetEncryptionPassphrase, void(const std::string&));
   MOCK_METHOD1(SetDecryptionPassphrase, void(const std::string&));
+  MOCK_METHOD1(AddTrustedVaultDecryptionKeys,
+               void(const std::vector<std::string>&));
   MOCK_METHOD0(StopSyncingForShutdown, void());
   MOCK_METHOD1(Shutdown, void(ShutdownReason));
   MOCK_METHOD0(EnableEncryptEverything, void());
diff --git a/components/sync/engine/sync_encryption_handler.h b/components/sync/engine/sync_encryption_handler.h
index c4d77cfb..c8c90651 100644
--- a/components/sync/engine/sync_encryption_handler.h
+++ b/components/sync/engine/sync_encryption_handler.h
@@ -6,6 +6,7 @@
 #define COMPONENTS_SYNC_ENGINE_SYNC_ENCRYPTION_HANDLER_H_
 
 #include <string>
+#include <vector>
 
 #include "base/time/time.h"
 #include "components/sync/base/model_type.h"
@@ -21,7 +22,6 @@
 
 // Reasons due to which Cryptographer might require a passphrase.
 enum PassphraseRequiredReason {
-  REASON_PASSPHRASE_NOT_REQUIRED = 0,  // Initial value.
   REASON_ENCRYPTION = 1,               // The cryptographer requires a
                                        // passphrase for its first attempt at
                                        // encryption. Happens only during
@@ -77,6 +77,16 @@
     // now used to encrypt sync data.
     virtual void OnPassphraseAccepted() = 0;
 
+    // Called when decryption keys are required in order to decrypt pending
+    // Nigori keys and resume sync, for the TRUSTED_VAULT_PASSPHRASE case. This
+    // can be resolved by calling AddTrustedVaultDecryptionKeys() with the
+    // appropriate keys.
+    virtual void OnTrustedVaultKeyRequired() = 0;
+
+    // Called when the keys provided via AddTrustedVaultDecryptionKeys have been
+    // accepted and there are no longer pending keys.
+    virtual void OnTrustedVaultKeyAccepted() = 0;
+
     // |bootstrap_token| is an opaque base64 encoded representation of the key
     // generated by the current passphrase, and is provided to the observer for
     // persistence purposes and use in a future initialization of sync (e.g.
@@ -162,6 +172,14 @@
   // be empty.
   virtual void SetDecryptionPassphrase(const std::string& passphrase) = 0;
 
+  // Analogous to SetDecryptionPassphrase but specifically for
+  // TRUSTED_VAULT_PASSPHRASE: it provides new decryption keys that could
+  // allow decrypting pending Nigori keys. Notifies observers of the result of
+  // the operation via OnTrustedVaultKeyAccepted if the provided keys
+  // successfully decrypted pending keys.
+  virtual void AddTrustedVaultDecryptionKeys(
+      const std::vector<std::string>& keys) = 0;
+
   // Enables encryption of all datatypes.
   virtual void EnableEncryptEverything() = 0;
 
diff --git a/components/sync/engine/sync_engine.h b/components/sync/engine/sync_engine.h
index 0cd3776e..5037d65 100644
--- a/components/sync/engine/sync_engine.h
+++ b/components/sync/engine/sync_engine.h
@@ -8,6 +8,7 @@
 #include <map>
 #include <memory>
 #include <string>
+#include <vector>
 
 #include "base/callback.h"
 #include "base/compiler_specific.h"
@@ -134,6 +135,14 @@
   // are no pending keys.
   virtual void SetDecryptionPassphrase(const std::string& passphrase) = 0;
 
+  // Analogous to SetDecryptionPassphrase but specifically for
+  // TRUSTED_VAULT_PASSPHRASE: it provides new decryption keys that could
+  // allow decrypting pending Nigori keys. Notifies observers of the result of
+  // the operation via OnTrustedVaultKeyAccepted if the provided keys
+  // successfully decrypted pending keys.
+  virtual void AddTrustedVaultDecryptionKeys(
+      const std::vector<std::string>& keys) = 0;
+
   // Kick off shutdown procedure. Attempts to cut short any long-lived or
   // blocking sync thread tasks so that the shutdown on sync thread task that
   // we're about to post won't have to wait very long.
diff --git a/components/sync/engine/sync_string_conversions.cc b/components/sync/engine/sync_string_conversions.cc
index f168555..0315bde 100644
--- a/components/sync/engine/sync_string_conversions.cc
+++ b/components/sync/engine/sync_string_conversions.cc
@@ -25,7 +25,6 @@
 // Helper function that converts a PassphraseRequiredReason value to a string.
 const char* PassphraseRequiredReasonToString(PassphraseRequiredReason reason) {
   switch (reason) {
-    ENUM_CASE(REASON_PASSPHRASE_NOT_REQUIRED);
     ENUM_CASE(REASON_ENCRYPTION);
     ENUM_CASE(REASON_DECRYPTION);
   }
diff --git a/components/sync/engine_impl/debug_info_event_listener.cc b/components/sync/engine_impl/debug_info_event_listener.cc
index f9f3b11..7ff9037 100644
--- a/components/sync/engine_impl/debug_info_event_listener.cc
+++ b/components/sync/engine_impl/debug_info_event_listener.cc
@@ -74,6 +74,16 @@
   CreateAndAddEvent(sync_pb::SyncEnums::PASSPHRASE_ACCEPTED);
 }
 
+void DebugInfoEventListener::OnTrustedVaultKeyRequired() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  CreateAndAddEvent(sync_pb::SyncEnums::TRUSTED_VAULT_KEY_REQUIRED);
+}
+
+void DebugInfoEventListener::OnTrustedVaultKeyAccepted() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  CreateAndAddEvent(sync_pb::SyncEnums::TRUSTED_VAULT_KEY_ACCEPTED);
+}
+
 void DebugInfoEventListener::OnBootstrapTokenUpdated(
     const std::string& bootstrap_token,
     BootstrapTokenType type) {
diff --git a/components/sync/engine_impl/debug_info_event_listener.h b/components/sync/engine_impl/debug_info_event_listener.h
index 5510a27..e3d89ae 100644
--- a/components/sync/engine_impl/debug_info_event_listener.h
+++ b/components/sync/engine_impl/debug_info_event_listener.h
@@ -58,6 +58,8 @@
       const KeyDerivationParams& key_derivation_params,
       const sync_pb::EncryptedData& pending_keys) override;
   void OnPassphraseAccepted() override;
+  void OnTrustedVaultKeyRequired() override;
+  void OnTrustedVaultKeyAccepted() override;
   void OnBootstrapTokenUpdated(const std::string& bootstrap_token,
                                BootstrapTokenType type) override;
   void OnEncryptedTypesChanged(ModelTypeSet encrypted_types,
diff --git a/components/sync/engine_impl/js_sync_encryption_handler_observer.cc b/components/sync/engine_impl/js_sync_encryption_handler_observer.cc
index 490ac6b0..2ad5e6ef 100644
--- a/components/sync/engine_impl/js_sync_encryption_handler_observer.cc
+++ b/components/sync/engine_impl/js_sync_encryption_handler_observer.cc
@@ -48,6 +48,24 @@
   HandleJsEvent(FROM_HERE, "onPassphraseAccepted", JsEventDetails(&details));
 }
 
+void JsSyncEncryptionHandlerObserver::OnTrustedVaultKeyRequired() {
+  if (!event_handler_.IsInitialized()) {
+    return;
+  }
+  base::DictionaryValue details;
+  HandleJsEvent(FROM_HERE, "OnTrustedVaultKeyRequired",
+                JsEventDetails(&details));
+}
+
+void JsSyncEncryptionHandlerObserver::OnTrustedVaultKeyAccepted() {
+  if (!event_handler_.IsInitialized()) {
+    return;
+  }
+  base::DictionaryValue details;
+  HandleJsEvent(FROM_HERE, "OnTrustedVaultKeyAccepted",
+                JsEventDetails(&details));
+}
+
 void JsSyncEncryptionHandlerObserver::OnBootstrapTokenUpdated(
     const std::string& boostrap_token,
     BootstrapTokenType type) {
diff --git a/components/sync/engine_impl/js_sync_encryption_handler_observer.h b/components/sync/engine_impl/js_sync_encryption_handler_observer.h
index 07bab58..51188b05 100644
--- a/components/sync/engine_impl/js_sync_encryption_handler_observer.h
+++ b/components/sync/engine_impl/js_sync_encryption_handler_observer.h
@@ -36,6 +36,8 @@
       const KeyDerivationParams& key_derivation_params,
       const sync_pb::EncryptedData& pending_keys) override;
   void OnPassphraseAccepted() override;
+  void OnTrustedVaultKeyRequired() override;
+  void OnTrustedVaultKeyAccepted() override;
   void OnBootstrapTokenUpdated(const std::string& bootstrap_token,
                                BootstrapTokenType type) override;
   void OnEncryptedTypesChanged(ModelTypeSet encrypted_types,
diff --git a/components/sync/engine_impl/js_sync_encryption_handler_observer_unittest.cc b/components/sync/engine_impl/js_sync_encryption_handler_observer_unittest.cc
index 2654b0d..702d87e 100644
--- a/components/sync/engine_impl/js_sync_encryption_handler_observer_unittest.cc
+++ b/components/sync/engine_impl/js_sync_encryption_handler_observer_unittest.cc
@@ -63,9 +63,6 @@
   base::DictionaryValue reason_encryption_details;
   base::DictionaryValue reason_decryption_details;
 
-  reason_passphrase_not_required_details.SetString(
-      "reason",
-      PassphraseRequiredReasonToString(REASON_PASSPHRASE_NOT_REQUIRED));
   reason_encryption_details.SetString(
       "reason", PassphraseRequiredReasonToString(REASON_ENCRYPTION));
   reason_decryption_details.SetString(
@@ -73,19 +70,12 @@
 
   EXPECT_CALL(mock_js_event_handler_,
               HandleJsEvent("onPassphraseRequired",
-                            HasDetailsAsDictionary(
-                                reason_passphrase_not_required_details)));
-  EXPECT_CALL(mock_js_event_handler_,
-              HandleJsEvent("onPassphraseRequired",
                             HasDetailsAsDictionary(reason_encryption_details)));
   EXPECT_CALL(mock_js_event_handler_,
               HandleJsEvent("onPassphraseRequired",
                             HasDetailsAsDictionary(reason_decryption_details)));
 
   js_sync_encryption_handler_observer_.OnPassphraseRequired(
-      REASON_PASSPHRASE_NOT_REQUIRED, KeyDerivationParams::CreateForPbkdf2(),
-      sync_pb::EncryptedData());
-  js_sync_encryption_handler_observer_.OnPassphraseRequired(
       REASON_ENCRYPTION, KeyDerivationParams::CreateForPbkdf2(),
       sync_pb::EncryptedData());
   js_sync_encryption_handler_observer_.OnPassphraseRequired(
diff --git a/components/sync/engine_impl/model_type_registry.cc b/components/sync/engine_impl/model_type_registry.cc
index eb6ba28..e134cea 100644
--- a/components/sync/engine_impl/model_type_registry.cc
+++ b/components/sync/engine_impl/model_type_registry.cc
@@ -329,6 +329,16 @@
   }
 }
 
+void ModelTypeRegistry::OnTrustedVaultKeyRequired() {}
+
+void ModelTypeRegistry::OnTrustedVaultKeyAccepted() {
+  for (const auto& worker : model_type_workers_) {
+    if (encrypted_types_.Has(worker->GetModelType())) {
+      worker->EncryptionAcceptedMaybeApplyUpdates();
+    }
+  }
+}
+
 void ModelTypeRegistry::OnBootstrapTokenUpdated(
     const std::string& bootstrap_token,
     BootstrapTokenType type) {}
diff --git a/components/sync/engine_impl/model_type_registry.h b/components/sync/engine_impl/model_type_registry.h
index 2c3cdb1..79ce5f09 100644
--- a/components/sync/engine_impl/model_type_registry.h
+++ b/components/sync/engine_impl/model_type_registry.h
@@ -78,6 +78,8 @@
       const KeyDerivationParams& key_derivation_params,
       const sync_pb::EncryptedData& pending_keys) override;
   void OnPassphraseAccepted() override;
+  void OnTrustedVaultKeyRequired() override;
+  void OnTrustedVaultKeyAccepted() override;
   void OnBootstrapTokenUpdated(const std::string& bootstrap_token,
                                BootstrapTokenType type) override;
   void OnEncryptedTypesChanged(ModelTypeSet encrypted_types,
diff --git a/components/sync/engine_impl/sync_encryption_handler_impl.cc b/components/sync/engine_impl/sync_encryption_handler_impl.cc
index 5d93f3e..c43ed337 100644
--- a/components/sync/engine_impl/sync_encryption_handler_impl.cc
+++ b/components/sync/engine_impl/sync_encryption_handler_impl.cc
@@ -766,6 +766,12 @@
   FinishSetPassphrase(success, bootstrap_token, &trans, &node);
 }
 
+void SyncEncryptionHandlerImpl::AddTrustedVaultDecryptionKeys(
+    const std::vector<std::string>& keys) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  NOTIMPLEMENTED();
+}
+
 void SyncEncryptionHandlerImpl::EnableEncryptEverything() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   WriteTransaction trans(FROM_HERE, user_share_);
diff --git a/components/sync/engine_impl/sync_encryption_handler_impl.h b/components/sync/engine_impl/sync_encryption_handler_impl.h
index 4fc51829..db85ef6 100644
--- a/components/sync/engine_impl/sync_encryption_handler_impl.h
+++ b/components/sync/engine_impl/sync_encryption_handler_impl.h
@@ -67,6 +67,8 @@
   bool Init() override;
   void SetEncryptionPassphrase(const std::string& passphrase) override;
   void SetDecryptionPassphrase(const std::string& passphrase) override;
+  void AddTrustedVaultDecryptionKeys(
+      const std::vector<std::string>& keys) override;
   void EnableEncryptEverything() override;
   bool IsEncryptEverythingEnabled() const override;
   base::Time GetKeystoreMigrationTime() const override;
diff --git a/components/sync/engine_impl/sync_encryption_handler_impl_unittest.cc b/components/sync/engine_impl/sync_encryption_handler_impl_unittest.cc
index 80c17edc..0c93b3a 100644
--- a/components/sync/engine_impl/sync_encryption_handler_impl_unittest.cc
+++ b/components/sync/engine_impl/sync_encryption_handler_impl_unittest.cc
@@ -72,6 +72,8 @@
                     const KeyDerivationParams&,
                     const sync_pb::EncryptedData&));  // NOLINT
   MOCK_METHOD0(OnPassphraseAccepted, void());         // NOLINT
+  MOCK_METHOD0(OnTrustedVaultKeyRequired, void());    // NOLINT
+  MOCK_METHOD0(OnTrustedVaultKeyAccepted, void());    // NOLINT
   MOCK_METHOD2(OnBootstrapTokenUpdated,
                void(const std::string&, BootstrapTokenType type));  // NOLINT
   MOCK_METHOD2(OnEncryptedTypesChanged, void(ModelTypeSet, bool));  // NOLINT
diff --git a/components/sync/engine_impl/sync_manager_impl.cc b/components/sync/engine_impl/sync_manager_impl.cc
index 3aa864ce..5d88716c 100644
--- a/components/sync/engine_impl/sync_manager_impl.cc
+++ b/components/sync/engine_impl/sync_manager_impl.cc
@@ -430,6 +430,14 @@
   // Does nothing.
 }
 
+void SyncManagerImpl::OnTrustedVaultKeyRequired() {
+  // Does nothing.
+}
+
+void SyncManagerImpl::OnTrustedVaultKeyAccepted() {
+  // Does nothing.
+}
+
 void SyncManagerImpl::OnBootstrapTokenUpdated(
     const std::string& bootstrap_token,
     BootstrapTokenType type) {
diff --git a/components/sync/engine_impl/sync_manager_impl.h b/components/sync/engine_impl/sync_manager_impl.h
index 150b3173..4f2eb97 100644
--- a/components/sync/engine_impl/sync_manager_impl.h
+++ b/components/sync/engine_impl/sync_manager_impl.h
@@ -117,6 +117,8 @@
       const KeyDerivationParams& key_derivation_params,
       const sync_pb::EncryptedData& pending_keys) override;
   void OnPassphraseAccepted() override;
+  void OnTrustedVaultKeyRequired() override;
+  void OnTrustedVaultKeyAccepted() override;
   void OnBootstrapTokenUpdated(const std::string& bootstrap_token,
                                BootstrapTokenType type) override;
   void OnEncryptedTypesChanged(ModelTypeSet encrypted_types,
diff --git a/components/sync/engine_impl/sync_manager_impl_unittest.cc b/components/sync/engine_impl/sync_manager_impl_unittest.cc
index c1fd8ba..d1acbc2 100644
--- a/components/sync/engine_impl/sync_manager_impl_unittest.cc
+++ b/components/sync/engine_impl/sync_manager_impl_unittest.cc
@@ -911,6 +911,8 @@
                     const KeyDerivationParams&,
                     const sync_pb::EncryptedData&));  // NOLINT
   MOCK_METHOD0(OnPassphraseAccepted, void());         // NOLINT
+  MOCK_METHOD0(OnTrustedVaultKeyRequired, void());    // NOLINT
+  MOCK_METHOD0(OnTrustedVaultKeyAccepted, void());    // NOLINT
   MOCK_METHOD2(OnBootstrapTokenUpdated,
                void(const std::string&, BootstrapTokenType type));  // NOLINT
   MOCK_METHOD2(OnEncryptedTypesChanged, void(ModelTypeSet, bool));  // NOLINT
diff --git a/components/sync/nigori/nigori_sync_bridge_impl.cc b/components/sync/nigori/nigori_sync_bridge_impl.cc
index 6d4c2b66..0f7b8db6 100644
--- a/components/sync/nigori/nigori_sync_bridge_impl.cc
+++ b/components/sync/nigori/nigori_sync_bridge_impl.cc
@@ -70,18 +70,22 @@
   return cryptographer.Encrypt(proto.key_bag(), encrypted);
 }
 
-// TODO(crbug.com/922900): This should be revisited because the encryption key
-// should be determined by keystore keys, which does not always match the
-// default encryption key.
 bool EncryptKeystoreDecryptorToken(
     const CryptographerImpl& cryptographer,
-    sync_pb::EncryptedData* keystore_decryptor_token) {
+    sync_pb::EncryptedData* keystore_decryptor_token,
+    const std::vector<std::string>& keystore_keys) {
   DCHECK(keystore_decryptor_token);
 
   const sync_pb::NigoriKey default_key = cryptographer.ExportDefaultKey();
 
-  return cryptographer.EncryptString(default_key.SerializeAsString(),
-                                     keystore_decryptor_token);
+  // TODO(crbug.com/922900): consider maintaining cached version of this
+  // |keystore_cryptographer| to avoid its creation here and inside
+  // DecryptKestoreDecryptorToken().
+  std::unique_ptr<CryptographerImpl> keystore_cryptographer =
+      CreateCryptographerFromKeystoreKeys(keystore_keys);
+
+  return keystore_cryptographer->EncryptString(default_key.SerializeAsString(),
+                                               keystore_decryptor_token);
 }
 
 // Attempts to decrypt |keystore_decryptor_token| with |keystore_keys|. Returns
@@ -131,7 +135,8 @@
 
   NigoriSpecifics specifics;
   if (!EncryptKeystoreDecryptorToken(
-          *cryptographer, specifics.mutable_keystore_decryptor_token())) {
+          *cryptographer, specifics.mutable_keystore_decryptor_token(),
+          keystore_keys)) {
     DLOG(ERROR) << "Failed to encrypt default key as keystore_decryptor_token.";
     return base::nullopt;
   }
@@ -677,9 +682,7 @@
     return;
   }
 
-  sync_pb::NigoriKeyBag decrypted_pending_keys;
-  if (!state_.cryptographer->Decrypt(*state_.pending_keys,
-                                     &decrypted_pending_keys)) {
+  if (!TryDecryptPendingKeys()) {
     // TODO(crbug.com/922900): old implementation assumes that pending keys
     // encryption key may change in between of OnPassphraseRequired() and
     // SetDecryptionPassphrase() calls, verify whether it's really possible.
@@ -691,10 +694,7 @@
     return;
   }
 
-  state_.cryptographer->EmplaceKeysFrom(
-      NigoriKeyBag::CreateFromProto(decrypted_pending_keys));
   state_.cryptographer->SelectDefaultEncryptionKey(new_key_name);
-  state_.pending_keys.reset();
 
   storage_->StoreData(SerializeAsNigoriLocalData());
   for (auto& observer : observers_) {
@@ -711,6 +711,45 @@
   NOTIMPLEMENTED();
 }
 
+void NigoriSyncBridgeImpl::AddTrustedVaultDecryptionKeys(
+    const std::vector<std::string>& keys) {
+  // This API gets plumbed and ultimately exposed to layers outside the sync
+  // codebase and even outside the browser, so there are no preconditions and
+  // instead we ignore invalid or partially invalid input.
+  if (state_.passphrase_type != NigoriSpecifics::TRUSTED_VAULT_PASSPHRASE ||
+      !state_.pending_keys || keys.empty()) {
+    return;
+  }
+
+  for (const std::string& key : keys) {
+    if (!key.empty()) {
+      state_.cryptographer->EmplaceKey(key,
+                                       GetKeyDerivationParamsForPendingKeys());
+    }
+  }
+
+  const std::string pending_key_name = state_.pending_keys->key_name();
+
+  if (TryDecryptPendingKeys()) {
+    state_.cryptographer->SelectDefaultEncryptionKey(pending_key_name);
+  }
+
+  storage_->StoreData(SerializeAsNigoriLocalData());
+
+  for (auto& observer : observers_) {
+    observer.OnCryptographerStateChanged(state_.cryptographer.get(),
+                                         state_.pending_keys.has_value());
+  }
+
+  if (!state_.pending_keys) {
+    for (auto& observer : observers_) {
+      observer.OnTrustedVaultKeyAccepted();
+    }
+  }
+
+  MaybeNotifyBootstrapTokenUpdated();
+}
+
 void NigoriSyncBridgeImpl::EnableEncryptEverything() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   NOTIMPLEMENTED();
@@ -910,15 +949,37 @@
     observer.OnCryptographerStateChanged(state_.cryptographer.get(),
                                          state_.pending_keys.has_value());
   }
-  if (state_.pending_keys.has_value()) {
-    // Update with keystore Nigori shouldn't reach this point, since it should
-    // report model error if it has pending keys.
-    for (auto& observer : observers_) {
-      observer.OnPassphraseRequired(REASON_DECRYPTION,
-                                    GetKeyDerivationParamsForPendingKeys(),
-                                    *state_.pending_keys);
-    }
+
+  if (!state_.pending_keys.has_value()) {
+    return base::nullopt;
   }
+
+  switch (state_.passphrase_type) {
+    case NigoriSpecifics::UNKNOWN:
+    case NigoriSpecifics::IMPLICIT_PASSPHRASE:
+      NOTREACHED();
+      break;
+    case NigoriSpecifics::KEYSTORE_PASSPHRASE: {
+      // Update with keystore Nigori shouldn't reach this point, since it should
+      // report model error if it has pending keys.
+      NOTREACHED();
+      break;
+    }
+    case NigoriSpecifics::CUSTOM_PASSPHRASE:
+    case NigoriSpecifics::FROZEN_IMPLICIT_PASSPHRASE:
+      for (auto& observer : observers_) {
+        observer.OnPassphraseRequired(REASON_DECRYPTION,
+                                      GetKeyDerivationParamsForPendingKeys(),
+                                      *state_.pending_keys);
+      }
+      break;
+    case NigoriSpecifics::TRUSTED_VAULT_PASSPHRASE:
+      for (auto& observer : observers_) {
+        observer.OnTrustedVaultKeyRequired();
+      }
+      break;
+  }
+
   return base::nullopt;
 }
 
@@ -979,6 +1040,18 @@
   state_.cryptographer->EmplaceKeysFrom(NigoriKeyBag::CreateFromProto(key_bag));
 }
 
+bool NigoriSyncBridgeImpl::TryDecryptPendingKeys() {
+  sync_pb::NigoriKeyBag decrypted_pending_keys;
+  if (!state_.cryptographer->Decrypt(*state_.pending_keys,
+                                     &decrypted_pending_keys)) {
+    return false;
+  }
+  state_.cryptographer->EmplaceKeysFrom(
+      NigoriKeyBag::CreateFromProto(decrypted_pending_keys));
+  state_.pending_keys.reset();
+  return true;
+}
+
 std::unique_ptr<EntityData> NigoriSyncBridgeImpl::GetData() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   if (state_.passphrase_type == NigoriSpecifics::UNKNOWN) {
@@ -1013,7 +1086,8 @@
   }
   if (state_.passphrase_type == NigoriSpecifics::KEYSTORE_PASSPHRASE) {
     EncryptKeystoreDecryptorToken(*state_.cryptographer,
-                                  specifics.mutable_keystore_decryptor_token());
+                                  specifics.mutable_keystore_decryptor_token(),
+                                  state_.keystore_keys);
   }
   if (!state_.keystore_migration_time.is_null()) {
     specifics.set_keystore_migration_time(
diff --git a/components/sync/nigori/nigori_sync_bridge_impl.h b/components/sync/nigori/nigori_sync_bridge_impl.h
index fbf29e0b..7f05601 100644
--- a/components/sync/nigori/nigori_sync_bridge_impl.h
+++ b/components/sync/nigori/nigori_sync_bridge_impl.h
@@ -60,6 +60,8 @@
   bool Init() override;
   void SetEncryptionPassphrase(const std::string& passphrase) override;
   void SetDecryptionPassphrase(const std::string& passphrase) override;
+  void AddTrustedVaultDecryptionKeys(
+      const std::vector<std::string>& keys) override;
   void EnableEncryptEverything() override;
   bool IsEncryptEverythingEnabled() const override;
   base::Time GetKeystoreMigrationTime() const override;
@@ -102,6 +104,12 @@
   void UpdateCryptographerFromNonKeystoreNigori(
       const sync_pb::EncryptedData& keybag);
 
+  // Uses the cryptographer to try to decrypt pending keys. If success, the
+  // newly decrypted keys are put in the cryptographer's keybag, pending keys
+  // are cleared and the function returns true. Otherwise, it returns false and
+  // the state remains unchanged. It does not change the default key.
+  bool TryDecryptPendingKeys();
+
   base::Time GetExplicitPassphraseTime() const;
 
   // Returns key derivation params based on |passphrase_type_| and
diff --git a/components/sync/nigori/nigori_sync_bridge_impl_unittest.cc b/components/sync/nigori/nigori_sync_bridge_impl_unittest.cc
index 6fc8832..24782a0 100644
--- a/components/sync/nigori/nigori_sync_bridge_impl_unittest.cc
+++ b/components/sync/nigori/nigori_sync_bridge_impl_unittest.cc
@@ -108,6 +108,21 @@
          given.blob() == expected.blob();
 }
 
+MATCHER_P3(EncryptedDataEqAfterDecryption,
+           expected,
+           password,
+           derivation_params,
+           "") {
+  const sync_pb::EncryptedData& given = arg;
+  std::unique_ptr<CryptographerImpl> cryptographer =
+      CryptographerImpl::FromSingleKeyForTesting(password, derivation_params);
+  std::string decrypted_given;
+  EXPECT_TRUE(cryptographer->DecryptToString(given, &decrypted_given));
+  std::string decrypted_expected;
+  EXPECT_TRUE(cryptographer->DecryptToString(expected, &decrypted_expected));
+  return decrypted_given == decrypted_expected;
+}
+
 MATCHER_P2(IsDummyNigoriMetadataBatchWithTokenAndSequenceNumber,
            expected_token,
            expected_sequence_number,
@@ -293,6 +308,8 @@
                     const KeyDerivationParams&,
                     const sync_pb::EncryptedData&));
   MOCK_METHOD0(OnPassphraseAccepted, void());
+  MOCK_METHOD0(OnTrustedVaultKeyRequired, void());
+  MOCK_METHOD0(OnTrustedVaultKeyAccepted, void());
   MOCK_METHOD2(OnBootstrapTokenUpdated,
                void(const std::string&, BootstrapTokenType type));
   MOCK_METHOD2(OnEncryptedTypesChanged, void(ModelTypeSet, bool));
@@ -450,6 +467,42 @@
   EXPECT_THAT(cryptographer, HasDefaultKeyDerivedFrom(kGaiaKeyParams));
 }
 
+TEST_F(NigoriSyncBridgeImplTest, ShouldExposeBackwardCompatibleKeystoreNigori) {
+  const KeyParams kGaiaKeyParams = Pbkdf2KeyParams("gaia_key");
+  const std::string kRawKeystoreKey = "raw_keystore_key";
+  const KeyParams kKeystoreKeyParams = KeystoreKeyParams(kRawKeystoreKey);
+  EntityData entity_data;
+  *entity_data.specifics.mutable_nigori() = BuildKeystoreNigoriSpecifics(
+      /*keybag_keys_params=*/{kGaiaKeyParams, kKeystoreKeyParams},
+      /*keystore_decryptor_params=*/kGaiaKeyParams,
+      /*keystore_key_params=*/kKeystoreKeyParams);
+  sync_pb::EncryptedData original_encryption_keybag =
+      entity_data.specifics.nigori().encryption_keybag();
+  sync_pb::EncryptedData original_keystore_decryptor_token =
+      entity_data.specifics.nigori().keystore_decryptor_token();
+
+  ASSERT_TRUE(bridge()->SetKeystoreKeys({kRawKeystoreKey}));
+  ASSERT_THAT(bridge()->MergeSyncData(std::move(entity_data)),
+              Eq(base::nullopt));
+
+  std::unique_ptr<EntityData> local_entity_data = bridge()->GetData();
+  ASSERT_TRUE(local_entity_data);
+  ASSERT_TRUE(local_entity_data->specifics.has_nigori());
+  // Note: EncryptedDataEqAfterDecryption() exercises more strict requirements
+  // than bridge must support, because there is nothing wrong with reordering
+  // of the keys in encryption_keybag, which will lead to failing this
+  // expectation.
+  EXPECT_THAT(local_entity_data->specifics.nigori().encryption_keybag(),
+              EncryptedDataEqAfterDecryption(original_encryption_keybag,
+                                             kGaiaKeyParams.password,
+                                             kGaiaKeyParams.derivation_params));
+  EXPECT_THAT(
+      local_entity_data->specifics.nigori().keystore_decryptor_token(),
+      EncryptedDataEqAfterDecryption(original_keystore_decryptor_token,
+                                     kKeystoreKeyParams.password,
+                                     kKeystoreKeyParams.derivation_params));
+}
+
 // Tests that we can successfully use old keys from encryption_keybag in
 // backward compatible mode.
 TEST_F(NigoriSyncBridgeImplTest,
@@ -799,10 +852,10 @@
 // the sync protocol.
 TEST_F(NigoriSyncBridgeImplTest,
        ShouldRequireUserActionIfInitiallyUsingTrustedVault) {
-  const std::string kTrustedVaultPassphrase = "trusted_vault_passphrase";
+  const std::string kTrustedVaultKey = "trusted_vault_key";
   EntityData entity_data;
-  *entity_data.specifics.mutable_nigori() = BuildTrustedVaultNigoriSpecifics(
-      {Pbkdf2KeyParams(kTrustedVaultPassphrase)});
+  *entity_data.specifics.mutable_nigori() =
+      BuildTrustedVaultNigoriSpecifics({Pbkdf2KeyParams(kTrustedVaultKey)});
 
   ASSERT_TRUE(bridge()->SetKeystoreKeys({"keystore_key"}));
 
@@ -811,9 +864,7 @@
   EXPECT_CALL(*observer(),
               OnPassphraseTypeChanged(PassphraseType::kTrustedVaultPassphrase,
                                       NullTime()));
-  EXPECT_CALL(*observer(), OnPassphraseRequired(/*reason=*/REASON_DECRYPTION,
-                                                /*key_derivation_params=*/_,
-                                                /*pending_keys=*/_));
+  EXPECT_CALL(*observer(), OnTrustedVaultKeyRequired());
   EXPECT_THAT(bridge()->MergeSyncData(std::move(entity_data)),
               Eq(base::nullopt));
   EXPECT_THAT(bridge()->GetPassphraseTypeForTesting(),
@@ -822,11 +873,11 @@
               Eq(SyncEncryptionHandler::SensitiveTypes()));
   EXPECT_TRUE(bridge()->HasPendingKeysForTesting());
 
-  EXPECT_CALL(*observer(), OnPassphraseAccepted());
+  EXPECT_CALL(*observer(), OnTrustedVaultKeyAccepted());
   EXPECT_CALL(*observer(), OnCryptographerStateChanged(
                                NotNull(), /*has_pending_keys=*/false));
   EXPECT_CALL(*observer(), OnBootstrapTokenUpdated(_, _)).Times(0);
-  bridge()->SetDecryptionPassphrase(kTrustedVaultPassphrase);
+  bridge()->AddTrustedVaultDecryptionKeys({kTrustedVaultKey});
   EXPECT_FALSE(bridge()->HasPendingKeysForTesting());
 }
 
@@ -835,7 +886,7 @@
 // passphrase by means other than the sync protocol.
 TEST_F(NigoriSyncBridgeImplTest,
        ShouldProcessRemoteTransitionFromKeystoreToTrustedVault) {
-  const std::string kTrustedVaultPassphrase = "trusted_vault_passphrase";
+  const std::string kTrustedVaultKey = "trusted_vault_key";
 
   EntityData default_entity_data;
   *default_entity_data.specifics.mutable_nigori() =
@@ -849,8 +900,7 @@
 
   EntityData new_entity_data;
   *new_entity_data.specifics.mutable_nigori() =
-      BuildTrustedVaultNigoriSpecifics(
-          {Pbkdf2KeyParams(kTrustedVaultPassphrase)});
+      BuildTrustedVaultNigoriSpecifics({Pbkdf2KeyParams(kTrustedVaultKey)});
 
   EXPECT_CALL(*observer(), OnEncryptedTypesChanged(_, _)).Times(0);
   EXPECT_CALL(*observer(), OnBootstrapTokenUpdated(_, _)).Times(0);
@@ -859,9 +909,7 @@
   EXPECT_CALL(*observer(),
               OnPassphraseTypeChanged(PassphraseType::kTrustedVaultPassphrase,
                                       NullTime()));
-  EXPECT_CALL(*observer(), OnPassphraseRequired(/*reason=*/REASON_DECRYPTION,
-                                                /*key_derivation_params=*/_,
-                                                /*pending_keys=*/_));
+  EXPECT_CALL(*observer(), OnTrustedVaultKeyRequired());
   EXPECT_THAT(bridge()->ApplySyncChanges(std::move(new_entity_data)),
               Eq(base::nullopt));
   EXPECT_THAT(bridge()->GetPassphraseTypeForTesting(),
@@ -870,11 +918,11 @@
               Eq(SyncEncryptionHandler::SensitiveTypes()));
   EXPECT_TRUE(bridge()->HasPendingKeysForTesting());
 
-  EXPECT_CALL(*observer(), OnPassphraseAccepted());
+  EXPECT_CALL(*observer(), OnTrustedVaultKeyAccepted());
   EXPECT_CALL(*observer(), OnCryptographerStateChanged(
                                NotNull(), /*has_pending_keys=*/false));
   EXPECT_CALL(*observer(), OnBootstrapTokenUpdated(_, _)).Times(0);
-  bridge()->SetDecryptionPassphrase(kTrustedVaultPassphrase);
+  bridge()->AddTrustedVaultDecryptionKeys({kTrustedVaultKey});
   EXPECT_FALSE(bridge()->HasPendingKeysForTesting());
 }
 
@@ -882,18 +930,18 @@
 // vault passphrase.
 TEST_F(NigoriSyncBridgeImplTest,
        ShouldProcessRemoteKeyRotationForTrustedVault) {
-  const std::string kTrustedVaultPassphrase = "trusted_vault_passphrase";
-  const std::string kRotatedTrustedVaultPassphrase = "rotated_vault_passphrase";
+  const std::string kTrustedVaultKey = "trusted_vault_key";
+  const std::string kRotatedTrustedVaultKey = "rotated_vault_key";
 
   EntityData entity_data;
-  *entity_data.specifics.mutable_nigori() = BuildTrustedVaultNigoriSpecifics(
-      {Pbkdf2KeyParams(kTrustedVaultPassphrase)});
+  *entity_data.specifics.mutable_nigori() =
+      BuildTrustedVaultNigoriSpecifics({Pbkdf2KeyParams(kTrustedVaultKey)});
 
   ASSERT_TRUE(bridge()->SetKeystoreKeys({"keystore_key"}));
   ASSERT_THAT(bridge()->MergeSyncData(std::move(entity_data)),
               Eq(base::nullopt));
   ASSERT_TRUE(bridge()->HasPendingKeysForTesting());
-  bridge()->SetDecryptionPassphrase(kTrustedVaultPassphrase);
+  bridge()->AddTrustedVaultDecryptionKeys({kTrustedVaultKey});
   ASSERT_FALSE(bridge()->HasPendingKeysForTesting());
   ASSERT_THAT(bridge()->GetPassphraseTypeForTesting(),
               Eq(sync_pb::NigoriSpecifics::TRUSTED_VAULT_PASSPHRASE));
@@ -901,25 +949,23 @@
   EntityData new_entity_data;
   *new_entity_data.specifics.mutable_nigori() =
       BuildTrustedVaultNigoriSpecifics(
-          {Pbkdf2KeyParams(kTrustedVaultPassphrase),
-           Pbkdf2KeyParams(kRotatedTrustedVaultPassphrase)});
+          {Pbkdf2KeyParams(kTrustedVaultKey),
+           Pbkdf2KeyParams(kRotatedTrustedVaultKey)});
   EXPECT_CALL(*observer(), OnEncryptedTypesChanged(_, _)).Times(0);
   EXPECT_CALL(*observer(), OnBootstrapTokenUpdated(_, _)).Times(0);
   EXPECT_CALL(*observer(), OnPassphraseTypeChanged(_, _)).Times(0);
   EXPECT_CALL(*observer(), OnCryptographerStateChanged(
                                NotNull(), /*has_pending_keys=*/true));
-  EXPECT_CALL(*observer(), OnPassphraseRequired(/*reason=*/REASON_DECRYPTION,
-                                                /*key_derivation_params=*/_,
-                                                /*pending_keys=*/_));
+  EXPECT_CALL(*observer(), OnTrustedVaultKeyRequired());
 
   EXPECT_THAT(bridge()->ApplySyncChanges(std::move(new_entity_data)),
               Eq(base::nullopt));
   EXPECT_TRUE(bridge()->HasPendingKeysForTesting());
 
-  EXPECT_CALL(*observer(), OnPassphraseAccepted());
+  EXPECT_CALL(*observer(), OnTrustedVaultKeyAccepted());
   EXPECT_CALL(*observer(), OnCryptographerStateChanged(
                                NotNull(), /*has_pending_keys=*/false));
-  bridge()->SetDecryptionPassphrase(kRotatedTrustedVaultPassphrase);
+  bridge()->AddTrustedVaultDecryptionKeys({kRotatedTrustedVaultKey});
   EXPECT_FALSE(bridge()->HasPendingKeysForTesting());
 }
 
@@ -927,18 +973,18 @@
 // passphrase.
 TEST_F(NigoriSyncBridgeImplTest,
        ShouldTransitionLocallyFromTrustedVaultToCustomPassphrase) {
-  const std::string kTrustedVaultPassphrase = "trusted_vault_passphrase";
+  const std::string kTrustedVaultKey = "trusted_vault_key";
   const std::string kCustomPassphrase = "custom_passphrase";
 
   EntityData entity_data;
-  *entity_data.specifics.mutable_nigori() = BuildTrustedVaultNigoriSpecifics(
-      {Pbkdf2KeyParams(kTrustedVaultPassphrase)});
+  *entity_data.specifics.mutable_nigori() =
+      BuildTrustedVaultNigoriSpecifics({Pbkdf2KeyParams(kTrustedVaultKey)});
 
   ASSERT_TRUE(bridge()->SetKeystoreKeys({"keystore_key"}));
   ASSERT_THAT(bridge()->MergeSyncData(std::move(entity_data)),
               Eq(base::nullopt));
   ASSERT_TRUE(bridge()->HasPendingKeysForTesting());
-  bridge()->SetDecryptionPassphrase(kTrustedVaultPassphrase);
+  bridge()->AddTrustedVaultDecryptionKeys({kTrustedVaultKey});
   ASSERT_FALSE(bridge()->HasPendingKeysForTesting());
   ASSERT_THAT(bridge()->GetPassphraseTypeForTesting(),
               Eq(sync_pb::NigoriSpecifics::TRUSTED_VAULT_PASSPHRASE));
diff --git a/components/sync/protocol/proto_enum_conversions.cc b/components/sync/protocol/proto_enum_conversions.cc
index 2235a55..344ce275a 100644
--- a/components/sync/protocol/proto_enum_conversions.cc
+++ b/components/sync/protocol/proto_enum_conversions.cc
@@ -255,7 +255,7 @@
 const char* ProtoEnumToString(
     sync_pb::SyncEnums::SingletonDebugEventType type) {
   ASSERT_ENUM_BOUNDS(sync_pb::SyncEnums, SingletonDebugEventType,
-                     CONNECTION_STATUS_CHANGE, BOOTSTRAP_TOKEN_UPDATED);
+                     CONNECTION_STATUS_CHANGE, TRUSTED_VAULT_KEY_ACCEPTED);
   switch (type) {
     ENUM_CASE(sync_pb::SyncEnums, CONNECTION_STATUS_CHANGE);
     ENUM_CASE(sync_pb::SyncEnums, UPDATED_TOKEN);
@@ -270,6 +270,8 @@
     ENUM_CASE(sync_pb::SyncEnums, KEYSTORE_TOKEN_UPDATED);
     ENUM_CASE(sync_pb::SyncEnums, CONFIGURE_COMPLETE);
     ENUM_CASE(sync_pb::SyncEnums, BOOTSTRAP_TOKEN_UPDATED);
+    ENUM_CASE(sync_pb::SyncEnums, TRUSTED_VAULT_KEY_REQUIRED);
+    ENUM_CASE(sync_pb::SyncEnums, TRUSTED_VAULT_KEY_ACCEPTED);
   }
   NOTREACHED();
   return "";
diff --git a/components/sync/protocol/sync_enums.proto b/components/sync/protocol/sync_enums.proto
index 5017d263..d13cfba1 100644
--- a/components/sync/protocol/sync_enums.proto
+++ b/components/sync/protocol/sync_enums.proto
@@ -19,32 +19,39 @@
 message SyncEnums {
   // These events are sent by the DebugInfo class for singleton events.
   enum SingletonDebugEventType {
-    CONNECTION_STATUS_CHANGE = 1;  // Connection status change. Note this
-                                   // gets generated even during a successful
-                                   // connection.
-    UPDATED_TOKEN = 2;             // Client received an updated token.
-    PASSPHRASE_REQUIRED = 3;       // Cryptographer needs passphrase.
-    PASSPHRASE_ACCEPTED = 4;       // Passphrase was accepted by cryptographer.
-    INITIALIZATION_COMPLETE = 5;   // Sync Initialization is complete.
-
-    // |STOP_SYNCING_PERMANENTLY| event should never be seen by the server in
-    // the absence of bugs.
-    STOP_SYNCING_PERMANENTLY = 6;  // Server sent stop syncing permanently.
-
-    ENCRYPTION_COMPLETE = 7;      // Client has finished encrypting all data.
-    ACTIONABLE_ERROR = 8;         // Client received an actionable error.
-    ENCRYPTED_TYPES_CHANGED = 9;  // Set of encrypted types has changed.
-    // NOTE: until m25 bootstrap token updated also
-    // shared this field (erroneously).
-    PASSPHRASE_TYPE_CHANGED = 10;  // The encryption passphrase state changed.
-    KEYSTORE_TOKEN_UPDATED = 11;   // A new keystore encryption token was
-                                   // persisted.
-    CONFIGURE_COMPLETE = 12;       // The datatype manager has finished an
-                                   // at least partially successful
-                                   // configuration and is once again syncing
-                                   // with the server.
-    BOOTSTRAP_TOKEN_UPDATED = 13;  // A new cryptographer bootstrap token was
-                                   // generated.
+    // Connection status change. Note this gets generated even during a
+    // successful connection.
+    CONNECTION_STATUS_CHANGE = 1;
+    // Client received an updated token.
+    UPDATED_TOKEN = 2;
+    // Cryptographer needs passphrase.
+    PASSPHRASE_REQUIRED = 3;
+    // Passphrase was accepted by cryptographer.
+    PASSPHRASE_ACCEPTED = 4;
+    // Sync Initialization is complete.
+    INITIALIZATION_COMPLETE = 5;
+    // Server sent stop syncing permanently. This event should never be seen by
+    // the server in the absence of bugs.
+    STOP_SYNCING_PERMANENTLY = 6;
+    // Client has finished encrypting all data.
+    ENCRYPTION_COMPLETE = 7;
+    // Client received an actionable error.
+    ACTIONABLE_ERROR = 8;
+    // Set of encrypted types has changed.
+    ENCRYPTED_TYPES_CHANGED = 9;
+    // The encryption passphrase state changed.
+    PASSPHRASE_TYPE_CHANGED = 10;
+    // A new keystore encryption token was persisted.
+    KEYSTORE_TOKEN_UPDATED = 11;
+    // The datatype manager has finished an at least partially successful
+    // configuration and is once again syncing with the server.
+    CONFIGURE_COMPLETE = 12;
+    // A new cryptographer bootstrap token was generated.
+    BOOTSTRAP_TOKEN_UPDATED = 13;
+    // Cryptographer needs trusted vault decryption keys.
+    TRUSTED_VAULT_KEY_REQUIRED = 14;
+    // Cryptographer no longer needs trusted vault decryption keys.
+    TRUSTED_VAULT_KEY_ACCEPTED = 15;
   }
 
   // See ui/base/page_transition_types.h for detailed information on the
diff --git a/components/sync/syncable/nigori_handler_proxy.cc b/components/sync/syncable/nigori_handler_proxy.cc
index f2b7ebdf..e644fc8 100644
--- a/components/sync/syncable/nigori_handler_proxy.cc
+++ b/components/sync/syncable/nigori_handler_proxy.cc
@@ -36,6 +36,14 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 }
 
+void NigoriHandlerProxy::OnTrustedVaultKeyRequired() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+}
+
+void NigoriHandlerProxy::OnTrustedVaultKeyAccepted() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+}
+
 void NigoriHandlerProxy::OnBootstrapTokenUpdated(
     const std::string& bootrstrap_token,
     BootstrapTokenType type) {
diff --git a/components/sync/syncable/nigori_handler_proxy.h b/components/sync/syncable/nigori_handler_proxy.h
index 89ea905..8c847197 100644
--- a/components/sync/syncable/nigori_handler_proxy.h
+++ b/components/sync/syncable/nigori_handler_proxy.h
@@ -41,6 +41,8 @@
       const KeyDerivationParams& key_derivation_params,
       const sync_pb::EncryptedData& pending_keys) override;
   void OnPassphraseAccepted() override;
+  void OnTrustedVaultKeyRequired() override;
+  void OnTrustedVaultKeyAccepted() override;
   void OnBootstrapTokenUpdated(const std::string& bootrstrap_token,
                                BootstrapTokenType type) override;
   void OnEncryptedTypesChanged(ModelTypeSet encrypted_types,
diff --git a/components/sync/test/fake_sync_encryption_handler.cc b/components/sync/test/fake_sync_encryption_handler.cc
index bf48204..e250953 100644
--- a/components/sync/test/fake_sync_encryption_handler.cc
+++ b/components/sync/test/fake_sync_encryption_handler.cc
@@ -119,6 +119,11 @@
   // Do nothing.
 }
 
+void FakeSyncEncryptionHandler::AddTrustedVaultDecryptionKeys(
+    const std::vector<std::string>& encryption_keys) {
+  // Do nothing.
+}
+
 void FakeSyncEncryptionHandler::EnableEncryptEverything() {
   if (encrypt_everything_)
     return;
diff --git a/components/sync/test/fake_sync_encryption_handler.h b/components/sync/test/fake_sync_encryption_handler.h
index 122b32e..a2958af 100644
--- a/components/sync/test/fake_sync_encryption_handler.h
+++ b/components/sync/test/fake_sync_encryption_handler.h
@@ -37,6 +37,8 @@
   bool Init() override;
   void SetEncryptionPassphrase(const std::string& passphrase) override;
   void SetDecryptionPassphrase(const std::string& passphrase) override;
+  void AddTrustedVaultDecryptionKeys(
+      const std::vector<std::string>& keys) override;
   void EnableEncryptEverything() override;
   bool IsEncryptEverythingEnabled() const override;
   base::Time GetKeystoreMigrationTime() const override;
diff --git a/components/ukm/content/source_url_recorder_browsertest.cc b/components/ukm/content/source_url_recorder_browsertest.cc
index 72969135..1f842d0 100644
--- a/components/ukm/content/source_url_recorder_browsertest.cc
+++ b/components/ukm/content/source_url_recorder_browsertest.cc
@@ -6,7 +6,6 @@
 
 #include "base/files/scoped_temp_dir.h"
 #include "base/test/scoped_feature_list.h"
-#include "build/build_config.h"
 #include "components/ukm/content/source_url_recorder.h"
 #include "components/ukm/test_ukm_recorder.h"
 #include "content/public/browser/web_contents.h"
@@ -83,13 +82,7 @@
   base::ScopedTempDir downloads_directory_;
 };
 
-#if defined(OS_WIN)
-#define MAYBE_Basic DISABLED_Basic
-#else
-#define MAYBE_Basic Basic
-#endif
-IN_PROC_BROWSER_TEST_F(SourceUrlRecorderWebContentsObserverBrowserTest,
-                       MAYBE_Basic) {
+IN_PROC_BROWSER_TEST_F(SourceUrlRecorderWebContentsObserverBrowserTest, Basic) {
   using Entry = ukm::builders::DocumentCreated;
 
   GURL url = embedded_test_server()->GetURL("/title1.html");
@@ -114,13 +107,8 @@
   EXPECT_NE(source->id(), ukm_entries[0]->source_id);
 }
 
-#if defined(OS_WIN)
-#define MAYBE_IgnoreUrlInSubframe DISABLED_IgnoreUrlInSubframe
-#else
-#define MAYBE_IgnoreUrlInSubframe IgnoreUrlInSubframe
-#endif
 IN_PROC_BROWSER_TEST_F(SourceUrlRecorderWebContentsObserverBrowserTest,
-                       MAYBE_IgnoreUrlInSubframe) {
+                       IgnoreUrlInSubframe) {
   using Entry = ukm::builders::DocumentCreated;
 
   GURL main_url = embedded_test_server()->GetURL("/page_with_iframe.html");
diff --git a/components/viz/service/display/software_renderer.cc b/components/viz/service/display/software_renderer.cc
index dc382c9..76a7693 100644
--- a/components/viz/service/display/software_renderer.cc
+++ b/components/viz/service/display/software_renderer.cc
@@ -649,9 +649,12 @@
 }
 
 bool SoftwareRenderer::ShouldApplyBackdropFilters(
-    const cc::FilterOperations* backdrop_filters) const {
+    const cc::FilterOperations* backdrop_filters,
+    const RenderPassDrawQuad* quad) const {
   if (!backdrop_filters)
     return false;
+  if (quad->shared_quad_state->opacity == 0.f)
+    return false;
   DCHECK(!backdrop_filters->IsEmpty());
   return true;
 }
@@ -755,7 +758,7 @@
     SkTileMode content_tile_mode) const {
   const cc::FilterOperations* backdrop_filters =
       BackdropFiltersForPass(quad->render_pass_id);
-  if (!ShouldApplyBackdropFilters(backdrop_filters))
+  if (!ShouldApplyBackdropFilters(backdrop_filters, quad))
     return nullptr;
   base::Optional<gfx::RRectF> backdrop_filter_bounds_input =
       BackdropFilterBoundsForPass(quad->render_pass_id);
diff --git a/components/viz/service/display/software_renderer.h b/components/viz/service/display/software_renderer.h
index c9d67650..11b3998 100644
--- a/components/viz/service/display/software_renderer.h
+++ b/components/viz/service/display/software_renderer.h
@@ -79,8 +79,8 @@
   void DrawTextureQuad(const TextureDrawQuad* quad);
   void DrawTileQuad(const TileDrawQuad* quad);
   void DrawUnsupportedQuad(const DrawQuad* quad);
-  bool ShouldApplyBackdropFilters(
-      const cc::FilterOperations* backdrop_filters) const;
+  bool ShouldApplyBackdropFilters(const cc::FilterOperations* backdrop_filters,
+                                  const RenderPassDrawQuad* quad) const;
   sk_sp<SkImage> ApplyImageFilter(SkImageFilter* filter,
                                   const RenderPassDrawQuad* quad,
                                   const SkBitmap& to_filter,
diff --git a/content/browser/back_forward_cache_browsertest.cc b/content/browser/back_forward_cache_browsertest.cc
index 44ca3d6..68763d3 100644
--- a/content/browser/back_forward_cache_browsertest.cc
+++ b/content/browser/back_forward_cache_browsertest.cc
@@ -36,6 +36,7 @@
 #include "third_party/blink/public/common/scheduler/web_scheduler_tracked_feature.h"
 
 using testing::Each;
+using testing::ElementsAre;
 using testing::Not;
 
 namespace content {
@@ -82,8 +83,36 @@
     return web_contents()->GetFrameTree()->root()->render_manager();
   }
 
+  void ExpectOutcome(BackForwardCacheMetrics::HistoryNavigationOutcome outcome,
+                     base::Location location) {
+    base::HistogramBase::Sample sample = base::HistogramBase::Sample(outcome);
+    auto it = std::find_if(
+        expected_outcomes_.begin(), expected_outcomes_.end(),
+        [sample](const base::Bucket& bucket) { return bucket.min == sample; });
+    if (it == expected_outcomes_.end()) {
+      expected_outcomes_.push_back(base::Bucket(sample, 1));
+    } else {
+      it->count++;
+    }
+
+    EXPECT_EQ(expected_outcomes_,
+              histogram_tester_.GetAllSamples(
+                  "BackForwardCache.HistoryNavigationOutcome"))
+        << location.ToString();
+  }
+
+  void ExpectOutcomeIsEmpty(base::Location location) {
+    EXPECT_THAT(histogram_tester_.GetAllSamples(
+                    "BackForwardCache.HistoryNavigationOutcome"),
+                ElementsAre())
+        << location.ToString();
+  }
+
  private:
   base::test::ScopedFeatureList feature_list_;
+
+  base::HistogramTester histogram_tester_;
+  std::vector<base::Bucket> expected_outcomes_;
 };
 
 // Match RenderFrameHostImpl* that are in the BackForwardCache.
@@ -166,6 +195,9 @@
   EXPECT_EQ(rfh_a->GetVisibilityState(), PageVisibilityState::kVisible);
   EXPECT_TRUE(rfh_b->is_in_back_forward_cache());
   EXPECT_EQ(rfh_b->GetVisibilityState(), PageVisibilityState::kHidden);
+
+  ExpectOutcome(BackForwardCacheMetrics::HistoryNavigationOutcome::kRestored,
+                FROM_HERE);
 }
 
 // Navigate from A to B and go back.
@@ -200,6 +232,9 @@
   EXPECT_EQ(rfh_a, current_frame_host());
   EXPECT_FALSE(rfh_a->is_in_back_forward_cache());
   EXPECT_TRUE(rfh_b->is_in_back_forward_cache());
+
+  ExpectOutcome(BackForwardCacheMetrics::HistoryNavigationOutcome::kRestored,
+                FROM_HERE);
 }
 
 // Navigate from back and forward repeatedly.
@@ -229,6 +264,9 @@
   EXPECT_FALSE(rfh_a->is_in_back_forward_cache());
   EXPECT_TRUE(rfh_b->is_in_back_forward_cache());
 
+  ExpectOutcome(BackForwardCacheMetrics::HistoryNavigationOutcome::kRestored,
+                FROM_HERE);
+
   // 4) Go forward to B.
   web_contents()->GetController().GoForward();
   EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
@@ -237,6 +275,9 @@
   EXPECT_TRUE(rfh_a->is_in_back_forward_cache());
   EXPECT_FALSE(rfh_b->is_in_back_forward_cache());
 
+  ExpectOutcome(BackForwardCacheMetrics::HistoryNavigationOutcome::kRestored,
+                FROM_HERE);
+
   // 5) Go back to A.
   web_contents()->GetController().GoBack();
   EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
@@ -245,6 +286,9 @@
   EXPECT_FALSE(rfh_a->is_in_back_forward_cache());
   EXPECT_TRUE(rfh_b->is_in_back_forward_cache());
 
+  ExpectOutcome(BackForwardCacheMetrics::HistoryNavigationOutcome::kRestored,
+                FROM_HERE);
+
   // 6) Go forward to B.
   web_contents()->GetController().GoForward();
   EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
@@ -255,6 +299,9 @@
 
   EXPECT_FALSE(delete_observer_rfh_a.deleted());
   EXPECT_FALSE(delete_observer_rfh_b.deleted());
+
+  ExpectOutcome(BackForwardCacheMetrics::HistoryNavigationOutcome::kRestored,
+                FROM_HERE);
 }
 
 // The current page can't enter the BackForwardCache if another page can script
@@ -355,6 +402,9 @@
   EXPECT_FALSE(rfh_a->is_in_back_forward_cache());
   EXPECT_FALSE(rfh_b->is_in_back_forward_cache());
   EXPECT_TRUE(rfh_c->is_in_back_forward_cache());
+
+  ExpectOutcome(BackForwardCacheMetrics::HistoryNavigationOutcome::kRestored,
+                FROM_HERE);
 }
 
 // Ensure flushing the BackForwardCache works properly.
@@ -439,6 +489,11 @@
 
   // If/when the cache size is increased, this can be tested iteratively, see
   // deleted code in: https://crrev.com/c/1782902.
+
+  web_contents()->GetController().GoToOffset(-2);
+  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
+  ExpectOutcome(BackForwardCacheMetrics::HistoryNavigationOutcome::kEvicted,
+                FROM_HERE);
 }
 
 // Test documents are evicted from the BackForwardCache at some point.
@@ -511,6 +566,9 @@
   // Even after a new IPC round trip with the renderer, b2 must still be alive.
   EXPECT_EQ("I am alive", EvalJs(b2, "window.alive"));
   EXPECT_FALSE(b2_observer.deleted());
+
+  ExpectOutcome(BackForwardCacheMetrics::HistoryNavigationOutcome::kRestored,
+                FROM_HERE);
 }
 
 // Similar to BackForwardCacheBrowserTest.SubframeSurviveCache*
@@ -551,6 +609,9 @@
   // Even after a new IPC round trip with the renderer, b2 must still be alive.
   EXPECT_EQ("I am alive", EvalJs(b2, "window.alive"));
   EXPECT_FALSE(b2_observer.deleted());
+
+  ExpectOutcome(BackForwardCacheMetrics::HistoryNavigationOutcome::kRestored,
+                FROM_HERE);
 }
 
 // Similar to BackForwardCacheBrowserTest.tSubframeSurviveCache*
@@ -595,6 +656,9 @@
   EXPECT_EQ("I am alive", EvalJs(b2, "window.alive"));
   EXPECT_FALSE(b2_observer.deleted());
 
+  ExpectOutcome(BackForwardCacheMetrics::HistoryNavigationOutcome::kRestored,
+                FROM_HERE);
+
   // 4) Go forward to b3(a4).
   web_contents()->GetController().GoForward();
   EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
@@ -606,6 +670,9 @@
   // Even after a new IPC round trip with the renderer, a4 must still be alive.
   EXPECT_EQ("I am alive", EvalJs(a4, "window.alive"));
   EXPECT_FALSE(a4_observer.deleted());
+
+  ExpectOutcome(BackForwardCacheMetrics::HistoryNavigationOutcome::kRestored,
+                FROM_HERE);
 }
 
 // Similar to BackForwardCacheBrowserTest.SubframeSurviveCache*
@@ -730,6 +797,9 @@
   // Page B should be deleted (not cached).
   delete_observer_rfh_b.WaitUntilDeleted();
 
+  ExpectOutcome(BackForwardCacheMetrics::HistoryNavigationOutcome::kRestored,
+                FROM_HERE);
+
   // 4. Navigate from a cacheable page to a cacheable page (A->C).
   EXPECT_TRUE(NavigateToURL(shell(), url_c));
   EXPECT_EQ(web_contents()->GetLastCommittedURL(), url_c);
@@ -748,6 +818,9 @@
   // Page C should be in the cache.
   EXPECT_FALSE(delete_observer_rfh_c.deleted());
   EXPECT_TRUE(rfh_c->is_in_back_forward_cache());
+
+  ExpectOutcome(BackForwardCacheMetrics::HistoryNavigationOutcome::kRestored,
+                FROM_HERE);
 }
 
 IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
@@ -1296,6 +1369,12 @@
 
   // RenderFrameHost A is evicted from the BackForwardCache:
   delete_observer_rfh_a.WaitUntilDeleted();
+
+  // 4) Go back to A.
+  web_contents()->GetController().GoBack();
+  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
+  ExpectOutcome(BackForwardCacheMetrics::HistoryNavigationOutcome::kEvicted,
+                FROM_HERE);
 }
 
 // Similar to BackForwardCacheBrowserTest.EvictionOnJavaScriptExecution.
@@ -1334,6 +1413,12 @@
   // The A(B) page is evicted. So A and B are removed:
   delete_observer_rfh_a.WaitUntilDeleted();
   delete_observer_rfh_b.WaitUntilDeleted();
+
+  // 4) Go back to A.
+  web_contents()->GetController().GoBack();
+  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
+  ExpectOutcome(BackForwardCacheMetrics::HistoryNavigationOutcome::kEvicted,
+                FROM_HERE);
 }
 
 IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
@@ -1368,6 +1453,12 @@
 
   // RenderFrameHost A is evicted from the BackForwardCache:
   delete_observer_rfh_a.WaitUntilDeleted();
+
+  // 5) Go back to A.
+  web_contents()->GetController().GoBack();
+  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
+  ExpectOutcome(BackForwardCacheMetrics::HistoryNavigationOutcome::kEvicted,
+                FROM_HERE);
 }
 
 // Similar to BackForwardCacheBrowserTest.EvictionOnJavaScriptExecution, but
@@ -1451,6 +1542,9 @@
   EXPECT_FALSE(rfh_a->is_in_back_forward_cache());
   EXPECT_TRUE(rfh_b->is_in_back_forward_cache());
   EXPECT_NE("initial document", EvalJs(rfh_a, "window.foo"));
+
+  ExpectOutcome(BackForwardCacheMetrics::HistoryNavigationOutcome::kRestored,
+                FROM_HERE);
 }
 
 // Tests the events are fired when going back from the cache.
@@ -1708,6 +1802,9 @@
   RenderFrameHostImpl* rfh_a2 = current_frame_host();
   EXPECT_NE(rfh_a2, rfh_b);
   EXPECT_EQ(rfh_a2->GetLastCommittedURL(), url_a);
+
+  ExpectOutcome(BackForwardCacheMetrics::HistoryNavigationOutcome::kEvicted,
+                FROM_HERE);
 }
 
 // Test that if the renderer process crashes while a document is in the
@@ -1744,6 +1841,12 @@
   // rfh_b should still be the current frame.
   EXPECT_EQ(current_frame_host(), rfh_b);
   EXPECT_FALSE(delete_observer_rfh_b.deleted());
+
+  // 4) Go back to A.
+  web_contents()->GetController().GoBack();
+  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
+  ExpectOutcome(BackForwardCacheMetrics::HistoryNavigationOutcome::kEvicted,
+                FROM_HERE);
 }
 
 // The test is simulating a race condition. The scheduler tracked features are
@@ -1969,6 +2072,8 @@
   // The page is controlled by a service worker, so it shouldn't have been
   // cached.
   deleted.WaitUntilDeleted();
+
+  ExpectOutcomeIsEmpty(FROM_HERE);
 }
 
 class BackForwardCacheBrowserTestWithServiceWorkerEnabled
@@ -2335,4 +2440,35 @@
   delete_observer_rfh_a.WaitUntilDeleted();
 }
 
+// Confirm that same-document navigation and not history-navigation does not
+// record metrics.
+IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, MetricsNotRecorded) {
+  ASSERT_TRUE(embedded_test_server()->Start());
+  const GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
+  const GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
+  const GURL url_b2(embedded_test_server()->GetURL("b.com", "/title1.html#2"));
+
+  // 1) Navigate to A.
+  EXPECT_TRUE(NavigateToURL(shell(), url_a));
+  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
+
+  // 2) Navigate to B.
+  EXPECT_TRUE(NavigateToURL(shell(), url_b));
+  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
+
+  // 3) Navigate to B#2 (same document navigation).
+  EXPECT_TRUE(ExecJs(shell(), JsReplace("location = $1", url_b2.spec())));
+  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
+
+  // 4) Go back to B.
+  web_contents()->GetController().GoBack();
+  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
+  ExpectOutcomeIsEmpty(FROM_HERE);
+
+  // 5) Navigate to A.
+  EXPECT_TRUE(NavigateToURL(shell(), url_a));
+  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
+  ExpectOutcomeIsEmpty(FROM_HERE);
+}
+
 }  // namespace content
diff --git a/content/browser/dom_storage/session_storage_context_mojo_unittest.cc b/content/browser/dom_storage/session_storage_context_mojo_unittest.cc
index f5826f06..6f73b9d 100644
--- a/content/browser/dom_storage/session_storage_context_mojo_unittest.cc
+++ b/content/browser/dom_storage/session_storage_context_mojo_unittest.cc
@@ -613,7 +613,9 @@
   ShutdownContext();
 }
 
-TEST_F(SessionStorageContextMojoTest, CorruptionOnDisk) {
+// TODO(https://crbug.com/1010414): This test is flaky on various Linux bots and
+// fuchsia_x64 (does that count as OS_LINUX or not?); fix and re-enable.
+TEST_F(SessionStorageContextMojoTest, DISABLED_CorruptionOnDisk) {
   std::string namespace_id = base::GenerateGUID();
   url::Origin origin = url::Origin::Create(GURL("http://foobar.com"));
 
diff --git a/content/browser/frame_host/back_forward_cache_impl.cc b/content/browser/frame_host/back_forward_cache_impl.cc
index aba51a2a..094a8390 100644
--- a/content/browser/frame_host/back_forward_cache_impl.cc
+++ b/content/browser/frame_host/back_forward_cache_impl.cc
@@ -128,26 +128,6 @@
     : render_frame_host(std::move(rfh)), proxy_hosts(std::move(proxies)) {}
 BackForwardCacheImpl::Entry::~Entry() {}
 
-int BackForwardCacheImpl::Entry::GetNavigationEntryId() {
-  CHECK(render_frame_host);
-  return render_frame_host->nav_entry_id();
-}
-
-bool BackForwardCacheImpl::Entry::IsEvictedFromBackForwardCache() {
-  CHECK(render_frame_host);
-  return render_frame_host->is_evicted_from_back_forward_cache();
-}
-
-void BackForwardCacheImpl::Entry::EvictFromBackForwardCache() {
-  CHECK(render_frame_host);
-  return render_frame_host->EvictFromBackForwardCache();
-}
-
-void BackForwardCacheImpl::Entry::LeaveBackForwardCache() {
-  CHECK(render_frame_host);
-  return render_frame_host->LeaveBackForwardCache();
-}
-
 std::string BackForwardCacheImpl::CanStoreDocumentResult::ToString() {
   using Reason = BackForwardCacheMetrics::CanNotStoreDocumentReason;
 
@@ -324,10 +304,10 @@
   // full.
   size_t available_count = 0;
   for (auto& stored_entry : entries_) {
-    if (stored_entry->IsEvictedFromBackForwardCache())
+    if (stored_entry->render_frame_host->is_evicted_from_back_forward_cache())
       continue;
     if (++available_count > size_limit)
-      stored_entry->EvictFromBackForwardCache();
+      stored_entry->render_frame_host->EvictFromBackForwardCache();
   }
 }
 
@@ -353,7 +333,7 @@
   auto matching_entry = std::find_if(
       entries_.begin(), entries_.end(),
       [navigation_entry_id](std::unique_ptr<Entry>& entry) {
-        return entry->GetNavigationEntryId() == navigation_entry_id;
+        return entry->render_frame_host->nav_entry_id() == navigation_entry_id;
       });
 
   // Not found.
@@ -361,12 +341,13 @@
     return nullptr;
 
   // Don't restore an evicted frame.
-  if ((*matching_entry)->IsEvictedFromBackForwardCache())
+  if ((*matching_entry)
+          ->render_frame_host->is_evicted_from_back_forward_cache())
     return nullptr;
 
   std::unique_ptr<Entry> entry = std::move(*matching_entry);
   entries_.erase(matching_entry);
-  entry->LeaveBackForwardCache();
+  entry->render_frame_host->LeaveBackForwardCache();
   return entry;
 }
 
@@ -403,14 +384,15 @@
   auto matching_entry = std::find_if(
       entries_.begin(), entries_.end(),
       [navigation_entry_id](std::unique_ptr<Entry>& entry) {
-        return entry->GetNavigationEntryId() == navigation_entry_id;
+        return entry->render_frame_host->nav_entry_id() == navigation_entry_id;
       });
 
   if (matching_entry == entries_.end())
     return nullptr;
 
   // Don't return the frame if it is evicted.
-  if ((*matching_entry)->IsEvictedFromBackForwardCache())
+  if ((*matching_entry)
+          ->render_frame_host->is_evicted_from_back_forward_cache())
     return nullptr;
 
   return (*matching_entry).get();
@@ -420,9 +402,9 @@
   TRACE_EVENT0("navigation", "BackForwardCache::DestroyEvictedFrames");
   if (entries_.empty())
     return;
-  entries_.erase(std::remove_if(entries_.begin(), entries_.end(),
-                                [](std::unique_ptr<Entry>& entry) {
-                                  return entry->IsEvictedFromBackForwardCache();
-                                }));
+  entries_.erase(std::remove_if(
+      entries_.begin(), entries_.end(), [](std::unique_ptr<Entry>& entry) {
+        return entry->render_frame_host->is_evicted_from_back_forward_cache();
+      }));
 }
 }  // namespace content
diff --git a/content/browser/frame_host/back_forward_cache_impl.h b/content/browser/frame_host/back_forward_cache_impl.h
index 3bae916..8c9bcea 100644
--- a/content/browser/frame_host/back_forward_cache_impl.h
+++ b/content/browser/frame_host/back_forward_cache_impl.h
@@ -43,14 +43,6 @@
           RenderFrameProxyHostMap proxy_hosts);
     ~Entry();
 
-    // These functions forward to the underlying render_frame_host. Do not call
-    // just before storing or right after restoring, as the Entry will be in an
-    // invalid state.
-    int GetNavigationEntryId();
-    bool IsEvictedFromBackForwardCache();
-    void EvictFromBackForwardCache();
-    void LeaveBackForwardCache();
-
     // The main document being stored.
     std::unique_ptr<RenderFrameHostImpl> render_frame_host;
 
diff --git a/content/browser/frame_host/back_forward_cache_metrics.cc b/content/browser/frame_host/back_forward_cache_metrics.cc
index c004112..9b20ac30 100644
--- a/content/browser/frame_host/back_forward_cache_metrics.cc
+++ b/content/browser/frame_host/back_forward_cache_metrics.cc
@@ -4,6 +4,7 @@
 
 #include "content/browser/frame_host/back_forward_cache_metrics.h"
 
+#include "base/metrics/histogram_macros.h"
 #include "content/browser/frame_host/navigation_entry_impl.h"
 #include "content/browser/frame_host/navigation_request.h"
 #include "content/public/browser/browser_thread.h"
@@ -93,6 +94,24 @@
 
 void BackForwardCacheMetrics::DidCommitNavigation(
     NavigationRequest* navigation) {
+  bool is_history_navigation =
+      navigation->GetPageTransition() & ui::PAGE_TRANSITION_FORWARD_BACK;
+  if (navigation->IsInMainFrame() && !navigation->IsSameDocument() &&
+      is_history_navigation) {
+    // TODO(hajimehoshi): Use kNotCachedDueToExperimentCondition if the
+    // experiment condition does not match.
+    HistoryNavigationOutcome outcome = HistoryNavigationOutcome::kNotCached;
+    if (navigation->is_served_from_back_forward_cache())
+      outcome = HistoryNavigationOutcome::kRestored;
+    if (evicted_ || last_committed_main_frame_navigation_id_ == -1) {
+      DCHECK(!navigation->is_served_from_back_forward_cache());
+      outcome = HistoryNavigationOutcome::kEvicted;
+    }
+    UMA_HISTOGRAM_ENUMERATION("BackForwardCache.HistoryNavigationOutcome",
+                              outcome, HistoryNavigationOutcome::kMaxValue);
+    evicted_ = false;
+  }
+
   if (last_committed_main_frame_navigation_id_ != -1 &&
       navigation->IsInMainFrame()) {
     // We've visited an entry associated with this main frame document before,
@@ -166,4 +185,8 @@
   }
 }
 
+void BackForwardCacheMetrics::MarkEvictedFromBackForwardCache() {
+  evicted_ = true;
+}
+
 }  // namespace content
diff --git a/content/browser/frame_host/back_forward_cache_metrics.h b/content/browser/frame_host/back_forward_cache_metrics.h
index f424666..dbc663a 100644
--- a/content/browser/frame_host/back_forward_cache_metrics.h
+++ b/content/browser/frame_host/back_forward_cache_metrics.h
@@ -43,6 +43,16 @@
     kDisableForRenderFrameHostCalled
   };
 
+  // Please keep in sync with BackForwardCacheHistoryNavigationOutcome in
+  // tools/metrics/histograms/enums.xml.
+  enum class HistoryNavigationOutcome {
+    kRestored = 0,
+    kNotCached = 1,
+    kEvicted = 2,
+    kNotCachedDueToExperimentCondition = 3,
+    kMaxValue = kNotCachedDueToExperimentCondition,
+  };
+
   // Creates a potential new metrics object for the navigation.
   // Note that this object will not be used if the entry we are navigating to
   // already has the BackForwardCacheMetrics object (which happens for history
@@ -81,6 +91,10 @@
   // placed in the back-forward cache.
   void RecordFeatureUsage(RenderFrameHostImpl* main_frame);
 
+  // Marks when the page is evicted.
+  // TODO(hajimehoshi): Add the parameter representing the reason.
+  void MarkEvictedFromBackForwardCache();
+
   // Injects a clock for mocking time.
   // Should be called only from the UI thread.
   CONTENT_EXPORT static void OverrideTimeForTesting(base::TickClock* clock);
@@ -120,6 +134,8 @@
   base::Optional<base::TimeTicks> started_navigation_timestamp_;
   base::Optional<base::TimeTicks> navigated_away_from_main_document_timestamp_;
 
+  bool evicted_ = false;
+
   DISALLOW_COPY_AND_ASSIGN(BackForwardCacheMetrics);
 };
 
diff --git a/content/browser/frame_host/navigation_request.cc b/content/browser/frame_host/navigation_request.cc
index 4e3e596..35b7d36 100644
--- a/content/browser/frame_host/navigation_request.cc
+++ b/content/browser/frame_host/navigation_request.cc
@@ -2308,7 +2308,7 @@
     std::unique_ptr<BackForwardCacheImpl::Entry> restored_bfcache_entry =
         controller->GetBackForwardCache().RestoreEntry(nav_entry_id_);
 
-    // The only time restored_rfh can be nullptr here, is if the
+    // The only time restored_bfcache_entry can be nullptr here, is if the
     // document was evicted from the BackForwardCache since this navigation
     // started.
     //
diff --git a/content/browser/frame_host/render_frame_host_impl.cc b/content/browser/frame_host/render_frame_host_impl.cc
index 1c232cde..d6d88e7b 100644
--- a/content/browser/frame_host/render_frame_host_impl.cc
+++ b/content/browser/frame_host/render_frame_host_impl.cc
@@ -3519,6 +3519,11 @@
     DCHECK_EQ(top_document->is_in_back_forward_cache(), in_back_forward_cache);
   }
 
+  if (BackForwardCacheMetrics* metrics =
+          top_document->GetBackForwardCacheMetrics()) {
+    metrics->MarkEvictedFromBackForwardCache();
+  }
+
   if (!in_back_forward_cache) {
     // A document is evicted from the BackForwardCache, but it has already been
     // restored. The current document should be reloaded, because it is not
@@ -7668,4 +7673,14 @@
   frame_bindings_control_->EnableMojoJsBindings();
 }
 
+BackForwardCacheMetrics* RenderFrameHostImpl::GetBackForwardCacheMetrics() {
+  NavigationEntryImpl* navigation_entry =
+      static_cast<NavigationControllerImpl*>(
+          frame_tree_node_->navigator()->GetController())
+          ->GetEntryWithUniqueID(nav_entry_id());
+  if (!navigation_entry)
+    return nullptr;
+  return navigation_entry->back_forward_cache_metrics();
+}
+
 }  // namespace content
diff --git a/content/browser/frame_host/render_frame_host_impl.h b/content/browser/frame_host/render_frame_host_impl.h
index 456a163..a59c120 100644
--- a/content/browser/frame_host/render_frame_host_impl.h
+++ b/content/browser/frame_host/render_frame_host_impl.h
@@ -151,6 +151,7 @@
 namespace content {
 class AppCacheNavigationHandle;
 class AuthenticatorImpl;
+class BackForwardCacheMetrics;
 class BundledExchangesHandle;
 class FrameTree;
 class FrameTreeNode;
@@ -1896,6 +1897,10 @@
   // and ineligible for caching.
   void MaybeEvictFromBackForwardCache();
 
+  // Returns the BackForwardCacheMetrics associated with the last
+  // NavigationEntry this RenderFrameHostImpl committed.
+  BackForwardCacheMetrics* GetBackForwardCacheMetrics();
+
   // The RenderViewHost that this RenderFrameHost is associated with.
   //
   // It is kept alive as long as any RenderFrameHosts or RenderFrameProxyHosts
diff --git a/content/renderer/media/webrtc/media_stream_track_metrics.cc b/content/renderer/media/webrtc/media_stream_track_metrics.cc
index e2b4c01..31a5b5c5 100644
--- a/content/renderer/media/webrtc/media_stream_track_metrics.cc
+++ b/content/renderer/media/webrtc/media_stream_track_metrics.cc
@@ -8,10 +8,10 @@
 #include <string>
 
 #include "base/hash/md5.h"
-#include "base/threading/thread_task_runner_handle.h"
-#include "content/child/child_thread_impl.h"
-#include "content/public/common/service_names.mojom.h"
-#include "content/renderer/render_thread_impl.h"
+#include "base/strings/stringprintf.h"
+#include "base/threading/thread_checker.h"
+#include "third_party/blink/public/common/thread_safe_browser_interface_broker_proxy.h"
+#include "third_party/blink/public/platform/platform.h"
 
 namespace content {
 
@@ -213,19 +213,14 @@
                                                   Kind kind,
                                                   LifetimeEvent event,
                                                   Direction direction) {
-  RenderThreadImpl* render_thread = RenderThreadImpl::current();
-  // |render_thread| can be NULL in certain cases when running as part
-  // |of a unit test.
-  if (render_thread) {
-    if (event == LifetimeEvent::kConnected) {
-      GetMediaStreamTrackMetricsHost()->AddTrack(
-          MakeUniqueId(track_id, direction), kind == Kind::kAudio,
-          direction == Direction::kReceive);
-    } else {
-      DCHECK_EQ(LifetimeEvent::kDisconnected, event);
-      GetMediaStreamTrackMetricsHost()->RemoveTrack(
-          MakeUniqueId(track_id, direction));
-    }
+  if (event == LifetimeEvent::kConnected) {
+    GetMediaStreamTrackMetricsHost()->AddTrack(
+        MakeUniqueId(track_id, direction), kind == Kind::kAudio,
+        direction == Direction::kReceive);
+  } else {
+    DCHECK_EQ(LifetimeEvent::kDisconnected, event);
+    GetMediaStreamTrackMetricsHost()->RemoveTrack(
+        MakeUniqueId(track_id, direction));
   }
 }
 
@@ -264,7 +259,7 @@
 mojo::Remote<blink::mojom::MediaStreamTrackMetricsHost>&
 MediaStreamTrackMetrics::GetMediaStreamTrackMetricsHost() {
   if (!track_metrics_host_) {
-    ChildThreadImpl::current()->BindHostReceiver(
+    blink::Platform::Current()->GetBrowserInterfaceBrokerProxy()->GetInterface(
         track_metrics_host_.BindNewPipeAndPassReceiver());
   }
   return track_metrics_host_;
diff --git a/content/renderer/service_worker/embedded_worker_instance_client_impl.cc b/content/renderer/service_worker/embedded_worker_instance_client_impl.cc
index 3f26eba..0a6359d1 100644
--- a/content/renderer/service_worker/embedded_worker_instance_client_impl.cc
+++ b/content/renderer/service_worker/embedded_worker_instance_client_impl.cc
@@ -175,9 +175,6 @@
           ? blink::WebEmbeddedWorkerStartData::kWaitForDebugger
           : blink::WebEmbeddedWorkerStartData::kDontWaitForDebugger;
   start_data->devtools_worker_token = params.devtools_worker_token;
-  start_data->privacy_preferences = blink::PrivacyPreferences(
-      params.renderer_preferences->enable_do_not_track,
-      params.renderer_preferences->enable_referrers);
   return start_data;
 }
 
diff --git a/content/renderer/service_worker/embedded_worker_instance_client_impl.h b/content/renderer/service_worker/embedded_worker_instance_client_impl.h
index 37ecbe1..65326601 100644
--- a/content/renderer/service_worker/embedded_worker_instance_client_impl.h
+++ b/content/renderer/service_worker/embedded_worker_instance_client_impl.h
@@ -11,7 +11,6 @@
 #include "content/common/content_export.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/receiver.h"
-#include "third_party/blink/public/common/privacy_preferences.h"
 #include "third_party/blink/public/mojom/service_worker/embedded_worker.mojom.h"
 #include "third_party/blink/public/mojom/service_worker/service_worker_installed_scripts_manager.mojom.h"
 #include "third_party/blink/public/mojom/worker/worker_content_settings_proxy.mojom.h"
diff --git a/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt
index 20ca850..c8d9e64 100644
--- a/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt
@@ -319,6 +319,18 @@
 crbug.com/990368 [ mac passthrough ] conformance/canvas/draw-static-webgl-to-multiple-canvas-test.html [ Failure ]
 crbug.com/990368 [ mac passthrough ] conformance/canvas/draw-webgl-to-canvas-test.html [ Failure ]
 crbug.com/990368 [ mac passthrough ] conformance/canvas/to-data-url-test.html [ Failure ]
+crbug.com/990368 [ mac passthrough ] conformance/extensions/oes-texture-float-linear.html [ Failure ]
+crbug.com/990368 [ mac passthrough ] conformance/extensions/oes-texture-float-with-canvas.html [ Failure ]
+crbug.com/990368 [ mac passthrough ] conformance/glsl/samplers/glsl-function-texture2dproj.html [ Failure ]
+crbug.com/990368 [ mac passthrough ] conformance/textures/canvas/tex-2d-rgb-rgb-unsigned_byte.html [ Failure ]
+crbug.com/990368 [ mac passthrough ] conformance/textures/canvas/tex-2d-rgb-rgb-unsigned_short_5_6_5.html [ Failure ]
+crbug.com/990368 [ mac passthrough ] conformance/textures/canvas/tex-2d-rgba-rgba-unsigned_byte.html [ Failure ]
+crbug.com/990368 [ mac passthrough ] conformance/textures/canvas/tex-2d-rgba-rgba-unsigned_short_4_4_4_4.html [ Failure ]
+crbug.com/990368 [ mac passthrough ] conformance/textures/misc/canvas-teximage-after-multiple-drawimages.html [ Failure ]
+crbug.com/990368 [ mac passthrough ] conformance/textures/misc/texparameter-test.html [ Failure ]
+crbug.com/990368 [ mac passthrough ] conformance/textures/misc/texture-active-bind-2.html [ Failure ]
+crbug.com/990368 [ mac passthrough ] conformance/textures/misc/texture-active-bind.html [ Failure ]
+crbug.com/990368 [ mac passthrough ] conformance/textures/misc/texture-npot.html [ Failure ]
 crbug.com/angleproject/3812 [ mac passthrough ] conformance/context/context-attribute-preserve-drawing-buffer.html [ Failure ]
 crbug.com/990368 [ mac passthrough ] conformance/context/premultiplyalpha-test.html [ Failure ]
 crbug.com/989194 [ mac passthrough ] conformance/extensions/oes-texture-float-with-video.html [ Failure ]
diff --git a/device/vr/openxr/openxr_controller.cc b/device/vr/openxr/openxr_controller.cc
index 67d9f1a..f8e64e1 100644
--- a/device/vr/openxr/openxr_controller.cc
+++ b/device/vr/openxr/openxr_controller.cc
@@ -232,7 +232,7 @@
     base::Optional<GamepadButton> button =
         GetButton(static_cast<OpenXrButtonType>(i));
     if (button) {
-      buttons.push_back(std::move(GetXRGamepadButtonPtr(button.value())));
+      buttons.push_back(GetXRGamepadButtonPtr(button.value()));
     } else {
       return {};
     }
diff --git a/extensions/browser/content_verifier.cc b/extensions/browser/content_verifier.cc
index 3cef80c7..1f1ab63 100644
--- a/extensions/browser/content_verifier.cc
+++ b/extensions/browser/content_verifier.cc
@@ -588,7 +588,7 @@
   ExtensionId extension_id = content_hash->extension_id();
   if (g_content_verifier_test_observer) {
     g_content_verifier_test_observer->OnFetchComplete(
-        extension_id, content_hash->has_verified_contents());
+        extension_id, content_hash->succeeded());
   }
 
   VLOG(1) << "OnFetchComplete " << extension_id
diff --git a/extensions/browser/content_verifier/content_hash.cc b/extensions/browser/content_verifier/content_hash.cc
index bdb2a99..1531b9a 100644
--- a/extensions/browser/content_verifier/content_hash.cc
+++ b/extensions/browser/content_verifier/content_hash.cc
@@ -121,7 +121,7 @@
 ContentHash::TreeHashVerificationResult ContentHash::VerifyTreeHashRoot(
     const base::FilePath& relative_path,
     const std::string* root) const {
-  DCHECK(status_ >= Status::kHasVerifiedContents && verified_contents_);
+  DCHECK(verified_contents_);
   if (!verified_contents_->HasTreeHashRoot(relative_path))
     return TreeHashVerificationResult::NO_ENTRY;
 
@@ -132,7 +132,7 @@
 }
 
 const ComputedHashes::Reader& ContentHash::computed_hashes() const {
-  DCHECK(status_ == Status::kSucceeded && computed_hashes_);
+  DCHECK(succeeded_ && computed_hashes_);
   return *computed_hashes_;
 }
 
@@ -145,12 +145,7 @@
       extension_root_(root),
       verified_contents_(std::move(verified_contents)),
       computed_hashes_(std::move(computed_hashes)) {
-  if (!verified_contents_)
-    status_ = Status::kInvalid;
-  else if (!computed_hashes_)
-    status_ = Status::kHasVerifiedContents;
-  else
-    status_ = Status::kSucceeded;
+  succeeded_ = verified_contents_ != nullptr && computed_hashes_ != nullptr;
 }
 
 ContentHash::~ContentHash() = default;
@@ -309,7 +304,7 @@
                       timer.Elapsed());
 
   if (result)
-    status_ = Status::kSucceeded;
+    succeeded_ = true;
 
   return result;
 }
@@ -344,7 +339,7 @@
       // will_create = true;
     } else {
       // Read successful.
-      status_ = Status::kSucceeded;
+      succeeded_ = true;
       computed_hashes_ = std::move(computed_hashes);
       return;
     }
@@ -364,7 +359,7 @@
     return;
 
   // Read successful.
-  status_ = Status::kSucceeded;
+  succeeded_ = true;
   computed_hashes_ = std::move(computed_hashes);
 }
 
diff --git a/extensions/browser/content_verifier/content_hash.h b/extensions/browser/content_verifier/content_hash.h
index 8863fc9..93818a2 100644
--- a/extensions/browser/content_verifier/content_hash.h
+++ b/extensions/browser/content_verifier/content_hash.h
@@ -120,11 +120,9 @@
 
   const ComputedHashes::Reader& computed_hashes() const;
 
-  bool has_verified_contents() const {
-    return status_ >= Status::kHasVerifiedContents;
-  }
-
-  bool succeeded() const { return status_ >= Status::kSucceeded; }
+  // Returns whether or not computed_hashes.json (and, if needed,
+  // verified_contents.json too) was read correctly and is ready to use.
+  bool succeeded() const { return succeeded_; }
 
   // If ContentHash creation writes computed_hashes.json, then this returns the
   // FilePaths whose content hash didn't match expected hashes.
@@ -138,24 +136,13 @@
   // for |this| to succeed.
   // TODO(lazyboy): Remove this once https://crbug.com/819832 is fixed.
   bool might_require_computed_hashes_force_creation() const {
-    return !succeeded() && has_verified_contents() &&
+    return !succeeded() && verified_contents_ != nullptr &&
            !did_attempt_creating_computed_hashes_;
   }
 
  private:
   friend class base::RefCountedThreadSafe<ContentHash>;
 
-  enum class Status {
-    // Retrieving hashes failed.
-    kInvalid,
-    // Retrieved valid verified_contents.json, but there was no
-    // computed_hashes.json.
-    kHasVerifiedContents,
-    // Both verified_contents.json and computed_hashes.json were read
-    // correctly.
-    kSucceeded,
-  };
-
   ContentHash(const ExtensionId& id,
               const base::FilePath& root,
               std::unique_ptr<VerifiedContents> verified_contents,
@@ -198,7 +185,7 @@
   const ExtensionId extension_id_;
   const base::FilePath extension_root_;
 
-  Status status_ = Status::kInvalid;
+  bool succeeded_ = false;
 
   bool did_attempt_creating_computed_hashes_ = false;
 
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder_passthrough.cc b/gpu/command_buffer/service/gles2_cmd_decoder_passthrough.cc
index 8d5aec4..fc98910 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder_passthrough.cc
+++ b/gpu/command_buffer/service/gles2_cmd_decoder_passthrough.cc
@@ -1747,11 +1747,6 @@
   if (!image)
     return;
 
-  // Because the binding is deferred, this texture may not be currently bound
-  // any more. Bind it again.
-  GLenum texture_type = TextureTargetToTextureType(target);
-  api()->glBindTextureFn(texture_type, texture->service_id());
-
   // TODO: internalformat?
   if (image->ShouldBindOrCopy() == gl::GLImage::BIND)
     image->BindTexImage(target);
@@ -1762,14 +1757,6 @@
   // However, for now, we only try once.
   texture->set_is_bind_pending(false);
 
-  // Re-bind the previous texture
-  const BoundTexture& bound_texture =
-      bound_textures_[static_cast<size_t>(GLenumToTextureTarget(texture_type))]
-                     [active_texture_unit_];
-  GLuint prev_texture =
-      bound_texture.texture ? bound_texture.texture->service_id() : 0;
-  api()->glBindTextureFn(texture_type, prev_texture);
-
   // Update any binding points that are currently bound for this texture.
   RebindTexture(texture);
 
diff --git a/gpu/config/gpu_info_collector_win.cc b/gpu/config/gpu_info_collector_win.cc
index 483b4f20..b7ff063d 100644
--- a/gpu/config/gpu_info_collector_win.cc
+++ b/gpu/config/gpu_info_collector_win.cc
@@ -229,21 +229,13 @@
       return false;
   }
 
-  const VS_FIXEDFILEINFO* fixed_file_info =
-      file_version_info->fixed_file_info();
-  const int major = HIWORD(fixed_file_info->dwFileVersionMS);
-  const int minor = LOWORD(fixed_file_info->dwFileVersionMS);
-  const int minor_1 = HIWORD(fixed_file_info->dwFileVersionLS);
-
   // From the Canary crash logs, the broken amdvlk64.dll versions
   // are 1.0.39.0, 1.0.51.0 and 1.0.54.0. In the manual test, version
   // 9.2.10.1 dated 12/6/2017 works and version 1.0.54.0 dated 11/2/1017
   // crashes. All version numbers small than 1.0.54.0 will be marked as
   // broken.
-  if (major == 1 && minor == 0 && minor_1 <= 54) {
-    return true;
-  }
-  return false;
+  const base::Version kBadAMDVulkanDriverVersion("1.0.54.0");
+  return file_version_info->GetFileVersion() <= kBadAMDVulkanDriverVersion;
 }
 
 bool BadVulkanDllVersion() {
@@ -253,13 +245,6 @@
   if (!file_version_info)
     return false;
 
-  const VS_FIXEDFILEINFO* fixed_file_info =
-      file_version_info->fixed_file_info();
-  const int major = HIWORD(fixed_file_info->dwFileVersionMS);
-  const int minor = LOWORD(fixed_file_info->dwFileVersionMS);
-  const int build_1 = HIWORD(fixed_file_info->dwFileVersionLS);
-  const int build_2 = LOWORD(fixed_file_info->dwFileVersionLS);
-
   // From the logs, most vulkan-1.dll crashs are from the following versions.
   // As of 7/23/2018.
   // 0.0.0.0 -  # of crashes: 6556
@@ -271,13 +256,12 @@
   // The GPU could be from any vendor, but only some certain models would crash.
   // For those that don't crash, they usually return failures upon GPU vulkan
   // support querying even though the GPU drivers can support it.
-  if ((major == 0 && minor == 0 && build_1 == 0 && build_2 == 0) ||
-      (major == 1 && minor == 0 && build_1 == 26 && build_2 == 0) ||
-      (major == 1 && minor == 0 && build_1 == 33 && build_2 == 0) ||
-      (major == 1 && minor == 0 && build_1 == 42 && build_2 == 0) ||
-      (major == 1 && minor == 0 && build_1 == 42 && build_2 == 1) ||
-      (major == 1 && minor == 0 && build_1 == 51 && build_2 == 0)) {
-    return true;
+  base::Version fv = file_version_info->GetFileVersion();
+  const char* const kBadVulkanDllVersion[] = {
+      "0.0.0.0", "1.0.26.0", "1.0.33.0", "1.0.42.0", "1.0.42.1", "1.0.51.0"};
+  for (const char* bad_version : kBadVulkanDllVersion) {
+    if (fv == base::Version(bad_version))
+      return true;
   }
   return false;
 }
diff --git a/ios/chrome/app/main_controller.mm b/ios/chrome/app/main_controller.mm
index 6f3184227..3c0be83 100644
--- a/ios/chrome/app/main_controller.mm
+++ b/ios/chrome/app/main_controller.mm
@@ -1684,7 +1684,7 @@
     DCHECK_EQ(self.currentBVC, self.mainCoordinator.activeViewController);
     baseViewController = self.currentBVC;
   }
-  DCHECK(![baseViewController presentedViewController]);
+
   if ([self currentBrowserState]->IsOffTheRecord()) {
     NOTREACHED();
     return;
@@ -1710,7 +1710,7 @@
     DCHECK_EQ(self.currentBVC, self.mainCoordinator.activeViewController);
     baseViewController = self.currentBVC;
   }
-  DCHECK(![baseViewController presentedViewController]);
+
   if (_settingsNavigationController) {
     // Navigate to the Google services settings if the settings dialog is
     // already opened.
diff --git a/ios/chrome/browser/infobars/infobar_badge_tab_helper.mm b/ios/chrome/browser/infobars/infobar_badge_tab_helper.mm
index 7b88589..7ab5cb2a 100644
--- a/ios/chrome/browser/infobars/infobar_badge_tab_helper.mm
+++ b/ios/chrome/browser/infobars/infobar_badge_tab_helper.mm
@@ -33,7 +33,8 @@
 
 void InfobarBadgeTabHelper::UpdateBadgeForInfobarAccepted(
     InfobarType infobar_type) {
-  infobar_badge_models_[infobar_type].badgeState |= BadgeStateAccepted;
+  infobar_badge_models_[infobar_type].badgeState |=
+      BadgeStateAccepted | BadgeStateRead;
   [delegate_ updateInfobarBadge:infobar_badge_models_[infobar_type]];
 }
 
diff --git a/ios/chrome/browser/infobars/infobar_badge_tab_helper_unittest.mm b/ios/chrome/browser/infobars/infobar_badge_tab_helper_unittest.mm
index 8043cc5..09d9488 100644
--- a/ios/chrome/browser/infobars/infobar_badge_tab_helper_unittest.mm
+++ b/ios/chrome/browser/infobars/infobar_badge_tab_helper_unittest.mm
@@ -237,7 +237,10 @@
   tab_helper()->UpdateBadgeForInfobarAccepted(
       InfobarType::kInfobarTypePasswordSave);
   EXPECT_TRUE(infobar_badge_tab_delegate_.badgeIsTappable);
-  EXPECT_TRUE(infobar_badge_tab_delegate_.badgeState &= BadgeStateAccepted);
+  EXPECT_EQ(BadgeStateAccepted,
+            infobar_badge_tab_delegate_.badgeState & BadgeStateAccepted);
+  EXPECT_EQ(BadgeStateRead,
+            infobar_badge_tab_delegate_.badgeState & BadgeStateRead);
 }
 
 // Test the badge state after doesn't change after adding an Infobar with no
diff --git a/ios/chrome/browser/prerender/preload_controller.mm b/ios/chrome/browser/prerender/preload_controller.mm
index a79762a1..37f3274 100644
--- a/ios/chrome/browser/prerender/preload_controller.mm
+++ b/ios/chrome/browser/prerender/preload_controller.mm
@@ -32,6 +32,7 @@
 #import "ios/web/public/navigation/web_state_policy_decider_bridge.h"
 #include "ios/web/public/thread/web_thread.h"
 #import "ios/web/public/ui/java_script_dialog_presenter.h"
+#include "ios/web/public/web_client.h"
 #import "ios/web/public/web_state.h"
 #import "ios/web/public/web_state_observer_bridge.h"
 #import "ios/web/web_state/ui/crw_web_controller.h"
@@ -322,7 +323,8 @@
 }
 
 - (std::unique_ptr<web::WebState>)releasePrerenderContents {
-  if (!_webState)
+  if (!_webState ||
+      _webState->GetNavigationManager()->IsRestoreSessionInProgress())
     return nullptr;
 
   self.successfulPrerendersPerSessionCount++;
@@ -495,13 +497,20 @@
   self.prerenderedURL = self.scheduledURL;
   std::unique_ptr<PrerenderRequest> request = std::move(_scheduledRequest);
 
-  if (!self.prerenderedURL.is_valid()) {
+  web::WebState* webStateToReplace = [self.delegate webStateToReplace];
+  if (!self.prerenderedURL.is_valid() || !webStateToReplace) {
     [self destroyPreviewContents];
     return;
   }
 
   web::WebState::CreateParams createParams(self.browserState);
-  _webState = web::WebState::Create(createParams);
+  if (web::GetWebClient()->IsSlimNavigationManagerEnabled()) {
+    _webState = web::WebState::CreateWithStorageSession(
+        createParams, webStateToReplace->BuildSessionStorage());
+  } else {
+    _webState = web::WebState::Create(createParams);
+  }
+
   // Add the preload controller as a policyDecider before other tab helpers, so
   // that it can block the navigation if needed before other policy deciders
   // execute thier side effects (eg. AppLauncherTabHelper launching app).
diff --git a/ios/chrome/browser/prerender/preload_controller_delegate.h b/ios/chrome/browser/prerender/preload_controller_delegate.h
index 691cbcd3..856d97d7 100644
--- a/ios/chrome/browser/prerender/preload_controller_delegate.h
+++ b/ios/chrome/browser/prerender/preload_controller_delegate.h
@@ -12,6 +12,10 @@
 // A protocol implemented by a delegate of PreloadController
 @protocol PreloadControllerDelegate
 
+// WebState from which preload controller should copy the session history.
+// This web state will be replaced on successful preload.
+- (web::WebState*)webStateToReplace;
+
 // Should preload controller request a desktop site.
 - (BOOL)preloadShouldUseDesktopUserAgent;
 
diff --git a/ios/chrome/browser/prerender/prerender_egtest.mm b/ios/chrome/browser/prerender/prerender_egtest.mm
index c435a66f..42a5e47 100644
--- a/ios/chrome/browser/prerender/prerender_egtest.mm
+++ b/ios/chrome/browser/prerender/prerender_egtest.mm
@@ -65,11 +65,6 @@
         @"Disabled for iPad due to alternate letters educational screen.");
   }
 
-  if ([ChromeEarlGrey isSlimNavigationManagerEnabled]) {
-    // TODO(crbug.com/834116): Fix and enable this test.
-    EARL_GREY_TEST_DISABLED(@"Prerender is not supported by slim-nav yet.");
-  }
-
   [ChromeEarlGrey clearBrowsingHistory];
   // Set server up.
   int visitCounter = 0;
diff --git a/ios/chrome/browser/prerender/prerender_service.mm b/ios/chrome/browser/prerender/prerender_service.mm
index 45961002..692441d8 100644
--- a/ios/chrome/browser/prerender/prerender_service.mm
+++ b/ios/chrome/browser/prerender/prerender_service.mm
@@ -39,13 +39,6 @@
                                       const web::Referrer& referrer,
                                       ui::PageTransition transition,
                                       bool immediately) {
-  // PrerenderService is not compatible with WKBasedNavigationManager because it
-  // loads the URL in a new WKWebView, which doesn't have the current session
-  // history. TODO(crbug.com/814789): decide whether PrerenderService needs to
-  // be supported after evaluating the performance impact in Finch experiment.
-  if (web::GetWebClient()->IsSlimNavigationManagerEnabled())
-    return;
-
   [controller_ prerenderURL:url
                    referrer:referrer
                  transition:transition
@@ -64,6 +57,11 @@
 
   std::unique_ptr<web::WebState> new_web_state =
       [controller_ releasePrerenderContents];
+  if (!new_web_state) {
+    CancelPrerender();
+    return false;
+  }
+
   DCHECK_NE(WebStateList::kInvalidIndex, web_state_list->active_index());
 
   web::NavigationManager* active_navigation_manager =
@@ -79,8 +77,13 @@
   web::NavigationManager* new_navigation_manager =
       new_web_state->GetNavigationManager();
 
-  if (new_navigation_manager->CanPruneAllButLastCommittedItem()) {
-    new_navigation_manager->CopyStateFromAndPrune(active_navigation_manager);
+  bool slim_navigation_manager_enabled =
+      web::GetWebClient()->IsSlimNavigationManagerEnabled();
+  if (new_navigation_manager->CanPruneAllButLastCommittedItem() ||
+      slim_navigation_manager_enabled) {
+    if (!slim_navigation_manager_enabled) {
+      new_navigation_manager->CopyStateFromAndPrune(active_navigation_manager);
+    }
     loading_prerender_ = true;
     web_state_list->ReplaceWebStateAt(web_state_list->active_index(),
                                       std::move(new_web_state));
diff --git a/ios/chrome/browser/sync/sync_setup_service.cc b/ios/chrome/browser/sync/sync_setup_service.cc
index b4ccaba..70b53b79 100644
--- a/ios/chrome/browser/sync/sync_setup_service.cc
+++ b/ios/chrome/browser/sync/sync_setup_service.cc
@@ -149,7 +149,8 @@
   }
   if (sync_service_->HasUnrecoverableError())
     return kSyncServiceUnrecoverableError;
-  if (sync_service_->GetUserSettings()->IsPassphraseRequiredForDecryption())
+  if (sync_service_->GetUserSettings()
+          ->IsPassphraseRequiredForPreferredDataTypes())
     return kSyncServiceNeedsPassphrase;
   if (!IsFirstSetupComplete() && IsSyncEnabled())
     return kSyncSettingsNotConfirmed;
diff --git a/ios/chrome/browser/ui/authentication/signin_promo_view_mediator.h b/ios/chrome/browser/ui/authentication/signin_promo_view_mediator.h
index 0d57d9e9..47e9ea2 100644
--- a/ios/chrome/browser/ui/authentication/signin_promo_view_mediator.h
+++ b/ios/chrome/browser/ui/authentication/signin_promo_view_mediator.h
@@ -26,8 +26,9 @@
 // way to go backwards. All states can be skipped except |NeverVisible| and
 // |Invalid|.
 enum class SigninPromoViewState {
-  // Initial state. When -[SigninPromoViewMediator signinPromoViewRemoved]  is
-  // called with that state, no metrics is recorded.
+  // Initial state. When -[SigninPromoViewMediator
+  // signinPromoViewIsRemoved] is called with that state, no metrics is
+  // recorded.
   NeverVisible = 0,
   // None of the buttons has been used yet.
   Unused,
@@ -55,13 +56,18 @@
 
 // Chrome identity used to configure the view in a warm state mode. Otherwise
 // contains nil.
-@property(nonatomic, readonly) ChromeIdentity* defaultIdentity;
+@property(nonatomic, strong, readonly) ChromeIdentity* defaultIdentity;
 
 // Sign-in promo view state.
-@property(nonatomic) ios::SigninPromoViewState signinPromoViewState;
+@property(nonatomic, assign) ios::SigninPromoViewState signinPromoViewState;
 
 // YES if the sign-in interaction controller is shown.
-@property(nonatomic, readonly, getter=isSigninInProgress) BOOL signinInProgress;
+@property(nonatomic, assign, readonly, getter=isSigninInProgress)
+    BOOL signinInProgress;
+
+// Returns YES if the sign-in promo view is |Invalid|, |Closed| or invisible.
+@property(nonatomic, assign, readonly, getter=isInvalidClosedOrNeverVisible)
+    BOOL invalidClosedOrNeverVisible;
 
 // Registers the feature preferences.
 + (void)registerBrowserStatePrefs:(user_prefs::PrefRegistrySyncable*)registry;
@@ -90,19 +96,16 @@
 // Increments the "shown" counter used for histograms. Called when the signin
 // promo view is visible. If the sign-in promo is already visible, this method
 // does nothing.
-- (void)signinPromoViewVisible;
+- (void)signinPromoViewIsVisible;
 
 // Called when the sign-in promo view is hidden. If the sign-in promo view has
 // never been shown, or it is already hidden, this method does nothing.
-- (void)signinPromoViewHidden;
+- (void)signinPromoViewIsHidden;
 
 // Called when the sign-in promo view is removed from the view hierarchy (it or
 // one of its superviews is removed). The mediator should not be used after this
 // called.
-- (void)signinPromoViewRemoved;
-
-// Returns YES if the sign-in promo view is |Invalid|, |Closed| or |Invisible|.
-- (BOOL)isInvalidClosedOrNeverVisible;
+- (void)signinPromoViewIsRemoved;
 
 @end
 
diff --git a/ios/chrome/browser/ui/authentication/signin_promo_view_mediator.mm b/ios/chrome/browser/ui/authentication/signin_promo_view_mediator.mm
index 62b194d..dd95fe9 100644
--- a/ios/chrome/browser/ui/authentication/signin_promo_view_mediator.mm
+++ b/ios/chrome/browser/ui/authentication/signin_promo_view_mediator.mm
@@ -35,8 +35,12 @@
 #endif
 
 namespace {
+
+// Number of times the sign-in promo should be displayed until it is
+// automatically dismissed.
 const int kAutomaticSigninPromoViewDismissCount = 20;
 
+// Returns true if the sign-in promo is supported for |access_point|.
 bool IsSupportedAccessPoint(signin_metrics::AccessPoint access_point) {
   switch (access_point) {
     case signin_metrics::AccessPoint::ACCESS_POINT_SETTINGS:
@@ -44,11 +48,36 @@
     case signin_metrics::AccessPoint::ACCESS_POINT_RECENT_TABS:
     case signin_metrics::AccessPoint::ACCESS_POINT_TAB_SWITCHER:
       return true;
-    default:
+    case signin_metrics::AccessPoint::ACCESS_POINT_START_PAGE:
+    case signin_metrics::AccessPoint::ACCESS_POINT_NTP_LINK:
+    case signin_metrics::AccessPoint::ACCESS_POINT_MENU:
+    case signin_metrics::AccessPoint::ACCESS_POINT_SUPERVISED_USER:
+    case signin_metrics::AccessPoint::ACCESS_POINT_EXTENSION_INSTALL_BUBBLE:
+    case signin_metrics::AccessPoint::ACCESS_POINT_EXTENSIONS:
+    case signin_metrics::AccessPoint::ACCESS_POINT_APPS_PAGE_LINK:
+    case signin_metrics::AccessPoint::ACCESS_POINT_BOOKMARK_BUBBLE:
+    case signin_metrics::AccessPoint::ACCESS_POINT_AVATAR_BUBBLE_SIGN_IN:
+    case signin_metrics::AccessPoint::ACCESS_POINT_USER_MANAGER:
+    case signin_metrics::AccessPoint::ACCESS_POINT_DEVICES_PAGE:
+    case signin_metrics::AccessPoint::ACCESS_POINT_CLOUD_PRINT:
+    case signin_metrics::AccessPoint::ACCESS_POINT_CONTENT_AREA:
+    case signin_metrics::AccessPoint::ACCESS_POINT_SIGNIN_PROMO:
+    case signin_metrics::AccessPoint::ACCESS_POINT_UNKNOWN:
+    case signin_metrics::AccessPoint::ACCESS_POINT_PASSWORD_BUBBLE:
+    case signin_metrics::AccessPoint::ACCESS_POINT_AUTOFILL_DROPDOWN:
+    case signin_metrics::AccessPoint::ACCESS_POINT_NTP_CONTENT_SUGGESTIONS:
+    case signin_metrics::AccessPoint::ACCESS_POINT_RESIGNIN_INFOBAR:
+    case signin_metrics::AccessPoint::ACCESS_POINT_SAVE_CARD_BUBBLE:
+    case signin_metrics::AccessPoint::ACCESS_POINT_MANAGE_CARDS_BUBBLE:
+    case signin_metrics::AccessPoint::ACCESS_POINT_MACHINE_LOGON:
+    case signin_metrics::AccessPoint::ACCESS_POINT_GOOGLE_SERVICES_SETTINGS:
+    case signin_metrics::AccessPoint::ACCESS_POINT_MAX:
       return false;
   }
 }
 
+// Records in histogram, the number of times the sign-in promo is displayed
+// before the sign-in button is pressed.
 void RecordImpressionsTilSigninButtonsHistogramForAccessPoint(
     signin_metrics::AccessPoint access_point,
     int displayed_count) {
@@ -63,13 +92,40 @@
           "MobileSignInPromo.SettingsManager.ImpressionsTilSigninButtons",
           displayed_count);
       break;
-    default:
+    case signin_metrics::AccessPoint::ACCESS_POINT_RECENT_TABS:
+    case signin_metrics::AccessPoint::ACCESS_POINT_TAB_SWITCHER:
+    case signin_metrics::AccessPoint::ACCESS_POINT_START_PAGE:
+    case signin_metrics::AccessPoint::ACCESS_POINT_NTP_LINK:
+    case signin_metrics::AccessPoint::ACCESS_POINT_MENU:
+    case signin_metrics::AccessPoint::ACCESS_POINT_SUPERVISED_USER:
+    case signin_metrics::AccessPoint::ACCESS_POINT_EXTENSION_INSTALL_BUBBLE:
+    case signin_metrics::AccessPoint::ACCESS_POINT_EXTENSIONS:
+    case signin_metrics::AccessPoint::ACCESS_POINT_APPS_PAGE_LINK:
+    case signin_metrics::AccessPoint::ACCESS_POINT_BOOKMARK_BUBBLE:
+    case signin_metrics::AccessPoint::ACCESS_POINT_AVATAR_BUBBLE_SIGN_IN:
+    case signin_metrics::AccessPoint::ACCESS_POINT_USER_MANAGER:
+    case signin_metrics::AccessPoint::ACCESS_POINT_DEVICES_PAGE:
+    case signin_metrics::AccessPoint::ACCESS_POINT_CLOUD_PRINT:
+    case signin_metrics::AccessPoint::ACCESS_POINT_CONTENT_AREA:
+    case signin_metrics::AccessPoint::ACCESS_POINT_SIGNIN_PROMO:
+    case signin_metrics::AccessPoint::ACCESS_POINT_UNKNOWN:
+    case signin_metrics::AccessPoint::ACCESS_POINT_PASSWORD_BUBBLE:
+    case signin_metrics::AccessPoint::ACCESS_POINT_AUTOFILL_DROPDOWN:
+    case signin_metrics::AccessPoint::ACCESS_POINT_NTP_CONTENT_SUGGESTIONS:
+    case signin_metrics::AccessPoint::ACCESS_POINT_RESIGNIN_INFOBAR:
+    case signin_metrics::AccessPoint::ACCESS_POINT_SAVE_CARD_BUBBLE:
+    case signin_metrics::AccessPoint::ACCESS_POINT_MANAGE_CARDS_BUBBLE:
+    case signin_metrics::AccessPoint::ACCESS_POINT_MACHINE_LOGON:
+    case signin_metrics::AccessPoint::ACCESS_POINT_GOOGLE_SERVICES_SETTINGS:
+    case signin_metrics::AccessPoint::ACCESS_POINT_MAX:
       NOTREACHED() << "Unexpected value for access point "
                    << static_cast<int>(access_point);
       break;
   }
 }
 
+// Records in histogram, the number of times the sign-in promo is displayed
+// before the cancel button is pressed.
 void RecordImpressionsTilDismissHistogramForAccessPoint(
     signin_metrics::AccessPoint access_point,
     int displayed_count) {
@@ -84,13 +140,40 @@
           "MobileSignInPromo.SettingsManager.ImpressionsTilDismiss",
           displayed_count);
       break;
-    default:
+    case signin_metrics::AccessPoint::ACCESS_POINT_RECENT_TABS:
+    case signin_metrics::AccessPoint::ACCESS_POINT_TAB_SWITCHER:
+    case signin_metrics::AccessPoint::ACCESS_POINT_START_PAGE:
+    case signin_metrics::AccessPoint::ACCESS_POINT_NTP_LINK:
+    case signin_metrics::AccessPoint::ACCESS_POINT_MENU:
+    case signin_metrics::AccessPoint::ACCESS_POINT_SUPERVISED_USER:
+    case signin_metrics::AccessPoint::ACCESS_POINT_EXTENSION_INSTALL_BUBBLE:
+    case signin_metrics::AccessPoint::ACCESS_POINT_EXTENSIONS:
+    case signin_metrics::AccessPoint::ACCESS_POINT_APPS_PAGE_LINK:
+    case signin_metrics::AccessPoint::ACCESS_POINT_BOOKMARK_BUBBLE:
+    case signin_metrics::AccessPoint::ACCESS_POINT_AVATAR_BUBBLE_SIGN_IN:
+    case signin_metrics::AccessPoint::ACCESS_POINT_USER_MANAGER:
+    case signin_metrics::AccessPoint::ACCESS_POINT_DEVICES_PAGE:
+    case signin_metrics::AccessPoint::ACCESS_POINT_CLOUD_PRINT:
+    case signin_metrics::AccessPoint::ACCESS_POINT_CONTENT_AREA:
+    case signin_metrics::AccessPoint::ACCESS_POINT_SIGNIN_PROMO:
+    case signin_metrics::AccessPoint::ACCESS_POINT_UNKNOWN:
+    case signin_metrics::AccessPoint::ACCESS_POINT_PASSWORD_BUBBLE:
+    case signin_metrics::AccessPoint::ACCESS_POINT_AUTOFILL_DROPDOWN:
+    case signin_metrics::AccessPoint::ACCESS_POINT_NTP_CONTENT_SUGGESTIONS:
+    case signin_metrics::AccessPoint::ACCESS_POINT_RESIGNIN_INFOBAR:
+    case signin_metrics::AccessPoint::ACCESS_POINT_SAVE_CARD_BUBBLE:
+    case signin_metrics::AccessPoint::ACCESS_POINT_MANAGE_CARDS_BUBBLE:
+    case signin_metrics::AccessPoint::ACCESS_POINT_MACHINE_LOGON:
+    case signin_metrics::AccessPoint::ACCESS_POINT_GOOGLE_SERVICES_SETTINGS:
+    case signin_metrics::AccessPoint::ACCESS_POINT_MAX:
       NOTREACHED() << "Unexpected value for access point "
                    << static_cast<int>(access_point);
       break;
   }
 }
 
+// Records in histogram, the number of times the sign-in promo is displayed
+// before the close button is pressed.
 void RecordImpressionsTilXButtonHistogramForAccessPoint(
     signin_metrics::AccessPoint access_point,
     int displayed_count) {
@@ -105,13 +188,39 @@
           "MobileSignInPromo.SettingsManager.ImpressionsTilXButton",
           displayed_count);
       break;
-    default:
+    case signin_metrics::AccessPoint::ACCESS_POINT_RECENT_TABS:
+    case signin_metrics::AccessPoint::ACCESS_POINT_TAB_SWITCHER:
+    case signin_metrics::AccessPoint::ACCESS_POINT_START_PAGE:
+    case signin_metrics::AccessPoint::ACCESS_POINT_NTP_LINK:
+    case signin_metrics::AccessPoint::ACCESS_POINT_MENU:
+    case signin_metrics::AccessPoint::ACCESS_POINT_SUPERVISED_USER:
+    case signin_metrics::AccessPoint::ACCESS_POINT_EXTENSION_INSTALL_BUBBLE:
+    case signin_metrics::AccessPoint::ACCESS_POINT_EXTENSIONS:
+    case signin_metrics::AccessPoint::ACCESS_POINT_APPS_PAGE_LINK:
+    case signin_metrics::AccessPoint::ACCESS_POINT_BOOKMARK_BUBBLE:
+    case signin_metrics::AccessPoint::ACCESS_POINT_AVATAR_BUBBLE_SIGN_IN:
+    case signin_metrics::AccessPoint::ACCESS_POINT_USER_MANAGER:
+    case signin_metrics::AccessPoint::ACCESS_POINT_DEVICES_PAGE:
+    case signin_metrics::AccessPoint::ACCESS_POINT_CLOUD_PRINT:
+    case signin_metrics::AccessPoint::ACCESS_POINT_CONTENT_AREA:
+    case signin_metrics::AccessPoint::ACCESS_POINT_SIGNIN_PROMO:
+    case signin_metrics::AccessPoint::ACCESS_POINT_UNKNOWN:
+    case signin_metrics::AccessPoint::ACCESS_POINT_PASSWORD_BUBBLE:
+    case signin_metrics::AccessPoint::ACCESS_POINT_AUTOFILL_DROPDOWN:
+    case signin_metrics::AccessPoint::ACCESS_POINT_NTP_CONTENT_SUGGESTIONS:
+    case signin_metrics::AccessPoint::ACCESS_POINT_RESIGNIN_INFOBAR:
+    case signin_metrics::AccessPoint::ACCESS_POINT_SAVE_CARD_BUBBLE:
+    case signin_metrics::AccessPoint::ACCESS_POINT_MANAGE_CARDS_BUBBLE:
+    case signin_metrics::AccessPoint::ACCESS_POINT_MACHINE_LOGON:
+    case signin_metrics::AccessPoint::ACCESS_POINT_GOOGLE_SERVICES_SETTINGS:
+    case signin_metrics::AccessPoint::ACCESS_POINT_MAX:
       NOTREACHED() << "Unexpected value for access point "
                    << static_cast<int>(access_point);
       break;
   }
 }
 
+// Returns the DisplayedCount preference key string for |access_point|.
 const char* DisplayedCountPreferenceKey(
     signin_metrics::AccessPoint access_point) {
   switch (access_point) {
@@ -119,11 +228,37 @@
       return prefs::kIosBookmarkSigninPromoDisplayedCount;
     case signin_metrics::AccessPoint::ACCESS_POINT_SETTINGS:
       return prefs::kIosSettingsSigninPromoDisplayedCount;
-    default:
+    case signin_metrics::AccessPoint::ACCESS_POINT_RECENT_TABS:
+    case signin_metrics::AccessPoint::ACCESS_POINT_TAB_SWITCHER:
+    case signin_metrics::AccessPoint::ACCESS_POINT_START_PAGE:
+    case signin_metrics::AccessPoint::ACCESS_POINT_NTP_LINK:
+    case signin_metrics::AccessPoint::ACCESS_POINT_MENU:
+    case signin_metrics::AccessPoint::ACCESS_POINT_SUPERVISED_USER:
+    case signin_metrics::AccessPoint::ACCESS_POINT_EXTENSION_INSTALL_BUBBLE:
+    case signin_metrics::AccessPoint::ACCESS_POINT_EXTENSIONS:
+    case signin_metrics::AccessPoint::ACCESS_POINT_APPS_PAGE_LINK:
+    case signin_metrics::AccessPoint::ACCESS_POINT_BOOKMARK_BUBBLE:
+    case signin_metrics::AccessPoint::ACCESS_POINT_AVATAR_BUBBLE_SIGN_IN:
+    case signin_metrics::AccessPoint::ACCESS_POINT_USER_MANAGER:
+    case signin_metrics::AccessPoint::ACCESS_POINT_DEVICES_PAGE:
+    case signin_metrics::AccessPoint::ACCESS_POINT_CLOUD_PRINT:
+    case signin_metrics::AccessPoint::ACCESS_POINT_CONTENT_AREA:
+    case signin_metrics::AccessPoint::ACCESS_POINT_SIGNIN_PROMO:
+    case signin_metrics::AccessPoint::ACCESS_POINT_UNKNOWN:
+    case signin_metrics::AccessPoint::ACCESS_POINT_PASSWORD_BUBBLE:
+    case signin_metrics::AccessPoint::ACCESS_POINT_AUTOFILL_DROPDOWN:
+    case signin_metrics::AccessPoint::ACCESS_POINT_NTP_CONTENT_SUGGESTIONS:
+    case signin_metrics::AccessPoint::ACCESS_POINT_RESIGNIN_INFOBAR:
+    case signin_metrics::AccessPoint::ACCESS_POINT_SAVE_CARD_BUBBLE:
+    case signin_metrics::AccessPoint::ACCESS_POINT_MANAGE_CARDS_BUBBLE:
+    case signin_metrics::AccessPoint::ACCESS_POINT_MACHINE_LOGON:
+    case signin_metrics::AccessPoint::ACCESS_POINT_GOOGLE_SERVICES_SETTINGS:
+    case signin_metrics::AccessPoint::ACCESS_POINT_MAX:
       return nullptr;
   }
 }
 
+// Returns AlreadySeen preference key string for |access_point|.
 const char* AlreadySeenSigninViewPreferenceKey(
     signin_metrics::AccessPoint access_point) {
   switch (access_point) {
@@ -131,36 +266,72 @@
       return prefs::kIosBookmarkPromoAlreadySeen;
     case signin_metrics::AccessPoint::ACCESS_POINT_SETTINGS:
       return prefs::kIosSettingsPromoAlreadySeen;
-    default:
+    case signin_metrics::AccessPoint::ACCESS_POINT_RECENT_TABS:
+    case signin_metrics::AccessPoint::ACCESS_POINT_TAB_SWITCHER:
+    case signin_metrics::AccessPoint::ACCESS_POINT_START_PAGE:
+    case signin_metrics::AccessPoint::ACCESS_POINT_NTP_LINK:
+    case signin_metrics::AccessPoint::ACCESS_POINT_MENU:
+    case signin_metrics::AccessPoint::ACCESS_POINT_SUPERVISED_USER:
+    case signin_metrics::AccessPoint::ACCESS_POINT_EXTENSION_INSTALL_BUBBLE:
+    case signin_metrics::AccessPoint::ACCESS_POINT_EXTENSIONS:
+    case signin_metrics::AccessPoint::ACCESS_POINT_APPS_PAGE_LINK:
+    case signin_metrics::AccessPoint::ACCESS_POINT_BOOKMARK_BUBBLE:
+    case signin_metrics::AccessPoint::ACCESS_POINT_AVATAR_BUBBLE_SIGN_IN:
+    case signin_metrics::AccessPoint::ACCESS_POINT_USER_MANAGER:
+    case signin_metrics::AccessPoint::ACCESS_POINT_DEVICES_PAGE:
+    case signin_metrics::AccessPoint::ACCESS_POINT_CLOUD_PRINT:
+    case signin_metrics::AccessPoint::ACCESS_POINT_CONTENT_AREA:
+    case signin_metrics::AccessPoint::ACCESS_POINT_SIGNIN_PROMO:
+    case signin_metrics::AccessPoint::ACCESS_POINT_UNKNOWN:
+    case signin_metrics::AccessPoint::ACCESS_POINT_PASSWORD_BUBBLE:
+    case signin_metrics::AccessPoint::ACCESS_POINT_AUTOFILL_DROPDOWN:
+    case signin_metrics::AccessPoint::ACCESS_POINT_NTP_CONTENT_SUGGESTIONS:
+    case signin_metrics::AccessPoint::ACCESS_POINT_RESIGNIN_INFOBAR:
+    case signin_metrics::AccessPoint::ACCESS_POINT_SAVE_CARD_BUBBLE:
+    case signin_metrics::AccessPoint::ACCESS_POINT_MANAGE_CARDS_BUBBLE:
+    case signin_metrics::AccessPoint::ACCESS_POINT_MACHINE_LOGON:
+    case signin_metrics::AccessPoint::ACCESS_POINT_GOOGLE_SERVICES_SETTINGS:
+    case signin_metrics::AccessPoint::ACCESS_POINT_MAX:
       return nullptr;
   }
 }
+
 }  // namespace
 
-@interface SigninPromoViewMediator ()<ChromeIdentityServiceObserver,
-                                      ChromeBrowserProviderObserver>
-// Presenter which can show signin UI.
-@property(nonatomic, readonly, weak) id<SigninPresenter> presenter;
-
-// Redefined to be readwrite.
-@property(nonatomic, readwrite, getter=isSigninInProgress)
-    BOOL signinInProgress;
-@end
-
-@implementation SigninPromoViewMediator {
-  ios::ChromeBrowserState* _browserState;
-  signin_metrics::AccessPoint _accessPoint;
+@interface SigninPromoViewMediator () <ChromeBrowserProviderObserver,
+                                       ChromeIdentityServiceObserver> {
   std::unique_ptr<ChromeIdentityServiceObserverBridge> _identityServiceObserver;
   std::unique_ptr<ChromeBrowserProviderObserverBridge> _browserProviderObserver;
-  UIImage* _identityAvatar;
-  BOOL _isSigninPromoViewVisible;
 }
 
-@synthesize consumer = _consumer;
-@synthesize defaultIdentity = _defaultIdentity;
-@synthesize signinPromoViewState = _signinPromoViewState;
-@synthesize presenter = _presenter;
-@synthesize signinInProgress = _signinInProgress;
+// Redefined to be readwrite.
+@property(nonatomic, strong, readwrite) ChromeIdentity* defaultIdentity;
+@property(nonatomic, assign, readwrite, getter=isSigninInProgress)
+    BOOL signinInProgress;
+
+// Presenter which can show signin UI.
+@property(nonatomic, weak, readonly) id<SigninPresenter> presenter;
+
+// The coordinator's BrowserState.
+@property(nonatomic, assign, readonly) ios::ChromeBrowserState* browserState;
+
+// The access point for the sign-in promo view.
+@property(nonatomic, assign, readonly) signin_metrics::AccessPoint accessPoint;
+
+// Identity avatar.
+@property(nonatomic, strong) UIImage* identityAvatar;
+
+// YES if the sign-in promo is currently visible by the user.
+@property(nonatomic, assign, getter=isSigninPromoViewVisible)
+    BOOL signinPromoViewVisible;
+
+// YES if the sign-in promo is either invalid or closed.
+@property(nonatomic, assign, readonly, getter=isInvalidOrClosed)
+    BOOL invalidOrClosed;
+
+@end
+
+@implementation SigninPromoViewMediator
 
 + (void)registerBrowserStatePrefs:(user_prefs::PrefRegistrySyncable*)registry {
   // Bookmarks
@@ -221,24 +392,14 @@
   DCHECK_EQ(ios::SigninPromoViewState::Invalid, _signinPromoViewState);
 }
 
-- (BOOL)isInvalidOrClosed {
-  return _signinPromoViewState == ios::SigninPromoViewState::Closed ||
-         _signinPromoViewState == ios::SigninPromoViewState::Invalid;
-}
-
-- (BOOL)isInvalidClosedOrNeverVisible {
-  return [self isInvalidOrClosed] ||
-         _signinPromoViewState == ios::SigninPromoViewState::NeverVisible;
-}
-
 - (SigninPromoViewConfigurator*)createConfigurator {
   BOOL hasCloseButton =
-      AlreadySeenSigninViewPreferenceKey(_accessPoint) != nullptr;
+      AlreadySeenSigninViewPreferenceKey(self.accessPoint) != nullptr;
   if (_defaultIdentity) {
     return [[SigninPromoViewConfigurator alloc]
         initWithUserEmail:_defaultIdentity.userEmail
              userFullName:_defaultIdentity.userFullName
-                userImage:_identityAvatar
+                userImage:self.identityAvatar
            hasCloseButton:hasCloseButton];
   }
   return [[SigninPromoViewConfigurator alloc] initWithUserEmail:nil
@@ -247,10 +408,83 @@
                                                  hasCloseButton:hasCloseButton];
 }
 
+- (void)signinPromoViewIsVisible {
+  DCHECK(!self.invalidOrClosed);
+  if (self.signinPromoViewVisible)
+    return;
+  if (self.signinPromoViewState == ios::SigninPromoViewState::NeverVisible)
+    self.signinPromoViewState = ios::SigninPromoViewState::Unused;
+  self.signinPromoViewVisible = YES;
+  signin_metrics::RecordSigninImpressionUserActionForAccessPoint(
+      self.accessPoint);
+  signin_metrics::RecordSigninImpressionWithAccountUserActionForAccessPoint(
+      self.accessPoint, !!_defaultIdentity);
+  const char* displayedCountPreferenceKey =
+      DisplayedCountPreferenceKey(self.accessPoint);
+  if (!displayedCountPreferenceKey)
+    return;
+  PrefService* prefs = self.browserState->GetPrefs();
+  int displayedCount = prefs->GetInteger(displayedCountPreferenceKey);
+  ++displayedCount;
+  prefs->SetInteger(displayedCountPreferenceKey, displayedCount);
+}
+
+- (void)signinPromoViewIsHidden {
+  DCHECK(!self.invalidOrClosed);
+  self.signinPromoViewVisible = NO;
+}
+
+- (void)signinPromoViewIsRemoved {
+  DCHECK_NE(ios::SigninPromoViewState::Invalid, self.signinPromoViewState);
+  DCHECK(!self.signinInProgress);
+  BOOL wasNeverVisible =
+      self.signinPromoViewState == ios::SigninPromoViewState::NeverVisible;
+  BOOL wasUnused =
+      self.signinPromoViewState == ios::SigninPromoViewState::Unused;
+  self.signinPromoViewState = ios::SigninPromoViewState::Invalid;
+  self.signinPromoViewVisible = NO;
+  if (wasNeverVisible)
+    return;
+  // If the sign-in promo view has been used at least once, it should not be
+  // counted as dismissed (even if the sign-in has been canceled).
+  const char* displayedCountPreferenceKey =
+      DisplayedCountPreferenceKey(self.accessPoint);
+  if (!displayedCountPreferenceKey || !wasUnused)
+    return;
+  // If the sign-in view is removed when the user is authenticated, then the
+  // sign-in has been done by another view, and this mediator cannot be counted
+  // as being dismissed.
+  AuthenticationService* authService =
+      AuthenticationServiceFactory::GetForBrowserState(self.browserState);
+  if (authService->IsAuthenticated())
+    return;
+  PrefService* prefs = self.browserState->GetPrefs();
+  int displayedCount = prefs->GetInteger(displayedCountPreferenceKey);
+  RecordImpressionsTilDismissHistogramForAccessPoint(self.accessPoint,
+                                                     displayedCount);
+}
+
+#pragma mark - Public properties
+
+- (BOOL)isInvalidClosedOrNeverVisible {
+  return self.invalidOrClosed ||
+         self.signinPromoViewState == ios::SigninPromoViewState::NeverVisible;
+}
+
+#pragma mark - Private properties
+
+- (BOOL)isInvalidOrClosed {
+  return self.signinPromoViewState == ios::SigninPromoViewState::Closed ||
+         self.signinPromoViewState == ios::SigninPromoViewState::Invalid;
+}
+
+#pragma mark - Private
+
+// Sets the Chrome identity to display in the sign-in promo.
 - (void)selectIdentity:(ChromeIdentity*)identity {
   _defaultIdentity = identity;
   if (!_defaultIdentity) {
-    _identityAvatar = nil;
+    self.identityAvatar = nil;
   } else {
     __weak SigninPromoViewMediator* weakSelf = self;
     ios::GetChromeBrowserProvider()
@@ -264,8 +498,9 @@
   }
 }
 
+// Updates the Chrome identity avatar in the sign-in promo.
 - (void)identityAvatarUpdated:(UIImage*)identityAvatar {
-  _identityAvatar = identityAvatar;
+  self.identityAvatar = identityAvatar;
   [self sendConsumerNotificationWithIdentityChanged:NO];
 }
 
@@ -276,87 +511,39 @@
   if (self.signinInProgress)
     return;
   SigninPromoViewConfigurator* configurator = [self createConfigurator];
-  [_consumer configureSigninPromoWithConfigurator:configurator
-                                  identityChanged:identityChanged];
+  [self.consumer configureSigninPromoWithConfigurator:configurator
+                                      identityChanged:identityChanged];
 }
 
+// Records in histogram, the number of time the sign-in promo is displayed
+// before the sign-in button is pressed, if the current access point supports
+// it.
 - (void)sendImpressionsTillSigninButtonsHistogram {
-  DCHECK(![self isInvalidClosedOrNeverVisible]);
+  DCHECK(!self.invalidClosedOrNeverVisible);
   const char* displayedCountPreferenceKey =
-      DisplayedCountPreferenceKey(_accessPoint);
+      DisplayedCountPreferenceKey(self.accessPoint);
   if (!displayedCountPreferenceKey)
     return;
-  PrefService* prefs = _browserState->GetPrefs();
+  PrefService* prefs = self.browserState->GetPrefs();
   int displayedCount = prefs->GetInteger(displayedCountPreferenceKey);
-  RecordImpressionsTilSigninButtonsHistogramForAccessPoint(_accessPoint,
+  RecordImpressionsTilSigninButtonsHistogramForAccessPoint(self.accessPoint,
                                                            displayedCount);
 }
 
-- (void)signinPromoViewVisible {
-  DCHECK(![self isInvalidOrClosed]);
-  if (_isSigninPromoViewVisible)
-    return;
-  if (_signinPromoViewState == ios::SigninPromoViewState::NeverVisible)
-    _signinPromoViewState = ios::SigninPromoViewState::Unused;
-  _isSigninPromoViewVisible = YES;
-  signin_metrics::RecordSigninImpressionUserActionForAccessPoint(_accessPoint);
-  signin_metrics::RecordSigninImpressionWithAccountUserActionForAccessPoint(
-      _accessPoint, !!_defaultIdentity);
-  const char* displayedCountPreferenceKey =
-      DisplayedCountPreferenceKey(_accessPoint);
-  if (!displayedCountPreferenceKey)
-    return;
-  PrefService* prefs = _browserState->GetPrefs();
-  int displayedCount = prefs->GetInteger(displayedCountPreferenceKey);
-  ++displayedCount;
-  prefs->SetInteger(displayedCountPreferenceKey, displayedCount);
-}
-
-- (void)signinPromoViewHidden {
-  DCHECK(![self isInvalidOrClosed]);
-  _isSigninPromoViewVisible = NO;
-}
-
-- (void)signinPromoViewRemoved {
-  DCHECK_NE(ios::SigninPromoViewState::Invalid, _signinPromoViewState);
-  DCHECK(!self.signinInProgress);
-  BOOL wasNeverVisible =
-      _signinPromoViewState == ios::SigninPromoViewState::NeverVisible;
-  BOOL wasUnused = _signinPromoViewState == ios::SigninPromoViewState::Unused;
-  _signinPromoViewState = ios::SigninPromoViewState::Invalid;
-  _isSigninPromoViewVisible = NO;
-  if (wasNeverVisible)
-    return;
-  // If the sign-in promo view has been used at least once, it should not be
-  // counted as dismissed (even if the sign-in has been canceled).
-  const char* displayedCountPreferenceKey =
-      DisplayedCountPreferenceKey(_accessPoint);
-  if (!displayedCountPreferenceKey || !wasUnused)
-    return;
-  // If the sign-in view is removed when the user is authenticated, then the
-  // sign-in has been done by another view, and this mediator cannot be counted
-  // as being dismissed.
-  AuthenticationService* authService =
-      AuthenticationServiceFactory::GetForBrowserState(_browserState);
-  if (authService->IsAuthenticated())
-    return;
-  PrefService* prefs = _browserState->GetPrefs();
-  int displayedCount = prefs->GetInteger(displayedCountPreferenceKey);
-  RecordImpressionsTilDismissHistogramForAccessPoint(_accessPoint,
-                                                     displayedCount);
-}
-
+// Finishes the sign-in process.
 - (void)signinCallback {
-  DCHECK_EQ(ios::SigninPromoViewState::UsedAtLeastOnce, _signinPromoViewState);
+  DCHECK_EQ(ios::SigninPromoViewState::UsedAtLeastOnce,
+            self.signinPromoViewState);
   DCHECK(self.signinInProgress);
   self.signinInProgress = NO;
-  if ([_consumer respondsToSelector:@selector(signinDidFinish)])
-    [_consumer signinDidFinish];
+  if ([self.consumer respondsToSelector:@selector(signinDidFinish)])
+    [self.consumer signinDidFinish];
 }
 
+// Starts sign-in process with the Chrome identity from |identity|.
 - (void)showSigninWithIdentity:(ChromeIdentity*)identity
                    promoAction:(signin_metrics::PromoAction)promoAction {
-  _signinPromoViewState = ios::SigninPromoViewState::UsedAtLeastOnce;
+  self.signinPromoViewState = ios::SigninPromoViewState::UsedAtLeastOnce;
   self.signinInProgress = YES;
   __weak SigninPromoViewMediator* weakSelf = self;
   ShowSigninCommandCompletionCallback completion = ^(BOOL succeeded) {
@@ -373,7 +560,7 @@
     ShowSigninCommand* command = [[ShowSigninCommand alloc]
         initWithOperation:AUTHENTICATION_OPERATION_SIGNIN
                  identity:identity
-              accessPoint:_accessPoint
+              accessPoint:self.accessPoint
               promoAction:promoAction
                  callback:completion];
     [self.presenter showSignin:command];
@@ -421,15 +608,15 @@
 - (void)signinPromoViewDidTapSigninWithNewAccount:
     (SigninPromoView*)signinPromoView {
   DCHECK(!_defaultIdentity);
-  DCHECK(_isSigninPromoViewVisible);
-  DCHECK(![self isInvalidClosedOrNeverVisible]);
+  DCHECK(self.signinPromoViewVisible);
+  DCHECK(!self.invalidClosedOrNeverVisible);
   [self sendImpressionsTillSigninButtonsHistogram];
   // On iOS, the promo does not have a button to add and account when there is
   // already an account on the device. That flow goes through the NOT_DEFAULT
   // promo instead. Always use the NO_EXISTING_ACCOUNT variant.
   signin_metrics::PromoAction promo_action =
       signin_metrics::PromoAction::PROMO_ACTION_NEW_ACCOUNT_NO_EXISTING_ACCOUNT;
-  signin_metrics::RecordSigninUserActionForAccessPoint(_accessPoint,
+  signin_metrics::RecordSigninUserActionForAccessPoint(self.accessPoint,
                                                        promo_action);
   [self showSigninWithIdentity:nil promoAction:promo_action];
 }
@@ -437,12 +624,12 @@
 - (void)signinPromoViewDidTapSigninWithDefaultAccount:
     (SigninPromoView*)signinPromoView {
   DCHECK(_defaultIdentity);
-  DCHECK(_isSigninPromoViewVisible);
-  DCHECK(![self isInvalidClosedOrNeverVisible]);
+  DCHECK(self.signinPromoViewVisible);
+  DCHECK(!self.invalidClosedOrNeverVisible);
   [self sendImpressionsTillSigninButtonsHistogram];
   signin_metrics::PromoAction promo_action =
       signin_metrics::PromoAction::PROMO_ACTION_WITH_DEFAULT;
-  signin_metrics::RecordSigninUserActionForAccessPoint(_accessPoint,
+  signin_metrics::RecordSigninUserActionForAccessPoint(self.accessPoint,
                                                        promo_action);
   [self showSigninWithIdentity:_defaultIdentity promoAction:promo_action];
 }
@@ -450,35 +637,35 @@
 - (void)signinPromoViewDidTapSigninWithOtherAccount:
     (SigninPromoView*)signinPromoView {
   DCHECK(_defaultIdentity);
-  DCHECK(_isSigninPromoViewVisible);
-  DCHECK(![self isInvalidClosedOrNeverVisible]);
+  DCHECK(self.signinPromoViewVisible);
+  DCHECK(!self.invalidClosedOrNeverVisible);
   [self sendImpressionsTillSigninButtonsHistogram];
   signin_metrics::PromoAction promo_action =
       signin_metrics::PromoAction::PROMO_ACTION_NOT_DEFAULT;
-  signin_metrics::RecordSigninUserActionForAccessPoint(_accessPoint,
+  signin_metrics::RecordSigninUserActionForAccessPoint(self.accessPoint,
                                                        promo_action);
   [self showSigninWithIdentity:nil promoAction:promo_action];
 }
 
 - (void)signinPromoViewCloseButtonWasTapped:(SigninPromoView*)view {
-  DCHECK(_isSigninPromoViewVisible);
-  DCHECK(![self isInvalidClosedOrNeverVisible]);
-  _signinPromoViewState = ios::SigninPromoViewState::Closed;
+  DCHECK(self.signinPromoViewVisible);
+  DCHECK(!self.invalidClosedOrNeverVisible);
+  self.signinPromoViewState = ios::SigninPromoViewState::Closed;
   const char* alreadySeenSigninViewPreferenceKey =
-      AlreadySeenSigninViewPreferenceKey(_accessPoint);
+      AlreadySeenSigninViewPreferenceKey(self.accessPoint);
   DCHECK(alreadySeenSigninViewPreferenceKey);
-  PrefService* prefs = _browserState->GetPrefs();
+  PrefService* prefs = self.browserState->GetPrefs();
   prefs->SetBoolean(alreadySeenSigninViewPreferenceKey, true);
   const char* displayedCountPreferenceKey =
-      DisplayedCountPreferenceKey(_accessPoint);
+      DisplayedCountPreferenceKey(self.accessPoint);
   if (displayedCountPreferenceKey) {
     int displayedCount = prefs->GetInteger(displayedCountPreferenceKey);
-    RecordImpressionsTilXButtonHistogramForAccessPoint(_accessPoint,
+    RecordImpressionsTilXButtonHistogramForAccessPoint(self.accessPoint,
                                                        displayedCount);
   }
-  if ([_consumer respondsToSelector:@selector
-                 (signinPromoViewMediatorCloseButtonWasTapped:)]) {
-    [_consumer signinPromoViewMediatorCloseButtonWasTapped:self];
+  if ([self.consumer respondsToSelector:@selector
+                     (signinPromoViewMediatorCloseButtonWasTapped:)]) {
+    [self.consumer signinPromoViewMediatorCloseButtonWasTapped:self];
   }
 }
 
diff --git a/ios/chrome/browser/ui/authentication/signin_promo_view_mediator_unittest.mm b/ios/chrome/browser/ui/authentication/signin_promo_view_mediator_unittest.mm
index fe3eb914..858a1e8 100644
--- a/ios/chrome/browser/ui/authentication/signin_promo_view_mediator_unittest.mm
+++ b/ios/chrome/browser/ui/authentication/signin_promo_view_mediator_unittest.mm
@@ -47,7 +47,7 @@
     // in the test.
     EXPECT_FALSE(ios::FakeChromeIdentityService::GetInstanceFromChromeProvider()
                      ->HasPendingCallback());
-    [mediator_ signinPromoViewRemoved];
+    [mediator_ signinPromoViewIsRemoved];
     EXPECT_EQ(ios::SigninPromoViewState::Invalid,
               mediator_.signinPromoViewState);
     mediator_ = nil;
@@ -267,13 +267,13 @@
 }
 
 // Tests the view state before and after calling -[SigninPromoViewMediator
-// signinPromoViewVisible].
+// signinPromoViewIsVisible].
 TEST_F(SigninPromoViewMediatorTest, SigninPromoViewStateVisible) {
   CreateMediator(signin_metrics::AccessPoint::ACCESS_POINT_RECENT_TABS);
   // Test initial state.
   EXPECT_EQ(ios::SigninPromoViewState::NeverVisible,
             mediator_.signinPromoViewState);
-  [mediator_ signinPromoViewVisible];
+  [mediator_ signinPromoViewIsVisible];
   // Test state once the sign-in promo view is visible.
   EXPECT_EQ(ios::SigninPromoViewState::Unused, mediator_.signinPromoViewState);
 }
@@ -281,7 +281,7 @@
 // Tests the view state while signing in.
 TEST_F(SigninPromoViewMediatorTest, SigninPromoViewStateSignedin) {
   CreateMediator(signin_metrics::AccessPoint::ACCESS_POINT_RECENT_TABS);
-  [mediator_ signinPromoViewVisible];
+  [mediator_ signinPromoViewIsVisible];
   __block ShowSigninCommandCompletionCallback completion;
   ShowSigninCommandCompletionCallback completion_arg =
       [OCMArg checkWithBlock:^BOOL(ShowSigninCommandCompletionCallback value) {
@@ -297,14 +297,14 @@
                                PROMO_ACTION_NEW_ACCOUNT_NO_EXISTING_ACCOUNT
                         completion:completion_arg]);
   [mediator_ signinPromoViewDidTapSigninWithNewAccount:signin_promo_view_];
-  EXPECT_TRUE(mediator_.isSigninInProgress);
+  EXPECT_TRUE(mediator_.signinInProgress);
   EXPECT_EQ(ios::SigninPromoViewState::UsedAtLeastOnce,
             mediator_.signinPromoViewState);
   EXPECT_NE(nil, (id)completion);
   // Stop sign-in.
   OCMExpect([consumer_ signinDidFinish]);
   completion(YES);
-  EXPECT_FALSE(mediator_.isSigninInProgress);
+  EXPECT_FALSE(mediator_.signinInProgress);
   EXPECT_EQ(ios::SigninPromoViewState::UsedAtLeastOnce,
             mediator_.signinPromoViewState);
 }
@@ -314,7 +314,7 @@
 TEST_F(SigninPromoViewMediatorTest,
        SigninPromoViewNoUpdateNotificationWhileSignin) {
   CreateMediator(signin_metrics::AccessPoint::ACCESS_POINT_RECENT_TABS);
-  [mediator_ signinPromoViewVisible];
+  [mediator_ signinPromoViewIsVisible];
   __block ShowSigninCommandCompletionCallback completion;
   ShowSigninCommandCompletionCallback completion_arg =
       [OCMArg checkWithBlock:^BOOL(ShowSigninCommandCompletionCallback value) {
@@ -345,7 +345,7 @@
        SigninPromoViewNoUpdateNotificationWhileSignin2) {
   AddDefaultIdentity();
   CreateMediator(signin_metrics::AccessPoint::ACCESS_POINT_RECENT_TABS);
-  [mediator_ signinPromoViewVisible];
+  [mediator_ signinPromoViewIsVisible];
   __block ShowSigninCommandCompletionCallback completion;
   ShowSigninCommandCompletionCallback completion_arg =
       [OCMArg checkWithBlock:^BOOL(ShowSigninCommandCompletionCallback value) {
diff --git a/ios/chrome/browser/ui/badges/badge_constants.h b/ios/chrome/browser/ui/badges/badge_constants.h
index 99db012..0fb1223b 100644
--- a/ios/chrome/browser/ui/badges/badge_constants.h
+++ b/ios/chrome/browser/ui/badges/badge_constants.h
@@ -16,4 +16,7 @@
 // A11y identifier for the Badge Popup Menu Table View.
 extern NSString* const kBadgePopupMenuTableViewAccessibilityIdentifier;
 
+// A11y identifier for the unread indicator above the displayed badge.
+extern NSString* const kBadgeUnreadIndicatorAccessibilityIdentifier;
+
 #endif  // IOS_CHROME_BROWSER_UI_BADGES_BADGE_CONSTANTS_H_
diff --git a/ios/chrome/browser/ui/badges/badge_constants.mm b/ios/chrome/browser/ui/badges/badge_constants.mm
index 803194f..a49b4b5 100644
--- a/ios/chrome/browser/ui/badges/badge_constants.mm
+++ b/ios/chrome/browser/ui/badges/badge_constants.mm
@@ -22,3 +22,6 @@
 
 NSString* const kBadgePopupMenuTableViewAccessibilityIdentifier =
     @"badgePopupMenuOverflowAXID";
+
+NSString* const kBadgeUnreadIndicatorAccessibilityIdentifier =
+    @"badgeUnreadIndicatorAXID";
diff --git a/ios/chrome/browser/ui/badges/badge_consumer.h b/ios/chrome/browser/ui/badges/badge_consumer.h
index e55d3830..a02638f 100644
--- a/ios/chrome/browser/ui/badges/badge_consumer.h
+++ b/ios/chrome/browser/ui/badges/badge_consumer.h
@@ -17,6 +17,9 @@
 // |displayedBadgeItem| and |fullscreenBadgeItem|.
 - (void)updateDisplayedBadge:(id<BadgeItem>)displayedBadgeItem
              fullScreenBadge:(id<BadgeItem>)fullscreenBadgeItem;
+// Notifies the consumer whether or not there are unread badges. See
+// BadgeStateRead for more information.
+- (void)markDisplayedBadgeAsRead:(BOOL)read;
 
 @end
 
diff --git a/ios/chrome/browser/ui/badges/badge_item.h b/ios/chrome/browser/ui/badges/badge_item.h
index 86287287..63f2f75 100644
--- a/ios/chrome/browser/ui/badges/badge_item.h
+++ b/ios/chrome/browser/ui/badges/badge_item.h
@@ -11,13 +11,18 @@
 
 // States for the InfobarBadge.
 typedef NS_OPTIONS(NSUInteger, BadgeState) {
-  // The badge is not accepted.
+  // The badge has not been accepted nor has it been read.
   BadgeStateNone = 0,
+  // This property is set if it is read (i.e. the menu is opened, if it is set
+  // as the displayed badge, or if the user has accepted the badge action).
+  // Not set if the user has not seen the badge yet (e.g. the badge is in the
+  // overflow menu and the user has yet to open the menu).
+  BadgeStateRead = 1 << 0,
   // The badge's banner is currently being presented.
-  BadgeStatePresented = 1 << 0,
+  BadgeStatePresented = 1 << 1,
   // The Infobar Badge is accepted. e.g. The Infobar was accepted/confirmed, and
   // the Infobar action has taken place.
-  BadgeStateAccepted = 1 << 1,
+  BadgeStateAccepted = 1 << 2,
 };
 
 // Holds properties and values the UI needs to configure a badge button.
diff --git a/ios/chrome/browser/ui/badges/badge_mediator.mm b/ios/chrome/browser/ui/badges/badge_mediator.mm
index 7586443..bd174f67 100644
--- a/ios/chrome/browser/ui/badges/badge_mediator.mm
+++ b/ios/chrome/browser/ui/badges/badge_mediator.mm
@@ -146,10 +146,14 @@
   // Get all non-fullscreen badges.
   for (id<BadgeItem> item in self.badges) {
     if (!item.fullScreen) {
+      // Mark each badge as read since the overflow menu is about to be
+      // displayed.
+      item.badgeState |= BadgeStateRead;
       [popupMenuBadges addObject:item];
     }
   }
   [self.dispatcher displayPopupMenuWithBadgeItems:popupMenuBadges];
+  [self updateConsumerReadStatus];
   // TODO(crbug.com/976901): Add metric for this action.
 }
 
@@ -178,6 +182,18 @@
 
 #pragma mark - Private
 
+// Directs consumer to update read status depending on the state of the
+// non-fullscreen badges.
+- (void)updateConsumerReadStatus {
+  for (id<BadgeItem> item in self.badges) {
+    if (!item.fullScreen && item.badgeState & BadgeStateRead) {
+      [self.consumer markDisplayedBadgeAsRead:NO];
+      return;
+    }
+  }
+  [self.consumer markDisplayedBadgeAsRead:YES];
+}
+
 // Gets the last fullscreen and non-fullscreen badges.
 // This assumes that there is only ever one fullscreen badge, so the last badge
 // in |badges| should be the only one.
@@ -198,7 +214,7 @@
     if (item.fullScreen) {
       fullScreenBadge = item;
     } else {
-      if (item.badgeState == BadgeStatePresented) {
+      if (item.badgeState & BadgeStatePresented) {
         presentingBadge = item;
       }
       displayedBadge = item;
@@ -218,9 +234,14 @@
                          ? presentingBadge
                          : [[BadgeTappableItem alloc]
                                initWithBadgeType:BadgeType::kBadgeTypeOverflow];
+  } else {
+    // Since there is only one non-fullscreen badge, it will be fixed as the
+    // displayed badge, so mark it as read.
+    displayedBadge.badgeState |= BadgeStateRead;
   }
   [self.consumer updateDisplayedBadge:displayedBadge
                       fullScreenBadge:fullScreenBadge];
+  [self updateConsumerReadStatus];
 }
 
 - (void)updateNewWebState:(web::WebState*)newWebState
diff --git a/ios/chrome/browser/ui/badges/badge_mediator_unittest.mm b/ios/chrome/browser/ui/badges/badge_mediator_unittest.mm
index e2e770914..f6545cc 100644
--- a/ios/chrome/browser/ui/badges/badge_mediator_unittest.mm
+++ b/ios/chrome/browser/ui/badges/badge_mediator_unittest.mm
@@ -59,6 +59,7 @@
 @interface FakeBadgeConsumer : NSObject <BadgeConsumer>
 @property(nonatomic, strong) id<BadgeItem> displayedBadge;
 @property(nonatomic, assign) BOOL hasIncognitoBadge;
+@property(nonatomic, assign) BOOL hasUnreadBadge;
 @end
 
 @implementation FakeBadgeConsumer
@@ -72,6 +73,9 @@
   self.hasIncognitoBadge = fullscreenBadgeItem != nil;
   self.displayedBadge = displayedBadgeItem;
 }
+- (void)markDisplayedBadgeAsRead:(BOOL)read {
+  self.hasUnreadBadge = !read;
+}
 @end
 
 class BadgeMediatorTest : public PlatformTest {
@@ -165,6 +169,15 @@
             BadgeType::kBadgeTypePasswordSave);
 }
 
+TEST_F(BadgeMediatorTest, BadgeMediatorTestMarkAsRead) {
+  AddAndActivateWebState(/*index=*/0, /*incognito=*/false);
+  AddInfobar();
+  AddSecondInfobar();
+  ASSERT_EQ(BadgeType::kBadgeTypeOverflow,
+            badge_consumer_.displayedBadge.badgeType);
+  EXPECT_TRUE(badge_consumer_.hasUnreadBadge);
+}
+
 // Test that the BadgeMediator updates the current badges to none when switching
 // to a second WebState after an infobar is added to the first WebState.
 TEST_F(BadgeMediatorTest, BadgeMediatorTestSwitchWebState) {
diff --git a/ios/chrome/browser/ui/badges/badge_view_controller.mm b/ios/chrome/browser/ui/badges/badge_view_controller.mm
index ccb00aa..fe2314bf 100644
--- a/ios/chrome/browser/ui/badges/badge_view_controller.mm
+++ b/ios/chrome/browser/ui/badges/badge_view_controller.mm
@@ -7,9 +7,11 @@
 #include "base/logging.h"
 #import "ios/chrome/browser/ui/badges/badge_button.h"
 #import "ios/chrome/browser/ui/badges/badge_button_factory.h"
+#import "ios/chrome/browser/ui/badges/badge_constants.h"
 #import "ios/chrome/browser/ui/badges/badge_item.h"
 #import "ios/chrome/browser/ui/elements/extended_touch_target_button.h"
 #import "ios/chrome/browser/ui/util/named_guide.h"
+#import "ios/chrome/common/colors/semantic_color_names.h"
 #import "ios/chrome/common/ui_util/constraints_ui_util.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
@@ -20,7 +22,14 @@
 
 // FullScreen progress threshold in which to toggle between full screen on and
 // off mode for the badge view.
-const double kFullScreenProgressThreshold = 0.85;
+const CGFloat kFullScreenProgressThreshold = 0.85;
+
+// Spacing between the top and trailing anchors of |unreadIndicatorView| and
+// |displayedBadge|.
+const CGFloat kUnreadIndicatorViewSpacing = 10.0;
+
+// Height of |unreadIndicatorView|.
+const CGFloat kUnreadIndicatorViewHeight = 6.0;
 
 }  // namespace
 
@@ -43,6 +52,10 @@
 // StackView holding the displayedBadge and fullScreenBadge.
 @property(nonatomic, strong) UIStackView* stackView;
 
+// View that displays a blue dot on the top-right corner of the displayed badge
+// if there are unread badges to be shown in the overflow menu.
+@property(nonatomic, strong) UIView* unreadIndicatorView;
+
 @end
 
 @implementation BadgeViewController
@@ -117,6 +130,37 @@
   }
 }
 
+- (void)markDisplayedBadgeAsRead:(BOOL)read {
+  // Lazy init if the unread indicator needs to be shown.
+  if (!self.unreadIndicatorView && !read) {
+    // Add unread indicator to the displayed badge.
+    self.unreadIndicatorView = [[UIView alloc] init];
+    self.unreadIndicatorView.layer.cornerRadius =
+        kUnreadIndicatorViewHeight / 2;
+    self.unreadIndicatorView.backgroundColor =
+        [UIColor colorNamed:kToolbarButtonColor];
+    self.unreadIndicatorView.translatesAutoresizingMaskIntoConstraints = NO;
+    self.unreadIndicatorView.accessibilityIdentifier =
+        kBadgeUnreadIndicatorAccessibilityIdentifier;
+    [_displayedBadge addSubview:self.unreadIndicatorView];
+    [NSLayoutConstraint activateConstraints:@[
+      [self.unreadIndicatorView.trailingAnchor
+          constraintEqualToAnchor:_displayedBadge.trailingAnchor
+                         constant:-kUnreadIndicatorViewSpacing],
+      [self.unreadIndicatorView.topAnchor
+          constraintEqualToAnchor:_displayedBadge.topAnchor
+                         constant:kUnreadIndicatorViewSpacing],
+      [self.unreadIndicatorView.heightAnchor
+          constraintEqualToConstant:kUnreadIndicatorViewHeight],
+      [self.unreadIndicatorView.heightAnchor
+          constraintEqualToAnchor:self.unreadIndicatorView.widthAnchor]
+    ]];
+  }
+  if (self.unreadIndicatorView) {
+    self.unreadIndicatorView.hidden = read;
+  }
+}
+
 #pragma mark FullscreenUIElement
 
 - (void)updateForFullscreenProgress:(CGFloat)progress {
@@ -149,6 +193,7 @@
   [_displayedBadge removeFromSuperview];
   if (!badgeButton) {
     _displayedBadge = nil;
+    self.unreadIndicatorView = nil;
     return;
   }
   _displayedBadge = badgeButton;
diff --git a/ios/chrome/browser/ui/bookmarks/bookmark_home_mediator.mm b/ios/chrome/browser/ui/bookmarks/bookmark_home_mediator.mm
index de671eb2..73e74ae 100644
--- a/ios/chrome/browser/ui/bookmarks/bookmark_home_mediator.mm
+++ b/ios/chrome/browser/ui/bookmarks/bookmark_home_mediator.mm
@@ -277,12 +277,13 @@
     [self.sharedState.tableViewModel
                         addItem:item
         toSectionWithIdentifier:BookmarkHomeSectionIdentifierPromo];
-    [mediator signinPromoViewVisible];
+    [mediator signinPromoViewIsVisible];
   } else {
-    if (![mediator isInvalidClosedOrNeverVisible]) {
+    if (!mediator.invalidClosedOrNeverVisible) {
       // When the sign-in view is closed, the promo state changes, but
-      // -[SigninPromoViewMediator signinPromoViewHidden] should not be called.
-      [mediator signinPromoViewHidden];
+      // -[SigninPromoViewMediator signinPromoViewIsHidden] should not be
+      // called.
+      [mediator signinPromoViewIsHidden];
     }
 
     DCHECK([self.sharedState.tableViewModel
diff --git a/ios/chrome/browser/ui/bookmarks/bookmark_promo_controller.mm b/ios/chrome/browser/ui/bookmarks/bookmark_promo_controller.mm
index 0ebe73b..dca6c695 100644
--- a/ios/chrome/browser/ui/bookmarks/bookmark_promo_controller.mm
+++ b/ios/chrome/browser/ui/bookmarks/bookmark_promo_controller.mm
@@ -67,7 +67,7 @@
 }
 
 - (void)dealloc {
-  [_signinPromoViewMediator signinPromoViewRemoved];
+  [_signinPromoViewMediator signinPromoViewIsRemoved];
 }
 
 - (void)hidePromoCell {
@@ -103,7 +103,7 @@
 
 // Called when a user signs into Google services such as sync.
 - (void)onPrimaryAccountSet:(const CoreAccountInfo&)primaryAccountInfo {
-  if (!self.signinPromoViewMediator.isSigninInProgress)
+  if (!self.signinPromoViewMediator.signinInProgress)
     self.shouldShowSigninPromo = NO;
 }
 
diff --git a/ios/chrome/browser/ui/bookmarks/cells/bookmark_home_promo_item.mm b/ios/chrome/browser/ui/bookmarks/cells/bookmark_home_promo_item.mm
index 1c6734e..1f08583 100644
--- a/ios/chrome/browser/ui/bookmarks/cells/bookmark_home_promo_item.mm
+++ b/ios/chrome/browser/ui/bookmarks/cells/bookmark_home_promo_item.mm
@@ -45,7 +45,7 @@
   [[mediator createConfigurator]
       configureSigninPromoView:signinPromoCell.signinPromoView];
   signinPromoCell.selectionStyle = UITableViewCellSelectionStyleNone;
-  [mediator signinPromoViewVisible];
+  [mediator signinPromoViewIsVisible];
 }
 
 @end
diff --git a/ios/chrome/browser/ui/browser_view/BUILD.gn b/ios/chrome/browser/ui/browser_view/BUILD.gn
index cfa78c6..27f483d 100644
--- a/ios/chrome/browser/ui/browser_view/BUILD.gn
+++ b/ios/chrome/browser/ui/browser_view/BUILD.gn
@@ -120,6 +120,7 @@
     "//ios/chrome/browser/ui/overscroll_actions",
     "//ios/chrome/browser/ui/page_info:coordinator",
     "//ios/chrome/browser/ui/page_info/requirements",
+    "//ios/chrome/browser/ui/passwords",
     "//ios/chrome/browser/ui/payments",
     "//ios/chrome/browser/ui/popup_menu",
     "//ios/chrome/browser/ui/presenters",
diff --git a/ios/chrome/browser/ui/browser_view/browser_coordinator.mm b/ios/chrome/browser/ui/browser_view/browser_coordinator.mm
index 0b69cf98..e3be0d4 100644
--- a/ios/chrome/browser/ui/browser_view/browser_coordinator.mm
+++ b/ios/chrome/browser/ui/browser_view/browser_coordinator.mm
@@ -35,6 +35,7 @@
 #import "ios/chrome/browser/ui/download/pass_kit_coordinator.h"
 #import "ios/chrome/browser/ui/open_in/open_in_mediator.h"
 #import "ios/chrome/browser/ui/page_info/page_info_legacy_coordinator.h"
+#import "ios/chrome/browser/ui/passwords/password_breach_coordinator.h"
 #import "ios/chrome/browser/ui/print/print_controller.h"
 #import "ios/chrome/browser/ui/qr_scanner/qr_scanner_legacy_coordinator.h"
 #import "ios/chrome/browser/ui/reading_list/reading_list_coordinator.h"
@@ -115,6 +116,10 @@
 // Coordinator for the PassKit UI presentation.
 @property(nonatomic, strong) PassKitCoordinator* passKitCoordinator;
 
+// Coordinator for the password breach UI presentation.
+@property(nonatomic, strong)
+    PasswordBreachCoordinator* passwordBreachCoordinator;
+
 // Used to display the Print UI. Nil if not visible.
 // TODO(crbug.com/910017): Convert to coordinator.
 @property(nonatomic, strong) PrintController* printController;
@@ -215,6 +220,8 @@
   [self.readingListCoordinator stop];
   self.readingListCoordinator = nil;
 
+  [self.passwordBreachCoordinator stop];
+
   [self.viewController clearPresentedStateWithCompletion:completion
                                           dismissOmnibox:dismissOmnibox];
 }
@@ -313,6 +320,9 @@
   self.passKitCoordinator = [[PassKitCoordinator alloc]
       initWithBaseViewController:self.viewController];
 
+  self.passwordBreachCoordinator = [[PasswordBreachCoordinator alloc]
+      initWithBaseViewController:self.viewController];
+
   self.printController = [[PrintController alloc]
       initWithContextGetter:self.browserState->GetRequestContext()];
 
@@ -361,6 +371,9 @@
   [self.passKitCoordinator stop];
   self.passKitCoordinator = nil;
 
+  [self.passwordBreachCoordinator stop];
+  self.passwordBreachCoordinator = nil;
+
   self.printController = nil;
 
   [self.qrScannerCoordinator stop];
diff --git a/ios/chrome/browser/ui/browser_view/browser_view_controller.mm b/ios/chrome/browser/ui/browser_view/browser_view_controller.mm
index cb501f03..ad3ac22 100644
--- a/ios/chrome/browser/ui/browser_view/browser_view_controller.mm
+++ b/ios/chrome/browser/ui/browser_view/browser_view_controller.mm
@@ -4757,6 +4757,10 @@
   return [self userAgentType] == web::UserAgentType::DESKTOP;
 }
 
+- (web::WebState*)webStateToReplace {
+  return self.currentWebState;
+}
+
 #pragma mark - NetExportTabHelperDelegate
 
 - (void)netExportTabHelper:(NetExportTabHelper*)tabHelper
diff --git a/ios/chrome/browser/ui/commands/application_commands.h b/ios/chrome/browser/ui/commands/application_commands.h
index b3fdbb4..bc2edd1 100644
--- a/ios/chrome/browser/ui/commands/application_commands.h
+++ b/ios/chrome/browser/ui/commands/application_commands.h
@@ -18,11 +18,15 @@
 // may also be forwarded directly to a settings navigation controller.
 @protocol ApplicationSettingsCommands
 
-// Shows the accounts settings UI, presenting from |baseViewController|.
+// TODO(crbug.com/779791) : Do not pass baseViewController through dispatcher.
+// Shows the accounts settings UI, presenting from |baseViewController|. If
+// |baseViewController| is nil BVC will be used as presenterViewController.
 - (void)showAccountsSettingsFromViewController:
     (UIViewController*)baseViewController;
 
+// TODO(crbug.com/779791) : Do not pass baseViewController through dispatcher.
 // Shows the Google services settings UI, presenting from |baseViewController|.
+// If |baseViewController| is nil BVC will be used as presenterViewController.
 - (void)showGoogleServicesSettingsFromViewController:
     (UIViewController*)baseViewController;
 
diff --git a/ios/chrome/browser/ui/content_suggestions/content_suggestions_header_view_controller.mm b/ios/chrome/browser/ui/content_suggestions/content_suggestions_header_view_controller.mm
index 4b78ef5..fe52ac3 100644
--- a/ios/chrome/browser/ui/content_suggestions/content_suggestions_header_view_controller.mm
+++ b/ios/chrome/browser/ui/content_suggestions/content_suggestions_header_view_controller.mm
@@ -366,7 +366,7 @@
 
 - (void)identityDiscTapped {
   base::RecordAction(base::UserMetricsAction("MobileNTPIdentityDiscTapped"));
-  [self.dispatcher showGoogleServicesSettingsFromViewController:self];
+  [self.dispatcher showGoogleServicesSettingsFromViewController:nil];
 }
 
 // TODO(crbug.com/807330) The fakebox is currently a collection of views spread
diff --git a/ios/chrome/browser/ui/passwords/BUILD.gn b/ios/chrome/browser/ui/passwords/BUILD.gn
new file mode 100644
index 0000000..b5f4c5f
--- /dev/null
+++ b/ios/chrome/browser/ui/passwords/BUILD.gn
@@ -0,0 +1,24 @@
+# Copyright 2019 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+source_set("passwords") {
+  configs += [ "//build/config/compiler:enable_arc" ]
+  sources = [
+    "password_breach_coordinator.h",
+    "password_breach_coordinator.mm",
+    "password_breach_view_controller.h",
+    "password_breach_view_controller.mm",
+  ]
+  deps = [
+    "resources",
+    "//base",
+    "//components/password_manager/core/browser",
+    "//ios/chrome/app/strings",
+    "//ios/chrome/browser/ui/coordinators:chrome_coordinators",
+    "//ios/chrome/common/colors",
+    "//ios/chrome/common/ui_util",
+    "//ui/base",
+  ]
+  libs = [ "UIKit.framework" ]
+}
diff --git a/ios/chrome/browser/ui/passwords/OWNERS b/ios/chrome/browser/ui/passwords/OWNERS
new file mode 100644
index 0000000..747ac7d3
--- /dev/null
+++ b/ios/chrome/browser/ui/passwords/OWNERS
@@ -0,0 +1,4 @@
+javierrobles@chromium.org
+
+# TEAM: ios-directory-owners@chromium.org
+# OS: iOS
\ No newline at end of file
diff --git a/ios/chrome/browser/ui/passwords/password_breach_coordinator.h b/ios/chrome/browser/ui/passwords/password_breach_coordinator.h
new file mode 100644
index 0000000..7f95a649
--- /dev/null
+++ b/ios/chrome/browser/ui/passwords/password_breach_coordinator.h
@@ -0,0 +1,16 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_CHROME_BROWSER_UI_PASSWORDS_PASSWORD_BREACH_COORDINATOR_H_
+#define IOS_CHROME_BROWSER_UI_PASSWORDS_PASSWORD_BREACH_COORDINATOR_H_
+
+#import "ios/chrome/browser/ui/coordinators/chrome_coordinator.h"
+
+// Presents and stops the Password Breach feature, which consists in alerting
+// the user that Chrome detected a leaked credential. In some scenarios it
+// prompts for a checkup of the stored passwords.
+@interface PasswordBreachCoordinator : ChromeCoordinator
+@end
+
+#endif  // IOS_CHROME_BROWSER_UI_PASSWORDS_PASSWORD_BREACH_COORDINATOR_H_
diff --git a/ios/chrome/browser/ui/passwords/password_breach_coordinator.mm b/ios/chrome/browser/ui/passwords/password_breach_coordinator.mm
new file mode 100644
index 0000000..d202e16a
--- /dev/null
+++ b/ios/chrome/browser/ui/passwords/password_breach_coordinator.mm
@@ -0,0 +1,38 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "ios/chrome/browser/ui/passwords/password_breach_coordinator.h"
+
+#import "ios/chrome/browser/ui/passwords/password_breach_view_controller.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+@interface PasswordBreachCoordinator ()
+
+// The main view controller for this coordinator.
+@property(nonatomic, strong) PasswordBreachViewController* viewController;
+
+@end
+
+@implementation PasswordBreachCoordinator
+
+- (void)start {
+  [super start];
+  self.viewController = [[PasswordBreachViewController alloc] init];
+  [self.baseViewController presentViewController:self.viewController
+                                        animated:YES
+                                      completion:nil];
+}
+
+- (void)stop {
+  [self.viewController.presentingViewController
+      dismissViewControllerAnimated:YES
+                         completion:nil];
+  self.viewController = nil;
+  [super stop];
+}
+
+@end
diff --git a/ios/chrome/browser/ui/passwords/password_breach_view_controller.h b/ios/chrome/browser/ui/passwords/password_breach_view_controller.h
new file mode 100644
index 0000000..328b6f5
--- /dev/null
+++ b/ios/chrome/browser/ui/passwords/password_breach_view_controller.h
@@ -0,0 +1,13 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_CHROME_BROWSER_UI_PASSWORDS_PASSWORD_BREACH_VIEW_CONTROLLER_H_
+#define IOS_CHROME_BROWSER_UI_PASSWORDS_PASSWORD_BREACH_VIEW_CONTROLLER_H_
+
+#import <UIKit/UIKit.h>
+
+@interface PasswordBreachViewController : UIViewController
+@end
+
+#endif  // IOS_CHROME_BROWSER_UI_PASSWORDS_PASSWORD_BREACH_VIEW_CONTROLLER_H_
diff --git a/ios/chrome/browser/ui/passwords/password_breach_view_controller.mm b/ios/chrome/browser/ui/passwords/password_breach_view_controller.mm
new file mode 100644
index 0000000..94248f6
--- /dev/null
+++ b/ios/chrome/browser/ui/passwords/password_breach_view_controller.mm
@@ -0,0 +1,120 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "ios/chrome/browser/ui/passwords/password_breach_view_controller.h"
+
+#include "base/strings/sys_string_conversions.h"
+#include "components/password_manager/core/browser/leak_detection_dialog_utils.h"
+#import "ios/chrome/common/colors/semantic_color_names.h"
+#import "ios/chrome/common/ui_util/constraints_ui_util.h"
+#include "ios/chrome/grit/ios_strings.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+using base::SysUTF16ToNSString;
+using password_manager::GetAcceptButtonLabel;
+using password_manager::GetCancelButtonLabel;
+using password_manager::GetDescription;
+using password_manager::GetTitle;
+using password_manager::CredentialLeakType;
+
+namespace {
+constexpr CGFloat kStackViewSpacing = 16.0;
+}  // namespace
+
+@implementation PasswordBreachViewController
+
+#pragma mark - Public
+
+- (void)viewDidLoad {
+  [super viewDidLoad];
+  self.view.backgroundColor = [UIColor colorNamed:kBackgroundColor];
+
+  // TODO(crbug.com/1008862): Pass these at init instead of using these empty
+  // ones.
+  CredentialLeakType leakType = CredentialLeakType();
+  GURL URL = GURL();
+
+  UIButton* doneButton = [UIButton buttonWithType:UIButtonTypeSystem];
+  [doneButton addTarget:self
+                 action:@selector(didTapDoneButton)
+       forControlEvents:UIControlEventTouchUpInside];
+  NSString* doneTitle = SysUTF16ToNSString(GetCancelButtonLabel());
+  [doneButton setTitle:doneTitle forState:UIControlStateNormal];
+  doneButton.translatesAutoresizingMaskIntoConstraints = NO;
+  [self.view addSubview:doneButton];
+
+  UIImage* image = [UIImage imageNamed:@"password_breach_illustration"];
+  UIImageView* imageView = [[UIImageView alloc] initWithImage:image];
+  imageView.contentMode = UIViewContentModeScaleAspectFit;
+  imageView.translatesAutoresizingMaskIntoConstraints = NO;
+
+  UILabel* title = [[UILabel alloc] init];
+  title.numberOfLines = 0;
+  title.font = [UIFont preferredFontForTextStyle:UIFontTextStyleHeadline];
+  title.textColor = [UIColor colorNamed:kTextPrimaryColor];
+  title.text = SysUTF16ToNSString(GetTitle(leakType));
+  title.translatesAutoresizingMaskIntoConstraints = NO;
+
+  UILabel* subtitle = [[UILabel alloc] init];
+  subtitle.numberOfLines = 0;
+  subtitle.textColor = [UIColor colorNamed:kTextSecondaryColor];
+  subtitle.text = SysUTF16ToNSString(GetDescription(leakType, URL));
+  subtitle.translatesAutoresizingMaskIntoConstraints = NO;
+
+  UIStackView* stackView = [[UIStackView alloc]
+      initWithArrangedSubviews:@[ imageView, title, subtitle ]];
+  stackView.axis = UILayoutConstraintAxisVertical;
+  stackView.alignment = UIStackViewAlignmentFill;
+  stackView.translatesAutoresizingMaskIntoConstraints = NO;
+  stackView.spacing = kStackViewSpacing;
+  [self.view addSubview:stackView];
+
+  UIButton* primaryActionButton = [UIButton buttonWithType:UIButtonTypeSystem];
+  [primaryActionButton addTarget:self
+                          action:@selector(didTapPrimaryActionButton)
+                forControlEvents:UIControlEventTouchUpInside];
+  NSString* primaryActionTitle =
+      SysUTF16ToNSString(GetAcceptButtonLabel(leakType));
+  [primaryActionButton setTitle:primaryActionTitle
+                       forState:UIControlStateNormal];
+  primaryActionButton.translatesAutoresizingMaskIntoConstraints = NO;
+  [self.view addSubview:primaryActionButton];
+
+  UILayoutGuide* margins = self.view.layoutMarginsGuide;
+
+  // Primary Action Button constraints.
+  AddSameConstraintsToSides(margins, doneButton,
+                            LayoutSides::kTrailing | LayoutSides::kTop);
+
+  // Stack View (and its contents) constraints.
+  CGFloat imageAspectRatio = image.size.width / image.size.height;
+  [imageView.widthAnchor constraintEqualToAnchor:imageView.heightAnchor
+                                      multiplier:imageAspectRatio]
+      .active = YES;
+  AddSameCenterConstraints(margins, stackView);
+  AddSameConstraintsToSides(margins, stackView,
+                            LayoutSides::kLeading | LayoutSides::kTrailing);
+
+  // Primary Action Button constraints.
+  AddSameConstraintsToSides(
+      margins, primaryActionButton,
+      LayoutSides::kLeading | LayoutSides::kTrailing | LayoutSides::kBottom);
+}
+
+#pragma mark - Private
+
+// Handle taps on the done button.
+- (void)didTapDoneButton {
+  // TODO(crbug.com/1008862): Hook up with a mediator.
+}
+
+// Handle taps on the primary action button.
+- (void)didTapPrimaryActionButton {
+  // TODO(crbug.com/1008862): Hook up with a mediator.
+}
+
+@end
diff --git a/ios/chrome/browser/ui/passwords/resources/BUILD.gn b/ios/chrome/browser/ui/passwords/resources/BUILD.gn
new file mode 100644
index 0000000..1cf482d9c
--- /dev/null
+++ b/ios/chrome/browser/ui/passwords/resources/BUILD.gn
@@ -0,0 +1,20 @@
+# Copyright 2019 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/config/ios/asset_catalog.gni")
+import("//build/config/ios/rules.gni")
+
+group("resources") {
+  deps = [
+    ":password_breach_illustration",
+  ]
+}
+
+imageset("password_breach_illustration") {
+  sources = [
+    "password_breach_illustration.imageset/Contents.json",
+    "password_breach_illustration.imageset/illustration_dark.png",
+    "password_breach_illustration.imageset/illustration_light.png",
+  ]
+}
diff --git a/ios/chrome/browser/ui/passwords/resources/password_breach_illustration.imageset/Contents.json b/ios/chrome/browser/ui/passwords/resources/password_breach_illustration.imageset/Contents.json
new file mode 100644
index 0000000..e5b1b4d6
--- /dev/null
+++ b/ios/chrome/browser/ui/passwords/resources/password_breach_illustration.imageset/Contents.json
@@ -0,0 +1,22 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "filename" : "illustration_light.png"
+    },
+    {
+      "idiom" : "universal",
+      "filename" : "illustration_dark.png",
+      "appearances" : [
+        {
+          "appearance" : "luminosity",
+          "value" : "dark"
+        }
+      ]
+    }
+  ],
+  "info" : {
+    "version" : 1,
+    "author" : "xcode"
+  }
+}
\ No newline at end of file
diff --git a/ios/chrome/browser/ui/passwords/resources/password_breach_illustration.imageset/illustration_dark.png b/ios/chrome/browser/ui/passwords/resources/password_breach_illustration.imageset/illustration_dark.png
new file mode 100644
index 0000000..5eb2ae9
--- /dev/null
+++ b/ios/chrome/browser/ui/passwords/resources/password_breach_illustration.imageset/illustration_dark.png
Binary files differ
diff --git a/ios/chrome/browser/ui/passwords/resources/password_breach_illustration.imageset/illustration_light.png b/ios/chrome/browser/ui/passwords/resources/password_breach_illustration.imageset/illustration_light.png
new file mode 100644
index 0000000..75f9b19
--- /dev/null
+++ b/ios/chrome/browser/ui/passwords/resources/password_breach_illustration.imageset/illustration_light.png
Binary files differ
diff --git a/ios/chrome/browser/ui/recent_tabs/recent_tabs_table_view_controller.mm b/ios/chrome/browser/ui/recent_tabs/recent_tabs_table_view_controller.mm
index d319c29..4bea287 100644
--- a/ios/chrome/browser/ui/recent_tabs/recent_tabs_table_view_controller.mm
+++ b/ios/chrome/browser/ui/recent_tabs/recent_tabs_table_view_controller.mm
@@ -141,7 +141,7 @@
 }
 
 - (void)dealloc {
-  [_signinPromoViewMediator signinPromoViewRemoved];
+  [_signinPromoViewMediator signinPromoViewIsRemoved];
 }
 
 - (void)viewDidLoad {
@@ -541,7 +541,7 @@
   if ((newSessionState == self.sessionState &&
        self.sessionState !=
            SessionsSyncUserState::USER_SIGNED_IN_SYNC_ON_WITH_SESSIONS) ||
-      self.signinPromoViewMediator.isSigninInProgress) {
+      self.signinPromoViewMediator.signinInProgress) {
     // No need to refresh the sections since all states other than
     // USER_SIGNED_IN_SYNC_ON_WITH_SESSIONS only have static content. This means
     // that if the previous State is the same as the new one the static content
@@ -576,7 +576,7 @@
   // table updates rely on knowing the previous state.
   self.sessionState = newSessionState;
   if (self.sessionState != SessionsSyncUserState::USER_SIGNED_OUT) {
-    [self.signinPromoViewMediator signinPromoViewRemoved];
+    [self.signinPromoViewMediator signinPromoViewIsRemoved];
     self.signinPromoViewMediator.consumer = nil;
     self.signinPromoViewMediator = nil;
   }
@@ -665,7 +665,7 @@
       [self.tableViewModel itemTypeForIndexPath:indexPath];
   // If SigninPromo will be shown, |self.signinPromoViewMediator| must know.
   if (itemTypeSelected == ItemTypeOtherDevicesSigninPromo) {
-    [self.signinPromoViewMediator signinPromoViewVisible];
+    [self.signinPromoViewMediator signinPromoViewIsVisible];
   }
   // Retrieve favicons for closed tabs and remote sessions.
   if (itemTypeSelected == ItemTypeRecentlyClosed ||
diff --git a/ios/chrome/browser/ui/settings/autofill/autofill_credit_card_edit_table_view_controller.mm b/ios/chrome/browser/ui/settings/autofill/autofill_credit_card_edit_table_view_controller.mm
index 5d96196..5c2b63c4 100644
--- a/ios/chrome/browser/ui/settings/autofill/autofill_credit_card_edit_table_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/autofill/autofill_credit_card_edit_table_view_controller.mm
@@ -303,9 +303,10 @@
   }
 }
 
-#pragma mark - SettingsRootTableViewController
+#pragma mark - UIAdaptivePresentationControllerDelegate
 
-- (BOOL)shouldDismissViewControllerBySwipeDown {
+- (BOOL)presentationControllerShouldDismiss:
+    (UIPresentationController*)presentationController {
   return !self.tableView.editing;
 }
 
diff --git a/ios/chrome/browser/ui/settings/autofill/autofill_edit_table_view_controller.mm b/ios/chrome/browser/ui/settings/autofill/autofill_edit_table_view_controller.mm
index 70a668a..1a5d379 100644
--- a/ios/chrome/browser/ui/settings/autofill/autofill_edit_table_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/autofill/autofill_edit_table_view_controller.mm
@@ -73,7 +73,10 @@
   return YES;
 }
 
-- (BOOL)shouldDismissViewControllerBySwipeDown {
+#pragma mark - UIAdaptivePresentationControllerDelegate
+
+- (BOOL)presentationControllerShouldDismiss:
+    (UIPresentationController*)presentationController {
   return !self.tableView.editing;
 }
 
diff --git a/ios/chrome/browser/ui/settings/clear_browsing_data/clear_browsing_data_table_view_controller.mm b/ios/chrome/browser/ui/settings/clear_browsing_data/clear_browsing_data_table_view_controller.mm
index b04fccb..0c50cc60 100644
--- a/ios/chrome/browser/ui/settings/clear_browsing_data/clear_browsing_data_table_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/clear_browsing_data/clear_browsing_data_table_view_controller.mm
@@ -334,12 +334,6 @@
   }
 }
 
-#pragma mark - SettingsRootTableViewController
-
-- (BOOL)shouldDismissViewControllerBySwipeDown {
-  return !self.chromeActivityOverlayCoordinator.started;
-}
-
 #pragma mark - TableViewTextLinkCellDelegate
 
 - (void)tableViewTextLinkCell:(TableViewTextLinkCell*)cell
@@ -436,8 +430,8 @@
 
 - (void)presentationControllerDidDismiss:
     (UIPresentationController*)presentationController {
-  // Call dismiss to clean up state and  stop the Coordinator.
-  [self dismiss];
+  // Call prepareForDismissal to clean up state and  stop the Coordinator.
+  [self prepareForDismissal];
 }
 
 - (BOOL)presentationControllerShouldDismiss:
diff --git a/ios/chrome/browser/ui/settings/settings_navigation_controller.mm b/ios/chrome/browser/ui/settings/settings_navigation_controller.mm
index 637e7f6d..2f546650 100644
--- a/ios/chrome/browser/ui/settings/settings_navigation_controller.mm
+++ b/ios/chrome/browser/ui/settings/settings_navigation_controller.mm
@@ -47,11 +47,13 @@
 @property(nonatomic, strong)
     GoogleServicesSettingsCoordinator* googleServicesSettingsCoordinator;
 
-// Current SettingsViewController being presented by this Navigation Controller.
+// Current UIViewController being presented by this Navigation Controller.
 // If nil it means the Navigation Controller is not presenting anything, or the
-// VC being presented is not a SettingsRootTableViewController.
+// VC being presented doesn't conform to
+// UIAdaptivePresentationControllerDelegate.
 @property(nonatomic, weak)
-    SettingsRootTableViewController* currentPresentedSettingsViewController;
+    UIViewController<UIAdaptivePresentationControllerDelegate>*
+        currentPresentedViewController;
 
 // The SettingsNavigationControllerDelegate for this NavigationController.
 @property(nonatomic, weak) id<SettingsNavigationControllerDelegate>
@@ -376,16 +378,53 @@
 
 - (BOOL)presentationControllerShouldDismiss:
     (UIPresentationController*)presentationController {
-  return [self.currentPresentedSettingsViewController
-              shouldDismissViewControllerBySwipeDown];
+  if (@available(iOS 13, *)) {
+    if ([self.currentPresentedViewController
+            respondsToSelector:@selector
+            (presentationControllerShouldDismiss:)]) {
+      return [self.currentPresentedViewController
+          presentationControllerShouldDismiss:presentationController];
+    }
+  }
+  return NO;
 }
 
 - (void)presentationControllerDidDismiss:
     (UIPresentationController*)presentationController {
+  if (@available(iOS 13, *)) {
+    if ([self.currentPresentedViewController
+            respondsToSelector:@selector(presentationControllerDidDismiss:)]) {
+      [self.currentPresentedViewController
+          presentationControllerDidDismiss:presentationController];
+    }
+  }
   // Call settingsWasDismissed to make sure any necessary cleanup is performed.
   [self.settingsNavigationDelegate settingsWasDismissed];
 }
 
+- (void)presentationControllerDidAttemptToDismiss:
+    (UIPresentationController*)presentationController {
+  if (@available(iOS 13, *)) {
+    if ([self.currentPresentedViewController
+            respondsToSelector:@selector
+            (presentationControllerDidAttemptToDismiss:)]) {
+      [self.currentPresentedViewController
+          presentationControllerDidAttemptToDismiss:presentationController];
+    }
+  }
+}
+
+- (void)presentationControllerWillDismiss:
+    (UIPresentationController*)presentationController {
+  if (@available(iOS 13, *)) {
+    if ([self.currentPresentedViewController
+            respondsToSelector:@selector(presentationControllerWillDismiss:)]) {
+      [self.currentPresentedViewController
+          presentationControllerWillDismiss:presentationController];
+    }
+  }
+}
+
 #pragma mark - Accessibility
 
 - (BOOL)accessibilityPerformEscape {
@@ -407,8 +446,9 @@
 - (void)navigationController:(UINavigationController*)navigationController
       willShowViewController:(UIViewController*)viewController
                     animated:(BOOL)animated {
-  self.currentPresentedSettingsViewController =
-      base::mac::ObjCCast<SettingsRootTableViewController>(viewController);
+  self.currentPresentedViewController = base::mac::ObjCCast<
+      UIViewController<UIAdaptivePresentationControllerDelegate>>(
+      viewController);
 }
 
 #pragma mark - UIResponder
diff --git a/ios/chrome/browser/ui/settings/settings_root_table_view_controller.h b/ios/chrome/browser/ui/settings/settings_root_table_view_controller.h
index e70a6e2..86ff7a0 100644
--- a/ios/chrome/browser/ui/settings/settings_root_table_view_controller.h
+++ b/ios/chrome/browser/ui/settings/settings_root_table_view_controller.h
@@ -19,7 +19,8 @@
 // AppBar.
 @interface SettingsRootTableViewController
     : ChromeTableViewController <SettingsRootViewControlling,
-                                 TableViewLinkHeaderFooterItemDelegate>
+                                 TableViewLinkHeaderFooterItemDelegate,
+                                 UIAdaptivePresentationControllerDelegate>
 
 // Delete button for the toolbar.
 @property(nonatomic, strong, readonly) UIBarButtonItem* deleteButton;
@@ -81,11 +82,6 @@
 // * Removes the transparent veil.
 - (void)allowUserInteraction;
 
-// Returns YES. Subclasses can override to prevent a swipe down dismissal. This
-// is useful when the ViewController contains editable fields and accidental
-// dismissals want to be avoided.
-- (BOOL)shouldDismissViewControllerBySwipeDown;
-
 @end
 
 #endif  // IOS_CHROME_BROWSER_UI_SETTINGS_SETTINGS_ROOT_TABLE_VIEW_CONTROLLER_H_
diff --git a/ios/chrome/browser/ui/settings/settings_root_table_view_controller.mm b/ios/chrome/browser/ui/settings/settings_root_table_view_controller.mm
index 5a98c85..1fbc3c0f 100644
--- a/ios/chrome/browser/ui/settings/settings_root_table_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/settings_root_table_view_controller.mm
@@ -353,7 +353,10 @@
   self.savedBarButtonItemPosition = kUndefinedBarButtonItemPosition;
 }
 
-- (BOOL)shouldDismissViewControllerBySwipeDown {
+#pragma mark - UIAdaptivePresentationControllerDelegate
+
+- (BOOL)presentationControllerShouldDismiss:
+    (UIPresentationController*)presentationController {
   return YES;
 }
 
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 2c5a9ed..b7267ac 100644
--- a/ios/chrome/browser/ui/settings/settings_table_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/settings_table_view_controller.mm
@@ -368,7 +368,7 @@
         _signinPromoViewMediator.consumer = self;
       }
     } else {
-      [_signinPromoViewMediator signinPromoViewRemoved];
+      [_signinPromoViewMediator signinPromoViewIsRemoved];
       _signinPromoViewMediator = nil;
     }
     [model addItem:[self signInTextItem]
@@ -377,7 +377,7 @@
     // Account section
     [model addSectionWithIdentifier:SectionIdentifierAccount];
     _hasRecordedSigninImpression = NO;
-    [_signinPromoViewMediator signinPromoViewRemoved];
+    [_signinPromoViewMediator signinPromoViewIsRemoved];
     _signinPromoViewMediator = nil;
     [model addItem:[self accountCellItem]
         toSectionWithIdentifier:SectionIdentifierAccount];
@@ -456,7 +456,7 @@
     signinPromoItem.configurator =
         [_signinPromoViewMediator createConfigurator];
     signinPromoItem.delegate = _signinPromoViewMediator;
-    [_signinPromoViewMediator signinPromoViewVisible];
+    [_signinPromoViewMediator signinPromoViewIsVisible];
     return signinPromoItem;
   }
   if (!_hasRecordedSigninImpression) {
@@ -1096,7 +1096,7 @@
   _googleServicesSettingsCoordinator = nil;
   _settingsHasBeenDismissed = YES;
   [self.signinInteractionCoordinator cancel];
-  [_signinPromoViewMediator signinPromoViewRemoved];
+  [_signinPromoViewMediator signinPromoViewIsRemoved];
   _signinPromoViewMediator = nil;
   [self stopBrowserStateServiceObservers];
 }
diff --git a/ios/chrome/browser/ui/settings/sync/sync_encryption_passphrase_table_view_controller.mm b/ios/chrome/browser/ui/settings/sync/sync_encryption_passphrase_table_view_controller.mm
index a49bf946..8a9a5ab 100644
--- a/ios/chrome/browser/ui/settings/sync/sync_encryption_passphrase_table_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/sync/sync_encryption_passphrase_table_view_controller.mm
@@ -189,7 +189,10 @@
       forSectionWithIdentifier:SectionIdentifierPassphrase];
 }
 
-- (BOOL)shouldDismissViewControllerBySwipeDown {
+#pragma mark - UIAdaptivePresentationControllerDelegate
+
+- (BOOL)presentationControllerShouldDismiss:
+    (UIPresentationController*)presentationController {
   return ![passphrase_.text length];
 }
 
diff --git a/ios/chrome/browser/web/font_size_js_unittest.mm b/ios/chrome/browser/web/font_size_js_unittest.mm
index b9426d71..f1d29937e 100644
--- a/ios/chrome/browser/web/font_size_js_unittest.mm
+++ b/ios/chrome/browser/web/font_size_js_unittest.mm
@@ -59,16 +59,8 @@
 
 // Tests that __gCrWeb.accessibility.adjustFontSize works for any scale.
 TEST_F(FontSizeJsTest, TestAdjustFontSizeForScale) {
-  // TODO(crbug.com/983776): This test fails when compiled with Xcode 10 and
-  // running on iOS 13 because expected font size and actual font size don't
-  // match. Re-enable this test when Xcode 11 is used for compiling.
-#if !defined(__IPHONE_13_0) || (__IPHONE_OS_VERSION_MAX_ALLOWED < __IPHONE_13_0)
-  if (base::ios::IsRunningOnIOS13OrLater()) {
-    return;
-  }
-#endif
-  // TODO(crbug.com/983776): This test also appears to be generally broken on
-  // iPads with beta 5.  It appears to be a simulator bug.  Re-enable on beta 6.
+  // TODO(crbug.com/983776): This test fails on ipad since beta5 due to a
+  // simulator bug. Re-enable this once the bug is fixed.
   if (base::ios::IsRunningOnIOS13OrLater() &&
       ui::GetDeviceFormFactor() == ui::DEVICE_FORM_FACTOR_TABLET) {
     return;
@@ -188,16 +180,8 @@
 
 // Tests that __gCrWeb.accessibility.adjustFontSize works for any CSS unit.
 TEST_F(FontSizeJsTest, TestAdjustFontSizeForUnit) {
-  // TODO(crbug.com/983776): This test fails when compiled with Xcode 10 and
-  // running on iOS 13 because expected font size and actual font size don't
-  // match. Re-enable this test when Xcode 11 is used for compiling.
-#if !defined(__IPHONE_13_0) || (__IPHONE_OS_VERSION_MAX_ALLOWED < __IPHONE_13_0)
-  if (base::ios::IsRunningOnIOS13OrLater()) {
-    return;
-  }
-#endif
-  // TODO(crbug.com/983776): This test also appears to be generally broken on
-  // iPads with beta 5.  It appears to be a simulator bug.  Re-enable on beta 6.
+  // TODO(crbug.com/983776): This test fails on ipad since beta5 due to a
+  // simulator bug. Re-enable this once the bug is fixed.
   if (base::ios::IsRunningOnIOS13OrLater() &&
       ui::GetDeviceFormFactor() == ui::DEVICE_FORM_FACTOR_TABLET) {
     return;
@@ -269,16 +253,8 @@
 
 // Tests that __gCrWeb.accessibility.adjustFontSize works for nested elements.
 TEST_F(FontSizeJsTest, TestAdjustFontSizeForNestedElements) {
-  // TODO(crbug.com/983776): This test fails when compiled with Xcode 10 and
-  // running on iOS 13 because expected font size and actual font size don't
-  // match. Re-enable this test when Xcode 11 is used for compiling.
-#if !defined(__IPHONE_13_0) || (__IPHONE_OS_VERSION_MAX_ALLOWED < __IPHONE_13_0)
-  if (base::ios::IsRunningOnIOS13OrLater()) {
-    return;
-  }
-#endif
-  // TODO(crbug.com/983776): This test also appears to be generally broken on
-  // iPads with beta 5.  It appears to be a simulator bug.  Re-enable on beta 6.
+  // TODO(crbug.com/983776): This test fails on ipad since beta5 due to a
+  // simulator bug. Re-enable this once the bug is fixed.
   if (base::ios::IsRunningOnIOS13OrLater() &&
       ui::GetDeviceFormFactor() == ui::DEVICE_FORM_FACTOR_TABLET) {
     return;
diff --git a/ios/showcase/badges/sc_badge_coordinator.mm b/ios/showcase/badges/sc_badge_coordinator.mm
index ba71dee7d..6441788 100644
--- a/ios/showcase/badges/sc_badge_coordinator.mm
+++ b/ios/showcase/badges/sc_badge_coordinator.mm
@@ -81,6 +81,7 @@
       initWithBadgeType:BadgeType::kBadgeTypeOverflow];
   [self.consumer setupWithDisplayedBadge:displayedBadge
                          fullScreenBadge:incognitoItem];
+  [self.consumer markDisplayedBadgeAsRead:NO];
 }
 
 @end
@@ -121,6 +122,7 @@
       initWithInfobarType:InfobarType::kInfobarTypePasswordSave] ];
   [self.badgePopupMenuCoordinator setBadgeItemsToShow:badgeItems];
   [self.badgePopupMenuCoordinator start];
+  [self.consumer markDisplayedBadgeAsRead:YES];
 }
 
 @end
diff --git a/ios/showcase/badges/sc_badge_egtest.mm b/ios/showcase/badges/sc_badge_egtest.mm
index f008253..ff5d7413 100644
--- a/ios/showcase/badges/sc_badge_egtest.mm
+++ b/ios/showcase/badges/sc_badge_egtest.mm
@@ -59,12 +59,17 @@
                                           kSCDisplayedBadgeToggleButton)]
       performAction:grey_tap()];
 
-  // Assert that overflow badge is shown and tap on it.
+  // Assert that overflow badge and the unread indicator is shown and tap on it.
   [[EarlGrey selectElementWithMatcher:
                  grey_allOf(grey_accessibilityID(
                                 kBadgeButtonOverflowAccessibilityIdentifier),
                             grey_sufficientlyVisible(), nil)]
       assertWithMatcher:grey_sufficientlyVisible()];
+  [[EarlGrey selectElementWithMatcher:
+                 grey_allOf(grey_accessibilityID(
+                                kBadgeUnreadIndicatorAccessibilityIdentifier),
+                            grey_sufficientlyVisible(), nil)]
+      assertWithMatcher:grey_sufficientlyVisible()];
   [[EarlGrey
       selectElementWithMatcher:grey_accessibilityID(
                                    kBadgeButtonOverflowAccessibilityIdentifier)]
@@ -77,6 +82,18 @@
                          kBadgePopupMenuTableViewAccessibilityIdentifier),
                      grey_sufficientlyVisible(), nil)]
       assertWithMatcher:grey_sufficientlyVisible()];
+
+  // Dismiss popup menu by tapping outside of the menu. Tapping the displayed
+  // badge is sufficient here. Assert that the unread indicator is not there
+  // anymore.
+  [[EarlGrey selectElementWithMatcher:grey_accessibilityID(
+                                          kSCDisplayedBadgeToggleButton)]
+      performAction:grey_tap()];
+  [[EarlGrey selectElementWithMatcher:
+                 grey_allOf(grey_accessibilityID(
+                                kBadgeUnreadIndicatorAccessibilityIdentifier),
+                            grey_sufficientlyVisible(), nil)]
+      assertWithMatcher:grey_notVisible()];
 }
 
 @end
diff --git a/ios/web_view/internal/sync/cwv_sync_controller.mm b/ios/web_view/internal/sync/cwv_sync_controller.mm
index 33f7586..f1112d4 100644
--- a/ios/web_view/internal/sync/cwv_sync_controller.mm
+++ b/ios/web_view/internal/sync/cwv_sync_controller.mm
@@ -178,7 +178,8 @@
 #pragma mark - Public Methods
 
 - (BOOL)isPassphraseNeeded {
-  return _syncService->GetUserSettings()->IsPassphraseRequiredForDecryption();
+  return _syncService->GetUserSettings()
+      ->IsPassphraseRequiredForPreferredDataTypes();
 }
 
 - (BOOL)isConsentNeeded {
diff --git a/ios/web_view/internal/sync/cwv_sync_controller_unittest.mm b/ios/web_view/internal/sync/cwv_sync_controller_unittest.mm
index e3bcad2e..b681d2b 100644
--- a/ios/web_view/internal/sync/cwv_sync_controller_unittest.mm
+++ b/ios/web_view/internal/sync/cwv_sync_controller_unittest.mm
@@ -233,7 +233,7 @@
 // Verifies CWVSyncController's passphrase API.
 TEST_F(CWVSyncControllerTest, Passphrase) {
   EXPECT_CALL(*mock_sync_service()->GetMockUserSettings(),
-              IsPassphraseRequiredForDecryption())
+              IsPassphraseRequiredForPreferredDataTypes())
       .WillOnce(Return(true));
   EXPECT_TRUE(sync_controller_.passphraseNeeded);
   EXPECT_CALL(*mock_sync_service()->GetMockUserSettings(),
diff --git a/ipc/ipc_message_start.h b/ipc/ipc_message_start.h
index 93bc399..9fa28c6 100644
--- a/ipc/ipc_message_start.h
+++ b/ipc/ipc_message_start.h
@@ -24,10 +24,7 @@
   GpuChannelMsgStart,
   MediaMsgStart,
   PpapiMsgStart,
-  DOMStorageMsgStart,
   ResourceMsgStart,
-  BlobMsgStart,
-  MidiMsgStart,
   ChromeMsgStart,
   DragMsgStart,
   PrintMsgStart,
@@ -40,10 +37,8 @@
   BrowserPluginMsgStart,
   AndroidWebViewMsgStart,
   MediaPlayerMsgStart,
-  TracingMsgStart,
   PeerConnectionTrackerMsgStart,
   WebRtcLoggingMsgStart,
-  TtsMsgStart,
   NaClHostMsgStart,
   EncryptedMediaMsgStart,
   CastMsgStart,
diff --git a/media/gpu/v4l2/v4l2_slice_video_decode_accelerator.cc b/media/gpu/v4l2/v4l2_slice_video_decode_accelerator.cc
index 496c819..64e33c7 100644
--- a/media/gpu/v4l2/v4l2_slice_video_decode_accelerator.cc
+++ b/media/gpu/v4l2/v4l2_slice_video_decode_accelerator.cc
@@ -1375,9 +1375,12 @@
   DCHECK(output_wait_map_.empty());
   output_buffer_map_.resize(buffers.size());
 
-  if (image_processor_device_ && !CreateImageProcessor()) {
-    NOTIFY_ERROR(PLATFORM_FAILURE);
-    return;
+  // In import mode we will create the IP when importing the first buffer.
+  if (image_processor_device_ && output_mode_ == Config::OutputMode::ALLOCATE) {
+    if (!CreateImageProcessor()) {
+      NOTIFY_ERROR(PLATFORM_FAILURE);
+      return;
+    }
   }
 
   // Reserve all buffers until ImportBufferForPictureTask() is called
@@ -1426,8 +1429,11 @@
         }
       }
 
-      ImportBufferForPictureTask(output_record.picture_id,
-                                 std::move(passed_dmabuf_fds));
+      int plane_horiz_bits_per_pixel = VideoFrame::PlaneHorizontalBitsPerPixel(
+          V4L2Device::V4L2PixFmtToVideoPixelFormat(gl_image_format_fourcc_), 0);
+      ImportBufferForPictureTask(
+          output_record.picture_id, std::move(passed_dmabuf_fds),
+          gl_image_size_.width() * plane_horiz_bits_per_pixel / 8);
     }  // else we'll get triggered via ImportBufferForPicture() from client.
     DVLOGF(3) << "buffer[" << i << "]: picture_id=" << output_record.picture_id;
   }
@@ -1544,12 +1550,14 @@
     dmabuf_fds.pop_back();
   }
 
-  ImportBufferForPictureTask(picture_buffer_id, std::move(dmabuf_fds));
+  ImportBufferForPictureTask(picture_buffer_id, std::move(dmabuf_fds),
+                             handle.planes[0].stride);
 }
 
 void V4L2SliceVideoDecodeAccelerator::ImportBufferForPictureTask(
     int32_t picture_buffer_id,
-    std::vector<base::ScopedFD> passed_dmabuf_fds) {
+    std::vector<base::ScopedFD> passed_dmabuf_fds,
+    int32_t stride) {
   DVLOGF(3) << "picture_buffer_id=" << picture_buffer_id;
   DCHECK(decoder_thread_task_runner_->BelongsToCurrentThread());
 
@@ -1577,6 +1585,34 @@
     return;
   }
 
+  // TODO(crbug.com/982172): This must be done in AssignPictureBuffers().
+  // However the size of PictureBuffer might not be adjusted by ARC++. So we
+  // keep this until ARC++ side is fixed.
+  int plane_horiz_bits_per_pixel = VideoFrame::PlaneHorizontalBitsPerPixel(
+      V4L2Device::V4L2PixFmtToVideoPixelFormat(gl_image_format_fourcc_), 0);
+  if (plane_horiz_bits_per_pixel == 0 ||
+      (stride * 8) % plane_horiz_bits_per_pixel != 0) {
+    VLOGF(1) << "Invalid format " << gl_image_format_fourcc_ << " or stride "
+             << stride;
+    NOTIFY_ERROR(INVALID_ARGUMENT);
+    return;
+  }
+  int adjusted_coded_width = stride * 8 / plane_horiz_bits_per_pixel;
+  if (image_processor_device_ && !image_processor_) {
+    DCHECK_EQ(kAwaitingPictureBuffers, state_);
+    // This is the first buffer import. Create the image processor and change
+    // the decoder state. The client may adjust the coded width. We don't have
+    // the final coded size in AssignPictureBuffers yet. Use the adjusted coded
+    // width to create the image processor.
+    DVLOGF(3) << "Original gl_image_size=" << gl_image_size_.ToString()
+              << ", adjusted coded width=" << adjusted_coded_width;
+    DCHECK_GE(adjusted_coded_width, gl_image_size_.width());
+    gl_image_size_.set_width(adjusted_coded_width);
+    if (!CreateImageProcessor())
+      return;
+  }
+  DCHECK_EQ(gl_image_size_.width(), adjusted_coded_width);
+
   // If in import mode, build output_frame from the passed DMABUF FDs.
   if (output_mode_ == Config::OutputMode::IMPORT) {
     DCHECK_EQ(gl_image_planes_count_, passed_dmabuf_fds.size());
diff --git a/media/gpu/v4l2/v4l2_slice_video_decode_accelerator.h b/media/gpu/v4l2/v4l2_slice_video_decode_accelerator.h
index 73e0fab..685837b 100644
--- a/media/gpu/v4l2/v4l2_slice_video_decode_accelerator.h
+++ b/media/gpu/v4l2/v4l2_slice_video_decode_accelerator.h
@@ -265,10 +265,11 @@
 
   // Use buffer backed by dmabuf file descriptors in |passed_dmabuf_fds| for the
   // OutputRecord associated with |picture_buffer_id|, taking ownership of the
-  // file descriptors.
-  void ImportBufferForPictureTask(
-      int32_t picture_buffer_id,
-      std::vector<base::ScopedFD> passed_dmabuf_fds);
+  // file descriptors. |stride| is the number of bytes from one row of pixels
+  // to the next row.
+  void ImportBufferForPictureTask(int32_t picture_buffer_id,
+                                  std::vector<base::ScopedFD> passed_dmabuf_fds,
+                                  int32_t stride);
 
   // Check that |planes| and |dmabuf_fds| are valid in import mode and call
   // ImportBufferForPictureTask.
diff --git a/net/socket/ssl_client_socket_impl.cc b/net/socket/ssl_client_socket_impl.cc
index 179e4a3..99ff309 100644
--- a/net/socket/ssl_client_socket_impl.cc
+++ b/net/socket/ssl_client_socket_impl.cc
@@ -1009,6 +1009,9 @@
 }
 
 int SSLClientSocketImpl::DoHandshakeComplete(int result) {
+  if (in_confirm_handshake_)
+    MaybeRecordEarlyDataResult();
+
   if (result < 0)
     return result;
 
@@ -1411,6 +1414,8 @@
       DCHECK_NE(kSSLClientSocketNoPendingResult, signature_result_);
       pending_read_error_ = ERR_IO_PENDING;
     } else {
+      if (pending_read_ssl_error_ == SSL_ERROR_EARLY_DATA_REJECTED)
+        MaybeRecordEarlyDataResult();
       pending_read_error_ = MapLastOpenSSLError(
           pending_read_ssl_error_, err_tracer, &pending_read_error_info_);
     }
@@ -1428,6 +1433,8 @@
     // next call of DoPayloadRead.
     rv = total_bytes_read;
 
+    MaybeRecordEarlyDataResult();
+
     // Do not treat insufficient data as an error to return in the next call to
     // DoPayloadRead() - instead, let the call fall through to check SSL_read()
     // again. The transport may have data available by then.
@@ -1834,6 +1841,22 @@
                             negotiated_protocol_, kProtoLast + 1);
 }
 
+void SSLClientSocketImpl::MaybeRecordEarlyDataResult() {
+  DCHECK(ssl_);
+  if (!ssl_config_.early_data_enabled || recorded_early_data_result_)
+    return;
+
+  recorded_early_data_result_ = true;
+  // Since the two-parameter version of the macro (which asks for a max
+  // value) requires that the max value sentinel be named |kMaxValue|,
+  // transform the max-value sentinel into a one-past-the-end ("boundary")
+  // sentinel by adding 1, in order to be able to use the three-parameter
+  // macro.
+  UMA_HISTOGRAM_ENUMERATION("Net.SSLHandshakeEarlyDataReason",
+                            SSL_get_early_data_reason(ssl_.get()),
+                            ssl_early_data_reason_max_value + 1);
+}
+
 int SSLClientSocketImpl::MapLastOpenSSLError(
     int ssl_error,
     const crypto::OpenSSLErrStackTracer& tracer,
diff --git a/net/socket/ssl_client_socket_impl.h b/net/socket/ssl_client_socket_impl.h
index 85f0ec7..789b420 100644
--- a/net/socket/ssl_client_socket_impl.h
+++ b/net/socket/ssl_client_socket_impl.h
@@ -200,6 +200,11 @@
   // in a UMA histogram.
   void RecordNegotiatedProtocol() const;
 
+  // Records the result of a handshake where early data was requested
+  // in the corresponding UMA histogram.  This will happen at most once
+  // during the lifetime of the socket.
+  void MaybeRecordEarlyDataResult();
+
   // Returns the net error corresponding to the most recent OpenSSL
   // error. ssl_error is the output of SSL_get_error.
   int MapLastOpenSSLError(int ssl_error,
@@ -219,6 +224,10 @@
   int user_write_buf_len_;
   bool first_post_handshake_write_ = true;
 
+  // True if we've already recorded the result of our attempt to
+  // use early data.
+  bool recorded_early_data_result_ = false;
+
   // Used by DoPayloadRead() when attempting to fill the caller's buffer with
   // as much data as possible without blocking.
   // If DoPayloadRead() encounters an error after having read some data, stores
diff --git a/net/socket/ssl_client_socket_unittest.cc b/net/socket/ssl_client_socket_unittest.cc
index c9fefcb5..12f340b 100644
--- a/net/socket/ssl_client_socket_unittest.cc
+++ b/net/socket/ssl_client_socket_unittest.cc
@@ -87,6 +87,7 @@
 #include "third_party/boringssl/src/include/openssl/bio.h"
 #include "third_party/boringssl/src/include/openssl/evp.h"
 #include "third_party/boringssl/src/include/openssl/pem.h"
+#include "third_party/boringssl/src/include/openssl/ssl.h"
 #include "url/gurl.h"
 #include "url/origin.h"
 
@@ -830,6 +831,11 @@
     return embedded_test_server_.get();
   }
 
+  void SetServerConfig(SSLServerConfig server_config) {
+    embedded_test_server()->ResetSSLConfig(net::EmbeddedTestServer::CERT_OK,
+                                           server_config);
+  }
+
   // The SpawnedTestServer object, after calling StartTestServer().
   const SpawnedTestServer* spawned_test_server() const {
     return spawned_test_server_.get();
@@ -1355,11 +1361,6 @@
     server->RegisterRequestHandler(base::BindRepeating(&HandleZeroRTTRequest));
   }
 
-  void SetServerConfig(SSLServerConfig server_config) {
-    embedded_test_server()->ResetSSLConfig(net::EmbeddedTestServer::CERT_OK,
-                                           server_config);
-  }
-
   FakeBlockingStreamSocket* MakeClient(bool early_data_enabled) {
     SSLConfig ssl_config;
     ssl_config.early_data_enabled = early_data_enabled;
@@ -5787,6 +5788,142 @@
   }
 }
 
+TEST_F(SSLClientSocketTest, EarlyDataReason) {
+  const char kReasonHistogram[] = "Net.SSLHandshakeEarlyDataReason";
+
+  // Enable all test features in the server.
+  SSLServerConfig server_config;
+  server_config.version_max = SSL_PROTOCOL_VERSION_TLS1_3;
+  server_config.early_data_enabled = true;
+  ASSERT_TRUE(
+      StartEmbeddedTestServer(EmbeddedTestServer::CERT_OK, server_config));
+
+  SSLContextConfig client_context_config;
+  client_context_config.version_max = SSL_PROTOCOL_VERSION_TLS1_3;
+  ssl_config_service_->UpdateSSLConfigAndNotify(client_context_config);
+
+  SSLConfig client_config;
+  client_config.early_data_enabled = true;
+
+  // Make the initial connection.
+  {
+    base::HistogramTester histograms;
+    int rv;
+    ASSERT_TRUE(CreateAndConnectSSLClientSocket(client_config, &rv));
+    EXPECT_THAT(rv, IsOk());
+
+    // Sanity check
+    SSLInfo info;
+    ASSERT_TRUE(sock_->GetSSLInfo(&info));
+    EXPECT_EQ(SSLInfo::HANDSHAKE_FULL, info.handshake_type);
+
+    // TLS 1.2 with False Start and TLS 1.3 cause the ticket to arrive later, so
+    // use the socket to ensure the session ticket has been picked up.
+    EXPECT_THAT(MakeHTTPRequest(sock_.get()), IsOk());
+
+    histograms.ExpectUniqueSample(kReasonHistogram,
+                                  ssl_early_data_no_session_offered, 1);
+  }
+
+  // Make a resumption connection.
+  {
+    int rv;
+    ASSERT_TRUE(CreateAndConnectSSLClientSocket(client_config, &rv));
+    EXPECT_THAT(rv, IsOk());
+
+    // Sanity check
+    SSLInfo info;
+    ASSERT_TRUE(sock_->GetSSLInfo(&info));
+    EXPECT_EQ(SSLInfo::HANDSHAKE_RESUME, info.handshake_type);
+
+    base::HistogramTester histograms;
+    TestCompletionCallback callback;
+    EXPECT_THAT(
+        callback.GetResult(sock_->ConfirmHandshake(callback.callback())),
+        IsOk());
+    histograms.ExpectUniqueSample(kReasonHistogram, ssl_early_data_accepted, 1);
+  }
+
+  // Reset the server's state: this will mean the server declines to resume
+  // the session and, in particular, 0-RTT.
+  SetServerConfig(server_config);
+
+  {
+    int rv;
+    ASSERT_TRUE(CreateAndConnectSSLClientSocket(client_config, &rv));
+    EXPECT_THAT(rv, IsOk());
+
+    // Sanity check
+    SSLInfo info;
+    ASSERT_TRUE(sock_->GetSSLInfo(&info));
+    EXPECT_EQ(SSLInfo::HANDSHAKE_RESUME, info.handshake_type);
+
+    base::HistogramTester histograms;
+    TestCompletionCallback callback;
+    EXPECT_EQ(callback.GetResult(sock_->ConfirmHandshake(callback.callback())),
+              ERR_EARLY_DATA_REJECTED);
+    histograms.ExpectUniqueSample(kReasonHistogram,
+                                  ssl_early_data_session_not_resumed, 1);
+  }
+}
+
+// Test that we correctly log 0-RTT handshake results when
+// the handshake concludes while we're reading the ServerHello.
+TEST_F(SSLClientSocketTest, EarlyDataReasonReadServerHello) {
+  const char kReasonHistogram[] = "Net.SSLHandshakeEarlyDataReason";
+
+  // Enable all test features in the server.
+  SSLServerConfig server_config;
+  server_config.version_max = SSL_PROTOCOL_VERSION_TLS1_3;
+  server_config.early_data_enabled = true;
+  ASSERT_TRUE(
+      StartEmbeddedTestServer(EmbeddedTestServer::CERT_OK, server_config));
+
+  SSLContextConfig client_context_config;
+  client_context_config.version_max = SSL_PROTOCOL_VERSION_TLS1_3;
+  ssl_config_service_->UpdateSSLConfigAndNotify(client_context_config);
+
+  SSLConfig client_config;
+  client_config.early_data_enabled = true;
+
+  // Make the initial connection.
+  {
+    base::HistogramTester histograms;
+    int rv;
+    ASSERT_TRUE(CreateAndConnectSSLClientSocket(client_config, &rv));
+    EXPECT_THAT(rv, IsOk());
+
+    // Sanity check
+    SSLInfo info;
+    ASSERT_TRUE(sock_->GetSSLInfo(&info));
+    EXPECT_EQ(SSLInfo::HANDSHAKE_FULL, info.handshake_type);
+
+    // TLS 1.2 with False Start and TLS 1.3 cause the ticket to arrive later, so
+    // use the socket to ensure the session ticket has been picked up.
+    EXPECT_THAT(MakeHTTPRequest(sock_.get()), IsOk());
+  }
+
+  // Make a resumption connection with early data.
+  {
+    int rv;
+    ASSERT_TRUE(CreateAndConnectSSLClientSocket(client_config, &rv));
+    EXPECT_THAT(rv, IsOk());
+
+    // Sanity check
+    SSLInfo info;
+    ASSERT_TRUE(sock_->GetSSLInfo(&info));
+    EXPECT_EQ(SSLInfo::HANDSHAKE_RESUME, info.handshake_type);
+
+    base::HistogramTester histograms;
+    TestCompletionCallback callback;
+
+    // Conclude the handshake by reading the ServerHello and make sure
+    // we still logged the result.
+    EXPECT_THAT(MakeHTTPRequest(sock_.get()), IsOk());
+    histograms.ExpectUniqueSample(kReasonHistogram, ssl_early_data_accepted, 1);
+  }
+}
+
 TEST_F(SSLClientSocketTest, VersionOverride) {
   // Enable all test features in the server.
   SSLServerConfig server_config;
diff --git a/services/tracing/perfetto/json_trace_exporter.cc b/services/tracing/perfetto/json_trace_exporter.cc
index c058b06..7ff1e98 100644
--- a/services/tracing/perfetto/json_trace_exporter.cc
+++ b/services/tracing/perfetto/json_trace_exporter.cc
@@ -359,6 +359,17 @@
   metadata_->SetDictionary("perfetto_trace_stats", std::move(dict));
 }
 
+void JSONTraceExporter::AddMetadata(const std::string& entry_name,
+                                    std::unique_ptr<base::Value> value) {
+  if (!metadata_filter_predicate_.is_null() &&
+      !metadata_filter_predicate_.Run(entry_name)) {
+    metadata_->SetString(entry_name, kStrippedArgument);
+    return;
+  }
+
+  metadata_->Set(entry_name, std::move(value));
+}
+
 JSONTraceExporter::ScopedJSONTraceEventAppender
 JSONTraceExporter::AddTraceEvent(const char* name,
                                  const char* categories,
diff --git a/services/tracing/perfetto/json_trace_exporter.h b/services/tracing/perfetto/json_trace_exporter.h
index c309fa5..cfb107b 100644
--- a/services/tracing/perfetto/json_trace_exporter.h
+++ b/services/tracing/perfetto/json_trace_exporter.h
@@ -255,6 +255,9 @@
                                              int32_t pid,
                                              int32_t tid);
 
+  void AddMetadata(const std::string& entry_name,
+                   std::unique_ptr<base::Value> value);
+
  private:
   // Used by the implementation to ensure the proper separators exist between
   // trace events in the array.
diff --git a/services/tracing/perfetto/track_event_json_exporter.cc b/services/tracing/perfetto/track_event_json_exporter.cc
index f44b1e31..9acebe52 100644
--- a/services/tracing/perfetto/track_event_json_exporter.cc
+++ b/services/tracing/perfetto/track_event_json_exporter.cc
@@ -94,17 +94,17 @@
     // and wait for the first state_clear before emitting anything.
     if (packet.trusted_packet_sequence_id() !=
         current_state_->trusted_packet_sequence_id) {
+      stats_.sequences_seen++;
       StartNewState(packet.trusted_packet_sequence_id(),
                     packet.incremental_state_cleared());
     } else if (packet.incremental_state_cleared()) {
+      stats_.incremental_state_resets++;
       ResetIncrementalState();
     } else if (packet.previous_packet_dropped()) {
       // If we've lost packets we can no longer trust any timestamp data and
       // other state which might have been dropped. We will keep skipping events
       // until we start a new sequence.
-      LOG_IF(ERROR, current_state_->incomplete)
-          << "Previous packet was dropped. Skipping TraceEvents until reset or "
-          << "new sequence.";
+      stats_.packets_with_previous_packet_dropped++;
       current_state_->incomplete = true;
     }
 
@@ -136,6 +136,7 @@
   }
   if (!has_more) {
     EmitThreadDescriptorIfNeeded();
+    EmitStats();
   }
 }
 
@@ -227,6 +228,7 @@
 
   // InternedData is only emitted on sequences with incremental state.
   if (current_state_->incomplete) {
+    stats_.packets_dropped_invalid_incremental_state++;
     return;
   }
 
@@ -311,6 +313,7 @@
 
   // ProcessDescriptor is only emitted on sequences with incremental state.
   if (current_state_->incomplete) {
+    stats_.packets_dropped_invalid_incremental_state++;
     return;
   }
 
@@ -388,6 +391,7 @@
 
   // ThreadDescriptor is only emitted on sequences with incremental state.
   if (current_state_->incomplete) {
+    stats_.packets_dropped_invalid_incremental_state++;
     return;
   }
 
@@ -472,6 +476,7 @@
 
   // TrackEvents need incremental state.
   if (current_state_->incomplete) {
+    stats_.packets_dropped_invalid_incremental_state++;
     return;
   }
 
@@ -500,12 +505,8 @@
   const std::string joined_categories = base::JoinString(all_categories, ",");
 
   DCHECK(track.has_legacy_event()) << "required field legacy_event missing";
-  auto maybe_builder =
+  auto builder =
       HandleLegacyEvent(track.legacy_event(), joined_categories, timestamp_us);
-  if (!maybe_builder) {
-    return;
-  }
-  auto& builder = *maybe_builder;
 
   if (thread_time_us) {
     builder.AddThreadTimestamp(*thread_time_us);
@@ -532,7 +533,12 @@
 
 void TrackEventJSONExporter::HandleStreamingProfilePacket(
     const perfetto::protos::StreamingProfilePacket& profile_packet) {
-  if (current_state_->incomplete || !ShouldOutputTraceEvents()) {
+  if (current_state_->incomplete) {
+    stats_.packets_dropped_invalid_incremental_state++;
+    return;
+  }
+
+  if (!ShouldOutputTraceEvents()) {
     return;
   }
 
@@ -701,7 +707,7 @@
   }
 }
 
-base::Optional<JSONTraceExporter::ScopedJSONTraceEventAppender>
+JSONTraceExporter::ScopedJSONTraceEventAppender
 TrackEventJSONExporter::HandleLegacyEvent(const TrackEvent::LegacyEvent& event,
                                           const std::string& categories,
                                           int64_t timestamp_us) {
@@ -791,4 +797,17 @@
   builder.AddFlags(flags, id, event.id_scope());
   return builder;
 }
+
+void TrackEventJSONExporter::EmitStats() {
+  auto value = std::make_unique<base::DictionaryValue>();
+  value->SetInteger("sequences_seen", stats_.sequences_seen);
+  value->SetInteger("incremental_state_resets",
+                    stats_.incremental_state_resets);
+  value->SetInteger("packets_dropped_invalid_incremental_state",
+                    stats_.packets_dropped_invalid_incremental_state);
+  value->SetInteger("packets_with_previous_packet_dropped",
+                    stats_.packets_with_previous_packet_dropped);
+  AddMetadata("json_exporter_stats", std::move(value));
+}
+
 }  // namespace tracing
diff --git a/services/tracing/perfetto/track_event_json_exporter.h b/services/tracing/perfetto/track_event_json_exporter.h
index 5583cea..8565f3a 100644
--- a/services/tracing/perfetto/track_event_json_exporter.h
+++ b/services/tracing/perfetto/track_event_json_exporter.h
@@ -154,17 +154,27 @@
                            ArgumentBuilder* args_builder);
 
   // Used to handle the LegacyEvent message found inside the TrackEvent proto.
-  base::Optional<ScopedJSONTraceEventAppender> HandleLegacyEvent(
+  ScopedJSONTraceEventAppender HandleLegacyEvent(
       const perfetto::protos::TrackEvent_LegacyEvent& event,
       const std::string& categories,
       int64_t timestamp_us);
 
+  void EmitStats();
+
   // Tracks all the interned state in the current sequence.
   std::unique_ptr<ProducerWriterState> current_state_;
 
   // Tracks out-of-order seqeuence data.
   std::map<uint32_t, UnorderedProducerWriterState> unordered_state_data_;
 
+  struct Stats {
+    int sequences_seen = 0;
+    int incremental_state_resets = 0;
+    int packets_dropped_invalid_incremental_state = 0;
+    int packets_with_previous_packet_dropped = 0;
+  };
+  Stats stats_;
+
   DISALLOW_COPY_AND_ASSIGN(TrackEventJSONExporter);
 };
 
diff --git a/services/tracing/perfetto/track_event_json_exporter_unittest.cc b/services/tracing/perfetto/track_event_json_exporter_unittest.cc
index 0fbb8c4..8d9403d 100644
--- a/services/tracing/perfetto/track_event_json_exporter_unittest.cc
+++ b/services/tracing/perfetto/track_event_json_exporter_unittest.cc
@@ -414,7 +414,8 @@
   FinalizePackets(trace_packet_protos);
   // No traceEvents or data was emitted but a process descriptor without extra
   // data should just be an empty array and not cause crashes.
-  EXPECT_EQ("{\"traceEvents\":[]}", unparsed_trace_data_);
+  EXPECT_THAT(unparsed_trace_data_,
+              testing::StartsWith("{\"traceEvents\":[],"));
 }
 
 TEST_F(TrackEventJsonExporterTest, SortIndexProcessDescriptor) {
@@ -575,7 +576,8 @@
   FinalizePackets(trace_packet_protos);
   // No traceEvents or data was emitted but a thread descriptor should be an
   // empty array and not cause crashes.
-  EXPECT_EQ("{\"traceEvents\":[]}", unparsed_trace_data_);
+  EXPECT_THAT(unparsed_trace_data_,
+              testing::StartsWith("{\"traceEvents\":[],"));
 }
 
 TEST_F(TrackEventJsonExporterTest, SortIndexThreadDescriptor) {
@@ -715,7 +717,8 @@
                             &trace_packet_protos);
   FinalizePackets(trace_packet_protos);
   // Interned data by itself does not call any trace events to be emitted.
-  EXPECT_EQ("{\"traceEvents\":[]}", unparsed_trace_data_);
+  EXPECT_THAT(unparsed_trace_data_,
+              testing::StartsWith("{\"traceEvents\":[],"));
 }
 
 TEST_F(TrackEventJsonExporterTest, LegacyEventBasicTest) {
@@ -1758,10 +1761,11 @@
   // base class. See json_trace_exporter_unittest for a more complete test.
   trace_packet_protos.back().mutable_trace_stats();
   FinalizePackets(trace_packet_protos);
-  EXPECT_TRUE(base::StartsWith(unparsed_trace_data_,
-                               "{\"traceEvents\":[],"
-                               "\"metadata\":{\"perfetto_trace_stats\":{\"",
-                               base::CompareCase::SENSITIVE));
+
+  EXPECT_THAT(unparsed_trace_data_, testing::StartsWith("{\"traceEvents\":[],"
+                                                        "\"metadata\":{"));
+  EXPECT_THAT(unparsed_trace_data_,
+              testing::HasSubstr("\"perfetto_trace_stats\":{\""));
 }
 
 TEST_F(TrackEventJsonExporterTest, ComplexLongSequenceWithDroppedPackets) {
diff --git a/testing/buildbot/filters/bfcache.chrome_public_test_apk.filter b/testing/buildbot/filters/bfcache.chrome_public_test_apk.filter
index 25aa7788..df82a83e 100644
--- a/testing/buildbot/filters/bfcache.chrome_public_test_apk.filter
+++ b/testing/buildbot/filters/bfcache.chrome_public_test_apk.filter
@@ -14,11 +14,15 @@
 -org.chromium.chrome.browser.customtabs.DetachedResourceRequestTest.testSafeBrowsingSubresource
 -org.chromium.chrome.browser.customtabs.DetachedResourceRequestTest.testSafeBrowsingSubresourceBeforeNative
 -org.chromium.chrome.browser.externalnav.UrlOverridingTest.testRedirectionFromIntent
+-org.chromium.chrome.browser.keyboard_accessory.PasswordGenerationIntegrationTest.testAutomaticGenerationUsePassword
+-org.chromium.chrome.browser.keyboard_accessory.PasswordGenerationIntegrationTest.testManualGenerationUsePassword
 -org.chromium.chrome.browser.incognito.IncognitoTabLauncherTest.testLaunchIncognitoNewTab
 -org.chromium.chrome.browser.incognito.IncognitoTabLauncherTest.testLaunchIncognitoNewTab_omniboxFocused
 -org.chromium.chrome.browser.infobar.InfoBarTest.testInfoBarForGeolocationDisappearsOnBack
 -org.chromium.chrome.browser.media.MediaLauncherActivityTest.testHandleImageIntent
 -org.chromium.chrome.browser.media.MediaLauncherActivityTest.testHandleVideoIntent
+-org.chromium.chrome.browser.notifications.NotificationPlatformBridgeIntentTest.testLaunchNotificationPreferencesForCategory
+-org.chromium.chrome.browser.notifications.NotificationPlatformBridgeIntentTest.testLaunchNotificationPreferencesForWebsite
 -org.chromium.chrome.browser.offlinepages.indicator.OfflineIndicatorControllerTest.testDoNotShowOfflineIndicatorOnErrorPageWhenOffline
 -org.chromium.chrome.browser.password_manager.OnboardingDialogIntegrationTest.testOnboardingAccepted
 -org.chromium.chrome.browser.password_manager.OnboardingDialogIntegrationTest.testOnboardingDismissedPressedBack
diff --git a/testing/buildbot/filters/bfcache.content_browsertests.filter b/testing/buildbot/filters/bfcache.content_browsertests.filter
index 0940316..4b48dad 100644
--- a/testing/buildbot/filters/bfcache.content_browsertests.filter
+++ b/testing/buildbot/filters/bfcache.content_browsertests.filter
@@ -80,3 +80,17 @@
 # In debug mode. The FrameHostInterceptor fails, because it doesn't take into
 # account pages in the BackForwardCache.
 -SecurityExploitBrowserTest.InvalidBeginNavigationInitiator
+
+# NOTREACHED() is hit in RenderFrameHostManager::GetFrameHostForNavigation,
+# because, "A frame that's pending deletion should never be navigated.".
+-NavigationControllerBrowserTest.PageStateWithIframeAfterForwardInCompetingFrames
+-SitePerProcessBrowserTest.TwoCrossSitePendingNavigationsAndMainFrameWins
+
+# Failing on android only, need to be triaged, see https://crbug.com/1006267.
+-BackForwardCacheMetricsBrowserTest.Fetch
+-BackForwardCacheMetricsBrowserTest.XHR
+-MSE_ClearKey/EncryptedMediaTest.ConfigChangeVideo_ClearToEncrypted/0
+-TouchpadPinchBrowserTest.WheelListenerPreventingPinch/1
+-WebRtcBrowserTest.CanSetupH264VideoCallOnSupportedDevice
+-WithoutCORBProtectionSniffing/CrossSiteDocumentBlockingTest.AllowImagesWithSniffing/0
+-WithCORBProtectionSniffing/CrossSiteDocumentBlockingTest.AllowImagesWithSniffing/0
diff --git a/testing/buildbot/filters/bfcache.content_shell_test_apk.filter b/testing/buildbot/filters/bfcache.content_shell_test_apk.filter
index 2d8f82a..caa6dfa 100644
--- a/testing/buildbot/filters/bfcache.content_shell_test_apk.filter
+++ b/testing/buildbot/filters/bfcache.content_shell_test_apk.filter
@@ -1 +1,4 @@
 # These tests currently fail when run with --enable-features=BackForwardCache
+
+# Fails for unknown reason.
+-org.chromium.content.browser.InterstitialPageTest.testCloseInterstitial
diff --git a/testing/buildbot/filters/bfcache.content_unittests.filter b/testing/buildbot/filters/bfcache.content_unittests.filter
index d230600..3a35d00 100644
--- a/testing/buildbot/filters/bfcache.content_unittests.filter
+++ b/testing/buildbot/filters/bfcache.content_unittests.filter
@@ -39,3 +39,20 @@
 # to exist.
 -WebContentsImplTest.CrossSiteBoundaries
 -WebContentsImplTest.NavigateFromSitelessUrl
+
+# Tests below are failing on android only.
+# Need to be triaged, see https://crbug.com/1007276.
+
+# Numerous tests from these suites are crashing with:
+# [FATAL:deferred_sequenced_task_runner.cc(83)] Check failed: !target_task_runner_
+-AudibleMetricsTest.*
+-GeolocationServiceTest.*
+-MediaInternalsAudioLogTest/MediaInternalsAudioLogTest.*
+-MediaInternalsVideoCaptureDeviceTest.NotifyVideoCaptureDeviceCapabilitiesEnumerated
+-MediaSessionControllerTest.*
+-MediaSessionEnabledTestInstances/MediaSessionControllersManagerTest.*
+-MediaSessionImplServiceRoutingTest.*
+-MediaSessionImplTest.*
+-MediaSessionImplUmaTest.*
+-MediaStreamManagerTest.*
+-RenderFrameAudioOutputStreamFactoryTest.*
diff --git a/testing/buildbot/filters/bfcache.unit_tests.filter b/testing/buildbot/filters/bfcache.unit_tests.filter
index 2d8f82a..c54a330d 100644
--- a/testing/buildbot/filters/bfcache.unit_tests.filter
+++ b/testing/buildbot/filters/bfcache.unit_tests.filter
@@ -1 +1,16 @@
 # These tests currently fail when run with --enable-features=BackForwardCache
+
+# Tests below are failing on android only.
+# Need to be triaged, see https://crbug.com/1007276.
+
+# Crashing with:
+# [FATAL:deferred_sequenced_task_runner.cc(83)] Check failed: !target_task_runner_
+-ChromeBrowserMainExtraPartsMetricsTest.VerifyTouchEventsEnabledIsRecordedAfterPostBrowserStart
+-ServicesTest.ConnectToFilePatch
+-SoundContentSettingObserverTest.AudioMutingUpdatesWithNavigation
+
+# Crashing with:
+# [FATAL:navigation_url_loader.cc(55)] Check failed: g_loader_factory == nullptr || factory == nullptr
+-AddressAccessoryControllerTest.RefreshSuggestionsCallsUI
+-CreditCardAccessoryControllerTest.PreventsFillingInsecureContexts
+
diff --git a/third_party/blink/common/BUILD.gn b/third_party/blink/common/BUILD.gn
index a41aeb7..853d173 100644
--- a/third_party/blink/common/BUILD.gn
+++ b/third_party/blink/common/BUILD.gn
@@ -79,7 +79,6 @@
     "origin_trials/trial_token_validator.cc",
     "page/page_zoom.cc",
     "peerconnection/webrtc_ip_handling_policy.cc",
-    "privacy_preferences.cc",
     "scheduler/web_scheduler_tracked_feature.cc",
     "service_worker/service_worker_status_code.cc",
     "service_worker/service_worker_type_converters.cc",
diff --git a/third_party/blink/common/privacy_preferences.cc b/third_party/blink/common/privacy_preferences.cc
deleted file mode 100644
index 48351fa..0000000
--- a/third_party/blink/common/privacy_preferences.cc
+++ /dev/null
@@ -1,14 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "third_party/blink/public/common/privacy_preferences.h"
-
-namespace blink {
-
-PrivacyPreferences::PrivacyPreferences(bool enable_do_not_track,
-                                       bool enable_referrers)
-    : enable_do_not_track(enable_do_not_track),
-      enable_referrers(enable_referrers) {}
-
-}  // namespace blink
diff --git a/third_party/blink/public/common/BUILD.gn b/third_party/blink/public/common/BUILD.gn
index c341439..a3093c09 100644
--- a/third_party/blink/public/common/BUILD.gn
+++ b/third_party/blink/public/common/BUILD.gn
@@ -115,7 +115,6 @@
     "page/page_zoom.h",
     "peerconnection/webrtc_ip_handling_policy.h",
     "prerender/prerender_rel_type.h",
-    "privacy_preferences.h",
     "scheduler/web_scheduler_tracked_feature.h",
     "screen_orientation/web_screen_orientation_lock_type.h",
     "screen_orientation/web_screen_orientation_type.h",
diff --git a/third_party/blink/public/common/privacy_preferences.h b/third_party/blink/public/common/privacy_preferences.h
deleted file mode 100644
index efcadde..0000000
--- a/third_party/blink/public/common/privacy_preferences.h
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef THIRD_PARTY_BLINK_PUBLIC_COMMON_PRIVACY_PREFERENCES_H_
-#define THIRD_PARTY_BLINK_PUBLIC_COMMON_PRIVACY_PREFERENCES_H_
-
-#include "third_party/blink/public/common/common_export.h"
-
-namespace blink {
-
-// Subset of content::RendererPreferences for passing the security info to
-// blink.
-// TODO(crbug.com/869748): Move this into a mojom struct and use the new struct
-// as a part of RendererPreferences once RendererPreferences becomes a mojom
-// struct.
-struct BLINK_COMMON_EXPORT PrivacyPreferences {
-  PrivacyPreferences() = default;
-  PrivacyPreferences(bool enable_do_not_track,
-                     bool enable_referrers);
-
-  // These default values are coming from the defaults in
-  // content::RendererPreferences.
-  bool enable_do_not_track = false;
-  bool enable_referrers = true;
-};
-
-}  // namespace blink
-
-#endif  // THIRD_PARTY_BLINK_PUBLIC_COMMON_PRIVACY_PREFERENCES_H_
diff --git a/third_party/blink/public/mojom/renderer_preferences.mojom b/third_party/blink/public/mojom/renderer_preferences.mojom
index 26c3aa13..e614a3b 100644
--- a/third_party/blink/public/mojom/renderer_preferences.mojom
+++ b/third_party/blink/public/mojom/renderer_preferences.mojom
@@ -63,7 +63,6 @@
   bool use_custom_colors = true;
 
   // Set to false to not send referrers.
-  // The default value should be in sync with blink::PrivacyPreferences.
   bool enable_referrers = true;
 
   // Set to true to allow third-party sub-content to pop-up HTTP basic auth
@@ -71,7 +70,6 @@
   bool allow_cross_origin_auth_prompt = false;
 
   // Set to true to indicate that the preference to set DNT to 1 is enabled.
-  // The default value should be in sync with blink::PrivacyPreferences.
   bool enable_do_not_track = false;
 
   // Whether to allow the use of Encrypted Media Extensions (EME), except for
diff --git a/third_party/blink/public/web/web_embedded_worker_start_data.h b/third_party/blink/public/web/web_embedded_worker_start_data.h
index be3cf43..6d6952a 100644
--- a/third_party/blink/public/web/web_embedded_worker_start_data.h
+++ b/third_party/blink/public/web/web_embedded_worker_start_data.h
@@ -33,7 +33,6 @@
 
 #include "base/unguessable_token.h"
 #include "services/network/public/mojom/ip_address_space.mojom-shared.h"
-#include "third_party/blink/public/common/privacy_preferences.h"
 #include "third_party/blink/public/mojom/service_worker/service_worker_registration.mojom-shared.h"
 #include "third_party/blink/public/platform/web_content_security_policy.h"
 #include "third_party/blink/public/platform/web_string.h"
@@ -56,8 +55,6 @@
 
   network::mojom::IPAddressSpace address_space;
 
-  PrivacyPreferences privacy_preferences;
-
   WebEmbeddedWorkerStartData() : wait_for_debugger_mode(kDontWaitForDebugger) {}
 };
 
diff --git a/third_party/blink/renderer/bindings/core/v8/to_v8_for_core.h b/third_party/blink/renderer/bindings/core/v8/to_v8_for_core.h
index 5087a684..bdf622c 100644
--- a/third_party/blink/renderer/bindings/core/v8/to_v8_for_core.h
+++ b/third_party/blink/renderer/bindings/core/v8/to_v8_for_core.h
@@ -12,6 +12,7 @@
 #include "third_party/blink/renderer/bindings/core/v8/script_value.h"
 #include "third_party/blink/renderer/core/typed_arrays/array_buffer_view_helpers.h"
 #include "third_party/blink/renderer/platform/bindings/to_v8.h"
+#include "third_party/blink/renderer/platform/heap/disallow_new_wrapper.h"
 #include "v8/include/v8.h"
 
 namespace blink {
@@ -64,6 +65,14 @@
   return value.V8Value();
 }
 
+inline v8::Local<v8::Value> ToV8(const DisallowNewWrapper<ScriptValue>* value,
+                                 v8::Local<v8::Object> creation_context,
+                                 v8::Isolate* isolate) {
+  if (value->Value().IsEmpty())
+    return v8::Undefined(isolate);
+  return value->Value().V8Value();
+}
+
 // Cannot define in ScriptValue because of the circular dependency between toV8
 // and ScriptValue
 template <typename T>
diff --git a/third_party/blink/renderer/bindings/core/v8/worker_or_worklet_script_controller.cc b/third_party/blink/renderer/bindings/core/v8/worker_or_worklet_script_controller.cc
index 57106f4..1463b2b 100644
--- a/third_party/blink/renderer/bindings/core/v8/worker_or_worklet_script_controller.cc
+++ b/third_party/blink/renderer/bindings/core/v8/worker_or_worklet_script_controller.cc
@@ -114,9 +114,8 @@
   rejected_promises_->Dispose();
   rejected_promises_ = nullptr;
 
-  world_->Dispose();
-
   DisposeContextIfNeeded();
+  world_->Dispose();
 }
 
 void WorkerOrWorkletScriptController::DisposeContextIfNeeded() {
@@ -132,9 +131,23 @@
 
   {
     ScriptState::Scope scope(script_state_);
+    v8::Local<v8::Context> context = script_state_->GetContext();
+    // After disposing the world, all Blink->V8 references are gone. Blink
+    // stand-alone GCs may collect the WorkerOrWorkletGlobalScope because there
+    // are no more roots (V8->Blink references that are actually found by
+    // iterating Blink->V8 references). Clear the back pointers to avoid
+    // referring to cleared memory on the next GC in case the JS wrapper objects
+    // survived.
+    v8::Local<v8::Object> global_proxy_object = context->Global();
+    v8::Local<v8::Object> global_object =
+        global_proxy_object->GetPrototype().As<v8::Object>();
+    DCHECK(!global_object.IsEmpty());
+    V8DOMWrapper::ClearNativeInfo(isolate_, global_object);
+    V8DOMWrapper::ClearNativeInfo(isolate_, global_proxy_object);
+
     // This detaches v8::MicrotaskQueue pointer from v8::Context, so that we can
     // destroy EventLoop safely.
-    script_state_->GetContext()->DetachGlobal();
+    context->DetachGlobal();
   }
 
   script_state_->DisposePerContextData();
diff --git a/third_party/blink/renderer/bindings/modules/v8/generated.gni b/third_party/blink/renderer/bindings/modules/v8/generated.gni
index 60255a8..ee03000 100644
--- a/third_party/blink/renderer/bindings/modules/v8/generated.gni
+++ b/third_party/blink/renderer/bindings/modules/v8/generated.gni
@@ -96,6 +96,8 @@
   "$bindings_modules_v8_output_dir/unsigned_long_or_unsigned_long_sequence.h",
   "$bindings_modules_v8_output_dir/unsigned_long_sequence_or_gpu_extent_3d_dict.cc",
   "$bindings_modules_v8_output_dir/unsigned_long_sequence_or_gpu_extent_3d_dict.h",
+  "$bindings_modules_v8_output_dir/unsigned_long_sequence_or_gpu_origin_3d_dict.cc",
+  "$bindings_modules_v8_output_dir/unsigned_long_sequence_or_gpu_origin_3d_dict.h",
   "$bindings_modules_v8_output_dir/webgl_rendering_context_or_webgl2_rendering_context.cc",
   "$bindings_modules_v8_output_dir/webgl_rendering_context_or_webgl2_rendering_context.h",
 ]
diff --git a/third_party/blink/renderer/core/animation_frame/worker_animation_frame_provider.cc b/third_party/blink/renderer/core/animation_frame/worker_animation_frame_provider.cc
index 3590b42..6152182 100644
--- a/third_party/blink/renderer/core/animation_frame/worker_animation_frame_provider.cc
+++ b/third_party/blink/renderer/core/animation_frame/worker_animation_frame_provider.cc
@@ -56,6 +56,7 @@
             // we abort the whole process.
             if (!inside_raf_scope.AddOffscreenCanvas(offscreen_canvas)) {
               provider->begin_frame_provider_->FinishBeginFrame(args);
+              provider->begin_frame_provider_->RequestBeginFrame();
               return;
             }
           }
diff --git a/third_party/blink/renderer/core/css/style_recalc_root.cc b/third_party/blink/renderer/core/css/style_recalc_root.cc
index 0a251fb..255d93f 100644
--- a/third_party/blink/renderer/core/css/style_recalc_root.cc
+++ b/third_party/blink/renderer/core/css/style_recalc_root.cc
@@ -36,7 +36,7 @@
 
 #if DCHECK_IS_ON()
 ContainerNode* StyleRecalcRoot::Parent(const Node& node) const {
-  return node.ParentOrShadowHostNode();
+  return node.GetStyleRecalcParent();
 }
 
 bool StyleRecalcRoot::IsChildDirty(const ContainerNode& node) const {
@@ -50,7 +50,7 @@
 
 void StyleRecalcRoot::ClearChildDirtyForAncestors(ContainerNode& parent) const {
   for (ContainerNode* ancestor = &parent; ancestor;
-       ancestor = ancestor->ParentOrShadowHostNode()) {
+       ancestor = ancestor->GetStyleRecalcParent()) {
     ancestor->ClearChildNeedsStyleRecalc();
     DCHECK(!ancestor->NeedsStyleRecalc());
   }
diff --git a/third_party/blink/renderer/core/dom/flat_tree_node_data.h b/third_party/blink/renderer/core/dom/flat_tree_node_data.h
index 1c95ad2f..a991349 100644
--- a/third_party/blink/renderer/core/dom/flat_tree_node_data.h
+++ b/third_party/blink/renderer/core/dom/flat_tree_node_data.h
@@ -47,6 +47,7 @@
   friend class FlatTreeTraversal;
   friend class HTMLSlotElement;
   friend HTMLSlotElement* Node::AssignedSlot() const;
+  friend Element* Node::FlatTreeParentForChildDirty() const;
 
   WeakMember<HTMLSlotElement> assigned_slot_;
   WeakMember<Node> previous_in_assigned_nodes_;
diff --git a/third_party/blink/renderer/core/dom/node.cc b/third_party/blink/renderer/core/dom/node.cc
index 2d68f98d..d60f5f9 100644
--- a/third_party/blink/renderer/core/dom/node.cc
+++ b/third_party/blink/renderer/core/dom/node.cc
@@ -1209,10 +1209,10 @@
 }
 
 void Node::MarkAncestorsWithChildNeedsStyleRecalc() {
-  ContainerNode* ancestor = ParentOrShadowHostNode();
+  ContainerNode* ancestor = GetStyleRecalcParent();
   bool parent_dirty = ancestor && ancestor->NeedsStyleRecalc();
   for (; ancestor && !ancestor->ChildNeedsStyleRecalc();
-       ancestor = ancestor->ParentOrShadowHostNode()) {
+       ancestor = ancestor->GetStyleRecalcParent()) {
     if (!ancestor->isConnected())
       return;
     ancestor->SetChildNeedsStyleRecalc();
@@ -1242,7 +1242,7 @@
   if (RuntimeEnabledFeatures::DisplayLockingEnabled() &&
       GetDocument().LockedDisplayLockCount() > 0) {
     for (auto* ancestor_copy = ancestor; ancestor_copy;
-         ancestor_copy = ancestor_copy->ParentOrShadowHostNode()) {
+         ancestor_copy = ancestor_copy->GetStyleRecalcParent()) {
       auto* ancestor_copy_element = DynamicTo<Element>(ancestor_copy);
       if (ancestor_copy_element &&
           ancestor_copy_element->StyleRecalcBlockedByDisplayLock(
@@ -1256,12 +1256,12 @@
   GetDocument().ScheduleLayoutTreeUpdateIfNeeded();
 }
 
-Element* Node::GetReattachParent() const {
+Element* Node::FlatTreeParentForChildDirty() const {
   if (IsPseudoElement())
     return ParentOrShadowHostElement();
   if (IsChildOfV1ShadowHost()) {
-    if (HTMLSlotElement* slot = AssignedSlot())
-      return slot;
+    if (auto* data = GetFlatTreeNodeData())
+      return data->AssignedSlot();
   }
   if (IsInV0ShadowTree() || IsChildOfV0ShadowHost()) {
     if (ShadowRootWhereNodeCanBeDistributedForV0(*this)) {
@@ -1274,6 +1274,12 @@
   return ParentOrShadowHostElement();
 }
 
+ContainerNode* Node::GetStyleRecalcParent() const {
+  if (RuntimeEnabledFeatures::FlatTreeStyleRecalcEnabled())
+    return FlatTreeParentForChildDirty();
+  return ParentOrShadowHostNode();
+}
+
 void Node::MarkAncestorsWithChildNeedsReattachLayoutTree() {
   DCHECK(isConnected());
   Element* ancestor = GetReattachParent();
diff --git a/third_party/blink/renderer/core/dom/node.h b/third_party/blink/renderer/core/dom/node.h
index cfb63a5e..7493a2f 100644
--- a/third_party/blink/renderer/core/dom/node.h
+++ b/third_party/blink/renderer/core/dom/node.h
@@ -627,7 +627,9 @@
   bool IsChildOfV1ShadowHost() const;
   bool IsChildOfV0ShadowHost() const;
   ShadowRoot* V1ShadowRootOfParent() const;
-  Element* GetReattachParent() const;
+  Element* FlatTreeParentForChildDirty() const;
+  ContainerNode* GetStyleRecalcParent() const;
+  Element* GetReattachParent() const { return FlatTreeParentForChildDirty(); }
 
   bool IsDocumentTypeNode() const { return getNodeType() == kDocumentTypeNode; }
   virtual bool ChildTypeAllowed(NodeType) const { return false; }
diff --git a/third_party/blink/renderer/core/editing/finder/find_task_controller.cc b/third_party/blink/renderer/core/editing/finder/find_task_controller.cc
index a67f6e0..ce853a5 100644
--- a/third_party/blink/renderer/core/editing/finder/find_task_controller.cc
+++ b/third_party/blink/renderer/core/editing/finder/find_task_controller.cc
@@ -4,6 +4,7 @@
 
 #include "third_party/blink/renderer/core/editing/finder/find_task_controller.h"
 
+#include "third_party/blink/public/mojom/frame/find_in_page.mojom-blink.h"
 #include "third_party/blink/renderer/core/dom/document.h"
 #include "third_party/blink/renderer/core/dom/idle_request_options.h"
 #include "third_party/blink/renderer/core/dom/range.h"
diff --git a/third_party/blink/renderer/core/editing/finder/find_task_controller.h b/third_party/blink/renderer/core/editing/finder/find_task_controller.h
index f38c2662..58e6c839 100644
--- a/third_party/blink/renderer/core/editing/finder/find_task_controller.h
+++ b/third_party/blink/renderer/core/editing/finder/find_task_controller.h
@@ -5,7 +5,7 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_EDITING_FINDER_FIND_TASK_CONTROLLER_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_EDITING_FINDER_FIND_TASK_CONTROLLER_H_
 
-#include "third_party/blink/public/mojom/frame/find_in_page.mojom-blink.h"
+#include "third_party/blink/public/mojom/frame/find_in_page.mojom-blink-forward.h"
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/editing/position.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
diff --git a/third_party/blink/renderer/core/editing/finder/text_finder.h b/third_party/blink/renderer/core/editing/finder/text_finder.h
index cf2233af..c025a32 100644
--- a/third_party/blink/renderer/core/editing/finder/text_finder.h
+++ b/third_party/blink/renderer/core/editing/finder/text_finder.h
@@ -32,7 +32,7 @@
 #define THIRD_PARTY_BLINK_RENDERER_CORE_EDITING_FINDER_TEXT_FINDER_H_
 
 #include "base/macros.h"
-#include "third_party/blink/public/mojom/frame/find_in_page.mojom-blink.h"
+#include "third_party/blink/public/mojom/frame/find_in_page.mojom-blink-forward.h"
 #include "third_party/blink/public/platform/web_float_point.h"
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/platform/geometry/float_rect.h"
diff --git a/third_party/blink/renderer/core/editing/finder/text_finder_test.cc b/third_party/blink/renderer/core/editing/finder/text_finder_test.cc
index f56e897..f45477f 100644
--- a/third_party/blink/renderer/core/editing/finder/text_finder_test.cc
+++ b/third_party/blink/renderer/core/editing/finder/text_finder_test.cc
@@ -5,6 +5,7 @@
 #include "third_party/blink/renderer/core/editing/finder/text_finder.h"
 
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/mojom/frame/find_in_page.mojom-blink.h"
 #include "third_party/blink/public/platform/platform.h"
 #include "third_party/blink/public/platform/web_float_rect.h"
 #include "third_party/blink/public/web/web_document.h"
diff --git a/third_party/blink/renderer/core/fetch/body.cc b/third_party/blink/renderer/core/fetch/body.cc
index 8bc79a6..162d2d5 100644
--- a/third_party/blink/renderer/core/fetch/body.cc
+++ b/third_party/blink/renderer/core/fetch/body.cc
@@ -22,6 +22,7 @@
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
 #include "third_party/blink/renderer/platform/bindings/script_state.h"
 #include "third_party/blink/renderer/platform/bindings/v8_throw_exception.h"
+#include "third_party/blink/renderer/platform/heap/disallow_new_wrapper.h"
 #include "third_party/blink/renderer/platform/heap/heap.h"
 #include "third_party/blink/renderer/platform/network/parsed_content_type.h"
 #include "third_party/blink/renderer/platform/wtf/functional.h"
@@ -146,8 +147,8 @@
     if (v8::JSON::Parse(Resolver()->GetScriptState()->GetContext(),
                         input_string)
             .ToLocal(&parsed)) {
-      ResolveLater(
-          ScriptValue(Resolver()->GetScriptState()->GetIsolate(), parsed));
+      ResolveLater(WrapPersistent(WrapDisallowNew(
+          ScriptValue(Resolver()->GetScriptState()->GetIsolate(), parsed))));
     } else
       Resolver()->Reject(trycatch.Exception());
   }
diff --git a/third_party/blink/renderer/core/frame/web_local_frame_impl.h b/third_party/blink/renderer/core/frame/web_local_frame_impl.h
index ab52cef..769973a 100644
--- a/third_party/blink/renderer/core/frame/web_local_frame_impl.h
+++ b/third_party/blink/renderer/core/frame/web_local_frame_impl.h
@@ -40,7 +40,7 @@
 #include "mojo/public/cpp/bindings/pending_associated_remote.h"
 #include "third_party/blink/public/mojom/ad_tagging/ad_frame.mojom-blink.h"
 #include "third_party/blink/public/mojom/devtools/devtools_agent.mojom-blink.h"
-#include "third_party/blink/public/mojom/frame/find_in_page.mojom-blink.h"
+#include "third_party/blink/public/mojom/frame/find_in_page.mojom-blink-forward.h"
 #include "third_party/blink/public/mojom/frame/lifecycle.mojom-blink.h"
 #include "third_party/blink/public/mojom/portal/portal.mojom-blink.h"
 #include "third_party/blink/public/platform/web_file_system_type.h"
diff --git a/third_party/blink/renderer/core/script/resources/layered_api/elements/switch/index.mjs b/third_party/blink/renderer/core/script/resources/layered_api/elements/switch/index.mjs
index 81a02e5..6732ffd 100644
--- a/third_party/blink/renderer/core/script/resources/layered_api/elements/switch/index.mjs
+++ b/third_party/blink/renderer/core/script/resources/layered_api/elements/switch/index.mjs
@@ -9,10 +9,37 @@
 import {SwitchTrack} from './track.mjs';
 
 const generateStyleSheet = style.styleSheetFactory();
+const generateMaterialStyleSheet = style.materialStyleSheetFactory();
 
 // https://github.com/tkent-google/std-switch/issues/2
 const STATE_ATTR = 'on';
 
+function parentOrHostElement(element) {
+  const parent = element.parentNode;
+  if (!parent) {
+    return null;
+  }
+  if (parent.nodeType === Node.ELEMENT_NODE) {
+    return parent;
+  }
+  if (parent.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
+    return parent.host;
+  }
+  return null;
+}
+
+function shouldUsePlatformTheme(element) {
+  for (; element; element = parentOrHostElement(element)) {
+    const themeValue = element.getAttribute('theme');
+    if (themeValue === 'match-platform') {
+      return true;
+    } else if (themeValue === 'platform-agnostic') {
+      return false;
+    }
+  }
+  return false;
+}
+
 export class StdSwitchElement extends HTMLElement {
   // TODO(tkent): The following should be |static fooBar = value;|
   // after enabling babel-eslint.
@@ -27,6 +54,7 @@
   #track;
   #containerElement;
   #inUserAction = false;
+  #shadowRoot;
 
   constructor() {
     super();
@@ -62,6 +90,11 @@
   }
 
   connectedCallback() {
+    // The element might have been disconnected when the callback is invoked.
+    if (!this.isConnected) {
+      return;
+    }
+
     // TODO(tkent): We should not add tabindex attribute.
     // https://github.com/w3c/webcomponents/issues/762
     if (!this.hasAttribute('tabindex')) {
@@ -76,6 +109,15 @@
         this.setAttribute('role', 'switch');
       }
     }
+
+    if (shouldUsePlatformTheme(this)) {
+      // TODO(tkent): Should we apply Cocoa-like on macOS and Fluent-like
+      // on Windows?
+      this.#shadowRoot.adoptedStyleSheets =
+          [generateStyleSheet(), generateMaterialStyleSheet()];
+    } else {
+      this.#shadowRoot.adoptedStyleSheets = [generateStyleSheet()];
+    }
   }
 
   formResetCallback() {
@@ -100,7 +142,7 @@
     thumbElement.id = 'thumb';
     thumbElement.part.add('thumb');
 
-    root.adoptedStyleSheets = [generateStyleSheet()];
+    this.#shadowRoot = root;
   };
 
   #onClick = () => {
diff --git a/third_party/blink/renderer/core/script/resources/layered_api/elements/switch/style.mjs b/third_party/blink/renderer/core/script/resources/layered_api/elements/switch/style.mjs
index ba213120a..ded00d0 100644
--- a/third_party/blink/renderer/core/script/resources/layered_api/elements/switch/style.mjs
+++ b/third_party/blink/renderer/core/script/resources/layered_api/elements/switch/style.mjs
@@ -11,6 +11,10 @@
 const THUMB_MARGIN_START = '2px';
 const THUMB_MARGIN_END = '2px';
 
+const MATERIAL_THUMB_SIZE = '20px';
+const RIPPLE_COLOR = 'rgba(100,100,100,0.3)';
+const RIPPLE_MAX_SIZE = '48px';
+
 // Returns a function returning a CSSStyleSheet().
 // TODO(tkent): Share this stylesheet factory feature with elements/toast/.
 export function styleSheetFactory() {
@@ -151,6 +155,122 @@
   };
 }
 
+export function materialStyleSheetFactory() {
+  let styleSheet;
+  return () => {
+    if (!styleSheet) {
+      styleSheet = new CSSStyleSheet();
+      styleSheet.replaceSync(`
+:host {
+  block-size: 20px;
+  inline-size: 36px;
+}
+
+#track,
+:host(:active) #track {
+  background: rgba(0,0,0,0.4);
+  block-size: 14px;
+  border: none;
+  box-shadow: none;
+}
+
+:host([on]) #track,
+:host([on]:active) #track,
+:host([on]:focus) #track {
+  border: none;
+  box-shadow: none;
+}
+
+#trackFill,
+:host([on]:active) #trackFill {
+  background: rgba(63,81,181,0.5);
+}
+
+#thumb,
+:host(:focus) #thumb {
+  block-size: ${MATERIAL_THUMB_SIZE};
+  border-radius: calc(${MATERIAL_THUMB_SIZE} / 2);
+  border: none;
+  box-shadow: 0 1px 5px 0 rgba(0,0,0,0.6);
+  inline-size: ${MATERIAL_THUMB_SIZE};
+  margin-inline-start: -100%;
+}
+
+:host([on]) #thumb,
+:host([on]:focus) #thumb,
+:host([on]:not(:disabled):hover) #thumb {
+  background: rgb(63,81,181);
+  border: none;
+  margin-inline-start: calc(0px - 20px);
+}
+
+:host(:not(:disabled):hover) #thumb {
+  inline-size: ${MATERIAL_THUMB_SIZE};
+}
+
+/*
+ * Ripple effect
+ *
+ * Translucent circle is painted on the thumb if the element is :active or
+ * :focus-visible.  It has
+ *   - Size transition when it appears
+ *   - Opacity transition when it disappears
+ * part(thumb)::before represents the former, and part(thumb)::after represents
+ * the latter.
+ */
+#thumb::before {
+  background: ${RIPPLE_COLOR};
+  block-size: 0px;
+  border-radius: 0px;
+  content: "";
+  display: inline-block;
+  inline-size: 0px;
+  left: calc(${MATERIAL_THUMB_SIZE} / 2);
+  position: relative;
+  top: calc(${MATERIAL_THUMB_SIZE} / 2);
+  transition: none;
+}
+
+:host(:active) #thumb::before,
+:host(:focus-visible) #thumb::before {
+  block-size: ${RIPPLE_MAX_SIZE};
+  border-radius: calc(${RIPPLE_MAX_SIZE} / 2);
+  inline-size: ${RIPPLE_MAX_SIZE};
+  left: calc((${MATERIAL_THUMB_SIZE} - ${RIPPLE_MAX_SIZE}) / 2);
+  top: calc((${MATERIAL_THUMB_SIZE} - ${RIPPLE_MAX_SIZE}) / 2);
+  transition: all linear 0.1s;
+}
+
+#thumb::after {
+  background: ${RIPPLE_COLOR};
+  block-size: ${RIPPLE_MAX_SIZE};
+  border-radius: calc(${RIPPLE_MAX_SIZE} / 2);
+  content: "";
+  display: inline-block;
+  inline-size: ${RIPPLE_MAX_SIZE};
+  left: calc((${MATERIAL_THUMB_SIZE} - ${RIPPLE_MAX_SIZE}) / 2);
+  opacity: 0;
+  position: relative;
+  /* Why 18px? */
+  top: calc((${MATERIAL_THUMB_SIZE} - ${RIPPLE_MAX_SIZE}) / 2 - 18px);
+  transition: opacity linear 0.3s;
+}
+
+:host(:active) #thumb::after,
+:host(:focus-visible) #thumb::after {
+  block-size: 0px;
+  content: "";
+  inline-size: 0px;
+  opacity: 1;
+  transition: none;
+}
+
+`);
+    }
+    return styleSheet;
+  };
+}
+
 /**
  * Add '$part-transitioning' part to the element if the element already has
  * a part name.
diff --git a/third_party/blink/renderer/core/svg/animation/smil_animation_sandwich.h b/third_party/blink/renderer/core/svg/animation/smil_animation_sandwich.h
index d5ef9ab..b4fd552 100644
--- a/third_party/blink/renderer/core/svg/animation/smil_animation_sandwich.h
+++ b/third_party/blink/renderer/core/svg/animation/smil_animation_sandwich.h
@@ -50,6 +50,70 @@
 
 // This class implements/helps with implementing the "sandwich model" from SMIL.
 // https://www.w3.org/TR/SMIL3/smil-animation.html#animationNS-AnimationSandwichModel
+//
+// A "sandwich" contains all the animation elements (actually timed elements in
+// our case because of how we handle <discard>) that targets a specific
+// attribute (or property) on a certain element.
+//
+// Consider the following simple example:
+//
+// <svg>
+//   <rect id="foo" width="100" height="100" fill="yellow">
+//     <set id="s1" attributeName="fill" to="blue" begin="1s; 3s" dur="1s"/>
+//     <set id="s2" attributeName="fill" to="lightblue" begin="1.5s" dur="2s"/>
+//   </rect>
+// </svg>
+//
+// In this case there is only one sandwich: <#foo, "fill">
+//
+// The sandwich is priority-sorted with the priority being derived from when
+// the currently active interval began - later is higher. In the above example
+// there are three intervals: [1s 2s) and [3s 4s) for the first <set> element
+// (in tree-order) and [1.5s 3.5s) for the second <set> element. The animation
+// elements are only active within the intervals defined (no fill="freeze").
+//
+// When the first interval of the first <set> starts (at 1s), it is the only
+// active animation and thus the only one to apply. When the second interval
+// starts (at 1.5s) its animation gets a higher priority and replaces the lower
+// priority animation from the first <set>. The first <set> then ends at 2s,
+// leaving the second <set> as the only active animation. When the first <set>
+// then starts again at 3s it gets a higher priority because of the later begin
+// time and replaces the animation from the second <set>. When the second <set>
+// ends at 3.5s nothing changes because the first <set> is still active. When
+// the second <set> ends again at 4s, no animation apply and the target reverts
+// to the base value (yellow) again.
+//
+// Schematically (right hand side exclusive):
+//
+// 0s -> 1s:   No animations apply (fill=yellow)
+//             Sandwich order: (s1) (s2) [both inactive]
+// 1s -> 1.5s: The first <set> apply (fill=blue)
+//             Sandwich order: s1 (s2)   [only s1 active]
+// 1.5s -> 2s: The second <set> apply (fill=lightblue)
+//             Sandwich order: s1 s2
+// 2s -> 3s:   The second <set> apply (fill=lightblue)
+//             Sandwich order: (s1) s2
+// 3s -> 3.5s: The first <set> apply (fill=blue)
+//             Sandwich order: s2 s1
+// 3.5s -> 4s: The first <set> apply (fill=blue)
+//             Sandwich order: (s2) s1
+// 4s -> ...:  No animations apply (fill=yellow)
+//
+// -----
+//
+// Implementation details:
+//
+// UpdateTiming() handles updates to interval and transitions the active state.
+//
+// UpdateSyncBases() handles the sorting described above (as well notifying
+// about new intervals).
+//
+// UpdateActiveAnimationStack() constructs a vector containing only the active
+// elements.
+//
+// ApplyAnimationValues() computes the actual animation value based on the
+// vector of active elements and applies it to the target element.
+//
 class SMILAnimationSandwich : public GarbageCollected<SMILAnimationSandwich> {
  public:
   using ScheduledVector = HeapVector<Member<SVGSMILElement>>;
diff --git a/third_party/blink/renderer/devtools/front_end/common/SegmentedRange.js b/third_party/blink/renderer/devtools/front_end/common/SegmentedRange.js
index f11db7c..97447fe 100644
--- a/third_party/blink/renderer/devtools/front_end/common/SegmentedRange.js
+++ b/third_party/blink/renderer/devtools/front_end/common/SegmentedRange.js
@@ -12,7 +12,7 @@
    */
   constructor(begin, end, data) {
     if (begin > end) {
-      console.assert(false, 'Invalid segment');
+      throw new Error('Invalid segment');
     }
     this.begin = begin;
     this.end = end;
diff --git a/third_party/blink/renderer/devtools/front_end/sdk/NetworkRequest.js b/third_party/blink/renderer/devtools/front_end/sdk/NetworkRequest.js
index 1ba954cb..5e74e634 100644
--- a/third_party/blink/renderer/devtools/front_end/sdk/NetworkRequest.js
+++ b/third_party/blink/renderer/devtools/front_end/sdk/NetworkRequest.js
@@ -1485,7 +1485,7 @@
       `This cookie didn't specify a SameSite attribute when it was stored and was defaulted to "SameSite=Lax" and broke the same rules specified in the SameSiteLax value. The cookie had to have been set with "SameSite=None" to enable third-party usage.`;
     case Protocol.Network.CookieBlockedReason.SameSiteNoneInsecure:
       return ls
-      `This cookie had the "SameSite=None" attribute and the connection was not secure. Cookies without SameSite restrictions must be sent over a secure connection.`;
+      `This cookie had the "SameSite=None" attribute but was not marked "Secure". Cookies without SameSite restrictions must be marked "Secure" and sent over a secure connection.`;
     case Protocol.Network.CookieBlockedReason.UserPreferences:
       return ls`This cookie was not sent due to user preferences.`;
     case Protocol.Network.CookieBlockedReason.UnknownError:
diff --git a/third_party/blink/renderer/devtools/front_end/sdk/sdk_strings.grdp b/third_party/blink/renderer/devtools/front_end/sdk/sdk_strings.grdp
index 9e4c73df..e7921c2b 100644
--- a/third_party/blink/renderer/devtools/front_end/sdk/sdk_strings.grdp
+++ b/third_party/blink/renderer/devtools/front_end/sdk/sdk_strings.grdp
@@ -3,6 +3,9 @@
   <message name="IDS_DEVTOOLS_021aa7730980fe55529e460ee1367179" desc="Tooltip to explain why a cookie was blocked">
     This cookie had the &quot;SameSite=Extended&quot; attribute and the request was made on a different site. The different site is outside of the cookie&apos;s trusted first-party set.
   </message>
+  <message name="IDS_DEVTOOLS_04608e209233c72a33b9a80e8ff2bd58" desc="Tooltip to explain why a cookie was blocked">
+    This cookie had the &quot;SameSite=None&quot; attribute but was not marked &quot;Secure&quot;. Cookies without SameSite restrictions must be marked &quot;Secure&quot; and sent over a secure connection.
+  </message>
   <message name="IDS_DEVTOOLS_07553a11db31a4433684be32cc4716e3" desc="Text in Network Manager">
     Cross-Origin Read Blocking (CORB) blocked cross-origin response <ph name="NETWORKREQUEST_URL__">$1s<ex>https://example.com</ex></ph> with MIME type <ph name="NETWORKREQUEST_MIMETYPE">$2s<ex>application</ex></ph>. See https://www.chromestatus.com/feature/5629709824032768 for more details.
   </message>
@@ -204,9 +207,6 @@
   <message name="IDS_DEVTOOLS_794f64c7f20487f6e13679201deeab3d" desc="Text in DOMDebugger Model">
     Picture-in-Picture
   </message>
-  <message name="IDS_DEVTOOLS_7a3a5ae00a1133b1042cf4edf671736a" desc="Tooltip to explain why a cookie was blocked">
-    This cookie had the &quot;SameSite=None&quot; attribute and the connection was not secure. Cookies without SameSite restrictions must be sent over a secure connection.
-  </message>
   <message name="IDS_DEVTOOLS_7c2bc755363ab11a1611bfa369654ff8" desc="Title of a setting under the Rendering category that can be invoked through the Command Menu">
     Show frames per second (FPS) meter
   </message>
diff --git a/third_party/blink/renderer/devtools/front_end/sources/DebuggerPlugin.js b/third_party/blink/renderer/devtools/front_end/sources/DebuggerPlugin.js
index b277a0d..0cd0eff1 100644
--- a/third_party/blink/renderer/devtools/front_end/sources/DebuggerPlugin.js
+++ b/third_party/blink/renderer/devtools/front_end/sources/DebuggerPlugin.js
@@ -435,6 +435,12 @@
     let startHighlight;
     let endHighlight;
 
+    const selectedCallFrame =
+        /** @type {!SDK.DebuggerModel.CallFrame} */ (UI.context.flavor(SDK.DebuggerModel.CallFrame));
+    if (!selectedCallFrame) {
+      return null;
+    }
+
     if (textSelection && !textSelection.isEmpty()) {
       if (textSelection.startLine !== textSelection.endLine || textSelection.startLine !== mouseLine ||
           mouseColumn < textSelection.startColumn || mouseColumn > textSelection.endColumn) {
@@ -476,24 +482,53 @@
         if (tokenBefore.type === 'js-meta') {
           break;
         }
+        if (tokenBefore.type === 'js-string-2') {
+          // If we hit a template literal, find the opening ` in this line.
+          // TODO(bmeurer): We should eventually replace this tokenization
+          // approach with a proper soluation based on parsing, maybe reusing
+          // the Parser and AST inside V8 for this (or potentially relying on
+          // acorn to do the job).
+          if (tokenBefore.endColumn < 2) {
+            return null;
+          }
+          startHighlight = line.lastIndexOf('`', tokenBefore.endColumn - 2);
+          if (startHighlight < 0) {
+            return null;
+          }
+          break;
+        }
         startHighlight = tokenBefore.startColumn;
       }
     }
 
+    // The eager evaluation on works sort of reliably within the top-most scope of
+    // the selected call frame, so don't even try outside the top-most scope.
+    const [scope] = selectedCallFrame.scopeChain();
+    if (scope && scope.startLocation() && scope.endLocation()) {
+      if (editorLineNumber < scope.startLocation().lineNumber) {
+        return null;
+      }
+      if (editorLineNumber === scope.startLocation().lineNumber &&
+          startHighlight < scope.startLocation().columnNumber) {
+        return null;
+      }
+      if (editorLineNumber > scope.endLocation().lineNumber) {
+        return null;
+      }
+      if (editorLineNumber === scope.endLocation().lineNumber && endHighlight > scope.endLocation().columnNumber) {
+        return null;
+      }
+    }
+
     let objectPopoverHelper;
     let highlightDescriptor;
 
     return {
       box: anchorBox,
       show: async popover => {
-        const selectedCallFrame = UI.context.flavor(SDK.DebuggerModel.CallFrame);
-        if (!selectedCallFrame) {
-          return false;
-        }
         const evaluationText = this._textEditor.line(editorLineNumber).substring(startHighlight, endHighlight + 1);
         const resolvedText = await Sources.SourceMapNamesResolver.resolveExpression(
-            /** @type {!SDK.DebuggerModel.CallFrame} */ (selectedCallFrame), evaluationText, this._uiSourceCode,
-            editorLineNumber, startHighlight, endHighlight);
+            selectedCallFrame, evaluationText, this._uiSourceCode, editorLineNumber, startHighlight, endHighlight);
         const result = await selectedCallFrame.evaluate({
           expression: resolvedText || evaluationText,
           objectGroup: 'popover',
diff --git a/third_party/blink/renderer/devtools/front_end/timeline/UIDevtoolsUtils.js b/third_party/blink/renderer/devtools/front_end/timeline/UIDevtoolsUtils.js
index 9fc9ae96..f9f7214 100644
--- a/third_party/blink/renderer/devtools/front_end/timeline/UIDevtoolsUtils.js
+++ b/third_party/blink/renderer/devtools/front_end/timeline/UIDevtoolsUtils.js
@@ -36,7 +36,7 @@
    * @return {boolean}
    */
   static isUiDevTools() {
-    return Runtime.queryParam('uiDevTools') === 'true';
+    return Root.Runtime.queryParam('uiDevTools') === 'true';
   }
 
   /**
diff --git a/third_party/blink/renderer/devtools/tests/front_end/common/SegmentedRange.ts b/third_party/blink/renderer/devtools/tests/front_end/common/SegmentedRange.ts
new file mode 100644
index 0000000..ce0f789f7
--- /dev/null
+++ b/third_party/blink/renderer/devtools/tests/front_end/common/SegmentedRange.ts
@@ -0,0 +1,180 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+const { assert } = chai;
+
+import { default as SegmentedRange, Segment } from '../../../front_end/common/SegmentedRange.js';
+
+describe('Segment', () => {
+  it('calculates intersections', () => {
+    const segmentA = new Segment(1, 2, 'A');
+    const segmentB = new Segment(1.5, 2.5, 'B');
+    const segmentC = new Segment(3, 5, 'C');
+
+    assert.isTrue(segmentA.intersects(segmentB));
+    assert.isFalse(segmentA.intersects(segmentC));
+  });
+
+  it('throws for invalid segments', () => {
+    assert.throws(() => new Segment(3, 2, 'V'));
+  });
+});
+
+describe('SegmentedRange', () => {
+  let segmentedRange: SegmentedRange;
+
+  function mergeSegments(first, second) {
+    const inOrder = first.end >= second.begin;
+    const matchingData = first.data === second.data;
+    return inOrder && matchingData ? first : null;
+  }
+
+  beforeEach(() => {
+    segmentedRange = new SegmentedRange(mergeSegments);
+  });
+
+  it('handles single ranges', () => {
+    segmentedRange.append(new Segment(0, 1, 'A'));
+
+    assert.deepEqual(segmentedRange.segments(), [{ begin: 0, end: 1, data: 'A' }]);
+  });
+
+  it('handles two adjacent ranges', () => {
+    const segmentA = new Segment(1, 2, 'A');
+    const segmentB = new Segment(2, 3, 'B');
+
+    segmentedRange.append(segmentA);
+    segmentedRange.append(segmentB);
+
+    assert.deepEqual(segmentedRange.segments(), [{ begin: 1, end: 2, data: 'A' }, { begin: 2, end: 3, data: 'B' }]);
+  });
+
+  it('handles two overlapping mergeable ranges', () => {
+    const segmentA = new Segment(1, 2, 'A');
+    const segmentB = new Segment(1.5, 3, 'A');
+
+    segmentedRange.append(segmentA);
+    segmentedRange.append(segmentB);
+
+    assert.deepEqual(segmentedRange.segments(), [{ begin: 1, end: 3, data: 'A' }]);
+  });
+
+  it('handles multiple overlapping mergeable ranges', () => {
+    const segmentA = new Segment(1, 2, 'A');
+    const segmentB = new Segment(3, 5, 'A');
+    const segmentC = new Segment(1.5, 3.5, 'A');
+
+    segmentedRange.append(segmentA);
+    segmentedRange.append(segmentB);
+    segmentedRange.append(segmentC);
+
+    assert.deepEqual(segmentedRange.segments(), [{ begin: 1, end: 5, data: 'A' }]);
+  });
+
+  it('handles multiple overlapping non-mergeable ranges', () => {
+    const segmentA = new Segment(1, 2, 'A');
+    const segmentB = new Segment(3, 5, 'A');
+    const segmentC = new Segment(1.5, 3.5, 'B');
+
+    segmentedRange.append(segmentA);
+    segmentedRange.append(segmentB);
+    segmentedRange.append(segmentC);
+
+    assert.deepEqual(segmentedRange.segments(), [{ begin: 1, end: 1.5, data: 'A' }, { begin: 1.5, end: 3.5, data: 'B' },
+        { begin: 3.5, end: 5, data: 'A' }]);
+  });
+
+  it('handles two overlapping non-mergeable ranges', () => {
+    const segmentA = new Segment(1, 2, 'A');
+    const segmentB = new Segment(1.5, 3, 'B');
+
+    segmentedRange.append(segmentA);
+    segmentedRange.append(segmentB);
+
+    assert.deepEqual(segmentedRange.segments(), [{ begin: 1, end: 1.5, data: 'A' }, { begin: 1.5, end: 3, data: 'B' }]);
+  });
+
+  it('handles nested, mergeable ranges', () => {
+    const segmentA = new Segment(0, 4, 'A');
+    const segmentB = new Segment(2, 3, 'A');
+
+    segmentedRange.append(segmentA);
+    segmentedRange.append(segmentB);
+
+    assert.deepEqual(segmentedRange.segments(), [{ begin: 0, end: 4, data: 'A' }]);
+  });
+
+  it('handles nested, non-mergeable ranges', () => {
+    const segmentA = new Segment(0, 4, 'A');
+    const segmentB = new Segment(2, 3, 'B');
+
+    segmentedRange.append(segmentA);
+    segmentedRange.append(segmentB);
+
+    assert.deepEqual(segmentedRange.segments(), [{ begin: 0, end: 2, data: 'A' }, { begin: 2, end: 3, data: 'B' },
+        { begin: 3, end: 4, data: 'A' }]);
+  });
+
+  it('handles out-of-order, mergeable ranges', () => {
+    const segmentA = new Segment(0, 2, 'A');
+    const segmentB = new Segment(3, 5, 'A');
+    const segmentC = new Segment(2, 3, 'A');
+
+    segmentedRange.append(segmentA);
+    segmentedRange.append(segmentB);
+    segmentedRange.append(segmentC);
+
+    assert.deepEqual(segmentedRange.segments(), [{ begin: 0, end: 5, data: 'A' }]);
+  });
+
+  it('handles out-of-order, non-mergeable ranges', () => {
+    const segmentA = new Segment(0, 2, 'A');
+    const segmentB = new Segment(3, 5, 'A');
+    const segmentC = new Segment(2, 3, 'B');
+
+    segmentedRange.append(segmentA);
+    segmentedRange.append(segmentB);
+    segmentedRange.append(segmentC);
+
+    assert.deepEqual(segmentedRange.segments(), [{ begin: 0, end: 2, data: 'A' }, { begin: 2, end: 3, data: 'B' },
+        { begin: 3, end: 5, data: 'A' }]);
+  });
+
+  it('handles one segment consuming many mergeable ranges', () => {
+    const segmentA = new Segment(0, 1, 'A');
+    const segmentB = new Segment(2, 3, 'A');
+    const segmentC = new Segment(4, 5, 'A');
+    const segmentD = new Segment(6, 7, 'A');
+
+    // E merges A through D.
+    const segmentE = new Segment(2, 6, 'A');
+
+    segmentedRange.append(segmentA);
+    segmentedRange.append(segmentB);
+    segmentedRange.append(segmentC);
+    segmentedRange.append(segmentD);
+    segmentedRange.append(segmentE);
+
+    assert.deepEqual(segmentedRange.segments(), [{ begin: 0, end: 1, data: 'A' }, { begin: 2, end: 7, data: 'A' }]);
+  });
+
+  it('handles one segment consuming many non-mergeable ranges', () => {
+    const segmentA = new Segment(0, 1, 'A');
+    const segmentB = new Segment(2, 3, 'A');
+    const segmentC = new Segment(4, 5, 'A');
+    const segmentD = new Segment(6, 7, 'A');
+
+    // E merges A through D.
+    const segmentE = new Segment(2, 6, 'B');
+
+    segmentedRange.append(segmentA);
+    segmentedRange.append(segmentB);
+    segmentedRange.append(segmentC);
+    segmentedRange.append(segmentD);
+    segmentedRange.append(segmentE);
+
+    assert.deepEqual(segmentedRange.segments(), [{ begin: 0, end: 1, data: 'A' }, { begin: 2, end: 6, data: 'B' },
+        { begin: 6, end: 7, data: 'A' }]);
+  });
+});
diff --git a/third_party/blink/renderer/modules/modules_idl_files.gni b/third_party/blink/renderer/modules/modules_idl_files.gni
index c3425692..b95055b 100644
--- a/third_party/blink/renderer/modules/modules_idl_files.gni
+++ b/third_party/blink/renderer/modules/modules_idl_files.gni
@@ -854,7 +854,7 @@
           "webgpu/gpu_fence_descriptor.idl",
           "webgpu/gpu_limits.idl",
           "webgpu/gpu_object_descriptor_base.idl",
-          "webgpu/gpu_origin_3d.idl",
+          "webgpu/gpu_origin_3d_dict.idl",
           "webgpu/gpu_pipeline_descriptor_base.idl",
           "webgpu/gpu_pipeline_layout_descriptor.idl",
           "webgpu/gpu_pipeline_stage_descriptor.idl",
diff --git a/third_party/blink/renderer/modules/speech/speech_recognition.cc b/third_party/blink/renderer/modules/speech/speech_recognition.cc
index 013294d..b1b94b6 100644
--- a/third_party/blink/renderer/modules/speech/speech_recognition.cc
+++ b/third_party/blink/renderer/modules/speech/speech_recognition.cc
@@ -60,9 +60,8 @@
       GetExecutionContext()->GetTaskRunner(blink::TaskType::kMiscPlatformAPI);
   mojo::PendingRemote<mojom::blink::SpeechRecognitionSessionClient>
       session_client;
-  binding_.Bind(session_client.InitWithNewPipeAndPassReceiver(),
-                GetExecutionContext()->GetInterfaceInvalidator(), task_runner);
-  binding_.set_connection_error_handler(WTF::Bind(
+  receiver_.Bind(session_client.InitWithNewPipeAndPassReceiver(), task_runner);
+  receiver_.set_disconnect_handler(WTF::Bind(
       &SpeechRecognition::OnConnectionError, WrapWeakPersistent(this)));
 
   mojo::PendingReceiver<mojom::blink::SpeechRecognitionSession>
@@ -175,7 +174,7 @@
   started_ = false;
   stopping_ = false;
   session_.reset();
-  binding_.Close();
+  receiver_.reset();
   DispatchEvent(*Event::Create(event_type_names::kEnd));
 }
 
@@ -189,6 +188,7 @@
 
 void SpeechRecognition::ContextDestroyed(ExecutionContext*) {
   controller_ = nullptr;
+  receiver_.reset();
 }
 
 bool SpeechRecognition::HasPendingActivity() const {
@@ -222,7 +222,7 @@
       controller_(SpeechRecognitionController::From(frame)),
       started_(false),
       stopping_(false),
-      binding_(this) {}
+      receiver_(this) {}
 
 SpeechRecognition::~SpeechRecognition() = default;
 
diff --git a/third_party/blink/renderer/modules/speech/speech_recognition.h b/third_party/blink/renderer/modules/speech/speech_recognition.h
index fb936e1..46e890c 100644
--- a/third_party/blink/renderer/modules/speech/speech_recognition.h
+++ b/third_party/blink/renderer/modules/speech/speech_recognition.h
@@ -26,6 +26,7 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_SPEECH_SPEECH_RECOGNITION_H_
 #define THIRD_PARTY_BLINK_RENDERER_MODULES_SPEECH_SPEECH_RECOGNITION_H_
 
+#include "mojo/public/cpp/bindings/receiver.h"
 #include "third_party/blink/public/mojom/speech/speech_recognizer.mojom-blink.h"
 #include "third_party/blink/public/platform/web_private_ptr.h"
 #include "third_party/blink/renderer/bindings/core/v8/active_script_wrappable.h"
@@ -36,7 +37,6 @@
 #include "third_party/blink/renderer/modules/speech/speech_grammar_list.h"
 #include "third_party/blink/renderer/modules/speech/speech_recognition_result.h"
 #include "third_party/blink/renderer/platform/heap/handle.h"
-#include "third_party/blink/renderer/platform/mojo/revocable_binding.h"
 #include "third_party/blink/renderer/platform/mojo/revocable_interface_ptr.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
 
@@ -135,7 +135,7 @@
   bool started_;
   bool stopping_;
   HeapVector<Member<SpeechRecognitionResult>> final_results_;
-  RevocableBinding<mojom::blink::SpeechRecognitionSessionClient> binding_;
+  mojo::Receiver<mojom::blink::SpeechRecognitionSessionClient> receiver_;
   RevocableInterfacePtr<mojom::blink::SpeechRecognitionSession> session_;
 };
 
diff --git a/third_party/blink/renderer/modules/webgpu/dawn_conversions.cc b/third_party/blink/renderer/modules/webgpu/dawn_conversions.cc
index 804bd054..96aa518 100644
--- a/third_party/blink/renderer/modules/webgpu/dawn_conversions.cc
+++ b/third_party/blink/renderer/modules/webgpu/dawn_conversions.cc
@@ -8,7 +8,7 @@
 
 #include "third_party/blink/renderer/bindings/modules/v8/double_sequence_or_gpu_color_dict.h"
 #include "third_party/blink/renderer/bindings/modules/v8/unsigned_long_sequence_or_gpu_extent_3d_dict.h"
-#include "third_party/blink/renderer/modules/webgpu/gpu_origin_3d.h"
+#include "third_party/blink/renderer/bindings/modules/v8/unsigned_long_sequence_or_gpu_origin_3d_dict.h"
 #include "third_party/blink/renderer/modules/webgpu/gpu_pipeline_stage_descriptor.h"
 #include "third_party/blink/renderer/modules/webgpu/gpu_shader_module.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
@@ -767,13 +767,30 @@
   return dawn_extent;
 }
 
-DawnOrigin3D AsDawnType(const GPUOrigin3D* webgpu_origin) {
+DawnOrigin3D AsDawnType(
+    const UnsignedLongSequenceOrGPUOrigin3DDict* webgpu_origin) {
   DCHECK(webgpu_origin);
 
   DawnOrigin3D dawn_origin = {};
-  dawn_origin.x = webgpu_origin->x();
-  dawn_origin.y = webgpu_origin->y();
-  dawn_origin.z = webgpu_origin->z();
+
+  if (webgpu_origin->IsUnsignedLongSequence()) {
+    const Vector<uint32_t>& webgpu_origin_sequence =
+        webgpu_origin->GetAsUnsignedLongSequence();
+    DCHECK_EQ(webgpu_origin_sequence.size(), 3UL);
+    dawn_origin.x = webgpu_origin_sequence[0];
+    dawn_origin.y = webgpu_origin_sequence[1];
+    dawn_origin.z = webgpu_origin_sequence[2];
+
+  } else if (webgpu_origin->IsGPUOrigin3DDict()) {
+    const GPUOrigin3DDict* webgpu_origin_3d_dict =
+        webgpu_origin->GetAsGPUOrigin3DDict();
+    dawn_origin.x = webgpu_origin_3d_dict->x();
+    dawn_origin.y = webgpu_origin_3d_dict->y();
+    dawn_origin.z = webgpu_origin_3d_dict->z();
+
+  } else {
+    NOTREACHED();
+  }
 
   return dawn_origin;
 }
diff --git a/third_party/blink/renderer/modules/webgpu/dawn_conversions.h b/third_party/blink/renderer/modules/webgpu/dawn_conversions.h
index d1600a8..bffacb6 100644
--- a/third_party/blink/renderer/modules/webgpu/dawn_conversions.h
+++ b/third_party/blink/renderer/modules/webgpu/dawn_conversions.h
@@ -21,9 +21,9 @@
 
 class DoubleSequenceOrGPUColorDict;
 class GPUColorDict;
-class GPUOrigin3D;
 class GPUPipelineStageDescriptor;
 class UnsignedLongSequenceOrGPUExtent3DDict;
+class UnsignedLongSequenceOrGPUOrigin3DDict;
 
 // Convert WebGPU bitfield values to Dawn enums. These have the same value.
 template <typename DawnEnum>
@@ -42,7 +42,7 @@
 DawnColor AsDawnType(const GPUColorDict*);
 DawnColor AsDawnType(const DoubleSequenceOrGPUColorDict*);
 DawnExtent3D AsDawnType(const UnsignedLongSequenceOrGPUExtent3DDict*);
-DawnOrigin3D AsDawnType(const GPUOrigin3D*);
+DawnOrigin3D AsDawnType(const UnsignedLongSequenceOrGPUOrigin3DDict*);
 
 using OwnedPipelineStageDescriptor =
     std::tuple<DawnPipelineStageDescriptor, std::unique_ptr<char[]>>;
diff --git a/third_party/blink/renderer/modules/webgpu/gpu.idl b/third_party/blink/renderer/modules/webgpu/gpu.idl
index c2cf76db..ac2e51f 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu.idl
+++ b/third_party/blink/renderer/modules/webgpu/gpu.idl
@@ -7,6 +7,7 @@
 typedef unsigned long long GPUBufferSize;
 typedef (sequence<double> or GPUColorDict) GPUColor;
 typedef (sequence<unsigned long> or GPUExtent3DDict) GPUExtent3D;
+typedef (sequence<unsigned long> or GPUOrigin3DDict) GPUOrigin3D;
 
 [
     RuntimeEnabled=WebGPU
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_command_encoder.cc b/third_party/blink/renderer/modules/webgpu/gpu_command_encoder.cc
index b783cb42..4bbe51b 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_command_encoder.cc
+++ b/third_party/blink/renderer/modules/webgpu/gpu_command_encoder.cc
@@ -6,6 +6,7 @@
 
 #include "third_party/blink/renderer/bindings/modules/v8/double_sequence_or_gpu_color_dict.h"
 #include "third_party/blink/renderer/bindings/modules/v8/unsigned_long_sequence_or_gpu_extent_3d_dict.h"
+#include "third_party/blink/renderer/bindings/modules/v8/unsigned_long_sequence_or_gpu_origin_3d_dict.h"
 #include "third_party/blink/renderer/modules/webgpu/dawn_conversions.h"
 #include "third_party/blink/renderer/modules/webgpu/gpu_buffer.h"
 #include "third_party/blink/renderer/modules/webgpu/gpu_buffer_copy_view.h"
@@ -15,7 +16,6 @@
 #include "third_party/blink/renderer/modules/webgpu/gpu_compute_pass_descriptor.h"
 #include "third_party/blink/renderer/modules/webgpu/gpu_compute_pass_encoder.h"
 #include "third_party/blink/renderer/modules/webgpu/gpu_device.h"
-#include "third_party/blink/renderer/modules/webgpu/gpu_origin_3d.h"
 #include "third_party/blink/renderer/modules/webgpu/gpu_render_pass_color_attachment_descriptor.h"
 #include "third_party/blink/renderer/modules/webgpu/gpu_render_pass_depth_stencil_attachment_descriptor.h"
 #include "third_party/blink/renderer/modules/webgpu/gpu_render_pass_descriptor.h"
@@ -36,6 +36,23 @@
   return true;
 }
 
+bool ValidateTextureCopyView(GPUTextureCopyView* texture_copy_view,
+                             ExceptionState& exception_state) {
+  DCHECK(texture_copy_view);
+
+  if (texture_copy_view->hasOrigin()) {
+    const UnsignedLongSequenceOrGPUOrigin3DDict origin =
+        texture_copy_view->origin();
+    if (origin.IsUnsignedLongSequence() &&
+        origin.GetAsUnsignedLongSequence().size() != 3) {
+      exception_state.ThrowRangeError(
+          "texture copy view origin length must be 3");
+      return false;
+    }
+  }
+  return true;
+}
+
 DawnRenderPassColorAttachmentDescriptor AsDawnType(
     const GPURenderPassColorAttachmentDescriptor* webgpu_desc) {
   DCHECK(webgpu_desc);
@@ -139,7 +156,11 @@
   dawn_view.texture = webgpu_view->texture()->GetHandle();
   dawn_view.mipLevel = webgpu_view->mipLevel();
   dawn_view.arrayLayer = webgpu_view->arrayLayer();
-  dawn_view.origin = AsDawnType(webgpu_view->origin());
+  if (webgpu_view->hasOrigin()) {
+    dawn_view.origin = AsDawnType(&webgpu_view->origin());
+  } else {
+    dawn_view.origin = DawnOrigin3D{};
+  }
 
   return dawn_view;
 }
@@ -262,7 +283,8 @@
     GPUTextureCopyView* destination,
     UnsignedLongSequenceOrGPUExtent3DDict& copy_size,
     ExceptionState& exception_state) {
-  if (!ValidateCopySize(copy_size, exception_state)) {
+  if (!ValidateCopySize(copy_size, exception_state) ||
+      !ValidateTextureCopyView(destination, exception_state)) {
     return;
   }
 
@@ -279,7 +301,8 @@
     GPUBufferCopyView* destination,
     UnsignedLongSequenceOrGPUExtent3DDict& copy_size,
     ExceptionState& exception_state) {
-  if (!ValidateCopySize(copy_size, exception_state)) {
+  if (!ValidateCopySize(copy_size, exception_state) ||
+      !ValidateTextureCopyView(source, exception_state)) {
     return;
   }
 
@@ -296,7 +319,9 @@
     GPUTextureCopyView* destination,
     UnsignedLongSequenceOrGPUExtent3DDict& copy_size,
     ExceptionState& exception_state) {
-  if (!ValidateCopySize(copy_size, exception_state)) {
+  if (!ValidateCopySize(copy_size, exception_state) ||
+      !ValidateTextureCopyView(source, exception_state) ||
+      !ValidateTextureCopyView(destination, exception_state)) {
     return;
   }
 
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_origin_3d.idl b/third_party/blink/renderer/modules/webgpu/gpu_origin_3d_dict.idl
similarity index 90%
rename from third_party/blink/renderer/modules/webgpu/gpu_origin_3d.idl
rename to third_party/blink/renderer/modules/webgpu/gpu_origin_3d_dict.idl
index fbfb79b..741be63 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_origin_3d.idl
+++ b/third_party/blink/renderer/modules/webgpu/gpu_origin_3d_dict.idl
@@ -4,7 +4,7 @@
 
 // https://gpuweb.github.io/gpuweb/
 
-dictionary GPUOrigin3D {
+dictionary GPUOrigin3DDict {
     unsigned long x = 0;
     unsigned long y = 0;
     unsigned long z = 0;
diff --git a/third_party/blink/renderer/modules/websockets/mock_websocket_channel.h b/third_party/blink/renderer/modules/websockets/mock_websocket_channel.h
index 3c9e0aa..de129bd 100644
--- a/third_party/blink/renderer/modules/websockets/mock_websocket_channel.h
+++ b/third_party/blink/renderer/modules/websockets/mock_websocket_channel.h
@@ -52,6 +52,7 @@
     FailMock(reason, level, location.get());
   }
   MOCK_METHOD0(Disconnect, void());
+  MOCK_METHOD0(CancelHandshake, void());
   MOCK_METHOD0(ApplyBackpressure, void());
   MOCK_METHOD0(RemoveBackpressure, void());
 };
diff --git a/third_party/blink/renderer/modules/websockets/websocket_channel.h b/third_party/blink/renderer/modules/websockets/websocket_channel.h
index d2f1df6a..5b315322 100644
--- a/third_party/blink/renderer/modules/websockets/websocket_channel.h
+++ b/third_party/blink/renderer/modules/websockets/websocket_channel.h
@@ -102,6 +102,10 @@
   // Do not call any methods after calling this method.
   virtual void Disconnect() = 0;  // Will suppress didClose().
 
+  // Cancel the WebSocket handshake. Does nothing if the connection is already
+  // established. Do not call any other methods after this one.
+  virtual void CancelHandshake() = 0;
+
   // Clients can call ApplyBackpressure() to indicate that they want to stop
   // receiving new messages. WebSocketChannelClient::DidReceive*Message() may
   // still be called after this, until existing flow control quota is used up.
diff --git a/third_party/blink/renderer/modules/websockets/websocket_channel_impl.cc b/third_party/blink/renderer/modules/websockets/websocket_channel_impl.cc
index 4d58e824..da930a59 100644
--- a/third_party/blink/renderer/modules/websockets/websocket_channel_impl.cc
+++ b/third_party/blink/renderer/modules/websockets/websocket_channel_impl.cc
@@ -417,6 +417,18 @@
   Dispose();
 }
 
+void WebSocketChannelImpl::CancelHandshake() {
+  NETWORK_DVLOG(1) << this << " CancelHandshake()";
+  if (GetState() != State::kConnecting)
+    return;
+
+  // This may still disconnect even if the handshake is complete if we haven't
+  // got the message yet.
+  // TODO(ricea): Plumb it through to the network stack to fix the race
+  // condition.
+  Disconnect();
+}
+
 void WebSocketChannelImpl::ApplyBackpressure() {
   backpressure_ = true;
 }
diff --git a/third_party/blink/renderer/modules/websockets/websocket_channel_impl.h b/third_party/blink/renderer/modules/websockets/websocket_channel_impl.h
index 317ddb5a..4d5de65 100644
--- a/third_party/blink/renderer/modules/websockets/websocket_channel_impl.h
+++ b/third_party/blink/renderer/modules/websockets/websocket_channel_impl.h
@@ -107,6 +107,7 @@
             mojom::ConsoleMessageLevel,
             std::unique_ptr<SourceLocation>) override;
   void Disconnect() override;
+  void CancelHandshake() override;
   void ApplyBackpressure() override;
   void RemoveBackpressure() override;
 
diff --git a/third_party/blink/renderer/modules/websockets/websocket_stream.cc b/third_party/blink/renderer/modules/websockets/websocket_stream.cc
index 49252d33..053fb14 100644
--- a/third_party/blink/renderer/modules/websockets/websocket_stream.cc
+++ b/third_party/blink/renderer/modules/websockets/websocket_stream.cc
@@ -14,6 +14,7 @@
 #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_throw_dom_exception.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_websocket_close_info.h"
+#include "third_party/blink/renderer/core/dom/abort_signal.h"
 #include "third_party/blink/renderer/core/execution_context/execution_context.h"
 #include "third_party/blink/renderer/core/streams/readable_stream.h"
 #include "third_party/blink/renderer/core/streams/readable_stream_default_controller_interface.h"
@@ -598,7 +599,21 @@
   DVLOG(1) << "WebSocketStream " << this << " Connect() url=" << url
            << " options=" << options;
 
-  // TODO(ricea): Support AbortSignal.
+  auto* signal = options->signal();
+  if (signal && signal->aborted()) {
+    auto exception = V8ThrowDOMException::CreateOrEmpty(
+        script_state->GetIsolate(), DOMExceptionCode::kAbortError,
+        "WebSocket handshake was aborted");
+    connection_resolver_->Reject(exception);
+    closed_resolver_->Reject(exception);
+    return;
+  }
+
+  if (signal) {
+    signal->AddAlgorithm(
+        WTF::Bind(&WebSocketStream::OnAbort, WrapWeakPersistent(this)));
+  }
+
   auto result = common_.Connect(
       ExecutionContext::From(script_state), url,
       options->hasProtocols() ? options->protocols() : Vector<String>(),
@@ -680,6 +695,22 @@
                                             "A network error occurred");
 }
 
+void WebSocketStream::OnAbort() {
+  DVLOG(1) << "WebSocketStream " << this << " OnAbort()";
+
+  if (was_ever_connected_ || !channel_)
+    return;
+
+  channel_->CancelHandshake();
+  channel_ = nullptr;
+
+  auto exception = V8ThrowDOMException::CreateOrEmpty(
+      script_state_->GetIsolate(), DOMExceptionCode::kAbortError,
+      "WebSocket handshake was aborted");
+  connection_resolver_->Reject(exception);
+  closed_resolver_->Reject(exception);
+}
+
 WebSocketCloseInfo* WebSocketStream::MakeCloseInfo(uint16_t code,
                                                    const String& reason) {
   DVLOG(1) << "WebSocketStream MakeCloseInfo() code=" << code
diff --git a/third_party/blink/renderer/modules/websockets/websocket_stream.h b/third_party/blink/renderer/modules/websockets/websocket_stream.h
index 1438614e..df0ecc29 100644
--- a/third_party/blink/renderer/modules/websockets/websocket_stream.h
+++ b/third_party/blink/renderer/modules/websockets/websocket_stream.h
@@ -108,6 +108,7 @@
   void CloseInternal(int code,
                      const String& reason,
                      ExceptionState& exception_state);
+  void OnAbort();
 
   v8::Local<v8::Value> CreateNetworkErrorDOMException();
   static WebSocketCloseInfo* MakeCloseInfo(uint16_t code, const String& reason);
diff --git a/third_party/blink/renderer/platform/BUILD.gn b/third_party/blink/renderer/platform/BUILD.gn
index c103b596..854d6c6 100644
--- a/third_party/blink/renderer/platform/BUILD.gn
+++ b/third_party/blink/renderer/platform/BUILD.gn
@@ -1227,9 +1227,7 @@
     "mojo/interface_invalidator.cc",
     "mojo/interface_invalidator.h",
     "mojo/mojo_helper.h",
-    "mojo/revocable_binding.h",
     "mojo/revocable_interface_ptr.h",
-    "mojo/revocable_strong_binding.h",
     "mojo/string16_mojom_traits.cc",
     "mojo/string16_mojom_traits.h",
     "p2p/empty_network_manager.cc",
diff --git a/third_party/blink/renderer/platform/heap/BUILD.gn b/third_party/blink/renderer/platform/heap/BUILD.gn
index 01955c9..d696d261 100644
--- a/third_party/blink/renderer/platform/heap/BUILD.gn
+++ b/third_party/blink/renderer/platform/heap/BUILD.gn
@@ -30,6 +30,7 @@
     "blink_gc_memory_dump_provider.h",
     "cancelable_task_scheduler.cc",
     "cancelable_task_scheduler.h",
+    "disallow_new_wrapper.h",
     "finalizer_traits.h",
     "garbage_collected.h",
     "gc_info.cc",
diff --git a/third_party/blink/renderer/platform/heap/disallow_new_wrapper.h b/third_party/blink/renderer/platform/heap/disallow_new_wrapper.h
new file mode 100644
index 0000000..118cadb
--- /dev/null
+++ b/third_party/blink/renderer/platform/heap/disallow_new_wrapper.h
@@ -0,0 +1,39 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_HEAP_DISALLOW_NEW_WRAPPER_H_
+#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_HEAP_DISALLOW_NEW_WRAPPER_H_
+
+#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
+
+namespace blink {
+
+// DisallowNewWrapper wraps a disallow new type in a GarbageCollected class.
+template <typename T>
+class DisallowNewWrapper : public GarbageCollected<DisallowNewWrapper<T>> {
+ public:
+  explicit DisallowNewWrapper(const T& value) : value_(value) {
+    static_assert(WTF::IsDisallowNew<T>::value,
+                  "T needs to be a disallow new type");
+    static_assert(WTF::IsTraceable<T>::value, "T needs to be traceable");
+  }
+
+  const T& Value() const { return value_; }
+
+  void Trace(Visitor* visitor) { visitor->Trace(value_); }
+
+ private:
+  T value_;
+};
+
+// Wraps a disallow new type in a GarbageCollected class, making it possible to
+// be referenced off heap from a Persistent.
+template <typename T>
+DisallowNewWrapper<T>* WrapDisallowNew(const T& value) {
+  return MakeGarbageCollected<DisallowNewWrapper<T>>(value);
+}
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_HEAP_DISALLOW_NEW_WRAPPER_H_
diff --git a/third_party/blink/renderer/platform/mojo/interface_invalidator_test.cc b/third_party/blink/renderer/platform/mojo/interface_invalidator_test.cc
index 901b04af..4f6d275 100644
--- a/third_party/blink/renderer/platform/mojo/interface_invalidator_test.cc
+++ b/third_party/blink/renderer/platform/mojo/interface_invalidator_test.cc
@@ -16,9 +16,7 @@
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/platform/scheduler/test/renderer_scheduler_test_support.h"
 #include "third_party/blink/renderer/platform/mojo/interface_invalidator.h"
-#include "third_party/blink/renderer/platform/mojo/revocable_binding.h"
 #include "third_party/blink/renderer/platform/mojo/revocable_interface_ptr.h"
-#include "third_party/blink/renderer/platform/mojo/revocable_strong_binding.h"
 
 namespace blink {
 
@@ -341,279 +339,6 @@
   EXPECT_FALSE(result);
 }
 
-class RevocablePingServiceImpl : public PingServiceImplBase {
- public:
-  RevocablePingServiceImpl(
-      mojo::InterfaceRequest<mojo::test::blink::PingService> request,
-      InterfaceInvalidator* invalidator,
-      bool send_response = true)
-      : PingServiceImplBase(send_response),
-        error_handler_called_(false),
-        binding_(this, std::move(request), invalidator) {
-    binding_.set_connection_error_handler(
-        base::BindOnce(DoSetFlag, &error_handler_called_));
-  }
-
-  ~RevocablePingServiceImpl() override {}
-
-  bool error_handler_called() { return error_handler_called_; }
-
-  RevocableBinding<mojo::test::blink::PingService>* binding() {
-    return &binding_;
-  }
-
- private:
-  bool error_handler_called_;
-  RevocableBinding<mojo::test::blink::PingService> binding_;
-
-  DISALLOW_COPY_AND_ASSIGN(RevocablePingServiceImpl);
-};
-
-TEST_F(InterfaceInvalidatorTest, DestroyInvalidatesRevocableBinding) {
-  mojo::test::blink::PingServicePtr ptr;
-  auto invalidator = std::make_unique<InterfaceInvalidator>();
-  RevocablePingServiceImpl impl(MakeRequest(&ptr), invalidator.get());
-
-  bool ping_called = false;
-  ptr->Ping(base::BindRepeating(DoSetFlag, &ping_called));
-  base::RunLoop().RunUntilIdle();
-  ASSERT_TRUE(ping_called);
-
-  bool error_handler_called = false;
-  ptr.set_connection_error_handler(
-      base::BindOnce(DoSetFlag, &error_handler_called));
-
-  invalidator.reset();
-  impl.set_ping_handler(base::BindRepeating([] { FAIL(); }));
-  ptr->Ping(base::BindRepeating([] { FAIL(); }));
-  base::RunLoop().RunUntilIdle();
-
-  EXPECT_TRUE(error_handler_called);
-  EXPECT_TRUE(impl.error_handler_called());
-  EXPECT_TRUE(ptr.encountered_error());
-  EXPECT_TRUE(ptr);
-  EXPECT_TRUE(*impl.binding());
-}
-
-TEST_F(InterfaceInvalidatorTest, InvalidateBindingBeforeResponse) {
-  mojo::test::blink::PingServicePtr ptr;
-  auto invalidator = std::make_unique<InterfaceInvalidator>();
-  RevocablePingServiceImpl impl(MakeRequest(&ptr), invalidator.get());
-  impl.set_ping_handler(
-      base::BindLambdaForTesting([&] { invalidator.reset(); }));
-
-  bool ptr_error_handler_called = false;
-  ptr.set_connection_error_handler(
-      base::BindOnce(DoSetFlag, &ptr_error_handler_called));
-  ptr->Ping(base::BindRepeating([] { FAIL(); }));
-  base::RunLoop().RunUntilIdle();
-
-  EXPECT_TRUE(ptr_error_handler_called);
-  EXPECT_TRUE(impl.error_handler_called());
-  EXPECT_TRUE(*impl.binding());
-}
-
-TEST_F(InterfaceInvalidatorTest, InvalidateBindingAfterResponse) {
-  mojo::test::blink::PingServicePtr ptr;
-  auto invalidator = std::make_unique<InterfaceInvalidator>();
-  RevocablePingServiceImpl impl(MakeRequest(&ptr), invalidator.get());
-  impl.set_post_ping_handler(base::BindLambdaForTesting([&] {
-    invalidator.reset();
-    impl.set_ping_handler(base::BindRepeating([] { FAIL(); }));
-  }));
-
-  bool ptr_error_handler_called = false;
-  ptr.set_connection_error_handler(
-      base::BindOnce(DoSetFlag, &ptr_error_handler_called));
-  bool ping_called = false;
-  ptr->Ping(base::BindRepeating(DoSetFlag, &ping_called));
-  ptr->Ping(base::BindRepeating([] { FAIL(); }));
-  base::RunLoop().RunUntilIdle();
-
-  EXPECT_TRUE(ping_called);
-  EXPECT_TRUE(ptr_error_handler_called);
-  EXPECT_TRUE(impl.error_handler_called());
-  EXPECT_TRUE(*impl.binding());
-}
-
-TEST_F(InterfaceInvalidatorTest, UnbindThenInvalidate) {
-  mojo::test::blink::PingServicePtr ptr;
-  auto invalidator = std::make_unique<InterfaceInvalidator>();
-  RevocablePingServiceImpl impl(MakeRequest(&ptr), invalidator.get());
-  ptr.set_connection_error_handler(base::BindOnce([] { FAIL(); }));
-
-  PingServiceImpl impl2(impl.binding()->Unbind());
-  invalidator.reset();
-  bool ping_called = false;
-  ptr->Ping(base::BindRepeating(DoSetFlag, &ping_called));
-  base::RunLoop().RunUntilIdle();
-
-  EXPECT_TRUE(ping_called);
-  EXPECT_FALSE(impl.error_handler_called());
-}
-
-TEST_F(InterfaceInvalidatorTest, UnbindInvalidatedRevocableBinding) {
-  mojo::test::blink::PingServicePtr ptr;
-  auto invalidator = std::make_unique<InterfaceInvalidator>();
-  RevocablePingServiceImpl impl(MakeRequest(&ptr), invalidator.get());
-
-  bool ptr_error_handler_called = false;
-  ptr.set_connection_error_handler(
-      base::BindOnce(DoSetFlag, &ptr_error_handler_called));
-
-  invalidator.reset();
-  base::RunLoop().RunUntilIdle();
-  ASSERT_TRUE(ptr_error_handler_called);
-  ASSERT_TRUE(impl.error_handler_called());
-
-  PingServiceImpl impl2(impl.binding()->Unbind());
-  impl2.set_ping_handler(base::BindRepeating([] { FAIL(); }));
-  ptr->Ping(base::BindRepeating([] { FAIL(); }));
-  base::RunLoop().RunUntilIdle();
-  EXPECT_TRUE(impl2.error_handler_called());
-}
-
-TEST_F(InterfaceInvalidatorTest, UnbindBeforeConnectionErrorNotification) {
-  mojo::test::blink::PingServicePtr ptr;
-  auto invalidator = std::make_unique<InterfaceInvalidator>();
-  RevocablePingServiceImpl impl(MakeRequest(&ptr), invalidator.get());
-
-  bool ptr_error_handler_called = false;
-  ptr.set_connection_error_handler(
-      base::BindOnce(DoSetFlag, &ptr_error_handler_called));
-
-  invalidator.reset();
-  PingServiceImpl impl2(impl.binding()->Unbind());
-  impl2.set_ping_handler(base::BindRepeating([] { FAIL(); }));
-  ptr->Ping(base::BindRepeating([] { FAIL(); }));
-  base::RunLoop().RunUntilIdle();
-
-  EXPECT_FALSE(impl.error_handler_called());
-  EXPECT_TRUE(impl2.error_handler_called());
-  EXPECT_TRUE(ptr_error_handler_called);
-}
-
-TEST_F(InterfaceInvalidatorTest, InvalidateClosedRevocableBinding) {
-  mojo::test::blink::PingServicePtr ptr;
-  auto invalidator = std::make_unique<InterfaceInvalidator>();
-  RevocablePingServiceImpl impl(MakeRequest(&ptr), invalidator.get());
-
-  impl.binding()->Close();
-  invalidator.reset();
-  base::RunLoop().RunUntilIdle();
-
-  EXPECT_FALSE(impl.error_handler_called());
-  EXPECT_FALSE(*impl.binding());
-}
-
-TEST_F(InterfaceInvalidatorTest, CloseInvalidatedRevocableBinding) {
-  mojo::test::blink::PingServicePtr ptr;
-  auto invalidator = std::make_unique<InterfaceInvalidator>();
-  RevocablePingServiceImpl impl(MakeRequest(&ptr), invalidator.get());
-
-  invalidator.reset();
-  impl.binding()->Close();
-  base::RunLoop().RunUntilIdle();
-
-  EXPECT_FALSE(impl.error_handler_called());
-  EXPECT_FALSE(*impl.binding());
-}
-
-TEST_F(InterfaceInvalidatorTest, InvalidateErroredRevocableBinding) {
-  mojo::test::blink::PingServicePtr ptr;
-  auto invalidator = std::make_unique<InterfaceInvalidator>();
-  RevocablePingServiceImpl impl(MakeRequest(&ptr), invalidator.get());
-
-  int called = 0;
-  impl.binding()->set_connection_error_handler(
-      base::BindLambdaForTesting([&] { called++; }));
-
-  ptr.reset();
-  base::RunLoop().RunUntilIdle();
-  EXPECT_EQ(1, called);
-  invalidator.reset();
-  base::RunLoop().RunUntilIdle();
-  EXPECT_EQ(1, called);
-}
-
-TEST_F(InterfaceInvalidatorTest, InvalidateWhileRevocableBindingPaused) {
-  mojo::test::blink::PingServicePtr ptr;
-  auto invalidator = std::make_unique<InterfaceInvalidator>();
-  RevocablePingServiceImpl impl(MakeRequest(&ptr), invalidator.get());
-
-  impl.binding()->PauseIncomingMethodCallProcessing();
-  invalidator.reset();
-  base::RunLoop().RunUntilIdle();
-  EXPECT_FALSE(impl.error_handler_called());
-  impl.binding()->ResumeIncomingMethodCallProcessing();
-  base::RunLoop().RunUntilIdle();
-  EXPECT_TRUE(impl.error_handler_called());
-}
-
-TEST_F(InterfaceInvalidatorTest, InvalidateRevocableBindingDuringSyncIPC) {
-  mojo::test::blink::PingServicePtr ptr;
-  auto invalidator = std::make_unique<InterfaceInvalidator>();
-  RevocablePingServiceImpl impl(MakeRequest(&ptr), invalidator.get());
-
-  impl.set_ping_handler(
-      base::BindLambdaForTesting([&] { invalidator.reset(); }));
-  bool result = ptr->Ping();
-  EXPECT_FALSE(result);
-}
-
-TEST_F(InterfaceInvalidatorTest,
-       InvalidateRevocableBindingDuringSyncIPCWithoutResponse) {
-  mojo::test::blink::PingServicePtr ptr;
-  auto invalidator = std::make_unique<InterfaceInvalidator>();
-  RevocablePingServiceImpl impl(MakeRequest(&ptr), invalidator.get(),
-                                false /* send_response */);
-
-  impl.set_ping_handler(
-      base::BindLambdaForTesting([&] { invalidator.reset(); }));
-  bool result = ptr->Ping();
-  EXPECT_FALSE(result);
-}
-
-TEST_F(InterfaceInvalidatorTest, InvalidateStrongBinding) {
-  mojo::test::blink::PingServicePtr ptr;
-  auto invalidator = std::make_unique<InterfaceInvalidator>();
-  auto impl_ptr =
-      MakeRevocableStrongBinding(std::make_unique<PingServiceImplBase>(),
-                                 MakeRequest(&ptr), invalidator.get());
-  auto* impl = reinterpret_cast<PingServiceImplBase*>(impl_ptr->impl());
-
-  bool impl_called = false;
-  impl->set_ping_handler(base::BindRepeating(DoSetFlag, &impl_called));
-  bool ping_called = false;
-  ptr->Ping(base::BindRepeating(DoSetFlag, &ping_called));
-  base::RunLoop().RunUntilIdle();
-  ASSERT_TRUE(impl_called);
-  ASSERT_TRUE(ping_called);
-
-  impl->set_ping_handler(base::BindRepeating([] { FAIL(); }));
-  invalidator.reset();
-  ptr->Ping(base::BindRepeating([] { FAIL(); }));
-  base::RunLoop().RunUntilIdle();
-
-  ASSERT_FALSE(impl_ptr);
-}
-
-TEST_F(InterfaceInvalidatorTest, InvalidateStrongBindingAfterError) {
-  mojo::test::blink::PingServicePtr ptr;
-  auto invalidator = std::make_unique<InterfaceInvalidator>();
-  auto impl_ptr =
-      MakeRevocableStrongBinding(std::make_unique<PingServiceImplBase>(),
-                                 MakeRequest(&ptr), invalidator.get());
-  ptr.set_connection_error_handler(base::BindOnce([] { FAIL(); }));
-
-  ptr.reset();
-  base::RunLoop().RunUntilIdle();
-  invalidator.reset();
-  base::RunLoop().RunUntilIdle();
-
-  ASSERT_FALSE(impl_ptr);
-}
-
 }  // namespace
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/mojo/revocable_binding.h b/third_party/blink/renderer/platform/mojo/revocable_binding.h
deleted file mode 100644
index a467727..0000000
--- a/third_party/blink/renderer/platform/mojo/revocable_binding.h
+++ /dev/null
@@ -1,182 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_MOJO_REVOCABLE_BINDING_H_
-#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_MOJO_REVOCABLE_BINDING_H_
-
-#include <utility>
-
-#include "base/callback_forward.h"
-#include "base/macros.h"
-#include "base/memory/ref_counted.h"
-#include "base/single_thread_task_runner.h"
-#include "mojo/public/cpp/bindings/binding.h"
-#include "mojo/public/cpp/bindings/connection_error_callback.h"
-#include "mojo/public/cpp/bindings/interface_request.h"
-#include "mojo/public/cpp/bindings/lib/binding_state.h"
-#include "mojo/public/cpp/bindings/raw_ptr_impl_ref_traits.h"
-#include "mojo/public/cpp/system/core.h"
-#include "third_party/blink/renderer/platform/mojo/interface_invalidator.h"
-
-namespace blink {
-
-class MessageReceiver;
-
-// RevocableBinding is a wrapper around a Binding that has to be tied to an
-// InterfaceInvalidator when bound to a message pipe. The underlying connection
-// is automatically closed once the InterfaceInvalidator is destroyed, and the
-// binding will behave as if its peer had closed the connection. This is useful
-// for tying the lifetime of mojo interfaces to another object.
-//
-// TODO(austinct): Add set_connection_error_with_reason_handler(),
-// CloseWithReason(), ReportBadMessage() and GetBadMessageCallback() methods if
-// needed. Undesirable for now because of the std::string parameter.
-template <typename Interface,
-          typename ImplRefTraits = mojo::RawPtrImplRefTraits<Interface>>
-class RevocableBinding : public InterfaceInvalidator::Observer {
- public:
-  using ImplPointerType = typename ImplRefTraits::PointerType;
-
-  // Constructs an incomplete binding that will use the implementation |impl|.
-  // The binding may be completed with a subsequent call to the |Bind| method.
-  // Does not take ownership of |impl|, which must outlive the binding.
-  explicit RevocableBinding(ImplPointerType impl) : binding_(std::move(impl)) {}
-
-  // Constructs a completed binding of |impl| to the message pipe endpoint in
-  // |request|, taking ownership of the endpoint. Does not take ownership of
-  // |impl|, which must outlive the binding. Ties the lifetime of the binding to
-  // |invalidator|.
-  RevocableBinding(ImplPointerType impl,
-                   mojo::InterfaceRequest<Interface> request,
-                   InterfaceInvalidator* invalidator,
-                   scoped_refptr<base::SingleThreadTaskRunner> runner = nullptr)
-      : RevocableBinding(std::move(impl)) {
-    Bind(std::move(request), invalidator, std::move(runner));
-  }
-
-  // Tears down the binding, closing the message pipe and leaving the interface
-  // implementation unbound.
-  ~RevocableBinding() { SetInvalidator(nullptr); }
-
-  // Completes a binding that was constructed with only an interface
-  // implementation by removing the message pipe endpoint from |request| and
-  // binding it to the previously specified implementation. Ties the lifetime of
-  // the binding to |invalidator|.
-  void Bind(mojo::InterfaceRequest<Interface> request,
-            InterfaceInvalidator* invalidator,
-            scoped_refptr<base::SingleThreadTaskRunner> runner = nullptr) {
-    DCHECK(invalidator);
-    binding_.Bind(std::move(request), std::move(runner));
-    SetInvalidator(invalidator);
-  }
-
-  // Adds a message filter to be notified of each incoming message before
-  // dispatch. If a filter returns |false| from Accept(), the message is not
-  // dispatched and the pipe is closed. Filters cannot be removed.
-  void AddFilter(std::unique_ptr<MessageReceiver> filter) {
-    binding_.AddFilter(std::move(filter));
-  }
-
-  // Whether there are any associated interfaces running on the pipe currently.
-  bool HasAssociatedInterfaces() const {
-    return binding_.HasAssociatedInterfaces();
-  }
-
-  // Stops processing incoming messages until
-  // ResumeIncomingMethodCallProcessing().
-  // Outgoing messages are still sent.
-  //
-  // No errors are detected on the message pipe while paused.
-  //
-  // This method may only be called if the object has been bound to a message
-  // pipe and there are no associated interfaces running.
-  void PauseIncomingMethodCallProcessing() {
-    binding_.PauseIncomingMethodCallProcessing();
-  }
-  void ResumeIncomingMethodCallProcessing() {
-    binding_.ResumeIncomingMethodCallProcessing();
-  }
-
-  // Closes the message pipe that was previously bound. Put this object into a
-  // state where it can be rebound to a new pipe.
-  void Close() {
-    SetInvalidator(nullptr);
-    binding_.Close();
-  }
-
-  // Unbinds the underlying pipe from this binding and returns it so it can be
-  // used in another context, such as on another sequence or with a different
-  // implementation. Put this object into a state where it can be rebound to a
-  // new pipe.
-  //
-  // This method may only be called if the object has been bound to a message
-  // pipe and there are no associated interfaces running.
-  //
-  // TODO(yzshen): For now, users need to make sure there is no one holding
-  // on to associated interface endpoint handles at both sides of the
-  // message pipe in order to call this method. We need a way to forcefully
-  // invalidate associated interface endpoint handles.
-  mojo::InterfaceRequest<Interface> Unbind() {
-    SetInvalidator(nullptr);
-    return binding_.Unbind();
-  }
-
-  // Sets an error handler that will be called if a connection error occurs on
-  // the bound message pipe.
-  //
-  // This method may only be called after this RevocableBinding has been bound
-  // to a message pipe. The error handler will be reset when this
-  // RevocableBinding is unbound, closed or invalidated.
-  void set_connection_error_handler(base::OnceClosure error_handler) {
-    binding_.set_connection_error_handler(std::move(error_handler));
-  }
-
-  // Returns the interface implementation that was previously specified. Caller
-  // does not take ownership.
-  Interface* impl() { return binding_.impl(); }
-
-  // Indicates whether the binding has been completed (i.e., whether a message
-  // pipe has been bound to the implementation).
-  explicit operator bool() const { return static_cast<bool>(binding_); }
-
-  // Sends a no-op message on the underlying message pipe and runs the current
-  // message loop until its response is received. This can be used in tests to
-  // verify that no message was sent on a message pipe in response to some
-  // stimulus.
-  void FlushForTesting() { binding_.FlushForTesting(); }
-
- private:
-  // InterfaceInvalidator::Observer
-  void OnInvalidate() override {
-    if (binding_) {
-      binding_.internal_state()->RaiseError();
-    }
-    if (invalidator_) {
-      invalidator_->RemoveObserver(this);
-    }
-    invalidator_.reset();
-  }
-
-  // Replaces the existing invalidator with a new invalidator and changes the
-  // invalidator being observed.
-  void SetInvalidator(InterfaceInvalidator* invalidator) {
-    if (invalidator_)
-      invalidator_->RemoveObserver(this);
-
-    invalidator_.reset();
-    if (invalidator) {
-      invalidator_ = invalidator->GetWeakPtr();
-      invalidator_->AddObserver(this);
-    }
-  }
-
-  mojo::Binding<Interface, ImplRefTraits> binding_;
-  base::WeakPtr<InterfaceInvalidator> invalidator_;
-
-  DISALLOW_COPY_AND_ASSIGN(RevocableBinding);
-};
-
-}  // namespace blink
-
-#endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_MOJO_REVOCABLE_BINDING_H_
diff --git a/third_party/blink/renderer/platform/mojo/revocable_strong_binding.h b/third_party/blink/renderer/platform/mojo/revocable_strong_binding.h
deleted file mode 100644
index 80286873..0000000
--- a/third_party/blink/renderer/platform/mojo/revocable_strong_binding.h
+++ /dev/null
@@ -1,134 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_MOJO_REVOCABLE_STRONG_BINDING_H_
-#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_MOJO_REVOCABLE_STRONG_BINDING_H_
-
-#include <memory>
-#include <utility>
-
-#include "base/bind.h"
-#include "base/callback.h"
-#include "base/macros.h"
-#include "base/memory/weak_ptr.h"
-#include "mojo/public/cpp/bindings/connection_error_callback.h"
-#include "mojo/public/cpp/bindings/filter_chain.h"
-#include "mojo/public/cpp/bindings/interface_request.h"
-#include "mojo/public/cpp/bindings/message_header_validator.h"
-#include "mojo/public/cpp/system/core.h"
-#include "third_party/blink/renderer/platform/mojo/revocable_binding.h"
-
-namespace blink {
-
-class InterfaceInvalidator;
-
-template <typename Interface>
-class RevocableStrongBinding;
-
-template <typename Interface>
-using RevocableStrongBindingPtr =
-    base::WeakPtr<RevocableStrongBinding<Interface>>;
-
-// This is a wrapper around a StrongBinding that binds it to an interface
-// invalidator. When the invalidator is destroyed or a connection error is
-// detected, the interface implementation is deleted. If the task runner that a
-// RevocableStrongBinding is bound on is stopped, the connection error handler
-// will not be invoked and the implementation will not be deleted.
-//
-// To use, call RevocableStrongBinding<T>::Create() (see below) or the helper
-// MakeRevocableStrongBinding function:
-//
-//   MakeRevocableStrongBinding(std::make_unique<FooImpl>(),
-//                         std::move(foo_request));
-//
-template <typename Interface>
-class RevocableStrongBinding {
- public:
-  // Create a new RevocableStrongBinding instance. The instance owns itself,
-  // cleaning up only in the event of a pipe connection error or invalidation.
-  // Returns a WeakPtr to the new RevocableStrongBinding instance.
-  static RevocableStrongBindingPtr<Interface> Create(
-      std::unique_ptr<Interface> impl,
-      mojo::InterfaceRequest<Interface> request,
-      InterfaceInvalidator* invalidator) {
-    RevocableStrongBinding* binding = new RevocableStrongBinding(
-        std::move(impl), std::move(request), invalidator);
-    return binding->weak_factory_.GetWeakPtr();
-  }
-
-  // Note: The error handler must not delete the interface implementation.
-  //
-  // This method may only be called after this RevocableStrongBinding has been
-  // bound to a message pipe.
-  void set_connection_error_handler(base::OnceClosure error_handler) {
-    DCHECK(binding_.is_bound());
-    connection_error_handler_ = std::move(error_handler);
-  }
-
-  // Stops processing incoming messages until
-  // ResumeIncomingMethodCallProcessing().
-  // Outgoing messages are still sent.
-  //
-  // No errors are detected on the message pipe while paused.
-  //
-  // This method may only be called if the object has been bound to a message
-  // pipe and there are no associated interfaces running.
-  void PauseIncomingMethodCallProcessing() {
-    binding_.PauseIncomingMethodCallProcessing();
-  }
-  void ResumeIncomingMethodCallProcessing() {
-    binding_.ResumeIncomingMethodCallProcessing();
-  }
-
-  // Forces the binding to close. This destroys the RevocableStrongBinding
-  // instance.
-  void Close() { delete this; }
-
-  Interface* impl() { return impl_.get(); }
-
-  // Sends a message on the underlying message pipe and runs the current
-  // message loop until its response is received. This can be used in tests to
-  // verify that no message was sent on a message pipe in response to some
-  // stimulus.
-  void FlushForTesting() { binding_.FlushForTesting(); }
-
- private:
-  RevocableStrongBinding(std::unique_ptr<Interface> impl,
-                         mojo::InterfaceRequest<Interface> request,
-                         InterfaceInvalidator* invalidator)
-      : impl_(std::move(impl)),
-        binding_(impl_.get(), std::move(request), invalidator) {
-    binding_.set_connection_error_handler(base::BindOnce(
-        &RevocableStrongBinding::OnConnectionError, base::Unretained(this)));
-  }
-
-  ~RevocableStrongBinding() = default;
-
-  void OnConnectionError() {
-    if (connection_error_handler_) {
-      std::move(connection_error_handler_).Run();
-    }
-    Close();
-  }
-
-  std::unique_ptr<Interface> impl_;
-  base::OnceClosure connection_error_handler_;
-  RevocableBinding<Interface> binding_;
-  base::WeakPtrFactory<RevocableStrongBinding> weak_factory_{this};
-
-  DISALLOW_COPY_AND_ASSIGN(RevocableStrongBinding);
-};
-
-template <typename Interface, typename Impl>
-RevocableStrongBindingPtr<Interface> MakeRevocableStrongBinding(
-    std::unique_ptr<Impl> impl,
-    mojo::InterfaceRequest<Interface> request,
-    InterfaceInvalidator* invalidator) {
-  return RevocableStrongBinding<Interface>::Create(
-      std::move(impl), std::move(request), invalidator);
-}
-
-}  // namespace blink
-
-#endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_MOJO_REVOCABLE_STRONG_BINDING_H_
diff --git a/third_party/blink/renderer/platform/wtf/functional.h b/third_party/blink/renderer/platform/wtf/functional.h
index 5e4b373..51a0479 100644
--- a/third_party/blink/renderer/platform/wtf/functional.h
+++ b/third_party/blink/renderer/platform/wtf/functional.h
@@ -209,6 +209,9 @@
                 "GCed types are forbidden as bound parameters.");
   static_assert(!WTF::IsStackAllocatedType<T>::value,
                 "Stack allocated types are forbidden as bound parameters.");
+  static_assert(
+      !(WTF::IsDisallowNew<T>::value && WTF::IsTraceable<T>::value),
+      "Traceable disallow new types are forbidden as bound parameters.");
 };
 
 template <typename Index, typename... Args>
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 13c9b0f0..4ae8ec8 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -3388,11 +3388,11 @@
 crbug.com/626703 external/wpt/css/css-writing-modes/text-combine-upright-digits-002-manual.html [ Skip ]
 crbug.com/626703 external/wpt/css/css-ui/webkit-appearance-button-bevel-001.html [ Failure ]
 crbug.com/626703 external/wpt/css/css-writing-modes/text-combine-upright-all-002-manual.html [ Skip ]
-crbug.com/626703 virtual/streaming-preload/external/wpt/html/semantics/scripting-1/the-script-element/json-module/parse-error.html [ Timeout ]
+crbug.com/626703 virtual/streaming-preload/external/wpt/html/semantics/scripting-1/the-script-element/json-module/parse-error.tentative.html [ Timeout ]
 crbug.com/626703 external/wpt/css/css-ui/webkit-appearance-auto-001.html [ Failure ]
 crbug.com/626703 external/wpt/web-animations/interfaces/Animation/persist.html [ Timeout ]
 crbug.com/626703 external/wpt/css/css-writing-modes/text-combine-upright-digits-004-manual.html [ Skip ]
-crbug.com/626703 external/wpt/html/semantics/scripting-1/the-script-element/json-module/parse-error.html [ Timeout ]
+crbug.com/626703 external/wpt/html/semantics/scripting-1/the-script-element/json-module/parse-error.tentative.html [ Timeout ]
 crbug.com/626703 [ Mac10.13 ] external/wpt/preload/preload-with-type.html [ Failure Timeout ]
 crbug.com/626703 [ Retina ] external/wpt/preload/preload-with-type.html [ Timeout ]
 crbug.com/626703 [ Mac10.13 ] external/wpt/preload/onload-event.html [ Failure Timeout ]
@@ -5483,7 +5483,6 @@
 crbug.com/923244 external/wpt/webrtc/RTCDTMFSender-ontonechange.https.html [ Failure Pass ]
 # WebRTC Plan B
 crbug.com/920979 virtual/webrtc-wpt-plan-b/external/wpt/webrtc/RTCPeerConnection-remote-track-mute.https.html [ Timeout ]
-crbug.com/997201 virtual/webrtc-wpt-plan-b/external/wpt/webrtc/RTCPeerConnection-iceConnectionState.https.html [ Pass Failure ]
 crbug.com/920979 virtual/webrtc-wpt-plan-b/external/wpt/webrtc/RTCTrackEvent-fire.html [ Timeout ]
 # WebRTC codec tests - software H.264 not present on webkit bot family
 crbug.com/840659 external/wpt/webrtc/protocol/video-codecs.https.html [ Pass Failure ]
@@ -5843,6 +5842,7 @@
 # Allow failure until its appearance gets stable.
 crbug.com/972476 std-switch/switch-appearance.html [ Failure ]
 crbug.com/972476 std-switch/switch-appearance-customization.html [ Failure ]
+crbug.com/972476 std-switch/switch-appearance-theme.html [ Failure ]
 
 # Sheriff 2019-05-20
 crbug.com/965389 [ Mac ] media/track/track-cue-rendering-position-auto.html [ Pass Failure ]
@@ -6013,7 +6013,6 @@
 crbug.com/988074 [ Linux ] virtual/omt-worker-fetch/http/tests/security/cors-rfc1918/addressspace-serviceworker-basic.html [ Pass Failure ]
 
 # Sheriff 2019-08-05
-crbug.com/981522 [ Linux ] external/wpt/svg/animations/correct-events-for-short-animations-with-syncbases.html [ Pass Failure Timeout ]
 crbug.com/991243 [ Linux ] external/wpt/workers/semantics/multiple-workers/003.html [ Pass Timeout ]
 crbug.com/991243 [ Linux ] virtual/omt-worker-fetch/external/wpt/workers/semantics/multiple-workers/003.html [ Pass Timeout ]
 
@@ -6196,3 +6195,9 @@
 crbug.com/1010032 [ Win7 ] fast/writing-mode/Kusa-Makura-background-canvas.html [ Failure ]
 crbug.com/1010170 [ Mac ] media/video-played-reset.html [ Failure ]
 
+# Sheriff 2019-10-02
+crbug.com/1010472 [ Linux Mac Debug ] fast/canvas/color-space/canvas-drawImage-offscreenCanvas.html [ Timeout Pass ]
+crbug.com/1010483 [ Win ] fast/parser/residual-style-dom.html [ Crash Pass ]
+crbug.com/1010483 [ Win ] fast/parser/residual-style-hang.html [ Crash Pass ]
+crbug.com/1010483 [ Win ] fast/selectors/specificity-overflow.html [ Crash Pass ]
+
diff --git a/third_party/blink/web_tests/W3CImportExpectations b/third_party/blink/web_tests/W3CImportExpectations
index 1d035abb..5e1ff29 100644
--- a/third_party/blink/web_tests/W3CImportExpectations
+++ b/third_party/blink/web_tests/W3CImportExpectations
@@ -10,8 +10,8 @@
 # https://chromium.googlesource.com/chromium/src/+/master/docs/testing/web_test_expectations.md
 
 external/wpt/.codecov.yml [ Skip ]
+external/wpt/.github [ Skip ]
 external/wpt/.gitmodules [ Skip ]
-external/wpt/.travis.yml [ Skip ]
 external/wpt/CODEOWNERS [ Skip ]
 external/wpt/WebIDL/invalid [ Skip ]
 external/wpt/WebIDL/readme.txt [ Skip ]
diff --git a/third_party/blink/web_tests/external/wpt/.github/META.yml b/third_party/blink/web_tests/external/wpt/.github/META.yml
deleted file mode 100644
index 06083d1..0000000
--- a/third_party/blink/web_tests/external/wpt/.github/META.yml
+++ /dev/null
@@ -1,3 +0,0 @@
-suggested_reviewers:
-  - jgraham
-  - jugglinmike
diff --git a/third_party/blink/web_tests/external/wpt/.github/workflows/pull_request.yml b/third_party/blink/web_tests/external/wpt/.github/workflows/pull_request.yml
deleted file mode 100644
index 81a53c67..0000000
--- a/third_party/blink/web_tests/external/wpt/.github/workflows/pull_request.yml
+++ /dev/null
@@ -1,17 +0,0 @@
-on: pull_request
-name: Synchronize the Pull Request Preview
-jobs:
-  update-pr-preview:
-    runs-on: ubuntu-18.04
-    steps:
-    - uses: actions/checkout@v1
-      with:
-        ref: refs/heads/master
-        fetch-depth: 50
-    - name: update-pr-preview
-      uses: ./tools/docker/github
-      env:
-        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-      with:
-        entrypoint: python
-        args: tools/ci/update_pr_preview.py https://api.github.com
diff --git a/third_party/blink/web_tests/external/wpt/.github/workflows/push-build-publish-documentation-website.yml b/third_party/blink/web_tests/external/wpt/.github/workflows/push-build-publish-documentation-website.yml
deleted file mode 100644
index f6a1401..0000000
--- a/third_party/blink/web_tests/external/wpt/.github/workflows/push-build-publish-documentation-website.yml
+++ /dev/null
@@ -1,19 +0,0 @@
-on:
-  push:
-    branches:
-      - master
-name: Build & Publish Documentation Website
-jobs:
-  website-build-and-publish:
-    runs-on: ubuntu-18.04
-    steps:
-    - uses: actions/checkout@v1
-      with:
-        fetch-depth: 50
-    - name: website-build-and-publish
-      uses: ./tools/docker/documentation
-      env:
-        DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
-      with:
-        entrypoint: /bin/bash
-        args: tools/ci/website_build.sh
diff --git a/third_party/blink/web_tests/external/wpt/.github/workflows/push-build-release-manifest.yml b/third_party/blink/web_tests/external/wpt/.github/workflows/push-build-release-manifest.yml
deleted file mode 100644
index baa06cc..0000000
--- a/third_party/blink/web_tests/external/wpt/.github/workflows/push-build-release-manifest.yml
+++ /dev/null
@@ -1,19 +0,0 @@
-on:
-  push:
-    branches:
-      - master
-name: Build & Release Manifest
-jobs:
-  manifest-build-and-tag:
-    runs-on: ubuntu-18.04
-    steps:
-    - uses: actions/checkout@v1
-      with:
-        fetch-depth: 50
-    - name: manifest-build-and-tag
-      uses: ./tools/docker/github
-      env:
-        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-      with:
-        entrypoint: python
-        args: tools/ci/manifest_build.py
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/json-module/README.md b/third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/json-module/README.md
new file mode 100644
index 0000000..204fd59d
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/json-module/README.md
@@ -0,0 +1,2 @@
+This directory contains an experimental feature that is not currently standardized due to a security
+issue. Support was removed in https://github.com/whatwg/html/pull/4943.
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/json-module/invalid-content-type.html b/third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/json-module/invalid-content-type.tentative.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/json-module/invalid-content-type.html
rename to third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/json-module/invalid-content-type.tentative.html
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/json-module/module-expected.txt b/third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/json-module/module.tentative-expected.txt
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/json-module/module-expected.txt
rename to third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/json-module/module.tentative-expected.txt
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/json-module/module.html b/third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/json-module/module.tentative.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/json-module/module.html
rename to third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/json-module/module.tentative.html
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/json-module/non-object.any-expected.txt b/third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/json-module/non-object.tentative.any-expected.txt
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/json-module/non-object.any-expected.txt
rename to third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/json-module/non-object.tentative.any-expected.txt
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/json-module/non-object.any.js b/third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/json-module/non-object.tentative.any.js
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/json-module/non-object.any.js
rename to third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/json-module/non-object.tentative.any.js
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/json-module/non-object.any.serviceworker-expected.txt b/third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/json-module/non-object.tentative.any.serviceworker-expected.txt
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/json-module/non-object.any.serviceworker-expected.txt
rename to third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/json-module/non-object.tentative.any.serviceworker-expected.txt
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/json-module/non-object.any.sharedworker-expected.txt b/third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/json-module/non-object.tentative.any.sharedworker-expected.txt
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/json-module/non-object.any.sharedworker-expected.txt
rename to third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/json-module/non-object.tentative.any.sharedworker-expected.txt
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/json-module/non-object.any.worker-expected.txt b/third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/json-module/non-object.tentative.any.worker-expected.txt
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/json-module/non-object.any.worker-expected.txt
rename to third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/json-module/non-object.tentative.any.worker-expected.txt
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/json-module/parse-error.html b/third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/json-module/parse-error.tentative.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/json-module/parse-error.html
rename to third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/json-module/parse-error.tentative.html
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/json-module/utf8-expected.txt b/third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/json-module/utf8.tentative-expected.txt
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/json-module/utf8-expected.txt
rename to third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/json-module/utf8.tentative-expected.txt
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/json-module/utf8.html b/third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/json-module/utf8.tentative.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/json-module/utf8.html
rename to third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/json-module/utf8.tentative.html
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/json-module/valid-content-type-expected.txt b/third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/json-module/valid-content-type.tentative-expected.txt
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/json-module/valid-content-type-expected.txt
rename to third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/json-module/valid-content-type.tentative-expected.txt
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/json-module/valid-content-type.html b/third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/json-module/valid-content-type.tentative.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/json-module/valid-content-type.html
rename to third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/json-module/valid-content-type.tentative.html
diff --git a/third_party/blink/web_tests/external/wpt/web-animations/interfaces/Document/getAnimations-expected.txt b/third_party/blink/web_tests/external/wpt/web-animations/interfaces/Document/getAnimations-expected.txt
index e91647a..8598275 100644
--- a/third_party/blink/web_tests/external/wpt/web-animations/interfaces/Document/getAnimations-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/web-animations/interfaces/Document/getAnimations-expected.txt
@@ -4,7 +4,7 @@
 PASS Test the order of document.getAnimations with script generated animations
 PASS Test document.getAnimations for a disconnected node
 PASS Test document.getAnimations with null target
-FAIL Test document.getAnimations for elements inside same-origin iframes assert_true: Not expecting event, but got load event expected true got false
+FAIL Test document.getAnimations for elements inside same-origin iframes assert_equals: expected 1 but got 0
 PASS Triggers a style change event
 Harness: the test ran to completion.
 
diff --git a/third_party/blink/web_tests/external/wpt/web-animations/interfaces/Document/getAnimations.html b/third_party/blink/web_tests/external/wpt/web-animations/interfaces/Document/getAnimations.html
index f5df980c..f6a6c3e 100644
--- a/third_party/blink/web_tests/external/wpt/web-animations/interfaces/Document/getAnimations.html
+++ b/third_party/blink/web_tests/external/wpt/web-animations/interfaces/Document/getAnimations.html
@@ -68,11 +68,12 @@
   const iframe = document.createElement('iframe');
 
   const eventWatcher = new EventWatcher(t, iframe, ['load']);
+  const event_promise = eventWatcher.wait_for('load');
 
   document.body.appendChild(iframe);
   t.add_cleanup(() => { document.body.removeChild(iframe); });
 
-  await eventWatcher.wait_for('load');
+  await event_promise;
 
   const div = createDiv(t, iframe.contentDocument)
   const effect = new KeyframeEffect(div, null, 100 * MS_PER_SEC);
diff --git a/third_party/blink/web_tests/external/wpt/webrtc/RTCPeerConnection-iceConnectionState.https.html b/third_party/blink/web_tests/external/wpt/webrtc/RTCPeerConnection-iceConnectionState.https.html
index 8e853e4..9dd364ed 100644
--- a/third_party/blink/web_tests/external/wpt/webrtc/RTCPeerConnection-iceConnectionState.https.html
+++ b/third_party/blink/web_tests/external/wpt/webrtc/RTCPeerConnection-iceConnectionState.https.html
@@ -360,7 +360,14 @@
   delete pc1.candidateBuffer;
   await listenToIceConnected(pc1);
   await listenToIceConnected(pc2);
-  assert_array_equals(pc1.iceStates, ['new', 'checking', 'connected']);
+  // While we're waiting for pc2, pc1 may or may not have transitioned
+  // to "completed" state, so allow for both cases.
+  if (pc1.iceStates.length == 3) {
+    assert_array_equals(pc1.iceStates, ['new', 'checking', 'connected']);
+  } else {
+    assert_array_equals(pc1.iceStates, ['new', 'checking', 'connected',
+                                        'completed']);
+  }
   assert_array_equals(pc2.iceStates, ['new', 'checking', 'connected']);
 }, 'Responder ICE connection state behaves as expected');
 
diff --git a/third_party/blink/web_tests/external/wpt/websockets/stream-tentative/abort.any.js b/third_party/blink/web_tests/external/wpt/websockets/stream-tentative/abort.any.js
new file mode 100644
index 0000000..2392188
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/websockets/stream-tentative/abort.any.js
@@ -0,0 +1,46 @@
+// META: script=../websocket.sub.js
+// META: script=resources/url-constants.js
+// META: script=/common/utils.js
+// META: global=window,worker
+
+promise_test(async t => {
+  const controller = new AbortController();
+  controller.abort();
+  const key = token();
+  const wsUrl = new URL(
+      `/fetch/api/resources/stash-put.py?key=${key}&value=connected`,
+      location.href);
+  wsUrl.protocol = wsUrl.protocol.replace('http', 'ws');
+  // We intentionally use the port for the HTTP server, not the WebSocket
+  // server, because we don't expect the connection to be performed.
+  const wss = new WebSocketStream(wsUrl, { signal: controller.signal });
+  await promise_rejects(t, 'AbortError', wss.connection,
+                        'connection should reject');
+  await promise_rejects(t, 'AbortError', wss.closed, 'closed should reject');
+  // An incorrect implementation could pass this test due a race condition,
+  // but it is hard to completely eliminate the possibility.
+  const response = await fetch(`/fetch/api/resources/stash-take.py?key=${key}`);
+  assert_equals(await response.text(), 'null', 'response should be null');
+}, 'abort before constructing should prevent connection');
+
+promise_test(async t => {
+  const controller = new AbortController();
+  const wss = new WebSocketStream(`${BASEURL}/handshake_sleep_2`,
+                                  { signal: controller.signal });
+  // Give the connection a chance to start.
+  await new Promise(resolve => t.step_timeout(resolve, 0));
+  controller.abort();
+  await promise_rejects(t, 'AbortError', wss.connection,
+                        'connection should reject');
+  await promise_rejects(t, 'AbortError', wss.closed, 'closed should reject');
+}, 'abort during handshake should work');
+
+promise_test(async t => {
+  const controller = new AbortController();
+  const wss = new WebSocketStream(ECHOURL, { signal: controller.signal });
+  const { readable, writable } = await wss.connection;
+  controller.abort();
+  writable.getWriter().write('connected');
+  const { value } = await readable.getReader().read();
+  assert_equals(value, 'connected', 'value should match');
+}, 'abort after connect should do nothing');
diff --git a/third_party/blink/web_tests/platform/mac-mac10.10/virtual/webrtc-wpt-plan-b/external/wpt/webrtc/RTCPeerConnection-iceConnectionState.https-expected.txt b/third_party/blink/web_tests/platform/mac-mac10.10/virtual/webrtc-wpt-plan-b/external/wpt/webrtc/RTCPeerConnection-iceConnectionState.https-expected.txt
deleted file mode 100644
index 64dbbdf..0000000
--- a/third_party/blink/web_tests/platform/mac-mac10.10/virtual/webrtc-wpt-plan-b/external/wpt/webrtc/RTCPeerConnection-iceConnectionState.https-expected.txt
+++ /dev/null
@@ -1,14 +0,0 @@
-This is a testharness.js-based test.
-PASS Initial iceConnectionState should be new
-PASS Closing the connection should set iceConnectionState to closed
-PASS connection with one data channel should eventually have connected or completed connection state
-FAIL connection with one data channel should eventually have connected connection state Cannot read property 'transport' of null
-PASS connection with audio track should eventually have connected connection state
-PASS connection with audio and video tracks should eventually have connected connection state
-FAIL ICE can connect in a recvonly usecase promise_test: Unhandled rejection with value: object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'."
-FAIL iceConnectionState changes at the right time, with bundle policy balanced promise_test: Unhandled rejection with value: object "TypeError: Cannot read property 'sender' of undefined"
-FAIL iceConnectionState changes at the right time, with bundle policy max-bundle promise_test: Unhandled rejection with value: object "TypeError: Cannot read property 'sender' of undefined"
-FAIL iceConnectionState changes at the right time, with bundle policy max-compat promise_test: Unhandled rejection with value: object "TypeError: Cannot read property 'sender' of undefined"
-PASS Responder ICE connection state behaves as expected
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/platform/mac-mac10.11/virtual/webrtc-wpt-plan-b/external/wpt/webrtc/RTCPeerConnection-iceConnectionState.https-expected.txt b/third_party/blink/web_tests/platform/mac-mac10.11/virtual/webrtc-wpt-plan-b/external/wpt/webrtc/RTCPeerConnection-iceConnectionState.https-expected.txt
deleted file mode 100644
index 9fd6106..0000000
--- a/third_party/blink/web_tests/platform/mac-mac10.11/virtual/webrtc-wpt-plan-b/external/wpt/webrtc/RTCPeerConnection-iceConnectionState.https-expected.txt
+++ /dev/null
@@ -1,14 +0,0 @@
-This is a testharness.js-based test.
-PASS Initial iceConnectionState should be new
-PASS Closing the connection should set iceConnectionState to closed
-PASS connection with one data channel should eventually have connected or completed connection state
-FAIL connection with one data channel should eventually have connected connection state Cannot read property 'transport' of null
-PASS connection with audio track should eventually have connected connection state
-PASS connection with audio and video tracks should eventually have connected connection state
-FAIL ICE can connect in a recvonly usecase promise_test: Unhandled rejection with value: object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'."
-FAIL iceConnectionState changes at the right time, with bundle policy balanced promise_test: Unhandled rejection with value: object "TypeError: Cannot read property 'sender' of undefined"
-FAIL iceConnectionState changes at the right time, with bundle policy max-bundle promise_test: Unhandled rejection with value: object "TypeError: Cannot read property 'sender' of undefined"
-FAIL iceConnectionState changes at the right time, with bundle policy max-compat promise_test: Unhandled rejection with value: object "TypeError: Cannot read property 'sender' of undefined"
-FAIL Responder ICE connection state behaves as expected assert_array_equals: lengths differ, expected 3 got 4
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/platform/mac-mac10.12/virtual/webrtc-wpt-plan-b/external/wpt/webrtc/RTCPeerConnection-iceConnectionState.https-expected.txt b/third_party/blink/web_tests/platform/mac-mac10.12/virtual/webrtc-wpt-plan-b/external/wpt/webrtc/RTCPeerConnection-iceConnectionState.https-expected.txt
deleted file mode 100644
index 64dbbdf..0000000
--- a/third_party/blink/web_tests/platform/mac-mac10.12/virtual/webrtc-wpt-plan-b/external/wpt/webrtc/RTCPeerConnection-iceConnectionState.https-expected.txt
+++ /dev/null
@@ -1,14 +0,0 @@
-This is a testharness.js-based test.
-PASS Initial iceConnectionState should be new
-PASS Closing the connection should set iceConnectionState to closed
-PASS connection with one data channel should eventually have connected or completed connection state
-FAIL connection with one data channel should eventually have connected connection state Cannot read property 'transport' of null
-PASS connection with audio track should eventually have connected connection state
-PASS connection with audio and video tracks should eventually have connected connection state
-FAIL ICE can connect in a recvonly usecase promise_test: Unhandled rejection with value: object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'."
-FAIL iceConnectionState changes at the right time, with bundle policy balanced promise_test: Unhandled rejection with value: object "TypeError: Cannot read property 'sender' of undefined"
-FAIL iceConnectionState changes at the right time, with bundle policy max-bundle promise_test: Unhandled rejection with value: object "TypeError: Cannot read property 'sender' of undefined"
-FAIL iceConnectionState changes at the right time, with bundle policy max-compat promise_test: Unhandled rejection with value: object "TypeError: Cannot read property 'sender' of undefined"
-PASS Responder ICE connection state behaves as expected
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/platform/mac/std-switch/switch-appearance-theme-expected.png b/third_party/blink/web_tests/platform/mac/std-switch/switch-appearance-theme-expected.png
new file mode 100644
index 0000000..aadbe3b
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac/std-switch/switch-appearance-theme-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/virtual/webrtc-wpt-plan-b/external/wpt/webrtc/RTCPeerConnection-iceConnectionState.https-expected.txt b/third_party/blink/web_tests/platform/mac/virtual/webrtc-wpt-plan-b/external/wpt/webrtc/RTCPeerConnection-iceConnectionState.https-expected.txt
deleted file mode 100644
index 9fd6106..0000000
--- a/third_party/blink/web_tests/platform/mac/virtual/webrtc-wpt-plan-b/external/wpt/webrtc/RTCPeerConnection-iceConnectionState.https-expected.txt
+++ /dev/null
@@ -1,14 +0,0 @@
-This is a testharness.js-based test.
-PASS Initial iceConnectionState should be new
-PASS Closing the connection should set iceConnectionState to closed
-PASS connection with one data channel should eventually have connected or completed connection state
-FAIL connection with one data channel should eventually have connected connection state Cannot read property 'transport' of null
-PASS connection with audio track should eventually have connected connection state
-PASS connection with audio and video tracks should eventually have connected connection state
-FAIL ICE can connect in a recvonly usecase promise_test: Unhandled rejection with value: object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'."
-FAIL iceConnectionState changes at the right time, with bundle policy balanced promise_test: Unhandled rejection with value: object "TypeError: Cannot read property 'sender' of undefined"
-FAIL iceConnectionState changes at the right time, with bundle policy max-bundle promise_test: Unhandled rejection with value: object "TypeError: Cannot read property 'sender' of undefined"
-FAIL iceConnectionState changes at the right time, with bundle policy max-compat promise_test: Unhandled rejection with value: object "TypeError: Cannot read property 'sender' of undefined"
-FAIL Responder ICE connection state behaves as expected assert_array_equals: lengths differ, expected 3 got 4
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/platform/win/std-switch/switch-appearance-theme-expected.png b/third_party/blink/web_tests/platform/win/std-switch/switch-appearance-theme-expected.png
new file mode 100644
index 0000000..aadbe3b
--- /dev/null
+++ b/third_party/blink/web_tests/platform/win/std-switch/switch-appearance-theme-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/std-switch/switch-appearance-theme.html b/third_party/blink/web_tests/std-switch/switch-appearance-theme.html
new file mode 100644
index 0000000..c226ca5
--- /dev/null
+++ b/third_party/blink/web_tests/std-switch/switch-appearance-theme.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<body>
+<script type="module">
+import 'std:elements/switch';
+
+document.body.insertAdjacentHTML('beforeend', `
+<h2>Attribute on the element</h2>
+<std-switch theme="platform-agnostic"></std-switch>
+<std-switch theme="match-platform"></std-switch>`);
+
+document.body.insertAdjacentHTML('beforeend', `
+<h2>Attribute on an ancestor</h2>
+<span theme="platform-agnostic"><std-switch></std-switch></span>
+<i theme="match-platform"><b><std-switch></std-switch></b></i>`);
+
+document.body.insertAdjacentHTML('beforeend', `
+<h2>Nested attributes</h2>
+<s theme="match-platform"><span theme="platform-agnostic"><code theme="foo"
+><std-switch></std-switch></code></span></s>
+<kbd theme="platform-agnostic"><i theme="match-platform"><b><std-switch></std-switch></b></i></kbd>`);
+
+document.body.insertAdjacentHTML('beforeend', `
+<h2>Attribute over shadow-boundary</h2>
+<span theme="platform-agnostic"><span id="host1"></span></span>
+<i theme="match-platform"><b><span id="host2"></span></b></i>`);
+let root1 = document.querySelector('#host1').attachShadow({mode:'open'});
+root1.innerHTML = '<std-switch></std-switch>';
+let root2 = document.querySelector('#host2').attachShadow({mode:'closed'});
+root2.innerHTML = '<std-switch></std-switch>';
+
+document.body.insertAdjacentHTML('beforeend', `
+<h2>NO support for updating attribute values for now</h2>
+<std-switch id="update1" theme="match-platform"></std-switch>
+<std-switch id="update2" theme="platform-agnostic"></std-switch>`);
+document.querySelector('#update1').setAttribute('theme', 'platform-agnostic');
+document.querySelector('#update2').setAttribute('theme', 'match-platform');
+</script>
+</body>
diff --git a/third_party/blink/web_tests/virtual/json-modules/external/wpt/html/semantics/scripting-1/the-script-element/json-module/invalid-content-type-expected.txt b/third_party/blink/web_tests/virtual/json-modules/external/wpt/html/semantics/scripting-1/the-script-element/json-module/invalid-content-type.tentative-expected.txt
similarity index 100%
rename from third_party/blink/web_tests/virtual/json-modules/external/wpt/html/semantics/scripting-1/the-script-element/json-module/invalid-content-type-expected.txt
rename to third_party/blink/web_tests/virtual/json-modules/external/wpt/html/semantics/scripting-1/the-script-element/json-module/invalid-content-type.tentative-expected.txt
diff --git a/third_party/blink/web_tests/virtual/json-modules/external/wpt/html/semantics/scripting-1/the-script-element/json-module/module-expected.txt b/third_party/blink/web_tests/virtual/json-modules/external/wpt/html/semantics/scripting-1/the-script-element/json-module/module.tentative-expected.txt
similarity index 100%
rename from third_party/blink/web_tests/virtual/json-modules/external/wpt/html/semantics/scripting-1/the-script-element/json-module/module-expected.txt
rename to third_party/blink/web_tests/virtual/json-modules/external/wpt/html/semantics/scripting-1/the-script-element/json-module/module.tentative-expected.txt
diff --git a/third_party/blink/web_tests/virtual/json-modules/external/wpt/html/semantics/scripting-1/the-script-element/json-module/non-object.any-expected.txt b/third_party/blink/web_tests/virtual/json-modules/external/wpt/html/semantics/scripting-1/the-script-element/json-module/non-object.tentative.any-expected.txt
similarity index 100%
rename from third_party/blink/web_tests/virtual/json-modules/external/wpt/html/semantics/scripting-1/the-script-element/json-module/non-object.any-expected.txt
rename to third_party/blink/web_tests/virtual/json-modules/external/wpt/html/semantics/scripting-1/the-script-element/json-module/non-object.tentative.any-expected.txt
diff --git a/third_party/blink/web_tests/virtual/json-modules/external/wpt/html/semantics/scripting-1/the-script-element/json-module/non-object.any.worker-expected.txt b/third_party/blink/web_tests/virtual/json-modules/external/wpt/html/semantics/scripting-1/the-script-element/json-module/non-object.tentative.any.worker-expected.txt
similarity index 100%
rename from third_party/blink/web_tests/virtual/json-modules/external/wpt/html/semantics/scripting-1/the-script-element/json-module/non-object.any.worker-expected.txt
rename to third_party/blink/web_tests/virtual/json-modules/external/wpt/html/semantics/scripting-1/the-script-element/json-module/non-object.tentative.any.worker-expected.txt
diff --git a/third_party/blink/web_tests/virtual/json-modules/external/wpt/html/semantics/scripting-1/the-script-element/json-module/parse-error-expected.txt b/third_party/blink/web_tests/virtual/json-modules/external/wpt/html/semantics/scripting-1/the-script-element/json-module/parse-error.tentative-expected.txt
similarity index 91%
rename from third_party/blink/web_tests/virtual/json-modules/external/wpt/html/semantics/scripting-1/the-script-element/json-module/parse-error-expected.txt
rename to third_party/blink/web_tests/virtual/json-modules/external/wpt/html/semantics/scripting-1/the-script-element/json-module/parse-error.tentative-expected.txt
index 166837e4..2fcf3eb 100644
--- a/third_party/blink/web_tests/virtual/json-modules/external/wpt/html/semantics/scripting-1/the-script-element/json-module/parse-error-expected.txt
+++ b/third_party/blink/web_tests/virtual/json-modules/external/wpt/html/semantics/scripting-1/the-script-element/json-module/parse-error.tentative-expected.txt
@@ -1,4 +1,4 @@
 This is a testharness.js-based test.
-FAIL JSON modules: parse error assert_equals: expected "http://web-platform.test:8001/html/semantics/scripting-1/the-script-element/json-module/parse-error.json" but got "http://web-platform.test:8001/html/semantics/scripting-1/the-script-element/json-module/parse-error.html"
+FAIL JSON modules: parse error assert_equals: expected "http://web-platform.test:8001/html/semantics/scripting-1/the-script-element/json-module/parse-error.json" but got "http://web-platform.test:8001/html/semantics/scripting-1/the-script-element/json-module/parse-error.tentative.html"
 Harness: the test ran to completion.
 
diff --git a/third_party/blink/web_tests/virtual/json-modules/external/wpt/html/semantics/scripting-1/the-script-element/json-module/valid-content-type-expected.txt b/third_party/blink/web_tests/virtual/json-modules/external/wpt/html/semantics/scripting-1/the-script-element/json-module/valid-content-type.tentative-expected.txt
similarity index 100%
rename from third_party/blink/web_tests/virtual/json-modules/external/wpt/html/semantics/scripting-1/the-script-element/json-module/valid-content-type-expected.txt
rename to third_party/blink/web_tests/virtual/json-modules/external/wpt/html/semantics/scripting-1/the-script-element/json-module/valid-content-type.tentative-expected.txt
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 56d8613..b6dd0c0 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -4691,6 +4691,13 @@
   <int value="5" label="Overlay Insecure Non-player-element Fullscreen"/>
 </enum>
 
+<enum name="BackForwardCacheHistoryNavigationOutcome">
+  <int value="0" label="Restored"/>
+  <int value="1" label="Not cached"/>
+  <int value="2" label="Evicted"/>
+  <int value="3" label="Not cached due to the experiment condition"/>
+</enum>
+
 <enum name="BackForwardNavigationType">
   <int value="0" label="Fast back navigation with WKBackForwardList"/>
   <int value="1" label="Slow back navigation"/>
@@ -57740,6 +57747,34 @@
   <int value="5" label="TLS 1.3 0-RTT handshake (0-RTT)"/>
 </enum>
 
+<enum name="SSLHandshakeEarlyDataReason">
+<!-- Corresponds to //third_party/boringssl's ssl_early_data_reason_t -->
+
+  <int value="0"
+      label="The handshake has not progressed far enough for the 0-RTT status
+             to be known."/>
+  <int value="1" label="0-RTT is disabled for this connection."/>
+  <int value="2" label="0-RTT was accepted."/>
+  <int value="3"
+      label="The negotiated protocol version does not support 0-RTT."/>
+  <int value="4"
+      label="The peer declined to offer or accept 0-RTT for an unknown
+             reason."/>
+  <int value="5" label="The client did not offer a session."/>
+  <int value="6" label="The server declined to resume the session."/>
+  <int value="7" label="The session does not support 0-RTT."/>
+  <int value="8" label="The server sent a HelloRetryRequest."/>
+  <int value="9"
+      label="The negotiated ALPN protocol did not match the session."/>
+  <int value="10"
+      label="The connection negotiated Channel ID, which is incompatible with
+             0-RTT."/>
+  <int value="11"
+      label="The connection negotiated token binding, which is incompatible
+             with 0-RTT."/>
+  <int value="12" label="The client and server ticket age were too far apart."/>
+</enum>
+
 <enum name="SSLHashAlgorithm">
   <obsolete>
     Removed June 2016.
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index 8c7e3c2..a129957 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -13045,6 +13045,18 @@
   </summary>
 </histogram>
 
+<histogram name="BackForwardCache.HistoryNavigationOutcome"
+    enum="BackForwardCacheHistoryNavigationOutcome" expires_after="2020-10-01">
+  <owner>bfcache-dev@chromium.org</owner>
+  <owner>hajimehoshi@chromium.org</owner>
+  <summary>
+    When navigating back to a page in the session history, this records whether
+    the page was restored from the BackForwardCache or not.
+
+    This recording starts as of M79.
+  </summary>
+</histogram>
+
 <histogram base="true" name="BackgroundFetch.EventDispatchFailure.Dispatch"
     enum="ServiceWorkerStatusCode">
 <!-- Name completed by histogram_suffixes name="BackgroundFetchEvents" -->
@@ -82384,6 +82396,19 @@
   </summary>
 </histogram>
 
+<histogram name="Net.SSLHandshakeEarlyDataReason"
+    enum="SSLHandshakeEarlyDataReason">
+<!-- expires-never: Used to keep track of the TLS ecosystem. -->
+
+  <owner>davidben@chromium.org</owner>
+  <owner>svaldez@chromium.org</owner>
+  <summary>
+    Indicates whether a TLS 1.3 connection with 0-RTT enabled ended up using
+    0-RTT or not, and why; this includes reasons such as the server declining to
+    resume the connection and the client not having enough tickets available.
+  </summary>
+</histogram>
+
 <histogram name="Net.SSLHostInfoDNSLookup" units="ms"
     expires_after="2015-11-11">
   <obsolete>
diff --git a/tools/metrics/ukm/ukm.xml b/tools/metrics/ukm/ukm.xml
index 8df77ef..237fabc 100644
--- a/tools/metrics/ukm/ukm.xml
+++ b/tools/metrics/ukm/ukm.xml
@@ -5569,6 +5569,25 @@
     </summary>
   </metric>
   <metric name="InteractiveTiming.LongestInputDelay3">
+    <obsolete>
+      Removed on September 2019 in favor of InteractiveTiming.LongestInputDelay4
+      which includes events which otherwise would have been filtered.
+    </obsolete>
+    <summary>
+      Measures longest Input Delay, the longest duration between the hardware
+      timestamp and the start of event processing on the main thread for the
+      meaningful input per navigation. In ms.
+    </summary>
+    <aggregation>
+      <history>
+        <index fields="profile.country"/>
+        <statistics>
+          <quantiles type="std-percentiles"/>
+        </statistics>
+      </history>
+    </aggregation>
+  </metric>
+  <metric name="InteractiveTiming.LongestInputDelay4">
     <summary>
       Measures longest Input Delay, the longest duration between the hardware
       timestamp and the start of event processing on the main thread for the
@@ -5606,6 +5625,17 @@
     </summary>
   </metric>
   <metric name="InteractiveTiming.LongestInputTimestamp3">
+    <obsolete>
+      Removed on February 2019 in favor of
+      InteractiveTiming.LongestInputTimestamp4 which includes inputs which would
+      have previously been filtered.
+    </obsolete>
+    <summary>
+      The duration between navigation start and the hardware timestamp of the
+      meaningful input with longest queuing delay per navigation. In ms.
+    </summary>
+  </metric>
+  <metric name="InteractiveTiming.LongestInputTimestamp4">
     <summary>
       The duration between navigation start and the hardware timestamp of the
       meaningful input with longest queuing delay per navigation. In ms.
diff --git a/tools/perf/core/results_processor/processor.py b/tools/perf/core/results_processor/processor.py
index 32f47a4..9eeef263 100644
--- a/tools/perf/core/results_processor/processor.py
+++ b/tools/perf/core/results_processor/processor.py
@@ -9,8 +9,12 @@
 """
 
 import json
+import logging
 import os
+import random
+import re
 
+from py_utils import cloud_storage
 from core.results_processor import command_line
 from core.results_processor import compute_metrics
 from core.results_processor import formatters
@@ -43,7 +47,8 @@
 
   _AggregateTraces(intermediate_results)
 
-  _UploadArtifacts(intermediate_results, options.upload_bucket)
+  UploadArtifacts(
+      intermediate_results, options.upload_bucket, options.results_label)
 
   if any(fmt in FORMATS_WITH_METRICS for fmt in options.output_formats):
     histogram_dicts = _ComputeMetrics(intermediate_results,
@@ -88,20 +93,48 @@
         del artifacts[trace]
 
 
-def _UploadArtifacts(intermediate_results, upload_bucket):
+def _RemoteName(results_label, start_time, test_path, artifact_name):
+  """Construct a name for a given artifact, under which it will be
+  stored in the cloud.
+  """
+  if results_label:
+    identifier_parts = [re.sub(r'\W+', '_', results_label)]
+  else:
+    identifier_parts = []
+  # Time is rounded to seconds and delimiters are removed.
+  # The first 19 chars of the string match 'YYYY-MM-DDTHH:MM:SS'.
+  identifier_parts.append(re.sub(r'\W+', '', start_time[:19]))
+  identifier_parts.append(str(random.randint(1, 1e5)))
+  run_identifier = '_'.join(identifier_parts)
+  return '/'.join([run_identifier, test_path, artifact_name])
+
+
+def UploadArtifacts(intermediate_results, upload_bucket, results_label):
   """Upload all artifacts to cloud.
 
   For each test run, uploads all its artifacts to cloud and sets remoteUrl
   fields in intermediate_results.
   """
-  if upload_bucket is not None:
-    for result in intermediate_results['testResults']:
-      artifacts = result.get('artifacts', {})
-      for artifact in artifacts.values():
-        # For now, the uploading is done by Telemetry, so we just check that
-        # remoteUrls are set.
-        # TODO(crbug.com/981349): replace this with actual uploading code
-        assert 'remoteUrl' in artifact
+  if upload_bucket is None:
+    return
+
+  start_time = intermediate_results['benchmarkRun']['startTime']
+  for result in intermediate_results['testResults']:
+    artifacts = result.get('artifacts', {})
+    for name, artifact in artifacts.iteritems():
+      if 'remoteUrl' in artifact:
+        continue
+      # TODO(crbug.com/981349): Remove this check after Telemetry does not
+      # save histograms as an artifact anymore.
+      if name == compute_metrics.HISTOGRAM_DICTS_FILE:
+        continue
+      artifact['remoteUrl'] = cloud_storage.Insert(
+          upload_bucket,
+          _RemoteName(results_label, start_time, result['testPath'], name),
+          artifact['filePath'],
+      )
+      logging.info('Uploaded %s of %s to %s\n' % (
+          name, result['testPath'], artifact['remoteUrl']))
 
 
 def _ComputeMetrics(intermediate_results, results_label):
diff --git a/tools/perf/core/results_processor/processor_unittest.py b/tools/perf/core/results_processor/processor_unittest.py
index 1df87c2..aa872e5 100644
--- a/tools/perf/core/results_processor/processor_unittest.py
+++ b/tools/perf/core/results_processor/processor_unittest.py
@@ -6,6 +6,8 @@
 
 import unittest
 
+import mock
+
 from core.results_processor import processor
 from core.results_processor import testing
 
@@ -37,3 +39,56 @@
     self.assertIn(['linux'], diag_values)
     self.assertIn([['documentation', 'url']], diag_values)
     self.assertIn(['label'], diag_values)
+
+  def testUploadArtifacts(self):
+    in_results = testing.IntermediateResults(
+        test_results=[
+            testing.TestResult(
+                'benchmark/story',
+                artifacts={'log': testing.Artifact('/log.log')},
+            ),
+            testing.TestResult(
+                'benchmark/story',
+                artifacts={
+                  'trace.html': testing.Artifact('/trace.html'),
+                  'screenshot': testing.Artifact('/screenshot.png'),
+                },
+            ),
+        ],
+    )
+
+    with mock.patch('py_utils.cloud_storage.Insert') as cloud_patch:
+      cloud_patch.return_value = 'gs://url'
+      processor.UploadArtifacts(in_results, 'bucket', None)
+      cloud_patch.assert_has_calls([
+          mock.call('bucket', mock.ANY, '/log.log'),
+          mock.call('bucket', mock.ANY, '/trace.html'),
+          mock.call('bucket', mock.ANY, '/screenshot.png'),
+        ],
+        any_order=True,
+      )
+
+    for result in in_results['testResults']:
+      for artifact in result['artifacts'].itervalues():
+        self.assertEqual(artifact['remoteUrl'], 'gs://url')
+
+  def testUploadArtifacts_CheckRemoteUrl(self):
+    in_results = testing.IntermediateResults(
+        test_results=[
+            testing.TestResult(
+                'benchmark/story',
+                artifacts={'trace.html': testing.Artifact('/trace.html')},
+            ),
+        ],
+        start_time='2019-10-01T12:00:00.123456Z',
+    )
+
+    with mock.patch('py_utils.cloud_storage.Insert') as cloud_patch:
+      with mock.patch('random.randint') as randint_patch:
+        randint_patch.return_value = 54321
+        processor.UploadArtifacts(in_results, 'bucket', 'src@abc + 123')
+        cloud_patch.assert_called_once_with(
+            'bucket',
+            'src_abc_123_20191001T120000_54321/benchmark/story/trace.html',
+            '/trace.html'
+        )
diff --git a/tools/perf/page_sets/data/octane.json b/tools/perf/page_sets/data/octane.json
index 382d879..24494d6a 100644
--- a/tools/perf/page_sets/data/octane.json
+++ b/tools/perf/page_sets/data/octane.json
@@ -1,9 +1,9 @@
 {
     "archives": {
-        "http://chromium.github.io/octane/index.html?auto=1": {
+        "Octane": {
             "DEFAULT": "octane_002.wprgo"
         }
     },
     "description": "Describes the Web Page Replay archives for a story set. Don't edit by hand! Use record_wpr for updating.",
     "platform_specific": true
-}
\ No newline at end of file
+}
diff --git a/tools/perf/page_sets/octane_pages.py b/tools/perf/page_sets/octane_pages.py
index e3f8a32..35457ef 100644
--- a/tools/perf/page_sets/octane_pages.py
+++ b/tools/perf/page_sets/octane_pages.py
@@ -53,7 +53,8 @@
 
 
 class OctaneStory(press_story.PressStory):
-  URL='http://chromium.github.io/octane/index.html?auto=1'
+  URL = 'http://chromium.github.io/octane/index.html?auto=1'
+  NAME = 'Octane'
 
   def RunNavigateSteps(self, action_runner):
     total_memory = (
@@ -97,8 +98,8 @@
         # Collect all test scores to compute geometric mean.
         all_scores.append(score)
     total = statistics.GeometricMean(all_scores)
-    self.AddJavascriptMetricSummaryValue(
-        scalar.ScalarValue(None, 'Total.Score', 'score', total,
+    self.AddJavascriptMetricValue(
+        scalar.ScalarValue(self, 'Total.Score', 'score', total,
                            description='Geometric mean of the scores of each '
                            'individual benchmark in the Octane '
                            'benchmark collection.'))
diff --git a/ui/display/mac/screen_mac.mm b/ui/display/mac/screen_mac.mm
index 170963e..85545ec 100644
--- a/ui/display/mac/screen_mac.mm
+++ b/ui/display/mac/screen_mac.mm
@@ -143,8 +143,8 @@
   ScreenMac()
       : configure_timer_(FROM_HERE,
                          base::TimeDelta::FromMilliseconds(kConfigureDelayMs),
-                         base::Bind(&ScreenMac::ConfigureTimerFired,
-                                    base::Unretained(this))) {
+                         base::BindRepeating(&ScreenMac::ConfigureTimerFired,
+                                             base::Unretained(this))) {
     old_displays_ = displays_ = BuildDisplaysFromQuartz();
     CGDisplayRegisterReconfigurationCallback(
         ScreenMac::DisplayReconfigurationCallBack, this);
diff --git a/ui/display/manager/apply_content_protection_task_unittest.cc b/ui/display/manager/apply_content_protection_task_unittest.cc
index ec7494c..864c2cd 100644
--- a/ui/display/manager/apply_content_protection_task_unittest.cc
+++ b/ui/display/manager/apply_content_protection_task_unittest.cc
@@ -64,8 +64,8 @@
   request[1] = CONTENT_PROTECTION_METHOD_HDCP;
   ApplyContentProtectionTask task(
       &layout_manager, &display_delegate_, request,
-      base::Bind(&ApplyContentProtectionTaskTest::ResponseCallback,
-                 base::Unretained(this)));
+      base::BindOnce(&ApplyContentProtectionTaskTest::ResponseCallback,
+                     base::Unretained(this)));
   task.Run();
 
   EXPECT_EQ(Response::SUCCESS, response_);
@@ -82,8 +82,8 @@
   request[1] = CONTENT_PROTECTION_METHOD_HDCP;
   ApplyContentProtectionTask task(
       &layout_manager, &display_delegate_, request,
-      base::Bind(&ApplyContentProtectionTaskTest::ResponseCallback,
-                 base::Unretained(this)));
+      base::BindOnce(&ApplyContentProtectionTaskTest::ResponseCallback,
+                     base::Unretained(this)));
   task.Run();
 
   EXPECT_EQ(Response::SUCCESS, response_);
@@ -101,8 +101,8 @@
   request[1] = CONTENT_PROTECTION_METHOD_HDCP;
   ApplyContentProtectionTask task(
       &layout_manager, &display_delegate_, request,
-      base::Bind(&ApplyContentProtectionTaskTest::ResponseCallback,
-                 base::Unretained(this)));
+      base::BindOnce(&ApplyContentProtectionTaskTest::ResponseCallback,
+                     base::Unretained(this)));
   task.Run();
 
   EXPECT_EQ(Response::FAILURE, response_);
@@ -120,8 +120,8 @@
   request[1] = CONTENT_PROTECTION_METHOD_HDCP;
   ApplyContentProtectionTask task(
       &layout_manager, &display_delegate_, request,
-      base::Bind(&ApplyContentProtectionTaskTest::ResponseCallback,
-                 base::Unretained(this)));
+      base::BindOnce(&ApplyContentProtectionTaskTest::ResponseCallback,
+                     base::Unretained(this)));
   task.Run();
 
   EXPECT_EQ(Response::FAILURE, response_);
@@ -139,8 +139,8 @@
   request[1] = CONTENT_PROTECTION_METHOD_HDCP;
   ApplyContentProtectionTask task(
       &layout_manager, &display_delegate_, request,
-      base::Bind(&ApplyContentProtectionTaskTest::ResponseCallback,
-                 base::Unretained(this)));
+      base::BindOnce(&ApplyContentProtectionTaskTest::ResponseCallback,
+                     base::Unretained(this)));
   task.Run();
 
   EXPECT_EQ(Response::FAILURE, response_);
@@ -159,8 +159,8 @@
   request[1] = CONTENT_PROTECTION_METHOD_NONE;
   ApplyContentProtectionTask task(
       &layout_manager, &display_delegate_, request,
-      base::Bind(&ApplyContentProtectionTaskTest::ResponseCallback,
-                 base::Unretained(this)));
+      base::BindOnce(&ApplyContentProtectionTaskTest::ResponseCallback,
+                     base::Unretained(this)));
   task.Run();
 
   EXPECT_EQ(Response::SUCCESS, response_);
diff --git a/ui/display/manager/configure_displays_task.cc b/ui/display/manager/configure_displays_task.cc
index b70753f..06d1212 100644
--- a/ui/display/manager/configure_displays_task.cc
+++ b/ui/display/manager/configure_displays_task.cc
@@ -46,10 +46,10 @@
 ConfigureDisplaysTask::ConfigureDisplaysTask(
     NativeDisplayDelegate* delegate,
     const std::vector<DisplayConfigureRequest>& requests,
-    const ResponseCallback& callback)
+    ResponseCallback callback)
     : delegate_(delegate),
       requests_(requests),
-      callback_(callback),
+      callback_(std::move(callback)),
       is_configuring_(false),
       num_displays_configured_(0),
       task_status_(SUCCESS) {
@@ -76,16 +76,17 @@
       size_t index = pending_request_indexes_.front();
       DisplayConfigureRequest* request = &requests_[index];
       pending_request_indexes_.pop();
-      delegate_->Configure(*request->display, request->mode, request->origin,
-                           base::Bind(&ConfigureDisplaysTask::OnConfigured,
-                                      weak_ptr_factory_.GetWeakPtr(), index));
+      delegate_->Configure(
+          *request->display, request->mode, request->origin,
+          base::BindOnce(&ConfigureDisplaysTask::OnConfigured,
+                         weak_ptr_factory_.GetWeakPtr(), index));
     }
   }
 
   // Nothing should be modified after the |callback_| is called since the
   // task may be deleted in the callback.
   if (num_displays_configured_ == requests_.size())
-    callback_.Run(task_status_);
+    std::move(callback_).Run(task_status_);
 }
 
 void ConfigureDisplaysTask::OnConfigurationChanged() {}
diff --git a/ui/display/manager/configure_displays_task.h b/ui/display/manager/configure_displays_task.h
index 81dd612..86368b3e 100644
--- a/ui/display/manager/configure_displays_task.h
+++ b/ui/display/manager/configure_displays_task.h
@@ -50,11 +50,11 @@
     PARTIAL_SUCCESS,
   };
 
-  typedef base::Callback<void(Status)> ResponseCallback;
+  using ResponseCallback = base::OnceCallback<void(Status)>;
 
   ConfigureDisplaysTask(NativeDisplayDelegate* delegate,
                         const std::vector<DisplayConfigureRequest>& requests,
-                        const ResponseCallback& callback);
+                        ResponseCallback callback);
   ~ConfigureDisplaysTask() override;
 
   // Starts the configuration task.
diff --git a/ui/display/manager/configure_displays_task_unittest.cc b/ui/display/manager/configure_displays_task_unittest.cc
index ea95f9a..a032da4 100644
--- a/ui/display/manager/configure_displays_task_unittest.cc
+++ b/ui/display/manager/configure_displays_task_unittest.cc
@@ -67,11 +67,11 @@
 }  // namespace
 
 TEST_F(ConfigureDisplaysTaskTest, ConfigureWithNoDisplays) {
-  ConfigureDisplaysTask::ResponseCallback callback = base::Bind(
+  ConfigureDisplaysTask::ResponseCallback callback = base::BindOnce(
       &ConfigureDisplaysTaskTest::ConfigureCallback, base::Unretained(this));
 
   ConfigureDisplaysTask task(&delegate_, std::vector<DisplayConfigureRequest>(),
-                             callback);
+                             std::move(callback));
 
   task.Run();
 
@@ -81,13 +81,13 @@
 }
 
 TEST_F(ConfigureDisplaysTaskTest, ConfigureWithOneDisplay) {
-  ConfigureDisplaysTask::ResponseCallback callback = base::Bind(
+  ConfigureDisplaysTask::ResponseCallback callback = base::BindOnce(
       &ConfigureDisplaysTaskTest::ConfigureCallback, base::Unretained(this));
 
   std::vector<DisplayConfigureRequest> requests(
       1,
       DisplayConfigureRequest(displays_[0].get(), &small_mode_, gfx::Point()));
-  ConfigureDisplaysTask task(&delegate_, requests, callback);
+  ConfigureDisplaysTask task(&delegate_, requests, std::move(callback));
   task.Run();
 
   EXPECT_TRUE(callback_called_);
@@ -97,7 +97,7 @@
 }
 
 TEST_F(ConfigureDisplaysTaskTest, ConfigureWithTwoDisplay) {
-  ConfigureDisplaysTask::ResponseCallback callback = base::Bind(
+  ConfigureDisplaysTask::ResponseCallback callback = base::BindOnce(
       &ConfigureDisplaysTaskTest::ConfigureCallback, base::Unretained(this));
 
   std::vector<DisplayConfigureRequest> requests;
@@ -106,7 +106,7 @@
         displays_[i].get(), displays_[i]->native_mode(), gfx::Point()));
   }
 
-  ConfigureDisplaysTask task(&delegate_, requests, callback);
+  ConfigureDisplaysTask task(&delegate_, requests, std::move(callback));
   task.Run();
 
   EXPECT_TRUE(callback_called_);
@@ -120,14 +120,14 @@
 }
 
 TEST_F(ConfigureDisplaysTaskTest, DisableDisplayFails) {
-  ConfigureDisplaysTask::ResponseCallback callback = base::Bind(
+  ConfigureDisplaysTask::ResponseCallback callback = base::BindOnce(
       &ConfigureDisplaysTaskTest::ConfigureCallback, base::Unretained(this));
 
   delegate_.set_max_configurable_pixels(1);
 
   std::vector<DisplayConfigureRequest> requests(
       1, DisplayConfigureRequest(displays_[0].get(), nullptr, gfx::Point()));
-  ConfigureDisplaysTask task(&delegate_, requests, callback);
+  ConfigureDisplaysTask task(&delegate_, requests, std::move(callback));
   task.Run();
 
   EXPECT_TRUE(callback_called_);
@@ -139,14 +139,14 @@
 }
 
 TEST_F(ConfigureDisplaysTaskTest, ConfigureWithOneDisplayFails) {
-  ConfigureDisplaysTask::ResponseCallback callback = base::Bind(
+  ConfigureDisplaysTask::ResponseCallback callback = base::BindOnce(
       &ConfigureDisplaysTaskTest::ConfigureCallback, base::Unretained(this));
 
   delegate_.set_max_configurable_pixels(1);
 
   std::vector<DisplayConfigureRequest> requests(
       1, DisplayConfigureRequest(displays_[1].get(), &big_mode_, gfx::Point()));
-  ConfigureDisplaysTask task(&delegate_, requests, callback);
+  ConfigureDisplaysTask task(&delegate_, requests, std::move(callback));
   task.Run();
 
   EXPECT_TRUE(callback_called_);
@@ -160,7 +160,7 @@
 }
 
 TEST_F(ConfigureDisplaysTaskTest, ConfigureWithTwoDisplayFails) {
-  ConfigureDisplaysTask::ResponseCallback callback = base::Bind(
+  ConfigureDisplaysTask::ResponseCallback callback = base::BindOnce(
       &ConfigureDisplaysTaskTest::ConfigureCallback, base::Unretained(this));
 
   delegate_.set_max_configurable_pixels(1);
@@ -171,7 +171,7 @@
         displays_[i].get(), displays_[i]->native_mode(), gfx::Point()));
   }
 
-  ConfigureDisplaysTask task(&delegate_, requests, callback);
+  ConfigureDisplaysTask task(&delegate_, requests, std::move(callback));
   task.Run();
 
   EXPECT_TRUE(callback_called_);
@@ -186,7 +186,7 @@
 }
 
 TEST_F(ConfigureDisplaysTaskTest, ConfigureWithTwoDisplaysPartialSuccess) {
-  ConfigureDisplaysTask::ResponseCallback callback = base::Bind(
+  ConfigureDisplaysTask::ResponseCallback callback = base::BindOnce(
       &ConfigureDisplaysTaskTest::ConfigureCallback, base::Unretained(this));
 
   delegate_.set_max_configurable_pixels(small_mode_.size().GetArea());
@@ -197,7 +197,7 @@
         displays_[i].get(), displays_[i]->native_mode(), gfx::Point()));
   }
 
-  ConfigureDisplaysTask task(&delegate_, requests, callback);
+  ConfigureDisplaysTask task(&delegate_, requests, std::move(callback));
   task.Run();
 
   EXPECT_TRUE(callback_called_);
@@ -212,7 +212,7 @@
 }
 
 TEST_F(ConfigureDisplaysTaskTest, AsyncConfigureWithTwoDisplaysPartialSuccess) {
-  ConfigureDisplaysTask::ResponseCallback callback = base::Bind(
+  ConfigureDisplaysTask::ResponseCallback callback = base::BindOnce(
       &ConfigureDisplaysTaskTest::ConfigureCallback, base::Unretained(this));
 
   delegate_.set_run_async(true);
@@ -224,7 +224,7 @@
         displays_[i].get(), displays_[i]->native_mode(), gfx::Point()));
   }
 
-  ConfigureDisplaysTask task(&delegate_, requests, callback);
+  ConfigureDisplaysTask task(&delegate_, requests, std::move(callback));
   task.Run();
 
   EXPECT_FALSE(callback_called_);
diff --git a/ui/display/manager/display_configurator.cc b/ui/display/manager/display_configurator.cc
index 754f6a67..b474d32c 100644
--- a/ui/display/manager/display_configurator.cc
+++ b/ui/display/manager/display_configurator.cc
@@ -661,8 +661,8 @@
 
   display_control_changing_ = true;
   native_display_delegate_->TakeDisplayControl(
-      base::Bind(&DisplayConfigurator::OnDisplayControlTaken,
-                 weak_ptr_factory_.GetWeakPtr(), base::Passed(&callback)));
+      base::BindOnce(&DisplayConfigurator::OnDisplayControlTaken,
+                     weak_ptr_factory_.GetWeakPtr(), base::Passed(&callback)));
 }
 
 void DisplayConfigurator::OnDisplayControlTaken(DisplayControlCallback callback,
@@ -705,8 +705,8 @@
   // them for output.
   SetDisplayPowerInternal(
       chromeos::DISPLAY_POWER_ALL_OFF, kSetDisplayPowerNoFlags,
-      base::Bind(&DisplayConfigurator::SendRelinquishDisplayControl,
-                 weak_ptr_factory_.GetWeakPtr(), base::Passed(&callback)));
+      base::BindOnce(&DisplayConfigurator::SendRelinquishDisplayControl,
+                     weak_ptr_factory_.GetWeakPtr(), base::Passed(&callback)));
 }
 
 void DisplayConfigurator::SendRelinquishDisplayControl(
@@ -716,9 +716,9 @@
     // Set the flag early such that an incoming configuration event won't start
     // while we're releasing control of the displays.
     display_externally_controlled_ = true;
-    native_display_delegate_->RelinquishDisplayControl(
-        base::Bind(&DisplayConfigurator::OnDisplayControlRelinquished,
-                   weak_ptr_factory_.GetWeakPtr(), base::Passed(&callback)));
+    native_display_delegate_->RelinquishDisplayControl(base::BindOnce(
+        &DisplayConfigurator::OnDisplayControlRelinquished,
+        weak_ptr_factory_.GetWeakPtr(), base::Passed(&callback)));
   } else {
     display_control_changing_ = false;
     std::move(callback).Run(false);
@@ -753,8 +753,8 @@
       native_display_delegate_.get(), layout_manager_.get(),
       requested_display_state_, GetRequestedPowerState(),
       kSetDisplayPowerForceProbe, /*force_configure=*/true,
-      base::Bind(&DisplayConfigurator::OnConfigured,
-                 weak_ptr_factory_.GetWeakPtr()));
+      base::BindOnce(&DisplayConfigurator::OnConfigured,
+                     weak_ptr_factory_.GetWeakPtr()));
   configuration_task_->Run();
 }
 
@@ -794,7 +794,7 @@
 void DisplayConfigurator::SetDisplayPowerInternal(
     chromeos::DisplayPowerState power_state,
     int flags,
-    const ConfigurationCallback& callback) {
+    ConfigurationCallback callback) {
   // Only skip if the current power state is the same and the latest requested
   // power state is the same. If |pending_power_state_ != current_power_state_|
   // then there is a current task pending or the last configuration failed. In
@@ -803,14 +803,14 @@
   if (power_state == current_power_state_ &&
       power_state == pending_power_state_ &&
       !(flags & kSetDisplayPowerForceProbe)) {
-    callback.Run(true);
+    std::move(callback).Run(true);
     return;
   }
 
   pending_power_state_ = power_state;
   has_pending_power_state_ = true;
   pending_power_flags_ = flags;
-  queued_configuration_callbacks_.push_back(callback);
+  queued_configuration_callbacks_.push_back(std::move(callback));
 
   if (configure_timer_.IsRunning()) {
     // If there is a configuration task scheduled, avoid performing
@@ -826,9 +826,9 @@
 void DisplayConfigurator::SetDisplayPower(
     chromeos::DisplayPowerState power_state,
     int flags,
-    const ConfigurationCallback& callback) {
+    ConfigurationCallback callback) {
   if (configurator_disabled()) {
-    callback.Run(false);
+    std::move(callback).Run(false);
     return;
   }
 
@@ -838,7 +838,7 @@
           << (configure_timer_.IsRunning() ? "Running" : "Stopped");
 
   requested_power_state_ = power_state;
-  SetDisplayPowerInternal(*requested_power_state_, flags, callback);
+  SetDisplayPowerInternal(*requested_power_state_, flags, std::move(callback));
 }
 
 void DisplayConfigurator::SetDisplayMode(MultipleDisplayState new_state) {
@@ -892,10 +892,9 @@
   observers_.RemoveObserver(observer);
 }
 
-void DisplayConfigurator::SuspendDisplays(
-    const ConfigurationCallback& callback) {
+void DisplayConfigurator::SuspendDisplays(ConfigurationCallback callback) {
   if (configurator_disabled()) {
-    callback.Run(false);
+    std::move(callback).Run(false);
     return;
   }
 
@@ -910,7 +909,7 @@
   // unless explicitly requested by lucid sleep code). Use
   // SetDisplayPowerInternal so requested_power_state_ is maintained.
   SetDisplayPowerInternal(chromeos::DISPLAY_POWER_ALL_OFF,
-                          kSetDisplayPowerNoFlags, callback);
+                          kSetDisplayPowerNoFlags, std::move(callback));
 }
 
 void DisplayConfigurator::ResumeDisplays() {
@@ -968,8 +967,8 @@
       native_display_delegate_.get(), layout_manager_.get(),
       requested_display_state_, pending_power_state_, pending_power_flags_,
       force_configure_,
-      base::Bind(&DisplayConfigurator::OnConfigured,
-                 weak_ptr_factory_.GetWeakPtr()));
+      base::BindOnce(&DisplayConfigurator::OnConfigured,
+                     weak_ptr_factory_.GetWeakPtr()));
 
   // Reset the flags before running the task; otherwise it may end up scheduling
   // another configuration.
@@ -1049,15 +1048,15 @@
 }
 
 void DisplayConfigurator::CallAndClearInProgressCallbacks(bool success) {
-  for (const auto& callback : in_progress_configuration_callbacks_)
-    callback.Run(success);
+  for (auto& callback : in_progress_configuration_callbacks_)
+    std::move(callback).Run(success);
 
   in_progress_configuration_callbacks_.clear();
 }
 
 void DisplayConfigurator::CallAndClearQueuedCallbacks(bool success) {
-  for (const auto& callback : queued_configuration_callbacks_)
-    callback.Run(success);
+  for (auto& callback : queued_configuration_callbacks_)
+    std::move(callback).Run(success);
 
   queued_configuration_callbacks_.clear();
 }
diff --git a/ui/display/manager/display_configurator.h b/ui/display/manager/display_configurator.h
index d8e6755..f6b5d23 100644
--- a/ui/display/manager/display_configurator.h
+++ b/ui/display/manager/display_configurator.h
@@ -47,7 +47,7 @@
 class DISPLAY_MANAGER_EXPORT DisplayConfigurator
     : public NativeDisplayObserver {
  public:
-  using ConfigurationCallback = base::Callback<void(bool /* success */)>;
+  using ConfigurationCallback = base::OnceCallback<void(bool /* success */)>;
   using DisplayControlCallback = base::OnceCallback<void(bool success)>;
 
   using DisplayStateList = std::vector<DisplaySnapshot*>;
@@ -227,7 +227,7 @@
   // operation.
   void SetDisplayPower(chromeos::DisplayPowerState power_state,
                        int flags,
-                       const ConfigurationCallback& callback);
+                       ConfigurationCallback callback);
 
   // Force switching the display mode to |new_state|. Returns false if
   // switching failed (possibly because |new_state| is invalid for the
@@ -245,7 +245,7 @@
   // configure them for their resume state. This allows faster resume on
   // machines where display configuration is slow. On completion of the display
   // configuration |callback| is executed synchronously or asynchronously.
-  void SuspendDisplays(const ConfigurationCallback& callback);
+  void SuspendDisplays(ConfigurationCallback callback);
 
   // Reprobes displays to handle changes made while the system was
   // suspended.
@@ -291,7 +291,7 @@
   // invoked (perhaps synchronously) on completion.
   void SetDisplayPowerInternal(chromeos::DisplayPowerState power_state,
                                int flags,
-                               const ConfigurationCallback& callback);
+                               ConfigurationCallback callback);
 
   // Configures displays. Invoked by |configure_timer_|.
   void ConfigureDisplays();
diff --git a/ui/display/manager/display_configurator_unittest.cc b/ui/display/manager/display_configurator_unittest.cc
index d9c2cb2..e4563cb5 100644
--- a/ui/display/manager/display_configurator_unittest.cc
+++ b/ui/display/manager/display_configurator_unittest.cc
@@ -152,16 +152,13 @@
 class ConfigurationWaiter {
  public:
   explicit ConfigurationWaiter(DisplayConfigurator::TestApi* test_api)
-      : on_configured_callback_(base::Bind(&ConfigurationWaiter::OnConfigured,
-                                           base::Unretained(this))),
-        test_api_(test_api),
-        callback_result_(CALLBACK_NOT_CALLED) {}
+      : test_api_(test_api), callback_result_(CALLBACK_NOT_CALLED) {}
 
   ~ConfigurationWaiter() = default;
 
-  const DisplayConfigurator::ConfigurationCallback& on_configuration_callback()
-      const {
-    return on_configured_callback_;
+  DisplayConfigurator::ConfigurationCallback on_configuration_callback() {
+    return base::BindOnce(&ConfigurationWaiter::OnConfigured,
+                          base::Unretained(this));
   }
 
   CallbackResult callback_result() const { return callback_result_; }
@@ -190,9 +187,6 @@
     callback_result_ = status ? CALLBACK_SUCCESS : CALLBACK_FAILURE;
   }
 
-  // Passed with configuration requests to run OnConfigured().
-  const DisplayConfigurator::ConfigurationCallback on_configured_callback_;
-
   DisplayConfigurator::TestApi* test_api_;  // Not owned.
 
   // The status of the display configuration.
diff --git a/ui/display/manager/display_util.cc b/ui/display/manager/display_util.cc
index 25a4aac..57ce085 100644
--- a/ui/display/manager/display_util.cc
+++ b/ui/display/manager/display_util.cc
@@ -49,11 +49,11 @@
 // zoom values that includes a zoom level to go to the native resolution of the
 // display. Ensure that the list of DSFs are in sync with the list of default
 // device scale factors in display_change_observer.cc.
-constexpr std::array<ZoomListBucketDsf, 7> kZoomListBucketsForDsf{{
+constexpr std::array<ZoomListBucketDsf, 6> kZoomListBucketsForDsf{{
     {1.25f, {0.7f, 1.f / 1.25f, 0.85f, 0.9f, 0.95f, 1.f, 1.1f, 1.2f, 1.3f}},
     {1.6f, {1.f / 1.6f, 0.7f, 0.75f, 0.8f, 0.85f, 0.9f, 1.f, 1.15f, 1.3f}},
-    {1.6f, {1.f / 1.6f, 0.7f, 0.75f, 0.8f, 0.85f, 0.9f, 1.f, 1.15f, 1.3f}},
-    {1.77777f, {1.f / 1.77777f, 0.7f, 0.8f, 0.9f, 1.f, 1.2f, 1.35f}},
+    {1.77777f,
+     {1.f / 1.77777f, 0.65f, 0.75f, 0.8f, 0.9f, 1.f, 1.1f, 1.2f, 1.3f}},
     {2.f, {1.f / 2.f, 0.6f, 0.7f, 0.8f, 0.9f, 1.f, 1.1f, 1.25f, 1.5f}},
     {2.25f, {1.f / 2.25f, 0.6f, 0.7f, 0.8f, 0.9f, 1.f, 1.15f, 1.3f, 1.5f}},
     {2.66666f,
diff --git a/ui/display/manager/display_utils_unittest.cc b/ui/display/manager/display_utils_unittest.cc
index 3468b89..9f0f09f 100644
--- a/ui/display/manager/display_utils_unittest.cc
+++ b/ui/display/manager/display_utils_unittest.cc
@@ -69,6 +69,7 @@
         checks |= 0x01;
       if (WithinEpsilon(zoom_values[j], 1.f))
         checks |= 0x02;
+      EXPECT_LT(0.0f, zoom_values[j]);
     }
     EXPECT_TRUE(checks & 0x01) << "Inverse of " << dsf << " not on the list.";
     EXPECT_TRUE(checks & 0x02) << "Zoom level of unity is not on the list.";
diff --git a/ui/display/manager/query_content_protection_task_unittest.cc b/ui/display/manager/query_content_protection_task_unittest.cc
index 7404e3b8..26faf97 100644
--- a/ui/display/manager/query_content_protection_task_unittest.cc
+++ b/ui/display/manager/query_content_protection_task_unittest.cc
@@ -74,8 +74,8 @@
 
   QueryContentProtectionTask task(
       &layout_manager, &display_delegate_, 1,
-      base::Bind(&QueryContentProtectionTaskTest::ResponseCallback,
-                 base::Unretained(this)));
+      base::BindOnce(&QueryContentProtectionTaskTest::ResponseCallback,
+                     base::Unretained(this)));
   task.Run();
 
   ASSERT_TRUE(response_);
@@ -92,8 +92,8 @@
 
   QueryContentProtectionTask task(
       &layout_manager, &display_delegate_, 1,
-      base::Bind(&QueryContentProtectionTaskTest::ResponseCallback,
-                 base::Unretained(this)));
+      base::BindOnce(&QueryContentProtectionTaskTest::ResponseCallback,
+                     base::Unretained(this)));
   task.Run();
 
   ASSERT_TRUE(response_);
@@ -111,8 +111,8 @@
 
   QueryContentProtectionTask task(
       &layout_manager, &display_delegate_, 1,
-      base::Bind(&QueryContentProtectionTaskTest::ResponseCallback,
-                 base::Unretained(this)));
+      base::BindOnce(&QueryContentProtectionTaskTest::ResponseCallback,
+                     base::Unretained(this)));
   task.Run();
 
   ASSERT_TRUE(response_);
@@ -128,8 +128,8 @@
 
   QueryContentProtectionTask task(
       &layout_manager, &display_delegate_, 1,
-      base::Bind(&QueryContentProtectionTaskTest::ResponseCallback,
-                 base::Unretained(this)));
+      base::BindOnce(&QueryContentProtectionTaskTest::ResponseCallback,
+                     base::Unretained(this)));
   task.Run();
 
   ASSERT_TRUE(response_);
@@ -147,8 +147,8 @@
 
   QueryContentProtectionTask task(
       &layout_manager, &display_delegate_, 1,
-      base::Bind(&QueryContentProtectionTaskTest::ResponseCallback,
-                 base::Unretained(this)));
+      base::BindOnce(&QueryContentProtectionTaskTest::ResponseCallback,
+                     base::Unretained(this)));
   task.Run();
 
   ASSERT_TRUE(response_);
@@ -166,8 +166,8 @@
 
   QueryContentProtectionTask task(
       &layout_manager, &display_delegate_, 1,
-      base::Bind(&QueryContentProtectionTaskTest::ResponseCallback,
-                 base::Unretained(this)));
+      base::BindOnce(&QueryContentProtectionTaskTest::ResponseCallback,
+                     base::Unretained(this)));
   task.Run();
 
   ASSERT_TRUE(response_);
@@ -185,8 +185,8 @@
 
   QueryContentProtectionTask task(
       &layout_manager, &display_delegate_, 1,
-      base::Bind(&QueryContentProtectionTaskTest::ResponseCallback,
-                 base::Unretained(this)));
+      base::BindOnce(&QueryContentProtectionTaskTest::ResponseCallback,
+                     base::Unretained(this)));
   task.Run();
 
   ASSERT_TRUE(response_);
@@ -205,8 +205,8 @@
 
   QueryContentProtectionTask task(
       &layout_manager, &display_delegate_, 1,
-      base::Bind(&QueryContentProtectionTaskTest::ResponseCallback,
-                 base::Unretained(this)));
+      base::BindOnce(&QueryContentProtectionTaskTest::ResponseCallback,
+                     base::Unretained(this)));
   task.Run();
 
   ASSERT_TRUE(response_);
@@ -226,8 +226,8 @@
 
   QueryContentProtectionTask task1(
       &layout_manager, &display_delegate_, 1,
-      base::Bind(&QueryContentProtectionTaskTest::ResponseCallback,
-                 base::Unretained(this)));
+      base::BindOnce(&QueryContentProtectionTaskTest::ResponseCallback,
+                     base::Unretained(this)));
   task1.Run();
 
   ASSERT_TRUE(response_);
@@ -241,8 +241,8 @@
 
   QueryContentProtectionTask task2(
       &layout_manager, &display_delegate_, 2,
-      base::Bind(&QueryContentProtectionTaskTest::ResponseCallback,
-                 base::Unretained(this)));
+      base::BindOnce(&QueryContentProtectionTaskTest::ResponseCallback,
+                     base::Unretained(this)));
   task2.Run();
 
   ASSERT_TRUE(response_);
diff --git a/ui/display/manager/update_display_configuration_task.cc b/ui/display/manager/update_display_configuration_task.cc
index d7e51730..1c35b18 100644
--- a/ui/display/manager/update_display_configuration_task.cc
+++ b/ui/display/manager/update_display_configuration_task.cc
@@ -20,14 +20,14 @@
     chromeos::DisplayPowerState new_power_state,
     int power_flags,
     bool force_configure,
-    const ResponseCallback& callback)
+    ResponseCallback callback)
     : delegate_(delegate),
       layout_manager_(layout_manager),
       new_display_state_(new_display_state),
       new_power_state_(new_power_state),
       power_flags_(power_flags),
       force_configure_(force_configure),
-      callback_(callback),
+      callback_(std::move(callback)),
       requesting_displays_(false) {
   delegate_->AddObserver(this);
 }
@@ -39,8 +39,8 @@
 void UpdateDisplayConfigurationTask::Run() {
   requesting_displays_ = true;
   delegate_->GetDisplays(
-      base::Bind(&UpdateDisplayConfigurationTask::OnDisplaysUpdated,
-                 weak_ptr_factory_.GetWeakPtr()));
+      base::BindOnce(&UpdateDisplayConfigurationTask::OnDisplaysUpdated,
+                     weak_ptr_factory_.GetWeakPtr()));
 }
 
 void UpdateDisplayConfigurationTask::OnConfigurationChanged() {}
@@ -72,8 +72,8 @@
           << " force_configure=" << force_configure_
           << " display_count=" << cached_displays_.size();
   if (ShouldConfigure()) {
-    EnterState(base::Bind(&UpdateDisplayConfigurationTask::OnStateEntered,
-                          weak_ptr_factory_.GetWeakPtr()));
+    EnterState(base::BindOnce(&UpdateDisplayConfigurationTask::OnStateEntered,
+                              weak_ptr_factory_.GetWeakPtr()));
   } else {
     // If we don't have to configure then we're sticking with the old
     // configuration. Update it such that it reflects in the reported value.
@@ -83,21 +83,21 @@
 }
 
 void UpdateDisplayConfigurationTask::EnterState(
-    const ConfigureDisplaysTask::ResponseCallback& callback) {
+    ConfigureDisplaysTask::ResponseCallback callback) {
   VLOG(2) << "EnterState";
   std::vector<DisplayConfigureRequest> requests;
   if (!layout_manager_->GetDisplayLayout(cached_displays_, new_display_state_,
                                          new_power_state_, &requests)) {
-    callback.Run(ConfigureDisplaysTask::ERROR);
+    std::move(callback).Run(ConfigureDisplaysTask::ERROR);
     return;
   }
   if (!requests.empty()) {
     configure_task_.reset(
-        new ConfigureDisplaysTask(delegate_, requests, callback));
+        new ConfigureDisplaysTask(delegate_, requests, std::move(callback)));
     configure_task_->Run();
   } else {
     VLOG(2) << "No displays";
-    callback.Run(ConfigureDisplaysTask::SUCCESS);
+    std::move(callback).Run(ConfigureDisplaysTask::SUCCESS);
   }
 }
 
@@ -116,7 +116,7 @@
           layout_manager_->GetPowerState() != new_power_state_ ||
           force_configure_) {
         new_display_state_ = MULTIPLE_DISPLAY_STATE_MULTI_EXTENDED;
-        EnterState(base::Bind(
+        EnterState(base::BindOnce(
             &UpdateDisplayConfigurationTask::OnEnableSoftwareMirroring,
             weak_ptr_factory_.GetWeakPtr()));
         return;
@@ -145,8 +145,9 @@
 }
 
 void UpdateDisplayConfigurationTask::FinishConfiguration(bool success) {
-  callback_.Run(success, cached_displays_, cached_unassociated_displays_,
-                new_display_state_, new_power_state_);
+  std::move(callback_).Run(success, cached_displays_,
+                           cached_unassociated_displays_, new_display_state_,
+                           new_power_state_);
 }
 
 bool UpdateDisplayConfigurationTask::ShouldForceDpms() const {
diff --git a/ui/display/manager/update_display_configuration_task.h b/ui/display/manager/update_display_configuration_task.h
index 79cc57a..846f344 100644
--- a/ui/display/manager/update_display_configuration_task.h
+++ b/ui/display/manager/update_display_configuration_task.h
@@ -24,7 +24,7 @@
 class DISPLAY_MANAGER_EXPORT UpdateDisplayConfigurationTask
     : public NativeDisplayObserver {
  public:
-  using ResponseCallback = base::RepeatingCallback<void(
+  using ResponseCallback = base::OnceCallback<void(
       /*success=*/bool,
       /*displays=*/const std::vector<DisplaySnapshot*>&,
       /*unassociated_displays=*/const std::vector<DisplaySnapshot*>&,
@@ -37,7 +37,7 @@
                                  chromeos::DisplayPowerState new_power_state,
                                  int power_flags,
                                  bool force_configure,
-                                 const ResponseCallback& callback);
+                                 ResponseCallback callback);
   ~UpdateDisplayConfigurationTask() override;
 
   void Run();
@@ -62,7 +62,7 @@
 
   // Starts the configuration process. |callback| is used to continue the task
   // after |configure_taks_| finishes executing.
-  void EnterState(const ConfigureDisplaysTask::ResponseCallback& callback);
+  void EnterState(ConfigureDisplaysTask::ResponseCallback callback);
 
   // Finishes display configuration and runs |callback_|.
   void FinishConfiguration(bool success);
diff --git a/ui/display/manager/update_display_configuration_task_unittest.cc b/ui/display/manager/update_display_configuration_task_unittest.cc
index 58e5788..c851883 100644
--- a/ui/display/manager/update_display_configuration_task_unittest.cc
+++ b/ui/display/manager/update_display_configuration_task_unittest.cc
@@ -217,8 +217,8 @@
     UpdateDisplayConfigurationTask task(
         &delegate_, &layout_manager_, MULTIPLE_DISPLAY_STATE_HEADLESS,
         chromeos::DISPLAY_POWER_ALL_ON, 0, false,
-        base::Bind(&UpdateDisplayConfigurationTaskTest::ResponseCallback,
-                   base::Unretained(this)));
+        base::BindOnce(&UpdateDisplayConfigurationTaskTest::ResponseCallback,
+                       base::Unretained(this)));
     task.Run();
   }
 
@@ -236,8 +236,8 @@
     UpdateDisplayConfigurationTask task(
         &delegate_, &layout_manager_, MULTIPLE_DISPLAY_STATE_SINGLE,
         chromeos::DISPLAY_POWER_ALL_ON, 0, false,
-        base::Bind(&UpdateDisplayConfigurationTaskTest::ResponseCallback,
-                   base::Unretained(this)));
+        base::BindOnce(&UpdateDisplayConfigurationTaskTest::ResponseCallback,
+                       base::Unretained(this)));
     task.Run();
   }
 
@@ -259,8 +259,8 @@
     UpdateDisplayConfigurationTask task(
         &delegate_, &layout_manager_, MULTIPLE_DISPLAY_STATE_MULTI_EXTENDED,
         chromeos::DISPLAY_POWER_ALL_ON, 0, false,
-        base::Bind(&UpdateDisplayConfigurationTaskTest::ResponseCallback,
-                   base::Unretained(this)));
+        base::BindOnce(&UpdateDisplayConfigurationTaskTest::ResponseCallback,
+                       base::Unretained(this)));
     task.Run();
   }
 
@@ -285,8 +285,8 @@
     UpdateDisplayConfigurationTask task(
         &delegate_, &layout_manager_, MULTIPLE_DISPLAY_STATE_MULTI_MIRROR,
         chromeos::DISPLAY_POWER_ALL_ON, 0, false,
-        base::Bind(&UpdateDisplayConfigurationTaskTest::ResponseCallback,
-                   base::Unretained(this)));
+        base::BindOnce(&UpdateDisplayConfigurationTaskTest::ResponseCallback,
+                       base::Unretained(this)));
     task.Run();
   }
 
@@ -310,8 +310,8 @@
     UpdateDisplayConfigurationTask task(
         &delegate_, &layout_manager_, MULTIPLE_DISPLAY_STATE_MULTI_MIRROR,
         chromeos::DISPLAY_POWER_ALL_ON, 0, false,
-        base::Bind(&UpdateDisplayConfigurationTaskTest::ResponseCallback,
-                   base::Unretained(this)));
+        base::BindOnce(&UpdateDisplayConfigurationTaskTest::ResponseCallback,
+                       base::Unretained(this)));
     task.Run();
   }
 
@@ -328,8 +328,8 @@
     UpdateDisplayConfigurationTask task(
         &delegate_, &layout_manager_, MULTIPLE_DISPLAY_STATE_MULTI_EXTENDED,
         chromeos::DISPLAY_POWER_ALL_ON, 0, false,
-        base::Bind(&UpdateDisplayConfigurationTaskTest::ResponseCallback,
-                   base::Unretained(this)));
+        base::BindOnce(&UpdateDisplayConfigurationTaskTest::ResponseCallback,
+                       base::Unretained(this)));
     task.Run();
   }
 
@@ -355,8 +355,8 @@
     UpdateDisplayConfigurationTask task(
         &delegate_, &layout_manager_, MULTIPLE_DISPLAY_STATE_SINGLE,
         chromeos::DISPLAY_POWER_ALL_ON, 0, false,
-        base::Bind(&UpdateDisplayConfigurationTaskTest::ResponseCallback,
-                   base::Unretained(this)));
+        base::BindOnce(&UpdateDisplayConfigurationTaskTest::ResponseCallback,
+                       base::Unretained(this)));
     task.Run();
   }
 
@@ -375,8 +375,8 @@
     UpdateDisplayConfigurationTask task(
         &delegate_, &layout_manager_, MULTIPLE_DISPLAY_STATE_SINGLE,
         chromeos::DISPLAY_POWER_ALL_OFF, 0, false,
-        base::Bind(&UpdateDisplayConfigurationTaskTest::ResponseCallback,
-                   base::Unretained(this)));
+        base::BindOnce(&UpdateDisplayConfigurationTaskTest::ResponseCallback,
+                       base::Unretained(this)));
     task.Run();
   }
 
@@ -399,8 +399,8 @@
     UpdateDisplayConfigurationTask task(
         &delegate_, &layout_manager_, MULTIPLE_DISPLAY_STATE_MULTI_EXTENDED,
         chromeos::DISPLAY_POWER_ALL_ON, 0, false,
-        base::Bind(&UpdateDisplayConfigurationTaskTest::ResponseCallback,
-                   base::Unretained(this)));
+        base::BindOnce(&UpdateDisplayConfigurationTaskTest::ResponseCallback,
+                       base::Unretained(this)));
     task.Run();
   }
 
@@ -410,8 +410,8 @@
     UpdateDisplayConfigurationTask task(
         &delegate_, &layout_manager_, MULTIPLE_DISPLAY_STATE_MULTI_MIRROR,
         chromeos::DISPLAY_POWER_ALL_ON, 0, false,
-        base::Bind(&UpdateDisplayConfigurationTaskTest::ResponseCallback,
-                   base::Unretained(this)));
+        base::BindOnce(&UpdateDisplayConfigurationTaskTest::ResponseCallback,
+                       base::Unretained(this)));
     task.Run();
   }
 
@@ -433,8 +433,8 @@
     UpdateDisplayConfigurationTask task(
         &delegate_, &layout_manager_, MULTIPLE_DISPLAY_STATE_MULTI_EXTENDED,
         chromeos::DISPLAY_POWER_ALL_ON, 0, false,
-        base::Bind(&UpdateDisplayConfigurationTaskTest::ResponseCallback,
-                   base::Unretained(this)));
+        base::BindOnce(&UpdateDisplayConfigurationTaskTest::ResponseCallback,
+                       base::Unretained(this)));
     task.Run();
   }
 
@@ -444,8 +444,8 @@
     UpdateDisplayConfigurationTask task(
         &delegate_, &layout_manager_, MULTIPLE_DISPLAY_STATE_MULTI_MIRROR,
         chromeos::DISPLAY_POWER_ALL_ON, 0, true /* force_configure */,
-        base::Bind(&UpdateDisplayConfigurationTaskTest::ResponseCallback,
-                   base::Unretained(this)));
+        base::BindOnce(&UpdateDisplayConfigurationTaskTest::ResponseCallback,
+                       base::Unretained(this)));
     task.Run();
   }
 
diff --git a/ui/display/win/color_profile_reader.cc b/ui/display/win/color_profile_reader.cc
index 71af8e68..fdfcacb 100644
--- a/ui/display/win/color_profile_reader.cc
+++ b/ui/display/win/color_profile_reader.cc
@@ -63,10 +63,10 @@
   base::PostTaskAndReplyWithResult(
       FROM_HERE,
       {base::ThreadPool(), base::MayBlock(), base::TaskPriority::BEST_EFFORT},
-      base::Bind(&ColorProfileReader::ReadProfilesOnBackgroundThread,
-                 new_device_to_path_map),
-      base::Bind(&ColorProfileReader::ReadProfilesCompleted,
-                 weak_factory_.GetWeakPtr()));
+      base::BindOnce(&ColorProfileReader::ReadProfilesOnBackgroundThread,
+                     new_device_to_path_map),
+      base::BindOnce(&ColorProfileReader::ReadProfilesCompleted,
+                     weak_factory_.GetWeakPtr()));
 }
 
 // static
diff --git a/ui/display/win/screen_win.cc b/ui/display/win/screen_win.cc
index 200c336..53b6124 100644
--- a/ui/display/win/screen_win.cc
+++ b/ui/display/win/screen_win.cc
@@ -679,9 +679,8 @@
 
 void ScreenWin::Initialize() {
   color_profile_reader_->UpdateIfNeeded();
-  singleton_hwnd_observer_.reset(
-      new gfx::SingletonHwndObserver(
-          base::Bind(&ScreenWin::OnWndProc, base::Unretained(this))));
+  singleton_hwnd_observer_.reset(new gfx::SingletonHwndObserver(
+      base::BindRepeating(&ScreenWin::OnWndProc, base::Unretained(this))));
   UpdateFromDisplayInfos(GetDisplayInfosFromSystem());
   RecordDisplayScaleFactors();
 
diff --git a/ui/gl/yuv_to_rgb_converter.cc b/ui/gl/yuv_to_rgb_converter.cc
index 0dc4d466..e85c85dd 100644
--- a/ui/gl/yuv_to_rgb_converter.cc
+++ b/ui/gl/yuv_to_rgb_converter.cc
@@ -14,6 +14,11 @@
 namespace gl {
 namespace {
 
+const char kVertexHeaderES2[] =
+    "precision mediump float;\n"
+    "#define ATTRIBUTE attribute\n"
+    "#define VARYING varying\n";
+
 const char kVertexHeaderES3[] =
     "#version 300 es\n"
     "precision mediump float;\n"
@@ -30,6 +35,13 @@
     "#define ATTRIBUTE in\n"
     "#define VARYING out\n";
 
+const char kFragmentHeaderES2[] =
+    "#extension GL_ARB_texture_rectangle : require\n"
+    "precision mediump float;\n"
+    "#define VARYING varying\n"
+    "#define FRAGCOLOR gl_FragColor\n"
+    "#define TEX texture2DRect\n";
+
 const char kFragmentHeaderES3[] =
     "#version 300 es\n"
     "precision mediump float;\n"
@@ -89,27 +101,33 @@
   DCHECK(color_transform->CanGetShaderSource());
   std::string do_color_conversion = color_transform->GetShaderSource();
 
-  bool use_es3 = gl_version_info.is_es3;
-  bool use_core_profile = gl_version_info.is_desktop_core_profile;
+  const char* fragment_header = nullptr;
+  const char* vertex_header = nullptr;
+  if (gl_version_info.is_es2) {
+    vertex_header = kVertexHeaderES2;
+    fragment_header = kFragmentHeaderES2;
+  } else if (gl_version_info.is_es3) {
+    vertex_header = kVertexHeaderES3;
+    fragment_header = kFragmentHeaderES3;
+  } else if (gl_version_info.is_desktop_core_profile) {
+    vertex_header = kVertexHeaderCoreProfile;
+    fragment_header = kFragmentHeaderCoreProfile;
+  } else {
+    DCHECK(!gl_version_info.is_es);
+    vertex_header = kVertexHeaderCompatiblityProfile;
+    fragment_header = kFragmentHeaderCompatiblityProfile;
+  }
+  DCHECK(vertex_header && fragment_header);
+
   glGenFramebuffersEXT(1, &framebuffer_);
   vertex_buffer_ = GLHelper::SetupQuadVertexBuffer();
   vertex_shader_ = GLHelper::LoadShader(
       GL_VERTEX_SHADER,
-      base::StringPrintf(
-          "%s\n%s",
-          use_es3 ? kVertexHeaderES3
-                  : (use_core_profile ? kVertexHeaderCoreProfile
-                                      : kVertexHeaderCompatiblityProfile),
-          kVertexShader)
-          .c_str());
+      base::StringPrintf("%s\n%s", vertex_header, kVertexShader).c_str());
   fragment_shader_ = GLHelper::LoadShader(
       GL_FRAGMENT_SHADER,
-      base::StringPrintf(
-          "%s\n%s\n%s",
-          use_es3 ? kFragmentHeaderES3
-                  : (use_core_profile ? kFragmentHeaderCoreProfile
-                                      : kFragmentHeaderCompatiblityProfile),
-          do_color_conversion.c_str(), kFragmentShader)
+      base::StringPrintf("%s\n%s\n%s", fragment_header,
+                         do_color_conversion.c_str(), kFragmentShader)
           .c_str());
   program_ = GLHelper::SetupProgram(vertex_shader_, fragment_shader_);
 
@@ -127,7 +145,9 @@
   glUniform1i(y_sampler_location, 0);
   glUniform1i(uv_sampler_location, 1);
 
-  if (use_es3 || use_core_profile) {
+  bool has_vertex_array_objects =
+      gl_version_info.is_es3 || gl_version_info.is_desktop_core_profile;
+  if (has_vertex_array_objects) {
     glGenVertexArraysOES(1, &vertex_array_object_);
   }
 }
diff --git a/ui/login/display_manager.js b/ui/login/display_manager.js
index d72f89a..9f0942b 100644
--- a/ui/login/display_manager.js
+++ b/ui/login/display_manager.js
@@ -293,9 +293,11 @@
      * @param {number} height client area height
      */
     setClientAreaSize: function(width, height) {
-      var clientArea = $('outer-container');
-      var bottom = parseInt(window.getComputedStyle(clientArea).bottom);
-      clientArea.style.minHeight = cr.ui.toCssPx(height - bottom);
+      if (!cr.isChromeOS) {
+        var clientArea = $('outer-container');
+        var bottom = parseInt(window.getComputedStyle(clientArea).bottom);
+        clientArea.style.minHeight = cr.ui.toCssPx(height - bottom);
+      }
     },
 
     /**
diff --git a/ui/ozone/platform/wayland/BUILD.gn b/ui/ozone/platform/wayland/BUILD.gn
index 24a14654..6c553dd 100644
--- a/ui/ozone/platform/wayland/BUILD.gn
+++ b/ui/ozone/platform/wayland/BUILD.gn
@@ -292,6 +292,7 @@
     "//ui/ozone:test_support",
     "//ui/ozone/common/linux:drm",
     "//ui/ozone/common/linux:gbm",
+    "//ui/platform_window/platform_window_handler",
   ]
 
   import("//ui/base/ui_features.gni")
diff --git a/ui/ozone/platform/wayland/DEPS b/ui/ozone/platform/wayland/DEPS
index 32f3223e..60eec9e 100644
--- a/ui/ozone/platform/wayland/DEPS
+++ b/ui/ozone/platform/wayland/DEPS
@@ -5,6 +5,7 @@
   "+mojo/public",
   "+ui/base/clipboard/clipboard_constants.h",
   "+ui/base/dragdrop/drag_drop_types.h",
+  "+ui/base/dragdrop/file_info.h",
   "+ui/base/dragdrop/os_exchange_data.h",
   "+ui/base/dragdrop/os_exchange_data_provider_aura.h",
 ]
diff --git a/ui/ozone/platform/wayland/common/wayland_util.cc b/ui/ozone/platform/wayland/common/wayland_util.cc
index 1bc9349..068ce0f8 100644
--- a/ui/ozone/platform/wayland/common/wayland_util.cc
+++ b/ui/ozone/platform/wayland/common/wayland_util.cc
@@ -131,12 +131,12 @@
   return true;
 }
 
-void ReadDataFromFD(base::ScopedFD fd, std::string* contents) {
+void ReadDataFromFD(base::ScopedFD fd, std::vector<uint8_t>* contents) {
   DCHECK(contents);
-  char buffer[1 << 10];  // 1 kB in bytes.
+  uint8_t buffer[1 << 10];  // 1 kB in bytes.
   ssize_t length;
   while ((length = read(fd.get(), buffer, sizeof(buffer))) > 0)
-    contents->append(buffer, length);
+    contents->insert(contents->end(), buffer, buffer + length);
 }
 
 }  // namespace wl
diff --git a/ui/ozone/platform/wayland/common/wayland_util.h b/ui/ozone/platform/wayland/common/wayland_util.h
index dd7ec71..4b69969 100644
--- a/ui/ozone/platform/wayland/common/wayland_util.h
+++ b/ui/ozone/platform/wayland/common/wayland_util.h
@@ -5,7 +5,7 @@
 #ifndef UI_OZONE_PLATFORM_WAYLAND_COMMON_WAYLAND_UTIL_H_
 #define UI_OZONE_PLATFORM_WAYLAND_COMMON_WAYLAND_UTIL_H_
 
-#include <string>
+#include <vector>
 
 #include <wayland-client.h>
 
@@ -43,7 +43,7 @@
 bool DrawBitmap(const SkBitmap& bitmap, ui::WaylandShmBuffer* out_buffer);
 
 // Helper function to read data from a file.
-void ReadDataFromFD(base::ScopedFD fd, std::string* contents);
+void ReadDataFromFD(base::ScopedFD fd, std::vector<uint8_t>* contents);
 
 }  // namespace wl
 
diff --git a/ui/ozone/platform/wayland/host/gtk_primary_selection_device.cc b/ui/ozone/platform/wayland/host/gtk_primary_selection_device.cc
index 70be8577..d1e49b7 100644
--- a/ui/ozone/platform/wayland/host/gtk_primary_selection_device.cc
+++ b/ui/ozone/platform/wayland/host/gtk_primary_selection_device.cc
@@ -53,7 +53,7 @@
     self->ResetDataOffer();
 
     // Clear Clipboard cache.
-    self->connection()->clipboard()->SetData(std::string(), std::string());
+    self->connection()->clipboard()->SetData({}, {});
     return;
   }
 
diff --git a/ui/ozone/platform/wayland/host/internal/wayland_data_device_base.cc b/ui/ozone/platform/wayland/host/internal/wayland_data_device_base.cc
index bfaa6d7..ca94ce9 100644
--- a/ui/ozone/platform/wayland/host/internal/wayland_data_device_base.cc
+++ b/ui/ozone/platform/wayland/host/internal/wayland_data_device_base.cc
@@ -54,7 +54,7 @@
 void WaylandDataDeviceBase::ReadClipboardDataFromFD(
     base::ScopedFD fd,
     const std::string& mime_type) {
-  std::string contents;
+  std::vector<uint8_t> contents;
   wl::ReadDataFromFD(std::move(fd), &contents);
   connection_->clipboard()->SetData(contents, mime_type);
 }
@@ -89,8 +89,14 @@
 void WaylandDataDeviceBase::DeferredReadCallbackInternal(struct wl_callback* cb,
                                                          uint32_t time) {
   DCHECK(!deferred_read_closure_.is_null());
-  std::move(deferred_read_closure_).Run();
+
+  // The callback must be reset before invoking the closure because the latter
+  // may want to set another callback.  That typically happens when non-trivial
+  // data types are dropped; they have fallbacks to plain text so several
+  // roundtrips to data are chained.
   deferred_read_callback_.reset();
+
+  std::move(deferred_read_closure_).Run();
 }
 
 }  // namespace internal
diff --git a/ui/ozone/platform/wayland/host/internal/wayland_data_device_base.h b/ui/ozone/platform/wayland/host/internal/wayland_data_device_base.h
index 824bfc8..f5ff764 100644
--- a/ui/ozone/platform/wayland/host/internal/wayland_data_device_base.h
+++ b/ui/ozone/platform/wayland/host/internal/wayland_data_device_base.h
@@ -58,8 +58,7 @@
                                    struct wl_callback* cb,
                                    uint32_t time);
 
-  void DeferredReadCallbackInternal(struct wl_callback* cb,
-                                    uint32_t time);
+  void DeferredReadCallbackInternal(struct wl_callback* cb, uint32_t time);
 
   // Used to call out to WaylandConnection once clipboard data
   // has been successfully read.
diff --git a/ui/ozone/platform/wayland/host/wayland_clipboard.cc b/ui/ozone/platform/wayland/host/wayland_clipboard.cc
index 1b07e98..dd8c2326 100644
--- a/ui/ozone/platform/wayland/host/wayland_clipboard.cc
+++ b/ui/ozone/platform/wayland/host/wayland_clipboard.cc
@@ -108,13 +108,12 @@
   }
 }
 
-void WaylandClipboard::SetData(const std::string& contents,
+void WaylandClipboard::SetData(const std::vector<uint8_t>& contents,
                                const std::string& mime_type) {
   if (!data_map_)
     return;
 
-  (*data_map_)[mime_type] =
-      std::vector<uint8_t>(contents.begin(), contents.end());
+  (*data_map_)[mime_type] = contents;
 
   if (!read_clipboard_closure_.is_null()) {
     auto it = data_map_->find(mime_type);
diff --git a/ui/ozone/platform/wayland/host/wayland_clipboard.h b/ui/ozone/platform/wayland/host/wayland_clipboard.h
index e720bdd2..cd95b60 100644
--- a/ui/ozone/platform/wayland/host/wayland_clipboard.h
+++ b/ui/ozone/platform/wayland/host/wayland_clipboard.h
@@ -5,6 +5,9 @@
 #ifndef UI_OZONE_PLATFORM_WAYLAND_HOST_WAYLAND_CLIPBOARD_H_
 #define UI_OZONE_PLATFORM_WAYLAND_HOST_WAYLAND_CLIPBOARD_H_
 
+#include <string>
+#include <vector>
+
 #include "base/callback.h"
 #include "base/macros.h"
 #include "ui/ozone/platform/wayland/host/wayland_data_source.h"
@@ -49,7 +52,8 @@
       PlatformClipboard::SequenceNumberUpdateCb cb) override;
 
   void DataSourceCancelled(ClipboardBuffer buffer);
-  void SetData(const std::string& contents, const std::string& mime_type);
+  void SetData(const std::vector<uint8_t>& contents,
+               const std::string& mime_type);
   void UpdateSequenceNumber(ClipboardBuffer buffer);
 
  private:
diff --git a/ui/ozone/platform/wayland/host/wayland_connection.cc b/ui/ozone/platform/wayland/host/wayland_connection.cc
index 232f853..03d575a 100644
--- a/ui/ozone/platform/wayland/host/wayland_connection.cc
+++ b/ui/ozone/platform/wayland/host/wayland_connection.cc
@@ -169,7 +169,7 @@
 
 void WaylandConnection::RequestDragData(
     const std::string& mime_type,
-    base::OnceCallback<void(const std::string&)> callback) {
+    base::OnceCallback<void(const std::vector<uint8_t>&)> callback) {
   data_device_->RequestDragData(mime_type, std::move(callback));
 }
 
diff --git a/ui/ozone/platform/wayland/host/wayland_connection.h b/ui/ozone/platform/wayland/host/wayland_connection.h
index e26d191f..a241c70c 100644
--- a/ui/ozone/platform/wayland/host/wayland_connection.h
+++ b/ui/ozone/platform/wayland/host/wayland_connection.h
@@ -119,8 +119,9 @@
   // Requests the data to the platform when Chromium gets drag-and-drop started
   // by others. Once reading the data from platform is done, |callback| should
   // be called with the data.
-  void RequestDragData(const std::string& mime_type,
-                       base::OnceCallback<void(const std::string&)> callback);
+  void RequestDragData(
+      const std::string& mime_type,
+      base::OnceCallback<void(const std::vector<uint8_t>&)> callback);
 
   // Returns true when dragging is entered or started.
   bool IsDragInProgress();
diff --git a/ui/ozone/platform/wayland/host/wayland_data_device.cc b/ui/ozone/platform/wayland/host/wayland_data_device.cc
index 5c56235..20a347a 100644
--- a/ui/ozone/platform/wayland/host/wayland_data_device.cc
+++ b/ui/ozone/platform/wayland/host/wayland_data_device.cc
@@ -9,58 +9,182 @@
 
 #include "base/bind.h"
 #include "base/strings/string16.h"
+#include "base/strings/string_split.h"
 #include "base/strings/utf_string_conversions.h"
 #include "third_party/skia/include/core/SkBitmap.h"
 #include "third_party/skia/include/core/SkCanvas.h"
 #include "ui/base/clipboard/clipboard_constants.h"
 #include "ui/base/dragdrop/drag_drop_types.h"
+#include "ui/base/dragdrop/file_info.h"
 #include "ui/base/dragdrop/os_exchange_data.h"
 #include "ui/base/dragdrop/os_exchange_data_provider_aura.h"
 #include "ui/ozone/platform/wayland/common/wayland_util.h"
 #include "ui/ozone/platform/wayland/host/wayland_connection.h"
 #include "ui/ozone/platform/wayland/host/wayland_window.h"
+#include "url/gurl.h"
+#include "url/url_canon.h"
+#include "url/url_util.h"
 
 namespace ui {
 
 namespace {
 
-int GetOperation(uint32_t source_actions, uint32_t dnd_action) {
+constexpr OSExchangeData::FilenameToURLPolicy kFilenameToURLPolicy =
+    OSExchangeData::FilenameToURLPolicy::CONVERT_FILENAMES;
+
+// Converts raw data to either narrow or wide string.
+template <typename StringType>
+StringType BytesTo(const PlatformClipboard::Data& bytes) {
+  if (bytes.size() % sizeof(typename StringType::value_type) != 0U) {
+    // This is suspicious.
+    LOG(WARNING)
+        << "Data is possibly truncated, or a wrong conversion is requested.";
+  }
+
+  StringType result;
+  result.assign(reinterpret_cast<typename StringType::const_pointer>(&bytes[0]),
+                bytes.size() / sizeof(typename StringType::value_type));
+  return result;
+}
+
+// Returns actions possible with the given source and drag'n'drop actions.
+// Also converts enums: input params are wl_data_device_manager_dnd_action but
+// the result is ui::DragDropTypes.
+int GetPossibleActions(uint32_t source_actions, uint32_t dnd_action) {
+  // If drag'n'drop action is set, use it but check for ASK action (see below).
   uint32_t action = dnd_action != WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE
                         ? dnd_action
                         : source_actions;
 
+  // We accept any action except ASK (see below).
   int operation = DragDropTypes::DRAG_NONE;
   if (action & WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY)
     operation |= DragDropTypes::DRAG_COPY;
   if (action & WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE)
     operation |= DragDropTypes::DRAG_MOVE;
-  // TODO(jkim): Implement branch for WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK
-  if (action & WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK)
-    operation |= DragDropTypes::DRAG_COPY;
+  if (action & WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK) {
+    // This is very rare and non-standard.  Chromium doesn't set this when
+    // anything is dragged from it, neither it provides any UI for asking
+    // the user about the desired drag'n'drop action when data is dragged
+    // from an external source.
+    // We are safe with not adding anything here.  However, keep NOTIMPLEMENTED
+    // for an (unlikely) event of this being hit in distant future.
+    NOTIMPLEMENTED_LOG_ONCE();
+  }
   return operation;
 }
 
-void AddStringToOSExchangeData(const std::string& data,
-                               OSExchangeData* os_exchange_data) {
+void AddString(const PlatformClipboard::Data& data,
+               OSExchangeData* os_exchange_data) {
   DCHECK(os_exchange_data);
+
   if (data.empty())
     return;
 
-  base::string16 string16 = base::UTF8ToUTF16(data);
-  os_exchange_data->SetString(string16);
+  os_exchange_data->SetString(base::UTF8ToUTF16(BytesTo<std::string>(data)));
 }
 
-void AddToOSExchangeData(const std::string& data,
+void AddHtml(const PlatformClipboard::Data& data,
+             OSExchangeData* os_exchange_data) {
+  DCHECK(os_exchange_data);
+
+  if (data.empty())
+    return;
+
+  os_exchange_data->SetHtml(base::UTF8ToUTF16(BytesTo<std::string>(data)),
+                            GURL());
+}
+
+// Parses |data| as if it had text/uri-list format.  Its brief spec is:
+// 1.  Any lines beginning with the '#' character are comment lines.
+// 2.  Non-comment lines shall be URIs (URNs or URLs).
+// 3.  Lines are terminated with a CRLF pair.
+// 4.  URL encoding is used.
+void AddFiles(const PlatformClipboard::Data& data,
+              OSExchangeData* os_exchange_data) {
+  DCHECK(os_exchange_data);
+
+  std::string data_as_string = BytesTo<std::string>(data);
+
+  const auto lines = base::SplitString(
+      data_as_string, "\r\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
+  std::vector<FileInfo> filenames;
+  for (const auto& line : lines) {
+    if (line.empty() || line[0] == '#')
+      continue;
+    GURL url(line);
+    if (!url.is_valid() || !url.SchemeIsFile()) {
+      LOG(WARNING) << "Invalid URI found: " << line;
+      continue;
+    }
+
+    std::string url_path = url.path();
+    url::RawCanonOutputT<base::char16> unescaped;
+    url::DecodeURLEscapeSequences(url_path.data(), url_path.size(),
+                                  url::DecodeURLMode::kUTF8OrIsomorphic,
+                                  &unescaped);
+
+    std::string path8;
+    base::UTF16ToUTF8(unescaped.data(), unescaped.length(), &path8);
+    const base::FilePath path(path8);
+    filenames.push_back({path, path.BaseName()});
+  }
+  if (filenames.empty())
+    return;
+
+  os_exchange_data->SetFilenames(filenames);
+}
+
+// Parses |data| as if it had text/x-moz-url format, which is basically
+// two lines separated with newline, where the first line is the URL and
+// the second one is page title.  The unpleasant feature of text/x-moz-url is
+// that the URL has UTF-16 encoding.
+void AddUrl(const PlatformClipboard::Data& data,
+            OSExchangeData* os_exchange_data) {
+  DCHECK(os_exchange_data);
+
+  if (data.empty())
+    return;
+
+  base::string16 data_as_string16 = BytesTo<base::string16>(data);
+
+  const auto lines =
+      base::SplitString(data_as_string16, base::ASCIIToUTF16("\r\n"),
+                        base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
+  if (lines.size() != 2U) {
+    LOG(WARNING) << "Invalid data passed as text/x-moz-url; it must contain "
+                 << "exactly 2 lines but has " << lines.size() << " instead.";
+    return;
+  }
+  GURL url(lines[0]);
+  if (!url.is_valid()) {
+    LOG(WARNING) << "Invalid data passed as text/x-moz-url; the first line "
+                 << "must contain a valid URL but it doesn't.";
+    return;
+  }
+
+  os_exchange_data->SetURL(url, lines[1]);
+}
+
+void AddToOSExchangeData(const PlatformClipboard::Data& data,
                          const std::string& mime_type,
                          OSExchangeData* os_exchange_data) {
   DCHECK(os_exchange_data);
   if ((mime_type == kMimeTypeText || mime_type == kMimeTypeTextUtf8)) {
     DCHECK(!os_exchange_data->HasString());
-    AddStringToOSExchangeData(data, os_exchange_data);
-    return;
+    AddString(data, os_exchange_data);
+  } else if (mime_type == kMimeTypeHTML) {
+    DCHECK(!os_exchange_data->HasHtml());
+    AddHtml(data, os_exchange_data);
+  } else if (mime_type == kMimeTypeMozillaURL) {
+    DCHECK(!os_exchange_data->HasURL(kFilenameToURLPolicy));
+    AddUrl(data, os_exchange_data);
+  } else if (mime_type == kMimeTypeURIList) {
+    DCHECK(!os_exchange_data->HasFile());
+    AddFiles(data, os_exchange_data);
+  } else {
+    LOG(WARNING) << "Unhandled MIME type: " << mime_type;
   }
-  // TODO(crbug.com/875164): Fix mime types support.
-  NOTREACHED();
 }
 
 }  // namespace
@@ -80,7 +204,7 @@
 
 void WaylandDataDevice::RequestDragData(
     const std::string& mime_type,
-    base::OnceCallback<void(const std::string&)> callback) {
+    base::OnceCallback<void(const PlatformClipboard::Data&)> callback) {
   base::ScopedFD fd = drag_offer_->Receive(mime_type);
   if (!fd.is_valid()) {
     LOG(ERROR) << "Failed to open file descriptor.";
@@ -100,25 +224,24 @@
   DCHECK(buffer);
   DCHECK(source_data_);
 
-  if (mime_type != kMimeTypeText && mime_type != kMimeTypeTextUtf8)
-    return;
-
-  const OSExchangeData::FilenameToURLPolicy policy =
-      OSExchangeData::FilenameToURLPolicy::DO_NOT_CONVERT_FILENAMES;
-  // TODO(jkim): Handle other data format as well.
-  if (source_data_->HasURL(policy)) {
+  if (mime_type == kMimeTypeMozillaURL &&
+      source_data_->HasURL(kFilenameToURLPolicy)) {
     GURL url;
     base::string16 title;
-    source_data_->GetURLAndTitle(policy, &url, &title);
+    source_data_->GetURLAndTitle(kFilenameToURLPolicy, &url, &title);
     buffer->append(url.spec());
-    return;
-  }
-
-  if (source_data_->HasString()) {
+  } else if (mime_type == kMimeTypeHTML && source_data_->HasHtml()) {
+    base::string16 data;
+    GURL base_url;
+    source_data_->GetHtml(&data, &base_url);
+    buffer->append(base::UTF16ToUTF8(data));
+  } else if (source_data_->HasString()) {
     base::string16 data;
     source_data_->GetString(&data);
     buffer->append(base::UTF16ToUTF8(data));
-    return;
+  } else {
+    LOG(WARNING) << "Cannot deliver data of type " << mime_type
+                 << " and no text representation is available.";
   }
 }
 
@@ -147,8 +270,8 @@
 
 void WaylandDataDevice::ReadDragDataFromFD(
     base::ScopedFD fd,
-    base::OnceCallback<void(const std::string&)> callback) {
-  std::string contents;
+    base::OnceCallback<void(const PlatformClipboard::Data&)> callback) {
+  PlatformClipboard::Data contents;
   wl::ReadDataFromFD(std::move(fd), &contents);
   std::move(callback).Run(contents);
 }
@@ -193,7 +316,7 @@
   self->drag_offer_ = std::move(self->new_offer_);
   self->window_ = window;
 
-  // TODO(crbug.com/875164): Set mime type the client can accept. Now it sets
+  // TODO(crbug.com/1004715): Set mime type the client can accept.  Now it sets
   // all mime types offered because current implementation doesn't decide
   // action based on mime type.
   self->unprocessed_mime_types_.clear();
@@ -202,17 +325,18 @@
     self->drag_offer_->Accept(serial, mime);
   }
 
-  int operation = GetOperation(self->drag_offer_->source_actions(),
-                               self->drag_offer_->dnd_action());
   gfx::PointF point(wl_fixed_to_double(x), wl_fixed_to_double(y));
 
   // If |source_data_| is set, it means that dragging is started from the
   // same window and it's not needed to read data through Wayland.
-  std::unique_ptr<OSExchangeData> pdata;
+  std::unique_ptr<OSExchangeData> dragged_data;
   if (!self->IsDraggingExternalData())
-    pdata = std::make_unique<OSExchangeData>(
+    dragged_data = std::make_unique<OSExchangeData>(
         self->source_data_->provider().Clone());
-  self->window_->OnDragEnter(point, std::move(pdata), operation);
+  self->window_->OnDragEnter(
+      point, std::move(dragged_data),
+      GetPossibleActions(self->drag_offer_->source_actions(),
+                         self->drag_offer_->dnd_action()));
 }
 
 void WaylandDataDevice::OnMotion(void* data,
@@ -226,10 +350,11 @@
     return;
   }
 
-  int operation = GetOperation(self->drag_offer_->source_actions(),
-                               self->drag_offer_->dnd_action());
   gfx::PointF point(wl_fixed_to_double(x), wl_fixed_to_double(y));
-  int client_operation = self->window_->OnDragMotion(point, time, operation);
+  int client_operation = self->window_->OnDragMotion(
+      point, time,
+      GetPossibleActions(self->drag_offer_->source_actions(),
+                         self->drag_offer_->dnd_action()));
   self->SetOperation(client_operation);
 }
 
@@ -239,25 +364,21 @@
     LOG(ERROR) << "Failed to get window.";
     return;
   }
-  if (!self->IsDraggingExternalData()) {
-    // When the drag session started from a chromium window, source_data_
-    // already holds the data and already forwarded it to delegate through
-    // OnDragEnter, so at this point (onDragDrop) the delegate expects a
-    // nullptr and the data will be read internally with no need to read it
-    // through Wayland pipe and so on.
-    self->HandleReceivedData(nullptr);
-  } else {
-    // Creates buffer to receive data from Wayland.
+  if (self->IsDraggingExternalData()) {
+    // We are about to accept data dragged from another application.
+    // Reading all the data may take some time so we set
+    // |is_handling_dropped_data_| that will postpone handling of OnLeave
+    // until reading is completed.
+    self->is_handling_dropped_data_ = true;
     self->received_data_ = std::make_unique<OSExchangeData>(
         std::make_unique<OSExchangeDataProviderAura>());
-    // In order to guarantee all data received, it sets
-    // |is_handling_dropped_data_| and defers OnLeave event handling if it gets
-    // OnLeave event before completing to read the data.
-    self->is_handling_dropped_data_ = true;
-    // Starts to read the data on Drop event because read(..) API blocks
-    // awaiting data to be sent to pipe if we try to read the data on OnEnter.
-    // 'Weston' also reads data on OnDrop event and other examples do as well.
     self->HandleUnprocessedMimeTypes();
+  } else {
+    // If the drag session had been started internally by chromium,
+    // |source_data_| already holds the data, and it is already forwarded to the
+    // delegate through OnDragEnter, so here we short-cut the data transfer by
+    // sending nullptr.
+    self->HandleReceivedData(nullptr);
   }
 }
 
@@ -345,7 +466,8 @@
   }
 }
 
-void WaylandDataDevice::OnDragDataReceived(const std::string& contents) {
+void WaylandDataDevice::OnDragDataReceived(
+    const PlatformClipboard::Data& contents) {
   if (!contents.empty()) {
     AddToOSExchangeData(contents, unprocessed_mime_types_.front(),
                         received_data_.get());
@@ -359,7 +481,6 @@
 
 void WaylandDataDevice::HandleReceivedData(
     std::unique_ptr<ui::OSExchangeData> received_data) {
-  // TODO(crbug.com/875164): Fix mime types support.
   unprocessed_mime_types_.clear();
 
   window_->OnDragDrop(std::move(received_data));
@@ -370,12 +491,21 @@
 
 std::string WaylandDataDevice::SelectNextMimeType() {
   while (!unprocessed_mime_types_.empty()) {
-    std::string& mime_type = unprocessed_mime_types_.front();
+    const std::string& mime_type = unprocessed_mime_types_.front();
     if ((mime_type == kMimeTypeText || mime_type == kMimeTypeTextUtf8) &&
         !received_data_->HasString()) {
       return mime_type;
     }
-    // TODO(crbug.com/875164): Fix mime types support.
+    if (mime_type == kMimeTypeURIList && !received_data_->HasFile()) {
+      return mime_type;
+    }
+    if (mime_type == kMimeTypeMozillaURL &&
+        !received_data_->HasURL(kFilenameToURLPolicy)) {
+      return mime_type;
+    }
+    if (mime_type == kMimeTypeHTML && !received_data_->HasHtml()) {
+      return mime_type;
+    }
     unprocessed_mime_types_.pop_front();
   }
   return {};
diff --git a/ui/ozone/platform/wayland/host/wayland_data_device.h b/ui/ozone/platform/wayland/host/wayland_data_device.h
index e22edd8..0bd74651 100644
--- a/ui/ozone/platform/wayland/host/wayland_data_device.h
+++ b/ui/ozone/platform/wayland/host/wayland_data_device.h
@@ -20,6 +20,7 @@
 #include "ui/ozone/platform/wayland/host/internal/wayland_data_device_base.h"
 #include "ui/ozone/platform/wayland/host/wayland_data_offer.h"
 #include "ui/ozone/platform/wayland/host/wayland_shm_buffer.h"
+#include "ui/ozone/public/platform_clipboard.h"
 
 class SkBitmap;
 
@@ -40,8 +41,9 @@
   // Requests the data to the platform when Chromium gets drag-and-drop started
   // by others. Once reading the data from platform is done, |callback| should
   // be called with the data.
-  void RequestDragData(const std::string& mime_type,
-                       base::OnceCallback<void(const std::string&)> callback);
+  void RequestDragData(
+      const std::string& mime_type,
+      base::OnceCallback<void(const PlatformClipboard::Data&)> callback);
   // Delivers the data owned by Chromium which initiates drag-and-drop. |buffer|
   // is an output parameter and it should be filled with the data corresponding
   // to mime_type.
@@ -59,7 +61,7 @@
  private:
   void ReadDragDataFromFD(
       base::ScopedFD fd,
-      base::OnceCallback<void(const std::string&)> callback);
+      base::OnceCallback<void(const PlatformClipboard::Data&)> callback);
 
   // If source_data_ is not set, data is being dragged from an external
   // application (non-chromium).
@@ -105,7 +107,7 @@
   const SkBitmap* PrepareDragIcon(const OSExchangeData& data);
   void DrawDragIcon(const SkBitmap* bitmap);
 
-  void OnDragDataReceived(const std::string& contents);
+  void OnDragDataReceived(const PlatformClipboard::Data& contents);
 
   // HandleUnprocessedMimeTypes asynchronously request and read data for every
   // negotiated mime type, one after another (OnDragDataReceived calls back
diff --git a/ui/ozone/platform/wayland/host/wayland_data_device_unittest.cc b/ui/ozone/platform/wayland/host/wayland_data_device_unittest.cc
index 8ab31d7..4726a86 100644
--- a/ui/ozone/platform/wayland/host/wayland_data_device_unittest.cc
+++ b/ui/ozone/platform/wayland/host/wayland_data_device_unittest.cc
@@ -5,10 +5,17 @@
 #include <wayland-server.h>
 
 #include <memory>
+#include <string>
+#include <vector>
 
 #include "base/bind.h"
+#include "base/containers/flat_set.h"
+#include "base/strings/string16.h"
+#include "base/strings/utf_string_conversions.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/clipboard/clipboard_constants.h"
 #include "ui/base/dragdrop/drag_drop_types.h"
+#include "ui/base/dragdrop/file_info.h"
 #include "ui/base/dragdrop/os_exchange_data.h"
 #include "ui/events/base_event_utils.h"
 #include "ui/ozone/platform/wayland/test/constants.h"
@@ -20,9 +27,65 @@
 #include "ui/ozone/platform/wayland/test/test_wayland_server_thread.h"
 #include "ui/ozone/platform/wayland/test/wayland_test.h"
 #include "ui/ozone/public/platform_clipboard.h"
+#include "ui/platform_window/platform_window_handler/wm_drop_handler.h"
+#include "url/gurl.h"
+
+using testing::_;
+using testing::Mock;
 
 namespace ui {
 
+namespace {
+
+constexpr OSExchangeData::FilenameToURLPolicy kFilenameToURLPolicy =
+    OSExchangeData::FilenameToURLPolicy::CONVERT_FILENAMES;
+
+template <typename StringType>
+ui::PlatformClipboard::Data ToClipboardData(const StringType& data_string) {
+  ui::PlatformClipboard::Data result;
+  auto* begin =
+      reinterpret_cast<typename ui::PlatformClipboard::Data::const_pointer>(
+          data_string.data());
+  result.assign(begin, begin + (data_string.size() *
+                                sizeof(typename StringType::value_type)));
+  return result;
+}
+
+}  // namespace
+
+class MockDropHandler : public WmDropHandler {
+ public:
+  MockDropHandler() = default;
+  ~MockDropHandler() override {}
+
+  MOCK_METHOD3(OnDragEnter,
+               void(const gfx::PointF& point,
+                    std::unique_ptr<OSExchangeData> data,
+                    int operation));
+  MOCK_METHOD2(OnDragMotion, int(const gfx::PointF& point, int operation));
+  MOCK_METHOD0(MockOnDragDrop, void());
+  MOCK_METHOD0(OnDragLeave, void());
+
+  void SetOnDropClosure(base::RepeatingClosure closure) {
+    on_drop_closure_ = closure;
+  }
+
+  ui::OSExchangeData* dropped_data() { return dropped_data_.get(); }
+
+ protected:
+  void OnDragDrop(std::unique_ptr<ui::OSExchangeData> data) override {
+    dropped_data_ = std::move(data);
+    MockOnDragDrop();
+    on_drop_closure_.Run();
+    on_drop_closure_.Reset();
+  }
+
+ private:
+  base::RepeatingClosure on_drop_closure_;
+
+  std::unique_ptr<ui::OSExchangeData> dropped_data_;
+};
+
 // This class mocks how a real clipboard/ozone client would
 // hook to PlatformClipboard, with one difference: real clients
 // have no access to the WaylandConnection instance like this
@@ -42,15 +105,10 @@
   ~MockClipboardClient() = default;
 
   // Fill the clipboard backing store with sample data.
-  void SetData(const std::string& utf8_text,
+  void SetData(const PlatformClipboard::Data& data,
                const std::string& mime_type,
                PlatformClipboard::OfferDataClosure callback) {
-    // This mimics how Mus' ClipboardImpl writes data to the DataMap.
-    std::vector<char> object_map(utf8_text.begin(), utf8_text.end());
-    char* object_data = &object_map.front();
-    data_types_[mime_type] =
-        std::vector<uint8_t>(object_data, object_data + object_map.size());
-
+    data_types_[mime_type] = data;
     delegate_->OfferClipboardData(ClipboardBuffer::kCopyPaste, data_types_,
                                   std::move(callback));
   }
@@ -86,11 +144,15 @@
 
     clipboard_client_ =
         std::make_unique<MockClipboardClient>(connection_.get());
+
+    drop_handler_ = std::make_unique<MockDropHandler>();
+    SetWmDropHandler(window_.get(), drop_handler_.get());
   }
 
  protected:
   wl::TestDataDeviceManager* data_device_manager_;
   std::unique_ptr<MockClipboardClient> clipboard_client_;
+  std::unique_ptr<MockDropHandler> drop_handler_;
 
  private:
   DISALLOW_COPY_AND_ASSIGN(WaylandDataDeviceManagerTest);
@@ -98,14 +160,17 @@
 
 TEST_P(WaylandDataDeviceManagerTest, WriteToClipboard) {
   // The client writes data to the clipboard ...
-  clipboard_client_->SetData(wl::kSampleClipboardText, wl::kTextMimeTypeUtf8,
+  PlatformClipboard::Data data;
+  data.assign(wl::kSampleClipboardText,
+              wl::kSampleClipboardText + strlen(wl::kSampleClipboardText));
+  clipboard_client_->SetData(data, wl::kTextMimeTypeUtf8,
                              base::BindOnce([]() {}));
   Sync();
 
   // ... and the server reads it.
   base::RunLoop run_loop;
   auto callback = base::BindOnce(
-      [](base::RunLoop* loop, std::vector<uint8_t>&& data) {
+      [](base::RunLoop* loop, PlatformClipboard::Data&& data) {
         std::string string_data(data.begin(), data.end());
         EXPECT_EQ(wl::kSampleClipboardText, string_data);
         loop->Quit();
@@ -120,7 +185,8 @@
   // TODO(nickdiego): implement this in terms of an actual wl_surface that
   // gets focused and compositor sends data_device data to it.
   auto* data_offer = data_device_manager_->data_device()->OnDataOffer();
-  data_offer->OnOffer(wl::kTextMimeTypeUtf8);
+  data_offer->OnOffer(wl::kTextMimeTypeUtf8,
+                      ToClipboardData(std::string(wl::kSampleClipboardText)));
   data_device_manager_->data_device()->OnSelection(data_offer);
   Sync();
 
@@ -128,7 +194,7 @@
   // The Server writes in some sample data, and we check it matches
   // expectation.
   auto callback =
-      base::BindOnce([](const base::Optional<std::vector<uint8_t>>& data) {
+      base::BindOnce([](const base::Optional<PlatformClipboard::Data>& data) {
         std::string string_data = std::string(data->begin(), data->end());
         EXPECT_EQ(wl::kSampleClipboardText, string_data);
       });
@@ -141,7 +207,7 @@
   // from the server, the response callback should be gracefully called with
   // an empty string.
   auto callback =
-      base::BindOnce([](const base::Optional<std::vector<uint8_t>>& data) {
+      base::BindOnce([](const base::Optional<PlatformClipboard::Data>& data) {
         std::string string_data = std::string(data->begin(), data->end());
         EXPECT_EQ("", string_data);
       });
@@ -150,8 +216,10 @@
 
 TEST_P(WaylandDataDeviceManagerTest, IsSelectionOwner) {
   auto callback = base::BindOnce([]() {});
-  clipboard_client_->SetData(wl::kSampleClipboardText, wl::kTextMimeTypeUtf8,
-                             std::move(callback));
+  PlatformClipboard::Data data;
+  data.assign(wl::kSampleClipboardText,
+              wl::kSampleClipboardText + strlen(wl::kSampleClipboardText));
+  clipboard_client_->SetData(data, wl::kTextMimeTypeUtf8, std::move(callback));
   Sync();
   ASSERT_TRUE(clipboard_client_->IsSelectionOwner());
 
@@ -181,7 +249,7 @@
   // The server reads the data and the callback gets it.
   base::RunLoop run_loop;
   auto callback = base::BindOnce(
-      [](base::RunLoop* loop, std::vector<uint8_t>&& data) {
+      [](base::RunLoop* loop, PlatformClipboard::Data&& data) {
         std::string result(data.begin(), data.end());
         EXPECT_EQ(wl::kSampleTextForDragAndDrop, result);
         loop->Quit();
@@ -212,13 +280,13 @@
   // when trying to read it.
   base::RunLoop run_loop;
   auto callback = base::BindOnce(
-      [](base::RunLoop* loop, std::vector<uint8_t>&& data) {
+      [](base::RunLoop* loop, PlatformClipboard::Data&& data) {
         std::string result(data.begin(), data.end());
         EXPECT_EQ("", result);
         loop->Quit();
       },
       &run_loop);
-  data_device_manager_->data_source()->ReadData(wl::kTextMimeTypeText,
+  data_device_manager_->data_source()->ReadData(ui::kMimeTypeText,
                                                 std::move(callback));
   run_loop.Run();
   window_->set_pointer_focus(restored_focus);
@@ -226,7 +294,9 @@
 
 TEST_P(WaylandDataDeviceManagerTest, ReceiveDrag) {
   auto* data_offer = data_device_manager_->data_device()->OnDataOffer();
-  data_offer->OnOffer(wl::kTextMimeTypeText);
+  data_offer->OnOffer(
+      ui::kMimeTypeText,
+      ToClipboardData(std::string(wl::kSampleTextForDragAndDrop)));
 
   gfx::Point entered_point(10, 10);
   // The server sends an enter event.
@@ -245,17 +315,172 @@
 
   Sync();
 
-  auto callback = base::BindOnce([](const std::string& contents) {
-    EXPECT_EQ(wl::kSampleTextForDragAndDrop, contents);
+  auto callback = base::BindOnce([](const PlatformClipboard::Data& contents) {
+    std::string result;
+    result.assign(reinterpret_cast<std::string::const_pointer>(&contents[0]),
+                  contents.size());
+    EXPECT_EQ(wl::kSampleTextForDragAndDrop, result);
   });
 
   // The client requests the data and gets callback with it.
-  connection_->RequestDragData(wl::kTextMimeTypeText, std::move(callback));
+  connection_->RequestDragData(ui::kMimeTypeText, std::move(callback));
   Sync();
 
   data_device_manager_->data_device()->OnLeave();
 }
 
+TEST_P(WaylandDataDeviceManagerTest, DropSeveralMimeTypes) {
+  auto* data_offer = data_device_manager_->data_device()->OnDataOffer();
+  data_offer->OnOffer(
+      ui::kMimeTypeText,
+      ToClipboardData(std::string(wl::kSampleTextForDragAndDrop)));
+  data_offer->OnOffer(
+      ui::kMimeTypeMozillaURL,
+      ToClipboardData(base::UTF8ToUTF16("https://sample.com/\r\n"
+                                        "Sample")));
+  data_offer->OnOffer(
+      ui::kMimeTypeURIList,
+      ToClipboardData(std::string("file:///home/user/file\r\n")));
+
+  EXPECT_CALL(*drop_handler_, OnDragEnter(_, _, _)).Times(1);
+  gfx::Point entered_point(10, 10);
+  data_device_manager_->data_device()->OnEnter(
+      1002, surface_->resource(), wl_fixed_from_int(entered_point.x()),
+      wl_fixed_from_int(entered_point.y()), data_offer);
+  Sync();
+  Mock::VerifyAndClearExpectations(drop_handler_.get());
+
+  EXPECT_CALL(*drop_handler_, MockOnDragDrop()).Times(1);
+  base::RunLoop loop;
+  drop_handler_->SetOnDropClosure(loop.QuitClosure());
+  data_device_manager_->data_device()->OnDrop();
+
+  // Here we are expecting three data items, so there will be three roundtrips
+  // to the Wayland and back.  Hence Sync() three times.
+  Sync();
+  Sync();
+  Sync();
+  loop.Run();
+  Mock::VerifyAndClearExpectations(drop_handler_.get());
+
+  EXPECT_TRUE(drop_handler_->dropped_data()->HasString());
+  EXPECT_TRUE(drop_handler_->dropped_data()->HasFile());
+  EXPECT_TRUE(drop_handler_->dropped_data()->HasURL(kFilenameToURLPolicy));
+
+  data_device_manager_->data_device()->OnLeave();
+}
+
+// Tests URI validation for text/uri-list MIME type.  Log warnings rendered in
+// the console when this test is running are the expected and valid side effect.
+TEST_P(WaylandDataDeviceManagerTest, ValidateDroppedUriList) {
+  const struct {
+    std::string content;
+    base::flat_set<std::string> expected_uris;
+  } kCases[] = {{{}, {}},
+                {"file:///home/user/file\r\n", {"/home/user/file"}},
+                {"# Comment\r\n"
+                 "file:///home/user/file\r\n"
+                 "file:///home/guest/file\r\n"
+                 "not a filename at all\r\n"
+                 "https://valid.url/but/scheme/is/not/file/so/invalid\r\n",
+                 {"/home/user/file", "/home/guest/file"}}};
+
+  for (const auto& kCase : kCases) {
+    auto* data_offer = data_device_manager_->data_device()->OnDataOffer();
+    data_offer->OnOffer(ui::kMimeTypeURIList, ToClipboardData(kCase.content));
+
+    EXPECT_CALL(*drop_handler_, OnDragEnter(_, _, _)).Times(1);
+    gfx::Point entered_point(10, 10);
+    data_device_manager_->data_device()->OnEnter(
+        1002, surface_->resource(), wl_fixed_from_int(entered_point.x()),
+        wl_fixed_from_int(entered_point.y()), data_offer);
+    Sync();
+    Mock::VerifyAndClearExpectations(drop_handler_.get());
+
+    EXPECT_CALL(*drop_handler_, MockOnDragDrop()).Times(1);
+    base::RunLoop loop;
+    drop_handler_->SetOnDropClosure(loop.QuitClosure());
+    data_device_manager_->data_device()->OnDrop();
+
+    Sync();
+    loop.Run();
+    Mock::VerifyAndClearExpectations(drop_handler_.get());
+
+    if (kCase.expected_uris.empty()) {
+      EXPECT_FALSE(drop_handler_->dropped_data()->HasFile());
+    } else {
+      EXPECT_TRUE(drop_handler_->dropped_data()->HasFile());
+      std::vector<FileInfo> filenames;
+      EXPECT_TRUE(drop_handler_->dropped_data()->GetFilenames(&filenames));
+      EXPECT_EQ(filenames.size(), kCase.expected_uris.size());
+      for (const auto& filename : filenames)
+        EXPECT_EQ(kCase.expected_uris.count(filename.path.AsUTF8Unsafe()), 1U);
+    }
+
+    EXPECT_CALL(*drop_handler_, OnDragLeave()).Times(1);
+    data_device_manager_->data_device()->OnLeave();
+    Sync();
+    Mock::VerifyAndClearExpectations(drop_handler_.get());
+  }
+}
+
+// Tests URI validation for text/x-moz-url MIME type.  Log warnings rendered in
+// the console when this test is running are the expected and valid side effect.
+TEST_P(WaylandDataDeviceManagerTest, ValidateDroppedXMozUrl) {
+  const struct {
+    std::string content;
+    std::string expected_url;
+    std::string expected_title;
+  } kCases[] = {
+      {{}, {}, {}},
+      {"http://sample.com/\r\nSample", "http://sample.com/", "Sample"},
+      {"http://title.must.be.set/", {}, {}},
+      {"url.must.be.valid/and/have.scheme\r\nInvalid URL", {}, {}},
+      {"file:///files/are/ok\r\nThe policy allows that", "file:///files/are/ok",
+       "The policy allows that"}};
+
+  for (const auto& kCase : kCases) {
+    auto* data_offer = data_device_manager_->data_device()->OnDataOffer();
+    data_offer->OnOffer(ui::kMimeTypeMozillaURL,
+                        ToClipboardData(base::UTF8ToUTF16(kCase.content)));
+
+    EXPECT_CALL(*drop_handler_, OnDragEnter(_, _, _)).Times(1);
+    gfx::Point entered_point(10, 10);
+    data_device_manager_->data_device()->OnEnter(
+        1002, surface_->resource(), wl_fixed_from_int(entered_point.x()),
+        wl_fixed_from_int(entered_point.y()), data_offer);
+    Sync();
+    Mock::VerifyAndClearExpectations(drop_handler_.get());
+
+    EXPECT_CALL(*drop_handler_, MockOnDragDrop()).Times(1);
+    base::RunLoop loop;
+    drop_handler_->SetOnDropClosure(loop.QuitClosure());
+    data_device_manager_->data_device()->OnDrop();
+
+    Sync();
+    loop.Run();
+    Mock::VerifyAndClearExpectations(drop_handler_.get());
+
+    const auto* const dropped_data = drop_handler_->dropped_data();
+    if (kCase.expected_url.empty()) {
+      EXPECT_FALSE(dropped_data->HasURL(kFilenameToURLPolicy));
+    } else {
+      EXPECT_TRUE(dropped_data->HasURL(kFilenameToURLPolicy));
+      GURL url;
+      base::string16 title;
+      EXPECT_TRUE(
+          dropped_data->GetURLAndTitle(kFilenameToURLPolicy, &url, &title));
+      EXPECT_EQ(url.spec(), kCase.expected_url);
+      EXPECT_EQ(title, base::UTF8ToUTF16(kCase.expected_title));
+    }
+
+    EXPECT_CALL(*drop_handler_, OnDragLeave()).Times(1);
+    data_device_manager_->data_device()->OnLeave();
+    Sync();
+    Mock::VerifyAndClearExpectations(drop_handler_.get());
+  }
+}
+
 INSTANTIATE_TEST_SUITE_P(XdgVersionV5Test,
                          WaylandDataDeviceManagerTest,
                          ::testing::Values(kXdgShellV5));
diff --git a/ui/ozone/platform/wayland/host/wayland_data_source.cc b/ui/ozone/platform/wayland/host/wayland_data_source.cc
index 4dcbeb9..e35746e 100644
--- a/ui/ozone/platform/wayland/host/wayland_data_source.cc
+++ b/ui/ozone/platform/wayland/host/wayland_data_source.cc
@@ -7,6 +7,7 @@
 #include "base/files/file_util.h"
 #include "ui/base/clipboard/clipboard_constants.h"
 #include "ui/base/dragdrop/drag_drop_types.h"
+#include "ui/base/dragdrop/os_exchange_data.h"
 #include "ui/ozone/platform/wayland/host/wayland_connection.h"
 #include "ui/ozone/platform/wayland/host/wayland_window.h"
 
@@ -38,10 +39,24 @@
 }
 
 void WaylandDataSource::Offer(const ui::OSExchangeData& data) {
-  // TODO(jkim): Handle mime types based on data.
+  // Drag'n'drop manuals usually suggest putting data in order so the more
+  // specific a MIME type is, the earlier it occurs in the list.  Wayland specs
+  // don't say anything like that, but here we follow that common practice:
+  // begin with URIs and end with plain text.  Just in case.
   std::vector<std::string> mime_types;
-  mime_types.push_back(kMimeTypeText);
-  mime_types.push_back(kMimeTypeTextUtf8);
+  if (data.HasFile()) {
+    mime_types.push_back(kMimeTypeURIList);
+  }
+  if (data.HasURL(ui::OSExchangeData::FilenameToURLPolicy::CONVERT_FILENAMES)) {
+    mime_types.push_back(kMimeTypeMozillaURL);
+  }
+  if (data.HasHtml()) {
+    mime_types.push_back(kMimeTypeHTML);
+  }
+  if (data.HasString()) {
+    mime_types.push_back(kMimeTypeTextUtf8);
+    mime_types.push_back(kMimeTypeText);
+  }
 
   source_window_ =
       connection_->wayland_window_manager()->GetCurrentFocusedWindow();
diff --git a/ui/ozone/platform/wayland/test/test_data_offer.cc b/ui/ozone/platform/wayland/test/test_data_offer.cc
index 64e8b4f..456945f 100644
--- a/ui/ozone/platform/wayland/test/test_data_offer.cc
+++ b/ui/ozone/platform/wayland/test/test_data_offer.cc
@@ -19,9 +19,12 @@
 
 namespace {
 
-void WriteDataOnWorkerThread(base::ScopedFD fd, const std::string& utf8_text) {
-  if (!base::WriteFileDescriptor(fd.get(), utf8_text.data(), utf8_text.size()))
+void WriteDataOnWorkerThread(base::ScopedFD fd,
+                             const ui::PlatformClipboard::Data& data) {
+  if (!base::WriteFileDescriptor(
+          fd.get(), reinterpret_cast<const char*>(data.data()), data.size())) {
     LOG(ERROR) << "Failed to write selection data to clipboard.";
+  }
 }
 
 void DataOfferAccept(wl_client* client,
@@ -70,17 +73,15 @@
 
 void TestDataOffer::Receive(const std::string& mime_type, base::ScopedFD fd) {
   DCHECK(fd.is_valid());
-  std::string text_data;
-  if (mime_type == kTextMimeTypeUtf8)
-    text_data = kSampleClipboardText;
-  else if (mime_type == kTextMimeTypeText)
-    text_data = kSampleTextForDragAndDrop;
 
-  task_runner_->PostTask(FROM_HERE, base::BindOnce(&WriteDataOnWorkerThread,
-                                                   std::move(fd), text_data));
+  task_runner_->PostTask(FROM_HERE,
+                         base::BindOnce(&WriteDataOnWorkerThread, std::move(fd),
+                                        data_to_offer_[mime_type]));
 }
 
-void TestDataOffer::OnOffer(const std::string& mime_type) {
+void TestDataOffer::OnOffer(const std::string& mime_type,
+                            const ui::PlatformClipboard::Data& data) {
+  data_to_offer_[mime_type] = data;
   wl_data_offer_send_offer(resource(), mime_type.c_str());
 }
 
diff --git a/ui/ozone/platform/wayland/test/test_data_offer.h b/ui/ozone/platform/wayland/test/test_data_offer.h
index 7595e4e..4ca6c58 100644
--- a/ui/ozone/platform/wayland/test/test_data_offer.h
+++ b/ui/ozone/platform/wayland/test/test_data_offer.h
@@ -15,6 +15,7 @@
 #include "base/memory/weak_ptr.h"
 #include "base/threading/thread.h"
 #include "ui/ozone/platform/wayland/test/server_object.h"
+#include "ui/ozone/public/platform_clipboard.h"
 
 struct wl_resource;
 
@@ -32,10 +33,13 @@
   ~TestDataOffer() override;
 
   void Receive(const std::string& mime_type, base::ScopedFD fd);
-  void OnOffer(const std::string& mime_type);
+  void OnOffer(const std::string& mime_type,
+               const ui::PlatformClipboard::Data& data);
 
  private:
   const scoped_refptr<base::SequencedTaskRunner> task_runner_;
+  ui::PlatformClipboard::DataMap data_to_offer_;
+
   base::WeakPtrFactory<TestDataOffer> write_data_weak_ptr_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(TestDataOffer);
diff --git a/ui/views/controls/scroll_view.cc b/ui/views/controls/scroll_view.cc
index 78806f645..d95cd8a 100644
--- a/ui/views/controls/scroll_view.cc
+++ b/ui/views/controls/scroll_view.cc
@@ -18,7 +18,6 @@
 #include "ui/views/background.h"
 #include "ui/views/border.h"
 #include "ui/views/controls/focus_ring.h"
-#include "ui/views/controls/separator.h"
 #include "ui/views/style/platform_style.h"
 #include "ui/views/widget/widget.h"
 
@@ -160,21 +159,15 @@
 };
 
 ScrollView::ScrollView()
-    : contents_viewport_(new Viewport(this)),
-      header_viewport_(new Viewport(this)),
-      horiz_sb_(PlatformStyle::CreateScrollBar(true).release()),
-      vert_sb_(PlatformStyle::CreateScrollBar(false).release()),
-      corner_view_(new ScrollCornerView()),
-      more_content_left_(std::make_unique<Separator>()),
-      more_content_top_(std::make_unique<Separator>()),
-      more_content_right_(std::make_unique<Separator>()),
-      more_content_bottom_(std::make_unique<Separator>()),
+    : horiz_sb_(PlatformStyle::CreateScrollBar(true)),
+      vert_sb_(PlatformStyle::CreateScrollBar(false)),
+      corner_view_(std::make_unique<ScrollCornerView>()),
       scroll_with_layers_enabled_(base::FeatureList::IsEnabled(
           ::features::kUiCompositorScrollWithLayers)) {
   set_notify_enter_exit_on_child(true);
 
-  AddChildView(contents_viewport_);
-  AddChildView(header_viewport_);
+  contents_viewport_ = AddChildView(std::make_unique<Viewport>(this));
+  header_viewport_ = AddChildView(std::make_unique<Viewport>(this));
 
   // Don't add the scrollbars as children until we discover we need them
   // (ShowOrHideScrollBar).
@@ -211,13 +204,7 @@
   });
 }
 
-ScrollView::~ScrollView() {
-  // The scrollbars may not have been added, delete them to ensure they get
-  // deleted.
-  delete horiz_sb_;
-  delete vert_sb_;
-  delete corner_view_;
-}
+ScrollView::~ScrollView() = default;
 
 // static
 std::unique_ptr<ScrollView> ScrollView::CreateScrollViewWithBorder() {
@@ -326,20 +313,22 @@
                                                     : 0;
 }
 
-void ScrollView::SetHorizontalScrollBar(ScrollBar* horiz_sb) {
+ScrollBar* ScrollView::SetHorizontalScrollBar(
+    std::unique_ptr<ScrollBar> horiz_sb) {
   DCHECK(horiz_sb);
   horiz_sb->SetVisible(horiz_sb_->GetVisible());
-  delete horiz_sb_;
   horiz_sb->set_controller(this);
-  horiz_sb_ = horiz_sb;
+  horiz_sb_ = std::move(horiz_sb);
+  return horiz_sb_.get();
 }
 
-void ScrollView::SetVerticalScrollBar(ScrollBar* vert_sb) {
+ScrollBar* ScrollView::SetVerticalScrollBar(
+    std::unique_ptr<ScrollBar> vert_sb) {
   DCHECK(vert_sb);
   vert_sb->SetVisible(vert_sb_->GetVisible());
-  delete vert_sb_;
   vert_sb->set_controller(this);
-  vert_sb_ = vert_sb;
+  vert_sb_ = std::move(vert_sb);
+  return vert_sb_.get();
 }
 
 void ScrollView::SetHasFocusIndicator(bool has_focus_indicator) {
@@ -466,9 +455,9 @@
   bool corner_view_required =
       horiz_sb_required && vert_sb_required && !vert_sb_->OverlapsContent();
   // Take action.
-  SetControlVisibility(horiz_sb_, horiz_sb_required);
-  SetControlVisibility(vert_sb_, vert_sb_required);
-  SetControlVisibility(corner_view_, corner_view_required);
+  SetControlVisibility(horiz_sb_.get(), horiz_sb_required);
+  SetControlVisibility(vert_sb_.get(), vert_sb_required);
+  SetControlVisibility(corner_view_.get(), corner_view_required);
 
   // Default.
   if (!horiz_sb_required) {
@@ -648,13 +637,13 @@
     return;
 
   gfx::ScrollOffset offset = CurrentOffset();
-  if (source == horiz_sb_ && horiz_sb_->GetVisible()) {
+  if (source == horiz_sb_.get() && horiz_sb_->GetVisible()) {
     position = AdjustPosition(offset.x(), position, contents_->width(),
                               contents_viewport_->width());
     if (offset.x() == position)
       return;
     offset.set_x(position);
-  } else if (source == vert_sb_ && vert_sb_->GetVisible()) {
+  } else if (source == vert_sb_.get() && vert_sb_->GetVisible()) {
     position = AdjustPosition(offset.y(), position, contents_->height(),
                               contents_viewport_->height());
     if (offset.y() == position)
diff --git a/ui/views/controls/scroll_view.h b/ui/views/controls/scroll_view.h
index cebafcd..53a9fe13 100644
--- a/ui/views/controls/scroll_view.h
+++ b/ui/views/controls/scroll_view.h
@@ -15,6 +15,7 @@
 #include "ui/native_theme/native_theme.h"
 #include "ui/views/controls/focus_ring.h"
 #include "ui/views/controls/scrollbar/scroll_bar.h"
+#include "ui/views/controls/separator.h"
 
 namespace gfx {
 class ScrollOffset;
@@ -116,14 +117,15 @@
   int GetScrollBarLayoutWidth() const;
   int GetScrollBarLayoutHeight() const;
 
-  // Returns the horizontal/vertical scrollbar. This may return NULL.
-  const ScrollBar* horizontal_scroll_bar() const { return horiz_sb_; }
-  const ScrollBar* vertical_scroll_bar() const { return vert_sb_; }
+  // Returns the horizontal/vertical scrollbar. This may return null.
+  ScrollBar* horizontal_scroll_bar() { return horiz_sb_.get(); }
+  const ScrollBar* horizontal_scroll_bar() const { return horiz_sb_.get(); }
+  ScrollBar* vertical_scroll_bar() { return vert_sb_.get(); }
+  const ScrollBar* vertical_scroll_bar() const { return vert_sb_.get(); }
 
-  // Customize the scrollbar design. ScrollView takes the ownership of the
-  // specified ScrollBar. |horiz_sb| and |vert_sb| cannot be NULL.
-  void SetHorizontalScrollBar(ScrollBar* horiz_sb);
-  void SetVerticalScrollBar(ScrollBar* vert_sb);
+  // Customize the scrollbar design. |horiz_sb| and |vert_sb| cannot be null.
+  ScrollBar* SetHorizontalScrollBar(std::unique_ptr<ScrollBar> horiz_sb);
+  ScrollBar* SetVerticalScrollBar(std::unique_ptr<ScrollBar> vert_sb);
 
   // Gets/Sets whether this ScrollView has a focus indicator or not.
   bool GetHasFocusIndicator() const { return draw_focus_indicator_; }
@@ -233,19 +235,21 @@
   View* header_viewport_;
 
   // Horizontal scrollbar.
-  ScrollBar* horiz_sb_;
+  std::unique_ptr<ScrollBar> horiz_sb_;
 
   // Vertical scrollbar.
-  ScrollBar* vert_sb_;
+  std::unique_ptr<ScrollBar> vert_sb_;
 
   // Corner view.
-  View* corner_view_;
+  std::unique_ptr<View> corner_view_;
 
   // Hidden content indicators
-  std::unique_ptr<Separator> more_content_left_;
-  std::unique_ptr<Separator> more_content_top_;
-  std::unique_ptr<Separator> more_content_right_;
-  std::unique_ptr<Separator> more_content_bottom_;
+  std::unique_ptr<Separator> more_content_left_ = std::make_unique<Separator>();
+  std::unique_ptr<Separator> more_content_top_ = std::make_unique<Separator>();
+  std::unique_ptr<Separator> more_content_right_ =
+      std::make_unique<Separator>();
+  std::unique_ptr<Separator> more_content_bottom_ =
+      std::make_unique<Separator>();
 
   // The min and max height for the bounded scroll view. These are negative
   // values if the view is not bounded.
diff --git a/ui/views/controls/scroll_view_unittest.cc b/ui/views/controls/scroll_view_unittest.cc
index 685261b..73af6e5 100644
--- a/ui/views/controls/scroll_view_unittest.cc
+++ b/ui/views/controls/scroll_view_unittest.cc
@@ -44,9 +44,10 @@
       : scroll_view_(scroll_view) {}
 
   ScrollBar* GetScrollBar(ScrollBarOrientation orientation) {
-    ScrollBar* scroll_bar = orientation == VERTICAL ? scroll_view_->vert_sb_
-                                                    : scroll_view_->horiz_sb_;
-    return static_cast<ScrollBar*>(scroll_bar);
+    ScrollBar* scroll_bar = orientation == VERTICAL
+                                ? scroll_view_->vertical_scroll_bar()
+                                : scroll_view_->horizontal_scroll_bar();
+    return scroll_bar;
   }
 
   const base::OneShotTimer& GetScrollBarTimer(
@@ -69,7 +70,7 @@
     return ScrollBar::GetHideTimerForTesting(GetScrollBar(orientation));
   }
 
-  View* corner_view() { return scroll_view_->corner_view_; }
+  View* corner_view() { return scroll_view_->corner_view_.get(); }
   View* contents_viewport() { return scroll_view_->contents_viewport_; }
 
   Separator* more_content_left() {
@@ -1507,10 +1508,10 @@
 
   constexpr int kThickness = 1;
   // Assume horizontal scroll bar is the default and is overlapping.
-  scroll_view_->SetHorizontalScrollBar(new TestScrollBar(
+  scroll_view_->SetHorizontalScrollBar(std::make_unique<TestScrollBar>(
       /* horizontal */ true, /* overlaps_content */ true, kThickness));
   // Assume vertical scroll bar is custom and it we want it to not overlap.
-  scroll_view_->SetVerticalScrollBar(new TestScrollBar(
+  scroll_view_->SetVerticalScrollBar(std::make_unique<TestScrollBar>(
       /* horizontal */ false, /* overlaps_content */ false, kThickness));
 
   // Also, let's turn off horizontal scroll bar.
diff --git a/ui/webui/resources/cr_elements/cr_input/cr_input.js b/ui/webui/resources/cr_elements/cr_input/cr_input.js
index 8604791..ffe566a 100644
--- a/ui/webui/resources/cr_elements/cr_input/cr_input.js
+++ b/ui/webui/resources/cr_elements/cr_input/cr_input.js
@@ -225,7 +225,7 @@
 
   /** @private */
   onFocus_: function() {
-    if (!this.focusInput_()) {
+    if (!this.focusInput()) {
       return;
     }
     // Always select the <input> element on focus. TODO(stevenjb/scottchen):
@@ -235,10 +235,12 @@
   },
 
   /**
+   * Focuses the input element.
+   * TODO(crbug.com/882612): Replace this with focus() after resolving the text
+   * selection issue described in onFocus_().
    * @return {boolean} Whether the <input> element was focused.
-   * @private
    */
-  focusInput_: function() {
+  focusInput: function() {
     if (this.shadowRoot.activeElement == this.inputElement) {
       return false;
     }
@@ -346,7 +348,7 @@
    * @param {number=} end
    */
   select: function(start, end) {
-    this.focusInput_();
+    this.focusInput();
     if (start !== undefined && end !== undefined) {
       this.inputElement.setSelectionRange(start, end);
     } else {